JAL-3026 fixes MigLayout, JSON parser
[jalview.git] / src / net / miginfocom / swing / SwingComponentWrapper.java
1 package net.miginfocom.swing;
2 /*
3  * License (BSD):
4  * ==============
5  *
6  * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com)
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without modification,
10  * are permitted provided that the following conditions are met:
11  * Redistributions of source code must retain the above copyright notice, this list
12  * of conditions and the following disclaimer.
13  * Redistributions in binary form must reproduce the above copyright notice, this
14  * list of conditions and the following disclaimer in the documentation and/or other
15  * materials provided with the distribution.
16  * Neither the name of the MiG InfoCom AB nor the names of its contributors may be
17  * used to endorse or promote products derived from this software without specific
18  * prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23  * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
26  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
29  * OF SUCH DAMAGE.
30  *
31  * @version 1.0
32  * @author Mikael Grev, MiG InfoCom AB
33  *         Date: 2006-sep-08
34  */
35
36 import net.miginfocom.layout.ComponentWrapper;
37 import net.miginfocom.layout.ContainerWrapper;
38 import net.miginfocom.layout.LayoutUtil;
39 import net.miginfocom.layout.PlatformDefaults;
40
41 import javax.swing.*;
42 import javax.swing.border.Border;
43 import javax.swing.text.JTextComponent;
44 import java.awt.*;
45 import java.awt.geom.Rectangle2D;
46 import java.util.IdentityHashMap;
47 import java.util.StringTokenizer;
48
49 /**
50  */
51 public class SwingComponentWrapper implements ComponentWrapper
52 {
53         private static boolean maxSet = false;
54
55         private static boolean vp = true;
56
57         /** Debug color for component bounds outline.
58          */
59         private static final Color DB_COMP_OUTLINE = new Color(0, 0, 200);
60
61         /** Property to use in LAF settings and as JComponent client property
62          * to specify the visual padding.
63          * <p>
64          */
65         private static final String VISUAL_PADDING_PROPERTY = net.miginfocom.layout.PlatformDefaults.VISUAL_PADDING_PROPERTY;
66
67         private final Component c;
68         private int compType = TYPE_UNSET;
69         private Boolean bl = null;
70         private boolean prefCalled = false;
71
72         public SwingComponentWrapper(Component c)
73         {
74                 this.c = c;
75         }
76
77         @Override
78         public final int getBaseline(int width, int height)
79         {
80                 int h = height;
81                 int[] visPad = getVisualPadding();
82                 if (h < 0) {
83                         h = c.getHeight();
84                 } else if (visPad != null) {
85                         h = height + visPad[0] + visPad[2];
86                 }
87                 int baseLine = c.getBaseline(width < 0 ? c.getWidth() : width, h);
88                 if (baseLine != -1 && visPad != null)
89                         baseLine -= visPad[0];
90
91                 return baseLine;
92         }
93
94         @Override
95         public final Object getComponent()
96         {
97                 return c;
98         }
99
100         /** Cache.
101          */
102         private final static IdentityHashMap<FontMetrics, Point.Float> FM_MAP = new IdentityHashMap<FontMetrics, Point.Float>(4);
103         private final static Font SUBST_FONT = new Font("sansserif", Font.PLAIN, 11);
104
105         @Override
106         public final float getPixelUnitFactor(boolean isHor)
107         {
108                 switch (PlatformDefaults.getLogicalPixelBase()) {
109                         case PlatformDefaults.BASE_FONT_SIZE:
110                                 Font font = c.getFont();
111                                 FontMetrics fm = c.getFontMetrics(font != null ? font : SUBST_FONT);
112                                 Point.Float p = FM_MAP.get(fm);
113                                 if (p == null) {
114                                         Rectangle2D r = fm.getStringBounds("X", c.getGraphics());
115                                         p = new Point.Float(((float) r.getWidth()) / 6f, ((float) r.getHeight()) / 13.27734375f);
116                                         FM_MAP.put(fm, p);
117                                 }
118                                 return isHor ? p.x : p.y;
119
120                         case PlatformDefaults.BASE_SCALE_FACTOR:
121
122                                 Float s = isHor ? PlatformDefaults.getHorizontalScaleFactor() : PlatformDefaults.getVerticalScaleFactor();
123                                 float scaleFactor = (s != null) ? s : 1f;
124
125                                 // Swing in Java 9 scales automatically using the system scale factor(s) that the
126                                 // user can change in the system settings (Windows: Control Panel; Mac: System Preferences).
127                                 // Each connected screen may use its own scale factor
128                                 // (e.g. 1.5 for primary 4K 40inch screen and 1.0 for secondary HD screen).
129                                 float screenScale = isJava9orLater
130                                         ? 1f // use system scale factor(s)
131                                         : (float) (isHor ? getHorizontalScreenDPI() : getVerticalScreenDPI()) / (float) PlatformDefaults.getDefaultDPI();
132                                 return scaleFactor * screenScale;
133
134                         default:
135                                 return 1f;
136                 }
137         }
138
139         private static boolean isJava9orLater;
140         static {
141                 try {
142                         // Java 9 version-String Scheme: http://openjdk.java.net/jeps/223
143                         StringTokenizer st = new StringTokenizer(System.getProperty("java.version"), "._-+");
144                         int majorVersion = Integer.parseInt(st.nextToken());
145                         isJava9orLater = majorVersion >= 9;
146                 } catch (Exception e) {
147                         // Java 8 or older
148                 }
149         }
150
151 //      /** Cache.
152 //       */
153 //      private final static IdentityHashMap<FontMetrics, Point.Float> FM_MAP2 = new IdentityHashMap<FontMetrics, Point.Float>(4);
154 //      private final static Font SUBST_FONT2 = new Font("sansserif", Font.PLAIN, 11);
155 //
156 //      public float getDialogUnit(boolean isHor)
157 //      {
158 //              Font font = c.getFont();
159 //              FontMetrics fm = c.getFontMetrics(font != null ? font : SUBST_FONT2);
160 //              Point.Float dluP = FM_MAP2.get(fm);
161 //              if (dluP == null) {
162 //                      float w = fm.charWidth('X') / 4f;
163 //                      int ascent = fm.getAscent();
164 //                      float h = (ascent > 14 ? ascent : ascent + (15 - ascent) / 3) / 8f;
165 //
166 //                      dluP = new Point.Float(w, h);
167 //                      FM_MAP2.put(fm, dluP);
168 //              }
169 //              return isHor ? dluP.x : dluP.y;
170 //      }
171
172         @Override
173         public final int getX()
174         {
175                 return c.getX();
176         }
177
178         @Override
179         public final int getY()
180         {
181                 return c.getY();
182         }
183
184         @Override
185         public final int getHeight()
186         {
187                 return c.getHeight();
188         }
189
190         @Override
191         public final int getWidth()
192         {
193                 return c.getWidth();
194         }
195
196         @Override
197         public final int getScreenLocationX()
198         {
199                 Point p = new Point();
200                 SwingUtilities.convertPointToScreen(p, c);
201                 return p.x;
202         }
203
204         @Override
205         public final int getScreenLocationY()
206         {
207                 Point p = new Point();
208                 SwingUtilities.convertPointToScreen(p, c);
209                 return p.y;
210         }
211
212         @Override
213         public final int getMinimumHeight(int sz)
214         {
215                 if (prefCalled == false) {
216                         c.getPreferredSize(); // To defeat a bug where the minimum size is different before and after the first call to getPreferredSize();
217                         prefCalled = true;
218                 }
219                 return c.getMinimumSize().height;
220         }
221
222         @Override
223         public final int getMinimumWidth(int sz)
224         {
225                 if (prefCalled == false) {
226                         c.getPreferredSize(); // To defeat a bug where the minimum size is different before and after the first call to getPreferredSize();
227                         prefCalled = true;
228                 }
229                 return c.getMinimumSize().width;
230         }
231         @Override
232         public final int getPreferredHeight(int sz)
233         {
234                 // If the component has not gotten size yet and there is a size hint, trick Swing to return a better height.
235                 if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1)
236                         c.setBounds(c.getX(), c.getY(), sz, 1);
237
238                 return c.getPreferredSize().height;
239         }
240
241         @Override
242         public final int getPreferredWidth(int sz)
243         {
244                 // If the component has not gotten size yet and there is a size hint, trick Swing to return a better height.
245                 if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1)
246                         c.setBounds(c.getX(), c.getY(), 1, sz);
247
248                 return c.getPreferredSize().width;
249         }
250
251         @Override
252         public final int getMaximumHeight(int sz)
253         {
254                 if (!isMaxSet(c))
255                         return Integer.MAX_VALUE;
256
257                 return c.getMaximumSize().height;
258         }
259
260         @Override
261         public final int getMaximumWidth(int sz)
262         {
263                 if (!isMaxSet(c))
264                         return Integer.MAX_VALUE;
265
266                 return c.getMaximumSize().width;
267         }
268
269
270         private boolean isMaxSet(Component c)
271         {
272                 return c.isMaximumSizeSet();
273         }
274
275         @Override
276         public final ContainerWrapper getParent()
277         {
278                 Container p = c.getParent();
279                 return p != null ? new SwingContainerWrapper(p) : null;
280         }
281
282     @Override
283     public final int getHorizontalScreenDPI() {
284         try {
285             return c.getToolkit().getScreenResolution();
286         } catch (HeadlessException ex) {
287             return PlatformDefaults.getDefaultDPI();
288         }
289     }
290
291         @Override
292         public final int getVerticalScreenDPI()
293         {
294         try {
295             return c.getToolkit().getScreenResolution();
296         } catch (HeadlessException ex) {
297             return PlatformDefaults.getDefaultDPI();
298         }
299         }
300
301         @Override
302         public final int getScreenWidth()
303         {
304                 try {
305                         return c.getToolkit().getScreenSize().width;
306                 } catch (HeadlessException ex) {
307                         return 1024;
308                 }
309         }
310
311         @Override
312         public final int getScreenHeight()
313         {
314                 try {
315                         return c.getToolkit().getScreenSize().height;
316                 } catch (HeadlessException ex) {
317                         return 768;
318                 }
319         }
320
321         @Override
322         public final boolean hasBaseline()
323         {
324                 if (bl == null) {
325                         try {
326                                 // Removed since OTHER is sometimes returned even though there is a valid baseline (e.g. an empty JComboBox)
327 //                              if (c.getBaselineResizeBehavior() == Component.BaselineResizeBehavior.OTHER) {
328 //                                      bl = Boolean.FALSE;
329 //                              } else {
330                                         // Removed since it made some components layout themselves to the minimum size and that stuck after that. E.g. JLabel with HTML content and white spaces would be very tall.
331 //                                      Dimension d = c.getPreferredSize();
332 //                                      bl = getBaseline(d.width, d.height) > -1;
333                                         bl = getBaseline(8192, 8192) > -1;  // Use large number but don't risk overflow or exposing size bugs with Integer.MAX_VALUE
334 //                              }
335                         } catch (Throwable ex) {
336                                 bl = Boolean.FALSE;
337                         }
338                 }
339                 return bl;
340         }
341
342         @Override
343         public final String getLinkId()
344         {
345                 return c.getName();
346         }
347
348         @Override
349         public final void setBounds(int x, int y, int width, int height)
350         {
351                 c.setBounds(x, y, width, height);
352         }
353
354         @Override
355         public boolean isVisible()
356         {
357                 return c.isVisible();
358         }
359
360         @Override
361         public final int[] getVisualPadding()
362         {
363                 int[] padding = null;
364                 if (isVisualPaddingEnabled()) {
365                         //First try "visualPadding" client property
366                         if (c instanceof JComponent) {
367                                 JComponent component = (JComponent) c;
368                                 Object padValue = component.getClientProperty(VISUAL_PADDING_PROPERTY);
369
370                                 if (padValue instanceof int[] ) {
371                                         //client property value could be an int[]
372                                         padding = (int[]) padValue;
373                                 } else if (padValue instanceof Insets) {
374                                         //OR client property value could be an Insets
375                                         Insets padInsets = (Insets) padValue;
376                                         padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right };
377                                 }
378
379                                 if (padding == null) {
380                                         //No client property set on the individual JComponent,
381                                         //      so check for a LAF setting for the component type.
382                                         String classID;
383                                         switch (getComponentType(false)) {
384                                                 case TYPE_BUTTON:
385                                                         Border border = component.getBorder();
386                                                         if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) {
387                                                                 if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) {
388                                                                         Object buttonType = component.getClientProperty("JButton.buttonType");
389                                                                         if (buttonType == null) {
390                                                                                 classID = component.getHeight() < 33 ? "Button" : "Button.bevel";
391                                                                         } else {
392                                                                                 classID = "Button." + buttonType;
393                                                                         }
394                                                                         if (((AbstractButton) component).getIcon() != null)
395                                                                                 classID += ".icon";
396                                                                 } else {
397                                                                         classID = "Button";
398                                                                 }
399                                                         } else {
400                                                                 classID = "";
401                                                         }
402                                                         break;
403
404                                                 case TYPE_CHECK_BOX:
405                                                         border = component.getBorder();
406                                                         if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) {
407                                                                 Object size = component.getClientProperty("JComponent.sizeVariant");
408                                                                 if (size != null && size.toString().equals("regular") == false) {
409                                                                         size = "." + size;
410                                                                 } else {
411                                                                         size = "";
412                                                                 }
413
414                                                                 if (component instanceof JRadioButton) {
415                                                                         classID = "RadioButton" + size;
416                                                                 } else if (component instanceof JCheckBox) {
417                                                                         classID = "CheckBox" + size;
418                                                                 } else {
419                                                                         classID = "ToggleButton" + size;
420                                                                 }
421                                                         } else {
422                                                                 classID = "";
423                                                         }
424                                                         break;
425
426                                                 case TYPE_COMBO_BOX:
427                                                         if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) {
428                                                                 if (((JComboBox) component).isEditable()) {
429                                                                         Object isSquare = component.getClientProperty("JComboBox.isSquare");
430                                                                         if (isSquare != null && isSquare.toString().equals("true")) {
431                                                                                 classID = "ComboBox.editable.isSquare";
432                                                                         } else {
433                                                                                 classID = "ComboBox.editable";
434                                                                         }
435
436                                                                 } else {
437                                                                         Object isSquare = component.getClientProperty("JComboBox.isSquare");
438                                                                         Object isPopDown = component.getClientProperty("JComboBox.isPopDown");
439
440                                                                         if (isSquare != null && isSquare.toString().equals("true")) {
441                                                                                 classID = "ComboBox.isSquare";
442                                                                         } else if (isPopDown != null && isPopDown.toString().equals("true")) {
443                                                                                 classID = "ComboBox.isPopDown";
444                                                                         } else {
445                                                                                 classID = "ComboBox";
446                                                                         }
447                                                                 }
448                                                         } else {
449                                                                 classID = "ComboBox";
450                                                         }
451                                                         break;
452                                                 case TYPE_CONTAINER:
453                                                         classID = "Container";
454                                                         break;
455                                                 case TYPE_IMAGE:
456                                                         classID = "Image";
457                                                         break;
458                                                 case TYPE_LABEL:
459                                                         classID = "Label";
460                                                         break;
461                                                 case TYPE_LIST:
462                                                         classID = "List";
463                                                         break;
464                                                 case TYPE_PANEL:
465                                                         classID = "Panel";
466                                                         break;
467                                                 case TYPE_PROGRESS_BAR:
468                                                         classID = "ProgressBar";
469                                                         break;
470                                                 case TYPE_SCROLL_BAR:
471                                                         classID = "ScrollBar";
472                                                         break;
473                                                 case TYPE_SCROLL_PANE:
474                                                         classID = "ScrollPane";
475                                                         break;
476                                                 case TYPE_SEPARATOR:
477                                                         classID = "Separator";
478                                                         break;
479                                                 case TYPE_SLIDER:
480                                                         classID = "Slider";
481                                                         break;
482                                                 case TYPE_SPINNER:
483                                                         classID = "Spinner";
484                                                         break;
485                                                 case TYPE_TABLE:
486                                                         classID = "Table";
487                                                         break;
488                                                 case TYPE_TABBED_PANE:
489                                                         classID = "TabbedPane";
490                                                         break;
491                                                 case TYPE_TEXT_AREA:
492                                                         classID = "TextArea";
493                                                         break;
494                                                 case TYPE_TEXT_FIELD:
495                                                         border = component.getBorder();
496                                                         if (!component.isOpaque() && border != null && border.getClass().getSimpleName().equals("AquaTextFieldBorder")) {
497                                                                 classID = "TextField";
498                                                         } else {
499                                                                 classID = "";
500                                                         }
501                                                         break;
502                                                 case TYPE_TREE:
503                                                         classID = "Tree";
504                                                         break;
505                                                 case TYPE_UNKNOWN:
506                                                         classID = "Other";
507                                                         break;
508                                                 case TYPE_UNSET:
509                                                 default:
510                                                         classID = "";
511                                                         break;
512                                         }
513
514                                         padValue = PlatformDefaults.getDefaultVisualPadding(classID + "." + VISUAL_PADDING_PROPERTY);
515                                         if (padValue instanceof int[]) {
516                                                 //client property value could be an int[]
517                                                 padding = (int[]) padValue;
518                                         } else if (padValue instanceof Insets) {
519                                                 //OR client property value could be an Insets
520                                                 Insets padInsets = (Insets) padValue;
521                                                 padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right };
522                                         }
523                                 }
524                         }
525                 }
526                 return padding;
527         }
528
529         /**
530          * @deprecated Java 1.4 is not supported anymore
531          */
532         public static boolean isMaxSizeSetOn1_4()
533         {
534                 return maxSet;
535         }
536
537         /**
538          * @deprecated Java 1.4 is not supported anymore
539          */
540         public static void setMaxSizeSetOn1_4(boolean b)
541         {
542                 maxSet = b;
543         }
544
545         public static boolean isVisualPaddingEnabled()
546         {
547                 return vp;
548         }
549
550         public static void setVisualPaddingEnabled(boolean b)
551         {
552                 vp = b;
553         }
554
555         @Override
556         public final void paintDebugOutline(boolean showVisualPadding)
557         {
558                 if (c.isShowing() == false)
559                         return;
560
561                 Graphics2D g = (Graphics2D) c.getGraphics();
562                 if (g == null)
563                         return;
564
565                 g.setPaint(DB_COMP_OUTLINE);
566                 g.setStroke(new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[] {2f, 4f}, 0));
567                 g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
568
569                 if (showVisualPadding && isVisualPaddingEnabled()) {
570                         int[] padding = getVisualPadding();
571                         if (padding != null) {
572                                 g.setColor(Color.GREEN);
573                                 g.drawRect(padding[1], padding[0], (getWidth() - 1) - (padding[1] + padding[3]), (getHeight() - 1) - (padding[0] + padding[2]));
574                         }
575                 }
576         }
577
578         @Override
579         public int getComponentType(boolean disregardScrollPane)
580         {
581                 if (compType == TYPE_UNSET)
582                         compType = checkType(disregardScrollPane);
583
584                 return compType;
585         }
586
587         @Override
588         public int getLayoutHashCode()
589         {
590                 Dimension d = c.getMaximumSize();
591                 int hash = d.width + (d.height << 5);
592
593                 d = c.getPreferredSize();
594                 hash += (d.width << 10) + (d.height << 15);
595
596                 d = c.getMinimumSize();
597                 hash += (d.width << 20) + (d.height << 25);
598
599                 if (c.isVisible())
600                         hash += 1324511;
601
602                 String id = getLinkId();
603                 if (id != null)
604                         hash += id.hashCode();
605
606                 return hash;
607         }
608
609         private int checkType(boolean disregardScrollPane)
610         {
611                 Component c = this.c;
612
613                 if (disregardScrollPane) {
614                         if (c instanceof JScrollPane) {
615                                 c = ((JScrollPane) c).getViewport().getView();
616                         } else if (c instanceof ScrollPane) {
617                                 c = ((ScrollPane) c).getComponent(0);
618                         }
619                 }
620
621                 if (c instanceof JTextField || c instanceof TextField) {
622                         return TYPE_TEXT_FIELD;
623                 } else if (c instanceof JLabel || c instanceof Label) {
624                         return TYPE_LABEL;
625                 } else if (c instanceof JCheckBox || c instanceof JRadioButton || c instanceof Checkbox) {
626                         return TYPE_CHECK_BOX;
627                 } else if (c instanceof AbstractButton || c instanceof Button) {
628                         return TYPE_BUTTON;
629                 } else if (c instanceof JComboBox || c instanceof Choice) {
630                         return TYPE_COMBO_BOX;
631                 } else if (c instanceof JTextComponent || c instanceof TextComponent) {
632                         return TYPE_TEXT_AREA;
633                 } else if (c instanceof JPanel || c instanceof Canvas) {
634                         return TYPE_PANEL;
635                 } else if (c instanceof JList || c instanceof List) {
636                         return TYPE_LIST;
637                 } else if (c instanceof JTable) {
638                         return TYPE_TABLE;
639                 } else if (c instanceof JSeparator) {
640                         return TYPE_SEPARATOR;
641                 } else if (c instanceof JSpinner) {
642                         return TYPE_SPINNER;
643                 } else if (c instanceof JTabbedPane) {
644                         return TYPE_TABBED_PANE;
645                 } else if (c instanceof JProgressBar) {
646                         return TYPE_PROGRESS_BAR;
647                 } else if (c instanceof JSlider) {
648                         return TYPE_SLIDER;
649                 } else if (c instanceof JScrollPane) {
650                         return TYPE_SCROLL_PANE;
651                 } else if (c instanceof JScrollBar || c instanceof Scrollbar) {
652                         return TYPE_SCROLL_BAR;
653                 } else if (c instanceof Container) {    // only AWT components is not containers.
654                         return TYPE_CONTAINER;
655                 }
656                 return TYPE_UNKNOWN;
657         }
658
659         @Override
660         public final int hashCode()
661         {
662                 return getComponent().hashCode();
663         }
664
665         @Override
666         public final boolean equals(Object o)
667         {
668                 if (o instanceof ComponentWrapper == false)
669                         return false;
670
671                 return c.equals(((ComponentWrapper) o).getComponent());
672         }
673
674         @Override
675         public int getContentBias()
676         {
677                 return c instanceof JTextArea || c instanceof JEditorPane || (c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty("migLayout.dynamicAspectRatio"))) ? LayoutUtil.HORIZONTAL : -1;
678         }
679 }