1: package com.holub.ui;
2:
3: import java.util.*;
4: import java.io.*;
5: import java.awt.*;
6: import java.awt.event.*;
7: import javax.swing.*;
8:
9: import com.holub.tools.debug.Assert;
10:
11: /** A Menu_site is a frame that holds a menu bar. Other objects in
12: * the system (which do not have to be visual objects) can negotiate
13: * with the Menu_site to have menu's placed on the site's menu
14: * bar (or within submenus already found on the menu bar).
15: */
16:
17: // Possible extensions:
18: // * Stack existing menus and menu items if you add one with the same
19: // name. Restore old item from the stack when menu reverts to previous
20: // state.
21:
22: public interface Menu_site
23: {
24: /*****************************************************************
25: * Adds a menu to the menu_site's main menu bar.
26: * @param requester The object that creates the menu. This
27: * does not have to be a visual object.
28: * @param menu The menu to be added
29: */
30:
31: void add_menu(Object requester, JMenu menu );
32:
33: /*****************************************************************
34: * Adds a line item to a menu already on the menu_site's main
35: * menu bar.
36: *
37: * @param requester The object that creates the menu. This
38: * does not have to be a visual object.
39: * @param item The line-item to be added. This could
40: * be a single JMenuItem or an entire JMenu.
41: * @param to_this_menu The label that identifies the menu to
42: * which the item is to be added. The
43: * to_this_menu string is first compared
44: * against all the (invisible) menu names
45: * (set with JMenuItem.setName())
46: * If no match is found, then the string
47: * is compared to the (visible) labels
48: * (usually passed into the JMenu or JMenuItem
49: * constructor). Note that all the menu names
50: * are examined before any of the labels are
51: * examined (by design).
52: * <p>
53: * The "Help" menu (identified by a label or
54: * name having the value "Help"). No special
55: * methods are required to manipulate it.
56: *
57: * @throws IllegalArgumentException (a RuntimeException) if the
58: * menu specified in <code>to_this_menu</code>
59: * can't be found.
60: */
61:
62: void add_line_item( Object requester, JMenuItem item,
63: String to_this_menu);
64:
65: /*****************************************************************
66: * Removes all JMenus and JMenuItem's that were added by this
67: * requester. Note that the AWT JMenuBar is, itself, destroyed
68: * when it has no menus on it.
69: */
70:
71: void remove_my_menus( Object requester );
72:
73: /*****************************************************************
74: * This class provides support for implementing a Menu_site.
75: * It has default versions of all menu-site methods, and also
76: * maintains the menu bar itself (which is installed
77: * on the frame, a reference to which is passed to the
78: * Implementation(JFrame) constructor). Use it like this:
79: *
80: * <PRE>
81: * class Main_frame extends JFrame implements Menu_site
82: * {
83: * final Menu_site.Implementation support;
84: *
85: * Main_frame()
86: * { support = new Menu_site.Implementation(this);
87: * //...
88: * }
89: *
90: * public void add_menu(Object requester, JMenu item )
91: * { support.add_menu( requester, item );
92: * }
93: *
94: * public void
95: * add_line_item(Object requester,JMenuItem item,String here)
96: * { support.add_line_item(requester, item, here );
97: * }
98: *
99: * public void remove_my_menus(Object requester)
100: * { support.remove_my_menus( requester );
101: * }
102: * }
103: * </PRE>
104: */
105:
106: public static class Implementation
107: implements Menu_site, Serializable
108: {
109: // The "requesters" table keeps track of who requested which
110: // menu items. It is indexed by requester and contains a
111: // Vector of Implementation.Item objects that identify all
112: // items added by that requester.
113: //
114: // The Implementation also encapsulates the menu bar itself,
115: // which it creates and installs on its menu_frame (passed
116: // into the constructor) when the first item is added.
117:
118: private /*final*/ JFrame menu_frame;
119:
120: private JMenuBar menu_bar = null;
121: private Hashtable requesters = new Hashtable();
122:
123: //------------------------------------------------------------
124: public Implementation( JFrame menu_frame )
125: { this.menu_frame = menu_frame;
126: }
127: //------------------------------------------------------------
128: public void add_menu( Object requester, JMenu menu )
129: {
130: if( menu_bar == null )
131: menu_frame.setJMenuBar( menu_bar = new JMenuBar() );
132:
133: Item item = new Item(menu, menu_bar, is_help_menu(menu));
134:
135: requester_menus( requester ).addElement(item);
136: item.attach_menu_to_container();
137: }
138: //------------------------------------------------------------
139: public void add_line_item( Object requester,
140: JMenuItem line_item, String to_this_menu )
141: {
142: Assert.is_true(requester != null, "null requester" );
143: Assert.is_true(line_item != null, "null line_item" );
144: Assert.is_true(to_this_menu != null, "null to_this_menu");
145:
146: JMenu found = null;
147: int menu_count = menu_bar.getMenuCount();
148:
149: // Find the menu into which the line item should be
150: // inserted. First check for a match of the (invisible)
151: // menu names.
152:
153: int i = 0;
154: for(; i < menu_count; ++i )
155: { found = menu_bar.getMenu(i);
156: if( to_this_menu.equals( found.getName() ) )
157: break;
158: }
159:
160: // If that didn't work, check for a match of
161: // the (visible) menu labels.
162:
163: if( i >= menu_count )
164: for( i = 0 ; i < menu_count; ++i )
165: { found = menu_bar.getMenu(i);
166: if( to_this_menu.equals( found.getText() ) )
167: break;
168: }
169:
170: // If you can't find it either place, throw an exception.
171:
172: if( i >= menu_count )
173: throw new IllegalArgumentException(
174: "add_line_item() can't find menu ("
175: + to_this_menu + ")" );
176:
177: Item item = new Item( line_item, found, false );
178:
179: requester_menus( requester ).addElement( item );
180: item.attach_menu_to_container();
181: }
182: //------------------------------------------------------------
183: public void remove_my_menus( Object requester )
184: {
185: Vector menus = (Vector)( requesters.remove(requester) );
186:
187: if( menus != null )
188: { Enumeration e = menus.elements();
189: while( e.hasMoreElements() )
190: ((Item)(e.nextElement()))
191: .detach_menu_from_container();
192: }
193: }
194:
195: /*************************************************************
196: * Set the default font used by the {@link #line_item} and
197: * {@link #menu} convenience methods. If you never call this
198: * method, 10-point, SanSerif, bold is used.
199: */
200: static public void setFont( Font menu_font )
201: { Implementation.menu_font = menu_font;
202: }
203:
204: static private Font menu_font =
205: new Font("SansSerif", Font.BOLD, 10);
206:
207: /*************************************************************
208: * This is a convenience method that manufactures menu items
209: * with text labels. The font used is a bit more tractable
210: * than the default Java look-and-feel font.
211: */
212: static public JMenuItem line_item(String text,
213: ActionListener action)
214: { JMenuItem item = new JMenuItem( text );
215: item.setFont( menu_font );
216: item.addActionListener( action );
217: return item;
218: }
219: /*************************************************************
220: * This is a convenience method that manufactures JMenu's
221: * that use the same font as the line items manufactured by
222: * {@link #line_item}.
223: */
224: static public JMenu menu(String text)
225: { JMenu item = new JMenu( text );
226: item.setFont( menu_font );
227: return item;
228: }
229:
230: //============================================================
231: // Private support methods and classes
232:
233: /*************************************************************
234: * The menu_bar_contents vector contains references to the
235: * various menus that comprise the menu bar. This kluge is
236: * necessary because Swing does not yet support the notion
237: * of a Help menu, and it won't let you insert menus anywhere
238: * other than the far right of the menu bar, where the Help
239: * menu should be. Consequently, when a new menu is added to
240: * a menu bar, you need to clear out the existing menu bar
241: * and rebuild it from scratch. The menu_bar_has_help
242: * indicates that a help menu has been installed (It's
243: * assumed to be at the far right of the menu bar.)
244: */
245:
246: private final LinkedList menu_bar_contents = new LinkedList();
247:
248: /*************************************************************
249: * An Item makes the association between a line item or
250: * submenu and the MenuBar or Menu that contains it. You can
251: * ask an Item to add or remove itself from its container.
252: * All the weirdness associated with help menus is handled
253: * here.
254: */
255:
256: private final class Item implements Serializable
257: {
258: private final JMenuItem line_item;
259: private Object container;
260: private final boolean is_help_menu;
261:
262: public Item( JMenuItem line_item, Object container,
263: boolean is_help_menu)
264: {
265: Assert.is_true( container instanceof JMenu ||
266: container instanceof JMenuBar );
267:
268: this.line_item = line_item;
269: this.container = container;
270: this.is_help_menu = is_help_menu;
271: }
272:
273: /********************************************************
274: * Attach a menu item to it's container (either a menu
275: * bar or a menu). Items are added at the end of the
276: * <code>menu_bar_contents</code> list unless a help
277: * menu exists, in which case items are added at
278: * the penultimate position.
279: */
280:
281: public final void attach_menu_to_container()
282: {
283: if( container instanceof JMenu )
284: ((JMenu)container).add( line_item );
285: else
286: {
287: if( menu_bar_contents.size() <= 0 )
288: {
289: menu_bar_contents.add( this );
290: ((JMenuBar)container).add( line_item );
291: }
292: else
293: { Item last =
294: (Item)( menu_bar_contents.getLast() );
295: if( !last.is_help_menu )
296: { menu_bar_contents.add(this);
297: ((JMenuBar)container).add( line_item );
298: }
299: else // remove the help menu, add the new
300: { // item, then put the help menu back
301: // (following the new item).
302:
303: menu_bar_contents.removeLast();
304: menu_bar_contents.add( this );
305: menu_bar_contents.add( last );
306: container = regenerate();
307: }
308: }
309: }
310: }
311:
312: /********************************************************
313: * Remove the current menu item from its container
314: * (either a menu bar or a menu).
315: */
316: public final void detach_menu_from_container()
317: {
318: if( container instanceof JMenu )
319: { ((JMenu)container).remove( line_item );
320: }
321: else
322: { ((JMenuBar)container).remove( line_item );
323: menu_bar_contents.remove( this );
324: container = regenerate();
325: }
326: }
327:
328:
329: /********************************************************
330: * Replace the old menu bar with a new one that reflects
331: * the current state of the <code>menu_bar_contents</code>
332: * list.
333: */
334: private JMenuBar regenerate()
335: {
336: // Create the new menu bar and populate it from
337: // the current-contents list.
338:
339: menu_bar = new JMenuBar();
340: ListIterator i = menu_bar_contents.listIterator(0);
341: while( i.hasNext() )
342: menu_bar.add( ((Item)(i.next())).line_item );
343:
344: // Replace the old menu bar with the new one.
345: // Calling setVisible causes the menu bar to be
346: // redrawn with a minimum amount of flicker. Without
347: // it, the redraw doesn't happen at all.
348:
349: menu_frame.setJMenuBar( menu_bar );
350: menu_frame.setVisible( true );
351:
352: return menu_bar;
353: }
354: }
355: /************************************************************
356: * Return a vector of menu items associated with a given
357: * requester. A new (empty) vector is created and returned
358: * if there are no menus associated with the requester at
359: * present.
360: */
361: private Vector requester_menus( Object requester )
362: {
363: Assert.is_true( requester != null, "Bad argument" );
364: Assert.is_true( requesters != null, "No requesters" );
365:
366: Vector menus = (Vector)( requesters.get(requester) );
367: if( menus == null )
368: { menus = new Vector();
369: requesters.put( requester, menus );
370: }
371: return menus;
372: }
373: /************************************************************
374: * Return true if the menu passed as an argument is the
375: * "help" menu. The name "help" is not case sensitive.
376: */
377: private boolean is_help_menu( JMenu menu )
378: {
379: String s = menu.getName();
380: if( s != null && s.toLowerCase().equals("help") )
381: return true;
382:
383: s = menu.getText();
384: return (s != null && s.toLowerCase().equals("help"));
385: }
386: }
387:
388: /***************************************************************
389: * This inner class tests the Menu_site.Implementation. Do not
390: * Ship Menu_site$Test.class with the applications. Test the
391: * code by invoking "java com.holub.tools.Menu_site\$Test".
392: * The test code creates three menus:
393: * o A Help menu that contains three line items, each added in
394: * a different way. Nothing happens when these are selected.
395: * o A "Removal" menu that disappears when selected.
396: * o A "Main" menu that initially contains a single line item
397: * called "Add an Item." Selecting this item adds a "Remove
398: * Menus" Item to the "Main" menu. Selecting that removes
399: * both the Main and Help (but not the "Removal") menus.
400: */
401:
402: static class Test extends JFrame implements Menu_site
403: {
404: static Test instance;
405:
406: Test()
407: { setSize( 400, 200 );
408: addWindowListener
409: ( new WindowAdapter()
410: { public void windowClosing( WindowEvent e )
411: { System.exit(1);
412: }
413: }
414: );
415: show();
416: }
417: //------------------------------------------------------------
418: final Menu_site.Implementation support
419: = new Menu_site.Implementation(this);
420:
421: public void add_menu(Object requester, JMenu item )
422: { support.add_menu( requester, item );
423: }
424:
425: public void
426: add_line_item(Object requester, JMenuItem item,
427: String to_this_menu)
428: { support.add_line_item(requester, item, to_this_menu );
429: }
430:
431: public void remove_my_menus(Object requester)
432: { support.remove_my_menus( requester );
433: }
434:
435: //------------------------------------------------------------
436: static class Remove_listener implements ActionListener
437: { public void actionPerformed( ActionEvent e )
438: { instance.remove_my_menus( instance );
439: }
440: }
441:
442: static public void main( String[] args )
443: {
444: instance = new Test();
445:
446: JMenu main_menu = new JMenu( "Main" );
447: instance.add_menu( instance, main_menu );
448:
449: JMenuItem add_an_item_menu = new JMenuItem(
450: "Add Line Item to Menu" );
451: add_an_item_menu.addActionListener
452: ( new ActionListener()
453: { public void actionPerformed( ActionEvent e )
454: {
455: JMenuItem remove_menus = new JMenuItem(
456: "Remove Main and Help menus");
457:
458: remove_menus.addActionListener
459: ( new ActionListener()
460: { public void actionPerformed(ActionEvent e)
461: { instance.remove_my_menus(instance);
462: }
463: }
464: );
465:
466: // Add line item via menu site
467: instance.add_line_item(instance,
468: remove_menus, "Main");
469: }
470: }
471: );
472:
473: // Add this line-item directly to the "Main" menu (without
474: // going through the menu site) just to make sure that
475: // we can.
476:
477: main_menu.add( add_an_item_menu );
478:
479: // Create a help menu. Do it in the middle of things
480: // to make sure that it ends up on the far right.
481: // Use all three mechanisms for adding menu items directly
482: // using the menu's "name," and using the menu's "text").
483:
484: JMenu help = new JMenu("Help"); // "Help" is the menu text
485: help.setName("HelpMenu"); // "HelpMenu" is the
486: // (invisible) menu name
487:
488: JMenuItem help_by_text= new JMenuItem("Added using text");
489: JMenuItem help_by_name= new JMenuItem("Added using name");
490: JMenuItem help_direct = new JMenuItem("Added directly");
491:
492: ActionListener help_listener =
493: new ActionListener()
494: { public void actionPerformed(ActionEvent e)
495: { System.out.println("No help available"); }
496: };
497:
498: help_by_text.addActionListener( help_listener );
499: help_by_name.addActionListener( help_listener );
500: help_direct.addActionListener ( help_listener );
501:
502: instance.add_menu ( instance, help );
503:
504: help .add (help_direct );
505: instance.add_line_item(instance, help_by_text, "Help" );
506: instance.add_line_item(instance, help_by_name, "HelpMenu");
507:
508: // Create a second "requester" and have it add a Removal
509: // menu with the name Removal_menu. Picking that menu
510: // will remove only the menu for the current requester.
511:
512: final int x[] = new int[1];
513:
514: JMenu remove_menu = new JMenu("Removal");
515: remove_menu.setName( "Removal_menu" );
516: instance.add_menu( x, remove_menu );
517:
518: // Create and add a line item to the Removal_menu by name
519: // (the earlier example used the Label).
520:
521: JMenuItem remove_me = new JMenuItem("Click_to_Remove_Me");
522: remove_me.addActionListener
523: ( new ActionListener()
524: { public void actionPerformed( ActionEvent e )
525: { instance.remove_my_menus(x);
526: }
527: }
528: );
529: instance.add_line_item( x, remove_me, "Removal_menu" );
530: }
531: }
532: }
|