001package org.jdesktop.swingx.plaf; 002 003import java.awt.Component; 004import java.awt.Container; 005import java.awt.Dimension; 006import java.awt.Graphics; 007import java.awt.Insets; 008import java.awt.LayoutManager; 009import java.awt.Rectangle; 010import java.beans.PropertyChangeEvent; 011import java.beans.PropertyChangeListener; 012 013import javax.swing.JTextField; 014import javax.swing.SwingUtilities; 015import javax.swing.border.Border; 016import javax.swing.plaf.UIResource; 017import javax.swing.plaf.basic.BasicBorders.MarginBorder; 018 019import org.jdesktop.swingx.prompt.BuddySupport; 020import org.jdesktop.swingx.prompt.BuddySupport.Position; 021 022public class BuddyLayoutAndBorder implements LayoutManager, Border, PropertyChangeListener, UIResource { 023 private JTextField textField; 024 025 private Border borderDelegate; 026 027 /** 028 * Installs a {@link BuddyLayoutAndBorder} as a layout and border of the 029 * given text field. Registers a {@link PropertyChangeListener} to wrap any 030 * subsequently set border on the text field. 031 */ 032 protected void install(JTextField textField) { 033 uninstall(); 034 this.textField = textField; 035 036 textField.setLayout(this); 037 038 replaceBorderIfNecessary(); 039 textField.addPropertyChangeListener("border", this); 040 } 041 042 public Border getBorderDelegate() { 043 return borderDelegate; 044 } 045 046 /** 047 * Wraps and replaces the text fields default border with this object, to 048 * honor the button margins and sizes of the search, clear and popup buttons 049 * and the layout style. 050 */ 051 protected void replaceBorderIfNecessary() { 052 Border original = textField.getBorder(); 053 054 if (!(original instanceof BuddyLayoutAndBorder)) { 055 borderDelegate = original; 056 textField.setBorder(this); 057 } 058 } 059 060 /** 061 * Does nothing. 062 * 063 * @see BuddySupport#add(javax.swing.JComponent, Position, JTextField) 064 */ 065 @Override 066 public void addLayoutComponent(String name, Component comp) { 067 } 068 069 @Override 070 public Dimension minimumLayoutSize(Container parent) { 071 return preferredLayoutSize(parent); 072 } 073 074 @Override 075 public Dimension preferredLayoutSize(Container parent) { 076 Dimension d = new Dimension(); 077 078 // height of highest buddy. 079 for (Component c : BuddySupport.getLeft(textField)) { 080 d.height = Math.max(d.height, c.getPreferredSize().height); 081 } 082 for (Component c : BuddySupport.getRight(textField)) { 083 d.height = Math.max(d.height, c.getPreferredSize().height); 084 } 085 086 Insets insets = getRealBorderInsets(); 087 d.height += insets.top + insets.bottom; 088 d.width += insets.left + insets.right; 089 090 Insets outerMargin = BuddySupport.getOuterMargin(textField); 091 if (outerMargin != null) { 092 d.width += outerMargin.left + outerMargin.right; 093 d.height += outerMargin.bottom + outerMargin.top; 094 } 095 096 return d; 097 } 098 099 /** 100 * Does nothing. 101 * 102 * @see BuddySupport#remove(javax.swing.JComponent, JTextField) 103 */ 104 @Override 105 public void removeLayoutComponent(Component comp) { 106 } 107 108 @Override 109 public void layoutContainer(Container parent) { 110 Rectangle visibleRect = getVisibleRect(); 111 Dimension size; 112 113 for (Component comp : BuddySupport.getLeft(textField)) { 114 if (!comp.isVisible()) { 115 continue; 116 } 117 size = comp.getPreferredSize(); 118 comp.setBounds(visibleRect.x, centerY(visibleRect, size), size.width, size.height); 119 120 visibleRect.x += size.width; 121 visibleRect.width -= size.width; 122 } 123 124 for (Component comp : BuddySupport.getRight(textField)) { 125 if (!comp.isVisible()) { 126 continue; 127 } 128 129 size = comp.getPreferredSize(); 130 comp.setBounds(visibleRect.x + visibleRect.width - size.width, centerY(visibleRect, size), size.width, 131 size.height); 132 visibleRect.width -= size.width; 133 } 134 } 135 136 protected int centerY(Rectangle rect, Dimension size) { 137 return (int) (rect.getCenterY() - (size.height / 2)); 138 } 139 140 /** 141 * @return the rectangle allocated by the text field, including the space 142 * allocated by the child components left and right, the text fields 143 * original border insets and the outer margin. 144 * 145 */ 146 protected Rectangle getVisibleRect() { 147 Rectangle alloc = SwingUtilities.getLocalBounds(textField); 148 149 substractInsets(alloc, getRealBorderInsets()); 150 substractInsets(alloc, BuddySupport.getOuterMargin(textField)); 151 152 return alloc; 153 } 154 155 private void substractInsets(Rectangle alloc, Insets insets) { 156 if (insets != null) { 157 alloc.x += insets.left; 158 alloc.y += insets.top; 159 alloc.width -= insets.left + insets.right; 160 alloc.height -= insets.top + insets.bottom; 161 } 162 } 163 164 /** 165 * Returns the {@link Insets} of the original {@link Border} plus the space 166 * required by the child components. 167 * 168 * @see javax.swing.border.Border#getBorderInsets(java.awt.Component) 169 */ 170 @Override 171 public Insets getBorderInsets(Component c) { 172 Insets insets = null; 173 if (borderDelegate != null) { 174 // Original insets are cloned to make it work in Mac OS X Aqua LnF. 175 // Seems that this LnF uses a shared insets instance which should 176 // not be modified. 177 // Include margin here 178 insets = (Insets) borderDelegate.getBorderInsets(textField).clone(); 179 } else { 180 insets = new Insets(0, 0, 0, 0); 181 } 182 //somehow this happens sometimes 183 if (textField == null) { 184 return insets; 185 } 186 187 for (Component comp : BuddySupport.getLeft(textField)) { 188 insets.left += comp.isVisible() ? comp.getPreferredSize().width : 0; 189 } 190 for (Component comp : BuddySupport.getRight(textField)) { 191 insets.right += comp.isVisible() ? comp.getPreferredSize().width : 0; 192 } 193 194 Insets outerMargin = BuddySupport.getOuterMargin(textField); 195 if (outerMargin != null) { 196 insets.left += outerMargin.left; 197 insets.right += outerMargin.right; 198 insets.top += outerMargin.top; 199 insets.bottom += outerMargin.bottom; 200 } 201 202 return insets; 203 } 204 205 /** 206 * Returns the insets of the original border (without the margin! Beware of 207 * {@link MarginBorder}!). 208 * 209 * @return the insets of the border delegate 210 */ 211 public Insets getRealBorderInsets() { 212 if (borderDelegate == null) { 213 //SwingX 1287: null borders are possible and give no insets 214 return new Insets(0, 0, 0, 0); 215 } 216 217 Insets insets = borderDelegate.getBorderInsets(textField); 218 219 // for some reason, all LnFs add the margin to the insets. 220 // we want the insets without the margin, so substract the margin here!! 221 // TODO: consider checking, if the current border really includes the 222 // margin. Consider: 223 // 1. Not only MarginBorder adds margin 224 // 2. Calling getBorderInsets(null) is not appropriate, since some 225 // Borders can't handle null values. 226 Insets margin = textField.getMargin(); 227 if (margin != null) { 228 insets.left -= margin.left; 229 insets.right -= margin.right; 230 insets.top -= margin.top; 231 insets.bottom -= margin.bottom; 232 } 233 234 return insets; 235 } 236 237 @Override 238 public boolean isBorderOpaque() { 239 if (borderDelegate == null) { 240 return false; 241 } 242 return borderDelegate.isBorderOpaque(); 243 } 244 245 @Override 246 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { 247 if (borderDelegate != null) { 248 borderDelegate.paintBorder(c, g, x, y, width, height); 249 } 250 } 251 252 @Override 253 public void propertyChange(PropertyChangeEvent evt) { 254 replaceBorderIfNecessary(); 255 } 256 257 public void uninstall() { 258 if (textField != null) { 259 textField.removePropertyChangeListener("border", this); 260 if (textField.getBorder() == this) { 261 textField.setBorder(borderDelegate); 262 } 263 textField.setLayout(null); 264 textField = null; 265 } 266 } 267 268 @Override 269 public String toString() { 270 return String.format("%s (%s): %s", getClass().getName(), getBorderInsets(null), borderDelegate); 271 } 272}