blob: e0965c3d3d368e4f08a15555f5a74ac4f0ba9f9b [file] [log] [blame]
/**
* 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();
}
}
}