/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jkiss.dbeaver.ui.controls; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.viewers.*; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.*; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.ui.ILabelProviderEx; import org.jkiss.dbeaver.ui.ILazyLabelProvider; import org.jkiss.dbeaver.ui.UIIcon; import org.jkiss.dbeaver.ui.UIUtils; import org.jkiss.dbeaver.ui.dialogs.BaseDialog; import org.jkiss.dbeaver.utils.RuntimeUtils; import org.jkiss.utils.CommonUtils; import java.lang.reflect.Array; import java.text.Collator; import java.text.NumberFormat; import java.util.*; import java.util.List; /** * Tree/table viewer column controller */ public class ViewerColumnController { private static final Log log = Log.getLog(ViewerColumnController.class); private static final String DATA_KEY = ViewerColumnController.class.getSimpleName(); private final String configId; private final ColumnViewer viewer; private final List<ColumnInfo> columns = new ArrayList<>(); private boolean clickOnHeader; private boolean isPacking, isInitializing; private transient Listener menuListener; public static ViewerColumnController getFromControl(Control control) { return (ViewerColumnController)control.getData(DATA_KEY); } public ViewerColumnController(String id, ColumnViewer viewer) { this.configId = id; this.viewer = viewer; final Control control = this.viewer.getControl(); control.setData(DATA_KEY, this); if (control instanceof Tree || control instanceof Table) { menuListener = new Listener() { @Override public void handleEvent(Event event) { Point pt = control.getDisplay().map(null, control, new Point(event.x, event.y)); Rectangle clientArea = ((Composite) control).getClientArea(); if (RuntimeUtils.isPlatformMacOS()) { clickOnHeader = pt.y < 0; } else { if (control instanceof Tree) { clickOnHeader = clientArea.y <= pt.y && pt.y < (clientArea.y + ((Tree) control).getHeaderHeight()); } else { clickOnHeader = clientArea.y <= pt.y && pt.y < (clientArea.y + ((Table) control).getHeaderHeight()); } } } }; control.addListener(SWT.MenuDetect, menuListener); } } public void dispose() { clearColumns(); final Control control = this.viewer.getControl(); if (!control.isDisposed()) { if (menuListener != null) { control.removeListener(SWT.MenuDetect, menuListener); menuListener = null; } } } public boolean isClickOnHeader() { return clickOnHeader; } public void fillConfigMenu(IMenuManager menuManager) { menuManager.add(new Action("Configure columns ...") { @Override public void run() { configureColumns(); } }); } public void addColumn(String name, String description, int style, boolean defaultVisible, boolean required, CellLabelProvider labelProvider) { addColumn(name, description, style, defaultVisible, required, false, null, labelProvider, null); } public void addColumn(String name, String description, int style, boolean defaultVisible, boolean required, boolean isNumeric, Object userData, CellLabelProvider labelProvider, EditingSupport editingSupport) { columns.add( new ColumnInfo( name, description, style, defaultVisible, required, isNumeric, userData, labelProvider, editingSupport, columns.size())); } private void clearColumns() { for (ColumnInfo columnInfo : columns) { if (columnInfo.column != null) { columnInfo.column.dispose(); columnInfo.column = null; } } columns.clear(); } public void createColumns() { this.createColumns(true); } public void createColumns(boolean pack) { try { readColumnsConfiguration(); } catch (Exception e) { // Possibly incompatible format from previous version log.warn("Failed to load configuration for '" + this.configId + "'", e); } recreateColumns(pack); } private void recreateColumns(boolean pack) { final Control control = viewer.getControl(); control.setRedraw(false); isInitializing = true; try { boolean needRefresh = false; for (ColumnInfo columnInfo : columns) { if (columnInfo.column != null) { columnInfo.column.dispose(); columnInfo.column = null; needRefresh = true; } } createVisibleColumns(); if (pack && !isAllSized()) { repackColumns(); control.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { control.removeControlListener(this); repackColumns(); } }); } if (needRefresh) { viewer.refresh(); for (ColumnInfo columnInfo : getVisibleColumns()) { if (columnInfo.column instanceof TreeColumn) { ((TreeColumn) columnInfo.column).pack(); } else { ((TableColumn) columnInfo.column).pack(); } } } } finally { control.setRedraw(true); isInitializing = false; } } private boolean isAllSized() { for (ColumnInfo columnInfo : columns) { if (columnInfo.visible && columnInfo.width <= 0) { return false; } } return true; } public void repackColumns() { if (isAllSized()) { return; } isPacking = true; try { if (viewer instanceof TreeViewer) { float[] ratios = null; if (((TreeViewer) viewer).getTree().getColumnCount() == 2) { ratios = new float[]{0.6f, 0.4f}; } UIUtils.packColumns(((TreeViewer) viewer).getTree(), false, ratios); } else if (viewer instanceof TableViewer) { UIUtils.packColumns(((TableViewer)viewer).getTable()); } } finally { isPacking = false; } } public void sortByColumn(int index, int direction) { final ColumnInfo columnInfo = columns.get(index); columnInfo.sortListener.sortViewer(columnInfo.column, direction); } private void createVisibleColumns() { boolean hasLazyColumns = false; List<ColumnInfo> visibleColumns = getVisibleColumns(); for (int i = 0; i < visibleColumns.size(); i++) { final ColumnInfo columnInfo = visibleColumns.get(i); columnInfo.order = i; final Item colItem; ViewerColumn viewerColumn; if (viewer instanceof TreeViewer) { final TreeViewerColumn item = new TreeViewerColumn((TreeViewer) viewer, columnInfo.style); viewerColumn = item; final TreeColumn column = item.getColumn(); colItem = column; column.setText(columnInfo.name); column.setMoveable(true); column.setWidth(columnInfo.width); if (!CommonUtils.isEmpty(columnInfo.description)) { column.setToolTipText(columnInfo.description); } column.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { columnInfo.width = column.getWidth(); saveColumnConfig(); } @Override public void controlMoved(ControlEvent e) { if (!isInitializing && e.getSource() instanceof TreeColumn) { updateColumnOrder(column, column.getParent().getColumnOrder()); } } }); columnInfo.column = column; } else if (viewer instanceof TableViewer) { final TableViewerColumn item = new TableViewerColumn((TableViewer) viewer, columnInfo.style); viewerColumn = item; final TableColumn column = item.getColumn(); colItem = column; column.setText(columnInfo.name); column.setMoveable(true); column.setWidth(columnInfo.width); if (!CommonUtils.isEmpty(columnInfo.description)) { column.setToolTipText(columnInfo.description); } column.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { columnInfo.width = column.getWidth(); saveColumnConfig(); } @Override public void controlMoved(ControlEvent e) { if (!isInitializing && e.getSource() instanceof TableColumn) { updateColumnOrder(column, column.getParent().getColumnOrder()); } } }); columnInfo.column = column; } else { continue; } viewerColumn.setLabelProvider(columnInfo.labelProvider); viewerColumn.setEditingSupport(columnInfo.editingSupport); colItem.setData(columnInfo); if (columnInfo.labelProvider instanceof ILazyLabelProvider) { hasLazyColumns = true; } else if (columnInfo.labelProvider instanceof ILabelProvider) { columnInfo.sortListener = new SortListener(columnInfo); columnInfo.column.addListener(SWT.Selection, columnInfo.sortListener); } } if (hasLazyColumns) { viewer.getControl().addListener(SWT.PaintItem, new Listener() { public void handleEvent(Event event) { if (viewer instanceof TreeViewer) { TreeColumn column = ((TreeViewer) viewer).getTree().getColumn(event.index); if (((ColumnInfo) column.getData()).labelProvider instanceof ILazyLabelProvider && CommonUtils.isEmpty(((TreeItem) event.item).getText(event.index))) { final String lazyText = ((ILazyLabelProvider) ((ColumnInfo) column.getData()).labelProvider).getLazyText(event.item.getData()); if (!CommonUtils.isEmpty(lazyText)) { ((TreeItem) event.item).setText(event.index, lazyText); } } } else { TableColumn column = ((TableViewer) viewer).getTable().getColumn(event.index); if (((ColumnInfo) column.getData()).labelProvider instanceof ILazyLabelProvider && CommonUtils.isEmpty(((TableItem) event.item).getText(event.index))) { final String lazyText = ((ILazyLabelProvider) ((ColumnInfo) column.getData()).labelProvider).getLazyText(event.item.getData()); if (!CommonUtils.isEmpty(lazyText)) { ((TableItem) event.item).setText(event.index, lazyText); } } } } }); } } private List<ColumnInfo> getVisibleColumns() { List<ColumnInfo> visibleList = new ArrayList<>(); for (ColumnInfo column : columns) { if (column.visible) { visibleList.add(column); } } Collections.sort(visibleList, new ColumnInfoComparator()); return visibleList; } // Read config from dialog settings private void readColumnsConfiguration() { final Collection<ViewerColumnRegistry.ColumnState> savedConfig = ViewerColumnRegistry.getInstance().getSavedConfig(configId); if (savedConfig == null) { return; } for (ColumnInfo columnInfo : columns) { for (ViewerColumnRegistry.ColumnState savedState : savedConfig) { if (columnInfo.name.equals(savedState.name)) { columnInfo.visible = savedState.visible; columnInfo.order = savedState.order; columnInfo.width = savedState.width; break; } } } } public Object getColumnData(int columnIndex) { final Control control = viewer.getControl(); ColumnInfo columnInfo; if (control instanceof Tree) { columnInfo = (ColumnInfo) ((Tree) control).getColumn(columnIndex).getData(); } else { columnInfo = (ColumnInfo) ((Table) control).getColumn(columnIndex).getData(); } return columnInfo.userData; } public <T> T[] getColumnsData(Class<T> type) { T[] newArray = (T[]) Array.newInstance(type, columns.size()); for (int i = 0; i < columns.size(); i++) { newArray[i] = type.cast(columns.get(i).userData); } return newArray; } public void configureColumns() { ConfigDialog configDialog = new ConfigDialog(); if (configDialog.open() != IDialogConstants.OK_ID) { return; } saveColumnConfig(); } private void updateColumnOrder(Item column, int[] order) { if (isPacking) { return; } ColumnInfo columnInfo = (ColumnInfo) column.getData(); boolean updated = false; for (int i = 0; i < order.length; i++) { if (order[i] == columnInfo.order) { columnInfo.order = i; updated = true; break; } } if (updated) { saveColumnConfig(); } } private void saveColumnConfig() { // Save settings ViewerColumnRegistry.getInstance().updateConfig(configId, columns); } public int getColumnsCount() { final Control control = viewer.getControl(); return control instanceof Tree ? ((Tree) control).getColumnCount() : ((Table) control).getColumnCount(); } private static class ColumnInfo extends ViewerColumnRegistry.ColumnState { final String description; final int style; final boolean defaultVisible; final boolean required; final boolean numeric; final Object userData; final CellLabelProvider labelProvider; final EditingSupport editingSupport; Item column; SortListener sortListener; private ColumnInfo(String name, String description, int style, boolean defaultVisible, boolean required, boolean numeric, Object userData, CellLabelProvider labelProvider, EditingSupport editingSupport, int order) { this.name = name; this.description = description; this.style = style; this.defaultVisible = defaultVisible; this.required = required; this.numeric = numeric; this.userData = userData; this.visible = defaultVisible; this.labelProvider = labelProvider; this.editingSupport = editingSupport; this.order = order; } } private class ConfigDialog extends BaseDialog { private Table colTable; //private final Map<ColumnInfo, Button> buttonMap = new HashMap<>(); protected ConfigDialog() { super(viewer.getControl().getShell(), "Configure columns", UIIcon.CONFIGURATION); } protected void setShellStyle(int newShellStyle) { super.setShellStyle(newShellStyle & ~SWT.MAX); } @Override protected boolean isResizable() { return true; } @Override protected Composite createDialogArea(Composite parent) { Composite composite = super.createDialogArea(parent); UIUtils.createControlLabel(composite, "Select columns you want to display"); List<ColumnInfo> orderedList = new ArrayList<>(columns); Collections.sort(orderedList, new ColumnInfoComparator()); colTable = new Table(composite, SWT.BORDER | SWT.CHECK | SWT.H_SCROLL | SWT.V_SCROLL); colTable.setLayoutData(new GridData(GridData.FILL_BOTH)); colTable.setLinesVisible(true); colTable.addListener(SWT.Selection,new Listener() { public void handleEvent(Event event) { if( event.detail == SWT.CHECK ) { if (((TableItem)event.item).getGrayed()) { ((TableItem)event.item).setChecked(true); event.doit = false; } } } }); final TableColumn nameColumn = new TableColumn(colTable, SWT.LEFT); nameColumn.setText("Name"); final TableColumn descColumn = new TableColumn(colTable, SWT.LEFT); descColumn.setText("Description"); for (ColumnInfo columnInfo : orderedList) { TableItem colItem = new TableItem(colTable, SWT.NONE); colItem.setData(columnInfo); colItem.setText(0, columnInfo.name); if (!CommonUtils.isEmpty(columnInfo.description)) { colItem.setText(1, columnInfo.description); } colItem.setChecked(columnInfo.visible); if (columnInfo.required) { colItem.setGrayed(true); } } nameColumn.pack(); if (nameColumn.getWidth() > 300) { nameColumn.setWidth(300); } descColumn.pack(); if (descColumn.getWidth() > 400) { descColumn.setWidth(400); } return parent; } @Override protected void okPressed() { boolean recreateColumns = false; for (TableItem item : colTable.getItems()) { ColumnInfo ci = (ColumnInfo) item.getData(); if (item.getChecked() != ci.visible) { ci.visible = item.getChecked(); recreateColumns = true; } } if (recreateColumns) { recreateColumns(true); } super.okPressed(); } } private static class ColumnInfoComparator implements Comparator<ColumnInfo> { @Override public int compare(ColumnInfo o1, ColumnInfo o2) { return o1.order - o2.order; } } private class SortListener implements Listener { ColumnInfo columnInfo; int sortDirection = SWT.DOWN; Item prevColumn = null; public SortListener(ColumnInfo columnInfo) { this.columnInfo = columnInfo; } @Override public void handleEvent(Event e) { Item column = (Item)e.widget; if (prevColumn == column) { // Set reverse order sortDirection = sortDirection == SWT.UP ? SWT.DOWN : SWT.UP; } prevColumn = column; sortViewer(column, sortDirection); } private void sortViewer(final Item column, final int sortDirection) { Collator collator = Collator.getInstance(); if (viewer instanceof TreeViewer) { ((TreeViewer)viewer).getTree().setSortColumn((TreeColumn) column); ((TreeViewer)viewer).getTree().setSortDirection(sortDirection); } else { ((TableViewer)viewer).getTable().setSortColumn((TableColumn) column); ((TableViewer)viewer).getTable().setSortDirection(sortDirection); } final ILabelProvider labelProvider = (ILabelProvider)columnInfo.labelProvider; final ILabelProviderEx exLabelProvider = labelProvider instanceof ILabelProviderEx ? (ILabelProviderEx)labelProvider : null; viewer.setComparator(new ViewerComparator(collator) { private final NumberFormat numberFormat = NumberFormat.getInstance(); @Override public int compare(Viewer v, Object e1, Object e2) { int result; String value1; String value2; if (exLabelProvider != null) { value1 = exLabelProvider.getText(e1, false); value2 = exLabelProvider.getText(e2, false); } else { value1 = labelProvider.getText(e1); value2 = labelProvider.getText(e2); } if (value1 == null && value2 == null) { result = 0; } else if (value1 == null) { result = -1; } else if (value2 == null) { result = 1; } else { if (columnInfo.numeric) { try { final Number num1 = numberFormat.parse(value1); final Number num2 = numberFormat.parse(value2); if (num1.getClass() == num2.getClass() && num1 instanceof Comparable) { result = ((Comparable) num1).compareTo(num2); } else { // Dunno how to compare result = 0; } } catch (Exception e) { // not numbers result = value1.compareToIgnoreCase(value2); } } else { result = value1.compareToIgnoreCase(value2); } } return sortDirection == SWT.DOWN ? result : -result; } }); } } }