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)