Repository: kivy/kivy

Issue:
actionbar: entries get swapped between the dropdowns of consecutively clicked actiongroups
<!--
The issue tracker is a tool to address bugs.
Please use the #kivy IRC channel on freenode or Stack Overflow for
support questions, more information at https://git.io/vM1yQ.

Before opening a new issue, make sure you do the following:
    * check that your issue isn't already filed: https://git.io/vM1iE
    * prepare a short, runnable example that reproduces the issue
    * reproduce the problem with the latest development version of Kivy
    * double-check that the issue is indeed a bug and not a support request
-->

### Versions
* Python: 3.4.3
* OS: Linux-3.13.0-24-generic-x86_64
* Kivy: 1.10.1.dev0
* Kivy installation method: manual (git repo)


### Description
Steps to recreate the problem
1. Run `python3 ./kivy/uix/actionbar.py`
2. Quickly click Group1 and Group2 alternately until the options from one menu show up in the other


### Code and Logs
Having debugged the events, it appears that the problem is multiple calls of the ActionGroup._toggle_dropdown(). Both the on_release and dismiss events trigger the same _toggle_dropdown. The result is that the event is called three times so the is_open property is set incorrectly.



Discussion:
proposed solution #5338 

Resolving patch:
diff --git a/kivy/uix/actionbar.py b/kivy/uix/actionbar.py
--- a/kivy/uix/actionbar.py
+++ b/kivy/uix/actionbar.py
@@ -24,6 +24,10 @@
 after other :class:`ActionItems <ActionItem>`.
 An :class:`ActionView` will contain an :class:`ActionOverflow`.
 A :class:`ContextualActionView` is a subclass of an :class:`ActionView`.
+
+.. versionchanged:: 1.10.1
+    :class:`ActionGroup` core rewritten from :class:`Spinner` to pure
+    :class:`DropDown`
 '''
 
 __all__ = ('ActionBarException', 'ActionItem', 'ActionButton',
@@ -260,14 +264,8 @@ class ActionDropDown(DropDown):
     '''ActionDropDown class, see module documentation for more information.
     '''
 
-    def on_touch_down(self, touch):
-        if super(ActionDropDown, self).on_touch_down(touch):
-            if self.auto_dismiss:
-                self.dismiss()
-            return True
-
 
-class ActionGroup(ActionItem, Spinner):
+class ActionGroup(ActionItem, Button):
     '''ActionGroup class, see module documentation for more information.
     '''
 
@@ -314,57 +312,96 @@ class ActionGroup(ActionItem, Spinner):
     .. versionadded:: 1.10.0
     '''
 
+    is_open = BooleanProperty(False)
+    '''By default, the DropDown is not open. Set to True to open it.
+
+    :attr:`is_open` is a :class:`~kivy.properties.BooleanProperty` and
+    defaults to False.
+    '''
+
     def __init__(self, **kwargs):
         self.list_action_item = []
         self._list_overflow_items = []
         super(ActionGroup, self).__init__(**kwargs)
-        self.dropdown_cls = ActionDropDown
 
-    def add_widget(self, item):
-        if isinstance(item, ActionSeparator):
-            super(ActionGroup, self).add_widget(item)
+        # real is_open independent on public event
+        self._is_open = False
+
+        # create DropDown for the group and save its state to _is_open
+        self._dropdown = ActionDropDown()
+        self._dropdown.bind(attach_to=lambda ins, value: setattr(
+            self, '_is_open', True if value else False
+        ))
+
+        # put open/close responsibility to the event
+        # - trigger dropdown opening when clicked
+        self.bind(on_release=lambda *args: setattr(
+            self, 'is_open', True
+        ))
+
+        # - trigger dropdown closing when an item
+        #   in the dropdown is clicked
+        self._dropdown.bind(on_dismiss=lambda *args: setattr(
+            self, 'is_open', False
+        ))
+
+    def on_is_open(self, instance, value):
+        # opening only if the DropDown is closed
+        if value and not self._is_open:
+            self._toggle_dropdown()
+            self._dropdown.open(self)
             return
 
-        if not isinstance(item, ActionItem):
-            raise ActionBarException('ActionGroup only accepts ActionItem')
-
-        self.list_action_item.append(item)
-
-    def show_group(self):
-        self.clear_widgets()
-        for item in self._list_overflow_items + self.list_action_item:
-            item.inside_group = True
-            self._dropdown.add_widget(item)
-
-    def _build_dropdown(self, *largs):
-        if self._dropdown:
-            self._dropdown.unbind(on_dismiss=self._toggle_dropdown)
+        # closing is_open manually, dismiss manually
+        if not value and self._is_open:
             self._dropdown.dismiss()
-            self._dropdown = None
-        self._dropdown = self.dropdown_cls()
-        self._dropdown.bind(on_dismiss=self._toggle_dropdown)
-
-    def _update_dropdown(self, *largs):
-        pass
 
     def _toggle_dropdown(self, *largs):
-        self.is_open = not self.is_open
         ddn = self._dropdown
         ddn.size_hint_x = None
+
+        # if container was set incorrectly and/or is missing
         if not ddn.container:
             return
         children = ddn.container.children
 
+        # set DropDown width manually or if not set, then widen
+        # the ActionGroup + DropDown until the widest child fits
         if children:
             ddn.width = self.dropdown_width or max(
-                self.width, max(c.pack_width for c in children))
+                self.width, max(c.pack_width for c in children)
+            )
         else:
             ddn.width = self.width
 
+        # set the DropDown children's height
         for item in children:
             item.size_hint_y = None
             item.height = max([self.height, sp(48)])
 
+            # dismiss DropDown manually
+            # auto_dismiss applies to touching outside of the DropDown
+            item.bind(on_release=ddn.dismiss)
+
+    def add_widget(self, item):
+        # if adding ActionSeparator ('normal' mode,
+        # everything visible), add it to the parent
+        if isinstance(item, ActionSeparator):
+            super(ActionGroup, self).add_widget(item)
+            return
+
+        if not isinstance(item, ActionItem):
+            raise ActionBarException('ActionGroup only accepts ActionItem')
+
+        self.list_action_item.append(item)
+
+    def show_group(self):
+        # 'normal' mode, items can fit to the view
+        self.clear_widgets()
+        for item in self._list_overflow_items + self.list_action_item:
+            item.inside_group = True
+            self._dropdown.add_widget(item)
+
     def clear_widgets(self):
         self._dropdown.clear_widgets()
 
@@ -757,14 +794,14 @@ def _pop_contextual_action_view(self):
             ActionButton:
                 text: 'Btn2'
             ActionGroup:
-                text: 'Group 2'
+                text: 'Group 1'
                 ActionButton:
                     text: 'Btn3'
                 ActionButton:
                     text: 'Btn4'
             ActionGroup:
                 dropdown_width: 200
-                text: 'Group1'
+                text: 'Group 2'
                 ActionButton:
                     text: 'Btn5'
                 ActionButton:


Test patch: