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