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