001: /*=============================================================================
002: * Copyright Texas Instruments 2002. All Rights Reserved.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package ti.chimera;
020:
021: import ti.chimera.registry.*;
022: import ti.exceptions.*;
023:
024: import java.util.*;
025: import javax.swing.*;
026: import java.awt.event.*;
027: import java.awt.Component;
028: import java.beans.*;
029:
030: /**
031: * A registry-backed menu bar. Given a root path into the registry, this will
032: * construct a menubar that stays in sync with the tree of action lists in the
033: * tree, ie. a directory node will become a <code>JMenu</code> and a leaf node
034: * will become a <code>JMenuItem</code> which, when selected, will execute the
035: * list of <code>Action</code>s contained by that node.
036: *
037: * @author ;Rob Clark;a0873619;San Diego;;
038: * @version 0.1
039: */
040: public class MenuBar extends JMenuBar {
041: private MenuNodeSubscriber ns;
042: private Main main;
043: private Registry registry;
044: private String path;
045:
046: /**
047: * Class Constructor.
048: */
049: public MenuBar(Main main, String path) {
050: super ();
051:
052: this .path = path;
053: this .main = main;
054: this .registry = main.getRegistry();
055:
056: synchronized (registry) {
057: if (!registry.exists("/MenuBar"))
058: registry.mkdir("/MenuBar");
059: }
060:
061: setVisible(true);
062: }
063:
064: /**
065: */
066: public void setVisible(boolean visible) {
067: try {
068: if (visible) {
069: ns = new MenuNodeSubscriber(path, this );
070: } else if (ns != null) {
071: ns.dispose();
072: ns = null;
073: }
074: } catch (RegistryException e) {
075: throw new ProgrammingErrorException(e);
076: }
077:
078: super .setVisible(visible);
079: }
080:
081: /**
082: * Because not yet implemented in JMenuBar...
083: */
084: private JMenu helpMenu;
085:
086: public void setHelpMenu(JMenu menu) {
087: if (helpMenu != null)
088: remove(helpMenu);
089: helpMenu = menu;
090: addImpl(helpMenu, null, -1);
091: }
092:
093: public JMenu getHelpMenu() {
094: return helpMenu;
095: }
096:
097: public Component add(final Component c) {
098: if (helpMenu != null)
099: remove(helpMenu);
100:
101: addImpl(c, null, -1);
102:
103: if (helpMenu != null) {
104: try {
105: addImpl(helpMenu = rebuildMenu(helpMenu), null, -1);
106: } catch (ArrayIndexOutOfBoundsException e) {
107: // XXX work-around for an aqua l&f (as of v1.3.1) bug
108: }
109: }
110:
111: return c;
112: }
113:
114: // this is a work-around for an aqua l&f bug when using system menubar:
115: private JMenu rebuildMenu(JMenu menu) {
116: Component[] menuItems = menu.getMenuComponents();
117: JMenu newMenu = new JMenu(menu.getText());
118: if (ns != null)
119: ns.replace(menu, newMenu);
120: for (int i = 0; i < menuItems.length; i++)
121: newMenu.add(menuItems[i]);
122: return newMenu;
123: }
124:
125: // protected void addImpl( final Component comp, final Object constraints, final int index )
126: // {
127: // SwingUtilities.invokeLater( new Runnable() {
128: // public void run() { addImplImpl( comp, constraints, index ); }
129: // } );
130: // }
131: // protected void addImplImpl( Component comp, Object constraints, int index )
132: // {
133: // super.addImpl( comp, constraints, index );
134: // }
135:
136: /**
137: * A menu node subscriber which adds/removes a JMenu or JMenuItem, to add to
138: * it's parent component (JMenuBar or JMenu).
139: */
140: private class MenuNodeSubscriber implements NodeSubscriber {
141: private String path;
142: private JComponent parent;
143: private JComponent component;
144:
145: private DirectoryTable lastDt; // for JMenu nodes, the last contents of directory
146: private LinkedList actionList; // for JMenuItem nodes, the list of actions to invoke
147:
148: private Hashtable childTable = new Hashtable(); // maps child name to subscriber
149:
150: private PropertyChangeListener actionPropertyChangeListener; // watches for action being enabled/disabled for JMenuItems
151:
152: private Node subscribedNode;
153:
154: /**
155: * Constructor used to construct subscriber for the root of the menu-bar
156: * part of the registry tree. The root has no parent (well it does, but
157: * it's parent is whatever the MenuBar gets added to, so from our point
158: * of view it has no parent).
159: */
160: MenuNodeSubscriber(String path, JComponent component)
161: throws RegistryException {
162: this (path, null, component);
163: }
164:
165: /**
166: * Class constructor for a subscriber corresponding to a child node... in
167: * the case of child nodes, we know the parent but the component itself is
168: * lazily constructed the first time we get a publish(), because that is
169: * the first time we know whether it is a directory node or not.
170: */
171: private MenuNodeSubscriber(String path, JComponent parent,
172: JComponent component) throws RegistryException {
173: this .path = path;
174: this .parent = parent;
175: this .component = component;
176:
177: // note: use subscribe() instead of registry.subscribeToValue() because
178: // otherwise the subscriber could attach to a new node in the registry
179: // with the same path, resulting in a race condition that sometimes
180: // causes duplicate menu entries. See ticket #296
181: subscribedNode = registry.resolve(path);
182: subscribedNode.subscribe(this );
183: }
184:
185: /**
186: * Called by parent (who created us to begin with) when the node we correspond
187: * to is unlinked.
188: */
189: synchronized void dispose() {
190: subscribedNode.unsubscribe(this );
191:
192: if ((component != null) && (parent != null)) {
193: parent.remove(component);
194: component = null;
195: }
196:
197: for (Iterator itr = childTable.values().iterator(); itr
198: .hasNext();)
199: ((MenuNodeSubscriber) (itr.next())).dispose();
200: }
201:
202: // part of the aqua l&f bug work-around:
203: void replace(JComponent oldMenu, JComponent newMenu) {
204: if (parent == oldMenu)
205: parent = newMenu;
206: else if (component == oldMenu)
207: component = newMenu;
208:
209: for (Iterator itr = childTable.values().iterator(); itr
210: .hasNext();)
211: ((MenuNodeSubscriber) (itr.next())).replace(oldMenu,
212: newMenu);
213: }
214:
215: /**
216: * Called by registry to publish a (new) value for the node we are subscribed to.
217: */
218: public void publish(final Node node, final Object value) {
219: SwingUtilities.invokeLater(new Runnable() {
220:
221: public void run() {
222: try {
223: publishImpl(node, value);
224: } catch (Exception e) {
225: throw new ProgrammingErrorException(e);
226: }
227: }
228:
229: });
230: }
231:
232: private void publishImpl(Node node, Object value)
233: throws RegistryException {
234: if (value instanceof DirectoryTable) {
235: if (component == null) {
236: String name = registry.basename(path);
237: component = new JMenu(name);
238:
239: if ((parent instanceof MenuBar)
240: && name.equalsIgnoreCase("help"))
241: ((MenuBar) parent)
242: .setHelpMenu((JMenu) component);
243: else
244: parent.add(component);
245: }
246:
247: DirectoryTable newDt = (DirectoryTable) value;
248:
249: for (Iterator added = newDt.notIn(lastDt); added
250: .hasNext();) {
251: String name = (String) (added.next());
252: String childPath = path + "/" + name;
253: // don't bother creating the child if it has already been removed:
254: if (registry.exists(childPath))
255: childTable.put(name, new MenuNodeSubscriber(
256: childPath, component, null));
257: }
258:
259: if (lastDt != null) {
260: for (Iterator removed = lastDt.notIn(newDt); removed
261: .hasNext();) {
262: String name = (String) (removed.next());
263: if (childTable.containsKey(name))
264: ((MenuNodeSubscriber) (childTable
265: .remove(name))).dispose();
266: }
267: }
268:
269: lastDt = newDt;
270: } else {
271: if (component == null) {
272: String name = registry.basename(path);
273:
274: if (name.indexOf('.') == 0) {
275: parent.add(component = new JSeparator());
276: } else {
277: parent.add(component = new JMenuItem(name));
278:
279: ((JMenuItem) component)
280: .addActionListener(new AbstractAction() {
281:
282: public void actionPerformed(
283: ActionEvent evt) {
284: // copy to array to avoid concurrent modification problems:
285: ActionListener[] as = (ActionListener[]) (actionList
286: .toArray(new ActionListener[actionList
287: .size()]));
288: for (int i = 0; i < as.length; i++)
289: if (!((as[i] instanceof Action) && !((Action) (as[i]))
290: .isEnabled()))
291: as[i]
292: .actionPerformed(evt);
293: }
294:
295: });
296:
297: actionPropertyChangeListener = new PropertyChangeListener() {
298:
299: public void propertyChange(
300: PropertyChangeEvent evt) {
301: refresh();
302: }
303:
304: };
305: }
306: }
307:
308: if (actionList != null) {
309: for (Iterator itr = actionList.iterator(); itr
310: .hasNext();) {
311: Object obj = itr.next();
312: if (obj instanceof Action)
313: ((Action) obj)
314: .removePropertyChangeListener(actionPropertyChangeListener);
315: }
316: }
317:
318: actionList = (LinkedList) value;
319:
320: if (actionList != null) {
321: for (Iterator itr = actionList.iterator(); itr
322: .hasNext();) {
323: Object obj = itr.next();
324: if (obj instanceof Action)
325: ((Action) obj)
326: .addPropertyChangeListener(actionPropertyChangeListener);
327: }
328: }
329:
330: refresh();
331: }
332: }
333:
334: private synchronized void refresh() {
335: boolean enabled = false;
336: Icon icon = null;
337: KeyStroke accel = null;
338:
339: if (actionList != null) {
340: for (Iterator itr = actionList.iterator(); !enabled
341: && itr.hasNext();) {
342: Object obj = itr.next();
343: if (obj instanceof Action) {
344: Action a = (Action) obj;
345: Object tmp;
346:
347: enabled = a.isEnabled();
348:
349: tmp = a.getValue(Action.SMALL_ICON);
350: if (tmp != null)
351: icon = (Icon) tmp;
352:
353: tmp = a.getValue(Action.ACCELERATOR_KEY);
354: if (tmp != null)
355: accel = (KeyStroke) tmp;
356: } else {
357: enabled = true;
358: }
359: }
360: }
361:
362: if (component != null) {
363: if (component instanceof AbstractButton)
364: ((AbstractButton) component).setIcon(icon);
365:
366: if (component instanceof JMenuItem)
367: ((JMenuItem) component).setAccelerator(accel);
368:
369: component.setEnabled(enabled);
370: }
371: }
372: }
373: }
374:
375: /*
376: * Local Variables:
377: * tab-width: 2
378: * indent-tabs-mode: nil
379: * mode: java
380: * c-indentation-style: java
381: * c-basic-offset: 2
382: * eval: (c-set-offset 'substatement-open '0)
383: * eval: (c-set-offset 'case-label '+)
384: * eval: (c-set-offset 'inclass '+)
385: * eval: (c-set-offset 'inline-open '0)
386: * End:
387: */
|