summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java6
-rw-r--r--android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java31
-rw-r--r--android/src/com/android/tools/idea/editors/theme/attributes/AttributesGrouper.java122
-rw-r--r--android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java7
-rw-r--r--android/testSrc/com/android/tools/idea/editors/theme/attributes/AttributesGrouperTest.java154
5 files changed, 250 insertions, 70 deletions
diff --git a/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java b/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java
index e24965dc055..ef9bae30ed1 100644
--- a/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java
+++ b/android/guiTestSrc/com/android/tools/idea/tests/gui/theme/ThemeEditorTableTest.java
@@ -209,12 +209,12 @@ public class ThemeEditorTableTest extends GuiTestCase {
ThemeEditorFixture themeEditor = ThemeEditorTestUtils.openThemeEditor(projectFrame);
ThemeEditorTableFixture themeEditorTable = themeEditor.getPropertiesTable();
- TableCell cell = row(3).column(0);
+ TableCell cell = row(1).column(0);
Component colorRenderer = themeEditorTable.getRendererComponent(cell);
assertNotNull(colorRenderer);
ResourceComponentFixture resourceComponent = new ResourceComponentFixture(myRobot, (ResourceComponent)colorRenderer);
- assertEquals("colorBackground", resourceComponent.getAttributeName());
+ assertEquals("android:colorBackground", resourceComponent.getAttributeName());
assertEquals(Font.PLAIN, resourceComponent.getValueFont().getStyle());
assertEquals("@android:color/background_holo_light", resourceComponent.getValueString());
@@ -231,7 +231,7 @@ public class ThemeEditorTableTest extends GuiTestCase {
assertNotNull(colorRenderer);
resourceComponent = new ResourceComponentFixture(myRobot, (ResourceComponent)colorRenderer);
- assertEquals("colorBackground", resourceComponent.getAttributeName());
+ assertEquals("android:colorBackground", resourceComponent.getAttributeName());
assertEquals(ResourceHelper.colorToString(color), resourceComponent.getColorValue());
assertEquals(Font.BOLD, resourceComponent.getValueFont().getStyle());
assertEquals("@color/background_holo_light", resourceComponent.getValueString());
diff --git a/android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java b/android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java
index ae60ae82e44..053c3a6a6a2 100644
--- a/android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java
+++ b/android/src/com/android/tools/idea/editors/theme/ThemeEditorComponent.java
@@ -50,6 +50,7 @@ import com.android.tools.idea.rendering.ResourceNotificationManager.ResourceChan
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
@@ -84,6 +85,8 @@ import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowFilter;
+import javax.swing.RowSorter;
+import javax.swing.SortOrder;
import javax.swing.event.DocumentEvent;
import javax.swing.plaf.PanelUI;
import javax.swing.table.DefaultTableCellRenderer;
@@ -99,6 +102,7 @@ import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -113,6 +117,28 @@ public class ThemeEditorComponent extends Splitter {
private static final JBColor PREVIEW_BACKGROUND = new JBColor(new Color(0xFAFAFA), new Color(0x343739));
+ /**
+ * Comparator used for simple mode attribute sorting
+ */
+ private static final Comparator SIMPLE_MODE_COMPARATOR = new Comparator() {
+ @Override
+ public int compare(Object o1, Object o2) {
+ // The parent attribute goes always first
+ if (o1 instanceof String) {
+ return -1;
+ } else if (o2 instanceof String) {
+ return 1;
+ }
+
+ if (o1 instanceof EditedStyleItem && o2 instanceof EditedStyleItem) {
+ return ((EditedStyleItem)o1).compareTo((EditedStyleItem)o2);
+ }
+
+ // Fall-back for other comparisons
+ return Ordering.usingToString().compare(o1, o2);
+ }
+ };
+
public static final float HEADER_FONT_SCALE = 1.3f;
public static final int REGULAR_CELL_PADDING = 4;
public static final int LARGE_CELL_PADDING = 10;
@@ -499,10 +525,12 @@ public class ThemeEditorComponent extends Splitter {
if (myPanel.isAdvancedMode()) {
myAttributesFilter.setFilterEnabled(false);
myAttributesSorter.setRowFilter(myAttributesFilter);
+ myAttributesSorter.setSortKeys(null);
} else {
mySimpleModeFilter.configure(myModel.getDefinedAttributes(), ThemeEditorUtils.isAppCompatTheme(
myThemeEditorContext.getConfiguration()));
myAttributesSorter.setRowFilter(mySimpleModeFilter);
+ myAttributesSorter.setSortKeys(ImmutableList.of(new RowSorter.SortKey(0, SortOrder.ASCENDING)));
}
}
@@ -774,6 +802,9 @@ public class ThemeEditorComponent extends Splitter {
myAttributesTable.setRowSorter(null); // Clean any previous row sorters.
myAttributesSorter = new TableRowSorter<AttributesTableModel>(myModel);
+ // This is only used when the sort keys are set (only set in simple mode).
+ myAttributesSorter.setComparator(0, SIMPLE_MODE_COMPARATOR);
+
configureFilter();
myAttributesTable.setModel(myModel);
diff --git a/android/src/com/android/tools/idea/editors/theme/attributes/AttributesGrouper.java b/android/src/com/android/tools/idea/editors/theme/attributes/AttributesGrouper.java
index bcde2764173..7090222ab3a 100644
--- a/android/src/com/android/tools/idea/editors/theme/attributes/AttributesGrouper.java
+++ b/android/src/com/android/tools/idea/editors/theme/attributes/AttributesGrouper.java
@@ -16,9 +16,17 @@
package com.android.tools.idea.editors.theme.attributes;
import com.android.tools.idea.editors.theme.datamodels.EditedStyleItem;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.TreeMultimap;
import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
public class AttributesGrouper {
private AttributesGrouper() { }
@@ -28,7 +36,7 @@ public class AttributesGrouper {
TYPE("By Type");
final private String myText;
-
+
GroupBy(String text) {
myText = text;
}
@@ -43,7 +51,13 @@ public class AttributesGrouper {
* Helper data structure to hold information for (temporary) algorithm for splitting attributes
* to labelled group.
*/
- private static class Group {
+ private enum Group {
+ STYLES("Styles", ImmutableList.of("style", "theme")),
+ COLORS("Colors", ImmutableList.of("color")),
+ DRAWABLES("Drawables", ImmutableList.of("drawable")),
+ METRICS("Metrics", ImmutableList.of("size", "width", "height")),
+ OTHER("Everything Else", Collections.<String>emptyList());
+
/**
* Group name, as appears on properties panel
*/
@@ -54,88 +68,61 @@ public class AttributesGrouper {
*/
public final List<String> markers;
- public Group(String name, List<String> markers) {
+ Group(@NotNull String name, @NotNull List<String> markers) {
this.name = name;
this.markers = markers;
}
- public static Group of(String name, String... markers) {
- return new Group(name, Arrays.asList(markers));
+ private static Group getGroupFromName(String name) {
+ for (Group group : Group.values()) {
+ for (final String marker : group.markers) {
+ if (StringUtil.containsIgnoreCase(name, marker)) {
+ return group;
+ }
+ }
+ }
+ return OTHER;
}
}
- private static final List<Group> GROUPS = Arrays.asList(
- Group.of("Styles", "style", "theme"),
- Group.of("Colors", "color"),
- Group.of("Drawables", "drawable"),
- Group.of("Metrics", "size", "width", "height")
- );
- @SuppressWarnings("unchecked")
- public static List<TableLabel> generateLabelsForType(final List<EditedStyleItem> source, final List<EditedStyleItem> sink) {
+ @NotNull
+ private static List<TableLabel> generateLabelsForType(@NotNull final List<EditedStyleItem> source, @NotNull final List<EditedStyleItem> sink) {
+ final Multimap<Group, EditedStyleItem> classes = HashMultimap.create();
- final List<EditedStyleItem>[] classes = new List[GROUPS.size() + 1];
- final int otherGroupIndex = GROUPS.size();
-
- for (int i = 0; i < classes.length; i++) {
- classes[i] = new ArrayList<EditedStyleItem>();
- }
-
- outer:
for (final EditedStyleItem item : source) {
final String name = item.getName();
-
- for (int index = 0; index < GROUPS.size(); index++) {
- final Group group = GROUPS.get(index);
- for (final String marker : group.markers) {
- if (StringUtil.containsIgnoreCase(name, marker)) {
- classes[index].add(item);
- continue outer;
- }
- }
- }
-
- // haven't found any group, will put the item into "Other"
- classes[otherGroupIndex].add(item);
+ classes.put(Group.getGroupFromName(name), item);
}
final List<TableLabel> labels = new ArrayList<TableLabel>();
int offset = 0;
- for (int index = 0; index < GROUPS.size(); index++) {
- final Group group = GROUPS.get(index);
- final int size = classes[index].size();
-
- if (size != 0) {
+ for (Group group : Group.values()) {
+ Collection<EditedStyleItem> elements = classes.get(group);
+
+ boolean addHeader = !elements.isEmpty();
+ if (addHeader && group == Group.OTHER) {
+ // Adding "Everything else" label only in case when there are at least one other label,
+ // because having "Everything else" as the only label present looks quite silly
+ addHeader = offset != 0;
+ }
+ if (addHeader) {
labels.add(new TableLabel(group.name, offset));
}
- offset += size;
- }
-
- final int otherGroupSize = classes[otherGroupIndex].size();
- // Adding "Everything else" label only in case when there are at least one other label,
- // because having "Everything else" as the only label present looks quite silly
- if (otherGroupSize != 0 && labels.size() > 0) {
- labels.add(new TableLabel("Everything Else", offset));
- }
+ sink.addAll(elements);
- for (final List<EditedStyleItem> list : classes) {
- for (final EditedStyleItem item : list) {
- sink.add(item);
- }
+ offset += elements.size();
}
return labels;
}
- private static List<TableLabel> generateLabelsForGroup(final List<EditedStyleItem> source, final List<EditedStyleItem> sink) {
- Map<String, List<EditedStyleItem>> classes = new TreeMap<String, List<EditedStyleItem>>();
+ static List<TableLabel> generateLabelsForGroup(final List<EditedStyleItem> source, final List<EditedStyleItem> sink) {
+ TreeMultimap<String, EditedStyleItem> classes = TreeMultimap.create();
for (EditedStyleItem item : source){
String group = item.getAttrGroup();
- if (!classes.containsKey(group)) {
- classes.put(group, new ArrayList<EditedStyleItem>());
- }
- classes.get(group).add(item);
+ classes.put(group, item);
}
final List<TableLabel> labels = new ArrayList<TableLabel>();
@@ -152,13 +139,16 @@ public class AttributesGrouper {
return labels;
}
- public static List<TableLabel> generateLabels(GroupBy group, final List<EditedStyleItem> source, final List<EditedStyleItem> sink) {
- if (group == GroupBy.TYPE) {
- return generateLabelsForType(source, sink);
- }
- else if (group == GroupBy.GROUP) {
- return generateLabelsForGroup(source, sink);
+ @NotNull
+ public static List<TableLabel> generateLabels(@NotNull GroupBy group, final List<EditedStyleItem> source, final List<EditedStyleItem> sink) {
+ switch(group) {
+ case TYPE:
+ return generateLabelsForType(source, sink);
+ case GROUP:
+ return generateLabelsForGroup(source, sink);
+
+ default:
+ throw new IllegalArgumentException();
}
- return null;
}
}
diff --git a/android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java b/android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java
index be7d89ea8d1..2ae49c99b87 100644
--- a/android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java
+++ b/android/src/com/android/tools/idea/editors/theme/datamodels/EditedStyleItem.java
@@ -40,7 +40,7 @@ import java.util.Collections;
* <p/>
* If the attribute is declared locally in multiple resource folders, this class also contains the alternative values for the attribute.
*/
-public class EditedStyleItem {
+public class EditedStyleItem implements Comparable<EditedStyleItem> {
private final static Logger LOG = Logger.getInstance(EditedStyleItem.class);
private final static String DEPRECATED = "deprecated";
@@ -187,4 +187,9 @@ public class EditedStyleItem {
return androidTargetData.isResourcePublic(ResourceType.ATTR.getName(), getName());
}
+
+ @Override
+ public int compareTo(EditedStyleItem that) {
+ return getName().compareTo(that.getName());
+ }
}
diff --git a/android/testSrc/com/android/tools/idea/editors/theme/attributes/AttributesGrouperTest.java b/android/testSrc/com/android/tools/idea/editors/theme/attributes/AttributesGrouperTest.java
new file mode 100644
index 00000000000..d1c3eaff255
--- /dev/null
+++ b/android/testSrc/com/android/tools/idea/editors/theme/attributes/AttributesGrouperTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.editors.theme.attributes;
+
+import com.android.tools.idea.editors.theme.datamodels.EditedStyleItem;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Index.atIndex;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AttributesGrouperTest {
+ /**
+ * Creates a list of {@link EditedStyleItem} mocks from the given list of pairs (attribute name, attribute group)
+ */
+ @NotNull
+ private static List<EditedStyleItem> fromList(String... args) {
+ assert args.length % 2 == 0;
+
+ ImmutableList.Builder<EditedStyleItem> builder = ImmutableList.builder();
+ for (int i = 0; i < args.length; i += 2) {
+ String attributeName = args[i];
+ String attributeGroup = args[i + 1];
+
+ EditedStyleItem styleItem = mock(EditedStyleItem.class);
+ when(styleItem.getName()).thenReturn(attributeName);
+ when(styleItem.getAttrGroup()).thenReturn(attributeGroup);
+ builder.add(styleItem);
+ }
+
+ return builder.build();
+ }
+
+ /**
+ * Extracts the table label strings from the list of {@link TableLabel} objects
+ */
+ @NotNull
+ private static List<String> getTableNames(@NotNull List<TableLabel> tableLabels) {
+ return Lists.transform(tableLabels, new Function<TableLabel, String>() {
+ @Nullable
+ @Override
+ public String apply(@Nullable TableLabel input) {
+ assertNotNull(input);
+ return input.getLabelName();
+ }
+ });
+ }
+
+ @Test
+ public void testEmptySource() {
+ ArrayList<EditedStyleItem> sink = Lists.newArrayList();
+ List<TableLabel> tableLabels = AttributesGrouper.generateLabels(AttributesGrouper.GroupBy.GROUP, Collections.<EditedStyleItem>emptyList(), sink);
+ assertThat(sink).isEmpty();
+ assertThat(tableLabels).isEmpty();
+
+ tableLabels = AttributesGrouper.generateLabels(AttributesGrouper.GroupBy.TYPE, Collections.<EditedStyleItem>emptyList(), sink);
+ assertThat(sink).isEmpty();
+ assertThat(tableLabels).isEmpty();
+ }
+
+ @Test
+ public void testGenerateLabels() {
+ ArrayList<EditedStyleItem> sink = Lists.newArrayList();
+ List<EditedStyleItem> source = fromList(
+ "colorItem", "Colors",
+ "itemColor", "Colors",
+ "theDrawableThing", "Drawables",
+ "drawableStyle", "Styles",
+ "otherThing", "Styles"
+ );
+
+ // Group by Attribute Group
+ List<TableLabel> tableLabels = AttributesGrouper.generateLabels(AttributesGrouper.GroupBy.GROUP, source, sink);
+ assertThat(tableLabels).hasSize(3);
+ // Labels must be alphabetically sorted
+ assertThat(getTableNames(tableLabels))
+ .contains("Colors", atIndex(0))
+ .contains("Drawables", atIndex(1))
+ .contains("Styles", atIndex(2));
+ assertThat(tableLabels.get(0).getRowPosition()).isEqualTo(0);
+ assertThat(tableLabels.get(1).getRowPosition()).isEqualTo(2); // Only 1 drawable
+ assertThat(tableLabels.get(2).getRowPosition()).isEqualTo(3);
+ sink.clear();
+
+ // Group by Type
+ tableLabels = AttributesGrouper.generateLabels(AttributesGrouper.GroupBy.TYPE, source, sink);
+ assertThat(tableLabels).hasSize(4);
+ // Labels are sorted with an arbitrary order
+ assertThat(getTableNames(tableLabels))
+ .contains("Styles", atIndex(0))
+ .contains("Colors", atIndex(1))
+ .contains("Drawables", atIndex(2))
+ .contains("Everything Else", atIndex(3));
+ assertThat(tableLabels.get(0).getRowPosition()).isEqualTo(0);
+ assertThat(tableLabels.get(1).getRowPosition()).isEqualTo(1);
+ assertThat(tableLabels.get(2).getRowPosition()).isEqualTo(3);
+ assertThat(tableLabels.get(3).getRowPosition()).isEqualTo(4);
+ }
+
+ @Test
+ public void testEverythingElse() {
+ ArrayList<EditedStyleItem> sink = Lists.newArrayList();
+ List<EditedStyleItem> source = fromList(
+ "item1", "Colors",
+ "item2", "Colors",
+ "item3", "Drawables",
+ "otherThing", "Styles"
+ );
+
+ List<TableLabel> tableLabels = AttributesGrouper.generateLabels(AttributesGrouper.GroupBy.TYPE, source, sink);
+ // Everything is in the "Everything Else" category so we just do not have headers
+ assertThat(tableLabels).isEmpty();
+ assertThat(sink).hasSize(4);
+ sink.clear();
+
+ source = fromList(
+ "itemColor1", "Colors",
+ "item2", "Colors",
+ "item3", "Drawables",
+ "otherThing", "Styles"
+ );
+ tableLabels = AttributesGrouper.generateLabels(AttributesGrouper.GroupBy.TYPE, source, sink);
+ // Now we have one color so two labels should be generated
+ assertThat(tableLabels).hasSize(2);
+ assertThat(getTableNames(tableLabels))
+ .contains("Colors", atIndex(0))
+ .contains("Everything Else", atIndex(1));
+ assertThat(sink).hasSize(4);
+ }
+} \ No newline at end of file