From: hansonr Date: Thu, 5 Jul 2018 08:35:09 +0000 (+0100) Subject: JAL-3026 fixes MigLayout, JSON parser X-Git-Tag: Release_2_11_4_0~45^2~18^2~586 X-Git-Url: http://source.jalview.org/gitweb/?a=commitdiff_plain;h=f7124dc8d666e958ad9925e846e5f46932838bf8;p=jalview.git JAL-3026 fixes MigLayout, JSON parser MiG layout was referencing java.beans only for serialization JSONParser was using a bitwise syntax for char not supported in transpiler SwingJS jalview.javascript.web proxy for WebResources not set up properly - JSONParser is working - PDB REST service call is working - MiGLayout is loading, but is not laying out properly. --- diff --git a/src/jalview/fts/core/FTSDataColumnPreferences.java b/src/jalview/fts/core/FTSDataColumnPreferences.java index cb6249e..e5042ae 100644 --- a/src/jalview/fts/core/FTSDataColumnPreferences.java +++ b/src/jalview/fts/core/FTSDataColumnPreferences.java @@ -155,7 +155,6 @@ public class FTSDataColumnPreferences extends JScrollPane int columnIndexToSort = 2; sortKeys.add(new RowSorter.SortKey(columnIndexToSort, SortOrder.ASCENDING)); - sorter.setSortKeys(sortKeys); sorter.setComparator(columnIndexToSort, new Comparator() { @@ -166,7 +165,8 @@ public class FTSDataColumnPreferences extends JScrollPane return o1.getSortOrder() - o2.getSortOrder(); } }); - sorter.sort(); + sorter.setSortKeys(sortKeys); + // BH 2018 setSortKeys does a sort sorter.sort(); tbl_FTSDataColumnPrefs .setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); diff --git a/src/jalview/fts/service/pdb/PDBFTSRestClient.java b/src/jalview/fts/service/pdb/PDBFTSRestClient.java index a03cf14..d8ab7de 100644 --- a/src/jalview/fts/service/pdb/PDBFTSRestClient.java +++ b/src/jalview/fts/service/pdb/PDBFTSRestClient.java @@ -83,7 +83,7 @@ public class PDBFTSRestClient extends FTSRestClient * @return the pdbResponse object for the given request * @throws Exception */ - @SuppressWarnings("unused") + @SuppressWarnings({ "unused", "unchecked" }) @Override public FTSRestResponse executeRequest(FTSRestRequest pdbRestRequest) throws Exception @@ -133,16 +133,23 @@ public class PDBFTSRestClient extends FTSRestClient + (pdbRestRequest.isAllowUnpublishedEntries() ? "" : " AND status:REL"); + // Build request parameters for the REST Request + + // BH 2018 the trick here is to coerce the classes in Javascript to be + // different from the ones in Java yet still allow this to be correct for Java + Class clientResponseClass; if (/** @j2sNative true || */ false) { + // JavaScript only -- coerce types to Java types for Java client = (Client) (Object) new jalview.javascript.web.Client(); + clientResponseClass = (Class) (Object) jalview.javascript.web.ClientResponse.class; } else { - // Build request parameters for the REST Request - ClientConfig clientConfig = new DefaultClientConfig(); - client = Client.create(clientConfig); + // Java only + client = Client.create(new DefaultClientConfig()); + clientResponseClass = ClientResponse.class; } if (pdbRestRequest.isFacet()) @@ -169,7 +176,7 @@ public class PDBFTSRestClient extends FTSRestClient // Execute the REST request ClientResponse clientResponse = webResource - .accept(MediaType.APPLICATION_JSON).get(ClientResponse.class); + .accept(MediaType.APPLICATION_JSON).get(clientResponseClass ); // Get the JSON string from the response object String responseString = clientResponse.getEntity(String.class); @@ -470,4 +477,30 @@ public class PDBFTSRestClient extends FTSRestClient } return allDefaultDisplayedStructureDataColumns; } + + public static void main(String[] args) { + + + String s = "e"; + + + char c = 'c'; + char f = 'f'; + s += c | f; + int x = c&f; + int y = 2 & c; + int z = c ^ 5; + JSONParser jsonParser = new JSONParser(); + try + { + JSONObject jsonObj = (JSONObject) jsonParser.parse("{\"a\":3}"); + System.out.println(jsonObj); + } catch (ParseException e) + { + e.printStackTrace(); + } + + } + + } diff --git a/src/jalview/gui/Preferences.java b/src/jalview/gui/Preferences.java index 019b29e..d9fa3c6 100755 --- a/src/jalview/gui/Preferences.java +++ b/src/jalview/gui/Preferences.java @@ -382,7 +382,8 @@ public class Preferences extends GPreferences new RowSorter.SortKey(m.getNameColumn(), SortOrder.ASCENDING)); sorter.setSortKeys(sortKeys); - sorter.sort(); + // BH 2018 setSortKeys will do the sort + // sorter.sort(); // set up filtering ActionListener onReset; diff --git a/src/jalview/javascript/web/ClientResponse.java b/src/jalview/javascript/web/ClientResponse.java index e33dd98..66a1b19 100644 --- a/src/jalview/javascript/web/ClientResponse.java +++ b/src/jalview/javascript/web/ClientResponse.java @@ -6,9 +6,9 @@ public class ClientResponse { private String response; - private String encoding; + private String[] encoding; - public ClientResponse(String response, String encoding) + public ClientResponse(String response, String... encoding) { this.response = response; this.encoding = encoding; @@ -47,7 +47,7 @@ public class ClientResponse public int getStatus() { // note, we could get the actual response. I am just assuming it is 200 or 400 - return (response != null && (response.startsWith("{") == encoding.equals("application/json")) + return (response != null && (response.startsWith("{") == encoding[0].equals("application/json")) ? 200 : 400); } diff --git a/src/jalview/javascript/web/WebResource.java b/src/jalview/javascript/web/WebResource.java index 299c727..6d12932 100644 --- a/src/jalview/javascript/web/WebResource.java +++ b/src/jalview/javascript/web/WebResource.java @@ -3,6 +3,9 @@ package jalview.javascript.web; import java.net.URI; import java.net.URISyntaxException; +/* this class is a proxy for + * + */ public class WebResource { @@ -18,7 +21,7 @@ public class WebResource params += (params == "" ? "?" : "&") + key + "="; /** * @j2sNative - * value = encodeURIComonent(value); + * value = encodeURIComponent(value); */ params += value; return this; @@ -36,7 +39,7 @@ public class WebResource } } - public Builder accept(String encoding) + public Builder accept(String... encoding) { return new Builder(getURI(), encoding); } @@ -44,9 +47,9 @@ public class WebResource public static class Builder { private URI uri; - private String encoding; + private String[] encoding; - public Builder(URI uri, String encoding) + public Builder(URI uri, String... encoding) { this.uri = uri; this.encoding = encoding; // application/json diff --git a/src/net/miginfocom/README_SWINGJS.txt b/src/net/miginfocom/README_SWINGJS.txt new file mode 100644 index 0000000..cec8076 --- /dev/null +++ b/src/net/miginfocom/README_SWINGJS.txt @@ -0,0 +1,10 @@ +https://github.com/mikaelgrev/miglayout.git +2018.07.03 BH Latest commit f2a231c + + +Status +====== + +7/3/2018 + +no changes necessary diff --git a/src/net/miginfocom/layout/AC.java b/src/net/miginfocom/layout/AC.java new file mode 100644 index 0000000..231b333 --- /dev/null +++ b/src/net/miginfocom/layout/AC.java @@ -0,0 +1,571 @@ +package net.miginfocom.layout; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.util.ArrayList; + +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** A constraint that holds the column or row constraints for the grid. It also holds the gaps between the rows and columns. + *

+ * This class is a holder and builder for a number of {@link net.miginfocom.layout.DimConstraint}s. + *

+ * For a more thorough explanation of what these constraints do, and how to build the constraints, see the White Paper or Cheat Sheet at www.migcomponents.com. + *

+ * Note that there are two way to build this constraint. Through String (e.g. "[100]3[200,fill]" or through API (E.g. + * new AC().size("100").gap("3").size("200").fill(). + */ +public final class AC //implements Externalizable +{ + private final ArrayList cList = new ArrayList(1); + + private transient int curIx = 0; + + /** Constructor. Creates an instance that can be configured manually. Will be initialized with a default + * {@link net.miginfocom.layout.DimConstraint}. + */ + public AC() + { + cList.add(new DimConstraint()); + } + + /** Property. The different {@link net.miginfocom.layout.DimConstraint}s that this object consists of. + * These DimConstraints contains all information in this class. + *

+ * Yes, we are embarrassingly aware that the method is misspelled. + * @return The different {@link net.miginfocom.layout.DimConstraint}s that this object consists of. A new list and + * never null. + */ + public final DimConstraint[] getConstaints() + { + return cList.toArray(new DimConstraint[cList.size()]); + } + + /** Sets the different {@link net.miginfocom.layout.DimConstraint}s that this object should consists of. + *

+ * Yes, we are embarrassingly aware that the method is misspelled. + * @param constr The different {@link net.miginfocom.layout.DimConstraint}s that this object consists of. The list + * will be copied for storage. null or and empty array will reset the constraints to one DimConstraint + * with default values. + */ + public final void setConstaints(DimConstraint[] constr) + { + if (constr == null || constr.length < 1 ) + constr = new DimConstraint[] {new DimConstraint()}; + + cList.clear(); + cList.ensureCapacity(constr.length); + for (DimConstraint c : constr) + cList.add(c); + } + + /** Returns the number of rows/columns that this constraints currently have. + * @return The number of rows/columns that this constraints currently have. At least 1. + */ + public int getCount() + { + return cList.size(); + } + + /** Sets the total number of rows/columns to size. If the number of rows/columns is already more + * than size nothing will happen. + * @param size The total number of rows/columns + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC count(int size) + { + makeSize(size); + return this; + } + + /** Specifies that the current row/column should not be grid-like. The while row/column will have its components layed out + * in one single cell. It is the same as to say that the cells in this column/row will all be merged (a.k.a spanned). + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC noGrid() + { + return noGrid(curIx); + } + + /** Specifies that the indicated rows/columns should not be grid-like. The while row/column will have its components layed out + * in one single cell. It is the same as to say that the cells in this column/row will all be merged (a.k.a spanned). + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC noGrid(int... indexes) + { + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setNoGrid(true); + } + return this; + } + + /** Sets the current row/column to i. If the current number of rows/columns is less than i a call + * to {@link #count(int)} will set the size accordingly. + *

+ * The next call to any of the constraint methods (e.g. {@link net.miginfocom.layout.AC#noGrid}) will be carried + * out on this new row/column. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param i The new current row/column. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC index(int i) + { + makeSize(i); + curIx = i; + return this; + } + + /** Specifies that the current row/column's component should grow by default. It does not affect the size of the row/column. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC fill() + { + return fill(curIx); + } + + /** Specifies that the indicated rows'/columns' component should grow by default. It does not affect the size of the row/column. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC fill(int... indexes) + { + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setFill(true); + } + return this; + } + +// /** Specifies that the current row/column should be put in the end group s and will thus share the same ending +// * coordinate within the group. +// *

+// * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. +// * @param s A name to associate on the group that should be the same for other rows/columns in the same group. +// * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). +// */ +// public final AxisConstraint endGroup(String s) +// { +// return endGroup(s, curIx); +// } +// +// /** Specifies that the indicated rows/columns should be put in the end group s and will thus share the same ending +// * coordinate within the group. +// *

+// * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. +// * @param s A name to associate on the group that should be the same for other rows/columns in the same group. +// * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. +// * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). +// */ +// public final AxisConstraint endGroup(String s, int... indexes) +// { +// for (int i = indexes.length - 1; i >= 0; i--) { +// int ix = indexes[i]; +// makeSize(ix); +// cList.get(ix).setEndGroup(s); +// } +// return this; +// } + + /** Specifies that the current row/column should be put in the size group s and will thus share the same size + * constraints as the other components in the group. + *

+ * Same as sizeGroup("") + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final AC sizeGroup() + { + return sizeGroup("", curIx); + } + + /** Specifies that the current row/column should be put in the size group s and will thus share the same size + * constraints as the other components in the group. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s A name to associate on the group that should be the same for other rows/columns in the same group. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC sizeGroup(String s) + { + return sizeGroup(s, curIx); + } + + /** Specifies that the indicated rows/columns should be put in the size group s and will thus share the same size + * constraints as the other components in the group. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s A name to associate on the group that should be the same for other rows/columns in the same group. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC sizeGroup(String s, int... indexes) + { + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setSizeGroup(s); + } + return this; + } + + /** Specifies the current row/column's min and/or preferred and/or max size. E.g. "10px" or "50:100:200". + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s The minimum and/or preferred and/or maximum size of this row. The string will be interpreted + * as a BoundSize. For more info on how BoundSize is formatted see the documentation. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC size(String s) + { + return size(s, curIx); + } + + /** Specifies the indicated rows'/columns' min and/or preferred and/or max size. E.g. "10px" or "50:100:200". + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The minimum and/or preferred and/or maximum size of this row. The string will be interpreted + * as a BoundSize. For more info on how BoundSize is formatted see the documentation. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC size(String size, int... indexes) + { + BoundSize bs = ConstraintParser.parseBoundSize(size, false, true); + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setSize(bs); + } + return this; + } + + /** Specifies the gap size to be the default one AND moves to the next column/row. The method is called .gap() + * rather the more natural .next() to indicate that it is very much related to the other .gap(..) methods. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC gap() + { + curIx++; + makeSize(curIx); + return this; + } + + /** Specifies the gap size to size AND moves to the next column/row. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size minimum and/or preferred and/or maximum size of the gap between this and the next row/column. + * The string will be interpreted as a BoundSize. For more info on how BoundSize is formatted see the documentation. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC gap(String size) + { + return gap(size, curIx++); + } + + /** Specifies the indicated rows'/columns' gap size to size. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size minimum and/or preferred and/or maximum size of the gap between this and the next row/column. + * The string will be interpreted as a BoundSize. For more info on how BoundSize is formatted see the documentation. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC gap(String size, int... indexes) + { + BoundSize bsa = size != null ? ConstraintParser.parseBoundSize(size, true, true) : null; + + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix + 1); + if (bsa != null) + cList.get(ix).setGapAfter(bsa); + } + return this; + } + + /** Specifies the current row/column's columns default alignment for its components. It does not affect the positioning + * or size of the columns/row itself. For columns it is the horizontal alignment (e.g. "left") and for rows it is the vertical + * alignment (e.g. "top"). + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param side The default side to align the components. E.g. "top" or "left", or "leading" or "trailing" or "bottom" or "right". + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC align(String side) + { + return align(side, curIx); + } + + /** Specifies the indicated rows'/columns' columns default alignment for its components. It does not affect the positioning + * or size of the columns/row itself. For columns it is the horizontal alignment (e.g. "left") and for rows it is the vertical + * alignment (e.g. "top"). + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param side The default side to align the components. E.g. "top" or "left", or "before" or "after" or "bottom" or "right". + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC align(String side, int... indexes) + { + UnitValue al = ConstraintParser.parseAlignKeywords(side, true); + if (al == null) + al = ConstraintParser.parseAlignKeywords(side, false); + + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setAlign(al); + } + return this; + } + + /** Specifies the current row/column's grow priority. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The new grow priority. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC growPrio(int p) + { + return growPrio(p, curIx); + } + + /** Specifies the indicated rows'/columns' grow priority. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The new grow priority. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC growPrio(int p, int... indexes) + { + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setGrowPriority(p); + } + return this; + } + + /** Specifies the current row/column's grow weight within columns/rows with the grow priority 100f. + *

+ * Same as grow(100f) + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final AC grow() + { + return grow(100f, curIx); + } + + /** Specifies the current row/column's grow weight within columns/rows with the same grow priority. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param w The new grow weight. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC grow(float w) + { + return grow(w, curIx); + } + + /** Specifies the indicated rows'/columns' grow weight within columns/rows with the same grow priority. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param w The new grow weight. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC grow(float w, int... indexes) + { + Float gw = new Float(w); + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setGrow(gw); + } + return this; + } + + /** Specifies the current row/column's shrink priority. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The new shrink priority. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC shrinkPrio(int p) + { + return shrinkPrio(p, curIx); + } + + /** Specifies the indicated rows'/columns' shrink priority. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The new shrink priority. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + */ + public final AC shrinkPrio(int p, int... indexes) + { + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setShrinkPriority(p); + } + return this; + } + + /** Specifies that the current row/column's shrink weight within the columns/rows with the shrink priority 100f. + *

+ * Same as shrink(100f). + *

+ * For a more thorough explanation of what this constraint does see the White Paper or Cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final AC shrink() + { + return shrink(100f, curIx); + } + + /** Specifies that the current row/column's shrink weight within the columns/rows with the same shrink priority. + *

+ * For a more thorough explanation of what this constraint does see the White Paper or Cheat Sheet at www.migcomponents.com. + * @param w The shrink weight. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final AC shrink(float w) + { + return shrink(w, curIx); + } + + /** Specifies the indicated rows'/columns' shrink weight within the columns/rows with the same shrink priority. + *

+ * For a more thorough explanation of what this constraint does see the White Paper or Cheat Sheet at www.migcomponents.com. + * @param w The shrink weight. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final AC shrink(float w, int... indexes) + { + Float sw = new Float(w); + for (int i = indexes.length - 1; i >= 0; i--) { + int ix = indexes[i]; + makeSize(ix); + cList.get(ix).setShrink(sw); + } + return this; + } + + /** Specifies that the current row/column's shrink weight within the columns/rows with the same shrink priority. + *

+ * For a more thorough explanation of what this constraint does see the White Paper or Cheat Sheet at www.migcomponents.com. + * @param w The shrink weight. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + * @deprecated in 3.7.2. Use {@link #shrink(float)} instead. + */ + public final AC shrinkWeight(float w) + { + return shrink(w); + } + + /** Specifies the indicated rows'/columns' shrink weight within the columns/rows with the same shrink priority. + *

+ * For a more thorough explanation of what this constraint does see the White Paper or Cheat Sheet at www.migcomponents.com. + * @param w The shrink weight. + * @param indexes The index(es) (0-based) of the columns/rows that should be affected by this constraint. + * @return this so it is possible to chain calls. E.g. new AxisConstraint().noGrid().gap().fill(). + * @deprecated in 3.7.2. Use {@link #shrink(float, int...)} instead. + */ + public final AC shrinkWeight(float w, int... indexes) + { + return shrink(w, indexes); + } + + private void makeSize(int sz) + { + if (cList.size() <= sz) { + cList.ensureCapacity(sz); + for (int i = cList.size(); i <= sz; i++) + cList.add(new DimConstraint()); + } + } + +// // ************************************************ +// // Persistence Delegate and Serializable combined. +// // ************************************************ +// +// private Object readResolve() throws ObjectStreamException +// { +// return LayoutUtil.getSerializedObject(this); +// } +// +// @Override +// public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException +// { +// LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in)); +// } +// +// @Override +// public void writeExternal(ObjectOutput out) throws IOException +// { +// if (getClass() == AC.class) +// LayoutUtil.writeAsXML(out, this); +// } +} \ No newline at end of file diff --git a/src/net/miginfocom/layout/AnimSpec.java b/src/net/miginfocom/layout/AnimSpec.java new file mode 100644 index 0000000..c1ece4f --- /dev/null +++ b/src/net/miginfocom/layout/AnimSpec.java @@ -0,0 +1,107 @@ +package net.miginfocom.layout; + +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + */ + +import java.io.Serializable; + +/** + * @author Mikael Grev, MiG InfoCom AB + * Date: 14-09-24 + * Time: 17:05 + */ +public class AnimSpec implements Serializable +{ +// public static final AnimSpec OFF = new AnimSpec(-1, 0, 0); + public static final AnimSpec DEF = new AnimSpec(0, 0, 0.2f, 0.2f); + + private final int prio; + private final int durMillis; + private final float easeIn, easeOut; + + /** + * @param prio The animation priority. When added with the general animation priority of the layout the animation will + * be done if the resulting value is > 0. + * @param durMillis Duration in milliseconds. <=0 means default value should be used and > 0 is the number of millis + * @param easeIn 0 is linear (no ease). 1 is max ease. Always clamped between these values. + * @param easeOut 0 is linear (no ease). 1 is max ease. Always clamped between these values. + */ + public AnimSpec(int prio, int durMillis, float easeIn, float easeOut) + { + this.prio = prio; + this.durMillis = durMillis; + this.easeIn = LayoutUtil.clamp(easeIn, 0, 1); + this.easeOut = LayoutUtil.clamp(easeOut, 0, 1); + } + + /** + * @return The animation priority. When added with the general animation priority of the layout the animation will + * be done if the resulting value is > 0. + */ + public int getPriority() + { + return prio; + } + + /** + * @param defMillis Default used if the millis in the spec is set to "default". + * @return Duration in milliseconds. <=0 means default value should be used and > 0 is the number of millis + */ + public int getDurationMillis(int defMillis) + { + return durMillis > 0 ? durMillis : defMillis; + } + + /** + * @return Duration in milliseconds. <= 0 means default value should be used and > 0 is the number of millis + */ + public int getDurationMillis() + { + return durMillis; + } + + /** + * @return A value between 0 and 1 where 0 is no ease in and 1 is maximum ease in. + */ + public float getEaseIn() + { + return easeIn; + } + + /** + * @return A value between 0 and 1 where 0 is no ease out and 1 is maximum ease out. + */ + public float getEaseOut() + { + return easeOut; + } +} diff --git a/src/net/miginfocom/layout/BoundSize.java b/src/net/miginfocom/layout/BoundSize.java new file mode 100644 index 0000000..35301c1 --- /dev/null +++ b/src/net/miginfocom/layout/BoundSize.java @@ -0,0 +1,375 @@ +package net.miginfocom.layout; + +//import java.beans.Encoder; +//import java.beans.Expression; +//import java.beans.PersistenceDelegate; +import java.io.Serializable; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** + * A size that contains minimum, preferred and maximum size of type + * {@link UnitValue}. + *

+ * This class is a simple value container and it is immutable. + *

+ * If a size is missing (i.e., null) that boundary should be + * considered "not in use". + *

+ * You can create a BoundSize from a String with the use of + * {@link ConstraintParser#parseBoundSize(String, boolean, boolean)} + */ +public class BoundSize implements Serializable +{ + public static final BoundSize NULL_SIZE = new BoundSize(null, null); + + public static final BoundSize ZERO_PIXEL = new BoundSize(UnitValue.ZERO, + "0px"); + + private final transient UnitValue min; + + private final transient UnitValue pref; + + private final transient UnitValue max; + + private final transient boolean gapPush; + + /** + * Constructor that use the same value for min/preferred/max size. + * + * @param minMaxPref + * The value to use for min/preferred/max size. + * @param createString + * The string used to create the BoundsSize. + */ + public BoundSize(UnitValue minMaxPref, String createString) + { + this(minMaxPref, minMaxPref, minMaxPref, createString); + } + + /** + * Constructor. This method is here for serialization only and should + * normally not be used. Use + * {@link ConstraintParser#parseBoundSize(String, boolean, boolean)} instead. + * + * @param min + * The minimum size. May be null. + * @param preferred + * The preferred size. May be null. + * @param max + * The maximum size. May be null. + * @param createString + * The string used to create the BoundsSize. + */ + public BoundSize(UnitValue min, UnitValue preferred, UnitValue max, + String createString) // Bound to old delegate!!!!! + { + this(min, preferred, max, false, createString); + } + + /** + * Constructor. This method is here for serialization only and should + * normally not be used. Use + * {@link ConstraintParser#parseBoundSize(String, boolean, boolean)} instead. + * + * @param min + * The minimum size. May be null. + * @param preferred + * The preferred size. May be null. + * @param max + * The maximum size. May be null. + * @param gapPush + * If the size should be hinted as "pushing" and thus want to occupy + * free space if no one else is claiming it. + * @param createString + * The string used to create the BoundsSize. + */ + public BoundSize(UnitValue min, UnitValue preferred, UnitValue max, + boolean gapPush, String createString) + { + this.min = min; + this.pref = preferred; + this.max = max; + this.gapPush = gapPush; + + LayoutUtil.putCCString(this, createString); // this escapes!! + } + + /** + * Returns the minimum size as sent into the constructor. + * + * @return The minimum size as sent into the constructor. May be + * null. + */ + public final UnitValue getMin() + { + return min; + } + + /** + * Returns the preferred size as sent into the constructor. + * + * @return The preferred size as sent into the constructor. May be + * null. + */ + public final UnitValue getPreferred() + { + return pref; + } + + /** + * Returns the maximum size as sent into the constructor. + * + * @return The maximum size as sent into the constructor. May be + * null. + */ + public final UnitValue getMax() + { + return max; + } + + /** + * If the size should be hinted as "pushing" and thus want to occupy free + * space if no one else is claiming it. + * + * @return The value. + */ + public boolean getGapPush() + { + return gapPush; + } + + /** + * Returns if this bound size has no min, preferred and maximum size set (they + * are all null) + * + * @return If unset. + */ + public boolean isUnset() + { + // Most common case by far is this == ZERO_PIXEL... + return this == ZERO_PIXEL || (pref == null && min == null && max == null + && gapPush == false); + } + + /** + * Makes sure that size is within min and max of this size. + * + * @param size + * The size to constrain. + * @param refValue + * The reference to use for relative sizes. + * @param parent + * The parent container. + * @return The size, constrained within min and max. + */ + public int constrain(int size, float refValue, ContainerWrapper parent) + { + if (max != null) + size = Math.min(size, max.getPixels(refValue, parent, parent)); + if (min != null) + size = Math.max(size, min.getPixels(refValue, parent, parent)); + return size; + } + + /** + * Returns the minimum, preferred or maximum size for this bounded size. + * + * @param sizeType + * The type. LayoutUtil.MIN, + * LayoutUtil.PREF or LayoutUtil.MAX. + * @return + */ + final UnitValue getSize(int sizeType) + { + switch (sizeType) + { + case LayoutUtil.MIN: + return min; + case LayoutUtil.PREF: + return pref; + case LayoutUtil.MAX: + return max; + default: + throw new IllegalArgumentException("Unknown size: " + sizeType); + } + } + + /** + * Convert the bound sizes to pixels. + *

+ * null bound sizes will be 0 for min and preferred and + * {@link net.miginfocom.layout.LayoutUtil#INF} for max. + * + * @param refSize + * The reference size. + * @param parent + * The parent. Not null. + * @param comp + * The component, if applicable, can be null. + * @return An array of length three (min,pref,max). + */ + final int[] getPixelSizes(float refSize, ContainerWrapper parent, + ComponentWrapper comp) + { + return new int[] { + min != null ? min.getPixels(refSize, parent, comp) : 0, + pref != null ? pref.getPixels(refSize, parent, comp) : 0, + max != null ? max.getPixels(refSize, parent, comp) + : LayoutUtil.INF }; + } + + /** + * Returns the a constraint string that can be re-parsed to be the exact same + * UnitValue. + * + * @return A String. Never null. + */ + String getConstraintString() + { + String cs = LayoutUtil.getCCString(this); + if (cs != null) + return cs; + + if (min == pref && pref == max) + return min != null ? (min.getConstraintString() + "!") : "null"; + + StringBuilder sb = new StringBuilder(16); + + if (min != null) + sb.append(min.getConstraintString()).append(':'); + + if (pref != null) + { + if (min == null && max != null) + sb.append(":"); + sb.append(pref.getConstraintString()); + } + else if (min != null) + { + sb.append('n'); + } + + if (max != null) + sb.append(sb.length() == 0 ? "::" : ":") + .append(max.getConstraintString()); + + if (gapPush) + { + if (sb.length() > 0) + sb.append(':'); + sb.append("push"); + } + + return sb.toString(); + } + + void checkNotLinked() + { + if (isLinked()) + throw new IllegalArgumentException("Size may not contain links"); + } + + boolean isLinked() + { + return min != null && min.isLinkedDeep() + || pref != null && pref.isLinkedDeep() + || max != null && max.isLinkedDeep(); + } + + boolean isAbsolute() + { + return (min == null || min.isAbsoluteDeep()) + && (pref == null || pref.isAbsoluteDeep()) + && (max == null || max.isAbsoluteDeep()); + } + + public String toString() + { + return "BoundSize{" + "min=" + min + ", pref=" + pref + ", max=" + max + + ", gapPush=" + gapPush + '}'; + } + +// static +// { +// if (LayoutUtil.HAS_BEANS) +// { +// LayoutUtil.setDelegate(BoundSize.class, new PersistenceDelegate() +// { +// @Override +// protected Expression instantiate(Object oldInstance, Encoder out) +// { +// BoundSize bs = (BoundSize) oldInstance; +// if (Grid.TEST_GAPS) +// { +// return new Expression(oldInstance, BoundSize.class, "new", +// new Object[] +// { bs.getMin(), bs.getPreferred(), bs.getMax(), +// bs.getGapPush(), bs.getConstraintString() }); +// } +// else +// { +// return new Expression(oldInstance, BoundSize.class, "new", +// new Object[] +// { bs.getMin(), bs.getPreferred(), bs.getMax(), +// bs.getConstraintString() }); +// } +// } +// }); +// } +// } + + // // ************************************************ + // // Persistence Delegate and Serializable combined. + // // ************************************************ + // + // private static final long serialVersionUID = 1L; + // + // protected Object readResolve() throws ObjectStreamException + // { + // return LayoutUtil.getSerializedObject(this); + // } + // + // private void writeObject(ObjectOutputStream out) throws IOException + // { + // if (getClass() == BoundSize.class) + // LayoutUtil.writeAsXML(out, this); + // } + // + // private void readObject(ObjectInputStream in) throws IOException, + // ClassNotFoundException + // { + // LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in)); + // } +} diff --git a/src/net/miginfocom/layout/CC.java b/src/net/miginfocom/layout/CC.java new file mode 100644 index 0000000..e4cfff9 --- /dev/null +++ b/src/net/miginfocom/layout/CC.java @@ -0,0 +1,1880 @@ +package net.miginfocom.layout; + +import java.util.ArrayList; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** A simple value holder for one component's constraint. + */ +public final class CC// s implements Externalizable +{ + private static final BoundSize DEF_GAP = BoundSize.NULL_SIZE; // Only used to denote default wrap/newline gap. + + static final String[] DOCK_SIDES = {"north", "west", "south", "east"}; + + // See the getters and setters for information about the properties below. + + private int dock = -1; + + private UnitValue[] pos = null; // [x1, y1, x2, y2] + + private UnitValue[] padding = null; // top, left, bottom, right + + private UnitValue[] visualPadding = null; // top, left, bottom, right + + private Boolean flowX = null; + + private int skip = 0; + + private int split = 1; + + private int spanX = 1, spanY = 1; + + private int cellX = -1, cellY = 0; // If cellX is -1 then cellY is also considered -1. cellY is never negative. + + private String tag = null; + + private String id = null; + + private int hideMode = -1; + + private DimConstraint hor = new DimConstraint(); + + private DimConstraint ver = new DimConstraint(); + + private BoundSize newline = null; + + private BoundSize wrap = null; + + private boolean boundsInGrid = true; + + private boolean external = false; + + private Float pushX = null, pushY = null; + + private AnimSpec animSpec = AnimSpec.DEF; + + + // ***** Tmp cache field + + private static final String[] EMPTY_ARR = new String[0]; + + private transient String[] linkTargets = null; + + /** Empty constructor. + */ + public CC() + { + } + + String[] getLinkTargets() + { + if (linkTargets == null) { + final ArrayList targets = new ArrayList(2); + + if (pos != null) { + for (int i = 0; i < pos.length ; i++) + addLinkTargetIDs(targets, pos[i]); + } + + linkTargets = targets.size() == 0 ? EMPTY_ARR : targets.toArray(new String[targets.size()]); + } + return linkTargets; + } + + private void addLinkTargetIDs(ArrayList targets, UnitValue uv) + { + if (uv != null) { + String linkId = uv.getLinkTargetId(); + if (linkId != null) { + targets.add(linkId); + } else { + for (int i = uv.getSubUnitCount() - 1; i >= 0; i--) { + UnitValue subUv = uv.getSubUnitValue(i); + if (subUv.isLinkedDeep()) + addLinkTargetIDs(targets, subUv); + } + } + } + } + + // ********************************************************** + // Chaining constraint setters + // ********************************************************** + + /** Specifies that the component should be put in the end group s and will thus share the same ending + * coordinate as them within the group. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s A name to associate on the group that should be the same for other rows/columns in the same group. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC endGroupX(String s) + { + hor.setEndGroup(s); + return this; + } + + /** Specifies that the component should be put in the size group s and will thus share the same size + * as them within the group. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s A name to associate on the group that should be the same for other rows/columns in the same group. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC sizeGroupX(String s) + { + hor.setSizeGroup(s); + return this; + } + + /** The minimum size for the component. The value will override any value that is set on the component itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The size expressed as a UnitValue. E.g. "100px" or "200mm". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC minWidth(String size) + { + hor.setSize(LayoutUtil.derive(hor.getSize(), ConstraintParser.parseUnitValue(size, true), null, null)); + return this; + } + + /** The size for the component as a min and/or preferred and/or maximum size. The value will override any value that is set on + * the component itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The size expressed as a BoundSize. E.g. "50:100px:200mm" or "100px". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC width(String size) + { + hor.setSize(ConstraintParser.parseBoundSize(size, false, true)); + return this; + } + + /** The maximum size for the component. The value will override any value that is set on the component itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The size expressed as a UnitValue. E.g. "100px" or "200mm". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC maxWidth(String size) + { + hor.setSize(LayoutUtil.derive(hor.getSize(), null, null, ConstraintParser.parseUnitValue(size, true))); + return this; + } + + + /** The horizontal gap before and/or after the component. The gap is towards cell bounds and/or other component bounds. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param before The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @param after The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC gapX(String before, String after) + { + if (before != null) + hor.setGapBefore(ConstraintParser.parseBoundSize(before, true, true)); + + if (after != null) + hor.setGapAfter(ConstraintParser.parseBoundSize(after, true, true)); + + return this; + } + + /** Same functionality as getHorizontal().setAlign(ConstraintParser.parseUnitValue(unitValue, true)) only this method + * returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param align The align keyword or for instance "100px". E.g "left", "right", "leading" or "trailing". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC alignX(String align) + { + hor.setAlign(ConstraintParser.parseUnitValueOrAlign(align, true, null)); + return this; + } + + /** The grow priority compared to other components in the same cell. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The grow priority. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC growPrioX(int p) + { + hor.setGrowPriority(p); + return this; + } + + /** Grow priority for the component horizontally and optionally vertically. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param widthHeight The new shrink weight and height. 1-2 arguments, never null. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC growPrio(int ... widthHeight) + { + switch (widthHeight.length) { + default: + throw new IllegalArgumentException("Illegal argument count: " + widthHeight.length); + case 2: + growPrioY(widthHeight[1]); + case 1: + growPrioX(widthHeight[0]); + } + return this; + } + + /** Grow weight for the component horizontally. It default to weight 100. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #growX(float) + */ + public final CC growX() + { + hor.setGrow(ResizeConstraint.WEIGHT_100); + return this; + } + + /** Grow weight for the component horizontally. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param w The new grow weight. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC growX(float w) + { + hor.setGrow(new Float(w)); + return this; + } + + /** grow weight for the component horizontally and optionally vertically. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param widthHeight The new shrink weight and height. 1-2 arguments, never null. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC grow(float ... widthHeight) + { + switch (widthHeight.length) { + default: + throw new IllegalArgumentException("Illegal argument count: " + widthHeight.length); + case 2: + growY(widthHeight[1]); + case 1: + growX(widthHeight[0]); + } + return this; + } + + /** The shrink priority compared to other components in the same cell. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The shrink priority. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC shrinkPrioX(int p) + { + hor.setShrinkPriority(p); + return this; + } + + /** Shrink priority for the component horizontally and optionally vertically. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param widthHeight The new shrink weight and height. 1-2 arguments, never null. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC shrinkPrio(int ... widthHeight) + { + switch (widthHeight.length) { + default: + throw new IllegalArgumentException("Illegal argument count: " + widthHeight.length); + case 2: + shrinkPrioY(widthHeight[1]); + case 1: + shrinkPrioX(widthHeight[0]); + } + return this; + } + + /** Shrink weight for the component horizontally. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param w The new shrink weight. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC shrinkX(float w) + { + hor.setShrink(new Float(w)); + return this; + } + + /** Shrink weight for the component horizontally and optionally vertically. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param widthHeight The new shrink weight and height. 1-2 arguments, never null. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC shrink(float ... widthHeight) + { + switch (widthHeight.length) { + default: + throw new IllegalArgumentException("Illegal argument count: " + widthHeight.length); + case 2: + shrinkY(widthHeight[1]); + case 1: + shrinkX(widthHeight[0]); + } + return this; + } + + /** The end group that this component should be placed in. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s The name of the group. If null that means no group (default) + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC endGroupY(String s) + { + ver.setEndGroup(s); + return this; + } + + /** The end group(s) that this component should be placed in. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param xy The end group for x and y respectively. 1-2 arguments, not null. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC endGroup(String ... xy) + { + switch (xy.length) { + default: + throw new IllegalArgumentException("Illegal argument count: " + xy.length); + case 2: + endGroupY(xy[1]); + case 1: + endGroupX(xy[0]); + } + return this; + } + + /** The size group that this component should be placed in. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s The name of the group. If null that means no group (default) + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC sizeGroupY(String s) + { + ver.setSizeGroup(s); + return this; + } + + /** The size group(s) that this component should be placed in. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param xy The size group for x and y respectively. 1-2 arguments, not null. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC sizeGroup(String ... xy) + { + switch (xy.length) { + default: + throw new IllegalArgumentException("Illegal argument count: " + xy.length); + case 2: + sizeGroupY(xy[1]); + case 1: + sizeGroupX(xy[0]); + } + return this; + } + + /** The minimum size for the component. The value will override any value that is set on the component itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The size expressed as a UnitValue. E.g. "100px" or "200mm". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC minHeight(String size) + { + ver.setSize(LayoutUtil.derive(ver.getSize(), ConstraintParser.parseUnitValue(size, false), null, null)); + return this; + } + + /** The size for the component as a min and/or preferred and/or maximum size. The value will override any value that is set on + * the component itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The size expressed as a BoundSize. E.g. "50:100px:200mm" or "100px". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC height(String size) + { + ver.setSize(ConstraintParser.parseBoundSize(size, false, false)); + return this; + } + + /** The maximum size for the component. The value will override any value that is set on the component itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The size expressed as a UnitValue. E.g. "100px" or "200mm". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC maxHeight(String size) + { + ver.setSize(LayoutUtil.derive(ver.getSize(), null, null, ConstraintParser.parseUnitValue(size, false))); + return this; + } + + /** The vertical gap before (normally above) and/or after (normally below) the component. The gap is towards cell bounds and/or other component bounds. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param before The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @param after The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC gapY(String before, String after) + { + if (before != null) + ver.setGapBefore(ConstraintParser.parseBoundSize(before, true, false)); + + if (after != null) + ver.setGapAfter(ConstraintParser.parseBoundSize(after, true, false)); + + return this; + } + + /** Same functionality as getVertical().setAlign(ConstraintParser.parseUnitValue(unitValue, true)) only this method + * returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param align The align keyword or for instance "100px". E.g "top" or "bottom". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC alignY(String align) + { + ver.setAlign(ConstraintParser.parseUnitValueOrAlign(align, false, null)); + return this; + } + + /** The grow priority compared to other components in the same cell. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The grow priority. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC growPrioY(int p) + { + ver.setGrowPriority(p); + return this; + } + + /** Grow weight for the component vertically. Defaults to 100. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #growY(Float) + */ + public final CC growY() + { + ver.setGrow(ResizeConstraint.WEIGHT_100); + return this; + } + + /** Grow weight for the component vertically. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param w The new grow weight. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC growY(float w) + { + ver.setGrow(w); + return this; + } + + /** Grow weight for the component vertically. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param w The new grow weight. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + @Deprecated + public final CC growY(Float w) + { + ver.setGrow(w); + return this; + } + + /** The shrink priority compared to other components in the same cell. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The shrink priority. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC shrinkPrioY(int p) + { + ver.setShrinkPriority(p); + return this; + } + + /** Shrink weight for the component horizontally. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param w The new shrink weight. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC shrinkY(float w) + { + ver.setShrink(new Float(w)); + return this; + } + + /** How this component, if hidden (not visible), should be treated. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param mode The mode. Default to the mode in the {@link net.miginfocom.layout.LC}. + * 0 == Normal. Bounds will be calculated as if the component was visible.
+ * 1 == If hidden the size will be 0, 0 but the gaps remain.
+ * 2 == If hidden the size will be 0, 0 and gaps set to zero.
+ * 3 == If hidden the component will be disregarded completely and not take up a cell in the grid.. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC hideMode(int mode) + { + setHideMode(mode); + return this; + } + + /** The id used to reference this component in some constraints. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s The id or null. May consist of a groupID and an componentID which are separated by a dot: ".". E.g. "grp1.id1". + * The dot should never be first or last if present. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + */ + public final CC id(String s) + { + setId(s); + return this; + } + + /** Same functionality as {@link #setTag(String tag)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param tag The new tag. May be null. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setTag(String) + */ + public final CC tag(String tag) + { + setTag(tag); + return this; + } + + /** Set the cell(s) that the component should occupy in the grid. Same functionality as {@link #setCellX(int col)} and + * {@link #setCellY(int row)} together with {@link #setSpanX(int width)} and {@link #setSpanY(int height)}. This method + * returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param colRowWidthHeight cellX, cellY, spanX, spanY respectively. 1-4 arguments, not null. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setCellX(int) + * @see #setCellY(int) + * @see #setSpanX(int) + * @see #setSpanY(int) + * @since 3.7.2. Replacing cell(int, int) and cell(int, int, int, int) + */ + public final CC cell(int ... colRowWidthHeight) + { + switch (colRowWidthHeight.length) { + default: + throw new IllegalArgumentException("Illegal argument count: " + colRowWidthHeight.length); + case 4: + setSpanY(colRowWidthHeight[3]); + case 3: + setSpanX(colRowWidthHeight[2]); + case 2: + setCellY(colRowWidthHeight[1]); + case 1: + setCellX(colRowWidthHeight[0]); + } + return this; + } + + /** Same functionality as spanX(cellsX).spanY(cellsY) which means this cell will span cells in both x and y. + * This method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * Since 3.7.2 this takes an array/vararg whereas it previously only took two specific values, xSpan and ySpan. + * @param cells spanX and spanY, when present, and in that order. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setSpanY(int) + * @see #setSpanX(int) + * @see #spanY() + * @see #spanX() + * @since 3.7.2 Replaces span(int, int). + */ + public final CC span(int ... cells) + { + if (cells == null || cells.length == 0) { + setSpanX(LayoutUtil.INF); + setSpanY(1); + } else if (cells.length == 1) { + setSpanX(cells[0]); + setSpanY(1); + } else { + setSpanX(cells[0]); + setSpanY(cells[1]); + } + return this; + } + + /** Corresponds exactly to the "gap left right top bottom" keyword. + * @param args Same as for the "gap" keyword. Length 1-4, never null buf elements can be null. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC gap(String ... args) + { + switch (args.length) { + default: + throw new IllegalArgumentException("Illegal argument count: " + args.length); + case 4: + gapBottom(args[3]); + case 3: + gapTop(args[2]); + case 2: + gapRight(args[1]); + case 1: + gapLeft(args[0]); + } + return this; + } + + /** Sets the horizontal gap before the component. + *

+ * Note! This is currently same as gapLeft(). This might change in 4.x. + * @param boundsSize The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC gapBefore(String boundsSize) + { + hor.setGapBefore(ConstraintParser.parseBoundSize(boundsSize, true, true)); + return this; + } + + /** Sets the horizontal gap after the component. + *

+ * Note! This is currently same as gapRight(). This might change in 4.x. + * @param boundsSize The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC gapAfter(String boundsSize) + { + hor.setGapAfter(ConstraintParser.parseBoundSize(boundsSize, true, true)); + return this; + } + + /** Sets the gap above the component. + * @param boundsSize The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC gapTop(String boundsSize) + { + ver.setGapBefore(ConstraintParser.parseBoundSize(boundsSize, true, false)); + return this; + } + + /** Sets the gap to the left the component. + * @param boundsSize The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC gapLeft(String boundsSize) + { + hor.setGapBefore(ConstraintParser.parseBoundSize(boundsSize, true, true)); + return this; + } + + /** Sets the gap below the component. + * @param boundsSize The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC gapBottom(String boundsSize) + { + ver.setGapAfter(ConstraintParser.parseBoundSize(boundsSize, true, false)); + return this; + } + + /** Sets the gap to the right of the component. + * @param boundsSize The size of the gap expressed as a BoundSize. E.g. "50:100px:200mm" or "100px!". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final CC gapRight(String boundsSize) + { + hor.setGapAfter(ConstraintParser.parseBoundSize(boundsSize, true, true)); + return this; + } + + /** Same functionality as calling {@link #setSpanY(int)} with LayoutUtil.INF which means this cell will span the rest of the column. + * This method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setSpanY(int) + * @see #spanY() + */ + public final CC spanY() + { + return spanY(LayoutUtil.INF); + } + + /** Same functionality as {@link #setSpanY(int)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param cells The number of cells to span (i.e. merge). + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setSpanY(int) + */ + public final CC spanY(int cells) + { + setSpanY(cells); + return this; + } + + /** Same functionality as {@link #setSpanX(int)} which means this cell will span the rest of the row. + * This method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setSpanX(int) + * @see #spanX() + */ + public final CC spanX() + { + return spanX(LayoutUtil.INF); + } + + /** Same functionality as {@link #setSpanX(int)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param cells The number of cells to span (i.e. merge). + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setSpanY(int) + */ + public final CC spanX(int cells) + { + setSpanX(cells); + return this; + } + + /** Same functionality as pushX().pushY() which means this cell will push in both x and y dimensions. + * This method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setPushX(Float) + * @see #setPushX(Float) + * @see #pushY() + * @see #pushX() + */ + public final CC push() + { + return pushX().pushY(); + } + + /** Same functionality as pushX(weightX).pushY(weightY) which means this cell will push in both x and y dimensions. + * This method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param weightX The weight used in the push. + * @param weightY The weight used in the push. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setPushY(Float) + * @see #setPushX(Float) + * @see #pushY() + * @see #pushX() + */ + public final CC push(Float weightX, Float weightY) + { + return pushX(weightX).pushY(weightY); + } + + /** Same functionality as {@link #setPushY(Float)} which means this cell will push the rest of the column. + * This method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setPushY(Float) + */ + public final CC pushY() + { + return pushY(ResizeConstraint.WEIGHT_100); + } + + /** Same functionality as {@link #setPushY(Float weight)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param weight The weight used in the push. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setPushY(Float) + */ + public final CC pushY(Float weight) + { + setPushY(weight); + return this; + } + + /** Same functionality as {@link #setPushX(Float)} which means this cell will push the rest of the row. + * This method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setPushX(Float) + */ + public final CC pushX() + { + return pushX(ResizeConstraint.WEIGHT_100); + } + + /** Same functionality as {@link #setPushX(Float weight)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param weight The weight used in the push. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setPushY(Float) + */ + public final CC pushX(Float weight) + { + setPushX(weight); + return this; + } + + /** Same functionality as {@link #setSplit(int parts)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param parts The number of parts (i.e. component slots) the cell should be divided into. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setSplit(int) + */ + public final CC split(int parts) + { + setSplit(parts); + return this; + } + + /** Same functionality as split(LayoutUtil.INF), which means split until one of the keywords that breaks the split is found for + * a component after this one (e.g. wrap, newline and skip). + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setSplit(int) + * @since 3.7.2 + */ + public final CC split() + { + setSplit(LayoutUtil.INF); + return this; + } + + /** Same functionality as {@link #setSkip(int)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param cells How many cells in the grid that should be skipped before the component that this constraint belongs to + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setSkip(int) + */ + public final CC skip(int cells) + { + setSkip(cells); + return this; + } + + /** Same functionality as skip(1). + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setSkip(int) + * @since 3.7.2 + */ + public final CC skip() + { + setSkip(1); + return this; + } + + /** Same functionality as calling {@link #setExternal(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setExternal(boolean) + */ + public final CC external() + { + setExternal(true); + return this; + } + + /** Same functionality as calling {@link #setFlowX(Boolean)} with Boolean.TRUE only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setFlowX(Boolean) + */ + public final CC flowX() + { + setFlowX(Boolean.TRUE); + return this; + } + + /** Same functionality as calling {@link #setFlowX(Boolean)} with Boolean.FALSE only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setFlowX(Boolean) + */ + public final CC flowY() + { + setFlowX(Boolean.FALSE); + return this; + } + + + /** Same functionality as {@link #growX()} and {@link #growY()}. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #growX() + * @see #growY() + */ + public final CC grow() + { + growX(); + growY(); + return this; + } + + /** Same functionality as calling {@link #setNewline(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setNewline(boolean) + */ + public final CC newline() + { + setNewline(true); + return this; + } + + /** Same functionality as {@link #setNewlineGapSize(BoundSize)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param gapSize The gap size that will override the gap size in the row/column constraints if != null. E.g. "5px" or "unrel". + * If null or "" the newline size will be set to the default size and turned on. This is different compared to + * {@link #setNewlineGapSize(BoundSize)}. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setNewlineGapSize(BoundSize) + */ + public final CC newline(String gapSize) + { + BoundSize bs = ConstraintParser.parseBoundSize(gapSize, true, (flowX != null && flowX == false)); + if (bs != null) { + setNewlineGapSize(bs); + } else { + setNewline(true); + } + return this; + } + + /** Same functionality as calling {@link #setWrap(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setWrap(boolean) + */ + public final CC wrap() + { + setWrap(true); + return this; + } + + /** Same functionality as {@link #setWrapGapSize(BoundSize)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param gapSize The gap size that will override the gap size in the row/column constraints if != null. E.g. "5px" or "unrel". + * If null or "" the wrap size will be set to the default size and turned on. This is different compared to + * {@link #setWrapGapSize(BoundSize)}. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setWrapGapSize(BoundSize) + */ + public final CC wrap(String gapSize) + { + BoundSize bs = ConstraintParser.parseBoundSize(gapSize, true, (flowX != null && flowX == false)); + if (bs != null) { + setWrapGapSize(bs); + } else { + setWrap(true); + } + return this; + } + + /** Same functionality as calling {@link #setDockSide(int)} with 0 only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setDockSide(int) + */ + public final CC dockNorth() + { + setDockSide(0); + return this; + } + + /** Same functionality as calling {@link #setDockSide(int)} with 1 only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setDockSide(int) + */ + public final CC dockWest() + { + setDockSide(1); + return this; + } + + /** Same functionality as calling {@link #setDockSide(int)} with 2 only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setDockSide(int) + */ + public final CC dockSouth() + { + setDockSide(2); + return this; + } + + /** Same functionality as calling {@link #setDockSide(int)} with 3 only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setDockSide(int) + */ + public final CC dockEast() + { + setDockSide(3); + return this; + } + + /** Sets the x-coordinate for the component. This is used to set the x coordinate position to a specific value. The component + * bounds is still precalculated to the grid cell and this method should be seen as a way to correct the x position. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param x The x position as a UnitValue. E.g. "10" or "40mm" or "container.x+10". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setPos(UnitValue[]) + * @see #setBoundsInGrid(boolean) + */ + public final CC x(String x) + { + return corrPos(x, 0); + } + + /** Sets the y-coordinate for the component. This is used to set the y coordinate position to a specific value. The component + * bounds is still precalculated to the grid cell and this method should be seen as a way to correct the y position. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param y The y position as a UnitValue. E.g. "10" or "40mm" or "container.x+10". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setPos(UnitValue[]) + * @see #setBoundsInGrid(boolean) + */ + public final CC y(String y) + { + return corrPos(y, 1); + } + + /** Sets the x2-coordinate for the component (right side). This is used to set the x2 coordinate position to a specific value. The component + * bounds is still precalculated to the grid cell and this method should be seen as a way to correct the x position. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param x2 The x2 side's position as a UnitValue. E.g. "10" or "40mm" or "container.x2 - 10". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setPos(UnitValue[]) + * @see #setBoundsInGrid(boolean) + */ + public final CC x2(String x2) + { + return corrPos(x2, 2); + } + + /** Sets the y2-coordinate for the component (bottom side). This is used to set the y2 coordinate position to a specific value. The component + * bounds is still precalculated to the grid cell and this method should be seen as a way to correct the y position. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param y2 The y2 side's position as a UnitValue. E.g. "10" or "40mm" or "container.x2 - 10". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setPos(UnitValue[]) + * @see #setBoundsInGrid(boolean) + */ + public final CC y2(String y2) + { + return corrPos(y2, 3); + } + + private final CC corrPos(String uv, int ix) + { + UnitValue[] b = getPos(); + if (b == null) + b = new UnitValue[4]; + + b[ix] = ConstraintParser.parseUnitValue(uv, (ix % 2 == 0)); + setPos(b); + + setBoundsInGrid(true); + return this; + } + + /** Same functionality as {@link #x(String x)} and {@link #y(String y)} together. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param x The x position as a UnitValue. E.g. "10" or "40mm" or "container.x+10". + * @param y The y position as a UnitValue. E.g. "10" or "40mm" or "container.x+10". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setPos(UnitValue[]) + */ + public final CC pos(String x, String y) + { + UnitValue[] b = getPos(); + if (b == null) + b = new UnitValue[4]; + + b[0] = ConstraintParser.parseUnitValue(x, true); + b[1] = ConstraintParser.parseUnitValue(y, false); + setPos(b); + + setBoundsInGrid(false); + return this; + } + + /** Same functionality as {@link #x(String x)}, {@link #y(String y)}, {@link #y2(String y)} and {@link #y2(String y)} together. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param x The x position as a UnitValue. E.g. "10" or "40mm" or "container.x+10". + * @param y The y position as a UnitValue. E.g. "10" or "40mm" or "container.x+10". + * @param x2 The x2 side's position as a UnitValue. E.g. "10" or "40mm" or "container.x2 - 10". + * @param y2 The y2 side's position as a UnitValue. E.g. "10" or "40mm" or "container.x2 - 10". + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setPos(UnitValue[]) + */ + public final CC pos(String x, String y, String x2, String y2) + { + setPos(new UnitValue[] { + ConstraintParser.parseUnitValue(x, true), + ConstraintParser.parseUnitValue(y, false), + ConstraintParser.parseUnitValue(x2, true), + ConstraintParser.parseUnitValue(y2, false), + }); + setBoundsInGrid(false); + return this; + } + + /** Same functionality as {@link #setPadding(UnitValue[])} but the unit values as absolute pixels. This method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param top The top padding that will be added to the y coordinate at the last stage in the layout. + * @param left The top padding that will be added to the x coordinate at the last stage in the layout. + * @param bottom The top padding that will be added to the y2 coordinate at the last stage in the layout. + * @param right The top padding that will be added to the x2 coordinate at the last stage in the layout. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setTag(String) + */ + public final CC pad(int top, int left, int bottom, int right) + { + setPadding(new UnitValue[] { + new UnitValue(top), new UnitValue(left), new UnitValue(bottom), new UnitValue(right) + }); + return this; + } + + /** Same functionality as setPadding(ConstraintParser.parseInsets(pad, false))} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param pad The string to parse. E.g. "10 10 10 10" or "20". If less than 4 groups the last will be used for the missing. + * @return this so it is possible to chain calls. E.g. new ComponentConstraint().noGrid().gap().fill(). + * @see #setTag(String) + */ + public final CC pad(String pad) + { + setPadding(pad != null ? ConstraintParser.parseInsets(pad, false) : null); + return this; + } + + // ********************************************************** + // Bean properties + // ********************************************************** + + /** Returns the horizontal dimension constraint for this component constraint. It has constraints for the horizontal size + * and grow/shrink priorities and weights. + *

+ * Note! If any changes is to be made it must be made direct when the object is returned. It is not allowed to save the + * constraint for later use. + * @return The current dimension constraint. Never null. + */ + public DimConstraint getHorizontal() + { + return hor; + } + + /** Sets the horizontal dimension constraint for this component constraint. It has constraints for the horizontal size + * and grow/shrink priorities and weights. + * @param h The new dimension constraint. If null it will be reset to new DimConstraint(); + */ + public void setHorizontal(DimConstraint h) + { + hor = h != null ? h : new DimConstraint(); + } + + /** Returns the vertical dimension constraint for this component constraint. It has constraints for the vertical size + * and grow/shrink priorities and weights. + *

+ * Note! If any changes is to be made it must be made direct when the object is returned. It is not allowed to save the + * constraint for later use. + * @return The current dimension constraint. Never null. + */ + public DimConstraint getVertical() + { + return ver; + } + + /** Sets the vertical dimension constraint for this component constraint. It has constraints for the vertical size + * and grow/shrink priorities and weights. + * @param v The new dimension constraint. If null it will be reset to new DimConstraint(); + */ + public void setVertical(DimConstraint v) + { + ver = v != null ? v : new DimConstraint(); + } + + /** Returns the vertical or horizontal dim constraint. + *

+ * Note! If any changes is to be made it must be made direct when the object is returned. It is not allowed to save the + * constraint for later use. + * @param isHor If the horizontal constraint should be returned. + * @return The dim constraint. Never null. + */ + public DimConstraint getDimConstraint(boolean isHor) + { + return isHor ? hor : ver; + } + + /** Returns the absolute positioning of one or more of the edges. This will be applied last in the layout cycle and will not + * affect the flow or grid positions. The positioning is relative to the parent and can not (as padding) be used + * to adjust the edges relative to the old value. May be null and elements may be null. + * null value(s) for the x2 and y2 will be interpreted as to keep the preferred size and thus the x1 + * and x2 will just absolutely positions the component. + *

+ * Note that {@link #setBoundsInGrid(boolean)} changes the interpretation of this property slightly. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value as a new array, free to modify. + */ + public UnitValue[] getPos() + { + return pos != null ? new UnitValue[] {pos[0], pos[1], pos[2], pos[3]} : null; + } + + /** Sets absolute positioning of one or more of the edges. This will be applied last in the layout cycle and will not + * affect the flow or grid positions. The positioning is relative to the parent and can not (as padding) be used + * to adjust the edges relative to the old value. May be null and elements may be null. + * null value(s) for the x2 and y2 will be interpreted as to keep the preferred size and thus the x1 + * and x2 will just absolutely positions the component. + *

+ * Note that {@link #setBoundsInGrid(boolean)} changes the interpretation of this property slightly. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param pos UnitValue[] {x, y, x2, y2}. Must be null or of length 4. Elements can be null. + */ + public void setPos(UnitValue[] pos) + { + this.pos = pos != null ? new UnitValue[] {pos[0], pos[1], pos[2], pos[3]} : null; + linkTargets = null; + } + + /** Returns if the absolute pos value should be corrections to the component that is in a normal cell. If false + * the value of pos is truly absolute in that it will not affect the grid or have a default bounds in the grid. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + * @see #getPos() + */ + public boolean isBoundsInGrid() + { + return boundsInGrid; + } + + /** Sets if the absolute pos value should be corrections to the component that is in a normal cell. If false + * the value of pos is truly absolute in that it will not affect the grid or have a default bounds in the grid. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param b true for bounds taken from the grid position. false is default. + * @see #setPos(UnitValue[]) + */ + void setBoundsInGrid(boolean b) + { + this.boundsInGrid = b; + } + + /** Returns the absolute cell position in the grid or -1 if cell positioning is not used. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public int getCellX() + { + return cellX; + } + + /** Set an absolute cell x-position in the grid. If >= 0 this point points to the absolute cell that this constaint's component should occupy. + * If there's already a component in that cell they will split the cell. The flow will then continue after this cell. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param x The x-position or -1 to disable cell positioning. + */ + public void setCellX(int x) + { + cellX = x; + } + + /** Returns the absolute cell position in the grid or -1 if cell positioning is not used. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public int getCellY() + { + return cellX < 0 ? -1 : cellY; + } + + /** Set an absolute cell x-position in the grid. If >= 0 this point points to the absolute cell that this constaint's component should occupy. + * If there's already a component in that cell they will split the cell. The flow will then continue after this cell. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param y The y-position or -1 to disable cell positioning. + */ + public void setCellY(int y) + { + if (y < 0) + cellX = -1; + cellY = y < 0 ? 0 : y; + } + + /** Sets the docking side. -1 means no docking.
+ * Valid sides are: north = 0, west = 1, south = 2, east = 3. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current side. + */ + public int getDockSide() + { + return dock; + } + + /** Sets the docking side. -1 means no docking.
+ * Valid sides are: north = 0, west = 1, south = 2, east = 3. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param side -1 or 0-3. + */ + public void setDockSide(int side) + { + if (side < -1 || side > 3) + throw new IllegalArgumentException("Illegal dock side: " + side); + dock = side; + } + + /** Returns if this component should have its bounds handled by an external source and not this layout manager. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public boolean isExternal() + { + return external; + } + + /** If this boolean is true this component is not handled in any way by the layout manager and the component can have its bounds set by an external + * handler which is normally by the use of some component.setBounds(x, y, width, height) directly (for Swing). + *

+ * The bounds will not affect the minimum and preferred size of the container. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param b true means that the bounds are not changed. + */ + public void setExternal(boolean b) + { + this.external = b; + } + + /** Returns if the flow in the cell is in the horizontal dimension. Vertical if false. Only the first + * component is a cell can set the flow. + *

+ * If null the flow direction is inherited by from the {@link net.miginfocom.layout.LC}. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public Boolean getFlowX() + { + return flowX; + } + + /** Sets if the flow in the cell is in the horizontal dimension. Vertical if false. Only the first + * component is a cell can set the flow. + *

+ * If null the flow direction is inherited by from the {@link net.miginfocom.layout.LC}. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param b Boolean.TRUE means horizontal flow in the cell. + */ + public void setFlowX(Boolean b) + { + this.flowX = b; + } + + /** Sets how a component that is hidden (not visible) should be treated by default. + * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The mode:
+ * 0 == Normal. Bounds will be calculated as if the component was visible.
+ * 1 == If hidden the size will be 0, 0 but the gaps remain.
+ * 2 == If hidden the size will be 0, 0 and gaps set to zero.
+ * 3 == If hidden the component will be disregarded completely and not take up a cell in the grid.. + */ + public int getHideMode() + { + return hideMode; + } + + /** Sets how a component that is hidden (not visible) should be treated by default. + * @param mode The mode:
+ * 0 == Normal. Bounds will be calculated as if the component was visible.
+ * 1 == If hidden the size will be 0, 0 but the gaps remain.
+ * 2 == If hidden the size will be 0, 0 and gaps set to zero.
+ * 3 == If hidden the component will be disregarded completely and not take up a cell in the grid.. + */ + public void setHideMode(int mode) + { + if (mode < -1 || mode > 3) + throw new IllegalArgumentException("Wrong hideMode: " + mode); + + hideMode = mode; + } + + /** Returns the id used to reference this component in some constraints. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The id or null. May consist of a groupID and an componentID which are separated by a dot: ".". E.g. "grp1.id1". + * The dot should never be first or last if present. + */ + public String getId() + { + return id; + } + + /** Sets the id used to reference this component in some constraints. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param id The id or null. May consist of a groupID and an componentID which are separated by a dot: ".". E.g. "grp1.id1". + * The dot should never be first or last if present. + */ + public void setId(String id) + { + this.id = id; + } + + /** Returns the absolute resizing in the last stage of the layout cycle. May be null and elements may be null. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. null or of length 4. + */ + public UnitValue[] getPadding() + { + return padding != null ? new UnitValue[] {padding[0], padding[1], padding[2], padding[3]} : null; + } + + /** Sets the absolute resizing in the last stage of the layout cycle. These values are added to the edges and can thus for + * instance be used to grow or reduce the size or move the component an absolute number of pixels. May be null + * and elements may be null. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param sides top, left, bottom right. Must be null or of length 4. + */ + public void setPadding(UnitValue[] sides) + { + this.padding = sides != null ? new UnitValue[] {sides[0], sides[1], sides[2], sides[3]} : null; + } + + /** Returns the visual padding used when laying out this Component. May be null and elements may be null. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. null or of length 4. + */ + public UnitValue[] getVisualPadding() + { + return visualPadding != null ? new UnitValue[] {visualPadding[0], visualPadding[1], visualPadding[2], visualPadding[3]} : null; + } + + /** Sets the visual padding used when laying out this Component. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param sides top, left, bottom right. Must be null or of length 4. + */ + public void setVisualPadding(UnitValue[] sides) + { + this.visualPadding = sides != null ? new UnitValue[] {sides[0], sides[1], sides[2], sides[3]} : null; + } + + /** Returns how many cells in the grid that should be skipped before the component that this constraint belongs to. + *

+ * Note that only the first component will be checked for this property. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. 0 if no skip. + */ + public int getSkip() + { + return skip; + } + + /** Sets how many cells in the grid that should be skipped before the component that this constraint belongs to. + *

+ * Note that only the first component will be checked for this property. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param cells How many cells in the grid that should be skipped before the component that this constraint belongs to + */ + public void setSkip(int cells) + { + this.skip = cells; + } + + /** Returns the number of cells the cell that this constraint's component will span in the indicated dimension. 1 is default and + * means that it only spans the current cell. LayoutUtil.INF is used to indicate a span to the end of the column/row. + *

+ * Note that only the first component will be checked for this property. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public int getSpanX() + { + return spanX; + } + + /** Sets the number of cells the cell that this constraint's component will span in the indicated dimension. 1 is default and + * means that it only spans the current cell. LayoutUtil.INF is used to indicate a span to the end of the column/row. + *

+ * Note that only the first component will be checked for this property. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param cells The number of cells to span (i.e. merge). + */ + public void setSpanX(int cells) + { + this.spanX = cells; + } + + /** Returns the number of cells the cell that this constraint's component will span in the indicated dimension. 1 is default and + * means that it only spans the current cell. LayoutUtil.INF is used to indicate a span to the end of the column/row. + *

+ * Note that only the first component will be checked for this property. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public int getSpanY() + { + return spanY; + } + + /** Sets the number of cells the cell that this constraint's component will span in the indicated dimension. 1 is default and + * means that it only spans the current cell. LayoutUtil.INF is used to indicate a span to the end of the column/row. + *

+ * Note that only the first component will be checked for this property. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param cells The number of cells to span (i.e. merge). + */ + public void setSpanY(int cells) + { + this.spanY = cells; + } + + /** "pushx" indicates that the column that this component is in (this first if the component spans) should default to growing. + * If any other column has been set to grow this push value on the component does nothing as the column's explicit grow weight + * will take precedence. Push is normally used when the grid has not been defined in the layout. + *

+ * If multiple components in a column has push weights set the largest one will be used for the column. + * @return The current push value. Default is null. + */ + public Float getPushX() + { + return pushX; + } + + /** "pushx" indicates that the column that this component is in (this first if the component spans) should default to growing. + * If any other column has been set to grow this push value on the component does nothing as the column's explicit grow weight + * will take precedence. Push is normally used when the grid has not been defined in the layout. + *

+ * If multiple components in a column has push weights set the largest one will be used for the column. + * @param weight The new push value. Default is null. + */ + public void setPushX(Float weight) + { + this.pushX = weight; + } + + /** "pushx" indicates that the row that this component is in (this first if the component spans) should default to growing. + * If any other row has been set to grow this push value on the component does nothing as the row's explicit grow weight + * will take precedence. Push is normally used when the grid has not been defined in the layout. + *

+ * If multiple components in a row has push weights set the largest one will be used for the row. + * @return The current push value. Default is null. + */ + public Float getPushY() + { + return pushY; + } + + /** "pushx" indicates that the row that this component is in (this first if the component spans) should default to growing. + * If any other row has been set to grow this push value on the component does nothing as the row's explicit grow weight + * will take precedence. Push is normally used when the grid has not been defined in the layout. + *

+ * If multiple components in a row has push weights set the largest one will be used for the row. + * @param weight The new push value. Default is null. + */ + public void setPushY(Float weight) + { + this.pushY = weight; + } + + /** Returns in how many parts the current cell (that this constraint's component will be in) should be split in. If for instance + * it is split in two, the next component will also share the same cell. Note that the cell can also span a number of + * cells, which means that you can for instance span three cells and split that big cell for two components. Split can be + * set to a very high value to make all components in the same row/column share the same cell (e.g. LayoutUtil.INF). + *

+ * Note that only the first component will be checked for this property. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public int getSplit() + { + return split; + } + + /** Sets in how many parts the current cell (that this constraint's component will be in) should be split in. If for instance + * it is split in two, the next component will also share the same cell. Note that the cell can also span a number of + * cells, which means that you can for instance span three cells and split that big cell for two components. Split can be + * set to a very high value to make all components in the same row/column share the same cell (e.g. LayoutUtil.INF). + *

+ * Note that only the first component will be checked for this property. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param parts The number of parts (i.e. component slots) the cell should be divided into. + */ + public void setSplit(int parts) + { + this.split = parts; + } + + /** Tags the component with metadata. Currently only used to tag buttons with for instance "cancel" or "ok" to make them + * show up in the correct order depending on platform. See {@link PlatformDefaults#setButtonOrder(String)} for information. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. May be null. + */ + public String getTag() + { + return tag; + } + + /** Optional tag that gives more context to this constraint's component. It is for instance used to tag buttons in a + * button bar with the button type such as "ok", "help" or "cancel". + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param tag The new tag. May be null. + */ + public void setTag(String tag) + { + this.tag = tag; + } + + /** Returns if the flow should wrap to the next line/column after the component that this constraint belongs to. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public boolean isWrap() + { + return wrap != null; + } + + /** Sets if the flow should wrap to the next line/column after the component that this constraint belongs to. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param b true means wrap after. + */ + public void setWrap(boolean b) + { + wrap = b ? (wrap == null ? DEF_GAP : wrap) : null; + } + + /** Returns the wrap size if it is a custom size. If wrap was set to true with {@link #setWrap(boolean)} then this method will + * return null since that means that the gap size should be the default one as defined in the rows spec. + * @return The custom gap size. NOTE! Will return null for both no wrap and default wrap. + * @see #isWrap() + * @see #setWrap(boolean) + * @since 2.4.2 + */ + public BoundSize getWrapGapSize() + { + return wrap == DEF_GAP ? null : wrap; + } + + /** Set the wrap size and turns wrap on if != null. + * @param s The custom gap size. NOTE! null will not turn on or off wrap, it will only set the wrap gap size to "default". + * A non-null value will turn on wrap though. + * @see #isWrap() + * @see #setWrap(boolean) + * @since 2.4.2 + */ + public void setWrapGapSize(BoundSize s) + { + wrap = s == null ? (wrap != null ? DEF_GAP : null) : s; + } + + /** Returns if the flow should wrap to the next line/column before the component that this constraint belongs to. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current value. + */ + public boolean isNewline() + { + return newline != null; + } + + /** Sets if the flow should wrap to the next line/column before the component that this constraint belongs to. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param b true means wrap before. + */ + public void setNewline(boolean b) + { + newline = b ? (newline == null ? DEF_GAP : newline) : null; + } + + /** Returns the newline size if it is a custom size. If newline was set to true with {@link #setNewline(boolean)} then this method will + * return null since that means that the gap size should be the default one as defined in the rows spec. + * @return The custom gap size. NOTE! Will return null for both no newline and default newline. + * @see #isNewline() + * @see #setNewline(boolean) + * @since 2.4.2 + */ + public BoundSize getNewlineGapSize() + { + return newline == DEF_GAP ? null : newline; + } + + /** Set the newline size and turns newline on if != null. + * @param s The custom gap size. NOTE! null will not turn on or off newline, it will only set the newline gap size to "default". + * A non-null value will turn on newline though. + * @see #isNewline() + * @see #setNewline(boolean) + * @since 2.4.2 + */ + public void setNewlineGapSize(BoundSize s) + { + newline = s == null ? (newline != null ? DEF_GAP : null) : s; + } + + /** Returns the animation spec. Default is a spec where animation is off (prio 0). + * @return Never null. + */ + public AnimSpec getAnimSpec() + { + return animSpec; + } + + +// // ************************************************ +// // Persistence Delegate and Serializable combined. +// // ************************************************ +// +// private Object readResolve() throws ObjectStreamException +// { +// return LayoutUtil.getSerializedObject(this); +// } +// +// @Override +// public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException +// { +// LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in)); +// } +// +// @Override +// public void writeExternal(ObjectOutput out) throws IOException +// { +// if (getClass() == CC.class) +// LayoutUtil.writeAsXML(out, this); +// } +} \ No newline at end of file diff --git a/src/net/miginfocom/layout/ComponentWrapper.java b/src/net/miginfocom/layout/ComponentWrapper.java new file mode 100644 index 0000000..7a97297 --- /dev/null +++ b/src/net/miginfocom/layout/ComponentWrapper.java @@ -0,0 +1,305 @@ +package net.miginfocom.layout; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** A class that wraps the important parts of a Component. + *

+ * NOTE!.equals() and .hashcode() should be forwarded to the wrapped component. E.g. + *

+ * 	public int hashCode()
+	{
+		return getComponent().hashCode();
+	}
+
+	public final boolean equals(Object o)
+	{
+		 if (o instanceof ComponentWrapper == false)
+			 return false;
+
+		 return getComponent().equals(((ComponentWrapper) o).getComponent());
+	}
+ * 
+ */ +public interface ComponentWrapper +{ + static final int TYPE_UNSET = -1; + public static final int TYPE_UNKNOWN = 0; + public static final int TYPE_CONTAINER = 1; + public static final int TYPE_LABEL = 2; + public static final int TYPE_TEXT_FIELD = 3; + public static final int TYPE_TEXT_AREA = 4; + public static final int TYPE_BUTTON = 5; + public static final int TYPE_LIST = 6; + public static final int TYPE_TABLE = 7; + public static final int TYPE_SCROLL_PANE = 8; + public static final int TYPE_IMAGE = 9; + public static final int TYPE_PANEL = 10; + public static final int TYPE_COMBO_BOX = 11; + public static final int TYPE_SLIDER = 12; + public static final int TYPE_SPINNER = 13; + public static final int TYPE_PROGRESS_BAR = 14; + public static final int TYPE_TREE = 15; + public static final int TYPE_CHECK_BOX = 16; + public static final int TYPE_SCROLL_BAR = 17; + public static final int TYPE_SEPARATOR = 18; + public static final int TYPE_TABBED_PANE = 19; + + /** Returns the actual object that this wrapper is aggregating. This might be needed for getting + * information about the object that the wrapper interface does not provide. + *

+ * If this is a container the container should be returned instead. + * @return The actual object that this wrapper is aggregating. Not null. + */ + public abstract Object getComponent(); + + /** Returns the current x coordinate for this component. + * @return The current x coordinate for this component. + */ + public abstract int getX(); + + /** Returns the current y coordinate for this component. + * @return The current y coordinate for this component. + */ + public abstract int getY(); + + /** Returns the current width for this component. + * @return The current width for this component. + */ + public abstract int getWidth(); + + /** Returns the current height for this component. + * @return The current height for this component. + */ + public abstract int getHeight(); + + /** Returns the screen x-coordinate for the upper left coordinate of the component layout-able bounds. + * @return The screen x-coordinate for the upper left coordinate of the component layout-able bounds. + */ + public abstract int getScreenLocationX(); + + /** Returns the screen y-coordinate for the upper left coordinate of the component layout-able bounds. + * @return The screen y-coordinate for the upper left coordinate of the component layout-able bounds. + */ + public abstract int getScreenLocationY(); + + /** Returns the minimum width of the component. + * @param hHint The Size hint for the other dimension. An implementation can use this value or the + * current size for the widget in this dimension, or a combination of both, to calculate the correct size.
+ * Use -1 to denote that there is no hint. This corresponds with SWT.DEFAULT. + * @return The minimum width of the component. + * @since 3.5. Added the hint as a parameter knowing that a correction and recompilation is necessary for + * any implementing classes. This change was worth it though. + */ + public abstract int getMinimumWidth(int hHint); + + /** Returns the minimum height of the component. + * @param wHint The Size hint for the other dimension. An implementation can use this value or the + * current size for the widget in this dimension, or a combination of both, to calculate the correct size.
+ * Use -1 to denote that there is no hint. This corresponds with SWT.DEFAULT. + * @return The minimum height of the component. + * @since 3.5. Added the hint as a parameter knowing that a correction and recompilation is necessary for + * any implementing classes. This change was worth it though. + */ + public abstract int getMinimumHeight(int wHint); + + /** Returns the preferred width of the component. + * @param hHint The Size hint for the other dimension. An implementation can use this value or the + * current size for the widget in this dimension, or a combination of both, to calculate the correct size.
+ * Use -1 to denote that there is no hint. This corresponds with SWT.DEFAULT. + * @return The preferred width of the component. + * @since 3.5. Added the hint as a parameter knowing that a correction and recompilation is necessary for + * any implementing classes. This change was worth it though. + */ + public abstract int getPreferredWidth(int hHint); + + /** Returns the preferred height of the component. + * @param wHint The Size hint for the other dimension. An implementation can use this value or the + * current size for the widget in this dimension, or a combination of both, to calculate the correct size.
+ * Use -1 to denote that there is no hint. This corresponds with SWT.DEFAULT. + * @return The preferred height of the component. + * @since 3.5. Added the hint as a parameter knowing that a correction and recompilation is necessary for + * any implementing classes. This change was worth it though. + */ + public abstract int getPreferredHeight(int wHint); + + /** Returns the maximum width of the component. + * @param hHint The Size hint for the other dimension. An implementation can use this value or the + * current size for the widget in this dimension, or a combination of both, to calculate the correct size.
+ * Use -1 to denote that there is no hint. This corresponds with SWT.DEFAULT. + * @return The maximum width of the component. + * @since 3.5. Added the hint as a parameter knowing that a correction and recompilation is necessary for + * any implementing classes. This change was worth it though. + */ + public abstract int getMaximumWidth(int hHint); + + /** Returns the maximum height of the component. + * @param wHint The Size hint for the other dimension. An implementation can use this value or the + * current size for the widget in this dimension, or a combination of both, to calculate the correct size.
+ * Use -1 to denote that there is no hint. This corresponds with SWT.DEFAULT. + * @return The maximum height of the component. + * @since 3.5. Added the hint as a parameter knowing that a correction and recompilation is necessary for + * any implementing classes. This change was worth it though. + */ + public abstract int getMaximumHeight(int wHint); + + /** Sets the component's bounds. + * @param x The x coordinate. + * @param y The y coordinate. + * @param width The width. + * @param height The height. + */ + public abstract void setBounds(int x, int y, int width, int height); + + /** Returns if the component's visibility is set to true. This should not return if the component is + * actually visible, but if the visibility is set to true or not. + * @return true means visible. + */ + public abstract boolean isVisible(); + + /** Returns the baseline for the component given the suggested height. + * @param width The width to calculate for if other than the current. If -1 the current size should be used. + * @param height The height to calculate for if other than the current. If -1 the current size should be used. + * @return The baseline from the top or -1 if not applicable. + */ + public abstract int getBaseline(int width, int height); + + /** Returns if the component has a baseline and if it can be retrieved. Should for instance return + * false for Swing before mustang. + * @return If the component has a baseline and if it can be retrieved. + */ + public abstract boolean hasBaseline(); + + /** Returns the container for this component. + * @return The container for this component. Will return null if the component has no parent. + */ + public abstract ContainerWrapper getParent(); + + /** Returns the pixel unit factor for the horizontal or vertical dimension. + *

+ * The factor is 1 for both dimensions on the normal font in a JPanel on Windows. The factor should increase with a bigger "X". + *

+ * This is the Swing version: + *

+	 * Rectangle2D r = fm.getStringBounds("X", parent.getGraphics());
+	 * wFactor = r.getWidth() / 6;
+	 * hFactor = r.getHeight() / 13.27734375f;
+	 * 
+ * @param isHor If it is the horizontal factor that should be returned. + * @return The factor. + */ + public abstract float getPixelUnitFactor(boolean isHor); + + /** Returns the DPI (Dots Per Inch) of the screen the component is currently in or for the default + * screen if the component is not visible. + *

+ * If headless mode {@link net.miginfocom.layout.PlatformDefaults#getDefaultDPI} will be returned. + * @return The DPI. + */ + public abstract int getHorizontalScreenDPI(); + + /** Returns the DPI (Dots Per Inch) of the screen the component is currently in or for the default + * screen if the component is not visible. + *

+ * If headless mode {@link net.miginfocom.layout.PlatformDefaults#getDefaultDPI} will be returned. + * @return The DPI. + */ + public abstract int getVerticalScreenDPI(); + + /** Returns the pixel size of the screen that the component is currently in or for the default + * screen if the component is not visible or null. + *

+ * If in headless mode 1024 is returned. + * @return The screen size. E.g. 1280. + */ + public abstract int getScreenWidth(); + + /** Returns the pixel size of the screen that the component is currently in or for the default + * screen if the component is not visible or null. + *

+ * If in headless mode 768 is returned. + * @return The screen size. E.g. 1024. + */ + public abstract int getScreenHeight(); + + /** Returns a String id that can be used to reference the component in link constraints. This value should + * return the default id for the component. The id can be set for a component in the constraints and if + * so the value returned by this method will never be used. If there are no sensible id for the component + * null should be returned. + *

+ * For instance the Swing implementation returns the string returned from Component.getName(). + * @return The string link id or null. + */ + public abstract String getLinkId(); + + /** Returns a hash code that should be reasonably different for anything that might change the layout. This value is used to + * know if the component layout needs to clear any caches. + * @return A hash code that should be reasonably different for anything that might change the layout. Returns -1 if the widget is + * disposed. + */ + public abstract int getLayoutHashCode(); + + /** Returns the padding on a component by component basis. This method can be overridden to return padding to compensate for example for + * borders that have shadows or where the outer most pixel is not the visual "edge" to align to. + *

+ * Default implementation returns null for all components except for Windows XP's JTabbedPane which will return new Insets(0, 0, 2, 2). + *

+ * NOTE! To reduce generated garbage the returned padding should never be changed so that the same insets can be returned many times. + * @return null if no padding. NOTE! To reduce generated garbage the returned padding should never be changed so that + * the same insets can be returned many times. [top, left, bottom, right] + */ + public int[] getVisualPadding(); + + /** Paints component outline to indicate where it is. + * @param showVisualPadding If the visual padding should be shown in the debug drawing. + */ + public abstract void paintDebugOutline(boolean showVisualPadding); + + /** Returns the type of component that this wrapper is wrapping. + *

+ * This method can be invoked often so the result should be cached. + *

+ * @param disregardScrollPane Is true any wrapping scroll pane should be disregarded and the type + * of the scrolled component should be returned. + * @return The type of component that this wrapper is wrapping. E.g. {@link #TYPE_LABEL}. + */ + public abstract int getComponentType(boolean disregardScrollPane); + + /** Returns in what way the min/pref/max sizes relates to it's height or width for the current settings of the component (like wrapText). + * If the min/pref/max height depends on it's width return {@link net.miginfocom.layout.LayoutUtil#HORIZONTAL} + * If the min/pref/max width depends on it's height (not common) return {@link net.miginfocom.layout.LayoutUtil#VERTICAL} + * If there is no connection between the preferred min/pref/max and the size of the component return -1. + * @since 5.0 + */ + public abstract int getContentBias(); +} \ No newline at end of file diff --git a/src/net/miginfocom/layout/ConstraintParser.java b/src/net/miginfocom/layout/ConstraintParser.java new file mode 100644 index 0000000..f819fde --- /dev/null +++ b/src/net/miginfocom/layout/ConstraintParser.java @@ -0,0 +1,1481 @@ +package net.miginfocom.layout; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** Parses string constraints. + */ +public final class ConstraintParser +{ + private ConstraintParser() + { + } + + /** Parses the layout constraints and stores the parsed values in the transient (cache) member variables. + * @param s The String to parse. Should not be null and must be lower case and trimmed. + * @throws RuntimeException if the constraint was not valid. + * @return The parsed constraint. Never null. + */ + public static LC parseLayoutConstraint(String s) + { + LC lc = new LC(); + if (s.isEmpty()) + return lc; + + String[] parts = toTrimmedTokens(s, ','); + + // First check for "ltr" or "rtl" since that will affect the interpretation of the other constraints. + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + if (part == null) + continue; + + int len = part.length(); + if (len == 3 || len == 11) { // Optimization + if (part.equals("ltr") || part.equals("rtl") || part.equals("lefttoright") || part.equals("righttoleft")) { + lc.setLeftToRight(part.charAt(0) == 'l' ? Boolean.TRUE : Boolean.FALSE); + parts[i] = null; // So we will not try to interpret it again + } + + if (part.equals("ttb") || part.equals("btt") || part.equals("toptobottom") || part.equals("bottomtotop")) { + lc.setTopToBottom(part.charAt(0) == 't'); + parts[i] = null; // So we will not try to interpret it again + } + } + } + + for (String part : parts) { + if (part == null || part.length() == 0) + continue; + + try { + int ix = -1; + char c = part.charAt(0); + + if (c == 'w' || c == 'h') { + + ix = startsWithLenient(part, "wrap", -1, true); + if (ix > -1) { + String num = part.substring(ix).trim(); + lc.setWrapAfter(num.length() != 0 ? Integer.parseInt(num) : 0); + continue; + } + + boolean isHor = c == 'w'; + if (isHor && (part.startsWith("w ") || part.startsWith("width "))) { + String sz = part.substring(part.charAt(1) == ' ' ? 2 : 6).trim(); + lc.setWidth(parseBoundSize(sz, false, true)); + continue; + } + + if (!isHor && (part.startsWith("h ") || part.startsWith("height "))) { + String uvStr = part.substring(part.charAt(1) == ' ' ? 2 : 7).trim(); + lc.setHeight(parseBoundSize(uvStr, false, false)); + continue; + } + + if (part.length() > 5) { + String sz = part.substring(5).trim(); + if (part.startsWith("wmin ")) { + lc.minWidth(sz); + continue; + } else if (part.startsWith("wmax ")) { + lc.maxWidth(sz); + continue; + } else if (part.startsWith("hmin ")) { + lc.minHeight(sz); + continue; + } else if (part.startsWith("hmax ")) { + lc.maxHeight(sz); + continue; + } + } + + if (part.startsWith("hidemode ")) { + lc.setHideMode(Integer.parseInt(part.substring(9))); + continue; + } + } + + if (c == 'g') { + if (part.startsWith("gapx ")) { + lc.setGridGapX(parseBoundSize(part.substring(5).trim(), true, true)); + continue; + } + + if (part.startsWith("gapy ")) { + lc.setGridGapY(parseBoundSize(part.substring(5).trim(), true, false)); + continue; + } + + if (part.startsWith("gap ")) { + String[] gaps = toTrimmedTokens(part.substring(4).trim(), ' '); + lc.setGridGapX(parseBoundSize(gaps[0], true, true)); + lc.setGridGapY(gaps.length > 1 ? parseBoundSize(gaps[1], true, false) : lc.getGridGapX()); + continue; + } + } + + if (c == 'd') { + ix = startsWithLenient(part, "debug", 5, true); + if (ix > -1) { + String millis = part.substring(ix).trim(); + lc.setDebugMillis(millis.length() > 0 ? Integer.parseInt(millis) : 1000); + continue; + } + } + + if (c == 'n') { + if (part.equals("nogrid")) { + lc.setNoGrid(true); + continue; + } + + if (part.equals("nocache")) { + lc.setNoCache(true); + continue; + } + + if (part.equals("novisualpadding")) { + lc.setVisualPadding(false); + continue; + } + } + + if (c == 'f') { + if (part.equals("fill") || part.equals("fillx") || part.equals("filly")) { + lc.setFillX(part.length() == 4 || part.charAt(4) == 'x'); + lc.setFillY(part.length() == 4 || part.charAt(4) == 'y'); + continue; + } + + if (part.equals("flowy")) { + lc.setFlowX(false); + continue; + } + + if (part.equals("flowx")) { + lc.setFlowX(true); // This is the default but added for consistency + continue; + } + } + + if (c == 'i') { + ix = startsWithLenient(part, "insets", 3, true); + if (ix > -1) { + String insStr = part.substring(ix).trim(); + UnitValue[] ins = parseInsets(insStr, true); + LayoutUtil.putCCString(ins, insStr); + lc.setInsets(ins); + continue; + } + } + + if (c == 'a') { + ix = startsWithLenient(part, new String[]{"aligny", "ay"}, new int[]{6, 2}, true); + if (ix > -1) { + UnitValue align = parseUnitValueOrAlign(part.substring(ix).trim(), false, null); + if (align == UnitValue.BASELINE_IDENTITY) + throw new IllegalArgumentException("'baseline' can not be used to align the whole component group."); + lc.setAlignY(align); + continue; + } + + ix = startsWithLenient(part, new String[]{"alignx", "ax"}, new int[]{6, 2}, true); + if (ix > -1) { + lc.setAlignX(parseUnitValueOrAlign(part.substring(ix).trim(), true, null)); + continue; + } + + ix = startsWithLenient(part, "align", 2, true); + if (ix > -1) { + String[] gaps = toTrimmedTokens(part.substring(ix).trim(), ' '); + lc.setAlignX(parseUnitValueOrAlign(gaps[0], true, null)); + if (gaps.length > 1) { + UnitValue align = parseUnitValueOrAlign(gaps[1], false, null); + if (align == UnitValue.BASELINE_IDENTITY) + throw new IllegalArgumentException("'baseline' can not be used to align the whole component group."); + lc.setAlignY(align); + } + continue; + } + } + + if (c == 'p') { + if (part.startsWith("packalign ")) { + String[] packs = toTrimmedTokens(part.substring(10).trim(), ' '); + lc.setPackWidthAlign(packs[0].length() > 0 ? Float.parseFloat(packs[0]) : 0.5f); + if (packs.length > 1) + lc.setPackHeightAlign(Float.parseFloat(packs[1])); + continue; + } + + if (part.startsWith("pack ") || part.equals("pack")) { + String ps = part.substring(4).trim(); + String[] packs = toTrimmedTokens(ps.length() > 0 ? ps : "pref pref", ' '); + lc.setPackWidth(parseBoundSize(packs[0], false, true)); + if (packs.length > 1) + lc.setPackHeight(parseBoundSize(packs[1], false, false)); + + continue; + } + } + + if (lc.getAlignX() == null) { + UnitValue alignX = parseAlignKeywords(part, true); + if (alignX != null) { + lc.setAlignX(alignX); + continue; + } + } + + UnitValue alignY = parseAlignKeywords(part, false); + if (alignY != null) { + lc.setAlignY(alignY); + continue; + } + + throw new IllegalArgumentException("Unknown Constraint: '" + part + "'\n"); + + } catch (Exception ex) { + throw new IllegalArgumentException("Illegal Constraint: '" + part + "'\n" + ex.getMessage()); + } + } + +// lc = (LC) serializeTest(lc); + + return lc; + } + + /** Parses the column or rows constraints. They normally looks something like "[min:pref]rel[10px][]". + * @param s The string to parse. Not null. + * @return An array of {@link DimConstraint}s that is as many are there exist "[...]" sections in the string that is parsed. + * @throws RuntimeException if the constraint was not valid. + */ + public static AC parseRowConstraints(String s) + { + return parseAxisConstraint(s, false); + } + + /** Parses the column or rows constraints. They normally looks something like "[min:pref]rel[10px][]". + * @param s The string to parse. Not null. + * @return An array of {@link DimConstraint}s that is as many are there exist "[...]" sections in the string that is parsed. + * @throws RuntimeException if the constraint was not valid. + */ + public static AC parseColumnConstraints(String s) + { + return parseAxisConstraint(s, true); + } + + /** Parses the column or rows constraints. They normally looks something like "[min:pref]rel[10px][]". + * @param s The string to parse. Not null. + * @param isCols If this for columns rather than rows. + * @return An array of {@link DimConstraint}s that is as many are there exist "[...]" sections in the string that is parsed. + * @throws RuntimeException if the constraint was not valid. + */ + private static AC parseAxisConstraint(String s, boolean isCols) + { + s = s.trim(); + + if (s.length() == 0) + return new AC(); // Short circuit for performance. + + s = s.toLowerCase(); + + ArrayList parts = getRowColAndGapsTrimmed(s); + + BoundSize[] gaps = new BoundSize[(parts.size() >> 1) + 1]; + for (int i = 0, iSz = parts.size(), gIx = 0; i < iSz; i += 2, gIx++) + gaps[gIx] = parseBoundSize(parts.get(i), true, isCols); + + DimConstraint[] colSpecs = new DimConstraint[parts.size() >> 1]; + for (int i = 0, gIx = 0; i < colSpecs.length; i++, gIx++) { + if (gIx >= gaps.length - 1) + gIx = gaps.length - 2; + + colSpecs[i] = parseDimConstraint(parts.get((i << 1) + 1), gaps[gIx], gaps[gIx + 1], isCols); + } + + AC ac = new AC(); + ac.setConstaints(colSpecs); + +// ac = (AC) serializeTest(ac); + + return ac; + } + + /** Parses a single column or row constraint. + * @param s The single constraint to parse. May look something like "min:pref,fill,grow". Should not be null and must + * be lower case and trimmed. + * @param gapBefore The default gap "before" the column/row constraint. Can be overridden with a "gap" section within s. + * @param gapAfter The default gap "after" the column/row constraint. Can be overridden with a "gap" section within s. + * @param isCols If the constraints are column constraints rather than row constraints. + * @return A single constraint. Never null. + * @throws RuntimeException if the constraint was not valid. + */ + private static DimConstraint parseDimConstraint(String s, BoundSize gapBefore, BoundSize gapAfter, boolean isCols) + { + DimConstraint dimConstraint = new DimConstraint(); + + // Default values. + dimConstraint.setGapBefore(gapBefore); + dimConstraint.setGapAfter(gapAfter); + + String[] parts = toTrimmedTokens(s, ','); + for (int i = 0; i < parts.length; i++) { + String part = parts[i]; + try { + if (part.length() == 0) + continue; + + if (part.equals("fill")) { + dimConstraint.setFill(true); +// dimConstraint.setAlign(null); // Can not have both fill and alignment (changed for 3.5 since it can have "growy 0") + continue; + } + + if (part.equals("nogrid")) { + dimConstraint.setNoGrid(true); + continue; + } + + int ix = -1; + char c = part.charAt(0); + + if (c == 's') { + ix = startsWithLenient(part, new String[] {"sizegroup", "sg"}, new int[] {5, 2}, true); + if (ix > -1) { + dimConstraint.setSizeGroup(part.substring(ix).trim()); + continue; + } + + + ix = startsWithLenient(part, new String[] {"shrinkprio", "shp"}, new int[] {10, 3}, true); + if (ix > -1) { + dimConstraint.setShrinkPriority(Integer.parseInt(part.substring(ix).trim())); + continue; + } + + ix = startsWithLenient(part, "shrink", 6, true); + if (ix > -1) { + dimConstraint.setShrink(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100)); + continue; + } + } + + if (c == 'g') { + ix = startsWithLenient(part, new String[] {"growpriority", "gp"}, new int[] {5, 2}, true); + if (ix > -1) { + dimConstraint.setGrowPriority(Integer.parseInt(part.substring(ix).trim())); + continue; + } + + ix = startsWithLenient(part, "grow", 4, true); + if (ix > -1) { + dimConstraint.setGrow(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100)); + continue; + } + } + + if (c == 'a') { + ix = startsWithLenient(part, "align", 2, true); + if (ix > -1) { +// if (dimConstraint.isFill() == false) // Swallow, but ignore if fill is set. (changed for 3.5 since it can have "growy 0") + dimConstraint.setAlign(parseUnitValueOrAlign(part.substring(ix).trim(), isCols, null)); + continue; + } + } + + UnitValue align = parseAlignKeywords(part, isCols); + if (align != null) { +// if (dimConstraint.isFill() == false) // Swallow, but ignore if fill is set. (changed for 3.5 since it can have "growy 0") + dimConstraint.setAlign(align); + continue; + } + + // Only min:pref:max still left that is ok + dimConstraint.setSize(parseBoundSize(part, false, isCols)); + + } catch (Exception ex) { + throw new IllegalArgumentException("Illegal constraint: '" + part + "'\n" + ex.getMessage()); + } + } + return dimConstraint; + } + + /** Parses all component constraints and stores the parsed values in the transient (cache) member variables. + * @param constrMap The constraints as Strings. Strings must be lower case and trimmed + * @return The parsed constraints. Never null. + */ + public static Map parseComponentConstraints(Map constrMap) + { + HashMap flowConstrMap = new HashMap(); + + for (Iterator> it = constrMap.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = it.next(); + flowConstrMap.put(entry.getKey(), parseComponentConstraint(entry.getValue())); + } + + return flowConstrMap; + } + + /** Parses one component constraint and returns the parsed value. + * @param s The string to parse. Must be lower case and trimmed. + * @throws RuntimeException if the constraint was not valid. + * @return The parsed constraint. Never null. + */ + public static CC parseComponentConstraint(String s) + { + CC cc = new CC(); + + if (s == null || s.isEmpty()) + return cc; + + String[] parts = toTrimmedTokens(s, ','); + + for (String part : parts) { + try { + if (part.length() == 0) + continue; + + int ix = -1; + char c = part.charAt(0); + + if (c == 'n') { + if (part.equals("north")) { + cc.setDockSide(0); + continue; + } + + if (part.equals("newline")) { + cc.setNewline(true); + continue; + } + + if (part.startsWith("newline ")) { + String gapSz = part.substring(7).trim(); + cc.setNewlineGapSize(parseBoundSize(gapSz, true, true)); + continue; + } + } + + if (c == 'f' && (part.equals("flowy") || part.equals("flowx"))) { + cc.setFlowX(part.charAt(4) == 'x' ? Boolean.TRUE : Boolean.FALSE); + continue; + } + + if (c == 's') { + ix = startsWithLenient(part, "skip", 4, true); + if (ix > -1) { + String num = part.substring(ix).trim(); + cc.setSkip(num.length() != 0 ? Integer.parseInt(num) : 1); + continue; + } + + ix = startsWithLenient(part, "split", 5, true); + if (ix > -1) { + String split = part.substring(ix).trim(); + cc.setSplit(split.length() > 0 ? Integer.parseInt(split) : LayoutUtil.INF); + continue; + } + + if (part.equals("south")) { + cc.setDockSide(2); + continue; + } + + ix = startsWithLenient(part, new String[]{"spany", "sy"}, new int[]{5, 2}, true); + if (ix > -1) { + cc.setSpanY(parseSpan(part.substring(ix).trim())); + continue; + } + + ix = startsWithLenient(part, new String[]{"spanx", "sx"}, new int[]{5, 2}, true); + if (ix > -1) { + cc.setSpanX(parseSpan(part.substring(ix).trim())); + continue; + } + + ix = startsWithLenient(part, "span", 4, true); + if (ix > -1) { + String[] spans = toTrimmedTokens(part.substring(ix).trim(), ' '); + cc.setSpanX(spans[0].length() > 0 ? Integer.parseInt(spans[0]) : LayoutUtil.INF); + cc.setSpanY(spans.length > 1 ? Integer.parseInt(spans[1]) : 1); + continue; + } + + ix = startsWithLenient(part, "shrinkx", 7, true); + if (ix > -1) { + cc.getHorizontal().setShrink(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100)); + continue; + } + + ix = startsWithLenient(part, "shrinky", 7, true); + if (ix > -1) { + cc.getVertical().setShrink(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100)); + continue; + } + + ix = startsWithLenient(part, "shrink", 6, false); + if (ix > -1) { + String[] shrinks = toTrimmedTokens(part.substring(ix).trim(), ' '); + cc.getHorizontal().setShrink(parseFloat(shrinks[0], ResizeConstraint.WEIGHT_100)); + if (shrinks.length > 1) + cc.getVertical().setShrink(parseFloat(shrinks[1], ResizeConstraint.WEIGHT_100)); + continue; + } + + ix = startsWithLenient(part, new String[]{"shrinkprio", "shp"}, new int[]{10, 3}, true); + if (ix > -1) { + String sp = part.substring(ix).trim(); + if (sp.startsWith("x") || sp.startsWith("y")) { // To handle "gpx", "gpy", "shrinkpriorityx", shrinkpriorityy" + (sp.startsWith("x") ? cc.getHorizontal() : cc.getVertical()).setShrinkPriority(Integer.parseInt(sp.substring(2))); + } else { + String[] shrinks = toTrimmedTokens(sp, ' '); + cc.getHorizontal().setShrinkPriority(Integer.parseInt(shrinks[0])); + if (shrinks.length > 1) + cc.getVertical().setShrinkPriority(Integer.parseInt(shrinks[1])); + } + continue; + } + + ix = startsWithLenient(part, new String[]{"sizegroupx", "sizegroupy", "sgx", "sgy"}, new int[]{9, 9, 2, 2}, true); + if (ix > -1) { + String sg = part.substring(ix).trim(); + char lc = part.charAt(ix - 1); + if (lc != 'y') + cc.getHorizontal().setSizeGroup(sg); + if (lc != 'x') + cc.getVertical().setSizeGroup(sg); + continue; + } + } + + if (c == 'g') { + ix = startsWithLenient(part, "growx", 5, true); + if (ix > -1) { + cc.getHorizontal().setGrow(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100)); + continue; + } + + ix = startsWithLenient(part, "growy", 5, true); + if (ix > -1) { + cc.getVertical().setGrow(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100)); + continue; + } + + ix = startsWithLenient(part, "grow", 4, false); + if (ix > -1) { + String[] grows = toTrimmedTokens(part.substring(ix).trim(), ' '); + cc.getHorizontal().setGrow(parseFloat(grows[0], ResizeConstraint.WEIGHT_100)); + cc.getVertical().setGrow(parseFloat(grows.length > 1 ? grows[1] : "", ResizeConstraint.WEIGHT_100)); + continue; + } + + ix = startsWithLenient(part, new String[]{"growprio", "gp"}, new int[]{8, 2}, true); + if (ix > -1) { + String gp = part.substring(ix).trim(); + char c0 = gp.length() > 0 ? gp.charAt(0) : ' '; + if (c0 == 'x' || c0 == 'y') { // To handle "gpx", "gpy", "growpriorityx", growpriorityy" + (c0 == 'x' ? cc.getHorizontal() : cc.getVertical()).setGrowPriority(Integer.parseInt(gp.substring(2))); + } else { + String[] grows = toTrimmedTokens(gp, ' '); + cc.getHorizontal().setGrowPriority(Integer.parseInt(grows[0])); + if (grows.length > 1) + cc.getVertical().setGrowPriority(Integer.parseInt(grows[1])); + } + continue; + } + + if (part.startsWith("gap")) { + BoundSize[] gaps = parseGaps(part); // Changes order!! + if (gaps[0] != null) + cc.getVertical().setGapBefore(gaps[0]); + if (gaps[1] != null) + cc.getHorizontal().setGapBefore(gaps[1]); + if (gaps[2] != null) + cc.getVertical().setGapAfter(gaps[2]); + if (gaps[3] != null) + cc.getHorizontal().setGapAfter(gaps[3]); + continue; + } + } + + if (c == 'a') { + ix = startsWithLenient(part, new String[]{"aligny", "ay"}, new int[]{6, 2}, true); + if (ix > -1) { + cc.getVertical().setAlign(parseUnitValueOrAlign(part.substring(ix).trim(), false, null)); + continue; + } + + ix = startsWithLenient(part, new String[]{"alignx", "ax"}, new int[]{6, 2}, true); + if (ix > -1) { + cc.getHorizontal().setAlign(parseUnitValueOrAlign(part.substring(ix).trim(), true, null)); + continue; + } + + ix = startsWithLenient(part, "align", 2, true); + if (ix > -1) { + String[] gaps = toTrimmedTokens(part.substring(ix).trim(), ' '); + cc.getHorizontal().setAlign(parseUnitValueOrAlign(gaps[0], true, null)); + if (gaps.length > 1) + cc.getVertical().setAlign(parseUnitValueOrAlign(gaps[1], false, null)); + continue; + } + } + + if ((c == 'x' || c == 'y') && part.length() > 2) { + char c2 = part.charAt(1); + if (c2 == ' ' || (c2 == '2' && part.charAt(2) == ' ')) { + if (cc.getPos() == null) { + cc.setPos(new UnitValue[4]); + } else if (cc.isBoundsInGrid() == false) { + throw new IllegalArgumentException("Cannot combine 'position' with 'x/y/x2/y2' keywords."); + } + + int edge = (c == 'x' ? 0 : 1) + (c2 == '2' ? 2 : 0); + UnitValue[] pos = cc.getPos(); + pos[edge] = parseUnitValue(part.substring(2).trim(), null, c == 'x'); + cc.setPos(pos); + cc.setBoundsInGrid(true); + continue; + } + } + + if (c == 'c') { + ix = startsWithLenient(part, "cell", 4, true); + if (ix > -1) { + String[] grs = toTrimmedTokens(part.substring(ix).trim(), ' '); + if (grs.length < 2) + throw new IllegalArgumentException("At least two integers must follow " + part); + cc.setCellX(Integer.parseInt(grs[0])); + cc.setCellY(Integer.parseInt(grs[1])); + if (grs.length > 2) + cc.setSpanX(Integer.parseInt(grs[2])); + if (grs.length > 3) + cc.setSpanY(Integer.parseInt(grs[3])); + continue; + } + } + + if (c == 'p') { + ix = startsWithLenient(part, "pos", 3, true); + if (ix > -1) { + if (cc.getPos() != null && cc.isBoundsInGrid()) + throw new IllegalArgumentException("Can not combine 'pos' with 'x/y/x2/y2' keywords."); + + String[] pos = toTrimmedTokens(part.substring(ix).trim(), ' '); + UnitValue[] bounds = new UnitValue[4]; + for (int j = 0; j < pos.length; j++) + bounds[j] = parseUnitValue(pos[j], null, j % 2 == 0); + + if (bounds[0] == null && bounds[2] == null || bounds[1] == null && bounds[3] == null) + throw new IllegalArgumentException("Both x and x2 or y and y2 can not be null!"); + + cc.setPos(bounds); + cc.setBoundsInGrid(false); + continue; + } + + ix = startsWithLenient(part, "pad", 3, true); + if (ix > -1) { + UnitValue[] p = parseInsets(part.substring(ix).trim(), false); + cc.setPadding(new UnitValue[]{ + p[0], + p.length > 1 ? p[1] : null, + p.length > 2 ? p[2] : null, + p.length > 3 ? p[3] : null}); + continue; + } + + ix = startsWithLenient(part, "pushx", 5, true); + if (ix > -1) { + cc.setPushX(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100)); + continue; + } + + ix = startsWithLenient(part, "pushy", 5, true); + if (ix > -1) { + cc.setPushY(parseFloat(part.substring(ix).trim(), ResizeConstraint.WEIGHT_100)); + continue; + } + + ix = startsWithLenient(part, "push", 4, false); + if (ix > -1) { + String[] pushs = toTrimmedTokens(part.substring(ix).trim(), ' '); + cc.setPushX(parseFloat(pushs[0], ResizeConstraint.WEIGHT_100)); + cc.setPushY(parseFloat(pushs.length > 1 ? pushs[1] : "", ResizeConstraint.WEIGHT_100)); + continue; + } + } + + if (c == 't') { + ix = startsWithLenient(part, "tag", 3, true); + if (ix > -1) { + cc.setTag(part.substring(ix).trim()); + continue; + } + } + + if (c == 'w' || c == 'h') { + if (part.equals("wrap")) { + cc.setWrap(true); + continue; + } + + if (part.startsWith("wrap ")) { + String gapSz = part.substring(5).trim(); + cc.setWrapGapSize(parseBoundSize(gapSz, true, true)); + continue; + } + + boolean isHor = c == 'w'; + if (isHor && (part.startsWith("w ") || part.startsWith("width "))) { + String uvStr = part.substring(part.charAt(1) == ' ' ? 2 : 6).trim(); + cc.getHorizontal().setSize(parseBoundSize(uvStr, false, true)); + continue; + } + + if (!isHor && (part.startsWith("h ") || part.startsWith("height "))) { + String uvStr = part.substring(part.charAt(1) == ' ' ? 2 : 7).trim(); + cc.getVertical().setSize(parseBoundSize(uvStr, false, false)); + continue; + } + + if (part.startsWith("wmin ") || part.startsWith("wmax ") || part.startsWith("hmin ") || part.startsWith("hmax ")) { + String uvStr = part.substring(5).trim(); + if (uvStr.length() > 0) { + UnitValue uv = parseUnitValue(uvStr, null, isHor); + boolean isMin = part.charAt(3) == 'n'; + DimConstraint dc = isHor ? cc.getHorizontal() : cc.getVertical(); + dc.setSize(new BoundSize( + isMin ? uv : dc.getSize().getMin(), + dc.getSize().getPreferred(), + isMin ? (dc.getSize().getMax()) : uv, + uvStr + )); + continue; + } + } + + if (part.equals("west")) { + cc.setDockSide(1); + continue; + } + + if (part.startsWith("hidemode ")) { + cc.setHideMode(Integer.parseInt(part.substring(9))); + continue; + } + } + + if (c == 'i' && part.startsWith("id ")) { + cc.setId(part.substring(3).trim()); + int dIx = cc.getId().indexOf('.'); + if (dIx == 0 || dIx == cc.getId().length() - 1) + throw new IllegalArgumentException("Dot must not be first or last!"); + + continue; + } + + if (c == 'e') { + if (part.equals("east")) { + cc.setDockSide(3); + continue; + } + + if (part.equals("external")) { + cc.setExternal(true); + continue; + } + + ix = startsWithLenient(part, new String[]{"endgroupx", "endgroupy", "egx", "egy"}, new int[]{-1, -1, -1, -1}, true); + if (ix > -1) { + String sg = part.substring(ix).trim(); + char lc = part.charAt(ix - 1); + DimConstraint dc = (lc == 'x' ? cc.getHorizontal() : cc.getVertical()); + dc.setEndGroup(sg); + continue; + } + } + + if (c == 'd') { + if (part.equals("dock north")) { + cc.setDockSide(0); + continue; + } + if (part.equals("dock west")) { + cc.setDockSide(1); + continue; + } + if (part.equals("dock south")) { + cc.setDockSide(2); + continue; + } + if (part.equals("dock east")) { + cc.setDockSide(3); + continue; + } + + if (part.equals("dock center")) { + cc.getHorizontal().setGrow(100f); + cc.getVertical().setGrow(100f); + cc.setPushX(100f); + cc.setPushY(100f); + continue; + } + } + + if (c == 'v') { + ix = startsWithLenient(part, new String[] {"visualpadding", "vp"}, new int[] {3, 2}, true); + if (ix > -1) { + UnitValue[] p = parseInsets(part.substring(ix).trim(), false); + cc.setVisualPadding(new UnitValue[] { + p[0], + p.length > 1 ? p[1] : null, + p.length > 2 ? p[2] : null, + p.length > 3 ? p[3] : null}); + continue; + } + } + + UnitValue horAlign = parseAlignKeywords(part, true); + if (horAlign != null) { + cc.getHorizontal().setAlign(horAlign); + continue; + } + + UnitValue verAlign = parseAlignKeywords(part, false); + if (verAlign != null) { + cc.getVertical().setAlign(verAlign); + continue; + } + + throw new IllegalArgumentException("Unknown keyword."); + + } catch (Exception ex) { + throw new IllegalArgumentException("Error parsing Constraint: '" + part + "'", ex); + } + } + +// cc = (CC) serializeTest(cc); + + return cc; + } + + /** Parses insets which consists of 1-4 UnitValues. + * @param s The string to parse. E.g. "10 10 10 10" or "20". If less than 4 groups the last will be used for the missing. + * @param acceptPanel If "panel" and "dialog" should be accepted. They are used to access platform defaults. + * @return An array of length 4 with the parsed insets. + * @throws IllegalArgumentException if the parsing could not be done. + */ + public static UnitValue[] parseInsets(String s, boolean acceptPanel) + { + if (s.length() == 0 || s.equals("dialog") || s.equals("panel")) { + if (acceptPanel == false) + throw new IllegalArgumentException("Insets now allowed: " + s + "\n"); + + boolean isPanel = s.startsWith("p"); + UnitValue[] ins = new UnitValue[4]; + for (int j = 0; j < 4; j++) + ins[j] = isPanel ? PlatformDefaults.getPanelInsets(j) : PlatformDefaults.getDialogInsets(j); + + return ins; + } else { + String[] insS = toTrimmedTokens(s, ' '); + UnitValue[] ins = new UnitValue[4]; + for (int j = 0; j < 4; j++) { + UnitValue insSz = parseUnitValue(insS[j < insS.length ? j : insS.length - 1], UnitValue.ZERO, j % 2 == 1); + ins[j] = insSz != null ? insSz : PlatformDefaults.getPanelInsets(j); + } + return ins; + } + } + + /** Parses gaps. + * @param s The string that contains gap information. Should start with "gap". + * @return The gaps as specified in s. Indexed: [top,left,bottom,right][min,pref,max] or + * [before,after][min,pref,max] if oneDim is true. + */ + private static BoundSize[] parseGaps(String s) + { + BoundSize[] ret = new BoundSize[4]; + + int ix = startsWithLenient(s, "gaptop", -1, true); + if (ix > -1) { + s = s.substring(ix).trim(); + ret[0] = parseBoundSize(s, true, false); + return ret; + } + + ix = startsWithLenient(s, "gapleft", -1, true); + if (ix > -1) { + s = s.substring(ix).trim(); + ret[1] = parseBoundSize(s, true, true); + return ret; + } + + ix = startsWithLenient(s, "gapbottom", -1, true); + if (ix > -1) { + s = s.substring(ix).trim(); + ret[2] = parseBoundSize(s, true, false); + return ret; + } + + ix = startsWithLenient(s, "gapright", -1, true); + if (ix > -1) { + s = s.substring(ix).trim(); + ret[3] = parseBoundSize(s, true, true); + return ret; + } + + ix = startsWithLenient(s, "gapbefore", -1, true); + if (ix > -1) { + s = s.substring(ix).trim(); + ret[1] = parseBoundSize(s, true, true); + return ret; + } + + ix = startsWithLenient(s, "gapafter", -1, true); + if (ix > -1) { + s = s.substring(ix).trim(); + ret[3] = parseBoundSize(s, true, true); + return ret; + } + + ix = startsWithLenient(s, new String[] {"gapx", "gapy"}, null, true); + if (ix > -1) { + boolean x = s.charAt(3) == 'x'; + String[] gaps = toTrimmedTokens(s.substring(ix).trim(), ' '); + ret[x ? 1 : 0] = parseBoundSize(gaps[0], true, x); + if (gaps.length > 1) + ret[x ? 3 : 2] = parseBoundSize(gaps[1], true, !x); + return ret; + } + + ix = startsWithLenient(s, "gap ", 1, true); + if (ix > -1) { + String[] gaps = toTrimmedTokens(s.substring(ix).trim(), ' '); + + ret[1] = parseBoundSize(gaps[0], true, true); // left + if (gaps.length > 1) { + ret[3] = parseBoundSize(gaps[1], true, false); // right + if (gaps.length > 2) { + ret[0] = parseBoundSize(gaps[2], true, true); // top + if (gaps.length > 3) + ret[2] = parseBoundSize(gaps[3], true, false); // bottom + } + } + return ret; + } + + throw new IllegalArgumentException("Unknown Gap part: '" + s + "'"); + } + + private static int parseSpan(String s) + { + return s.length() > 0 ? Integer.parseInt(s) : LayoutUtil.INF; + } + + private static Float parseFloat(String s, Float nullVal) + { + return s.length() > 0 ? new Float(Float.parseFloat(s)) : nullVal; + } + + /** Parses a single "min:pref:max" value. May look something like "10px:20lp:30%" or "pref!". + * @param s The string to parse. Not null. + * @param isGap If this bound size is a gap (different empty string handling). + * @param isHor If the size is for the horizontal dimension. + * @return A bound size that may be null if the string was "null", "n" or null. + */ + public static BoundSize parseBoundSize(String s, boolean isGap, boolean isHor) + { + if (s.length() == 0 || s.equals("null") || s.equals("n")) + return null; + + String cs = s; + boolean push = false; + if (s.endsWith("push")) { + push = true; + int l = s.length(); + s = s.substring(0, l - (s.endsWith(":push") ? 5 : 4)); + if (s.length() == 0) + return new BoundSize(null, null, null, true, cs); + } + + String[] sizes = toTrimmedTokens(s, ':'); + String s0 = sizes[0]; + + if (sizes.length == 1) { + boolean hasEM = s0.endsWith("!"); + if (hasEM) + s0 = s0.substring(0, s0.length() - 1); + UnitValue uv = parseUnitValue(s0, null, isHor); + return new BoundSize(((isGap || hasEM) ? uv : null), uv, (hasEM ? uv : null), push, cs); + + } else if (sizes.length == 2) { + return new BoundSize(parseUnitValue(s0, null, isHor), parseUnitValue(sizes[1], null, isHor), null, push, cs); + } else if (sizes.length == 3) { + return new BoundSize(parseUnitValue(s0, null, isHor), parseUnitValue(sizes[1], null, isHor), parseUnitValue(sizes[2], null, isHor), push, cs); + } else { + throw new IllegalArgumentException("Min:Preferred:Max size section must contain 0, 1 or 2 colons. '" + cs + "'"); + } + } + + /** Parses a single unit value that may also be an alignment as parsed by {@link #parseAlignKeywords(String, boolean)}. + * @param s The string to parse. Not null. May look something like "10px" or "5dlu". + * @param isHor If the value is for the horizontal dimension. + * @param emptyReplacement A replacement if s is empty. May be null. + * @return The parsed unit value. May be null. + */ + public static UnitValue parseUnitValueOrAlign(String s, boolean isHor, UnitValue emptyReplacement) + { + if (s.length() == 0) + return emptyReplacement; + + UnitValue align = parseAlignKeywords(s, isHor); + if (align != null) + return align; + + return parseUnitValue(s, emptyReplacement, isHor); + } + + /** Parses a single unit value. E.g. "10px" or "5in" + * @param s The string to parse. Not null. May look something like "10px" or "5dlu". + * @param isHor If the value is for the horizontal dimension. + * @return The parsed unit value. null is empty string, + */ + public static UnitValue parseUnitValue(String s, boolean isHor) + { + return parseUnitValue(s, null, isHor); + } + + /** Parses a single unit value. + * @param s The string to parse. May be null. May look something like "10px" or "5dlu". + * @param emptyReplacement A replacement s is empty or null. May be null. + * @param isHor If the value is for the horizontal dimension. + * @return The parsed unit value. May be null. + */ + private static UnitValue parseUnitValue(String s, UnitValue emptyReplacement, boolean isHor) + { + if (s == null || s.length() == 0) + return emptyReplacement; + + String cs = s; // Save creation string. + char c0 = s.charAt(0); + + // Remove start and end parentheses, if there. + if (c0 == '(' && s.charAt(s.length() - 1) == ')') + s = s.substring(1, s.length() - 1); + + if (c0 == 'n' && (s.equals("null") || s.equals("n"))) + return null; + + if (c0 == 'i' && s.equals("inf")) + return UnitValue.INF; + + int oper = getOper(s); + boolean inline = oper == UnitValue.ADD || oper == UnitValue.SUB || oper == UnitValue.MUL || oper == UnitValue.DIV; + + if (oper != UnitValue.STATIC) { // It is a multi-value + + String[] uvs; + if (inline == false) { // If the format is of type "opr(xxx,yyy)" (compared to in-line "10%+15px") + String sub = s.substring(4, s.length() - 1).trim(); + uvs = toTrimmedTokens(sub, ','); + if (uvs.length == 1) + return parseUnitValue(sub, null, isHor); + } else { + char delim; + if (oper == UnitValue.ADD) { + delim = '+'; + } else if (oper == UnitValue.SUB) { + delim = '-'; + } else if (oper == UnitValue.MUL) { + delim = '*'; + } else { // div left + delim = '/'; + } + uvs = toTrimmedTokens(s, delim); + if (uvs.length > 2) { // More than one +-*/. + String last = uvs[uvs.length - 1]; + String first = s.substring(0, s.length() - last.length() - 1); + uvs = new String[] {first, last}; + } + } + + if (uvs.length != 2) + throw new IllegalArgumentException("Malformed UnitValue: '" + s + "'"); + + UnitValue sub1 = parseUnitValue(uvs[0], null, isHor); + UnitValue sub2 = parseUnitValue(uvs[1], null, isHor); + + if (sub1 == null || sub2 == null) + throw new IllegalArgumentException("Malformed UnitValue. Must be two sub-values: '" + s + "'"); + + return new UnitValue(isHor, oper, sub1, sub2, cs); + } else { + try { + String[] numParts = getNumTextParts(s); + float value = numParts[0].length() > 0 ? Float.parseFloat(numParts[0]) : 1; // e.g. "related" has no number part.. + + return new UnitValue(value, numParts[1], isHor, oper, cs); + + } catch(Exception e) { + throw new IllegalArgumentException("Malformed UnitValue: '" + s + "'", e); + } + } + } + + /** Parses alignment keywords and returns the appropriate UnitValue. + * @param s The string to parse. Not null. + * @param isHor If alignments for horizontal is checked. false means vertical. + * @return The unit value or null if not recognized (no exception). + */ + static UnitValue parseAlignKeywords(String s, boolean isHor) + { + if (startsWithLenient(s, "center", 1, false) != -1) + return UnitValue.CENTER; + + if (isHor) { + if (startsWithLenient(s, "left", 1, false) != -1) + return UnitValue.LEFT; + + if (startsWithLenient(s, "right", 1, false) != -1) + return UnitValue.RIGHT; + + if (startsWithLenient(s, "leading", 4, false) != -1) + return UnitValue.LEADING; + + if (startsWithLenient(s, "trailing", 5, false) != -1) + return UnitValue.TRAILING; + + if (startsWithLenient(s, "label", 5, false) != -1) + return UnitValue.LABEL; + + } else { + + if (startsWithLenient(s, "baseline", 4, false) != -1) + return UnitValue.BASELINE_IDENTITY; + + if (startsWithLenient(s, "top", 1, false) != -1) + return UnitValue.TOP; + + if (startsWithLenient(s, "bottom", 1, false) != -1) + return UnitValue.BOTTOM; + } + + return null; + } + + /** Splits a text-number combination such as "hello 10.0" into {"hello", "10.0"}. + * @param s The string to split. Not null. Needs be be reasonably formatted since the method + * only finds the first 0-9 or . and cuts the string in half there. + * @return Always length 2 and no null elements. Elements are "" if no part found. + */ + private static String[] getNumTextParts(String s) + { + for (int i = 0, iSz = s.length(); i < iSz; i++) { + char c = s.charAt(i); + if (c == ' ') + throw new IllegalArgumentException("Space in UnitValue: '" + s + "'"); + + if ((c < '0' || c > '9') && c != '.' && c != '-') + return new String[] {s.substring(0, i).trim(), s.substring(i).trim()}; + } + return new String[] {s, ""}; + } + + /** Returns the operation depending on the start character. + * @param s The string to check. Not null. + * @return E.g. UnitValue.ADD, UnitValue.SUB or UnitValue.STATIC. Returns negative value for in-line operations. + */ + private static int getOper(String s) + { + int len = s.length(); + if (len < 3) + return UnitValue.STATIC; + + if (len > 5 && s.charAt(3) == '(' && s.charAt(len - 1) == ')') { + if (s.startsWith("min(")) + return UnitValue.MIN; + + if (s.startsWith("max(")) + return UnitValue.MAX; + + if (s.startsWith("mid(")) + return UnitValue.MID; + } + + // Try in-line add/sub. E.g. "pref+10px". + for (int j = 0; j < 2; j++) { // First +- then */ (precedence) + for (int i = len - 1, p = 0; i > 0; i--) { + char c = s.charAt(i); + if (c == ')') { + p++; + } else if (c == '(') { + p--; + } else if (p == 0) { + if (j == 0) { + if (c == '+') + return UnitValue.ADD; + if (c == '-') + return UnitValue.SUB; + } else { + if (c == '*') + return UnitValue.MUL; + if (c == '/') + return UnitValue.DIV; + } + } + } + } + return UnitValue.STATIC; + } + + /** Returns if a string shares at least a specified numbers starting characters with a number of matches. + *

+ * This method just exercise {@link #startsWithLenient(String, String, int, boolean)} with every one of + * matches and minChars. + * @param s The string to check. Not null. + * @param matches A number of possible starts for s. + * @param minChars The minimum number of characters to match for every element in matches. Needs + * to be of same length as matches. Can be null. + * @param acceptTrailing If after the required number of characters are matched on recognized characters that are not + * in one of the the matches string should be accepted. For instance if "abczz" should be matched with + * "abcdef" and min chars 3. + * @return The index of the first unmatched character if minChars was reached or -1 if a match was not + * found. + */ + private static int startsWithLenient(String s, String[] matches, int[] minChars, boolean acceptTrailing) + { + for (int i = 0; i < matches.length; i++) { + int minChar = minChars != null ? minChars[i] : -1; + int ix = startsWithLenient(s, matches[i], minChar, acceptTrailing); + if (ix > -1) + return ix; + } + return -1; + } + + /** Returns if a string shares at least a specified numbers starting characters with a match. + * @param s The string to check. Not null and must be trimmed. + * @param match The possible start for s. Not null and must be trimmed. + * @param minChars The mimimum number of characters to match to s for it this to be considered a match. -1 means + * the full length of match. + * @param acceptTrailing If after the required number of charecters are matched unrecognized characters that are not + * in one of the the matches string should be accepted. For instance if "abczz" should be matched with + * "abcdef" and min chars 3. + * @return The index of the first unmatched character if minChars was reached or -1 if a match was not + * found. + */ + private static int startsWithLenient(String s, String match, int minChars, boolean acceptTrailing) + { + if (s.charAt(0) != match.charAt(0)) // Fast sanity check. + return -1; + + if (minChars == -1) + minChars = match.length(); + + int sSz = s.length(); + if (sSz < minChars) + return -1; + + int mSz = match.length(); + int sIx = 0; + for (int mIx = 0; mIx < mSz; sIx++, mIx++) { + while (sIx < sSz && (s.charAt(sIx) == ' ' || s.charAt(sIx) == '_')) // Disregard spaces and _ + sIx++; + + if (sIx >= sSz || s.charAt(sIx) != match.charAt(mIx)) + return mIx >= minChars && (acceptTrailing || sIx >= sSz) && (sIx >= sSz || s.charAt(sIx - 1) == ' ') ? sIx : -1; + } + return sIx >= sSz || acceptTrailing ||s.charAt(sIx) == ' ' ? sIx : -1; + } + + /** Parses a string and returns it in those parts of the string that are separated with a sep character. + *

+ * separator characters within parentheses will not be counted or handled in any way, whatever the depth. + *

+ * A space separator will be a hit to one or more spaces and thus not return empty strings. + * @param s The string to parse. If it starts and/or ends with a sep the first and/or last element returned will be "". If + * two sep are next to each other and empty element will be "between" the periods. The sep themselves will never be returned. + * @param sep The separator char. + * @return Those parts of the string that are separated with sep. Never null and at least of size 1 + * @since 6.7.2 Changed so more than one space in a row works as one space. + */ + private static String[] toTrimmedTokens(String s, char sep) + { + int toks = 0, sSize = s.length(); + boolean disregardDoubles = sep == ' '; + + // Count the sep:s + int p = 0; + for(int i = 0; i < sSize; i++) { + char c = s.charAt(i); + if (c == '(') { + p++; + } else if (c == ')') { + p--; + } else if (p == 0 && c == sep) { + toks++; + while (disregardDoubles && i < sSize - 1 && s.charAt(i + 1) == ' ') + i++; + } + if (p < 0) + throw new IllegalArgumentException("Unbalanced parentheses: '" + s + "'"); + } + if (p != 0) + throw new IllegalArgumentException("Unbalanced parentheses: '" + s + "'"); + + if (toks == 0) + return new String [] {s.trim()}; + + String[] retArr = new String[toks + 1]; + + int st = 0, pNr = 0; + p = 0; + for (int i = 0; i < sSize; i++) { + + char c = s.charAt(i); + if (c == '(') { + p++; + } else if (c == ')') { + p--; + } else if (p == 0 && c == sep) { + retArr[pNr++] = s.substring(st, i).trim(); + st = i + 1; + while (disregardDoubles && i < sSize - 1 && s.charAt(i + 1) == ' ') + i++; + } + } + + retArr[pNr++] = s.substring(st, sSize).trim(); + return retArr; + } + + /** Parses "AAA[BBB]CCC[DDD]EEE" into {"AAA", "BBB", "CCC", "DDD", "EEE", "FFF"}. Handles empty parts. Will always start and end outside + * a [] block so that the number of returned elemets will always be uneven and at least of length 3. + *

+ * "|" is interpreted as "][". + * @param s The string. Might be "" but not null. Should be trimmed. + * @return The string divided into elements. Never null and at least of length 3. + * @throws IllegalArgumentException If a [] mismatch of some kind. (If not same [ as ] count or if the interleave.) + */ + private static ArrayList getRowColAndGapsTrimmed(String s) + { + if (s.indexOf('|') != -1) + s = s.replaceAll("\\|", "]["); + + ArrayList retList = new ArrayList(Math.max(s.length() >> 2 + 1, 3)); // Approx return length. + int s0 = 0, s1 = 0; // '[' and ']' count. + int st = 0; // Start of "next token to add". + for (int i = 0, iSz = s.length(); i < iSz; i++) { + char c = s.charAt(i); + if (c == '[') { + s0++; + } else if (c == ']') { + s1++; + } else { + continue; + } + + if (s0 != s1 && (s0 - 1) != s1) + break; // Wrong [ or ] found. Break for throw. + + retList.add(s.substring(st, i).trim()); + st = i + 1; + } + if (s0 != s1) + throw new IllegalArgumentException("'[' and ']' mismatch in row/column format string: " + s); + + if (s0 == 0) { + retList.add(""); + retList.add(s); + retList.add(""); + } else if (retList.size() % 2 == 0) { + retList.add(s.substring(st, s.length())); + } + + return retList; + } + + /** Makes null "", trims and converts to lower case. + * @param s The string + * @return Not null. + */ + public static String prepare(String s) + { + return s != null ? s.trim().toLowerCase() : ""; + } + +// /** Tests to serialize and deserialize the object with both XMLEncoder/Decoder and through Serializable +// * @param o The object to serialize +// * @return The same object after a tri through the process. +// */ +// public static final Object serializeTest(Object o) +// { +// try { +// ByteArrayOutputStream barr = new ByteArrayOutputStream(); +// XMLEncoder enc = new XMLEncoder(barr); +// enc.writeObject(o); +// enc.close(); +// +// XMLDecoder dec = new XMLDecoder(new ByteArrayInputStream(barr.toByteArray())); +// o = dec.readObject(); +// dec.close(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// try { +// ByteArrayOutputStream barr = new ByteArrayOutputStream(); +// ObjectOutputStream oos = new ObjectOutputStream(barr); +// oos.writeObject(o); +// oos.close(); +// +// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); +// o = ois.readObject(); +// ois.close(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// return o; +// } +} diff --git a/src/net/miginfocom/layout/ContainerWrapper.java b/src/net/miginfocom/layout/ContainerWrapper.java new file mode 100644 index 0000000..e6b53e0 --- /dev/null +++ b/src/net/miginfocom/layout/ContainerWrapper.java @@ -0,0 +1,69 @@ +package net.miginfocom.layout; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** A class that wraps a container that contains components. + */ +public interface ContainerWrapper extends ComponentWrapper +{ + /** Returns the components of the container that wrapper is wrapping. + * @return The components of the container that wrapper is wrapping. Never null. + */ + public abstract ComponentWrapper[] getComponents(); + + /** Returns the number of components that this parent has. + * @return The number of components that this parent has. + */ + public abstract int getComponentCount(); + + /** Returns the LayoutHandler (in Swing terms) that is handling the layout of this container. + * If there exist no such class the method should return the same as {@link #getComponent()}, which is the + * container itself. + * @return The layout handler instance. Never null. + */ + public abstract Object getLayout(); + + /** Returns if this container is using left-to-right component ordering. + * @return If this container is using left-to-right component ordering. + */ + public abstract boolean isLeftToRight(); + + /** Paints a cell to indicate where it is. + * @param x The x coordinate to start the drawing. + * @param y The x coordinate to start the drawing. + * @param width The width to draw/fill + * @param height The height to draw/fill + */ + public abstract void paintDebugCell(int x, int y, int width, int height); +} diff --git a/src/net/miginfocom/layout/DimConstraint.java b/src/net/miginfocom/layout/DimConstraint.java new file mode 100644 index 0000000..0ea29df --- /dev/null +++ b/src/net/miginfocom/layout/DimConstraint.java @@ -0,0 +1,477 @@ +package net.miginfocom.layout; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** A simple value holder for a constraint for one dimension. + */ +public final class DimConstraint// implements Externalizable +{ + /** How this entity can be resized in the dimension that this constraint represents. + */ + final ResizeConstraint resize = new ResizeConstraint(); + + // Look at the properties' getter/setter methods for explanation + + private String sizeGroup = null; // A "context" compared with equals. + + private BoundSize size = BoundSize.NULL_SIZE; // Min, pref, max. Never null, but sizes can be null. + + private BoundSize gapBefore = null, gapAfter = null; + + private UnitValue align = null; + + + // ************** Only applicable on components! ******************* + + private String endGroup = null; // A "context" compared with equals. + + + // ************** Only applicable on rows/columns! ******************* + + private boolean fill = false; + + private boolean noGrid = false; + + /** Empty constructor. + */ + public DimConstraint() + { + } + + /** Returns the grow priority. Relative priority is used for determining which entities gets the extra space first. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The grow priority. + */ + public int getGrowPriority() + { + return resize.growPrio; + } + + /** Sets the grow priority. Relative priority is used for determining which entities gets the extra space first. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The new grow priority. + */ + public void setGrowPriority(int p) + { + resize.growPrio = p; + } + + /** Returns the grow weight.

+ * Grow weight is how flexible the entity should be, relative to other entities, when it comes to growing. null or + * zero mean it will never grow. An entity that has twice the grow weight compared to another entity will get twice + * as much of available space. + *

+ * GrowWeight are only compared within the same GrowPrio. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current grow weight. + */ + public Float getGrow() + { + return resize.grow; + } + + /** Sets the grow weight.

+ * Grow weight is how flexible the entity should be, relative to other entities, when it comes to growing. null or + * zero mean it will never grow. An entity that has twice the grow weight compared to another entity will get twice + * as much of available space. + *

+ * GrowWeight are only compared within the same GrowPrio. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param weight The new grow weight. + */ + public void setGrow(Float weight) + { + resize.grow = weight; + } + + /** Returns the shrink priority. Relative priority is used for determining which entities gets smaller first when space is scarce. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The shrink priority. + */ + public int getShrinkPriority() + { + return resize.shrinkPrio; + } + + /** Sets the shrink priority. Relative priority is used for determining which entities gets smaller first when space is scarce. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param p The new shrink priority. + */ + public void setShrinkPriority(int p) + { + resize.shrinkPrio = p; + } + + /** Returns the shrink priority. Relative priority is used for determining which entities gets smaller first when space is scarce. + * Shrink weight is how flexible the entity should be, relative to other entities, when it comes to shrinking. null or + * zero mean it will never shrink (default). An entity that has twice the shrink weight compared to another entity will get twice + * as much of available space. + *

+ * Shrink(Weight) are only compared within the same ShrinkPrio. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current shrink weight. + */ + public Float getShrink() + { + return resize.shrink; + } + + /** Sets the shrink priority. Relative priority is used for determining which entities gets smaller first when space is scarce. + * Shrink weight is how flexible the entity should be, relative to other entities, when it comes to shrinking. null or + * zero mean it will never shrink (default). An entity that has twice the shrink weight compared to another entity will get twice + * as much of available space. + *

+ * Shrink(Weight) are only compared within the same ShrinkPrio. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param weight The new shrink weight. + */ + public void setShrink(Float weight) + { + resize.shrink = weight; + } + + public UnitValue getAlignOrDefault(boolean isCols) + { + if (align != null) + return align; + + if (isCols) + return UnitValue.LEADING; + + return fill || PlatformDefaults.getDefaultRowAlignmentBaseline() == false ? UnitValue.CENTER : UnitValue.BASELINE_IDENTITY; + } + + /** Returns the alignment used either as a default value for sub-entities or for this entity. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The alignment. + */ + public UnitValue getAlign() + { + return align; + } + + /** Sets the alignment used wither as a default value for sub-entities or for this entity. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param uv The new shrink priority. E.g. {@link UnitValue#CENTER} or {@link net.miginfocom.layout.UnitValue#LEADING}. + */ + public void setAlign(UnitValue uv) + { + this.align = uv; + } + + /** Returns the gap after this entity. The gap is an empty space and can have a min/preferred/maximum size so that it can shrink and + * grow depending on available space. Gaps are against other entities' edges and not against other entities' gaps. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The gap after this entity + */ + public BoundSize getGapAfter() + { + return gapAfter; + } + + /** Sets the gap after this entity. The gap is an empty space and can have a min/preferred/maximum size so that it can shrink and + * grow depending on available space. Gaps are against other entities' edges and not against other entities' gaps. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The new gap. + * @see net.miginfocom.layout.ConstraintParser#parseBoundSize(String, boolean, boolean) + */ + public void setGapAfter(BoundSize size) + { + this.gapAfter = size; + } + + boolean hasGapAfter() + { + return gapAfter != null && gapAfter.isUnset() == false; + } + + boolean isGapAfterPush() + { + return gapAfter != null && gapAfter.getGapPush(); + } + + /** Returns the gap before this entity. The gap is an empty space and can have a min/preferred/maximum size so that it can shrink and + * grow depending on available space. Gaps are against other entities' edges and not against other entities' gaps. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The gap before this entity + */ + public BoundSize getGapBefore() + { + return gapBefore; + } + + /** Sets the gap before this entity. The gap is an empty space and can have a min/preferred/maximum size so that it can shrink and + * grow depending on available space. Gaps are against other entities' edges and not against other entities' gaps. + *

+ * See also {@link net.miginfocom.layout.ConstraintParser#parseBoundSize(String, boolean, boolean)}. + * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The new gap. + */ + public void setGapBefore(BoundSize size) + { + this.gapBefore = size; + } + + boolean hasGapBefore() + { + return gapBefore != null && gapBefore.isUnset() == false; + } + + boolean isGapBeforePush() + { + return gapBefore != null && gapBefore.getGapPush(); + } + + /** Returns the min/preferred/max size for the entity in the dimension that this object describes. + *

+ * See also {@link net.miginfocom.layout.ConstraintParser#parseBoundSize(String, boolean, boolean)}. + * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current size. Never null since v3.5. + */ + public BoundSize getSize() + { + return size; + } + + /** Sets the min/preferred/max size for the entity in the dimension that this object describes. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param size The new size. May be null. + */ + public void setSize(BoundSize size) + { + if (size != null) + size.checkNotLinked(); + this.size = size; + } + + /** Returns the size group that this entity should be in for the dimension that this object is describing. + * If this constraint is in a size group that is specified here. null means no size group + * and all other values are legal. Comparison with .equals(). Components/columns/rows in the same size group + * will have the same min/preferred/max size; that of the largest in the group for the first two and the + * smallest for max. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current size group. May be null. + */ + public String getSizeGroup() + { + return sizeGroup; + } + + /** Sets the size group that this entity should be in for the dimension that this object is describing. + * If this constraint is in a size group that is specified here. null means no size group + * and all other values are legal. Comparison with .equals(). Components/columns/rows in the same size group + * will have the same min/preferred/max size; that of the largest in the group for the first two and the + * smallest for max. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s The new size group. null disables size grouping. + */ + public void setSizeGroup(String s) + { + sizeGroup = s; + } + + // ************** Only applicable on components ! ******************* + + /** Returns the end group that this entity should be in for the dimension that this object is describing. + * If this constraint is in an end group that is specified here. null means no end group + * and all other values are legal. Comparison with .equals(). Components in the same end group + * will have the same end coordinate. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return The current end group. null may be returned. + */ + public String getEndGroup() + { + return endGroup; + } + + /** Sets the end group that this entity should be in for the dimension that this object is describing. + * If this constraint is in an end group that is specified here. null means no end group + * and all other values are legal. Comparison with .equals(). Components in the same end group + * will have the same end coordinate. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s The new end group. null disables end grouping. + */ + public void setEndGroup(String s) + { + endGroup = s; + } + + // ************** Not applicable on components below ! ******************* + + /** Returns if the component in the row/column that this constraint should default be grown in the same dimension that + * this constraint represents (width for column and height for a row). + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return true means that components should grow. + */ + public boolean isFill() + { + return fill; + } + + /** Sets if the component in the row/column that this constraint should default be grown in the same dimension that + * this constraint represents (width for column and height for a row). + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param b true means that components should grow. + */ + public void setFill(boolean b) + { + fill = b; + } + + /** Returns if the row/column should default to flow and not to grid behaviour. This means that the whole row/column + * will be one cell and all components will end up in that cell. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return true means that the whole row/column should be one cell. + */ + public boolean isNoGrid() + { + return noGrid; + } + + /** Sets if the row/column should default to flow and not to grid behaviour. This means that the whole row/column + * will be one cell and all components will end up in that cell. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param b true means that the whole row/column should be one cell. + */ + public void setNoGrid(boolean b) + { + this.noGrid = b; + } + + /** Returns the gaps as pixel values. + * @param parent The parent. Used to get the pixel values. + * @param defGap The default gap to use if there is no gap set on this object (i.e. it is null). + * @param refSize The reference size used to get the pixel sizes. + * @param before IF it is the gap before rather than the gap after to return. + * @return The [min,preferred,max] sizes for the specified gap. Uses {@link net.miginfocom.layout.LayoutUtil#NOT_SET} + * for gap sizes that are null. Returns null if there was no gap specified. A new and free to use array. + */ + int[] getRowGaps(ContainerWrapper parent, BoundSize defGap, int refSize, boolean before) + { + BoundSize gap = before ? gapBefore : gapAfter; + if (gap == null || gap.isUnset()) + gap = defGap; + + if (gap == null || gap.isUnset()) + return null; + + int[] ret = new int[3]; + for (int i = LayoutUtil.MIN; i <= LayoutUtil.MAX; i++) { + UnitValue uv = gap.getSize(i); + ret[i] = uv != null ? uv.getPixels(refSize, parent, null) : LayoutUtil.NOT_SET; + } + return ret; + } + + /** Returns the gaps as pixel values. + * @param parent The parent. Used to get the pixel values. + * @param comp The component that the gap is for. If not for a component it is null. + * @param adjGap The gap that the adjacent component, if any, has towards comp. + * @param adjacentComp The adjacent component if any. May be null. + * @param refSize The reference size used to get the pixel sizes. + * @param adjacentSide What side the adjacentComp is on. 0 = top, 1 = left, 2 = bottom, 3 = right. + * @param tag The tag string that the component might be tagged with in the component constraints. May be null. + * @param isLTR If it is left-to-right. + * @return The [min,preferred,max] sizes for the specified gap. Uses {@link net.miginfocom.layout.LayoutUtil#NOT_SET} + * for gap sizes that are null. Returns null if there was no gap specified. A new and free to use array. + */ + int[] getComponentGaps(ContainerWrapper parent, ComponentWrapper comp, BoundSize adjGap, ComponentWrapper adjacentComp, String tag, int refSize, int adjacentSide, boolean isLTR) + { + BoundSize gap = adjacentSide < 2 ? gapBefore : gapAfter; + + boolean hasGap = gap != null && gap.getGapPush(); + if ((gap == null || gap.isUnset()) && (adjGap == null || adjGap.isUnset()) && comp != null) + gap = PlatformDefaults.getDefaultComponentGap(comp, adjacentComp, adjacentSide + 1, tag, isLTR); + + if (gap == null) + return hasGap ? new int[] {0, 0, LayoutUtil.NOT_SET} : null; + + int[] ret = new int[3]; + for (int i = LayoutUtil.MIN; i <= LayoutUtil.MAX; i++) { + UnitValue uv = gap.getSize(i); + ret[i] = uv != null ? uv.getPixels(refSize, parent, null) : LayoutUtil.NOT_SET; + } + return ret; + } + +// // ************************************************ +// // Persistence Delegate and Serializable combined. +// // ************************************************ +// +// private Object readResolve() throws ObjectStreamException +// { +// return LayoutUtil.getSerializedObject(this); +// } +// +// @Override +// public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException +// { +// LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in)); +// } +// +// @Override +// public void writeExternal(ObjectOutput out) throws IOException +// { +// if (getClass() == DimConstraint.class) +// LayoutUtil.writeAsXML(out, this); +// } +} diff --git a/src/net/miginfocom/layout/Grid.java b/src/net/miginfocom/layout/Grid.java new file mode 100644 index 0000000..ec56de4 --- /dev/null +++ b/src/net/miginfocom/layout/Grid.java @@ -0,0 +1,2496 @@ +package net.miginfocom.layout; + + +import java.lang.ref.WeakReference; +import java.util.*; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** Holds components in a grid. Does most of the logic behind the layout manager. + */ +public final class Grid +{ + public static final boolean TEST_GAPS = true; + + private static final Float[] GROW_100 = new Float[] {ResizeConstraint.WEIGHT_100}; + + private static final DimConstraint DOCK_DIM_CONSTRAINT = new DimConstraint(); + static { + DOCK_DIM_CONSTRAINT.setGrowPriority(0); + } + + /** This is the maximum grid position for "normal" components. Docking components use the space out to + * MAX_DOCK_GRID and below 0. + */ + private static final int MAX_GRID = 30000; + + /** Docking components will use the grid coordinates -MAX_DOCK_GRID -> 0 and MAX_GRID -> MAX_DOCK_GRID. + */ + private static final int MAX_DOCK_GRID = 32767; + + /** A constraint used for gaps. + */ + private static final ResizeConstraint GAP_RC_CONST = new ResizeConstraint(200, ResizeConstraint.WEIGHT_100, 50, null); + private static final ResizeConstraint GAP_RC_CONST_PUSH = new ResizeConstraint(200, ResizeConstraint.WEIGHT_100, 50, ResizeConstraint.WEIGHT_100); + + /** Used for components that doesn't have a CC set. Not that it's really really important that the CC is never changed in this Grid class. + */ + private static final CC DEF_CC = new CC(); + + /** The constraints. Never null. + */ + private final LC lc; + + /** The parent that is layout out and this grid is done for. Never null. + */ + private final ContainerWrapper container; + + /** An x, y array implemented as a sparse array to accommodate for any grid size without wasting memory (or rather 15 bit (0-MAX_GRID * 0-MAX_GRID). + */ + private final LinkedHashMap grid = new LinkedHashMap(); // [(y << 16) + x] -> Cell. null key for absolute positioned compwraps + + private HashMap wrapGapMap = null; // Row or Column index depending in the dimension that "wraps". Normally row indexes but may be column indexes if "flowy". 0 means before first row/col. + + /** The size of the grid. Row count and column count. + */ + private final TreeSet rowIndexes = new TreeSet(), colIndexes = new TreeSet(); + + /** The row and column specifications. + */ + private final AC rowConstr, colConstr; + + /** The in the constructor calculated min/pref/max sizes of the rows and columns. + */ + private FlowSizeSpec colFlowSpecs = null, rowFlowSpecs = null; + + /** Components that are connections in one dimension (such as baseline alignment for instance) are grouped together and stored here. + * One for each row/column. + */ + private final ArrayList[] colGroupLists, rowGroupLists; //[(start)row/col number] + + /** The in the constructor calculated min/pref/max size of the whole grid. + */ + private int[] width = null, height = null; + + /** If debug is on contains the bounds for things to paint when calling {@link ContainerWrapper#paintDebugCell(int, int, int, int)} + */ + private ArrayList debugRects = null; // [x, y, width, height] + + /** If any of the absolute coordinates for component bounds has links the name of the target is in this Set. + * Since it requires some memory and computations this is checked at the creation so that + * the link information is only created if needed later. + *

+ * The boolean is true for groups id:s and null for normal id:s. + */ + private HashMap linkTargetIDs = null; + + private final int dockOffY, dockOffX; + + private final Float[] pushXs, pushYs; + + private final ArrayList callbackList; + + /** Constructor. + * @param container The container that will be laid out. + * @param lc The form flow constraints. + * @param rowConstr The rows specifications. If more cell rows are required, the last element will be used for when there is no corresponding element in this array. + * @param colConstr The columns specifications. If more cell rows are required, the last element will be used for when there is no corresponding element in this array. + * @param ccMap The map containing the parsed constraints for each child component of parent. Will not be altered. Can have null CC which will use a common + * cached one. + * @param callbackList A list of callbacks or null if none. Will not be altered. + */ + public Grid(ContainerWrapper container, LC lc, AC rowConstr, AC colConstr, Map ccMap, ArrayList callbackList) + { + this.lc = lc; + this.rowConstr = rowConstr; + this.colConstr = colConstr; + this.container = container; + this.callbackList = callbackList; + + int wrap = lc.getWrapAfter() != 0 ? lc.getWrapAfter() : (lc.isFlowX() ? colConstr : rowConstr).getConstaints().length; + boolean useVisualPadding = lc.isVisualPadding(); + + final ComponentWrapper[] comps = container.getComponents(); + + boolean hasTagged = false; // So we do not have to sort if it will not do any good + boolean hasPushX = false, hasPushY = false; + boolean hitEndOfRow = false; + final int[] cellXY = new int[2]; + final ArrayList spannedRects = new ArrayList(2); + + final DimConstraint[] specs = (lc.isFlowX() ? rowConstr : colConstr).getConstaints(); + + int sizeGroupsX = 0, sizeGroupsY = 0; + int[] dockInsets = null; // top, left, bottom, right insets for docks. + + LinkHandler.clearTemporaryBounds(container.getLayout()); + + for (int i = 0; i < comps.length;) { + ComponentWrapper comp = comps[i]; + CC rootCc = getCC(comp, ccMap); + + addLinkIDs(rootCc); + + int hideMode = comp.isVisible() ? -1 : rootCc.getHideMode() != -1 ? rootCc.getHideMode() : lc.getHideMode(); + + if (hideMode == 3) { // To work with situations where there are components that does not have a layout manager, or not this one. + setLinkedBounds(comp, rootCc, comp.getX(), comp.getY(), comp.getWidth(), comp.getHeight(), rootCc.isExternal()); + i++; + continue; // The "external" component should not be handled further. + } + + if (rootCc.getHorizontal().getSizeGroup() != null) + sizeGroupsX++; + if (rootCc.getVertical().getSizeGroup() != null) + sizeGroupsY++; + + // Special treatment of absolute positioned components. + if (getPos(comp, rootCc) != null || rootCc.isExternal()) { + + CompWrap cw = new CompWrap(comp, rootCc, hideMode, useVisualPadding); + Cell cell = grid.get(null); + if (cell == null) { + grid.put(null, new Cell(cw)); + } else { + cell.compWraps.add(cw); + } + + if (!rootCc.isBoundsInGrid() || rootCc.isExternal()) { + setLinkedBounds(comp, rootCc, comp.getX(), comp.getY(), comp.getWidth(), comp.getHeight(), rootCc.isExternal()); + i++; + continue; + } + } + + if (rootCc.getDockSide() != -1) { + if (dockInsets == null) + dockInsets = new int[] {-MAX_DOCK_GRID, -MAX_DOCK_GRID, MAX_DOCK_GRID, MAX_DOCK_GRID}; + + addDockingCell(dockInsets, rootCc.getDockSide(), new CompWrap(comp, rootCc, hideMode, useVisualPadding)); + i++; + continue; + } + + Boolean cellFlowX = rootCc.getFlowX(); + Cell cell = null; + + if (rootCc.isNewline()) { + wrap(cellXY, rootCc.getNewlineGapSize()); + } else if (hitEndOfRow) { + wrap(cellXY, null); + } + hitEndOfRow = false; + + final boolean isRowInGridMode = !lc.isNoGrid() && !((DimConstraint) LayoutUtil.getIndexSafe(specs, lc.isFlowX() ? cellXY[1] : cellXY[0])).isNoGrid(); + + // Move to a free y, x if no absolute grid specified + int cx = rootCc.getCellX(); + int cy = rootCc.getCellY(); + if ((cx < 0 || cy < 0) && isRowInGridMode && rootCc.getSkip() == 0) { // 3.7.2: If skip, don't find an empty cell first. + while (!isCellFree(cellXY[1], cellXY[0], spannedRects)) { + if (Math.abs(increase(cellXY, 1)) >= wrap) + wrap(cellXY, null); + } + } else { + if (cx >= 0 && cy >= 0) { + if (cy >= 0) { + cellXY[0] = cx; + cellXY[1] = cy; + } else { // Only one coordinate is specified. Use the current row (flowx) or column (flowy) to fill in. + if (lc.isFlowX()) { + cellXY[0] = cx; + } else { + cellXY[1] = cx; + } + } + ensureIndexSizes(cx, cy); + } + cell = getCell(cellXY[1], cellXY[0]); // Might be null + } + + // Skip a number of cells. Changed for 3.6.1 to take wrap into account and thus "skip" to the next and possibly more rows. + for (int s = 0, skipCount = rootCc.getSkip(); s < skipCount; s++) { + do { + if (Math.abs(increase(cellXY, 1)) >= wrap) + wrap(cellXY, null); + } while (!isCellFree(cellXY[1], cellXY[0], spannedRects)); + } + + // If cell is not created yet, create it and set it. + if (cell == null) { + int spanx = Math.min(!isRowInGridMode && lc.isFlowX() ? LayoutUtil.INF : rootCc.getSpanX(), MAX_GRID - cellXY[0]); + int spany = Math.min(!isRowInGridMode && !lc.isFlowX() ? LayoutUtil.INF : rootCc.getSpanY(), MAX_GRID - cellXY[1]); + + cell = new Cell(spanx, spany, cellFlowX != null ? cellFlowX : lc.isFlowX()); + + setCell(cellXY[1], cellXY[0], cell); + + // Add a rectangle so we can know that spanned cells occupy more space. + if (spanx > 1 || spany > 1) + spannedRects.add(new int[] {cellXY[0], cellXY[1], spanx, spany}); + } + + // Add the one, or all, components that split the grid position to the same Cell. + boolean wrapHandled = false; + int splitLeft = isRowInGridMode ? rootCc.getSplit() - 1 : LayoutUtil.INF; + boolean splitExit = false; + final boolean spanRestOfRow = (lc.isFlowX() ? rootCc.getSpanX() : rootCc.getSpanY()) == LayoutUtil.INF; + + for (; splitLeft >= 0 && i < comps.length; splitLeft--) { + ComponentWrapper compAdd = comps[i]; + CC cc = getCC(compAdd, ccMap); + + addLinkIDs(cc); + + boolean visible = compAdd.isVisible(); + hideMode = visible ? -1 : cc.getHideMode() != -1 ? cc.getHideMode() : lc.getHideMode(); + + if (cc.isExternal() || hideMode == 3) { + i++; + splitLeft++; // Added for 3.5.5 so that these components does not "take" a split slot. + continue; // To work with situations where there are components that does not have a layout manager, or not this one. + } + + hasPushX |= (visible || hideMode > 1) && (cc.getPushX() != null); + hasPushY |= (visible || hideMode > 1) && (cc.getPushY() != null); + + if (cc != rootCc) { // If not first in a cell + if (cc.isNewline() || !cc.isBoundsInGrid() || cc.getDockSide() != -1) + break; + + if (splitLeft > 0 && cc.getSkip() > 0) { + splitExit = true; + break; + } + } + + CompWrap cw = new CompWrap(compAdd, cc, hideMode, useVisualPadding); + cell.compWraps.add(cw); + cell.hasTagged |= cc.getTag() != null; + hasTagged |= cell.hasTagged; + + if (cc != rootCc) { + if (cc.getHorizontal().getSizeGroup() != null) + sizeGroupsX++; + if (cc.getVertical().getSizeGroup() != null) + sizeGroupsY++; + } + + i++; + + if ((cc.isWrap() || (spanRestOfRow && splitLeft == 0))) { + if (cc.isWrap()) { + wrap(cellXY, cc.getWrapGapSize()); + } else { + hitEndOfRow = true; + } + wrapHandled = true; + break; + } + } + + if (!wrapHandled && isRowInGridMode) { + int span = lc.isFlowX() ? cell.spanx : cell.spany; + if (Math.abs((lc.isFlowX() ? cellXY[0] : cellXY[1])) + span >= wrap) { + hitEndOfRow = true; + } else { + increase(cellXY, splitExit ? span - 1 : span); + } + } + } + + // If there were size groups, calculate the largest values in the groups (for min/pref/max) and enforce them on the rest in the group. + if (sizeGroupsX > 0 || sizeGroupsY > 0) { + HashMap sizeGroupMapX = sizeGroupsX > 0 ? new HashMap(sizeGroupsX) : null; + HashMap sizeGroupMapY = sizeGroupsY > 0 ? new HashMap(sizeGroupsY) : null; + ArrayList sizeGroupCWs = new ArrayList(Math.max(sizeGroupsX, sizeGroupsY)); + + for (Cell cell : grid.values()) { + for (int i = 0; i < cell.compWraps.size(); i++) { + CompWrap cw = cell.compWraps.get(i); + String sgx = cw.cc.getHorizontal().getSizeGroup(); + String sgy = cw.cc.getVertical().getSizeGroup(); + + if (sgx != null || sgy != null) { + if (sgx != null && sizeGroupMapX != null) + addToSizeGroup(sizeGroupMapX, sgx, cw.getSizes(true)); + if (sgy != null && sizeGroupMapY != null) + addToSizeGroup(sizeGroupMapY, sgy, cw.getSizes(false)); + sizeGroupCWs.add(cw); + } + } + } + + // Set/equalize the sizeGroups to same the values. + for (CompWrap cw : sizeGroupCWs) { + if (sizeGroupMapX != null) + cw.setForcedSizes(sizeGroupMapX.get(cw.cc.getHorizontal().getSizeGroup()), true); // Target method handles null sizes + if (sizeGroupMapY != null) + cw.setForcedSizes(sizeGroupMapY.get(cw.cc.getVertical().getSizeGroup()), false); // Target method handles null sizes + } + } // Component loop + + if (hasTagged) + sortCellsByPlatform(grid.values(), container); + + // Calculate gaps now that the cells are filled and we know all adjacent components. + boolean ltr = LayoutUtil.isLeftToRight(lc, container); + for (Cell cell : grid.values()) { + ArrayList cws = cell.compWraps; + + for (int i = 0, lastI = cws.size() - 1; i <= lastI; i++) { + CompWrap cw = cws.get(i); + ComponentWrapper cwBef = i > 0 ? cws.get(i - 1).comp : null; + ComponentWrapper cwAft = i < lastI ? cws.get(i + 1).comp : null; + + String tag = getCC(cw.comp, ccMap).getTag(); + CC ccBef = cwBef != null ? getCC(cwBef, ccMap) : null; + CC ccAft = cwAft != null ? getCC(cwAft, ccMap) : null; + + cw.calcGaps(cwBef, ccBef, cwAft, ccAft, tag, cell.flowx, ltr); + } + } + + dockOffX = getDockInsets(colIndexes); + dockOffY = getDockInsets(rowIndexes); + + // Add synthetic indexes for empty rows and columns so they can get a size + ensureIndexSizes(colConstr.getCount(), rowConstr.getCount()); + + colGroupLists = divideIntoLinkedGroups(false); + rowGroupLists = divideIntoLinkedGroups(true); + + pushXs = hasPushX || lc.isFillX() ? getDefaultPushWeights(false) : null; + pushYs = hasPushY || lc.isFillY() ? getDefaultPushWeights(true) : null; + + if (LayoutUtil.isDesignTime(container)) + saveGrid(container, grid); + } + + private void ensureIndexSizes(int colCount, int rowCount) + { + for (int i = 0; i < colCount; i++) + colIndexes.add(i); + for (int i = 0; i < rowCount; i++) + rowIndexes.add(i); + } + + private static CC getCC(ComponentWrapper comp, Map ccMap) + { + CC cc = ccMap.get(comp); + return cc != null ? cc : DEF_CC; + } + + private void addLinkIDs(CC cc) + { + String[] linkIDs = cc.getLinkTargets(); + for (String linkID : linkIDs) { + if (linkTargetIDs == null) + linkTargetIDs = new HashMap(); + linkTargetIDs.put(linkID, null); + } + } + + /** If the container (parent) that this grid is laying out has changed its bounds, call this method to + * clear any cached values min/pref/max sizes of the components and rows/columns. + *

+ * If any component can have changed cell the grid needs to be recreated. + */ + public void invalidateContainerSize() + { + colFlowSpecs = null; + invalidateComponentSizes(); + } + + private void invalidateComponentSizes() + { + for (Cell cell : grid.values()) { + for (CompWrap compWrap : cell.compWraps) + compWrap.invalidateSizes(); + } + } + + /** + * @deprecated since 5.0 Last boolean is not needed and is gotten from the new {@link net.miginfocom.layout.ComponentWrapper#getContentBias()} instead; + */ + public boolean layout(int[] bounds, UnitValue alignX, UnitValue alignY, boolean debug, boolean notUsed) + { + return layoutImpl(bounds, alignX, alignY, debug, false); + } + + /** Does the actual layout. Uses many values calculated in the constructor. + * @param bounds The bounds to layout against. Normally that of the parent. [x, y, width, height]. + * @param alignX The alignment for the x-axis. Can be null. + * @param alignY The alignment for the y-axis. Can be null. + * @param debug If debug information should be saved in {@link #debugRects}. + * @return If the layout has changed the preferred size and there is need for a new layout. This can happen if one or more components + * in the grid has a content bias according to {@link net.miginfocom.layout.ComponentWrapper#getContentBias()}. + * @since 5.0 + */ + public boolean layout(int[] bounds, UnitValue alignX, UnitValue alignY, boolean debug) + { + return layoutImpl(bounds, alignX, alignY, debug, false); + } + + /** Does the actual layout. Uses many values calculated in the constructor. + * @param bounds The bounds to layout against. Normally that of the parent. [x, y, width, height]. + * @param alignX The alignment for the x-axis. Can be null. + * @param alignY The alignment for the y-axis. Can be null. + * @param debug If debug information should be saved in {@link #debugRects}. + * @param trialRun If true the bounds calculated will not be transferred to the components. Only the internal size + * of the components will be calculated. + * @return If the layout has changed the preferred size and there is need for a new layout. This can happen if one or more components + * in the grid has a content bias according to {@link net.miginfocom.layout.ComponentWrapper#getContentBias()}. + * @since 5.0 + */ + private boolean layoutImpl(int[] bounds, UnitValue alignX, UnitValue alignY, boolean debug, boolean trialRun) + { + if (debug) + debugRects = new ArrayList(); + + if (colFlowSpecs == null) + checkSizeCalcs(bounds[2], bounds[3]); + + resetLinkValues(true, true); + + layoutInOneDim(bounds[2], alignX, false, pushXs); + layoutInOneDim(bounds[3], alignY, true, pushYs); + + HashMap endGrpXMap = null, endGrpYMap = null; + int compCount = container.getComponentCount(); + + // Transfer the calculated bound from the ComponentWrappers to the actual Components. + boolean addVisualPadding = lc.isVisualPadding(); + boolean layoutAgain = false; + if (compCount > 0) { + for (int j = 0; j < (linkTargetIDs != null ? 2 : 1); j++) { // First do the calculations (maybe more than once) then set the bounds when done + boolean doAgain; + int count = 0; + do { + doAgain = false; + for (Cell cell : grid.values()) { + for (CompWrap cw : cell.compWraps) { + if (j == 0) { + doAgain |= doAbsoluteCorrections(cw, bounds); + if (!doAgain) { // If we are going to do this again, do not bother this time around + if (cw.cc.getHorizontal().getEndGroup() != null) + endGrpXMap = addToEndGroup(endGrpXMap, cw.cc.getHorizontal().getEndGroup(), cw.x + cw.w); + + if (cw.cc.getVertical().getEndGroup() != null) + endGrpYMap = addToEndGroup(endGrpYMap, cw.cc.getVertical().getEndGroup(), cw.y + cw.h); + } + + // @since 3.7.2 Needed or absolute "pos" pointing to "visual" or "container" didn't work if + // their bounds changed during the layout cycle. At least not in SWT. + if (linkTargetIDs != null && (linkTargetIDs.containsKey("visual") || linkTargetIDs.containsKey("container"))) { + layoutAgain = true; + } + } + + if (linkTargetIDs == null || j == 1) { + if (cw.cc.getHorizontal().getEndGroup() != null) + cw.w = endGrpXMap.get(cw.cc.getHorizontal().getEndGroup()) - cw.x; + + if (cw.cc.getVertical().getEndGroup() != null) + cw.h = endGrpYMap.get(cw.cc.getVertical().getEndGroup()) - cw.y; + + cw.x += bounds[0]; + cw.y += bounds[1]; + + if (!trialRun) + cw.transferBounds(addVisualPadding); + + if (callbackList != null) { + for (LayoutCallback callback : callbackList) + callback.correctBounds(cw.comp); + } + } + } + } + clearGroupLinkBounds(); + if (++count > ((compCount << 3) + 10)) { + System.err.println("Unstable cyclic dependency in absolute linked values."); + break; + } + + } while (doAgain); + } + } + + // Add debug shapes for the "cells". Use the CompWraps as base for inding the cells. + if (debug) { + for (Cell cell : grid.values()) { + ArrayList compWraps = cell.compWraps; + for (CompWrap cw : compWraps) { + LinkedDimGroup hGrp = getGroupContaining(colGroupLists, cw); + LinkedDimGroup vGrp = getGroupContaining(rowGroupLists, cw); + + if (hGrp != null && vGrp != null) + debugRects.add(new int[]{hGrp.lStart + bounds[0] - (hGrp.fromEnd ? hGrp.lSize : 0), vGrp.lStart + bounds[1] - (vGrp.fromEnd ? vGrp.lSize : 0), hGrp.lSize, vGrp.lSize}); + } + } + } + return layoutAgain; + } + + public void paintDebug() + { + if (debugRects != null) { + container.paintDebugOutline(lc.isVisualPadding()); + + ArrayList painted = new ArrayList(); + for (int[] r : debugRects) { + if (!painted.contains(r)) { + container.paintDebugCell(r[0], r[1], r[2], r[3]); + painted.add(r); + } + } + + for (Cell cell : grid.values()) { + ArrayList compWraps = cell.compWraps; + for (CompWrap compWrap : compWraps) + compWrap.comp.paintDebugOutline(lc.isVisualPadding()); + } + } + } + + public ContainerWrapper getContainer() + { + return container; + } + + public final int[] getWidth() + { + return getWidth(lastRefHeight); + } + + public final int[] getWidth(int refHeight) + { + checkSizeCalcs(lastRefWidth, refHeight); + return width.clone(); + } + + public final int[] getHeight() + { + return getHeight(lastRefWidth); + } + + public final int[] getHeight(int refWidth) + { + checkSizeCalcs(refWidth, lastRefHeight); + return height.clone(); + } + + private int lastRefWidth = 0, lastRefHeight = 0; + + private void checkSizeCalcs(int refWidth, int refHeight) + { + if (colFlowSpecs == null) + calcGridSizes(refWidth, refHeight); + + if ((refWidth > 0 && refWidth != lastRefWidth) || (refHeight > 0 && refHeight != lastRefHeight)) { + int[] refBounds = new int[] {0, 0, (refWidth > 0 ? refWidth : width[LayoutUtil.PREF]), (refHeight > 0 ? refHeight : height[LayoutUtil.PREF])}; + layoutImpl(refBounds, null, null, false, true); + calcGridSizes(refWidth, refHeight); + } + + lastRefWidth = refWidth; + lastRefHeight = refHeight; + } + + private void calcGridSizes(int refWidth, int refHeight) + { + // Note, in these calls the grid can be invalidated and specs set to null. Therefore use local versions. + FlowSizeSpec colSpecs = calcRowsOrColsSizes(true, refWidth); + FlowSizeSpec rowSpecs = calcRowsOrColsSizes(false, refHeight); + + colFlowSpecs = colSpecs; + rowFlowSpecs = rowSpecs; + + width = getMinPrefMaxSumSize(true, colSpecs.sizes); + height = getMinPrefMaxSumSize(false, rowSpecs.sizes); + + if (linkTargetIDs == null) { + resetLinkValues(false, true); + } else { + // This call makes some components flicker on SWT. They get their bounds changed twice since + // the change might affect the absolute size adjustment below. There's no way around this that + // I know of. + layout(new int[]{0, 0, refWidth, refHeight}, null, null, false); + resetLinkValues(false, false); + } + + adjustSizeForAbsolute(true); + adjustSizeForAbsolute(false); + } + + private UnitValue[] getPos(ComponentWrapper cw, CC cc) + { + UnitValue[] callbackPos = null; + if (callbackList != null) { + for (int i = 0; i < callbackList.size() && callbackPos == null; i++) + callbackPos = callbackList.get(i).getPosition(cw); // NOT a copy! + } + + // If one is null, return the other (which many also be null) + UnitValue[] ccPos = cc.getPos(); // A copy!! + if (callbackPos == null || ccPos == null) + return callbackPos != null ? callbackPos : ccPos; + + // Merge + for (int i = 0; i < 4; i++) { + UnitValue cbUv = callbackPos[i]; + if (cbUv != null) + ccPos[i] = cbUv; + } + + return ccPos; + } + + private BoundSize[] getCallbackSize(ComponentWrapper cw) + { + if (callbackList != null) { + for (LayoutCallback callback : callbackList) { + BoundSize[] bs = callback.getSize(cw); // NOT a copy! + if (bs != null) + return bs; + } + } + return null; + } + + private static int getDockInsets(TreeSet set) + { + int c = 0; + for (Integer i : set) { + if (i < -MAX_GRID) { + c++; + } else { + break; // Since they are sorted we can break + } + } + return c; + } + + /** + * @param cw Never null. + * @param cc Never null. + * @param external The bounds should be stored even if they are not in {@link #linkTargetIDs}. + * @return If a change has been made. + */ + private boolean setLinkedBounds(ComponentWrapper cw, CC cc, int x, int y, int w, int h, boolean external) + { + String id = cc.getId() != null ? cc.getId() : cw.getLinkId(); + if (id == null) + return false; + + String gid = null; + int grIx = id.indexOf('.'); + if (grIx != -1 ) { + gid = id.substring(0, grIx); + id = id.substring(grIx + 1); + } + + Object lay = container.getLayout(); + boolean changed = false; + if (external || (linkTargetIDs != null && linkTargetIDs.containsKey(id))) + changed = LinkHandler.setBounds(lay, id, x, y, w, h, !external, false); + + if (gid != null && (external || (linkTargetIDs != null && linkTargetIDs.containsKey(gid)))) { + if (linkTargetIDs == null) + linkTargetIDs = new HashMap(4); + + linkTargetIDs.put(gid, Boolean.TRUE); + changed |= LinkHandler.setBounds(lay, gid, x, y, w, h, !external, true); + } + + return changed; + } + + /** Go to next cell. + * @param p The point to increase + * @param cnt How many cells to advance. + * @return The new value in the "increasing" dimension. + */ + private int increase(int[] p, int cnt) + { + return lc.isFlowX() ? (p[0] += cnt) : (p[1] += cnt); + } + + /** Wraps to the next row or column depending on if horizontal flow or vertical flow is used. + * @param cellXY The point to wrap and thus set either x or y to 0 and increase the other one. + * @param gapSize The gaps size specified in a "wrap XXX" or "newline XXX" or null if none. + */ + private void wrap(int[] cellXY, BoundSize gapSize) + { + boolean flowx = lc.isFlowX(); + cellXY[0] = flowx ? 0 : cellXY[0] + 1; + cellXY[1] = flowx ? cellXY[1] + 1 : 0; + + if (gapSize != null) { + if (wrapGapMap == null) + wrapGapMap = new HashMap(8); + + wrapGapMap.put(cellXY[flowx ? 1 : 0], gapSize); + } + + // add the row/column so that the gap in the last row/col will not be removed. + if (flowx) { + rowIndexes.add(cellXY[1]); + } else { + colIndexes.add(cellXY[0]); + } + } + + /** Sort components (normally buttons in a button bar) so they appear in the correct order. + * @param cells The cells to sort. + * @param parent The parent. + */ + private static void sortCellsByPlatform(Collection cells, ContainerWrapper parent) + { + String order = PlatformDefaults.getButtonOrder(); + String orderLo = order.toLowerCase(); + + int unrelSize = PlatformDefaults.convertToPixels(1, "u", true, 0, parent, null); + + if (unrelSize == UnitConverter.UNABLE) + throw new IllegalArgumentException("'unrelated' not recognized by PlatformDefaults!"); + + int[] gapUnrel = new int[] {unrelSize, unrelSize, LayoutUtil.NOT_SET}; + int[] flGap = new int[] {0, 0, LayoutUtil.NOT_SET}; + + for (Cell cell : cells) { + if (!cell.hasTagged) + continue; + + CompWrap prevCW = null; + boolean nextUnrel = false; + boolean nextPush = false; + ArrayList sortedList = new ArrayList(cell.compWraps.size()); + + for (int i = 0, iSz = orderLo.length(); i < iSz; i++) { + char c = orderLo.charAt(i); + if (c == '+' || c == '_') { + nextUnrel = true; + if (c == '+') + nextPush = true; + } else { + String tag = PlatformDefaults.getTagForChar(c); + if (tag != null) { + for (int j = 0, jSz = cell.compWraps.size(); j < jSz; j++) { + CompWrap cw = cell.compWraps.get(j); + if (tag.equals(cw.cc.getTag())) { + if (Character.isUpperCase(order.charAt(i))) + cw.adjustMinHorSizeUp((int) PlatformDefaults.getMinimumButtonWidthIncludingPadding(0, parent, cw.comp)); + + sortedList.add(cw); + + if (nextUnrel) { + (prevCW != null ? prevCW : cw).mergeGapSizes(gapUnrel, cell.flowx, prevCW == null); + if (nextPush) { + cw.forcedPushGaps = 1; + nextUnrel = false; + nextPush = false; + } + } + + // "unknown" components will always get an Unrelated gap. + if (c == 'u') + nextUnrel = true; + prevCW = cw; + } + } + } + } + } + + // If we have a gap that was supposed to push but no more components was found to but the "gap before" then compensate. + if (sortedList.size() > 0) { + CompWrap cw = sortedList.get(sortedList.size() - 1); + if (nextUnrel) { + cw.mergeGapSizes(gapUnrel, cell.flowx, false); + if (nextPush) + cw.forcedPushGaps |= 2; + } + + // Remove first and last gap if not set explicitly. + if (cw.cc.getHorizontal().getGapAfter() == null) + cw.setGaps(flGap, 3); + + cw = sortedList.get(0); + if (cw.cc.getHorizontal().getGapBefore() == null) + cw.setGaps(flGap, 1); + } + + // Exchange the unsorted CompWraps for the sorted one. + if (cell.compWraps.size() == sortedList.size()) { + cell.compWraps.clear(); + } else { + cell.compWraps.removeAll(sortedList); + } + cell.compWraps.addAll(sortedList); + } + } + + private Float[] getDefaultPushWeights(boolean isRows) + { + ArrayList[] groupLists = isRows ? rowGroupLists : colGroupLists; + + Float[] pushWeightArr = GROW_100; // Only create specific if any of the components have grow. + for (int i = 0, ix = 1; i < groupLists.length; i++, ix += 2) { + ArrayList grps = groupLists[i]; + Float rowPushWeight = null; + for (LinkedDimGroup grp : grps) { + for (int c = 0; c < grp._compWraps.size(); c++) { + CompWrap cw = grp._compWraps.get(c); + int hideMode = cw.comp.isVisible() ? -1 : cw.cc.getHideMode() != -1 ? cw.cc.getHideMode() : lc.getHideMode(); + + Float pushWeight = hideMode < 2 ? (isRows ? cw.cc.getPushY() : cw.cc.getPushX()) : null; + if (rowPushWeight == null || (pushWeight != null && pushWeight > rowPushWeight)) + rowPushWeight = pushWeight; + } + } + + if (rowPushWeight != null) { + if (pushWeightArr == GROW_100) + pushWeightArr = new Float[(groupLists.length << 1) + 1]; + pushWeightArr[ix] = rowPushWeight; + } + } + + return pushWeightArr; + } + + private void clearGroupLinkBounds() + { + if (linkTargetIDs == null) + return; + + for (Map.Entry o : linkTargetIDs.entrySet()) { + if (o.getValue() == Boolean.TRUE) + LinkHandler.clearBounds(container.getLayout(), o.getKey()); + } + } + + private void resetLinkValues(boolean parentSize, boolean compLinks) + { + Object lay = container.getLayout(); + if (compLinks) + LinkHandler.clearTemporaryBounds(lay); + + boolean defIns = !hasDocks(); + + int parW = parentSize ? lc.getWidth().constrain(container.getWidth(), getParentSize(container, true), container) : 0; + int parH = parentSize ? lc.getHeight().constrain(container.getHeight(), getParentSize(container, false), container) : 0; + + int insX = LayoutUtil.getInsets(lc, 0, defIns).getPixels(0, container, null); + int insY = LayoutUtil.getInsets(lc, 1, defIns).getPixels(0, container, null); + int visW = parW - insX - LayoutUtil.getInsets(lc, 2, defIns).getPixels(0, container, null); + int visH = parH - insY - LayoutUtil.getInsets(lc, 3, defIns).getPixels(0, container, null); + + LinkHandler.setBounds(lay, "visual", insX, insY, visW, visH, true, false); + LinkHandler.setBounds(lay, "container", 0, 0, parW, parH, true, false); + } + + /** Returns the {@link net.miginfocom.layout.Grid.LinkedDimGroup} that has the {@link net.miginfocom.layout.Grid.CompWrap} + * cw. + * @param groupLists The lists to search in. + * @param cw The component wrap to find. + * @return The linked group or null if none had the component wrap. + */ + private static LinkedDimGroup getGroupContaining(ArrayList[] groupLists, CompWrap cw) + { + for (ArrayList groups : groupLists) { + for (LinkedDimGroup group : groups) { + ArrayList cwList = group._compWraps; + for (CompWrap aCwList : cwList) { + if (aCwList == cw) + return group; + } + } + } + return null; + } + + private boolean doAbsoluteCorrections(CompWrap cw, int[] bounds) + { + boolean changed = false; + + int[] stSz = getAbsoluteDimBounds(cw, bounds[2], true); + if (stSz != null) + cw.setDimBounds(stSz[0], stSz[1], true); + + stSz = getAbsoluteDimBounds(cw, bounds[3], false); + if (stSz != null) + cw.setDimBounds(stSz[0], stSz[1], false); + + // If there is a link id, store the new bounds. + if (linkTargetIDs != null) + changed = setLinkedBounds(cw.comp, cw.cc, cw.x, cw.y, cw.w, cw.h, false); + + return changed; + } + + /** Adjust grid's width or height for the absolute components' positions. + */ + private void adjustSizeForAbsolute(boolean isHor) + { + int[] curSizes = isHor ? width : height; + + Cell absCell = grid.get(null); + if (absCell == null || absCell.compWraps.size() == 0) + return; + + ArrayList cws = absCell.compWraps; + + int maxEnd = 0; + for (int j = 0, cwSz = absCell.compWraps.size(); j < cwSz + 3; j++) { // "Do Again" max absCell.compWraps.size() + 3 times. + boolean doAgain = false; + for (int i = 0; i < cwSz; i++) { + CompWrap cw = cws.get(i); + int[] stSz = getAbsoluteDimBounds(cw, 0, isHor); + int end = stSz[0] + stSz[1]; + if (maxEnd < end) + maxEnd = end; + + // If there is a link id, store the new bounds. + if (linkTargetIDs != null) + doAgain |= setLinkedBounds(cw.comp, cw.cc, stSz[0], stSz[0], stSz[1], stSz[1], false); + } + if (!doAgain) + break; + + // We need to check this again since the coords may be smaller this round. + maxEnd = 0; + clearGroupLinkBounds(); + } + + maxEnd += LayoutUtil.getInsets(lc, isHor ? 3 : 2, !hasDocks()).getPixels(0, container, null); + + if (curSizes[LayoutUtil.MIN] < maxEnd) + curSizes[LayoutUtil.MIN] = maxEnd; + if (curSizes[LayoutUtil.PREF] < maxEnd) + curSizes[LayoutUtil.PREF] = maxEnd; + } + + private int[] getAbsoluteDimBounds(CompWrap cw, int refSize, boolean isHor) + { + if (cw.cc.isExternal()) { + if (isHor) { + return new int[] {cw.comp.getX(), cw.comp.getWidth()}; + } else { + return new int[] {cw.comp.getY(), cw.comp.getHeight()}; + } + } + + UnitValue[] pad = cw.cc.getPadding(); + + // If no changes do not create a lot of objects + UnitValue[] pos = getPos(cw.comp, cw.cc); + if (pos == null && pad == null) + return null; + + // Set start + int st = isHor ? cw.x : cw.y; + int sz = isHor ? cw.w : cw.h; + + // If absolute, use those coordinates instead. + if (pos != null) { + UnitValue stUV = pos[isHor ? 0 : 1]; + UnitValue endUV = pos[isHor ? 2 : 3]; + + int minSz = cw.getSize(LayoutUtil.MIN, isHor); + int maxSz = cw.getSize(LayoutUtil.MAX, isHor); + sz = Math.min(Math.max(cw.getSize(LayoutUtil.PREF, isHor), minSz), maxSz); + + if (stUV != null) { + st = stUV.getPixels(stUV.getUnit() == UnitValue.ALIGN ? sz : refSize, container, cw.comp); + + if (endUV != null) // if (endUV == null && cw.cc.isBoundsIsGrid() == true) + sz = Math.min(Math.max((isHor ? (cw.x + cw.w) : (cw.y + cw.h)) - st, minSz), maxSz); + } + + if (endUV != null) { + if (stUV != null) { // if (stUV != null || cw.cc.isBoundsIsGrid()) { + sz = Math.min(Math.max(endUV.getPixels(refSize, container, cw.comp) - st, minSz), maxSz); + } else { + st = endUV.getPixels(refSize, container, cw.comp) - sz; + } + } + } + + // If constraint has padding -> correct the start/size + if (pad != null) { + UnitValue uv = pad[isHor ? 1 : 0]; + int p = uv != null ? uv.getPixels(refSize, container, cw.comp) : 0; + st += p; + uv = pad[isHor ? 3 : 2]; + sz += -p + (uv != null ? uv.getPixels(refSize, container, cw.comp) : 0); + } + + return new int[] {st, sz}; + } + + private void layoutInOneDim(int refSize, UnitValue align, boolean isRows, Float[] defaultPushWeights) + { + boolean fromEnd = !(isRows ? lc.isTopToBottom() : LayoutUtil.isLeftToRight(lc, container)); + DimConstraint[] primDCs = (isRows ? rowConstr : colConstr).getConstaints(); + FlowSizeSpec fss = isRows ? rowFlowSpecs : colFlowSpecs; + ArrayList[] rowCols = isRows ? rowGroupLists : colGroupLists; + + int[] rowColSizes = LayoutUtil.calculateSerial(fss.sizes, fss.resConstsInclGaps, defaultPushWeights, LayoutUtil.PREF, refSize); + + if (LayoutUtil.isDesignTime(container)) { + TreeSet indexes = isRows ? rowIndexes : colIndexes; + int[] ixArr = new int[indexes.size()]; + int ix = 0; + for (Integer i : indexes) + ixArr[ix++] = i; + + putSizesAndIndexes(container.getComponent(), rowColSizes, ixArr, isRows); + } + + int curPos = align != null ? align.getPixels(refSize - LayoutUtil.sum(rowColSizes), container, null) : 0; + + if (fromEnd) + curPos = refSize - curPos; + + for (int i = 0 ; i < rowCols.length; i++) { + ArrayList linkedGroups = rowCols[i]; + int scIx = i - (isRows ? dockOffY : dockOffX); + + int bIx = i << 1; + int bIx2 = bIx + 1; + + curPos += (fromEnd ? -rowColSizes[bIx] : rowColSizes[bIx]); + + DimConstraint primDC = scIx >= 0 ? primDCs[scIx >= primDCs.length ? primDCs.length - 1 : scIx] : DOCK_DIM_CONSTRAINT; + + int rowSize = rowColSizes[bIx2]; + + for (LinkedDimGroup group : linkedGroups) { + int groupSize = rowSize; + if (group.span > 1) + groupSize = LayoutUtil.sum(rowColSizes, bIx2, Math.min((group.span << 1) - 1, rowColSizes.length - bIx2 - 1)); + + group.layout(primDC, curPos, groupSize, group.span); + } + + curPos += (fromEnd ? -rowSize : rowSize); + } + } + + private static void addToSizeGroup(HashMap sizeGroups, String sizeGroup, int[] size) + { + int[] sgSize = sizeGroups.get(sizeGroup); + if (sgSize == null) { + sizeGroups.put(sizeGroup, new int[] {size[LayoutUtil.MIN], size[LayoutUtil.PREF], size[LayoutUtil.MAX]}); + } else { + sgSize[LayoutUtil.MIN] = Math.max(size[LayoutUtil.MIN], sgSize[LayoutUtil.MIN]); + sgSize[LayoutUtil.PREF] = Math.max(size[LayoutUtil.PREF], sgSize[LayoutUtil.PREF]); + sgSize[LayoutUtil.MAX] = Math.min(size[LayoutUtil.MAX], sgSize[LayoutUtil.MAX]); + } + } + + private static HashMap addToEndGroup(HashMap endGroups, String endGroup, int end) + { + if (endGroup != null) { + if (endGroups == null) + endGroups = new HashMap(4); + + Integer oldEnd = endGroups.get(endGroup); + if (oldEnd == null || end > oldEnd) + endGroups.put(endGroup, end); + } + return endGroups; + } + + /** Calculates Min, Preferred and Max size for the columns OR rows. + * @param isHor If it is the horizontal dimension to calculate. + * @param containerSize The reference container size in the dimension. If <= 0 it will be replaced by the actual container's size. + * @return The sizes in a {@link net.miginfocom.layout.Grid.FlowSizeSpec}. + */ + private FlowSizeSpec calcRowsOrColsSizes(boolean isHor, int containerSize) + { + ArrayList[] groupsLists = isHor ? colGroupLists : rowGroupLists; + Float[] defPush = isHor ? pushXs : pushYs; + + if (containerSize <= 0) + containerSize = isHor ? container.getWidth() : container.getHeight(); + + BoundSize cSz = isHor ? lc.getWidth() : lc.getHeight(); + if (!cSz.isUnset()) + containerSize = cSz.constrain(containerSize, getParentSize(container, isHor), container); + + DimConstraint[] primDCs = (isHor? colConstr : rowConstr).getConstaints(); + TreeSet primIndexes = isHor ? colIndexes : rowIndexes; + + int[][] rowColBoundSizes = new int[primIndexes.size()][]; + HashMap sizeGroupMap = new HashMap(4); + DimConstraint[] allDCs = new DimConstraint[primIndexes.size()]; + + Iterator primIt = primIndexes.iterator(); + for (int r = 0; r < rowColBoundSizes.length; r++) { + int cellIx = primIt.next(); + int[] rowColSizes = new int[3]; + + if (cellIx >= -MAX_GRID && cellIx <= MAX_GRID) { // If not dock cell + allDCs[r] = primDCs[cellIx >= primDCs.length ? primDCs.length - 1 : cellIx]; + } else { + allDCs[r] = DOCK_DIM_CONSTRAINT; + } + + ArrayList groups = groupsLists[r]; + + int[] groupSizes = new int[] { + getTotalGroupsSizeParallel(groups, LayoutUtil.MIN, false), + getTotalGroupsSizeParallel(groups, LayoutUtil.PREF, false), + LayoutUtil.INF}; + + correctMinMax(groupSizes); + BoundSize dimSize = allDCs[r].getSize(); + + for (int sType = LayoutUtil.MIN; sType <= LayoutUtil.MAX; sType++) { + + int rowColSize = groupSizes[sType]; + + UnitValue uv = dimSize.getSize(sType); + if (uv != null) { + // If the size of the column is a link to some other size, use that instead + int unit = uv.getUnit(); + if (unit == UnitValue.PREF_SIZE) { + rowColSize = groupSizes[LayoutUtil.PREF]; + } else if (unit == UnitValue.MIN_SIZE) { + rowColSize = groupSizes[LayoutUtil.MIN]; + } else if (unit == UnitValue.MAX_SIZE) { + rowColSize = groupSizes[LayoutUtil.MAX]; + } else { + rowColSize = uv.getPixels(containerSize, container, null); + } + } else if (cellIx >= -MAX_GRID && cellIx <= MAX_GRID && rowColSize == 0) { + rowColSize = LayoutUtil.isDesignTime(container) ? LayoutUtil.getDesignTimeEmptySize() : 0; // Empty rows with no size set gets XX pixels if design time + } + + rowColSizes[sType] = rowColSize; + } + + correctMinMax(rowColSizes); + addToSizeGroup(sizeGroupMap, allDCs[r].getSizeGroup(), rowColSizes); + + rowColBoundSizes[r] = rowColSizes; + } + + // Set/equalize the size groups to same the values. + if (sizeGroupMap.size() > 0) { + for (int r = 0; r < rowColBoundSizes.length; r++) { + if (allDCs[r].getSizeGroup() != null) + rowColBoundSizes[r] = sizeGroupMap.get(allDCs[r].getSizeGroup()); + } + } + + // Add the gaps + ResizeConstraint[] resConstrs = getRowResizeConstraints(allDCs); + + boolean[] fillInPushGaps = new boolean[allDCs.length + 1]; + int[][] gapSizes = getRowGaps(allDCs, containerSize, isHor, fillInPushGaps); + + FlowSizeSpec fss = mergeSizesGapsAndResConstrs(resConstrs, fillInPushGaps, rowColBoundSizes, gapSizes); + + // Spanning components are not handled yet. Check and adjust the multi-row min/pref they enforce. + adjustMinPrefForSpanningComps(allDCs, defPush, fss, groupsLists); + + return fss; + } + + private static int getParentSize(ComponentWrapper cw, boolean isHor) + { + ContainerWrapper p = cw.getParent(); + return p != null ? (isHor ? cw.getWidth() : cw.getHeight()) : 0; + } + + private int[] getMinPrefMaxSumSize(boolean isHor, int[][] sizes) + { + int[] retSizes = new int[3]; + + BoundSize sz = isHor ? lc.getWidth() : lc.getHeight(); + + for (int i = 0; i < sizes.length; i++) { + if (sizes[i] != null) { + int[] size = sizes[i]; + for (int sType = LayoutUtil.MIN; sType <= LayoutUtil.MAX; sType++) { + if (sz.getSize(sType) != null) { + if (i == 0) + retSizes[sType] = sz.getSize(sType).getPixels(getParentSize(container, isHor), container, null); + } else { + int s = size[sType]; + + if (s != LayoutUtil.NOT_SET) { + if (sType == LayoutUtil.PREF) { + int bnd = size[LayoutUtil.MAX]; + if (bnd != LayoutUtil.NOT_SET && bnd < s) + s = bnd; + + bnd = size[LayoutUtil.MIN]; + if (bnd > s) // Includes s == LayoutUtil.NOT_SET since < 0. + s = bnd; + } + + retSizes[sType] += s; // MAX compensated below. + } + + // So that MAX is always correct. + if (size[LayoutUtil.MAX] == LayoutUtil.NOT_SET || retSizes[LayoutUtil.MAX] > LayoutUtil.INF) + retSizes[LayoutUtil.MAX] = LayoutUtil.INF; + } + } + } + } + + correctMinMax(retSizes); + + return retSizes; + } + + private static ResizeConstraint[] getRowResizeConstraints(DimConstraint[] specs) + { + ResizeConstraint[] resConsts = new ResizeConstraint[specs.length]; + for (int i = 0; i < resConsts.length; i++) + resConsts[i] = specs[i].resize; + return resConsts; + } + + private static ResizeConstraint[] getComponentResizeConstraints(ArrayList compWraps, boolean isHor) + { + ResizeConstraint[] resConsts = new ResizeConstraint[compWraps.size()]; + for (int i = 0; i < resConsts.length; i++) { + CC fc = compWraps.get(i).cc; + resConsts[i] = fc.getDimConstraint(isHor).resize; + + // Always grow docking components in the correct dimension. + int dock = fc.getDockSide(); + if (isHor ? (dock == 0 || dock == 2) : (dock == 1 || dock == 3)) { + ResizeConstraint dc = resConsts[i]; + resConsts[i] = new ResizeConstraint(dc.shrinkPrio, dc.shrink, dc.growPrio, ResizeConstraint.WEIGHT_100); + } + } + return resConsts; + } + + private static boolean[] getComponentGapPush(ArrayList compWraps, boolean isHor) + { + // Make one element bigger and or the after gap with the next before gap. + boolean[] barr = new boolean[compWraps.size() + 1]; + for (int i = 0; i < barr.length; i++) { + + boolean push = i > 0 && compWraps.get(i - 1).isPushGap(isHor, false); + + if (!push && i < (barr.length - 1)) + push = compWraps.get(i).isPushGap(isHor, true); + + barr[i] = push; + } + return barr; + } + + /** Returns the row gaps in pixel sizes. One more than there are specs sent in. + * @param specs + * @param refSize + * @param isHor + * @param fillInPushGaps If the gaps are pushing. NOTE! this argument will be filled in and thus changed! + * @return The row gaps in pixel sizes. One more than there are specs sent in. + */ + private int[][] getRowGaps(DimConstraint[] specs, int refSize, boolean isHor, boolean[] fillInPushGaps) + { + BoundSize defGap = isHor ? lc.getGridGapX() : lc.getGridGapY(); + if (defGap == null) + defGap = isHor ? PlatformDefaults.getGridGapX() : PlatformDefaults.getGridGapY(); + int[] defGapArr = defGap.getPixelSizes(refSize, container, null); + + boolean defIns = !hasDocks(); + + UnitValue firstGap = LayoutUtil.getInsets(lc, isHor ? 1 : 0, defIns); + UnitValue lastGap = LayoutUtil.getInsets(lc, isHor ? 3 : 2, defIns); + + int[][] retValues = new int[specs.length + 1][]; + + for (int i = 0, wgIx = 0; i < retValues.length; i++) { + DimConstraint specBefore = i > 0 ? specs[i - 1] : null; + DimConstraint specAfter = i < specs.length ? specs[i] : null; + + // No gap if between docking components. + boolean edgeBefore = (specBefore == DOCK_DIM_CONSTRAINT || specBefore == null); + boolean edgeAfter = (specAfter == DOCK_DIM_CONSTRAINT || specAfter == null); + if (edgeBefore && edgeAfter) + continue; + + BoundSize wrapGapSize = (wrapGapMap == null || isHor == lc.isFlowX() ? null : wrapGapMap.get(wgIx++)); + + if (wrapGapSize == null) { + + int[] gapBefore = specBefore != null ? specBefore.getRowGaps(container, null, refSize, false) : null; + int[] gapAfter = specAfter != null ? specAfter.getRowGaps(container, null, refSize, true) : null; + + if (edgeBefore && gapAfter == null && firstGap != null) { + + int bef = firstGap.getPixels(refSize, container, null); + retValues[i] = new int[] {bef, bef, bef}; + + } else if (edgeAfter && gapBefore == null && firstGap != null) { + + int aft = lastGap.getPixels(refSize, container, null); + retValues[i] = new int[] {aft, aft, aft}; + + } else { + retValues[i] = gapAfter != gapBefore ? mergeSizes(gapAfter, gapBefore) : new int[] {defGapArr[0], defGapArr[1], defGapArr[2]}; + } + + if (specBefore != null && specBefore.isGapAfterPush() || specAfter != null && specAfter.isGapBeforePush()) + fillInPushGaps[i] = true; + } else { + + if (wrapGapSize.isUnset()) { + retValues[i] = new int[] {defGapArr[0], defGapArr[1], defGapArr[2]}; + } else { + retValues[i] = wrapGapSize.getPixelSizes(refSize, container, null); + } + fillInPushGaps[i] = wrapGapSize.getGapPush(); + } + } + return retValues; + } + + private static int[][] getGaps(ArrayList compWraps, boolean isHor) + { + int compCount = compWraps.size(); + int[][] retValues = new int[compCount + 1][]; + + retValues[0] = compWraps.get(0).getGaps(isHor, true); + for (int i = 0; i < compCount; i++) { + int[] gap1 = compWraps.get(i).getGaps(isHor, false); + int[] gap2 = i < compCount - 1 ? compWraps.get(i + 1).getGaps(isHor, true) : null; + + retValues[i + 1] = mergeSizes(gap1, gap2); + } + + return retValues; + } + + private boolean hasDocks() + { + return (dockOffX > 0 || dockOffY > 0 || rowIndexes.last() > MAX_GRID || colIndexes.last() > MAX_GRID); + } + + /** Adjust min/pref size for columns(or rows) that has components that spans multiple columns (or rows). + * @param specs The specs for the columns or rows. Last index will be used if count is greater than this array's length. + * @param defPush The default grow weight if the specs does not have anyone that will grow. Comes from "push" in the CC. + * @param fss + * @param groupsLists + */ + private void adjustMinPrefForSpanningComps(DimConstraint[] specs, Float[] defPush, FlowSizeSpec fss, ArrayList[] groupsLists) + { + for (int r = groupsLists.length - 1; r >= 0; r--) { // Since 3.7.3 Iterate from end to start. Will solve some multiple spanning components hard to solve problems. + ArrayList groups = groupsLists[r]; + + for (LinkedDimGroup group : groups) { + if (group.span == 1) + continue; + + int[] sizes = group.getMinPrefMax(); + for (int s = LayoutUtil.MIN; s <= LayoutUtil.PREF; s++) { + int cSize = sizes[s]; + if (cSize == LayoutUtil.NOT_SET) + continue; + + int rowSize = 0; + int sIx = (r << 1) + 1; + int len = Math.min((group.span << 1), fss.sizes.length - sIx) - 1; + for (int j = sIx; j < sIx + len; j++) { + int sz = fss.sizes[j][s]; + if (sz != LayoutUtil.NOT_SET) + rowSize += sz; + } + + if (rowSize < cSize && len > 0) { + for (int eagerness = 0, newRowSize = 0; eagerness < 4 && newRowSize < cSize; eagerness++) + newRowSize = fss.expandSizes(specs, defPush, cSize, sIx, len, s, eagerness); + } + } + } + } + } + + /** For one dimension divide the component wraps into logical groups. One group for component wraps that share a common something, + * line the property to layout by base line. + * @param isRows If rows, and not columns, are to be divided. + * @return One ArrayList for every row/column. + */ + private ArrayList[] divideIntoLinkedGroups(boolean isRows) + { + boolean fromEnd = !(isRows ? lc.isTopToBottom() : LayoutUtil.isLeftToRight(lc, container)); + TreeSet primIndexes = isRows ? rowIndexes : colIndexes; + TreeSet secIndexes = isRows ? colIndexes : rowIndexes; + DimConstraint[] primDCs = (isRows ? rowConstr : colConstr).getConstaints(); + + @SuppressWarnings("unchecked") + ArrayList[] groupLists = new ArrayList[primIndexes.size()]; + + int gIx = 0; + for (int i : primIndexes) { + + DimConstraint dc; + if (i >= -MAX_GRID && i <= MAX_GRID) { // If not dock cell + dc = primDCs[i >= primDCs.length ? primDCs.length - 1 : i]; + } else { + dc = DOCK_DIM_CONSTRAINT; + } + + ArrayList groupList = new ArrayList(4); + groupLists[gIx++] = groupList; + + for (Integer ix : secIndexes) { + Cell cell = isRows ? getCell(i, ix) : getCell(ix, i); + if (cell == null || cell.compWraps.size() == 0) + continue; + + int span = (isRows ? cell.spany : cell.spanx); + if (span > 1) + span = convertSpanToSparseGrid(i, span, primIndexes); + + boolean isPar = (cell.flowx == isRows); + + if ((!isPar && cell.compWraps.size() > 1) || span > 1) { + + int linkType = isPar ? LinkedDimGroup.TYPE_PARALLEL : LinkedDimGroup.TYPE_SERIAL; + LinkedDimGroup lg = new LinkedDimGroup("p," + ix, span, linkType, !isRows, fromEnd); + lg.setCompWraps(cell.compWraps); + groupList.add(lg); + } else { + for (int cwIx = 0; cwIx < cell.compWraps.size(); cwIx++) { + CompWrap cw = cell.compWraps.get(cwIx); + boolean rowBaselineAlign = (isRows && lc.isTopToBottom() && dc.getAlignOrDefault(!isRows) == UnitValue.BASELINE_IDENTITY); // Disable baseline for bottomToTop since I can not verify it working. + boolean isBaseline = isRows && cw.isBaselineAlign(rowBaselineAlign); + + String linkCtx = isBaseline ? "baseline" : null; + + // Find a group with same link context and put it in that group. + boolean foundList = false; + for (int glIx = 0, lastGl = groupList.size() - 1; glIx <= lastGl; glIx++) { + LinkedDimGroup group = groupList.get(glIx); + if (group.linkCtx == linkCtx || linkCtx != null && linkCtx.equals(group.linkCtx)) { + group.addCompWrap(cw); + foundList = true; + break; + } + } + + // If none found and at last add a new group. + if (!foundList) { + int linkType = isBaseline ? LinkedDimGroup.TYPE_BASELINE : LinkedDimGroup.TYPE_PARALLEL; + LinkedDimGroup lg = new LinkedDimGroup(linkCtx, 1, linkType, !isRows, fromEnd); + lg.addCompWrap(cw); + groupList.add(lg); + } + } + } + } + } + return groupLists; + } + + /** Spanning is specified in the uncompressed grid number. They can for instance be more than 60000 for the outer + * edge dock grid cells. When the grid is compressed and indexed after only the cells that area occupied the span + * is erratic. This method use the row/col indexes and corrects the span to be correct for the compressed grid. + * @param span The span in the uncompressed grid. LayoutUtil.INF will be interpreted to span the rest + * of the column/row excluding the surrounding docking components. + * @param indexes The indexes in the correct dimension. + * @return The converted span. + */ + private static int convertSpanToSparseGrid(int curIx, int span, TreeSet indexes) + { + int lastIx = curIx + span; + int retSpan = 1; + + for (Integer ix : indexes) { + if (ix <= curIx) + continue; // We have not arrived to the correct index yet + + if (ix >= lastIx) + break; + + retSpan++; + } + return retSpan; + } + + private boolean isCellFree(int r, int c, ArrayList occupiedRects) + { + if (getCell(r, c) != null) + return false; + + for (int[] rect : occupiedRects) { + if (rect[0] <= c && rect[1] <= r && rect[0] + rect[2] > c && rect[1] + rect[3] > r) + return false; + } + return true; + } + + private Cell getCell(int r, int c) + { + return grid.get((r << 16) + (c & 0xffff)); + } + + private void setCell(int r, int c, Cell cell) + { + if (c < 0 || r < 0) + throw new IllegalArgumentException("Cell position cannot be negative. row: " + r + ", col: " + c); + + if (c > MAX_GRID || r > MAX_GRID) + throw new IllegalArgumentException("Cell position out of bounds. Out of cells. row: " + r + ", col: " + c); + + rowIndexes.add(r); + colIndexes.add(c); + + grid.put((r << 16) + (c & 0xffff), cell); + } + + /** Adds a docking cell. That cell is outside the normal cell indexes. + * @param dockInsets The current dock insets. Will be updated! + * @param side top == 0, left == 1, bottom = 2, right = 3. + * @param cw The compwrap to put in a cell and add. + */ + private void addDockingCell(int[] dockInsets, int side, CompWrap cw) + { + int r, c, spanx = 1, spany = 1; + switch (side) { + case 0: + case 2: + r = side == 0 ? dockInsets[0]++ : dockInsets[2]--; + c = dockInsets[1]; + spanx = dockInsets[3] - dockInsets[1] + 1; // The +1 is for cell 0. + colIndexes.add(dockInsets[3]); // Make sure there is a receiving cell + break; + + case 1: + case 3: + c = side == 1 ? dockInsets[1]++ : dockInsets[3]--; + r = dockInsets[0]; + spany = dockInsets[2] - dockInsets[0] + 1; // The +1 is for cell 0. + rowIndexes.add(dockInsets[2]); // Make sure there is a receiving cell + break; + + default: + throw new IllegalArgumentException("Internal error 123."); + } + + rowIndexes.add(r); + colIndexes.add(c); + + grid.put((r << 16) + (c & 0xffff), new Cell(cw, spanx, spany, spanx > 1)); + } + + /** A simple representation of a cell in the grid. Contains a number of component wraps, if they span more than one cell. + */ + private static class Cell + { + private final int spanx, spany; + private final boolean flowx; + private final ArrayList compWraps = new ArrayList(2); + + private boolean hasTagged = false; // If one or more components have styles and need to be checked by the component sorter + + private Cell(CompWrap cw) + { + this(cw, 1, 1, true); + } + + private Cell(int spanx, int spany, boolean flowx) + { + this(null, spanx, spany, flowx); + } + + private Cell(CompWrap cw, int spanx, int spany, boolean flowx) + { + if (cw != null) + compWraps.add(cw); + this.spanx = spanx; + this.spany = spany; + this.flowx = flowx; + } + } + + /** A number of component wraps that share a layout "something" in one dimension + */ + private static class LinkedDimGroup + { + private static final int TYPE_SERIAL = 0; + private static final int TYPE_PARALLEL = 1; + private static final int TYPE_BASELINE = 2; + + private final String linkCtx; + private final int span; + private final int linkType; + private final boolean isHor, fromEnd; + + private final ArrayList _compWraps = new ArrayList(4); + + private int lStart = 0, lSize = 0; // Currently mostly for debug painting + + private LinkedDimGroup(String linkCtx, int span, int linkType, boolean isHor, boolean fromEnd) + { + this.linkCtx = linkCtx; + this.span = span; + this.linkType = linkType; + this.isHor = isHor; + this.fromEnd = fromEnd; + } + + private void addCompWrap(CompWrap cw) + { + _compWraps.add(cw); + } + + private void setCompWraps(ArrayList cws) + { + if (_compWraps != cws) { + _compWraps.clear(); + _compWraps.addAll(cws); + } + } + + private void layout(DimConstraint dc, int start, int size, int spanCount) + { + lStart = start; + lSize = size; + + if (_compWraps.isEmpty()) + return; + + ContainerWrapper parent = _compWraps.get(0).comp.getParent(); + if (linkType == TYPE_PARALLEL) { + layoutParallel(parent, _compWraps, dc, start, size, isHor, fromEnd); + } else if (linkType == TYPE_BASELINE) { + layoutBaseline(parent, _compWraps, dc, start, size, LayoutUtil.PREF, spanCount); + } else { + layoutSerial(parent, _compWraps, dc, start, size, isHor, spanCount, fromEnd); + } + } + + /** Returns the min/pref/max sizes for this cell. Returned array must not be altered + * @return A shared min/pref/max array of sizes. Always of length 3 and never null. Will always be of type STATIC and PIXEL. + */ + private int[] getMinPrefMax() + { + int[] sizes = new int[3]; + if (!_compWraps.isEmpty()) { + for (int sType = LayoutUtil.MIN; sType <= LayoutUtil.PREF; sType++) { + if (linkType == TYPE_PARALLEL) { + sizes[sType] = getTotalSizeParallel(_compWraps, sType, isHor); + } else if (linkType == TYPE_BASELINE) { + AboveBelow aboveBelow = getBaselineAboveBelow(_compWraps, sType, false); + sizes[sType] = aboveBelow.sum(); + } else { + sizes[sType] = getTotalSizeSerial(_compWraps, sType, isHor); + } + } + sizes[LayoutUtil.MAX] = LayoutUtil.INF; + } + return sizes; + } + } + + /** Wraps a {@link java.awt.Component} together with its constraint. Caches a lot of information about the component so + * for instance not the preferred size has to be calculated more than once. + * + * Note! Does not ask the min/pref/max sizes again after the constructor. This means that + */ + private final class CompWrap + { + private final ComponentWrapper comp; + private final CC cc; + private final int eHideMode; + private final boolean useVisualPadding; + private boolean sizesOk = false; + private boolean isAbsolute; + + private int[][] gaps; // [top,left(actually before),bottom,right(actually after)][min,pref,max] + + private final int[] horSizes = new int[3]; + private final int[] verSizes = new int[3]; + + private int x = LayoutUtil.NOT_SET, y = LayoutUtil.NOT_SET, w = LayoutUtil.NOT_SET, h = LayoutUtil.NOT_SET; + + private int forcedPushGaps = 0; // 1 == before, 2 = after. Bitwise. + + /** + * @param c + * @param cc + * @param eHideMode Effective hide mode. <= 0 means visible. + * @param useVisualPadding + */ + private CompWrap(ComponentWrapper c, CC cc, int eHideMode, boolean useVisualPadding) + { + this.comp = c; + this.cc = cc; + this.eHideMode = eHideMode; + this.useVisualPadding = useVisualPadding; + this.isAbsolute = cc.getHorizontal().getSize().isAbsolute() && cc.getVertical().getSize().isAbsolute(); + + if (eHideMode > 1) { + gaps = new int[4][]; + for (int i = 0; i < gaps.length; i++) + gaps[i] = new int[3]; + } + } + + private int[] getSizes(boolean isHor) + { + validateSize(); + return isHor ? horSizes : verSizes; + } + + private void validateSize() + { + BoundSize[] callbackSz = getCallbackSize(comp); + + if (isAbsolute && sizesOk && callbackSz == null) + return; + + if (eHideMode <= 0) { + int contentBias = comp.getContentBias(); + + int sizeHint = contentBias == -1 ? -1 : (contentBias == 0 ? (w != LayoutUtil.NOT_SET ? w : comp.getWidth()) : (h != LayoutUtil.NOT_SET ? h : comp.getHeight())); + + BoundSize hBS = (callbackSz != null && callbackSz[0] != null) ? callbackSz[0] : cc.getHorizontal().getSize(); + BoundSize vBS = (callbackSz != null && callbackSz[1] != null) ? callbackSz[1] : cc.getVertical().getSize(); + + for (int i = LayoutUtil.MIN; i <= LayoutUtil.MAX; i++) { + switch (contentBias) { + case -1: // None + default: + horSizes[i] = getSize(hBS, i, true, useVisualPadding, -1); + verSizes[i] = getSize(vBS, i, false, useVisualPadding, -1); + break; + case 0: // Hor + horSizes[i] = getSize(hBS, i, true, useVisualPadding, -1); + verSizes[i] = getSize(vBS, i, false, useVisualPadding, sizeHint > 0 ? sizeHint : horSizes[i]); + break; + case 1: // Ver + verSizes[i] = getSize(vBS, i, false, useVisualPadding, -1); + horSizes[i] = getSize(hBS, i, true, useVisualPadding, sizeHint > 0 ? sizeHint : verSizes[i]); + break; + } + } + + correctMinMax(horSizes); + correctMinMax(verSizes); + } else { + Arrays.fill(horSizes, 0); // Needed if component goes from visible -> invisible without recreating the grid. + Arrays.fill(verSizes, 0); + } + sizesOk = true; + } + + private int getSize(BoundSize uvs, int sizeType, boolean isHor, boolean useVP, int sizeHint) + { + int size; + if (uvs == null || uvs.getSize(sizeType) == null) { + switch(sizeType) { + case LayoutUtil.MIN: + size = isHor ? comp.getMinimumWidth(sizeHint) : comp.getMinimumHeight(sizeHint); + break; + case LayoutUtil.PREF: + size = isHor ? comp.getPreferredWidth(sizeHint) : comp.getPreferredHeight(sizeHint); + break; + default: + size = isHor ? comp.getMaximumWidth(sizeHint) : comp.getMaximumHeight(sizeHint); + break; + } + if (useVP) { + //Do not include visual padding when calculating layout + int[] visualPadding = comp.getVisualPadding(); + + // Assume visualPadding is of length 4: top, left, bottom, right + if (visualPadding != null && visualPadding.length > 0) + size -= isHor ? (visualPadding[1] + visualPadding[3]) : (visualPadding[0] + visualPadding[2]); + } + } else { + ContainerWrapper par = comp.getParent(); + float refValue = isHor ? par.getWidth() : par.getHeight(); + size = uvs.getSize(sizeType).getPixels(refValue, par, comp); + } + return size; + } + + + private void calcGaps(ComponentWrapper before, CC befCC, ComponentWrapper after, CC aftCC, String tag, boolean flowX, boolean isLTR) + { + ContainerWrapper par = comp.getParent(); + int parW = par.getWidth(); + int parH = par.getHeight(); + + BoundSize befGap = before != null ? (flowX ? befCC.getHorizontal() : befCC.getVertical()).getGapAfter() : null; + BoundSize aftGap = after != null ? (flowX ? aftCC.getHorizontal() : aftCC.getVertical()).getGapBefore() : null; + + mergeGapSizes(cc.getVertical().getComponentGaps(par, comp, befGap, (flowX ? null : before), tag, parH, 0, isLTR), false, true); + mergeGapSizes(cc.getHorizontal().getComponentGaps(par, comp, befGap, (flowX ? before : null), tag, parW, 1, isLTR), true, true); + mergeGapSizes(cc.getVertical().getComponentGaps(par, comp, aftGap, (flowX ? null : after), tag, parH, 2, isLTR), false, false); + mergeGapSizes(cc.getHorizontal().getComponentGaps(par, comp, aftGap, (flowX ? after : null), tag, parW, 3, isLTR), true, false); + } + + private void setDimBounds(int start, int size, boolean isHor) + { + if (isHor) { + if (start != x || w != size) { + x = start; + w = size; + if (comp.getContentBias() == LayoutUtil.HORIZONTAL) + invalidateSizes(); // Only for components that have a bias the sizes will have changed. + } + } else { + if (start != y || h != size) { + y = start; + h = size; + if (comp.getContentBias() == LayoutUtil.VERTICAL) + invalidateSizes(); // Only for components that have a bias the sizes will have changed. + } + } + } + + void invalidateSizes() + { + sizesOk = false; + } + + private boolean isPushGap(boolean isHor, boolean isBefore) + { + if (isHor && ((isBefore ? 1 : 2) & forcedPushGaps) != 0) + return true; // Forced + + DimConstraint dc = cc.getDimConstraint(isHor); + BoundSize s = isBefore ? dc.getGapBefore() : dc.getGapAfter(); + return s != null && s.getGapPush(); + } + + /** Transfers the bounds to the component + */ + private void transferBounds(boolean addVisualPadding) + { + if (cc.isExternal()) + return; + + int compX = x; + int compY = y; + int compW = w; + int compH = h; + + if (addVisualPadding) { + //Add the visual padding back to the component when changing its size + int[] visualPadding = comp.getVisualPadding(); + if (visualPadding != null) { + //assume visualPadding is of length 4: top, left, bottom, right + compX -= visualPadding[1]; + compY -= visualPadding[0]; + compW += (visualPadding[1] + visualPadding[3]); + compH += (visualPadding[0] + visualPadding[2]); + } + } + + comp.setBounds(compX, compY, compW, compH); + } + + private void setForcedSizes(int[] sizes, boolean isHor) + { + if (sizes == null) + return; + + System.arraycopy(sizes, 0, getSizes(isHor), 0, 3); + sizesOk = true; + } + + private void setGaps(int[] minPrefMax, int ix) + { + if (gaps == null) + gaps = new int[][] {null, null, null, null}; + + gaps[ix] = minPrefMax; + } + + private void mergeGapSizes(int[] sizes, boolean isHor, boolean isTL) + { + if (gaps == null) + gaps = new int[][] {null, null, null, null}; + + if (sizes == null) + return; + + int gapIX = getGapIx(isHor, isTL); + int[] oldGaps = gaps[gapIX]; + if (oldGaps == null) { + oldGaps = new int[] {0, 0, LayoutUtil.INF}; + gaps[gapIX] = oldGaps; + } + + oldGaps[LayoutUtil.MIN] = Math.max(sizes[LayoutUtil.MIN], oldGaps[LayoutUtil.MIN]); + oldGaps[LayoutUtil.PREF] = Math.max(sizes[LayoutUtil.PREF], oldGaps[LayoutUtil.PREF]); + oldGaps[LayoutUtil.MAX] = Math.min(sizes[LayoutUtil.MAX], oldGaps[LayoutUtil.MAX]); + } + + private int getGapIx(boolean isHor, boolean isTL) + { + return isHor ? (isTL ? 1 : 3) : (isTL ? 0 : 2); + } + + private int getSizeInclGaps(int sizeType, boolean isHor) + { + return filter(sizeType, getGapBefore(sizeType, isHor) + getSize(sizeType, isHor) + getGapAfter(sizeType, isHor)); + } + + private int getSize(int sizeType, boolean isHor) + { + return filter(sizeType, getSizes(isHor)[sizeType]); + } + + private int getGapBefore(int sizeType, boolean isHor) + { + int[] gaps = getGaps(isHor, true); + return gaps != null ? filter(sizeType, gaps[sizeType]) : 0; + } + + private int getGapAfter(int sizeType, boolean isHor) + { + int[] gaps = getGaps(isHor, false); + return gaps != null ? filter(sizeType, gaps[sizeType]) : 0; + } + + private int[] getGaps(boolean isHor, boolean isTL) + { + return gaps[getGapIx(isHor, isTL)]; + } + + private int filter(int sizeType, int size) + { + if (size == LayoutUtil.NOT_SET) + return sizeType != LayoutUtil.MAX ? 0 : LayoutUtil.INF; + return constrainSize(size); + } + + private boolean isBaselineAlign(boolean defValue) + { + Float g = cc.getVertical().getGrow(); + if (g != null && g.intValue() != 0) + return false; + + UnitValue al = cc.getVertical().getAlign(); + return (al != null ? al == UnitValue.BASELINE_IDENTITY : defValue) && comp.hasBaseline(); + } + + private int getBaseline(int sizeType) + { + return comp.getBaseline(getSize(sizeType, true), getSize(sizeType, false)); + } + + void adjustMinHorSizeUp(int minSize) + { + int[] sz = getSizes(true); + if (sz[LayoutUtil.MIN] < minSize) + sz[LayoutUtil.MIN] = minSize; + correctMinMax(sz); + } + } + + //*************************************************************************************** + //* Helper Methods + //*************************************************************************************** + + private static void layoutBaseline(ContainerWrapper parent, ArrayList compWraps, DimConstraint dc, int start, int size, int sizeType, int spanCount) + { + AboveBelow aboveBelow = getBaselineAboveBelow(compWraps, sizeType, true); + int blRowSize = aboveBelow.sum(); + + CC cc = compWraps.get(0).cc; + + // Align for the whole baseline component array + UnitValue align = cc.getVertical().getAlign(); + if (spanCount == 1 && align == null) + align = dc.getAlignOrDefault(false); + if (align == UnitValue.BASELINE_IDENTITY) + align = UnitValue.CENTER; + + int offset = start + aboveBelow.maxAbove + (align != null ? Math.max(0, align.getPixels(size - blRowSize, parent, null)) : 0); + for (CompWrap cw : compWraps) { + cw.y += offset; + if (cw.y + cw.h > start + size) + cw.h = start + size - cw.y; + } + } + + private static void layoutSerial(ContainerWrapper parent, ArrayList compWraps, DimConstraint dc, int start, int size, boolean isHor, int spanCount, boolean fromEnd) + { + FlowSizeSpec fss = mergeSizesGapsAndResConstrs( + getComponentResizeConstraints(compWraps, isHor), + getComponentGapPush(compWraps, isHor), + getComponentSizes(compWraps, isHor), + getGaps(compWraps, isHor)); + + Float[] pushW = dc.isFill() ? GROW_100 : null; + int[] sizes = LayoutUtil.calculateSerial(fss.sizes, fss.resConstsInclGaps, pushW, LayoutUtil.PREF, size); + setCompWrapBounds(parent, sizes, compWraps, dc.getAlignOrDefault(isHor), start, size, isHor, fromEnd); + } + + private static void setCompWrapBounds(ContainerWrapper parent, int[] allSizes, ArrayList compWraps, UnitValue rowAlign, int start, int size, boolean isHor, boolean fromEnd) + { + int totSize = LayoutUtil.sum(allSizes); + CC cc = compWraps.get(0).cc; + UnitValue align = correctAlign(cc, rowAlign, isHor, fromEnd); + + int cSt = start; + int slack = size - totSize; + if (slack > 0 && align != null) { + int al = Math.min(slack, Math.max(0, align.getPixels(slack, parent, null))); + cSt += (fromEnd ? -al : al); + } + + for (int i = 0, bIx = 0, iSz = compWraps.size(); i < iSz; i++) { + CompWrap cw = compWraps.get(i); + if (fromEnd ) { + cSt -= allSizes[bIx++]; + cw.setDimBounds(cSt - allSizes[bIx], allSizes[bIx], isHor); + cSt -= allSizes[bIx++]; + } else { + cSt += allSizes[bIx++]; + cw.setDimBounds(cSt, allSizes[bIx], isHor); + cSt += allSizes[bIx++]; + } + } + } + + private static void layoutParallel(ContainerWrapper parent, ArrayList compWraps, DimConstraint dc, int start, int size, boolean isHor, boolean fromEnd) + { + int[][] sizes = new int[compWraps.size()][]; // [compIx][gapBef,compSize,gapAft] + + for (int i = 0; i < sizes.length; i++) { + CompWrap cw = compWraps.get(i); + + DimConstraint cDc = cw.cc.getDimConstraint(isHor); + + ResizeConstraint[] resConstr = new ResizeConstraint[] { + cw.isPushGap(isHor, true) ? GAP_RC_CONST_PUSH : GAP_RC_CONST, + cDc.resize, + cw.isPushGap(isHor, false) ? GAP_RC_CONST_PUSH : GAP_RC_CONST, + }; + + int[][] sz = new int[][] { + cw.getGaps(isHor, true), cw.getSizes(isHor), cw.getGaps(isHor, false) + }; + + Float[] pushW = dc.isFill() ? GROW_100 : null; + + sizes[i] = LayoutUtil.calculateSerial(sz, resConstr, pushW, LayoutUtil.PREF, size); + } + + UnitValue rowAlign = dc.getAlignOrDefault(isHor); + setCompWrapBounds(parent, sizes, compWraps, rowAlign, start, size, isHor, fromEnd); + } + + private static void setCompWrapBounds(ContainerWrapper parent, int[][] sizes, ArrayList compWraps, UnitValue rowAlign, int start, int size, boolean isHor, boolean fromEnd) + { + for (int i = 0; i < sizes.length; i++) { + CompWrap cw = compWraps.get(i); + + UnitValue align = correctAlign(cw.cc, rowAlign, isHor, fromEnd); + + int[] cSizes = sizes[i]; + int gapBef = cSizes[0]; + int cSize = cSizes[1]; // No Math.min(size, cSizes[1]) here! + int gapAft = cSizes[2]; + + int cSt = fromEnd ? start - gapBef : start + gapBef; + int slack = size - cSize - gapBef - gapAft; + if (slack > 0 && align != null) { + int al = Math.min(slack, Math.max(0, align.getPixels(slack, parent, null))); + cSt += (fromEnd ? -al : al); + } + + cw.setDimBounds(fromEnd ? cSt - cSize : cSt, cSize, isHor); + } + } + + private static UnitValue correctAlign(CC cc, UnitValue rowAlign, boolean isHor, boolean fromEnd) + { + UnitValue align = (isHor ? cc.getHorizontal() : cc.getVertical()).getAlign(); + if (align == null) + align = rowAlign; + if (align == UnitValue.BASELINE_IDENTITY) + align = UnitValue.CENTER; + + if (fromEnd) { + if (align == UnitValue.LEFT) + align = UnitValue.RIGHT; + else if (align == UnitValue.RIGHT) + align = UnitValue.LEFT; + } + return align; + } + + private static class AboveBelow { + int maxAbove; + int maxBelow; + + AboveBelow(int maxAbove, int maxBelow) { + this.maxAbove = maxAbove; + this.maxBelow = maxBelow; + } + + int sum() { + return maxAbove + maxBelow; + } + } + + private static AboveBelow getBaselineAboveBelow(ArrayList compWraps, int sType, boolean centerBaseline) + { + int maxAbove = Integer.MIN_VALUE; + int maxBelow = Integer.MIN_VALUE; + for (CompWrap cw : compWraps) { + int height = cw.getSize(sType, false); + if (height >= LayoutUtil.INF) + return new AboveBelow(LayoutUtil.INF / 2, LayoutUtil.INF / 2); + + int baseline = cw.getBaseline(sType); + int above = baseline + cw.getGapBefore(sType, false); + maxAbove = Math.max(above, maxAbove); + maxBelow = Math.max(height - baseline + cw.getGapAfter(sType, false), maxBelow); + + if (centerBaseline) + cw.setDimBounds(-baseline, height, false); + } + return new AboveBelow(maxAbove, maxBelow); + } + + private static int getTotalSizeParallel(ArrayList compWraps, int sType, boolean isHor) + { + int size = sType == LayoutUtil.MAX ? LayoutUtil.INF : 0; + + for (CompWrap cw : compWraps) { + int cwSize = cw.getSizeInclGaps(sType, isHor); + if (cwSize >= LayoutUtil.INF) + return LayoutUtil.INF; + + if (sType == LayoutUtil.MAX ? cwSize < size : cwSize > size) + size = cwSize; + } + return constrainSize(size); + } + + private static int getTotalSizeSerial(ArrayList compWraps, int sType, boolean isHor) + { + int totSize = 0; + for (int i = 0, iSz = compWraps.size(), lastGapAfter = 0; i < iSz; i++) { + CompWrap wrap = compWraps.get(i); + int gapBef = wrap.getGapBefore(sType, isHor); + if (gapBef > lastGapAfter) + totSize += gapBef - lastGapAfter; + + totSize += wrap.getSize(sType, isHor); + totSize += (lastGapAfter = wrap.getGapAfter(sType, isHor)); + + if (totSize >= LayoutUtil.INF) + return LayoutUtil.INF; + } + return constrainSize(totSize); + } + + private static int getTotalGroupsSizeParallel(ArrayList groups, int sType, boolean countSpanning) + { + int size = sType == LayoutUtil.MAX ? LayoutUtil.INF : 0; + for (LinkedDimGroup group : groups) { + if (countSpanning || group.span == 1) { + int grpSize = group.getMinPrefMax()[sType]; + if (grpSize >= LayoutUtil.INF) + return LayoutUtil.INF; + + if (sType == LayoutUtil.MAX ? grpSize < size : grpSize > size) + size = grpSize; + } + } + return constrainSize(size); + } + + /** + * @param compWraps + * @param isHor + * @return Might contain LayoutUtil.NOT_SET + */ + private static int[][] getComponentSizes(ArrayList compWraps, boolean isHor) + { + int[][] compSizes = new int[compWraps.size()][]; + for (int i = 0; i < compSizes.length; i++) + compSizes[i] = compWraps.get(i).getSizes(isHor); + return compSizes; + } + + /** Merges sizes and gaps together with Resize Constraints. For gaps {@link #GAP_RC_CONST} is used. + * @param resConstr One resize constraint for every row/component. Can be lesser in length and the last element should be used for missing elements. + * @param gapPush If the corresponding gap should be considered pushing and thus want to take free space if left over. Should be one more than resConstrs! + * @param minPrefMaxSizes The sizes (min/pref/max) for every row/component. + * @param gapSizes The gaps before and after each row/component packed in one double sized array. + * @return A holder for the merged values. + */ + private static FlowSizeSpec mergeSizesGapsAndResConstrs(ResizeConstraint[] resConstr, boolean[] gapPush, int[][] minPrefMaxSizes, int[][] gapSizes) + { + int[][] sizes = new int[(minPrefMaxSizes.length << 1) + 1][]; // Make room for gaps around. + ResizeConstraint[] resConstsInclGaps = new ResizeConstraint[sizes.length]; + + sizes[0] = gapSizes[0]; + for (int i = 0, crIx = 1; i < minPrefMaxSizes.length; i++, crIx += 2) { + + // Component bounds and constraints + resConstsInclGaps[crIx] = resConstr[i]; + sizes[crIx] = minPrefMaxSizes[i]; + + sizes[crIx + 1] = gapSizes[i + 1]; + + if (sizes[crIx - 1] != null) + resConstsInclGaps[crIx - 1] = gapPush[i < gapPush.length ? i : gapPush.length - 1] ? GAP_RC_CONST_PUSH : GAP_RC_CONST; + + if (i == (minPrefMaxSizes.length - 1) && sizes[crIx + 1] != null) + resConstsInclGaps[crIx + 1] = gapPush[(i + 1) < gapPush.length ? (i + 1) : gapPush.length - 1] ? GAP_RC_CONST_PUSH : GAP_RC_CONST; + } + + // Check for null and set it to 0, 0, 0. + for (int i = 0; i < sizes.length; i++) { + if (sizes[i] == null) + sizes[i] = new int[3]; + } + + return new FlowSizeSpec(sizes, resConstsInclGaps); + } + + private static int[] mergeSizes(int[] oldValues, int[] newValues) + { + if (oldValues == null) + return newValues; + + if (newValues == null) + return oldValues; + + int[] ret = new int[oldValues.length]; + for (int i = 0; i < ret.length; i++) + ret[i] = mergeSizes(oldValues[i], newValues[i], true); + + return ret; + } + + private static int mergeSizes(int oldValue, int newValue, boolean toMax) + { + if (oldValue == LayoutUtil.NOT_SET || oldValue == newValue) + return newValue; + + if (newValue == LayoutUtil.NOT_SET) + return oldValue; + + return toMax != oldValue > newValue ? newValue : oldValue; + } + + private static int constrainSize(int s) + { + return s > 0 ? (s < LayoutUtil.INF ? s : LayoutUtil.INF) : 0; + } + + private static void correctMinMax(int s[]) + { + if (s[LayoutUtil.MIN] > s[LayoutUtil.MAX]) + s[LayoutUtil.MIN] = s[LayoutUtil.MAX]; // Since MAX is almost always explicitly set use that + + if (s[LayoutUtil.PREF] < s[LayoutUtil.MIN]) + s[LayoutUtil.PREF] = s[LayoutUtil.MIN]; + + if (s[LayoutUtil.PREF] > s[LayoutUtil.MAX]) + s[LayoutUtil.PREF] = s[LayoutUtil.MAX]; + } + + private static final class FlowSizeSpec + { + private final int[][] sizes; // [row/col index][min, pref, max] + private final ResizeConstraint[] resConstsInclGaps; // [row/col index] + + private FlowSizeSpec(int[][] sizes, ResizeConstraint[] resConstsInclGaps) + { + this.sizes = sizes; + this.resConstsInclGaps = resConstsInclGaps; + } + + /** + * @param specs The specs for the columns or rows. Last index will be used of fromIx + len is greater than this array's length. + * @param targetSize The size to try to meet. + * @param defGrow The default grow weight if the specs does not have anyone that will grow. Comes from "push" in the CC. + * @param fromIx + * @param len + * @param sizeType + * @param eagerness How eager the algorithm should be to try to expand the sizes. + *

    + *
  • 0 - Grow only rows/columns which have the sizeType set to be the containing components AND which has a grow weight > 0. + *
  • 1 - Grow only rows/columns which have the sizeType set to be the containing components AND which has a grow weight > 0 OR unspecified. + *
  • 2 - Grow all rows/columns that have a grow weight > 0. + *
  • 3 - Grow all rows/columns that have a grow weight > 0 OR unspecified. + *
+ * @return The new size. + */ + private int expandSizes(DimConstraint[] specs, Float[] defGrow, int targetSize, int fromIx, int len, int sizeType, int eagerness) + { + ResizeConstraint[] resConstr = new ResizeConstraint[len]; + int[][] sizesToExpand = new int[len][]; + for (int i = 0; i < len; i++) { + int[] minPrefMax = sizes[i + fromIx]; + sizesToExpand[i] = new int[] {minPrefMax[sizeType], minPrefMax[LayoutUtil.PREF], minPrefMax[LayoutUtil.MAX]}; + + if (eagerness <= 1 && i % 2 == 0) { // (i % 2 == 0) means only odd indexes, which is only rows/col indexes and not gaps. + int cIx = (i + fromIx - 1) >> 1; + DimConstraint spec = (DimConstraint) LayoutUtil.getIndexSafe(specs, cIx); + + BoundSize sz = spec.getSize(); + if ( (sizeType == LayoutUtil.MIN && sz.getMin() != null && sz.getMin().getUnit() != UnitValue.MIN_SIZE) || + (sizeType == LayoutUtil.PREF && sz.getPreferred() != null && sz.getPreferred().getUnit() != UnitValue.PREF_SIZE)) { + continue; + } + } + resConstr[i] = (ResizeConstraint) LayoutUtil.getIndexSafe(resConstsInclGaps, i + fromIx); + } + + Float[] growW = (eagerness == 1 || eagerness == 3) ? extractSubArray(specs, defGrow, fromIx, len): null; + int[] newSizes = LayoutUtil.calculateSerial(sizesToExpand, resConstr, growW, LayoutUtil.PREF, targetSize); + int newSize = 0; + + for (int i = 0; i < len; i++) { + int s = newSizes[i]; + sizes[i + fromIx][sizeType] = s; + newSize += s; + } + return newSize; + } + } + + private static Float[] extractSubArray(DimConstraint[] specs, Float[] arr, int ix, int len) + { + if (arr == null || arr.length < ix + len) { + Float[] growLastArr = new Float[len]; + + // Handle a group where some rows (first one/few and/or last one/few) are docks. + for (int i = ix + len - 1; i >= 0; i -= 2) { + int specIx = (i >> 1); + if (specs[specIx] != DOCK_DIM_CONSTRAINT) { + growLastArr[i - ix] = ResizeConstraint.WEIGHT_100; + return growLastArr; + } + } + return growLastArr; + } + + Float[] newArr = new Float[len]; + System.arraycopy(arr, ix, newArr, 0, len); + return newArr; + } + + private static WeakHashMap[] PARENT_ROWCOL_SIZES_MAP = null; + @SuppressWarnings( "unchecked" ) + private static synchronized void putSizesAndIndexes(Object parComp, int[] sizes, int[] ixArr, boolean isRows) + { + if (PARENT_ROWCOL_SIZES_MAP == null) // Lazy since only if designing in IDEs + PARENT_ROWCOL_SIZES_MAP = new WeakHashMap[] {new WeakHashMap(4), new WeakHashMap(4)}; + + PARENT_ROWCOL_SIZES_MAP[isRows ? 0 : 1].put(parComp, new int[][]{ixArr, sizes}); + } + + static synchronized int[][] getSizesAndIndexes(Object parComp, boolean isRows) + { + if (PARENT_ROWCOL_SIZES_MAP == null) + return null; + + return PARENT_ROWCOL_SIZES_MAP[isRows ? 0 : 1].get(parComp); + } + + private static WeakHashMap> PARENT_GRIDPOS_MAP = null; + private static synchronized void saveGrid(ComponentWrapper parComp, LinkedHashMap grid) + { + if (PARENT_GRIDPOS_MAP == null) // Lazy since only if designing in IDEs + PARENT_GRIDPOS_MAP = new WeakHashMap>(4); + + ArrayList weakCells = new ArrayList(grid.size()); + + for (Map.Entry e : grid.entrySet()) { + Cell cell = e.getValue(); + Integer xyInt = e.getKey(); + if (xyInt != null) { + int x = (xyInt << 16) >> 16; + int y = xyInt >> 16; + + for (CompWrap cw : cell.compWraps) + weakCells.add(new WeakCell(cw.comp.getComponent(), x, y, cell.spanx, cell.spany)); + } + } + + PARENT_GRIDPOS_MAP.put(parComp.getComponent(), weakCells); + } + + static synchronized HashMap getGridPositions(Object parComp) + { + ArrayList weakCells = PARENT_GRIDPOS_MAP != null ? PARENT_GRIDPOS_MAP.get(parComp) : null; + if (weakCells == null) + return null; + + HashMap retMap = new HashMap(); + + for (WeakCell wc : weakCells) { + Object component = wc.componentRef.get(); + if (component != null) + retMap.put(component, new int[] {wc.x, wc.y, wc.spanX, wc.spanY}); + } + + return retMap; + } + + private static class WeakCell + { + private final WeakReference componentRef; + private final int x, y, spanX, spanY; + + private WeakCell(Object component, int x, int y, int spanX, int spanY) + { + this.componentRef = new WeakReference(component); + this.x = x; + this.y = y; + this.spanX = spanX; + this.spanY = spanY; + } + } +} diff --git a/src/net/miginfocom/layout/InCellGapProvider.java b/src/net/miginfocom/layout/InCellGapProvider.java new file mode 100644 index 0000000..bccf60c --- /dev/null +++ b/src/net/miginfocom/layout/InCellGapProvider.java @@ -0,0 +1,67 @@ +package net.miginfocom.layout; + +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** An interface to implement if you want to decide the gaps between two types of components within the same cell. + *

+ * E.g.: + * + *

+ * {@code
+ * if (adjacentComp == null || adjacentSide == SwingConstants.LEFT || adjacentSide == SwingConstants.TOP)
+ *	  return null;
+ *
+ * boolean isHor = (adjacentSide == SwingConstants.LEFT || adjacentSide == SwingConstants.RIGHT);
+ *
+ * if (adjacentComp.getComponentType(false) == ComponentWrapper.TYPE_LABEL && comp.getComponentType(false) == ComponentWrapper.TYPE_TEXT_FIELD)
+ *    return isHor ? UNRELATED_Y : UNRELATED_Y;
+ *
+ * return (adjacentSide == SwingConstants.LEFT || adjacentSide == SwingConstants.RIGHT) ? RELATED_X : RELATED_Y;
+ * }
+ * 
+ */ +public interface InCellGapProvider +{ + /** Returns the default gap between two components that are in the same cell. + * @param comp The component that the gap is for. Never null. + * @param adjacentComp The adjacent component if any. May be null. + * @param adjacentSide What side the adjacentComp is on. {@link javax.swing.SwingUtilities#TOP} or + * {@link javax.swing.SwingUtilities#LEFT} or {@link javax.swing.SwingUtilities#BOTTOM} or {@link javax.swing.SwingUtilities#RIGHT}. + * @param tag The tag string that the component might be tagged with in the component constraints. May be null. + * @param isLTR If it is left-to-right. + * @return The default gap between two components or null if there should be no gap. + */ + public abstract BoundSize getDefaultGap(ComponentWrapper comp, ComponentWrapper adjacentComp, int adjacentSide, String tag, boolean isLTR); +} diff --git a/src/net/miginfocom/layout/LC.java b/src/net/miginfocom/layout/LC.java new file mode 100644 index 0000000..3c8fc69 --- /dev/null +++ b/src/net/miginfocom/layout/LC.java @@ -0,0 +1,1045 @@ +package net.miginfocom.layout; + +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + + + +/** Contains the constraints for an instance of the {@link LC} layout manager. + */ +public final class LC //implements Externalizable +{ + // See the corresponding set/get method for documentation of the property! + + private int wrapAfter = LayoutUtil.INF; + + private Boolean leftToRight = null; + + private UnitValue[] insets = null; // Never null elements but if unset array is null + + private UnitValue alignX = null, alignY = null; + + private BoundSize gridGapX = null, gridGapY = null; + + private BoundSize width = BoundSize.NULL_SIZE, height = BoundSize.NULL_SIZE; + + private BoundSize packW = BoundSize.NULL_SIZE, packH = BoundSize.NULL_SIZE; + + private float pwAlign = 0.5f, phAlign = 1.0f; + + private int debugMillis = 0; + + private int hideMode = 0; + + private boolean noCache = false; + + private boolean flowX = true; + + private boolean fillX = false, fillY = false; + + private boolean topToBottom = true; + + private boolean noGrid = false; + + private boolean visualPadding = true; + + /** Empty constructor. + */ + public LC() + { + } + + // ************************************************************************ + // * JavaBean get/set methods. + // ************************************************************************ + + + /** If components have sizes or positions linked to the bounds of the parent in some way (as for instance the "%" unit has) the cache + * must be turned off for the panel. If components does not get the correct or expected size or position try to set this property to true. + * @return true means no cache and slightly slower layout. + */ + public boolean isNoCache() + { + return noCache; + } + + /** If components have sizes or positions linked to the bounds of the parent in some way (as for instance the "%" unit has) the cache + * must be turned off for the panel. If components does not get the correct or expected size or position try to set this property to true. + * @param b true means no cache and slightly slower layout. + */ + public void setNoCache(boolean b) + { + this.noCache = b; + } + + /** If the laid out components' bounds in total is less than the final size of the container these align values will be used to align the components + * in the parent. null is default and that means top/left alignment. The relative distances between the components will not be affected + * by this property. + * @return The current alignment. + */ + public final UnitValue getAlignX() + { + return alignX; + } + + /** If the laid out components' bounds in total is less than the final size of the container these align values will be used to align the components + * in the parent. null is default and that means top/left alignment. The relative distances between the components will not be affected + * by this property. + * @param uv The new alignment. Use {@link ConstraintParser#parseAlignKeywords(String, boolean)} to create the {@link UnitValue}. May be null. + */ + public final void setAlignX(UnitValue uv) + { + this.alignX = uv; + } + + /** If the laid out components' bounds in total is less than the final size of the container these align values will be used to align the components + * in the parent. null is default and that means top/left alignment. The relative distances between the components will not be affected + * by this property. + * @return The current alignment. + */ + public final UnitValue getAlignY() + { + return alignY; + } + + /** If the laid out components' bounds in total is less than the final size of the container these align values will be used to align the components + * in the parent. null is default and that means top/left alignment. The relative distances between the components will not be affected + * by this property. + * @param uv The new alignment. Use {@link ConstraintParser#parseAlignKeywords(String, boolean)} to create the {@link UnitValue}. May be null. + */ + public final void setAlignY(UnitValue uv) + { + this.alignY = uv; + } + + /** If > 0 the debug decorations will be repainted every millis. No debug information if <= 0 (default). + * @return The current debug repaint interval. + */ + public final int getDebugMillis() + { + return debugMillis; + } + + /** If > 0 the debug decorations will be repainted every millis. No debug information if <= 0 (default). + * @param millis The new debug repaint interval. + */ + public final void setDebugMillis(int millis) + { + this.debugMillis = millis; + } + + /** If the layout should always claim the whole bounds of the laid out container even if the preferred size is smaller. + * @return true means fill. false is default. + */ + public final boolean isFillX() + { + return fillX; + } + + /** If the layout should always claim the whole bounds of the laid out container even if the preferred size is smaller. + * @param b true means fill. false is default. + */ + public final void setFillX(boolean b) + { + this.fillX = b; + } + + /** If the layout should always claim the whole bounds of the laid out container even if the preferred size is smaller. + * @return true means fill. false is default. + */ + public final boolean isFillY() + { + return fillY; + } + + /** If the layout should always claim the whole bounds of the laid out container even if the preferred size is smaller. + * @param b true means fill. false is default. + */ + public final void setFillY(boolean b) + { + this.fillY = b; + } + + /** The default flow direction. Normally (which is true) this is horizontal and that means that the "next" component + * will be put in the cell to the right (or to the left if left-to-right is false). + * @return true is the default flow horizontally. + * @see #setLeftToRight(Boolean) + */ + public final boolean isFlowX() + { + return flowX; + } + + /** The default flow direction. Normally (which is true) this is horizontal and that means that the "next" component + * will be put in the cell to the right (or to the left if left-to-right is false). + * @param b true is the default flow horizontally. + * @see #setLeftToRight(Boolean) + */ + public final void setFlowX(boolean b) + { + this.flowX = b; + } + + /** If non-null (null is default) these value will be used as the default gaps between the columns in the grid. + * @return The default grid gap between columns in the grid. null if the platform default is used. + */ + public final BoundSize getGridGapX() + { + return gridGapX; + } + + /** If non-null (null is default) these value will be used as the default gaps between the columns in the grid. + * @param x The default grid gap between columns in the grid. If null the platform default is used. + */ + public final void setGridGapX(BoundSize x) + { + this.gridGapX = x; + } + + /** If non-null (null is default) these value will be used as the default gaps between the rows in the grid. + * @return The default grid gap between rows in the grid. null if the platform default is used. + */ + public final BoundSize getGridGapY() + { + return gridGapY; + } + + /** If non-null (null is default) these value will be used as the default gaps between the rows in the grid. + * @param y The default grid gap between rows in the grid. If null the platform default is used. + */ + public final void setGridGapY(BoundSize y) + { + this.gridGapY = y; + } + + /** How a component that is hidden (not visible) should be treated by default. + * @return The mode:
+ * 0 == Normal. Bounds will be calculated as if the component was visible.
+ * 1 == If hidden the size will be 0, 0 but the gaps remain.
+ * 2 == If hidden the size will be 0, 0 and gaps set to zero.
+ * 3 == If hidden the component will be disregarded completely and not take up a cell in the grid.. + */ + public final int getHideMode() + { + return hideMode; + } + + /** How a component that is hidden (not visible) should be treated. + * @param mode The mode:
+ * 0 == Normal. Bounds will be calculated as if the component was visible.
+ * 1 == If hidden the size will be 0, 0 but the gaps remain.
+ * 2 == If hidden the size will be 0, 0 and gaps set to zero.
+ * 3 == If hidden the component will be disregarded completely and not take up a cell in the grid.. + */ + public final void setHideMode(int mode) + { + if (mode < 0 || mode > 3) + throw new IllegalArgumentException("Wrong hideMode: " + mode); + + this.hideMode = mode; + } + + /** The insets for the layed out panel. The insets will be an empty space around the components in the panel. null values + * means that the default panel insets for the platform is used. See {@link PlatformDefaults#setDialogInsets(net.miginfocom.layout.UnitValue, net.miginfocom.layout.UnitValue, net.miginfocom.layout.UnitValue, net.miginfocom.layout.UnitValue)}. + * @return The insets. Of length 4 (top, left, bottom, right) or null. The elements (1 to 4) may be null. The array is a copy and can be used freely. + * @see net.miginfocom.layout.ConstraintParser#parseInsets(String, boolean) + */ + public final UnitValue[] getInsets() + { + return insets != null ? new UnitValue[] {insets[0], insets[1], insets[2], insets[3]} : null; + } + + /** The insets for the layed out panel. The insets will be an empty space around the components in the panel. null values + * means that the default panel insets for the platform is used. See {@link PlatformDefaults#setDialogInsets(net.miginfocom.layout.UnitValue, net.miginfocom.layout.UnitValue, net.miginfocom.layout.UnitValue, net.miginfocom.layout.UnitValue)}. + * @param ins The new insets. Must be of length 4 (top, left, bottom, right) or null. The elements (1 to 4) may be null to use + * the platform default for that side. The array is copied for storage. + * @see net.miginfocom.layout.ConstraintParser#parseInsets(String, boolean) + */ + public final void setInsets(UnitValue[] ins) + { + this.insets = ins != null ? new UnitValue[] {ins[0], ins[1], ins[2], ins[3]} : null; + } + + /** If the layout should be forced to be left-to-right or right-to-left. A value of null is default and + * means that this will be picked up from the {@link java.util.Locale} that the container being layed out is reporting. + * @return Boolean.TRUE if force left-to-right. Boolean.FALSE if force tight-to-left. null + * for the default "let the current Locale decide". + */ + public final Boolean getLeftToRight() + { + return leftToRight; + } + + /** If the layout should be forced to be left-to-right or right-to-left. A value of null is default and + * means that this will be picked up from the {@link java.util.Locale} that the container being layed out is reporting. + * @param b Boolean.TRUE to force left-to-right. Boolean.FALSE to force tight-to-left. null + * for the default "let the current Locale decide". + */ + public final void setLeftToRight(Boolean b) + { + this.leftToRight = b; + } + + /** If the whole layout should be non grid based. It is the same as setting the "nogrid" property on every row/column in the grid. + * @return true means not grid based. false is default. + */ + public final boolean isNoGrid() + { + return noGrid; + } + + /** If the whole layout should be non grid based. It is the same as setting the "nogrid" property on every row/column in the grid. + * @param b true means no grid. false is default. + */ + public final void setNoGrid(boolean b) + { + this.noGrid = b; + } + + /** If the layout should go from the default top-to-bottom in the grid instead of the optional bottom-to-top. + * @return true for the default top-to-bottom. + */ + public final boolean isTopToBottom() + { + return topToBottom; + } + + /** If the layout should go from the default top-to-bottom in the grid instead of the optional bottom-to-top. + * @param b true for the default top-to-bottom. + */ + public final void setTopToBottom(boolean b) + { + this.topToBottom = b; + } + + /** If visual padding should be automatically used and compensated for by this layout instance. + * @return true if visual padding. + */ + public final boolean isVisualPadding() + { + return visualPadding; + } + + /** If visual padding should be automatically used and compensated for by this layout instance. + * @param b true turns on visual padding. + */ + public final void setVisualPadding(boolean b) + { + this.visualPadding = b; + } + + /** Returns after what cell the grid should always auto wrap. + * @return After what cell the grid should always auto wrap. If 0 the number of columns/rows in the + * {@link net.miginfocom.layout.AC} is used. LayoutUtil.INF is used for no auto wrap. + */ + public final int getWrapAfter() + { + return wrapAfter; + } + + /** Sets after what cell the grid should always auto wrap. + * @param count After what cell the grid should always auto wrap. If 0 the number of columns/rows in the + * {@link net.miginfocom.layout.AC} is used. LayoutUtil.INF is used for no auto wrap. + */ + public final void setWrapAfter(int count) + { + this.wrapAfter = count; + } + + /** Returns the "pack width" for the window that this container is located in. When the size of this container changes + * the size of the window will be corrected to be within this BoundsSize. It can be used to set the minimum and/or maximum size of the window + * as well as the size window should optimally get. This optimal size is normally its "preferred" size which is why "preferred" + * is the normal value to set here. + *

+ * ":push" can be appended to the bound size to only push the size bigger and never shrink it if the preferred size gets smaller. + *

+ * E.g. "pref", "100:pref", "pref:700", "300::700", "pref:push" + * @return The current value. Never null. Check if not set with .isUnset(). + * @since 3.5 + */ + public final BoundSize getPackWidth() + { + return packW; + } + + /** Sets the "pack width" for the window that this container is located in. When the size of this container changes + * the size of the window will be corrected to be within this BoundsSize. It can be used to set the minimum and/or maximum size of the window + * as well as the size window should optimally get. This optimal size is normally its "preferred" size which is why "preferred" + * is the normal value to set here. + *

+ * ":push" can be appended to the bound size to only push the size bigger and never shrink it if the preferred size gets smaller. + *

+ * E.g. "pref", "100:pref", "pref:700", "300::700", "pref:push" + * @param size The new pack size. If null it will be corrected to an "unset" BoundSize. + * @since 3.5 + */ + public final void setPackWidth(BoundSize size) + { + packW = size != null ? size : BoundSize.NULL_SIZE; + } + + /** Returns the "pack height" for the window that this container is located in. When the size of this container changes + * the size of the window will be corrected to be within this BoundsSize. It can be used to set the minimum and/or maximum size of the window + * as well as the size window should optimally get. This optimal size is normally its "preferred" size which is why "preferred" + * is the normal value to set here. + *

+ * ":push" can be appended to the bound size to only push the size bigger and never shrink it if the preferred size gets smaller. + *

+ * E.g. "pref", "100:pref", "pref:700", "300::700", "pref:push" + * @return The current value. Never null. Check if not set with .isUnset(). + * @since 3.5 + */ + public final BoundSize getPackHeight() + { + return packH; + } + + /** Sets the "pack height" for the window that this container is located in. When the size of this container changes + * the size of the window will be corrected to be within this BoundsSize. It can be used to set the minimum and/or maximum size of the window + * as well as the size window should optimally get. This optimal size is normally its "preferred" size which is why "preferred" + * is the normal value to set here. + *

+ * ":push" can be appended to the bound size to only push the size bigger and never shrink it if the preferred size gets smaller. + *

+ * E.g. "pref", "100:pref", "pref:700", "300::700", "pref:push" + * @param size The new pack size. If null it will be corrected to an "unset" BoundSize. + * @since 3.5 + */ + public final void setPackHeight(BoundSize size) + { + packH = size != null ? size : BoundSize.NULL_SIZE; + } + + + /** If there is a resize of the window due to packing (see {@link #setPackHeight(BoundSize)} this value, which is between 0f and 1f, + * decides where the extra/superfluous size is placed. 0f means that the window will resize so that the upper part moves up and the + * lower side stays in the same place. 0.5f will expand/reduce the window equally upwards and downwards. 1f will do the opposite of 0f + * of course. + * @return The pack alignment. Always between 0f and 1f, inclusive. + * @since 3.5 + */ + public final float getPackHeightAlign() + { + return phAlign; + } + + /** If there is a resize of the window due to packing (see {@link #setPackHeight(BoundSize)} this value, which is between 0f and 1f, + * decides where the extra/superfluous size is placed. 0f means that the window will resize so that the upper part moves up and the + * lower side stays in the same place. 0.5f will expand/reduce the window equally upwards and downwards. 1f will do the opposite of 0f + * of course. + * @param align The pack alignment. Always between 0f and 1f, inclusive. Values outside this will be truncated. + * @since 3.5 + */ + public final void setPackHeightAlign(float align) + { + phAlign = Math.max(0f, Math.min(1f, align)); + } + + /** If there is a resize of the window due to packing (see {@link #setPackHeight(BoundSize)} this value, which is between 0f and 1f, + * decides where the extra/superfluous size is placed. 0f means that the window will resize so that the left part moves left and the + * right side stays in the same place. 0.5f will expand/reduce the window equally to the right and lefts. 1f will do the opposite of 0f + * of course. + * @return The pack alignment. Always between 0f and 1f, inclusive. + * @since 3.5 + */ + public final float getPackWidthAlign() + { + return pwAlign; + } + + /** If there is a resize of the window due to packing (see {@link #setPackHeight(BoundSize)} this value, which is between 0f and 1f, + * decides where the extra/superfluous size is placed. 0f means that the window will resize so that the left part moves left and the + * right side stays in the same place. 0.5f will expand/reduce the window equally to the right and lefts. 1f will do the opposite of 0f + * of course. + * @param align The pack alignment. Always between 0f and 1f, inclusive. Values outside this will be truncated. + * @since 3.5 + */ + public final void setPackWidthAlign(float align) + { + pwAlign = Math.max(0f, Math.min(1f, align)); + } + + /** Returns the minimum/preferred/maximum size for the container that this layout constraint is set for. Any of these + * sizes that is not null will be returned directly instead of determining the corresponding size through + * asking the components in this container. + * @return The width for the container that this layout constraint is set for. Not null but + * all sizes can be null. + * @since 3.5 + */ + public final BoundSize getWidth() + { + return width; + } + + /** Sets the minimum/preferred/maximum size for the container that this layout constraint is set for. Any of these + * sizes that is not null will be returned directly instead of determining the corresponding size through + * asking the components in this container. + * @param size The width for the container that this layout constraint is set for. null is translated to + * a bound size containing only null sizes. + * @since 3.5 + */ + public final void setWidth(BoundSize size) + { + this.width = size != null ? size : BoundSize.NULL_SIZE; + } + + /** Returns the minimum/preferred/maximum size for the container that this layout constraint is set for. Any of these + * sizes that is not null will be returned directly instead of determining the corresponding size through + * asking the components in this container. + * @return The height for the container that this layout constraint is set for. Not null but + * all sizes can be null. + * @since 3.5 + */ + public final BoundSize getHeight() + { + return height; + } + + /** Sets the minimum/preferred/maximum size for the container that this layout constraint is set for. Any of these + * sizes that is not null will be returned directly instead of determining the corresponding size through + * asking the components in this container. + * @param size The height for the container that this layout constraint is set for. null is translated to + * a bound size containing only null sizes. + * @since 3.5 + */ + public final void setHeight(BoundSize size) + { + this.height = size != null ? size : BoundSize.NULL_SIZE; + } + + // ************************************************************************ + // * Builder methods. + // ************************************************************************ + + /** Short for, and thus same as, .pack("pref", "pref"). + *

+ * Same functionality as {@link #setPackHeight(BoundSize)} and {@link #setPackWidth(net.miginfocom.layout.BoundSize)} + * only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.5 + */ + public final LC pack() + { + return pack("pref", "pref"); + } + + /** Sets the pack width and height. + *

+ * Same functionality as {@link #setPackHeight(BoundSize)} and {@link #setPackWidth(net.miginfocom.layout.BoundSize)} + * only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param width The pack width. May be null. + * @param height The pack height. May be null. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.5 + */ + public final LC pack(String width, String height) + { + setPackWidth(width != null ? ConstraintParser.parseBoundSize(width, false, true) : BoundSize.NULL_SIZE); + setPackHeight(height != null ? ConstraintParser.parseBoundSize(height, false, false) : BoundSize.NULL_SIZE); + return this; + } + + /** Sets the pack width and height alignment. + *

+ * Same functionality as {@link #setPackHeightAlign(float)} and {@link #setPackWidthAlign(float)} + * only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param alignX The pack width alignment. 0.5f is default. + * @param alignY The pack height alignment. 0.5f is default. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.5 + */ + public final LC packAlign(float alignX, float alignY) + { + setPackWidthAlign(alignX); + setPackHeightAlign(alignY); + return this; + } + + /** Sets a wrap after the number of columns/rows that is defined in the {@link net.miginfocom.layout.AC}. + *

+ * Same functionality as calling {@link #setWrapAfter(int)} with 0 only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC wrap() + { + setWrapAfter(0); + return this; + } + + /** Same functionality as {@link #setWrapAfter(int)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param count After what cell the grid should always auto wrap. If 0 the number of columns/rows in the + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC wrapAfter(int count) + { + setWrapAfter(count); + return this; + } + + /** Same functionality as calling {@link #setNoCache(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC noCache() + { + setNoCache(true); + return this; + } + + /** Same functionality as calling {@link #setFlowX(boolean)} with false only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC flowY() + { + setFlowX(false); + return this; + } + + /** Same functionality as calling {@link #setFlowX(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC flowX() + { + setFlowX(true); + return this; + } + + /** Same functionality as calling {@link #setFillX(boolean)} with true and {@link #setFillY(boolean)} with true conmbined.T his method returns + * this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC fill() + { + setFillX(true); + setFillY(true); + return this; + } + + /** Same functionality as calling {@link #setFillX(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC fillX() + { + setFillX(true); + return this; + } + + /** Same functionality as calling {@link #setFillY(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC fillY() + { + setFillY(true); + return this; + } + + /** Same functionality as {@link #setLeftToRight(Boolean)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param b true for forcing left-to-right. false for forcing right-to-left. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC leftToRight(boolean b) + { + setLeftToRight(b ? Boolean.TRUE : Boolean.FALSE); + return this; + } + + /** Same functionality as setLeftToRight(false) only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final LC rightToLeft() + { + setLeftToRight(Boolean.FALSE); + return this; + } + + /** Same functionality as calling {@link #setTopToBottom(boolean)} with false only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC bottomToTop() + { + setTopToBottom(false); + return this; + } + + /** Same functionality as calling {@link #setTopToBottom(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @since 3.7.2 + */ + public final LC topToBottom() + { + setTopToBottom(true); + return this; + } + + /** Same functionality as calling {@link #setNoGrid(boolean)} with true only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC noGrid() + { + setNoGrid(true); + return this; + } + + /** Same functionality as calling {@link #setVisualPadding(boolean)} with false only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC noVisualPadding() + { + setVisualPadding(false); + return this; + } + + /** Sets the same inset (expressed as a UnitValue, e.g. "10px" or "20mm") all around. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param allSides The unit value to set for all sides. May be null which means that the default panel insets + * for the platform is used. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setInsets(UnitValue[]) + */ + public final LC insetsAll(String allSides) + { + UnitValue insH = ConstraintParser.parseUnitValue(allSides, true); + UnitValue insV = ConstraintParser.parseUnitValue(allSides, false); + insets = new UnitValue[] {insV, insH, insV, insH}; // No setter to avoid copy again + return this; + } + + /** Same functionality as setInsets(ConstraintParser.parseInsets(s, true)). This method returns this + * for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param s The string to parse. E.g. "10 10 10 10" or "20". If less than 4 groups the last will be used for the missing. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setInsets(UnitValue[]) + */ + public final LC insets(String s) + { + insets = ConstraintParser.parseInsets(s, true); + return this; + } + + /** Sets the different insets (expressed as a UnitValues, e.g. "10px" or "20mm") for the corresponding sides. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param top The top inset. E.g. "10px" or "10mm" or "related". May be null in which case the default inset for this + * side for the platform will be used. + * @param left The left inset. E.g. "10px" or "10mm" or "related". May be null in which case the default inset for this + * side for the platform will be used. + * @param bottom The bottom inset. E.g. "10px" or "10mm" or "related". May be null in which case the default inset for this + * side for the platform will be used. + * @param right The right inset. E.g. "10px" or "10mm" or "related". May be null in which case the default inset for this + * side for the platform will be used. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setInsets(UnitValue[]) + */ + public final LC insets(String top, String left, String bottom, String right) + { + insets = new UnitValue[] { // No setter to avoid copy again + ConstraintParser.parseUnitValue(top, false), + ConstraintParser.parseUnitValue(left, true), + ConstraintParser.parseUnitValue(bottom, false), + ConstraintParser.parseUnitValue(right, true)}; + return this; + } + + /** Same functionality as setAlignX(ConstraintParser.parseUnitValueOrAlign(unitValue, true)) only this method returns this + * for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param align The align keyword or for instance "100px". E.g "left", "right", "leading" or "trailing". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setAlignX(UnitValue) + */ + public final LC alignX(String align) + { + setAlignX(ConstraintParser.parseUnitValueOrAlign(align, true, null)); + return this; + } + + /** Same functionality as setAlignY(ConstraintParser.parseUnitValueOrAlign(align, false)) only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param align The align keyword or for instance "100px". E.g "top" or "bottom". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setAlignY(UnitValue) + */ + public final LC alignY(String align) + { + setAlignY(ConstraintParser.parseUnitValueOrAlign(align, false, null)); + return this; + } + + /** Sets both the alignX and alignY as the same time. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param ax The align keyword or for instance "100px". E.g "left", "right", "leading" or "trailing". + * @param ay The align keyword or for instance "100px". E.g "top" or "bottom". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #alignX(String) + * @see #alignY(String) + */ + public final LC align(String ax, String ay) + { + if (ax != null) + alignX(ax); + + if (ay != null) + alignY(ay); + + return this; + } + + /** Same functionality as setGridGapX(ConstraintParser.parseBoundSize(boundsSize, true, true)) only this method + * returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param boundsSize The BoundSize of the gap. This is a minimum and/or preferred and/or maximum size. E.g. + * "50:100:200" or "100px". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setGridGapX(BoundSize) + */ + public final LC gridGapX(String boundsSize) + { + setGridGapX(ConstraintParser.parseBoundSize(boundsSize, true, true)); + return this; + } + + /** Same functionality as setGridGapY(ConstraintParser.parseBoundSize(boundsSize, true, false)) only this method + * returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param boundsSize The BoundSize of the gap. This is a minimum and/or preferred and/or maximum size. E.g. + * "50:100:200" or "100px". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setGridGapY(BoundSize) + */ + public final LC gridGapY(String boundsSize) + { + setGridGapY(ConstraintParser.parseBoundSize(boundsSize, true, false)); + return this; + } + + /** Sets both grid gaps at the same time. see {@link #gridGapX(String)} and {@link #gridGapY(String)}. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param gapx The BoundSize of the gap. This is a minimum and/or preferred and/or maximum size. E.g. + * "50:100:200" or "100px". + * @param gapy The BoundSize of the gap. This is a minimum and/or preferred and/or maximum size. E.g. + * "50:100:200" or "100px". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #gridGapX(String) + * @see #gridGapY(String) + */ + public final LC gridGap(String gapx, String gapy) + { + if (gapx != null) + gridGapX(gapx); + + if (gapy != null) + gridGapY(gapy); + + return this; + } + + /** Calls {@link #debug(int)} with 300 as an argument. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setDebugMillis(int) + */ + public final LC debug() + { + setDebugMillis(300); + return this; + } + + /** Same functionality as {@link #setDebugMillis(int repaintMillis)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param repaintMillis The new debug repaint interval. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setDebugMillis(int) + */ + public final LC debug(int repaintMillis) + { + setDebugMillis(repaintMillis); + return this; + } + + /** Same functionality as {@link #setHideMode(int mode)} only this method returns this for chaining multiple calls. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcomponents.com. + * @param mode The mode:
+ * 0 == Normal. Bounds will be calculated as if the component was visible.
+ * 1 == If hidden the size will be 0, 0 but the gaps remain.
+ * 2 == If hidden the size will be 0, 0 and gaps set to zero.
+ * 3 == If hidden the component will be disregarded completely and not take up a cell in the grid.. + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + * @see #setHideMode(int) + */ + public final LC hideMode(int mode) + { + setHideMode(mode); + return this; + } + + /** The minimum width for the container. The value will override any value that is set on the container itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or Cheat Sheet at www.migcontainers.com. + * @param width The width expressed as a UnitValue. E.g. "100px" or "200mm". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC minWidth(String width) + { + setWidth(LayoutUtil.derive(getWidth(), ConstraintParser.parseUnitValue(width, true), null, null)); + return this; + } + + /** The width for the container as a min and/or preferred and/or maximum width. The value will override any value that is set on + * the container itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or Cheat Sheet at www.migcontainers.com. + * @param width The width expressed as a BoundSize. E.g. "50:100px:200mm" or "100px". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC width(String width) + { + setWidth(ConstraintParser.parseBoundSize(width, false, true)); + return this; + } + + /** The maximum width for the container. The value will override any value that is set on the container itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or Cheat Sheet at www.migcontainers.com. + * @param width The width expressed as a UnitValue. E.g. "100px" or "200mm". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC maxWidth(String width) + { + setWidth(LayoutUtil.derive(getWidth(), null, null, ConstraintParser.parseUnitValue(width, true))); + return this; + } + + /** The minimum height for the container. The value will override any value that is set on the container itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or Cheat Sheet at www.migcontainers.com. + * @param height The height expressed as a UnitValue. E.g. "100px" or "200mm". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC minHeight(String height) + { + setHeight(LayoutUtil.derive(getHeight(), ConstraintParser.parseUnitValue(height, false), null, null)); + return this; + } + + /** The height for the container as a min and/or preferred and/or maximum height. The value will override any value that is set on + * the container itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcontainers.com. + * @param height The height expressed as a BoundSize. E.g. "50:100px:200mm" or "100px". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC height(String height) + { + setHeight(ConstraintParser.parseBoundSize(height, false, false)); + return this; + } + + /** The maximum height for the container. The value will override any value that is set on the container itself. + *

+ * For a more thorough explanation of what this constraint does see the white paper or cheat Sheet at www.migcontainers.com. + * @param height The height expressed as a UnitValue. E.g. "100px" or "200mm". + * @return this so it is possible to chain calls. E.g. new LayoutConstraint().noGrid().gap().fill(). + */ + public final LC maxHeight(String height) + { + setHeight(LayoutUtil.derive(getHeight(), null, null, ConstraintParser.parseUnitValue(height, false))); + return this; + } +// +// // ************************************************ +// // Persistence Delegate and Serializable combined. +// // ************************************************ +// +// private Object readResolve() throws ObjectStreamException +// { +// return LayoutUtil.getSerializedObject(this); +// } +// +// @Override +// public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException +// { +// LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in)); +// } +// +// @Override +// public void writeExternal(ObjectOutput out) throws IOException +// { +// if (getClass() == LC.class) +// LayoutUtil.writeAsXML(out, this); +// } +} diff --git a/src/net/miginfocom/layout/LayoutCallback.java b/src/net/miginfocom/layout/LayoutCallback.java new file mode 100644 index 0000000..f685fc0 --- /dev/null +++ b/src/net/miginfocom/layout/LayoutCallback.java @@ -0,0 +1,77 @@ +package net.miginfocom.layout; + +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** A class to extend if you want to provide more control over where a component is placed or the size of it. + *

+ * Note! Returned arrays from this class will never be altered. This means that caching of arrays in these methods + * is OK. + */ +public abstract class LayoutCallback +{ + /** Returns a position similar to the "pos" the component constraint. + * @param comp The component wrapper that holds the actual component (JComponent is Swing and Control in SWT). + * Should not be altered. + * @return The [x, y, x2, y2] as explained in the documentation for "pos". If null + * is returned nothing is done and this is the default. + * @see UnitValue + * @see net.miginfocom.layout.ConstraintParser#parseUnitValue(String, boolean) + */ + public UnitValue[] getPosition(ComponentWrapper comp) + { + return null; + } + + /** Returns a size similar to the "width" and "height" in the component constraint. + * @param comp The component wrapper that holds the actual component (JComponent is Swing and Control in SWT). + * Should not be altered. + * @return The [width, height] as explained in the documentation for "width" and "height". If null + * is returned nothing is done and this is the default. + * @see net.miginfocom.layout.BoundSize + * @see net.miginfocom.layout.ConstraintParser#parseBoundSize(String, boolean, boolean) + */ + public BoundSize[] getSize(ComponentWrapper comp) + { + return null; + } + + /** A last minute change of the bounds. The bound for the layout cycle has been set and you can correct there + * after any set of rules you like. + * @param comp The component wrapper that holds the actual component (JComponent is Swing and Control in SWT). + */ + public void correctBounds(ComponentWrapper comp) + { + } +} diff --git a/src/net/miginfocom/layout/LayoutUtil.java b/src/net/miginfocom/layout/LayoutUtil.java new file mode 100644 index 0000000..4a23e0f --- /dev/null +++ b/src/net/miginfocom/layout/LayoutUtil.java @@ -0,0 +1,609 @@ +package net.miginfocom.layout; + +//import java.beans.Beans; +//import java.beans.ExceptionListener; +//import java.beans.Introspector; +//import java.beans.PersistenceDelegate; +//import java.beans.XMLDecoder; +//import java.beans.XMLEncoder; +//import java.io.ByteArrayInputStream; +//import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.IOException; +//import java.io.ObjectInput; +//import java.io.ObjectOutput; +//import java.io.OutputStream; +import java.util.IdentityHashMap; +import java.util.TreeSet; +import java.util.WeakHashMap; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** A utility class that has only static helper methods. + */ +public final class LayoutUtil +{ + /** A substitute value for a really large value. Integer.MAX_VALUE is not used since that means a lot of defensive code + * for potential overflow must exist in many places. This value is large enough for being unreasonable yet it is hard to + * overflow. + */ + public static final int INF = (Integer.MAX_VALUE >> 10) - 100; // To reduce likelihood of overflow errors when calculating. + + /** Tag int for a value that in considered "not set". Used as "null" element in int arrays. + */ + static final int NOT_SET = Integer.MIN_VALUE + 12346; // Magic value... + + // Index for the different sizes + public static final int MIN = 0; + public static final int PREF = 1; + public static final int MAX = 2; + + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + + private static volatile WeakHashMap CR_MAP = null; + private static volatile WeakHashMap DT_MAP = null; // The Containers that have design time. Value not used. + private static int eSz = 0; + private static int globalDebugMillis = 0; + + // public static final boolean HAS_BEANS = hasBeans(); + +// private static boolean hasBeans() +// { +// try { +// LayoutUtil.class.getClassLoader().loadClass("java.beans.Beans"); +// return true; +// } catch (Throwable e) { +// return false; +// } +// } + + private LayoutUtil() + { + } + + /** Returns the current version of MiG Layout. + * @return The current version of MiG Layout. E.g. "3.6.3" or "4.0" + */ + public static String getVersion() + { + return "5.0"; + } + + /** If global debug should be on or off. If > 0 then debug is turned on for all MigLayout + * instances. + * @return The current debug milliseconds. + * @see LC#setDebugMillis(int) + */ + public static int getGlobalDebugMillis() + { + return globalDebugMillis; + } + + /** If global debug should be on or off. If > 0 then debug is turned on for all MigLayout + * instances. + *

+ * Note! This is a passive value and will be read by panels when the needed, which is normally + * when they repaint/layout. + * @param millis The new debug milliseconds. 0 turns of global debug and leaves debug up to every + * individual panel. + * @see LC#setDebugMillis(int) + */ + public static void setGlobalDebugMillis(int millis) + { + globalDebugMillis = millis; + } + + /** Sets if design time is turned on for a Container in {@link ContainerWrapper}. + * @param cw The container to set design time for. null is legal and can be used as + * a key to turn on/off design time "in general". Note though that design time "in general" is + * always on as long as there is at least one ContainerWrapper with design time. + *

+ * If this method has not ever been called it will default to what + * Beans.isDesignTime() returns. This means that if you call + * this method you indicate that you will take responsibility for the design time value. + * @param b true means design time on. + */ + public static void setDesignTime(ContainerWrapper cw, boolean b) + { + if (DT_MAP == null) + DT_MAP = new WeakHashMap(); + + DT_MAP.put((cw != null ? cw.getComponent() : null), b); + } + + /** Returns if design time is turned on for a Container in {@link ContainerWrapper}. + * @param cw The container to set design time for. null is legal will return true + * if there is at least one ContainerWrapper (or null) that have design time + * turned on. + * @return If design time is set for cw. + */ + public static boolean isDesignTime(ContainerWrapper cw) + { + if (DT_MAP == null) + return false;// BH 2018 //HAS_BEANS && Beans.isDesignTime(); + + // assume design time "in general" (cw is null) if there is at least one container with design time + // (for storing constraints creation strings in method putCCString()) + if (cw == null && DT_MAP != null && !DT_MAP.isEmpty() ) + return true; + + if (cw != null && DT_MAP.containsKey(cw.getComponent()) == false) + cw = null; + + Boolean b = DT_MAP.get(cw != null ? cw.getComponent() : null); + return b != null && b; + } + + /** The size of an empty row or columns in a grid during design time. + * @return The number of pixels. Default is 15. + */ + public static int getDesignTimeEmptySize() + { + return eSz; + } + + /** The size of an empty row or columns in a grid during design time. + * @param pixels The number of pixels. Default is 0 (it was 15 prior to v3.7.2, but since that meant different behaviour + * under design time by default it was changed to be 0, same as non-design time). IDE vendors can still set it to 15 to + * get the old behaviour. + */ + public static void setDesignTimeEmptySize(int pixels) + { + eSz = pixels; + } + + /** Associates con with the creation string s. The con object should + * probably have an equals method that compares identities or con objects that .equals() will only + * be able to have one creation string. + *

+ * If {@link LayoutUtil#isDesignTime(ContainerWrapper)} returns false the method does nothing. + * @param con The object. if null the method does nothing. + * @param s The creation string. if null the method does nothing. + */ + static void putCCString(Object con, String s) + { + if (s != null && con != null && isDesignTime(null)) { + if (CR_MAP == null) + CR_MAP = new WeakHashMap(64); + + CR_MAP.put(con, s); + } + } + +// /** Sets/add the persistence delegates to be used for a class. +// * @param c The class to set the registered delegate for. +// * @param del The new delegate or null to erase to old one. +// */ +// static synchronized void setDelegate(Class c, PersistenceDelegate del) +// { +// try { +// Introspector.getBeanInfo(c, Introspector.IGNORE_ALL_BEANINFO).getBeanDescriptor().setValue("persistenceDelegate", del); +// } catch (Exception ignored) { +// } +// } + + /** Returns strings set with {@link #putCCString(Object, String)} or null if nothing is associated or + * {@link LayoutUtil#isDesignTime(ContainerWrapper)} returns false. + * @param con The constrain object. + * @return The creation string or null if nothing is registered with the con object. + */ + static String getCCString(Object con) + { + return CR_MAP != null ? CR_MAP.get(con) : null; + } + + static void throwCC() + { + throw new IllegalStateException("setStoreConstraintData(true) must be set for strings to be saved."); + } + + /** Takes a number on min/preferred/max sizes and resize constraints and returns the calculated sizes which sum should add up to bounds. Whether the sum + * will actually equal bounds is dependent on the pref/max sizes and resize constraints. + * @param sizes [ix],[MIN][PREF][MAX]. Grid.CompWrap.NOT_SET will be treated as N/A or 0. A "[MIN][PREF][MAX]" array with null elements will be interpreted as very flexible (no bounds) + * but if the array itself is null it will not get any size. + * @param resConstr Elements can be null and the whole array can be null. null means that the size will not be flexible at all. + * Can have length less than sizes in which case the last element should be used for the elements missing. + * @param defPushWeights If there is no grow weight for a resConstr the corresponding value of this array is used. + * These forced resConstr will be grown last though and only if needed to fill to the bounds. + * @param startSizeType The initial size to use. E.g. {@link net.miginfocom.layout.LayoutUtil#MIN}. + * @param bounds To use for relative sizes. + * @return The sizes. Array length will match sizes. + */ + static int[] calculateSerial(int[][] sizes, ResizeConstraint[] resConstr, Float[] defPushWeights, int startSizeType, int bounds) + { + float[] lengths = new float[sizes.length]; // heights/widths that are set + float usedLength = 0.0f; + + // Give all preferred size to start with + for (int i = 0; i < sizes.length; i++) { + if (sizes[i] != null) { + float len = sizes[i][startSizeType] != NOT_SET ? sizes[i][startSizeType] : 0; + int newSizeBounded = getBrokenBoundary(len, sizes[i][MIN], sizes[i][MAX]); + if (newSizeBounded != NOT_SET) + len = newSizeBounded; + + usedLength += len; + lengths[i] = len; + } + } + + int useLengthI = Math.round(usedLength); + if (useLengthI != bounds && resConstr != null) { + boolean isGrow = useLengthI < bounds; + + // Create a Set with the available priorities + TreeSet prioList = new TreeSet(); + for (int i = 0; i < sizes.length; i++) { + ResizeConstraint resC = (ResizeConstraint) getIndexSafe(resConstr, i); + if (resC != null) + prioList.add(isGrow ? resC.growPrio : resC.shrinkPrio); + } + Integer[] prioIntegers = prioList.toArray(new Integer[prioList.size()]); + + for (int force = 0; force <= ((isGrow && defPushWeights != null) ? 1 : 0); force++) { // Run twice if defGrow and the need for growing. + for (int pr = prioIntegers.length - 1; pr >= 0; pr--) { + int curPrio = prioIntegers[pr]; + + float totWeight = 0f; + Float[] resizeWeight = new Float[sizes.length]; + for (int i = 0; i < sizes.length; i++) { + if (sizes[i] == null) // if no min/pref/max size at all do not grow or shrink. + continue; + + ResizeConstraint resC = (ResizeConstraint) getIndexSafe(resConstr, i); + if (resC != null) { + int prio = isGrow ? resC.growPrio : resC.shrinkPrio; + + if (curPrio == prio) { + if (isGrow) { + resizeWeight[i] = (force == 0 || resC.grow != null) ? resC.grow : (defPushWeights[i < defPushWeights.length ? i : defPushWeights.length - 1]); + } else { + resizeWeight[i] = resC.shrink; + } + if (resizeWeight[i] != null) + totWeight += resizeWeight[i]; + } + } + } + + if (totWeight > 0f) { + boolean hit; + do { + float toChange = bounds - usedLength; + hit = false; + float changedWeight = 0f; + for (int i = 0; i < sizes.length && totWeight > 0.0001f; i++) { + + Float weight = resizeWeight[i]; + if (weight != null) { + float sizeDelta = toChange * weight / totWeight; + float newSize = lengths[i] + sizeDelta; + + if (sizes[i] != null) { + int newSizeBounded = getBrokenBoundary(newSize, sizes[i][MIN], sizes[i][MAX]); + if (newSizeBounded != NOT_SET) { + resizeWeight[i] = null; + hit = true; + changedWeight += weight; + newSize = newSizeBounded; + sizeDelta = newSize - lengths[i]; + } + } + + lengths[i] = newSize; + usedLength += sizeDelta; + } + } + totWeight -= changedWeight; + } while (hit); + } + } + } + } + return roundSizes(lengths); + } + + static Object getIndexSafe(Object[] arr, int ix) + { + return arr != null ? arr[ix < arr.length ? ix : arr.length - 1] : null; + } + + /** Returns the broken boundary if sz is outside the boundaries lower or upper. If both boundaries + * are broken, the lower one is returned. If sz is < 0 then new Float(0f) is returned so that no sizes can be + * negative. + * @param sz The size to check + * @param lower The lower boundary (or null for no boundary). + * @param upper The upper boundary (or null for no boundary). + * @return The broken boundary. + */ + private static int getBrokenBoundary(float sz, int lower, int upper) + { + if (lower != NOT_SET) { + if (sz < lower) + return lower; + } else if (sz < 0f) { + return 0; + } + + if (upper != NOT_SET && sz > upper) + return upper; + + return NOT_SET; + } + + + static int sum(int[] terms, int start, int len) + { + int s = 0; + for (int i = start, iSz = start + len; i < iSz; i++) + s += terms[i]; + return s; + } + + static int sum(int[] terms) + { + return sum(terms, 0, terms.length); + } + + /** Keeps f within min and max. Min is of higher priority if min is larger than max. + * @param f The value to clamp + * @param min + * @param max + * @return The clamped value, between min and max. + */ + static float clamp(float f, float min, float max) + { + return Math.max(min, Math.min(f, max)); + } + + /** Keeps i within min and max. Min is of higher priority if min is larger than max. + * @param i The value to clamp + * @param min + * @param max + * @return The clamped value, between min and max. + */ + static int clamp(int i, int min, int max) + { + return Math.max(min, Math.min(i, max)); + } + + public static int getSizeSafe(int[] sizes, int sizeType) + { + if (sizes == null || sizes[sizeType] == NOT_SET) + return sizeType == MAX ? LayoutUtil.INF : 0; + return sizes[sizeType]; + } + + static BoundSize derive(BoundSize bs, UnitValue min, UnitValue pref, UnitValue max) + { + if (bs == null || bs.isUnset()) + return new BoundSize(min, pref, max, null); + + return new BoundSize( + min != null ? min : bs.getMin(), + pref != null ? pref : bs.getPreferred(), + max != null ? max : bs.getMax(), + bs.getGapPush(), + null); + } + + /** Returns if left-to-right orientation is used. If not set explicitly in the layout constraints the Locale + * of the parent is used. + * @param lc The constraint if there is one. Can be null. + * @param container The parent that may be used to get the left-to-right if lc does not specify this. + * @return If left-to-right orientation is currently used. + */ + public static boolean isLeftToRight(LC lc, ContainerWrapper container) + { + if (lc != null && lc.getLeftToRight() != null) + return lc.getLeftToRight(); + + return container == null || container.isLeftToRight(); + } + + /** Round a number of float sizes into int sizes so that the total length match up + * @param sizes The sizes to round + * @return An array of equal length as sizes. + */ + static int[] roundSizes(float[] sizes) + { + int[] retInts = new int[sizes.length]; + float posD = 0; + + for (int i = 0; i < retInts.length; i++) { + int posI = (int) (posD + 0.5f); + + posD += sizes[i]; + + retInts[i] = (int) (posD + 0.5f) - posI; + } + + return retInts; + } + + /** Safe equals. null == null, but null never equals anything else. + * @param o1 The first object. May be null. + * @param o2 The second object. May be null. + * @return Returns true if o1 and o2 are equal (using .equals()) or both are null. + */ + static boolean equals(Object o1, Object o2) + { + return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2)); + } + +// static int getBaselineCorrect(Component comp) +// { +// Dimension pSize = comp.getPreferredSize(); +// int baseline = comp.getBaseline(pSize.width, pSize.height); +// int nextBaseline = comp.getBaseline(pSize.width, pSize.height + 1); +// +// // Amount to add to height when calculating where baseline +// // lands for a particular height: +// int padding = 0; +// +// // Where the baseline is relative to the mid point +// int baselineOffset = baseline - pSize.height / 2; +// if (pSize.height % 2 == 0 && baseline != nextBaseline) { +// padding = 1; +// } else if (pSize.height % 2 == 1 && baseline == nextBaseline) { +// baselineOffset--; +// padding = 1; +// } +// +// // The following calculates where the baseline lands for +// // the height z: +// return (pSize.height + padding) / 2 + baselineOffset; +// } + + + /** Returns the insets for the side. + * @param side top == 0, left == 1, bottom = 2, right = 3. + * @param getDefault If true the default insets will get retrieved if lc has none set. + * @return The insets for the side. Never null. + */ + static UnitValue getInsets(LC lc, int side, boolean getDefault) + { + UnitValue[] i = lc.getInsets(); + return (i != null && i[side] != null) ? i[side] : (getDefault ? PlatformDefaults.getPanelInsets(side) : UnitValue.ZERO); + } + +// /** Writes the object and CLOSES the stream. Uses the persistence delegate registered in this class. +// * @param os The stream to write to. Will be closed. +// * @param o The object to be serialized. +// * @param listener The listener to receive the exceptions if there are any. If null not used. +// */ +// static void writeXMLObject(OutputStream os, Object o, ExceptionListener listener) +// { +// ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); +// Thread.currentThread().setContextClassLoader(LayoutUtil.class.getClassLoader()); +// +// XMLEncoder encoder = new XMLEncoder(os); +// +// if (listener != null) +// encoder.setExceptionListener(listener); +// +// encoder.writeObject(o); +// encoder.close(); // Must be closed to write. +// +// Thread.currentThread().setContextClassLoader(oldClassLoader); +// } +// +// private static ByteArrayOutputStream writeOutputStream = null; +// /** Writes an object to XML. +// * @param out The object out to write to. Will not be closed. +// * @param o The object to write. +// */ +// public static synchronized void writeAsXML(ObjectOutput out, Object o) throws IOException +// { +// if (writeOutputStream == null) +// writeOutputStream = new ByteArrayOutputStream(16384); +// +// writeOutputStream.reset(); +// +// writeXMLObject(writeOutputStream, o, new ExceptionListener() { +// @Override +// public void exceptionThrown(Exception e) { +// e.printStackTrace(); +// }}); +// +// byte[] buf = writeOutputStream.toByteArray(); +// +// out.writeInt(buf.length); +// out.write(buf); +// } +// +// private static byte[] readBuf = null; +// /** Reads an object from in using the +// * @param in The object input to read from. +// * @return The object. Never null. +// * @throws IOException If there was a problem saving as XML +// */ +// public static synchronized Object readAsXML(ObjectInput in) throws IOException +// { +// if (readBuf == null) +// readBuf = new byte[16384]; +// +// Thread cThread = Thread.currentThread(); +// ClassLoader oldCL = null; +// +// try { +// oldCL = cThread.getContextClassLoader(); +// cThread.setContextClassLoader(LayoutUtil.class.getClassLoader()); +// } catch(SecurityException ignored) { +// } +// +// Object o = null; +// try { +// int length = in.readInt(); +// if (length > readBuf.length) +// readBuf = new byte[length]; +// +// in.readFully(readBuf, 0, length); +// +// o = new XMLDecoder(new ByteArrayInputStream(readBuf, 0, length)).readObject(); +// +// } catch(EOFException ignored) { +// } +// +// if (oldCL != null) +// cThread.setContextClassLoader(oldCL); +// +// return o; +// } + + private static final IdentityHashMap SER_MAP = new IdentityHashMap(2); + + /** Sets the serialized object and associates it with caller. + * @param caller The object created o + * @param o The just serialized object. + */ + public static void setSerializedObject(Object caller, Object o) + { + synchronized(SER_MAP) { + SER_MAP.put(caller, o); + } + } + + /** Returns the serialized object that are associated with caller. It also removes it from the list. + * @param caller The original creator of the object. + * @return The object. + */ + public static Object getSerializedObject(Object caller) + { + synchronized(SER_MAP) { + return SER_MAP.remove(caller); + } + } +} diff --git a/src/net/miginfocom/layout/LinkHandler.java b/src/net/miginfocom/layout/LinkHandler.java new file mode 100644 index 0000000..2aab6f1 --- /dev/null +++ b/src/net/miginfocom/layout/LinkHandler.java @@ -0,0 +1,182 @@ +package net.miginfocom.layout; + +import java.util.HashMap; +import java.util.WeakHashMap; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** + */ +public final class LinkHandler +{ + public static final int X = 0; + public static final int Y = 1; + public static final int WIDTH = 2; + public static final int HEIGHT = 3; + public static final int X2 = 4; + public static final int Y2 = 5; + + // indices for values of LAYOUTS + private static final int VALUES = 0; + private static final int VALUES_TEMP = 1; + + private static final WeakHashMap[]> LAYOUTS = new WeakHashMap[]>(); + + private LinkHandler() + { + } + + public synchronized static Integer getValue(Object layout, String key, int type) + { + Integer ret = null; + + HashMap[] layoutValues = LAYOUTS.get(layout); + if (layoutValues != null) { + int[] rect = layoutValues[VALUES_TEMP].get(key); + if (rect != null && rect[type] != LayoutUtil.NOT_SET) { + ret = rect[type]; + } else { + rect = layoutValues[VALUES].get(key); + ret = (rect != null && rect[type] != LayoutUtil.NOT_SET) ? rect[type] : null; + } + } + return ret; + } + + /** Sets a key that can be linked to from any component. + * @param layout The MigLayout instance + * @param key The key to link to. This is the same as the ID in a component constraint. + * @param x x + * @param y y + * @param width Width + * @param height Height + * @return If the value was changed + */ + public synchronized static boolean setBounds(Object layout, String key, int x, int y, int width, int height) + { + return setBounds(layout, key, x, y, width, height, false, false); + } + + synchronized static boolean setBounds(Object layout, String key, int x, int y, int width, int height, boolean temporary, boolean incCur) + { + HashMap[] layoutValues = LAYOUTS.get(layout); + if (layoutValues != null) { + HashMap map = layoutValues[temporary ? VALUES_TEMP : VALUES]; + int[] old = map.get(key); + + if (old == null || old[X] != x || old[Y] != y || old[WIDTH] != width || old[HEIGHT] != height) { + if (old == null || incCur == false) { + map.put(key, new int[] {x, y, width, height, x + width, y + height}); + return true; + } else { + boolean changed = false; + + if (x != LayoutUtil.NOT_SET) { + if (old[X] == LayoutUtil.NOT_SET || x < old[X]) { + old[X] = x; + old[WIDTH] = old[X2] - x; + changed = true; + } + + if (width != LayoutUtil.NOT_SET) { + int x2 = x + width; + if (old[X2] == LayoutUtil.NOT_SET || x2 > old[X2]) { + old[X2] = x2; + old[WIDTH] = x2 - old[X]; + changed = true; + } + } + } + + if (y != LayoutUtil.NOT_SET) { + if (old[Y] == LayoutUtil.NOT_SET || y < old[Y]) { + old[Y] = y; + old[HEIGHT] = old[Y2] - y; + changed = true; + } + + if (height != LayoutUtil.NOT_SET) { + int y2 = y + height; + if (old[Y2] == LayoutUtil.NOT_SET || y2 > old[Y2]) { + old[Y2] = y2; + old[HEIGHT] = y2 - old[Y]; + changed = true; + } + } + } + return changed; + } + } + return false; + } + + int[] bounds = new int[] {x, y, width, height, x + width, y + height}; + + HashMap values_temp = new HashMap(4); + if (temporary) + values_temp.put(key, bounds); + + HashMap values = new HashMap(4); + if (temporary == false) + values.put(key, bounds); + + LAYOUTS.put(layout, new HashMap[] {values, values_temp}); + + return true; + } + + /** This method clear any weak references right away instead of waiting for the GC. This might be advantageous + * if lots of layout are created and disposed of quickly to keep memory consumption down. + * @since 3.7.4 + */ + public synchronized static void clearWeakReferencesNow() + { + LAYOUTS.clear(); + } + + public synchronized static boolean clearBounds(Object layout, String key) + { + HashMap[] layoutValues = LAYOUTS.get(layout); + if (layoutValues != null) + return layoutValues[VALUES].remove(key) != null; + return false; + } + + synchronized static void clearTemporaryBounds(Object layout) + { + HashMap[] layoutValues = LAYOUTS.get(layout); + if (layoutValues != null) + layoutValues[VALUES_TEMP].clear(); + } +} diff --git a/src/net/miginfocom/layout/PlatformDefaults.java b/src/net/miginfocom/layout/PlatformDefaults.java new file mode 100644 index 0000000..e1b72c4 --- /dev/null +++ b/src/net/miginfocom/layout/PlatformDefaults.java @@ -0,0 +1,855 @@ +package net.miginfocom.layout; + +import java.util.HashMap; + +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + * @author Xxxx Xxxx, Xxxx - Gnome support + * Date: 2008-jan-16 + */ + +/** Currently handles Windows, Mac OS X, and GNOME spacing. + */ +public final class PlatformDefaults +{ + /** Property to use in LAF settings and as JComponent client property + * to specify the visual padding. + *

+ */ + public static String VISUAL_PADDING_PROPERTY = "visualPadding"; + + private static int DEF_H_UNIT = UnitValue.LPX; + private static int DEF_V_UNIT = UnitValue.LPY; + + private static InCellGapProvider GAP_PROVIDER = null; + + private static volatile int MOD_COUNT = 0; + +// private static final UnitValue LPX1 = new UnitValue(1, UnitValue.LPX, null); +// private static final UnitValue LPX4 = new UnitValue(4, UnitValue.LPX, null); + private static final UnitValue LPX6 = new UnitValue(6, UnitValue.LPX, null); + private static final UnitValue LPX7 = new UnitValue(7, UnitValue.LPX, null); +// private static final UnitValue LPX8 = new UnitValue(8, UnitValue.LPX, null); +// private static final UnitValue LPX9 = new UnitValue(9, UnitValue.LPX, null); +// private static final UnitValue LPX10 = new UnitValue(10, UnitValue.LPX, null); + private static final UnitValue LPX11 = new UnitValue(11, UnitValue.LPX, null); + private static final UnitValue LPX12 = new UnitValue(12, UnitValue.LPX, null); +// private static final UnitValue LPX14 = new UnitValue(14, UnitValue.LPX, null); + private static final UnitValue LPX16 = new UnitValue(16, UnitValue.LPX, null); + private static final UnitValue LPX18 = new UnitValue(18, UnitValue.LPX, null); + private static final UnitValue LPX20 = new UnitValue(20, UnitValue.LPX, null); + +// private static final UnitValue LPY1 = new UnitValue(1, UnitValue.LPY, null); +// private static final UnitValue LPY4 = new UnitValue(4, UnitValue.LPY, null); + private static final UnitValue LPY6 = new UnitValue(6, UnitValue.LPY, null); + private static final UnitValue LPY7 = new UnitValue(7, UnitValue.LPY, null); +// private static final UnitValue LPY8 = new UnitValue(8, UnitValue.LPY, null); +// private static final UnitValue LPY9 = new UnitValue(9, UnitValue.LPY, null); +// private static final UnitValue LPY10 = new UnitValue(10, UnitValue.LPY, null); + private static final UnitValue LPY11 = new UnitValue(11, UnitValue.LPY, null); + private static final UnitValue LPY12 = new UnitValue(12, UnitValue.LPY, null); +// private static final UnitValue LPY14 = new UnitValue(14, UnitValue.LPY, null); + private static final UnitValue LPY16 = new UnitValue(16, UnitValue.LPY, null); + private static final UnitValue LPY18 = new UnitValue(18, UnitValue.LPY, null); + private static final UnitValue LPY20 = new UnitValue(20, UnitValue.LPY, null); + + public static final int WINDOWS_XP = 0; + public static final int MAC_OSX = 1; + public static final int GNOME = 2; +// private static final int KDE = 3; + + private static int CUR_PLAF = WINDOWS_XP; + + // Used for holding values. + private final static UnitValue[] PANEL_INS = new UnitValue[4]; + private final static UnitValue[] DIALOG_INS = new UnitValue[4]; + + private static String BUTTON_FORMAT = null; + + private static final HashMap HOR_DEFS = new HashMap(32); + private static final HashMap VER_DEFS = new HashMap(32); + private static BoundSize DEF_VGAP = null, DEF_HGAP = null; + static BoundSize RELATED_X = null, RELATED_Y = null, UNRELATED_X = null, UNRELATED_Y = null; + private static UnitValue BUTT_WIDTH = null; + private static UnitValue BUTT_PADDING = null; + + private static Float horScale = null, verScale = null; + + /** I value indicating that the size of the font for the container of the component + * will be used as a base for calculating the logical pixel size. This is much as how + * Windows calculated DLU (dialog units). + * @see net.miginfocom.layout.UnitValue#LPX + * @see net.miginfocom.layout.UnitValue#LPY + * @see #setLogicalPixelBase(int) + */ + public static final int BASE_FONT_SIZE = 100; + + /** I value indicating that the screen DPI will be used as a base for calculating the + * logical pixel size. + *

+ * This is the default value. + * @see net.miginfocom.layout.UnitValue#LPX + * @see net.miginfocom.layout.UnitValue#LPY + * @see #setLogicalPixelBase(int) + * @see #setVerticalScaleFactor(Float) + * @see #setHorizontalScaleFactor(Float) + */ + public static final int BASE_SCALE_FACTOR = 101; + + /** I value indicating that the size of a logical pixel should always be a real pixel + * and thus no compensation will be made. + * @see net.miginfocom.layout.UnitValue#LPX + * @see net.miginfocom.layout.UnitValue#LPY + * @see #setLogicalPixelBase(int) + */ + public static final int BASE_REAL_PIXEL = 102; + + private static int LP_BASE = BASE_SCALE_FACTOR; + + private static Integer BASE_DPI_FORCED = null; + private static int BASE_DPI = 96; + + private static boolean dra = true; + + private static final HashMap VISUAL_BOUNDS = new HashMap(64); + + static { + setPlatform(getCurrentPlatform()); + MOD_COUNT = 0; + } + + /** Returns the platform that the JRE is running on currently. + * @return The platform that the JRE is running on currently. E.g. {@link #MAC_OSX}, {@link #WINDOWS_XP}, or {@link #GNOME}. + */ + public static int getCurrentPlatform() + { + final String os = System.getProperty("os.name"); + if (os.startsWith("Mac OS")) { + return MAC_OSX; + } else if (os.startsWith("Linux")) { + return GNOME; + } else { + return WINDOWS_XP; + } + } + + private PlatformDefaults() + { + } + + /** Set the defaults to the default for the platform + * @param plaf The platform. PlatformDefaults.WINDOWS_XP, + * PlatformDefaults.MAC_OSX, or + * PlatformDefaults.GNOME. + */ + public static void setPlatform(int plaf) + { + switch (plaf) { + case WINDOWS_XP: + setDefaultVisualPadding("TabbedPane." + VISUAL_PADDING_PROPERTY, new int[]{1, 0, 1, 2}); + setRelatedGap(LPX7, LPY7); + setUnrelatedGap(LPX11, LPY11); + setParagraphGap(LPX20, LPY20); + setIndentGap(LPX11, LPY11); + setGridCellGap(LPX7, LPY7); + + setMinimumButtonWidth(new UnitValue(75, UnitValue.LPX, null)); + setButtonOrder("L_E+U+YNBXOCAH_I_R"); + setDialogInsets(LPY11, LPX11, LPY11, LPX11); + setPanelInsets(LPY7, LPX7, LPY7, LPX7); + break; + + case MAC_OSX: + + setDefaultVisualPadding("Button." + VISUAL_PADDING_PROPERTY, new int[]{3, 6, 5, 6}); + setDefaultVisualPadding("Button.icon." + VISUAL_PADDING_PROPERTY, new int[]{3, 2, 3, 2}); + setDefaultVisualPadding("Button.square." + VISUAL_PADDING_PROPERTY, new int[]{4, 4, 4, 4}); + setDefaultVisualPadding("Button.square.icon." + VISUAL_PADDING_PROPERTY, new int[]{4, 4, 4, 4}); + setDefaultVisualPadding("Button.gradient." + VISUAL_PADDING_PROPERTY, new int[]{5, 4, 5, 4}); + setDefaultVisualPadding("Button.gradient.icon." + VISUAL_PADDING_PROPERTY, new int[]{5, 4, 5, 4}); + setDefaultVisualPadding("Button.bevel." + VISUAL_PADDING_PROPERTY, new int[]{2, 2, 3, 2}); + setDefaultVisualPadding("Button.bevel.icon." + VISUAL_PADDING_PROPERTY, new int[]{2, 2, 3, 2}); + setDefaultVisualPadding("Button.textured." + VISUAL_PADDING_PROPERTY, new int[]{3, 2, 3, 2}); + setDefaultVisualPadding("Button.textured.icon." + VISUAL_PADDING_PROPERTY, new int[]{3, 2, 3, 2}); + setDefaultVisualPadding("Button.roundRect." + VISUAL_PADDING_PROPERTY, new int[]{5, 4, 5, 4}); + setDefaultVisualPadding("Button.roundRect.icon." + VISUAL_PADDING_PROPERTY, new int[]{5, 4, 5, 4}); + setDefaultVisualPadding("Button.recessed." + VISUAL_PADDING_PROPERTY, new int[]{5, 4, 5, 4}); + setDefaultVisualPadding("Button.recessed.icon." + VISUAL_PADDING_PROPERTY, new int[]{5, 4, 5, 4}); + setDefaultVisualPadding("Button.help." + VISUAL_PADDING_PROPERTY, new int[]{4, 3, 3, 4}); + setDefaultVisualPadding("Button.help.icon." + VISUAL_PADDING_PROPERTY, new int[]{4, 3, 3, 4}); + + setDefaultVisualPadding("ComboBox." + VISUAL_PADDING_PROPERTY, new int[]{2, 4, 4, 5}); + setDefaultVisualPadding("ComboBox.isPopDown." + VISUAL_PADDING_PROPERTY, new int[]{2, 5, 4, 5}); + setDefaultVisualPadding("ComboBox.isSquare." + VISUAL_PADDING_PROPERTY, new int[]{1, 6, 5, 7}); + + setDefaultVisualPadding("ComboBox.editable." + VISUAL_PADDING_PROPERTY, new int[]{3, 3, 3, 2}); + setDefaultVisualPadding("ComboBox.editable.isSquare." + VISUAL_PADDING_PROPERTY, new int[]{3, 3, 3, 1}); + + setDefaultVisualPadding("TextField." + VISUAL_PADDING_PROPERTY, new int[]{3, 3, 3, 3}); + setDefaultVisualPadding("TabbedPane." + VISUAL_PADDING_PROPERTY, new int[]{4, 8, 11, 8}); + + setDefaultVisualPadding("Spinner." + VISUAL_PADDING_PROPERTY, new int[]{3, 3, 3, 1}); + + setDefaultVisualPadding("RadioButton." + VISUAL_PADDING_PROPERTY, new int[]{4, 6, 3, 5}); + setDefaultVisualPadding("RadioButton.small." + VISUAL_PADDING_PROPERTY, new int[]{4, 6, 3, 5}); + setDefaultVisualPadding("RadioButton.mini." + VISUAL_PADDING_PROPERTY, new int[]{5, 7, 4, 5}); + setDefaultVisualPadding("CheckBox." + VISUAL_PADDING_PROPERTY, new int[]{5, 7, 4, 5}); + setDefaultVisualPadding("CheckBox.small." + VISUAL_PADDING_PROPERTY, new int[]{5, 7, 4, 5}); + setDefaultVisualPadding("CheckBox.mini." + VISUAL_PADDING_PROPERTY, new int[]{6, 7, 3, 5}); + + setRelatedGap(LPX7, LPY7); + setUnrelatedGap(LPX11, LPY11); + setParagraphGap(LPX20, LPY20); + setIndentGap(LPX11, LPY11); + setGridCellGap(LPX7, LPY7); + + setMinimumButtonWidth(new UnitValue(70, UnitValue.LPX, null)); + setMinimumButtonPadding(new UnitValue(8, UnitValue.LPX, null)); + setButtonOrder("L_HE+U+NYBXCOA_I_R"); + setDialogInsets(LPY20, LPX20, LPY20, LPX20); + setPanelInsets(LPY16, LPX16, LPY16, LPX16); + break; + + case GNOME: + setRelatedGap(LPX6, LPY6); // GNOME HIG 8.2.3 + setUnrelatedGap(LPX12, LPY12); // GNOME HIG 8.2.3 + setParagraphGap(LPX18, LPY18); // GNOME HIG 8.2.3 + setIndentGap(LPX12, LPY12); // GNOME HIG 8.2.3 + setGridCellGap(LPX6, LPY6); // GNOME HIG 8.2.3 + + // GtkButtonBox, child-min-width property default value + setMinimumButtonWidth(new UnitValue(85, UnitValue.LPX, null)); + setButtonOrder("L_HE+UNYACBXO_I_R"); // GNOME HIG 3.4.2, 3.7.1 + setDialogInsets(LPY12, LPX12, LPY12, LPX12); // GNOME HIG 3.4.3 + setPanelInsets(LPY6, LPX6, LPY6, LPX6); // ??? + break; + default: + throw new IllegalArgumentException("Unknown platform: " + plaf); + } + CUR_PLAF = plaf; + BASE_DPI = BASE_DPI_FORCED != null ? BASE_DPI_FORCED : getPlatformDPI(plaf); + } + + /** Sets the visual bounds for a component type. + * @param key The component type. E.g. "TabbedPane.visualPadding" or "ComboBox.editable.isSquare.visualPadding". See source code for list. + * @param insets Top, left, bottom, right. Always length 4 or null. + * @see net.miginfocom.layout.ComponentWrapper#getVisualPadding() + */ + public static void setDefaultVisualPadding(String key, int[] insets) + { + VISUAL_BOUNDS.put(key, insets); + } + + /** Returns the visual bounds for a component type. + * @param key The component type. E.g. "TabbedPane.visualPadding" or "ComboBox.editable.isSquare.visualPadding". See source code for list. + * @return insets Top, left, bottom, right. Always length 4 or null. Live object, MUST NOT BE CHANGED!. + * @see net.miginfocom.layout.ComponentWrapper#getVisualPadding() + */ + public static int[] getDefaultVisualPadding(String key) + { + return VISUAL_BOUNDS.get(key); + } + + public static int getPlatformDPI(int plaf) + { + switch (plaf) { + case WINDOWS_XP: + case GNOME: + return 96; + case MAC_OSX: + try { + return java.awt.Toolkit.getDefaultToolkit().getScreenResolution(); + } catch (Throwable t) { + return 72; + } + default: + throw new IllegalArgumentException("Unknown platform: " + plaf); + } + } + + /** Returns the current platform + * @return PlatformDefaults.WINDOWS or PlatformDefaults.MAC_OSX + */ + public static int getPlatform() + { + return CUR_PLAF; + } + + public static int getDefaultDPI() + { + return BASE_DPI; + } + + /** Sets the default platform DPI. Normally this is set in the {@link #setPlatform(int)} for the different platforms + * but it can be tweaked here. For instance SWT on Mac does this. + *

+ * Note that this is not the actual current DPI, but the base DPI for the toolkit. + * @param dpi The base DPI. If null the default DPI is reset to the platform base DPI. + */ + public static void setDefaultDPI(Integer dpi) + { + BASE_DPI = dpi != null ? dpi : getPlatformDPI(CUR_PLAF); + BASE_DPI_FORCED = dpi; + } + + /** The forced scale factor that all screen relative units (e.g. millimeters, inches and logical pixels) will be multiplied + * with. If null this will default to a scale that will scale the current screen to the default screen resolution + * (72 DPI for Mac and 92 DPI for Windows). + * @return The forced scale or null for default scaling. + * @see #getHorizontalScaleFactor() + * @see ComponentWrapper#getHorizontalScreenDPI() + */ + public static Float getHorizontalScaleFactor() + { + return horScale; + } + + /** The forced scale factor that all screen relative units (e.g. millimeters, inches and logical pixels) will be multiplied + * with. If null this will default to a scale that will scale the current screen to the default screen resolution + * (72 DPI for Mac and 92 DPI for Windows). + * @param f The forced scale or null for default scaling. + * @see #getHorizontalScaleFactor() + * @see ComponentWrapper#getHorizontalScreenDPI() + */ + public static void setHorizontalScaleFactor(Float f) + { + if (!LayoutUtil.equals(horScale, f)) { + horScale = f; + MOD_COUNT++; + } + } + + /** The forced scale factor that all screen relative units (e.g. millimeters, inches and logical pixels) will be multiplied + * with. If null this will default to a scale that will scale the current screen to the default screen resolution + * (72 DPI for Mac and 92 DPI for Windows). + * @return The forced scale or null for default scaling. + * @see #getHorizontalScaleFactor() + * @see ComponentWrapper#getVerticalScreenDPI() + */ + public static Float getVerticalScaleFactor() + { + return verScale; + } + + /** The forced scale factor that all screen relative units (e.g. millimeters, inches and logical pixels) will be multiplied + * with. If null this will default to a scale that will scale the current screen to the default screen resolution + * (72 DPI for Mac and 92 DPI for Windows). + * @param f The forced scale or null for default scaling. + * @see #getHorizontalScaleFactor() + * @see ComponentWrapper#getVerticalScreenDPI() + */ + public static void setVerticalScaleFactor(Float f) + { + if (!LayoutUtil.equals(verScale, f)) { + verScale = f; + MOD_COUNT++; + } + } + + /** What base value should be used to calculate logical pixel sizes. + * @return The current base. Default is {@link #BASE_SCALE_FACTOR} + * @see #BASE_FONT_SIZE + * @see #BASE_SCALE_FACTOR + * @see #BASE_REAL_PIXEL +*/ + public static int getLogicalPixelBase() + { + return LP_BASE; + } + + /** What base value should be used to calculate logical pixel sizes. + * @param base The new base. Default is {@link #BASE_SCALE_FACTOR} + * @see #BASE_FONT_SIZE + * @see #BASE_SCALE_FACTOR + * @see #BASE_REAL_PIXEL + */ + public static void setLogicalPixelBase(int base) + { + if (LP_BASE != base) { + if (base < BASE_FONT_SIZE || base > BASE_REAL_PIXEL) + throw new IllegalArgumentException("Unrecognized base: " + base); + + LP_BASE = base; + MOD_COUNT++; + } + } + + /** Sets gap value for components that are "related". + * @param x The value that will be transformed to pixels. If null the current value will not change. + * @param y The value that will be transformed to pixels. If null the current value will not change. + */ + public static void setRelatedGap(UnitValue x, UnitValue y) + { + setUnitValue(new String[] {"r", "rel", "related"}, x, y); + + RELATED_X = new BoundSize(x, x, null, "rel:rel"); + RELATED_Y = new BoundSize(y, y, null, "rel:rel"); + } + + /** Sets gap value for components that are "unrelated". + * @param x The value that will be transformed to pixels. If null the current value will not change. + * @param y The value that will be transformed to pixels. If null the current value will not change. + */ + public static void setUnrelatedGap(UnitValue x, UnitValue y) + { + setUnitValue(new String[] {"u", "unrel", "unrelated"}, x, y); + + UNRELATED_X = new BoundSize(x, x, null, "unrel:unrel"); + UNRELATED_Y = new BoundSize(y, y, null, "unrel:unrel"); + } + + /** Sets paragraph gap value for components. + * @param x The value that will be transformed to pixels. If null the current value will not change. + * @param y The value that will be transformed to pixels. If null the current value will not change. + */ + public static void setParagraphGap(UnitValue x, UnitValue y) + { + setUnitValue(new String[] {"p", "para", "paragraph"}, x, y); + } + + /** Sets gap value for components that are "intended". + * @param x The value that will be transformed to pixels. If null the current value will not change. + * @param y The value that will be transformed to pixels. If null the current value will not change. + */ + public static void setIndentGap(UnitValue x, UnitValue y) + { + setUnitValue(new String[] {"i", "ind", "indent"}, x, y); + } + + /** Sets gap between two cells in the grid. Note that this is not a gap between component IN a cell, that has to be set + * on the component constraints. The value will be the min and preferred size of the gap. + * @param x The value that will be transformed to pixels. If null the current value will not change. + * @param y The value that will be transformed to pixels. If null the current value will not change. + */ + public static void setGridCellGap(UnitValue x, UnitValue y) + { + if (x != null) + DEF_HGAP = new BoundSize(x, x, null, null); + + if (y != null) + DEF_VGAP = new BoundSize(y, y, null, null); + + MOD_COUNT++; + } + + /** Sets the recommended minimum button width. + * @param width The recommended minimum button width. + */ + public static void setMinimumButtonWidth(UnitValue width) + { + BUTT_WIDTH = width; + MOD_COUNT++; + } + + /** Returns the recommended minimum button width depending on the current set platform. + * @return The recommended minimum button width depending on the current set platform. + */ + public static UnitValue getMinimumButtonWidth() + { + return BUTT_WIDTH; + } + + public static void setMinimumButtonPadding(UnitValue padding) + { + BUTT_PADDING = padding; + MOD_COUNT++; + } + + public static UnitValue getMinimumButtonPadding() + { + return BUTT_PADDING; + } + + public static float getMinimumButtonWidthIncludingPadding(float refValue, ContainerWrapper parent, ComponentWrapper comp) + { + final int buttonMinWidth = getMinimumButtonWidth().getPixels(refValue, parent, comp); + if (comp != null && getMinimumButtonPadding() != null) { + return Math.max(comp.getMinimumWidth(comp.getWidth()) + getMinimumButtonPadding().getPixels(refValue, parent, comp) * 2, buttonMinWidth); + } else { + return buttonMinWidth; + } + } + + /** Returns the unit value associated with the unit. (E.i. "related" or "indent"). Must be lower case. + * @param unit The unit string. + * @return The unit value associated with the unit. null for unrecognized units. + */ + public static UnitValue getUnitValueX(String unit) + { + return HOR_DEFS.get(unit); + } + + /** Returns the unit value associated with the unit. (E.i. "related" or "indent"). Must be lower case. + * @param unit The unit string. + * @return The unit value associated with the unit. null for unrecognized units. + */ + public static UnitValue getUnitValueY(String unit) + { + return VER_DEFS.get(unit); + } + + /** Sets the unit value associated with a unit string. This may be used to store values for new unit strings + * or modify old. Note that if a built in unit (such as "related") is modified all versions of it must be + * set (I.e. "r", "rel" and "related"). The build in values will be reset to the default ones if the platform + * is re-set. + * @param unitStrings The unit strings. E.g. "mu", "myunit". Will be converted to lower case and trimmed. Not null. + * @param x The value for the horizontal dimension. If null the value is not changed. + * @param y The value for the vertical dimension. Might be same object as for x. If null the value is not changed. + */ + public static void setUnitValue(String[] unitStrings, UnitValue x, UnitValue y) + { + for (String unitString : unitStrings) { + String s = unitString.toLowerCase().trim(); + if (x != null) + HOR_DEFS.put(s, x); + if (y != null) + VER_DEFS.put(s, y); + } + MOD_COUNT++; + } + + /** Understands ("r", "rel", "related") OR ("u", "unrel", "unrelated") OR ("i", "ind", "indent") OR ("p", "para", "paragraph"). + */ + static int convertToPixels(float value, String unit, boolean isHor, float ref, ContainerWrapper parent, ComponentWrapper comp) + { + UnitValue uv = (isHor ? HOR_DEFS : VER_DEFS).get(unit); + return uv != null ? Math.round(value * uv.getPixels(ref, parent, comp)) : UnitConverter.UNABLE; + } + + /** Returns the order for the typical buttons in a standard button bar. It is one letter per button type. + * @return The button order. + * @see #setButtonOrder(String) + */ + public static String getButtonOrder() + { + return BUTTON_FORMAT; + } + + /** Sets the order for the typical buttons in a standard button bar. It is one letter per button type. + *

+ * Letter in upper case will get the minimum button width that the {@link #getMinimumButtonWidth()} specifies + * and letters in lower case will get the width the current look&feel specifies. + *

+ * Gaps will never be added to before the first component or after the last component. However, '+' (push) will be + * applied before and after as well, but with a minimum size of 0 if first/last so there will not be a gap + * before or after. + *

+ * If gaps are explicitly set on buttons they will never be reduced, but they may be increased. + *

+ * These are the characters that can be used: + *

    + *
  • 'L' - Buttons with this style tag will statically end up on the left end of the bar. + *
  • 'R' - Buttons with this style tag will statically end up on the right end of the bar. + *
  • 'H' - A tag for the "help" button that normally is supposed to be on the right. + *
  • 'E' - A tag for the "help2" button that normally is supposed to be on the left. + *
  • 'Y' - A tag for the "yes" button. + *
  • 'N' - A tag for the "no" button. + *
  • 'X' - A tag for the "next >" or "forward >" button. + *
  • 'B' - A tag for the "< back" or "< previous" button. + *
  • 'I' - A tag for the "finish" button. + *
  • 'A' - A tag for the "apply" button. + *
  • 'C' - A tag for the "cancel" or "close" button. + *
  • 'O' - A tag for the "ok" or "done" button. + *
  • 'U' - All Uncategorized, Other, or "Unknown" buttons. Tag will be "other". + *
  • '+' - A glue push gap that will take as much space as it can and at least an "unrelated" gap. (Platform dependent) + *
  • '_' - (underscore) An "unrelated" gap. (Platform dependent) + *
+ *

+ * Even though the style tags are normally applied to buttons this works with all components. + *

+ * The normal style for MAC OS X is "L_HE+U+NYBXCOA_I_R", + * for Windows is "L_E+U+YNBXOCAH_I_R", and for GNOME is + * "L_HE+UNYACBXO_I_R". + * + * @param order The new button order for the current platform. + */ + public static void setButtonOrder(String order) + { + BUTTON_FORMAT = order; + MOD_COUNT++; + } + + /** Returns the tag (used in the {@link CC}) for a char. The char is same as used in {@link #getButtonOrder()}. + * @param c The char. Must be lower case! + * @return The tag that corresponds to the char or null if the char is unrecognized. + */ + static String getTagForChar(char c) + { + switch (c) { + case 'o': + return "ok"; + case 'c': + return "cancel"; + case 'h': + return "help"; + case 'e': + return "help2"; + case 'y': + return "yes"; + case 'n': + return "no"; + case 'a': + return "apply"; + case 'x': + return "next"; // a.k.a forward + case 'b': + return "back"; // a.k.a. previous + case 'i': + return "finish"; + case 'l': + return "left"; + case 'r': + return "right"; + case 'u': + return "other"; + default: + return null; + } + } + + /** Returns the platform recommended inter-cell gap in the horizontal (x) dimension.. + * @return The platform recommended inter-cell gap in the horizontal (x) dimension.. + */ + public static BoundSize getGridGapX() + { + return DEF_HGAP; + } + + /** Returns the platform recommended inter-cell gap in the vertical (x) dimension.. + * @return The platform recommended inter-cell gap in the vertical (x) dimension.. + */ + public static BoundSize getGridGapY() + { + return DEF_VGAP; + } + + /** Returns the default dialog insets depending of the current platform. + * @param side top == 0, left == 1, bottom = 2, right = 3. + * @return The insets. Never null. + */ + public static UnitValue getDialogInsets(int side) + { + return DIALOG_INS[side]; + } + + /** Sets the default insets for a dialog. Values that are null will not be changed. + * @param top The top inset. May be null. + * @param left The left inset. May be null. + * @param bottom The bottom inset. May be null. + * @param right The right inset. May be null. + */ + public static void setDialogInsets(UnitValue top, UnitValue left, UnitValue bottom, UnitValue right) + { + if (top != null) + DIALOG_INS[0] = top; + + if (left != null) + DIALOG_INS[1] = left; + + if (bottom != null) + DIALOG_INS[2] = bottom; + + if (right != null) + DIALOG_INS[3] = right; + + MOD_COUNT++; + } + + /** Returns the default panel insets depending of the current platform. + * @param side top == 0, left == 1, bottom = 2, right = 3. + * @return The insets. Never null. + */ + public static UnitValue getPanelInsets(int side) + { + return PANEL_INS[side]; + } + + /** Sets the default insets for a dialog. Values that are null will not be changed. + * @param top The top inset. May be null. + * @param left The left inset. May be null. + * @param bottom The bottom inset. May be null. + * @param right The right inset. May be null. + */ + public static void setPanelInsets(UnitValue top, UnitValue left, UnitValue bottom, UnitValue right) + { + if (top != null) + PANEL_INS[0] = top; + + if (left != null) + PANEL_INS[1] = left; + + if (bottom != null) + PANEL_INS[2] = bottom; + + if (right != null) + PANEL_INS[3] = right; + + MOD_COUNT++; + } + + /** Returns the percentage used for alignment for labels (0 is left, 50 is center and 100 is right). + * @return The percentage used for alignment for labels + */ + public static float getLabelAlignPercentage() + { + return CUR_PLAF == MAC_OSX ? 1f : 0f; + } + + /** Returns the default gap between two components that are in the same cell. + * @param comp The component that the gap is for. Never null. + * @param adjacentComp The adjacent component if any. May be null. + * @param adjacentSide What side the adjacentComp is on. {@link javax.swing.SwingUtilities#TOP} (1) or + * {@link javax.swing.SwingUtilities#LEFT} (2) or {@link javax.swing.SwingUtilities#BOTTOM} (3) or {@link javax.swing.SwingUtilities#RIGHT} (4). + * @param tag The tag string that the component might be tagged with in the component constraints. May be null. + * @param isLTR If it is left-to-right. + * @return The default gap between two components or null if there should be no gap. + */ + static BoundSize getDefaultComponentGap(ComponentWrapper comp, ComponentWrapper adjacentComp, int adjacentSide, String tag, boolean isLTR) + { + if (GAP_PROVIDER != null) + return GAP_PROVIDER.getDefaultGap(comp, adjacentComp, adjacentSide, tag, isLTR); + + if (adjacentComp == null) + return null; + +// if (adjacentComp == null || adjacentSide == SwingConstants.LEFT || adjacentSide == SwingConstants.TOP) +// return null; + +// SwingConstants.RIGHT == 4, SwingConstants.LEFT == 2 + return (adjacentSide == 2 || adjacentSide == 4) ? RELATED_X : RELATED_Y; + } + + /** Returns the current gap provider or null if none is set and "related" should always be used. + * @return The current gap provider or null if none is set and "related" should always be used. + */ + public static InCellGapProvider getGapProvider() + { + return GAP_PROVIDER; + } + + /** Sets the current gap provider or null if none is set and "related" should always be used. + * @param provider The current gap provider or null if none is set and "related" should always be used. + */ + public static void setGapProvider(InCellGapProvider provider) + { + GAP_PROVIDER = provider; + } + + /** Returns how many times the defaults has been changed. This can be used as a light weight check to + * see if layout caches needs to be refreshed. + * @return How many times the defaults has been changed. + */ + public static int getModCount() + { + return MOD_COUNT; + } + + /** Tells all layout manager instances to revalidate and recalculated everything. + */ + public void invalidate() + { + MOD_COUNT++; + } + + /** Returns the current default unit. The default unit is the unit used if no unit is set. E.g. "width 10". + * @return The current default unit. + * @see UnitValue#PIXEL + * @see UnitValue#LPX + */ + public static int getDefaultHorizontalUnit() + { + return DEF_H_UNIT; + } + + /** Sets the default unit. The default unit is the unit used if no unit is set. E.g. "width 10". + * @param unit The new default unit. + * @see UnitValue#PIXEL + * @see UnitValue#LPX + */ + public static void setDefaultHorizontalUnit(int unit) + { + if (unit < UnitValue.PIXEL || unit > UnitValue.LABEL_ALIGN) + throw new IllegalArgumentException("Illegal Unit: " + unit); + + if (DEF_H_UNIT != unit) { + DEF_H_UNIT = unit; + MOD_COUNT++; + } + } + + /** Returns the current default unit. The default unit is the unit used if no unit is set. E.g. "width 10". + * @return The current default unit. + * @see UnitValue#PIXEL + * @see UnitValue#LPY + */ + public static int getDefaultVerticalUnit() + { + return DEF_V_UNIT; + } + + /** Sets the default unit. The default unit is the unit used if no unit is set. E.g. "width 10". + * @param unit The new default unit. + * @see UnitValue#PIXEL + * @see UnitValue#LPY + */ + public static void setDefaultVerticalUnit(int unit) + { + if (unit < UnitValue.PIXEL || unit > UnitValue.LABEL_ALIGN) + throw new IllegalArgumentException("Illegal Unit: " + unit); + + if (DEF_V_UNIT != unit) { + DEF_V_UNIT = unit; + MOD_COUNT++; + } + } + + /** The default alignment for rows. Pre v3.5 this was false but now it is + * true. + * @return The current value. Default is true. + * @since 3.5 + */ + public static boolean getDefaultRowAlignmentBaseline() + { + return dra; + } + + /** The default alignment for rows. Pre v3.5 this was false but now it is + * true. + * @param b The new value. Default is true from v3.5. + * @since 3.5 + */ + public static void setDefaultRowAlignmentBaseline(boolean b) + { + dra = b; + } +} diff --git a/src/net/miginfocom/layout/ResizeConstraint.java b/src/net/miginfocom/layout/ResizeConstraint.java new file mode 100644 index 0000000..6a4b7f9 --- /dev/null +++ b/src/net/miginfocom/layout/ResizeConstraint.java @@ -0,0 +1,94 @@ +package net.miginfocom.layout; + +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ +//import java.io.Externalizable; + +/** A parsed constraint that specifies how an entity (normally column/row or component) can shrink or + * grow compared to other entities. + */ +final class ResizeConstraint //implements Externalizable +{ + static final Float WEIGHT_100 = 100f; + + /** How flexible the entity should be, relative to other entities, when it comes to growing. null or + * zero mean it will never grow. An entity that has twice the growWeight compared to another entity will get twice + * as much of available space. + *

+ * "grow" are only compared within the same "growPrio". + */ + Float grow = null; + + /** The relative priority used for determining which entities gets the extra space first. + */ + int growPrio = 100; + + Float shrink = WEIGHT_100; + + int shrinkPrio = 100; + + public ResizeConstraint() // For Externalizable + { + } + + ResizeConstraint(int shrinkPrio, Float shrinkWeight, int growPrio, Float growWeight) + { + this.shrinkPrio = shrinkPrio; + this.shrink = shrinkWeight; + this.growPrio = growPrio; + this.grow = growWeight; + } + +// // ************************************************ +// // Persistence Delegate and Serializable combined. +// // ************************************************ +// +// private Object readResolve() throws ObjectStreamException +// { +// return LayoutUtil.getSerializedObject(this); +// } +// +// @Override +// public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException +// { +// LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in)); +// } +// +// @Override +// public void writeExternal(ObjectOutput out) throws IOException +// { +// if (getClass() == ResizeConstraint.class) +// LayoutUtil.writeAsXML(out, this); +// } +} diff --git a/src/net/miginfocom/layout/UnitConverter.java b/src/net/miginfocom/layout/UnitConverter.java new file mode 100644 index 0000000..3a03ea2 --- /dev/null +++ b/src/net/miginfocom/layout/UnitConverter.java @@ -0,0 +1,59 @@ +package net.miginfocom.layout; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +/** + */ +public abstract class UnitConverter +{ + /** Value to return if this converter can not handle the unit sent in as an argument + * to the convert method. + */ + public static final int UNABLE = -87654312; + + /** Converts value to pixels. + * @param value The value to be converted. + * @param unit The unit of value. Never null and at least one character. + * @param refValue Some reference value that may of may not be used. If the unit is percent for instance this value + * is the value to take the percent from. Usually the size of the parent component in the appropriate dimension. + * @param isHor If the value is horizontal (true) or vertical (false). + * @param parent The parent of the target component that value is to be applied to. + * Might for instance be needed to get the screen that the component is on in a multi screen environment. + *

+ * May be null in which case a "best guess" value should be returned. + * @param comp The component, if applicable, or null if none. + * @return The number of pixels if unit is handled by this converter, UnitConverter.UNABLE if not. + */ + public abstract int convertToPixels(float value, String unit, boolean isHor, float refValue, ContainerWrapper parent, ComponentWrapper comp); +} diff --git a/src/net/miginfocom/layout/UnitValue.java b/src/net/miginfocom/layout/UnitValue.java new file mode 100644 index 0000000..690f14a --- /dev/null +++ b/src/net/miginfocom/layout/UnitValue.java @@ -0,0 +1,691 @@ +package net.miginfocom.layout; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +//import java.beans.Encoder; +//import java.beans.Expression; +//import java.beans.PersistenceDelegate; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; + +public final class UnitValue implements Serializable +{ + private static final HashMap UNIT_MAP = new HashMap(32); + + private static final ArrayList CONVERTERS = new ArrayList(); + + /** An operation indicating a static value. + */ + public static final int STATIC = 100; + + /** An operation indicating a addition of two sub units. + */ + public static final int ADD = 101; // Must have "sub-unit values" + + /** An operation indicating a subtraction of two sub units + */ + public static final int SUB = 102; // Must have "sub-unit values" + + /** An operation indicating a multiplication of two sub units. + */ + public static final int MUL = 103; // Must have "sub-unit values" + + /** An operation indicating a division of two sub units. + */ + public static final int DIV = 104; // Must have "sub-unit values" + + /** An operation indicating the minimum of two sub units + */ + public static final int MIN = 105; // Must have "sub-unit values" + + /** An operation indicating the maximum of two sub units + */ + public static final int MAX = 106; // Must have "sub-unit values" + + /** An operation indicating the middle value of two sub units + */ + public static final int MID = 107; // Must have "sub-unit values" + + + + + /** A unit indicating pixels. + */ + public static final int PIXEL = 0; + + /** A unit indicating logical horizontal pixels. + */ + public static final int LPX = 1; + + /** A unit indicating logical vertical pixels. + */ + public static final int LPY = 2; + + /** A unit indicating millimeters. + */ + public static final int MM = 3; + + /** A unit indicating centimeters. + */ + public static final int CM = 4; + + /** A unit indicating inches. + */ + public static final int INCH = 5; + + /** A unit indicating percent. + */ + public static final int PERCENT = 6; + + /** A unit indicating points. + */ + public static final int PT = 7; + + /** A unit indicating screen percentage width. + */ + public static final int SPX = 8; + + /** A unit indicating screen percentage height. + */ + public static final int SPY = 9; + + /** A unit indicating alignment. + */ + public static final int ALIGN = 12; + + /** A unit indicating minimum size. + */ + public static final int MIN_SIZE = 13; + + /** A unit indicating preferred size. + */ + public static final int PREF_SIZE = 14; + + /** A unit indicating maximum size. + */ + public static final int MAX_SIZE = 15; + + /** A unit indicating button size. + */ + public static final int BUTTON = 16; + + /** A unit indicating linking to x. + */ + public static final int LINK_X = 18; // First link + + /** A unit indicating linking to y. + */ + public static final int LINK_Y = 19; + + /** A unit indicating linking to width. + */ + public static final int LINK_W = 20; + + /** A unit indicating linking to height. + */ + public static final int LINK_H = 21; + + /** A unit indicating linking to x2. + */ + public static final int LINK_X2 = 22; + + /** A unit indicating linking to y2. + */ + public static final int LINK_Y2 = 23; + + /** A unit indicating linking to x position on screen. + */ + public static final int LINK_XPOS = 24; + + /** A unit indicating linking to y position on screen. + */ + public static final int LINK_YPOS = 25; // Last link + + /** A unit indicating a lookup. + */ + public static final int LOOKUP = 26; + + /** A unit indicating label alignment. + */ + public static final int LABEL_ALIGN = 27; + + private static final int IDENTITY = -1; + + static { + UNIT_MAP.put("px", PIXEL); + UNIT_MAP.put("lpx", LPX); + UNIT_MAP.put("lpy", LPY); + UNIT_MAP.put("%", PERCENT); + UNIT_MAP.put("cm", CM); + UNIT_MAP.put("in", INCH); + UNIT_MAP.put("spx", SPX); + UNIT_MAP.put("spy", SPY); + UNIT_MAP.put("al", ALIGN); + UNIT_MAP.put("mm", MM); + UNIT_MAP.put("pt", PT); + UNIT_MAP.put("min", MIN_SIZE); + UNIT_MAP.put("minimum", MIN_SIZE); + UNIT_MAP.put("p", PREF_SIZE); + UNIT_MAP.put("pref", PREF_SIZE); + UNIT_MAP.put("max", MAX_SIZE); + UNIT_MAP.put("maximum", MAX_SIZE); + UNIT_MAP.put("button", BUTTON); + UNIT_MAP.put("label", LABEL_ALIGN); + } + + static final UnitValue ZERO = new UnitValue(0, null, PIXEL, true, STATIC, null, null, "0px"); + static final UnitValue TOP = new UnitValue(0, null, PERCENT, false, STATIC, null, null, "top"); + static final UnitValue LEADING = new UnitValue(0, null, PERCENT, true, STATIC, null, null, "leading"); + static final UnitValue LEFT = new UnitValue(0, null, PERCENT, true, STATIC, null, null, "left"); + static final UnitValue CENTER = new UnitValue(50, null, PERCENT, true, STATIC, null, null, "center"); + static final UnitValue TRAILING = new UnitValue(100, null, PERCENT, true, STATIC, null, null, "trailing"); + static final UnitValue RIGHT = new UnitValue(100, null, PERCENT, true, STATIC, null, null, "right"); + static final UnitValue BOTTOM = new UnitValue(100, null, PERCENT, false, STATIC, null, null, "bottom"); + static final UnitValue LABEL = new UnitValue(0, null, LABEL_ALIGN, false, STATIC, null, null, "label"); + + static final UnitValue INF = new UnitValue(LayoutUtil.INF, null, PIXEL, true, STATIC, null, null, "inf"); + + static final UnitValue BASELINE_IDENTITY = new UnitValue(0, null, IDENTITY, false, STATIC, null, null, "baseline"); + + private final transient float value; + private final transient int unit; + private final transient int oper; + private final transient String unitStr; + private transient String linkId = null; // Should be final, but initializes in a sub method. + private final transient boolean isHor; + private final transient UnitValue[] subUnits; + + // Pixel + public UnitValue(float value) // If hor/ver does not matter. + { + this(value, null, PIXEL, true, STATIC, null, null, value + "px"); + } + + public UnitValue(float value, int unit, String createString) // If hor/ver does not matter. + { + this(value, null, unit, true, STATIC, null, null, createString); + } + + public UnitValue(float value, String unitStr, boolean isHor, int oper, String createString) + { + this(value, unitStr, -1, isHor, oper, null, null, createString); + } + + UnitValue(boolean isHor, int oper, UnitValue sub1, UnitValue sub2, String createString) + { + this(0, "", -1, isHor, oper, sub1, sub2, createString); + if (sub1 == null || sub2 == null) + throw new IllegalArgumentException("Sub units is null!"); + } + + private UnitValue(float value, String unitStr, int unit, boolean isHor, int oper, UnitValue sub1, UnitValue sub2, String createString) + { + if (oper < STATIC || oper > MID) + throw new IllegalArgumentException("Unknown Operation: " + oper); + + if (oper >= ADD && oper <= MID && (sub1 == null || sub2 == null)) + throw new IllegalArgumentException(oper + " Operation may not have null sub-UnitValues."); + + this.value = value; + this.oper = oper; + this.isHor = isHor; + this.unitStr = unitStr; + this.unit = unitStr != null ? parseUnitString() : unit; + this.subUnits = sub1 != null && sub2 != null ? new UnitValue[] {sub1, sub2} : null; + + LayoutUtil.putCCString(this, createString); // "this" escapes!! Safe though. + } + + /** Returns the size in pixels rounded. + * @param refValue The reference value. Normally the size of the parent. For unit {@link #ALIGN} the current size of the component should be sent in. + * @param parent The parent. May be null for testing the validity of the value, but should normally not and are not + * required to return any usable value if null. + * @param comp The component, if any, that the value is for. Might be null if the value is not + * connected to any component. + * @return The size in pixels. + */ + public final int getPixels(float refValue, ContainerWrapper parent, ComponentWrapper comp) + { + return Math.round(getPixelsExact(refValue, parent, comp)); + } + + private static final float[] SCALE = new float[] {25.4f, 2.54f, 1f, 0f, 72f}; + /** Returns the size in pixels. + * @param refValue The reference value. Normally the size of the parent. For unit {@link #ALIGN} the current size of the component should be sent in. + * @param parent The parent. May be null for testing the validity of the value, but should normally not and are not + * required to return any usable value if null. + * @param comp The component, if any, that the value is for. Might be null if the value is not + * connected to any component. + * @return The size in pixels. + */ + public final float getPixelsExact(float refValue, ContainerWrapper parent, ComponentWrapper comp) + { + if (parent == null) + return 1; + + if (oper == STATIC) { + switch (unit) { + case PIXEL: + return value; + + case LPX: + case LPY: + return parent.getPixelUnitFactor(unit == LPX) * value; + + case MM: + case CM: + case INCH: + case PT: + float f = SCALE[unit - MM]; + Float s = isHor ? PlatformDefaults.getHorizontalScaleFactor() : PlatformDefaults.getVerticalScaleFactor(); + if (s != null) + f *= s; + + return (isHor ? parent.getHorizontalScreenDPI() : parent.getVerticalScreenDPI()) * value / f; + + case PERCENT: + return value * refValue * 0.01f; + + case SPX: + case SPY: + return (unit == SPX ? parent.getScreenWidth() : parent.getScreenHeight()) * value * 0.01f; + + case ALIGN: + Integer st = LinkHandler.getValue(parent.getLayout(), "visual", isHor ? LinkHandler.X : LinkHandler.Y); + Integer sz = LinkHandler.getValue(parent.getLayout(), "visual", isHor ? LinkHandler.WIDTH : LinkHandler.HEIGHT); + if (st == null || sz == null) + return 0; + return value * (Math.max(0, sz.intValue()) - refValue) + st; + + case MIN_SIZE: + if (comp == null) + return 0; + return isHor ? comp.getMinimumWidth(comp.getHeight()) : comp.getMinimumHeight(comp.getWidth()); + + case PREF_SIZE: + if (comp == null) + return 0; + return isHor ? comp.getPreferredWidth(comp.getHeight()) : comp.getPreferredHeight(comp.getWidth()); + + case MAX_SIZE: + if (comp == null) + return 0; + return isHor ? comp.getMaximumWidth(comp.getHeight()) : comp.getMaximumHeight(comp.getWidth()); + + case BUTTON: + return PlatformDefaults.getMinimumButtonWidthIncludingPadding(refValue, parent, comp); + + case LINK_X: + case LINK_Y: + case LINK_W: + case LINK_H: + case LINK_X2: + case LINK_Y2: + case LINK_XPOS: + case LINK_YPOS: + Integer v = LinkHandler.getValue(parent.getLayout(), getLinkTargetId(), unit - (unit >= LINK_XPOS ? LINK_XPOS : LINK_X)); + if (v == null) + return 0; + + if (unit == LINK_XPOS) + return parent.getScreenLocationX() + v; + if (unit == LINK_YPOS) + return parent.getScreenLocationY() + v; + + return v; + + case LOOKUP: + float res = lookup(refValue, parent, comp); + if (res != UnitConverter.UNABLE) + return res; + + case LABEL_ALIGN: + return PlatformDefaults.getLabelAlignPercentage() * refValue; + + case IDENTITY: + } + throw new IllegalArgumentException("Unknown/illegal unit: " + unit + ", unitStr: " + unitStr); + } + + if (subUnits != null && subUnits.length == 2) { + float r1 = subUnits[0].getPixelsExact(refValue, parent, comp); + float r2 = subUnits[1].getPixelsExact(refValue, parent, comp); + switch (oper) { + case ADD: + return r1 + r2; + case SUB: + return r1 - r2; + case MUL: + return r1 * r2; + case DIV: + return r1 / r2; + case MIN: + return r1 < r2 ? r1 : r2; + case MAX: + return r1 > r2 ? r1 : r2; + case MID: + return (r1 + r2) * 0.5f; + } + } + + throw new IllegalArgumentException("Internal: Unknown Oper: " + oper); + } + + private float lookup(float refValue, ContainerWrapper parent, ComponentWrapper comp) + { + float res = UnitConverter.UNABLE; + for (int i = CONVERTERS.size() - 1; i >= 0; i--) { + res = CONVERTERS.get(i).convertToPixels(value, unitStr, isHor, refValue, parent, comp); + if (res != UnitConverter.UNABLE) + return res; + } + return PlatformDefaults.convertToPixels(value, unitStr, isHor, refValue, parent, comp); + } + + private int parseUnitString() + { + int len = unitStr.length(); + if (len == 0) + return isHor ? PlatformDefaults.getDefaultHorizontalUnit() : PlatformDefaults.getDefaultVerticalUnit(); + + Integer u = UNIT_MAP.get(unitStr); + if (u != null) { + if (!isHor && (u == BUTTON || u == LABEL_ALIGN)) + throw new IllegalArgumentException("Not valid in vertical contexts: '" + unitStr + "'"); + + return u; + } + + if (unitStr.equals("lp")) + return isHor ? LPX : LPY; + + if (unitStr.equals("sp")) + return isHor ? SPX : SPY; + + if (lookup(0, null, null) != UnitConverter.UNABLE) // To test so we can fail fast + return LOOKUP; + + // Only link left. E.g. "otherID.width" + + int pIx = unitStr.indexOf('.'); + if (pIx != -1) { + linkId = unitStr.substring(0, pIx); + String e = unitStr.substring(pIx + 1); + + if (e.equals("x")) + return LINK_X; + if (e.equals("y")) + return LINK_Y; + if (e.equals("w") || e.equals("width")) + return LINK_W; + if (e.equals("h") || e.equals("height")) + return LINK_H; + if (e.equals("x2")) + return LINK_X2; + if (e.equals("y2")) + return LINK_Y2; + if (e.equals("xpos")) + return LINK_XPOS; + if (e.equals("ypos")) + return LINK_YPOS; + } + + throw new IllegalArgumentException("Unknown keyword: " + unitStr); + } + + final boolean isAbsolute() + { + switch (unit) { + case PIXEL: + case LPX: + case LPY: + case MM: + case CM: + case INCH: + case PT: + return true; + + case SPX: + case SPY: + case PERCENT: + case ALIGN: + case MIN_SIZE: + case PREF_SIZE: + case MAX_SIZE: + case BUTTON: + case LINK_X: + case LINK_Y: + case LINK_W: + case LINK_H: + case LINK_X2: + case LINK_Y2: + case LINK_XPOS: + case LINK_YPOS: + case LOOKUP: + case LABEL_ALIGN: + return false; + + case IDENTITY: + } + throw new IllegalArgumentException("Unknown/illegal unit: " + unit + ", unitStr: " + unitStr); + } + + final boolean isAbsoluteDeep() + { + if (subUnits != null) { + for (UnitValue subUnit : subUnits) { + if (subUnit.isAbsoluteDeep()) + return true; + } + } + return isAbsolute(); + } + + final boolean isLinked() + { + return linkId != null; + } + + final boolean isLinkedDeep() + { + if (subUnits != null) { + for (UnitValue subUnit : subUnits) { + if (subUnit.isLinkedDeep()) + return true; + } + } + return isLinked(); + } + + final String getLinkTargetId() + { + return linkId; + } + + final UnitValue getSubUnitValue(int i) + { + return subUnits[i]; + } + + final int getSubUnitCount() + { + return subUnits != null ? subUnits.length : 0; + } + + public final UnitValue[] getSubUnits() + { + return subUnits != null ? subUnits.clone() : null; + } + + public final int getUnit() + { + return unit; + } + + public final String getUnitString() + { + return unitStr; + } + + public final int getOperation() + { + return oper; + } + + public final float getValue() + { + return value; + } + + public final boolean isHorizontal() + { + return isHor; + } + + @Override + final public String toString() + { + return getClass().getName() + ". Value=" + value + ", unit=" + unit + ", unitString: " + unitStr + ", oper=" + oper + ", isHor: " + isHor; + } + + /** Returns the creation string for this object. Note that {@link LayoutUtil#setDesignTime(ContainerWrapper, boolean)} must be + * set to true for the creation strings to be stored. + * @return The constraint string or null if none is registered. + */ + public final String getConstraintString() + { + return LayoutUtil.getCCString(this); + } + + @Override + public final int hashCode() + { + return (int) (value * 12345) + (oper >>> 5) + unit >>> 17; + } + + /** Adds a global unit converter that can convert from some unit to pixels. + *

+ * This converter will be asked before the platform converter so the values for it (e.g. "related" and "unrelated") + * can be overridden. It is however not possible to override the built in ones (e.g. "mm", "pixel" or "lp"). + * @param conv The converter. Not null. + */ + public synchronized static void addGlobalUnitConverter(UnitConverter conv) + { + if (conv == null) + throw new NullPointerException(); + CONVERTERS.add(conv); + } + + /** Removed the converter. + * @param unit The converter. + * @return If there was a converter found and thus removed. + */ + public synchronized static boolean removeGlobalUnitConverter(UnitConverter unit) + { + return CONVERTERS.remove(unit); + } + + /** Returns the global converters currently registered. The platform converter will not be in this list. + * @return The converters. Never null. + */ + public synchronized static UnitConverter[] getGlobalUnitConverters() + { + return CONVERTERS.toArray(new UnitConverter[CONVERTERS.size()]); + } + + /** Returns the current default unit. The default unit is the unit used if no unit is set. E.g. "width 10". + * @return The current default unit. + * @see #PIXEL + * @see #LPX + * @deprecated Use {@link PlatformDefaults#getDefaultHorizontalUnit()} and {@link PlatformDefaults#getDefaultVerticalUnit()} instead. + */ + public static int getDefaultUnit() + { + return PlatformDefaults.getDefaultHorizontalUnit(); + } + + /** Sets the default unit. The default unit is the unit used if no unit is set. E.g. "width 10". + * @param unit The new default unit. + * @see #PIXEL + * @see #LPX + * @deprecated Use {@link PlatformDefaults#setDefaultHorizontalUnit(int)} and {@link PlatformDefaults#setDefaultVerticalUnit(int)} instead. + */ + public static void setDefaultUnit(int unit) + { + PlatformDefaults.setDefaultHorizontalUnit(unit); + PlatformDefaults.setDefaultVerticalUnit(unit); + } + +// static { +// if(LayoutUtil.HAS_BEANS){\ +// LayoutUtil.setDelegate(UnitValue.class, new PersistenceDelegate() { +// @Override +// protected Expression instantiate(Object oldInstance, Encoder out) +// { +// UnitValue uv = (UnitValue) oldInstance; +// String cs = uv.getConstraintString(); +// if (cs == null) +// throw new IllegalStateException("Design time must be on to use XML persistence. See LayoutUtil."); +// +// return new Expression(oldInstance, ConstraintParser.class, "parseUnitValueOrAlign", new Object[] { +// uv.getConstraintString(), (uv.isHorizontal() ? Boolean.TRUE : Boolean.FALSE), null +// }); +// } +// }); +// } +// } +// +// // ************************************************ +// // Persistence Delegate and Serializable combined. +// // ************************************************ +// +// private static final long serialVersionUID = 1L; +// +// private Object readResolve() throws ObjectStreamException +// { +// return LayoutUtil.getSerializedObject(this); +// } +// +// private void writeObject(ObjectOutputStream out) throws IOException +// { +// if (getClass() == UnitValue.class) +// LayoutUtil.writeAsXML(out, this); +// } +// +// private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException +// { +// LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in)); +// } +} diff --git a/src/net/miginfocom/swing/MigLayout.java b/src/net/miginfocom/swing/MigLayout.java new file mode 100644 index 0000000..8469d7c --- /dev/null +++ b/src/net/miginfocom/swing/MigLayout.java @@ -0,0 +1,816 @@ +package net.miginfocom.swing; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.LayoutManager2; +import java.awt.Point; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.Map; + +import javax.swing.BoxLayout; +import javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.JPopupMenu; +import javax.swing.JTextArea; +import javax.swing.OverlayLayout; +import javax.swing.SwingUtilities; +import javax.swing.Timer; + +import net.miginfocom.layout.AC; +import net.miginfocom.layout.BoundSize; +import net.miginfocom.layout.CC; +import net.miginfocom.layout.ComponentWrapper; +import net.miginfocom.layout.ConstraintParser; +import net.miginfocom.layout.ContainerWrapper; +import net.miginfocom.layout.Grid; +import net.miginfocom.layout.LC; +import net.miginfocom.layout.LayoutCallback; +import net.miginfocom.layout.LayoutUtil; +import net.miginfocom.layout.PlatformDefaults; +import net.miginfocom.layout.UnitValue; + +/** A very flexible layout manager. + *

+ * Read the documentation that came with this layout manager for information on usage. + */ +public class MigLayout implements LayoutManager2//, Externalizable +{ + // ******** Instance part ******** + + /** The component to string constraints mappings. + */ + private final Map scrConstrMap = new IdentityHashMap(8); + + /** Hold the serializable text representation of the constraints. + */ + private Object layoutConstraints = "", colConstraints = "", rowConstraints = ""; // Should never be null! + + // ******** Transient part ******** + + private transient ContainerWrapper cacheParentW = null; + + private transient final Map ccMap = new HashMap(8); + private transient javax.swing.Timer debugTimer = null; + + private transient LC lc = null; + private transient AC colSpecs = null, rowSpecs = null; + private transient Grid grid = null; + private transient int lastModCount = PlatformDefaults.getModCount(); + private transient int lastHash = -1; + private transient Dimension lastInvalidSize = null; + private transient boolean lastWasInvalid = false; // Added in 3.7.1. May have regressions + private transient Dimension lastParentSize = null; + + private transient ArrayList callbackList = null; + + private transient boolean dirty = true; + + /** Constructor with no constraints. + */ + public MigLayout() + { + this("", "", ""); + } + + /** Constructor. + * @param layoutConstraints The constraints that concern the whole layout. null will be treated as "". + */ + public MigLayout(String layoutConstraints) + { + this(layoutConstraints, "", ""); + } + + /** Constructor. + * @param layoutConstraints The constraints that concern the whole layout. null will be treated as "". + * @param colConstraints The constraints for the columns in the grid. null will be treated as "". + */ + public MigLayout(String layoutConstraints, String colConstraints) + { + this(layoutConstraints, colConstraints, ""); + } + + /** Constructor. + * @param layoutConstraints The constraints that concern the whole layout. null will be treated as "". + * @param colConstraints The constraints for the columns in the grid. null will be treated as "". + * @param rowConstraints The constraints for the rows in the grid. null will be treated as "". + */ + public MigLayout(String layoutConstraints, String colConstraints, String rowConstraints) + { + setLayoutConstraints(layoutConstraints); + setColumnConstraints(colConstraints); + setRowConstraints(rowConstraints); + } + + /** Constructor. + * @param layoutConstraints The constraints that concern the whole layout. null will be treated as an empty constraint. + */ + public MigLayout(LC layoutConstraints) + { + this(layoutConstraints, null, null); + } + + /** Constructor. + * @param layoutConstraints The constraints that concern the whole layout. null will be treated as an empty constraint. + * @param colConstraints The constraints for the columns in the grid. null will be treated as an empty constraint. + */ + public MigLayout(LC layoutConstraints, AC colConstraints) + { + this(layoutConstraints, colConstraints, null); + } + + /** Constructor. + * @param layoutConstraints The constraints that concern the whole layout. null will be treated as an empty constraint. + * @param colConstraints The constraints for the columns in the grid. null will be treated as an empty constraint. + * @param rowConstraints The constraints for the rows in the grid. null will be treated as an empty constraint. + */ + public MigLayout(LC layoutConstraints, AC colConstraints, AC rowConstraints) + { + setLayoutConstraints(layoutConstraints); + setColumnConstraints(colConstraints); + setRowConstraints(rowConstraints); + } + + /** Returns layout constraints either as a String or {@link net.miginfocom.layout.LC} depending what was sent in + * to the constructor or set with {@link #setLayoutConstraints(Object)}. + * @return The layout constraints either as a String or {@link net.miginfocom.layout.LC} depending what was sent in + * to the constructor or set with {@link #setLayoutConstraints(Object)}. Never null. + */ + public Object getLayoutConstraints() + { + return layoutConstraints; + } + + /** Sets the layout constraints for the layout manager instance as a String. + *

+ * See the class JavaDocs for information on how this string is formatted. + * @param constr The layout constraints as a String or {@link net.miginfocom.layout.LC} representation. null is converted to "" for storage. + * @throws RuntimeException if the constraint was not valid. + */ + public void setLayoutConstraints(Object constr) + { + if (constr == null || constr instanceof String) { + constr = ConstraintParser.prepare((String) constr); + lc = ConstraintParser.parseLayoutConstraint((String) constr); + } else if (constr instanceof LC) { + lc = (LC) constr; + } else { + throw new IllegalArgumentException("Illegal constraint type: " + constr.getClass().toString()); + } + layoutConstraints = constr; + dirty = true; + } + + /** Returns the column layout constraints either as a String or {@link net.miginfocom.layout.AC}. + * @return The column constraints either as a String or {@link net.miginfocom.layout.AC} depending what was sent in + * to the constructor or set with {@link #setColumnConstraints(Object)}. Never null. + */ + public Object getColumnConstraints() + { + return colConstraints; + } + + /** Sets the column layout constraints for the layout manager instance as a String. + *

+ * See the class JavaDocs for information on how this string is formatted. + * @param constr The column layout constraints as a String or {@link net.miginfocom.layout.AC} representation. null is converted to "" for storage. + * @throws RuntimeException if the constraint was not valid. + */ + public void setColumnConstraints(Object constr) + { + if (constr == null || constr instanceof String) { + constr = ConstraintParser.prepare((String) constr); + colSpecs = ConstraintParser.parseColumnConstraints((String) constr); + } else if (constr instanceof AC) { + colSpecs = (AC) constr; + } else { + throw new IllegalArgumentException("Illegal constraint type: " + constr.getClass().toString()); + } + colConstraints = constr; + dirty = true; + } + + /** Returns the row layout constraints either as a String or {@link net.miginfocom.layout.AC}. + * @return The row constraints either as a String or {@link net.miginfocom.layout.AC} depending what was sent in + * to the constructor or set with {@link #setRowConstraints(Object)}. Never null. + */ + public Object getRowConstraints() + { + return rowConstraints; + } + + /** Sets the row layout constraints for the layout manager instance as a String. + *

+ * See the class JavaDocs for information on how this string is formatted. + * @param constr The row layout constraints as a String or {@link net.miginfocom.layout.AC} representation. null is converted to "" for storage. + * @throws RuntimeException if the constraint was not valid. + */ + public void setRowConstraints(Object constr) + { + if (constr == null || constr instanceof String) { + constr = ConstraintParser.prepare((String) constr); + rowSpecs = ConstraintParser.parseRowConstraints((String) constr); + } else if (constr instanceof AC) { + rowSpecs = (AC) constr; + } else { + throw new IllegalArgumentException("Illegal constraint type: " + constr.getClass().toString()); + } + rowConstraints = constr; + dirty = true; + } + + /** Returns a shallow copy of the constraints map. + * @return A shallow copy of the constraints map. Never null. + */ + public Map getConstraintMap() + { + return new IdentityHashMap(scrConstrMap); + } + + /** Sets the constraints map. + * @param map The map. Will be copied. + */ + public void setConstraintMap(Map map) + { + scrConstrMap.clear(); + ccMap.clear(); + for (Map.Entry e : map.entrySet()) + setComponentConstraintsImpl(e.getKey(), e.getValue(), true); + } + + /** Returns the component constraints as a String representation. This string is the exact string as set with {@link #setComponentConstraints(java.awt.Component, Object)} + * or set when adding the component to the parent component. + *

+ * See the class JavaDocs for information on how this string is formatted. + * @param comp The component to return the constraints for. + * @return The component constraints as a String representation or null if the component is not registered + * with this layout manager. The returned values is either a String or a {@link net.miginfocom.layout.CC} + * depending on what constraint was sent in when the component was added. May be null. + */ + public Object getComponentConstraints(Component comp) + { + synchronized(comp.getParent().getTreeLock()) { + return scrConstrMap.get(comp); + } + } + + /** Sets the component constraint for the component that already must be handled by this layout manager. + *

+ * See the class JavaDocs for information on how this string is formatted. + * @param constr The component constraints as a String or {@link net.miginfocom.layout.CC}. null is ok. + * @param comp The component to set the constraints for. + * @throws RuntimeException if the constraint was not valid. + * @throws IllegalArgumentException If the component is not handling the component. + */ + public void setComponentConstraints(Component comp, Object constr) + { + setComponentConstraintsImpl(comp, constr, false); + } + + /** Sets the component constraint for the component that already must be handled by this layout manager. + *

+ * See the class JavaDocs for information on how this string is formatted. + * @param constr The component constraints as a String or {@link net.miginfocom.layout.CC}. null is ok. + * @param comp The component to set the constraints for. + * @param noCheck Does not check if the component is handled if true + * @throws RuntimeException if the constraint was not valid. + * @throws IllegalArgumentException If the component is not handling the component. + */ + private void setComponentConstraintsImpl(Component comp, Object constr, boolean noCheck) + { + Container parent = comp.getParent(); + synchronized(parent != null ? parent.getTreeLock() : new Object()) { // 3.7.2. No sync if not added to a hierarchy. Defeats a NPE. + if (noCheck == false && scrConstrMap.containsKey(comp) == false) + throw new IllegalArgumentException("Component must already be added to parent!"); + + ComponentWrapper cw = new SwingComponentWrapper(comp); + + if (constr == null || constr instanceof String) { + String cStr = ConstraintParser.prepare((String) constr); + + scrConstrMap.put(comp, constr); + ccMap.put(cw, ConstraintParser.parseComponentConstraint(cStr)); + + } else if (constr instanceof CC) { + + scrConstrMap.put(comp, constr); + ccMap.put(cw, (CC) constr); + + } else { + throw new IllegalArgumentException("Constraint must be String or ComponentConstraint: " + constr.getClass().toString()); + } + + dirty = true; + } + } + + /** Returns if this layout manager is currently managing this component. + * @param c The component to check. If null then false will be returned. + * @return If this layout manager is currently managing this component. + */ + public boolean isManagingComponent(Component c) + { + return scrConstrMap.containsKey(c); + } + + /** Adds the callback function that will be called at different stages of the layout cycle. + * @param callback The callback. Not null. + */ + public void addLayoutCallback(LayoutCallback callback) + { + if (callback == null) + throw new NullPointerException(); + + if (callbackList == null) + callbackList = new ArrayList(1); + + callbackList.add(callback); + + grid = null; + } + + /** Removes the callback if it exists. + * @param callback The callback. May be null. + */ + public void removeLayoutCallback(LayoutCallback callback) + { + if (callbackList != null) + callbackList.remove(callback); + } + + /** Sets the debugging state for this layout manager instance. If debug is turned on a timer will repaint the last laid out parent + * with debug information on top. + *

+ * Red fill and dashed red outline is used to indicate occupied cells in the grid. Blue dashed outline indicate + * component bounds set. + *

+ * Note that debug can also be set on the layout constraints. There it will be persisted. The value set here will not. See the class + * JavaDocs for information. + * @param parentW The parent to set debug for. + * @param b true means debug is turned on. + */ + private void setDebug(final ComponentWrapper parentW, boolean b) + { + if (b && (debugTimer == null || debugTimer.getDelay() != getDebugMillis())) { + if (debugTimer != null) + debugTimer.stop(); + + ContainerWrapper pCW = parentW.getParent(); + final Component parent = pCW != null ? (Component) pCW.getComponent() : null; + + debugTimer = new Timer(getDebugMillis(), new MyDebugRepaintListener()); + + if (parent != null) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + Container p = parent.getParent(); + if (p != null) { + if (p instanceof JComponent) { + ((JComponent) p).revalidate(); + } else { + parent.invalidate(); + p.validate(); + } + } + } + }); + } + + debugTimer.setInitialDelay(100); + debugTimer.start(); + + } else if (!b && debugTimer != null) { + debugTimer.stop(); + debugTimer = null; + } + } + + /** Returns the current debugging state. + * @return The current debugging state. + */ + private boolean getDebug() + { + return debugTimer != null; + } + + /** Returns the debug millis. Combines the value from {@link net.miginfocom.layout.LC#getDebugMillis()} and {@link net.miginfocom.layout.LayoutUtil#getGlobalDebugMillis()} + * @return The combined value. + */ + private int getDebugMillis() + { + int globalDebugMillis = LayoutUtil.getGlobalDebugMillis(); + return globalDebugMillis > 0 ? globalDebugMillis : lc.getDebugMillis(); + } + + /** Check if something has changed and if so recreate it to the cached objects. + * @param parent The parent that is the target for this layout manager. + */ + private void checkCache(Container parent) + { + if (parent == null) + return; + + if (dirty) + grid = null; + + cleanConstraintMaps(parent); + + // Check if the grid is valid + int mc = PlatformDefaults.getModCount(); + if (lastModCount != mc) { + grid = null; + lastModCount = mc; + } + + if (!parent.isValid()) { + if (!lastWasInvalid) { + lastWasInvalid = true; + + int hash = 0; + boolean resetLastInvalidOnParent = false; // Added in 3.7.3 to resolve a timing regression introduced in 3.7.1 + for (ComponentWrapper wrapper : ccMap.keySet()) { + Object component = wrapper.getComponent(); + if (component instanceof JTextArea || component instanceof JEditorPane) + resetLastInvalidOnParent = true; + + hash ^= wrapper.getLayoutHashCode(); + hash += 285134905; + } + if (resetLastInvalidOnParent) + resetLastInvalidOnParent(parent); + + if (hash != lastHash) { + grid = null; + lastHash = hash; + } + + Dimension ps = parent.getSize(); + if (lastInvalidSize == null || !lastInvalidSize.equals(ps)) { + grid = null; + lastInvalidSize = ps; + } + } + } else { + lastWasInvalid = false; + } + + ContainerWrapper par = checkParent(parent); + + setDebug(par, getDebugMillis() > 0); + + if (grid == null) + grid = new Grid(par, lc, rowSpecs, colSpecs, ccMap, callbackList); + + dirty = false; + } + + /** Checks so all components in ccMap actually exist in the parent's collection. Removes + * any references that don't. + * @param parent The parent to compare ccMap against. Never null. + */ + private void cleanConstraintMaps(Container parent) + { + HashSet parentCompSet = new HashSet(Arrays.asList(parent.getComponents())); + + Iterator> it = ccMap.entrySet().iterator(); + while(it.hasNext()) { + Component c = (Component) it.next().getKey().getComponent(); + if (parentCompSet.contains(c) == false) { + it.remove(); + scrConstrMap.remove(c); + } + } + } + + /** + * @since 3.7.3 + */ + private void resetLastInvalidOnParent(Container parent) + { + while (parent != null) { + LayoutManager layoutManager = parent.getLayout(); + if (layoutManager instanceof MigLayout) { + ((MigLayout) layoutManager).lastWasInvalid = false; + } + parent = parent.getParent(); + } + } + + private ContainerWrapper checkParent(Container parent) + { + if (parent == null) + return null; + + if (cacheParentW == null || cacheParentW.getComponent() != parent) + cacheParentW = new SwingContainerWrapper(parent); + + return cacheParentW; + } + + private long lastSize = 0; + + @Override + public void layoutContainer(final Container parent) + { + synchronized(parent.getTreeLock()) { + checkCache(parent); + + Insets i = parent.getInsets(); + int[] b = new int[] { + i.left, + i.top, + parent.getWidth() - i.left - i.right, + parent.getHeight() - i.top - i.bottom + }; + + if (grid.layout(b, lc.getAlignX(), lc.getAlignY(), getDebug())) { + grid = null; + checkCache(parent); + grid.layout(b, lc.getAlignX(), lc.getAlignY(), getDebug()); + } + + long newSize = grid.getHeight()[1] + (((long) grid.getWidth()[1]) << 32); + if (lastSize != newSize) { + lastSize = newSize; + final ContainerWrapper containerWrapper = checkParent(parent); + Window win = ((Window) SwingUtilities.getAncestorOfClass(Window.class, (Component)containerWrapper.getComponent())); + if (win != null) { + if (win.isVisible()) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + adjustWindowSize(containerWrapper); + } + }); + } else { + adjustWindowSize(containerWrapper); + } + } + } + lastInvalidSize = null; + } + } + + /** Checks the parent window/popup if its size is within parameters as set by the LC. + * @param parent The parent who's window to possibly adjust the size for. + */ + private void adjustWindowSize(ContainerWrapper parent) + { + BoundSize wBounds = lc.getPackWidth(); + BoundSize hBounds = lc.getPackHeight(); + + if (wBounds == BoundSize.NULL_SIZE && hBounds == BoundSize.NULL_SIZE) + return; + + Container packable = getPackable((Component) parent.getComponent()); + + if (packable != null) { + + Component pc = (Component) parent.getComponent(); + + Container c = pc instanceof Container ? (Container) pc : pc.getParent(); + for (; c != null; c = c.getParent()) { + LayoutManager layout = c.getLayout(); + if (layout instanceof BoxLayout || layout instanceof OverlayLayout) + ((LayoutManager2) layout).invalidateLayout(c); + } + + Dimension prefSize = packable.getPreferredSize(); + int targW = constrain(checkParent(packable), packable.getWidth(), prefSize.width, wBounds); + int targH = constrain(checkParent(packable), packable.getHeight(), prefSize.height, hBounds); + + Point p = packable.isShowing() ? packable.getLocationOnScreen() : packable.getLocation(); + + int x = Math.round(p.x - ((targW - packable.getWidth()) * (1 - lc.getPackWidthAlign()))); + int y = Math.round(p.y - ((targH - packable.getHeight()) * (1 - lc.getPackHeightAlign()))); + + if (packable instanceof JPopupMenu) { + JPopupMenu popupMenu = (JPopupMenu) packable; + popupMenu.setVisible(false); + popupMenu.setPopupSize(targW, targH); + Component invoker = popupMenu.getInvoker(); + Point popPoint = new Point(x, y); + SwingUtilities.convertPointFromScreen(popPoint, invoker); + ((JPopupMenu) packable).show(invoker, popPoint.x, popPoint.y); + + packable.setPreferredSize(null); // Reset preferred size so we don't read it again. + + } else { + packable.setBounds(x, y, targW, targH); + } + } + } + + /** Returns a high level window or popup to pack, if any. + * @return May be null. + */ + private Container getPackable(Component comp) + { + JPopupMenu popup = findType(JPopupMenu.class, comp); + if (popup != null) { // Lightweight/HeavyWeight popup must be handled separately + Container popupComp = popup; + while (popupComp != null) { + if (popupComp.getClass().getName().contains("HeavyWeightWindow")) + return popupComp; // Return the heavy weight window for normal processing + popupComp = popupComp.getParent(); + } + return popup; // Return the JPopup. + } + + return findType(Window.class, comp); + } + + public static E findType(Class clazz, Component comp) + { + while (comp != null && !clazz.isInstance(comp)) + comp = comp.getParent(); + + return (E) comp; + } + + + private int constrain(ContainerWrapper parent, int winSize, int prefSize, BoundSize constrain) + { + if (constrain == null) + return winSize; + + int retSize = winSize; + UnitValue wUV = constrain.getPreferred(); + if (wUV != null) + retSize = wUV.getPixels(prefSize, parent, parent); + + retSize = constrain.constrain(retSize, prefSize, parent); + + return constrain.getGapPush() ? Math.max(winSize, retSize) : retSize; + } + + @Override + public Dimension minimumLayoutSize(Container parent) + { + synchronized(parent.getTreeLock()) { + return getSizeImpl(parent, LayoutUtil.MIN); + } + } + + @Override + public Dimension preferredLayoutSize(Container parent) + { + synchronized(parent.getTreeLock()) { + if (lastParentSize == null || !parent.getSize().equals(lastParentSize)) { + for (ComponentWrapper wrapper : ccMap.keySet()) { + if (wrapper.getContentBias() != -1) { + layoutContainer(parent); + break; + } + } + } + + lastParentSize = parent.getSize(); + return getSizeImpl(parent, LayoutUtil.PREF); + } + } + + @Override + public Dimension maximumLayoutSize(Container parent) + { + return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); + } + + // Implementation method that does the job. + private Dimension getSizeImpl(Container parent, int sizeType) + { + checkCache(parent); + + Insets i = parent.getInsets(); + + int w = LayoutUtil.getSizeSafe(grid != null ? grid.getWidth() : null, sizeType) + i.left + i.right; + int h = LayoutUtil.getSizeSafe(grid != null ? grid.getHeight() : null, sizeType) + i.top + i.bottom; + + return new Dimension(w, h); + } + + @Override + public float getLayoutAlignmentX(Container parent) + { + return lc != null && lc.getAlignX() != null ? lc.getAlignX().getPixels(1, checkParent(parent), null) : 0; + } + + @Override + public float getLayoutAlignmentY(Container parent) + { + return lc != null && lc.getAlignY() != null ? lc.getAlignY().getPixels(1, checkParent(parent), null) : 0; + } + + @Override + public void addLayoutComponent(String s, Component comp) + { + addLayoutComponent(comp, s); + } + + @Override + public void addLayoutComponent(Component comp, Object constraints) + { + synchronized(comp.getParent().getTreeLock()) { + setComponentConstraintsImpl(comp, constraints, true); + } + } + + @Override + public void removeLayoutComponent(Component comp) + { + synchronized(comp.getParent().getTreeLock()) { + scrConstrMap.remove(comp); + ccMap.remove(new SwingComponentWrapper(comp)); + grid = null; // To clear references + } + } + + @Override + public void invalidateLayout(Container target) + { + dirty = true; + } + +// // ************************************************ +// // Persistence Delegate and Serializable combined. +// // ************************************************ +// +// private Object readResolve() throws ObjectStreamException +// { +// return LayoutUtil.getSerializedObject(this); +// } +// +// @Override +// public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException +// { +// LayoutUtil.setSerializedObject(this, LayoutUtil.readAsXML(in)); +// } +// +// @Override +// public void writeExternal(ObjectOutput out) throws IOException +// { +// if (getClass() == MigLayout.class) +// LayoutUtil.writeAsXML(out, this); +// } + + private class MyDebugRepaintListener implements ActionListener + { + @Override + public void actionPerformed(ActionEvent e) + { + if (grid != null) { + Component comp = (Component) grid.getContainer().getComponent(); + if (comp.isShowing()) { + grid.paintDebug(); + return; + } + } + debugTimer.stop(); + debugTimer = null; + } + } +} \ No newline at end of file diff --git a/src/net/miginfocom/swing/SwingComponentWrapper.java b/src/net/miginfocom/swing/SwingComponentWrapper.java new file mode 100644 index 0000000..46a78fd --- /dev/null +++ b/src/net/miginfocom/swing/SwingComponentWrapper.java @@ -0,0 +1,679 @@ +package net.miginfocom.swing; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +import net.miginfocom.layout.ComponentWrapper; +import net.miginfocom.layout.ContainerWrapper; +import net.miginfocom.layout.LayoutUtil; +import net.miginfocom.layout.PlatformDefaults; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.util.IdentityHashMap; +import java.util.StringTokenizer; + +/** + */ +public class SwingComponentWrapper implements ComponentWrapper +{ + private static boolean maxSet = false; + + private static boolean vp = true; + + /** Debug color for component bounds outline. + */ + private static final Color DB_COMP_OUTLINE = new Color(0, 0, 200); + + /** Property to use in LAF settings and as JComponent client property + * to specify the visual padding. + *

+ */ + private static final String VISUAL_PADDING_PROPERTY = net.miginfocom.layout.PlatformDefaults.VISUAL_PADDING_PROPERTY; + + private final Component c; + private int compType = TYPE_UNSET; + private Boolean bl = null; + private boolean prefCalled = false; + + public SwingComponentWrapper(Component c) + { + this.c = c; + } + + @Override + public final int getBaseline(int width, int height) + { + int h = height; + int[] visPad = getVisualPadding(); + if (h < 0) { + h = c.getHeight(); + } else if (visPad != null) { + h = height + visPad[0] + visPad[2]; + } + int baseLine = c.getBaseline(width < 0 ? c.getWidth() : width, h); + if (baseLine != -1 && visPad != null) + baseLine -= visPad[0]; + + return baseLine; + } + + @Override + public final Object getComponent() + { + return c; + } + + /** Cache. + */ + private final static IdentityHashMap FM_MAP = new IdentityHashMap(4); + private final static Font SUBST_FONT = new Font("sansserif", Font.PLAIN, 11); + + @Override + public final float getPixelUnitFactor(boolean isHor) + { + switch (PlatformDefaults.getLogicalPixelBase()) { + case PlatformDefaults.BASE_FONT_SIZE: + Font font = c.getFont(); + FontMetrics fm = c.getFontMetrics(font != null ? font : SUBST_FONT); + Point.Float p = FM_MAP.get(fm); + if (p == null) { + Rectangle2D r = fm.getStringBounds("X", c.getGraphics()); + p = new Point.Float(((float) r.getWidth()) / 6f, ((float) r.getHeight()) / 13.27734375f); + FM_MAP.put(fm, p); + } + return isHor ? p.x : p.y; + + case PlatformDefaults.BASE_SCALE_FACTOR: + + Float s = isHor ? PlatformDefaults.getHorizontalScaleFactor() : PlatformDefaults.getVerticalScaleFactor(); + float scaleFactor = (s != null) ? s : 1f; + + // Swing in Java 9 scales automatically using the system scale factor(s) that the + // user can change in the system settings (Windows: Control Panel; Mac: System Preferences). + // Each connected screen may use its own scale factor + // (e.g. 1.5 for primary 4K 40inch screen and 1.0 for secondary HD screen). + float screenScale = isJava9orLater + ? 1f // use system scale factor(s) + : (float) (isHor ? getHorizontalScreenDPI() : getVerticalScreenDPI()) / (float) PlatformDefaults.getDefaultDPI(); + return scaleFactor * screenScale; + + default: + return 1f; + } + } + + private static boolean isJava9orLater; + static { + try { + // Java 9 version-String Scheme: http://openjdk.java.net/jeps/223 + StringTokenizer st = new StringTokenizer(System.getProperty("java.version"), "._-+"); + int majorVersion = Integer.parseInt(st.nextToken()); + isJava9orLater = majorVersion >= 9; + } catch (Exception e) { + // Java 8 or older + } + } + +// /** Cache. +// */ +// private final static IdentityHashMap FM_MAP2 = new IdentityHashMap(4); +// private final static Font SUBST_FONT2 = new Font("sansserif", Font.PLAIN, 11); +// +// public float getDialogUnit(boolean isHor) +// { +// Font font = c.getFont(); +// FontMetrics fm = c.getFontMetrics(font != null ? font : SUBST_FONT2); +// Point.Float dluP = FM_MAP2.get(fm); +// if (dluP == null) { +// float w = fm.charWidth('X') / 4f; +// int ascent = fm.getAscent(); +// float h = (ascent > 14 ? ascent : ascent + (15 - ascent) / 3) / 8f; +// +// dluP = new Point.Float(w, h); +// FM_MAP2.put(fm, dluP); +// } +// return isHor ? dluP.x : dluP.y; +// } + + @Override + public final int getX() + { + return c.getX(); + } + + @Override + public final int getY() + { + return c.getY(); + } + + @Override + public final int getHeight() + { + return c.getHeight(); + } + + @Override + public final int getWidth() + { + return c.getWidth(); + } + + @Override + public final int getScreenLocationX() + { + Point p = new Point(); + SwingUtilities.convertPointToScreen(p, c); + return p.x; + } + + @Override + public final int getScreenLocationY() + { + Point p = new Point(); + SwingUtilities.convertPointToScreen(p, c); + return p.y; + } + + @Override + public final int getMinimumHeight(int sz) + { + if (prefCalled == false) { + c.getPreferredSize(); // To defeat a bug where the minimum size is different before and after the first call to getPreferredSize(); + prefCalled = true; + } + return c.getMinimumSize().height; + } + + @Override + public final int getMinimumWidth(int sz) + { + if (prefCalled == false) { + c.getPreferredSize(); // To defeat a bug where the minimum size is different before and after the first call to getPreferredSize(); + prefCalled = true; + } + return c.getMinimumSize().width; + } + @Override + public final int getPreferredHeight(int sz) + { + // If the component has not gotten size yet and there is a size hint, trick Swing to return a better height. + if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1) + c.setBounds(c.getX(), c.getY(), sz, 1); + + return c.getPreferredSize().height; + } + + @Override + public final int getPreferredWidth(int sz) + { + // If the component has not gotten size yet and there is a size hint, trick Swing to return a better height. + if (c.getWidth() == 0 && c.getHeight() == 0 && sz != -1) + c.setBounds(c.getX(), c.getY(), 1, sz); + + return c.getPreferredSize().width; + } + + @Override + public final int getMaximumHeight(int sz) + { + if (!isMaxSet(c)) + return Integer.MAX_VALUE; + + return c.getMaximumSize().height; + } + + @Override + public final int getMaximumWidth(int sz) + { + if (!isMaxSet(c)) + return Integer.MAX_VALUE; + + return c.getMaximumSize().width; + } + + + private boolean isMaxSet(Component c) + { + return c.isMaximumSizeSet(); + } + + @Override + public final ContainerWrapper getParent() + { + Container p = c.getParent(); + return p != null ? new SwingContainerWrapper(p) : null; + } + + @Override + public final int getHorizontalScreenDPI() { + try { + return c.getToolkit().getScreenResolution(); + } catch (HeadlessException ex) { + return PlatformDefaults.getDefaultDPI(); + } + } + + @Override + public final int getVerticalScreenDPI() + { + try { + return c.getToolkit().getScreenResolution(); + } catch (HeadlessException ex) { + return PlatformDefaults.getDefaultDPI(); + } + } + + @Override + public final int getScreenWidth() + { + try { + return c.getToolkit().getScreenSize().width; + } catch (HeadlessException ex) { + return 1024; + } + } + + @Override + public final int getScreenHeight() + { + try { + return c.getToolkit().getScreenSize().height; + } catch (HeadlessException ex) { + return 768; + } + } + + @Override + public final boolean hasBaseline() + { + if (bl == null) { + try { + // Removed since OTHER is sometimes returned even though there is a valid baseline (e.g. an empty JComboBox) +// if (c.getBaselineResizeBehavior() == Component.BaselineResizeBehavior.OTHER) { +// bl = Boolean.FALSE; +// } else { + // 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. +// Dimension d = c.getPreferredSize(); +// bl = getBaseline(d.width, d.height) > -1; + bl = getBaseline(8192, 8192) > -1; // Use large number but don't risk overflow or exposing size bugs with Integer.MAX_VALUE +// } + } catch (Throwable ex) { + bl = Boolean.FALSE; + } + } + return bl; + } + + @Override + public final String getLinkId() + { + return c.getName(); + } + + @Override + public final void setBounds(int x, int y, int width, int height) + { + c.setBounds(x, y, width, height); + } + + @Override + public boolean isVisible() + { + return c.isVisible(); + } + + @Override + public final int[] getVisualPadding() + { + int[] padding = null; + if (isVisualPaddingEnabled()) { + //First try "visualPadding" client property + if (c instanceof JComponent) { + JComponent component = (JComponent) c; + Object padValue = component.getClientProperty(VISUAL_PADDING_PROPERTY); + + if (padValue instanceof int[] ) { + //client property value could be an int[] + padding = (int[]) padValue; + } else if (padValue instanceof Insets) { + //OR client property value could be an Insets + Insets padInsets = (Insets) padValue; + padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right }; + } + + if (padding == null) { + //No client property set on the individual JComponent, + // so check for a LAF setting for the component type. + String classID; + switch (getComponentType(false)) { + case TYPE_BUTTON: + Border border = component.getBorder(); + if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) { + if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) { + Object buttonType = component.getClientProperty("JButton.buttonType"); + if (buttonType == null) { + classID = component.getHeight() < 33 ? "Button" : "Button.bevel"; + } else { + classID = "Button." + buttonType; + } + if (((AbstractButton) component).getIcon() != null) + classID += ".icon"; + } else { + classID = "Button"; + } + } else { + classID = ""; + } + break; + + case TYPE_CHECK_BOX: + border = component.getBorder(); + if (border != null && border.getClass().getName().startsWith("com.apple.laf.AquaButtonBorder")) { + Object size = component.getClientProperty("JComponent.sizeVariant"); + if (size != null && size.toString().equals("regular") == false) { + size = "." + size; + } else { + size = ""; + } + + if (component instanceof JRadioButton) { + classID = "RadioButton" + size; + } else if (component instanceof JCheckBox) { + classID = "CheckBox" + size; + } else { + classID = "ToggleButton" + size; + } + } else { + classID = ""; + } + break; + + case TYPE_COMBO_BOX: + if (PlatformDefaults.getPlatform() == PlatformDefaults.MAC_OSX) { + if (((JComboBox) component).isEditable()) { + Object isSquare = component.getClientProperty("JComboBox.isSquare"); + if (isSquare != null && isSquare.toString().equals("true")) { + classID = "ComboBox.editable.isSquare"; + } else { + classID = "ComboBox.editable"; + } + + } else { + Object isSquare = component.getClientProperty("JComboBox.isSquare"); + Object isPopDown = component.getClientProperty("JComboBox.isPopDown"); + + if (isSquare != null && isSquare.toString().equals("true")) { + classID = "ComboBox.isSquare"; + } else if (isPopDown != null && isPopDown.toString().equals("true")) { + classID = "ComboBox.isPopDown"; + } else { + classID = "ComboBox"; + } + } + } else { + classID = "ComboBox"; + } + break; + case TYPE_CONTAINER: + classID = "Container"; + break; + case TYPE_IMAGE: + classID = "Image"; + break; + case TYPE_LABEL: + classID = "Label"; + break; + case TYPE_LIST: + classID = "List"; + break; + case TYPE_PANEL: + classID = "Panel"; + break; + case TYPE_PROGRESS_BAR: + classID = "ProgressBar"; + break; + case TYPE_SCROLL_BAR: + classID = "ScrollBar"; + break; + case TYPE_SCROLL_PANE: + classID = "ScrollPane"; + break; + case TYPE_SEPARATOR: + classID = "Separator"; + break; + case TYPE_SLIDER: + classID = "Slider"; + break; + case TYPE_SPINNER: + classID = "Spinner"; + break; + case TYPE_TABLE: + classID = "Table"; + break; + case TYPE_TABBED_PANE: + classID = "TabbedPane"; + break; + case TYPE_TEXT_AREA: + classID = "TextArea"; + break; + case TYPE_TEXT_FIELD: + border = component.getBorder(); + if (!component.isOpaque() && border != null && border.getClass().getSimpleName().equals("AquaTextFieldBorder")) { + classID = "TextField"; + } else { + classID = ""; + } + break; + case TYPE_TREE: + classID = "Tree"; + break; + case TYPE_UNKNOWN: + classID = "Other"; + break; + case TYPE_UNSET: + default: + classID = ""; + break; + } + + padValue = PlatformDefaults.getDefaultVisualPadding(classID + "." + VISUAL_PADDING_PROPERTY); + if (padValue instanceof int[]) { + //client property value could be an int[] + padding = (int[]) padValue; + } else if (padValue instanceof Insets) { + //OR client property value could be an Insets + Insets padInsets = (Insets) padValue; + padding = new int[] { padInsets.top, padInsets.left, padInsets.bottom, padInsets.right }; + } + } + } + } + return padding; + } + + /** + * @deprecated Java 1.4 is not supported anymore + */ + public static boolean isMaxSizeSetOn1_4() + { + return maxSet; + } + + /** + * @deprecated Java 1.4 is not supported anymore + */ + public static void setMaxSizeSetOn1_4(boolean b) + { + maxSet = b; + } + + public static boolean isVisualPaddingEnabled() + { + return vp; + } + + public static void setVisualPaddingEnabled(boolean b) + { + vp = b; + } + + @Override + public final void paintDebugOutline(boolean showVisualPadding) + { + if (c.isShowing() == false) + return; + + Graphics2D g = (Graphics2D) c.getGraphics(); + if (g == null) + return; + + g.setPaint(DB_COMP_OUTLINE); + g.setStroke(new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[] {2f, 4f}, 0)); + g.drawRect(0, 0, getWidth() - 1, getHeight() - 1); + + if (showVisualPadding && isVisualPaddingEnabled()) { + int[] padding = getVisualPadding(); + if (padding != null) { + g.setColor(Color.GREEN); + g.drawRect(padding[1], padding[0], (getWidth() - 1) - (padding[1] + padding[3]), (getHeight() - 1) - (padding[0] + padding[2])); + } + } + } + + @Override + public int getComponentType(boolean disregardScrollPane) + { + if (compType == TYPE_UNSET) + compType = checkType(disregardScrollPane); + + return compType; + } + + @Override + public int getLayoutHashCode() + { + Dimension d = c.getMaximumSize(); + int hash = d.width + (d.height << 5); + + d = c.getPreferredSize(); + hash += (d.width << 10) + (d.height << 15); + + d = c.getMinimumSize(); + hash += (d.width << 20) + (d.height << 25); + + if (c.isVisible()) + hash += 1324511; + + String id = getLinkId(); + if (id != null) + hash += id.hashCode(); + + return hash; + } + + private int checkType(boolean disregardScrollPane) + { + Component c = this.c; + + if (disregardScrollPane) { + if (c instanceof JScrollPane) { + c = ((JScrollPane) c).getViewport().getView(); + } else if (c instanceof ScrollPane) { + c = ((ScrollPane) c).getComponent(0); + } + } + + if (c instanceof JTextField || c instanceof TextField) { + return TYPE_TEXT_FIELD; + } else if (c instanceof JLabel || c instanceof Label) { + return TYPE_LABEL; + } else if (c instanceof JCheckBox || c instanceof JRadioButton || c instanceof Checkbox) { + return TYPE_CHECK_BOX; + } else if (c instanceof AbstractButton || c instanceof Button) { + return TYPE_BUTTON; + } else if (c instanceof JComboBox || c instanceof Choice) { + return TYPE_COMBO_BOX; + } else if (c instanceof JTextComponent || c instanceof TextComponent) { + return TYPE_TEXT_AREA; + } else if (c instanceof JPanel || c instanceof Canvas) { + return TYPE_PANEL; + } else if (c instanceof JList || c instanceof List) { + return TYPE_LIST; + } else if (c instanceof JTable) { + return TYPE_TABLE; + } else if (c instanceof JSeparator) { + return TYPE_SEPARATOR; + } else if (c instanceof JSpinner) { + return TYPE_SPINNER; + } else if (c instanceof JTabbedPane) { + return TYPE_TABBED_PANE; + } else if (c instanceof JProgressBar) { + return TYPE_PROGRESS_BAR; + } else if (c instanceof JSlider) { + return TYPE_SLIDER; + } else if (c instanceof JScrollPane) { + return TYPE_SCROLL_PANE; + } else if (c instanceof JScrollBar || c instanceof Scrollbar) { + return TYPE_SCROLL_BAR; + } else if (c instanceof Container) { // only AWT components is not containers. + return TYPE_CONTAINER; + } + return TYPE_UNKNOWN; + } + + @Override + public final int hashCode() + { + return getComponent().hashCode(); + } + + @Override + public final boolean equals(Object o) + { + if (o instanceof ComponentWrapper == false) + return false; + + return c.equals(((ComponentWrapper) o).getComponent()); + } + + @Override + public int getContentBias() + { + return c instanceof JTextArea || c instanceof JEditorPane || (c instanceof JComponent && Boolean.TRUE.equals(((JComponent)c).getClientProperty("migLayout.dynamicAspectRatio"))) ? LayoutUtil.HORIZONTAL : -1; + } +} diff --git a/src/net/miginfocom/swing/SwingContainerWrapper.java b/src/net/miginfocom/swing/SwingContainerWrapper.java new file mode 100644 index 0000000..ae352ba --- /dev/null +++ b/src/net/miginfocom/swing/SwingContainerWrapper.java @@ -0,0 +1,120 @@ +package net.miginfocom.swing; +/* + * License (BSD): + * ============== + * + * Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (miglayout (at) miginfocom (dot) com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * Neither the name of the MiG InfoCom AB nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * @version 1.0 + * @author Mikael Grev, MiG InfoCom AB + * Date: 2006-sep-08 + */ + +import net.miginfocom.layout.ComponentWrapper; +import net.miginfocom.layout.ContainerWrapper; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Graphics2D; + +/** + */ +public final class SwingContainerWrapper extends SwingComponentWrapper implements ContainerWrapper +{ + /** Debug color for cell outline. + */ + private static final Color DB_CELL_OUTLINE = new Color(255, 0, 0); + + public SwingContainerWrapper(Container c) + { + super(c); + } + + @Override + public ComponentWrapper[] getComponents() + { + Container c = (Container) getComponent(); + ComponentWrapper[] cws = new ComponentWrapper[c.getComponentCount()]; + for (int i = 0; i < cws.length; i++) + cws[i] = new SwingComponentWrapper(c.getComponent(i)); + return cws; + } + + @Override + public int getComponentCount() + { + return ((Container) getComponent()).getComponentCount(); + } + + @Override + public Object getLayout() + { + return ((Container) getComponent()).getLayout(); + } + + @Override + public final boolean isLeftToRight() + { + return ((Container) getComponent()).getComponentOrientation().isLeftToRight(); + } + + @Override + public final void paintDebugCell(int x, int y, int width, int height) + { + Component c = (Component) getComponent(); + if (c.isShowing() == false) + return; + + Graphics2D g = (Graphics2D) c.getGraphics(); + if (g == null) + return; + + g.setStroke(new BasicStroke(1f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[] {2f, 3f}, 0)); + g.setPaint(DB_CELL_OUTLINE); + g.drawRect(x, y, width - 1, height - 1); + } + + @Override + public int getComponentType(boolean disregardScrollPane) + { + return TYPE_CONTAINER; + } + + // Removed for 2.3 because the parent.isValid() in MigLayout will catch this instead. + @Override + public int getLayoutHashCode() + { + long n = System.nanoTime(); + int h = super.getLayoutHashCode(); + + if (isLeftToRight()) + h += 416343; + + return 0; + } +} diff --git a/src/org/json/simple/parser/JSONParser.java b/src/org/json/simple/parser/JSONParser.java index 59f8e31..70e04cc 100644 --- a/src/org/json/simple/parser/JSONParser.java +++ b/src/org/json/simple/parser/JSONParser.java @@ -14,520 +14,598 @@ import java.util.Map; import org.json.simple.JSONArray; import org.json.simple.JSONObject; - /** * Parser for JSON text. Please note that JSONParser is NOT thread-safe. * * @author FangYidong */ -public class JSONParser { - public static final int S_INIT=0; - public static final int S_IN_FINISHED_VALUE=1;//string,number,boolean,null,object,array - public static final int S_IN_OBJECT=2; - public static final int S_IN_ARRAY=3; - public static final int S_PASSED_PAIR_KEY=4; - public static final int S_IN_PAIR_VALUE=5; - public static final int S_END=6; - public static final int S_IN_ERROR=-1; - - private LinkedList handlerStatusStack; - private Yylex lexer = new Yylex((Reader)null); - private Yytoken token = null; - private int status = S_INIT; - - private int peekStatus(LinkedList statusStack){ - if(statusStack.size()==0) - return -1; - Integer status=(Integer)statusStack.getFirst(); - return status.intValue(); - } - - /** - * Reset the parser to the initial state without resetting the underlying reader. - * - */ - public void reset(){ - token = null; - status = S_INIT; - handlerStatusStack = null; +public class JSONParser +{ + public static final int S_INIT = 0; + + public static final int S_IN_FINISHED_VALUE = 1;// string,number,boolean,null,object,array + + public static final int S_IN_OBJECT = 2; + + public static final int S_IN_ARRAY = 3; + + public static final int S_PASSED_PAIR_KEY = 4; + + public static final int S_IN_PAIR_VALUE = 5; + + public static final int S_END = 6; + + public static final int S_IN_ERROR = -1; + + private LinkedList handlerStatusStack; + + private Yylex lexer = new Yylex((Reader) null); + + private Yytoken token = null; + + private int status = S_INIT; + + private int peekStatus(LinkedList statusStack) + { + if (statusStack.size() == 0) + return -1; + Integer status = (Integer) statusStack.getFirst(); + return status.intValue(); + } + + /** + * Reset the parser to the initial state without resetting the underlying + * reader. + * + */ + public void reset() + { + token = null; + status = S_INIT; + handlerStatusStack = null; + } + + /** + * Reset the parser to the initial state with a new character reader. + * + * @param in + * - The new character reader. + * @throws IOException + * @throws ParseException + */ + public void reset(Reader in) + { + lexer.yyreset(in); + reset(); + } + + /** + * @return The position of the beginning of the current token. + */ + public int getPosition() + { + return lexer.getPosition(); + } + + public Object parse(String s) throws ParseException + { + return parse(s, (ContainerFactory) null); + } + + public Object parse(String s, ContainerFactory containerFactory) + throws ParseException + { + StringReader in = new StringReader(s); + try + { + return parse(in, containerFactory); + } catch (IOException ie) + { + /* + * Actually it will never happen. + */ + throw new ParseException(-1, + ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); + } + } + + public Object parse(Reader in) throws IOException, ParseException + { + return parse(in, (ContainerFactory) null); + } + + /** + * Parse JSON text into java object from the input source. + * + * @param in + * @param containerFactory + * - Use this factory to createyour own JSON object and JSON array + * containers. + * @return Instance of the following: org.json.simple.JSONObject, + * org.json.simple.JSONArray, java.lang.String, java.lang.Number, + * java.lang.Boolean, null + * + * @throws IOException + * @throws ParseException + */ + public Object parse(Reader in, ContainerFactory containerFactory) + throws IOException, ParseException + { + reset(in); + LinkedList statusStack = new LinkedList(); + LinkedList valueStack = new LinkedList(); + + try + { + do + { + nextToken(); + switch (status) + { + case S_INIT: + switch (token.type) + { + case Yytoken.TYPE_VALUE: + status = S_IN_FINISHED_VALUE; + statusStack.addFirst(new Integer(status)); + valueStack.addFirst(token.value); + break; + case Yytoken.TYPE_LEFT_BRACE: + status = S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + valueStack.addFirst(createObjectContainer(containerFactory)); + break; + case Yytoken.TYPE_LEFT_SQUARE: + status = S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + valueStack.addFirst(createArrayContainer(containerFactory)); + break; + default: + status = S_IN_ERROR; + }// inner switch + break; + + case S_IN_FINISHED_VALUE: + if (token.type == Yytoken.TYPE_EOF) + return valueStack.removeFirst(); + else + throw new ParseException(getPosition(), + ParseException.ERROR_UNEXPECTED_TOKEN, token); + + case S_IN_OBJECT: + switch (token.type) + { + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if (token.value instanceof String) + { + String key = (String) token.value; + valueStack.addFirst(key); + status = S_PASSED_PAIR_KEY; + statusStack.addFirst(new Integer(status)); + } + else + { + status = S_IN_ERROR; + } + break; + case Yytoken.TYPE_RIGHT_BRACE: + if (valueStack.size() > 1) + { + statusStack.removeFirst(); + valueStack.removeFirst(); + status = peekStatus(statusStack); + } + else + { + status = S_IN_FINISHED_VALUE; + } + break; + default: + status = S_IN_ERROR; + break; + }// inner switch + break; + + case S_PASSED_PAIR_KEY: + switch (token.type) + { + case Yytoken.TYPE_COLON: + break; + case Yytoken.TYPE_VALUE: + statusStack.removeFirst(); + String key = (String) valueStack.removeFirst(); + Map parent = (Map) valueStack.getFirst(); + parent.put(key, token.value); + status = peekStatus(statusStack); + break; + case Yytoken.TYPE_LEFT_SQUARE: + statusStack.removeFirst(); + key = (String) valueStack.removeFirst(); + parent = (Map) valueStack.getFirst(); + List newArray = createArrayContainer(containerFactory); + parent.put(key, newArray); + status = S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + valueStack.addFirst(newArray); + break; + case Yytoken.TYPE_LEFT_BRACE: + statusStack.removeFirst(); + key = (String) valueStack.removeFirst(); + parent = (Map) valueStack.getFirst(); + Map newObject = createObjectContainer(containerFactory); + parent.put(key, newObject); + status = S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + valueStack.addFirst(newObject); + break; + default: + status = S_IN_ERROR; + } + break; + + case S_IN_ARRAY: + switch (token.type) + { + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + List val = (List) valueStack.getFirst(); + val.add(token.value); + break; + case Yytoken.TYPE_RIGHT_SQUARE: + if (valueStack.size() > 1) + { + statusStack.removeFirst(); + valueStack.removeFirst(); + status = peekStatus(statusStack); + } + else + { + status = S_IN_FINISHED_VALUE; + } + break; + case Yytoken.TYPE_LEFT_BRACE: + val = (List) valueStack.getFirst(); + Map newObject = createObjectContainer(containerFactory); + val.add(newObject); + status = S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + valueStack.addFirst(newObject); + break; + case Yytoken.TYPE_LEFT_SQUARE: + val = (List) valueStack.getFirst(); + List newArray = createArrayContainer(containerFactory); + val.add(newArray); + status = S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + valueStack.addFirst(newArray); + break; + default: + status = S_IN_ERROR; + }// inner switch + break; + case S_IN_ERROR: + throw new ParseException(getPosition(), + ParseException.ERROR_UNEXPECTED_TOKEN, token); + }// switch + if (status == S_IN_ERROR) + { + throw new ParseException(getPosition(), + ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + } while (token.type != Yytoken.TYPE_EOF); + } catch (IOException ie) + { + throw ie; + } + + throw new ParseException(getPosition(), + ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + + private void nextToken() throws ParseException, IOException + { + token = lexer.yylex(); + if (token == null) + token = new Yytoken(Yytoken.TYPE_EOF, null); + } + + private Map createObjectContainer(ContainerFactory containerFactory) + { + if (containerFactory == null) + return new JSONObject(); + Map m = containerFactory.createObjectContainer(); + + if (m == null) + return new JSONObject(); + return m; + } + + private List createArrayContainer(ContainerFactory containerFactory) + { + if (containerFactory == null) + return new JSONArray(); + List l = containerFactory.creatArrayContainer(); + + if (l == null) + return new JSONArray(); + return l; + } + + public void parse(String s, ContentHandler contentHandler) + throws ParseException + { + parse(s, contentHandler, false); + } + + public void parse(String s, ContentHandler contentHandler, + boolean isResume) throws ParseException + { + StringReader in = new StringReader(s); + try + { + parse(in, contentHandler, isResume); + } catch (IOException ie) + { + /* + * Actually it will never happen. + */ + throw new ParseException(-1, + ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); + } + } + + public void parse(Reader in, ContentHandler contentHandler) + throws IOException, ParseException + { + parse(in, contentHandler, false); + } + + /** + * Stream processing of JSON text. + * + * @see ContentHandler + * + * @param in + * @param contentHandler + * @param isResume + * - Indicates if it continues previous parsing operation. If set to + * true, resume parsing the old stream, and parameter 'in' will be + * ignored. If this method is called for the first time in this + * instance, isResume will be ignored. + * + * @throws IOException + * @throws ParseException + */ + public void parse(Reader in, ContentHandler contentHandler, + boolean isResume) throws IOException, ParseException + { + if (!isResume) + { + reset(in); + handlerStatusStack = new LinkedList(); } - - /** - * Reset the parser to the initial state with a new character reader. - * - * @param in - The new character reader. - * @throws IOException - * @throws ParseException - */ - public void reset(Reader in){ - lexer.yyreset(in); - reset(); - } - - /** - * @return The position of the beginning of the current token. - */ - public int getPosition(){ - return lexer.getPosition(); - } - - public Object parse(String s) throws ParseException{ - return parse(s, (ContainerFactory)null); - } - - public Object parse(String s, ContainerFactory containerFactory) throws ParseException{ - StringReader in=new StringReader(s); - try{ - return parse(in, containerFactory); - } - catch(IOException ie){ - /* - * Actually it will never happen. - */ - throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); - } - } - - public Object parse(Reader in) throws IOException, ParseException{ - return parse(in, (ContainerFactory)null); - } - - /** - * Parse JSON text into java object from the input source. - * - * @param in - * @param containerFactory - Use this factory to createyour own JSON object and JSON array containers. - * @return Instance of the following: - * org.json.simple.JSONObject, - * org.json.simple.JSONArray, - * java.lang.String, - * java.lang.Number, - * java.lang.Boolean, - * null - * - * @throws IOException - * @throws ParseException - */ - public Object parse(Reader in, ContainerFactory containerFactory) throws IOException, ParseException{ - reset(in); - LinkedList statusStack = new LinkedList(); - LinkedList valueStack = new LinkedList(); - - try{ - do{ - nextToken(); - switch(status){ - case S_INIT: - switch(token.type){ - case Yytoken.TYPE_VALUE: - status=S_IN_FINISHED_VALUE; - statusStack.addFirst(new Integer(status)); - valueStack.addFirst(token.value); - break; - case Yytoken.TYPE_LEFT_BRACE: - status=S_IN_OBJECT; - statusStack.addFirst(new Integer(status)); - valueStack.addFirst(createObjectContainer(containerFactory)); - break; - case Yytoken.TYPE_LEFT_SQUARE: - status=S_IN_ARRAY; - statusStack.addFirst(new Integer(status)); - valueStack.addFirst(createArrayContainer(containerFactory)); - break; - default: - status=S_IN_ERROR; - }//inner switch - break; - - case S_IN_FINISHED_VALUE: - if(token.type==Yytoken.TYPE_EOF) - return valueStack.removeFirst(); - else - throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); - - case S_IN_OBJECT: - switch(token.type){ - case Yytoken.TYPE_COMMA: - break; - case Yytoken.TYPE_VALUE: - if(token.value instanceof String){ - String key=(String)token.value; - valueStack.addFirst(key); - status=S_PASSED_PAIR_KEY; - statusStack.addFirst(new Integer(status)); - } - else{ - status=S_IN_ERROR; - } - break; - case Yytoken.TYPE_RIGHT_BRACE: - if(valueStack.size()>1){ - statusStack.removeFirst(); - valueStack.removeFirst(); - status=peekStatus(statusStack); - } - else{ - status=S_IN_FINISHED_VALUE; - } - break; - default: - status=S_IN_ERROR; - break; - }//inner switch - break; - - case S_PASSED_PAIR_KEY: - switch(token.type){ - case Yytoken.TYPE_COLON: - break; - case Yytoken.TYPE_VALUE: - statusStack.removeFirst(); - String key=(String)valueStack.removeFirst(); - Map parent=(Map)valueStack.getFirst(); - parent.put(key,token.value); - status=peekStatus(statusStack); - break; - case Yytoken.TYPE_LEFT_SQUARE: - statusStack.removeFirst(); - key=(String)valueStack.removeFirst(); - parent=(Map)valueStack.getFirst(); - List newArray=createArrayContainer(containerFactory); - parent.put(key,newArray); - status=S_IN_ARRAY; - statusStack.addFirst(new Integer(status)); - valueStack.addFirst(newArray); - break; - case Yytoken.TYPE_LEFT_BRACE: - statusStack.removeFirst(); - key=(String)valueStack.removeFirst(); - parent=(Map)valueStack.getFirst(); - Map newObject=createObjectContainer(containerFactory); - parent.put(key,newObject); - status=S_IN_OBJECT; - statusStack.addFirst(new Integer(status)); - valueStack.addFirst(newObject); - break; - default: - status=S_IN_ERROR; - } - break; - - case S_IN_ARRAY: - switch(token.type){ - case Yytoken.TYPE_COMMA: - break; - case Yytoken.TYPE_VALUE: - List val=(List)valueStack.getFirst(); - val.add(token.value); - break; - case Yytoken.TYPE_RIGHT_SQUARE: - if(valueStack.size()>1){ - statusStack.removeFirst(); - valueStack.removeFirst(); - status=peekStatus(statusStack); - } - else{ - status=S_IN_FINISHED_VALUE; - } - break; - case Yytoken.TYPE_LEFT_BRACE: - val=(List)valueStack.getFirst(); - Map newObject=createObjectContainer(containerFactory); - val.add(newObject); - status=S_IN_OBJECT; - statusStack.addFirst(new Integer(status)); - valueStack.addFirst(newObject); - break; - case Yytoken.TYPE_LEFT_SQUARE: - val=(List)valueStack.getFirst(); - List newArray=createArrayContainer(containerFactory); - val.add(newArray); - status=S_IN_ARRAY; - statusStack.addFirst(new Integer(status)); - valueStack.addFirst(newArray); - break; - default: - status=S_IN_ERROR; - }//inner switch - break; - case S_IN_ERROR: - throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); - }//switch - if(status==S_IN_ERROR){ - throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); - } - }while(token.type!=Yytoken.TYPE_EOF); - } - catch(IOException ie){ - throw ie; - } - - throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); - } - - private void nextToken() throws ParseException, IOException{ - token = lexer.yylex(); - if(token == null) - token = new Yytoken(Yytoken.TYPE_EOF, null); - } - - private Map createObjectContainer(ContainerFactory containerFactory){ - if(containerFactory == null) - return new JSONObject(); - Map m = containerFactory.createObjectContainer(); - - if(m == null) - return new JSONObject(); - return m; - } - - private List createArrayContainer(ContainerFactory containerFactory){ - if(containerFactory == null) - return new JSONArray(); - List l = containerFactory.creatArrayContainer(); - - if(l == null) - return new JSONArray(); - return l; - } - - public void parse(String s, ContentHandler contentHandler) throws ParseException{ - parse(s, contentHandler, false); - } - - public void parse(String s, ContentHandler contentHandler, boolean isResume) throws ParseException{ - StringReader in=new StringReader(s); - try{ - parse(in, contentHandler, isResume); - } - catch(IOException ie){ - /* - * Actually it will never happen. - */ - throw new ParseException(-1, ParseException.ERROR_UNEXPECTED_EXCEPTION, ie); - } - } - - public void parse(Reader in, ContentHandler contentHandler) throws IOException, ParseException{ - parse(in, contentHandler, false); - } - - /** - * Stream processing of JSON text. - * - * @see ContentHandler - * - * @param in - * @param contentHandler - * @param isResume - Indicates if it continues previous parsing operation. - * If set to true, resume parsing the old stream, and parameter 'in' will be ignored. - * If this method is called for the first time in this instance, isResume will be ignored. - * - * @throws IOException - * @throws ParseException - */ - public void parse(Reader in, ContentHandler contentHandler, boolean isResume) throws IOException, ParseException{ - if(!isResume){ - reset(in); - handlerStatusStack = new LinkedList(); - } - else{ - if(handlerStatusStack == null){ - isResume = false; - reset(in); - handlerStatusStack = new LinkedList(); - } - } - - LinkedList statusStack = handlerStatusStack; - - try{ - do{ - switch(status){ - case S_INIT: - contentHandler.startJSON(); - nextToken(); - switch(token.type){ - case Yytoken.TYPE_VALUE: - status=S_IN_FINISHED_VALUE; - statusStack.addFirst(new Integer(status)); - if(!contentHandler.primitive(token.value)) - return; - break; - case Yytoken.TYPE_LEFT_BRACE: - status=S_IN_OBJECT; - statusStack.addFirst(new Integer(status)); - if(!contentHandler.startObject()) - return; - break; - case Yytoken.TYPE_LEFT_SQUARE: - status=S_IN_ARRAY; - statusStack.addFirst(new Integer(status)); - if(!contentHandler.startArray()) - return; - break; - default: - status=S_IN_ERROR; - }//inner switch - break; - - case S_IN_FINISHED_VALUE: - nextToken(); - if(token.type==Yytoken.TYPE_EOF){ - contentHandler.endJSON(); - status = S_END; - return; - } - else{ - status = S_IN_ERROR; - throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); - } - - case S_IN_OBJECT: - nextToken(); - switch(token.type){ - case Yytoken.TYPE_COMMA: - break; - case Yytoken.TYPE_VALUE: - if(token.value instanceof String){ - String key=(String)token.value; - status=S_PASSED_PAIR_KEY; - statusStack.addFirst(new Integer(status)); - if(!contentHandler.startObjectEntry(key)) - return; - } - else{ - status=S_IN_ERROR; - } - break; - case Yytoken.TYPE_RIGHT_BRACE: - if(statusStack.size()>1){ - statusStack.removeFirst(); - status=peekStatus(statusStack); - } - else{ - status=S_IN_FINISHED_VALUE; - } - if(!contentHandler.endObject()) - return; - break; - default: - status=S_IN_ERROR; - break; - }//inner switch - break; - - case S_PASSED_PAIR_KEY: - nextToken(); - switch(token.type){ - case Yytoken.TYPE_COLON: - break; - case Yytoken.TYPE_VALUE: - statusStack.removeFirst(); - status=peekStatus(statusStack); - if(!contentHandler.primitive(token.value)) - return; - if(!contentHandler.endObjectEntry()) - return; - break; - case Yytoken.TYPE_LEFT_SQUARE: - statusStack.removeFirst(); - statusStack.addFirst(new Integer(S_IN_PAIR_VALUE)); - status=S_IN_ARRAY; - statusStack.addFirst(new Integer(status)); - if(!contentHandler.startArray()) - return; - break; - case Yytoken.TYPE_LEFT_BRACE: - statusStack.removeFirst(); - statusStack.addFirst(new Integer(S_IN_PAIR_VALUE)); - status=S_IN_OBJECT; - statusStack.addFirst(new Integer(status)); - if(!contentHandler.startObject()) - return; - break; - default: - status=S_IN_ERROR; - } - break; - - case S_IN_PAIR_VALUE: - /* - * S_IN_PAIR_VALUE is just a marker to indicate the end of an object entry, it doesn't proccess any token, - * therefore delay consuming token until next round. - */ - statusStack.removeFirst(); - status = peekStatus(statusStack); - if(!contentHandler.endObjectEntry()) - return; - break; - - case S_IN_ARRAY: - nextToken(); - switch(token.type){ - case Yytoken.TYPE_COMMA: - break; - case Yytoken.TYPE_VALUE: - if(!contentHandler.primitive(token.value)) - return; - break; - case Yytoken.TYPE_RIGHT_SQUARE: - if(statusStack.size()>1){ - statusStack.removeFirst(); - status=peekStatus(statusStack); - } - else{ - status=S_IN_FINISHED_VALUE; - } - if(!contentHandler.endArray()) - return; - break; - case Yytoken.TYPE_LEFT_BRACE: - status=S_IN_OBJECT; - statusStack.addFirst(new Integer(status)); - if(!contentHandler.startObject()) - return; - break; - case Yytoken.TYPE_LEFT_SQUARE: - status=S_IN_ARRAY; - statusStack.addFirst(new Integer(status)); - if(!contentHandler.startArray()) - return; - break; - default: - status=S_IN_ERROR; - }//inner switch - break; - - case S_END: - return; - - case S_IN_ERROR: - throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); - }//switch - if(status==S_IN_ERROR){ - throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); - } - }while(token.type!=Yytoken.TYPE_EOF); - } - catch(IOException ie){ - status = S_IN_ERROR; - throw ie; - } - catch(ParseException pe){ - status = S_IN_ERROR; - throw pe; - } - catch(RuntimeException re){ - status = S_IN_ERROR; - throw re; - } - catch(Error e){ - status = S_IN_ERROR; - throw e; - } - - status = S_IN_ERROR; - throw new ParseException(getPosition(), ParseException.ERROR_UNEXPECTED_TOKEN, token); - } + else + { + if (handlerStatusStack == null) + { + isResume = false; + reset(in); + handlerStatusStack = new LinkedList(); + } + } + + LinkedList statusStack = handlerStatusStack; + + try + { + do + { + switch (status) + { + case S_INIT: + contentHandler.startJSON(); + nextToken(); + switch (token.type) + { + case Yytoken.TYPE_VALUE: + status = S_IN_FINISHED_VALUE; + statusStack.addFirst(new Integer(status)); + if (!contentHandler.primitive(token.value)) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + status = S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if (!contentHandler.startObject()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + status = S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if (!contentHandler.startArray()) + return; + break; + default: + status = S_IN_ERROR; + }// inner switch + break; + + case S_IN_FINISHED_VALUE: + nextToken(); + if (token.type == Yytoken.TYPE_EOF) + { + contentHandler.endJSON(); + status = S_END; + return; + } + else + { + status = S_IN_ERROR; + throw new ParseException(getPosition(), + ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + + case S_IN_OBJECT: + nextToken(); + switch (token.type) + { + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if (token.value instanceof String) + { + String key = (String) token.value; + status = S_PASSED_PAIR_KEY; + statusStack.addFirst(new Integer(status)); + if (!contentHandler.startObjectEntry(key)) + return; + } + else + { + status = S_IN_ERROR; + } + break; + case Yytoken.TYPE_RIGHT_BRACE: + if (statusStack.size() > 1) + { + statusStack.removeFirst(); + status = peekStatus(statusStack); + } + else + { + status = S_IN_FINISHED_VALUE; + } + if (!contentHandler.endObject()) + return; + break; + default: + status = S_IN_ERROR; + break; + }// inner switch + break; + + case S_PASSED_PAIR_KEY: + nextToken(); + switch (token.type) + { + case Yytoken.TYPE_COLON: + break; + case Yytoken.TYPE_VALUE: + statusStack.removeFirst(); + status = peekStatus(statusStack); + if (!contentHandler.primitive(token.value)) + return; + if (!contentHandler.endObjectEntry()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + statusStack.removeFirst(); + statusStack.addFirst(new Integer(S_IN_PAIR_VALUE)); + status = S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if (!contentHandler.startArray()) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + statusStack.removeFirst(); + statusStack.addFirst(new Integer(S_IN_PAIR_VALUE)); + status = S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if (!contentHandler.startObject()) + return; + break; + default: + status = S_IN_ERROR; + } + break; + + case S_IN_PAIR_VALUE: + /* + * S_IN_PAIR_VALUE is just a marker to indicate the end of an object entry, it doesn't proccess any token, + * therefore delay consuming token until next round. + */ + statusStack.removeFirst(); + status = peekStatus(statusStack); + if (!contentHandler.endObjectEntry()) + return; + break; + + case S_IN_ARRAY: + nextToken(); + switch (token.type) + { + case Yytoken.TYPE_COMMA: + break; + case Yytoken.TYPE_VALUE: + if (!contentHandler.primitive(token.value)) + return; + break; + case Yytoken.TYPE_RIGHT_SQUARE: + if (statusStack.size() > 1) + { + statusStack.removeFirst(); + status = peekStatus(statusStack); + } + else + { + status = S_IN_FINISHED_VALUE; + } + if (!contentHandler.endArray()) + return; + break; + case Yytoken.TYPE_LEFT_BRACE: + status = S_IN_OBJECT; + statusStack.addFirst(new Integer(status)); + if (!contentHandler.startObject()) + return; + break; + case Yytoken.TYPE_LEFT_SQUARE: + status = S_IN_ARRAY; + statusStack.addFirst(new Integer(status)); + if (!contentHandler.startArray()) + return; + break; + default: + status = S_IN_ERROR; + }// inner switch + break; + + case S_END: + return; + + case S_IN_ERROR: + throw new ParseException(getPosition(), + ParseException.ERROR_UNEXPECTED_TOKEN, token); + }// switch + if (status == S_IN_ERROR) + { + throw new ParseException(getPosition(), + ParseException.ERROR_UNEXPECTED_TOKEN, token); + } + } while (token.type != Yytoken.TYPE_EOF); + } catch (IOException ie) + { + status = S_IN_ERROR; + throw ie; + } catch (ParseException pe) + { + status = S_IN_ERROR; + throw pe; + } catch (RuntimeException re) + { + status = S_IN_ERROR; + throw re; + } catch (Error e) + { + status = S_IN_ERROR; + throw e; + } + + status = S_IN_ERROR; + throw new ParseException(getPosition(), + ParseException.ERROR_UNEXPECTED_TOKEN, token); + } } diff --git a/src/org/json/simple/parser/Yylex.java b/src/org/json/simple/parser/Yylex.java index dc36fa2..41569d6 100644 --- a/src/org/json/simple/parser/Yylex.java +++ b/src/org/json/simple/parser/Yylex.java @@ -96,8 +96,8 @@ class Yylex { int j = offset; /* index in unpacked array */ int l = packed.length(); while (i < l) { - int high = packed.charAt(i++) << 16; - result[j++] = high | packed.charAt(i++); + int high = packed.codePointAt(i++) << 16; + result[j++] = high | packed.codePointAt(i++); } return j; } @@ -492,13 +492,15 @@ int getPosition(){ /** - * Resumes scanning until the next regular expression is matched, - * the end of input is encountered or an I/O-Error occurs. + * Resumes scanning until the next regular expression is matched, the end of + * input is encountered or an I/O-Error occurs. * - * @return the next token - * @exception java.io.IOException if any I/O-Error occurs + * @return the next token + * @exception java.io.IOException + * if any I/O-Error occurs */ - public Yytoken yylex() throws java.io.IOException, ParseException { + public Yytoken yylex() throws java.io.IOException, ParseException + { int zzInput; int zzAction; @@ -506,61 +508,70 @@ int getPosition(){ int zzCurrentPosL; int zzMarkedPosL; int zzEndReadL = zzEndRead; - char [] zzBufferL = zzBuffer; - char [] zzCMapL = ZZ_CMAP; + char[] zzBufferL = zzBuffer; + char[] zzCMapL = ZZ_CMAP; - int [] zzTransL = ZZ_TRANS; - int [] zzRowMapL = ZZ_ROWMAP; - int [] zzAttrL = ZZ_ATTRIBUTE; + int[] zzTransL = ZZ_TRANS; + int[] zzRowMapL = ZZ_ROWMAP; + int[] zzAttrL = ZZ_ATTRIBUTE; - while (true) { + while (true) + { zzMarkedPosL = zzMarkedPos; - yychar+= zzMarkedPosL-zzStartRead; + yychar += zzMarkedPosL - zzStartRead; zzAction = -1; zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; - + zzState = ZZ_LEXSTATE[zzLexicalState]; + zzForAction: + { + while (true) + { - zzForAction: { - while (true) { - if (zzCurrentPosL < zzEndReadL) zzInput = zzBufferL[zzCurrentPosL++]; - else if (zzAtEOF) { + else if (zzAtEOF) + { zzInput = YYEOF; break zzForAction; } - else { + else + { // store back cached positions - zzCurrentPos = zzCurrentPosL; - zzMarkedPos = zzMarkedPosL; + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; boolean eof = zzRefill(); // get translated positions and possibly new buffer - zzCurrentPosL = zzCurrentPos; - zzMarkedPosL = zzMarkedPos; - zzBufferL = zzBuffer; - zzEndReadL = zzEndRead; - if (eof) { + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) + { zzInput = YYEOF; break zzForAction; } - else { + else + { zzInput = zzBufferL[zzCurrentPosL++]; } } - int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput] ]; - if (zzNext == -1) break zzForAction; + int zzNext = zzTransL[zzRowMapL[zzState] + zzCMapL[zzInput]]; + if (zzNext == -1) + break zzForAction; zzState = zzNext; int zzAttributes = zzAttrL[zzState]; - if ( (zzAttributes & 1) == 1 ) { + if ((zzAttributes & 1) == 1) + { zzAction = zzState; zzMarkedPosL = zzCurrentPosL; - if ( (zzAttributes & 8) == 8 ) break zzForAction; + if ((zzAttributes & 8) == 8) + break zzForAction; } } @@ -568,118 +579,177 @@ int getPosition(){ // store back cached position zzMarkedPos = zzMarkedPosL; - - switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { - case 11: - { sb.append(yytext()); - } - case 25: break; - case 4: - { sb = null; sb = new StringBuffer(); yybegin(STRING_BEGIN); - } - case 26: break; - case 16: - { sb.append('\b'); - } - case 27: break; - case 6: - { return new Yytoken(Yytoken.TYPE_RIGHT_BRACE,null); - } - case 28: break; - case 23: - { Boolean val=Boolean.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); - } - case 29: break; - case 22: - { return new Yytoken(Yytoken.TYPE_VALUE, null); - } - case 30: break; - case 13: - { yybegin(YYINITIAL);return new Yytoken(Yytoken.TYPE_VALUE, sb.toString()); - } - case 31: break; - case 12: - { sb.append('\\'); - } - case 32: break; - case 21: - { Double val=Double.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); - } - case 33: break; - case 1: - { throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_CHAR, new Character(yycharat(0))); - } - case 34: break; - case 8: - { return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE,null); - } - case 35: break; - case 19: - { sb.append('\r'); - } - case 36: break; - case 15: - { sb.append('/'); - } - case 37: break; - case 10: - { return new Yytoken(Yytoken.TYPE_COLON,null); - } - case 38: break; - case 14: - { sb.append('"'); - } - case 39: break; - case 5: - { return new Yytoken(Yytoken.TYPE_LEFT_BRACE,null); - } - case 40: break; - case 17: - { sb.append('\f'); - } - case 41: break; - case 24: - { try{ - int ch=Integer.parseInt(yytext().substring(2),16); - sb.append((char)ch); - } - catch(Exception e){ - throw new ParseException(yychar, ParseException.ERROR_UNEXPECTED_EXCEPTION, e); - } - } - case 42: break; - case 20: - { sb.append('\t'); - } - case 43: break; - case 7: - { return new Yytoken(Yytoken.TYPE_LEFT_SQUARE,null); - } - case 44: break; - case 2: - { Long val=Long.valueOf(yytext()); return new Yytoken(Yytoken.TYPE_VALUE, val); - } - case 45: break; - case 18: - { sb.append('\n'); - } - case 46: break; - case 9: - { return new Yytoken(Yytoken.TYPE_COMMA,null); - } - case 47: break; - case 3: - { - } - case 48: break; - default: - if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { - zzAtEOF = true; - return null; - } - else { - zzScanError(ZZ_NO_MATCH); - } + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) + { + case 11: + { + sb.append(yytext()); + } + case 25: + break; + case 4: + { + sb = null; + sb = new StringBuffer(); + yybegin(STRING_BEGIN); + } + case 26: + break; + case 16: + { + sb.append('\b'); + } + case 27: + break; + case 6: + { + return new Yytoken(Yytoken.TYPE_RIGHT_BRACE, null); + } + case 28: + break; + case 23: + { + Boolean val = Boolean.valueOf(yytext()); + return new Yytoken(Yytoken.TYPE_VALUE, val); + } + case 29: + break; + case 22: + { + return new Yytoken(Yytoken.TYPE_VALUE, null); + } + case 30: + break; + case 13: + { + yybegin(YYINITIAL); + return new Yytoken(Yytoken.TYPE_VALUE, sb.toString()); + } + case 31: + break; + case 12: + { + sb.append('\\'); + } + case 32: + break; + case 21: + { + Double val = Double.valueOf(yytext()); + return new Yytoken(Yytoken.TYPE_VALUE, val); + } + case 33: + break; + case 1: + { + throw new ParseException(yychar, + ParseException.ERROR_UNEXPECTED_CHAR, + new Character(yycharat(0))); + } + case 34: + break; + case 8: + { + return new Yytoken(Yytoken.TYPE_RIGHT_SQUARE, null); + } + case 35: + break; + case 19: + { + sb.append('\r'); + } + case 36: + break; + case 15: + { + sb.append('/'); + } + case 37: + break; + case 10: + { + return new Yytoken(Yytoken.TYPE_COLON, null); + } + case 38: + break; + case 14: + { + sb.append('"'); + } + case 39: + break; + case 5: + { + return new Yytoken(Yytoken.TYPE_LEFT_BRACE, null); + } + case 40: + break; + case 17: + { + sb.append('\f'); + } + case 41: + break; + case 24: + { + try + { + int ch = Integer.parseInt(yytext().substring(2), 16); + sb.append((char) ch); + } catch (Exception e) + { + throw new ParseException(yychar, + ParseException.ERROR_UNEXPECTED_EXCEPTION, e); + } + } + case 42: + break; + case 20: + { + sb.append('\t'); + } + case 43: + break; + case 7: + { + return new Yytoken(Yytoken.TYPE_LEFT_SQUARE, null); + } + case 44: + break; + case 2: + { + Long val = Long.valueOf(yytext()); + return new Yytoken(Yytoken.TYPE_VALUE, val); + } + case 45: + break; + case 18: + { + sb.append('\n'); + } + case 46: + break; + case 9: + { + return new Yytoken(Yytoken.TYPE_COMMA, null); + } + case 47: + break; + case 3: + { + } + case 48: + break; + default: + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) + { + zzAtEOF = true; + return null; + } + else + { + zzScanError(ZZ_NO_MATCH); + } } } } diff --git a/swingjs/SwingJS-site.zip b/swingjs/SwingJS-site.zip index 24dc06f..8754b6b 100644 Binary files a/swingjs/SwingJS-site.zip and b/swingjs/SwingJS-site.zip differ diff --git a/swingjs/net.sf.j2s.core.jar b/swingjs/net.sf.j2s.core.jar index ce4c218..764c0b4 100644 Binary files a/swingjs/net.sf.j2s.core.jar and b/swingjs/net.sf.j2s.core.jar differ