diff cagou/core/menu.py @ 222:a676cb07c1cb

core (menu): TouchMenuBehaviour: moved code showing ModernMenu on item from file sharing plugin to a generic behaviour, so it can be re-used elsewhere.
author Goffi <goffi@goffi.org>
date Tue, 26 Jun 2018 20:26:21 +0200
parents e42e0c45d384
children 15e47bbb192c
line wrap: on
line diff
--- a/cagou/core/menu.py	Tue Jun 26 07:36:11 2018 +0200
+++ b/cagou/core/menu.py	Tue Jun 26 20:26:21 2018 +0200
@@ -28,12 +28,14 @@
 from kivy.uix.popup import Popup
 from cagou.core.utils import FilterBehavior
 from kivy import properties
-from kivy.garden import contextmenu
+from kivy.garden import contextmenu, modernmenu
 from sat_frontends.quick_frontend import quick_menus
 from kivy.core.window import Window
 from kivy.animation import Animation
 from kivy.metrics import dp
+from kivy.clock import Clock
 from cagou import G
+from functools import partial
 import webbrowser
 
 ABOUT_TITLE = _(u"About {}".format(C.APP_NAME))
@@ -78,7 +80,8 @@
         profile = None
         if selected is not None:
             try:
-                profile = selected.profile
+                # FIXME: handle multi-profiles
+                profile = next(iter(selected.profiles))
             except AttributeError:
                 pass
 
@@ -129,7 +132,8 @@
         else:
             context_menu = contextmenu.ContextMenu()
             caller.add_widget(context_menu)
-            # FIXME: next line is needed after parent is set to avoid a display bug in contextmenu
+            # FIXME: next line is needed after parent is set to avoid
+            #        a display bug in contextmenu
             # TODO: fix this upstream
             context_menu._on_visible(False)
 
@@ -266,8 +270,11 @@
     # callback will be called with path to file to transfer
     # profiles if set will be sent to transfer widget, may be used to get specific files
     profiles = properties.ObjectProperty()
-    transfer_txt = _(u"Beware! The file will be sent to your server and stay unencrypted there\nServer admin(s) can see the file, and they choose how, when and if it will be deleted")
-    send_txt = _(u"The file will be sent unencrypted directly to your contact (without transiting by the server), except in some cases")
+    transfer_txt = _(u"Beware! The file will be sent to your server and stay unencrypted "
+                     u"there\nServer admin(s) can see the file, and they choose how, "
+                     u"when and if it will be deleted")
+    send_txt = _(u"The file will be sent unencrypted directly to your contact "
+                 u"(without transiting by the server), except in some cases")
     items_layout = properties.ObjectProperty()
     size_hint_close = (1, 0)
     size_hint_open = (1, 0.5)
@@ -295,8 +302,12 @@
                 self.callback(
                     file_path,
                     cleaning_cb,
-                    transfer_type = C.TRANSFER_UPLOAD if self.ids['upload_btn'].state == "down" else C.TRANSFER_SEND)
-            wid = plug_info['factory'](plug_info, onTransferCb, self.cancel_cb, self.profiles)
+                    transfer_type = (C.TRANSFER_UPLOAD
+                        if self.ids['upload_btn'].state == "down" else C.TRANSFER_SEND))
+            wid = plug_info['factory'](plug_info,
+                                       onTransferCb,
+                                       self.cancel_cb,
+                                       self.profiles)
             if not external:
                 G.host.showExtraUI(wid)
 
@@ -336,3 +347,100 @@
                        lambda c: c.jid,
                        width_cb=lambda c: c.width,
                        height_cb=lambda c: dp(70))
+
+
+class TouchMenu(modernmenu.ModernMenu):
+    pass
+
+
+class TouchMenuItemBehaviour(object):
+    """Class to use on every item where a menu may appear
+
+    main_wid attribute must be set to the class inheriting from TouchMenuBehaviour
+    do_item_action is the method called on simple click
+    getMenuChoices must return a list of menus for long press
+        menus there are dict as expected by ModernMenu
+        (translated text, index and callback)
+    """
+    main_wid = properties.ObjectProperty()
+    click_timeout = properties.NumericProperty(0.4)
+
+    def on_touch_down(self, touch):
+        if not self.collide_point(*touch.pos):
+            return
+        t = partial(self.open_menu, touch)
+        touch.ud['menu_timeout'] = t
+        Clock.schedule_once(t, self.click_timeout)
+        return super(TouchMenuItemBehaviour, self).on_touch_down(touch)
+
+    def do_item_action(self, touch):
+        pass
+
+    def on_touch_up(self, touch):
+        if touch.ud.get('menu_timeout'):
+            Clock.unschedule(touch.ud['menu_timeout'])
+            if self.collide_point(*touch.pos) and self.main_wid.menu is None:
+                self.do_item_action(touch)
+        return super(TouchMenuItemBehaviour, self).on_touch_up(touch)
+
+    def open_menu(self, touch, dt):
+        self.main_wid.open_menu(self, touch)
+        del touch.ud['menu_timeout']
+
+    def getMenuChoices(self):
+        """return choice adapted to selected item
+
+        @return (list[dict]): choices ad expected by ModernMenu
+        """
+        return []
+
+
+class TouchMenuBehaviour(object):
+    """Class to handle a menu appearing on long press on items
+
+    classes using this behaviour need to have a float_layout property
+    pointing the main FloatLayout.
+    """
+    float_layout = properties.ObjectProperty()
+
+    def __init__(self, *args, **kwargs):
+        super(TouchMenuBehaviour, self).__init__(*args, **kwargs)
+        self.menu = None
+        self.menu_item = None
+
+    ## menu methods ##
+
+    def clean_fl_children(self, layout, children):
+        """insure that self.menu and self.menu_item are None when menu is dimissed"""
+        if self.menu is not None and self.menu not in children:
+            self.menu = self.menu_item = None
+
+    def clear_menu(self):
+        """remove menu if there is one"""
+        if self.menu is not None:
+            self.menu.dismiss()
+            self.menu = None
+            self.menu_item = None
+
+    def open_menu(self, item, touch):
+        """open menu for item
+
+        @param item(PathWidget): item when the menu has been requested
+        @param touch(kivy.input.MotionEvent): touch data
+        """
+        if self.menu_item == item:
+            return
+        self.clear_menu()
+        pos = self.to_widget(*touch.pos)
+        choices = item.getMenuChoices()
+        if not choices:
+            return
+        self.menu = TouchMenu(choices=choices,
+                                center=pos,
+                                size_hint=(None, None))
+        self.float_layout.add_widget(self.menu)
+        self.menu.start_display(touch)
+        self.menu_item = item
+
+    def on_float_layout(self, wid, float_layout):
+        float_layout.bind(children=self.clean_fl_children)