JAL-3446 from applet, JAL-3584 tooltip fixes <br> and allows different
[jalview.git] / src / jalview / gui / JvSwingUtils.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.gui;
22
23 import java.awt.BorderLayout;
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Font;
27 import java.awt.GridLayout;
28 import java.awt.Rectangle;
29 import java.awt.event.ActionListener;
30 import java.awt.event.MouseAdapter;
31 import java.awt.event.MouseEvent;
32 import java.util.List;
33 import java.util.Objects;
34
35 import javax.swing.AbstractButton;
36 import javax.swing.BorderFactory;
37 import javax.swing.JButton;
38 import javax.swing.JComboBox;
39 import javax.swing.JComponent;
40 import javax.swing.JLabel;
41 import javax.swing.JMenu;
42 import javax.swing.JMenuItem;
43 import javax.swing.JPanel;
44 import javax.swing.JScrollBar;
45 import javax.swing.SwingConstants;
46 import javax.swing.border.Border;
47 import javax.swing.border.TitledBorder;
48
49 import jalview.util.MessageManager;
50 import jalview.util.Platform;
51
52 /**
53  * useful functions for building Swing GUIs
54  * 
55  * @author JimP
56  * 
57  */
58 public final class JvSwingUtils
59 {
60   static final String HTML_PREFIX = (Platform.isJS() ? 
61           "<html><div style=\"max-width:350px;overflow-wrap:break-word;display:inline-block\">"
62           : "<html><div style=\"width:350; text-align: justify; word-wrap: break-word;\">"
63             );
64
65   /**
66    * wrap a bare html safe string to around 60 characters per line using a CSS
67    * style class specifying word-wrap and break-word
68    * 
69    * @param enclose
70    *          if true, add &lt;html&gt; wrapper tags (currently false for only
71    *          two references -- both in Jws2Discoverer --
72    * @param ttext
73    * 
74    * @return
75    */
76   public static String wrapTooltip(boolean enclose, String ttext)
77   {
78     Objects.requireNonNull(ttext,
79             "Tootip text to format must not be null!");
80     ttext = ttext.trim().replaceAll("<br/>", "<br>");
81
82     boolean maxLengthExceeded = false;
83     boolean isHTML = ttext.startsWith("<html>");
84     if (isHTML)
85     {
86       ttext = ttext.substring(6);
87     }
88     if (ttext.endsWith("</html>"))
89     {
90       isHTML = true;
91       ttext = ttext.substring(0, ttext.length() - 7);
92     }
93     boolean hasBR = ttext.contains("<br>");
94     enclose |= isHTML || hasBR;
95     if (hasBR)
96     {  
97       int pt = -1, ptlast = -4;
98       while ((pt = ttext.indexOf("<br>", pt + 1)) >= 0) {
99         if (pt - ptlast - 4 > 60) {
100           maxLengthExceeded = true;
101           break;
102         }
103       }
104     }
105     else  
106     {
107       maxLengthExceeded = ttext.length() > 60;
108     }
109
110     String ret = (!enclose ? ttext : maxLengthExceeded ? HTML_PREFIX + ttext + "</div></html>" :
111       "<html>" + ttext + "</html>");
112     //System.out.println("JvSwUtil " + enclose + " " + maxLengthExceeded + " " + ret);
113     return ret;
114   }
115
116   public static JButton makeButton(String label, String tooltip,
117           ActionListener action)
118   {
119     JButton button = new JButton();
120     button.setText(label);
121     // TODO: get the base font metrics for the Jalview gui from somewhere
122     button.setFont(new java.awt.Font("Verdana", Font.PLAIN, 10));
123     button.setForeground(Color.black);
124     button.setHorizontalAlignment(SwingConstants.CENTER);
125     button.setToolTipText(tooltip);
126     button.addActionListener(action);
127     return button;
128   }
129
130   /**
131    * find or add a submenu with the given title in the given menu
132    * 
133    * @param menu
134    * @param submenu
135    * @return the new or existing submenu
136    */
137   public static JMenu findOrCreateMenu(JMenu menu, String submenu)
138   {
139     JMenu submenuinstance = null;
140     for (int i = 0, iSize = menu.getMenuComponentCount(); i < iSize; i++)
141     {
142       if (menu.getMenuComponent(i) instanceof JMenu
143               && ((JMenu) menu.getMenuComponent(i)).getText()
144                       .equals(submenu))
145       {
146         submenuinstance = (JMenu) menu.getMenuComponent(i);
147       }
148     }
149     if (submenuinstance == null)
150     {
151       submenuinstance = new JMenu(submenu);
152       menu.add(submenuinstance);
153     }
154     return submenuinstance;
155
156   }
157
158   /**
159    * 
160    * @param panel
161    * @param tooltip
162    * @param label
163    * @param valBox
164    * @return the GUI element created that was added to the layout so it's
165    *         attributes can be changed.
166    */
167   public static JPanel addtoLayout(JPanel panel, String tooltip,
168           JComponent label, JComponent valBox)
169   {
170     JPanel laypanel = new JPanel(new GridLayout(1, 2));
171     JPanel labPanel = new JPanel(new BorderLayout());
172     JPanel valPanel = new JPanel();
173     labPanel.setBounds(new Rectangle(7, 7, 158, 23));
174     valPanel.setBounds(new Rectangle(172, 7, 270, 23));
175     labPanel.add(label, BorderLayout.WEST);
176     valPanel.add(valBox);
177     laypanel.add(labPanel);
178     laypanel.add(valPanel);
179     valPanel.setToolTipText(tooltip);
180     labPanel.setToolTipText(tooltip);
181     valBox.setToolTipText(tooltip);
182     panel.add(laypanel);
183     panel.validate();
184     return laypanel;
185   }
186
187   public static void mgAddtoLayout(JPanel cpanel, String tooltip,
188           JLabel jLabel, JComponent name)
189   {
190     mgAddtoLayout(cpanel, tooltip, jLabel, name, null);
191   }
192
193   public static void mgAddtoLayout(JPanel cpanel, String tooltip,
194           JLabel jLabel, JComponent name, String params)
195   {
196     cpanel.add(jLabel);
197     if (params == null)
198     {
199       cpanel.add(name);
200     }
201     else
202     {
203       cpanel.add(name, params);
204     }
205     name.setToolTipText(tooltip);
206     jLabel.setToolTipText(tooltip);
207   }
208
209   /**
210    * standard font for labels and check boxes in dialog boxes
211    * 
212    * @return
213    */
214
215   public static Font getLabelFont()
216   {
217     return getLabelFont(false, false);
218   }
219
220   public static Font getLabelFont(boolean bold, boolean italic)
221   {
222     return new java.awt.Font("Verdana",
223             (!bold && !italic) ? Font.PLAIN
224                     : (bold ? Font.BOLD : 0) + (italic ? Font.ITALIC : 0),
225             11);
226   }
227
228   /**
229    * standard font for editable text areas
230    * 
231    * @return
232    */
233   public static Font getTextAreaFont()
234   {
235     return getLabelFont(false, false);
236   }
237
238   /**
239    * clean up a swing menu. Removes any empty submenus without selection
240    * listeners.
241    * 
242    * @param webService
243    */
244   public static void cleanMenu(JMenu webService)
245   {
246     for (int i = 0; i < webService.getItemCount();)
247     {
248       JMenuItem item = webService.getItem(i);
249       if (item instanceof JMenu && ((JMenu) item).getItemCount() == 0)
250       {
251         webService.remove(i);
252       }
253       else
254       {
255         i++;
256       }
257     }
258   }
259
260   /**
261    * Returns the proportion of its range that a scrollbar's position represents,
262    * as a value between 0 and 1. For example if the whole range is from 0 to
263    * 200, then a position of 40 gives proportion = 0.2.
264    * 
265    * @see http://www.javalobby.org/java/forums/t33050.html#91885334
266    * 
267    * @param scroll
268    * @return
269    */
270   public static float getScrollBarProportion(JScrollBar scroll)
271   {
272     /*
273      * The extent (scroll handle width) deduction gives the true operating range
274      * of possible positions.
275      */
276     int possibleRange = scroll.getMaximum() - scroll.getMinimum()
277             - scroll.getModel().getExtent();
278     float valueInRange = scroll.getValue()
279             - (scroll.getModel().getExtent() / 2f);
280     float proportion = valueInRange / possibleRange;
281     return proportion;
282   }
283
284   /**
285    * Returns the scroll bar position in its range that would match the given
286    * proportion (between 0 and 1) of the whole. For example if the whole range
287    * is from 0 to 200, then a proportion of 0.25 gives position 50.
288    * 
289    * @param scrollbar
290    * @param proportion
291    * @return
292    */
293   public static int getScrollValueForProportion(JScrollBar scrollbar,
294           float proportion)
295   {
296     /*
297      * The extent (scroll handle width) deduction gives the true operating range
298      * of possible positions.
299      */
300     float fraction = proportion
301             * (scrollbar.getMaximum() - scrollbar.getMinimum()
302                     - scrollbar.getModel().getExtent())
303             + (scrollbar.getModel().getExtent() / 2f);
304     return Math.min(Math.round(fraction), scrollbar.getMaximum());
305   }
306
307   public static void jvInitComponent(AbstractButton comp, String i18nString)
308   {
309     setColorAndFont(comp);
310     if (i18nString != null && !i18nString.isEmpty())
311     {
312       comp.setText(MessageManager.getString(i18nString));
313     }
314   }
315
316   public static void jvInitComponent(JComponent comp)
317   {
318     setColorAndFont(comp);
319   }
320
321   private static void setColorAndFont(JComponent comp)
322   {
323     comp.setBackground(Color.white);
324     comp.setFont(JvSwingUtils.getLabelFont());
325   }
326
327   /**
328    * A helper method to build a drop-down choice of values, with tooltips for
329    * the entries
330    * 
331    * @param entries
332    * @param tooltips
333    */
334   public static JComboBox<Object> buildComboWithTooltips(
335           List<Object> entries, List<String> tooltips)
336   {
337     JComboBox<Object> combo = new JComboBox<>();
338     final ComboBoxTooltipRenderer renderer = new ComboBoxTooltipRenderer();
339     combo.setRenderer(renderer);
340     for (Object attName : entries)
341     {
342       combo.addItem(attName);
343     }
344     renderer.setTooltips(tooltips);
345     final MouseAdapter mouseListener = new MouseAdapter()
346     {
347       @Override
348       public void mouseEntered(MouseEvent e)
349       {
350         int j = combo.getSelectedIndex();
351         if (j > -1)
352         {
353           combo.setToolTipText(tooltips.get(j));
354         }
355       }
356       @Override
357       public void mouseExited(MouseEvent e)
358       {
359         combo.setToolTipText(null);
360       }
361     };
362     for (Component c : combo.getComponents())
363     {
364       c.addMouseListener(mouseListener);
365     }
366     return combo;
367   }
368
369   /**
370    * Adds a titled border to the component in the default font and position (top
371    * left), optionally witht italic text
372    * 
373    * @param comp
374    * @param title
375    * @param italic
376    */
377   public static TitledBorder createTitledBorder(JComponent comp,
378           String title, boolean italic)
379   {
380     Font font = comp.getFont();
381     if (italic)
382     {
383       font = new Font(font.getName(), Font.ITALIC, font.getSize());
384     }
385     Border border = BorderFactory.createTitledBorder("");
386     TitledBorder titledBorder = BorderFactory.createTitledBorder(border,
387             title, TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION,
388             font);
389     comp.setBorder(titledBorder);
390
391     return titledBorder;
392   }
393
394 }