Repository: kivy/kivy

Issue:
ColorWheel Event Fires Multiple Time Per Change
### Versions

* Python: 2.7
* OS: Windows 7
* Kivy: 1.10

### Description

Found this issue playing around with the colorwheel, i was getting 4 events every time I updated the color, which is problematic since there's only one change (specifically for me trying to send network events).

Ideally for one change, we should get one event. 

### Code and Logs

from colorpicker.py:
```
color = ReferenceListProperty(r, g, b, a)
_hsv = ListProperty([1, 1, 1, 0])


def on__hsv(self, instance, value):
        c_hsv = Color(*value, mode='hsv')
        self.r = c_hsv.r
        self.g = c_hsv.g
        self.b = c_hsv.b
        self.a = c_hsv.a
        self.rgba = (self.r, self.g, self.b, self.a)
```

Binding To
`self.color_in.bind( _hsv = self.on_color )
`
Results In A Single Event:
`Color Change <style.AutonomousColorWheel object at 0x000000000EBD3660>	[0.225, 1, 0.45, 1]
`
Binding To
`self.color_in.bind( color= self.on_color )
`
Results In A Multiple Event:
```Color Change <style.AutonomousColorWheel object at 0x000000000EC41660>	[1.0, 0, 0, 0]
Color Change <style.AutonomousColorWheel object at 0x000000000EC41660>	[1.0, 0.3999999761581421, 0, 0]
Color Change <style.AutonomousColorWheel object at 0x000000000EC41660>	[1.0, 0.3999999761581421, 0.3999999761581421, 0]
Color Change <style.AutonomousColorWheel object at 0x000000000EC41660>	[1.0, 0.3999999761581421, 0.3999999761581421, 1.0]```




Discussion:
Seems like the ReferenceListProperty could have more love and prevent redispatching if the final value didn't change.
EDIT: nop, actually the ColorWheel code was bad about it.

Resolving patch:
diff --git a/kivy/uix/colorpicker.py b/kivy/uix/colorpicker.py
--- a/kivy/uix/colorpicker.py
+++ b/kivy/uix/colorpicker.py
@@ -279,16 +279,8 @@ def on_touch_up(self, touch):
             # _hsv based on the selected ColorArc
             piece = int((theta / (2 * pi)) * self._pieces_of_pie)
             division = int((r / self._radius) * self._piece_divisions)
-            self._hsv = \
-                self.arcs[self._pieces_of_pie * division + piece].color
-
-    def on__hsv(self, instance, value):
-        c_hsv = Color(*value, mode='hsv')
-        self.r = c_hsv.r
-        self.g = c_hsv.g
-        self.b = c_hsv.b
-        self.a = c_hsv.a
-        self.rgba = (self.r, self.g, self.b, self.a)
+            hsva = list(self.arcs[self._pieces_of_pie * division + piece].color)
+            self.color = list(hsv_to_rgb(*hsva[:3])) + hsva[-1:]
 
     def _get_touch_r(self, pos):
         return distance(pos, self._origin)
@@ -383,7 +375,16 @@ class ColorPicker(RelativeLayout):
     (1, 1, 1, 1).
     '''
 
-    hsv = ListProperty((1, 1, 1))
+    def _get_hsv(self):
+        return rgb_to_hsv(*self.color[:3])
+
+    def _set_hsv(self, value):
+        if self._updating_clr:
+            return
+        self.set_color(value)
+
+    hsv = AliasProperty(_get_hsv, _set_hsv, bind=('color', ))
+    #hsv = ListProperty((1, 1, 1))
     '''The :attr:`hsv` holds the color currently selected in hsv format.
 
     :attr:`hsv` is a :class:`~kivy.properties.ListProperty` and defaults to
@@ -393,7 +394,9 @@ def _get_hex(self):
         return get_hex_from_color(self.color)
 
     def _set_hex(self, value):
-        self.color = get_color_from_hex(value)[:4]
+        if self._updating_clr:
+            return
+        self.set_color(get_color_from_hex(value)[:4])
 
     hex_color = AliasProperty(_get_hex, _set_hex, bind=('color', ))
     '''The :attr:`hex_color` holds the currently selected color in hex.
@@ -414,19 +417,10 @@ def _set_hex(self, value):
     # now used only internally.
     foreground_color = ListProperty((1, 1, 1, 1))
 
-    def on_color(self, instance, value):
-        if not self._updating_clr:
-            self._updating_clr = True
-            self.hsv = rgb_to_hsv(*value[:3])
-            self._updating_clr = False
-
-    def on_hsv(self, instance, value):
-        if not self._updating_clr:
-            self._updating_clr = True
-            self.color[:3] = hsv_to_rgb(*value)
-            self._updating_clr = False
-
     def _trigger_update_clr(self, mode, clr_idx, text):
+        if self._updating_clr:
+            return
+        self._updating_clr = True
         self._upd_clr_list = mode, clr_idx, text
         ev = self._update_clr_ev
         if ev is None:
@@ -434,28 +428,48 @@ def _trigger_update_clr(self, mode, clr_idx, text):
         ev()
 
     def _update_clr(self, dt):
+        # to prevent interaction between hsv/rgba, we work internaly using rgba
         mode, clr_idx, text = self._upd_clr_list
         try:
             text = min(255, max(0, float(text)))
             if mode == 'rgb':
                 self.color[clr_idx] = float(text) / 255.
             else:
-                self.hsv[clr_idx] = float(text) / 255.
+                hsv = list(self.hsv[:])
+                hsv[clr_idx] = float(text) / 255.
+                self.color[:3] = hsv_to_rgb(*hsv)
         except ValueError:
             Logger.warning('ColorPicker: invalid value : {}'.format(text))
+        finally:
+            self._updating_clr = False
 
     def _update_hex(self, dt):
-        if len(self._upd_hex_list) != 9:
-            return
-        self.hex_color = self._upd_hex_list
+        try:
+            if len(self._upd_hex_list) != 9:
+                return
+            self._updating_clr = False
+            self.hex_color = self._upd_hex_list
+        finally:
+            self._updating_clr = False
 
     def _trigger_update_hex(self, text):
+        if self._updating_clr:
+            return
+        self._updating_clr = True
         self._upd_hex_list = text
         ev = self._update_hex_ev
         if ev is None:
             ev = self._update_hex_ev = Clock.create_trigger(self._update_hex)
         ev()
 
+    def set_color(self, color):
+        self._updating_clr = True
+        if len(color) == 3:
+            self.color[:3] = color
+        else:
+            self.color = color
+        self._updating_clr = False
+
     def __init__(self, **kwargs):
         self._updating_clr = False
         super(ColorPicker, self).__init__(**kwargs)


Test patch: