f61077ac40349d49e8d070ec45951dc33a4595af
[jalview.git] / EmbmenuFrame.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.appletgui;
22
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.FlowLayout;
26 import java.awt.Font;
27 import java.awt.Frame;
28 import java.awt.HeadlessException;
29 import java.awt.Label;
30 import java.awt.Menu;
31 import java.awt.MenuBar;
32 import java.awt.Panel;
33 import java.awt.PopupMenu;
34 import java.awt.event.MouseEvent;
35 import java.awt.event.MouseListener;
36 import java.util.HashMap;
37 import java.util.Map;
38
39 /**
40  * This class implements a pattern for embedding toolbars as a panel with popups
41  * for situations where the system menu bar is either invisible or
42  * inappropriate. It was derived from the code for embedding the jalview applet
43  * alignFrame as a component on the web-page, which requires the local
44  * alignFrame menu to be attached to that panel rather than placed on the parent
45  * (which isn't allowed anyhow). TODO: try to modify the embeddedMenu display so
46  * it looks like a real toolbar menu TODO: modify click/mouse handler for
47  * embeddedMenu so it behaves more like a real pulldown menu toolbar
48  * 
49  * @author Jim Procter and Andrew Waterhouse
50  * 
51  */
52 public class EmbmenuFrame extends Frame implements MouseListener
53 {
54   protected static final Font FONT_ARIAL_PLAIN_11 = new Font("Arial",
55           Font.PLAIN, 11);
56
57   public static final Font DEFAULT_MENU_FONT = FONT_ARIAL_PLAIN_11;
58
59   /**
60    * map from labels to popup menus for the embedded menubar
61    */
62   protected Map<Label, PopupMenu> embeddedPopup = new HashMap<Label, PopupMenu>();
63
64   /**
65    * the embedded menu is built on this and should be added to the frame at the
66    * appropriate position.
67    * 
68    */
69   protected Panel embeddedMenu;
70
71   public EmbmenuFrame() throws HeadlessException
72   {
73     super();
74   }
75
76   public EmbmenuFrame(String title) throws HeadlessException
77   {
78     super(title);
79   }
80
81   /**
82    * Check if the applet is running on a platform that requires the Frame
83    * menuBar to be embedded, and if so, embeds it.
84    * 
85    * @param tobeAdjusted
86    *          the panel that is to be reduced to make space for the embedded
87    *          menu bar
88    * @return true if menuBar was embedded and tobeAdjusted's height modified
89    */
90   protected boolean embedMenuIfNeeded(Panel tobeAdjusted)
91   {
92     MenuBar topMenuBar = getMenuBar();
93     if (topMenuBar == null)
94     {
95       return false;
96     }
97     // DEBUG Hint: can test embedded menus by inserting true here.
98     if (new jalview.util.Platform().isAMac())
99     {
100       // Build the embedded menu panel, allowing override with system font
101       embeddedMenu = makeEmbeddedPopupMenu(topMenuBar, true, false);
102       setMenuBar(null);
103       // add the components to the Panel area.
104       add(embeddedMenu, BorderLayout.NORTH);
105       tobeAdjusted.setSize(getSize().width,
106               getSize().height - embeddedMenu.getHeight());
107       return true;
108     }
109     return false;
110   }
111
112   /**
113    * Create or add elements to the embedded menu from menuBar. This removes all
114    * menu from menuBar and it is up to the caller to remove the now useless
115    * menuBar from the Frame if it is already attached.
116    * 
117    * @param menuBar
118    * @param overrideFonts
119    * @param append
120    *          true means existing menu will be emptied before adding new
121    *          elements
122    * @return
123    */
124   protected Panel makeEmbeddedPopupMenu(MenuBar menuBar,
125           boolean overrideFonts, boolean append)
126   {
127     if (!append)
128     {
129       embeddedPopup.clear(); // TODO: check if j1.1
130       if (embeddedMenu != null)
131       {
132         embeddedMenu.removeAll();
133       }
134     }
135     embeddedMenu = makeEmbeddedPopupMenu(menuBar, DEFAULT_MENU_FONT,
136             overrideFonts, new Panel(), this);
137     return embeddedMenu;
138   }
139
140   /**
141    * Generic method to move elements from menubar onto embeddedMenu using the
142    * existing or the supplied font, and adds binding from panel to attached
143    * menus in embeddedPopup This removes all menu from menuBar and it is up to
144    * the caller to remove the now useless menuBar from the Frame if it is
145    * already attached.
146    * 
147    * @param menuBar
148    *          must be non-null
149    * @param font
150    * @param overrideFonts
151    * @param embeddedMenu
152    *          if null, a new panel will be created and returned
153    * @param clickHandler
154    *          - usually the instance of EmbmenuFrame that holds references to
155    *          embeddedPopup and embeddedMenu
156    * @return the panel instance for convenience.
157    */
158   protected Panel makeEmbeddedPopupMenu(MenuBar menuBar, Font font,
159           boolean overrideFonts, Panel embeddedMenu,
160           MouseListener clickHandler)
161   {
162     if (overrideFonts)
163     {
164       Font mbf = menuBar.getFont();
165       if (mbf != null)
166       {
167         font = mbf;
168       }
169     }
170     if (embeddedMenu == null)
171     {
172       embeddedMenu = new Panel();
173     }
174     FlowLayout flowLayout1 = new FlowLayout();
175     embeddedMenu.setBackground(Color.lightGray);
176     embeddedMenu.setLayout(flowLayout1);
177     // loop thru
178     for (int mbi = 0, nMbi = menuBar.getMenuCount(); mbi < nMbi; mbi++)
179     {
180       Menu mi = menuBar.getMenu(mbi);
181       Label elab = new Label(mi.getLabel());
182       elab.setFont(font);
183       // add the menu entries
184       PopupMenu popup = new PopupMenu();
185       int m, mSize = mi.getItemCount();
186       for (m = 0; m < mSize; m++)
187       {
188         popup.add(mi.getItem(m));
189         mSize--;
190         m--;
191       }
192       embeddedPopup.put(elab, popup);
193       embeddedMenu.add(elab);
194       elab.addMouseListener(clickHandler);
195     }
196     flowLayout1.setAlignment(FlowLayout.LEFT);
197     flowLayout1.setHgap(2);
198     flowLayout1.setVgap(0);
199     return embeddedMenu;
200   }
201
202   public void mousePressed(MouseEvent evt)
203   {
204     PopupMenu popup = null;
205     Label source = (Label) evt.getSource();
206     popup = getPopupMenu(source);
207     if (popup != null)
208     {
209       embeddedMenu.add(popup);
210       popup.show(embeddedMenu, source.getBounds().x, source.getBounds().y
211               + source.getBounds().getSize().height);
212     }
213   }
214
215   /**
216    * get the menu for source from the hash.
217    * 
218    * @param source
219    *          what was clicked on.
220    */
221   PopupMenu getPopupMenu(Label source)
222   {
223     return embeddedPopup.get(source);
224   }
225
226   public void mouseClicked(MouseEvent evt)
227   {
228   }
229
230   public void mouseReleased(MouseEvent evt)
231   {
232   }
233
234   public void mouseEntered(MouseEvent evt)
235   {
236   }
237
238   public void mouseExited(MouseEvent evt)
239   {
240   }
241
242   /**
243    * called to clear the GUI resources taken up for embedding and remove any
244    * self references so we can be garbage collected.
245    */
246   public void destroyMenus()
247   {
248     if (embeddedPopup != null)
249     {
250       for (Label lb : embeddedPopup.keySet())
251       {
252         lb.removeMouseListener(this);
253       }
254       embeddedPopup.clear();
255     }
256     if (embeddedMenu != null)
257     {
258       embeddedMenu.removeAll();
259     }
260   }
261
262   /**
263    * calls destroyMenus()
264    */
265   public void finalize() throws Throwable
266   {
267     destroyMenus();
268     embeddedPopup = null;
269     embeddedMenu = null;
270     super.finalize();
271   }
272 }