update copyright
[fedora-idea.git] / plugins / ui-designer / src / com / intellij / uiDesigner / propertyInspector / PropertyInspectorTable.java
blob869ce92433f30101b153950e318c7b17e4c5bb42
1 /*
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;
59 import javax.swing.*;
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;
65 import java.awt.*;
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;
71 import java.util.*;
72 import java.util.List;
74 /**
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;
90 /**
91 * This listener gets notifications from current property editor
93 private final MyPropertyEditorListener myPropertyEditorListener;
94 /**
95 * Updates UIs of synthetic properties
97 private final MyLafManagerListener myLafManagerListener;
98 /**
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) {
128 myProject = project;
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();
139 setModel(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
152 // Popup menu
153 PopupHandler.installPopupHandler(
154 this,
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) {
162 finishEditing();
163 myEditor = editor;
164 if (myEditor == null) {
165 mySelection.clear();
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.
175 @Nullable
176 public IntrospectedProperty getSelectedIntrospectedProperty(){
177 Property property = getSelectedProperty();
178 if (property == null || !(property instanceof IntrospectedProperty)) {
179 return null;
182 return (IntrospectedProperty)property;
185 @Nullable public Property getSelectedProperty() {
186 final int selectedRow = getSelectedRow();
187 if(selectedRow < 0 || selectedRow >= getRowCount()){
188 return null;
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)) {
204 return null;
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)){
214 return this;
216 else if(DataConstants.PSI_ELEMENT.equals(dataId)){
217 final IntrospectedProperty introspectedProperty = getSelectedIntrospectedProperty();
218 if(introspectedProperty == null){
219 return null;
221 final PsiClass aClass = getComponentClass();
222 if(aClass == null){
223 return null;
226 final PsiMethod getter = PropertyUtil.findPropertyGetter(aClass, introspectedProperty.getName(), false, true);
227 if(getter != null){
228 return getter;
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)) {
237 return myEditor;
239 else if (DataConstants.FILE_EDITOR.equals(dataId)) {
240 return UIDesignerToolWindowManager.getInstance(myProject).getActiveFormFileEditor();
242 else if (DataConstants.HELP_ID.equals(dataId)) {
243 return ourHelpID;
245 else {
246 return null;
251 * Sets whenther "expert" properties are shown or not
253 void setShowExpertProperties(final boolean showExpertProperties){
254 if(myShowExpertProperties == showExpertProperties){
255 return;
257 myShowExpertProperties = showExpertProperties;
258 if (myEditor != null) {
259 synchWithTree(true);
263 public void addNotify() {
264 super.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){
278 super.setUI(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()) {
315 synchWithTree(true);
317 repaint();
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){
331 if (myInsideSynch) {
332 return;
334 myInsideSynch = true;
335 try {
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)) {
341 anyChanges = true;
342 break;
345 if (!anyChanges) return;
348 mySelection.clear();
349 Collections.addAll(mySelection, newSelection);
351 if (isEditing()){
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());
378 if(index!=-1){
379 indexToSelect=index;
380 }else{
381 break;
383 }else{
384 indexToSelect=index;
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);
396 finally {
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);
420 continue;
422 final Property otherProp = otherProperties.get(otherPropIndex);
423 if (!otherProp.getClass().equals(prop.getClass())) {
424 myProperties.remove(propIndex);
425 continue;
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);
431 continue;
433 for(int childIndex=0; childIndex<children.length; childIndex++) {
434 if (!Comparing.equal(children [childIndex].getName(), otherChildren [childIndex].getName())) {
435 myProperties.remove(propIndex);
436 break;
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)){
452 return i;
455 return -1;
459 * Populates result list with the properties available for the specified
460 * component
462 private void collectProperties(final RadComponent component, final ArrayList<Property> result) {
463 if (component instanceof RadRootContainer){
464 addProperty(result, myClassToBindProperty);
466 else {
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)) {
506 continue;
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;
551 return 0;
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);
563 return myCellEditor;
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);
578 repaint(cellRect);
579 return result;
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();
589 if(editor==null){
590 return;
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(){
605 if(editingRow==-1){
606 return;
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);
619 try {
620 final Object value = editor.getValue();
621 setValueAt(value, editingRow, editingColumn);
623 catch (final Exception exc) {
624 showInvalidInput(exc);
626 finally {
627 removeEditor();
628 myStoppingEditing = false;
632 private static void showInvalidInput(final Exception exc) {
633 final Throwable cause = exc.getCause();
634 String message;
635 if(cause != null){
636 message = cause.getMessage();
638 else{
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();
655 // Expand property
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
670 if(selectedRow!=-1){
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();
682 // Expand property
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
694 if(selectedRow!=-1){
695 getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
699 @Nullable
700 ErrorInfo getErrorInfoForRow(final int row) {
701 LOG.assertTrue(row < myProperties.size());
702 if (mySelection.size() != 1) {
703 return null;
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);
714 else {
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())) {
720 errorInfo = err;
721 break;
726 return errorInfo;
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>.
733 @Nullable
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());
742 if(row == -1){
743 return null;
745 return getErrorForRow(row);
748 private Object getSelectionValue(final Property property) {
749 if (mySelection.size() == 0) {
750 return null;
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));
761 break;
765 else {
766 //noinspection unchecked
767 otherValue = property.getValue(mySelection.get(i));
769 if (!Comparing.equal(result, otherValue)) {
770 return null;
773 return result;
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;
787 break;
791 else {
792 if (!setPropValue(property, mySelection.get(i), newValue)) return false;
795 return true;
798 private static boolean setPropValue(final Property property, final RadComponent c, final Object newValue) {
799 try {
800 //noinspection unchecked
801 property.setValue(c, newValue);
803 catch (Throwable e) {
804 LOG.debug(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());
809 return false;
811 return true;
814 public boolean isModifiedForSelection(final Property property) {
815 for(RadComponent c: mySelection) {
816 //noinspection unchecked
817 if (property.isModified(c)) {
818 return true;
821 return false;
825 * Adapter to TableModel
827 private final class MyModel extends AbstractTableModel {
828 private final String[] myColumnNames;
830 public MyModel(){
831 myColumnNames=new String[]{
832 UIDesignerBundle.message("column.property"),
833 UIDesignerBundle.message("column.value")};
836 public int getColumnCount(){
837 return 2;
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){
857 if (column != 1){
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()) {
872 return false;
874 final Ref<Boolean> result = new Ref<Boolean>(Boolean.FALSE);
875 CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
876 public void run() {
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) {
886 synchWithTree(true);
888 return retVal;
892 private final class MyPropertyEditorListener extends PropertyEditorAdapter{
893 public void valueCommitted(final PropertyEditor source, final boolean continueEditing, final boolean closeEditorOnError){
894 if(isEditing()){
895 final Object value;
896 final TableCellEditor tableCellEditor = cellEditor;
897 try {
898 value = tableCellEditor.getCellEditorValue();
900 catch (final Exception exc) {
901 showInvalidInput(exc);
902 return;
904 boolean valueAccepted = myModel.setValueAtRow(editingRow, value);
905 if (valueAccepted) {
906 if (!continueEditing) tableCellEditor.stopCellEditing();
908 else {
909 if (closeEditorOnError) tableCellEditor.cancelCellEditing();
914 public void editingCanceled(final PropertyEditor source) {
915 if(isEditing()){
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(
936 final JTable table,
937 final Object value,
938 final boolean selected,
939 final boolean hasFocus,
940 final int row,
941 final int column
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(
965 final JTable table,
966 @NotNull final Object value,
967 final boolean selected,
968 final boolean hasFocus,
969 final int row,
970 int column
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();
982 else {
983 // syntetic property
984 background = parent == null ? SYNTETIC_PROPERTY_BACKGROUND : SYNTETIC_SUBPROPERTY_BACKGROUND;
987 if (!selected){
988 myPropertyNameRenderer.setBackground(background);
991 if(column==0){ // painter for first column
992 SimpleTextAttributes attrs = getTextAttributes(row, property);
993 myPropertyNameRenderer.append(property.getName(), attrs);
995 // 2. Icon
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);
1001 }else{
1002 myPropertyNameRenderer.setIcon(myIndentedExpandIcon);
1005 else {
1006 if(isPropertyExpanded(property, parent)){
1007 myPropertyNameRenderer.setIcon(myCollapseIcon);
1008 }else{
1009 myPropertyNameRenderer.setIcon(myExpandIcon);
1012 }else{
1013 // If property doesn't have children then we have shift its text
1014 // to the right
1015 myPropertyNameRenderer.setIcon(myIndentIcons [getPropertyIndent(property)]);
1018 else if(column==1){ // painter for second column
1019 try {
1020 final PropertyRenderer renderer=property.getRenderer();
1021 //noinspection unchecked
1022 final JComponent component = renderer.getComponent(myEditor.getRootContainer(), getSelectionValue(property),
1023 selected, hasFocus);
1024 if (!selected) {
1025 component.setBackground(background);
1027 if (isModifiedForSelection(property)) {
1028 component.setFont(table.getFont().deriveFont(Font.BOLD));
1030 else {
1031 component.setFont(table.getFont());
1033 return component;
1035 catch(Exception ex) {
1036 LOG.debug(ex);
1037 myErrorRenderer.clear();
1038 myErrorRenderer.append(UIDesignerBundle.message("error.getting.value", ex.getMessage()), SimpleTextAttributes.ERROR_ATTRIBUTES);
1039 return myErrorRenderer;
1042 else{
1043 throw new IllegalArgumentException("wrong column: "+column);
1046 if (!selected) {
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) {
1061 // 1. Text
1062 ErrorInfo errInfo = getErrorInfoForRow(row);
1064 SimpleTextAttributes result;
1065 boolean modified;
1066 try {
1067 modified = isModifiedForSelection(property);
1069 catch(Exception ex) {
1070 // ignore exceptions here - they'll be reported as red property values
1071 modified = false;
1073 if (errInfo == null) {
1074 result = modified ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES;
1076 else {
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);
1083 if (modified) {
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);
1100 return result;
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){
1111 myEditor = editor;
1114 public Object getCellEditorValue(){
1115 try {
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;
1125 try {
1126 //noinspection unchecked
1127 return myEditor.getComponent(mySelection.get(0), getSelectionValue(property), null);
1129 catch(Exception ex) {
1130 LOG.debug(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());
1144 if (row == -1){
1145 return;
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) {
1151 return;
1154 final Property[] children = getPropChildren(property);
1155 if (children.length == 0) {
1156 return;
1159 if (isPropertyExpanded(property, property.getParent())) {
1160 collapseProperty(row);
1162 else {
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) {
1174 startEditing(row);
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){
1193 selectedRow -= 1;
1195 selectedRow=(selectedRow+rowCount)%rowCount;
1196 if(isEditing()){
1197 finishEditing();
1198 getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
1199 startEditing(selectedRow);
1200 } else {
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;
1217 if(isEditing()){
1218 finishEditing();
1219 getSelectionModel().setSelectionInterval(selectedRow,selectedRow);
1220 startEditing(selectedRow);
1221 }else{
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()){
1237 return;
1240 startEditing(selectedRow);
1245 * Expands property which has children or start editing atomic
1246 * property.
1248 private final class MyEnterAction extends AbstractAction{
1249 public void actionPerformed(final ActionEvent e){
1250 final int selectedRow=getSelectedRow();
1251 if(isEditing() || selectedRow==-1){
1252 return;
1255 final Property property=myProperties.get(selectedRow);
1256 if(getPropChildren(property).length>0){
1257 if(isPropertyExpanded(property, property.getParent())){
1258 collapseProperty(selectedRow);
1259 }else{
1260 expandProperty(selectedRow);
1262 }else{
1263 startEditing(selectedRow);
1268 private class MyExpandCurrentAction extends AbstractAction {
1269 private final boolean myExpand;
1271 public MyExpandCurrentAction(final boolean expand) {
1272 myExpand = expand;
1275 public void actionPerformed(ActionEvent e) {
1276 final int selectedRow=getSelectedRow();
1277 if(isEditing() || selectedRow==-1){
1278 return;
1280 final Property property=myProperties.get(selectedRow);
1281 if(getPropChildren(property).length>0) {
1282 if (myExpand) {
1283 if (!isPropertyExpanded(property, property.getParent())) {
1284 expandProperty(selectedRow);
1287 else {
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
1302 * properties.
1304 private void updateUI(final Property property){
1305 final PropertyRenderer renderer = property.getRenderer();
1306 renderer.updateUI();
1307 final PropertyEditor editor = property.getEditor();
1308 if(editor != null){
1309 editor.updateUI();
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)){
1315 updateUI(child);
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));