# HG changeset patch # User souliane # Date 1423337745 -3600 # Node ID c22b47d63fe29e02df60f1a7d21e0174e52b0fe9 # Parent 462d0458e6790eb1292fdbc6cc92e4ad0dc1128d browser_side: fixed DragAutoCompleteTextBox for the list manager diff -r 462d0458e679 -r c22b47d63fe2 src/browser/public/libervia.css --- a/src/browser/public/libervia.css Sat Feb 07 12:07:12 2015 +0100 +++ b/src/browser/public/libervia.css Sat Feb 07 20:35:45 2015 +0100 @@ -1389,12 +1389,6 @@ vertical-align: baseline; } -.itemPanel-dragover { - border-radius: 5px; - background: none repeat scroll 0% 0% rgb(135, 179, 255); - border: 1px dashed rgb(35,79,255); -} - .recipientSpacer { height: 15px; } @@ -1432,6 +1426,12 @@ vertical-align:middle; } +.contactGroupPanel.dragover { + border-radius: 5px !important; + background: none repeat scroll 0% 0% rgb(135, 179, 255) !important; + border: 1px dashed rgb(35,79,255) !important; +} + .toggleAssignedContacts { white-space: nowrap; } diff -r 462d0458e679 -r c22b47d63fe2 src/browser/sat_browser/list_manager.py --- 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)