blob: 4d8314e9a1412156e78539e94c358e4d0acab5f2 [file] [log] [blame]
/*
* Copyright 2000-2009 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.ide.commander;
import com.intellij.ide.structureView.StructureViewTreeElement;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.IndexComparator;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.ui.ListScrollingUtil;
import com.intellij.util.Alarm;
import com.intellij.util.containers.HashMap;
import gnu.trove.THashSet;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;
/**
* @author Eugene Belyaev
*/
public abstract class AbstractListBuilder {
protected final Project myProject;
protected final JList myList;
protected final Model myModel;
protected final AbstractTreeStructure myTreeStructure;
private final Comparator myComparator;
protected JLabel myParentTitle = null;
private boolean myIsDisposed;
private AbstractTreeNode myCurrentParent = null;
private final AbstractTreeNode myShownRoot;
public interface Model {
void removeAllElements();
void addElement(final Object node);
void replaceElements(final List newElements);
Object[] toArray();
int indexOf(final Object o);
int getSize();
Object getElementAt(int idx);
}
public AbstractListBuilder(final Project project,
final JList list,
final Model model,
final AbstractTreeStructure treeStructure,
final Comparator comparator,
final boolean showRoot) {
myProject = project;
myList = list;
myModel = model;
myTreeStructure = treeStructure;
myComparator = comparator;
final Object rootElement = myTreeStructure.getRootElement();
final Object[] rootChildren = myTreeStructure.getChildElements(rootElement);
if (!showRoot && rootChildren.length == 1 && shouldEnterSingleTopLevelElement(rootChildren[0])) {
myShownRoot = (AbstractTreeNode)rootChildren[0];
}
else {
myShownRoot = (AbstractTreeNode)rootElement;
}
}
protected abstract boolean shouldEnterSingleTopLevelElement(Object rootChild);
public final void setParentTitle(final JLabel parentTitle) {
myParentTitle = parentTitle;
}
public final void drillDown() {
final Object value = getSelectedValue();
if (value instanceof AbstractTreeNode) {
try {
final AbstractTreeNode node = (AbstractTreeNode)value;
buildList(node);
ensureSelectionExist();
}
finally {
updateParentTitle();
}
}
else { // an element that denotes parent
goUp();
}
}
public final void goUp() {
if (myCurrentParent == myShownRoot.getParent()) {
return;
}
final AbstractTreeNode element = myCurrentParent.getParent();
if (element == null) {
return;
}
try {
AbstractTreeNode oldParent = myCurrentParent;
buildList(element);
for (int i = 0; i < myModel.getSize(); i++) {
if (myModel.getElementAt(i) instanceof NodeDescriptor) {
final NodeDescriptor desc = (NodeDescriptor)myModel.getElementAt(i);
final Object elem = desc.getElement();
if (oldParent.equals(elem)) {
selectItem(i);
break;
}
}
}
}
finally {
updateParentTitle();
}
}
protected Object getSelectedValue() {
return myList.getSelectedValue();
}
protected void selectItem(int i) {
ListScrollingUtil.selectItem(myList, i);
}
protected void ensureSelectionExist() {
ListScrollingUtil.ensureSelectionExists(myList);
}
public final void selectElement(final Object element, VirtualFile virtualFile) {
if (element == null) {
return;
}
try {
AbstractTreeNode node = goDownToElement(element, virtualFile);
if (node == null) return;
AbstractTreeNode parentElement = node.getParent();
if (parentElement == null) return;
buildList(parentElement);
for (int i = 0; i < myModel.getSize(); i++) {
if (myModel.getElementAt(i) instanceof AbstractTreeNode) {
final AbstractTreeNode desc = (AbstractTreeNode)myModel.getElementAt(i);
if (desc.getValue() instanceof StructureViewTreeElement) {
StructureViewTreeElement treeelement = (StructureViewTreeElement)desc.getValue();
if (element.equals(treeelement.getValue())) {
selectItem(i);
break;
}
}
else {
if (element.equals(desc.getValue())) {
selectItem(i);
break;
}
}
}
}
}
finally {
updateParentTitle();
}
}
public final void enterElement(final PsiElement element, VirtualFile file) {
try {
AbstractTreeNode lastPathNode = goDownToElement(element, file);
if (lastPathNode == null) return;
buildList(lastPathNode);
ensureSelectionExist();
}
finally {
updateParentTitle();
}
}
private AbstractTreeNode goDownToElement(final Object element, VirtualFile file) {
return goDownToNode((AbstractTreeNode)myTreeStructure.getRootElement(), element, file);
}
public final void enterElement(final AbstractTreeNode element) {
try {
buildList(element);
ensureSelectionExist();
}
finally {
updateParentTitle();
}
}
private AbstractTreeNode goDownToNode(AbstractTreeNode lastPathNode, final Object lastPathElement, VirtualFile file) {
if (file == null) return lastPathNode;
AbstractTreeNode found = lastPathNode;
while (found != null) {
if (nodeIsAcceptableForElement(lastPathNode, lastPathElement)) {
break;
}
else {
found = findInChildren(lastPathNode, file, lastPathElement);
if (found != null) {
lastPathNode = found;
}
}
}
return lastPathNode;
}
private AbstractTreeNode findInChildren(AbstractTreeNode rootElement, VirtualFile file, Object element) {
Object[] childElements = getChildren(rootElement);
List<AbstractTreeNode> nodes = getAllAcceptableNodes(childElements, file);
if (nodes.size() == 1) return nodes.get(0);
if (nodes.isEmpty()) return null;
if (file.isDirectory()) {
return nodes.get(0);
}
else {
return performDeepSearch(nodes.toArray(), element, new THashSet<AbstractTreeNode>());
}
}
private AbstractTreeNode performDeepSearch(Object[] nodes, Object element, Set<AbstractTreeNode> visited) {
for (Object node1 : nodes) {
AbstractTreeNode node = (AbstractTreeNode)node1;
if (nodeIsAcceptableForElement(node, element)) return node;
Object[] children = getChildren(node);
if (visited.add(node)) {
AbstractTreeNode nodeResult = performDeepSearch(children, element, visited);
if (nodeResult != null) {
return nodeResult;
}
}
}
return null;
}
protected abstract boolean nodeIsAcceptableForElement(AbstractTreeNode node, Object element);
protected abstract List<AbstractTreeNode> getAllAcceptableNodes(Object[] childElements, VirtualFile file);
public void dispose() {
myIsDisposed = true;
}
private void buildList(final AbstractTreeNode parentElement) {
myCurrentParent = parentElement;
final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
alarm.addRequest(
new Runnable() {
@Override
public void run() {
myList.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
},
200
);
final Object[] children = getChildren(parentElement);
myModel.removeAllElements();
if (shouldAddTopElement()) {
myModel.addElement(new TopLevelNode(myProject, parentElement.getValue()));
}
for (Object aChildren : children) {
AbstractTreeNode child = (AbstractTreeNode)aChildren;
child.update();
}
if (myComparator != null) {
Arrays.sort(children, myComparator);
}
for (Object aChildren : children) {
myModel.addElement(aChildren);
}
final int n = alarm.cancelAllRequests();
if (n == 0) {
alarm.addRequest(
new Runnable() {
@Override
public void run() {
myList.setCursor(Cursor.getDefaultCursor());
}
},
0
);
}
}
protected boolean shouldAddTopElement() {
return !myShownRoot.equals(myCurrentParent);
}
private Object[] getChildren(final AbstractTreeNode parentElement) {
if (parentElement == null) {
return new Object[]{myTreeStructure.getRootElement()};
}
else {
return myTreeStructure.getChildElements(parentElement);
}
}
protected final void updateList() {
if (myIsDisposed || myCurrentParent == null) {
return;
}
if (myTreeStructure.hasSomethingToCommit()) {
myTreeStructure.commit();
}
AbstractTreeNode parentDescriptor = myCurrentParent;
while (true) {
parentDescriptor.update();
if (parentDescriptor.getValue() != null) break;
parentDescriptor = parentDescriptor.getParent();
}
final Object[] children = getChildren(parentDescriptor);
final HashMap<Object,Integer> elementToIndexMap = new HashMap<Object, Integer>();
for (int i = 0; i < children.length; i++) {
elementToIndexMap.put(children[i], Integer.valueOf(i));
}
final List<NodeDescriptor> resultDescriptors = new ArrayList<NodeDescriptor>();
final Object[] listChildren = myModel.toArray();
for (final Object child : listChildren) {
if (!(child instanceof NodeDescriptor)) {
continue;
}
final NodeDescriptor descriptor = (NodeDescriptor)child;
descriptor.update();
final Object newElement = descriptor.getElement();
final Integer index = newElement != null ? elementToIndexMap.get(newElement) : null;
if (index != null) {
resultDescriptors.add(descriptor);
descriptor.setIndex(index.intValue());
elementToIndexMap.remove(newElement);
}
}
for (final Object child : elementToIndexMap.keySet()) {
final Integer index = elementToIndexMap.get(child);
if (index != null) {
final NodeDescriptor childDescr = myTreeStructure.createDescriptor(child, parentDescriptor);
childDescr.setIndex(index.intValue());
childDescr.update();
resultDescriptors.add(childDescr);
}
}
final SelectionInfo selection = storeSelection();
if (myComparator != null) {
Collections.sort(resultDescriptors, myComparator);
}
else {
Collections.sort(resultDescriptors, IndexComparator.INSTANCE);
}
if (shouldAddTopElement()) {
final List elems = new ArrayList();
elems.add(new TopLevelNode(myProject, parentDescriptor.getValue()));
elems.addAll(resultDescriptors);
myModel.replaceElements(elems);
}
else {
myModel.replaceElements(resultDescriptors);
}
restoreSelection(selection);
updateParentTitle();
}
private static final class SelectionInfo {
public final ArrayList<Object> mySelectedObjects;
public final Object myLeadSelection;
public final int myLeadSelectionIndex;
public SelectionInfo(final ArrayList<Object> selectedObjects, final int leadSelectionIndex, final Object leadSelection) {
myLeadSelection = leadSelection;
myLeadSelectionIndex = leadSelectionIndex;
mySelectedObjects = selectedObjects;
}
}
private SelectionInfo storeSelection() {
final ListSelectionModel selectionModel = myList.getSelectionModel();
final ArrayList<Object> selectedObjects = new ArrayList<Object>();
final int[] selectedIndices = myList.getSelectedIndices();
final int leadSelectionIndex = selectionModel.getLeadSelectionIndex();
Object leadSelection = null;
for (final int index : selectedIndices) {
if (index < myList.getModel().getSize()) {
final Object o = myModel.getElementAt(index);
selectedObjects.add(o);
if (index == leadSelectionIndex) {
leadSelection = o;
}
}
}
return new SelectionInfo(selectedObjects, leadSelectionIndex, leadSelection);
}
private void restoreSelection(final SelectionInfo selection) {
final ArrayList<Object> selectedObjects = selection.mySelectedObjects;
final ListSelectionModel selectionModel = myList.getSelectionModel();
selectionModel.clearSelection();
if (!selectedObjects.isEmpty()) {
int leadIndex = -1;
for (int i = 0; i < selectedObjects.size(); i++) {
final Object o = selectedObjects.get(i);
final int index = myModel.indexOf(o);
if (index > -1) {
selectionModel.addSelectionInterval(index, index);
if (o == selection.myLeadSelection) {
leadIndex = index;
}
}
}
if (selectionModel.getMinSelectionIndex() == -1) {
final int toSelect = Math.min(selection.myLeadSelectionIndex, myModel.getSize() - 1);
if (toSelect >= 0) {
myList.setSelectedIndex(toSelect);
}
}
else if (leadIndex != -1) {
selectionModel.setLeadSelectionIndex(leadIndex);
}
}
}
public final AbstractTreeNode getParentNode() {
return myCurrentParent;
}
protected abstract void updateParentTitle();
public final void buildRoot() {
buildList(myShownRoot);
}
}