420f9a0934134ef20df01ceca81919aea25003a9
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / radComponents / RadContainer.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.radComponents;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.module.Module;
20 import com.intellij.openapi.util.Comparing;
21 import com.intellij.uiDesigner.*;
22 import com.intellij.uiDesigner.core.AbstractLayout;
23 import com.intellij.uiDesigner.designSurface.ComponentDropLocation;
24 import com.intellij.uiDesigner.lw.*;
25 import com.intellij.uiDesigner.palette.Palette;
26 import com.intellij.uiDesigner.propertyInspector.Property;
27 import com.intellij.uiDesigner.propertyInspector.PropertyEditor;
28 import com.intellij.uiDesigner.propertyInspector.PropertyRenderer;
29 import com.intellij.uiDesigner.propertyInspector.editors.string.StringEditor;
30 import com.intellij.uiDesigner.shared.BorderType;
31 import com.intellij.uiDesigner.shared.XYLayoutManager;
32 import com.intellij.uiDesigner.snapShooter.SnapshotContext;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import javax.swing.*;
38 import javax.swing.border.*;
39 import java.awt.*;
40 import java.util.ArrayList;
41
42 /**
43  * @author Anton Katilin
44  * @author Vladimir Kondratyev
45  */
46 public class RadContainer extends RadComponent implements IContainer {
47   private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.radComponents.RadContainer");
48
49   public static class Factory extends RadComponentFactory {
50     public RadComponent newInstance(Module module, Class aClass, String id) {
51       return new RadContainer(module, aClass, id);
52     }
53
54     public RadComponent newInstance(final Class componentClass, final String id, final Palette palette) {
55       return new RadContainer(componentClass, id, palette);
56     }
57   }
58
59   /**
60    * value: RadComponent[]
61    */
62   @NonNls
63   public static final String PROP_CHILDREN = "children";
64   /**
65    * Children components
66    */
67   private final ArrayList<RadComponent> myComponents;
68   /**
69    * Describes border's type.
70    */
71   @NotNull private BorderType myBorderType;
72   /**
73    * Border's title. If border doesn't have any title then
74    * this member is <code>null</code>.
75    */
76   @Nullable private StringDescriptor myBorderTitle;
77   private int myBorderTitleJustification;
78   private int myBorderTitlePosition;
79   private FontDescriptor myBorderTitleFont;
80   private ColorDescriptor myBorderTitleColor;
81   private Insets myBorderSize;
82   private ColorDescriptor myBorderColor;
83
84   protected RadLayoutManager myLayoutManager;
85   private LayoutManager myDelegeeLayout;
86
87   public RadContainer(final Module module, final String id){
88     this(module, JPanel.class, id);
89   }
90
91   public RadContainer(final Module module, final Class aClass, final String id){
92     super(module, aClass, id);
93
94     myComponents = new ArrayList<RadComponent>();
95
96     // By default container doesn't have any special border
97     setBorderType(BorderType.NONE);
98
99     myLayoutManager = createInitialLayoutManager();
100     if (myLayoutManager != null) {
101       final LayoutManager layoutManager = myLayoutManager.createLayout();
102       if (layoutManager != null) {
103         setLayout(layoutManager);
104       }
105     }
106   }
107
108   public RadContainer(@NotNull final Class aClass, @NotNull final String id, final Palette palette) {
109     this(null, aClass, id);
110     setPalette(palette);
111   }
112
113   @Nullable protected RadLayoutManager createInitialLayoutManager() {
114     String defaultLayoutManager = UIFormXmlConstants.LAYOUT_INTELLIJ;
115     if (getModule() != null) {
116       final GuiDesignerConfiguration configuration = GuiDesignerConfiguration.getInstance(getModule().getProject());
117       defaultLayoutManager = configuration.DEFAULT_LAYOUT_MANAGER;
118     }
119
120     try {
121       return LayoutManagerRegistry.createLayoutManager(defaultLayoutManager);
122     }
123     catch (Exception e) {
124       LOG.error(e);
125       return new RadGridLayoutManager();
126     }
127   }
128
129   public Property getInplaceProperty(final int x, final int y) {
130     // 1. We have to check whether user clicked inside border (if any) or not.
131     // In this case we have return inplace editor for border text
132     final Insets insets = getDelegee().getInsets(); // border insets
133     if(
134       x < insets.left  || x > getWidth() - insets.right ||
135       y < 0  || y > insets.top
136     ){
137       return super.getInplaceProperty(x, y);
138     }
139
140     // 2. Now we are sure that user clicked inside  title area
141     return new MyBorderTitleProperty();
142   }
143
144   @Override @Nullable
145   public Property getDefaultInplaceProperty() {
146     return new MyBorderTitleProperty();
147   }
148
149   @Override @Nullable
150   public Rectangle getDefaultInplaceEditorBounds() {
151     return getBorderInPlaceEditorBounds(new MyBorderTitleProperty());
152   }
153
154   public Rectangle getInplaceEditorBounds(final Property property, final int x, final int y) {
155     if(property instanceof MyBorderTitleProperty){ // If this is our property
156       return getBorderInPlaceEditorBounds(property);
157     }
158     return super.getInplaceEditorBounds(property, x, y);
159   }
160
161   private Rectangle getBorderInPlaceEditorBounds(final Property property) {
162     final MyBorderTitleProperty _property = (MyBorderTitleProperty)property;
163     final Insets insets = getDelegee().getInsets();
164     return new Rectangle(
165       insets.left,
166       0,
167       getWidth() - insets.left - insets.right,
168       _property.getPreferredSize().height
169     );
170   }
171
172   public final LayoutManager getLayout(){
173     if (myDelegeeLayout != null) {
174       return myDelegeeLayout;
175     }
176     return getDelegee().getLayout();
177   }
178
179   public final void setLayout(final LayoutManager layout) {
180     // some components (for example, JXCollapsiblePanel from SwingX) have asymmetrical getLayout/setLayout - a different
181     // layout is returned compared to what was passed to setLayout(). to avoid crashes, we store the layout we passed to
182     // the component.
183     myDelegeeLayout = layout;
184     getDelegee().setLayout(layout);
185
186     if (layout instanceof AbstractLayout) {
187       AbstractLayout aLayout = (AbstractLayout) layout;
188       for (int i=0; i < getComponentCount(); i++) {
189         final RadComponent c = getComponent(i);
190         aLayout.addLayoutComponent(c.getDelegee(), c.getConstraints());
191       }
192     }
193   }
194
195   public final boolean isXY(){
196     return getLayout() instanceof XYLayoutManager;
197   }
198
199   /**
200    * @param component component to be added.
201    *
202    * @exception java.lang.IllegalArgumentException if <code>component</code> is <code>null</code>
203    * @exception java.lang.IllegalArgumentException if <code>component</code> already exist in the
204    * container
205    */
206   public final void addComponent(@NotNull final RadComponent component, int index) {
207     if (myComponents.contains(component)) {
208       //noinspection HardCodedStringLiteral
209       throw new IllegalArgumentException("component is already added: " + component);
210     }
211
212     final RadComponent[] oldChildren = myComponents.toArray(new RadComponent[myComponents.size()]);
213
214     // Remove from old parent
215     final RadContainer oldParent=component.getParent();
216     if(oldParent!=null) {
217       oldParent.removeComponent(component);
218     }
219
220     // Attach to new parent
221     myComponents.add(index, component);
222     component.setParent(this);
223     myLayoutManager.addComponentToContainer(this, component, index);
224
225     final RadComponent[] newChildren = myComponents.toArray(new RadComponent[myComponents.size()]);
226     firePropertyChanged(PROP_CHILDREN, oldChildren, newChildren);
227   }
228
229   public final void addComponent(@NotNull final RadComponent component) {
230     addComponent(component, myComponents.size());
231   }
232
233   /**
234    * Removes specified <code>component</code> from the container.
235    * This method also removes component's delegee from the
236    * container's delegee. Client code is responsible for revalidation
237    * of invalid Swing hierarchy.
238    *
239    * @param component component to be removed.
240    *
241    * @exception java.lang.IllegalArgumentException if <code>component</code>
242    * is <code>null</code>
243    * @exception java.lang.IllegalArgumentException if <code>component</code>
244    * doesn't exist in the container
245    */
246   public final void removeComponent(@NotNull final RadComponent component){
247     if(!myComponents.contains(component)){
248       //noinspection HardCodedStringLiteral
249       throw new IllegalArgumentException("component is not added: " + component);
250     }
251
252     final RadComponent[] oldChildren = myComponents.toArray(new RadComponent[myComponents.size()]);
253
254     // Remove child
255     component.setParent(null);
256     myComponents.remove(component);
257     myLayoutManager.removeComponentFromContainer(this, component);
258
259     final RadComponent[] newChildren = myComponents.toArray(new RadComponent[myComponents.size()]);
260     firePropertyChanged(PROP_CHILDREN, oldChildren, newChildren);
261   }
262
263   public final RadComponent getComponent(final int index) {
264     return myComponents.get(index);
265   }
266
267   public final int getComponentCount() {
268     return myComponents.size();
269   }
270
271   public int indexOfComponent(IComponent component) {
272     return myComponents.indexOf(component);
273   }
274
275   /**
276    * @return new array with all children
277    */
278   public final RadComponent[] getComponents() {
279     return myComponents.toArray(new RadComponent[myComponents.size()]);
280   }
281
282   @NotNull
283   public ComponentDropLocation getDropLocation(@Nullable Point location) {
284     return getLayoutManager().getDropLocation(this, location);
285   }
286
287   public RadComponent findComponentInRect(final int startRow, final int startCol, final int rowSpan, final int colSpan) {
288     for(int r=startRow; r < startRow + rowSpan; r++) {
289       for(int c=startCol; c < startCol + colSpan; c++) {
290         final RadComponent result = getComponentAtGrid(r, c);
291         if (result != null) {
292           return result;
293         }
294       }
295     }
296     return null;
297   }
298
299   @Nullable
300   public RadComponent getComponentAtGrid(boolean rowFirst, int coord1, int coord2) {
301     return rowFirst ? getComponentAtGrid(coord1, coord2) : getComponentAtGrid(coord2, coord1);
302   }
303
304   @Nullable
305   public RadComponent getComponentAtGrid(int row, int col) {
306     return getGridLayoutManager().getComponentAtGrid(this, row, col);
307   }
308
309   public int getGridRowCount() {
310     return getGridLayoutManager().getGridRowCount(this);
311   }
312
313   public int getGridColumnCount() {
314     return getGridLayoutManager().getGridColumnCount(this);
315   }
316
317   public int getGridCellCount(boolean isRow) {
318     return isRow ? getGridRowCount() : getGridColumnCount();
319   }
320
321   public int getGridRowAt(int y) {
322     return getGridLayoutManager().getGridRowAt(this, y);
323   }
324
325   public int getGridColumnAt(int x) {
326     return getGridLayoutManager().getGridColumnAt(this, x);
327   }
328
329   /**
330    * @return border's type.
331    *
332    * @see com.intellij.uiDesigner.shared.BorderType
333    */
334   @NotNull
335   public final BorderType getBorderType(){
336     return myBorderType;
337   }
338
339   /**
340    * @see com.intellij.uiDesigner.shared.BorderType
341    *
342    * @exception java.lang.IllegalArgumentException if <code>type</code>
343    * is <code>null</code>
344    */
345   public final void setBorderType(@NotNull final BorderType type){
346     if(myBorderType==type){
347       return;
348     }
349     myBorderType=type;
350     updateBorder();
351   }
352
353   /**
354    * @return border's title. If the container doesn't have any title then the
355    * method returns <code>null</code>.
356    */
357   @Nullable
358   public final StringDescriptor getBorderTitle(){
359     return myBorderTitle;
360   }
361
362   /**
363    * @param title new border's title. <code>null</code> means that
364    * the containr doesn't have have titled border.
365    */
366   public final void setBorderTitle(final StringDescriptor title){
367     if(Comparing.equal(title,myBorderTitle)){
368       return;
369     }
370     myBorderTitle = title;
371     updateBorder();
372   }
373
374   public int getBorderTitleJustification() {
375     return myBorderTitleJustification;
376   }
377
378   public void setBorderTitleJustification(final int borderTitleJustification) {
379     if (myBorderTitleJustification != borderTitleJustification) {
380       myBorderTitleJustification = borderTitleJustification;
381       updateBorder();
382     }
383   }
384
385   public int getBorderTitlePosition() {
386     return myBorderTitlePosition;
387   }
388
389   public void setBorderTitlePosition(final int borderTitlePosition) {
390     if (myBorderTitlePosition != borderTitlePosition) {
391       myBorderTitlePosition = borderTitlePosition;
392       updateBorder();
393     }
394   }
395
396   public FontDescriptor getBorderTitleFont() {
397     return myBorderTitleFont;
398   }
399
400   public void setBorderTitleFont(final FontDescriptor borderTitleFont) {
401     if (!Comparing.equal(myBorderTitleFont, borderTitleFont)) {
402       myBorderTitleFont = borderTitleFont;
403       updateBorder();
404     }
405   }
406
407   public ColorDescriptor getBorderTitleColor() {
408     return myBorderTitleColor;
409   }
410
411   public void setBorderTitleColor(final ColorDescriptor borderTitleColor) {
412     if (!Comparing.equal(myBorderTitleColor, borderTitleColor)) {
413       myBorderTitleColor = borderTitleColor;
414       updateBorder();
415     }
416   }
417
418   public Insets getBorderSize() {
419     return myBorderSize;
420   }
421
422   public void setBorderSize(final Insets borderSize) {
423     if (!Comparing.equal(myBorderSize, borderSize)) {
424       myBorderSize = borderSize;
425       updateBorder();
426     }
427   }
428
429   public ColorDescriptor getBorderColor() {
430     return myBorderColor;
431   }
432
433   public void setBorderColor(final ColorDescriptor borderColor) {
434     if (!Comparing.equal(myBorderColor, borderColor)) {
435       myBorderColor = borderColor;
436       updateBorder();
437     }
438   }
439
440   /**
441    * Updates delegee's border
442    */
443   public boolean updateBorder() {
444     String title = null;
445     String oldTitle = null;
446     if (myBorderTitle != null) {
447       oldTitle = myBorderTitle.getResolvedValue();
448       myBorderTitle.setResolvedValue(null);
449       // NOTE: the explicit getValue() check is required for SnapShooter operation
450       if (myBorderTitle.getValue() != null) {
451         title = myBorderTitle.getValue();
452       }
453       else {
454         title = StringDescriptorManager.getInstance(getModule()).resolve(this, myBorderTitle);
455       }
456     }
457     Font font = (myBorderTitleFont != null) ? myBorderTitleFont.getResolvedFont(getDelegee().getFont()) : null;
458     Color titleColor = (myBorderTitleColor != null) ? myBorderTitleColor.getResolvedColor() : null;
459     Color borderColor = (myBorderColor != null) ? myBorderColor.getResolvedColor() : null;
460     getDelegee().setBorder(myBorderType.createBorder(title, myBorderTitleJustification, myBorderTitlePosition,
461                                                      font, titleColor, myBorderSize, borderColor));
462     return myBorderTitle != null && !Comparing.equal(oldTitle, myBorderTitle.getResolvedValue());
463   }
464
465   public RadLayoutManager getLayoutManager() {
466     RadContainer parent = this;
467     while(parent != null) {
468       if (parent.myLayoutManager != null) {
469         return parent.myLayoutManager;
470       }
471       parent = parent.getParent();
472     }
473     return null;
474   }
475
476   public void setLayoutManager(final RadLayoutManager layoutManager) {
477     myLayoutManager = layoutManager;
478     setLayout(myLayoutManager.createLayout());
479   }
480
481   public void setLayoutManager(RadLayoutManager layoutManager, LayoutManager layout) {
482     myLayoutManager = layoutManager;
483     setLayout(layout);
484   }
485
486   public RadComponent getActionTargetComponent(RadComponent child) {
487     return child;
488   }
489
490   @Override
491   public boolean areChildrenExclusive() {
492     return myLayoutManager.areChildrenExclusive();
493   }
494
495   /**
496    * Serializes container's border
497    */
498   protected final void writeBorder(final XmlWriter writer){
499     writer.startElement(UIFormXmlConstants.ELEMENT_BORDER);
500     try{
501       writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TYPE, getBorderType().getId());
502       if (getBorderTitle() != null) {
503         final StringDescriptor descriptor = getBorderTitle();
504         writer.writeStringDescriptor(descriptor, UIFormXmlConstants.ATTRIBUTE_TITLE,
505                                      UIFormXmlConstants.ATTRIBUTE_TITLE_RESOURCE_BUNDLE,
506                                      UIFormXmlConstants.ATTRIBUTE_TITLE_KEY);
507       }
508       if (myBorderTitleJustification != 0) {
509         writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TITLE_JUSTIFICATION, myBorderTitleJustification);
510       }
511       if (myBorderTitlePosition != 0) {
512         writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_TITLE_POSITION, myBorderTitlePosition);
513       }
514       if (myBorderTitleFont != null) {
515         writer.startElement(UIFormXmlConstants.ELEMENT_FONT);
516         writer.writeFontDescriptor(myBorderTitleFont);
517         writer.endElement();
518       }
519       if (myBorderTitleColor != null) {
520         writer.startElement(UIFormXmlConstants.ELEMENT_TITLE_COLOR);
521         writer.writeColorDescriptor(myBorderTitleColor);
522         writer.endElement();
523       }
524       if (myBorderSize != null) {
525         writer.startElement(UIFormXmlConstants.ELEMENT_SIZE);
526         writer.writeInsets(myBorderSize);
527         writer.endElement();
528       }
529       if (myBorderColor != null) {
530         writer.startElement(UIFormXmlConstants.ELEMENT_COLOR);
531         writer.writeColorDescriptor(myBorderColor);
532         writer.endElement();
533       }
534     }finally{
535       writer.endElement(); // border
536     }
537   }
538
539   /**
540    * Serializes container's children
541    */
542   protected final void writeChildren(final XmlWriter writer){
543     // Children
544     writer.startElement("children");
545     try{
546       writeChildrenImpl(writer);
547     }finally{
548       writer.endElement(); // children
549     }
550   }
551
552   protected final void writeChildrenImpl(final XmlWriter writer){
553     for (int i=0; i < getComponentCount(); i++) {
554       getComponent(i).write(writer);
555     }
556   }
557
558   public void write(final XmlWriter writer) {
559     if (isXY()) {
560       writer.startElement("xy");
561     }
562     else {
563       writer.startElement("grid");
564     }
565     try{
566       writeId(writer);
567       writeClassIfDifferent(writer, JPanel.class.getName());
568       writeBinding(writer);
569
570       if (myLayoutManager != null) {
571         writer.addAttribute(UIFormXmlConstants.ATTRIBUTE_LAYOUT_MANAGER, myLayoutManager.getName());
572       }
573
574       getLayoutManager().writeLayout(writer, this);
575
576       // Constraints and properties
577       writeConstraints(writer);
578       writeProperties(writer);
579
580       // Border
581       writeBorder(writer);
582
583       // Children
584       writeChildren(writer);
585     }finally{
586       writer.endElement(); // xy/grid
587     }
588   }
589
590   public boolean accept(ComponentVisitor visitor) {
591     if (!super.accept(visitor)) {
592       return false;
593     }
594
595     for (int i = 0; i < getComponentCount(); i++) {
596       final IComponent c = getComponent(i);
597       if (!c.accept(visitor)) {
598         return false;
599       }
600     }
601
602     return true;
603   }
604
605   protected void writeNoLayout(final XmlWriter writer, final String defaultClassName) {
606     writeId(writer);
607     writeClassIfDifferent(writer, defaultClassName);
608     writeBinding(writer);
609
610     // Constraints and properties
611     writeConstraints(writer);
612     writeProperties(writer);
613
614     // Margin and border
615     writeBorder(writer);
616     writeChildren(writer);
617   }
618
619   @Override
620   protected void importSnapshotComponent(final SnapshotContext context, final JComponent component) {
621     getLayoutManager().createSnapshotLayout(context, component, this, component.getLayout());
622     importSnapshotBorder(component);
623     for(Component child: component.getComponents()) {
624       if (child instanceof JComponent) {
625         RadComponent childComponent = createSnapshotComponent(context, (JComponent) child);
626         if (childComponent != null) {
627           getLayoutManager().addSnapshotComponent(component, (JComponent) child, this, childComponent);
628         }
629       }
630     }
631   }
632
633   private void importSnapshotBorder(final JComponent component) {
634     Border border = component.getBorder();
635     if (border != null) {
636       if (border instanceof TitledBorder) {
637         TitledBorder titledBorder = (TitledBorder) border;
638         setBorderTitle(StringDescriptor.create(titledBorder.getTitle()));
639         setBorderTitleJustification(titledBorder.getTitleJustification());
640         setBorderTitlePosition(titledBorder.getTitlePosition());
641         final Font titleFont = titledBorder.getTitleFont();
642         setBorderTitleFont(new FontDescriptor(titleFont.getName(), titleFont.getStyle(), titleFont.getSize()));
643         setBorderTitleColor(new ColorDescriptor(titledBorder.getTitleColor()));
644         border = titledBorder.getBorder();
645       }
646
647       if (border instanceof EtchedBorder) {
648         setBorderType(BorderType.ETCHED);
649       }
650       else if (border instanceof BevelBorder) {
651         BevelBorder bevelBorder = (BevelBorder) border;
652         setBorderType(bevelBorder.getBevelType() == BevelBorder.RAISED ? BorderType.BEVEL_RAISED : BorderType.BEVEL_LOWERED);
653       }
654       else if (border instanceof EmptyBorder) {
655         EmptyBorder emptyBorder = (EmptyBorder) border;
656         setBorderType(BorderType.EMPTY);
657         setBorderSize(emptyBorder.getBorderInsets());
658       }
659       else if (border instanceof LineBorder) {
660         LineBorder lineBorder = (LineBorder) border;
661         setBorderType(BorderType.LINE);
662         setBorderColor(new ColorDescriptor(lineBorder.getLineColor()));
663       }
664     }
665   }
666
667   public RadAbstractGridLayoutManager getGridLayoutManager() {
668     if (!(myLayoutManager instanceof RadAbstractGridLayoutManager)) {
669       throw new RuntimeException("Not a grid container");
670     }
671     return (RadAbstractGridLayoutManager) myLayoutManager;
672   }
673
674   @Nullable
675   public RadComponent findComponentWithConstraints(final Object constraints) {
676     for(RadComponent component: getComponents()) {
677       if (constraints.equals(component.getCustomLayoutConstraints())) {
678         return component;
679       }
680     }
681     return null;
682   }
683
684   private final class MyBorderTitleProperty extends Property<RadContainer, StringDescriptor> {
685     private final StringEditor myEditor;
686
687     public MyBorderTitleProperty() {
688       super(null, "Title");
689       myEditor = new StringEditor(getModule().getProject());
690     }
691
692     public Dimension getPreferredSize(){
693       return myEditor.getPreferredSize();
694     }
695
696     public StringDescriptor getValue(final RadContainer component) {
697       return myBorderTitle;
698     }
699
700     protected void setValueImpl(final RadContainer container, final StringDescriptor value) throws Exception {
701       setBorderTitle(value);
702     }
703
704     @NotNull
705     public PropertyRenderer<StringDescriptor> getRenderer() {
706       return null;
707     }
708
709     public PropertyEditor<StringDescriptor> getEditor() {
710       return myEditor;
711     }
712   }
713 }