| /* |
| * 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.uiDesigner.componentTree; |
| |
| import com.intellij.ide.util.treeView.AbstractTreeBuilder; |
| import com.intellij.ide.util.treeView.NodeDescriptor; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.progress.util.StatusBarProgress; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.uiDesigner.FormEditingUtil; |
| import com.intellij.uiDesigner.HierarchyChangeListener; |
| import com.intellij.uiDesigner.SelectionWatcher; |
| import com.intellij.uiDesigner.designSurface.GuiEditor; |
| import com.intellij.uiDesigner.propertyInspector.DesignerToolWindowManager; |
| import com.intellij.uiDesigner.propertyInspector.PropertyInspector; |
| import com.intellij.uiDesigner.radComponents.RadComponent; |
| import com.intellij.uiDesigner.radComponents.RadContainer; |
| import org.jetbrains.annotations.NotNull; |
| |
| import javax.swing.event.TreeSelectionEvent; |
| import javax.swing.event.TreeSelectionListener; |
| import javax.swing.tree.DefaultTreeModel; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.Set; |
| |
| /** |
| * @author Anton Katilin |
| * @author Vladimir Kondratyev |
| */ |
| public final class ComponentTreeBuilder extends AbstractTreeBuilder { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.componentTree.ComponentTreeBuilder"); |
| |
| private final GuiEditor myEditor; |
| private final MySelectionWatcher mySelectionWatcher; |
| /** |
| * More then 0 if we are inside some change. In this case we have not |
| * react on our own events. |
| */ |
| private int myInsideChange; |
| private final MyHierarchyChangeListener myHierarchyChangeListener; |
| private MyTreeSelectionListener myTreeSelectionListener; |
| |
| public ComponentTreeBuilder(final ComponentTree tree, @NotNull final GuiEditor editor) { |
| super(tree,(DefaultTreeModel)tree.getModel(), new ComponentTreeStructure(editor), MyComparator.ourComparator); |
| |
| myEditor = editor; |
| mySelectionWatcher = new MySelectionWatcher(editor); |
| |
| initRootNode(); |
| syncSelection(); |
| |
| myTreeSelectionListener = new MyTreeSelectionListener(); |
| myHierarchyChangeListener = new MyHierarchyChangeListener(); |
| getTree().getSelectionModel().addTreeSelectionListener(myTreeSelectionListener); |
| editor.addHierarchyChangeListener(myHierarchyChangeListener); |
| } |
| |
| |
| public void dispose() { |
| myEditor.removeHierarchyChangeListener(myHierarchyChangeListener); |
| if (myTreeSelectionListener != null) { |
| getTree().getSelectionModel().removeTreeSelectionListener(myTreeSelectionListener); |
| myTreeSelectionListener = null; |
| } |
| mySelectionWatcher.dispose(); |
| super.dispose(); |
| } |
| |
| private ComponentTreeStructure getComponentTreeStructure(){ |
| return (ComponentTreeStructure)getTreeStructure(); |
| } |
| |
| protected boolean isAlwaysShowPlus(final NodeDescriptor descriptor){ |
| return false; |
| } |
| |
| protected boolean isAutoExpandNode(final NodeDescriptor descriptor){ |
| return getComponentTreeStructure().isAutoExpandNode(descriptor); |
| } |
| |
| public void beginUpdateSelection() { |
| myInsideChange++; |
| } |
| |
| public void endUpdateSelection() { |
| myInsideChange--; |
| updateSelection(); |
| } |
| |
| /** |
| * This method synchronizes selection in the tree with the selected |
| * RadComponent in the component hierarchy |
| */ |
| private void syncSelection() { |
| // Found selected components |
| final RadContainer rootContainer=myEditor.getRootContainer(); |
| final ArrayList<RadComponent> selection = new ArrayList<RadComponent>(); |
| FormEditingUtil.iterate( |
| rootContainer, |
| new FormEditingUtil.ComponentVisitor<RadComponent>() { |
| public boolean visit(final RadComponent component) { |
| if(component.isSelected()){ |
| selection.add(component); |
| } |
| return true; |
| } |
| } |
| ); |
| if(selection.size() == 0){ |
| // If there is no selected component in the hierarchy, then |
| // we have to select RadRootContainer |
| selection.add(rootContainer); |
| } |
| |
| final ComponentPtr[] componentPtrs = new ComponentPtr[selection.size()]; |
| for (int i = 0; i < selection.size(); i++) { |
| componentPtrs [i] = new ComponentPtr(myEditor, selection.get(i)); |
| } |
| |
| // Set selection in the tree |
| select(componentPtrs, null); |
| |
| // Notify the ComponentTree that selected component changed |
| myEditor.fireSelectedComponentChanged(); |
| } |
| |
| @NotNull |
| protected ProgressIndicator createProgressIndicator() { |
| return new StatusBarProgress(); |
| } |
| |
| /** |
| * Compares RadComponent based on their natural order in the container. |
| */ |
| private static final class MyComparator implements Comparator<NodeDescriptor>{ |
| public static final MyComparator ourComparator=new MyComparator(); |
| |
| private static int indexOf(final RadContainer container, final RadComponent component){ |
| if (container != null) { |
| for(int i = container.getComponentCount() - 1; i >= 0 ; i--){ |
| if(component.equals(container.getComponent(i))){ |
| return i; |
| } |
| } |
| } |
| return -1; |
| } |
| |
| public int compare(final NodeDescriptor descriptor1, final NodeDescriptor descriptor2) { |
| if (descriptor1 instanceof ComponentPtrDescriptor && descriptor2 instanceof ComponentPtrDescriptor) { |
| final RadComponent component1 = ((ComponentPtrDescriptor)descriptor1).getComponent(); |
| final RadComponent component2 = ((ComponentPtrDescriptor)descriptor2).getComponent(); |
| if (component1 == null || component2 == null) { |
| return 0; |
| } |
| final RadContainer container1 = component1.getParent(); |
| final RadContainer container2 = component2.getParent(); |
| if(Comparing.equal(container1, container2)){ |
| return indexOf(container1, component1) - indexOf(container2, component2); |
| } |
| else{ |
| return 0; |
| } |
| }else{ |
| return 0; |
| } |
| } |
| } |
| |
| /** |
| * Synchronizes tree with GuiEditor |
| */ |
| private final class MyHierarchyChangeListener implements HierarchyChangeListener{ |
| public void hierarchyChanged(){ |
| if (myInsideChange>0) { |
| return; |
| } |
| |
| myInsideChange++; |
| try{ |
| queueUpdate().doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| // After updating the tree we have to synchronize the selection in the tree |
| // with selected element in the hierarchy |
| syncSelection(); |
| } |
| }); |
| }finally{ |
| myInsideChange--; |
| } |
| } |
| } |
| |
| /** |
| * Synchronizes selection in the tree with selection in the editor |
| */ |
| private final class MySelectionWatcher extends SelectionWatcher{ |
| public MySelectionWatcher(final GuiEditor editor) { |
| super(editor); |
| } |
| |
| protected void selectionChanged(final RadComponent component, final boolean ignored) { |
| updateSelection(); |
| } |
| } |
| |
| private void updateSelection() { |
| final PropertyInspector propertyInspector = DesignerToolWindowManager.getInstance(myEditor).getPropertyInspector(); |
| if (propertyInspector.isEditing()) { |
| propertyInspector.stopEditing(); |
| } |
| |
| if(myInsideChange > 0){ |
| return; |
| } |
| myInsideChange++; |
| try { |
| updateFromRoot(); |
| syncSelection(); |
| } finally { |
| myInsideChange--; |
| } |
| } |
| |
| /** |
| * Synchronizes GuiEditor with the tree |
| */ |
| private final class MyTreeSelectionListener implements TreeSelectionListener { |
| public void valueChanged(final TreeSelectionEvent e) { |
| if (myInsideChange>0) { |
| return; |
| } |
| |
| final Set<ComponentPtr> selectedElements = getSelectedElements(ComponentPtr.class); |
| myInsideChange++; |
| try{ |
| FormEditingUtil.clearSelection(myEditor.getRootContainer()); |
| boolean hasComponentInTab = false; |
| int count = 0; |
| for(ComponentPtr ptr: selectedElements) { |
| ptr.validate(); |
| if(ptr.isValid()) { |
| final RadComponent component=ptr.getComponent(); |
| LOG.assertTrue(component!=null); |
| if (!hasComponentInTab) { |
| hasComponentInTab = FormEditingUtil.selectComponent(myEditor, component); |
| } |
| else { |
| component.setSelected(true); |
| } |
| if (++count == selectedElements.size()) { |
| myEditor.scrollComponentInView(component); |
| } |
| } |
| } |
| |
| // Notify ComponentTree that selected component changed |
| myEditor.fireSelectedComponentChanged(); |
| }finally{ |
| myInsideChange--; |
| } |
| } |
| } |
| } |