/******************************************************************************* * Copyright (c) 2010, 2011 Tasktop Technologies 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: * 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; } }