IDEA-35738 Cannot drag around label with empty text
[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   public 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 if (ex instanceof InvocationTargetException) {
377       final Throwable target = ((InvocationTargetException)ex).getTargetException();
378       if (target != null && target != ex) {
379         return getExceptionMessage(target);
380       }
381     }
382     String message = ex.getMessage();
383     if (ex instanceof ClassNotFoundException) {
384       message =
385         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
389                 ? UIDesignerBundle.message("error.required.class.not.found.N", message)
390                 : UIDesignerBundle.message("error.required.class.not.found");
391     }
392     return message;
393   }
394
395   public static IComponent findComponentWithBinding(IComponent component, final String binding) {
396     return findComponentWithBinding(component, binding, null);
397   }
398
399   public static IComponent findComponentWithBinding(IComponent component,
400                                                     final String binding,
401                                                     @Nullable final IComponent exceptComponent) {
402     // Check that binding is unique
403     final Ref<IComponent> boundComponent = new Ref<IComponent>();
404     iterate(
405       component,
406       new ComponentVisitor() {
407         public boolean visit(final IComponent component) {
408           if (component != exceptComponent && binding.equals(component.getBinding())) {
409             boundComponent.set(component);
410             return false;
411           }
412           return true;
413         }
414       }
415     );
416
417     return boundComponent.get();
418   }
419
420   @Nullable
421   public static RadContainer getRadContainerAt(final RadRootContainer rootContainer, final int x, final int y,
422                                                int epsilon) {
423     RadComponent component = getRadComponentAt(rootContainer, x, y);
424     if (isNullOrRoot(component) && epsilon > 0) {
425       // try to find component near specified location
426       component = getRadComponentAt(rootContainer, x - epsilon, y - epsilon);
427       if (isNullOrRoot(component)) component = getRadComponentAt(rootContainer, x - epsilon, y + epsilon);
428       if (isNullOrRoot(component)) component = getRadComponentAt(rootContainer, x + epsilon, y - epsilon);
429       if (isNullOrRoot(component)) component = getRadComponentAt(rootContainer, x + epsilon, y + epsilon);
430     }
431
432     if (component != null) {
433       return component instanceof RadContainer
434              ? (RadContainer)component
435              : component.getParent();
436     }
437     return null;
438   }
439
440   private static boolean isNullOrRoot(final RadComponent component) {
441     return component == null || component instanceof RadRootContainer;
442   }
443
444   public static GridConstraints getDefaultConstraints(final RadComponent component) {
445     final Palette palette = Palette.getInstance(component.getModule().getProject());
446     final ComponentItem item = palette.getItem(component.getComponentClassName());
447     if (item != null) {
448       return item.getDefaultConstraints();
449     }
450     return new GridConstraints();
451   }
452
453   public static IRootContainer getRoot(IComponent component) {
454     while (component != null) {
455       if (component.getParentContainer() instanceof IRootContainer) {
456         return (IRootContainer)component.getParentContainer();
457       }
458       component = component.getParentContainer();
459     }
460     return null;
461   }
462
463   /**
464    * Iterates component and its children (if any)
465    */
466   public static void iterate(final IComponent component, final ComponentVisitor visitor) {
467     iterateImpl(component, visitor);
468   }
469
470   private static boolean iterateImpl(final IComponent component, final ComponentVisitor visitor) {
471     final boolean shouldContinue;
472     try {
473       shouldContinue = visitor.visit(component);
474     }
475     catch (ProcessCanceledException ex) {
476       return false;
477     }
478     if (!shouldContinue) {
479       return false;
480     }
481
482     if (!(component instanceof IContainer)) {
483       return true;
484     }
485
486     final IContainer container = (IContainer)component;
487
488     for (int i = 0; i < container.getComponentCount(); i++) {
489       final IComponent c = container.getComponent(i);
490       if (!iterateImpl(c, visitor)) {
491         return false;
492       }
493     }
494
495     return true;
496   }
497
498   public static Set<String> collectUsedBundleNames(final IRootContainer rootContainer) {
499     final Set<String> bundleNames = new HashSet<String>();
500     iterateStringDescriptors(rootContainer, new StringDescriptorVisitor<IComponent>() {
501       public boolean visit(final IComponent component, final StringDescriptor descriptor) {
502         if (descriptor.getBundleName() != null && !bundleNames.contains(descriptor.getBundleName())) {
503           bundleNames.add(descriptor.getBundleName());
504         }
505         return true;
506       }
507     });
508     return bundleNames;
509   }
510
511   public static Locale[] collectUsedLocales(final Module module, final IRootContainer rootContainer) {
512     final Set<Locale> locales = new HashSet<Locale>();
513     final PropertiesReferenceManager propManager = PropertiesReferenceManager.getInstance(module.getProject());
514     for (String bundleName : collectUsedBundleNames(rootContainer)) {
515       List<PropertiesFile> propFiles = propManager.findPropertiesFiles(module, bundleName.replace('/', '.'));
516       for (PropertiesFile propFile : propFiles) {
517         locales.add(propFile.getLocale());
518       }
519     }
520     return locales.toArray(new Locale[locales.size()]);
521   }
522
523   public static void deleteRowOrColumn(final GuiEditor editor, final RadContainer container,
524                                        final int[] cellsToDelete, final boolean isRow) {
525     Arrays.sort(cellsToDelete);
526     final int[] cells = ArrayUtil.reverseArray(cellsToDelete);
527     if (!editor.ensureEditable()) {
528       return;
529     }
530
531     Runnable runnable = new Runnable() {
532       public void run() {
533         if (!GridChangeUtil.canDeleteCells(container, cells, isRow)) {
534           Set<RadComponent> componentsInColumn = new HashSet<RadComponent>();
535           for (RadComponent component : container.getComponents()) {
536             GridConstraints c = component.getConstraints();
537             for (int cell : cells) {
538               if (c.contains(isRow, cell)) {
539                 componentsInColumn.add(component);
540                 break;
541               }
542             }
543           }
544
545           if (componentsInColumn.size() > 0) {
546             String message = isRow
547                              ? UIDesignerBundle.message("delete.row.nonempty", componentsInColumn.size(), cells.length)
548                              : UIDesignerBundle.message("delete.column.nonempty", componentsInColumn.size(), cells.length);
549
550             final int rc = Messages.showYesNoDialog(editor, message,
551                                                     isRow ? UIDesignerBundle.message("delete.row.title")
552                                                           : UIDesignerBundle.message("delete.column.title"), Messages.getQuestionIcon());
553             if (rc != DialogWrapper.OK_EXIT_CODE) {
554               return;
555             }
556
557             deleteComponents(componentsInColumn, false);
558           }
559         }
560
561         for (int cell : cells) {
562           container.getGridLayoutManager().deleteGridCells(container, cell, isRow);
563         }
564         editor.refreshAndSave(true);
565       }
566     };
567     CommandProcessor.getInstance().executeCommand(editor.getProject(), runnable,
568                                                   isRow ? UIDesignerBundle.message("command.delete.row")
569                                                         : UIDesignerBundle.message("command.delete.column"), null);
570   }
571
572   /**
573    * @param rootContainer
574    * @return id
575    */
576   public static String generateId(final RadRootContainer rootContainer) {
577     while (true) {
578       final String id = Integer.toString((int)(Math.random() * 1024 * 1024), 16);
579       if (findComponent(rootContainer, id) == null) {
580         return id;
581       }
582     }
583   }
584
585   /**
586    * @return {@link com.intellij.uiDesigner.designSurface.GuiEditor} from the context. Can be <code>null</code>.
587    */
588   @Nullable
589   public static GuiEditor getEditorFromContext(@NotNull final DataContext context) {
590     final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
591     if (editor instanceof UIFormEditor) {
592       return ((UIFormEditor)editor).getEditor();
593     }
594     else {
595       return GuiEditor.DATA_KEY.getData(context);
596     }
597   }
598
599   @Nullable
600   public static GuiEditor getActiveEditor(final DataContext context) {
601     Project project = PlatformDataKeys.PROJECT.getData(context);
602     if (project == null) {
603       return null;
604     }
605     final UIDesignerToolWindowManager toolWindowManager = UIDesignerToolWindowManager.getInstance(project);
606     if (toolWindowManager == null) {
607       return null;
608     }
609     return toolWindowManager.getActiveFormEditor();
610   }
611
612   /**
613    * @param componentToAssignBinding
614    * @param binding
615    * @param component                topmost container where to find duplicate binding. In most cases
616    *                                 it should be {@link com.intellij.uiDesigner.designSurface.GuiEditor#getRootContainer()}
617    */
618   public static boolean isBindingUnique(
619     final IComponent componentToAssignBinding,
620     final String binding,
621     final IComponent component
622   ) {
623     return findComponentWithBinding(component, binding, componentToAssignBinding) == null;
624   }
625
626   @Nullable
627   public static String buildResourceName(final PsiFile file) {
628     PsiDirectory directory = file.getContainingDirectory();
629     if (directory != null) {
630       PsiPackage pkg = JavaDirectoryService.getInstance().getPackage(directory);
631       String packageName = pkg != null ? pkg.getQualifiedName() : "";
632       return packageName.replace('.', '/') + '/' + file.getName();
633     }
634     return null;
635   }
636
637   @Nullable
638   public static RadContainer getSelectionParent(final List<RadComponent> selection) {
639     RadContainer parent = null;
640     for (RadComponent c : selection) {
641       if (parent == null) {
642         parent = c.getParent();
643       }
644       else if (parent != c.getParent()) {
645         parent = null;
646         break;
647       }
648     }
649     return parent;
650   }
651
652   public static Rectangle getSelectionBounds(List<RadComponent> selection) {
653     int minRow = Integer.MAX_VALUE;
654     int minCol = Integer.MAX_VALUE;
655     int maxRow = 0;
656     int maxCol = 0;
657
658     for (RadComponent c : selection) {
659       minRow = Math.min(minRow, c.getConstraints().getRow());
660       minCol = Math.min(minCol, c.getConstraints().getColumn());
661       maxRow = Math.max(maxRow, c.getConstraints().getRow() + c.getConstraints().getRowSpan());
662       maxCol = Math.max(maxCol, c.getConstraints().getColumn() + c.getConstraints().getColSpan());
663     }
664     return new Rectangle(minCol, minRow, maxCol - minCol, maxRow - minRow);
665   }
666
667   public static boolean isComponentSwitchedInView(@NotNull RadComponent component) {
668     while (component.getParent() != null) {
669       if (!component.getParent().getLayoutManager().isSwitchedToChild(component.getParent(), component)) {
670         return false;
671       }
672       component = component.getParent();
673     }
674     return true;
675   }
676
677   /**
678    * Selects the component and ensures that the tabbed panes containing the component are
679    * switched to the correct tab.
680    *
681    * @param editor
682    * @param component the component to select. @return true if the component is enclosed in at least one tabbed pane, false otherwise.
683    */
684   public static boolean selectComponent(final GuiEditor editor, @NotNull final RadComponent component) {
685     boolean hasTab = false;
686     RadComponent parent = component;
687     while (parent.getParent() != null) {
688       if (parent.getParent().getLayoutManager().switchContainerToChild(parent.getParent(), parent)) {
689         hasTab = true;
690       }
691       parent = parent.getParent();
692     }
693     component.setSelected(true);
694     editor.setSelectionLead(component);
695     return hasTab;
696   }
697
698   public static void selectSingleComponent(final GuiEditor editor, final RadComponent component) {
699     final RadContainer root = (RadContainer)getRoot(component);
700     if (root == null) return;
701
702     ComponentTreeBuilder builder = UIDesignerToolWindowManager.getInstance(component.getProject()).getComponentTreeBuilder();
703     // this can return null if the click to select the control also requested to grab the focus -
704     // the component tree will be instantiated after the event has been processed completely
705     if (builder != null) {
706       builder.beginUpdateSelection();
707     }
708     try {
709       clearSelection(root);
710       selectComponent(editor, component);
711       editor.setSelectionAnchor(component);
712       editor.scrollComponentInView(component);
713     }
714     finally {
715       if (builder != null) {
716         builder.endUpdateSelection();
717       }
718     }
719   }
720
721   public static void selectComponents(final GuiEditor editor, List<RadComponent> components) {
722     if (components.size() > 0) {
723       RadComponent component = components.get(0);
724       ComponentTreeBuilder builder = UIDesignerToolWindowManager.getInstance(component.getProject()).getComponentTreeBuilder();
725       if (builder == null) {
726         // race condition when handling event?
727         return;
728       }
729       builder.beginUpdateSelection();
730       try {
731         clearSelection((RadContainer)getRoot(component));
732         for (RadComponent aComponent : components) {
733           selectComponent(editor, aComponent);
734         }
735       }
736       finally {
737         builder.endUpdateSelection();
738       }
739     }
740   }
741
742   public static boolean isDropOnChild(final DraggedComponentList draggedComponentList,
743                                       final ComponentDropLocation location) {
744     if (location.getContainer() == null) {
745       return false;
746     }
747
748     for (RadComponent component : draggedComponentList.getComponents()) {
749       if (isChild(location.getContainer(), component)) {
750         return true;
751       }
752     }
753     return false;
754   }
755
756   private static boolean isChild(RadContainer maybeChild, RadComponent maybeParent) {
757     while (maybeChild != null) {
758       if (maybeParent == maybeChild) {
759         return true;
760       }
761       maybeChild = maybeChild.getParent();
762     }
763     return false;
764   }
765
766   public static PsiMethod findCreateComponentsMethod(final PsiClass aClass) {
767     PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();
768     PsiMethod method;
769     try {
770       method = factory.createMethodFromText("void " + AsmCodeGenerator.CREATE_COMPONENTS_METHOD_NAME + "() {}",
771                                             aClass);
772     }
773     catch (IncorrectOperationException e) {
774       throw new RuntimeException(e);
775     }
776     return aClass.findMethodBySignature(method, true);
777   }
778
779   public static Object getNextSaveUndoGroupId(final Project project) {
780     final GuiEditor guiEditor = UIDesignerToolWindowManager.getInstance(project).getActiveFormEditor();
781     return guiEditor == null ? null : guiEditor.getNextSaveGroupId();
782   }
783
784   public static int adjustForGap(final RadContainer container, final int cellIndex, final boolean isRow, final int delta) {
785     if (container.getGridLayoutManager().isGapCell(container, isRow, cellIndex)) {
786       return cellIndex + delta;
787     }
788     return cellIndex;
789   }
790
791   public static int prevRow(final RadContainer container, final int row) {
792     return adjustForGap(container, row - 1, true, -1);
793   }
794
795   public static int nextRow(final RadContainer container, final int row) {
796     return adjustForGap(container, row + 1, true, 1);
797   }
798
799   public static int prevCol(final RadContainer container, final int col) {
800     return adjustForGap(container, col - 1, false, -1);
801   }
802
803   public static int nextCol(final RadContainer container, final int col) {
804     return adjustForGap(container, col + 1, false, 1);
805   }
806
807   @Nullable
808   public static IButtonGroup findGroupForComponent(final IRootContainer radRootContainer, @NotNull final IComponent component) {
809     for (IButtonGroup group : radRootContainer.getButtonGroups()) {
810       for (String id : group.getComponentIds()) {
811         if (component.getId().equals(id)) {
812           return group;
813         }
814       }
815     }
816     return null;
817   }
818
819   public static void remapToActionTargets(final List<RadComponent> selection) {
820     for (int i = 0; i < selection.size(); i++) {
821       final RadComponent c = selection.get(i);
822       if (c.getParent() != null) {
823         selection.set(i, c.getParent().getActionTargetComponent(c));
824       }
825     }
826   }
827
828   public static void showPopupUnderComponent(final JBPopup popup, final RadComponent selectedComponent) {
829     // popup.showUnderneathOf() doesn't work on invisible components
830     Rectangle rc = selectedComponent.getBounds();
831     Point pnt = new Point(rc.x, rc.y + rc.height);
832     popup.show(new RelativePoint(selectedComponent.getDelegee().getParent(), pnt));
833   }
834
835   public interface StringDescriptorVisitor<T extends IComponent> {
836     boolean visit(T component, StringDescriptor descriptor);
837   }
838
839
840   public static void iterateStringDescriptors(final IComponent component,
841                                               final StringDescriptorVisitor<IComponent> visitor) {
842     iterate(component, new ComponentVisitor<IComponent>() {
843
844       public boolean visit(final IComponent component) {
845         for (IProperty prop : component.getModifiedProperties()) {
846           Object value = prop.getPropertyValue(component);
847           if (value instanceof StringDescriptor) {
848             if (!visitor.visit(component, (StringDescriptor)value)) {
849               return false;
850             }
851           }
852         }
853         if (component.getParentContainer() instanceof ITabbedPane) {
854           StringDescriptor tabTitle =
855             ((ITabbedPane)component.getParentContainer()).getTabProperty(component, ITabbedPane.TAB_TITLE_PROPERTY);
856           if (tabTitle != null && !visitor.visit(component, tabTitle)) {
857             return false;
858           }
859           StringDescriptor tabToolTip =
860             ((ITabbedPane)component.getParentContainer()).getTabProperty(component, ITabbedPane.TAB_TOOLTIP_PROPERTY);
861           if (tabToolTip != null && !visitor.visit(component, tabToolTip)) {
862             return false;
863           }
864         }
865         if (component instanceof IContainer) {
866           final StringDescriptor borderTitle = ((IContainer)component).getBorderTitle();
867           if (borderTitle != null && !visitor.visit(component, borderTitle)) {
868             return false;
869           }
870         }
871         return true;
872       }
873     });
874   }
875
876   public static void clearSelection(@NotNull final RadContainer container) {
877     container.setSelected(false);
878
879     for (int i = 0; i < container.getComponentCount(); i++) {
880       final RadComponent c = container.getComponent(i);
881       if (c instanceof RadContainer) {
882         clearSelection((RadContainer)c);
883       }
884       else {
885         c.setSelected(false);
886       }
887     }
888   }
889
890   /**
891    * Finds component with the specified <code>id</code> starting from the
892    * <code>container</code>. The method goes recursively through the hierarchy
893    * of components. Note, that if <code>container</code> itself has <code>id</code>
894    * then the method immediately retuns it.
895    *
896    * @return the found component.
897    */
898   @Nullable
899   public static IComponent findComponent(@NotNull final IComponent component, @NotNull final String id) {
900     if (id.equals(component.getId())) {
901       return component;
902     }
903     if (!(component instanceof IContainer)) {
904       return null;
905     }
906
907     final IContainer uiContainer = (IContainer)component;
908     for (int i = 0; i < uiContainer.getComponentCount(); i++) {
909       final IComponent found = findComponent(uiContainer.getComponent(i), id);
910       if (found != null) {
911         return found;
912       }
913     }
914     return null;
915   }
916
917   @Nullable
918   public static PsiClass findClassToBind(@NotNull final Module module, @NotNull final String classToBindName) {
919     return JavaPsiFacade.getInstance(module.getProject())
920       .findClass(classToBindName.replace('$', '.'), module.getModuleWithDependenciesScope());
921   }
922
923   public interface ComponentVisitor<Type extends IComponent> {
924     /**
925      * @return true if iteration should continue
926      */
927     boolean visit(Type component);
928   }
929 }