blob: 475220cb6d34c81cf51347030c5a977819c3619a [file] [log] [blame]
/*
* 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;
}
}