| /** |
| * Copyright (c) 2017 Eclipse contributors 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 |
| */ |
| package org.eclipse.emf.common.ui.viewer; |
| |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.swt.events.ControlAdapter; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.TreeEvent; |
| import org.eclipse.swt.events.TreeListener; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Item; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.TableColumn; |
| import org.eclipse.swt.widgets.Tree; |
| import org.eclipse.swt.widgets.TreeColumn; |
| |
| |
| /** |
| * This utility class provides {@link #addColumnResizer(Tree)} and {@link #addColumnResizer(Table)} to create a {@link ColumnResizer.Handler} that will pack all columns to their minimal size |
| * and will respond to {@link ControlListener#controlResized(ControlEvent) control resized} events to update the column sizes automatically. |
| * If needed, the last column will be expanded beyond its minimal packed size to make it large enough such that all Tree or Table columns exactly fit the {@link Composite#getClientArea() client area}. |
| * When the contents of the {@link Tree} or {@link Table} changes, the Handler can be instructed to {@link ColumnResizer.Handler#resizeColumns() resize} the columns. |
| * |
| * @since 2.14 |
| */ |
| public final class ColumnResizer |
| { |
| private ColumnResizer() |
| { |
| throw new RuntimeException("No instances"); |
| } |
| |
| /** |
| * A handler created by {@link ColumnResizer#addColumnResizer(Tree)} or {@link ColumnResizer#addColumnResizer(Table)}. |
| * that provides the ability to manually {@link #resizeColumns() resize} the columns to their minimal packed size when the contents of the {@link Tree} or {@link Table} changes. |
| * If needed, the last column will be expanded beyond its minimal packed size to make it large enough such that all Tree or Table columns exactly fit the {@link Composite#getClientArea() client area}. |
| * The handler itself will respond to {@link ControlListener#controlResized(ControlEvent) control resized} events to update the column sizes automatically. |
| */ |
| public static abstract class Handler |
| { |
| private Handler() |
| { |
| } |
| |
| /** |
| * Resize all columns to their minimal packed size. |
| * This should be called when the contents of the {@link Tree} or {@link Table} changes. |
| */ |
| public abstract void resizeColumns(); |
| |
| /** |
| * This removes this handler from the {@link Tree} or {@link Table} to which it was added. |
| */ |
| public abstract void dispose(); |
| } |
| |
| /** |
| * Creates a handler for resizing all columns to their minimal packed size. |
| */ |
| public static Handler addColumnResizer(Tree tree) |
| { |
| return new TreeColumnResizeHandler(tree); |
| } |
| |
| /** |
| * Creates a handler for resizing all columns to their minimal packed size. |
| */ |
| public static Handler addColumnResizer(Table table) |
| { |
| return new TableColumnResizeHandler(table); |
| } |
| |
| private static class ParentHandler extends ControlAdapter implements DisposeListener, Runnable |
| { |
| private final Composite control; |
| |
| private final Composite parent; |
| |
| private boolean dispatched; |
| |
| public ParentHandler(Composite control) |
| { |
| this.control = control; |
| this.parent = control.getParent(); |
| control.addDisposeListener(this); |
| parent.addControlListener(this); |
| } |
| |
| public void run() |
| { |
| if (!parent.isDisposed() && dispatched) |
| { |
| dispatched = false; |
| parent.setRedraw(true); |
| } |
| } |
| |
| @Override |
| public void controlResized(ControlEvent e) |
| { |
| if (!dispatched) |
| { |
| parent.setRedraw(false); |
| dispatched = true; |
| parent.getDisplay().asyncExec(this); |
| } |
| } |
| |
| public void widgetDisposed(DisposeEvent e) |
| { |
| parent.removeControlListener(this); |
| } |
| |
| public void dispose() |
| { |
| run(); |
| dispatched = false; |
| control.removeDisposeListener(this); |
| parent.removeControlListener(this); |
| } |
| } |
| |
| private static abstract class ColumnResizerHandler<T extends Composite, C extends Item> extends Handler implements ControlListener |
| { |
| private final T control; |
| |
| private final ParentHandler parentHandler; |
| |
| private int clientWidth = -1; |
| |
| private List<Integer> columnWidths = Collections.emptyList(); |
| |
| private boolean resizing; |
| |
| public ColumnResizerHandler(T control) |
| { |
| this.control = control; |
| for (C column : getColumns()) |
| { |
| disableResizeable(column); |
| } |
| |
| parentHandler = new ParentHandler(control); |
| control.addControlListener(this); |
| } |
| |
| public T getControl() |
| { |
| return control; |
| } |
| |
| protected abstract boolean isEmpty(); |
| |
| protected abstract List<? extends C> getColumns(); |
| |
| protected abstract void disableResizeable(C column); |
| |
| protected abstract int getWidth(C column); |
| |
| protected abstract void setWidth(C column, int width); |
| |
| protected abstract void pack(C column); |
| |
| protected List<Integer> getColumnWidths() |
| { |
| List<? extends C> columns = getColumns(); |
| List<Integer> result = new ArrayList<Integer>(columns.size()); |
| for (C column : columns) |
| { |
| result.add(getWidth(column)); |
| } |
| return result; |
| } |
| |
| public void controlResized(ControlEvent controlEvent) |
| { |
| if (!resizing) |
| { |
| T control = getControl(); |
| Rectangle clientArea = control.getClientArea(); |
| int clientWidth = clientArea.width - clientArea.x; |
| List<Integer> columnWidths = getColumnWidths(); |
| |
| boolean inputChanged = controlEvent == null; |
| if (inputChanged || clientWidth != this.clientWidth || this.columnWidths.equals(columnWidths)) |
| { |
| try |
| { |
| resizing = true; |
| control.setRedraw(false); |
| |
| List<? extends C> columns = getColumns(); |
| for (C column : columns) |
| { |
| pack(column); |
| } |
| |
| List<Integer> packedColumnWidths = getColumnWidths(); |
| int total = 0; |
| int limit = columns.size() - 1; |
| for (int i = 0; i < limit; ++i) |
| { |
| int width = packedColumnWidths.get(i) + 10; |
| total += width; |
| setWidth(columns.get(i), width); |
| } |
| |
| int width = packedColumnWidths.get(limit); |
| if (total + width < clientWidth) |
| { |
| width = clientWidth - total; |
| } |
| setWidth(columns.get(limit), width); |
| } |
| finally |
| { |
| this.clientWidth = clientWidth; |
| this.columnWidths = getColumnWidths(); |
| control.setRedraw(true); |
| parentHandler.run(); |
| resizing = false; |
| } |
| } |
| } |
| } |
| |
| public void controlMoved(ControlEvent e) |
| { |
| } |
| |
| @Override |
| public void resizeColumns() |
| { |
| controlResized(null); |
| } |
| |
| @Override |
| public void dispose() |
| { |
| parentHandler.dispose(); |
| control.removeControlListener(this); |
| } |
| } |
| |
| private static class TreeColumnResizeHandler extends ColumnResizerHandler<Tree, TreeColumn> |
| { |
| public TreeColumnResizeHandler(Tree tree) |
| { |
| super(tree); |
| |
| class TreeStateListener implements TreeListener, Runnable |
| { |
| private boolean dispatched; |
| |
| public void run() |
| { |
| dispatched = false; |
| if (!getControl().isDisposed()) |
| { |
| resizeColumns(); |
| } |
| } |
| |
| private void dispatch() |
| { |
| if (!dispatched) |
| { |
| dispatched = true; |
| getControl().getDisplay().asyncExec(this); |
| } |
| } |
| |
| public void treeCollapsed(TreeEvent e) |
| { |
| dispatch(); |
| } |
| |
| public void treeExpanded(TreeEvent e) |
| { |
| dispatch(); |
| } |
| } |
| |
| tree.addTreeListener(new TreeStateListener()); |
| } |
| |
| @Override |
| protected List<? extends TreeColumn> getColumns() |
| { |
| return Arrays.asList(getControl().getColumns()); |
| } |
| |
| @Override |
| protected void disableResizeable(TreeColumn column) |
| { |
| column.setResizable(false); |
| } |
| |
| @Override |
| protected int getWidth(TreeColumn column) |
| { |
| return column.getWidth(); |
| } |
| |
| @Override |
| protected void setWidth(TreeColumn column, int width) |
| { |
| column.setWidth(width); |
| } |
| |
| @Override |
| protected boolean isEmpty() |
| { |
| return getControl().getItemCount() != 0; |
| } |
| |
| @Override |
| protected void pack(TreeColumn column) |
| { |
| column.pack(); |
| } |
| } |
| |
| private static class TableColumnResizeHandler extends ColumnResizerHandler<Table, TableColumn> |
| { |
| public TableColumnResizeHandler(Table table) |
| { |
| super(table); |
| } |
| |
| @Override |
| protected List<? extends TableColumn> getColumns() |
| { |
| return Arrays.asList(getControl().getColumns()); |
| } |
| |
| @Override |
| protected void disableResizeable(TableColumn column) |
| { |
| column.setResizable(false); |
| } |
| |
| @Override |
| protected int getWidth(TableColumn column) |
| { |
| return column.getWidth(); |
| } |
| |
| @Override |
| protected void setWidth(TableColumn column, int width) |
| { |
| column.setWidth(width); |
| } |
| |
| @Override |
| protected boolean isEmpty() |
| { |
| return getControl().getItemCount() != 0; |
| } |
| |
| @Override |
| protected void pack(TableColumn column) |
| { |
| column.pack(); |
| } |
| } |
| } |