comparison browser_side/richtext.py @ 351:c943fd54c90e

browser_side: heavy refactorisation for microblogs: - RichTextEditor inheritates from BaseTextEditor - stuff related to display/edition have been moved from MicroblogEntry to LightTextEditor and RichTextEditor. Now the editors has two modes for display/edition and the microblog bubble is actually the editor itself. - RichTextEditor's display mode uses a LightTextEditor (this will be used for WYSIWYG edition) - addressing stuff of RichTextEditor have been moved to a child class RichMessageEditor (used for the rich text editor when clicking on the button left to the unibox) - handle blog titles TODO: - fix encode/decode errors when sending special chars - fix images maximal width in the bubble - rich content WYSIWYG edition
author souliane <souliane@mailoo.org>
date Wed, 12 Feb 2014 15:17:04 +0100
parents 82f9e92379b0
children 2610443b05a2
comparison
equal deleted inserted replaced
350:f1b9ec412769 351:c943fd54c90e
24 from pyjamas.ui.HTML import HTML 24 from pyjamas.ui.HTML import HTML
25 from pyjamas.ui.FlexTable import FlexTable 25 from pyjamas.ui.FlexTable import FlexTable
26 from pyjamas.ui.HorizontalPanel import HorizontalPanel 26 from pyjamas.ui.HorizontalPanel import HorizontalPanel
27 27
28 from dialog import ConfirmDialog, InfoDialog 28 from dialog import ConfirmDialog, InfoDialog
29 from base_panels import TitlePanel 29 from base_panels import TitlePanel, BaseTextEditor, LightTextEditor
30 from list_manager import ListManager 30 from list_manager import ListManager
31 import panels
31 32
32 from sat_frontends.tools import composition 33 from sat_frontends.tools import composition
33 34 from sat.core.i18n import _
34 # used for onCloseCallback 35
35 CANCEL, SYNC_NOT_SAVE, SAVE = xrange(0, 3) 36
36 37 class RichTextEditor(BaseTextEditor, FlexTable):
37
38 class RichTextEditor(FlexTable):
39 """Panel for the rich text editor.""" 38 """Panel for the rich text editor."""
40 39
41 def __init__(self, host, parent, onCloseCallback=None, options=()): 40 def __init__(self, host, content=None, modifiedCb=None, afterEditCb=None, options=None, style=None):
42 """Fill the editor with recipients panel, toolbar, text area... 41 """
43 @param host: the SatWebFrontend instance 42 @param host: the SatWebFrontend instance
44 @param parent: the parent panel 43 @param content: dict with at least a 'text' key
45 @param onCloseCallback: method to call when the dialog is closed 44 @param modifiedCb: method to be called when the text has been modified
46 @param options: list of options: 'no_recipient', 'no_command', 'no_style'... 45 @param afterEditCb: method to be called when the edition is done
46 @param options: list of UI options (see self.readOptions)
47 """ 47 """
48
49 # TODO: don't forget to comment this before commit
50 self._debug = False
51
52 # This must be done before FlexTable.__init__ because it is used by setVisible
53 self.host = host 48 self.host = host
54 49 self._debug = False # TODO: don't forget to set it False before commit
55 self.no_title = 'no_title' in options 50 self.__readOptions(options)
56 self.no_title = True # XXX: remove this line when titles are managed 51 self.style = {'main': 'richTextEditor',
57 self.no_close = 'no_close' in options 52 'title': 'richTextTitle',
53 'toolbar': 'richTextToolbaar',
54 'textarea': 'richTextArea'}
55 if isinstance(style, dict):
56 self.style.update(style)
57 self._prepareUI()
58 BaseTextEditor.__init__(self, content, None, modifiedCb, afterEditCb)
59
60 def __readOptions(self, options):
61 """Set the internal flags according to the given options."""
62 if options is None:
63 options = []
64 self.read_only = 'read_only' in options
58 self.update_msg = 'update_msg' in options 65 self.update_msg = 'update_msg' in options
59 self.no_recipient = 'no_recipient' in options 66 self.no_title = 'no_title' in options or self.read_only
60 self.no_command = 'no_command' in options 67 self.no_command = 'no_command' in options or self.read_only
61 self.no_sync_unibox = self.no_command or 'no_sync_unibox' in options 68
62 self.no_main_style = 'no_style' in options or 'no_main_style' in options 69 def _prepareUI(self, y_offset=0):
63 self.no_title_style = 'no_style' in options or 'no_title_style' in options 70 """Prepare the UI to host title panel, toolbar, text area...
64 self.no_textarea_style = 'no_style' in options or 'no_textarea_style' in options 71 @param y_offset: Y offset to start from (extra rows on top)"""
65 72 if not self.read_only:
66 offset1 = 0 if self.no_recipient else (len(composition.RECIPIENT_TYPES) + 1) 73 self.title_offset = y_offset
67 offset2 = 0 if self.no_title else 1 74 self.toolbar_offset = self.title_offset + (0 if self.no_title else 1)
68 offset3 = len(composition.RICH_FORMATS) if self._debug else 1 75 self.content_offset = self.toolbar_offset + (len(composition.RICH_SYNTAXES) if self._debug else 1)
69 offset4 = 0 if self.no_command else 1 76 self.command_offset = self.content_offset + 1
70 FlexTable.__init__(self, offset1 + offset2 + offset3 + 1 + offset4, 2) 77 FlexTable.__init__(self, self.command_offset + (0 if self.no_command else 1), 2)
71 if not self.no_main_style: 78 self.addStyleName(self.style['main'])
72 self.addStyleName('richTextEditor') 79
73 80 def refresh(self, edit=None):
74 self._parent = parent 81 """Refresh the UI for edition/display mode
75 self._on_close_callback = onCloseCallback 82 @param edit: set to True to display the edition mode"""
76 self.original_text = '' 83 if edit is None:
77 84 edit = hasattr(self, 'textarea') and self.textarea.getVisible()
78 current_offset = offset1 85
79 if not self.no_recipient: 86 for widget in ['title_panel', 'command']:
80 # recipient types sub-panels are automatically added by the manager 87 if hasattr(self, widget):
81 self.recipient = RecipientManager(self) 88 getattr(self, widget).setVisible(edit)
82 self.recipient.createWidgets(title_format="%s: ") 89
83 self.getFlexCellFormatter().setColSpan(current_offset - 1, 0, 2) 90 self.getFlexCellFormatter().setColSpan(self.content_offset, 0, 2)
84 spacer = HTML('') 91 if edit:
85 spacer.setStyleName('recipientSpacer') 92 if not hasattr(self, 'textarea'):
86 self.setWidget(current_offset - 1, 0, spacer) 93 self.textarea = TextArea() # for edition mode
87 94 self.textarea.addStyleName(self.style['textarea'])
88 current_offset += offset2 95 self.textarea.setWidth('100%') # CSS width doesn't do it, don't know why
89 if not self.no_title: 96 self.setWidget(self.content_offset, 0, self.textarea)
97 else:
98 if hasattr(self, 'toolbar'):
99 self.toolbar.setVisible(False)
100 if not hasattr(self, 'display'):
101 self.display = LightTextEditor() # for display mode
102 self.setWidget(self.content_offset, 0, self.display)
103 return
104
105 if not self.no_title and not hasattr(self, 'title_panel'):
90 self.title_panel = TitlePanel() 106 self.title_panel = TitlePanel()
91 self.title_panel.addStyleName("richTextTitle") 107 self.title_panel.addStyleName(self.style['title'])
92 self.getFlexCellFormatter().setColSpan(current_offset - 1, 0, 2) 108 self.getFlexCellFormatter().setColSpan(self.title_offset, 0, 2)
93 self.setWidget(current_offset - 1, 0, self.title_panel) 109 self.setWidget(self.title_offset, 0, self.title_panel)
94 110
95 # Rich text tool bar is automatically added by setVisible 111 if not self.no_command and not hasattr(self, 'command'):
96 self.offset_toolbar = offset1 + offset2
97
98 current_offset += offset3 + 1
99 self.textarea = TextArea()
100 if not self.no_textarea_style:
101 self.textarea.addStyleName('richTextArea')
102 self.getFlexCellFormatter().setColSpan(current_offset - 1, 0, 2)
103 self.setWidget(current_offset - 1, 0, self.textarea)
104
105 current_offset += offset4
106 if not self.no_command:
107 self.command = HorizontalPanel() 112 self.command = HorizontalPanel()
108 self.command.addStyleName("marginAuto") 113 self.command.addStyleName("marginAuto")
109 self.command.add(Button("Cancel", listener=self.cancelWithoutSaving)) 114 self.command.add(Button("Cancel", lambda: self.edit(True, True)))
110 if not self.no_sync_unibox: 115 self.command.add(Button("Update" if self.update_msg else "Send message", lambda: self.edit(False)))
111 self.command.add(Button("Back to quick box", listener=self.closeAndSave)) 116 self.getFlexCellFormatter().setColSpan(self.command_offset, 0, 2)
112 self.command.add(Button("Update" if self.update_msg else "Send message", 117 self.setWidget(self.command_offset, 0, self.command)
113 listener=self.__close if (self.update_msg or self.no_recipient) else self.sendMessage)) 118
114 self.getFlexCellFormatter().setColSpan(current_offset - 1, 0, 2) 119 def setToolBar(self, syntax):
115 self.setWidget(current_offset - 1, 0, self.command)
116
117 @classmethod
118 def getOrCreate(cls, host, parent=None, onCloseCallback=None):
119 """Get or create the richtext editor associated to that host.
120 Add it to parent if parent is not None, otherwise display it
121 in a popup dialog. Information are saved for later the widget
122 to be also automatically removed from its parent, or the
123 popup to be closed.
124 @param host: the host
125 @param parent: parent panel (or None to display in a popup).
126 @return: the RichTextEditor instance if parent is not None,
127 otherwise a popup DialogBox containing the RichTextEditor.
128 """
129 if not hasattr(host, 'richtext'):
130 host.richtext = RichTextEditor(host, parent, onCloseCallback)
131
132 def add(widget, parent):
133 if widget.getParent() is not None:
134 if widget.getParent() != parent:
135 widget.removeFromParent()
136 parent.add(widget)
137 else:
138 parent.add(widget)
139 widget.setVisible(True)
140
141 if parent is None:
142 if not hasattr(host.richtext, 'popup'):
143 host.richtext.popup = DialogBox(autoHide=False, centered=True)
144 host.richtext.popup.setHTML("Compose your message")
145 host.richtext.popup.add(host.richtext)
146 add(host.richtext, host.richtext.popup)
147 host.richtext.popup.center()
148 else:
149 add(host.richtext, parent)
150 host.richtext.syncFromUniBox()
151 return host.richtext.popup if parent is None else host.richtext
152
153 def setVisible(self, visible):
154 """Called each time the widget is displayed, after creation or after having been hidden."""
155 FlexTable.setVisible(self, visible)
156 if visible:
157 self.host.bridge.call('asyncGetParamA', self.setToolBar, composition.PARAM_NAME_SYNTAX, composition.PARAM_KEY_COMPOSITION) or self.setToolBar(None)
158
159 def __close(self, result=SAVE):
160 """Remove the widget from parent or close the popup."""
161 if not self.no_close:
162 if self._parent is None:
163 self.popup.hide()
164 else:
165 self.setVisible(False)
166 if self._on_close_callback is not None:
167 self._on_close_callback(result)
168
169 def setToolBar(self, _format):
170 """This method is called asynchronously after the parameter 120 """This method is called asynchronously after the parameter
171 holding the rich text format is retrieved. It is called at 121 holding the rich text syntax is retrieved. It is called at
172 each opening of the rich text editor because the user may 122 each call of self.edit(True) because the user may
173 have change his setting since the last time.""" 123 have change his setting since the last time."""
174 if _format is None or _format not in composition.RICH_FORMATS.keys(): 124 if syntax is None or syntax not in composition.RICH_SYNTAXES.keys():
175 _format = composition.RICH_FORMATS.keys()[0] 125 syntax = composition.RICH_SYNTAXES.keys()[0]
176 if hasattr(self, "_format") and self._format == _format: 126 if hasattr(self, "toolbar") and self.toolbar.syntax == syntax:
177 return 127 self.toolbar.setVisible(True)
178 self._format = _format
179 count = 0 128 count = 0
180 for _format in composition.RICH_FORMATS.keys() if self._debug else [self._format]: 129 for syntax in composition.RICH_SYNTAXES.keys() if self._debug else [syntax]:
181 toolbar = HorizontalPanel() 130 self.toolbar = HorizontalPanel()
182 toolbar.addStyleName('richTextToolbar') 131 self.toolbar.syntax = syntax
183 for key in composition.RICH_FORMATS[_format].keys(): 132 self.toolbar.addStyleName(self.style['toolbar'])
184 self.addToolbarButton(toolbar, _format, key) 133 for key in composition.RICH_SYNTAXES[syntax].keys():
185 label = Label("Format: %s" % _format) 134 self.addToolbarButton(syntax, key)
186 label.addStyleName("richTextFormatLabel") 135 label = Label(_("Syntax: %s") % syntax)
187 toolbar.add(label) 136 label.addStyleName("richTextSyntaxLabel")
188 self.getFlexCellFormatter().setColSpan(self.offset_toolbar + count, 0, 2) 137 self.toolbar.add(label)
189 self.setWidget(self.offset_toolbar + count, 0, toolbar) 138 self.getFlexCellFormatter().setColSpan(self.toolbar_offset + count, 0, 2)
139 self.setWidget(self.toolbar_offset + count, 0, self.toolbar)
190 count += 1 140 count += 1
191 141
192 @property 142 def addToolbarButton(self, syntax, key):
193 def format(self):
194 """Get the current text format"""
195 return self._format if hasattr(self, '_format') else None
196
197 def addToolbarButton(self, toolbar, _format, key):
198 """Add a button with the defined parameters.""" 143 """Add a button with the defined parameters."""
199 button = Button('<img src="%s" class="richTextIcon" />' % 144 button = Button('<img src="%s" class="richTextIcon" />' %
200 composition.RICH_BUTTONS[key]["icon"]) 145 composition.RICH_BUTTONS[key]["icon"])
201 button.setTitle(composition.RICH_BUTTONS[key]["tip"]) 146 button.setTitle(composition.RICH_BUTTONS[key]["tip"])
202 button.addStyleName('richTextToolButton') 147 button.addStyleName('richTextToolButton')
203 toolbar.add(button) 148 self.toolbar.add(button)
204 149
205 def button_callback(): 150 def button_callback():
206 """Generic callback for a toolbar button.""" 151 """Generic callback for a toolbar button."""
207 text = self.textarea.getText() 152 text = self.textarea.getText()
208 cursor_pos = self.textarea.getCursorPos() 153 cursor_pos = self.textarea.getCursorPos()
209 selection_length = self.textarea.getSelectionLength() 154 selection_length = self.textarea.getSelectionLength()
210 infos = composition.RICH_FORMATS[_format][key] 155 infos = composition.RICH_SYNTAXES[syntax][key]
211 if selection_length == 0: 156 if selection_length == 0:
212 middle_text = infos[1] 157 middle_text = infos[1]
213 else: 158 else:
214 middle_text = text[cursor_pos:cursor_pos + selection_length] 159 middle_text = text[cursor_pos:cursor_pos + selection_length]
215 self.textarea.setText(text[:cursor_pos] 160 self.textarea.setText(text[:cursor_pos]
220 self.textarea.setCursorPos(cursor_pos + len(infos[0]) + len(middle_text)) 165 self.textarea.setCursorPos(cursor_pos + len(infos[0]) + len(middle_text))
221 self.textarea.setFocus(True) 166 self.textarea.setFocus(True)
222 167
223 button.addClickListener(button_callback) 168 button.addClickListener(button_callback)
224 169
225 def syncFromUniBox(self): 170 def getContent(self):
171 assert(hasattr(self, 'textarea'))
172 assert(hasattr(self, 'toolbar'))
173 content = {'text': self.strproc(self.textarea.getText()), 'syntax': self.toolbar.syntax}
174 if hasattr(self, 'title_panel'):
175 content.update({'title': self.strproc(self.title_panel.getText())})
176 return content
177
178 def edit(self, edit=False, abort=False, sync=False):
179 """
180 Remark: the editor must be visible before you call this method.
181 @param edit: set to True to edit the content or False to only display it
182 @param abort: set to True to cancel the edition and loose the changes.
183 If edit and abort are both True, self.abortEdition can be used to ask for a
184 confirmation. When edit is False and abort is True, abortion is actually done.
185 @param sync: set to True to cancel the edition after the content has been saved somewhere else
186 """
187 self.refresh(edit)
188 BaseTextEditor.edit(self, edit, abort, sync)
189 if (edit and abort) or sync:
190 return
191
192 # the following must NOT be done at each UI refresh!
193 content = self._original_content
194 if edit:
195 def getParamCb(syntax):
196 # set the editable text in the current user-selected syntax
197 def syntaxConvertCb(text=None):
198 if text is not None:
199 # Important: this also update self._original_content
200 content.update({'text': text})
201 content.update({'syntax': syntax})
202 self.textarea.setText(content['text'])
203 if hasattr(self, 'title_panel') and 'title' in content:
204 self.title_panel.setText(content['title'])
205 self.title_panel.setStackVisible(0, content['title'] != '')
206 if content['text'] and content['syntax'] != syntax:
207 self.host.bridge.call('syntaxConvert', syntaxConvertCb, content['text'], content['syntax'])
208 else:
209 syntaxConvertCb()
210 self.setToolBar(syntax)
211 self.host.bridge.call('asyncGetParamA', getParamCb, composition.PARAM_NAME_SYNTAX, composition.PARAM_KEY_COMPOSITION) or self.setToolBar(None)
212 else:
213 if not self.initialized:
214 # set the display text in XHTML only during init because a new MicroblogEntry instance is created after each modification
215 text = content['text']
216 if 'title' in content and content['title']:
217 text = '<h1>%s</h1>%s' % (content['title'], content['text'])
218 self.display.setContent({'text': text})
219 self.display.edit(False)
220
221 def setFocus(self, focus):
222 self.textarea.setFocus(focus)
223
224 def abortEdition(self, content):
225 """Ask for confirmation before closing the dialog."""
226 def confirm_cb(answer):
227 if answer:
228 self.edit(False, True)
229 _dialog = ConfirmDialog(confirm_cb, text="Do you really want to %s?" % ("cancel your changes" if self.update_msg else "cancel this message"))
230 _dialog.show()
231
232
233 class RichMessageEditor(RichTextEditor):
234 """Use the rich text editor for sending messages with extended addressing.
235 Recipient panels are on top and data may be synchronized from/to the unibox."""
236
237 @classmethod
238 def getOrCreate(cls, host, parent=None, callback=None):
239 """Get or create the message editor associated to that host.
240 Add it to parent if parent is not None, otherwise display it
241 in a popup dialog.
242 @param host: the host
243 @param parent: parent panel (or None to display in a popup).
244 @return: the RichTextEditor instance if parent is not None,
245 otherwise a popup DialogBox containing the RichTextEditor.
246 """
247 if not hasattr(host, 'richtext'):
248 modifiedCb = lambda content: True
249
250 def afterEditCb(content):
251 if hasattr(host.richtext, 'popup'):
252 host.richtext.popup.hide()
253 else:
254 host.richtext.setVisible(False)
255 callback()
256 host.richtext.initialized = False
257 options = ['no_title']
258 style = {'main': 'richMessageEditor', 'textarea': 'richMessageArea'}
259 host.richtext = RichMessageEditor(host, None, modifiedCb, afterEditCb, options, style)
260
261 def add(widget, parent):
262 if widget.getParent() is not None:
263 if widget.getParent() != parent:
264 widget.removeFromParent()
265 parent.add(widget)
266 else:
267 parent.add(widget)
268 widget.setVisible(True)
269 widget.edit(True)
270
271 if parent is None:
272 if not hasattr(host.richtext, 'popup'):
273 host.richtext.popup = DialogBox(autoHide=False, centered=True)
274 host.richtext.popup.setHTML("Compose your message")
275 host.richtext.popup.add(host.richtext)
276 add(host.richtext, host.richtext.popup)
277 host.richtext.popup.center()
278 else:
279 add(host.richtext, parent)
280 return host.richtext.popup if parent is None else host.richtext
281
282 def _prepareUI(self, y_offset=0):
283 """Prepare the UI to host recipients panel, toolbar, text area...
284 @param y_offset: Y offset to start from (extra rows on top)"""
285 self.recipient_offset = y_offset
286 self.recipient_spacer_offset = self.recipient_offset + len(composition.RECIPIENT_TYPES)
287 RichTextEditor._prepareUI(self, self.recipient_spacer_offset + 1)
288
289 def refresh(self, edit=None):
290 """Refresh the UI between edition/display mode
291 @param edit: set to True to display the edition mode"""
292 if edit is None:
293 edit = hasattr(self, 'textarea') and self.textarea.getVisible()
294 RichTextEditor.refresh(self, edit)
295
296 for widget in ['recipient', 'recipient_spacer']:
297 if hasattr(self, widget):
298 getattr(self, widget).setVisible(edit)
299
300 if not edit:
301 return
302
303 if not hasattr(self, 'recipient'):
304 # recipient types sub-panels are automatically added by the manager
305 self.recipient = RecipientManager(self, self.recipient_offset)
306 self.recipient.createWidgets(title_format="%s: ")
307 self.recipient_spacer = HTML('')
308 self.recipient_spacer.setStyleName('recipientSpacer')
309 self.getFlexCellFormatter().setColSpan(self.recipient_spacer_offset, 0, 2)
310 self.setWidget(self.recipient_spacer_offset, 0, self.recipient_spacer)
311
312 if not hasattr(self, 'sync_button'):
313 self.sync_button = Button("Back to quick box", lambda: self.edit(True, sync=True))
314 self.command.insert(self.sync_button, 1)
315
316 def syncToEditor(self):
226 """Synchronize from unibox.""" 317 """Synchronize from unibox."""
318 def setContent(target, data):
319 if hasattr(self, 'recipient'):
320 self.recipient.setContacts({"To": [target]} if target else {})
321 self.setContent({'text': data if data else '', 'syntax': ''})
322 self.textarea.setText(data if data else '')
323 data, target = self.host.uni_box.getTargetAndData() if self.host.uni_box else (None, None)
324 setContent(target, data)
325
326 def __syncToUniBox(self, recipients=None, emptyText=False):
327 """Synchronize to unibox if a maximum of one recipient is set.
328 @return True if the sync could be done, False otherwise"""
227 if not self.host.uni_box: 329 if not self.host.uni_box:
228 return 330 return
229 data, target = self.host.uni_box.getTargetAndData() 331 setText = lambda: self.host.uni_box.setText("" if emptyText else self.textarea.getText())
230 if hasattr(self, 'recipient'):
231 self.recipient.setContacts({"To": [target]} if target else {})
232 self.textarea.setText(data if data else "")
233
234 def syncToUniBox(self, recipients=None, emptyText=False):
235 """Synchronize to unibox if a maximum of one recipient is set.
236 @return True if the sync could be done, False otherwise"""
237 def setText():
238 self.host.uni_box.setText("" if emptyText else self.textarea.getText())
239
240 if not self.host.uni_box:
241 return
242 if not hasattr(self, 'recipient'): 332 if not hasattr(self, 'recipient'):
243 setText() 333 setText()
244 return True 334 return True
245 if recipients is None: 335 if recipients is None:
246 recipients = self.recipient.getContacts() 336 recipients = self.recipient.getContacts()
255 if allowed < 0: 345 if allowed < 0:
256 return False 346 return False
257 # TODO: change this if later more then one recipients are allowed 347 # TODO: change this if later more then one recipients are allowed
258 target = recipients[key][0] 348 target = recipients[key][0]
259 setText() 349 setText()
260 from panels import ChatPanel, MicroblogPanel
261 if target == "": 350 if target == "":
262 return True 351 return True
263 if target.startswith("@"): 352 if target.startswith("@"):
264 _class = MicroblogPanel 353 _class = panels.MicroblogPanel
265 target = None if target == "@@" else target[1:] 354 target = None if target == "@@" else target[1:]
266 else: 355 else:
267 _class = ChatPanel 356 _class = panels.ChatPanel
268 self.host.getOrCreateLiberviaWidget(_class, target) 357 self.host.getOrCreateLiberviaWidget(_class, target)
269 return True 358 return True
270 359
271 def cancelWithoutSaving(self): 360 def syncFromEditor(self, content):
272 """Ask for confirmation before closing the dialog."""
273 if self.textarea.getText() == self.original_text:
274 self.__close(CANCEL)
275 return
276
277 def confirm_cb(answer):
278 if answer:
279 self.__close(CANCEL)
280
281 _dialog = ConfirmDialog(confirm_cb, text="Do you really want to %s?" % ("cancel your changes" if self.update_msg else "cancel this message"))
282 _dialog.show()
283
284 def closeAndSave(self):
285 """Synchronize to unibox and close the dialog afterward. Display 361 """Synchronize to unibox and close the dialog afterward. Display
286 a message and leave the dialog open if the sync was not possible.""" 362 a message and leave the dialog open if the sync was not possible."""
287 if self.syncToUniBox(): 363 if self.__syncToUniBox():
288 self.__close(SYNC_NOT_SAVE) 364 self._afterEditCb(content)
289 return 365 return
290 InfoDialog("Too many recipients", 366 InfoDialog("Too many recipients",
291 "A message with more than one direct recipient (To)," + 367 "A message with more than one direct recipient (To)," +
292 " or with any special recipient (Cc or Bcc), could not be" + 368 " or with any special recipient (Cc or Bcc), could not be" +
293 " stored in the quick box.\n\nPlease finish your composing" + 369 " stored in the quick box.\n\nPlease finish your composing" +
294 " in the rich text editor, and send your message directly" + 370 " in the rich text editor, and send your message directly" +
295 " from here.", Width="400px").center() 371 " from here.", Width="400px").center()
296 372
297 def sendMessage(self): 373 def edit(self, edit=True, abort=False, sync=False):
374 if not edit and not abort and not sync: # force sending message even when the text has not been modified
375 if not self.__sendMessage(): # message has not been sent (missing information), do nothing
376 return
377 RichTextEditor.edit(self, edit, abort, sync)
378
379 def __sendMessage(self):
298 """Send the message.""" 380 """Send the message."""
299 recipients = self.recipient.getContacts() 381 recipients = self.recipient.getContacts()
300 text = self.textarea.getText()
301 targets = [] 382 targets = []
302 for addr in recipients: 383 for addr in recipients:
303 for recipient in recipients[addr]: 384 for recipient in recipients[addr]:
304 if recipient.startswith("@"): 385 if recipient.startswith("@"):
305 targets.append(("PUBLIC", None, addr) if recipient == "@@" else ("GROUP", recipient[1:], addr)) 386 targets.append(("PUBLIC", None, addr) if recipient == "@@" else ("GROUP", recipient[1:], addr))
306 else: 387 else:
307 targets.append(("chat", recipient, addr)) 388 targets.append(("chat", recipient, addr))
308 # check that we actually have a message target and data 389 # check that we actually have a message target and data
390 text = self.textarea.getText()
309 if text == "" or len(targets) == 0: 391 if text == "" or len(targets) == 0:
310 InfoDialog("Missing information", 392 InfoDialog("Missing information",
311 "Some information are missing and the message hasn't been sent.", Width="400px").center() 393 "Some information are missing and the message hasn't been sent.", Width="400px").center()
312 return 394 return None
313 self.syncToUniBox(recipients, emptyText=True) 395 self.__syncToUniBox(recipients, emptyText=True)
314 self.host.send(targets, text, extra={'rich': text}) 396 extra = {'content_rich': text}
315 self.__close(SAVE) 397 if hasattr(self, 'title_panel'):
398 extra.update({'title': self.title_panel.getText()})
399 self.host.send(targets, text, extra=extra)
400 return True
316 401
317 402
318 class RecipientManager(ListManager): 403 class RecipientManager(ListManager):
319 """A manager for sub-panels to set the recipients for each recipient type.""" 404 """A manager for sub-panels to set the recipients for each recipient type."""
320 405