001: /*
002: * ActionSet.java - A set of actions
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2001, 2003 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit;
024:
025: import java.io.*;
026: import java.net.URL;
027: import java.util.*;
028:
029: import org.gjt.sp.jedit.gui.InputHandler;
030: import org.gjt.sp.util.Log;
031: import org.gjt.sp.util.XMLUtilities;
032:
033: /**
034: * A set of actions, either loaded from an XML file, or constructed at runtime
035: * by a plugin. <p>
036: *
037: * <h3>Action sets loaded from XML files</h3>
038: *
039: * Action sets are read from these files inside the plugin JAR:
040: * <ul>
041: * <li><code>actions.xml</code> - actions made available for use in jEdit views,
042: * including the view's <b>Plugins</b> menu, the tool bar, etc.</li>
043: * <li><code>browser.actions.xml</code> - actions for the file system browser's
044: * <b>Plugins</b> menu.</li>
045: * </ul>
046: *
047: * An action definition file has the following form:
048: *
049: * <pre><?xml version="1.0"?>
050: *<!DOCTYPE ACTIONS SYSTEM "actions.dtd">
051: *<ACTIONS>
052: * <ACTION NAME="some-action">
053: * <CODE>
054: * // BeanShell code evaluated when the action is invoked
055: * </CODE>
056: * </ACTION>
057: * <ACTION NAME="some-toggle-action">
058: * <CODE>
059: * // BeanShell code evaluated when the action is invoked
060: * </CODE>
061: * <IS_SELECTED>
062: * // BeanShell code that should evaluate to true or false
063: * </IS_SELECTED>
064: * </ACTION>
065: *</ACTIONS></pre>
066: *
067: * The following elements are valid:
068: *
069: * <ul>
070: * <li>
071: * <code>ACTIONS</code> is the top-level element and refers
072: * to the set of actions used by the plugin.
073: * </li>
074: * <li>
075: * An <code>ACTION</code> contains the data for a particular action.
076: * It has three attributes: a required <code>NAME</code>;
077: * an optional <code>NO_REPEAT</code>, which is a flag
078: * indicating whether the action should not be repeated with the
079: * <b>C+ENTER</b> command; and an optional
080: * <code>NO_RECORD</code> which is a a flag indicating whether the
081: * action should be recorded if it is invoked while the user is recording a
082: * macro. The two flag attributes
083: * can have two possible values, "TRUE" or
084: * "FALSE". In both cases, "FALSE" is the
085: * default if the attribute is not specified.
086: * </li>
087: * <li>
088: * An <code>ACTION</code> can have two child elements
089: * within it: a required <code>CODE</code> element which
090: * specifies the
091: * BeanShell code that will be executed when the action is invoked,
092: * and an optional <code>IS_SELECTED</code> element, used for
093: * checkbox
094: * menu items. The <code>IS_SELECTED</code> element contains
095: * BeanShell code that returns a boolean flag that will
096: * determine the state of the checkbox.
097: * </li>
098: * </ul>
099: *
100: * Each action must have a property <code><i>name</i>.label</code> containing
101: * the action's menu item label.
102: *
103: * <h3>View actions</h3>
104: *
105: * Actions defined in <code>actions.xml</code> can be added to the view's
106: * <b>Plugins</b> menu; see {@link EditPlugin}.
107: * The action code may use any standard predefined
108: * BeanShell variable; see {@link BeanShell}.
109: *
110: * <h3>File system browser actions</h3>
111: *
112: * Actions defined in <code>actions.xml</code> can be added to the file
113: * system browser's <b>Plugins</b> menu; see {@link EditPlugin}.
114: * The action code may use any standard predefined
115: * BeanShell variable, in addition to a variable <code>browser</code> which
116: * contains a reference to the current
117: * {@link org.gjt.sp.jedit.browser.VFSBrowser} instance.<p>
118: *
119: * File system browser actions should not define
120: * <code><IS_SELECTED></code> blocks.
121: *
122: * <h3>Custom action sets</h3>
123: *
124: * Call {@link jEdit#addActionSet(ActionSet)} to add a custom action set to
125: * jEdit's action context. You must also call {@link #initKeyBindings()} for new
126: * action sets. Don't forget to call {@link jEdit#removeActionSet(ActionSet)}
127: * before your plugin is unloaded, too.
128: *
129: * @see jEdit#getActionContext()
130: * @see org.gjt.sp.jedit.browser.VFSBrowser#getActionContext()
131: * @see ActionContext#getActionNames()
132: * @see ActionContext#getAction(String)
133: * @see jEdit#addActionSet(ActionSet)
134: * @see jEdit#removeActionSet(ActionSet)
135: * @see PluginJAR#getActionSet()
136: * @see BeanShell
137: * @see View
138: *
139: * @author Slava Pestov
140: * @author John Gellene (API documentation)
141: * @version $Id: ActionSet.java 9529 2007-05-12 15:06:52Z ezust $
142: * @since jEdit 4.0pre1
143: */
144: public class ActionSet {
145: //{{{ ActionSet constructor
146: /**
147: * Creates a new action set.
148: * @since jEdit 4.0pre1
149: */
150: public ActionSet() {
151: actions = new Hashtable();
152: loaded = true;
153: label = "<no label set; plugin bug>";
154: } //}}}
155:
156: //{{{ ActionSet constructor
157: /**
158: * Creates a new action set.
159: * @param plugin The plugin
160: * @param cachedActionNames The list of cached action names
161: * @param cachedActionToggleFlags The list of cached action toggle flags
162: * @param uri The actions.xml URI
163: * @since jEdit 4.2pre2
164: */
165: public ActionSet(PluginJAR plugin, String[] cachedActionNames,
166: boolean[] cachedActionToggleFlags, URL uri) {
167: this ();
168: this .plugin = plugin;
169: this .uri = uri;
170: if (cachedActionNames != null) {
171: for (int i = 0; i < cachedActionNames.length; i++) {
172: actions.put(cachedActionNames[i], placeholder);
173: jEdit.setTemporaryProperty(cachedActionNames[i]
174: + ".toggle",
175: cachedActionToggleFlags[i] ? "true" : "false");
176: }
177: }
178: loaded = false;
179: } //}}}
180:
181: //{{{ ActionSet constructor
182: /**
183: * Creates a new action set.
184: * @param label The label, shown in the shortcuts option pane
185: * @since jEdit 4.0pre1
186: */
187: public ActionSet(String label) {
188: this ();
189: setLabel(label);
190: } //}}}
191:
192: //{{{ getLabel() method
193: /**
194: * Return the action source label.
195: * @since jEdit 4.0pre1
196: */
197: public String getLabel() {
198: return label;
199: } //}}}
200:
201: //{{{ setLabel() method
202: /**
203: * Sets the action source label.
204: * @param label The label
205: * @since jEdit 4.0pre1
206: */
207: public void setLabel(String label) {
208: if (label == null)
209: throw new NullPointerException();
210: this .label = label;
211: } //}}}
212:
213: //{{{ getPluginJAR() method
214: /**
215: * Return the plugin this action set was loaded from, or null.
216: * @since jEdit 4.2pre13
217: */
218: public PluginJAR getPluginJAR() {
219: return plugin;
220: } //}}}
221:
222: //{{{ addAction() method
223: /**
224: * Adds an action to the action set.
225: * @param action The action
226: * @since jEdit 4.0pre1
227: */
228: public void addAction(EditAction action) {
229: actions.put(action.getName(), action);
230: if (context != null) {
231: context.actionNames = null;
232: context.actionHash.put(action.getName(), this );
233: }
234: } //}}}
235:
236: //{{{ removeAction() method
237: /**
238: * Removes an action from the action set.
239: * @param name The action name
240: * @since jEdit 4.0pre1
241: */
242: public void removeAction(String name) {
243: actions.remove(name);
244: if (context != null) {
245: context.actionNames = null;
246: context.actionHash.remove(name);
247: }
248: } //}}}
249:
250: //{{{ removeAllActions() method
251: /**
252: * Removes all actions from the action set.
253: * @since jEdit 4.0pre1
254: */
255: public void removeAllActions() {
256: if (context != null) {
257: context.actionNames = null;
258: String[] actions = getActionNames();
259: for (int i = 0; i < actions.length; i++) {
260: context.actionHash.remove(actions[i]);
261: }
262: }
263: this .actions.clear();
264: } //}}}
265:
266: //{{{ getAction() method
267: /**
268: * Returns an action with the specified name.<p>
269: *
270: * <b>Deferred loading:</b> this will load the action set if necessary.
271: *
272: * @param name The action name
273: * @since jEdit 4.0pre1
274: */
275: public EditAction getAction(String name) {
276: Object obj = actions.get(name);
277: if (obj == placeholder) {
278: load();
279: obj = actions.get(name);
280: if (obj == placeholder) {
281: Log.log(Log.WARNING, this , "Outdated cache");
282: obj = null;
283: }
284: }
285:
286: return (EditAction) obj;
287: } //}}}
288:
289: //{{{ getActionCount() method
290: /**
291: * Returns the number of actions in the set.
292: * @since jEdit 4.0pre1
293: */
294: public int getActionCount() {
295: return actions.size();
296: } //}}}
297:
298: //{{{ getActionNames() method
299: /**
300: * Returns an array of all action names in this action set.
301: * @since jEdit 4.2pre1
302: */
303: public String[] getActionNames() {
304: String[] retVal = new String[actions.size()];
305: Enumeration e = actions.keys();
306: int i = 0;
307: while (e.hasMoreElements()) {
308: retVal[i++] = (String) e.nextElement();
309: }
310: return retVal;
311: } //}}}
312:
313: //{{{ getCacheableActionNames() method
314: /**
315: * Returns an array of all action names in this action set that should
316: * be cached; namely, <code>BeanShellAction</code>s.
317: * @since jEdit 4.2pre1
318: */
319: public String[] getCacheableActionNames() {
320: LinkedList retVal = new LinkedList();
321: Enumeration e = actions.elements();
322: while (e.hasMoreElements()) {
323: Object obj = e.nextElement();
324: if (obj == placeholder) {
325: // ??? this should only be called with
326: // fully loaded action set
327: Log.log(Log.WARNING, this , "Action set not up "
328: + "to date");
329: } else if (obj instanceof BeanShellAction)
330: retVal.add(((BeanShellAction) obj).getName());
331: }
332: return (String[]) retVal.toArray(new String[retVal.size()]);
333: } //}}}
334:
335: //{{{ getActions() method
336: /**
337: * Returns an array of all actions in this action set.<p>
338: *
339: * <b>Deferred loading:</b> this will load the action set if necessary.
340: *
341: * @since jEdit 4.0pre1
342: */
343: public EditAction[] getActions() {
344: load();
345:
346: EditAction[] retVal = new EditAction[actions.size()];
347: Enumeration e = actions.elements();
348: int i = 0;
349: while (e.hasMoreElements()) {
350: retVal[i++] = (EditAction) e.nextElement();
351: }
352: return retVal;
353: } //}}}
354:
355: //{{{ contains() method
356: /**
357: * Returns if this action set contains the specified action.
358: * @param action The action
359: * @since jEdit 4.2pre1
360: */
361: public boolean contains(String action) {
362: boolean retval = actions.containsKey(action);
363: return retval;
364: // return actions.containsKey(action);
365: } //}}}
366:
367: //{{{ size() method
368: /**
369: * Returns the number of actions in this action set.
370: * @since jEdit 4.2pre2
371: */
372: public int size() {
373: return actions.size();
374: } //}}}
375:
376: //{{{ toString() method
377: public String toString() {
378: return label;
379: } //}}}
380:
381: //{{{ initKeyBindings() method
382: /**
383: * Initializes the action set's key bindings.
384: * jEdit calls this method for all registered action sets when the
385: * user changes key bindings in the <b>Global Options</b> dialog box.<p>
386: *
387: * Note if your plugin adds a custom action set to jEdit's collection,
388: * it must also call this method on the action set after adding it.
389: *
390: * @since jEdit 4.2pre1
391: */
392: public void initKeyBindings() {
393: InputHandler inputHandler = jEdit.getInputHandler();
394:
395: Iterator iter = actions.entrySet().iterator();
396: while (iter.hasNext()) {
397: Map.Entry entry = (Map.Entry) iter.next();
398: String name = (String) entry.getKey();
399:
400: String shortcut1 = jEdit.getProperty(name + ".shortcut");
401: if (shortcut1 != null)
402: inputHandler.addKeyBinding(shortcut1, name);
403:
404: String shortcut2 = jEdit.getProperty(name + ".shortcut2");
405: if (shortcut2 != null)
406: inputHandler.addKeyBinding(shortcut2, name);
407: }
408: } //}}}
409:
410: //{{{ load() method
411: /**
412: * Forces the action set to be loaded. Plugins and macros should not
413: * call this method.
414: * @since jEdit 4.2pre1
415: */
416: public void load() {
417: if (loaded)
418: return;
419:
420: loaded = true;
421: //actions.clear();
422:
423: Reader stream = null;
424:
425: try {
426: Log.log(Log.DEBUG, this , "Loading actions from " + uri);
427: ActionListHandler ah = new ActionListHandler(
428: uri.toString(), this );
429: if (XMLUtilities.parseXML(uri.openStream(), ah)) {
430: Log.log(Log.ERROR, this , "Unable to parse: " + uri);
431: }
432: } catch (IOException e) {
433: Log.log(Log.ERROR, this , uri, e);
434: }
435: } //}}}
436:
437: //{{{ Package-private members
438: ActionContext context;
439:
440: //{{{ getActionNames() method
441: void getActionNames(List vec) {
442: Enumeration e = actions.keys();
443: while (e.hasMoreElements())
444: vec.add(e.nextElement());
445: } //}}}
446:
447: //}}}
448:
449: //{{{ Private members
450: private String label;
451: private Hashtable actions;
452: private PluginJAR plugin;
453: private URL uri;
454: private boolean loaded;
455:
456: private static final Object placeholder = new Object();
457:
458: //}}}
459: }
|