/* * DockableWindowFactory.java - loads dockables.xml, etc * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2005 Slava Pestov * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.gui; //{{{ Imports import java.awt.Color; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Stack; import javax.swing.JComponent; import javax.swing.LookAndFeel; import javax.swing.UIManager; import org.gjt.sp.jedit.ActionSet; import org.gjt.sp.jedit.BeanShell; import org.gjt.sp.jedit.EditAction; import org.gjt.sp.jedit.GUIUtilities; import org.gjt.sp.jedit.MiscUtilities; import org.gjt.sp.jedit.PluginJAR; import org.gjt.sp.jedit.View; import org.gjt.sp.jedit.jEdit; import org.gjt.sp.util.Log; import org.gjt.sp.util.XMLUtilities; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.helpers.DefaultHandler; import org.gjt.sp.jedit.bsh.NameSpace; import org.gjt.sp.jedit.bsh.UtilEvalError; //}}} /** Loads <code>dockable.xml</code> files and manages creation * of new dockable windows. * * @see DockableWindowManager * * @since jEdit 4.3pre2 */ public class DockableWindowFactory { //{{{ getInstance() method public static synchronized DockableWindowFactory getInstance() { if(instance == null) instance = new DockableWindowFactory(); return instance; } //}}} //{{{ DockableWindowFactory constructor public DockableWindowFactory() { dockableWindowFactories = new HashMap<String, Window>(); } //}}} //{{{ loadDockableWindows() method /** * Plugins shouldn't need to call this method. * @since jEdit 4.2pre1 */ public void loadDockableWindows(PluginJAR plugin, URL uri, PluginJAR.PluginCacheEntry cache) { try { Log.log(Log.DEBUG,DockableWindowManager.class, "Loading dockables from " + uri); DockableListHandler dh = new DockableListHandler(plugin,uri); InputStream in = uri.openStream(); if(in == null) { // this happened when calling generateCache() in the context of 'find orphan jars' // in org.gjt.sp.jedit.pluginmgr.ManagePanel.FindOrphan.actionPerformed(ActionEvent) // because for not loaded plugins, the plugin will not be added to the list of pluginJars // so the org.gjt.sp.jedit.proto.jeditresource.PluginResURLConnection will not find the plugin // to read the resource from. // Better log a small error message than a big stack trace Log.log(Log.WARNING, this, "Unable to open: " + uri); } else { boolean failure = XMLUtilities.parseXML(in, dh); if (!failure && cache != null) { cache.cachedDockableNames = dh.getCachedDockableNames(); cache.cachedDockableActionFlags = dh.getCachedDockableActionFlags(); cache.cachedDockableMovableFlags = dh.getCachedDockableMovableFlags(); } } } catch(IOException e) { Log.log(Log.ERROR,DockableWindowManager.class,e); } } //}}} //{{{ unloadDockableWindows() method /** * Plugins shouldn't need to call this method. * @since jEdit 4.2pre1 */ public void unloadDockableWindows(PluginJAR plugin) { Iterator entries = dockableWindowFactories.entrySet().iterator(); while(entries.hasNext()) { Map.Entry entry = (Map.Entry)entries.next(); Window factory = (Window)entry.getValue(); if(factory.plugin == plugin) entries.remove(); } } //}}} //{{{ cacheDockableWindows() method /** * @since jEdit 4.2pre1 */ public void cacheDockableWindows(PluginJAR plugin, String[] name, boolean[] actions, boolean[] movable) { for(int i = 0; i < name.length; i++) { Window factory = new Window(plugin, name[i],null,actions[i],movable[i]); dockableWindowFactories.put(name[i],factory); } } //}}} //{{{ registerDockableWindow() method public void registerDockableWindow(PluginJAR plugin, String name, String code, boolean actions, boolean movable) { Window factory = dockableWindowFactories.get(name); if(factory != null) { factory.code = code; factory.loaded = true; } else { factory = new Window(plugin,name,code,actions, movable); dockableWindowFactories.put(name,factory); } } //}}} //{{{ getRegisteredDockableWindows() method public String[] getRegisteredDockableWindows() { String[] retVal = new String[dockableWindowFactories.size()]; Iterator<Window> entries = dockableWindowFactories.values().iterator(); int i = 0; while(entries.hasNext()) { Window factory = entries.next(); retVal[i++] = factory.name; } return retVal; } //}}} public Window getDockableWindowFactory(String name) { return dockableWindowFactories.get(name); } public String getDockableWindowPluginClass(String name) { Window w = getDockableWindowFactory(name); if (w == null || w.plugin == null || w.plugin.getPlugin() == null) return null; return w.plugin.getPlugin().getClassName(); } //{{{ getDockableWindowIterator() method Iterator<Window> getDockableWindowIterator() { return dockableWindowFactories.values().iterator(); } //}}} //{{{ DockableListHandler class class DockableListHandler extends DefaultHandler { //{{{ DockableListHandler constructor /** * @param plugin - the pluginJAR for which we are loading the dockables.xml * @param uri - the uri of the dockables.xml file? */ DockableListHandler(PluginJAR plugin, URL uri) { this.plugin = plugin; this.uri = uri; stateStack = new Stack<String>(); actions = true; movable = MOVABLE_DEFAULT; code = new StringBuilder(); cachedDockableNames = new LinkedList<String>(); cachedDockableActionFlags = new LinkedList<Boolean>(); cachedDockableMovableFlags = new LinkedList<Boolean>(); } //}}} //{{{ resolveEntity() method @Override public InputSource resolveEntity(String publicId, String systemId) { return XMLUtilities.findEntity(systemId, "dockables.dtd", MiscUtilities.class); } //}}} //{{{ characters() method @Override public void characters(char[] c, int off, int len) { String tag = peekElement(); if (tag.equals("DOCKABLE")) code.append(c, off, len); } //}}} //{{{ startElement() method @Override public void startElement(String uri, String localName, String qName, Attributes attrs) { String tag = pushElement(qName); if (tag.equals("DOCKABLE")) { dockableName = attrs.getValue("NAME"); actions = "FALSE".equals(attrs.getValue("NO_ACTIONS")); String movableAttr = attrs.getValue("MOVABLE"); if (movableAttr != null) movable = movableAttr.equalsIgnoreCase("TRUE"); } } //}}} //{{{ endElement() method @Override public void endElement(String uri, String localName, String name) { if(name == null) return; String tag = peekElement(); if(name.equals(tag)) { if(tag.equals("DOCKABLE")) { registerDockableWindow(plugin, dockableName,code.toString(),actions, movable); cachedDockableNames.add(dockableName); cachedDockableActionFlags.add( Boolean.valueOf(actions)); cachedDockableMovableFlags.add( Boolean.valueOf(movable)); // make default be true for the next // action actions = true; movable = MOVABLE_DEFAULT; code.setLength(0); } popElement(); } else { // can't happen throw new InternalError(); } } //}}} //{{{ startDocument() method @Override public void startDocument() { try { pushElement(null); } catch (Exception e) { Log.log(Log.ERROR, this, e); } } //}}} //{{{ getCachedDockableNames() method public String[] getCachedDockableNames() { return cachedDockableNames.toArray(new String[cachedDockableNames.size()]); } //}}} //{{{ getCachedDockableActionFlags() method public boolean[] getCachedDockableActionFlags() { return booleanListToArray(cachedDockableActionFlags); } //}}} //{{{ getCachedDockableMovableFlags() method public boolean[] getCachedDockableMovableFlags() { return booleanListToArray(cachedDockableMovableFlags); } //}}} //{{{ booleanListToArray() method /** * This method transforms a List<Boolean> into the corresponding * boolean[] array * @param list the List<Boolean> you want to convert * @return a boolean[] array */ private boolean[] booleanListToArray(java.util.List<Boolean> list) { boolean[] returnValue = new boolean[list.size()]; int i = 0; for (Boolean value : list) { returnValue[i++] = value.booleanValue(); } return returnValue; } //}}} //{{{ Private members //{{{ Instance variables private PluginJAR plugin; // What is the purpose of this? private URL uri; private java.util.List<String> cachedDockableNames; private java.util.List<Boolean> cachedDockableActionFlags; private java.util.List<Boolean> cachedDockableMovableFlags; private String dockableName; private StringBuilder code; private boolean actions; private boolean movable; static final boolean MOVABLE_DEFAULT = false; private Stack<String> stateStack; //}}} //{{{ pushElement() method private String pushElement(String name) { name = (name == null) ? null : name.intern(); stateStack.push(name); return name; } //}}} //{{{ peekElement() method private String peekElement() { return stateStack.peek(); } //}}} //{{{ popElement() method private String popElement() { return stateStack.pop(); } //}}} //}}} } //}}} //{{{ Window class class Window { PluginJAR plugin; String name; String code; boolean loaded; boolean movable; boolean isBeingCreated = false; //{{{ Window constructor Window(PluginJAR plugin, String name, String code, boolean actions, boolean movable) { this.plugin = plugin; this.name = name; this.code = code; this.movable = movable; if(code != null) loaded = true; if(actions) { ActionSet actionSet = (plugin == null ? jEdit.getBuiltInActionSet() : plugin.getActionSet()); actionSet.addAction(new OpenAction(name)); actionSet.addAction(new ToggleAction(name)); actionSet.addAction(new FloatAction(name)); String label = jEdit.getProperty(name + ".label"); if(label == null) label = "NO LABEL PROPERTY: " + name; String[] args = { label }; jEdit.setTemporaryProperty(name + ".label", label); jEdit.setTemporaryProperty(name + "-toggle.label", jEdit.getProperty( "view.docking.toggle.label",args)); jEdit.setTemporaryProperty(name + "-toggle.toggle","true"); jEdit.setTemporaryProperty(name + "-float.label", jEdit.getProperty( "view.docking.float.label",args)); } } //}}} //{{{ load() method void load() { if(loaded) return; loadDockableWindows(plugin,plugin.getDockablesURI(),null); } //}}} //{{{ createDockableWindow() method JComponent createDockableWindow(View view, String position) { // Avoid infinite recursion synchronized(this) { if (isBeingCreated) return null; isBeingCreated = true; } load(); if(!loaded) { Log.log(Log.WARNING,this,"Outdated cache"); return null; } NameSpace nameSpace = new NameSpace(BeanShell.getNameSpace(), "DockableWindowManager.Factory.createDockableWindow()"); try { nameSpace.setVariable("position",position); } catch(UtilEvalError e) { Log.log(Log.ERROR,this,e); } JComponent win = (JComponent)BeanShell.eval(view,nameSpace,code); if (jEdit.getBooleanProperty("textColors")) { LookAndFeel laf = UIManager.getLookAndFeel(); if (!laf.getID().equals("Metal")) { GUIUtilities.applyTextAreaColors(win); } } synchronized(this) { isBeingCreated = false; } return win; } //}}} //{{{ OpenAction class class OpenAction extends EditAction { private String dockable; //{{{ OpenAction constructor OpenAction(String name) { super(name); this.dockable = name; } //}}} //{{{ invoke() method public void invoke(View view) { view.getDockableWindowManager() .showDockableWindow(dockable); } //}}} //{{{ getCode() method @Override public String getCode() { return "view.getDockableWindowManager()" + ".showDockableWindow(\"" + dockable + "\");"; } //}}} } //}}} //{{{ ToggleAction class class ToggleAction extends EditAction { private String dockable; //{{{ ToggleAction constructor ToggleAction(String name) { super(name + "-toggle"); this.dockable = name; } //}}} //{{{ invoke() method public void invoke(View view) { view.getDockableWindowManager() .toggleDockableWindow(dockable); } //}}} //{{{ isSelected() method public boolean isSelected(View view) { return view.getDockableWindowManager() .isDockableWindowVisible(dockable); } //}}} //{{{ getCode() method @Override public String getCode() { return "view.getDockableWindowManager()" + ".toggleDockableWindow(\"" + dockable + "\");"; } //}}} } //}}} //{{{ FloatAction class class FloatAction extends EditAction { private String dockable; //{{{ FloatAction constructor FloatAction(String name) { super(name + "-float"); this.dockable = name; } //}}} //{{{ invoke() method public void invoke(View view) { view.getDockableWindowManager() .floatDockableWindow(dockable); } //}}} //{{{ getCode() method @Override public String getCode() { return "view.getDockableWindowManager()" + ".floatDockableWindow(\"" + dockable + "\");"; } //}}} } //}}} } //}}} private static DockableWindowFactory instance; private final Map<String, Window> dockableWindowFactories; }