| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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.intellij.ui.dualView; |
| |
| import com.intellij.icons.AllIcons; |
| import com.intellij.ide.util.PropertiesComponent; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.actionSystem.CommonShortcuts; |
| import com.intellij.openapi.actionSystem.Presentation; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.ui.ScrollPaneFactory; |
| import com.intellij.ui.UIBundle; |
| import com.intellij.ui.table.BaseTableView; |
| import com.intellij.ui.table.JBTable; |
| import com.intellij.ui.table.SelectionProvider; |
| import com.intellij.ui.table.TableView; |
| import com.intellij.ui.treeStructure.Tree; |
| import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns; |
| import com.intellij.ui.treeStructure.treetable.TreeTableModel; |
| import com.intellij.util.EditSourceOnDoubleClickHandler; |
| import com.intellij.util.config.Storage; |
| import com.intellij.util.ui.ColumnInfo; |
| import com.intellij.util.ui.ListTableModel; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.swing.*; |
| import javax.swing.border.Border; |
| import javax.swing.event.ListSelectionListener; |
| import javax.swing.event.TreeModelEvent; |
| import javax.swing.event.TreeModelListener; |
| import javax.swing.table.AbstractTableModel; |
| import javax.swing.table.TableCellRenderer; |
| import javax.swing.table.TableColumnModel; |
| import javax.swing.tree.TreeCellRenderer; |
| import javax.swing.tree.TreeNode; |
| import javax.swing.tree.TreePath; |
| import javax.swing.tree.TreeSelectionModel; |
| import java.awt.*; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.*; |
| import java.util.List; |
| |
| public class DualView extends JPanel { |
| private final CardLayout myCardLayout; |
| private TreeTableView myTreeView; |
| |
| private JTable myCurrentView = null; |
| |
| private TableView myFlatView; |
| @NonNls private static final String TREE = "TREE"; |
| @NonNls private static final String FLAT = "FLAT"; |
| private TreeCellRenderer myTreeCellRenderer; |
| private boolean myRootVisible; |
| private boolean myTableRefreshingIsLocked = false; |
| private CellWrapper myCellWrapper; |
| |
| private final Storage.PropertiesComponentStorage myFlatStorage; |
| private final Storage.PropertiesComponentStorage myTreeStorage; |
| private final PropertyChangeListener myPropertyChangeListener; |
| |
| private boolean myZipByHeight; |
| |
| public DualView(Object root, DualViewColumnInfo[] columns, @NonNls String columnServiceKey, Project project) { |
| super(new CardLayout()); |
| |
| myTreeStorage = new Storage.PropertiesComponentStorage(columnServiceKey + "_tree", |
| PropertiesComponent.getInstance(project)); |
| myFlatStorage = new Storage.PropertiesComponentStorage(columnServiceKey + "_flat", |
| PropertiesComponent.getInstance(project)); |
| |
| myCardLayout = (CardLayout)getLayout(); |
| |
| add(createTreeComponent(columns, (TreeNode)root), TREE); |
| |
| add(createFlatComponent(columns), FLAT); |
| |
| (myTreeView.getTreeViewModel()).addTreeModelListener(new TreeModelListener() { |
| public void treeNodesInserted(TreeModelEvent e) { |
| refreshFlatModel(); |
| } |
| |
| public void treeNodesRemoved(TreeModelEvent e) { |
| refreshFlatModel(); |
| } |
| |
| public void treeStructureChanged(TreeModelEvent e) { |
| refreshFlatModel(); |
| } |
| |
| public void treeNodesChanged(TreeModelEvent e) { |
| refreshFlatModel(); |
| } |
| }); |
| |
| setRootVisible(true); |
| |
| switchToTheFlatMode(); |
| |
| restoreState(); |
| |
| myPropertyChangeListener = new PropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent evt) { |
| saveState(); |
| } |
| }; |
| |
| addWidthListenersTo(myTreeView); |
| addWidthListenersTo(myFlatView); |
| } |
| |
| private void addWidthListenersTo(JTable treeView) { |
| TableColumnModel columnModel = treeView.getColumnModel(); |
| int columnCount = columnModel.getColumnCount(); |
| for (int i = 0; i < columnCount; i++) { |
| columnModel.getColumn(i).addPropertyChangeListener(myPropertyChangeListener); |
| } |
| } |
| |
| public void restoreState() { |
| BaseTableView.restore(myFlatStorage, myFlatView); |
| BaseTableView.restore(myTreeStorage, myTreeView); |
| } |
| |
| public void lockTableRefreshing() { |
| myTableRefreshingIsLocked = true; |
| } |
| |
| public void unlockTableRefreshingAndRefresh() { |
| myTableRefreshingIsLocked = false; |
| refreshFlatModel(); |
| } |
| |
| private void refreshFlatModel() { |
| if (myTableRefreshingIsLocked) return; |
| ((ListTableModel)myFlatView.getModel()).setItems(myTreeView.getFlattenItems()); |
| } |
| |
| private ColumnInfo[] createTreeColumns(DualViewColumnInfo[] columns) { |
| Collection<ColumnInfo> result = new ArrayList<ColumnInfo>(); |
| |
| final ColumnInfo firstColumn = columns[0]; |
| ColumnInfo firstTreeColumn = new ColumnInfo(firstColumn.getName()) { |
| public Object valueOf(Object object) { |
| return firstColumn.valueOf(object); |
| } |
| |
| public Class getColumnClass() { |
| return TreeTableModel.class; |
| } |
| |
| public boolean isCellEditable(Object o) { |
| return true; |
| } |
| }; |
| result.add(firstTreeColumn); |
| for (int i = 1; i < columns.length; i++) { |
| DualViewColumnInfo column = columns[i]; |
| if (column.shouldBeShownIsTheTree()) result.add(column); |
| } |
| |
| return result.toArray(new ColumnInfo[result.size()]); |
| } |
| |
| public void switchToTheFlatMode() { |
| if (myFlatView == myCurrentView) return; |
| copySelection(myTreeView, myFlatView); |
| changeViewTo(myFlatView); |
| myCardLayout.show(this, FLAT); |
| } |
| |
| private void changeViewTo(JTable view) { |
| myCurrentView = view; |
| if (myCurrentView != null) { |
| myCurrentView.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); |
| if (myCurrentView instanceof JBTable) { |
| ((JBTable)myCurrentView).setStriped(true); |
| } |
| final int row = myCurrentView.getSelectedRow(); |
| myCurrentView.scrollRectToVisible(myCurrentView.getCellRect(row, 0, true)); |
| } |
| } |
| |
| private static void copySelection(SelectionProvider from, SelectionProvider to) { |
| to.clearSelection(); |
| |
| Collection selection = from.getSelection(); |
| |
| for (Iterator each = selection.iterator(); each.hasNext();) { |
| to.addSelection(each.next()); |
| } |
| } |
| |
| public void switchToTheTreeMode() { |
| if (myTreeView == myCurrentView) return; |
| copySelection(myFlatView, myTreeView); |
| changeViewTo(myTreeView); |
| myCardLayout.show(this, TREE); |
| } |
| |
| private Component createTreeComponent(DualViewColumnInfo[] columns, TreeNode root) { |
| myTreeView = new TreeTableView(new ListTreeTableModelOnColumns(root, createTreeColumns(columns))) { |
| public TableCellRenderer getCellRenderer(int row, int column) { |
| return createWrappedRenderer(super.getCellRenderer(row, column)); |
| } |
| }; |
| myTreeView.getTree().getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); |
| JPanel result = new JPanel(new BorderLayout()); |
| result.add(ScrollPaneFactory.createScrollPane(myTreeView), BorderLayout.CENTER); |
| return result; |
| } |
| |
| private Component createFlatComponent(DualViewColumnInfo[] columns) { |
| |
| ArrayList<ColumnInfo> shownColumns = new ArrayList<ColumnInfo>(); |
| |
| for (int i = 0; i < columns.length; i++) { |
| DualViewColumnInfo column = columns[i]; |
| if (column.shouldBeShownIsTheTable()) shownColumns.add(column); |
| } |
| |
| ListTableModel flatModel = new ListTableModel(shownColumns.toArray(new ColumnInfo[shownColumns.size()])); |
| myFlatView = new TableView(flatModel) { |
| public TableCellRenderer getCellRenderer(int row, int column) { |
| return createWrappedRenderer(super.getCellRenderer(row, column)); |
| } |
| |
| @NotNull |
| @Override |
| public Component prepareRenderer(@NotNull TableCellRenderer renderer, int row, int column) { |
| final Component c = super.prepareRenderer(renderer, row, column); |
| if (c instanceof JComponent && !myFlatView.getCellSelectionEnabled()) { |
| ((JComponent)c).setBorder(null); |
| } |
| return c; |
| } |
| }; |
| myFlatView.setCellSelectionEnabled(false); |
| myFlatView.setColumnSelectionAllowed(false); |
| myFlatView.setRowSelectionAllowed(true); |
| |
| refreshFlatModel(); |
| |
| JPanel result = new JPanel(new BorderLayout()); |
| result.add(ScrollPaneFactory.createScrollPane(myFlatView), BorderLayout.CENTER); |
| return result; |
| } |
| |
| private TableCellRenderer createWrappedRenderer(final TableCellRenderer renderer) { |
| if (myCellWrapper == null) { |
| return renderer; |
| } |
| else { |
| return new TableCellRendererWrapper(renderer); |
| } |
| } |
| |
| public void expandAll() { |
| expandPath(myTreeView.getTree(), new TreePath(myTreeView.getTree().getModel().getRoot())); |
| } |
| |
| public void collapseAll() { |
| collapsePath(myTreeView.getTree(), new TreePath(myTreeView.getTree().getModel().getRoot())); |
| } |
| |
| private void expandPath(JTree tree, TreePath path) { |
| tree.expandPath(path); |
| |
| final TreeNode node = ((TreeNode)path.getLastPathComponent()); |
| final Enumeration children = node.children(); |
| while (children.hasMoreElements()) { |
| TreeNode child = (TreeNode)children.nextElement(); |
| expandPath(tree, path.pathByAddingChild(child)); |
| } |
| } |
| |
| private void collapsePath(JTree tree, TreePath path) { |
| |
| final TreeNode node = ((TreeNode)path.getLastPathComponent()); |
| final Enumeration children = node.children(); |
| while (children.hasMoreElements()) { |
| TreeNode child = (TreeNode)children.nextElement(); |
| collapsePath(tree, path.pathByAddingChild(child)); |
| } |
| |
| if (!((path.getLastPathComponent() == tree.getModel().getRoot()) && !myRootVisible)) { |
| tree.collapsePath(path); |
| } |
| } |
| |
| public List getSelection() { |
| ArrayList result = new ArrayList(); |
| SelectionProvider visibleTable = (SelectionProvider)getVisibleTable(); |
| Collection selection = visibleTable.getSelection(); |
| for (Iterator each = selection.iterator(); each.hasNext();) { |
| result.add(each.next()); |
| } |
| return result; |
| } |
| |
| private JTable getVisibleTable() { |
| return myCurrentView; |
| } |
| |
| public void setShowGrid(boolean aBoolean) { |
| myTreeView.setShowGrid(aBoolean); |
| } |
| |
| public void setSelectionInterval(int first, int last) { |
| final int treeRowCount = myTreeView.getModel().getRowCount(); |
| if (first < 0 || last < 0) return; |
| |
| if (first < treeRowCount && last < treeRowCount) { |
| myTreeView.getSelectionModel().addSelectionInterval(first, last); |
| } |
| |
| final int flatRowCount = myFlatView.getRowCount(); |
| if (first < flatRowCount && last < flatRowCount) { |
| myFlatView.getSelectionModel().addSelectionInterval(first, last); |
| } |
| } |
| |
| public void addListSelectionListener(ListSelectionListener listSelectionListener) { |
| myTreeView.getSelectionModel().addListSelectionListener(listSelectionListener); |
| myFlatView.getSelectionModel().addListSelectionListener(listSelectionListener); |
| } |
| |
| public void changeColumnSet(DualViewColumnInfo[] columns) { |
| myTreeView.setTableModel(new ListTreeTableModelOnColumns((TreeNode)myTreeView.getTreeViewModel().getRoot(), |
| createTreeColumns(columns))); |
| myFlatView.setModelAndUpdateColumns(new ListTableModel(columns)); |
| if (myTreeCellRenderer != null) myTreeView.setTreeCellRenderer(myTreeCellRenderer); |
| setRootVisible(myRootVisible); |
| |
| refreshFlatModel(); |
| |
| addWidthListenersTo(myTreeView); |
| addWidthListenersTo(myFlatView); |
| } |
| |
| public Tree getTree() { |
| return myTreeView.getTree(); |
| } |
| |
| public TreeTableView getTreeView() { |
| return myTreeView; |
| } |
| |
| public TableView getFlatView() { |
| return myFlatView; |
| } |
| |
| public void setViewBorder(Border border) { |
| if (myTreeView != null) ((JComponent)myTreeView.getParent().getParent()).setBorder(border); |
| if (myFlatView != null) ((JComponent)myFlatView.getParent().getParent()).setBorder(border); |
| } |
| |
| public void setRootVisible(boolean aBoolean) { |
| myRootVisible = aBoolean; |
| myTreeView.setRootVisible(myRootVisible); |
| } |
| |
| public void setTreeCellRenderer(TreeCellRenderer cellRenderer) { |
| myTreeCellRenderer = cellRenderer; |
| myTreeView.setTreeCellRenderer(cellRenderer); |
| } |
| |
| public AnAction getExpandAllAction() { |
| return new AnAction(UIBundle.message("tree.view.expand.all.action.name"), null, AllIcons.Actions.Expandall) { |
| public void update(AnActionEvent e) { |
| Presentation presentation = e.getPresentation(); |
| presentation.setVisible(true); |
| presentation.setEnabled(myCurrentView == myTreeView); |
| } |
| |
| public void actionPerformed(AnActionEvent e) { |
| expandAll(); |
| } |
| }; |
| } |
| |
| public AnAction getCollapseAllAction() { |
| return new AnAction(UIBundle.message("tree.view.collapse.all.action.name"), null, AllIcons.Actions.Collapseall) { |
| public void update(AnActionEvent e) { |
| Presentation presentation = e.getPresentation(); |
| presentation.setVisible(true); |
| presentation.setEnabled(myCurrentView == myTreeView); |
| } |
| |
| public void actionPerformed(AnActionEvent e) { |
| collapseAll(); |
| } |
| }; |
| } |
| |
| public void setCellWrapper(CellWrapper wrapper) { |
| myCellWrapper = wrapper; |
| } |
| |
| public void installEditSourceOnDoubleClickHandler() { |
| EditSourceOnDoubleClickHandler.install(myTreeView); |
| EditSourceOnDoubleClickHandler.install(myFlatView); |
| } |
| |
| public void installDoubleClickHandler(AnAction action) { |
| action.registerCustomShortcutSet(CommonShortcuts.DOUBLE_CLICK_1, myFlatView); |
| action.registerCustomShortcutSet(CommonShortcuts.DOUBLE_CLICK_1, myTreeView); |
| } |
| |
| public void dispose() { |
| saveState(); |
| } |
| |
| public void saveState() { |
| BaseTableView.store(myFlatStorage, myFlatView); |
| BaseTableView.store(myTreeStorage, myTreeView); |
| } |
| |
| public void setRoot(final TreeNode node, final List<Object> selection) { |
| final List<Object> currentlySelected = myFlatView.getSelectedObjects(); |
| final List<Object> targetSelection = (currentlySelected != null && (! currentlySelected.isEmpty())) ? currentlySelected : selection; |
| //final Object obj = myFlatView.getSelectedObject() != null ? myFlatView.getSelectedObject() : selection; |
| |
| myTreeView.getTreeViewModel().setRoot(node); |
| |
| if ((targetSelection != null) && (! targetSelection.isEmpty())) { |
| final List items = myFlatView.getItems(); |
| for (Object selElement : targetSelection) { |
| if (items.contains(selElement)) { |
| final int idx = items.indexOf(selElement); |
| setSelectionInterval(idx, idx); |
| } |
| } |
| } |
| } |
| |
| public void rebuild() { |
| ((AbstractTableModel)myFlatView.getModel()).fireTableDataChanged(); |
| ((AbstractTableModel)myTreeView.getModel()).fireTableDataChanged(); |
| } |
| |
| public class TableCellRendererWrapper implements TableCellRenderer { |
| private final TableCellRenderer myRenderer; |
| |
| public TableCellRendererWrapper(final TableCellRenderer renderer) { |
| myRenderer = renderer; |
| } |
| |
| public TableCellRenderer getRenderer() { |
| return myRenderer; |
| } |
| |
| public Component getTableCellRendererComponent(JTable table, |
| Object value, |
| boolean isSelected, |
| boolean hasFocus, |
| int row, |
| int column) { |
| Component result = myRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); |
| Object treeNode = null; |
| |
| final int modelRow = table.convertRowIndexToModel(row); |
| |
| if (myCurrentView == myTreeView) { |
| TreePath path = myTreeView.getTree().getPathForRow(modelRow); |
| if (path != null) { |
| treeNode = path.getLastPathComponent(); |
| } |
| } |
| else if (myCurrentView == myFlatView) { |
| treeNode = myFlatView.getItems().get(modelRow); |
| } |
| |
| myCellWrapper.wrap(result, table, value, isSelected, hasFocus, row, column, treeNode); |
| return result; |
| } |
| } |
| |
| @Override |
| public Dimension getPreferredSize() { |
| final Dimension was = super.getPreferredSize(); |
| if (! myZipByHeight) return was; |
| final int tableHeight = myFlatView.getTableHeader().getHeight() + myFlatView.getTableViewModel().getRowCount() * |
| myFlatView.getRowHeight(); |
| return new Dimension(was.width, tableHeight); |
| } |
| |
| @Override |
| public Dimension getMinimumSize() { |
| return myZipByHeight ? getPreferredSize() : super.getMinimumSize(); |
| } |
| |
| public void setZipByHeight(boolean zipByHeight) { |
| myZipByHeight = zipByHeight; |
| } |
| } |