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