View Javadoc
1   package fr.ifremer.quadrige3.ui.swing;
2   
3   /*-
4    * #%L
5    * Quadrige3 Core :: Quadrige3 UI Common
6    * $Id:$
7    * $HeadURL:$
8    * %%
9    * Copyright (C) 2017 Ifremer
10   * %%
11   * This program is free software: you can redistribute it and/or modify
12   * it under the terms of the GNU Affero General Public License as published by
13   * the Free Software Foundation, either version 3 of the License, or
14   * (at your option) any later version.
15   *
16   * This program is distributed in the hope that it will be useful,
17   * but WITHOUT ANY WARRANTY; without even the implied warranty of
18   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19   * GNU General Public License for more details.
20   *
21   * You should have received a copy of the GNU Affero General Public License
22   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
23   * #L%
24   */
25  
26  import jaxx.runtime.JAXXObject;
27  import jaxx.runtime.JAXXUtil;
28  import jaxx.runtime.SwingUtil;
29  import org.apache.commons.collections4.CollectionUtils;
30  import org.apache.commons.io.IOUtils;
31  import org.apache.commons.lang3.StringEscapeUtils;
32  import org.apache.commons.lang3.StringUtils;
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.jdesktop.beans.AbstractBean;
36  import org.nuiton.jaxx.application.ApplicationBusinessException;
37  
38  import javax.swing.*;
39  import javax.swing.text.JTextComponent;
40  import java.awt.*;
41  import java.awt.image.BufferedImage;
42  import java.io.BufferedReader;
43  import java.io.IOException;
44  import java.io.InputStream;
45  import java.io.StringReader;
46  import java.net.MalformedURLException;
47  import java.net.URL;
48  import java.net.URLConnection;
49  import java.text.DecimalFormat;
50  import java.text.DecimalFormatSymbols;
51  import java.util.*;
52  import java.util.stream.Collectors;
53  
54  import static org.nuiton.i18n.I18n.t;
55  
56  /**
57   * Created: 14/06/12
58   */
59  public class ApplicationUIUtil extends org.nuiton.jaxx.application.swing.util.ApplicationUIUtil {
60  
61      private static final DecimalFormatSymbols SYMBOLS = new DecimalFormatSymbols();
62  
63      private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat();
64  
65      private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
66  
67      static {
68          SYMBOLS.setDecimalSeparator('.');
69          SYMBOLS.setGroupingSeparator(' ');
70  
71          DECIMAL_FORMAT.setDecimalFormatSymbols(SYMBOLS);
72          DECIMAL_FORMAT.setGroupingUsed(false);
73  
74          Calendar calendar = Calendar.getInstance();
75          calendar.set(2000, Calendar.JANUARY, 1, 0, 0);
76  
77          DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
78      }
79  
80      /**
81       * Logger.
82       */
83      private static final Log LOG = LogFactory.getLog(ApplicationUIUtil.class);
84  
85      /**
86       * Private constructor (never instantiate util class)
87       */
88      protected ApplicationUIUtil() {
89      }
90  
91      /**
92       * <p>setParentUI.</p>
93       *
94       * @param ui       a {@link jaxx.runtime.JAXXObject} object.
95       * @param parentUI a {@link ApplicationUI} object.
96       */
97      public static void setParentUI(JAXXObject ui, ApplicationUI<?, ?> parentUI) {
98          JAXXUtil.initContext(ui, parentUI);
99          ApplicationUIContext context = parentUI.getHandler() != null ? parentUI.getHandler().getContext() : null;
100         context = context != null ? context : ApplicationUIContext.getInstance();
101         setApplicationContext(ui, context);
102     }
103 
104     /**
105      * <p>getParentUI.</p>
106      *
107      * @param ui a {@link ApplicationUI} object.
108      * @return a {@link ApplicationUI} object.
109      */
110     public static ApplicationUI<?, ?> getParentUI(ApplicationUI<?, ?> ui) {
111         return ui.getContextValue(ApplicationUI.class, JAXXUtil.PARENT);
112     }
113 
114     /**
115      * <p>tryToConnectToUpdateUrl.</p>
116      *
117      * @param urlAsString         a {@link java.lang.String} object.
118      * @param badUrlFormatI18nKey a {@link java.lang.String} object.
119      * @param notReachI18nKey     a {@link java.lang.String} object.
120      * @param notFoundI18nKey     a {@link java.lang.String} object.
121      */
122     public static void tryToConnectToUpdateUrl(String urlAsString,
123                                                String badUrlFormatI18nKey,
124                                                String notReachI18nKey,
125                                                String notFoundI18nKey) {
126         URL url;
127         // get url
128         try {
129             url = new URL(urlAsString);
130         } catch (MalformedURLException e) {
131             if (LOG.isErrorEnabled()) {
132                 LOG.error("Bad url syntax at " + urlAsString, e);
133             }
134             throw new ApplicationBusinessException(t(badUrlFormatI18nKey, urlAsString));
135         }
136 
137         URLConnection urlConnection;
138         // try to connect (fail if network or remote host does not exists)
139         try {
140             urlConnection = url.openConnection();
141             urlConnection.setConnectTimeout(10000);
142             urlConnection.connect();
143         } catch (Exception e) {
144             if (LOG.isErrorEnabled()) {
145                 LOG.error("Could not connect to " + urlAsString, e);
146             }
147             throw new ApplicationBusinessException(t(notReachI18nKey, urlAsString));
148         }
149 
150         // try to open the resource (fail if resources does not exist)
151         try {
152             urlConnection.setReadTimeout(1000);
153             InputStream inputStream = null;
154             try {
155                 inputStream = urlConnection.getInputStream();
156             } finally {
157                 IOUtils.closeQuietly(inputStream);
158             }
159         } catch (Exception e) {
160             if (LOG.isErrorEnabled()) {
161                 LOG.error("Could not found file at to " + urlAsString, e);
162             }
163             throw new ApplicationBusinessException(t(notFoundI18nKey, urlAsString));
164         }
165     }
166 
167     /**
168      * <p>getDecimalFormatSymbols.</p>
169      *
170      * @return a {@link java.text.DecimalFormatSymbols} object.
171      */
172     public static DecimalFormatSymbols getDecimalFormatSymbols() {
173         return SYMBOLS;
174     }
175 
176     /**
177      * <p>getDecimalFormat.</p>
178      *
179      * @param minDecimal a int.
180      * @param maxDecimal a int.
181      * @return a {@link java.text.DecimalFormat} object.
182      */
183     public static DecimalFormat getDecimalFormat(int minDecimal, int maxDecimal) {
184         DECIMAL_FORMAT.setMinimumFractionDigits(minDecimal);
185         DECIMAL_FORMAT.setMaximumFractionDigits(maxDecimal);
186         return DECIMAL_FORMAT;
187     }
188 
189     private static final String HTML_BEGIN = "<html>";
190     private static final String HTML_END = "</html>";
191     private static final String HTML_PATTERN = HTML_BEGIN + "%s" + HTML_END;
192     private static final String HTML_BR = "<br>";
193     private static final String HTML_BR2 = "<br/>";
194     private static final String HTML_LIST_PATTERN = "<ul>%s</ul>";
195     private static final String HTML_LIST_ITEM_PATTERN = "<li>%s</li>";
196     private static final String HTML_ITALIC_PATTERN = HTML_BEGIN + "<i>%s</i>" + HTML_END;
197 
198     /**
199      * <p>isHtmlString.</p>
200      *
201      * @param text a {@link java.lang.String} object.
202      * @return a boolean.
203      */
204     public static boolean isHtmlString(String text) {
205         if (StringUtils.isBlank(text)) {
206             return false;
207         }
208         String s = StringUtils.deleteWhitespace(text);
209         return s.startsWith(HTML_BEGIN) && s.endsWith(HTML_END);
210     }
211 
212     /**
213      * <p>getHtmlString.</p>
214      *
215      * @param text a {@link java.lang.String} object.
216      * @return a {@link java.lang.String} object.
217      */
218     public static String getHtmlString(String text) {
219 
220         if (StringUtils.isBlank(text)) {
221             return HTML_BEGIN + HTML_END;
222         } else if (isHtmlString(text)) {
223             return text;
224         }
225 
226         return String.format(HTML_PATTERN, formatHtmlText(text));
227     }
228 
229     public static String getHtmlItalicText(String text) {
230 
231         if (StringUtils.isBlank(text)) {
232             return HTML_BEGIN + HTML_END;
233         } else if (isHtmlString(text)) {
234             return getHtmlItalicText(removeHtmlTags(text));
235         }
236 
237         return String.format(HTML_ITALIC_PATTERN, text);
238     }
239 
240     private static String formatHtmlText(String text) {
241 
242         try (BufferedReader st = new BufferedReader(new StringReader(text))) {
243 
244             // Try with stream
245             return st.lines().filter(Objects::nonNull)
246                 .map(line -> {
247                     if (line.equalsIgnoreCase(HTML_BR2) || line.equalsIgnoreCase(HTML_BR)) {
248                         return HTML_BR;
249                     } else {
250                         return escapeHtmlString(line);
251                     }
252                 })
253                 .collect(Collectors.joining(HTML_BR));
254 
255         } catch (IOException e) {
256             LOG.warn("Problem formatting string to html", e);
257             return text;
258         }
259     }
260 
261     private static String escapeHtmlString(String str) {
262         boolean containsHTML = str.matches(".*<[^>]+>.*");
263         return containsHTML ? str : StringEscapeUtils.escapeHtml4(str);
264     }
265 
266     /**
267      * <p>getHtmlString.</p>
268      *
269      * @param strings a {@link java.util.List} object.
270      * @return a {@link java.lang.String} object.
271      */
272     public static String getHtmlString(Collection<String> strings) {
273         if (CollectionUtils.isEmpty(strings)) {
274             return getHtmlString("");
275         }
276         return String.format(HTML_PATTERN, formatHtmlList(strings));
277     }
278 
279     /**
280      * <p>formatHtmlList.</p>
281      *
282      * @param strings a {@link java.util.List} object.
283      * @return a {@link java.lang.String} object.
284      */
285     public static String formatHtmlList(Collection<String> strings) {
286         StringBuilder result = new StringBuilder();
287         for (String string : strings) {
288             result.append(String.format(HTML_LIST_ITEM_PATTERN, string));
289         }
290         return String.format(HTML_LIST_PATTERN, result.toString());
291     }
292 
293     /**
294      * <p>getHtmlString.</p>
295      *
296      * @param text    a {@link java.lang.String} object.
297      * @param strings a {@link java.util.List} object.
298      * @return a {@link java.lang.String} object.
299      */
300     public static String getHtmlString(String text, Collection<String> strings) {
301         if (CollectionUtils.isEmpty(strings)) {
302             return getHtmlString(text);
303         }
304         StringBuilder builder = new StringBuilder();
305         for (String string : strings) {
306             if (StringUtils.isNotBlank(string)) {
307                 builder.append(String.format(HTML_LIST_ITEM_PATTERN, formatHtmlText(string)));
308             }
309         }
310         String result = (text != null ? formatHtmlText(text) : "") + String.format(HTML_LIST_PATTERN, builder.toString());
311         return String.format(HTML_PATTERN, result);
312     }
313 
314     /**
315      * Remove HTML tag, that could have been added by getHtmlString() method
316      *
317      * @param htmlString a {@link java.lang.String} object.
318      * @return a {@link java.lang.String} object.
319      */
320     public static String removeHtmlTags(String htmlString) {
321         String result = htmlString;
322 
323         if (result.startsWith(HTML_BEGIN)) {
324             result = result.substring(HTML_BEGIN.length());
325         }
326         if (result.endsWith(HTML_END)) {
327             result = result.substring(0, result.length() - HTML_BEGIN.length() - 1);
328         }
329 
330         // remove br tags
331         result = result.replaceAll(HTML_BR, "").replaceAll(HTML_BR2, "");
332 
333         // some formatting
334         result = result.replaceAll("<strong>", "").replaceAll("<.strong>", "");
335         result = result.replaceAll("<li>", "").replaceAll("<.li>", "");
336         result = result.replaceAll("<ul>", "").replaceAll("<.ul>", "");
337 
338         return StringEscapeUtils.unescapeHtml4(result);
339     }
340 
341     /**
342      * <p>setComponentLocationOnScreen.</p>
343      *
344      * @param component a {@link java.awt.Component} object.
345      * @param point     a {@link java.awt.Point} object.
346      */
347     public static void setComponentLocationOnScreen(Component component, Point point) {
348 
349         if (component == null || point == null) {
350             return;
351         }
352 
353         Rectangle screenBounds = new Rectangle();
354         GraphicsDevice[] devices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
355         for (GraphicsDevice device : devices) {
356             screenBounds = screenBounds.union(device.getDefaultConfiguration().getBounds());
357         }
358 
359         if (point.x < screenBounds.x) {
360             point.x = 0;
361         } else if (point.x + component.getWidth() > screenBounds.width) {
362             point.x = screenBounds.width - component.getWidth();
363         }
364 
365         if (point.y < screenBounds.y) {
366             point.y = 0;
367         } else if (point.y + component.getHeight() > screenBounds.height) {
368             point.y = screenBounds.height - component.getHeight();
369         }
370 
371         component.setLocation(point);
372     }
373 
374     /**
375      * Like SwingXUtilities.setComponentTreeBackground(Component c, Color color), but filter out some components
376      *
377      * @param component a {@link java.awt.Component} object.
378      * @param color     a {@link java.awt.Color} object.
379      */
380     public static void setComponentTreeBackground(Component component, Color color) {
381 
382         if (component instanceof JTable
383             || component instanceof JTextComponent
384             || component instanceof JComboBox
385             || component instanceof JButton
386             || component instanceof JToggleButton
387         ) {
388             return;
389         }
390 
391         if (component instanceof JList && component.isEnabled()) {
392             return;
393         }
394 
395         component.setBackground(color);
396 
397         if (component instanceof Container) {
398             Container container = (Container) component;
399             if (container.getComponents() != null) {
400                 for (Component child : container.getComponents()) {
401                     setComponentTreeBackground(child, color);
402                 }
403             }
404         }
405     }
406 
407     /**
408      * <p>setChildrenLabelForeground.</p>
409      *
410      * @param container a {@link java.awt.Container} object.
411      * @param color     a {@link java.awt.Color} object.
412      */
413     public static void setChildrenLabelForeground(Container container, Color color) {
414 
415         if (container.getComponents() != null) {
416             for (Component child : container.getComponents()) {
417 
418                 if (child instanceof JLabel) {
419                     child.setForeground(color);
420                 } else if (child instanceof Container) {
421                     setChildrenLabelForeground((Container) child, color);
422                 }
423             }
424         }
425     }
426 
427     /**
428      * <p>destroy.</p>
429      *
430      * @param object to destroy.
431      */
432     public static void destroy(Object object) {
433 
434         if (object instanceof Container) {
435             Arrays.stream(((Container) object).getComponents()).forEach(ApplicationUIUtil::destroy);
436         }
437 
438         if (object instanceof ApplicationUI) {
439             ApplicationUI ui = (ApplicationUI) object;
440             ui.getHandler().onCloseUI();
441             ui.getHandler().clearValidators();
442             removeBindings(ui);
443             if (ui.getModel() instanceof AbstractBean && !(ui.getModel() instanceof ApplicationUIContext)) {
444                 removeListeners((AbstractBean) ui.getModel());
445             }
446         }
447 
448         if (object instanceof Component) {
449             removeListeners((Component) object);
450         }
451         if (object instanceof Window) {
452             ((Window) object).dispose();
453         }
454     }
455 
456     /**
457      * <p>removeListeners.</p>
458      *
459      * @param bean a {@link org.jdesktop.beans.AbstractBean} object.
460      */
461     public static void removeListeners(AbstractBean bean) {
462         Arrays.stream(bean.getPropertyChangeListeners()).forEach(bean::removePropertyChangeListener);
463         Arrays.stream(bean.getVetoableChangeListeners()).forEach(bean::removeVetoableChangeListener);
464     }
465 
466     /**
467      * <p>removeListeners.</p>
468      *
469      * @param component a {@link java.awt.Component} object.
470      */
471     public static void removeListeners(Component component) {
472         Arrays.stream(component.getPropertyChangeListeners()).forEach(component::removePropertyChangeListener);
473         Arrays.stream(component.getFocusListeners()).forEach(component::removeFocusListener);
474     }
475 
476     /**
477      * <p>removeBindings.</p>
478      *
479      * @param jaxxObject a {@link jaxx.runtime.JAXXObject} object.
480      */
481     public static void removeBindings(JAXXObject jaxxObject) {
482         Arrays.stream(jaxxObject.getDataBindings()).forEach(jaxxBinding -> SwingUtil.removeDataBinding(jaxxObject, jaxxBinding.getId()));
483     }
484 
485     /**
486      * <p>getDefault2DigitYearStart.</p>
487      *
488      * @return a {@link java.util.Date} object.
489      */
490     public static Date getDefault2DigitYearStart() {
491         return DEFAULT_TWO_DIGIT_YEAR_START;
492     }
493 
494     public static Cursor getCustomCursor(String actionIconName) {
495 
496         Toolkit toolkit = Toolkit.getDefaultToolkit();
497 
498         ImageIcon imageIcon = SwingUtil.createActionIcon(actionIconName);
499 
500         Dimension bestDimension = toolkit.getBestCursorSize(imageIcon.getIconWidth(), imageIcon.getIconHeight());
501         if (bestDimension.getWidth() == 0) {
502             return Cursor.getDefaultCursor();
503         }
504 
505         BufferedImage cursorImage = new BufferedImage(bestDimension.width, bestDimension.height, BufferedImage.TYPE_INT_ARGB);
506         Graphics2D graphics2D = cursorImage.createGraphics();
507         graphics2D.drawImage(imageIcon.getImage(),
508             (bestDimension.width - imageIcon.getIconWidth()) / 2,
509             (bestDimension.height - imageIcon.getIconHeight()) / 2,
510             imageIcon.getIconWidth(),
511             imageIcon.getIconHeight(),
512             null);
513 
514         return toolkit.createCustomCursor(
515             cursorImage,
516             new Point(bestDimension.width / 2, bestDimension.height / 2),
517             actionIconName);
518     }
519 
520 }