PageRenderTime 34ms CodeModel.GetById 13ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/JEditActionSet.java

#
Java | 469 lines | 179 code | 32 blank | 258 comment | 30 complexity | b1cea2856ce087973f36cf92b69206b0 MD5 | raw file
  1/*
  2 * JEditActionSet.java - A set of actions
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2001, 2003 Slava Pestov
  7 * Portions copyright (C) 2007 Matthieu Casanova
  8 *
  9 * This program is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU General Public License
 11 * as published by the Free Software Foundation; either version 2
 12 * of the License, or any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 *
 19 * You should have received a copy of the GNU General Public License
 20 * along with this program; if not, write to the Free Software
 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 22 */
 23
 24package org.gjt.sp.jedit;
 25
 26import java.io.*;
 27import java.net.URL;
 28import java.util.*;
 29
 30import org.gjt.sp.jedit.input.AbstractInputHandler;
 31import org.gjt.sp.jedit.input.InputHandlerProvider;
 32import org.gjt.sp.util.Log;
 33import org.gjt.sp.util.XMLUtilities;
 34
 35/**
 36 * A set of actions, either loaded from an XML file, or constructed at runtime
 37 * by a plugin. <p>
 38 *
 39 * <h3>Action sets loaded from XML files</h3>
 40 *
 41 * Action sets are read from these files inside the plugin JAR:
 42 * <ul>
 43 * <li><code>actions.xml</code> - actions made available for use in jEdit views,
 44 * including the view's <b>Plugins</b> menu, the tool bar, etc.</li>
 45 * <li><code>browser.actions.xml</code> - actions for the file system browser's
 46 * <b>Plugins</b> menu.</li>
 47 * </ul>
 48 *
 49 * An action definition file has the following form:
 50 *
 51 * <pre>&lt;?xml version="1.0"?&gt;
 52 *&lt;!DOCTYPE ACTIONS SYSTEM "actions.dtd"&gt;
 53 *&lt;ACTIONS&gt;
 54 *    &lt;ACTION NAME="some-action"&gt;
 55 *        &lt;CODE&gt;
 56 *            // BeanShell code evaluated when the action is invoked
 57 *        &lt;/CODE&gt;
 58 *    &lt;/ACTION&gt;
 59 *    &lt;ACTION NAME="some-toggle-action"&gt;
 60 *        &lt;CODE&gt;
 61 *            // BeanShell code evaluated when the action is invoked
 62 *        &lt;/CODE&gt;
 63 *        &lt;IS_SELECTED&gt;
 64 *            // BeanShell code that should evaluate to true or false
 65 *        &lt;/IS_SELECTED&gt;
 66 *    &lt;/ACTION&gt;
 67 *&lt;/ACTIONS&gt;</pre>
 68 *
 69 * The following elements are valid:
 70 *
 71 * <ul>
 72 * <li>
 73 * <code>ACTIONS</code> is the top-level element and refers
 74 * to the set of actions used by the plugin.
 75 * </li>
 76 * <li>
 77 * An <code>ACTION</code> contains the data for a particular action.
 78 * It has three attributes: a required <code>NAME</code>;
 79 * an optional <code>NO_REPEAT</code>, which is a flag
 80 * indicating whether the action should not be repeated with the
 81 * <b>C+ENTER</b> command; and an optional
 82 * <code>NO_RECORD</code> which is a a flag indicating whether the
 83 * action should be recorded if it is invoked while the user is recording a
 84 * macro. The two flag attributes
 85 * can have two possible values, "TRUE" or
 86 * "FALSE". In both cases, "FALSE" is the
 87 * default if the attribute is not specified.
 88 * </li>
 89 * <li>
 90 * An <code>ACTION</code> can have two child elements
 91 * within it: a required <code>CODE</code> element which
 92 * specifies the
 93 * BeanShell code that will be executed when the action is invoked,
 94 * and an optional <code>IS_SELECTED</code> element, used for
 95 * checkbox
 96 * menu items.  The <code>IS_SELECTED</code> element contains
 97 * BeanShell code that returns a boolean flag that will
 98 * determine the state of the checkbox.
 99 * </li>
100 * </ul>
101 *
102 * Each action must have a property <code><i>name</i>.label</code> containing
103 * the action's menu item label.
104 *
105 * <h3>View actions</h3>
106 *
107 * Actions defined in <code>actions.xml</code> can be added to the view's
108 * <b>Plugins</b> menu; see {@link EditPlugin}.
109 * The action code may use any standard predefined
110 * BeanShell variable; see {@link BeanShell}.
111 *
112 * <h3>File system browser actions</h3>
113 *
114 * Actions defined in <code>actions.xml</code> can be added to the file
115 * system browser's <b>Plugins</b> menu; see {@link EditPlugin}.
116 * The action code may use any standard predefined
117 * BeanShell variable, in addition to a variable <code>browser</code> which
118 * contains a reference to the current
119 * {@link org.gjt.sp.jedit.browser.VFSBrowser} instance.<p>
120 *
121 * File system browser actions should not define
122 * <code>&lt;IS_SELECTED&gt;</code> blocks.
123 *
124 * <h3>Custom action sets</h3>
125 *
126 * Call {@link jEdit#addActionSet(ActionSet)} to add a custom action set to
127 * jEdit's action context. You must also call {@link #initKeyBindings()} for new
128 * action sets. Don't forget to call {@link jEdit#removeActionSet(ActionSet)}
129 * before your plugin is unloaded, too.
130 *
131 * @see jEdit#getActionContext()
132 * @see org.gjt.sp.jedit.browser.VFSBrowser#getActionContext()
133 * @see ActionContext#getActionNames()
134 * @see ActionContext#getAction(String)
135 * @see jEdit#addActionSet(ActionSet)
136 * @see jEdit#removeActionSet(ActionSet)
137 * @see PluginJAR#getActionSet()
138 * @see BeanShell
139 * @see View
140 *
141 * @author Slava Pestov
142 * @author John Gellene (API documentation)
143 * @version $Id: ActionSet.java 9529 2007-05-12 15:06:52Z ezust $
144 * @since jEdit 4.3pre13
145 */
146public abstract class JEditActionSet<E extends JEditAbstractEditAction> implements InputHandlerProvider
147{
148	//{{{ JEditActionSet constructor
149	/**
150	 * Creates a new action set.
151	 * @since jEdit 4.3pre13
152	 */
153	protected JEditActionSet()
154	{
155		actions = new Hashtable<String, Object>();
156		loaded = true;
157	} //}}}
158	
159	//{{{ JEditActionSet constructor
160	/**
161	 * Creates a new action set.
162	 * @param cachedActionNames The list of cached action names
163	 * @param uri The actions.xml URI
164	 * @since jEdit 4.3pre13
165	 */
166	protected JEditActionSet(String[] cachedActionNames, URL uri)
167	{
168		this();
169		this.uri = uri;
170		if(cachedActionNames != null)
171		{
172			for(int i = 0; i < cachedActionNames.length; i++)
173			{
174				actions.put(cachedActionNames[i],placeholder);
175			}
176		}
177		loaded = false;
178	} //}}}
179
180	//{{{ addAction() method
181	/**
182	 * Adds an action to the action set.
183	 * @param action The action
184	 * @since jEdit 4.0pre1
185	 */
186	public void addAction(E action)
187	{
188		actions.put(action.getName(),action);
189		if(context != null)
190		{
191			context.actionNames = null;
192			context.actionHash.put(action.getName(),this);
193		}
194	} //}}}
195
196	//{{{ removeAction() method
197	/**
198	 * Removes an action from the action set.
199	 * @param name The action name
200	 * @since jEdit 4.0pre1
201	 */
202	public void removeAction(String name)
203	{
204		actions.remove(name);
205		if(context != null)
206		{
207			context.actionNames = null;
208			context.actionHash.remove(name);
209		}
210	} //}}}
211
212	//{{{ removeAllActions() method
213	/**
214	 * Removes all actions from the action set.
215	 * @since jEdit 4.0pre1
216	 */
217	public void removeAllActions()
218	{
219		if(context != null)
220		{
221			context.actionNames = null;
222			String[] actions = getActionNames();
223			for(int i = 0; i < actions.length; i++)
224			{
225				context.actionHash.remove(actions[i]);
226			}
227		}
228		actions.clear();
229	} //}}}
230
231	//{{{ getAction() method
232	/**
233	 * Returns an action with the specified name.<p>
234	 *
235	 * <b>Deferred loading:</b> this will load the action set if necessary.
236	 *
237	 * @param name The action name
238	 * @since jEdit 4.0pre1
239	 */
240	public E getAction(String name)
241	{
242		Object obj = actions.get(name);
243		if(obj == placeholder)
244		{
245			load();
246			obj = actions.get(name);
247			if(obj == placeholder)
248			{
249				Log.log(Log.WARNING,this,"Outdated cache");
250				obj = null;
251			}
252		}
253
254		return (E) obj;
255	} //}}}
256
257	//{{{ getActionCount() method
258	/**
259	 * Returns the number of actions in the set.
260	 * @since jEdit 4.0pre1
261	 */
262	public int getActionCount()
263	{
264		return actions.size();
265	} //}}}
266
267	//{{{ getActionNames() method
268	/**
269	 * Returns an array of all action names in this action set.
270	 * @since jEdit 4.2pre1
271	 */
272	public String[] getActionNames()
273	{
274		String[] retVal = new String[actions.size()];
275		Set<String> keys = actions.keySet();
276		int i = 0;
277		for (String key : keys)
278		{
279			retVal[i++] = key;
280		}
281		return retVal;
282	} //}}}
283
284	//{{{ getCacheableActionNames() method
285	/**
286	 * Returns an array of all action names in this action set that should
287	 * be cached; namely, <code>BeanShellAction</code>s.
288	 * @since jEdit 4.2pre1
289	 */
290	public String[] getCacheableActionNames()
291	{
292		LinkedList<String> retVal = new LinkedList<String>();
293		for (Object obj : actions.values())
294		{
295			if (obj == placeholder)
296			{
297				// ??? this should only be called with
298				// fully loaded action set
299				Log.log(Log.WARNING, this, "Action set not up "
300					+ "to date");
301			}
302			else if (obj instanceof JEditBeanShellAction)
303				retVal.add(((JEditBeanShellAction) obj).getName());
304		}
305		return retVal.toArray(new String[retVal.size()]);
306	} //}}}
307	
308	//{{{ getArray() method
309	/**
310	 * Returns an empty array E[].
311	 * I know it is bad, if you find a method to instantiate a generic Array,
312	 * tell me
313	 * @param size the size of the array
314	 * @return the empty array
315	 */
316	protected abstract E[] getArray(int size);		
317	//}}}
318
319	//{{{ getActions() method
320	/**
321	 * Returns an array of all actions in this action set.<p>
322	 *
323	 * <b>Deferred loading:</b> this will load the action set if necessary.
324	 *
325	 * @since jEdit 4.0pre1
326	 */
327	public E[] getActions()
328	{
329		load();
330		E[] retVal = getArray(actions.size());
331		Collection<Object> values = actions.values();
332		int i = 0;
333		for (Object value : values)
334		{
335			retVal[i++] = (E) value;
336		}
337		return retVal;
338	} //}}}
339
340	//{{{ contains() method
341	/**
342	 * Returns if this action set contains the specified action.
343	 * @param action The action
344	 * @since jEdit 4.2pre1
345	 */
346	public boolean contains(String action)
347	{
348		boolean retval = actions.containsKey(action);
349		return retval;
350//		return actions.containsKey(action);
351	} //}}}
352
353	//{{{ size() method
354	/**
355	 * Returns the number of actions in this action set.
356	 * @since jEdit 4.2pre2
357	 */
358	public int size()
359	{
360		return actions.size();
361	} //}}}
362
363	//{{{ load() method
364	/**
365	 * Forces the action set to be loaded. Plugins and macros should not
366	 * call this method.
367	 * @since jEdit 4.2pre1
368	 */
369	public void load()
370	{
371		if(loaded)
372			return;
373
374		loaded = true;
375		//actions.clear();
376
377		if (uri == null)
378			return;
379		try
380		{
381			Log.log(Log.DEBUG,this,"Loading actions from " + uri);
382			ActionListHandler ah = new ActionListHandler(uri.toString(),this);
383			if ( XMLUtilities.parseXML(uri.openStream(), ah))
384			{
385				Log.log(Log.ERROR, this, "Unable to parse: " + uri);
386			}
387		}
388		catch(IOException e)
389		{
390			Log.log(Log.ERROR,this,uri,e);
391		}
392	} //}}}
393	
394	//{{{ createBeanShellAction() method
395	/**
396	 * This method should be implemented to return an action that will execute
397	 * the given code
398	 * @since 4.3pre13
399	 */
400	protected abstract JEditAbstractEditAction createBeanShellAction(String actionName,
401									   String code,
402									   String selected,
403									   boolean noRepeat,
404									   boolean noRecord,
405									   boolean noRememberLast);
406	//}}}
407	
408	//{{{ initKeyBindings() method
409	/**
410	 * Initializes the action set's key bindings.
411	 * jEdit calls this method for all registered action sets when the
412	 * user changes key bindings in the <b>Global Options</b> dialog box.<p>
413	 *
414	 * Note if your plugin adds a custom action set to jEdit's collection,
415	 * it must also call this method on the action set after adding it.
416	 *
417	 * @since jEdit 4.2pre1
418	 */
419	public void initKeyBindings()
420	{
421		AbstractInputHandler inputHandler = getInputHandler();
422
423		Set<Map.Entry<String, Object>> entries = actions.entrySet();
424		for (Map.Entry<String, Object> entry : entries)
425		{
426			String name = entry.getKey();
427
428			String shortcut1 = getProperty(name + ".shortcut");
429			if(shortcut1 != null)
430				inputHandler.addKeyBinding(shortcut1,name);
431
432			String shortcut2 = getProperty(name + ".shortcut2");
433			if(shortcut2 != null)
434				inputHandler.addKeyBinding(shortcut2,name);
435		}
436	} //}}}
437	
438	//{{{ getProperty() method
439	/**
440	 * Returns a property for the given name.
441	 * In jEdit it will returns a jEdit.getProperty(name), but it can
442	 * return something else for a standalone textarea.
443	 * @param name the property name
444	 * @return the property value
445	 * @since 4.3pre13
446	 */
447	protected abstract String getProperty(String name);
448	//}}}
449
450	//{{{ Package-private members
451	JEditActionContext context;
452
453	//{{{ getActionNames() method
454	void getActionNames(List<String> list)
455	{
456		list.addAll(actions.keySet());
457	} //}}}
458
459	//}}}
460
461	//{{{ Private members
462	protected Hashtable<String,Object> actions;
463	protected URL uri;
464	protected boolean loaded;
465
466	protected static final Object placeholder = new Object();
467
468	//}}}
469}