/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG and others. * 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.internal.ui.ridgets.swt; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.layout.AbstractColumnLayout; import org.eclipse.jface.layout.TableColumnLayout; import org.eclipse.jface.layout.TreeColumnLayout; import org.eclipse.jface.util.Util; import org.eclipse.jface.viewers.ColumnLayoutData; import org.eclipse.jface.viewers.ColumnPixelData; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.TableLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.Widget; import org.eclipse.riena.ui.swt.layout.DpiTableColumnLayout; import org.eclipse.riena.ui.swt.layout.DpiTableLayout; /** * Helper class for layouting columns in a Table or Tree. */ public final class ColumnUtils { /** * Adjust the column widths of the given {@link Table}, according to the provided {@link ColumnLayoutData} array. The layout managers supported by this * method are: TableLayout, TableColumnLayout, other. * <p> * If the number of entries in {@code columnWidths} does not match the number of columns in the widget, the available width will be distributed equally to * all columns. The same will happen if {@code columnWidths} is null. Future width changes are not taken into account. * <p> * If the control has a TableLayout, the ColumnLayoutData will be used directy. Future width changes are not taken into account. * <p> * If the control's parent has a TableColumnLayout, the ColumnLayoutData will be used directly. If control and the parent have no layout, and parent only * contains the control, then a TableColumnLayout is used as well. Future width changes ARE taken into account. * <p> * In any other case: the available table width <i>at the time when this meethod is invoked</i> is distributed directly to the columns (via setWidth(...)). * Future width changes are not taken into account. * * @param control * a Table instance; never null * @param columnWidths * an Array with width information, one instance per column. The array may be null, in that case the available width is distributed equally to * all columns */ public static void applyColumnWidths(final Table control, final ColumnLayoutData[] columnWidths) { Assert.isNotNull(control); applyColumnWidths(new TableWrapper(control), columnWidths); } /** * Adjust the column widths of the given {@link Tree}, according to the provided {@link ColumnLayoutData} array. The layout managers supported by this * method are: TableLayout, TableColumnLayout, other. * <p> * If the number of entries in {@code columnWidths} does not match the number of columns in the widget, the available width will be distributed equally to * all columns. The same will happen if {@code columnWidths} is null. Future width changes are not taken into account. * <p> * If the control has a TableLayout, the ColumnLayoutData will be used directy. Future width changes are not taken into account. * <p> * If the control's parent has a TreeColumnLayout, the ColumnLayoutData will be used directly. If control and the parent have no layout, and parent only * contains the control, then a TreeColumnLayout is used as well. Future width changes ARE taken into account. * <p> * In any other case: the available table width <i>at the time when this meethod is invoked</i> is distributed directly to the columns (via setWidth(...)). * Future width changes are not taken into account. * * @param control * a Tree instance; never null * @param columnWidths * an Array with width information, one instance per column. The array may be null, in that case the available width is distributed equally to * all columns */ public static void applyColumnWidths(final Tree control, final ColumnLayoutData[] columnWidths) { Assert.isNotNull(control); applyColumnWidths(new TreeWrapper(control), columnWidths); } public static void applyColumnWidths(final ITableTreeWrapper controlWrapper, final ColumnLayoutData[] columnWidths) { Assert.isNotNull(controlWrapper); applyColumnWidths(controlWrapper, columnWidths, controlWrapper.getColumnCount()); } /** * Deep copy an array of {@link ColumnLayoutData} instances. * * @param source * an Array; may be null * @return a deep copy of the array or null (if {@code source} is null) * @throws RuntimeException * if the array contains types other than subclasses of {@link ColumnLayoutData} */ public static ColumnLayoutData[] copyWidths(final Object[] source) { ColumnLayoutData[] result = null; if (source != null) { result = new ColumnLayoutData[source.length]; for (int i = 0; i < source.length; i++) { if (source[i] instanceof ColumnPixelData) { final ColumnPixelData data = (ColumnPixelData) source[i]; result[i] = new ColumnPixelData(data.width, data.resizable, data.addTrim); } else if (source[i] instanceof ColumnWeightData) { final ColumnWeightData data = (ColumnWeightData) source[i]; result[i] = new ColumnWeightData(data.weight, data.minimumWidth, data.resizable); } else { final String msg = String.format("Unsupported type in column #%d: %s", i, source[i]); //$NON-NLS-1$ throw new IllegalArgumentException(msg); } } } return result; } // helping methods ////////////////// private static void applyColumnWidths(final ITableTreeWrapper controlWrapper, final ColumnLayoutData[] columnWidths, final int expectedCols) { final ColumnLayoutData[] columnData; if (columnWidths == null || columnWidths.length != expectedCols) { columnData = new ColumnLayoutData[expectedCols]; for (int i = 0; i < expectedCols; i++) { columnData[i] = new ColumnWeightData(1, true); } } else { columnData = columnWidths; } final Composite control = controlWrapper.getControl(); final Composite parent = control.getParent(); if (control.getLayout() instanceof TableLayout) { // TableLayout: use columnData instance for each column, apply to control final TableLayout layout = new DpiTableLayout(); for (int index = 0; index < expectedCols; index++) { layout.addColumnData(columnData[index]); } control.setLayout(layout); parent.layout(true, true); } else if ((control instanceof Tree && control.getLayout() == null && parent.getLayout() == null && parent.getChildren().length == 1) || parent.getLayout() instanceof TreeColumnLayout) { // TreeColumnLayout: use columnData instance for each column, apply to parent final AbstractColumnLayout layout = getOrCreateTreeColumnLayout(parent); for (int index = 0; index < expectedCols; index++) { final Widget column = controlWrapper.getColumn(index); layout.setColumnData(column, columnData[index]); // Workaround for Bug 204712 if (column instanceof TreeColumn) { ((TreeColumn) column).setResizable(columnData[index].resizable); } } parent.setLayout(layout); parent.layout(); } else if ((control instanceof Table && control.getLayout() == null && parent.getLayout() == null && parent.getChildren().length == 1) || parent.getLayout() instanceof TableColumnLayout || parent.getLayout() instanceof DpiTableColumnLayout) { // TableColumnLayout: use columnData instance for each column, apply to parent final AbstractColumnLayout layout = getOrCreateTableColumnLayout(parent); for (int index = 0; index < expectedCols; index++) { final Widget column = controlWrapper.getColumn(index); layout.setColumnData(column, columnData[index]); // Workaround for Bug 204712 if (column instanceof TableColumn) { ((TableColumn) column).setResizable(columnData[index].resizable); } } parent.setLayout(layout); parent.layout(); } else { // Other: manually compute width for each column, apply to TableColumn // 1. absolute widths: apply absolute widths first // 2. relative widths: // compute remaining width and total weight; for each column: apply // the largest value of either the relative width or the minimum width int widthRemaining = control.getClientArea().width; // Workaround for Bug 301682 if (widthRemaining == 0) { parent.layout(); widthRemaining = control.getClientArea().width; } int totalWeights = 0; for (int index = 0; index < expectedCols; index++) { final ColumnLayoutData data = columnData[index]; if (data instanceof ColumnPixelData) { final ColumnPixelData pixelData = (ColumnPixelData) data; int width = pixelData.width; if (pixelData.addTrim) { width = width + getColumnTrim(); } configureColumn(controlWrapper, index, width, data.resizable); widthRemaining = widthRemaining - width; } else if (data instanceof ColumnWeightData) { totalWeights = totalWeights + ((ColumnWeightData) data).weight; } } final int slice = totalWeights > 0 ? Math.max(0, widthRemaining / totalWeights) : 0; for (int index = 0; index < expectedCols; index++) { if (columnData[index] instanceof ColumnWeightData) { final ColumnWeightData data = (ColumnWeightData) columnData[index]; final int width = Math.max(data.minimumWidth, data.weight * slice); configureColumn(controlWrapper, index, width, data.resizable); } } } } private static void configureColumn(final ITableTreeWrapper controlWrapper, final int index, final int width, final boolean resizable) { controlWrapper.setWidth(index, width); controlWrapper.setResizable(index, resizable); } private static int getColumnTrim() { int result = 3; if (Util.isWindows()) { result = 4; } else if (Util.isMac()) { result = 24; } return result; } /* * Workaround for Bug 295404 - reusing existing TableColumnLayout */ private static AbstractColumnLayout getOrCreateTableColumnLayout(final Composite parent) { AbstractColumnLayout result; if (parent.getLayout() instanceof TableColumnLayout) { result = (TableColumnLayout) parent.getLayout(); } else if (parent.getLayout() instanceof DpiTableColumnLayout) { result = (DpiTableColumnLayout) parent.getLayout(); } else { result = new DpiTableColumnLayout(); } return result; } /* * Workaround for Bug 295404 - reusing existing TreeColumnLayout */ private static TreeColumnLayout getOrCreateTreeColumnLayout(final Composite parent) { TreeColumnLayout result; if (parent.getLayout() instanceof TreeColumnLayout) { result = (TreeColumnLayout) parent.getLayout(); } else { result = new TreeColumnLayout(); } return result; } private ColumnUtils() { // utility class } }