com.hp.alm.ali.idea.ui.MultipleItemsDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.hp.alm.ali.idea.ui.MultipleItemsDialog.java

Source

/*
 * Copyright 2013 Hewlett-Packard Development Company, L.P
 *
 * 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 com.hp.alm.ali.idea.ui;

import com.hp.alm.ali.idea.model.ItemsProvider;
import com.hp.alm.ali.idea.model.KeyValue;
import com.hp.alm.ali.idea.ui.dialog.MyDialog;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.labels.LinkLabel;
import com.intellij.ui.components.labels.LinkListener;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.StatusText;
import com.intellij.util.ui.UIUtil;
import net.coderazzi.filters.gui.IFilterEditor;
import net.coderazzi.filters.gui.IFilterHeaderObserver;
import net.coderazzi.filters.gui.TableFilterHeader;
import org.apache.commons.lang.ArrayUtils;

import javax.swing.BorderFactory;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JToggleButton;
import javax.swing.ListSelectionModel;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
import javax.swing.UIDefaults;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.RowSorterListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

public class MultipleItemsDialog<K, E extends KeyValue<K>> extends MyDialog implements IFilterHeaderObserver {

    private JLabel tooMany;
    private JLabel selected;
    private JToggleButton toggleSelected;
    private JButton okButton;
    private boolean ok;
    private MultipleItemsDialogModel<K, E> model;
    private JBTable table;
    private TableFilterHeader header;
    private MySelectionModel mySelectionModel;
    private MyListSelectionListener myListSelectionListener;

    public MultipleItemsDialog(Project project, String title, final boolean multiple, final List<E> fieldList,
            final List<K> selectedFields) {
        this(project, "Select " + title, new MultipleItemsDialogModel<K, E>(title, multiple,
                new ItemsProvider.Eager<E>(fieldList), selectedFields, null));
    }

    public MultipleItemsDialog(Project project, String title, final MultipleItemsDialogModel<K, E> model) {
        super(project, title, true);

        this.model = model;

        mySelectionModel = new MySelectionModel();
        myListSelectionListener = new MyListSelectionListener();

        tooMany = new JLabel("Too many results, narrow your search");
        tooMany.setBorder(BorderFactory.createEtchedBorder());
        tooMany.setVisible(false);
        selected = new JLabel("Showing currently selected items");
        selected.setVisible(false);
        toggleSelected = new JToggleButton();
        toggleSelected.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                model.setShowingSelected(toggleSelected.isSelected());
                if (!model.isShowingSelected() && !model.getSelectedFields().isEmpty()) {
                    updateSelectionFromModel();
                } else if (model.isShowingSelected()) {
                    header.getFilterEditor(1).setContent("");
                }
            }
        });
        updateSelected();

        table = new JBTable() {
            @Override
            public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
                int column = convertColumnIndexToModel(columnIndex);
                mySelectionModel.setFirstColumnEvent(column == 0);
                super.changeSelection(rowIndex, columnIndex, toggle, extend);
            }
        };
        table.setRowSelectionAllowed(true);
        table.setColumnSelectionAllowed(false);
        table.setAutoCreateColumnsFromModel(false);
        table.setModel(model);
        final MyTableRowSorter sorter = new MyTableRowSorter(model);
        table.setRowSorter(sorter);
        table.setDefaultRenderer(Boolean.class, new MyRenderer());
        table.setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
        table.setSelectionModel(mySelectionModel);

        sorter.setIgnoreAddRowSorterListener(true); // prevent auto-selection (functionality not accessible via proper API)
        header = new TableFilterHeader(table);
        sorter.setIgnoreAddRowSorterListener(false);

        sorter.setSortKeys(Arrays.asList(new RowSorter.SortKey(1, SortOrder.ASCENDING)));
        JPanel panel = new JPanel(new BorderLayout());
        JPanel toolbar = new JPanel(new BorderLayout());
        toolbar.setBorder(BorderFactory.createEtchedBorder());
        panel.add(toolbar, BorderLayout.NORTH);
        toolbar.add(toggleSelected, BorderLayout.EAST);

        if (model.isMultiple()) {
            table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);

            table.getColumnModel().addColumn(createColumn(0, model, 45, 45));
            header.getFilterEditor(0).setEditable(false);
            header.getFilterEditor(0).setUserInteractionEnabled(false);

            final LinkListener selectUnselect = new LinkListener() {
                public void linkSelected(LinkLabel aSource, Object aLinkData) {
                    if (model.isShowingSelected()) {
                        if (!Boolean.TRUE.equals(aLinkData)) {
                            List<Integer> ixs = new ArrayList<Integer>();
                            for (int i = 0; i < sorter.getViewRowCount(); i++) {
                                ixs.add(sorter.convertRowIndexToModel(i));
                            }
                            // make sure indexes are not affected by removal by starting from the last
                            Collections.sort(ixs);
                            Collections.reverse(ixs);
                            for (int ix : ixs) {
                                model.setValueAt(aLinkData, ix, 0);
                            }
                        }
                    } else {
                        if (Boolean.TRUE.equals(aLinkData)) {
                            mySelectionModel.doAddSelectionInterval(0, table.getRowCount() - 1);
                        } else {
                            mySelectionModel.removeSelectionInterval(0, table.getRowCount() - 1);
                        }
                    }
                }
            };

            JPanel left = new JPanel(new FlowLayout(FlowLayout.LEFT));
            left.add(new LinkLabel("Select All", IconLoader.getIcon("/actions/selectall.png"), selectUnselect,
                    true));
            left.add(new LinkLabel("Unselect All", IconLoader.getIcon("/actions/unselectall.png"), selectUnselect,
                    false));
            toolbar.add(left, BorderLayout.WEST);
        } else {
            table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        }
        table.getColumnModel().addColumn(createColumn(1, model, 450, null));
        table.getSelectionModel().addListSelectionListener(myListSelectionListener);

        model.addTableModelListener(new TableModelListener() {
            @Override
            public void tableChanged(TableModelEvent e) {
                selected.setVisible(model.isShowingSelected());
                tooMany.setVisible(model.hasMore() && !model.isShowingSelected());
                updateSelected();
            }
        });

        JPanel contentPanel = new JPanel(new BorderLayout());
        contentPanel.add(selected, BorderLayout.NORTH);
        contentPanel.add(new JBScrollPane(table), BorderLayout.CENTER);
        contentPanel.add(tooMany, BorderLayout.SOUTH);
        panel.add(contentPanel, BorderLayout.CENTER);
        JPanel buttons = new JPanel();
        okButton = new JButton("OK");
        okButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                ok = true;
                close(true);
            }
        });
        buttons.add(okButton);
        JButton cancel = new JButton("Cancel");
        cancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                close(false);
            }
        });
        buttons.add(cancel);
        panel.add(buttons, BorderLayout.SOUTH);
        getContentPane().add(panel, BorderLayout.CENTER);

        pack();
        setResizable(false);
        centerOnOwner();

        requestPropertyFilterFocus(header);

        load(true, null);
    }

    private void updateSelectionFromModel() {
        // try to highlight selected item when switching back from selected view
        myListSelectionListener.setIgnoreUpdateEvent(true);
        try {
            for (int i = 0; i < table.getRowCount(); i++) {
                int modelRow = table.convertRowIndexToModel(i);
                E value = model.getFields().get(modelRow);
                if (model.getSelectedFields().contains(value.getKey())) {
                    mySelectionModel.doAddSelectionInterval(i, i);
                    if (!model.isMultiple()) {
                        break;
                    }
                }
            }
        } finally {
            myListSelectionListener.setIgnoreUpdateEvent(false);
        }
    }

    private void updateOk() {
        okButton.setEnabled(model.isMultiple() || !model.getSelectedFields().isEmpty());
    }

    private void updateSelected() {
        int size = model.getSelectedFields().size();
        if (size > 0) {
            toggleSelected.setVisible(true);
            if (model.isMultiple()) {
                toggleSelected.setText("Show Selected (" + size + ")");
            } else {
                toggleSelected.setText("Show Selected");
            }
        } else {
            toggleSelected.setSelected(false);
            toggleSelected.setVisible(false);
            model.setShowingSelected(false);
        }
    }

    @Override
    public void tableFilterEditorCreated(TableFilterHeader tableFilterHeader, IFilterEditor iFilterEditor,
            TableColumn tableColumn) {
    }

    @Override
    public void tableFilterEditorExcluded(TableFilterHeader tableFilterHeader, IFilterEditor iFilterEditor,
            TableColumn tableColumn) {
    }

    @Override
    public void tableFilterUpdated(TableFilterHeader tableFilterHeader, IFilterEditor iFilterEditor,
            TableColumn tableColumn) {
        String filter = iFilterEditor.getContent().toString();
        if (!filter.isEmpty() && !filter.endsWith("*")) {
            // enforce prefix semantics to remain compatible with instant search that is used when all items fit into the first (filter-less) request
            iFilterEditor.setContent(filter + "*");
        } else {
            load(false, filter);
        }
    }

    private void load(final boolean first, final String filter) {
        final StatusText emptyText = table.getEmptyText();
        final String originalValue = emptyText.getText();
        emptyText.setText("Loading...");
        okButton.setEnabled(false);
        ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
            @Override
            public void run() {
                model.load(filter);
                UIUtil.invokeLaterIfNeeded(new Runnable() {
                    @Override
                    public void run() {
                        if (model.hasMore() && first) {
                            // truncated: if query changes execute fetch data again
                            header.addHeaderObserver(MultipleItemsDialog.this);
                            header.setInstantFiltering(false);
                            header.setFilterOnUpdates(false);
                        }
                        emptyText.setText(originalValue);
                        updateSelectionFromModel();
                        updateOk();
                    }
                });
            }
        });
    }

    private void requestPropertyFilterFocus(TableFilterHeader header) {
        IFilterEditor editor = header.getFilterEditor(model.getColumnCount() - 1);
        if (editor instanceof JComponent) {
            if (((JComponent) editor).getComponentCount() == 2) {
                ((JComponent) editor).getComponent(1).requestFocus();
            }
        }
    }

    private TableColumn createColumn(int index, TableModel model, int preferredWidth, Integer minWidth) {
        TableColumn column = new TableColumn(index);
        column.setPreferredWidth(preferredWidth);
        if (minWidth != null) {
            column.setMinWidth(minWidth);
        }
        column.setHeaderValue(model.getColumnName(index));
        return column;
    }

    public boolean isOk() {
        return ok;
    }

    private class MyRenderer extends JCheckBox implements TableCellRenderer {

        private Color selectedColor;
        private Color unselectedColor;

        public MyRenderer() {
            setHorizontalAlignment(JCheckBox.CENTER);
            UIDefaults defaults = javax.swing.UIManager.getDefaults();
            selectedColor = defaults.getColor("List.selectionBackground");
            unselectedColor = defaults.getColor("List.background");
        }

        public Component getTableCellRendererComponent(JTable jTable, Object value, boolean isSelected,
                boolean hasFocus, int rowIndex, int colIndex) {
            setSelected(Boolean.TRUE.equals(value));
            setBackground(table.isRowSelected(rowIndex) ? selectedColor : unselectedColor);
            return this;
        }
    }

    private static class MyTableRowSorter extends TableRowSorter<TableModel> {

        private boolean ignoreAddRowSorterListener;

        public MyTableRowSorter(TableModel model) {
            super(model);
        }

        @Override
        public void addRowSorterListener(RowSorterListener l) {
            if (!ignoreAddRowSorterListener) {
                super.addRowSorterListener(l);
            }
        }

        public void setIgnoreAddRowSorterListener(boolean ignore) {
            this.ignoreAddRowSorterListener = ignore;
        }
    }

    private class MyListSelectionListener implements ListSelectionListener {

        private boolean ignoreUpdateEvent;

        @Override
        public void valueChanged(ListSelectionEvent e) {
            if (!e.getValueIsAdjusting() && !model.isShowingSelected()) {
                update();
            }
        }

        public void setIgnoreUpdateEvent(boolean ignoreUpdateEvent) {
            this.ignoreUpdateEvent = ignoreUpdateEvent;
            update();
        }

        private void update() {
            if (!ignoreUpdateEvent) {
                HashSet<Integer> selectedRows = new HashSet<Integer>(
                        Arrays.asList(ArrayUtils.toObject(table.getSelectedRows())));
                for (int i = 0; i < table.getRowCount(); i++) {
                    int modelRow = table.convertRowIndexToModel(i);
                    model.setValueAt(selectedRows.contains(i), modelRow, 0);
                }
                updateOk();
            }
        }
    }

    private class MySelectionModel extends DefaultListSelectionModel {

        private boolean firstColumnEvent;
        private boolean passThrough;

        public void setFirstColumnEvent(boolean firstColumnEvent) {
            this.firstColumnEvent = firstColumnEvent;
            this.passThrough = false;
        }

        @Override
        public void setSelectionInterval(int index0, int index1) {
            if (!passThrough && firstColumnEvent) {
                toggleRowSelection(index1);
            } else {
                super.setSelectionInterval(index0, index1);
            }
        }

        private void toggleRowSelection(int rowIndex) {
            // avoid special handling on recursive calls
            passThrough = true;

            // when clicking on the checkbox column only consider the row where user clicked (index1)
            K key = model.getFields().get(table.convertRowIndexToModel(rowIndex)).getKey();
            if (model.getSelectedFields().contains(key)) {
                super.removeSelectionInterval(rowIndex, rowIndex);
            } else {
                super.addSelectionInterval(rowIndex, rowIndex);
            }
        }

        @Override
        public void addSelectionInterval(int index0, int index1) {
            if (!passThrough && firstColumnEvent) {
                toggleRowSelection(index1);
            } else {
                super.addSelectionInterval(index0, index1);
            }
        }

        public void doAddSelectionInterval(int index0, int index1) {
            super.addSelectionInterval(index0, index1);
        }
    }
}