replaced <code></code> with more concise {@code}
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / FormEditingUtil.java
1 /*
2  * Copyright 2000-2013 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.CommonDataKeys;
21 import com.intellij.openapi.actionSystem.DataContext;
22 import com.intellij.openapi.actionSystem.PlatformDataKeys;
23 import com.intellij.openapi.command.CommandProcessor;
24 import com.intellij.openapi.fileEditor.FileEditor;
25 import com.intellij.openapi.module.Module;
26 import com.intellij.openapi.progress.ProcessCanceledException;
27 import com.intellij.openapi.project.Project;
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.DesignerToolWindowManager;
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<>();
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<>();
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<>();
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}
351    */
352   @NotNull
353   public static ArrayList<RadComponent> getAllSelectedComponents(@NotNull final GuiEditor editor) {
354     final ArrayList<RadComponent> result = new ArrayList<>();
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<>();
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.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<>();
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<>();
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 = () -> {
532       if (!GridChangeUtil.canDeleteCells(container, cells, isRow)) {
533         Set<RadComponent> componentsInColumn = new HashSet<>();
534         for (RadComponent component : container.getComponents()) {
535           GridConstraints c = component.getConstraints();
536           for (int cell : cells) {
537             if (c.contains(isRow, cell)) {
538               componentsInColumn.add(component);
539               break;
540             }
541           }
542         }
543
544         if (componentsInColumn.size() > 0) {
545           String message = isRow
546                            ? UIDesignerBundle.message("delete.row.nonempty", componentsInColumn.size(), cells.length)
547                            : UIDesignerBundle.message("delete.column.nonempty", componentsInColumn.size(), cells.length);
548
549           final int rc = Messages.showYesNoDialog(editor, message,
550                                                   isRow ? UIDesignerBundle.message("delete.row.title")
551                                                         : UIDesignerBundle.message("delete.column.title"), Messages.getQuestionIcon());
552           if (rc != Messages.YES) {
553             return;
554           }
555
556           deleteComponents(componentsInColumn, false);
557         }
558       }
559
560       for (int cell : cells) {
561         container.getGridLayoutManager().deleteGridCells(container, cell, isRow);
562       }
563       editor.refreshAndSave(true);
564     };
565     CommandProcessor.getInstance().executeCommand(editor.getProject(), runnable,
566                                                   isRow ? UIDesignerBundle.message("command.delete.row")
567                                                         : UIDesignerBundle.message("command.delete.column"), null);
568   }
569
570   /**
571    * @param rootContainer
572    * @return id
573    */
574   public static String generateId(final RadRootContainer rootContainer) {
575     while (true) {
576       final String id = Integer.toString((int)(Math.random() * 1024 * 1024), 16);
577       if (findComponent(rootContainer, id) == null) {
578         return id;
579       }
580     }
581   }
582
583   /**
584    * @return {@link com.intellij.uiDesigner.designSurface.GuiEditor} from the context. Can be {@code null}.
585    */
586   @Nullable
587   public static GuiEditor getEditorFromContext(@NotNull final DataContext context) {
588     final FileEditor editor = PlatformDataKeys.FILE_EDITOR.getData(context);
589     if (editor instanceof UIFormEditor) {
590       return ((UIFormEditor)editor).getEditor();
591     }
592     else {
593       return GuiEditor.DATA_KEY.getData(context);
594     }
595   }
596
597   @Nullable
598   public static GuiEditor getActiveEditor(final DataContext context) {
599     Project project = CommonDataKeys.PROJECT.getData(context);
600     if (project == null) {
601       return null;
602     }
603     final DesignerToolWindowManager toolWindowManager = DesignerToolWindowManager.getInstance(project);
604     if (toolWindowManager == null) {
605       return null;
606     }
607     return toolWindowManager.getActiveFormEditor();
608   }
609
610   /**
611    * @param componentToAssignBinding
612    * @param binding
613    * @param component                topmost container where to find duplicate binding. In most cases
614    *                                 it should be {@link com.intellij.uiDesigner.designSurface.GuiEditor#getRootContainer()}
615    */
616   public static boolean isBindingUnique(
617     final IComponent componentToAssignBinding,
618     final String binding,
619     final IComponent component
620   ) {
621     return findComponentWithBinding(component, binding, componentToAssignBinding) == null;
622   }
623
624   @Nullable
625   public static String buildResourceName(final PsiFile file) {
626     PsiDirectory directory = file.getContainingDirectory();
627     if (directory != null) {
628       PsiPackage pkg = JavaDirectoryService.getInstance().getPackage(directory);
629       String packageName = pkg != null ? pkg.getQualifiedName() : "";
630       if (packageName.length() == 0) {
631         return file.getName();
632       }
633       return packageName.replace('.', '/') + '/' + file.getName();
634     }
635     return null;
636   }
637
638   @Nullable
639   public static RadContainer getSelectionParent(final List<RadComponent> selection) {
640     RadContainer parent = null;
641     for (RadComponent c : selection) {
642       if (parent == null) {
643         parent = c.getParent();
644       }
645       else if (parent != c.getParent()) {
646         parent = null;
647         break;
648       }
649     }
650     return parent;
651   }
652
653   public static Rectangle getSelectionBounds(List<RadComponent> selection) {
654     int minRow = Integer.MAX_VALUE;
655     int minCol = Integer.MAX_VALUE;
656     int maxRow = 0;
657     int maxCol = 0;
658
659     for (RadComponent c : selection) {
660       minRow = Math.min(minRow, c.getConstraints().getRow());
661       minCol = Math.min(minCol, c.getConstraints().getColumn());
662       maxRow = Math.max(maxRow, c.getConstraints().getRow() + c.getConstraints().getRowSpan());
663       maxCol = Math.max(maxCol, c.getConstraints().getColumn() + c.getConstraints().getColSpan());
664     }
665     return new Rectangle(minCol, minRow, maxCol - minCol, maxRow - minRow);
666   }
667
668   public static boolean isComponentSwitchedInView(@NotNull RadComponent component) {
669     while (component.getParent() != null) {
670       if (!component.getParent().getLayoutManager().isSwitchedToChild(component.getParent(), component)) {
671         return false;
672       }
673       component = component.getParent();
674     }
675     return true;
676   }
677
678   /**
679    * Selects the component and ensures that the tabbed panes containing the component are
680    * switched to the correct tab.
681    *
682    * @param editor
683    * @param component the component to select. @return true if the component is enclosed in at least one tabbed pane, false otherwise.
684    */
685   public static boolean selectComponent(final GuiEditor editor, @NotNull final RadComponent component) {
686     boolean hasTab = false;
687     RadComponent parent = component;
688     while (parent.getParent() != null) {
689       if (parent.getParent().getLayoutManager().switchContainerToChild(parent.getParent(), parent)) {
690         hasTab = true;
691       }
692       parent = parent.getParent();
693     }
694     component.setSelected(true);
695     editor.setSelectionLead(component);
696     return hasTab;
697   }
698
699   public static void selectSingleComponent(final GuiEditor editor, final RadComponent component) {
700     final RadContainer root = (RadContainer)getRoot(component);
701     if (root == null) return;
702
703     ComponentTreeBuilder builder = DesignerToolWindowManager.getInstance(editor).getComponentTreeBuilder();
704     // this can return null if the click to select the control also requested to grab the focus -
705     // the component tree will be instantiated after the event has been processed completely
706     if (builder != null) {
707       builder.beginUpdateSelection();
708     }
709     try {
710       clearSelection(root);
711       selectComponent(editor, component);
712       editor.setSelectionAnchor(component);
713       editor.scrollComponentInView(component);
714     }
715     finally {
716       if (builder != null) {
717         builder.endUpdateSelection();
718       }
719     }
720   }
721
722   public static void selectComponents(final GuiEditor editor, List<RadComponent> components) {
723     if (components.size() > 0) {
724       RadComponent component = components.get(0);
725       ComponentTreeBuilder builder = DesignerToolWindowManager.getInstance(editor).getComponentTreeBuilder();
726       if (builder == null) {
727         // race condition when handling event?
728         return;
729       }
730       builder.beginUpdateSelection();
731       try {
732         clearSelection((RadContainer)getRoot(component));
733         for (RadComponent aComponent : components) {
734           selectComponent(editor, aComponent);
735         }
736       }
737       finally {
738         builder.endUpdateSelection();
739       }
740     }
741   }
742
743   public static boolean isDropOnChild(final DraggedComponentList draggedComponentList,
744                                       final ComponentDropLocation location) {
745     if (location.getContainer() == null) {
746       return false;
747     }
748
749     for (RadComponent component : draggedComponentList.getComponents()) {
750       if (isChild(location.getContainer(), component)) {
751         return true;
752       }
753     }
754     return false;
755   }
756
757   private static boolean isChild(RadContainer maybeChild, RadComponent maybeParent) {
758     while (maybeChild != null) {
759       if (maybeParent == maybeChild) {
760         return true;
761       }
762       maybeChild = maybeChild.getParent();
763     }
764     return false;
765   }
766
767   public static PsiMethod findCreateComponentsMethod(final PsiClass aClass) {
768     PsiElementFactory factory = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory();
769     PsiMethod method;
770     try {
771       method = factory.createMethodFromText("void " + AsmCodeGenerator.CREATE_COMPONENTS_METHOD_NAME + "() {}",
772                                             aClass);
773     }
774     catch (IncorrectOperationException e) {
775       throw new RuntimeException(e);
776     }
777     return aClass.findMethodBySignature(method, true);
778   }
779
780   public static Object getNextSaveUndoGroupId(final Project project) {
781     final GuiEditor guiEditor = DesignerToolWindowManager.getInstance(project).getActiveFormEditor();
782     return guiEditor == null ? null : guiEditor.getNextSaveGroupId();
783   }
784
785   public static int adjustForGap(final RadContainer container, final int cellIndex, final boolean isRow, final int delta) {
786     if (container.getGridLayoutManager().isGapCell(container, isRow, cellIndex)) {
787       return cellIndex + delta;
788     }
789     return cellIndex;
790   }
791
792   public static int prevRow(final RadContainer container, final int row) {
793     return adjustForGap(container, row - 1, true, -1);
794   }
795
796   public static int nextRow(final RadContainer container, final int row) {
797     return adjustForGap(container, row + 1, true, 1);
798   }
799
800   public static int prevCol(final RadContainer container, final int col) {
801     return adjustForGap(container, col - 1, false, -1);
802   }
803
804   public static int nextCol(final RadContainer container, final int col) {
805     return adjustForGap(container, col + 1, false, 1);
806   }
807
808   @Nullable
809   public static IButtonGroup findGroupForComponent(final IRootContainer radRootContainer, @NotNull final IComponent component) {
810     for (IButtonGroup group : radRootContainer.getButtonGroups()) {
811       for (String id : group.getComponentIds()) {
812         if (component.getId().equals(id)) {
813           return group;
814         }
815       }
816     }
817     return null;
818   }
819
820   public static void remapToActionTargets(final List<RadComponent> selection) {
821     for (int i = 0; i < selection.size(); i++) {
822       final RadComponent c = selection.get(i);
823       if (c.getParent() != null) {
824         selection.set(i, c.getParent().getActionTargetComponent(c));
825       }
826     }
827   }
828
829   public static void showPopupUnderComponent(final JBPopup popup, final RadComponent selectedComponent) {
830     // popup.showUnderneathOf() doesn't work on invisible components
831     Rectangle rc = selectedComponent.getBounds();
832     Point pnt = new Point(rc.x, rc.y + rc.height);
833     popup.show(new RelativePoint(selectedComponent.getDelegee().getParent(), pnt));
834   }
835
836   public interface StringDescriptorVisitor<T extends IComponent> {
837     boolean visit(T component, StringDescriptor descriptor);
838   }
839
840
841   public static void iterateStringDescriptors(final IComponent component,
842                                               final StringDescriptorVisitor<IComponent> visitor) {
843     iterate(component, new ComponentVisitor<IComponent>() {
844
845       public boolean visit(final IComponent component) {
846         for (IProperty prop : component.getModifiedProperties()) {
847           Object value = prop.getPropertyValue(component);
848           if (value instanceof StringDescriptor) {
849             if (!visitor.visit(component, (StringDescriptor)value)) {
850               return false;
851             }
852           }
853         }
854         if (component.getParentContainer() instanceof ITabbedPane) {
855           StringDescriptor tabTitle =
856             ((ITabbedPane)component.getParentContainer()).getTabProperty(component, ITabbedPane.TAB_TITLE_PROPERTY);
857           if (tabTitle != null && !visitor.visit(component, tabTitle)) {
858             return false;
859           }
860           StringDescriptor tabToolTip =
861             ((ITabbedPane)component.getParentContainer()).getTabProperty(component, ITabbedPane.TAB_TOOLTIP_PROPERTY);
862           if (tabToolTip != null && !visitor.visit(component, tabToolTip)) {
863             return false;
864           }
865         }
866         if (component instanceof IContainer) {
867           final StringDescriptor borderTitle = ((IContainer)component).getBorderTitle();
868           if (borderTitle != null && !visitor.visit(component, borderTitle)) {
869             return false;
870           }
871         }
872         return true;
873       }
874     });
875   }
876
877   public static void clearSelection(@NotNull final RadContainer container) {
878     container.setSelected(false);
879
880     for (int i = 0; i < container.getComponentCount(); i++) {
881       final RadComponent c = container.getComponent(i);
882       if (c instanceof RadContainer) {
883         clearSelection((RadContainer)c);
884       }
885       else {
886         c.setSelected(false);
887       }
888     }
889   }
890
891   /**
892    * Finds component with the specified {@code id} starting from the
893    * {@code container}. The method goes recursively through the hierarchy
894    * of components. Note, that if {@code container} itself has {@code id}
895    * then the method immediately retuns it.
896    *
897    * @return the found component.
898    */
899   @Nullable
900   public static IComponent findComponent(@NotNull final IComponent component, @NotNull final String id) {
901     if (id.equals(component.getId())) {
902       return component;
903     }
904     if (!(component instanceof IContainer)) {
905       return null;
906     }
907
908     final IContainer uiContainer = (IContainer)component;
909     for (int i = 0; i < uiContainer.getComponentCount(); i++) {
910       final IComponent found = findComponent(uiContainer.getComponent(i), id);
911       if (found != null) {
912         return found;
913       }
914     }
915     return null;
916   }
917
918   @Nullable
919   public static PsiClass findClassToBind(@NotNull final Module module, @NotNull final String classToBindName) {
920     return JavaPsiFacade.getInstance(module.getProject())
921       .findClass(classToBindName.replace('$', '.'), module.getModuleWithDependenciesScope());
922   }
923
924   public interface ComponentVisitor<Type extends IComponent> {
925     /**
926      * @return true if iteration should continue
927      */
928     boolean visit(Type component);
929   }
930 }