Mercurial > libervia-web
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 |