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