001 /* JAPI - (Yet another (hopefully) useful) Java API 002 * 003 * Copyright (C) 2004-2006 Christian Hujer 004 * 005 * This program is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU General Public License as 007 * published by the Free Software Foundation; either version 2 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, but 011 * WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program; if not, write to the Free Software 017 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 018 * 02111-1307, USA. 019 */ 020 021 package net.sf.japi.swing; 022 023 import java.awt.BorderLayout; 024 import java.awt.Component; 025 import java.awt.Container; 026 import java.awt.Dimension; 027 import java.awt.Insets; 028 import java.io.Serializable; 029 import static java.lang.Math.max; 030 import java.util.ArrayList; 031 import java.util.List; 032 import javax.swing.JToolBar; 033 034 /** A LayoutManager that manages a layout of a {@link Container} similar to {@link BorderLayout} but with an important difference, it is possible to 035 * add as many components to a side layout region as you want. The desired purpose is to serve as LayoutManager for containers that shall contain 036 * toolbars. So this is a LayoutManager you always were looking for. 037 * <p /> 038 * Technically, this class is not a 100% replacement for {@link BorderLayout}. {@link JToolBar}'s UI ({@link 039 * javax.swing.plaf.basic.BasicToolBarUI}) directly looks for some features of the class {@link BorderLayout}, and if it is not {@link BorderLayout}, 040 * it uses some defaults. 041 * This class has been developed to make these defaults work as good as possible. 042 * Though this class doesn't technically replace {@link BorderLayout} - neither is this class a subclass of {@link BorderLayout} nor does it provide 043 * <em>all</em> methods {@link BorderLayout} does - it still does practically. 044 * <p /> 045 * The constant values {@link #NORTH}, {@link #SOUTH}, {@link #EAST}, {@link #WEST} and {@link #CENTER} are references to those of {@link 046 * BorderLayout}. 047 * The behaviour of {@link #CENTER} is that of {@link BorderLayout}: only one component can be added, subsequently added components overried all 048 * previously added. 049 * <p /> 050 * The behaviour of {@link #NORTH}, {@link #SOUTH}, {@link #EAST} and {@link #WEST} differs from {@link BorderLayout}. The position and layout 051 * behaviour is the same, with the slight difference that this LayoutManager is able to manage more than on single component in these four regions. 052 * Subsequently added components are placed from the outer to the inner. The first added component is the outmost component of that region, the last 053 * added component is the innermost component of that region. To place a component to the innermost level, simply add it to the same region again. 054 * <p /> 055 * Placing a component another level than the innermost of its destination region is currently not supported but might well be supported in future. 056 * <p /> 057 * There are four possible ways of specifying a constraint: 058 * <ul> 059 * <li>Use the geographical region constants from {@link BorderLayout}</li> 060 * <li>Use the geographical region constraints from this class</li> 061 * <li>Use an instance of {@link ToolBarConstraints}</li> 062 * <li>Use an enum region constraint from {@link ToolBarConstraints.Region}</li> 063 * </ul> 064 * . The constraint may be one of the String constants from this class or {@link BorderLayout} or it may 065 * be a {@link ToolBarConstraints}. 066 * @todo support rtl containers the same way as {@link BorderLayout} does it. 067 * @todo test {@link ToolBarConstraints} and {@link ToolBarConstraints.Region} 068 * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a> 069 * @see BorderLayout 070 * @see JToolBar 071 */ 072 @SuppressWarnings({"NonPrivateFieldAccessedInSynchronizedContext", "FieldAccessedSynchronizedAndUnsynchronized"}) 073 public class ToolBarLayout extends BorderLayout { 074 075 /** Serial Version. */ 076 @SuppressWarnings({"AnalyzingVariableNaming"}) 077 private static final long serialVersionUID = 1L; 078 079 /** Horizontal Gap. 080 * @serial include 081 */ 082 private int hgap; 083 084 /** Vertical Gap. 085 * @serial include 086 */ 087 private int vgap; 088 089 /** Components in the north region. 090 * @serial include 091 */ 092 private List<Component> north = new ArrayList<Component>(); 093 094 /** Components in the south region. 095 * @serial include 096 */ 097 private List<Component> south = new ArrayList<Component>(); 098 099 /** Components in the east region. 100 * @serial include 101 */ 102 private List<Component> east = new ArrayList<Component>(); 103 104 /** Components in the west region. 105 * @serial include 106 */ 107 private List<Component> west = new ArrayList<Component>(); 108 109 /** Component in the center region. 110 * @serial include 111 */ 112 private Component center; 113 114 /** Create a ToolBarLayout with zero gaps. */ 115 public ToolBarLayout() { 116 this(0, 0); 117 } 118 119 /** Create a TooLBarLayout. 120 * @param hgap horizontal gap between components 121 * @param vgap vertical gap between components 122 */ 123 public ToolBarLayout(final int hgap, final int vgap) { 124 this.hgap = hgap; 125 this.vgap = vgap; 126 } 127 128 private void addLayoutComponent(final ToolBarConstraints.Region region, final Component comp) { 129 synchronized (comp.getTreeLock()) { 130 List<Component> list = null; 131 switch (region) { 132 case NORTH: list = north; break; 133 case SOUTH: list = south; break; 134 case EAST: list = east; break; 135 case WEST: list = west; break; 136 case CENTER: center = comp; return; 137 } 138 assert list != null; 139 list.add(comp); 140 } 141 } 142 143 private void addLayoutComponent(final ToolBarConstraints constraints, final Component comp) { 144 synchronized (comp.getTreeLock()) { 145 List<Component> list = null; 146 switch (constraints.region) { 147 case NORTH: list = north; break; 148 case SOUTH: list = south; break; 149 case EAST: list = east; break; 150 case WEST: list = west; break; 151 case CENTER: center = comp; return; 152 } 153 int pos = constraints.position; 154 assert list != null; 155 if (pos > list.size() || pos < 0) { 156 pos = list.size(); 157 } 158 list.add(pos, comp); 159 } 160 } 161 162 /** {@inheritDoc} */ 163 @SuppressWarnings({"deprecation"}) 164 @Override public void addLayoutComponent(final String name, final Component comp) { 165 synchronized (comp.getTreeLock()) { 166 if (name == null || CENTER.equals(name)) { 167 center = comp; 168 } else if (NORTH.equals(name)) { 169 north.add(comp); 170 } else if (SOUTH.equals(name)) { 171 south.add(comp); 172 } else if (EAST.equals(name)) { 173 east.add(comp); 174 } else if (WEST.equals(name)) { 175 west.add(comp); 176 } else { 177 throw new IllegalArgumentException("cannot add to layout: unknown constraint: " + name); 178 } 179 } 180 } 181 182 /** {@inheritDoc} */ 183 @Override public void addLayoutComponent(final Component comp, final Object constraints) { 184 synchronized (comp.getTreeLock()) { 185 if (constraints == null || constraints instanceof String) { 186 addLayoutComponent((String)constraints, comp); 187 } else if (constraints instanceof ToolBarConstraints) { 188 addLayoutComponent((ToolBarConstraints)constraints, comp); 189 } else if (constraints instanceof ToolBarConstraints.Region) { 190 addLayoutComponent((ToolBarConstraints.Region)constraints, comp); 191 } else { 192 throw new IllegalArgumentException("cannot add to layout: constraint must be a string (or null)"); 193 } 194 } 195 } 196 197 /** {@inheritDoc} */ 198 @Override public float getLayoutAlignmentX(final Container parent) { 199 return 0.5f; 200 } 201 202 /** {@inheritDoc} */ 203 @Override public float getLayoutAlignmentY(final Container parent) { 204 return 0.5f; 205 } 206 207 /** {@inheritDoc} */ 208 @Override public void invalidateLayout(final Container target) { 209 /* Do Nothing, like BorderLayout. */ 210 } 211 212 /** {@inheritDoc} */ 213 @Override public void layoutContainer(final Container target) { 214 synchronized (target.getTreeLock()) { 215 final Insets insets = target.getInsets(); 216 Dimension dim; 217 int top = insets.top; 218 int bottom = target.getHeight() - insets.bottom; 219 int left = insets.left; 220 int right = target.getWidth() - insets.right; 221 for (final Component comp : north) { 222 if (comp.isVisible()) { 223 comp.setSize(right - left, comp.getHeight()); 224 dim = comp.getPreferredSize(); 225 comp.setBounds(left, top, right - left, dim.height); 226 top += dim.height + vgap; 227 } 228 } 229 for (final Component comp : south) { 230 if (comp.isVisible()) { 231 comp.setSize(right - left, comp.getHeight()); 232 dim = comp.getPreferredSize(); 233 comp.setBounds(left, bottom - dim.height, right - left, dim.height); 234 bottom -= dim.height + vgap; 235 } 236 } 237 for (final Component comp : east) { 238 if (comp.isVisible()) { 239 comp.setSize(comp.getWidth(), bottom - top); 240 dim = comp.getPreferredSize(); 241 comp.setBounds(right - dim.width, top, dim.width, bottom - top); 242 right -= dim.width + hgap; 243 } 244 } 245 for (final Component comp : west) { 246 if (comp.isVisible()) { 247 comp.setSize(comp.getWidth(), bottom - top); 248 dim = comp.getPreferredSize(); 249 comp.setBounds(left, top, dim.width, bottom - top); 250 left += dim.width + hgap; 251 } 252 } 253 if (center != null) { 254 center.setBounds(left, top, right - left, bottom - top); 255 } 256 } 257 } 258 259 /** {@inheritDoc} */ 260 @Override public Dimension maximumLayoutSize(final Container target) { 261 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); 262 } 263 264 /** {@inheritDoc} */ 265 @Override public Dimension minimumLayoutSize(final Container target) { 266 synchronized (target.getTreeLock()) { 267 Dimension dim; 268 final Dimension minimumLayoutSize = new Dimension(0, 0); 269 for (final Component comp : east) { 270 dim = comp.getMinimumSize(); 271 minimumLayoutSize.width += dim.width + hgap; 272 minimumLayoutSize.height = max(dim.height, minimumLayoutSize.height); 273 } 274 for (final Component comp : west) { 275 dim = comp.getMinimumSize(); 276 minimumLayoutSize.width += dim.width + hgap; 277 minimumLayoutSize.height = max(dim.height, minimumLayoutSize.height); 278 } 279 if (center != null) { 280 dim = center.getMinimumSize(); 281 minimumLayoutSize.width += dim.width; 282 minimumLayoutSize.height =max(dim.height, minimumLayoutSize.height); 283 } 284 for (final Component comp : north) { 285 dim = comp.getMinimumSize(); 286 minimumLayoutSize.width = max(dim.width, minimumLayoutSize.width); 287 minimumLayoutSize.height += dim.height + vgap; 288 } 289 for (final Component comp : south) { 290 dim = comp.getMinimumSize(); 291 minimumLayoutSize.width = max(dim.width, minimumLayoutSize.width); 292 minimumLayoutSize.height += dim.height + vgap; 293 } 294 final Insets insets = target.getInsets(); 295 minimumLayoutSize.width += insets.left + insets.right; 296 minimumLayoutSize.height += insets.top + insets.bottom; 297 return minimumLayoutSize; 298 } 299 } 300 301 /** {@inheritDoc} */ 302 @Override public Dimension preferredLayoutSize(final Container target) { 303 synchronized (target.getTreeLock()) { 304 Dimension dim; 305 final Dimension preferredLayoutSize = new Dimension(0, 0); 306 for (final Component comp : east) { 307 dim = comp.getPreferredSize(); 308 preferredLayoutSize.width += dim.width + hgap; 309 preferredLayoutSize.height = max(dim.height, preferredLayoutSize.height); 310 } 311 for (final Component comp : west) { 312 dim = comp.getPreferredSize(); 313 preferredLayoutSize.width += dim.width + hgap; 314 preferredLayoutSize.height = max(dim.height, preferredLayoutSize.height); 315 } 316 if (center != null) { 317 dim = center.getPreferredSize(); 318 preferredLayoutSize.width += dim.width; 319 preferredLayoutSize.height =max(dim.height, preferredLayoutSize.height); 320 } 321 for (final Component comp : north) { 322 dim = comp.getPreferredSize(); 323 preferredLayoutSize.width = max(dim.width, preferredLayoutSize.width); 324 preferredLayoutSize.height += dim.height + vgap; 325 } 326 for (final Component comp : south) { 327 dim = comp.getPreferredSize(); 328 preferredLayoutSize.width = max(dim.width, preferredLayoutSize.width); 329 preferredLayoutSize.height += dim.height + vgap; 330 } 331 final Insets insets = target.getInsets(); 332 preferredLayoutSize.width += insets.left + insets.right; 333 preferredLayoutSize.height += insets.top + insets.bottom; 334 return preferredLayoutSize; 335 } 336 } 337 338 /** {@inheritDoc} */ 339 @Override public void removeLayoutComponent(final Component comp) { 340 synchronized (comp.getTreeLock()) { 341 //noinspection ObjectEquality 342 if (comp == center) { 343 center = null; 344 } else if (north.contains(comp)) { 345 north.remove(comp); 346 } else if (south.contains(comp)) { 347 south.remove(comp); 348 } else if (east.contains(comp)) { 349 east.remove(comp); 350 } else if (west.contains(comp)) { 351 west.remove(comp); 352 } 353 } 354 } 355 356 /** {@inheritDoc} */ 357 @Override public String toString() { 358 return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + ']'; 359 } 360 361 /** Class for ToolBarLayout constraints. 362 * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a> 363 */ 364 public static class ToolBarConstraints implements Serializable { 365 366 /** Serial Version. */ 367 @SuppressWarnings({"AnalyzingVariableNaming"}) 368 private static final long serialVersionUID = 1L; 369 370 /** Enum for region. 371 * @author <a href="mailto:chris@riedquat.de">Christian Hujer</a> 372 */ 373 public enum Region { 374 375 /** Constraint for center region. */ 376 CENTER, 377 378 /** Constraint for north region, last component. */ 379 NORTH, 380 381 /** Constraint for south region, last component. */ 382 SOUTH, 383 384 /** Constraint for east region, last component. */ 385 EAST, 386 387 /** Constraint for west region, last component. */ 388 WEST, 389 390 } // enum Region 391 392 /** Constant for first position (0). */ 393 public static final int FIRST = 0; 394 395 /** Constant for last position (-1). */ 396 public static final int LAST = -1; 397 398 /** Region constraint. 399 * @serial include 400 */ 401 public Region region; 402 403 /** Position constraint. 404 * @serial include 405 */ 406 public int position; 407 408 /** Create Constraint. */ 409 public ToolBarConstraints() { 410 } 411 412 /** Create Constraint with values. 413 * @param region Region 414 * @param position Position, must be non-negative 415 */ 416 public ToolBarConstraints(final Region region, final int position) { 417 this.region = region; 418 this.position = position; 419 } 420 421 } // class ToolBarConstraint 422 423 } // class ToolBarLayoutManager