diff src/browser/sat_browser/list_manager.py @ 604:c22b47d63fe2 frontends_multi_profiles

browser_side: fixed DragAutoCompleteTextBox for the list manager
author souliane <souliane@mailoo.org>
date Sat, 07 Feb 2015 20:35:45 +0100
parents 32dbbc941123
children 6d3142b782c3
line wrap: on
line diff
--- a/src/browser/sat_browser/list_manager.py	Sat Feb 07 12:07:12 2015 +0100
+++ b/src/browser/sat_browser/list_manager.py	Sat Feb 07 20:35:45 2015 +0100
@@ -20,15 +20,11 @@
 from sat.core.log import getLogger
 log = getLogger(__name__)
 from pyjamas.ui.Button import Button
-from pyjamas.ui.ListBox import ListBox
 from pyjamas.ui.FlowPanel import FlowPanel
 from pyjamas.ui.AutoComplete import AutoCompleteTextBox
 from pyjamas.ui.KeyboardListener import KEY_ENTER
-from pyjamas.ui.MouseListener import MouseHandler
-from pyjamas.ui.FocusListener import FocusHandler
-from pyjamas.ui.DropWidget import DropWidget
+from pyjamas.ui.DragWidget import DragWidget
 from pyjamas.Timer import Timer
-from pyjamas import DOM
 
 import base_panels
 import base_widget
@@ -80,7 +76,6 @@
         self.style = {"keyItem": "itemKey",
                       "popupMenuItem": "itemKey",
                       "buttonCell": "itemButtonCell",
-                      "dragoverPanel": "itemPanel-dragover",
                       "keyPanel": "itemPanel",
                       "textBox": "itemTextBox",
                       "textBox-invalid": "itemTextBox-invalid",
@@ -258,9 +253,8 @@
         self.popup_menu = base_panels.PopupMenuPanel(entries, hide, callback, style={"item": self.style["popupMenuItem"]})
 
 
-class DragAutoCompleteTextBox(AutoCompleteTextBox, base_widget.DragLabel, MouseHandler, FocusHandler):
+class DragAutoCompleteTextBox(AutoCompleteTextBox, DragWidget):
     """A draggable AutoCompleteTextBox which is used for representing an item."""
-    # XXX: this class is NOT generic because of the onDragEnd method which calls methods from ListPanel. It's probably not reusable for another scenario.
 
     def __init__(self, list_panel, event_cbs, style):
         """
@@ -270,21 +264,80 @@
         @param style (dict)
         """
         AutoCompleteTextBox.__init__(self)
-        base_widget.DragLabel.__init__(self, '', 'CONTACT_TEXTBOX')  # The group prefix "@" is already in text so we use only the "CONTACT_TEXTBOX" type
+        DragWidget.__init__(self)
         self.list_panel = list_panel
         self.event_cbs = event_cbs
         self.style = style
+        self.addStyleName(style["textBox"])
+        self.reset()
+
+        # Parent classes already init self as an handler for these events
         self.addMouseListener(self)
         self.addFocusListener(self)
         self.addChangeListener(self)
-        self.addStyleName(style["textBox"])
-        self.reset()
+
+    def onDragStart(self, event):
+        """The user starts dragging the text box."""
+        self.list_panel.manager.target_drop_cell = None
+        self.setSelectionRange(len(self.getText()), 0)
+
+        dt = event.dataTransfer
+        dt.setData('text/plain', "%s\n%s" % (self.getText(), "CONTACT_TEXTBOX"))
+        dt.setDragImage(self.getElement(), 15, 15)
+
+    def onDragEnd(self, event):
+        """The user dropped the text box."""
+        target = self.list_panel.manager.target_drop_cell  # parent or another ListPanel
+        if self.getText() == "" or target is None:
+            return
+        self.event_cbs["drop"](self, target)
+
+    def onClick(self, sender):
+        """The choices list is clicked"""
+        assert sender == self.choices
+        AutoCompleteTextBox.onClick(self, sender)
+        self.validate()
+
+    def onChange(self, sender):
+        """The list selection or the text has been changed"""
+        assert sender == self.choices or sender == self
+        if sender == self.choices:
+            AutoCompleteTextBox.onChange(self, sender)
+        self.validate()
+
+    def onKeyUp(self, sender, keycode, modifiers):
+        """Listen for key stroke"""
+        assert sender == self
+        AutoCompleteTextBox.onKeyUp(self, sender, keycode, modifiers)
+        if keycode == KEY_ENTER:
+            self.validate()
+
+    def onMouseMove(self, sender):
+        """Mouse enters the area of a DragAutoCompleteTextBox."""
+        assert sender == self
+        if hasattr(sender, "remove_btn"):
+            sender.remove_btn.setVisible(True)
+
+    def onMouseLeave(self, sender):
+        """Mouse leaves the area of a DragAutoCompleteTextBox."""
+        assert sender == self
+        if hasattr(sender, "remove_btn"):
+            Timer(1500, lambda timer: sender.remove_btn.setVisible(False))
+
+    def onFocus(self, sender):
+        """The DragAutoCompleteTextBox has the focus."""
+        assert sender == self
+        # FIXME: this raises runtime JS error "Permission denied to access property..." when you drag the object
+        #sender.setSelectionRange(0, len(sender.getText()))
+        sender.event_cbs["focus"](sender)
 
     def reset(self):
+        """Reset the text box"""
         self.setText("")
         self.setValid()
 
     def setValid(self, valid=True):
+        """Change the style according to the text validity."""
         if self.getText() == "":
             valid = True
         if valid:
@@ -293,19 +346,13 @@
             self.addStyleName(self.style["textBox-invalid"])
         self.valid = valid
 
-    def onDragStart(self, event):
-        self._text = self.getText()
-        base_widget.DragLabel.onDragStart(self, event)
-        self.list_panel.manager.target_drop_cell = None
+    def validate(self):
+        """Check if the text is valid, update the style."""
         self.setSelectionRange(len(self.getText()), 0)
-
-    def onDragEnd(self, event):
-        target = self.list_panel.manager.target_drop_cell  # parent or another ListPanel
-        if self.getText() == "" or target is None:
-            return
-        self.event_cbs["drop"](self, target)
+        self.event_cbs["validate"](self)
 
     def setRemoveButton(self):
+        """Add the remove button after the text box."""
 
         def remove_cb(sender):
             """Callback for the button to remove this item."""
@@ -318,98 +365,22 @@
         self.list_panel.add(self.remove_btn)
 
     def removeOrReset(self):
+        """Remove the text box if the remove button exists, or reset the text box."""
         if hasattr(self, "remove_btn"):
             self.remove_btn.click()
         else:
             self.reset()
 
-    def onMouseMove(self, sender):
-        """Mouse enters the area of a DragAutoCompleteTextBox."""
-        if hasattr(sender, "remove_btn"):
-            sender.remove_btn.setVisible(True)
-
-    def onMouseLeave(self, sender):
-        """Mouse leaves the area of a DragAutoCompleteTextBox."""
-        if hasattr(sender, "remove_btn"):
-            Timer(1500, lambda timer: sender.remove_btn.setVisible(False))
-
-    def onFocus(self, sender):
-        sender.setSelectionRange(0, len(self.getText()))
-        self.event_cbs["focus"](sender)
-
-    def validate(self):
-        self.setSelectionRange(len(self.getText()), 0)
-        self.event_cbs["validate"](self)
-
-    def onChange(self, sender):
-        """The textbox or list selection is changed"""
-        if isinstance(sender, ListBox):
-            AutoCompleteTextBox.onChange(self, sender)
-        self.validate()
-
-    def onClick(self, sender):
-        """The list is clicked"""
-        AutoCompleteTextBox.onClick(self, sender)
-        self.validate()
-
-    def onKeyUp(self, sender, keycode, modifiers):
-        """Listen for ENTER key stroke"""
-        AutoCompleteTextBox.onKeyUp(self, sender, keycode, modifiers)
-        if keycode == KEY_ENTER:
-            self.validate()
-
-
-class DropCell(DropWidget):
-    """A cell where you can drop widgets. This class is NOT generic because of
-    onDrop which uses methods from ListPanel. It has been created to
-    separate the drag and drop methods from the others and add a bit of
-    readability, but it's probably not reusable for another scenario.
-    """
-
-    def __init__(self, drop_cbs):
-        """
-
-        @param drop_cbs (list[callable])
-        """
-        DropWidget.__init__(self)
-        self.drop_cbs = drop_cbs
-
-    def onDragEnter(self, event):
-        self.addStyleName(self.style["dragoverPanel"])
-        DOM.eventPreventDefault(event)
-
-    def onDragLeave(self, event):
-        if event.clientX <= self.getAbsoluteLeft() or event.clientY <= self.getAbsoluteTop()\
-            or event.clientX >= self.getAbsoluteLeft() + self.getOffsetWidth() - 1\
-            or event.clientY >= self.getAbsoluteTop() + self.getOffsetHeight() - 1:
-            # We check that we are inside widget's box, and we don't remove the style in this case because
-            # if the mouse is over a widget inside the DropWidget, we don't want the style to be removed
-            self.removeStyleName(self.style["dragoverPanel"])
-
-    def onDragOver(self, event):
-        DOM.eventPreventDefault(event)
-
-    def onDrop(self, event):
-        DOM.eventPreventDefault(event)
-        dt = event.dataTransfer
-        # 'text', 'text/plain', and 'Text' are equivalent.
-        item, item_type = dt.getData("text/plain").split('\n')  # Workaround for webkit, only text/plain seems to be managed
-        if item_type and item_type[-1] == '\0':  # Workaround for what looks like a pyjamas bug: the \0 should not be there, and
-            item_type = item_type[:-1]           # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report
-        if item_type in self.drop_cbs.keys():
-            self.drop_cbs[item_type](self, item)
-        self.removeStyleName(self.style["dragoverPanel"])
-
 
 VALID = 1
 INVALID = 2
 DELETE = 3
 
 
-class ListPanel(FlowPanel, DropCell):
+class ListPanel(FlowPanel, base_widget.DropCell):
     """Panel used for listing items sharing the same key. The key is showed as
     a Button to which you can bind a popup menu and the items are represented
-    with a sequence of DragAutoCompleteTextBoxeditable."""
+    with a sequence of DragAutoCompleteTextBox."""
     # XXX: beware that pyjamas.ui.FlowPanel is not fully implemented yet and can not be used with pyjamas.ui.Label
 
     def __init__(self, manager, data, style={}):
@@ -421,22 +392,29 @@
         """
         FlowPanel.__init__(self, Visible=(False if data["optional"] else True))
 
-        def setTargetDropCell(panel, item):
-            self.manager.target_drop_cell = panel
+        def setTargetDropCell(host, item):
+            self.manager.target_drop_cell = self
 
         # FIXME: dirty magic strings '@' and '@@'
-        drop_cbs = {"GROUP": lambda panel, item: self.addItem("@%s" % item),
-                    "CONTACT": lambda panel, item: self.addItem(tryJID(item)),
-                    "CONTACT_TITLE": lambda panel, item: self.addItem('@@'),
+        drop_cbs = {"GROUP": lambda host, item: self.addItem("@%s" % item),
+                    "CONTACT": lambda host, item: self.addItem(tryJID(item)),
+                    "CONTACT_TITLE": lambda host, item: self.addItem('@@'),
                     "CONTACT_TEXTBOX": setTargetDropCell
                     }
-        DropCell.__init__(self, drop_cbs)
+        base_widget.DropCell.__init__(self, None)
+        self.drop_keys = drop_cbs
         self.style = style
         self.addStyleName(self.style["keyPanel"])
         self.manager = manager
         self.key = data["title"]
         self._addTextBox()
 
+    def onDrop(self, event):
+        try:
+            base_widget.DropCell.onDrop(self, event)
+        except base_widget.NoLiberviaWidgetException:
+            pass
+
     def _addTextBox(self, switchPrevious=False):
         """Add an empty text box to the last position.
 
@@ -508,12 +486,13 @@
         return VALID if item in self.manager.items else INVALID
 
     def addItem(self, item, sender=None):
-        """
+        """Try to add an item. It will be added if it's a valid one.
 
         @param item (object): item to be added
         @param (DragAutoCompleteTextBox): widget triggering the event
         @param sender: if True, the item will be "written" to the last textbox
             and a new text box will be added afterward.
+        @return: True if the item has been added.
         """
         valid = self._checkItem(item, sender is not None)
         item_s = unicode(item)