public class ColumnLayoutsXmlStore extends XmlStore<ColumnLayout> {

    private static final String ROOT_TAG = "columns";

    private static final String COLUMN = "column";
    private static final String COLUMN_ORDER = "column_order";
    private static final String COLUMN_NAME = "column_name";
    private static final String COLUMN_SIZE = "column_size";

    private final String fileName;

    public ColumnLayoutsXmlStore(String fileName) {
        super(ROOT_TAG);
        this.fileName = fileName;
    }

    @Override
    protected Path getXmlFile() {
        return PathBuilder.getInstance().getColumnsPath().resolve(fileName);
    }

    @Override
    public List<ColumnLayout> readObjects() {
        List<ColumnLayout> layouts = super.readObjects();
        layouts.sort((o1, o2) -> Integer.compare(o1.getOrder(), o2.getOrder()));
        return layouts;
    }

    @Override
    protected ColumnLayout parseElement(Node node) {
        Element el = (Element) node;
        int order = Integer.parseInt(el.getElementsByTagName(COLUMN_ORDER).item(0).getTextContent());
        String name = el.getElementsByTagName(COLUMN_NAME).item(0).getTextContent();
        String value = el.getElementsByTagName(COLUMN_SIZE).item(0).getTextContent();
        Integer width = value.isEmpty() ? null : Integer.valueOf(value);

        return new ColumnLayout(order, Columns.valueOf(name), width);
    }

    @Override
    protected void appendChildren(Document xml, Element root, List<ColumnLayout> list) {
        for (ColumnLayout layout : list) {
            Element parent = xml.createElement(COLUMN);
            root.appendChild(parent);
            createSubElement(xml, parent, COLUMN_ORDER, Integer.toString(layout.getOrder()));
            createSubElement(xml, parent, COLUMN_NAME, layout.getColumn().name());
            Integer i = layout.getWidth();
            String width = i == null ? "" : String.valueOf(i);
            createSubElement(xml, parent, COLUMN_SIZE, width);
        }
    }
} public class ColumnConfigDialog extends Dialog {

    private final ExtendedTreeViewerComponent<?> tree;
    private final ResourceBundle bundle;
    private final Set<Columns> visibleColumn;

    private CheckboxTableViewer viewer;

    public ColumnConfigDialog(ResourceBundle resourceBundle,
            ExtendedTreeViewerComponent<?> tree, Shell parent) {
        super(parent);
        this.tree = tree;
        this.bundle = resourceBundle;
        this.visibleColumn = tree.getVisibleColumns();
    }

    @Override
    protected void configureShell(Shell newShell) {
        super.configureShell(newShell);
        newShell.setText(bundle.getString("columns"));
    }

    @Override
    protected Control createDialogArea(Composite parent) {
        Composite container = new Composite(parent, SWT.NONE);
        container.setLayout(new GridLayout());
        container.setLayoutData(new GridData(GridData.FILL_BOTH));

        DataSource<?> dataSource = tree.getDataSource();
        viewer = CheckboxTableViewer.newCheckList(container, SWT.BORDER);
        viewer.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        viewer.setContentProvider(ArrayContentProvider.getInstance());
        viewer.setLabelProvider(new LabelProvider() {

            @Override
            public String getText(Object element) {
                Columns column = (Columns) element;
                return dataSource.getLocalizeString(column.getColumnName());
            }
        });

        viewer.setInput(dataSource.getColumns());
        viewer.setCheckedElements(visibleColumn.toArray());

        return container;
    }

    @Override
    protected Point getInitialSize() {
        Point size = super.getInitialSize();
        return new Point(size.x, size.y + 25);
    }

    @Override
    protected void okPressed() {
        visibleColumn.clear();

        for (Object obj : viewer.getCheckedElements()) {
            visibleColumn.add((Columns) obj);
        }

        tree.showColumns();
        super.okPressed();
    }
} public class FilterChangeHandler {

    private final DataSource<? extends IObject> dataSource;
    private ExtendedTreeViewerComponent<?> tree;
    private final Map<Columns, ViewerFilter> columnFilters = new EnumMap<>(Columns.class);
    private ViewerFilter allTextFilter;

    public FilterChangeHandler(DataSource<? extends IObject> dataSource) {
        this.dataSource = dataSource;
    }

    public void setTree(ExtendedTreeViewerComponent<?> tree) {
        this.tree = tree;
    }

    void filterAllColumns(String searchText) {
        if (searchText.isEmpty()) {
            allTextFilter = null;
        } else {
            allTextFilter = new ViewerFilter() {
                @Override
                public boolean select(Viewer viewer, Object parentElement, Object element) {
                    if (parentElement instanceof IObject) {
                        // Show all children (not first-level elements) because all children
                        // should be shown if parent is shown. If parent is not matched then
                        // his children will not be shown anyway.
                        return true;
                    } else {
                        return dataSource.getColumnsToFilter().stream()
                                .anyMatch(column -> matches(column, (IObject)element, searchText,
                                        FilterOperation.CONTAINS));
                    }
                }
            };
        }
        updateFilters();
    }

    void filter(String searchText, FilterOperation value, Columns column) {
        if (searchText.isEmpty() || value == FilterOperation.NONE) {
            columnFilters.remove(column);
        } else {
            ViewerFilter columnFilter = new ViewerFilter() {
                @Override
                public boolean select(Viewer viewer, Object parentElement, Object element) {
                    if (parentElement instanceof IObject) {
                        // Show all children (not first-level elements) because all children
                        // should be shown if parent is shown. If parent is not matched then
                        // his children will not be shown anyway.
                        return true;
                    } else {
                        return matches(column, (IObject) element, searchText, value);
                    }
                }
            };
            columnFilters.put(column, columnFilter);
        }
        updateFilters();
    }

    void deactivateFilters() {
        allTextFilter = null;
        columnFilters.clear();
        updateFilters();
    }

    private boolean matches(Columns column, IObject element, String searchText,
            FilterOperation value) {
        if (element.hasChildren()) {
            for (IObject child : element.getChildren()) {
                if (matches(column, child, searchText, value)) {
                    return true;
                }
            }
        }
        String textFromObject = dataSource.getRowText(element, column);
        return value.matchesForType(textFromObject, searchText, getColumnType(column));
    }

    private ColumnType getColumnType(Columns column) {
        switch (column) {
        case PID: return ColumnType.INTEGER;
        case BLOCK_CREATE_DATE:
        case BLOCK_END_DATE:
        case BACKEND_START:
        case QUERY_START:
        case XACT_START:
        case DURATION:
        case STATE_CHANGE: return ColumnType.DATE;
        default: return ColumnType.STRING;
        }
    }

    private void updateFilters() {
        List<ViewerFilter> filters = new ArrayList<>(columnFilters.values());
        if (allTextFilter != null) {
            filters.add(allTextFilter);
        }
        tree.setFilters(filters.toArray(new ViewerFilter[0]));
    }
} public class FilterComposite extends Composite {

    private final Set<Columns> filterList;
    private final ResourceBundle innerResourceBundle;
    private final DataSource<?> dataSource;
    private final int numberOfColumns;
    private final FilterChangeHandler filterChangeHandler;
    private final List<Text> filterTextList = new ArrayList<>();

    public FilterComposite(Composite parent, int style, ResourceBundle innerResourceBundle,
            DataSource<?> dataSource, FilterChangeHandler filterChangeHandler) {
        super(parent, style);
        this.innerResourceBundle = innerResourceBundle;
        this.dataSource = dataSource;
        this.filterList = dataSource.getColumnsToFilter();
        this.filterChangeHandler = filterChangeHandler;

        // label, combo, text
        numberOfColumns = findColumnNumber() * 3;
        GridLayout glayout = new GridLayout();
        glayout.numColumns = numberOfColumns;
        setLayout(glayout);

        GridData layoutData = new GridData(SWT.FILL, SWT.TOP, true, false);
        setLayoutData(layoutData);

        createContent();
    }

    private void createContent() {
        createAllTextFilter();
        filterList.forEach(this::createFilterView);
    }

    private void createAllTextFilter() {
        Label label = new Label(this, SWT.NONE);
        GridData data = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
        label.setLayoutData(data);
        label.setText(innerResourceBundle.getString("filter"));
        label.setToolTipText(innerResourceBundle.getString("all-filter-tooltip"));

        Text filterText = new Text(this, SWT.FILL | SWT.BORDER);
        int horizontalSpan = Math.max(numberOfColumns - 1, 1);
        GridData textLayoutData = new GridData(SWT.FILL, SWT.FILL, true, false, horizontalSpan, 1);
        filterText.setLayoutData(textLayoutData);
        filterText.setToolTipText(innerResourceBundle.getString("all-filter-tooltip"));
        filterTextList.add(filterText);
        filterText.addModifyListener(e -> filterChangeHandler.filterAllColumns(filterText.getText()));
    }

    private void createFilterView(Columns column) {
        GridData comboLayoutData = new GridData(SWT.CENTER, SWT.CENTER, false,false);
        comboLayoutData.widthHint = 60;
        GridData textLayoutData = new GridData(SWT.FILL, SWT.FILL, true, false);
        textLayoutData.widthHint = 150;

        Label label = new Label(this, SWT.NONE);
        label.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
        label.setText(dataSource.getLocalizeString(column.getColumnName()));

        Combo combo = new Combo(this, SWT.DROP_DOWN | SWT.READ_ONLY);
        combo.setLayoutData(comboLayoutData);
        List<FilterOperation> filterValues = Arrays.asList(FilterOperation.values());
        filterValues.forEach( f -> combo.add(f.toString()));
        combo.select(FilterOperation.CONTAINS.ordinal());

        Text filterText = new Text(this, SWT.FILL | SWT.BORDER);
        filterText.setLayoutData(textLayoutData);
        filterTextList.add(filterText);

        combo.addModifyListener(e -> filterChangeHandler.filter(
                filterText.getText(), FilterOperation.find(combo.getText()), column));
        filterText.addModifyListener(e -> filterChangeHandler.filter(
                filterText.getText(), FilterOperation.find(combo.getText()), column));
    }

    /**
     * Method returns the number of columns in filter
     */
    private int findColumnNumber() {
        int i = filterList.size();

        if (i == 4) {
            return 2;
        }

        return Math.min(i, 3);
    }

    public void show() {
        this.setVisible(true);
        GridData layoutData = (GridData) this.getLayoutData();
        layoutData.exclude = false;
        this.getParent().layout();
    }

    public void hide() {
        this.setVisible(false);
        GridData layoutData = (GridData) this.getLayoutData();
        layoutData.exclude = true;
        filterChangeHandler.deactivateFilters();
        filterTextList.forEach(t -> t.setText(""));
        this.getParent().layout();
    }
} public class TreeViewer extends TreeViewer_ru {
} public abstract class DataSource<T extends IObject> implements ITableLabelProvider, ITreeContentProvider {

    protected List<ILabelProviderListener> listeners = new ArrayList<>();

    public abstract List<Columns> getColumns();

    public abstract Set<Columns> getColumnsToFilter();

    public abstract ResourceBundle getResourceBundle();

    public abstract String getRowText(Object element, Columns column);

    public abstract int compare(Object e1, Object e2, Columns column);

    public String getLocalizeString(String name) {
        if (getResourceBundle() == null || !getResourceBundle().containsKey(name)) {
            return name;
        }

        return getResourceBundle().getString(name);
    }

    @Override
    public String getColumnText(Object element, int columnIndex) {
        return getRowText(element, getColumns().get(columnIndex));
    }

    @Override
    public void dispose() {

    }

    @Override
    public boolean isLabelProperty(Object element, String property) {
        return false;
    }

    @Override
    public void addListener(ILabelProviderListener listener) {
        listeners.add(listener);
    }

    @Override
    public void removeListener(ILabelProviderListener listener) {
        listeners.remove(listener);
    }
} public interface IObject {

    List<? extends IObject> getChildren();

    boolean hasChildren();
} public class ExtendedTreeViewerComponent<T extends IObject> extends TreeViewer {

    boolean inRefresh;
    private DataSource<T> dataSource;
    private ColumnLayoutsXmlStore columnLayoutsStore;

    private final Set<Columns> visibleColumns = EnumSet.noneOf(Columns.class);
    private final EnumMap<Columns, TreeColumn> columns = new EnumMap<>(Columns.class);
    private final DBProcessesViewComparator comparator = new DBProcessesViewComparator();

    public ExtendedTreeViewerComponent(Composite parent, int style) {
        super(parent, style);
        getTree().setLinesVisible(true);
        getTree().setHeaderVisible(true);
        GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
        getTree().setLayoutData(data);
        setComparator(comparator);
    }

    public DataSource<T> getDataSource() {
        return dataSource;
    }

    public void setData(DataSource<T> dataSource, ColumnLayoutsXmlStore columnLayoutsStore) {
        if (this.dataSource != null) {
            throw new IllegalStateException("ExtendedTreeViewerComponent already contains data source");
        }
        this.dataSource = dataSource;
        this.columnLayoutsStore = columnLayoutsStore;
        createColumns();
        loadColumnsFromStore(columnLayoutsStore.readObjects());
        addListeners();
        setLabelProvider(this.dataSource);
        setContentProvider(this.dataSource);
        addDoubleClickListener(new DoubleClickListener());
    }

    private void loadColumnsFromStore(List<ColumnLayout> layouts) {
        int[] order = IntStream.range(0, columns.size()).toArray();

        // errors protection if columns count will changed
        for (int i = 0; i < layouts.size() && i < columns.size(); i++) {
            ColumnLayout layout = layouts.get(i);
            Columns column = layout.getColumn();
            TreeColumn swtColumn = columns.get(column);
            if (swtColumn != null) {
                // search column index or array
                int columnOrder = getColumnIndex(swtColumn, order);

                if (columnOrder != -1) {
                    int temp = order[columnOrder];

                    order[columnOrder] = order[i];
                    order[i] = temp;

                    Integer width = layout.getWidth();
                    if (width == null) {
                        swtColumn.setWidth(0);
                        swtColumn.setResizable(false);
                        visibleColumns.remove(column);
                    } else {
                        swtColumn.setWidth(width);
                    }
                }
            }
        }

        getTree().setColumnOrder(order);
    }

    public void refreshWithoutSelection() {
        inRefresh = true;
        refresh();
        inRefresh = false;
    }

    @Override
    protected void setSelectionToWidget(List v, boolean reveal) {
        if (!inRefresh) {
            super.setSelectionToWidget(v, reveal);
        }
    }

    private int getColumnIndex(TreeColumn swtColumn, int[] order) {
        int columnOrder = getTree().indexOf(swtColumn);
        for (int i = 0; i < order.length; i++) {
            if (columnOrder == order[i]) {
                return i;
            }
        }

        return -1;
    }

    private void createColumns() {
        for (Columns column : dataSource.getColumns()) {
            TreeColumn swtColumn = createColumn(column);
            swtColumn.setWidth(getColumnWidth(column));
            visibleColumns.add(column);
            columns.put(column, swtColumn);
        }
    }

    private TreeColumn createColumn(Columns column) {
        TreeViewerColumn treeColumn = new TreeViewerColumn(this, SWT.NONE);
        TreeColumn swtColumn = treeColumn.getColumn();

        swtColumn.setData(column);
        swtColumn.setMoveable(true);
        swtColumn.setText(dataSource.getLocalizeString(column.getColumnName()));
        swtColumn.setToolTipText(dataSource.getLocalizeString(getColumnTooltip(column)));
        swtColumn.addSelectionListener(new SelectionListener() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                selectSortColumn(swtColumn, (e.stateMask & SWT.CTRL) != 0);
            }

            @Override
            public void widgetDefaultSelected(SelectionEvent e) {
                selectSortColumn(swtColumn, (e.stateMask & SWT.CTRL) != 0);
            }
        });

        return swtColumn;
    }

    private void addListeners() {
        AggregatingListener al = new AggregatingListener(new ControlAdapter() {

            @Override
            public void controlResized(ControlEvent e) {
                saveLayouts();
            }

            @Override
            public void controlMoved(ControlEvent e) {
                saveLayouts();
            }
        });

        for (TreeColumn swtColumn : columns.values()) {
            swtColumn.addListener(SWT.Resize | SWT.Move, al);
        }
    }


    private void saveLayouts() {
        List<ColumnLayout> list = new ArrayList<>();

        int[] order = getTree().getColumnOrder();
        TreeColumn[] columns = getTree().getColumns();
        for (int i = 0; i < columns.length; i++) {

            TreeColumn col = columns[order[i]];
            Integer width = null;
            if (col.getResizable()) {
                width = col.getWidth();
            }
            list.add(new ColumnLayout(i, (Columns) col.getData(), width));
        }

        columnLayoutsStore.writeObjects(list);
    }

    private String getColumnTooltip(Columns column) {
        switch(column) {
        case PID:
        case BACKEND_TYPE:
        case APPLICATION_NAME:
        case DATABASE_NAME:
        case USER_NAME:
        case CLIENT:
        case BACKEND_START:
        case QUERY_START:
        case XACT_START:
        case STATE:
        case STATE_CHANGE:
        case BLOCKED:
        case LOCK_TYPE:
        case RELATION:
        case SLOW_QUERY:
        case QUERY: return column.name();
        case DURATION: return "now - XACT_START";
        default : return "";
        }
    }

    private int getColumnWidth(Columns column) {
        switch(column) {
        case PID:
        case BACKEND_TYPE: return 80;
        case BLOCKED_COUNT:
        case STATE:
        case DURATION: return 70;
        case CLIENT:
        case APPLICATION_NAME:
        case QUERY: return 100;
        case RELATION: return 130;
        case XACT_START:
        case STATE_CHANGE:
        case BLOCK_END_DATE:
        case SLOW_QUERY: return 150;
        default: return 110;
        }
    }

    private void selectSortColumn(TreeColumn column, boolean isNeedClear) {
        if (isNeedClear) {
            comparator.clearSortList();
            setColumnHeaders();
        }
        comparator.addSort(column);

        updateSortIndexes();
        refresh();
    }

    public void showColumns() {
        for (Entry<Columns, TreeColumn> entry : columns.entrySet()) {
            Columns column = entry.getKey();
            TreeColumn treeColumn = entry.getValue();
            if (visibleColumns.contains(column)) {
                if (!treeColumn.getResizable()) {
                    treeColumn.setWidth(getColumnWidth(column));
                    treeColumn.setResizable(true);
                }
            } else if (treeColumn.getResizable()) {
                treeColumn.setWidth(0);
                treeColumn.setResizable(false);
            }
        }
        saveLayouts();
    }

    private void setColumnHeaders() {
        for (TreeColumn s : getTree().getColumns()) {
            Columns col = (Columns) s.getData();
            s.setText(dataSource.getLocalizeString(col.getColumnName()));
        }
    }

    public Set<Columns> getVisibleColumns() {
        return visibleColumns;
    }

    private class DoubleClickListener implements IDoubleClickListener {
        @Override
        public void doubleClick(final DoubleClickEvent event) {
            TreeItem[] selectedItems = getTree().getSelection();
            if (selectedItems.length != 1) {
                return;
            }
            TreeItem selectedItem = selectedItems[0];
            if (selectedItem.getExpanded()) {
                internalCollapseToLevel(selectedItem, AbstractTreeViewer.ALL_LEVELS);
            } else {
                internalExpandToLevel(selectedItem, 1);
            }
        }
    }

    private void updateSortIndexes() {
        int i = 0;
        StringBuilder sb = new StringBuilder();
        for (SortingColumn col : comparator.sortOrder) {
            sb.setLength(0);
            sb.append(comparator.sortOrder.size() - i++)
            .append(!col.desc ? '\u25BF' : '\u25B5')
            .append('\t');

            sb.append(dataSource.getLocalizeString(col.col.getColumnName()));

            columns.get(col.col).setText(sb.toString());
        }
    }

    private class DBProcessesViewComparator extends ViewerComparator {

        private final Deque<SortingColumn> sortOrder = new LinkedList<>();

        public void clearSortList() {
            sortOrder.clear();
        }

        public void addSort(TreeColumn column) {
            Columns col = (Columns) column.getData();

            if (!sortOrder.isEmpty() && col.equals(sortOrder.getLast().col)) {
                SortingColumn oldCol = sortOrder.pollLast();
                sortOrder.addLast(new SortingColumn(col, !oldCol.desc));
            } else {
                SortingColumn c = new SortingColumn(col, false);
                sortOrder.remove(c);
                sortOrder.addLast(c);
            }
        }

        @Override
        public int compare(Viewer viewer, Object e1, Object e2) {
            Iterator<SortingColumn> it = sortOrder.descendingIterator();
            while (it.hasNext()) {
                SortingColumn c = it.next();
                int compareResult = dataSource.compare(e1, e2, c.col);

                if (compareResult != 0) {
                    if (c.desc) {
                        compareResult = -compareResult;
                    }
                    return compareResult;
                }
            }

            return 0;
        }
    }

    private static class SortingColumn {

        private final Columns col;
        private final boolean desc;

        public SortingColumn(Columns col, boolean desc) {
            this.col = col;
            this.desc = desc;
        }

        @Override
        public boolean equals(Object obj) {
            return obj instanceof SortingColumn
                    && ((SortingColumn) obj).col == col;
        }

        @Override
        public int hashCode() {
            return col.hashCode();
        }
    }
} public final class ImageUtils {

    private static final ConcurrentMap<String, Image> imagesMap = new ConcurrentHashMap<>();

    public static Image getImage(Images type) {
        return imagesMap.computeIfAbsent(type.toString(),
                k -> new Image(null, ImageUtils.class.getClassLoader().getResourceAsStream(type.getImageAddr())));
    }
} public enum Images {

    UPDATE("images/update_16.png", "update"),
    FILTER("images/filter.png", "filter"),
    TABLE("images/table_16.png", "columns");

    private String location;
    private String description;

    private Images(String location, String description) {
        this.location = location;
        this.description = description;
    }

    public String getImageAddr() {
        return location;
    }

    public String getDescription(ResourceBundle resources) {
        return resources.getString(description);
    }
}
import ru.taximaxim.treeviewer.dialog.ColumnConfigDialog;
import ru.taximaxim.treeviewer.filter.FilterChangeHandler;
import ru.taximaxim.treeviewer.filter.FilterComposite;
import ru.taximaxim.treeviewer.l10n.TreeViewer;
import ru.taximaxim.treeviewer.models.DataSource;
import ru.taximaxim.treeviewer.models.IObject;
import ru.taximaxim.treeviewer.tree.ExtendedTreeViewerComponent;
import ru.taximaxim.treeviewer.utils.ImageUtils;
import ru.taximaxim.treeviewer.utils.Images;
import java.util.Locale;
import java.util.ResourceBundle;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import ru.taximaxim.pgsqlblocks.xmlstore.ColumnLayoutsXmlStore;
/*-
 * ========================LICENSE_START=================================
 * pgSqlBlocks
 * %
 * Copyright (C) 2017 - 2018 "Technology" LLC
 * %
 * 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.
 * =========================LICENSE_END==================================
 */
package ru.taximaxim.treeviewer;




/**
 * Tree viewer that implements common logic. Comes with toolbar where filters and column selection are located.
 * <br>
 * Supports filtering, sorting, l10n, hiding columns.
 */
public class ExtendedTreeViewer<T extends IObject> extends Composite {

    private ResourceBundle resourceBundle;
    private ExtendedTreeViewerComponent<T> tree;
    private FilterComposite filterComposite;
    private final FilterChangeHandler filterChangeHandler;
    private ToolItem filterToolItem;
    private Runnable updateToolItemAction;

    public ExtendedTreeViewer(Composite parent, int style, Object userData,
            DataSource<T> dataSource, Locale locale,