69ef61ec612118cf57c6383616a1f14a8fc80cd7
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / FormEditingUtil.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.uiDesigner;
17
18 import com.intellij.lang.properties.PropertiesReferenceManager;
19 import com.intellij.lang.properties.psi.PropertiesFile;
20 import com.intellij.openapi.actionSystem.DataContext;
21 import com.intellij.openapi.actionSystem.PlatformDataKeys;
22 import com.intellij.openapi.command.CommandProcessor;
23 import com.intellij.openapi.fileEditor.FileEditor;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.progress.ProcessCanceledException;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.ui.DialogWrapper;
28 import com.intellij.openapi.ui.Messages;
29 import com.intellij.openapi.ui.popup.JBPopup;
30 import com.intellij.openapi.util.Ref;
31 import com.intellij.psi.*;
32 import com.intellij.ui.awt.RelativePoint;
33 import com.intellij.uiDesigner.compiler.AsmCodeGenerator;
34 import com.intellij.uiDesigner.componentTree.ComponentTreeBuilder;
35 import com.intellij.uiDesigner.core.GridConstraints;
36 import com.intellij.uiDesigner.designSurface.ComponentDropLocation;
37 import com.intellij.uiDesigner.designSurface.DraggedComponentList;
38 import com.intellij.uiDesigner.designSurface.GuiEditor;
39 import com.intellij.uiDesigner.designSurface.Painter;
40 import com.intellij.uiDesigner.editor.UIFormEditor;
41 import com.intellij.uiDesigner.lw.*;
42 import com.intellij.uiDesigner.palette.ComponentItem;
43 import com.intellij.uiDesigner.palette.Palette;
44 import com.intellij.uiDesigner.propertyInspector.UIDesignerToolWindowManager;
45 import com.intellij.uiDesigner.propertyInspector.properties.BindingProperty;
46 import com.intellij.uiDesigner.propertyInspector.properties.IntroComponentProperty;
47 import com.intellij.uiDesigner.radComponents.RadAbstractGridLayoutManager;
48 import com.intellij.uiDesigner.radComponents.RadComponent;
49 import com.intellij.uiDesigner.radComponents.RadContainer;
50 import com.intellij.uiDesigner.radComponents.RadRootContainer;
51 import com.intellij.util.ArrayUtil;
52 import com.intellij.util.IncorrectOperationException;
53 import com.intellij.util.containers.HashSet;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56
57 import javax.swing.*;
58 import java.awt.*;
59 import java.lang.reflect.InvocationTargetException;
60 import java.util.*;
61 import java.util.List;
62
63 /**
64  * @author Anton Katilin
65  * @author Vladimir Kondratyev
66  */
67 public final class FormEditingUtil {
68   private FormEditingUtil() {
69   }
70
71   public static boolean canDeleteSelection(final GuiEditor editor){
72     final ArrayList<RadComponent> selection = getSelectedComponents(editor);
73     if (selection.isEmpty()) return false;
74     final RadRootContainer rootContainer = editor.getRootContainer();
75     if (rootContainer.getComponentCount() > 0 && selection.contains(rootContainer.getComponent(0))) {
76       return false;
77     }
78     return true;
79   }
80
81   /**
82    * <b>This method must be executed in command</b>
83    *
84    * @param editor the editor in which the selection is deleted.
85    */
86   public static void deleteSelection(final GuiEditor editor){
87     final List<RadComponent> selection = getSelectedComponents(editor);
88     deleteComponents(selection, true);
89     editor.refreshAndSave(true);
90   }
91
92   public static void deleteComponents(final Collection<? extends RadComponent> selection, boolean deleteEmptyCells) {
93     if (selection.size() == 0) {
94       return;
95     }
96     final RadRootContainer rootContainer = (RadRootContainer) getRoot(selection.iterator().next());
97     final Set<String> deletedComponentIds = new HashSet<String>();
98     for (final RadComponent component : selection) {
99       boolean wasSelected = component.isSelected();
100       final RadContainer parent = component.getParent();
101
102       boolean wasPackedHorz = false;
103       boolean wasPackedVert = false;
104       if (parent.getParent() != null && parent.getParent().isXY()) {
105         final Dimension minSize = parent.getMinimumSize();
106         wasPackedHorz = parent.getWidth() == minSize.width;
107         wasPackedVert = parent.getHeight() == minSize.height;
108       }
109
110       iterate(component, new ComponentVisitor() {
111         public boolean visit(final IComponent c) {
112           RadComponent rc = (RadComponent)c;
113           BindingProperty.checkRemoveUnusedField(rootContainer, rc.getBinding(), null);
114           deletedComponentIds.add(rc.getId());
115           return true;
116         }
117       });
118
119
120       GridConstraints delConstraints = parent.getLayoutManager().isGrid() ? component.getConstraints() : null;
121
122       int index = parent.indexOfComponent(component);
123       parent.removeComponent(component);
124       if (wasSelected) {
125         if (parent.getComponentCount() > index) {
126           parent.getComponent(index).setSelected(true);
127         }
128         else if (index > 0 && parent.getComponentCount() == index) {
129           parent.getComponent(index-1).setSelected(true);
130         }
131         else {
132           parent.setSelected(true);
133         }
134       }
135       if (delConstraints != null && deleteEmptyCells) {
136         deleteEmptyGridCells(parent, delConstraints);
137       }
138
139       if (wasPackedHorz || wasPackedVert) {
140         final Dimension minSize = parent.getMinimumSize();
141         Dimension newSize = new Dimension(parent.getWidth(), parent.getHeight());
142         if (wasPackedHorz) {
143           newSize.width = minSize.width;
144         }
145         if (wasPackedVert) {
146           newSize.height = minSize.height;
147         }
148         parent.setSize(newSize);
149       }
150     }
151
152     iterate(rootContainer, new ComponentVisitor() {
153       public boolean visit(final IComponent component) {
154         RadComponent rc = (RadComponent)component;
155         for (IProperty p : component.getModifiedProperties()) {
156           if (p instanceof IntroComponentProperty) {
157             IntroComponentProperty icp = (IntroComponentProperty)p;
158             final String value = icp.getValue(rc);
159             if (deletedComponentIds.contains(value)) {
160               try {
161                 icp.resetValue(rc);
162               }
163               catch (Exception e) {
164                 // ignore
165               }
166             }
167           }
168         }
169         return true;
170       }
171     });
172   }
173
174   public static void deleteEmptyGridCells(final RadContainer parent, final GridConstraints delConstraints) {
175     deleteEmptyGridCells(parent, delConstraints, true);
176     deleteEmptyGridCells(parent, delConstraints, false);
177   }
178
179   private static void deleteEmptyGridCells(final RadContainer parent, final GridConstraints delConstraints, final boolean isRow) {
180     final RadAbstractGridLayoutManager layoutManager = parent.getGridLayoutManager();
181     for(int cell=delConstraints.getCell(isRow) + delConstraints.getSpan(isRow)-1; cell>= delConstraints.getCell(isRow); cell--) {
182       if (cell < parent.getGridCellCount(isRow) && GridChangeUtil.canDeleteCell(parent, cell, isRow) == GridChangeUtil.CellStatus.Empty &&
183           !layoutManager.isGapCell(parent, isRow, cell)) {
184         layoutManager.deleteGridCells(parent, cell, isRow);
185       }
186     }
187   }
188
189   private static final int EMPTY_COMPONENT_SIZE = 5;
190
191   private static Component getDeepestEmptyComponentAt(JComponent parent, Point location) {
192     int size = parent.getComponentCount();
193
194     for (int i = 0; i < size; i++) {
195       Component child = parent.getComponent(i);
196
197       if (child.isShowing()) {
198         if (child.getWidth() < EMPTY_COMPONENT_SIZE || child.getHeight() < EMPTY_COMPONENT_SIZE) {
199           Point childLocation = child.getLocationOnScreen();
200           Rectangle bounds = new Rectangle();
201
202           bounds.x = childLocation.x;
203           bounds.y = childLocation.y;
204           bounds.width = child.getWidth();
205           bounds.height = child.getHeight();
206           bounds.grow(child.getWidth() < EMPTY_COMPONENT_SIZE ? EMPTY_COMPONENT_SIZE : 0,
207                       child.getHeight() < EMPTY_COMPONENT_SIZE ? EMPTY_COMPONENT_SIZE : 0);
208
209           if (bounds.contains(location)) {
210             return child;
211           }
212         }
213
214         if (child instanceof JComponent) {
215           Component result = getDeepestEmptyComponentAt((JComponent)child, location);
216
217           if (result != null) {
218             return result;
219           }
220         }
221       }
222     }
223
224     return null;
225   }
226
227   /**
228    * @param x in editor pane coordinates
229    * @param y in editor pane coordinates
230    */
231   public static RadComponent getRadComponentAt(final RadRootContainer rootContainer, final int x, final int y){
232     Point location = new Point(x, y);
233     SwingUtilities.convertPointToScreen(location, rootContainer.getDelegee());
234     Component c = getDeepestEmptyComponentAt(rootContainer.getDelegee(), location);
235
236     if (c == null) {
237       c = SwingUtilities.getDeepestComponentAt(rootContainer.getDelegee(), x, y);
238     }
239
240     RadComponent result = null;
241
242     while (c != null) {
243       if (c instanceof JComponent){
244         final RadComponent component = (RadComponent)((JComponent)c).getClientProperty(RadComponent.CLIENT_PROP_RAD_COMPONENT);
245         if (component != null) {
246
247           if (result == null) {
248             result = component;
249           }
250           else {
251             final Point p = SwingUtilities.convertPoint(rootContainer.getDelegee(), x, y, c);
252             if (Painter.getResizeMask(component, p.x, p.y) != 0) {
253               result = component;
254             }
255           }
256         }
257       }
258       c = c.getParent();
259     }
260
261     return result;
262   }
263
264   /**
265    * @return component which has dragger. There is only one component with the dargger
266    * at a time.
267    */
268   @Nullable
269   public static RadComponent getDraggerHost(@NotNull final GuiEditor editor){
270     final Ref<RadComponent> result = new Ref<RadComponent>();
271     iterate(
272       editor.getRootContainer(),
273       new ComponentVisitor<RadComponent>() {
274         public boolean visit(final RadComponent component) {
275           if(component.hasDragger()){
276             result.set(component);
277             return false;
278           }
279           return true;
280         }
281       }
282     );
283
284     return result.get();
285   }
286
287
288   public static Cursor getMoveDropCursor() {
289     try {
290       return Cursor.getSystemCustomCursor("MoveDrop.32x32");
291     }
292     catch (Exception ex) {
293       return Cursor.getDefaultCursor();
294     }
295   }
296
297   public static Cursor getMoveNoDropCursor() {
298     try {
299       return Cursor.getSystemCustomCursor("MoveNoDrop.32x32");
300     }
301     catch (Exception ex) {
302       return Cursor.getDefaultCursor();
303     }
304   }
305
306   public static Cursor getCopyDropCursor() {
307     try {
308       return Cursor.getSystemCustomCursor("CopyDrop.32x32");
309     }
310     catch (Exception ex) {
311       return Cursor.getDefaultCursor();
312     }
313   }
314
315   /**
316    * @return currently selected components. Method returns the minimal amount of
317    * selected component which contains all selected components. It means that if the
318    * selected container contains some selected children then only this container
319    * will be added to the returned array.
320    */
321   @NotNull
322   public static ArrayList<RadComponent> getSelectedComponents(@NotNull final GuiEditor editor){
323     final ArrayList<RadComponent> result = new ArrayList<RadComponent>();
324     calcSelectedComponentsImpl(result, editor.getRootContainer());
325     return result;
326   }
327
328   private static void calcSelectedComponentsImpl(final ArrayList<RadComponent> result, final RadContainer container){
329     if (container.isSelected()) {
330       if (container.getParent() != null) { // ignore RadRootContainer
331         result.add(container);
332         return;
333       }
334     }
335
336     for (int i = 0; i < container.getComponentCount(); i++) {
337       final RadComponent component = container.getComponent(i);
338       if (component instanceof RadContainer) {
339         calcSelectedComponentsImpl(result, (RadContainer)component);
340       }
341       else {
342         if (component.isSelected()) {
343           result.add(component);
344         }
345       }
346     }
347   }
348
349   /**
350    * @return all selected component inside the <code>editor</code>
351    */
352   @NotNull
353   public static ArrayList<RadComponent> getAllSelectedComponents(@NotNull final GuiEditor editor){
354     final ArrayList<RadComponent> result = new ArrayList<RadComponent>();
355     iterate(
356       editor.getRootContainer(),
357       new ComponentVisitor<RadComponent>() {
358         public boolean visit(final RadComponent component) {
359           if (component.isSelected()) {
360             result.add(component);
361           }
362           return true;
363         }
364       }
365     );
366     return result;
367   }
368
369   public static String getExceptionMessage(Throwable ex) {
370     if (ex instanceof RuntimeException) {
371       final Throwable cause = ex.getCause();
372       if (cause != null && cause != ex) {
373         return getExceptionMessage(cause);
374       }
375     }
376     else
377     if (ex instanceof InvocationTargetException) {
378       final Throwable target = ((InvocationTargetException)ex).getTargetException();
379       if (target != null && target != ex) {
380         return getExceptionMessage(target);
381       }
382     }
383     String message = ex.getMessage();
384     if (ex instanceof ClassNotFoundException) {
385       message = message != null? UIDesignerBundle.message("error.class.not.found.N", message) : UIDesignerBundle.message("error.class.not.found");
386     }
387     else if (ex instanceof NoClassDefFoundError) {
388       message = message != null? UIDesignerBundle.message("error.required.class.not.found.N", message) : UIDesignerBundle.message("error.required.class.not.found");
389     }
390     return message;
391   }
392
393   public static IComponent findComponentWithBinding(IComponent component, final String binding) {
394     return findComponentWithBinding(component, binding, null);
395   }
396
397   public static IComponent findComponentWithBinding(IComponent component, final String binding, @Nullable final IComponent exceptComponent) {
398     // Check that binding is unique
399     final Ref<IComponent> boundComponent = new Ref<IComponent>();
400     iterate(
401       component,
402       new ComponentVisitor() {
403         public boolean visit(final IComponent component) {
404           if(component != exceptComponent && binding.equals(component.getBinding())) {
405             boundComponent.set(component);
406             return false;
407           }
408           return true;
409         }
410       }
411     );
412
413     return boundComponent.get();
414   }
415
416   @Nullable
417   public static RadContainer getRadContainerAt(final RadRootContainer rootContainer, final int x, final int y,
418                                                int epsilon) {
419     RadComponent component = getRadComponentAt(rootContainer, x, y);
420     if (isNullOrRoot(component) && epsilon > 0) {
421       // try to find component near specified location
422       component = getRadComponentAt(rootContainer, x-epsilon, y-epsilon);
423       if (isNullOrRoot(component)) component = getRadComponentAt(rootContainer, x-epsilon, y+epsilon);
424       if (isNullOrRoot(component)) component = getRadComponentAt(rootContainer, x+epsilon, y-epsilon);
425       if (isNullOrRoot(component)) component = getRadComponentAt(rootContainer, x+epsilon, y+epsilon);
426     }
427
428     if (component != null) {
429       return component instanceof RadContainer
430              ? (RadContainer)component
431              : component.getParent();
432     }
433     return null;
434   }
435
436   private static boolean isNullOrRoot(final RadComponent component) {
437     return component == null || component instanceof RadRootContainer;
438   }
439
440   public static GridConstraints getDefaultConstraints(final RadComponent component) {
441     final Palette palette = Palette.getInstance(component.getModule().getProject());
442     final ComponentItem item = palette.getItem(component.getComponentClassName());
443     if (item != null) {
444       return item.getDefaultConstraints();
445     }
446     return new GridConstraints();
447   }
448
449   public static IRootContainer getRoot(IComponent component) {
450     while(component != null) {
451       if (component.getParentContainer() instanceof IRootContainer) {
452         return (IRootContainer) component.getParentContainer();
453       }
454       component = component.getParentContainer();
455     }
456     return null;
457   }
458
459   /**
460    * Iterates component and its children (if any)
461    */
462   public static void iterate(final IComponent component, final ComponentVisitor visitor){
463     iterateImpl(component, visitor);
464   }
465
466   private static boolean iterateImpl(final IComponent component, final ComponentVisitor visitor) {
467     final boolean shouldContinue;
468     try {
469       shouldContinue = visitor.visit(component);
470     }
471     catch (ProcessCanceledException ex) {
472       return false;
473     }
474     if (!shouldContinue) {
475       return false;
476     }
477
478     if (!(component instanceof IContainer)) {
479       return true;
480     }
481
482     final IContainer container = (IContainer)component;
483
484     for (int i = 0; i < container.getComponentCount(); i++) {
485       final IComponent c = container.getComponent(i);
486       if (!iterateImpl(c, visitor)) {
487         return false;
488       }
489     }
490
491     return true;
492   }
493
494   public static Set<String> collectUsedBundleNames(final IRootContainer rootContainer) {
495     final Set<String> bundleNames = new HashSet<String>();
496     iterateStringDescriptors(rootContainer, new StringDescriptorVisitor<IComponent>() {
497       public boolean visit(final IComponent component, final StringDescriptor descriptor) {
498         if (descriptor.getBundleName() != null && !bundleNames.contains(descriptor.getBundleName())) {
499           bundleNames.add(descriptor.getBundleName());
500         }
501         return true;
502       }
503     });
504     return bundleNames;
505   }
506
507   public static Locale[] collectUsedLocales(final Module module, final IRootContainer rootContainer) {
508     final Set<Locale> locales = new HashSet<Locale>();
509     final PropertiesReferenceManager propManager = PropertiesReferenceManager.getInstance(module.getProject());
510     for(String bundleName: collectUsedBundleNames(rootContainer)) {
511       List<PropertiesFile> propFiles = propManager.findPropertiesFiles(module, bundleName.replace('/', '.'));
512       for(PropertiesFile propFile: propFiles) {
513         locales.add(propFile.getLocale());
514       }
515     }
516     return locales.toArray(new Locale[locales.size()]);
517   }
518
519   public static void deleteRowOrColumn(final GuiEditor editor, final RadContainer container,
520                                        final int[] cellsToDelete, final boolean isRow) {
521     Arrays.sort(cellsToDelete);
522     final int[] cells = ArrayUtil.reverseArray(cellsToDelete);
523     if (!editor.ensureEditable()) {
524       return;
525     }
526
527     Runnable runnable = new Runnable() {
528       public void run() {
529         if (!GridChangeUtil.canDeleteCells(container, cells, isRow)) {
530           Set<RadComponent> componentsInColumn = new HashSet<RadComponent>();
531           for(RadComponent component: container.getComponents()) {
532             GridConstraints c = component.getConstraints();
533             for(int cell: cells) {
534               if (c.contains(isRow, cell)) {
535                 componentsInColumn.add(component);
536                 break;
537               }
538             }
539           }
540
541           if (componentsInColumn.size() > 0) {
542             String message = isRow
543                              ? UIDesignerBundle.message("delete.row.nonempty", componentsInColumn.size(), cells.length)
544                              : UIDesignerBundle.message("delete.column.nonempty", componentsInColumn.size(), cells.length);
545
546             final int rc = Messages.showYesNoDialog(editor, message,
547                                                     isRow ? UIDesignerBundle.message("delete.row.title")
548                                                     : UIDesignerBundle.message("delete.column.title"), Messages.getQuestionIcon());
549             if (rc != DialogWrapper.OK_EXIT_CODE) {
550               return;
551             }
552
553             deleteComponents(componentsInColumn, false);
554           }
555         }
556
557         for(int cell: cells) {
558           container.getGridLayoutManager().deleteGridCells(container, cell, isRow);
559         }
560         editor.refreshAndSave(true);
561       }
562     };
563     CommandProcessor.getInstance().executeCommand(editor.getProject(), runnable,
564                                                   isRow ? UIDesignerBundle.message("command.delete.row")
565                                                         : UIDesignerBundle.message("command.delete.column"), null);
566   }
567
568   /**
569    * @return id
570      * @param rootContainer
571    */
572   public static String generateId(final RadRootContainer rootContainer) {
573     while (true) {
574       final String id = Integer.toString((int)(Math.random() * 1024 * 1024), 16);
575       if (findComponent(rootContainer, id) == null) {
576         return id;
577       }
578     }
579   }
580
581   /**
582    * @return {@link com.intellij.uiDesigner.designSurface.GuiEditor} from the context. Can be <code>null</code>.
583    */
584   @Nullable
585   public static GuiEditor getEditorFromContext(@NotNull final DataContext context){
586     final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
587     if(editor instanceof UIFormEditor){
588       return ((UIFormEditor)editor).getEditor();
589     }
590     else {
591       return GuiEditor.DATA_KEY.getData(context);
592     }
593   }
594
595   @Nullable public static GuiEditor getActiveEditor(final DataContext context){
596     Project project = PlatformDataKeys.PROJECT.getData(context);
597     if (project == null) {
598       return null;
599     }
600     final UIDesignerToolWindowManager toolWindowManager = UIDesignerToolWindowManager.getInstance(project);
601     if (toolWindowManager == null) {
602       return null;
603     }
604     return toolWindowManager.getActiveFormEditor();
605   }
606
607   /**
608    *
609    * @param componentToAssignBinding
610    * @param binding
611    * @param component topmost container where to find duplicate binding. In most cases
612    * it should be {@link com.intellij.uiDesigner.designSurface.GuiEditor#getRootContainer()}
613    */
614   public static boolean isBindingUnique(
615     final IComponent componentToAssignBinding,
616     final String binding,
617     final IComponent component
618   ) {
619     return findComponentWithBinding(component, binding, componentToAssignBinding) == null;
620   }
621
622   @Nullable
623   public static String buildResourceName(final PsiFile file) {
624     PsiDirectory directory = file.getContainingDirectory();
625     if (directory != null) {
626       PsiPackage pkg = JavaDirectoryService.getInstance().getPackage(directory);
627       String packageName = pkg != null ? pkg.getQualifiedName() : "";
628       return packageName.replace('.', '/') + '/' + file.getName();
629     }
630     return null;
631   }
632
633   @Nullable
634   public static RadContainer getSelectionParent(final List<RadComponent> selection) {
635     RadContainer parent = null;
636     for(RadComponent c: selection) {
637       if (parent == null) {
638         parent = c.getParent();
639       }
640       else if (parent != c.getParent()) {
641         parent = null;
642         break;
643       }
644     }
645     return parent;
646   }
647
648   public static Rectangle getSelectionBounds(List<RadComponent> selection) {
649     int minRow = Integer.MAX_VALUE;
650     int minCol = Integer.MAX_VALUE;
651     int maxRow = 0;
652     int maxCol = 0;
653
654     for(RadComponent c: selection) {
655       minRow = Math.min(minRow, c.getConstraints().getRow());
656       minCol = Math.min(minCol, c.getConstraints().getColumn());
657       maxRow = Math.max(maxRow, c.getConstraints().getRow() + c.getConstraints().getRowSpan());
658       maxCol = Math.max(maxCol, c.getConstraints().getColumn() + c.getConstraints().getColSpan());
659     }
660     return new Rectangle(minCol, minRow, maxCol-minCol, maxRow-minRow);
661   }
662
663   public static boolean isComponentSwitchedInView(@NotNull RadComponent component) {
664     while(component.getParent() != null) {
665       if (!component.getParent().getLayoutManager().isSwitchedToChild(component.getParent(), component)) {
666         return false;
667       }
668       component = component.getParent();
669     }
670     return true;
671   }
672
673   /**
674    * Selects the component and ensures that the tabbed panes containing the component are
675    * switched to the correct tab.
676    *
677    * @param editor
678    * @param component the component to select. @return true if the component is enclosed in at least one tabbed pane, false otherwise.
679    */
680   public static boolean selectComponent(final GuiEditor editor, @NotNull final RadComponent component) {
681     boolean hasTab = false;
682     RadComponent parent = component;
683     while(parent.getParent() != null) {
684       if (parent.getParent().getLayoutManager().switchContainerToChild(parent.getParent(), parent)) {
685         hasTab = true;
686       }
687       parent = parent.getParent();
688     }
689     component.setSelected(true);
690     editor.setSelectionLead(component);
691     return hasTab;
692   }
693
694   public static void selectSingleComponent(final GuiEditor editor, final RadComponent component) {
695     final RadContainer root = (RadContainer)getRoot(component);
696     if (root == null) return;
697
698     ComponentTreeBuilder builder = UIDesignerToolWindowManager.getInstance(component.getProject()).getComponentTreeBuilder();
699     // this can return null if the click to select the control also requested to grab the focus -
700     // the component tree will be instantiated after the event has been processed completely
701     if (builder != null) {
702       builder.beginUpdateSelection();
703     }
704     try {
705       clearSelection(root);
706       selectComponent(editor, component);
707       editor.setSelectionAnchor(component);
708       editor.scrollComponentInView(component);
709     }
710     finally {
711       if (builder != null) {
712         builder.endUpdateSelection();
713       }
714     }
715   }
716
717   public static void selectComponents(final GuiEditor editor, List<RadComponent> components) {
718     if (components.size() > 0) {
719       RadComponent component = components.get(0);
720       ComponentTreeBuilder builder = UIDesignerToolWindowManager.getInstance(component.getProject()).getComponentTreeBuilder();
721       if (builder == null) {
722         // race condition when handling event?
723         return;
724       }
725       builder.beginUpdateSelection();
726       try {
727         clearSelection((RadContainer) getRoot(component));
728         for(RadComponent aComponent: components) {
729           selectComponent(editor, aComponent);
730         }
731       }
732       finally {
733         builder.endUpdateSelection();
734       }
735     }
736   }
737
738   public static boolean isDropOnChild(final DraggedComponentList draggedComponentList,
739                                        final ComponentDropLocation location) {
740     if (location.getContainer() == null) {
741       return false;
742     }
743
744     for(RadComponent component: draggedComponentList.getComponents()) {
745       if (isChild(location.getContainer(), component)) {
746         return true;
747       }
748     }
749     return false;
750   }
751
752   private static boolean isChild(RadContainer maybeChild, RadComponent maybeParent) {
753     while(maybeChild != null) {
754       if (maybeParent == maybeChild) {
755         return true;
756       }
757       maybeChild = maybeChild.getParent();
758     }
759     return false;
760   }
761
762   public static PsiMethod findCreateComponentsMethod(final PsiClass aClass) {
763     PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();
764     PsiMethod method;
765     try {
766       method = factory.createMethodFromText("void " + AsmCodeGenerator.CREATE_COMPONENTS_METHOD_NAME + "() {}",
767                                             aClass);
768     }
769     catch (IncorrectOperationException e) {
770       throw new RuntimeException(e);
771     }
772     return aClass.findMethodBySignature(method, true);
773   }
774
775   public static Object getNextSaveUndoGroupId(final Project project) {
776     final GuiEditor guiEditor = UIDesignerToolWindowManager.getInstance(project).getActiveFormEditor();
777     return guiEditor == null ? null : guiEditor.getNextSaveGroupId();
778   }
779
780   public static int adjustForGap(final RadContainer container, final int cellIndex, final boolean isRow, final int delta) {
781     if (container.getGridLayoutManager().isGapCell(container, isRow, cellIndex)) {
782       return cellIndex+delta;
783     }
784     return cellIndex;
785   }
786
787   public static int prevRow(final RadContainer container, final int row) {
788     return adjustForGap(container, row-1, true, -1);
789   }
790
791   public static int nextRow(final RadContainer container, final int row) {
792     return adjustForGap(container, row+1, true, 1);
793   }
794
795   public static int prevCol(final RadContainer container, final int col) {
796     return adjustForGap(container, col-1, false, -1);
797   }
798
799   public static int nextCol(final RadContainer container, final int col) {
800     return adjustForGap(container, col+1, false, 1);
801   }
802
803   @Nullable public static IButtonGroup findGroupForComponent(final IRootContainer radRootContainer, @NotNull final IComponent component) {
804     for(IButtonGroup group: radRootContainer.getButtonGroups()) {
805       for(String id: group.getComponentIds()) {
806         if (component.getId().equals(id)) {
807           return group;
808         }
809       }
810     }
811     return null;
812   }
813
814   public static void remapToActionTargets(final List<RadComponent> selection) {
815     for(int i=0; i<selection.size(); i++) {
816       final RadComponent c = selection.get(i);
817       if (c.getParent() != null) {
818         selection.set(i, c.getParent().getActionTargetComponent(c));
819       }
820     }
821   }
822
823   public static void showPopupUnderComponent(final JBPopup popup, final RadComponent selectedComponent) {
824     // popup.showUnderneathOf() doesn't work on invisible components
825     Rectangle rc = selectedComponent.getBounds();
826     Point pnt = new Point(rc.x, rc.y + rc.height);
827     popup.show(new RelativePoint(selectedComponent.getDelegee().getParent(), pnt));
828   }
829
830   public interface StringDescriptorVisitor<T extends IComponent> {
831     boolean visit(T component, StringDescriptor descriptor);
832   }
833
834
835   public static void iterateStringDescriptors(final IComponent component,
836                                               final StringDescriptorVisitor<IComponent> visitor) {
837     iterate(component, new ComponentVisitor<IComponent>() {
838
839       public boolean visit(final IComponent component) {
840         for (IProperty prop : component.getModifiedProperties()) {
841           Object value = prop.getPropertyValue(component);
842           if (value instanceof StringDescriptor) {
843             if (!visitor.visit(component, (StringDescriptor)value)) {
844               return false;
845             }
846           }
847         }
848         if (component.getParentContainer() instanceof ITabbedPane) {
849           StringDescriptor tabTitle =
850             ((ITabbedPane)component.getParentContainer()).getTabProperty(component, ITabbedPane.TAB_TITLE_PROPERTY);
851           if (tabTitle != null && !visitor.visit(component, tabTitle)) {
852             return false;
853           }
854           StringDescriptor tabToolTip =
855             ((ITabbedPane)component.getParentContainer()).getTabProperty(component, ITabbedPane.TAB_TOOLTIP_PROPERTY);
856           if (tabToolTip != null && !visitor.visit(component, tabToolTip)) {
857             return false;
858           }
859         }
860         if (component instanceof IContainer) {
861           final StringDescriptor borderTitle = ((IContainer)component).getBorderTitle();
862           if (borderTitle != null && !visitor.visit(component, borderTitle)) {
863             return false;
864           }
865         }
866         return true;
867       }
868     });
869   }
870
871   public static void clearSelection(@NotNull final RadContainer container){
872     container.setSelected(false);
873
874     for (int i = 0; i < container.getComponentCount(); i++) {
875       final RadComponent c = container.getComponent(i);
876       if (c instanceof RadContainer) {
877         clearSelection((RadContainer)c);
878       }
879       else {
880         c.setSelected(false);
881       }
882     }
883   }
884
885   /**
886    * Finds component with the specified <code>id</code> starting from the
887    * <code>container</code>. The method goes recursively through the hierarchy
888    * of components. Note, that if <code>container</code> itself has <code>id</code>
889    * then the method immediately retuns it.
890    * @return the found component.
891    */
892   @Nullable
893   public static IComponent findComponent(@NotNull final IComponent component, @NotNull final String id) {
894     if (id.equals(component.getId())) {
895       return component;
896     }
897     if (!(component instanceof IContainer)) {
898       return null;
899     }
900
901     final IContainer uiContainer = (IContainer)component;
902     for (int i=0; i < uiContainer.getComponentCount(); i++){
903       final IComponent found = findComponent(uiContainer.getComponent(i), id);
904       if (found != null){
905         return found;
906       }
907     }
908     return null;
909   }
910
911   @Nullable
912   public static PsiClass findClassToBind(@NotNull final Module module, @NotNull final String classToBindName) {
913     return JavaPsiFacade.getInstance(module.getProject())
914       .findClass(classToBindName.replace('$', '.'), module.getModuleWithDependenciesScope());
915   }
916
917   public interface ComponentVisitor <Type extends IComponent>{
918     /**
919      * @return true if iteration should continue
920      */
921     boolean visit(Type component);
922   }
923 }