0001: /*
0002: * DockableWindowManager.java - manages dockable windows
0003: * :tabSize=8:indentSize=8:noTabs=false:
0004: * :folding=explicit:collapseFolds=1:
0005: *
0006: * Copyright (C) 2000, 2005 Slava Pestov
0007: *
0008: * This program is free software; you can redistribute it and/or
0009: * modify it under the terms of the GNU General Public License
0010: * as published by the Free Software Foundation; either version 2
0011: * of the License, or any later version.
0012: *
0013: * This program is distributed in the hope that it will be useful,
0014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0016: * GNU General Public License for more details.
0017: *
0018: * You should have received a copy of the GNU General Public License
0019: * along with this program; if not, write to the Free Software
0020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0021: */
0022:
0023: package org.gjt.sp.jedit.gui;
0024:
0025: //{{{ Imports
0026: import java.awt.Color;
0027: import java.awt.Component;
0028: import java.awt.Graphics;
0029: import java.awt.Rectangle;
0030: import java.awt.event.ActionEvent;
0031: import java.awt.event.ActionListener;
0032: import java.awt.event.KeyAdapter;
0033: import java.awt.event.KeyEvent;
0034: import java.awt.event.KeyListener;
0035: import java.util.*;
0036:
0037: import javax.swing.AbstractButton;
0038: import javax.swing.JComponent;
0039: import javax.swing.JMenuItem;
0040: import javax.swing.JPanel;
0041: import javax.swing.JPopupMenu;
0042: import javax.swing.SwingUtilities;
0043:
0044: import org.gjt.sp.jedit.EBComponent;
0045: import org.gjt.sp.jedit.EBMessage;
0046: import org.gjt.sp.jedit.EditBus;
0047: import org.gjt.sp.jedit.PluginJAR;
0048: import org.gjt.sp.jedit.View;
0049: import org.gjt.sp.jedit.jEdit;
0050: import org.gjt.sp.jedit.gui.KeyEventTranslator.Key;
0051: import org.gjt.sp.jedit.msg.DockableWindowUpdate;
0052: import org.gjt.sp.jedit.msg.PluginUpdate;
0053: import org.gjt.sp.jedit.msg.PropertiesChanged;
0054: import org.gjt.sp.util.Log;
0055:
0056: //}}}
0057:
0058: /**
0059: * <p>Keeps track of all dockable windows for a single View, and provides
0060: * an API for getting/showing/hiding them. </p>
0061: *
0062: * <p>Each {@link org.gjt.sp.jedit.View} has an instance of this class.</p>
0063: *
0064: * <p><b>dockables.xml:</b></p>
0065: *
0066: * <p>Dockable window definitions are read from <code>dockables.xml</code> files
0067: * contained inside plugin JARs. A dockable definition file has the following
0068: * form: </p>
0069: *
0070: * <pre><?xml version="1.0"?>
0071: *<!DOCTYPE DOCKABLES SYSTEM "dockables.dtd">
0072: *<DOCKABLES>
0073: * <DOCKABLE NAME="<i>dockableName</i>" MOVABLE="TRUE|FALSE">
0074: * // Code to create the dockable
0075: * </DOCKABLE>
0076: *</DOCKABLES></pre>
0077: *
0078: * <p>The MOVABLE attribute specifies the behavior when the docking position of
0079: * the dockable window is changed. If MOVABLE is TRUE, the existing instance of
0080: * the dockable window is moved to the new docking position, and if the dockable
0081: * window implements the DockableWindow interface (see {@link DockableWindow}),
0082: * it is also notified about the change in docking position before it is moved.
0083: * If MOVABLE is FALSE, the BeanShell code is invoked to get the instance of
0084: * the dockable window to put in the new docking position. Typically, the
0085: * BeanShell code returns a new instance of the dockable window, and the state
0086: * of the existing instance is not preserved after the change. It is therefore
0087: * recommended to set MOVABLE to TRUE for all dockables in order to make them
0088: * preserve their state when they are moved. For backward compatibility reasons,
0089: * this attribute is set to FALSE by default.</p>
0090: * <p>More than one <code><DOCKABLE></code> tag may be present. The code that
0091: * creates the dockable can reference any BeanShell built-in variable
0092: * (see {@link org.gjt.sp.jedit.BeanShell}), along with a variable
0093: * <code>position</code> whose value is one of
0094: * {@link #FLOATING}, {@link #TOP}, {@link #LEFT}, {@link #BOTTOM},
0095: * and {@link #RIGHT}. </p>
0096: *
0097: * <p>The following properties must be defined for each dockable window: </p>
0098: *
0099: * <ul>
0100: * <li><code><i>dockableName</i>.title</code> - the string to show on the dockable
0101: * button. </li>
0102: * <li><code><i>dockableName</i>.label</code> - The string to use for generating
0103: * menu items and action names. </li>
0104: * <li><code><i>dockableName</i>.longtitle</code> - (optional) the string to use
0105: * in the dockable's floating window title (when it is floating).
0106: * If not specified, the <code><i>dockableName</i>.title</code> property is used. </li>
0107: * </ul>
0108: *
0109: * A number of actions are automatically created for each dockable window:
0110: *
0111: * <ul>
0112: * <li><code><i>dockableName</i></code> - opens the dockable window.</li>
0113: * <li><code><i>dockableName</i>-toggle</code> - toggles the dockable window's visibility.</li>
0114: * <li><code><i>dockableName</i>-float</code> - opens the dockable window in a new
0115: * floating window.</li>
0116: * </ul>
0117: *
0118: * Note that only the first action needs a <code>label</code> property, the
0119: * rest have automatically-generated labels.
0120: *
0121: * <p> <b>Implementation details:</b></p>
0122: *
0123: * <p> When an instance of this class is initialized by the {@link org.gjt.sp.jedit.View}
0124: * class, it
0125: * iterates through the list of registered dockable windows (from jEdit itself,
0126: * and any loaded plugins) and
0127: * examines options supplied by the user in the <b>Global
0128: * Options</b> dialog box. Any plugins designated for one of the
0129: * four docking positions are displayed.</p>
0130: *
0131: * <p> To create an instance of a dockable window, the <code>DockableWindowManager</code>
0132: * finds and executes the BeanShell code extracted from the appropriate
0133: * <code>dockables.xml</code> file. This code will typically consist of a call
0134: * to the constructor of the dockable window component. The result of the
0135: * BeanShell expression, typically a newly constructed component, is placed
0136: * in a window managed by this class. </p>
0137: *
0138: * @see org.gjt.sp.jedit.View#getDockableWindowManager()
0139: *
0140: * @author Slava Pestov
0141: * @author John Gellene (API documentation)
0142: * @version $Id: DockableWindowManager.java 10703 2007-09-21 13:14:03Z shlomy $
0143: * @since jEdit 2.6pre3
0144: */
0145: public class DockableWindowManager extends JPanel implements
0146: EBComponent {
0147: //{{{ Constants
0148: /**
0149: * Floating position.
0150: * @since jEdit 2.6pre3
0151: */
0152: public static final String FLOATING = "floating";
0153:
0154: /**
0155: * Top position.
0156: * @since jEdit 2.6pre3
0157: */
0158: public static final String TOP = "top";
0159:
0160: /**
0161: * Left position.
0162: * @since jEdit 2.6pre3
0163: */
0164: public static final String LEFT = "left";
0165:
0166: /**
0167: * Bottom position.
0168: * @since jEdit 2.6pre3
0169: */
0170: public static final String BOTTOM = "bottom";
0171:
0172: /**
0173: * Right position.
0174: * @since jEdit 2.6pre3
0175: */
0176: public static final String RIGHT = "right";
0177: //}}}
0178:
0179: //{{{ Data members
0180: private View view;
0181: private DockableWindowFactory factory;
0182:
0183: /** A mapping from Strings to Entry objects. */
0184: private Map<String, Entry> windows;
0185: private PanelWindowContainer left;
0186: private PanelWindowContainer right;
0187: private PanelWindowContainer top;
0188: private PanelWindowContainer bottom;
0189: private List<Entry> clones;
0190:
0191: private Entry lastEntry;
0192: public Stack showStack = new Stack();
0193:
0194: // }}}
0195:
0196: //{{{ getRegisteredDockableWindows() method
0197: /**
0198: * @since jEdit 4.3pre2
0199: */
0200: public static String[] getRegisteredDockableWindows() {
0201: return DockableWindowFactory.getInstance()
0202: .getRegisteredDockableWindows();
0203: } //}}}
0204:
0205: //{{{ DockableWindowManager constructor
0206: /**
0207: * Creates a new dockable window manager.
0208: * @param view The view
0209: * @param factory A {@link DockableWindowFactory}, usually
0210: * <code>DockableWindowFactory.getInstance()</code>.
0211: * @param config A docking configuration
0212: * @since jEdit 2.6pre3
0213: */
0214: public DockableWindowManager(View view,
0215: DockableWindowFactory factory, View.ViewConfig config) {
0216: setLayout(new DockableLayout());
0217: this .view = view;
0218: this .factory = factory;
0219:
0220: windows = new HashMap<String, Entry>();
0221: clones = new ArrayList<Entry>();
0222:
0223: top = new PanelWindowContainer(this , TOP, config.topPos);
0224: left = new PanelWindowContainer(this , LEFT, config.leftPos);
0225: bottom = new PanelWindowContainer(this , BOTTOM,
0226: config.bottomPos);
0227: right = new PanelWindowContainer(this , RIGHT, config.rightPos);
0228:
0229: add(DockableLayout.TOP_BUTTONS, top.buttonPanel);
0230: add(DockableLayout.LEFT_BUTTONS, left.buttonPanel);
0231: add(DockableLayout.BOTTOM_BUTTONS, bottom.buttonPanel);
0232: add(DockableLayout.RIGHT_BUTTONS, right.buttonPanel);
0233:
0234: add(TOP, top.dockablePanel);
0235: add(LEFT, left.dockablePanel);
0236: add(BOTTOM, bottom.dockablePanel);
0237: add(RIGHT, right.dockablePanel);
0238: } //}}}
0239:
0240: //{{{ init() method
0241: /**
0242: * Initialises dockable window manager. Do not call this method directly.
0243: */
0244: public void init() {
0245: EditBus.addToBus(this );
0246:
0247: Iterator<DockableWindowFactory.Window> entries = factory
0248: .getDockableWindowIterator();
0249:
0250: while (entries.hasNext())
0251: addEntry(entries.next());
0252:
0253: propertiesChanged();
0254: } //}}}
0255:
0256: // {{{ closeListener() method
0257: /**
0258: *
0259: * The actionEvent "close-docking-area" by default only works on
0260: * windows that are docked. If you want your floatable plugins to also
0261: * respond to this event, you need to add key listeners to each component
0262: * in your plugin that usually has keyboard focus.
0263: * This function returns a key listener which does exactly that.
0264: * You should not need to call this method - it is used by FloatingWindowContainer.
0265: *
0266: * @param dockableName the name of your dockable
0267: * @return a KeyListener you can add to that plugin's component.
0268: * @since jEdit 4.3pre6
0269: *
0270: */
0271: public KeyListener closeListener(String dockableName) {
0272: return new KeyHandler(dockableName);
0273: }
0274:
0275: // }}}
0276:
0277: //{{{ getView() method
0278: /**
0279: * Returns this dockable window manager's view.
0280: * @since jEdit 4.0pre2
0281: */
0282: public View getView() {
0283: return view;
0284: } //}}}
0285:
0286: //{{{ floatDockableWindow() method
0287: /**
0288: * Opens a new instance of the specified dockable window in a floating
0289: * container.
0290: * @param name The dockable window name
0291: * @return The new dockable window instance
0292: * @since jEdit 4.1pre2
0293: */
0294: public JComponent floatDockableWindow(String name) {
0295: Entry entry = windows.get(name);
0296: if (entry == null) {
0297: Log
0298: .log(Log.ERROR, this , "Unknown dockable window: "
0299: + name);
0300: return null;
0301: }
0302:
0303: // create a copy of this dockable window and float it
0304: Entry newEntry = new Entry(entry.factory, FLOATING);
0305: newEntry.win = newEntry.factory.createDockableWindow(view,
0306: FLOATING);
0307:
0308: if (newEntry.win != null) {
0309: FloatingWindowContainer fwc = new FloatingWindowContainer(
0310: this , true);
0311: newEntry.container = fwc;
0312: newEntry.container.register(newEntry);
0313: newEntry.container.show(newEntry);
0314:
0315: }
0316: clones.add(newEntry);
0317: return newEntry.win;
0318: } //}}}
0319:
0320: //{{{ showDockableWindow() method
0321: /**
0322: * Opens the specified dockable window.
0323: * @param name The dockable window name
0324: * @since jEdit 2.6pre3
0325: */
0326: public void showDockableWindow(String name) {
0327: lastEntry = windows.get(name);
0328: if (lastEntry == null) {
0329: Log
0330: .log(Log.ERROR, this , "Unknown dockable window: "
0331: + name);
0332: return;
0333: }
0334:
0335: if (lastEntry.win == null) {
0336: lastEntry.win = lastEntry.factory.createDockableWindow(
0337: view, lastEntry.position);
0338: }
0339:
0340: if (lastEntry.win != null) {
0341: if (lastEntry.position.equals(FLOATING)
0342: && lastEntry.container == null) {
0343: FloatingWindowContainer fwc = new FloatingWindowContainer(
0344: this , view.isPlainView());
0345: lastEntry.container = fwc;
0346: lastEntry.container.register(lastEntry);
0347: }
0348: showStack.push(name);
0349: lastEntry.container.show(lastEntry);
0350: Object reason = DockableWindowUpdate.ACTIVATED;
0351: EditBus.send(new DockableWindowUpdate(this , reason, name));
0352: } else
0353: /* an error occurred */;
0354: } //}}}
0355:
0356: //{{{ addDockableWindow() method
0357: /**
0358: * Opens the specified dockable window. As of jEdit 4.0pre1, has the
0359: * same effect as calling showDockableWindow().
0360: * @param name The dockable window name
0361: * @since jEdit 2.6pre3
0362: */
0363: public void addDockableWindow(String name) {
0364: showDockableWindow(name);
0365: } //}}}
0366:
0367: //{{{ hideDockableWindow() method
0368: /**
0369: * Hides the specified dockable window.
0370: * @param name The dockable window name
0371: * @since jEdit 2.6pre3
0372: */
0373: public void hideDockableWindow(String name) {
0374:
0375: Entry entry = windows.get(name);
0376: if (entry == null) {
0377: Log
0378: .log(Log.ERROR, this , "Unknown dockable window: "
0379: + name);
0380: return;
0381: }
0382:
0383: if (entry.win == null)
0384: return;
0385: Object reason = DockableWindowUpdate.DEACTIVATED;
0386: EditBus.send(new DockableWindowUpdate(this , reason, name));
0387:
0388: entry.container.show(null);
0389: } //}}}
0390:
0391: //{{{ removeDockableWindow() method
0392: /**
0393: * Hides the specified dockable window. As of jEdit 4.2pre1, has the
0394: * same effect as calling hideDockableWindow().
0395: * @param name The dockable window name
0396: * @since jEdit 4.2pre1
0397: */
0398: public void removeDockableWindow(String name) {
0399: hideDockableWindow(name);
0400: } //}}}
0401:
0402: //{{{ toggleDockableWindow() method
0403: /**
0404: * Toggles the visibility of the specified dockable window.
0405: * @param name The dockable window name
0406: */
0407: public void toggleDockableWindow(String name) {
0408: if (isDockableWindowVisible(name))
0409: removeDockableWindow(name);
0410: else
0411: addDockableWindow(name);
0412: } //}}}
0413:
0414: //{{{ getDockableWindow() method
0415: /**
0416: * Returns the specified dockable window.
0417: *
0418: * Note that this method
0419: * will return null if the dockable has not been added yet.
0420: * Make sure you call {@link #addDockableWindow(String)} first.
0421: *
0422: * @param name The name of the dockable window
0423: * @since jEdit 4.1pre2
0424: */
0425: public JComponent getDockableWindow(String name) {
0426: return getDockable(name);
0427: } //}}}
0428:
0429: //{{{ getDockable() method
0430: /**
0431: * Returns the specified dockable window.
0432: *
0433: * Note that this method
0434: * will return null if the dockable has not been added yet.
0435: * Make sure you call {@link #addDockableWindow(String)} first.
0436: *
0437: * For historical reasons, this
0438: * does the same thing as {@link #getDockableWindow(String)}.
0439: *
0440: * @param name The name of the dockable window
0441: * @since jEdit 4.0pre1
0442: */
0443: public JComponent getDockable(String name) {
0444:
0445: Entry entry = windows.get(name);
0446: if (entry == null || entry.win == null)
0447: return null;
0448: else
0449: return entry.win;
0450: } //}}}
0451:
0452: //{{{ getDockableTitle() method
0453: /**
0454: * Returns the title of the specified dockable window.
0455: * @param name The name of the dockable window.
0456: * @since jEdit 4.1pre5
0457: */
0458: public String getDockableTitle(String name) {
0459: Entry e = windows.get(name);
0460: return e.longTitle();
0461: } //}}}
0462:
0463: //{{{ setDockableTitle() method
0464: /**
0465: * Changes the .longtitle property of a dockable window, which corresponds to the
0466: * title shown when it is floating (not docked). Fires a change event that makes sure
0467: * all floating dockables change their title.
0468: *
0469: * @param dockableName the name of the dockable, as specified in the dockables.xml
0470: * @param newTitle the new .longtitle you want to see above it.
0471: * @since 4.3pre5
0472: *
0473: */
0474: public void setDockableTitle(String dockableName, String newTitle) {
0475: Entry entry = windows.get(dockableName);
0476: String propName = entry.factory.name + ".longtitle";
0477: String oldTitle = jEdit.getProperty(propName);
0478: jEdit.setProperty(propName, newTitle);
0479: firePropertyChange(propName, oldTitle, newTitle);
0480: }
0481:
0482: // }}}
0483:
0484: //{{{ isDockableWindowVisible() method
0485: /**
0486: * Returns if the specified dockable window is visible.
0487: * @param name The dockable window name
0488: */
0489: public boolean isDockableWindowVisible(String name) {
0490: Entry entry = windows.get(name);
0491: if (entry == null || entry.win == null)
0492: return false;
0493: else
0494: return entry.container.isVisible(entry);
0495: } //}}}
0496:
0497: //{{{ isDockableWindowDocked() method
0498: /**
0499: * Returns if the specified dockable window is docked into the
0500: * view.
0501: * @param name The dockable's name
0502: * @since jEdit 4.0pre2
0503: */
0504: public boolean isDockableWindowDocked(String name) {
0505: Entry entry = windows.get(name);
0506: if (entry == null)
0507: return false;
0508: else
0509: return !entry.position.equals(FLOATING);
0510: } //}}}
0511:
0512: //{{{ closeCurrentArea() method
0513: /**
0514: * Closes the most recently focused dockable.
0515: * @since jEdit 4.1pre3
0516: */
0517: public void closeCurrentArea() {
0518: // I don't know of any other way to fix this, since invoking this
0519: // command from a menu results in the focus owner being the menu
0520: // until the menu goes away.
0521: SwingUtilities.invokeLater(new Runnable() {
0522: public void run() {
0523: /* Try to hide the last entry that was shown */
0524: try {
0525: String dockableName = showStack.pop().toString();
0526: hideDockableWindow(dockableName);
0527: return;
0528: } catch (Exception e) {
0529: }
0530:
0531: Component comp = view.getFocusOwner();
0532: while (comp != null) {
0533: //System.err.println(comp.getClass());
0534: if (comp instanceof DockablePanel) {
0535: DockablePanel panel = (DockablePanel) comp;
0536:
0537: PanelWindowContainer container = panel
0538: .getWindowContainer();
0539:
0540: container.show(null);
0541: return;
0542: }
0543:
0544: comp = comp.getParent();
0545: }
0546:
0547: getToolkit().beep();
0548: }
0549: });
0550: } //}}}
0551:
0552: //{{{ close() method
0553: /**
0554: * Called when the view is being closed.
0555: * @since jEdit 2.6pre3
0556: */
0557: public void close() {
0558: EditBus.removeFromBus(this );
0559:
0560: for (Entry entry : windows.values()) {
0561: if (entry.win != null)
0562: entry.container.unregister(entry);
0563: }
0564:
0565: for (Entry clone : clones) {
0566: if (clone.win != null)
0567: clone.container.unregister(clone);
0568: }
0569: } //}}}
0570:
0571: //{{{ getTopDockingArea() method
0572: public PanelWindowContainer getTopDockingArea() {
0573: return top;
0574: } //}}}
0575:
0576: //{{{ getLeftDockingArea() method
0577: public PanelWindowContainer getLeftDockingArea() {
0578: return left;
0579: } //}}}
0580:
0581: //{{{ getBottomDockingArea() method
0582: public PanelWindowContainer getBottomDockingArea() {
0583: return bottom;
0584: } //}}}
0585:
0586: //{{{ getRightDockingArea() method
0587: public PanelWindowContainer getRightDockingArea() {
0588: return right;
0589: } //}}}
0590:
0591: //{{{ createPopupMenu() method
0592: public JPopupMenu createPopupMenu(
0593: final DockableWindowContainer container,
0594: final String dockable, final boolean clone) {
0595: JPopupMenu popup = new JPopupMenu();
0596: if (dockable == null
0597: && container instanceof PanelWindowContainer) {
0598: ActionListener listener = new ActionListener() {
0599: public void actionPerformed(ActionEvent evt) {
0600: showDockableWindow(evt.getActionCommand());
0601: }
0602: };
0603:
0604: String[] dockables = ((PanelWindowContainer) container)
0605: .getDockables();
0606: Map<String, String> dockableMap = new TreeMap<String, String>();
0607: for (int i = 0; i < dockables.length; i++) {
0608: String action = dockables[i];
0609: dockableMap.put(getDockableTitle(action), action);
0610: }
0611: for (Map.Entry<String, String> entry : dockableMap
0612: .entrySet()) {
0613: JMenuItem item = new JMenuItem(entry.getKey());
0614: item.setActionCommand(entry.getValue());
0615: item.addActionListener(listener);
0616: popup.add(item);
0617: }
0618: } else {
0619: JMenuItem caption = new JMenuItem(
0620: getDockableTitle(dockable));
0621: caption.setEnabled(false);
0622: popup.add(caption);
0623: popup.addSeparator();
0624: String currentPos = jEdit.getProperty(dockable
0625: + ".dock-position", FLOATING);
0626: if (!clone) {
0627: String[] positions = { FLOATING, TOP, LEFT, BOTTOM,
0628: RIGHT };
0629: for (int i = 0; i < positions.length; i++) {
0630: final String pos = positions[i];
0631: if (pos.equals(currentPos))
0632: continue;
0633:
0634: JMenuItem moveMenuItem = new JMenuItem(jEdit
0635: .getProperty("view.docking.menu-" + pos));
0636:
0637: moveMenuItem
0638: .addActionListener(new ActionListener() {
0639: public void actionPerformed(
0640: ActionEvent evt) {
0641: jEdit.setProperty(dockable
0642: + ".dock-position", pos);
0643: EditBus
0644: .send(new DockableWindowUpdate(
0645: DockableWindowManager.this ,
0646: DockableWindowUpdate.PROPERTIES_CHANGED,
0647: null));
0648: showDockableWindow(dockable);
0649: }
0650: });
0651: popup.add(moveMenuItem);
0652: }
0653:
0654: popup.addSeparator();
0655: }
0656:
0657: JMenuItem cloneMenuItem = new JMenuItem(jEdit
0658: .getProperty("view.docking.menu-clone"));
0659:
0660: cloneMenuItem.addActionListener(new ActionListener() {
0661: public void actionPerformed(ActionEvent evt) {
0662: floatDockableWindow(dockable);
0663: }
0664: });
0665: popup.add(cloneMenuItem);
0666:
0667: popup.addSeparator();
0668:
0669: JMenuItem closeMenuItem = new JMenuItem(jEdit
0670: .getProperty("view.docking.menu-close"));
0671:
0672: closeMenuItem.addActionListener(new ActionListener() {
0673: public void actionPerformed(ActionEvent evt) {
0674: if (clone)
0675: ((FloatingWindowContainer) container).dispose();
0676: else
0677: removeDockableWindow(dockable);
0678: }
0679: });
0680: popup.add(closeMenuItem);
0681:
0682: if (!(clone || currentPos.equals(FLOATING))) {
0683: JMenuItem undockMenuItem = new JMenuItem(jEdit
0684: .getProperty("view.docking.menu-undock"));
0685:
0686: undockMenuItem.addActionListener(new ActionListener() {
0687: public void actionPerformed(ActionEvent evt) {
0688: jEdit.setProperty(dockable + ".dock-position",
0689: FLOATING);
0690: EditBus
0691: .send(new DockableWindowUpdate(
0692: DockableWindowManager.this ,
0693: DockableWindowUpdate.PROPERTIES_CHANGED,
0694: null));
0695: // Reset the window, propertiesChanged() doesn't
0696: // reset it for MOVABLE windows.
0697: Entry entry = windows.get(dockable);
0698: if (entry == null)
0699: Log.log(Log.ERROR, this ,
0700: "Unknown dockable window: "
0701: + dockable);
0702: else
0703: entry.win = null;
0704: }
0705: });
0706: popup.add(undockMenuItem);
0707: }
0708: }
0709:
0710: return popup;
0711: } //}}}
0712:
0713: //{{{ paintChildren() method
0714: public void paintChildren(Graphics g) {
0715: super .paintChildren(g);
0716:
0717: if (resizeRect != null) {
0718: g.setColor(Color.darkGray);
0719: g.fillRect(resizeRect.x, resizeRect.y, resizeRect.width,
0720: resizeRect.height);
0721: }
0722: } //}}}
0723:
0724: //{{{ handleMessage() method
0725: public void handleMessage(EBMessage msg) {
0726: if (msg instanceof DockableWindowUpdate) {
0727: if (((DockableWindowUpdate) msg).getWhat() == DockableWindowUpdate.PROPERTIES_CHANGED)
0728: propertiesChanged();
0729: } else if (msg instanceof PropertiesChanged)
0730: propertiesChanged();
0731: else if (msg instanceof PluginUpdate) {
0732: PluginUpdate pmsg = (PluginUpdate) msg;
0733: if (pmsg.getWhat() == PluginUpdate.LOADED) {
0734: Iterator<DockableWindowFactory.Window> iter = factory
0735: .getDockableWindowIterator();
0736:
0737: while (iter.hasNext()) {
0738: DockableWindowFactory.Window w = iter.next();
0739: if (w.plugin == pmsg.getPluginJAR())
0740: addEntry(w);
0741: }
0742:
0743: propertiesChanged();
0744: } else if (pmsg.isExiting()) {
0745: // we don't care
0746: } else if (pmsg.getWhat() == PluginUpdate.DEACTIVATED) {
0747: Iterator<Entry> iter = getAllPluginEntries(pmsg
0748: .getPluginJAR(), false);
0749: while (iter.hasNext()) {
0750: Entry entry = iter.next();
0751: if (entry.container != null)
0752: entry.container.remove(entry);
0753: }
0754: } else if (pmsg.getWhat() == PluginUpdate.UNLOADED) {
0755: Iterator<Entry> iter = getAllPluginEntries(pmsg
0756: .getPluginJAR(), true);
0757: while (iter.hasNext()) {
0758: Entry entry = iter.next();
0759: if (entry.container != null) {
0760: entry.container.unregister(entry);
0761: entry.win = null;
0762: entry.container = null;
0763: }
0764: }
0765: }
0766: }
0767: } //}}}
0768:
0769: //{{{ Package-private members
0770: int resizePos;
0771: /**
0772: * This is the rectangle you drag to resize the split.
0773: * It is used with non continuous layout.
0774: */
0775: Rectangle resizeRect;
0776:
0777: //{{{ setResizePos() method
0778: void setResizePos(int resizePos, PanelWindowContainer resizing) {
0779: this .resizePos = resizePos;
0780:
0781: if (resizePos < 0)
0782: resizePos = 0;
0783:
0784: if (continuousLayout)
0785: return;
0786:
0787: Rectangle newResizeRect = new Rectangle(0, 0,
0788: PanelWindowContainer.SPLITTER_WIDTH - 2,
0789: PanelWindowContainer.SPLITTER_WIDTH - 2);
0790: if (resizing == top) {
0791: resizePos = Math.min(resizePos, getHeight()
0792: - top.buttonPanel.getHeight()
0793: - bottom.dockablePanel.getHeight()
0794: - bottom.buttonPanel.getHeight()
0795: - PanelWindowContainer.SPLITTER_WIDTH);
0796: newResizeRect.x = top.dockablePanel.getX() + 1;
0797: newResizeRect.y = resizePos + top.buttonPanel.getHeight()
0798: + 1;
0799: newResizeRect.width = top.dockablePanel.getWidth() - 2;
0800: } else if (resizing == left) {
0801: resizePos = Math.min(resizePos, getWidth()
0802: - left.buttonPanel.getWidth()
0803: - right.dockablePanel.getWidth()
0804: - right.buttonPanel.getWidth()
0805: - PanelWindowContainer.SPLITTER_WIDTH);
0806: newResizeRect.x = resizePos + left.buttonPanel.getWidth()
0807: + 1;
0808: newResizeRect.y = left.dockablePanel.getY() + 1;
0809: newResizeRect.height = left.dockablePanel.getHeight() - 2;
0810: } else if (resizing == bottom) {
0811: resizePos = Math.min(resizePos, getHeight()
0812: - bottom.buttonPanel.getHeight()
0813: - top.dockablePanel.getHeight()
0814: - top.buttonPanel.getHeight()
0815: - PanelWindowContainer.SPLITTER_WIDTH);
0816: newResizeRect.x = bottom.dockablePanel.getX() + 1;
0817: newResizeRect.y = getHeight()
0818: - bottom.buttonPanel.getHeight() - resizePos
0819: - PanelWindowContainer.SPLITTER_WIDTH + 2;
0820: newResizeRect.width = bottom.dockablePanel.getWidth() - 2;
0821: } else if (resizing == right) {
0822: resizePos = Math.min(resizePos, getWidth()
0823: - right.buttonPanel.getWidth()
0824: - left.dockablePanel.getWidth()
0825: - left.buttonPanel.getWidth()
0826: - PanelWindowContainer.SPLITTER_WIDTH);
0827: newResizeRect.x = getWidth() - right.buttonPanel.getWidth()
0828: - resizePos - PanelWindowContainer.SPLITTER_WIDTH
0829: + 1;
0830: newResizeRect.y = right.dockablePanel.getY() + 1;
0831: newResizeRect.height = right.dockablePanel.getHeight() - 2;
0832: }
0833:
0834: Rectangle toRepaint;
0835: if (resizeRect == null)
0836: toRepaint = newResizeRect;
0837: else
0838: toRepaint = resizeRect.union(newResizeRect);
0839: resizeRect = newResizeRect;
0840: repaint(toRepaint);
0841: } //}}}
0842:
0843: //{{{ finishResizing() method
0844: void finishResizing() {
0845: resizeRect = null;
0846: repaint();
0847: } //}}}
0848:
0849: //}}}
0850:
0851: //{{{ propertiesChanged() method
0852: private void propertiesChanged() {
0853: if (view.isPlainView())
0854: return;
0855:
0856: ((DockableLayout) getLayout()).setAlternateLayout(jEdit
0857: .getBooleanProperty("view.docking.alternateLayout"));
0858:
0859: String[] windowList = factory.getRegisteredDockableWindows();
0860:
0861: for (int i = 0; i < windowList.length; i++) {
0862: String dockable = windowList[i];
0863: Entry entry = windows.get(dockable);
0864:
0865: String newPosition = jEdit.getProperty(dockable
0866: + ".dock-position", FLOATING);
0867: if (newPosition.equals(entry.position)) {
0868: continue;
0869: }
0870:
0871: entry.position = newPosition;
0872: if (entry.container != null) {
0873: entry.container.unregister(entry);
0874: entry.container = null;
0875: if (entry.factory.movable) {
0876: if (entry.win instanceof DockableWindow)
0877: ((DockableWindow) entry.win).move(newPosition);
0878: } else
0879: entry.win = null;
0880: }
0881:
0882: if (newPosition.equals(FLOATING)) {
0883: }
0884:
0885: else {
0886: if (newPosition.equals(TOP))
0887: entry.container = top;
0888: else if (newPosition.equals(LEFT))
0889: entry.container = left;
0890: else if (newPosition.equals(BOTTOM))
0891: entry.container = bottom;
0892: else if (newPosition.equals(RIGHT))
0893: entry.container = right;
0894: else {
0895: Log.log(Log.WARNING, this , "Unknown position: "
0896: + newPosition);
0897: continue;
0898: }
0899:
0900: entry.container.register(entry);
0901: }
0902: }
0903:
0904: top.sortDockables();
0905: left.sortDockables();
0906: bottom.sortDockables();
0907: right.sortDockables();
0908:
0909: continuousLayout = jEdit
0910: .getBooleanProperty("appearance.continuousLayout");
0911: revalidate();
0912: repaint();
0913: } //}}}
0914:
0915: //{{{ addEntry() method
0916: private void addEntry(DockableWindowFactory.Window factory) {
0917: Entry e;
0918: if (view.isPlainView()) {
0919: // don't show menu items to dock into a plain view
0920: e = new Entry(factory, FLOATING);
0921: } else {
0922: e = new Entry(factory);
0923: if (e.position.equals(FLOATING))
0924: /* nothing to do */;
0925: else if (e.position.equals(TOP))
0926: e.container = top;
0927: else if (e.position.equals(LEFT))
0928: e.container = left;
0929: else if (e.position.equals(BOTTOM))
0930: e.container = bottom;
0931: else if (e.position.equals(RIGHT))
0932: e.container = right;
0933: else {
0934: Log.log(Log.WARNING, this , "Unknown position: "
0935: + e.position);
0936: }
0937:
0938: if (e.container != null)
0939: e.container.register(e);
0940: }
0941: windows.put(factory.name, e);
0942: } //}}}
0943:
0944: //{{{ getAllPluginEntries() method
0945: /**
0946: * If remove is false, only remove from clones list, otherwise remove
0947: * from both entries and clones.
0948: */
0949: private Iterator<Entry> getAllPluginEntries(PluginJAR plugin,
0950: boolean remove) {
0951: List<Entry> returnValue = new LinkedList<Entry>();
0952: Iterator<Entry> iter = windows.values().iterator();
0953: while (iter.hasNext()) {
0954: Entry entry = iter.next();
0955: if (entry.factory.plugin == plugin) {
0956: returnValue.add(entry);
0957: if (remove)
0958: iter.remove();
0959: }
0960: }
0961:
0962: iter = clones.iterator();
0963: while (iter.hasNext()) {
0964: Entry entry = iter.next();
0965: if (entry.factory.plugin == plugin) {
0966: returnValue.add(entry);
0967: iter.remove();
0968: }
0969: }
0970:
0971: return returnValue.iterator();
0972: } //}}}
0973:
0974: private boolean continuousLayout;
0975:
0976: //{{{ Entry class
0977: class Entry {
0978: DockableWindowFactory.Window factory;
0979:
0980: // String title;
0981: String position;
0982: DockableWindowContainer container;
0983:
0984: // only set if open
0985: JComponent win;
0986:
0987: // only for docked
0988: AbstractButton btn;
0989:
0990: //{{{ Entry constructor
0991: Entry(DockableWindowFactory.Window factory) {
0992: this (factory, jEdit.getProperty(factory.name
0993: + ".dock-position", FLOATING));
0994: } //}}}
0995:
0996: /**
0997: * @return the long title for the dockable floating window.
0998: */
0999: public String longTitle() {
1000: String title = jEdit.getProperty(factory.name
1001: + ".longtitle");
1002: if (title == null)
1003: return shortTitle();
1004: else
1005: return title;
1006:
1007: }
1008:
1009: /**
1010: * @return The short title, for the dockable button text
1011: */
1012: public String shortTitle() {
1013:
1014: String title = jEdit.getProperty(factory.name + ".title");
1015: if (title == null)
1016: return "NO TITLE PROPERTY: " + factory.name;
1017: else
1018: return title;
1019: }
1020:
1021: /**
1022: * @return A label appropriate for the title on the dock buttons.
1023: */
1024: public String label() {
1025: String retval = jEdit.getProperty(factory.name + ".label");
1026: retval = retval.replaceAll("\\$", "");
1027: return retval;
1028: }
1029:
1030: //{{{ Entry constructor
1031: Entry(DockableWindowFactory.Window factory, String position) {
1032: this .factory = factory;
1033: this .position = position;
1034:
1035: // get the title here, not in the factory constructor,
1036: // since the factory might be created before a plugin's
1037: // props are loaded
1038:
1039: } //}}}
1040: } //}}}
1041:
1042: /**
1043: * This keyhandler responds to only two key events - those corresponding to
1044: * the close-docking-area action event.
1045: *
1046: * @author ezust
1047: *
1048: */
1049: class KeyHandler extends KeyAdapter {
1050: static final String action = "close-docking-area";
1051: Key b1, b2;
1052: String name;
1053:
1054: public KeyHandler(String dockableName) {
1055: String shortcut1 = jEdit.getProperty(action + ".shortcut");
1056: String shortcut2 = jEdit.getProperty(action + ".shortcut2");
1057: if (shortcut1 != null)
1058: b1 = KeyEventTranslator.parseKey(shortcut1);
1059: if (shortcut2 != null)
1060: b2 = KeyEventTranslator.parseKey(shortcut2);
1061: name = dockableName;
1062: }
1063:
1064: public void keyTyped(KeyEvent e) {
1065: char cc = e.getKeyChar();
1066: if ((b1 != null && cc == b1.key)
1067: || (b2 != null && cc == b2.key))
1068: hideDockableWindow(name);
1069: }
1070:
1071: }
1072:
1073: }
|