1 package net.miginfocom.swing;
6 * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com)
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.
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
32 * @author Mikael Grev, MiG InfoCom AB
36 import net.miginfocom.layout.ComponentWrapper;
37 import net.miginfocom.layout.ContainerWrapper;
38 import net.miginfocom.layout.LayoutUtil;
39 import net.miginfocom.layout.PlatformDefaults;
42 import javax.swing.border.Border;
43 import javax.swing.text.JTextComponent;
45 import java.awt.geom.Rectangle2D;
46 import java.util.IdentityHashMap;
47 import java.util.StringTokenizer;
51 public class SwingComponentWrapper implements ComponentWrapper
53 private static boolean maxSet = false;
55 private static boolean vp = true;
57 /** Debug color for component bounds outline.
59 private static final Color DB_COMP_OUTLINE = new Color(0, 0, 200);
61 /** Property to use in LAF settings and as JComponent client property
62 * to specify the visual padding.
65 private static final String VISUAL_PADDING_PROPERTY = net.miginfocom.layout.PlatformDefaults.VISUAL_PADDING_PROPERTY;
67 private final Component c;
68 private int compType = TYPE_UNSET;
69 private Boolean bl = null;
70 private boolean prefCalled = false;
72 public SwingComponentWrapper(Component c)
78 public final int getBaseline(int width, int height)
81 int[] visPad = getVisualPadding();
84 } else if (visPad != null) {
85 h = height + visPad[0] + visPad[2];
87 int baseLine = c.getBaseline(width < 0 ? c.getWidth() : width, h);
88 if (baseLine != -1 && visPad != null)
89 baseLine -= visPad[0];
95 public final Object getComponent()
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);
106 public final float getPixelUnitFactor(boolean isHor)
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);
114 Rectangle2D r = fm.getStringBounds("X", c.getGraphics());
115 p = new Point.Float(((float) r.getWidth()) / 6f, ((float) r.getHeight()) / 13.27734375f);
118 return isHor ? p.x : p.y;
120 case PlatformDefaults.BASE_SCALE_FACTOR:
122 Float s = isHor ? PlatformDefaults.getHorizontalScaleFactor() : PlatformDefaults.getVerticalScaleFactor();
123 float scaleFactor = (s != null) ? s : 1f;
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;
139 private static boolean isJava9orLater = true;
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
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);
156 // public float getDialogUnit(boolean isHor)
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;
166 // dluP = new Point.Float(w, h);
167 // FM_MAP2.put(fm, dluP);
169 // return isHor ? dluP.x : dluP.y;
173 public final int getX()
179 public final int getY()
185 public final int getHeight()
187 return c.getHeight();
191 public final int getWidth()
197 public final int getScreenLocationX()
199 Point p = new Point();
200 SwingUtilities.convertPointToScreen(p, c);
205 public final int getScreenLocationY()
207 Point p = new Point();
208 SwingUtilities.convertPointToScreen(p, c);
213 public final int getMinimumHeight(int sz)
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();
219 return c.getMinimumSize().height;
223 public final int getMinimumWidth(int sz)
225 System.out.println("SCW " + c.getName() + " " + c.getClass().getName() + " " + c.getMinimumSize() + " " + c.getPreferredSize() + " " + c.getMinimumSize());
226 if (prefCalled == false) {
227 c.getPreferredSize(); // To defeat a bug where the minimum size is different before and after the first call to getPreferredSize();
231 return c.getMinimumSize().width;
234 public final int getPreferredHeight(int sz)
236 // If the component has not gotten size yet and there is a size hint, trick Swing to return a better height.
237 if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1)
238 c.setBounds(c.getX(), c.getY(), sz, 1);
240 return c.getPreferredSize().height;
244 public final int getPreferredWidth(int sz)
246 // If the component has not gotten size yet and there is a size hint, trick Swing to return a better height.
247 if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1)
248 c.setBounds(c.getX(), c.getY(), 1, sz);
250 return c.getPreferredSize().width;
254 public final int getMaximumHeight(int sz)
257 return Integer.MAX_VALUE;
259 return c.getMaximumSize().height;
263 public final int getMaximumWidth(int sz)
266 return Integer.MAX_VALUE;
268 return c.getMaximumSize().width;
272 private boolean isMaxSet(Component c)
274 return c.isMaximumSizeSet();
278 public final ContainerWrapper getParent()
280 Container p = c.getParent();
281 return p != null ? new SwingContainerWrapper(p) : null;
285 public final int getHorizontalScreenDPI() {
287 return c.getToolkit().getScreenResolution();
288 } catch (HeadlessException ex) {
289 return PlatformDefaults.getDefaultDPI();
294 public final int getVerticalScreenDPI()
297 return c.getToolkit().getScreenResolution();
298 } catch (HeadlessException ex) {
299 return PlatformDefaults.getDefaultDPI();
304 public final int getScreenWidth()
307 return c.getToolkit().getScreenSize().width;
308 } catch (HeadlessException ex) {
314 public final int getScreenHeight()
317 return c.getToolkit().getScreenSize().height;
318 } catch (HeadlessException ex) {
324 public final boolean hasBaseline()
328 // Removed since OTHER is sometimes returned even though there is a valid baseline (e.g. an empty JComboBox)
329 // if (c.getBaselineResizeBehavior() == Component.BaselineResizeBehavior.OTHER) {
330 // bl = Boolean.FALSE;
332 // 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.
333 // Dimension d = c.getPreferredSize();
334 // bl = getBaseline(d.width, d.height) > -1;
335 bl = getBaseline(8192, 8192) > -1; // Use large number but don't risk overflow or exposing size bugs with Integer.MAX_VALUE
337 } catch (Throwable ex) {
345 public final String getLinkId()
351 public final void setBounds(int x, int y, int width, int height)
353 c.setBounds(x, y, width, height);
357 public boolean isVisible()
359 return c.isVisible();
363 public final int[] getVisualPadding()
365 int[] padding = null;
366 if (isVisualPaddingEnabled()) {
367 //First try "visualPadding" client property
368 if (c instanceof JComponent) {
369 JComponent component = (JComponent) c;
370 Object padValue = component.getClientProperty(VISUAL_PADDING_PROPERTY);
372 if (padValue instanceof int[] ) {
373 //client property value could be an int[]
374 padding = (int[]) padValue;
375 } else if (padValue instanceof Insets) {
376 //OR client property value could be an Insets
377 Insets padInsets = (Insets) padValue;
378 padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right };
381 if (padding == null) {
382 //No client property set on the individual JComponent,
383 // so check for a LAF setting for the component type.
385 switch (getComponentType(false)) {
387 Border border = component.getBorder();
388 if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) {
389 if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) {
390 Object buttonType = component.getClientProperty("JButton.buttonType");
391 if (buttonType == null) {
392 classID = component.getHeight() < 33 ? "Button" : "Button.bevel";
394 classID = "Button." + buttonType;
396 if (((AbstractButton) component).getIcon() != null)
407 border = component.getBorder();
408 if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) {
409 Object size = component.getClientProperty("JComponent.sizeVariant");
410 if (size != null && size.toString().equals("regular") == false) {
416 if (component instanceof JRadioButton) {
417 classID = "RadioButton" + size;
418 } else if (component instanceof JCheckBox) {
419 classID = "CheckBox" + size;
421 classID = "ToggleButton" + size;
429 if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) {
430 if (((JComboBox) component).isEditable()) {
431 Object isSquare = component.getClientProperty("JComboBox.isSquare");
432 if (isSquare != null && isSquare.toString().equals("true")) {
433 classID = "ComboBox.editable.isSquare";
435 classID = "ComboBox.editable";
439 Object isSquare = component.getClientProperty("JComboBox.isSquare");
440 Object isPopDown = component.getClientProperty("JComboBox.isPopDown");
442 if (isSquare != null && isSquare.toString().equals("true")) {
443 classID = "ComboBox.isSquare";
444 } else if (isPopDown != null && isPopDown.toString().equals("true")) {
445 classID = "ComboBox.isPopDown";
447 classID = "ComboBox";
451 classID = "ComboBox";
455 classID = "Container";
469 case TYPE_PROGRESS_BAR:
470 classID = "ProgressBar";
472 case TYPE_SCROLL_BAR:
473 classID = "ScrollBar";
475 case TYPE_SCROLL_PANE:
476 classID = "ScrollPane";
479 classID = "Separator";
490 case TYPE_TABBED_PANE:
491 classID = "TabbedPane";
494 classID = "TextArea";
496 case TYPE_TEXT_FIELD:
497 border = component.getBorder();
498 if (!component.isOpaque() && border != null && border.getClass().getSimpleName().equals("AquaTextFieldBorder")) {
499 classID = "TextField";
516 padValue = PlatformDefaults.getDefaultVisualPadding(classID + "." + VISUAL_PADDING_PROPERTY);
517 if (padValue instanceof int[]) {
518 //client property value could be an int[]
519 padding = (int[]) padValue;
520 } else if (padValue instanceof Insets) {
521 //OR client property value could be an Insets
522 Insets padInsets = (Insets) padValue;
523 padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right };
532 * @deprecated Java 1.4 is not supported anymore
534 public static boolean isMaxSizeSetOn1_4()
540 * @deprecated Java 1.4 is not supported anymore
542 public static void setMaxSizeSetOn1_4(boolean b)
547 public static boolean isVisualPaddingEnabled()
552 public static void setVisualPaddingEnabled(boolean b)
558 public final void paintDebugOutline(boolean showVisualPadding)
560 if (c.isShowing() == false)
563 Graphics2D g = (Graphics2D) c.getGraphics();
567 g.setPaint(DB_COMP_OUTLINE);
568 g.setStroke(new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[] {2f, 4f}, 0));
569 g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
571 if (showVisualPadding && isVisualPaddingEnabled()) {
572 int[] padding = getVisualPadding();
573 if (padding != null) {
574 g.setColor(Color.GREEN);
575 g.drawRect(padding[1], padding[0], (getWidth() - 1) - (padding[1] + padding[3]), (getHeight() - 1) - (padding[0] + padding[2]));
581 public int getComponentType(boolean disregardScrollPane)
583 if (compType == TYPE_UNSET)
584 compType = checkType(disregardScrollPane);
590 public int getLayoutHashCode()
592 Dimension d = c.getMaximumSize();
593 int hash = d.width + (d.height << 5);
595 d = c.getPreferredSize();
596 hash += (d.width << 10) + (d.height << 15);
598 d = c.getMinimumSize();
599 hash += (d.width << 20) + (d.height << 25);
604 String id = getLinkId();
606 hash += id.hashCode();
611 private int checkType(boolean disregardScrollPane)
613 Component c = this.c;
615 if (disregardScrollPane) {
616 if (c instanceof JScrollPane) {
617 c = ((JScrollPane) c).getViewport().getView();
618 } else if (c instanceof ScrollPane) {
619 c = ((ScrollPane) c).getComponent(0);
623 if (c instanceof JTextField || c instanceof TextField) {
624 return TYPE_TEXT_FIELD;
625 } else if (c instanceof JLabel || c instanceof Label) {
627 } else if (c instanceof JCheckBox || c instanceof JRadioButton || c instanceof Checkbox) {
628 return TYPE_CHECK_BOX;
629 } else if (c instanceof AbstractButton || c instanceof Button) {
631 } else if (c instanceof JComboBox || c instanceof Choice) {
632 return TYPE_COMBO_BOX;
633 } else if (c instanceof JTextComponent || c instanceof TextComponent) {
634 return TYPE_TEXT_AREA;
635 } else if (c instanceof JPanel || c instanceof Canvas) {
637 } else if (c instanceof JList || c instanceof List) {
639 } else if (c instanceof JTable) {
641 } else if (c instanceof JSeparator) {
642 return TYPE_SEPARATOR;
643 } else if (c instanceof JSpinner) {
645 } else if (c instanceof JTabbedPane) {
646 return TYPE_TABBED_PANE;
647 } else if (c instanceof JProgressBar) {
648 return TYPE_PROGRESS_BAR;
649 } else if (c instanceof JSlider) {
651 } else if (c instanceof JScrollPane) {
652 return TYPE_SCROLL_PANE;
653 } else if (c instanceof JScrollBar || c instanceof Scrollbar) {
654 return TYPE_SCROLL_BAR;
655 } else if (c instanceof Container) { // only AWT components is not containers.
656 return TYPE_CONTAINER;
662 public final int hashCode()
664 return getComponent().hashCode();
668 public final boolean equals(Object o)
670 if (o instanceof ComponentWrapper == false)
673 return c.equals(((ComponentWrapper) o).getComponent());
677 public int getContentBias()
679 return c instanceof JTextArea || c instanceof JEditorPane || (c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty("migLayout.dynamicAspectRatio"))) ? LayoutUtil.HORIZONTAL : -1;