blob: aa5161ea0b96f4e0492073ba7ebef825a301b6b3 [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.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--;
}
}
}
}