blob: 8e38c70a000f632884aaf20931ea23ebf8c18994 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2011 Tasktop Technologies and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Tasktop Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.mylyn.commons.ui;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.layout.AbstractColumnLayout;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.viewers.ColumnLayoutData;
import org.eclipse.jface.viewers.ColumnPixelData;
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.mylyn.commons.core.XmlMemento;
import org.eclipse.mylyn.internal.commons.ui.Messages;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
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.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
/**
* Provides an abstract implementation for persisting the column and sorting state of a {@link ColumnViewer}.
*
* @author Shawn Minto
* @author Steffen Pingel
* @since 3.7
*/
public abstract class AbstractColumnViewerSupport<T extends Item> {
private static class ColumnState {
// this represents the width of the column or the weight if it was weight data
int width;
}
public static final String KEY_COLUMN_CAN_HIDE = "org.eclipse.mylyn.column.viewer.support.column.can.hide"; //$NON-NLS-1$
// from AbstractColumnLayout
private static final String KEY_LAYOUT_DATA = Policy.JFACE + ".LAYOUT_DATA"; //$NON-NLS-1$
public static final String KEY_SUPPORTS_SORTING = "org.eclipse.mylyn.column.viewer.support.sorting"; //$NON-NLS-1$
private Menu contextMenu;
private final Control control;
private int[] defaultOrder;
private ColumnState[] defaults;
private int defaultSortColumnIndex;
private int defaultSortDirection;
private final boolean[] defaultVisibilities;
private final Menu headerMenu;
private ColumnState[] lastStates;
private final File stateFile;
private boolean supportsSorting;
private final ColumnViewer viewer;
public AbstractColumnViewerSupport(ColumnViewer viewer, File stateFile) {
this(viewer, stateFile, new boolean[0]);
}
public AbstractColumnViewerSupport(ColumnViewer viewer, File stateFile, boolean[] defaultVisibilities) {
Assert.isNotNull(viewer);
Assert.isNotNull(stateFile);
Assert.isNotNull(defaultVisibilities);
Object supportSort = viewer.getControl().getData(KEY_SUPPORTS_SORTING);
if (supportSort instanceof Boolean) {
supportsSorting = (Boolean) supportSort;
} else {
supportsSorting = true;
}
this.defaultVisibilities = defaultVisibilities;
this.viewer = viewer;
this.stateFile = stateFile;
control = viewer.getControl();
Composite parent = viewer.getControl().getParent();
headerMenu = new Menu(parent);
}
abstract void addColumnSelectionListener(T column, SelectionListener selectionListener);
private MenuItem createMenuItem(Menu parent, final T column, final int i) {
final MenuItem item = new MenuItem(parent, SWT.CHECK);
item.setText(column.getText());
item.setSelection(getWidth(column) > 0);
item.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
int lastWidth = getWidth(column);
if (lastWidth != 0) {
lastStates[i].width = lastWidth;
}
if (lastStates[i].width == 0) {
// if the user shrunk it to 0, use the default
lastStates[i].width = defaults[i].width;
}
if (lastStates[i].width == 0) {
// if the default and the last width was 0, then set to 150 pixels
lastStates[i].width = 150;
}
if (item.getSelection()) {
setWidth(column, lastStates[i].width);
} else {
setWidth(column, 0);
}
}
});
return item;
}
private void createRestoreDefaults(Menu parent) {
new MenuItem(parent, SWT.SEPARATOR);
final MenuItem restoreDefaults = new MenuItem(parent, SWT.PUSH);
restoreDefaults.setText(Messages.AbstractColumnViewerSupport_Restore_defaults);
restoreDefaults.addListener(SWT.Selection, new Listener() {
public void handleEvent(Event event) {
restoreDefaults();
}
});
}
abstract Rectangle getClientArea();
abstract T getColumn(int index);
abstract int getColumnIndexOf(T column);
abstract AbstractColumnLayout getColumnLayout();
private ColumnLayoutData getColumnLayoutData(Item column) {
Object data = column.getData(KEY_LAYOUT_DATA);
if (data instanceof ColumnLayoutData) {
return (ColumnLayoutData) data;
} else {
return null;
}
}
abstract int[] getColumnOrder();
abstract T[] getColumns();
abstract int getColumnWidth(T column);
abstract int getHeaderHeight();
abstract T getSortColumn();
abstract int getSortDirection();
private int getWidth(T column) {
ColumnLayoutData data = getColumnLayoutData(column);
AbstractColumnLayout columnLayout = getColumnLayout();
if (data != null && columnLayout != null) {
if (data instanceof ColumnWeightData) {
return ((ColumnWeightData) data).weight;
} else if (data instanceof ColumnPixelData) {
// turn this into a weighted width
int width = ((ColumnPixelData) data).width;
int totalWidth = control.getSize().x;
if (totalWidth == 0) {
return width;
} else {
return (width * 100) / totalWidth;
}
} else {
// we dont know
return getColumnWidth(column);
}
} else {
// if has column data, use that (pixel or weight)
return getColumnWidth(column);
}
}
private void initialize() {
T[] columns = getColumns();
defaults = new ColumnState[columns.length];
defaultSortColumnIndex = -1;
for (int i = 0; i < columns.length; i++) {
final T column = columns[i];
if (supportsSorting) {
addColumnSelectionListener(column, new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
int direction = getSortDirection();
if (getSortColumn() == column && direction != SWT.NONE) {
direction = (direction == SWT.DOWN) ? SWT.UP : SWT.NONE;
} else {
direction = SWT.DOWN;
}
setSortDirection(direction);
if (direction == SWT.NONE) {
setSortColumn(null);
} else {
setSortColumn(column);
}
viewer.refresh();
}
});
if (column == getSortColumn()) {
defaultSortColumnIndex = i;
}
}
MenuItem item = createMenuItem(headerMenu, column, i);
item.setEnabled(canHide(column));
defaults[i] = new ColumnState();
defaults[i].width = getWidth(column);
}
createRestoreDefaults(headerMenu);
defaultOrder = getColumnOrder();
defaultSortDirection = getSortDirection();
control.addListener(SWT.MenuDetect, new Listener() {
public void handleEvent(Event event) {
Menu menu = control.getMenu();
if (menu != null && menu != headerMenu) {
contextMenu = menu;
}
Display display = control.getDisplay();
Point pt = display.map(null, control, new Point(event.x, event.y));
Rectangle clientArea = getClientArea();
boolean header = clientArea.y <= pt.y && pt.y < (clientArea.y + getHeaderHeight());
control.setMenu(header ? headerMenu : contextMenu);
}
});
control.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent event) {
save();
}
});
}
private boolean canHide(T column) {
Object canHide = column.getData(KEY_COLUMN_CAN_HIDE);
return !(canHide instanceof Boolean) || ((Boolean) canHide).booleanValue();
}
void initializeViewerSupport() {
initialize();
restore();
T[] columns = getColumns();
lastStates = new ColumnState[columns.length];
for (int i = 0; i < columns.length; i++) {
final T column = columns[i];
lastStates[i] = new ColumnState();
lastStates[i].width = getWidth(column);
}
}
private void restore() {
if (stateFile.exists()) {
try {
FileReader reader = new FileReader(stateFile);
try {
XmlMemento memento = XmlMemento.createReadRoot(reader);
XmlMemento[] children = memento.getChildren("Column"); //$NON-NLS-1$
int[] order = new int[children.length];
for (int i = 0; i < children.length; i++) {
T column = getColumn(i);
Integer widthInteger = children[i].getInteger("width"); //$NON-NLS-1$
if (widthInteger != null) {
int width = widthInteger;
// ensure that columns that may not be hidden have a non zero width
if (width >= 0 && (width > 0 || canHide(column))) {
setWidth(column, width);
}
}
headerMenu.getItem(i).setSelection(getWidth(column) > 0);
Integer orderInteger = children[i].getInteger("order"); //$NON-NLS-1$
order[i] = (orderInteger != null) ? orderInteger.intValue() : 0;
restoreAdditionalChildInfo(children[i], column);
}
try {
setColumnOrder(order);
} catch (IllegalArgumentException e) {
// ignore
}
XmlMemento child = memento.getChild("Sort"); //$NON-NLS-1$
if (child != null) {
int columnIndex = child.getInteger("column"); //$NON-NLS-1$
T column = getColumn(columnIndex);
setSortColumn(column);
setSortDirection(child.getInteger("direction")); //$NON-NLS-1$
}
} catch (Exception e) {
// ignore
} finally {
reader.close();
}
} catch (IOException e) {
// ignore
}
viewer.refresh();
} else {
T[] columns = getColumns();
for (int i = 0; i < columns.length; i++) {
T column = columns[i];
if (i < defaultVisibilities.length && !defaultVisibilities[i]) {
setWidth(column, 0);
headerMenu.getItem(i).setSelection(false);
}
}
}
}
private void restoreDefaults() {
for (int index = 0; index < defaults.length; index++) {
T column = getColumn(index);
if (index < defaultVisibilities.length && !defaultVisibilities[index]) {
setWidth(column, 0);
} else {
setWidth(column, defaults[index].width);
}
// update the menu
headerMenu.getItem(index).setSelection(getWidth(column) > 0);
}
setColumnOrder(defaultOrder);
if (defaultSortColumnIndex != -1) {
setSortColumn(getColumn(defaultSortColumnIndex));
setSortDirection(defaultSortDirection);
} else {
setSortColumn(null);
}
viewer.refresh();
}
private void save() {
XmlMemento memento = XmlMemento.createWriteRoot("Viewer"); //$NON-NLS-1$
int[] order = getColumnOrder();
T[] columns = getColumns();
for (int i = 0; i < columns.length; i++) {
T column = columns[i];
XmlMemento child = memento.createChild("Column"); //$NON-NLS-1$
child.putInteger("width", getWidth(column)); //$NON-NLS-1$
child.putInteger("order", order[i]); //$NON-NLS-1$
saveAdditionalChildInfo(child, column);
}
T sortColumn = getSortColumn();
if (sortColumn != null) {
XmlMemento child = memento.createChild("Sort"); //$NON-NLS-1$
child.putInteger("column", getColumnIndexOf(sortColumn)); //$NON-NLS-1$
child.putInteger("direction", getSortDirection()); //$NON-NLS-1$
}
try {
FileWriter writer = new FileWriter(stateFile);
try {
memento.save(writer);
} finally {
writer.close();
}
} catch (IOException e) {
// ignore
}
}
abstract void setColumnOrder(int[] order);
abstract void setColumnResizable(T column, boolean resizable);
abstract void setColumnWidth(T column, int width);
abstract void setSortColumn(T column);
abstract void setSortDirection(int direction);
private void setWidth(T column, int width) {
// if has column data, set that (pixel or weight)
ColumnLayoutData data = getColumnLayoutData(column);
AbstractColumnLayout columnLayout = getColumnLayout();
if (data != null && columnLayout != null) {
if (width == 0) {
columnLayout.setColumnData(column, new ColumnPixelData(width, data.resizable));
} else {
columnLayout.setColumnData(column, new ColumnWeightData(width, data.resizable));
}
control.getParent().layout();
} else {
setColumnWidth(column, width);
}
setColumnResizable(column, width > 0);
}
/**
* @since 3.22
*/
protected void saveAdditionalChildInfo(XmlMemento child, T column) {
}
/**
* @since 3.22
*/
protected void restoreAdditionalChildInfo(XmlMemento xmlMemento, T column) {
}
/**
* @since 3.22
*/
protected Menu getHeaderMenu() {
return headerMenu;
}
}