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