2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com
.intellij
.uiDesigner
.propertyInspector
;
18 import com
.intellij
.codeInsight
.daemon
.impl
.SeverityRegistrar
;
19 import com
.intellij
.ide
.ui
.LafManager
;
20 import com
.intellij
.ide
.ui
.LafManagerListener
;
21 import com
.intellij
.lang
.annotation
.HighlightSeverity
;
22 import com
.intellij
.openapi
.actionSystem
.*;
23 import com
.intellij
.openapi
.command
.CommandProcessor
;
24 import com
.intellij
.openapi
.diagnostic
.Logger
;
25 import com
.intellij
.openapi
.editor
.colors
.EditorColorsManager
;
26 import com
.intellij
.openapi
.editor
.colors
.TextAttributesKey
;
27 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
28 import com
.intellij
.openapi
.module
.Module
;
29 import com
.intellij
.openapi
.project
.Project
;
30 import com
.intellij
.openapi
.ui
.Messages
;
31 import com
.intellij
.openapi
.util
.Comparing
;
32 import com
.intellij
.openapi
.util
.IconLoader
;
33 import com
.intellij
.openapi
.util
.Ref
;
34 import com
.intellij
.openapi
.wm
.ex
.IdeFocusTraversalPolicy
;
35 import com
.intellij
.psi
.JavaPsiFacade
;
36 import com
.intellij
.psi
.PsiClass
;
37 import com
.intellij
.psi
.PsiManager
;
38 import com
.intellij
.psi
.PsiMethod
;
39 import com
.intellij
.psi
.search
.GlobalSearchScope
;
40 import com
.intellij
.psi
.util
.PropertyUtil
;
41 import com
.intellij
.ui
.*;
42 import com
.intellij
.uiDesigner
.ErrorAnalyzer
;
43 import com
.intellij
.uiDesigner
.ErrorInfo
;
44 import com
.intellij
.uiDesigner
.Properties
;
45 import com
.intellij
.uiDesigner
.UIDesignerBundle
;
46 import com
.intellij
.uiDesigner
.actions
.ShowJavadocAction
;
47 import com
.intellij
.uiDesigner
.componentTree
.ComponentTree
;
48 import com
.intellij
.uiDesigner
.designSurface
.GuiEditor
;
49 import com
.intellij
.uiDesigner
.palette
.Palette
;
50 import com
.intellij
.uiDesigner
.propertyInspector
.properties
.*;
51 import com
.intellij
.uiDesigner
.radComponents
.*;
52 import com
.intellij
.util
.ui
.EmptyIcon
;
53 import com
.intellij
.util
.ui
.IndentedIcon
;
54 import com
.intellij
.util
.ui
.Table
;
55 import org
.jetbrains
.annotations
.NonNls
;
56 import org
.jetbrains
.annotations
.NotNull
;
57 import org
.jetbrains
.annotations
.Nullable
;
60 import javax
.swing
.event
.ChangeEvent
;
61 import javax
.swing
.plaf
.TableUI
;
62 import javax
.swing
.table
.AbstractTableModel
;
63 import javax
.swing
.table
.TableCellEditor
;
64 import javax
.swing
.table
.TableCellRenderer
;
66 import java
.awt
.event
.ActionEvent
;
67 import java
.awt
.event
.KeyEvent
;
68 import java
.awt
.event
.MouseAdapter
;
69 import java
.awt
.event
.MouseEvent
;
70 import java
.lang
.reflect
.InvocationTargetException
;
72 import java
.util
.List
;
75 * @author Anton Katilin
76 * @author Vladimir Kondratyev
78 public final class PropertyInspectorTable
extends Table
implements DataProvider
{
79 private static final Logger LOG
= Logger
.getInstance("#com.intellij.uiDesigner.propertyInspector.PropertyInspectorTable");
81 private static final Color SYNTETIC_PROPERTY_BACKGROUND
= new Color(230,230,230);
82 private static final Color SYNTETIC_SUBPROPERTY_BACKGROUND
= new Color(240,240,240);
84 private final ComponentTree myComponentTree
;
85 private final ArrayList
<Property
> myProperties
;
86 private final MyModel myModel
;
87 private final MyCompositeTableCellRenderer myCellRenderer
;
88 private final MyCellEditor myCellEditor
;
89 private GuiEditor myEditor
;
91 * This listener gets notifications from current property editor
93 private final MyPropertyEditorListener myPropertyEditorListener
;
95 * Updates UIs of synthetic properties
97 private final MyLafManagerListener myLafManagerListener
;
99 * This is property exists in this map then it's expanded.
100 * It means that its children is visible.
102 private final HashSet
<String
> myExpandedProperties
;
104 * Component to be edited
106 @NotNull private final List
<RadComponent
> mySelection
= new ArrayList
<RadComponent
>();
108 * If true then inspector will show "expert" properties
110 private boolean myShowExpertProperties
;
112 private final Map
<HighlightSeverity
, SimpleTextAttributes
> myHighlightAttributes
= new HashMap
<HighlightSeverity
, SimpleTextAttributes
>();
113 private final Map
<HighlightSeverity
, SimpleTextAttributes
> myModifiedHighlightAttributes
= new HashMap
<HighlightSeverity
, SimpleTextAttributes
>();
115 private final ClassToBindProperty myClassToBindProperty
;
116 private final BindingProperty myBindingProperty
;
117 private final BorderProperty myBorderProperty
;
118 private final LayoutManagerProperty myLayoutManagerProperty
= new LayoutManagerProperty();
119 private final ButtonGroupProperty myButtonGroupProperty
= new ButtonGroupProperty();
121 private boolean myInsideSynch
;
122 private boolean myStoppingEditing
;
123 private final Project myProject
;
125 @NonNls private static final String ourHelpID
= "guiDesigner.uiTour.inspector";
127 PropertyInspectorTable(Project project
, @NotNull final ComponentTree componentTree
) {
129 myClassToBindProperty
= new ClassToBindProperty(project
);
130 myBindingProperty
= new BindingProperty(project
);
131 myBorderProperty
= new BorderProperty(project
);
133 myPropertyEditorListener
= new MyPropertyEditorListener();
134 myLafManagerListener
= new MyLafManagerListener();
135 myComponentTree
=componentTree
;
136 myProperties
= new ArrayList
<Property
>();
137 myExpandedProperties
= new HashSet
<String
>();
138 myModel
= new MyModel();
140 setSelectionMode(ListSelectionModel
.SINGLE_SELECTION
);
142 myCellRenderer
= new MyCompositeTableCellRenderer();
143 myCellEditor
= new MyCellEditor();
145 addMouseListener(new MyMouseListener());
147 final AnAction quickJavadocAction
= ActionManager
.getInstance().getAction(IdeActions
.ACTION_QUICK_JAVADOC
);
148 new ShowJavadocAction().registerCustomShortcutSet(
149 quickJavadocAction
.getShortcutSet(), this
153 PopupHandler
.installPopupHandler(
155 (ActionGroup
)ActionManager
.getInstance().getAction(IdeActions
.GROUP_GUI_DESIGNER_PROPERTY_INSPECTOR_POPUP
),
156 ActionPlaces
.GUI_DESIGNER_PROPERTY_INSPECTOR_POPUP
, ActionManager
.getInstance());
158 TableToolTipHandler
.install(this);
161 public void setEditor(final GuiEditor editor
) {
164 if (myEditor
== null) {
166 myProperties
.clear();
167 myModel
.fireTableDataChanged();
172 * @return currently selected {@link IntrospectedProperty} or <code>null</code>
173 * if nothing selected or synthetic property is selected.
176 public IntrospectedProperty
getSelectedIntrospectedProperty(){
177 Property property
= getSelectedProperty();
178 if (property
== null || !(property
instanceof IntrospectedProperty
)) {
182 return (IntrospectedProperty
)property
;
185 @Nullable public Property
getSelectedProperty() {
186 final int selectedRow
= getSelectedRow();
187 if(selectedRow
< 0 || selectedRow
>= getRowCount()){
191 return myProperties
.get(selectedRow
);
195 * @return {@link PsiClass} of the component which properties are displayed inside the inspector
197 public PsiClass
getComponentClass(){
198 final Module module
= myEditor
.getModule();
200 if (mySelection
.size() == 0) return null;
201 String className
= mySelection
.get(0).getComponentClassName();
202 for(int i
=1; i
<mySelection
.size(); i
++) {
203 if (!Comparing
.equal(mySelection
.get(i
).getComponentClassName(), className
)) {
208 return JavaPsiFacade
.getInstance(module
.getProject())
209 .findClass(className
, GlobalSearchScope
.moduleWithDependenciesAndLibrariesScope(module
));
212 public Object
getData(final String dataId
) {
213 if(getClass().getName().equals(dataId
)){
216 else if(DataConstants
.PSI_ELEMENT
.equals(dataId
)){
217 final IntrospectedProperty introspectedProperty
= getSelectedIntrospectedProperty();
218 if(introspectedProperty
== null){
221 final PsiClass aClass
= getComponentClass();
226 final PsiMethod getter
= PropertyUtil
.findPropertyGetter(aClass
, introspectedProperty
.getName(), false, true);
231 return PropertyUtil
.findPropertySetter(aClass
, introspectedProperty
.getName(), false, true);
233 else if (DataConstants
.PSI_FILE
.equals(dataId
) && myEditor
!= null) {
234 return PsiManager
.getInstance(myEditor
.getProject()).findFile(myEditor
.getFile());
236 else if (GuiEditor
.class.getName().equals(dataId
)) {
239 else if (DataConstants
.FILE_EDITOR
.equals(dataId
)) {
240 return UIDesignerToolWindowManager
.getInstance(myProject
).getActiveFormFileEditor();
242 else if (DataConstants
.HELP_ID
.equals(dataId
)) {
251 * Sets whenther "expert" properties are shown or not
253 void setShowExpertProperties(final boolean showExpertProperties
){
254 if(myShowExpertProperties
== showExpertProperties
){
257 myShowExpertProperties
= showExpertProperties
;
258 if (myEditor
!= null) {
263 public void addNotify() {
265 LafManager
.getInstance().addLafManagerListener(myLafManagerListener
);
268 public void removeNotify() {
269 LafManager
.getInstance().removeLafManagerListener(myLafManagerListener
);
270 super.removeNotify();
274 * Standard JTable's UI has non convenient keybinding for
275 * editing. Therefore we have to replace some standard actions.
277 public void setUI(final TableUI ui
){
280 // Customize action and input maps
281 @NonNls final ActionMap actionMap
=getActionMap();
282 @NonNls final InputMap focusedInputMap
=getInputMap(JComponent
.WHEN_FOCUSED
);
283 @NonNls final InputMap ancestorInputMap
=getInputMap(JComponent
.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT
);
285 actionMap
.put("selectPreviousRow",new MySelectPreviousRowAction());
287 actionMap
.put("selectNextRow",new MySelectNextRowAction());
289 actionMap
.put("startEditing",new MyStartEditingAction());
290 focusedInputMap
.put(KeyStroke
.getKeyStroke(KeyEvent
.VK_F2
,0),"startEditing");
291 ancestorInputMap
.remove(KeyStroke
.getKeyStroke(KeyEvent
.VK_F2
,0));
293 actionMap
.put("smartEnter",new MyEnterAction());
294 focusedInputMap
.put(KeyStroke
.getKeyStroke(KeyEvent
.VK_ENTER
,0),"smartEnter");
295 ancestorInputMap
.remove(KeyStroke
.getKeyStroke(KeyEvent
.VK_ENTER
,0));
297 focusedInputMap
.put(KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
,0),"cancel");
298 ancestorInputMap
.put(KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
,0),"cancel");
300 actionMap
.put("expandCurrent", new MyExpandCurrentAction(true));
301 focusedInputMap
.put(KeyStroke
.getKeyStroke(KeyEvent
.VK_ADD
,0),"expandCurrent");
302 ancestorInputMap
.remove(KeyStroke
.getKeyStroke(KeyEvent
.VK_ADD
,0));
304 actionMap
.put("collapseCurrent", new MyExpandCurrentAction(false));
305 focusedInputMap
.put(KeyStroke
.getKeyStroke(KeyEvent
.VK_SUBTRACT
,0),"collapseCurrent");
306 ancestorInputMap
.remove(KeyStroke
.getKeyStroke(KeyEvent
.VK_SUBTRACT
,0));
309 public void setValueAt(final Object aValue
, final int row
, final int column
) {
310 final Property property
= myProperties
.get(row
);
311 super.setValueAt(aValue
, row
, column
);
312 // We need to repaint whole inspector because change of one property
313 // might causes change of another property.
314 if (property
.needRefreshPropertyList()) {
321 * Gets first selected component from ComponentTree and sets it for editing.
322 * The method tries to keep selection in the list, so if new component has the property
323 * which is already selected then the new value will be
324 * also selected. It is very convenient.
326 * @param forceSynch if <code>false</code> and selected component in the ComponentTree
327 * is the same as current component in the PropertyInspector then method does
328 * nothing such sace. If <code>true</code> then inspector is forced to resynch.
330 public void synchWithTree(final boolean forceSynch
){
334 myInsideSynch
= true;
336 RadComponent
[] newSelection
= myComponentTree
.getSelectedComponents();
337 if (!forceSynch
&& mySelection
.size() == newSelection
.length
) {
338 boolean anyChanges
= false;
339 for(RadComponent c
: newSelection
) {
340 if (!mySelection
.contains(c
)) {
345 if (!anyChanges
) return;
349 Collections
.addAll(mySelection
, newSelection
);
352 cellEditor
.stopCellEditing();
355 // Store selected property
356 final int selectedRow
=getSelectedRow();
357 Property selectedProperty
=null;
358 if(selectedRow
>= 0 && selectedRow
< myProperties
.size()){
359 selectedProperty
=myProperties
.get(selectedRow
);
362 collectPropertiesForSelection();
363 myModel
.fireTableDataChanged();
365 // Try to restore selection
366 final ArrayList
<Property
> reversePath
=new ArrayList
<Property
>(2);
367 while(selectedProperty
!=null){
368 reversePath
.add(selectedProperty
);
369 selectedProperty
=selectedProperty
.getParent();
371 int indexToSelect
=-1;
372 for(int i
=reversePath
.size()-1;i
>=0;i
--){
373 final Property property
=reversePath
.get(i
);
374 int index
=findPropertyByName(myProperties
, property
.getName());
375 if(index
==-1 && indexToSelect
!=-1){ // try to expand parent and try again
376 expandProperty(indexToSelect
);
377 index
=findPropertyByName(myProperties
, property
.getName());
388 if(indexToSelect
!=-1){
389 getSelectionModel().setSelectionInterval(indexToSelect
,indexToSelect
);
390 }else if(getRowCount()>0){
391 // Select first row if it's impossible to restore selection
392 getSelectionModel().setSelectionInterval(0,0);
394 TableUtil
.scrollSelectionToVisible(this);
397 myInsideSynch
= false;
401 private void collectPropertiesForSelection() {
402 myProperties
.clear();
403 if (mySelection
.size() > 0) {
404 collectProperties(mySelection
.get(0), myProperties
);
406 for(int propIndex
=myProperties
.size()-1; propIndex
>= 0; propIndex
--) {
407 if (!myProperties
.get(propIndex
).appliesToSelection(mySelection
)) {
408 myProperties
.remove(propIndex
);
412 for(int i
=1; i
<mySelection
.size(); i
++) {
413 ArrayList
<Property
> otherProperties
= new ArrayList
<Property
>();
414 collectProperties(mySelection
.get(i
), otherProperties
);
415 for(int propIndex
=myProperties
.size()-1; propIndex
>= 0; propIndex
--) {
416 final Property prop
= myProperties
.get(propIndex
);
417 int otherPropIndex
= findPropertyByName(otherProperties
, prop
.getName());
418 if (otherPropIndex
< 0) {
419 myProperties
.remove(propIndex
);
422 final Property otherProp
= otherProperties
.get(otherPropIndex
);
423 if (!otherProp
.getClass().equals(prop
.getClass())) {
424 myProperties
.remove(propIndex
);
427 Property
[] children
= prop
.getChildren(mySelection
.get(0));
428 Property
[] otherChildren
= otherProp
.getChildren(mySelection
.get(i
));
429 if (children
.length
!= otherChildren
.length
) {
430 myProperties
.remove(propIndex
);
433 for(int childIndex
=0; childIndex
<children
.length
; childIndex
++) {
434 if (!Comparing
.equal(children
[childIndex
].getName(), otherChildren
[childIndex
].getName())) {
435 myProperties
.remove(propIndex
);
445 * @return index of the property with specified <code>name</code>.
446 * If there is no such property then the method returns <code>-1</code>.
448 private static int findPropertyByName(final ArrayList
<Property
> properties
, final String name
){
449 for(int i
=properties
.size()-1;i
>=0;i
--){
450 final Property property
=properties
.get(i
);
451 if(property
.getName().equals(name
)){
459 * Populates result list with the properties available for the specified
462 private void collectProperties(final RadComponent component
, final ArrayList
<Property
> result
) {
463 if (component
instanceof RadRootContainer
){
464 addProperty(result
, myClassToBindProperty
);
467 if (!(component
instanceof RadVSpacer
|| component
instanceof RadHSpacer
)){
468 addProperty(result
, myBindingProperty
);
469 addProperty(result
, CustomCreateProperty
.getInstance(myProject
));
472 if(component
instanceof RadContainer
){
473 RadContainer container
= (RadContainer
) component
;
474 if (container
.getLayoutManager().getName() != null) {
475 addProperty(result
, myLayoutManagerProperty
);
477 addProperty(result
, myBorderProperty
);
479 final Property
[] containerProperties
= container
.getLayoutManager().getContainerProperties(myProject
);
480 addApplicableProperties(containerProperties
, container
, result
);
483 final RadContainer parent
= component
.getParent();
484 if (parent
!= null) {
485 final Property
[] properties
= parent
.getLayoutManager().getComponentProperties(myProject
, component
);
486 addApplicableProperties(properties
, component
, result
);
489 if (component
.getDelegee() instanceof AbstractButton
&&
490 !(component
.getDelegee() instanceof JButton
)) {
491 addProperty(result
, myButtonGroupProperty
);
493 if (!(component
instanceof RadVSpacer
|| component
instanceof RadHSpacer
)) {
494 addProperty(result
, ClientPropertiesProperty
.getInstance(myProject
));
497 if (component
.hasIntrospectedProperties()) {
498 final Class componentClass
= component
.getComponentClass();
499 final IntrospectedProperty
[] introspectedProperties
=
500 Palette
.getInstance(myEditor
.getProject()).getIntrospectedProperties(component
);
501 final Properties properties
= Properties
.getInstance();
502 for (final IntrospectedProperty property
: introspectedProperties
) {
503 if (!property
.appliesTo(component
)) continue;
504 if (!myShowExpertProperties
&& properties
.isExpertProperty(component
.getModule(), componentClass
, property
.getName()) &&
505 !isModifiedForSelection(property
)) {
508 addProperty(result
, property
);
514 private void addApplicableProperties(final Property
[] containerProperties
,
515 final RadComponent component
,
516 final ArrayList
<Property
> result
) {
517 for(Property prop
: containerProperties
) {
518 //noinspection unchecked
519 if (prop
.appliesTo(component
)) {
520 addProperty(result
, prop
);
525 private void addProperty(final ArrayList
<Property
> result
, final Property property
) {
526 result
.add(property
);
527 if (isPropertyExpanded(property
, property
.getParent())) {
528 for(Property child
: getPropChildren(property
)) {
529 addProperty(result
, child
);
534 private boolean isPropertyExpanded(final Property property
, final Property parent
) {
535 return myExpandedProperties
.contains(getDottedName(property
));
538 private static String
getDottedName(final Property property
) {
539 final Property parent
= property
.getParent();
540 if (parent
!= null) {
541 return parent
.getName() + "." + property
.getName();
543 return property
.getName();
546 private static int getPropertyIndent(final Property property
) {
547 final Property parent
= property
.getParent();
548 if (parent
!= null) {
549 return parent
.getParent() != null ?
2 : 1;
554 private Property
[] getPropChildren(final Property property
) {
555 return property
.getChildren(mySelection
.get(0));
558 public TableCellEditor
getCellEditor(final int row
, final int column
){
559 final PropertyEditor editor
= myProperties
.get(row
).getEditor();
560 editor
.removePropertyEditorListener(myPropertyEditorListener
); // we do not need to add listener on every invocation
561 editor
.addPropertyEditorListener(myPropertyEditorListener
);
562 myCellEditor
.setEditor(editor
);
566 public TableCellRenderer
getCellRenderer(final int row
,final int column
){
567 return myCellRenderer
;
571 * This method is overriden due to bug in the JTree. The problem is that
572 * JTree does not properly repaint edited cell if the editor is opaque or
573 * has opaque child components.
575 public boolean editCellAt(final int row
, final int column
, final EventObject e
){
576 final boolean result
= super.editCellAt(row
, column
, e
);
577 final Rectangle cellRect
= getCellRect(row
, column
, true);
583 * Starts editing property with the specified <code>index</code>.
584 * The method does nothing is property isn't editable.
586 private void startEditing(final int index
){
587 final Property property
=myProperties
.get(index
);
588 final PropertyEditor editor
=property
.getEditor();
592 editCellAt(index
,convertColumnIndexToView(1));
593 LOG
.assertTrue(editorComp
!=null);
594 // Now we have to request focus into the editor component
595 JComponent prefComponent
= editor
.getPreferredFocusedComponent((JComponent
)editorComp
);
596 if(prefComponent
== null){ // use default policy to find preferred focused component
597 prefComponent
= IdeFocusTraversalPolicy
.getPreferredFocusedComponent((JComponent
)editorComp
);
599 if (prefComponent
!= null) {
600 prefComponent
.requestFocusInWindow();
604 private void finishEditing(){
608 editingStopped(new ChangeEvent(cellEditor
));
611 public void editingStopped(final ChangeEvent ignored
){
612 LOG
.assertTrue(isEditing());
613 LOG
.assertTrue(editingRow
!=-1);
614 if (myStoppingEditing
) return;
615 myStoppingEditing
= true;
616 final Property property
=myProperties
.get(editingRow
);
617 final PropertyEditor editor
=property
.getEditor();
618 editor
.removePropertyEditorListener(myPropertyEditorListener
);
620 final Object value
= editor
.getValue();
621 setValueAt(value
, editingRow
, editingColumn
);
623 catch (final Exception exc
) {
624 showInvalidInput(exc
);
628 myStoppingEditing
= false;
632 private static void showInvalidInput(final Exception exc
) {
633 final Throwable cause
= exc
.getCause();
636 message
= cause
.getMessage();
639 message
= exc
.getMessage();
641 if (message
== null || message
.length() == 0) {
642 message
= UIDesignerBundle
.message("error.no.message");
644 Messages
.showMessageDialog(UIDesignerBundle
.message("error.setting.value", message
),
645 UIDesignerBundle
.message("title.invalid.input"), Messages
.getErrorIcon());
649 * Expands property with the specified index. The method fires event that
650 * model changes and keeps currently selected row.
652 private void expandProperty(final int index
){
653 final int selectedRow
=getSelectedRow();
656 final Property property
=myProperties
.get(index
);
657 final String dottedName
= getDottedName(property
);
659 // it's possible that property was expanded and we switched to a component which doesn't have this property
660 if (myExpandedProperties
.contains(dottedName
)) return;
661 myExpandedProperties
.add(dottedName
);
663 final Property
[] children
=getPropChildren(property
);
664 for (int i
= 0; i
< children
.length
; i
++) {
665 myProperties
.add(index
+ i
+ 1, children
[i
]);
667 myModel
.fireTableDataChanged();
669 // Restore selected row
671 getSelectionModel().setSelectionInterval(selectedRow
,selectedRow
);
676 * Collapse property with the specified index. The method fires event that
677 * model changes and keeps currently selected row.
679 private void collapseProperty(final int index
){
680 final int selectedRow
=getSelectedRow();
683 final Property property
=myProperties
.get(index
);
684 LOG
.assertTrue(isPropertyExpanded(property
, property
.getParent()));
685 myExpandedProperties
.remove(getDottedName(property
));
687 final Property
[] children
=getPropChildren(property
);
688 for (int i
=0; i
<children
.length
; i
++){
689 myProperties
.remove(index
+ 1);
691 myModel
.fireTableDataChanged();
693 // Restore selected row
695 getSelectionModel().setSelectionInterval(selectedRow
,selectedRow
);
700 ErrorInfo
getErrorInfoForRow(final int row
) {
701 LOG
.assertTrue(row
< myProperties
.size());
702 if (mySelection
.size() != 1) {
705 RadComponent component
= mySelection
.get(0);
706 final Property property
= myProperties
.get(row
);
707 ErrorInfo errorInfo
= null;
708 if(myClassToBindProperty
.equals(property
)){
709 errorInfo
= (ErrorInfo
)component
.getClientProperty(ErrorAnalyzer
.CLIENT_PROP_CLASS_TO_BIND_ERROR
);
711 else if(myBindingProperty
.equals(property
)){
712 errorInfo
= (ErrorInfo
)component
.getClientProperty(ErrorAnalyzer
.CLIENT_PROP_BINDING_ERROR
);
715 //noinspection unchecked
716 ArrayList
<ErrorInfo
> errors
= (ArrayList
<ErrorInfo
>) component
.getClientProperty(ErrorAnalyzer
.CLIENT_PROP_ERROR_ARRAY
);
717 if (errors
!= null) {
718 for(ErrorInfo err
: errors
) {
719 if (property
.getName().equals(err
.getPropertyName())) {
730 * @return first error for the property at the specified row. If component doesn't contain
731 * any error then the method returns <code>null</code>.
734 private String
getErrorForRow(final int row
){
735 LOG
.assertTrue(row
< myProperties
.size());
736 final ErrorInfo errorInfo
= getErrorInfoForRow(row
);
737 return errorInfo
!= null ? errorInfo
.myDescription
: null;
740 public String
getToolTipText(final MouseEvent e
) {
741 final int row
= rowAtPoint(e
.getPoint());
745 return getErrorForRow(row
);
748 private Object
getSelectionValue(final Property property
) {
749 if (mySelection
.size() == 0) {
752 //noinspection unchecked
753 Object result
= property
.getValue(mySelection
.get(0));
754 for(int i
=1; i
<mySelection
.size(); i
++) {
755 Object otherValue
= null;
756 if (property
instanceof IntrospectedProperty
) {
757 IntrospectedProperty
[] props
= Palette
.getInstance(myProject
).getIntrospectedProperties(mySelection
.get(i
));
758 for(IntrospectedProperty otherProperty
: props
) {
759 if (otherProperty
.getName().equals(property
.getName())) {
760 otherValue
= otherProperty
.getValue(mySelection
.get(i
));
766 //noinspection unchecked
767 otherValue
= property
.getValue(mySelection
.get(i
));
769 if (!Comparing
.equal(result
, otherValue
)) {
777 * @return false if some of the set value operations have failed; true if everything successful
779 private boolean setSelectionValue(Property property
, Object newValue
) {
780 if (!setPropValue(property
, mySelection
.get(0), newValue
)) return false;
781 for(int i
=1; i
<mySelection
.size(); i
++) {
782 if (property
instanceof IntrospectedProperty
) {
783 IntrospectedProperty
[] props
= Palette
.getInstance(myProject
).getIntrospectedProperties(mySelection
.get(i
));
784 for(IntrospectedProperty otherProperty
: props
) {
785 if (otherProperty
.getName().equals(property
.getName())) {
786 if (!setPropValue(otherProperty
, mySelection
.get(i
), newValue
)) return false;
792 if (!setPropValue(property
, mySelection
.get(i
), newValue
)) return false;
798 private static boolean setPropValue(final Property property
, final RadComponent c
, final Object newValue
) {
800 //noinspection unchecked
801 property
.setValue(c
, newValue
);
803 catch (Throwable e
) {
805 if(e
instanceof InvocationTargetException
){ // special handling of warapped exceptions
806 e
= ((InvocationTargetException
)e
).getTargetException();
808 Messages
.showMessageDialog(e
.getMessage(), UIDesignerBundle
.message("title.invalid.input"), Messages
.getErrorIcon());
814 public boolean isModifiedForSelection(final Property property
) {
815 for(RadComponent c
: mySelection
) {
816 //noinspection unchecked
817 if (property
.isModified(c
)) {
825 * Adapter to TableModel
827 private final class MyModel
extends AbstractTableModel
{
828 private final String
[] myColumnNames
;
831 myColumnNames
=new String
[]{
832 UIDesignerBundle
.message("column.property"),
833 UIDesignerBundle
.message("column.value")};
836 public int getColumnCount(){
840 public String
getColumnName(final int column
){
841 return myColumnNames
[column
];
844 public int getRowCount(){
845 return myProperties
.size();
848 public boolean isCellEditable(final int row
, final int column
){
849 return column
==1 && myProperties
.get(row
).getEditor() != null;
852 public Object
getValueAt(final int row
, final int column
){
853 return myProperties
.get(row
);
856 public void setValueAt(final Object newValue
, final int row
, final int column
){
858 throw new IllegalArgumentException("wrong index: " + column
);
860 setValueAtRow(row
, newValue
);
863 boolean setValueAtRow(final int row
, final Object newValue
) {
864 final Property property
=myProperties
.get(row
);
866 // Optimization: do nothing if value doesn't change
867 final Object oldValue
=getSelectionValue(property
);
868 boolean retVal
= true;
869 if(!Comparing
.equal(oldValue
,newValue
)){
870 final GuiEditor editor
= myEditor
;
871 if (!editor
.ensureEditable()) {
874 final Ref
<Boolean
> result
= new Ref
<Boolean
>(Boolean
.FALSE
);
875 CommandProcessor
.getInstance().executeCommand(myProject
, new Runnable() {
877 result
.set(setSelectionValue(property
, newValue
));
879 editor
.refreshAndSave(false);
881 }, UIDesignerBundle
.message("command.set.property.value"), null);
883 retVal
= result
.get().booleanValue();
885 if (property
.needRefreshPropertyList() && retVal
) {
892 private final class MyPropertyEditorListener
extends PropertyEditorAdapter
{
893 public void valueCommitted(final PropertyEditor source
, final boolean continueEditing
, final boolean closeEditorOnError
){
896 final TableCellEditor tableCellEditor
= cellEditor
;
898 value
= tableCellEditor
.getCellEditorValue();
900 catch (final Exception exc
) {
901 showInvalidInput(exc
);
904 boolean valueAccepted
= myModel
.setValueAtRow(editingRow
, value
);
906 if (!continueEditing
) tableCellEditor
.stopCellEditing();
909 if (closeEditorOnError
) tableCellEditor
.cancelCellEditing();
914 public void editingCanceled(final PropertyEditor source
) {
916 cellEditor
.cancelCellEditing();
921 private final class MyCompositeTableCellRenderer
implements TableCellRenderer
{
923 * This renderer paints first column with property names
925 private final ColoredTableCellRenderer myPropertyNameRenderer
;
926 private final ColoredTableCellRenderer myErrorRenderer
;
927 private final Icon myExpandIcon
;
928 private final Icon myCollapseIcon
;
929 private final Icon myIndentedExpandIcon
;
930 private final Icon myIndentedCollapseIcon
;
931 private final Icon
[] myIndentIcons
= new Icon
[3];
933 public MyCompositeTableCellRenderer(){
934 myPropertyNameRenderer
= new ColoredTableCellRenderer() {
935 protected void customizeCellRenderer(
938 final boolean selected
,
939 final boolean hasFocus
,
943 // We will append text later in the
944 setPaintFocusBorder(false);
945 setFocusBorderAroundIcon(true);
949 myErrorRenderer
= new ColoredTableCellRenderer() {
950 protected void customizeCellRenderer(JTable table
, Object value
, boolean selected
, boolean hasFocus
, int row
, int column
) {
951 setPaintFocusBorder(false);
955 myExpandIcon
=IconLoader
.getIcon("/com/intellij/uiDesigner/icons/expandNode.png");
956 myCollapseIcon
=IconLoader
.getIcon("/com/intellij/uiDesigner/icons/collapseNode.png");
957 for(int i
=0; i
<myIndentIcons
.length
; i
++) {
958 myIndentIcons
[i
] = new EmptyIcon(9 + 11 * i
, 9);
960 myIndentedExpandIcon
= new IndentedIcon(myExpandIcon
, 11);
961 myIndentedCollapseIcon
= new IndentedIcon(myCollapseIcon
, 11);
964 public Component
getTableCellRendererComponent(
966 @NotNull final Object value
,
967 final boolean selected
,
968 final boolean hasFocus
,
972 myPropertyNameRenderer
.getTableCellRendererComponent(table
,value
,selected
,hasFocus
,row
,column
);
974 column
=table
.convertColumnIndexToModel(column
);
975 final Property property
=(Property
)value
;
977 final Color background
;
978 final Property parent
= property
.getParent();
979 if (property
instanceof IntrospectedProperty
){
980 background
= table
.getBackground();
984 background
= parent
== null ? SYNTETIC_PROPERTY_BACKGROUND
: SYNTETIC_SUBPROPERTY_BACKGROUND
;
988 myPropertyNameRenderer
.setBackground(background
);
991 if(column
==0){ // painter for first column
992 SimpleTextAttributes attrs
= getTextAttributes(row
, property
);
993 myPropertyNameRenderer
.append(property
.getName(), attrs
);
996 if(getPropChildren(property
).length
>0) {
997 // This is composite property and we have to show +/- sign
998 if (parent
!= null) {
999 if(isPropertyExpanded(property
, parent
)){
1000 myPropertyNameRenderer
.setIcon(myIndentedCollapseIcon
);
1002 myPropertyNameRenderer
.setIcon(myIndentedExpandIcon
);
1006 if(isPropertyExpanded(property
, parent
)){
1007 myPropertyNameRenderer
.setIcon(myCollapseIcon
);
1009 myPropertyNameRenderer
.setIcon(myExpandIcon
);
1013 // If property doesn't have children then we have shift its text
1015 myPropertyNameRenderer
.setIcon(myIndentIcons
[getPropertyIndent(property
)]);
1018 else if(column
==1){ // painter for second column
1020 final PropertyRenderer renderer
=property
.getRenderer();
1021 //noinspection unchecked
1022 final JComponent component
= renderer
.getComponent(myEditor
.getRootContainer(), getSelectionValue(property
),
1023 selected
, hasFocus
);
1025 component
.setBackground(background
);
1027 if (isModifiedForSelection(property
)) {
1028 component
.setFont(table
.getFont().deriveFont(Font
.BOLD
));
1031 component
.setFont(table
.getFont());
1035 catch(Exception ex
) {
1037 myErrorRenderer
.clear();
1038 myErrorRenderer
.append(UIDesignerBundle
.message("error.getting.value", ex
.getMessage()), SimpleTextAttributes
.ERROR_ATTRIBUTES
);
1039 return myErrorRenderer
;
1043 throw new IllegalArgumentException("wrong column: "+column
);
1047 myPropertyNameRenderer
.setForeground(PropertyInspectorTable
.this.getForeground());
1048 if(property
instanceof IntrospectedProperty
){
1049 final RadComponent component
= mySelection
.get(0);
1050 final Class componentClass
= component
.getComponentClass();
1051 if (Properties
.getInstance().isExpertProperty(component
.getModule(), componentClass
, property
.getName())) {
1052 myPropertyNameRenderer
.setForeground(Color
.LIGHT_GRAY
);
1057 return myPropertyNameRenderer
;
1060 private SimpleTextAttributes
getTextAttributes(final int row
, final Property property
) {
1062 ErrorInfo errInfo
= getErrorInfoForRow(row
);
1064 SimpleTextAttributes result
;
1067 modified
= isModifiedForSelection(property
);
1069 catch(Exception ex
) {
1070 // ignore exceptions here - they'll be reported as red property values
1073 if (errInfo
== null) {
1074 result
= modified ? SimpleTextAttributes
.REGULAR_BOLD_ATTRIBUTES
: SimpleTextAttributes
.REGULAR_ATTRIBUTES
;
1077 final HighlightSeverity severity
= errInfo
.getHighlightDisplayLevel().getSeverity();
1078 Map
<HighlightSeverity
, SimpleTextAttributes
> cache
= modified ? myModifiedHighlightAttributes
: myHighlightAttributes
;
1079 result
= cache
.get(severity
);
1080 if (result
== null) {
1081 final TextAttributesKey attrKey
= SeverityRegistrar
.getInstance(myProject
).getHighlightInfoTypeBySeverity(severity
).getAttributesKey();
1082 TextAttributes textAttrs
= EditorColorsManager
.getInstance().getGlobalScheme().getAttributes(attrKey
);
1084 textAttrs
= textAttrs
.clone();
1085 textAttrs
.setFontType(textAttrs
.getFontType() | Font
.BOLD
);
1087 result
= SimpleTextAttributes
.fromTextAttributes(textAttrs
);
1088 cache
.put(severity
, result
);
1092 if (property
instanceof IntrospectedProperty
) {
1093 final RadComponent c
= mySelection
.get(0);
1094 if (Properties
.getInstance().isPropertyDeprecated(c
.getModule(), c
.getComponentClass(), property
.getName())) {
1095 return new SimpleTextAttributes(result
.getBgColor(), result
.getFgColor(), result
.getWaveColor(),
1096 result
.getStyle() | SimpleTextAttributes
.STYLE_STRIKEOUT
);
1105 * This is adapter from PropertyEditor to TableCellEditor interface
1107 private final class MyCellEditor
extends AbstractCellEditor
implements TableCellEditor
{
1108 private PropertyEditor myEditor
;
1110 public void setEditor(@NotNull final PropertyEditor editor
){
1114 public Object
getCellEditorValue(){
1116 return myEditor
.getValue();
1118 catch (Exception e
) {
1119 throw new RuntimeException(e
);
1123 public Component
getTableCellEditorComponent(final JTable table
, @NotNull final Object value
, final boolean isSelected
, final int row
, final int column
){
1124 final Property property
=(Property
)value
;
1126 //noinspection unchecked
1127 return myEditor
.getComponent(mySelection
.get(0), getSelectionValue(property
), null);
1129 catch(Exception ex
) {
1131 SimpleColoredComponent errComponent
= new SimpleColoredComponent();
1132 errComponent
.append(UIDesignerBundle
.message("error.getting.value", ex
.getMessage()), SimpleTextAttributes
.ERROR_ATTRIBUTES
);
1133 return errComponent
;
1139 * Expands/collapses rows
1141 private final class MyMouseListener
extends MouseAdapter
{
1142 public void mousePressed(final MouseEvent e
){
1143 final int row
= rowAtPoint(e
.getPoint());
1147 final Property property
= myProperties
.get(row
);
1148 int indent
= getPropertyIndent(property
) * 11;
1149 final Rectangle rect
= getCellRect(row
, convertColumnIndexToView(0), false);
1150 if (e
.getX() < rect
.x
+ indent
|| e
.getX() > rect
.x
+ 9 + indent
|| e
.getY() < rect
.y
|| e
.getY() > rect
.y
+ rect
.height
) {
1154 final Property
[] children
= getPropChildren(property
);
1155 if (children
.length
== 0) {
1159 if (isPropertyExpanded(property
, property
.getParent())) {
1160 collapseProperty(row
);
1163 expandProperty(row
);
1167 public void mouseClicked(MouseEvent e
) {
1168 if (e
.getClickCount() == 2 && e
.getButton() == MouseEvent
.BUTTON1
) {
1169 int row
= rowAtPoint(e
.getPoint());
1170 int column
= columnAtPoint(e
.getPoint());
1171 if (row
>= 0 && column
== 0) {
1172 final Property property
= myProperties
.get(row
);
1173 if (getPropChildren(property
).length
== 0) {
1182 * Reimplementation of LookAndFeel's SelectPreviousRowAction action.
1183 * Standard implementation isn't smart enough.
1185 * @see javax.swing.plaf.basic.BasicTableUI
1187 private final class MySelectPreviousRowAction
extends AbstractAction
{
1188 public void actionPerformed(final ActionEvent e
){
1189 final int rowCount
=getRowCount();
1190 LOG
.assertTrue(rowCount
>0);
1191 int selectedRow
=getSelectedRow();
1192 if(selectedRow
!=-1){
1195 selectedRow
=(selectedRow
+rowCount
)%rowCount
;
1198 getSelectionModel().setSelectionInterval(selectedRow
,selectedRow
);
1199 startEditing(selectedRow
);
1201 getSelectionModel().setSelectionInterval(selectedRow
,selectedRow
);
1207 * Reimplementation of LookAndFeel's SelectNextRowAction action.
1208 * Standard implementation isn't smart enough.
1210 * @see javax.swing.plaf.basic.BasicTableUI
1212 private final class MySelectNextRowAction
extends AbstractAction
{
1213 public void actionPerformed(final ActionEvent e
){
1214 final int rowCount
=getRowCount();
1215 LOG
.assertTrue(rowCount
>0);
1216 final int selectedRow
=(getSelectedRow()+1)%rowCount
;
1219 getSelectionModel().setSelectionInterval(selectedRow
,selectedRow
);
1220 startEditing(selectedRow
);
1222 getSelectionModel().setSelectionInterval(selectedRow
,selectedRow
);
1228 * Reimplementation of LookAndFeel's StartEditingAction action.
1229 * Standard implementation isn't smart enough.
1231 * @see javax.swing.plaf.basic.BasicTableUI
1233 private final class MyStartEditingAction
extends AbstractAction
{
1234 public void actionPerformed(final ActionEvent e
){
1235 final int selectedRow
=getSelectedRow();
1236 if(selectedRow
==-1 || isEditing()){
1240 startEditing(selectedRow
);
1245 * Expands property which has children or start editing atomic
1248 private final class MyEnterAction
extends AbstractAction
{
1249 public void actionPerformed(final ActionEvent e
){
1250 final int selectedRow
=getSelectedRow();
1251 if(isEditing() || selectedRow
==-1){
1255 final Property property
=myProperties
.get(selectedRow
);
1256 if(getPropChildren(property
).length
>0){
1257 if(isPropertyExpanded(property
, property
.getParent())){
1258 collapseProperty(selectedRow
);
1260 expandProperty(selectedRow
);
1263 startEditing(selectedRow
);
1268 private class MyExpandCurrentAction
extends AbstractAction
{
1269 private final boolean myExpand
;
1271 public MyExpandCurrentAction(final boolean expand
) {
1275 public void actionPerformed(ActionEvent e
) {
1276 final int selectedRow
=getSelectedRow();
1277 if(isEditing() || selectedRow
==-1){
1280 final Property property
=myProperties
.get(selectedRow
);
1281 if(getPropChildren(property
).length
>0) {
1283 if (!isPropertyExpanded(property
, property
.getParent())) {
1284 expandProperty(selectedRow
);
1288 if (isPropertyExpanded(property
, property
.getParent())) {
1289 collapseProperty(selectedRow
);
1297 * Updates UI of editors and renderers of all introspected properties
1299 private final class MyLafManagerListener
implements LafManagerListener
{
1301 * Recursively updates renderer and editor UIs of all synthetic
1304 private void updateUI(final Property property
){
1305 final PropertyRenderer renderer
= property
.getRenderer();
1306 renderer
.updateUI();
1307 final PropertyEditor editor
= property
.getEditor();
1311 final Property
[] children
= getPropChildren(property
);
1312 for (int i
= children
.length
- 1; i
>= 0; i
--) {
1313 final Property child
= children
[i
];
1314 if(!(child
instanceof IntrospectedProperty
)){
1320 public void lookAndFeelChanged(final LafManager source
) {
1321 updateUI(myBorderProperty
);
1322 updateUI(MarginProperty
.getInstance(myProject
));
1323 updateUI(HGapProperty
.getInstance(myProject
));
1324 updateUI(VGapProperty
.getInstance(myProject
));
1325 updateUI(HSizePolicyProperty
.getInstance(myProject
));
1326 updateUI(VSizePolicyProperty
.getInstance(myProject
));
1327 updateUI(HorzAlignProperty
.getInstance(myProject
));
1328 updateUI(VertAlignProperty
.getInstance(myProject
));
1329 updateUI(IndentProperty
.getInstance(myProject
));
1330 updateUI(UseParentLayoutProperty
.getInstance(myProject
));
1331 updateUI(MinimumSizeProperty
.getInstance(myProject
));
1332 updateUI(PreferredSizeProperty
.getInstance(myProject
));
1333 updateUI(MaximumSizeProperty
.getInstance(myProject
));
1334 updateUI(myButtonGroupProperty
);
1335 updateUI(myLayoutManagerProperty
);
1336 updateUI(SameSizeHorizontallyProperty
.getInstance(myProject
));
1337 updateUI(SameSizeVerticallyProperty
.getInstance(myProject
));
1338 updateUI(CustomCreateProperty
.getInstance(myProject
));
1339 updateUI(ClientPropertiesProperty
.getInstance(myProject
));