2b6f1aaf145c731c6ea57af0cb7e0a6c4b80afec
[jalview.git] / src2 / 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 = true;
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                 
230                 return c.getMinimumSize().width;
231         }
232         @Override
233         public final int getPreferredHeight(int sz)
234         {
235                 // If the component has not gotten size yet and there is a size hint, trick Swing to return a better height.
236                 if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1)
237                         c.setBounds(c.getX(), c.getY(), sz, 1);
238
239                 return c.getPreferredSize().height;
240         }
241
242         @Override
243         public final int getPreferredWidth(int sz)
244         {
245                 // If the component has not gotten size yet and there is a size hint, trick Swing to return a better height.
246                 if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1)
247                         c.setBounds(c.getX(), c.getY(), 1, sz);
248
249                 return c.getPreferredSize().width;
250         }
251
252         @Override
253         public final int getMaximumHeight(int sz)
254         {
255                 if (!isMaxSet(c))
256                         return Integer.MAX_VALUE;
257
258                 return c.getMaximumSize().height;
259         }
260
261         @Override
262         public final int getMaximumWidth(int sz)
263         {
264                 if (!isMaxSet(c))
265                         return Integer.MAX_VALUE;
266
267                 return c.getMaximumSize().width;
268         }
269
270
271         private boolean isMaxSet(Component c)
272         {
273                 return c.isMaximumSizeSet();
274         }
275
276         @Override
277         public final ContainerWrapper getParent()
278         {
279                 Container p = c.getParent();
280                 return p != null ? new SwingContainerWrapper(p) : null;
281         }
282
283     @Override
284     public final int getHorizontalScreenDPI() {
285         try {
286             return c.getToolkit().getScreenResolution();
287         } catch (HeadlessException ex) {
288             return PlatformDefaults.getDefaultDPI();
289         }
290     }
291
292         @Override
293         public final int getVerticalScreenDPI()
294         {
295         try {
296             return c.getToolkit().getScreenResolution();
297         } catch (HeadlessException ex) {
298             return PlatformDefaults.getDefaultDPI();
299         }
300         }
301
302         @Override
303         public final int getScreenWidth()
304         {
305                 try {
306                         return c.getToolkit().getScreenSize().width;
307                 } catch (HeadlessException ex) {
308                         return 1024;
309                 }
310         }
311
312         @Override
313         public final int getScreenHeight()
314         {
315                 try {
316                         return c.getToolkit().getScreenSize().height;
317                 } catch (HeadlessException ex) {
318                         return 768;
319                 }
320         }
321
322         @Override
323         public final boolean hasBaseline()
324         {
325                 if (bl == null) {
326                         try {
327                                 // Removed since OTHER is sometimes returned even though there is a valid baseline (e.g. an empty JComboBox)
328 //                              if (c.getBaselineResizeBehavior() == Component.BaselineResizeBehavior.OTHER) {
329 //                                      bl = Boolean.FALSE;
330 //                              } else {
331                                         // 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.
332 //                                      Dimension d = c.getPreferredSize();
333 //                                      bl = getBaseline(d.width, d.height) > -1;
334                                         bl = getBaseline(8192, 8192) > -1;  // Use large number but don't risk overflow or exposing size bugs with Integer.MAX_VALUE
335 //                              }
336                         } catch (Throwable ex) {
337                                 bl = Boolean.FALSE;
338                         }
339                 }
340                 return bl;
341         }
342
343         @Override
344         public final String getLinkId()
345         {
346                 return c.getName();
347         }
348
349         @Override
350         public final void setBounds(int x, int y, int width, int height)
351         {
352                 c.setBounds(x, y, width, height);
353         }
354
355         @Override
356         public boolean isVisible()
357         {
358                 return c.isVisible();
359         }
360
361         @Override
362         public final int[] getVisualPadding()
363         {
364                 int[] padding = null;
365                 if (isVisualPaddingEnabled()) {
366                         //First try "visualPadding" client property
367                         if (c instanceof JComponent) {
368                                 JComponent component = (JComponent) c;
369                                 Object padValue = component.getClientProperty(VISUAL_PADDING_PROPERTY);
370
371                                 if (padValue instanceof int[] ) {
372                                         //client property value could be an int[]
373                                         padding = (int[]) padValue;
374                                 } else if (padValue instanceof Insets) {
375                                         //OR client property value could be an Insets
376                                         Insets padInsets = (Insets) padValue;
377                                         padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right };
378                                 }
379
380                                 if (padding == null) {
381                                         //No client property set on the individual JComponent,
382                                         //      so check for a LAF setting for the component type.
383                                         String classID;
384                                         switch (getComponentType(false)) {
385                                                 case TYPE_BUTTON:
386                                                         Border border = component.getBorder();
387                                                         if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) {
388                                                                 if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) {
389                                                                         Object buttonType = component.getClientProperty("JButton.buttonType");
390                                                                         if (buttonType == null) {
391                                                                                 classID = component.getHeight() < 33 ? "Button" : "Button.bevel";
392                                                                         } else {
393                                                                                 classID = "Button." + buttonType;
394                                                                         }
395                                                                         if (((AbstractButton) component).getIcon() != null)
396                                                                                 classID += ".icon";
397                                                                 } else {
398                                                                         classID = "Button";
399                                                                 }
400                                                         } else {
401                                                                 classID = "";
402                                                         }
403                                                         break;
404
405                                                 case TYPE_CHECK_BOX:
406                                                         border = component.getBorder();
407                                                         if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) {
408                                                                 Object size = component.getClientProperty("JComponent.sizeVariant");
409                                                                 if (size != null && size.toString().equals("regular") == false) {
410                                                                         size = "." + size;
411                                                                 } else {
412                                                                         size = "";
413                                                                 }
414
415                                                                 if (component instanceof JRadioButton) {
416                                                                         classID = "RadioButton" + size;
417                                                                 } else if (component instanceof JCheckBox) {
418                                                                         classID = "CheckBox" + size;
419                                                                 } else {
420                                                                         classID = "ToggleButton" + size;
421                                                                 }
422                                                         } else {
423                                                                 classID = "";
424                                                         }
425                                                         break;
426
427                                                 case TYPE_COMBO_BOX:
428                                                         if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) {
429                                                                 if (((JComboBox) component).isEditable()) {
430                                                                         Object isSquare = component.getClientProperty("JComboBox.isSquare");
431                                                                         if (isSquare != null && isSquare.toString().equals("true")) {
432                                                                                 classID = "ComboBox.editable.isSquare";
433                                                                         } else {
434                                                                                 classID = "ComboBox.editable";
435                                                                         }
436
437                                                                 } else {
438                                                                         Object isSquare = component.getClientProperty("JComboBox.isSquare");
439                                                                         Object isPopDown = component.getClientProperty("JComboBox.isPopDown");
440
441                                                                         if (isSquare != null && isSquare.toString().equals("true")) {
442                                                                                 classID = "ComboBox.isSquare";
443                                                                         } else if (isPopDown != null && isPopDown.toString().equals("true")) {
444                                                                                 classID = "ComboBox.isPopDown";
445                                                                         } else {
446                                                                                 classID = "ComboBox";
447                                                                         }
448                                                                 }
449                                                         } else {
450                                                                 classID = "ComboBox";
451                                                         }
452                                                         break;
453                                                 case TYPE_CONTAINER:
454                                                         classID = "Container";
455                                                         break;
456                                                 case TYPE_IMAGE:
457                                                         classID = "Image";
458                                                         break;
459                                                 case TYPE_LABEL:
460                                                         classID = "Label";
461                                                         break;
462                                                 case TYPE_LIST:
463                                                         classID = "List";
464                                                         break;
465                                                 case TYPE_PANEL:
466                                                         classID = "Panel";
467                                                         break;
468                                                 case TYPE_PROGRESS_BAR:
469                                                         classID = "ProgressBar";
470                                                         break;
471                                                 case TYPE_SCROLL_BAR:
472                                                         classID = "ScrollBar";
473                                                         break;
474                                                 case TYPE_SCROLL_PANE:
475                                                         classID = "ScrollPane";
476                                                         break;
477                                                 case TYPE_SEPARATOR:
478                                                         classID = "Separator";
479                                                         break;
480                                                 case TYPE_SLIDER:
481                                                         classID = "Slider";
482                                                         break;
483                                                 case TYPE_SPINNER:
484                                                         classID = "Spinner";
485                                                         break;
486                                                 case TYPE_TABLE:
487                                                         classID = "Table";
488                                                         break;
489                                                 case TYPE_TABBED_PANE:
490                                                         classID = "TabbedPane";
491                                                         break;
492                                                 case TYPE_TEXT_AREA:
493                                                         classID = "TextArea";
494                                                         break;
495                                                 case TYPE_TEXT_FIELD:
496                                                         border = component.getBorder();
497                                                         if (!component.isOpaque() && border != null && border.getClass().getSimpleName().equals("AquaTextFieldBorder")) {
498                                                                 classID = "TextField";
499                                                         } else {
500                                                                 classID = "";
501                                                         }
502                                                         break;
503                                                 case TYPE_TREE:
504                                                         classID = "Tree";
505                                                         break;
506                                                 case TYPE_UNKNOWN:
507                                                         classID = "Other";
508                                                         break;
509                                                 case TYPE_UNSET:
510                                                 default:
511                                                         classID = "";
512                                                         break;
513                                         }
514
515                                         padValue = PlatformDefaults.getDefaultVisualPadding(classID + "." + VISUAL_PADDING_PROPERTY);
516                                         if (padValue instanceof int[]) {
517                                                 //client property value could be an int[]
518                                                 padding = (int[]) padValue;
519                                         } else if (padValue instanceof Insets) {
520                                                 //OR client property value could be an Insets
521                                                 Insets padInsets = (Insets) padValue;
522                                                 padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right };
523                                         }
524                                 }
525                         }
526                 }
527                 return padding;
528         }
529
530         /**
531          * @deprecated Java 1.4 is not supported anymore
532          */
533         public static boolean isMaxSizeSetOn1_4()
534         {
535                 return maxSet;
536         }
537
538         /**
539          * @deprecated Java 1.4 is not supported anymore
540          */
541         public static void setMaxSizeSetOn1_4(boolean b)
542         {
543                 maxSet = b;
544         }
545
546         public static boolean isVisualPaddingEnabled()
547         {
548                 return vp;
549         }
550
551         public static void setVisualPaddingEnabled(boolean b)
552         {
553                 vp = b;
554         }
555
556         @Override
557         public final void paintDebugOutline(boolean showVisualPadding)
558         {
559                 if (c.isShowing() == false)
560                         return;
561
562                 Graphics2D g = (Graphics2D) c.getGraphics();
563                 if (g == null)
564                         return;
565
566                 g.setPaint(DB_COMP_OUTLINE);
567                 g.setStroke(new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[] {2f, 4f}, 0));
568                 g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
569
570                 if (showVisualPadding && isVisualPaddingEnabled()) {
571                         int[] padding = getVisualPadding();
572                         if (padding != null) {
573                                 g.setColor(Color.GREEN);
574                                 g.drawRect(padding[1], padding[0], (getWidth() - 1) - (padding[1] + padding[3]), (getHeight() - 1) - (padding[0] + padding[2]));
575                         }
576                 }
577         }
578
579         @Override
580         public int getComponentType(boolean disregardScrollPane)
581         {
582                 if (compType == TYPE_UNSET)
583                         compType = checkType(disregardScrollPane);
584
585                 return compType;
586         }
587
588         @Override
589         public int getLayoutHashCode()
590         {
591                 Dimension d = c.getMaximumSize();
592                 int hash = d.width + (d.height << 5);
593
594                 d = c.getPreferredSize();
595                 hash += (d.width << 10) + (d.height << 15);
596
597                 d = c.getMinimumSize();
598                 hash += (d.width << 20) + (d.height << 25);
599
600                 if (c.isVisible())
601                         hash += 1324511;
602
603                 String id = getLinkId();
604                 if (id != null)
605                         hash += id.hashCode();
606
607                 return hash;
608         }
609
610         private int checkType(boolean disregardScrollPane)
611         {
612                 Component c = this.c;
613
614                 if (disregardScrollPane) {
615                         if (c instanceof JScrollPane) {
616                                 c = ((JScrollPane) c).getViewport().getView();
617                         } else if (c instanceof ScrollPane) {
618                                 c = ((ScrollPane) c).getComponent(0);
619                         }
620                 }
621
622                 if (c instanceof JTextField || c instanceof TextField) {
623                         return TYPE_TEXT_FIELD;
624                 } else if (c instanceof JLabel || c instanceof Label) {
625                         return TYPE_LABEL;
626                 } else if (c instanceof JCheckBox || c instanceof JRadioButton || c instanceof Checkbox) {
627                         return TYPE_CHECK_BOX;
628                 } else if (c instanceof AbstractButton || c instanceof Button) {
629                         return TYPE_BUTTON;
630                 } else if (c instanceof JComboBox || c instanceof Choice) {
631                         return TYPE_COMBO_BOX;
632                 } else if (c instanceof JTextComponent || c instanceof TextComponent) {
633                         return TYPE_TEXT_AREA;
634                 } else if (c instanceof JPanel || c instanceof Canvas) {
635                         return TYPE_PANEL;
636                 } else if (c instanceof JList || c instanceof List) {
637                         return TYPE_LIST;
638                 } else if (c instanceof JTable) {
639                         return TYPE_TABLE;
640                 } else if (c instanceof JSeparator) {
641                         return TYPE_SEPARATOR;
642                 } else if (c instanceof JSpinner) {
643                         return TYPE_SPINNER;
644                 } else if (c instanceof JTabbedPane) {
645                         return TYPE_TABBED_PANE;
646                 } else if (c instanceof JProgressBar) {
647                         return TYPE_PROGRESS_BAR;
648                 } else if (c instanceof JSlider) {
649                         return TYPE_SLIDER;
650                 } else if (c instanceof JScrollPane) {
651                         return TYPE_SCROLL_PANE;
652                 } else if (c instanceof JScrollBar || c instanceof Scrollbar) {
653                         return TYPE_SCROLL_BAR;
654                 } else if (c instanceof Container) {    // only AWT components is not containers.
655                         return TYPE_CONTAINER;
656                 }
657                 return TYPE_UNKNOWN;
658         }
659
660         @Override
661         public final int hashCode()
662         {
663                 return getComponent().hashCode();
664         }
665
666         @Override
667         public final boolean equals(Object o)
668         {
669                 if (o instanceof ComponentWrapper == false)
670                         return false;
671
672                 return c.equals(((ComponentWrapper) o).getComponent());
673         }
674
675         @Override
676         public int getContentBias()
677         {
678                 return c instanceof JTextArea || c instanceof JEditorPane || (c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty("migLayout.dynamicAspectRatio"))) ? LayoutUtil.HORIZONTAL : -1;
679         }
680 }