/* * DockableWindowManagerImpl.java - manages dockable windows * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2000, 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.awt.Component; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedWriter; import java.io.Closeable; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.TreeMap; import javax.swing.AbstractButton; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.UIManager; import org.gjt.sp.jedit.EditBus; import org.gjt.sp.jedit.PluginJAR; import org.gjt.sp.jedit.View; import org.gjt.sp.jedit.jEdit; import org.gjt.sp.jedit.EditBus.EBHandler; import org.gjt.sp.jedit.View.ViewConfig; import org.gjt.sp.jedit.msg.DockableWindowUpdate; import org.gjt.sp.jedit.msg.PluginUpdate; import org.gjt.sp.jedit.msg.PropertiesChanged; import org.gjt.sp.util.Log; import org.gjt.sp.util.XMLUtilities; import org.gjt.sp.util.IOUtilities; import org.xml.sax.Attributes; import org.xml.sax.helpers.DefaultHandler; // }}} /** Manages dockable windows for a single View. * * Concrete implementation of a Dockable Window Manager. * Aka the "classic" docking framework. * * @since jEdit 4.3pre16 * @author Slava Pestov * @author Shlomy Reinstein * @version $Id$ */ public class DockableWindowManagerImpl extends DockableWindowManager { //{{{ DockableWindowConfig class public static class DockableWindowConfig extends DockingLayout { // {{{ PerspectiveHandler class public class PerspectiveHandler extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attrs) { for (int i = 0; i < attrs.getLength(); i++) attribute(attrs.getQName(i), attrs.getValue(i)); } private void attribute(String aname, String value) { if(aname.equals("TOP")) top = value; else if(aname.equals("LEFT")) left = value; else if(aname.equals("BOTTOM")) bottom = value; else if(aname.equals("RIGHT")) right = value; else if(aname.equals("TOP_POS")) topPos = Integer.parseInt(value); else if(aname.equals("LEFT_POS")) leftPos = Integer.parseInt(value); else if(aname.equals("BOTTOM_POS")) bottomPos = Integer.parseInt(value); else if(aname.equals("RIGHT_POS")) rightPos = Integer.parseInt(value); } } // }}} // dockables public String top, left, bottom, right; public int topPos, leftPos, bottomPos, rightPos; public DefaultHandler getPerspectiveHandler() { return new PerspectiveHandler(); } public boolean saveLayout(String baseName, int viewIndex) { String lineSep = System.getProperty("line.separator"); String filename = getLayoutFilename(baseName, viewIndex); BufferedWriter out = null; try { out = new BufferedWriter(new FileWriter(filename)); out.write("<DOCKING LEFT=\""); out.write(left == null ? "" : left); out.write("\" TOP=\""); out.write(top == null ? "" : top); out.write("\" RIGHT=\""); out.write(right == null ? "" : right); out.write("\" BOTTOM=\""); out.write(bottom == null ? "" : bottom); out.write("\" LEFT_POS=\""); out.write(String.valueOf(leftPos)); out.write("\" TOP_POS=\""); out.write(String.valueOf(topPos)); out.write("\" RIGHT_POS=\""); out.write(String.valueOf(rightPos)); out.write("\" BOTTOM_POS=\""); out.write(String.valueOf(bottomPos)); out.write("\" />"); out.write(lineSep); } catch (IOException e) { Log.log(Log.ERROR, this, e, e); return false; } finally { IOUtilities.closeQuietly((Closeable)out); } return true; } @Override public boolean loadLayout(String baseName, int viewIndex) { String filename = getLayoutFilename(baseName, viewIndex); DefaultHandler handler = getPerspectiveHandler(); try { // no need to close the stream it is closed by XMLUtilities.parseXML() method XMLUtilities.parseXML(new FileInputStream(filename), handler); } catch (FileNotFoundException e) { return false; } catch (IOException e) { return false; } return true; } @Override public String getName() { return "DockableWindowManager"; } } //}}} //{{{ Data members /** A mapping from Strings to Entry objects. */ private Map<String, Entry> windows; private PanelWindowContainer left; private PanelWindowContainer right; private PanelWindowContainer top; private PanelWindowContainer bottom; private List<Entry> clones; private Entry lastEntry; public Stack<String> showStack = new Stack<String>(); // }}} // {{{ setDockingLayout() public void setDockingLayout(DockingLayout docking) { DockableWindowConfig config = (DockableWindowConfig) docking; if (config == null) return; if(config.top != null && config.top.length() != 0) showDockableWindow(config.top); if(config.left != null && config.left.length() != 0) showDockableWindow(config.left); if(config.bottom != null && config.bottom.length() != 0) showDockableWindow(config.bottom); if(config.right != null && config.right.length() != 0) showDockableWindow(config.right); } // }}} // {{{ getDockingLayout() @Override public DockingLayout getDockingLayout(ViewConfig config) { DockableWindowConfig docking = new DockableWindowConfig(); docking.top = getTopDockingArea().getCurrent(); docking.left = getLeftDockingArea().getCurrent(); docking.bottom = getBottomDockingArea().getCurrent(); docking.right = getRightDockingArea().getCurrent(); docking.topPos = getTopDockingArea().getDimension(); docking.leftPos = getLeftDockingArea().getDimension(); docking.bottomPos = getBottomDockingArea().getDimension(); docking.rightPos = getRightDockingArea().getDimension(); return docking; } // }}} //{{{ DockableWindowManagerImpl constructor /** * Creates a new dockable window manager. * @param view The view * @param factory A {@link DockableWindowFactory}, usually * <code>DockableWindowFactory.getInstance()</code>. * @param config A docking configuration * @since jEdit 2.6pre3 */ public DockableWindowManagerImpl(View view, DockableWindowFactory factory, View.ViewConfig config) { super(view, factory, config); setLayout(new DockableLayout()); windows = new HashMap<String, Entry>(); clones = new ArrayList<Entry>(); DockableWindowConfig docking = (DockableWindowConfig) config.docking; if (docking == null) docking = new DockableWindowConfig(); top = new PanelWindowContainer(this,TOP,docking.topPos); left = new PanelWindowContainer(this,LEFT,docking.leftPos); bottom = new PanelWindowContainer(this,BOTTOM,docking.bottomPos); right = new PanelWindowContainer(this,RIGHT,docking.rightPos); add(DockableLayout.TOP_BUTTONS,top.buttonPanel); add(DockableLayout.LEFT_BUTTONS,left.buttonPanel); add(DockableLayout.BOTTOM_BUTTONS,bottom.buttonPanel); add(DockableLayout.RIGHT_BUTTONS,right.buttonPanel); add(TOP,top.dockablePanel); add(LEFT,left.dockablePanel); add(BOTTOM,bottom.dockablePanel); add(RIGHT,right.dockablePanel); } //}}} //{{{ setMainPanel() method public void setMainPanel(JPanel panel) { add(panel, 0); } //}}} //{{{ init() method /** * Initialises dockable window manager. Do not call this method directly. */ public void init() { super.init(); Iterator<DockableWindowFactory.Window> entries = factory.getDockableWindowIterator(); while(entries.hasNext()) addEntry(entries.next()); propertiesChanged(); } //}}} //{{{ floatDockableWindow() method /** * Opens a new instance of the specified dockable window in a floating * container. * @param name The dockable window name * @return The new dockable window instance * @since jEdit 4.1pre2 */ public JComponent floatDockableWindow(String name) { Entry entry = windows.get(name); if(entry == null) { Log.log(Log.ERROR,this,"Unknown dockable window: " + name); return null; } // create a copy of this dockable window and float it Entry newEntry = new Entry(entry.factory,FLOATING); newEntry.win = newEntry.factory.createDockableWindow(view,FLOATING); if(newEntry.win != null) { FloatingWindowContainer fwc = new FloatingWindowContainer(this,true); newEntry.container = fwc; newEntry.container.register(newEntry); newEntry.container.show(newEntry); } clones.add(newEntry); return newEntry.win; } //}}} //{{{ showDockableWindow() method /** * Opens the specified dockable window. * @param name The dockable window name * @since jEdit 2.6pre3 */ public void showDockableWindow(String name) { lastEntry = windows.get(name); if(lastEntry == null) { Log.log(Log.ERROR,this,"Unknown dockable window: " + name); return; } if(lastEntry.win == null) { lastEntry.win = lastEntry.factory.createDockableWindow( view,lastEntry.position); } if(lastEntry.win != null) { if(lastEntry.position.equals(FLOATING) && lastEntry.container == null) { FloatingWindowContainer fwc = new FloatingWindowContainer( this,view.isPlainView()); lastEntry.container = fwc; lastEntry.container.register(lastEntry); } showStack.push(name); lastEntry.container.show(lastEntry); Object reason = DockableWindowUpdate.ACTIVATED; EditBus.send(new DockableWindowUpdate(this, reason, name)); } else /* an error occurred */; } //}}} //{{{ hideDockableWindow() method /** * Hides the specified dockable window. * @param name The dockable window name * @since jEdit 2.6pre3 */ public void hideDockableWindow(String name) { Entry entry = windows.get(name); if(entry == null) { Log.log(Log.ERROR,this,"Unknown dockable window: " + name); return; } if(entry.win == null) return; entry.container.show(null); } //}}} //{{{ getDockable() method /** * Returns the specified dockable window. * * Note that this method * will return null if the dockable has not been added yet. * Make sure you call {@link #addDockableWindow(String)} first. * * For historical reasons, this * does the same thing as {@link #getDockableWindow(String)}. * * @param name The name of the dockable window * @since jEdit 4.0pre1 */ public JComponent getDockable(String name) { Entry entry = windows.get(name); if(entry == null || entry.win == null) return null; else return entry.win; } //}}} //{{{ isDockableWindowVisible() method /** * Returns if the specified dockable window is visible. * @param name The dockable window name */ public boolean isDockableWindowVisible(String name) { Entry entry = windows.get(name); if(entry == null || entry.win == null) return false; else return entry.container.isVisible(entry); } //}}} //{{{ isDockableWindowDocked() method /** * Returns if the specified dockable window is docked into the * view. * @param name The dockable's name * @since jEdit 4.0pre2 */ public boolean isDockableWindowDocked(String name) { Entry entry = windows.get(name); if(entry == null) return false; else return !entry.position.equals(FLOATING); } //}}} //{{{ closeCurrentArea() method /** * Closes the most recently focused dockable. * @since jEdit 4.1pre3 */ public void closeCurrentArea() { // I don't know of any other way to fix this, since invoking this // command from a menu results in the focus owner being the menu // until the menu goes away. SwingUtilities.invokeLater(new Runnable() { public void run() { /* Try to hide the last entry that was shown */ try { String dockableName = showStack.pop(); hideDockableWindow(dockableName); return; } catch (Exception e) {} Component comp = view.getFocusOwner(); while(comp != null) { //System.err.println(comp.getClass()); if(comp instanceof DockablePanel) { DockablePanel panel = (DockablePanel) comp; PanelWindowContainer container = panel.getWindowContainer(); container.show((DockableWindowManagerImpl.Entry) null); return; } comp = comp.getParent(); } javax.swing.UIManager.getLookAndFeel().provideErrorFeedback(null); } }); } //}}} //{{{ close() method /** * Called when the view is being closed. * @since jEdit 2.6pre3 */ public void close() { super.close(); for (Entry entry : windows.values()) { if (entry.win != null) entry.container.unregister(entry); } for (Entry clone : clones) { if (clone.win != null) clone.container.unregister(clone); } } //}}} //{{{ getTopDockingArea() method public PanelWindowContainer getTopDockingArea() { return top; } //}}} //{{{ getLeftDockingArea() method public PanelWindowContainer getLeftDockingArea() { return left; } //}}} //{{{ getBottomDockingArea() method public PanelWindowContainer getBottomDockingArea() { return bottom; } //}}} //{{{ getRightDockingArea() method public PanelWindowContainer getRightDockingArea() { return right; } //}}} //{{{ createPopupMenu() method public JPopupMenu createPopupMenu( final DockableWindowContainer container, final String dockable, final boolean clone) { JPopupMenu popup = new JPopupMenu(); if(dockable == null && container instanceof PanelWindowContainer) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent evt) { showDockableWindow(evt.getActionCommand()); } }; String[] dockables = ((PanelWindowContainer) container).getDockables(); Map<String,String> dockableMap = new TreeMap<String, String>(); for (String action : dockables) dockableMap.put(getDockableTitle(action), action); for (Map.Entry<String, String> entry : dockableMap.entrySet()) { JMenuItem item = new JMenuItem(entry.getKey()); item.setActionCommand(entry.getValue()); item.addActionListener(listener); popup.add(item); } } else { JMenuItem caption = new JMenuItem(getDockableTitle(dockable)); caption.setEnabled(false); popup.add(caption); popup.addSeparator(); String currentPos = jEdit.getProperty(dockable + ".dock-position",FLOATING); if(!clone) { String[] positions = { FLOATING, TOP, LEFT, BOTTOM, RIGHT }; for (final String pos : positions) { if (pos.equals(currentPos)) continue; JMenuItem moveMenuItem = new JMenuItem(jEdit.getProperty("view.docking.menu-" + pos)); moveMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { jEdit.setProperty(dockable + ".dock-position", pos); EditBus.send( new DockableWindowUpdate(DockableWindowManagerImpl.this, DockableWindowUpdate.PROPERTIES_CHANGED, dockable)); showDockableWindow(dockable); } }); popup.add(moveMenuItem); } popup.addSeparator(); } JMenuItem cloneMenuItem = new JMenuItem(jEdit.getProperty("view.docking.menu-clone")); cloneMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { floatDockableWindow(dockable); } }); popup.add(cloneMenuItem); popup.addSeparator(); JMenuItem closeMenuItem = new JMenuItem(jEdit.getProperty("view.docking.menu-close")); closeMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { if(clone) ((FloatingWindowContainer)container).dispose(); else removeDockableWindow(dockable); } }); popup.add(closeMenuItem); if(!(clone || currentPos.equals(FLOATING))) { JMenuItem undockMenuItem = new JMenuItem(jEdit.getProperty("view.docking.menu-undock")); undockMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { jEdit.setProperty(dockable + ".dock-position",FLOATING); EditBus.send(new DockableWindowUpdate( DockableWindowManagerImpl.this, DockableWindowUpdate.PROPERTIES_CHANGED, dockable )); // Reset the window, propertiesChanged() doesn't // reset it for MOVABLE windows. Entry entry = windows.get(dockable); if (entry == null) Log.log(Log.ERROR,this,"Unknown dockable window: " + dockable); else entry.win = null; } }); popup.add(undockMenuItem); } } return popup; } //}}} //{{{ paintChildren() method public void paintChildren(Graphics g) { super.paintChildren(g); if(resizeRect != null) { g.setColor(Color.darkGray); g.fillRect(resizeRect.x,resizeRect.y, resizeRect.width,resizeRect.height); } } //}}} //{{{ handleDockableWindowUpdate() method @EBHandler public void handleDockableWindowUpdate(DockableWindowUpdate msg) { if (msg.getWhat() == DockableWindowUpdate.PROPERTIES_CHANGED) propertiesChanged(); } //}}} //{{{ handlePropertiesChanged() method @EBHandler public void handlePropertiesChanged(PropertiesChanged msg) { propertiesChanged(); } //}}} //{{{ handlePluginUpdate() method @EBHandler public void handlePluginUpdate(PluginUpdate pmsg) { if(pmsg.getWhat() == PluginUpdate.LOADED) { Iterator<DockableWindowFactory.Window> iter = factory.getDockableWindowIterator(); while(iter.hasNext()) { DockableWindowFactory.Window w = iter.next(); if(w.plugin == pmsg.getPluginJAR()) addEntry(w); } propertiesChanged(); } else if(pmsg.isExiting()) { // we don't care } else if(pmsg.getWhat() == PluginUpdate.DEACTIVATED) { Iterator<Entry> iter = getAllPluginEntries( pmsg.getPluginJAR(),false); while(iter.hasNext()) { Entry entry = iter.next(); if(entry.container != null) entry.container.remove(entry); } } else if(pmsg.getWhat() == PluginUpdate.UNLOADED) { Iterator<Entry> iter = getAllPluginEntries( pmsg.getPluginJAR(),true); while(iter.hasNext()) { Entry entry = iter.next(); if(entry.container != null) { entry.container.unregister(entry); entry.win = null; entry.container = null; } } } } //}}} //{{{ Package-private members int resizePos; /** * This is the rectangle you drag to resize the split. * It is used with non continuous layout. */ Rectangle resizeRect; //{{{ setResizePos() method void setResizePos(int resizePos, PanelWindowContainer resizing) { this.resizePos = resizePos; if(resizePos < 0) resizePos = 0; if (continuousLayout) return; Rectangle newResizeRect = new Rectangle(0,0, PanelWindowContainer.SPLITTER_WIDTH - 2, PanelWindowContainer.SPLITTER_WIDTH - 2); if(resizing == top) { resizePos = Math.min(resizePos,getHeight() - top.buttonPanel.getHeight() - bottom.dockablePanel.getHeight() - bottom.buttonPanel.getHeight() - PanelWindowContainer.SPLITTER_WIDTH); newResizeRect.x = top.dockablePanel.getX() + 1; newResizeRect.y = resizePos + top.buttonPanel.getHeight() + 1; newResizeRect.width = top.dockablePanel.getWidth() - 2; } else if(resizing == left) { resizePos = Math.min(resizePos,getWidth() - left.buttonPanel.getWidth() - right.dockablePanel.getWidth() - right.buttonPanel.getWidth() - PanelWindowContainer.SPLITTER_WIDTH); newResizeRect.x = resizePos + left.buttonPanel.getWidth() + 1; newResizeRect.y = left.dockablePanel.getY() + 1; newResizeRect.height = left.dockablePanel.getHeight() - 2; } else if(resizing == bottom) { resizePos = Math.min(resizePos,getHeight() - bottom.buttonPanel.getHeight() - top.dockablePanel.getHeight() - top.buttonPanel.getHeight() - PanelWindowContainer.SPLITTER_WIDTH); newResizeRect.x = bottom.dockablePanel.getX() + 1; newResizeRect.y = getHeight() - bottom.buttonPanel.getHeight() - resizePos - PanelWindowContainer.SPLITTER_WIDTH + 2; newResizeRect.width = bottom.dockablePanel.getWidth() - 2; } else if(resizing == right) { resizePos = Math.min(resizePos,getWidth() - right.buttonPanel.getWidth() - left.dockablePanel.getWidth() - left.buttonPanel.getWidth() - PanelWindowContainer.SPLITTER_WIDTH); newResizeRect.x = getWidth() - right.buttonPanel.getWidth() - resizePos - PanelWindowContainer.SPLITTER_WIDTH + 1; newResizeRect.y = right.dockablePanel.getY() + 1; newResizeRect.height = right.dockablePanel.getHeight() - 2; } Rectangle toRepaint; if(resizeRect == null) toRepaint = newResizeRect; else toRepaint = resizeRect.union(newResizeRect); resizeRect = newResizeRect; repaint(toRepaint); } //}}} //{{{ finishResizing() method void finishResizing() { resizeRect = null; repaint(); } //}}} //}}} //{{{ propertiesChanged() method protected void propertiesChanged() { if(view.isPlainView()) return; ((DockableLayout)getLayout()).setAlternateLayout( jEdit.getBooleanProperty("view.docking.alternateLayout")); String[] windowList = factory.getRegisteredDockableWindows(); for (String dockable : windowList) { Entry entry = windows.get(dockable); if (entry == null) { // this can happens during a plugin loading when a dockable has been registered but the window // is not yet created if the plugin has some jars. continue; } String newPosition = jEdit.getProperty(dockable + ".dock-position", FLOATING); if (newPosition.equals(entry.position)) continue; entry.position = newPosition; if (entry.container != null) { entry.container.unregister(entry); entry.container = null; if (entry.factory.movable && (!newPosition.equals(FLOATING))) { if (entry.win instanceof DockableWindow) ((DockableWindow) entry.win).move(newPosition); } else entry.win = null; } if (newPosition.equals(FLOATING)) { } else { if (newPosition.equals(TOP)) entry.container = top; else if (newPosition.equals(LEFT)) entry.container = left; else if (newPosition.equals(BOTTOM)) entry.container = bottom; else if (newPosition.equals(RIGHT)) entry.container = right; else { Log.log(Log.WARNING, this, "Unknown position: " + newPosition); continue; } entry.container.register(entry); } } top.sortDockables(); left.sortDockables(); bottom.sortDockables(); right.sortDockables(); continuousLayout = UIManager.getBoolean("SplitPane.continuousLayout"); revalidate(); repaint(); } //}}} //{{{ addEntry() method private void addEntry(DockableWindowFactory.Window factory) { Entry e; if(view.isPlainView()) { // don't show menu items to dock into a plain view e = new Entry(factory,FLOATING); } else { e = new Entry(factory); if(e.position.equals(FLOATING)) /* nothing to do */; else if(e.position.equals(TOP)) e.container = top; else if(e.position.equals(LEFT)) e.container = left; else if(e.position.equals(BOTTOM)) e.container = bottom; else if(e.position.equals(RIGHT)) e.container = right; else { Log.log(Log.WARNING,this, "Unknown position: " + e.position); } if(e.container != null) e.container.register(e); } windows.put(factory.name,e); } //}}} //{{{ getAllPluginEntries() method /** * If remove is false, only remove from clones list, otherwise remove * from both entries and clones. */ private Iterator<Entry> getAllPluginEntries(PluginJAR plugin, boolean remove) { List<Entry> returnValue = new LinkedList<Entry>(); Iterator<Entry> iter = windows.values().iterator(); while(iter.hasNext()) { Entry entry = iter.next(); if(entry.factory.plugin == plugin) { returnValue.add(entry); if(remove) iter.remove(); } } iter = clones.iterator(); while(iter.hasNext()) { Entry entry = iter.next(); if(entry.factory.plugin == plugin) { returnValue.add(entry); iter.remove(); } } return returnValue.iterator(); } //}}} private boolean continuousLayout; //{{{ Entry class static class Entry { DockableWindowFactory.Window factory; // String title; String position; DockableWindowContainer container; // only set if open JComponent win; // only for docked AbstractButton btn; //{{{ Entry constructor Entry(DockableWindowFactory.Window factory) { this(factory,jEdit.getProperty(factory.name + ".dock-position",FLOATING)); } //}}} /** * @return the long title for the dockable floating window. */ public String longTitle() { String title = jEdit.getProperty(factory.name + ".longtitle"); if (title == null) return shortTitle(); else return title; } /** * @return The short title, for the dockable button text */ public String shortTitle() { String title = jEdit.getProperty(factory.name + ".title"); if(title == null) return "NO TITLE PROPERTY: " + factory.name; else return title; } /** * @return A label appropriate for the title on the dock buttons. */ public String label() { String retval = jEdit.getProperty(factory.name + ".label"); retval = retval.replaceAll("\\$", ""); return retval; } //{{{ Entry constructor Entry(DockableWindowFactory.Window factory, String position) { this.factory = factory; this.position = position; // get the title here, not in the factory constructor, // since the factory might be created before a plugin's // props are loaded } //}}} } //}}} @Override public void disposeDockableWindow(String name) { // Nothing to do here; called as a response to plugin unloading, // handled differently by this implementation. } }