/jEdit/tags/jedit-4-3-pre15/org/gjt/sp/jedit/JEditActionSet.java

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