Mercurial > libervia-web
comparison src/browser/sat_browser/editor_widget.py @ 716:3b91225b457a
server + browser side: blogging refactoring (draft), huge commit sorry:
/!\ everything is not working yet, group blog is not working for now
- adaptation to backend changes
- frontend commons part of blog have been moved to QuickFrontend
- (editors) button "WYSIWYG edition" renamed to "preview"
- (editors) Shift + [ENTER] is now used to send a text message, [ENTER] to finish a ligne normally
- (editors) fixed modifiers handling
- global simplification, resulting of the refactoring
- with backend refactoring, we are now using PEP again, XEP-0277 compatibility is restored \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 16 Aug 2015 01:51:12 +0200 |
parents | c2f22ca12e23 |
children | f3cd261ea12f |
comparison
equal
deleted
inserted
replaced
715:b2465423c76e | 716:3b91225b457a |
---|---|
22 from sat_frontends.tools import strings | 22 from sat_frontends.tools import strings |
23 | 23 |
24 from pyjamas.ui.HTML import HTML | 24 from pyjamas.ui.HTML import HTML |
25 from pyjamas.ui.SimplePanel import SimplePanel | 25 from pyjamas.ui.SimplePanel import SimplePanel |
26 from pyjamas.ui.TextArea import TextArea | 26 from pyjamas.ui.TextArea import TextArea |
27 from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_SHIFT, KEY_UP, KEY_DOWN, KeyboardHandler | 27 from pyjamas.ui import KeyboardListener as keyb |
28 from pyjamas.ui.FocusListener import FocusHandler | 28 from pyjamas.ui.FocusListener import FocusHandler |
29 from pyjamas.ui.ClickListener import ClickHandler | 29 from pyjamas.ui.ClickListener import ClickHandler |
30 from pyjamas.ui.MouseListener import MouseHandler | 30 from pyjamas.ui.MouseListener import MouseHandler |
31 from pyjamas.Timer import Timer | 31 from pyjamas.Timer import Timer |
32 from pyjamas import DOM | 32 from pyjamas import DOM |
58 | 58 |
59 def history_cb(text): | 59 def history_cb(text): |
60 self.setText(text) | 60 self.setText(text) |
61 Timer(5, lambda timer: self.setCursorPos(len(text))) | 61 Timer(5, lambda timer: self.setCursorPos(len(text))) |
62 | 62 |
63 if keycode == KEY_ENTER: | 63 if keycode == keyb.KEY_ENTER: |
64 if _txt: | 64 if _txt: |
65 self.host.selected_widget.onTextEntered(_txt) | 65 self.host.selected_widget.onTextEntered(_txt) |
66 self.host._updateInputHistory(_txt) # FIXME: why using a global variable ? | 66 self.host._updateInputHistory(_txt) # FIXME: why using a global variable ? |
67 self.setText('') | 67 self.setText('') |
68 sender.cancelKey() | 68 sender.cancelKey() |
69 elif keycode == KEY_UP: | 69 elif keycode == keyb.KEY_UP: |
70 self.host._updateInputHistory(_txt, -1, history_cb) | 70 self.host._updateInputHistory(_txt, -1, history_cb) |
71 elif keycode == KEY_DOWN: | 71 elif keycode == keyb.KEY_DOWN: |
72 self.host._updateInputHistory(_txt, +1, history_cb) | 72 self.host._updateInputHistory(_txt, +1, history_cb) |
73 else: | 73 else: |
74 self._onComposing() | 74 self._onComposing() |
75 | 75 |
76 def _onComposing(self): | 76 def _onComposing(self): |
97 overwritten by the child class, you should consider calling this __init__ | 97 overwritten by the child class, you should consider calling this __init__ |
98 after all the parameters affecting this setContent method have been set. | 98 after all the parameters affecting this setContent method have been set. |
99 @param content: dict with at least a 'text' key | 99 @param content: dict with at least a 'text' key |
100 @param strproc: method to be applied on strings to clean the content | 100 @param strproc: method to be applied on strings to clean the content |
101 @param modifiedCb: method to be called when the text has been modified. | 101 @param modifiedCb: method to be called when the text has been modified. |
102 If this method returns: | 102 This method can return: |
103 - True: the modification will be saved and afterEditCb called; | 103 - True: the modification will be saved and afterEditCb called; |
104 - False: the modification won't be saved and afterEditCb called; | 104 - False: the modification won't be saved and afterEditCb called; |
105 - None: the modification won't be saved and afterEditCb not called. | 105 - None: the modification won't be saved and afterEditCb not called. |
106 @param afterEditCb: method to be called when the edition is done | 106 @param afterEditCb: method to be called when the edition is done |
107 """ | 107 """ |
108 if content is None: | 108 if content is None: |
109 content = {'text': ''} | 109 content = {'text': ''} |
110 assert('text' in content) | 110 assert 'text' in content |
111 if strproc is None: | 111 if strproc is None: |
112 def strproc(text): | 112 def strproc(text): |
113 try: | 113 try: |
114 return text.strip() | 114 return text.strip() |
115 except (TypeError, AttributeError): | 115 except (TypeError, AttributeError): |
116 return text | 116 return text |
117 self.strproc = strproc | 117 self.strproc = strproc |
118 self.__modifiedCb = modifiedCb | 118 self._modifiedCb = modifiedCb |
119 self._afterEditCb = afterEditCb | 119 self._afterEditCb = afterEditCb |
120 self.initialized = False | 120 self.initialized = False |
121 self.edit_listeners = [] | 121 self.edit_listeners = [] |
122 self.setContent(content) | 122 self.setContent(content) |
123 | 123 |
124 def setContent(self, content=None): | 124 def setContent(self, content=None): |
125 """Set the editable content. The displayed content, which is set from the child class, could differ. | 125 """Set the editable content. |
126 @param content: dict with at least a 'text' key | 126 The displayed content, which is set from the child class, could differ. |
127 | |
128 @param content (dict): content data, need at least a 'text' key | |
127 """ | 129 """ |
128 if content is None: | 130 if content is None: |
129 content = {'text': ''} | 131 content = {'text': ''} |
130 elif not isinstance(content, dict): | 132 elif not isinstance(content, dict): |
131 content = {'text': content} | 133 content = {'text': content} |
132 assert('text' in content) | 134 assert 'text' in content |
133 self._original_content = {} | 135 self._original_content = {} |
134 for key in content: | 136 for key in content: |
135 self._original_content[key] = self.strproc(content[key]) | 137 self._original_content[key] = self.strproc(content[key]) |
136 | 138 |
137 def getContent(self): | 139 def getContent(self): |
148 """ | 150 """ |
149 self._original_content = content | 151 self._original_content = content |
150 | 152 |
151 def getOriginalContent(self): | 153 def getOriginalContent(self): |
152 """ | 154 """ |
153 @return the original content before modification (dict) | 155 @return (dict): the original content before modification (i.e. content given in __init__) |
154 """ | 156 """ |
155 return self._original_content | 157 return self._original_content |
156 | 158 |
157 def modified(self, content=None): | 159 def modified(self, content=None): |
158 """Check if the content has been modified. | 160 """Check if the content has been modified. |
191 return | 193 return |
192 content = self.getContent() | 194 content = self.getContent() |
193 if abort: | 195 if abort: |
194 self._afterEditCb(content) | 196 self._afterEditCb(content) |
195 return | 197 return |
196 if self.__modifiedCb and self.modified(content): | 198 if self._modifiedCb and self.modified(content): |
197 result = self.__modifiedCb(content) # e.g.: send a message or update something | 199 result = self._modifiedCb(content) # e.g.: send a message or update something |
198 if result is not None: | 200 if result is not None: |
199 if self._afterEditCb: | 201 if self._afterEditCb: |
200 self._afterEditCb(content) # e.g.: restore the display mode | 202 self._afterEditCb(content) # e.g.: restore the display mode |
201 if result is True: | 203 if result is True: |
202 self.setContent(content) | 204 self.setContent(content) |
218 """Add a method to be called whenever the text is edited. | 220 """Add a method to be called whenever the text is edited. |
219 @param listener: method taking two arguments: sender, keycode""" | 221 @param listener: method taking two arguments: sender, keycode""" |
220 self.edit_listeners.append(listener) | 222 self.edit_listeners.append(listener) |
221 | 223 |
222 | 224 |
223 class SimpleTextEditor(BaseTextEditor, FocusHandler, KeyboardHandler, ClickHandler): | 225 class SimpleTextEditor(BaseTextEditor, FocusHandler, keyb.KeyboardHandler, ClickHandler): |
224 """Base class for manage a simple text editor.""" | 226 """Base class for manage a simple text editor.""" |
225 | 227 |
226 def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None): | 228 def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None): |
227 """ | 229 """ |
228 @param content | 230 @param content |
229 @param modifiedCb | 231 @param modifiedCb |
230 @param afterEditCb | 232 @param afterEditCb |
231 @param options: dict with the following value: | 233 @param options (dict): can have the following value: |
232 - no_xhtml: set to True to clean any xhtml content. | 234 - no_xhtml: set to True to clean any xhtml content. |
233 - enhance_display: if True, the display text will be enhanced with strings.addURLToText | 235 - enhance_display: if True, the display text will be enhanced with strings.addURLToText |
234 - listen_keyboard: set to True to terminate the edition with <enter> or <escape>. | 236 - listen_keyboard: set to True to terminate the edition with <enter> or <escape>. |
235 - listen_focus: set to True to terminate the edition when the focus is lost. | 237 - listen_focus: set to True to terminate the edition when the focus is lost. |
236 - listen_click: set to True to start the edition when you click on the widget. | 238 - listen_click: set to True to start the edition when you click on the widget. |
237 """ | 239 """ |
238 self.options = {'no_xhtml': False, | 240 self.options = {'no_xhtml': False, |
239 'enhance_display': True, | 241 'enhance_display': True, |
240 'listen_keyboard': True, | 242 'listen_keyboard': True, |
241 'listen_focus': False, | 243 'listen_focus': False, |
242 'listen_click': False | 244 'listen_click': False |
243 } | 245 } |
244 if options: | 246 if options: |
245 self.options.update(options) | 247 self.options.update(options) |
246 self.__shift_down = False | |
247 if self.options['listen_focus']: | 248 if self.options['listen_focus']: |
248 FocusHandler.__init__(self) | 249 FocusHandler.__init__(self) |
249 if self.options['listen_click']: | 250 if self.options['listen_click']: |
250 ClickHandler.__init__(self) | 251 ClickHandler.__init__(self) |
251 KeyboardHandler.__init__(self) | 252 keyb.KeyboardHandler.__init__(self) |
252 strproc = lambda text: html_tools.html_sanitize(html_tools.html_strip(text)) if self.options['no_xhtml'] else html_tools.html_strip(text) | 253 strproc = lambda text: html_tools.html_sanitize(html_tools.html_strip(text)) if self.options['no_xhtml'] else html_tools.html_strip(text) |
253 BaseTextEditor.__init__(self, content, strproc, modifiedCb, afterEditCb) | 254 BaseTextEditor.__init__(self, content, strproc, modifiedCb, afterEditCb) |
254 self.textarea = self.display = None | 255 self.textarea = self.display = None |
255 | 256 |
256 def setContent(self, content=None): | 257 def setContent(self, content=None): |
293 def setFocus(self, focus): | 294 def setFocus(self, focus): |
294 raise NotImplementedError | 295 raise NotImplementedError |
295 | 296 |
296 def onKeyDown(self, sender, keycode, modifiers): | 297 def onKeyDown(self, sender, keycode, modifiers): |
297 for listener in self.edit_listeners: | 298 for listener in self.edit_listeners: |
298 listener(self.textarea, keycode) | 299 listener(self.textarea, keycode, modifiers) # FIXME: edit_listeners must either be removed, or send an action instead of keycode/modifiers |
299 if not self.options['listen_keyboard']: | 300 if not self.options['listen_keyboard']: |
300 return | 301 return |
301 if keycode == KEY_SHIFT or self.__shift_down: # allow input a new line with <shift> + <enter> | 302 if keycode == keyb.KEY_ENTER and modifiers & keyb.MODIFIER_SHIFT: |
302 self.__shift_down = True | |
303 return | |
304 if keycode == KEY_ENTER: # finish the edition | |
305 self.textarea.setFocus(False) | 303 self.textarea.setFocus(False) |
306 if not self.options['listen_focus']: | 304 if not self.options['listen_focus']: |
307 self.edit(False) | 305 self.edit(False) |
308 | |
309 def onKeyUp(self, sender, keycode, modifiers): | |
310 if keycode == KEY_SHIFT: | |
311 self.__shift_down = False | |
312 | 306 |
313 def onLostFocus(self, sender): | 307 def onLostFocus(self, sender): |
314 """Finish the edition when focus is lost""" | 308 """Finish the edition when focus is lost""" |
315 if self.options['listen_focus']: | 309 if self.options['listen_focus']: |
316 self.edit(False) | 310 self.edit(False) |
323 def onBrowserEvent(self, event): | 317 def onBrowserEvent(self, event): |
324 if self.options['listen_focus']: | 318 if self.options['listen_focus']: |
325 FocusHandler.onBrowserEvent(self, event) | 319 FocusHandler.onBrowserEvent(self, event) |
326 if self.options['listen_click']: | 320 if self.options['listen_click']: |
327 ClickHandler.onBrowserEvent(self, event) | 321 ClickHandler.onBrowserEvent(self, event) |
328 KeyboardHandler.onBrowserEvent(self, event) | 322 keyb.KeyboardHandler.onBrowserEvent(self, event) |
329 | 323 |
330 | 324 |
331 class HTMLTextEditor(SimpleTextEditor, HTML, FocusHandler, KeyboardHandler): | 325 class HTMLTextEditor(SimpleTextEditor, HTML, FocusHandler, keyb.KeyboardHandler): |
332 """Manage a simple text editor with the HTML 5 "contenteditable" property.""" | 326 """Manage a simple text editor with the HTML 5 "contenteditable" property.""" |
333 | 327 |
334 def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None): | 328 def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None): |
335 HTML.__init__(self) | 329 HTML.__init__(self) |
336 SimpleTextEditor.__init__(self, content, modifiedCb, afterEditCb, options) | 330 SimpleTextEditor.__init__(self, content, modifiedCb, afterEditCb, options) |
351 self.getElement().focus() | 345 self.getElement().focus() |
352 else: | 346 else: |
353 self.getElement().blur() | 347 self.getElement().blur() |
354 | 348 |
355 | 349 |
356 class LightTextEditor(SimpleTextEditor, SimplePanel, FocusHandler, KeyboardHandler): | 350 class LightTextEditor(SimpleTextEditor, SimplePanel, FocusHandler, keyb.KeyboardHandler): |
357 """Manage a simple text editor with a TextArea for editing, HTML for display.""" | 351 """Manage a simple text editor with a TextArea for editing, HTML for display.""" |
358 | 352 |
359 def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None): | 353 def __init__(self, content=None, modifiedCb=None, afterEditCb=None, options=None): |
360 SimplePanel.__init__(self) | 354 SimplePanel.__init__(self) |
361 SimpleTextEditor.__init__(self, content, modifiedCb, afterEditCb, options) | 355 SimpleTextEditor.__init__(self, content, modifiedCb, afterEditCb, options) |