comparison src/browser/sat_browser/richtext.py @ 679:a90cc8fc9605

merged branch frontends_multi_profiles
author Goffi <goffi@goffi.org>
date Wed, 18 Mar 2015 16:15:18 +0100
parents 6d3142b782c3
children 9877607c719a
comparison
equal deleted inserted replaced
590:1bffc4c244c3 679:a90cc8fc9605
32 from pyjamas.ui.KeyboardListener import KeyboardHandler 32 from pyjamas.ui.KeyboardListener import KeyboardHandler
33 from __pyjamas__ import doc 33 from __pyjamas__ import doc
34 34
35 from constants import Const as C 35 from constants import Const as C
36 import dialog 36 import dialog
37 import base_panels 37 import base_panel
38 import editor_widget
38 import list_manager 39 import list_manager
39 import html_tools 40 import html_tools
40 import panels 41 import blog
41 42 import chat
42 43
43 class RichTextEditor(base_panels.BaseTextEditor, FlexTable): 44
45 class RichTextEditor(editor_widget.BaseTextEditor, FlexTable):
44 """Panel for the rich text editor.""" 46 """Panel for the rich text editor."""
45 47
46 def __init__(self, host, content=None, modifiedCb=None, afterEditCb=None, options=None, style=None): 48 def __init__(self, host, content=None, modifiedCb=None, afterEditCb=None, options=None, style=None):
47 """ 49 """
48 @param host: the SatWebFrontend instance 50 @param host: the SatWebFrontend instance
49 @param content: dict with at least a 'text' key 51 @param content: dict with at least a 'text' key
50 @param modifiedCb: method to be called when the text has been modified 52 @param modifiedCb: method to be called when the text has been modified
51 @param afterEditCb: method to be called when the edition is done 53 @param afterEditCb: method to be called when the edition is done
52 @param options: list of UI options (see self.readOptions) 54 @param options: list of UI options (see self.readOptions)
53 """ 55 """
56 FlexTable.__init__(self) # FIXME
54 self.host = host 57 self.host = host
55 self._debug = False # TODO: don't forget to set it False before commit 58 self._debug = False # TODO: don't forget to set it False before commit
56 self.wysiwyg = False 59 self.wysiwyg = False
57 self.__readOptions(options) 60 self.__readOptions(options)
58 self.style = {'main': 'richTextEditor', 61 self.style = {'main': 'richTextEditor',
60 'toolbar': 'richTextToolbar', 63 'toolbar': 'richTextToolbar',
61 'textarea': 'richTextArea'} 64 'textarea': 'richTextArea'}
62 if isinstance(style, dict): 65 if isinstance(style, dict):
63 self.style.update(style) 66 self.style.update(style)
64 self._prepareUI() 67 self._prepareUI()
65 base_panels.BaseTextEditor.__init__(self, content, None, modifiedCb, afterEditCb) 68 editor_widget.BaseTextEditor.__init__(self, content, None, modifiedCb, afterEditCb)
66 69
67 def __readOptions(self, options): 70 def __readOptions(self, options):
68 """Set the internal flags according to the given options.""" 71 """Set the internal flags according to the given options."""
69 if options is None: 72 if options is None:
70 options = [] 73 options = []
82 self.content_offset = self.toolbar_offset + (len(composition.RICH_SYNTAXES) if self._debug else 1) 85 self.content_offset = self.toolbar_offset + (len(composition.RICH_SYNTAXES) if self._debug else 1)
83 self.command_offset = self.content_offset + 1 86 self.command_offset = self.content_offset + 1
84 else: 87 else:
85 self.title_offset = self.toolbar_offset = self.content_offset = y_offset 88 self.title_offset = self.toolbar_offset = self.content_offset = y_offset
86 self.command_offset = self.content_offset + 1 89 self.command_offset = self.content_offset + 1
87 FlexTable.__init__(self, self.command_offset + (0 if self.no_command else 1), 2) 90 # FlexTable.__init__(self, rowspan=self.command_offset + (0 if self.no_command else 1), colspan=2) # FIXME
88 self.addStyleName(self.style['main']) 91 self.addStyleName(self.style['main'])
89 92
90 def addEditListener(self, listener): 93 def addEditListener(self, listener):
91 """Add a method to be called whenever the text is edited. 94 """Add a method to be called whenever the text is edited.
92 @param listener: method taking two arguments: sender, keycode""" 95 @param listener: method taking two arguments: sender, keycode"""
93 base_panels.BaseTextEditor.addEditListener(self, listener) 96 editor_widget.BaseTextEditor.addEditListener(self, listener)
94 if hasattr(self, 'display'): 97 if hasattr(self, 'display'):
95 self.display.addEditListener(listener) 98 self.display.addEditListener(listener)
96 99
97 def refresh(self, edit=None): 100 def refresh(self, edit=None):
98 """Refresh the UI for edition/display mode 101 """Refresh the UI for edition/display mode
105 getattr(self, widget).setVisible(edit) 108 getattr(self, widget).setVisible(edit)
106 109
107 if hasattr(self, 'toolbar'): 110 if hasattr(self, 'toolbar'):
108 self.toolbar.setVisible(False) 111 self.toolbar.setVisible(False)
109 if not hasattr(self, 'display'): 112 if not hasattr(self, 'display'):
110 self.display = base_panels.HTMLTextEditor(options={'enhance_display': False, 'listen_keyboard': False}) # for display mode 113 self.display = editor_widget.HTMLTextEditor(options={'enhance_display': False, 'listen_keyboard': False}) # for display mode
111 for listener in self.edit_listeners: 114 for listener in self.edit_listeners:
112 self.display.addEditListener(listener) 115 self.display.addEditListener(listener)
113 if not self.read_only and not hasattr(self, 'textarea'): 116 if not self.read_only and not hasattr(self, 'textarea'):
114 self.textarea = EditTextArea(self) # for edition mode 117 self.textarea = EditTextArea(self) # for edition mode
115 self.textarea.addStyleName(self.style['textarea']) 118 self.textarea.addStyleName(self.style['textarea'])
122 self.setWidget(self.content_offset, 0, self.display) 125 self.setWidget(self.content_offset, 0, self.display)
123 if not edit: 126 if not edit:
124 return 127 return
125 128
126 if not self.no_title and not hasattr(self, 'title_panel'): 129 if not self.no_title and not hasattr(self, 'title_panel'):
127 self.title_panel = base_panels.TitlePanel() 130 self.title_panel = base_panel.TitlePanel()
128 self.title_panel.addStyleName(self.style['title']) 131 self.title_panel.addStyleName(self.style['title'])
129 self.getFlexCellFormatter().setColSpan(self.title_offset, 0, 2) 132 self.getFlexCellFormatter().setColSpan(self.title_offset, 0, 2)
130 self.setWidget(self.title_offset, 0, self.title_panel) 133 self.setWidget(self.title_offset, 0, self.title_panel)
131 134
132 if not self.no_command and not hasattr(self, 'command'): 135 if not self.no_command and not hasattr(self, 'command'):
172 @param init: set to True to re-init without switching the widgets.""" 175 @param init: set to True to re-init without switching the widgets."""
173 def setWysiwyg(): 176 def setWysiwyg():
174 self.wysiwyg = wysiwyg 177 self.wysiwyg = wysiwyg
175 try: 178 try:
176 self.wysiwyg_button.setChecked(wysiwyg) 179 self.wysiwyg_button.setChecked(wysiwyg)
177 except TypeError: 180 except (AttributeError, TypeError):
178 pass 181 pass
179 try: 182 try:
180 if wysiwyg: 183 if wysiwyg:
181 self.syntax_label.addStyleName('transparent') 184 self.syntax_label.addStyleName('transparent')
182 else: 185 else:
183 self.syntax_label.removeStyleName('transparent') 186 self.syntax_label.removeStyleName('transparent')
184 except TypeError: 187 except (AttributeError, TypeError):
185 pass 188 pass
186 if not wysiwyg: 189 if not wysiwyg:
187 self.display.removeStyleName('richTextWysiwyg') 190 self.display.removeStyleName('richTextWysiwyg')
188 191
189 if init: 192 if init:
282 confirmation. When edit is False and abort is True, abortion is actually done. 285 confirmation. When edit is False and abort is True, abortion is actually done.
283 @param sync: set to True to cancel the edition after the content has been saved somewhere else 286 @param sync: set to True to cancel the edition after the content has been saved somewhere else
284 """ 287 """
285 if not (edit and abort): 288 if not (edit and abort):
286 self.refresh(edit) # not when we are asking for a confirmation 289 self.refresh(edit) # not when we are asking for a confirmation
287 base_panels.BaseTextEditor.edit(self, edit, abort, sync) # after the UI has been refreshed 290 editor_widget.BaseTextEditor.edit(self, edit, abort, sync) # after the UI has been refreshed
288 if (edit and abort): 291 if (edit and abort):
289 return # self.abortEdition is called by base_panels.BaseTextEditor.edit 292 return # self.abortEdition is called by editor_widget.BaseTextEditor.edit
290 self.setWysiwyg(False, init=True) # after base_panels.BaseTextEditor (it affects self.getContent) 293 self.setWysiwyg(False, init=True) # after editor_widget.BaseTextEditor (it affects self.getContent)
291 if sync: 294 if sync:
292 return 295 return
293 # the following must NOT be done at each UI refresh! 296 # the following must NOT be done at each UI refresh!
294 content = self._original_content 297 content = self._original_content
295 if edit: 298 if edit:
315 # set the display text in XHTML only during init because a new MicroblogEntry instance is created after each modification 318 # set the display text in XHTML only during init because a new MicroblogEntry instance is created after each modification
316 self.setDisplayContent() 319 self.setDisplayContent()
317 self.display.edit(False) 320 self.display.edit(False)
318 321
319 def setDisplayContent(self): 322 def setDisplayContent(self):
320 """Set the content of the base_panels.HTMLTextEditor which is used for display/wysiwyg""" 323 """Set the content of the editor_widget.HTMLTextEditor which is used for display/wysiwyg"""
321 content = self._original_content 324 content = self._original_content
322 text = content['text'] 325 text = content['text']
323 if 'title' in content and content['title']: 326 if 'title' in content and content['title']:
324 text = '<h1>%s</h1>%s' % (html_tools.html_sanitize(content['title']), content['text']) 327 text = '<h1>%s</h1>%s' % (html_tools.html_sanitize(content['title']), content['text'])
325 self.display.setContent({'text': text}) 328 self.display.setContent({'text': text})
438 setText = lambda: self.host.uni_box.setText("" if emptyText else self.getContent()['text']) 441 setText = lambda: self.host.uni_box.setText("" if emptyText else self.getContent()['text'])
439 if not hasattr(self, 'recipient'): 442 if not hasattr(self, 'recipient'):
440 setText() 443 setText()
441 return True 444 return True
442 if recipients is None: 445 if recipients is None:
443 recipients = self.recipient.getContacts() 446 recipients = self.recipient.getItemsByKey()
444 target = "" 447 target = ""
445 # we could eventually allow more in the future 448 # we could eventually allow more in the future
446 allowed = 1 449 allowed = 1
447 for key in recipients: 450 for key in recipients:
448 count = len(recipients[key]) 451 count = len(recipients[key])
455 target = recipients[key][0] 458 target = recipients[key][0]
456 setText() 459 setText()
457 if target == "": 460 if target == "":
458 return True 461 return True
459 if target.startswith("@"): 462 if target.startswith("@"):
460 _class = panels.MicroblogPanel 463 _class = blog.MicroblogPanel
461 target = None if target == "@@" else target[1:] 464 target = None if target == "@@" else target[1:]
462 else: 465 else:
463 _class = panels.ChatPanel 466 _class = chat.Chat
464 self.host.getOrCreateLiberviaWidget(_class, {'item': target}) 467 self.host.displayWidget(_class, target)
465 return True 468 return True
466 469
467 def syncFromEditor(self, content): 470 def syncFromEditor(self, content):
468 """Synchronize to unibox and close the dialog afterward. Display 471 """Synchronize to unibox and close the dialog afterward. Display
469 a message and leave the dialog open if the sync was not possible.""" 472 a message and leave the dialog open if the sync was not possible."""
483 return 486 return
484 RichTextEditor.edit(self, edit, abort, sync) 487 RichTextEditor.edit(self, edit, abort, sync)
485 488
486 def __sendMessage(self): 489 def __sendMessage(self):
487 """Send the message.""" 490 """Send the message."""
488 recipients = self.recipient.getContacts() 491 recipients = self.recipient.getItemsByKey()
489 targets = [] 492 targets = []
490 for addr in recipients: 493 for addr in recipients:
491 for recipient in recipients[addr]: 494 for recipient in recipients[addr]:
492 if recipient.startswith("@"): 495 if recipient.startswith("@"):
493 targets.append(("PUBLIC", None, addr) if recipient == "@@" else ("GROUP", recipient[1:], addr)) 496 targets.append(("PUBLIC", None, addr) if recipient == "@@" else ("GROUP", recipient[1:], addr))
514 # TODO: be sure we also display empty groups and disconnected contacts + their groups 517 # TODO: be sure we also display empty groups and disconnected contacts + their groups
515 # store the full list of potential recipients (groups and contacts) 518 # store the full list of potential recipients (groups and contacts)
516 list_ = [] 519 list_ = []
517 list_.append("@@") 520 list_.append("@@")
518 list_.extend("@%s" % group for group in parent.host.contact_panel.getGroups()) 521 list_.extend("@%s" % group for group in parent.host.contact_panel.getGroups())
519 list_.extend(contact for contact in parent.host.contact_panel.getContacts()) 522 list_.extend(contact for contact in parent.host.contact_list.roster_entities)
520 list_manager.ListManager.__init__(self, parent, composition.RECIPIENT_TYPES, list_, {'y': y_offset}) 523 list_manager.ListManager.__init__(self, parent, composition.RECIPIENT_TYPES, list_, {'y': y_offset})
521 524
522 self.registerPopupMenuPanel(entries=composition.RECIPIENT_TYPES, 525 self.registerPopupMenuPanel(entries=composition.RECIPIENT_TYPES,
523 hide=lambda sender, key: self.__children[key]["panel"].isVisible(), 526 hide=lambda sender, key: self.__children[key]["panel"].isVisible(),
524 callback=self.setContactPanelVisible) 527 callback=self.setContactPanelVisible)