summaryrefslogtreecommitdiff
path: root/designer
diff options
context:
space:
mode:
authorSiva Velusamy <vsiva@google.com>2015-06-04 12:23:03 -0700
committerDeepanshu Gupta <deepanshu@google.com>2015-07-13 17:39:09 -0700
commit4727e0140e84369d0eb166268883be3eed5896e3 (patch)
tree86eecb6497d4a4b679a4a489e54ada5fa49b0608 /designer
parentd0cd897dbd0ab280da18870c3c1f79dcbb01f2c2 (diff)
downloadidea-4727e0140e84369d0eb166268883be3eed5896e3.tar.gz
property sheet: editing support and other renderer improvements
This CL includes a number of changes to the property sheet: Renderer improvements: - The renderer and editor for booleans now use a ThreeStateCheckbox instead of a checkbox to allow for 3 states (yes, no, default). The actual widget used will be tweaked after UX review. - The default renderer for resource references will now attempt to show an icon for color and drawable references. - Hovering over a cell will automatically show a browse button that can be clicked to bring up the resource chooser dialog. Editors: - Includes editing support for: - booleans (using a ThreeStateCheckbox) - enums (using a JComboBox) - and other reference properties (just an EditorTextField) - Flag properties are missing, and auto complete within the text fields is also missing Misc: - A "Show expert properties" toolbar button has been added. This doesn't do anything right now, but will be wired up to show just a filtered list of properties. This CL includes it just so that the toolbar has some action setup in it. Change-Id: I089ce7da5158b6565b695c536e24d6411a73b0c9 (cherry picked from commit 846e9f1f055ea958a489874704950ae186689762)
Diffstat (limited to 'designer')
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/model/NlComponent.java3
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesModel.java11
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java35
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java31
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/ShowExpertProperties.java51
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/editors/NlBooleanEditor.java101
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/editors/NlEnumEditor.java102
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/editors/NlPropertyEditors.java79
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/editors/NlReferenceEditor.java136
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java66
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java12
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java11
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/ptable/renderers/PNameRenderer.java8
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/renderer/NlAttributeRenderer.java90
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java92
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/renderer/NlDefaultRenderer.java164
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderer.java42
-rw-r--r--designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderers.java13
-rw-r--r--designer/testSrc/com/android/tools/idea/uibuilder/property/renderer/NlDefaultRendererTest.java (renamed from designer/testSrc/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRendererTest.java)14
19 files changed, 985 insertions, 76 deletions
diff --git a/designer/src/com/android/tools/idea/uibuilder/model/NlComponent.java b/designer/src/com/android/tools/idea/uibuilder/model/NlComponent.java
index da8ce5e8763..7c563ae90a0 100644
--- a/designer/src/com/android/tools/idea/uibuilder/model/NlComponent.java
+++ b/designer/src/com/android/tools/idea/uibuilder/model/NlComponent.java
@@ -445,6 +445,9 @@ public class NlComponent {
public void setAttribute(@Nullable String namespace, @NonNull String attribute, @Nullable String value) {
// Handle validity
myTag.setAttribute(attribute, namespace, value);
+ if (snapshot != null) {
+ snapshot.setAttribute(attribute, namespace, null, value);
+ }
}
@Nullable
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesModel.java b/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesModel.java
index 304145ddf4d..65b0dfd0655 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesModel.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesModel.java
@@ -30,6 +30,7 @@ import java.util.List;
public class NlPropertiesModel extends PTableModel {
@Nullable private NlComponent myComponent;
+ private boolean myShowingExpertProperties;
public void update(@NotNull Iterable<NlComponent> selection, @Nullable final Runnable postUpdateRunnable) {
// TODO: handle multiple selections: show properties common to all selections
@@ -43,8 +44,6 @@ public class NlPropertiesModel extends PTableModel {
return;
}
- final String tagName = first.getTagName();
-
// Obtaining the properties, especially the first time around on a big project
// can take close to a second, so we do it on a separate thread..
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@@ -66,4 +65,12 @@ public class NlPropertiesModel extends PTableModel {
}
});
}
+
+ public boolean isShowingExpertProperties() {
+ return myShowingExpertProperties;
+ }
+
+ public void setShowExpertProperties(boolean en) {
+ myShowingExpertProperties = en;
+ }
} \ No newline at end of file
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java b/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java
index f0227e47ed5..67ddcaa0eb5 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/NlPropertiesPanel.java
@@ -15,11 +15,18 @@
*/
package com.android.tools.idea.uibuilder.property;
+import com.android.tools.idea.uibuilder.model.NlComponent;
import com.android.tools.idea.uibuilder.property.ptable.PTable;
import com.android.tools.idea.uibuilder.surface.ScreenView;
+import com.google.common.collect.Iterables;
+import com.intellij.openapi.actionSystem.ActionPlaces;
+import com.intellij.openapi.actionSystem.ActionToolbar;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.actionSystem.impl.ActionButton;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.ui.components.JBPanel;
import com.intellij.ui.components.JBScrollPane;
-import com.intellij.ui.table.JBTable;
import com.intellij.util.Alarm;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.MergingUpdateQueue;
@@ -29,16 +36,14 @@ import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
-import javax.swing.table.TableCellRenderer;
-import javax.swing.table.TableColumn;
import java.awt.*;
-import java.util.Enumeration;
public class NlPropertiesPanel extends JPanel implements ChangeListener {
private final PTable myTable;
private final ScreenView myScreenView;
private final NlPropertiesModel myModel;
private MergingUpdateQueue myUpdateQueue;
+ private JBLabel mySelectedComponentLabel;
public NlPropertiesPanel(@NotNull ScreenView screenView) {
super(new BorderLayout());
@@ -54,11 +59,28 @@ public class NlPropertiesPanel extends JPanel implements ChangeListener {
myTable.getEmptyText().setText("No selected component");
+ JPanel headerPanel = createHeaderPanel();
+ add(headerPanel, BorderLayout.NORTH);
add(new JBScrollPane(myTable), BorderLayout.CENTER);
myScreenView.getSelectionModel().addListener(this);
}
+ @NotNull
+ private JPanel createHeaderPanel() {
+ JBPanel panel = new JBPanel(new BorderLayout());
+
+ mySelectedComponentLabel = new JBLabel("");
+ panel.add(mySelectedComponentLabel, BorderLayout.CENTER);
+
+ ShowExpertProperties showExpertAction = new ShowExpertProperties(myModel);
+ ActionButton showExpertButton = new ActionButton(showExpertAction, showExpertAction.getTemplatePresentation(), ActionPlaces.UNKNOWN,
+ ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE);
+ panel.add(showExpertButton, BorderLayout.LINE_END);
+
+ return panel;
+ }
+
@Override
public void stateChanged(ChangeEvent e) {
myTable.setPaintBusy(true);
@@ -68,6 +90,11 @@ public class NlPropertiesPanel extends JPanel implements ChangeListener {
myModel.update(myScreenView.getSelectionModel().getSelection(), new Runnable() {
@Override
public void run() {
+ // TODO: handle multiple selections
+ final NlComponent first = Iterables.getFirst(myScreenView.getSelectionModel().getSelection(), null);
+ if (first != null) {
+ mySelectedComponentLabel.setText(first.getTagName());
+ }
myTable.setPaintBusy(false);
}
});
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java b/designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java
index 7a55529e65f..5bfe7d6974f 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/NlProperty.java
@@ -18,16 +18,19 @@ package com.android.tools.idea.uibuilder.property;
import com.android.SdkConstants;
import com.android.tools.idea.uibuilder.model.NlComponent;
import com.android.tools.idea.uibuilder.property.ptable.PTableItem;
+import com.android.tools.idea.uibuilder.property.editors.NlPropertyEditors;
import com.android.tools.idea.uibuilder.property.renderer.NlPropertyRenderers;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.xml.NamespaceAwareXmlAttributeDescriptor;
import com.intellij.xml.XmlAttributeDescriptor;
import org.jetbrains.android.dom.attrs.AttributeDefinition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.util.Collections;
import java.util.List;
@@ -63,6 +66,11 @@ public class NlProperty extends PTableItem {
myDefinition = attributeDefinition;
}
+ @NotNull
+ public NlComponent getComponent() {
+ return myComponent;
+ }
+
@Override
@NotNull
public String getName() {
@@ -75,6 +83,19 @@ public class NlProperty extends PTableItem {
return myComponent.getAttribute(myNamespace, myName);
}
+ @Override
+ public void setValue(Object value) {
+ assert ApplicationManager.getApplication().isDispatchThread();
+ final String attrValue = value == null ? null : value.toString();
+ String msg = String.format("Set %1$s.%2$s to %3$s", myComponent.getTagName(), myName, attrValue);
+ new WriteCommandAction.Simple(myComponent.getModel().getProject(), msg, myComponent.getTag().getContainingFile()) {
+ @Override
+ protected void run() throws Throwable {
+ myComponent.setAttribute(myNamespace, myName, attrValue);
+ }
+ }.execute();
+ }
+
@NotNull
public List<String> getParentStylables() {
return myDefinition == null ? Collections.<String>emptyList() : myDefinition.getParentStyleables();
@@ -92,6 +113,16 @@ public class NlProperty extends PTableItem {
}
@Override
+ public boolean isEditable(int col) {
+ return NlPropertyEditors.get(this) != null;
+ }
+
+ @Override
+ public TableCellEditor getCellEditor() {
+ return NlPropertyEditors.get(this);
+ }
+
+ @Override
public String toString() {
return Objects.toStringHelper(this)
.add("name", myName)
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ShowExpertProperties.java b/designer/src/com/android/tools/idea/uibuilder/property/ShowExpertProperties.java
new file mode 100644
index 00000000000..fdc33567406
--- /dev/null
+++ b/designer/src/com/android/tools/idea/uibuilder/property/ShowExpertProperties.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.uibuilder.property;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.actionSystem.ToggleAction;
+import org.jetbrains.annotations.NotNull;
+
+public class ShowExpertProperties extends ToggleAction {
+ private final NlPropertiesModel myModel;
+
+ public ShowExpertProperties(@NotNull NlPropertiesModel model) {
+ Presentation presentation = getTemplatePresentation();
+ String text = "Show expert properties";
+ presentation.setText(text);
+ presentation.setDescription(text);
+ presentation.setIcon(AllIcons.General.Filter);
+
+ myModel = model;
+ }
+
+ @Override
+ public boolean isSelected(AnActionEvent e) {
+ return myModel.isShowingExpertProperties();
+ }
+
+ @Override
+ public void setSelected(AnActionEvent e, boolean state) {
+ myModel.setShowExpertProperties(state);
+ }
+
+ @Override
+ public boolean isDumbAware() {
+ return true;
+ }
+}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlBooleanEditor.java b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlBooleanEditor.java
new file mode 100644
index 00000000000..9303970fe13
--- /dev/null
+++ b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlBooleanEditor.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.uibuilder.property.editors;
+
+import com.android.tools.idea.uibuilder.property.NlProperty;
+import com.android.tools.idea.uibuilder.property.renderer.NlBooleanRenderer;
+import com.intellij.openapi.ui.FixedSizeButton;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.ui.UIBundle;
+import com.intellij.util.ui.AbstractTableCellEditor;
+import com.intellij.util.ui.ThreeStateCheckBox;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.android.uipreview.ChooseResourceDialog;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class NlBooleanEditor extends AbstractTableCellEditor implements ActionListener {
+ private final JPanel myPanel;
+ private final FixedSizeButton myBrowseButton;
+ private final ThreeStateCheckBox myCheckbox;
+
+ private NlProperty myProperty;
+ private Object myValue;
+
+ public NlBooleanEditor() {
+ myPanel = new JPanel(new BorderLayout(SystemInfo.isMac ? 0 : 2, 0));
+
+ myCheckbox = new ThreeStateCheckBox();
+ myPanel.add(myCheckbox, BorderLayout.LINE_START);
+
+ myBrowseButton = new FixedSizeButton(myCheckbox);
+ myBrowseButton.setToolTipText(UIBundle.message("component.with.browse.button.browse.button.tooltip.text"));
+ myPanel.add(myBrowseButton, BorderLayout.LINE_END);
+
+ myCheckbox.addActionListener(this);
+ myBrowseButton.addActionListener(this);
+ }
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+ assert value instanceof NlProperty;
+
+ myProperty = (NlProperty)value;
+
+ Color fg = UIUtil.getTableSelectionForeground();
+ Color bg = UIUtil.getTableSelectionBackground();
+
+ myPanel.setForeground(fg);
+ myPanel.setBackground(bg);
+
+ for (int i = 0; i < myPanel.getComponentCount(); i++) {
+ Component comp = myPanel.getComponent(i);
+ comp.setForeground(fg);
+ comp.setBackground(bg);
+ }
+
+ String propValue = myProperty.getValue();
+ myValue = propValue;
+ ThreeStateCheckBox.State state = NlBooleanRenderer.getState(propValue);
+ myCheckbox.setState(state == null ? ThreeStateCheckBox.State.NOT_SELECTED : state);
+
+ return myPanel;
+ }
+
+ @Override
+ public Object getCellEditorValue() {
+ return myValue;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == myCheckbox) {
+ myValue = NlBooleanRenderer.getBoolean(myCheckbox.getState());
+ stopCellEditing();
+ } else if (e.getSource() == myBrowseButton) {
+ ChooseResourceDialog dialog = NlReferenceEditor.showResourceChooser(myProperty);
+ if (dialog.showAndGet()) {
+ myValue = dialog.getResourceName();
+ stopCellEditing();
+ } else {
+ cancelCellEditing();
+ }
+ }
+ }
+}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlEnumEditor.java b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlEnumEditor.java
new file mode 100644
index 00000000000..1eae24c874e
--- /dev/null
+++ b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlEnumEditor.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.uibuilder.property.editors;
+
+import com.android.tools.idea.uibuilder.property.NlProperty;
+import com.intellij.openapi.ui.FixedSizeButton;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.UIBundle;
+import com.intellij.ui.components.JBCheckBox;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.ui.AbstractTableCellEditor;
+import org.jetbrains.android.dom.attrs.AttributeDefinition;
+import org.jetbrains.android.uipreview.ChooseResourceDialog;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class NlEnumEditor extends AbstractTableCellEditor implements ActionListener {
+ private static final String UNSET = "<unset>";
+
+ private final JPanel myPanel;
+ private final JComboBox myCombo;
+ private final FixedSizeButton myBrowseButton;
+
+ private NlProperty myProperty;
+ private Object myValue;
+
+ public NlEnumEditor() {
+ myPanel = new JPanel(new BorderLayout(SystemInfo.isMac ? 0 : 2, 0));
+
+ myCombo = new JComboBox();
+ myCombo.setEditable(true);
+ myPanel.add(myCombo, BorderLayout.CENTER);
+
+ myBrowseButton = new FixedSizeButton(new JBCheckBox());
+ myBrowseButton.setToolTipText(UIBundle.message("component.with.browse.button.browse.button.tooltip.text"));
+ myPanel.add(myBrowseButton, BorderLayout.LINE_END);
+
+ myCombo.addActionListener(this);
+ myBrowseButton.addActionListener(this);
+ }
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+ assert value instanceof NlProperty;
+ myProperty = (NlProperty)value;
+
+ String propValue = StringUtil.notNullize(myProperty.getValue());
+ myValue = propValue;
+
+ AttributeDefinition definition = myProperty.getDefinition();
+ String[] values = definition == null ? ArrayUtil.EMPTY_STRING_ARRAY : definition.getValues();
+
+ DefaultComboBoxModel model = new DefaultComboBoxModel(values);
+ model.insertElementAt(UNSET, 0);
+ if (model.getIndexOf(propValue) == -1) {
+ model.insertElementAt(propValue, 1);
+ }
+ model.setSelectedItem(propValue);
+ myCombo.setModel(model);
+
+ return myPanel;
+ }
+
+ @Override
+ public Object getCellEditorValue() {
+ return UNSET.equals(myValue) ? null : myValue;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == myBrowseButton) {
+ ChooseResourceDialog dialog = NlReferenceEditor.showResourceChooser(myProperty);
+ if (dialog.showAndGet()) {
+ myValue = dialog.getResourceName();
+ stopCellEditing();
+ } else {
+ cancelCellEditing();
+ }
+ }
+ else if (e.getSource() == myCombo) {
+ myValue = myCombo.getModel().getSelectedItem();
+ stopCellEditing();
+ }
+ }
+}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlPropertyEditors.java b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlPropertyEditors.java
new file mode 100644
index 00000000000..ff2c7918fdd
--- /dev/null
+++ b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlPropertyEditors.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.uibuilder.property.editors;
+
+import com.android.tools.idea.uibuilder.property.NlProperty;
+import com.android.tools.idea.uibuilder.property.renderer.NlBooleanRenderer;
+import org.jetbrains.android.dom.attrs.AttributeDefinition;
+import org.jetbrains.android.dom.attrs.AttributeFormat;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.table.TableCellEditor;
+import java.util.Set;
+
+public class NlPropertyEditors {
+ private static NlBooleanEditor ourBooleanEditor;
+ private static NlEnumEditor ourComboEditor;
+ private static NlReferenceEditor ourDefaultEditor;
+
+ public static TableCellEditor get(@NotNull NlProperty property) {
+ AttributeDefinition definition = property.getDefinition();
+ if (definition == null) {
+ // TODO: default to text editor
+ return null;
+ }
+
+ Set<AttributeFormat> formats = definition.getFormats();
+ if (formats.isEmpty()) {
+ // either we don't know the format or we support multiple formats
+ // TODO: default to text editor
+ return null;
+ }
+
+ // TODO: there can be more than one format, we need to make this more customizable
+ if (formats.contains(AttributeFormat.Boolean)) {
+ return getBooleanEditor();
+ } else if (formats.contains(AttributeFormat.Enum)) {
+ return getComboEditor();
+ }
+
+ return getDefaultEditor();
+ }
+
+ private static TableCellEditor getBooleanEditor() {
+ if (ourBooleanEditor == null) {
+ ourBooleanEditor = new NlBooleanEditor();
+ }
+
+ return ourBooleanEditor;
+ }
+
+ private static TableCellEditor getComboEditor() {
+ if (ourComboEditor == null) {
+ ourComboEditor = new NlEnumEditor();
+ }
+
+ return ourComboEditor;
+ }
+
+ private static TableCellEditor getDefaultEditor() {
+ if (ourDefaultEditor == null) {
+ ourDefaultEditor = new NlReferenceEditor();
+ }
+
+ return ourDefaultEditor;
+ }
+}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/editors/NlReferenceEditor.java b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlReferenceEditor.java
new file mode 100644
index 00000000000..a7e5405cf67
--- /dev/null
+++ b/designer/src/com/android/tools/idea/uibuilder/property/editors/NlReferenceEditor.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.uibuilder.property.editors;
+
+import com.android.resources.ResourceType;
+import com.android.tools.idea.uibuilder.property.NlProperty;
+import com.android.tools.idea.uibuilder.property.renderer.NlDefaultRenderer;
+import com.intellij.android.designer.propertyTable.editors.ResourceEditor;
+import com.intellij.openapi.editor.impl.EditorComponentImpl;
+import com.intellij.openapi.editor.impl.EditorImpl;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.ui.FixedSizeButton;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.EditorTextField;
+import com.intellij.ui.UIBundle;
+import com.intellij.ui.components.JBCheckBox;
+import com.intellij.ui.components.JBLabel;
+import com.intellij.util.ui.AbstractTableCellEditor;
+import org.jetbrains.android.dom.AndroidDomUtil;
+import org.jetbrains.android.dom.attrs.AttributeDefinition;
+import org.jetbrains.android.dom.attrs.AttributeFormat;
+import org.jetbrains.android.uipreview.ChooseResourceDialog;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.EnumSet;
+import java.util.Set;
+
+public class NlReferenceEditor extends AbstractTableCellEditor implements ActionListener {
+ private final JPanel myPanel;
+ private final JBLabel myLabel;
+ private final EditorTextField myEditorTextField;
+ private final FixedSizeButton myBrowseButton;
+
+ private NlProperty myProperty;
+ private String myValue;
+
+ public NlReferenceEditor() {
+ myPanel = new JPanel(new BorderLayout(SystemInfo.isMac ? 0 : 2, 0));
+
+ myLabel = new JBLabel();
+ myPanel.add(myLabel, BorderLayout.LINE_START);
+
+ myEditorTextField = new EditorTextField();
+ myPanel.add(myEditorTextField, BorderLayout.CENTER);
+
+ myBrowseButton = new FixedSizeButton(new JBCheckBox());
+ myBrowseButton.setToolTipText(UIBundle.message("component.with.browse.button.browse.button.tooltip.text"));
+ myPanel.add(myBrowseButton, BorderLayout.LINE_END);
+
+ myEditorTextField
+ .registerKeyboardAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+ selectTextOnFocusGain(myEditorTextField);
+
+ myBrowseButton.addActionListener(this);
+ }
+
+ public static void selectTextOnFocusGain(EditorTextField textField) {
+ textField.addFocusListener(new FocusAdapter() {
+ @Override
+ public void focusGained(FocusEvent focusEvent) {
+ Object source = focusEvent.getSource();
+ if (source instanceof EditorComponentImpl) {
+ EditorImpl editor = ((EditorComponentImpl)source).getEditor();
+ editor.getSelectionModel().setSelection(0, editor.getDocument().getTextLength());
+ }
+ }
+ });
+ }
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+ assert value instanceof NlProperty;
+ myProperty = (NlProperty)value;
+
+ Icon icon = NlDefaultRenderer.getIcon(myProperty);
+ myLabel.setIcon(icon);
+ myLabel.setVisible(icon != null);
+
+ String propValue = StringUtil.notNullize(myProperty.getValue());
+ myValue = propValue;
+ myEditorTextField.setText(propValue);
+
+ return myPanel;
+ }
+
+ @Override
+ public Object getCellEditorValue() {
+ return myValue;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == myBrowseButton) {
+ ChooseResourceDialog dialog = showResourceChooser(myProperty);
+ if (dialog.showAndGet()) {
+ myValue = dialog.getResourceName();
+ stopCellEditing();
+ } else {
+ cancelCellEditing();
+ }
+ }
+ else if (e.getSource() == myEditorTextField) {
+ myValue = myEditorTextField.getDocument().getText();
+ stopCellEditing();
+ }
+ }
+
+ public static ChooseResourceDialog showResourceChooser(@NotNull NlProperty p) {
+ Module m = p.getComponent().getModel().getModule();
+ AttributeDefinition definition = p.getDefinition();
+ Set<AttributeFormat> formats = definition != null ? definition.getFormats() : EnumSet.allOf(AttributeFormat.class);
+
+ // for some special known properties, we can narrow down the possible types (rather than the all encompassing reference type)
+ String type = definition != null ? AndroidDomUtil.SPECIAL_RESOURCE_TYPES.get(definition.getName()) : null;
+ ResourceType[] types = type == null ? ResourceEditor.convertTypes(formats) : new ResourceType[]{ResourceType.getEnum(type)};
+
+ return new ChooseResourceDialog(m, types, p.getValue(), p.getComponent().getTag());
+ }
+}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java
index d983251632e..41e27556fd3 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTable.java
@@ -21,6 +21,7 @@ import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
+import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.*;
@@ -31,11 +32,20 @@ public class PTable extends JBTable {
private final PNameRenderer myNameRenderer = new PNameRenderer();
private PTableModel myModel;
+ private int myMouseHoverRow;
+ private int myMouseHoverCol;
+
public PTable(@NotNull PTableModel model) {
super(model);
myModel = model;
- setMaxItemsForSizeCalculation(20);
+ // since the row heights are uniform, there is no need to look at more than a few items
+ setMaxItemsForSizeCalculation(5);
+
+ // When a label cannot be fully displayed, hovering over it results in a popup that extends beyond the
+ // cell bounds to show the full value. We don't need this feature as it'll end up covering parts of the
+ // cell we don't want covered.
+ setExpandableItemsEnabled(false);
setShowColumns(false);
setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
@@ -50,6 +60,10 @@ public class PTable extends JBTable {
setRowSelectionAllowed(true);
addMouseListener(new MouseTableListener());
+
+ HoverListener hoverListener = new HoverListener();
+ addMouseMotionListener(hoverListener);
+ addMouseListener(hoverListener);
}
@Override
@@ -68,6 +82,17 @@ public class PTable extends JBTable {
return value.getCellRenderer();
}
+ @Override
+ public TableCellEditor getCellEditor(int row, int column) {
+ PTableItem value = (PTableItem)getValueAt(row, column);
+ return value.getCellEditor();
+ }
+
+ public boolean isHover(int row, int col) {
+ return row == myMouseHoverRow && col == myMouseHoverCol;
+ }
+
+ // Expand/Collapse group items if necessary
private class MouseTableListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
@@ -97,4 +122,43 @@ public class PTable extends JBTable {
}
}
}
+
+ // Repaint cells on mouse hover
+ private class HoverListener extends MouseAdapter {
+ private int myPreviousHoverRow = -1;
+ private int myPreviousHoverCol = -1;
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ myMouseHoverRow = rowAtPoint(e.getPoint());
+ if (myMouseHoverRow >= 0) {
+ myMouseHoverCol = columnAtPoint(e.getPoint());
+ }
+
+ // remove hover from the previous cell
+ if (myPreviousHoverRow != -1 && (myPreviousHoverRow != myMouseHoverRow || myPreviousHoverCol != myMouseHoverCol)) {
+ repaint(getCellRect(myPreviousHoverRow, myPreviousHoverCol, true));
+ myPreviousHoverRow = -1;
+ }
+
+ if (myMouseHoverCol < 0) {
+ return;
+ }
+
+ // repaint cell that has the hover
+ repaint(getCellRect(myMouseHoverRow, myMouseHoverCol, true));
+
+ myPreviousHoverRow = myMouseHoverRow;
+ myPreviousHoverCol = myMouseHoverCol;
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (myMouseHoverRow != -1 && myMouseHoverCol != -1) {
+ Rectangle cellRect = getCellRect(myMouseHoverRow, 1, true);
+ myMouseHoverRow = myMouseHoverCol = -1;
+ repaint(cellRect);
+ }
+ }
+ }
}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java
index e339a3e8d30..627a3639bde 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableItem.java
@@ -19,6 +19,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import java.util.Collections;
import java.util.List;
@@ -61,7 +62,18 @@ public abstract class PTableItem {
public abstract String getName();
+ public void setValue(Object value) {
+ }
+
public String getTooltipText() {
return null;
}
+
+ public TableCellEditor getCellEditor() {
+ return null;
+ }
+
+ public boolean isEditable(int col) {
+ return false;
+ }
}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java
index 1b231309216..5cd9f280dcf 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/ptable/PTableModel.java
@@ -48,6 +48,17 @@ public class PTableModel extends AbstractTableModel {
return myItems.get(row);
}
+ @Override
+ public void setValueAt(Object value, int row, int col) {
+ myItems.get(row).setValue(value);
+ fireTableCellUpdated(row, col);
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int col) {
+ return col == 1 && myItems.get(row).isEditable(col);
+ }
+
// TODO: if we want to support multiple levels of hierarchy, then this should
// be updated to collapse children recursively
public void collapse(int row) {
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/ptable/renderers/PNameRenderer.java b/designer/src/com/android/tools/idea/uibuilder/property/ptable/renderers/PNameRenderer.java
index c4d0b713259..e546ecae99c 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/ptable/renderers/PNameRenderer.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/ptable/renderers/PNameRenderer.java
@@ -15,9 +15,7 @@
*/
package com.android.tools.idea.uibuilder.property.ptable.renderers;
-import com.android.annotations.VisibleForTesting;
import com.android.tools.idea.uibuilder.property.ptable.PTableItem;
-import com.intellij.openapi.util.Couple;
import com.intellij.ui.ColoredTableCellRenderer;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
@@ -34,14 +32,10 @@ public class PNameRenderer implements TableCellRenderer {
myRenderer.clear();
PTableItem item = (PTableItem)value;
- Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
- boolean tableHasFocus = focusOwner != null && SwingUtilities.isDescendingFrom(focusOwner, table);
-
- Color background = table.getBackground();
Icon icon = UIUtil.getTreeNodeIcon(item.isExpanded(), isSelected, cellHasFocus);
myRenderer.getTableCellRendererComponent(table, value, isSelected, cellHasFocus, row, column);
- myRenderer.setBackground(isSelected ? UIUtil.getTreeSelectionBackground(tableHasFocus) : background);
+ myRenderer.setBackground(isSelected ? UIUtil.getTableSelectionBackground() : table.getBackground());
int beforeIcon = getBeforeIconSpacing(getDepth(item), icon.getIconWidth());
int afterIcon = getAfterIconSpacing(icon.getIconWidth());
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlAttributeRenderer.java b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlAttributeRenderer.java
new file mode 100644
index 00000000000..f0547295801
--- /dev/null
+++ b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlAttributeRenderer.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.uibuilder.property.renderer;
+
+import com.android.tools.idea.uibuilder.property.NlProperty;
+import com.android.tools.idea.uibuilder.property.ptable.PTable;
+import com.intellij.openapi.ui.FixedSizeButton;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.ui.UIBundle;
+import com.intellij.ui.components.JBCheckBox;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.android.dom.attrs.AttributeFormat;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.table.TableCellRenderer;
+import java.awt.*;
+import java.util.Set;
+
+public abstract class NlAttributeRenderer implements NlPropertyRenderer, TableCellRenderer {
+ private final JPanel myPanel;
+ private final FixedSizeButton myBrowseButton;
+
+ public NlAttributeRenderer() {
+ myPanel = new JPanel(new BorderLayout(SystemInfo.isMac ? 0 : 2, 0));
+
+ myBrowseButton = new FixedSizeButton(new JBCheckBox());
+ myBrowseButton.setToolTipText(UIBundle.message("component.with.browse.button.browse.button.tooltip.text"));
+ myPanel.add(myBrowseButton, BorderLayout.LINE_END);
+ }
+
+ public JPanel getContentPanel() {
+ return myPanel;
+ }
+
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
+ assert value instanceof NlProperty;
+ assert table instanceof PTable;
+
+ Color fg, bg;
+ if (isSelected) {
+ fg = UIUtil.getTableSelectionForeground();
+ bg = UIUtil.getTableSelectionBackground();
+ }
+ else {
+ fg = UIUtil.getTableForeground();
+ bg = UIUtil.getTableBackground();
+ }
+
+ myPanel.setForeground(fg);
+ myPanel.setBackground(bg);
+
+ for (int i = 0; i < myPanel.getComponentCount(); i++) {
+ Component comp = myPanel.getComponent(i);
+ comp.setForeground(fg);
+ comp.setBackground(bg);
+ }
+
+ boolean hover = ((PTable)table).isHover(row, col);
+ myBrowseButton.setVisible(hover);
+
+ customizeRenderContent(table, (NlProperty)value, isSelected, hasFocus, row, col);
+
+ return myPanel;
+ }
+
+ public abstract void customizeRenderContent(@NotNull JTable table,
+ @NotNull NlProperty p,
+ boolean selected,
+ boolean hasFocus,
+ int row,
+ int col);
+
+ @Override
+ public abstract boolean canRender(@NotNull NlProperty p, @NotNull Set<AttributeFormat> formats);
+}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java
index 321dd4b38e3..d0c841c56d8 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlBooleanRenderer.java
@@ -15,29 +15,97 @@
*/
package com.android.tools.idea.uibuilder.property.renderer;
+import com.android.SdkConstants;
+import com.android.ide.common.resources.ResourceResolver;
import com.android.tools.idea.uibuilder.property.NlProperty;
+import com.intellij.ui.SimpleColoredComponent;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.ui.ThreeStateCheckBox;
import com.intellij.util.ui.UIUtil;
+import org.jetbrains.android.dom.attrs.AttributeFormat;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import javax.swing.*;
-import javax.swing.table.TableCellRenderer;
import java.awt.*;
+import java.util.Set;
+
+public class NlBooleanRenderer extends NlAttributeRenderer {
+ private final ThreeStateCheckBox myCheckbox;
+ private final SimpleColoredComponent myLabel;
+
+ public NlBooleanRenderer() {
+ JPanel panel = getContentPanel();
+
+ myCheckbox = new ThreeStateCheckBox();
+ panel.add(myCheckbox, BorderLayout.LINE_START);
+
+ myLabel = new SimpleColoredComponent();
+ panel.add(myLabel, BorderLayout.CENTER);
+ }
-public class NlBooleanRenderer extends JCheckBox implements TableCellRenderer {
@Override
- public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
- assert value instanceof NlProperty;
- NlProperty p = (NlProperty)value;
+ public void customizeRenderContent(@NotNull JTable table, @NotNull NlProperty p, boolean selected, boolean hasFocus, int row, int col) {
+ myCheckbox.setEnabled(true);
+ myLabel.clear();
+
+ String propValue = p.getValue();
+ ThreeStateCheckBox.State state = getState(propValue);
+ if (state == null && propValue != null) {
+ myLabel.append(propValue, modifyAttributes(selected, SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES));
+
+ // TODO: what happens if this is configuration dependent? (in theory, those should be edited in the theme editor)
+ ResourceResolver resourceResolver = p.getComponent().getModel().getConfiguration().getResourceResolver();
+ if (resourceResolver != null) {
+ String resolvedValue = resourceResolver.findResValue(propValue, false).getValue();
+ state = getState(resolvedValue);
+ }
+ }
+
+ if (state != null) {
+ myCheckbox.setEnabled(true);
+ myCheckbox.setState(state);
+ } else {
+ myCheckbox.setEnabled(false);
+ }
+ }
+
+ @NotNull
+ private static SimpleTextAttributes modifyAttributes(boolean selected, SimpleTextAttributes attributes) {
+ return selected ? new SimpleTextAttributes(attributes.getStyle(), UIUtil.getTableSelectionForeground()) : attributes;
+ }
- if (isSelected) {
- setForeground(UIUtil.getTableSelectionForeground());
- setBackground(UIUtil.getTableSelectionBackground());
+ @Nullable
+ public static ThreeStateCheckBox.State getState(@Nullable String s) {
+ if (s == null) {
+ return ThreeStateCheckBox.State.DONT_CARE;
+ }
+ else if (SdkConstants.VALUE_TRUE.equalsIgnoreCase(s)) {
+ return ThreeStateCheckBox.State.SELECTED;
+ }
+ else if (SdkConstants.VALUE_FALSE.equalsIgnoreCase(s)) {
+ return ThreeStateCheckBox.State.NOT_SELECTED;
}
else {
- setForeground(UIUtil.getTableForeground());
- setBackground(UIUtil.getTableBackground());
+ return null;
}
+ }
- setSelected(Boolean.valueOf(p.getValue()));
- return this;
+ @Nullable
+ public static Boolean getBoolean(ThreeStateCheckBox.State state) {
+ switch (state) {
+ case DONT_CARE:
+ return null;
+ case SELECTED:
+ return Boolean.TRUE;
+ case NOT_SELECTED:
+ default:
+ return Boolean.FALSE;
+ }
+ }
+
+ @Override
+ public boolean canRender(@NotNull NlProperty p, @NotNull Set<AttributeFormat> formats) {
+ return formats.contains(AttributeFormat.Boolean);
}
}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlDefaultRenderer.java b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlDefaultRenderer.java
new file mode 100644
index 00000000000..93b56940743
--- /dev/null
+++ b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlDefaultRenderer.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tools.idea.uibuilder.property.renderer;
+
+import com.android.SdkConstants;
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.resources.ResourceResolver;
+import com.android.resources.ResourceType;
+import com.android.tools.idea.rendering.GutterIconCache;
+import com.android.tools.idea.rendering.ResourceHelper;
+import com.android.tools.idea.uibuilder.property.NlProperty;
+import com.intellij.ui.SimpleColoredComponent;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.ui.ColorIcon;
+import org.jetbrains.android.AndroidColorAnnotator;
+import org.jetbrains.android.dom.attrs.AttributeFormat;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.File;
+import java.util.Set;
+
+public class NlDefaultRenderer extends NlAttributeRenderer implements NlPropertyRenderer {
+ public static final int ICON_SIZE = 14;
+ private final SimpleColoredComponent myLabel;
+
+ public NlDefaultRenderer() {
+ JPanel panel = getContentPanel();
+
+ myLabel = new SimpleColoredComponent();
+ panel.add(myLabel, BorderLayout.CENTER);
+ }
+
+ @Override
+ public void customizeRenderContent(@NotNull JTable table, @NotNull NlProperty p, boolean selected, boolean hasFocus, int row, int col) {
+ myLabel.clear();
+ customize(p, col);
+ }
+
+
+ @VisibleForTesting
+ void customize(NlProperty property, int column) {
+ if (column == 0) {
+ appendName(property);
+ } else {
+ appendValue(property);
+ }
+ }
+
+ private void appendValue(@NotNull NlProperty property) {
+ String value = property.getValue();
+
+ Icon icon = getIcon(property);
+ if (icon != null) {
+ myLabel.setIcon(icon);
+ }
+
+ if (value == null) {
+ value = "";
+ }
+ myLabel.append(value, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ myLabel.setToolTipText(value);
+ }
+
+ @Nullable
+ public static Icon getIcon(@NotNull NlProperty property) {
+ String text = property.getValue();
+ if (text == null) {
+ return null;
+ }
+
+ if (isColorValue(text)) {
+ return getColorIcon(text);
+ }
+
+ ResourceResolver resolver = property.getComponent().getModel().getConfiguration().getResourceResolver();
+ if (resolver == null) {
+ return null;
+ }
+
+ if (text.startsWith(SdkConstants.COLOR_RESOURCE_PREFIX)
+ || text.startsWith(SdkConstants.ANDROID_COLOR_RESOURCE_PREFIX)) {
+ return getColorIcon(resolver, property, text);
+ }
+
+ if (text.startsWith(SdkConstants.DRAWABLE_PREFIX) ||
+ text.startsWith(SdkConstants.ANDROID_DRAWABLE_PREFIX) ||
+ text.startsWith(SdkConstants.MIPMAP_PREFIX)) {
+ return getDrawableIcon(resolver, property, text);
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static Icon getDrawableIcon(@NotNull ResourceResolver resolver, @NotNull NlProperty property, @NotNull String value) {
+ boolean isFrameworkRes = value.startsWith(SdkConstants.ANDROID_PREFIX);
+ ResourceType type = value.startsWith(SdkConstants.MIPMAP_PREFIX) ? ResourceType.MIPMAP : ResourceType.DRAWABLE;
+ ResourceValue drawable = resolver.resolveValue(type, property.getName(), value, isFrameworkRes);
+ if (drawable == null) {
+ return null;
+ }
+
+ File file = AndroidColorAnnotator.pickBestBitmap(ResourceHelper.resolveDrawable(resolver, drawable));
+ return file == null ? null : GutterIconCache.getInstance().getIcon(file.getPath());
+ }
+
+ @Nullable
+ private static Icon getColorIcon(@NotNull ResourceResolver resolver, @NotNull NlProperty property, @NotNull String value) {
+ boolean isFrameworkRes = value.startsWith(SdkConstants.ANDROID_COLOR_RESOURCE_PREFIX);
+ ResourceValue resourceValue = resolver.resolveValue(ResourceType.COLOR, property.getName(), value, isFrameworkRes);
+ if (resourceValue == null) {
+ return null;
+ }
+
+ String resolvedValue = resourceValue.getValue();
+ if (isColorValue(resolvedValue)) {
+ return getColorIcon(resolvedValue);
+ }
+
+ return null;
+ }
+
+ private static boolean isColorValue(@Nullable String value) {
+ return value != null && value.startsWith("#") && value.matches("#\\p{XDigit}+");
+ }
+
+ @Nullable
+ private static Icon getColorIcon(@NotNull String hexColor) {
+ Color color = ResourceHelper.parseColor(hexColor);
+ return color == null ? null : new ColorIcon(ICON_SIZE, color, true);
+ }
+
+ private void appendName(@NotNull NlProperty property) {
+ myLabel.append(property.getName());
+ myLabel.setToolTipText(property.getTooltipText());
+ }
+
+ @Override
+ public boolean canRender(@NotNull NlProperty p, @NotNull Set<AttributeFormat> formats) {
+ return true;
+ }
+
+ @VisibleForTesting
+ SimpleColoredComponent getLabel() {
+ return myLabel;
+ }
+}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderer.java b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderer.java
index 32aa55e17dc..56f3f4e505a 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderer.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderer.java
@@ -15,46 +15,12 @@
*/
package com.android.tools.idea.uibuilder.property.renderer;
-import com.android.annotations.VisibleForTesting;
import com.android.tools.idea.uibuilder.property.NlProperty;
-import com.intellij.ui.ColoredTableCellRenderer;
-import com.intellij.ui.SimpleTextAttributes;
+import org.jetbrains.android.dom.attrs.AttributeFormat;
import org.jetbrains.annotations.NotNull;
-import javax.swing.*;
+import java.util.Set;
-public class NlPropertyRenderer extends ColoredTableCellRenderer {
- @Override
- protected void customizeCellRenderer(JTable table, Object value, boolean selected, boolean hasFocus, int row, int column) {
- if (!(value instanceof NlProperty)) {
- return;
- }
-
- setPaintFocusBorder(false);
- setFocusBorderAroundIcon(true);
-
- customize((NlProperty)value, column);
- }
-
- @VisibleForTesting
- void customize(NlProperty property, int column) {
- if (column == 0) {
- appendName(property);
- } else {
- appendValue(property);
- }
- }
-
- private void appendValue(@NotNull NlProperty property) {
- String value = property.getValue();
- if (value == null) {
- value = "";
- }
- append(value, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
- }
-
- private void appendName(@NotNull NlProperty property) {
- append(property.getName());
- setToolTipText(property.getTooltipText());
- }
+public interface NlPropertyRenderer {
+ boolean canRender(@NotNull NlProperty p, @NotNull Set<AttributeFormat> formats);
}
diff --git a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderers.java b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderers.java
index 9e98312b762..f722ed88b68 100644
--- a/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderers.java
+++ b/designer/src/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRenderers.java
@@ -24,7 +24,7 @@ import javax.swing.table.TableCellRenderer;
import java.util.Set;
public class NlPropertyRenderers {
- private static NlPropertyRenderer ourDefaultRenderer;
+ private static NlDefaultRenderer ourDefaultRenderer;
private static NlBooleanRenderer ourBooleanRenderer;
@NotNull
@@ -36,14 +36,17 @@ public class NlPropertyRenderers {
Set<AttributeFormat> formats = definition.getFormats();
if (formats.size() == 1 && formats.contains(AttributeFormat.Boolean)) {
- return getBooleanRenderer();
+ NlBooleanRenderer renderer = getBooleanRenderer();
+ if (renderer.canRender(p, formats)) {
+ return renderer;
+ }
}
return getDefaultRenderer();
}
@NotNull
- private static TableCellRenderer getBooleanRenderer() {
+ private static NlBooleanRenderer getBooleanRenderer() {
if (ourBooleanRenderer == null) {
ourBooleanRenderer = new NlBooleanRenderer();
}
@@ -52,9 +55,9 @@ public class NlPropertyRenderers {
}
@NotNull
- private static TableCellRenderer getDefaultRenderer() {
+ private static NlDefaultRenderer getDefaultRenderer() {
if (ourDefaultRenderer == null) {
- ourDefaultRenderer = new NlPropertyRenderer();
+ ourDefaultRenderer = new NlDefaultRenderer();
}
return ourDefaultRenderer;
diff --git a/designer/testSrc/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRendererTest.java b/designer/testSrc/com/android/tools/idea/uibuilder/property/renderer/NlDefaultRendererTest.java
index c01db776254..3ed1b93fd4b 100644
--- a/designer/testSrc/com/android/tools/idea/uibuilder/property/renderer/NlPropertyRendererTest.java
+++ b/designer/testSrc/com/android/tools/idea/uibuilder/property/renderer/NlDefaultRendererTest.java
@@ -27,7 +27,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.List;
-public class NlPropertyRendererTest extends LayoutTestCase {
+public class NlDefaultRendererTest extends LayoutTestCase {
public void testSimple() {
@Language("XML")
String source = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
@@ -41,7 +41,7 @@ public class NlPropertyRendererTest extends LayoutTestCase {
List<NlProperty> properties = NlProperties.getInstance().getProperties(MockNlComponent.create(subTags[0]));
- NlPropertyRenderer renderer = new NlPropertyRenderer();
+ NlDefaultRenderer renderer = new NlDefaultRenderer();
NlProperty property = NlPropertiesTest.getPropertyByName(properties, "id");
validateRendering(renderer, property, "id", "@+id/textView");
@@ -53,16 +53,16 @@ public class NlPropertyRendererTest extends LayoutTestCase {
validateRendering(renderer, property, "focusable", "");
}
- private static void validateRendering(@NotNull NlPropertyRenderer renderer,
+ private static void validateRendering(@NotNull NlDefaultRenderer renderer,
@NotNull NlProperty property,
@NotNull String name,
@NotNull String value) {
- renderer.clear();
+ renderer.getLabel().clear();
renderer.customize(property, 0);
- assertEquals(name, renderer.getCharSequence(true));
+ assertEquals(name, renderer.getLabel().getCharSequence(true));
- renderer.clear();
+ renderer.getLabel().clear();
renderer.customize(property, 1);
- assertEquals(value, renderer.getCharSequence(true));
+ assertEquals(value, renderer.getLabel().getCharSequence(true));
}
}