Mercurial > libervia-web
diff browser_side/base_panels.py @ 349:f488692c4903
browser_side: LightTextEditor inheritates from BaseTextEditor + display URL in the status
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 12 Feb 2014 14:58:11 +0100 |
parents | f1ba38043d78 |
children | b95099a1d11b |
line wrap: on
line diff
--- a/browser_side/base_panels.py Wed Feb 12 14:51:13 2014 +0100 +++ b/browser_side/base_panels.py Wed Feb 12 14:58:11 2014 +0100 @@ -35,7 +35,7 @@ from datetime import datetime from time import time -from tools import html_sanitize, html_clean, inlineRoot +from tools import html_sanitize, html_strip, inlineRoot from sat_frontends.tools.strings import addURLToText from sat.core.i18n import _ @@ -137,7 +137,7 @@ certain items and also easily define their callbacks. The menu can be bound to any of the mouse button (left, middle, right). """ - def __init__(self, entries, hide=None, callback=None, vertical=True, style={}, **kwargs): + def __init__(self, entries, hide=None, callback=None, vertical=True, style=None, **kwargs): """ @param entries: a dict of dicts, where each sub-dict is representing one menu item: the sub-dict key can be used as the item text and @@ -157,7 +157,8 @@ self._callback = callback self.vertical = vertical self.style = {"selected": None, "menu": "recipientTypeMenu", "item": "popupMenuItem"} - self.style.update(style) + if isinstance(style, dict): + self.style.update(style) self._senders = {} def _show(self, sender): @@ -289,44 +290,176 @@ self.text_area.setText(text) -class LightTextEditor(HTML, FocusHandler, KeyboardHandler): +class BaseTextEditor(object): + """Basic definition of a text editor. The method edit gets a boolean parameter which + should be set to True when you want to edit the text and False to only display it.""" + + def __init__(self, content=None, strproc=None, modifiedCb=None, afterEditCb=None): + """ + Remark when inheriting this class: since the setContent method could be + overwritten by the child class, you should consider calling this __init__ + after all the parameters affecting this setContent method have been set. + @param content: dict with at least a 'text' key + @param strproc: method to be applied on strings to clean the content + @param modifiedCb: method to be called when the text has been modified. + If this method returns: + - True: the modification will be saved and afterEditCb called; + - False: the modification won't be saved and afterEditCb called; + - None: the modification won't be saved and afterEditCb not called. + @param afterEditCb: method to be called when the edition is done + """ + if content is None: + content = {'text': ''} + assert('text' in content) + if strproc is None: + def strproc(text): + try: + return text.strip() + except (TypeError, AttributeError): + return text + self.strproc = strproc + self.__modifiedCb = modifiedCb + self._afterEditCb = afterEditCb + self.initialized = False + self.setContent(content) + + def setContent(self, content=None): + """Set the editable content. The displayed content, which is set from the child class, could differ. + @param content: dict with at least a 'text' key + """ + if content is None: + content = {'text': ''} + elif not isinstance(content, dict): + content = {'text': content} + assert('text' in content) + self._original_content = {} + for key in content: + self._original_content[key] = self.strproc(content[key]) + + def getContent(self): + """Get the current edited or editable content. + @return: dict with at least a 'text' key + """ + raise NotImplementedError + + def modified(self, content=None): + """Check if the content has been modified. + Remark: we don't use the direct comparison because we want to ignore empty elements + @content: content to be check against the original content or None to use the current content + @return: True if the content has been modified. + """ + if content is None: + content = self.getContent() + # the following method returns True if one non empty element exists in a but not in b + diff1 = lambda a, b: [a[key] for key in set(a.keys()).difference(b.keys()) if a[key]] != [] + # the following method returns True if the values for the common keys are not equals + diff2 = lambda a, b: [1 for key in set(a.keys()).intersection(b.keys()) if a[key] != b[key]] != [] + # finally the combination of both to return True if a difference is found + diff = lambda a, b: diff1(a, b) or diff1(b, a) or diff2(a, b) - def __init__(self, content='', single_line=False, callback=None): + return diff(content, self._original_content) + + def edit(self, edit, abort=False, sync=False): + """ + Remark: the editor must be visible before you call this method. + @param edit: set to True to edit the content or False to only display it + @param abort: set to True to cancel the edition and loose the changes. + If edit and abort are both True, self.abortEdition can be used to ask for a + confirmation. When edit is False and abort is True, abortion is actually done. + @param sync: set to True to cancel the edition after the content has been saved somewhere else + """ + if edit: + if not self.initialized: + self.syncToEditor() # e.g.: use the selected target and unibox content + self.setFocus(True) + if abort: + content = self.getContent() + if not self.modified(content) or self.abortEdition(content): # e.g: ask for confirmation + self.edit(False, True, sync) + return + if sync: + self.syncFromEditor(content) # e.g.: save the content to unibox + return + else: + if not self.initialized: + return + content = self.getContent() + if abort: + self._afterEditCb(content) + return + if self.__modifiedCb and self.modified(content): + result = self.__modifiedCb(content) # e.g.: send a message or update something + if result is not None: + if self._afterEditCb: + self._afterEditCb(content) # e.g.: restore the display mode + if result is True: + self.setContent(content) + elif self._afterEditCb: + self._afterEditCb(content) + + self.initialized = True + + def setFocus(self, focus): + """ + @param focus: set to True to focus the editor + """ + raise NotImplementedError + + def syncToEditor(self): + pass + + def syncFromEditor(self, content): + pass + + def abortEdition(self, content): + return True + + +class LightTextEditor(BaseTextEditor, HTML, FocusHandler, KeyboardHandler): + """Manage a simple text editor whith the HTML 5 "contenteditable" property.""" + + def __init__(self, content=None, modifiedCb=None, afterEditCb=None, single_line=False): + """ + @param content + @param modifiedCb + @param afterEditCb + @param single_line: set to True to manage a single line editor. In that case + the edition would be terminated when the focus is lost or <enter> key is pressed. + """ HTML.__init__(self) if single_line: FocusHandler.__init__(self) KeyboardHandler.__init__(self) self.__single_line = single_line - self.__callback = callback - self.setContent(content) + strproc = lambda text: html_sanitize(html_strip(text)) if self.__single_line else html_strip(text) + BaseTextEditor.__init__(self, content, strproc, modifiedCb, afterEditCb) - def setContent(self, content=''): - self.__original_content = html_clean(content) if self.__single_line else content.strip() - self.setHTML('<div>%s</div>' % self.__original_content) + def setContent(self, content=None): + BaseTextEditor.setContent(self, content) def getContent(self): - content = DOM.getInnerHTML(self.getElement().firstChild) - return html_clean(content) if self.__single_line else content.strip() + text = DOM.getInnerHTML(self.getElement()) + return {'text': self.strproc(text) if text else ''} - def modified(self, content=None): - if content is None: - content = self.getContent() - return content != self.__original_content - - def edit(self, edit): + def edit(self, edit, abort=False, sync=False): + if edit: + self.setHTML(self._original_content['text']) self.getElement().setAttribute('contenteditable', 'true' if edit else 'false') + BaseTextEditor.edit(self, edit) if edit: - self.setFocus(True) if self.__single_line: self.addFocusListener(self) self.addKeyboardListener(self) else: + self.setDisplayContent() if self.__single_line: - self.removeFocusListener(self) - self.removeKeyboardListener(self) - content = self.getContent() - if self.modified(content) and self.__callback: - self.__callback(content) + if self in self._focusListeners: + self.removeFocusListener(self) + if self in self._keyboardListeners: + self.removeKeyboardListener(self) + + def setDisplayContent(self): + self.setHTML(addURLToText(self._original_content['text'])) def setFocus(self, focus): if focus: @@ -334,9 +467,10 @@ else: self.getElement().blur() - def onKeyDown(self, sender, keycode, modifiers): + def onKeyPress(self, sender, keycode, modifiers): if keycode == KEY_ENTER: - self.setFocus(False) + self.setFocus(False) # finish the edition def onLostFocus(self, sender): + """Finish the edition when focus is lost""" self.edit(False)