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