Mercurial > libervia-web
comparison 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 |
comparison
equal
deleted
inserted
replaced
348:83454ba70a9c | 349:f488692c4903 |
---|---|
33 from pyjamas import DOM | 33 from pyjamas import DOM |
34 | 34 |
35 from datetime import datetime | 35 from datetime import datetime |
36 from time import time | 36 from time import time |
37 | 37 |
38 from tools import html_sanitize, html_clean, inlineRoot | 38 from tools import html_sanitize, html_strip, inlineRoot |
39 | 39 |
40 from sat_frontends.tools.strings import addURLToText | 40 from sat_frontends.tools.strings import addURLToText |
41 from sat.core.i18n import _ | 41 from sat.core.i18n import _ |
42 | 42 |
43 | 43 |
135 """This implementation of a popup menu (context menu) allow you to assign | 135 """This implementation of a popup menu (context menu) allow you to assign |
136 two special methods which are common to all the items, in order to hide | 136 two special methods which are common to all the items, in order to hide |
137 certain items and also easily define their callbacks. The menu can be | 137 certain items and also easily define their callbacks. The menu can be |
138 bound to any of the mouse button (left, middle, right). | 138 bound to any of the mouse button (left, middle, right). |
139 """ | 139 """ |
140 def __init__(self, entries, hide=None, callback=None, vertical=True, style={}, **kwargs): | 140 def __init__(self, entries, hide=None, callback=None, vertical=True, style=None, **kwargs): |
141 """ | 141 """ |
142 @param entries: a dict of dicts, where each sub-dict is representing | 142 @param entries: a dict of dicts, where each sub-dict is representing |
143 one menu item: the sub-dict key can be used as the item text and | 143 one menu item: the sub-dict key can be used as the item text and |
144 description, but optional "title" and "desc" entries would be used | 144 description, but optional "title" and "desc" entries would be used |
145 if they exists. The sub-dicts may be extended later to do | 145 if they exists. The sub-dicts may be extended later to do |
155 self._entries = entries | 155 self._entries = entries |
156 self._hide = hide | 156 self._hide = hide |
157 self._callback = callback | 157 self._callback = callback |
158 self.vertical = vertical | 158 self.vertical = vertical |
159 self.style = {"selected": None, "menu": "recipientTypeMenu", "item": "popupMenuItem"} | 159 self.style = {"selected": None, "menu": "recipientTypeMenu", "item": "popupMenuItem"} |
160 self.style.update(style) | 160 if isinstance(style, dict): |
161 self.style.update(style) | |
161 self._senders = {} | 162 self._senders = {} |
162 | 163 |
163 def _show(self, sender): | 164 def _show(self, sender): |
164 """Popup the menu relative to this sender's position. | 165 """Popup the menu relative to this sender's position. |
165 @param sender: the widget that has been clicked | 166 @param sender: the widget that has been clicked |
287 | 288 |
288 def setText(self, text): | 289 def setText(self, text): |
289 self.text_area.setText(text) | 290 self.text_area.setText(text) |
290 | 291 |
291 | 292 |
292 class LightTextEditor(HTML, FocusHandler, KeyboardHandler): | 293 class BaseTextEditor(object): |
293 | 294 """Basic definition of a text editor. The method edit gets a boolean parameter which |
294 def __init__(self, content='', single_line=False, callback=None): | 295 should be set to True when you want to edit the text and False to only display it.""" |
296 | |
297 def __init__(self, content=None, strproc=None, modifiedCb=None, afterEditCb=None): | |
298 """ | |
299 Remark when inheriting this class: since the setContent method could be | |
300 overwritten by the child class, you should consider calling this __init__ | |
301 after all the parameters affecting this setContent method have been set. | |
302 @param content: dict with at least a 'text' key | |
303 @param strproc: method to be applied on strings to clean the content | |
304 @param modifiedCb: method to be called when the text has been modified. | |
305 If this method returns: | |
306 - True: the modification will be saved and afterEditCb called; | |
307 - False: the modification won't be saved and afterEditCb called; | |
308 - None: the modification won't be saved and afterEditCb not called. | |
309 @param afterEditCb: method to be called when the edition is done | |
310 """ | |
311 if content is None: | |
312 content = {'text': ''} | |
313 assert('text' in content) | |
314 if strproc is None: | |
315 def strproc(text): | |
316 try: | |
317 return text.strip() | |
318 except (TypeError, AttributeError): | |
319 return text | |
320 self.strproc = strproc | |
321 self.__modifiedCb = modifiedCb | |
322 self._afterEditCb = afterEditCb | |
323 self.initialized = False | |
324 self.setContent(content) | |
325 | |
326 def setContent(self, content=None): | |
327 """Set the editable content. The displayed content, which is set from the child class, could differ. | |
328 @param content: dict with at least a 'text' key | |
329 """ | |
330 if content is None: | |
331 content = {'text': ''} | |
332 elif not isinstance(content, dict): | |
333 content = {'text': content} | |
334 assert('text' in content) | |
335 self._original_content = {} | |
336 for key in content: | |
337 self._original_content[key] = self.strproc(content[key]) | |
338 | |
339 def getContent(self): | |
340 """Get the current edited or editable content. | |
341 @return: dict with at least a 'text' key | |
342 """ | |
343 raise NotImplementedError | |
344 | |
345 def modified(self, content=None): | |
346 """Check if the content has been modified. | |
347 Remark: we don't use the direct comparison because we want to ignore empty elements | |
348 @content: content to be check against the original content or None to use the current content | |
349 @return: True if the content has been modified. | |
350 """ | |
351 if content is None: | |
352 content = self.getContent() | |
353 # the following method returns True if one non empty element exists in a but not in b | |
354 diff1 = lambda a, b: [a[key] for key in set(a.keys()).difference(b.keys()) if a[key]] != [] | |
355 # the following method returns True if the values for the common keys are not equals | |
356 diff2 = lambda a, b: [1 for key in set(a.keys()).intersection(b.keys()) if a[key] != b[key]] != [] | |
357 # finally the combination of both to return True if a difference is found | |
358 diff = lambda a, b: diff1(a, b) or diff1(b, a) or diff2(a, b) | |
359 | |
360 return diff(content, self._original_content) | |
361 | |
362 def edit(self, edit, abort=False, sync=False): | |
363 """ | |
364 Remark: the editor must be visible before you call this method. | |
365 @param edit: set to True to edit the content or False to only display it | |
366 @param abort: set to True to cancel the edition and loose the changes. | |
367 If edit and abort are both True, self.abortEdition can be used to ask for a | |
368 confirmation. When edit is False and abort is True, abortion is actually done. | |
369 @param sync: set to True to cancel the edition after the content has been saved somewhere else | |
370 """ | |
371 if edit: | |
372 if not self.initialized: | |
373 self.syncToEditor() # e.g.: use the selected target and unibox content | |
374 self.setFocus(True) | |
375 if abort: | |
376 content = self.getContent() | |
377 if not self.modified(content) or self.abortEdition(content): # e.g: ask for confirmation | |
378 self.edit(False, True, sync) | |
379 return | |
380 if sync: | |
381 self.syncFromEditor(content) # e.g.: save the content to unibox | |
382 return | |
383 else: | |
384 if not self.initialized: | |
385 return | |
386 content = self.getContent() | |
387 if abort: | |
388 self._afterEditCb(content) | |
389 return | |
390 if self.__modifiedCb and self.modified(content): | |
391 result = self.__modifiedCb(content) # e.g.: send a message or update something | |
392 if result is not None: | |
393 if self._afterEditCb: | |
394 self._afterEditCb(content) # e.g.: restore the display mode | |
395 if result is True: | |
396 self.setContent(content) | |
397 elif self._afterEditCb: | |
398 self._afterEditCb(content) | |
399 | |
400 self.initialized = True | |
401 | |
402 def setFocus(self, focus): | |
403 """ | |
404 @param focus: set to True to focus the editor | |
405 """ | |
406 raise NotImplementedError | |
407 | |
408 def syncToEditor(self): | |
409 pass | |
410 | |
411 def syncFromEditor(self, content): | |
412 pass | |
413 | |
414 def abortEdition(self, content): | |
415 return True | |
416 | |
417 | |
418 class LightTextEditor(BaseTextEditor, HTML, FocusHandler, KeyboardHandler): | |
419 """Manage a simple text editor whith the HTML 5 "contenteditable" property.""" | |
420 | |
421 def __init__(self, content=None, modifiedCb=None, afterEditCb=None, single_line=False): | |
422 """ | |
423 @param content | |
424 @param modifiedCb | |
425 @param afterEditCb | |
426 @param single_line: set to True to manage a single line editor. In that case | |
427 the edition would be terminated when the focus is lost or <enter> key is pressed. | |
428 """ | |
295 HTML.__init__(self) | 429 HTML.__init__(self) |
296 if single_line: | 430 if single_line: |
297 FocusHandler.__init__(self) | 431 FocusHandler.__init__(self) |
298 KeyboardHandler.__init__(self) | 432 KeyboardHandler.__init__(self) |
299 self.__single_line = single_line | 433 self.__single_line = single_line |
300 self.__callback = callback | 434 strproc = lambda text: html_sanitize(html_strip(text)) if self.__single_line else html_strip(text) |
301 self.setContent(content) | 435 BaseTextEditor.__init__(self, content, strproc, modifiedCb, afterEditCb) |
302 | 436 |
303 def setContent(self, content=''): | 437 def setContent(self, content=None): |
304 self.__original_content = html_clean(content) if self.__single_line else content.strip() | 438 BaseTextEditor.setContent(self, content) |
305 self.setHTML('<div>%s</div>' % self.__original_content) | |
306 | 439 |
307 def getContent(self): | 440 def getContent(self): |
308 content = DOM.getInnerHTML(self.getElement().firstChild) | 441 text = DOM.getInnerHTML(self.getElement()) |
309 return html_clean(content) if self.__single_line else content.strip() | 442 return {'text': self.strproc(text) if text else ''} |
310 | 443 |
311 def modified(self, content=None): | 444 def edit(self, edit, abort=False, sync=False): |
312 if content is None: | 445 if edit: |
313 content = self.getContent() | 446 self.setHTML(self._original_content['text']) |
314 return content != self.__original_content | |
315 | |
316 def edit(self, edit): | |
317 self.getElement().setAttribute('contenteditable', 'true' if edit else 'false') | 447 self.getElement().setAttribute('contenteditable', 'true' if edit else 'false') |
448 BaseTextEditor.edit(self, edit) | |
318 if edit: | 449 if edit: |
319 self.setFocus(True) | |
320 if self.__single_line: | 450 if self.__single_line: |
321 self.addFocusListener(self) | 451 self.addFocusListener(self) |
322 self.addKeyboardListener(self) | 452 self.addKeyboardListener(self) |
323 else: | 453 else: |
454 self.setDisplayContent() | |
324 if self.__single_line: | 455 if self.__single_line: |
325 self.removeFocusListener(self) | 456 if self in self._focusListeners: |
326 self.removeKeyboardListener(self) | 457 self.removeFocusListener(self) |
327 content = self.getContent() | 458 if self in self._keyboardListeners: |
328 if self.modified(content) and self.__callback: | 459 self.removeKeyboardListener(self) |
329 self.__callback(content) | 460 |
461 def setDisplayContent(self): | |
462 self.setHTML(addURLToText(self._original_content['text'])) | |
330 | 463 |
331 def setFocus(self, focus): | 464 def setFocus(self, focus): |
332 if focus: | 465 if focus: |
333 self.getElement().focus() | 466 self.getElement().focus() |
334 else: | 467 else: |
335 self.getElement().blur() | 468 self.getElement().blur() |
336 | 469 |
337 def onKeyDown(self, sender, keycode, modifiers): | 470 def onKeyPress(self, sender, keycode, modifiers): |
338 if keycode == KEY_ENTER: | 471 if keycode == KEY_ENTER: |
339 self.setFocus(False) | 472 self.setFocus(False) # finish the edition |
340 | 473 |
341 def onLostFocus(self, sender): | 474 def onLostFocus(self, sender): |
475 """Finish the edition when focus is lost""" | |
342 self.edit(False) | 476 self.edit(False) |