OSDN Git Service

GLE2: Properly represent mixed choices in dynamic context menu.
authorRaphael Moll <ralf@android.com>
Sat, 11 Sep 2010 05:19:11 +0000 (22:19 -0700)
committerRaphael Moll <ralf@android.com>
Sat, 11 Sep 2010 05:26:24 +0000 (22:26 -0700)
Example: when display the context menu for a multiple selection,
some items might be on wrap_parent whilst others might be in
match_parent. This new change has the menu display a different
icon for these choices and indicate that N out of M items are
in a given state. Basically this converts boolean support to
a tristate support.

Change-Id: Ia409a87fa17e5d36912ace6dde5f4eb094fa865f

eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.png [new file with mode: 0755]
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/MenuAction.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java

diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.png b/eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.png
new file mode 100755 (executable)
index 0000000..79ffc2d
Binary files /dev/null and b/eclipse/plugins/com.android.ide.eclipse.adt/icons/match_multiple.png differ
index d3f34e5..942ad85 100755 (executable)
@@ -236,6 +236,9 @@ public abstract class MenuAction {
     /**
      * A toggle is a simple on/off action, displayed as an item in a context menu
      * with a check mark if the item is checked.
+     * <p/>
+     * Two toggles are equal if they have the same id, title and group-id.
+     * It is expected for the checked state and action closure to be different.
      */
     public static class Toggle extends Action {
         /**
@@ -303,6 +306,9 @@ public abstract class MenuAction {
      * Implementation detail: empty choices will not be displayed in the context menu.
      * <p/>
      * Choice items are sorted by their id, using String's natural sorting order.
+     * <p/>
+     * Two multiple choices are equal if they have the same id, title, group-id and choices.
+     * It is expected for the current state and action closure to be different.
      */
     public static class Choices extends Action {
 
index f9657af..97f2b95 100755 (executable)
@@ -19,6 +19,7 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2;
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule;
 import com.android.ide.eclipse.adt.editors.layout.gscripts.MenuAction;
+import com.android.ide.eclipse.adt.internal.editors.IconFactory;
 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy;
 import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine;
@@ -35,14 +36,11 @@ import org.eclipse.jface.action.Separator;
 import groovy.lang.Closure;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
 import java.util.TreeMap;
+import java.util.Map.Entry;
 import java.util.regex.Pattern;
 
 
@@ -373,6 +371,7 @@ import java.util.regex.Pattern;
             final TreeMap<String, ArrayList<MenuAction>> actionsMap) {
 
         final RulesEngine gre = mCanvas.getRulesEngine();
+        IconFactory factory = IconFactory.getInstance();
         MenuManager submenu = new MenuManager(firstAction.getTitle(), firstAction.getId());
 
         // Convert to a tree map as needed so that keys be naturally ordered.
@@ -380,14 +379,7 @@ import java.util.regex.Pattern;
             choiceMap = new TreeMap<String, String>(choiceMap);
         }
 
-        String current = firstAction.getCurrent();
-        Set<String> currents = null;
-        if (current.indexOf(MenuAction.Choices.CHOICE_SEP) >= 0) {
-            currents = new HashSet<String>(
-                    Arrays.asList(current.split(
-                            Pattern.quote(MenuAction.Choices.CHOICE_SEP))));
-            current = null;
-        }
+        String sepPattern = Pattern.quote(MenuAction.Choices.CHOICE_SEP);
 
         for (Entry<String, String> entry : choiceMap.entrySet() ) {
             final String key = entry.getKey();
@@ -402,26 +394,65 @@ import java.util.regex.Pattern;
                 continue;
             }
 
-            final boolean isChecked =
-                (currents != null && currents.contains(key)) ||
-                key.equals(current);
+            final List<MenuAction> actions = actionsMap.get(firstAction.getId());
+
+            if (actions == null || actions.isEmpty()) {
+                continue;
+            }
+
+            // Are all actions for this id checked, unchecked, or in a mixed state?
+            int numOff = 0;
+            int numOn = 0;
+            for (MenuAction a2 : actions) {
+                MenuAction.Choices choice = (MenuAction.Choices) a2;
+                String current = choice.getCurrent();
+                boolean found = false;
+
+                if (current.indexOf(MenuAction.Choices.CHOICE_SEP) >= 0) {
+                    // current choice has a separator, so it's a flag with multiple values
+                    // selected. Compare keys with the split values.
+                    if (current.indexOf(key) >= 0) {
+                        for(String value : current.split(sepPattern)) {
+                            if (key.equals(value)) {
+                                found = true;
+                                break;
+                            }
+                        }
+                    }
+                } else {
+                    // current choice has no separator, simply compare to the key
+                    found = key.equals(current);
+                }
+
+                if (found) {
+                    numOn++;
+                } else {
+                    numOff++;
+                }
+            }
+
+            // We consider the item to be checked if all actions are all checked.
+            // This means a mixed item will be first toggled from off to on by all the closures.
+            final boolean isChecked = numOff == 0 && numOn > 0;
+            boolean isMixed = numOff > 0 && numOn > 0;
+
+            if (isMixed) {
+                title += String.format(" (%1$d/%2$d)", numOn, numOff + numOn);
+            }
 
             Action a = new Action(title, IAction.AS_CHECK_BOX) {
                 @Override
                 public void run() {
-                    final List<MenuAction> actions = actionsMap.get(firstAction.getId());
-                    if (actions == null || actions.isEmpty()) {
-                        return;
-                    }
 
-                    String label = String.format("Change attribute %s", actions.get(0).getTitle());
+                    String label =
+                        String.format("Change attribute %1$s", actions.get(0).getTitle());
                     if (actions.size() > 1) {
-                        label += String.format(" (%d elements)", actions.size());
+                        label += String.format(" (%1$d elements)", actions.size());
                     }
 
                     if (mEditor.isEditXmlModelPending()) {
                         // This should not be happening.
-                        logError("Action '%s' failed: XML changes pending, document might be corrupt.", //$NON-NLS-1$
+                        logError("Action '%1$s' failed: XML changes pending, document might be corrupt.", //$NON-NLS-1$
                                  label);
                         return;
                     }
@@ -443,8 +474,12 @@ import java.util.regex.Pattern;
                     });
                 }
             };
-            a.setId(String.format("%s_%s", firstAction.getId(), key));     //$NON-NLS-1$
+            a.setId(String.format("%1$s_%2$s", firstAction.getId(), key));          //$NON-NLS-1$
             a.setChecked(isChecked);
+            if (isMixed) {
+                a.setImageDescriptor(factory.getImageDescriptor("match_multiple")); //$NON-NLS-1$
+            }
+
             submenu.add(a);
         }