| /******************************************************************************* |
| * Copyright (c) 2010 BSI Business Systems Integration AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * BSI Business Systems Integration AG - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.scout.rt.ui.swt; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.scout.commons.CompositeObject; |
| import org.eclipse.scout.commons.logger.IScoutLogger; |
| import org.eclipse.scout.commons.logger.ScoutLogManager; |
| import org.eclipse.scout.rt.client.ui.form.fields.IFormField; |
| import org.eclipse.scout.rt.ui.swt.basic.SwtScoutComposite; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Layout; |
| import org.eclipse.ui.forms.widgets.ILayoutExtension; |
| |
| /** |
| * Dynamic layout using logical grid data {@link LogicalGridData} to arrange |
| * fields. The grid data per field can be passed when adding the component to |
| * the container or set as client property with name {@link LogicalGridData#CLIENT_PROPERTY_NAME}. |
| */ |
| public class LogicalGridLayout extends Layout implements ILayoutExtension { |
| |
| private static final IScoutLogger LOG = ScoutLogManager.getLogger(LogicalGridLayout.class); |
| |
| public static final int MIN = 0; |
| public static final int PREF = 1; |
| public static final int MAX = 2; |
| |
| public static final float EPS = 1E-6f; |
| |
| private boolean m_debug; |
| private final int m_hgap; |
| private final int m_vgap; |
| private final int m_minWidth; |
| private LogicalGridLayoutInfo m_info; |
| private CompositeObject m_infoCacheKey; |
| |
| public LogicalGridLayout(int hgap, int vgap) { |
| this(hgap, vgap, -1); |
| } |
| |
| public LogicalGridLayout(int hgap, int vgap, int minWidth) { |
| m_hgap = hgap; |
| m_vgap = vgap; |
| m_minWidth = minWidth; |
| } |
| |
| public LogicalGridLayoutInfo getInfo() { |
| return m_info; |
| } |
| |
| @Override |
| public int computeMinimumWidth(Composite parent, boolean changed) { |
| return computeMinimumSize(parent, changed).x; |
| } |
| |
| public Point computeMinimumSize(Composite parent, boolean changed) { |
| return computeSize(parent, changed, SWT.DEFAULT, SWT.DEFAULT, MIN); |
| } |
| |
| @Override |
| protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { |
| return computeSize(composite, flushCache, wHint, hHint, PREF); |
| } |
| |
| @Override |
| public int computeMaximumWidth(Composite parent, boolean changed) { |
| return computeSize(parent, changed, SWT.DEFAULT, SWT.DEFAULT, MAX).x; |
| } |
| |
| public Point computeSize(Composite composite, boolean changed, int wHint, int hHint, int sizeFlag) { |
| if (changed) { |
| m_info = null; |
| } |
| if (wHint <= 0) { |
| wHint = SWT.DEFAULT; |
| } |
| validateLayout(composite, wHint, changed); |
| |
| Point min = new Point(0, 0); |
| Point pref = new Point(0, 0); |
| Point max = new Point(0, 0); |
| // w |
| int useCount = 0; |
| for (int i = 0; i < m_info.cols; i++) { |
| if (useCount > 0) { |
| min.x = min.x + m_hgap; |
| pref.x = pref.x + m_hgap; |
| max.x = max.x + m_hgap; |
| } |
| min.x = min.x + m_info.width[i][MIN]; |
| pref.x = pref.x + m_info.width[i][PREF]; |
| max.x = max.x + m_info.width[i][MAX]; |
| useCount++; |
| } |
| // h |
| useCount = 0; |
| for (int i = 0; i < m_info.rows; i++) { |
| if (useCount > 0) { |
| min.y = min.y + m_vgap; |
| pref.y = pref.y + m_vgap; |
| max.y = max.y + m_vgap; |
| } |
| min.y = min.y + m_info.height[i][MIN]; |
| pref.y = pref.y + m_info.height[i][PREF]; |
| max.y = max.y + m_info.height[i][MAX]; |
| useCount++; |
| } |
| Point size = new Point(0, 0); |
| switch (sizeFlag) { |
| case MIN: |
| if (m_minWidth < 0) { |
| size.x = min.x; |
| } |
| else { |
| size.x = m_minWidth; |
| } |
| size.y = min.y; |
| break; |
| case MAX: |
| size.x = min.x; |
| size.y = min.y; |
| break; |
| default: |
| // adjust width |
| if (wHint == SWT.DEFAULT) { |
| size.x = pref.x; |
| } |
| else { |
| size.x = wHint; |
| size.x = Math.min(max.x, size.x); |
| size.x = Math.max(min.x, size.x); |
| } |
| // adjust height |
| if (hHint == SWT.DEFAULT) { |
| size.y = pref.y; |
| } |
| else { |
| size.y = hHint; |
| size.y = Math.min(max.y, size.y); |
| size.y = Math.max(min.y, size.y); |
| } |
| break; |
| } |
| return size; |
| } |
| |
| @Override |
| protected void layout(Composite parent, boolean flushCache) { |
| if (flushCache) { |
| m_info = null; |
| } |
| Rectangle clientArea = parent.getClientArea(); |
| validateLayout(parent, clientArea.width, flushCache); |
| Point size = new Point(clientArea.width, clientArea.height); |
| Rectangle[][] cellBounds = m_info.layoutCellBounds(size); |
| if (m_debug || LOG.isDebugEnabled()) { |
| dumpLayoutInfo(parent); |
| } |
| // bounds |
| int n = m_info.components.length; |
| for (int i = 0; i < n; i++) { |
| Control comp = m_info.components[i]; |
| LogicalGridData data = m_info.gridDatas[i]; |
| Rectangle r1 = cellBounds[data.gridy][data.gridx]; |
| Rectangle r2 = cellBounds[data.gridy + data.gridh - 1][data.gridx + data.gridw - 1]; |
| Rectangle r = r1.union(r2); |
| if (data.topInset > 0) { |
| r.y += data.topInset; |
| r.height -= data.topInset; |
| } |
| if (data.fillHorizontal && data.fillVertical) { |
| // ok |
| } |
| else { |
| Point d = new Point(m_info.componentWidths[i], m_info.componentHeights[i]); |
| if (!data.fillHorizontal) { |
| if (d.x < r.width) { |
| int delta = r.width - d.x; |
| r.width = d.x; |
| if (data.horizontalAlignment == 0) { |
| r.x += delta / 2; |
| } |
| else if (data.horizontalAlignment > 0) { |
| r.x += delta; |
| } |
| } |
| } |
| if (!data.fillVertical) { |
| if (d.y < r.height) { |
| int delta = r.height - d.y; |
| if (data.heightHint == 0) { |
| r.height = d.y; |
| } |
| else { |
| r.height = data.heightHint; |
| } |
| if (data.verticalAlignment == 0) { |
| r.y += delta / 2; |
| } |
| else if (data.verticalAlignment > 0) { |
| r.y += delta; |
| } |
| } |
| } |
| } |
| comp.setBounds(r); |
| } |
| } |
| |
| public void setDebug(boolean b) { |
| m_debug = b; |
| } |
| |
| public void dumpLayoutInfo(Composite parent) { |
| dumpLayoutInfo(parent, new PrintWriter(System.out)); |
| } |
| |
| public void dumpLayoutInfo(Composite parent, PrintWriter out) { |
| Point parentSize = parent.getSize(); |
| Rectangle[][] cellBounds = m_info.layoutCellBounds(parentSize); |
| Object field = SwtScoutComposite.getScoutModelOnWidget(parent); |
| String className = "undefined (PROP_SCOUT_OBJECT not set!)"; |
| if (field != null) { |
| className = field.getClass().getSimpleName(); |
| } |
| out.println("DUMP layout of: " + className + " compSize= " + parentSize); |
| out.println(" containerBounds = " + parent.getClientArea()); |
| out.println(" Fields ---"); |
| for (int i = 0; i < m_info.components.length; i++) { |
| Control c = m_info.components[i]; |
| LogicalGridData data = m_info.gridDatas[i]; |
| try { |
| Rectangle r1 = cellBounds[data.gridy][data.gridx]; |
| Rectangle r2 = cellBounds[data.gridy + data.gridh - 1][data.gridx + data.gridw - 1]; |
| Rectangle r = r1.union(r2); |
| String scoutObjectName = "NOT DEFINED"; |
| Object scoutObject = SwtScoutComposite.getScoutModelOnWidget(c); |
| if (scoutObject != null) { |
| scoutObjectName = scoutObject.getClass().getSimpleName(); |
| } |
| out.println(" b=" + r + " " + scoutObjectName); |
| if (scoutObject instanceof IFormField) { |
| out.println(" model grid: " + ((IFormField) scoutObject).getGridData().toString()); |
| } |
| out.println(" uiGridData: " + data.toString()); |
| } |
| catch (ArrayIndexOutOfBoundsException e) { |
| out.print("unable to print layout info of: " + data); |
| e.printStackTrace(); |
| } |
| } |
| out.flush(); |
| } |
| |
| protected void validateLayout(Composite parent, int wHint, boolean flushCache) { |
| CompositeObject newKey = new CompositeObject(parent.getSize(), wHint == SWT.DEFAULT ? (m_infoCacheKey != null ? m_infoCacheKey.getComponent(1) : wHint) : wHint); |
| //check cache key |
| if (m_info != null) { |
| if (m_infoCacheKey == null || !m_infoCacheKey.equals(newKey)) { |
| m_info = null; |
| m_infoCacheKey = null; |
| } |
| } |
| if (m_info != null && m_infoCacheKey != null) { |
| return; |
| } |
| List<Control> visibleComps = new ArrayList<>(); |
| List<LogicalGridData> visibleCons = new ArrayList<>(); |
| for (Control comp : parent.getChildren()) { |
| if (comp.getVisible() && comp.getLayoutData() instanceof LogicalGridData) { |
| visibleComps.add(comp); |
| LogicalGridData cons = (LogicalGridData) comp.getLayoutData(); |
| cons.validate(); |
| visibleCons.add(cons); |
| } |
| } |
| m_info = new LogicalGridLayoutInfo(visibleComps.toArray(new Control[visibleComps.size()]), visibleCons.toArray(new LogicalGridData[visibleCons.size()]), m_hgap, m_vgap, wHint, flushCache); |
| m_infoCacheKey = newKey; |
| } |
| |
| } |