Mercurial > libervia-web
comparison browser_side/richtext.py @ 281:36ce989c73a5
browser_side: more customizable rich text editor
author | souliane <souliane@mailoo.org> |
---|---|
date | Mon, 09 Dec 2013 15:34:03 +0100 |
parents | 1ccdc34cfb60 |
children | ae3ec654836d |
comparison
equal
deleted
inserted
replaced
280:1ccdc34cfb60 | 281:36ce989c73a5 |
---|---|
27 from pyjamas.ui.Label import Label | 27 from pyjamas.ui.Label import Label |
28 from pyjamas.ui.FlexTable import FlexTable | 28 from pyjamas.ui.FlexTable import FlexTable |
29 from pyjamas.ui.HorizontalPanel import HorizontalPanel | 29 from pyjamas.ui.HorizontalPanel import HorizontalPanel |
30 from list_manager import ListManager | 30 from list_manager import ListManager |
31 from sat_frontends.tools import composition | 31 from sat_frontends.tools import composition |
32 import logging | |
33 | |
34 | |
35 # used for onCloseCallback | |
36 CANCEL, SYNC_NOT_SAVE, SAVE = xrange(0, 3) | |
32 | 37 |
33 | 38 |
34 class RichTextEditor(FlexTable): | 39 class RichTextEditor(FlexTable): |
35 """Panel for the rich text editor.""" | 40 """Panel for the rich text editor.""" |
36 | 41 |
37 def __init__(self, host, parent, onCloseCallback=None): | 42 def __init__(self, host, parent, onCloseCallback=None, options=()): |
38 """Fill the editor with recipients panel, toolbar, text area...""" | 43 """Fill the editor with recipients panel, toolbar, text area... |
44 @param host: the SatWebFrontend instance | |
45 @param parent: the parent panel | |
46 @param onCloseCallback: method to call when the dialog is closed | |
47 @param options: list of options: 'no_recipient', 'no_command', 'no_style'... | |
48 """ | |
39 | 49 |
40 # TODO: don't forget to comment this before commit | 50 # TODO: don't forget to comment this before commit |
41 self._debug = False | 51 self._debug = False |
42 | 52 |
43 # This must be done before FlexTable.__init__ because it is used by setVisible | 53 # This must be done before FlexTable.__init__ because it is used by setVisible |
44 self.host = host | 54 self.host = host |
45 | 55 |
46 offset1 = len(composition.RECIPIENT_TYPES) | 56 self.no_close = 'no_close' in options |
57 self.update_msg = 'update_msg' in options | |
58 self.no_recipient = 'no_recipient' in options | |
59 self.no_command = 'no_command' in options | |
60 self.no_sync_unibox = self.no_command or 'no_sync_unibox' in options | |
61 self.no_main_style = 'no_style' in options or 'no_main_style' in options | |
62 self.no_toolbar_style = 'no_style' in options or 'no_toolbar_style' in options | |
63 self.no_textarea_style = 'no_style' in options or 'no_textarea_style' in options | |
64 | |
65 offset1 = 0 if self.no_recipient else len(composition.RECIPIENT_TYPES) | |
47 offset2 = len(composition.RICH_FORMATS) if self._debug else 1 | 66 offset2 = len(composition.RICH_FORMATS) if self._debug else 1 |
48 FlexTable.__init__(self, offset1 + offset2 + 2, 2) | 67 offset3 = 0 if self.no_command else 1 |
49 self.addStyleName('richTextEditor') | 68 FlexTable.__init__(self, offset1 + offset2 + 1 + offset3, 2) |
69 if not self.no_main_style: | |
70 self.addStyleName('richTextEditor') | |
50 | 71 |
51 self._parent = parent | 72 self._parent = parent |
52 self._on_close_callback = onCloseCallback | 73 self._on_close_callback = onCloseCallback |
53 | 74 |
54 # recipient types sub-panels are automatically added by the manager | 75 if not self.no_recipient: |
55 self.recipient = RecipientManager(self) | 76 # recipient types sub-panels are automatically added by the manager |
56 self.recipient.createWidgets(title_format="%s: ") | 77 self.recipient = RecipientManager(self) |
78 self.recipient.createWidgets(title_format="%s: ") | |
57 | 79 |
58 # Rich text tool bar is automatically added by setVisible | 80 # Rich text tool bar is automatically added by setVisible |
59 | 81 |
60 self.textarea = TextArea() | 82 self.textarea = TextArea() |
61 self.textarea.addStyleName('richTextArea') | 83 if not self.no_textarea_style: |
62 | 84 self.textarea.addStyleName('richTextArea') |
63 self.command = HorizontalPanel() | |
64 self.command.addStyleName("marginAuto") | |
65 self.command.add(Button("Cancel", listener=self.cancelWithoutSaving)) | |
66 self.command.add(Button("Back to quick box", listener=self.closeAndSave)) | |
67 self.command.add(Button("Send message", listener=self.sendMessage)) | |
68 | 85 |
69 self.getFlexCellFormatter().setColSpan(offset1 + offset2, 0, 2) | 86 self.getFlexCellFormatter().setColSpan(offset1 + offset2, 0, 2) |
70 self.getFlexCellFormatter().setColSpan(offset1 + offset2 + 1, 0, 2) | |
71 self.setWidget(offset1 + offset2, 0, self.textarea) | 87 self.setWidget(offset1 + offset2, 0, self.textarea) |
72 self.setWidget(offset1 + offset2 + 1, 0, self.command) | 88 |
89 if not self.no_command: | |
90 self.command = HorizontalPanel() | |
91 self.command.addStyleName("marginAuto") | |
92 self.command.add(Button("Cancel", listener=self.cancelWithoutSaving)) | |
93 if not self.no_sync_unibox: | |
94 self.command.add(Button("Back to quick box", listener=self.closeAndSave)) | |
95 self.command.add(Button("Update" if self.update_msg else "Send message", | |
96 listener=self.__close if self.update_msg else self.sendMessage)) | |
97 self.getFlexCellFormatter().setColSpan(offset1 + offset2 + 1, 0, 2) | |
98 self.setWidget(offset1 + offset2 + 1, 0, self.command) | |
73 | 99 |
74 @classmethod | 100 @classmethod |
75 def getOrCreate(cls, host, parent=None, onCloseCallback=None): | 101 def getOrCreate(cls, host, parent=None, onCloseCallback=None): |
76 """Get or create the richtext editor associated to that host. | 102 """Get or create the richtext editor associated to that host. |
77 Add it to parent if parent is not None, otherwise display it | 103 Add it to parent if parent is not None, otherwise display it |
111 """Called each time the widget is displayed, after creation or after having been hidden.""" | 137 """Called each time the widget is displayed, after creation or after having been hidden.""" |
112 FlexTable.setVisible(self, visible) | 138 FlexTable.setVisible(self, visible) |
113 if visible: | 139 if visible: |
114 self.host.bridge.call('asyncGetParamA', self.setToolBar, composition.PARAM_NAME_SYNTAX, composition.PARAM_KEY_COMPOSITION) or self.setToolBar(None) | 140 self.host.bridge.call('asyncGetParamA', self.setToolBar, composition.PARAM_NAME_SYNTAX, composition.PARAM_KEY_COMPOSITION) or self.setToolBar(None) |
115 | 141 |
116 def __close(self): | 142 def __close(self, result=SAVE): |
117 """Remove the widget from parent or close the popup.""" | 143 """Remove the widget from parent or close the popup.""" |
118 if self._parent is None: | 144 if not self.no_close: |
119 self.popup.hide() | 145 if self._parent is None: |
120 else: | 146 self.popup.hide() |
121 self.setVisible(False) | 147 else: |
148 self.setVisible(False) | |
122 if self._on_close_callback is not None: | 149 if self._on_close_callback is not None: |
123 self._on_close_callback() | 150 self._on_close_callback(result) |
124 | 151 |
125 def setToolBar(self, _format): | 152 def setToolBar(self, _format): |
126 """This method is called asynchronously after the parameter | 153 """This method is called asynchronously after the parameter |
127 holding the rich text format is retrieved. It is called at | 154 holding the rich text format is retrieved. It is called at |
128 each opening of the rich text editor because the user may | 155 each opening of the rich text editor because the user may |
130 if _format is None or _format not in composition.RICH_FORMATS.keys(): | 157 if _format is None or _format not in composition.RICH_FORMATS.keys(): |
131 _format = composition.RICH_FORMATS.keys()[0] | 158 _format = composition.RICH_FORMATS.keys()[0] |
132 if hasattr(self, "_format") and self._format == _format: | 159 if hasattr(self, "_format") and self._format == _format: |
133 return | 160 return |
134 self._format = _format | 161 self._format = _format |
135 offset1 = len(composition.RECIPIENT_TYPES) | 162 offset1 = 0 if self.no_recipient else len(composition.RECIPIENT_TYPES) |
136 count = 0 | 163 count = 0 |
137 for _format in composition.RICH_FORMATS.keys() if self._debug else [self._format]: | 164 for _format in composition.RICH_FORMATS.keys() if self._debug else [self._format]: |
138 toolbar = HorizontalPanel() | 165 toolbar = HorizontalPanel() |
139 toolbar.addStyleName('richTextToolbar') | 166 if not self.no_toolbar_style: |
167 toolbar.addStyleName('richTextToolbar') | |
140 for key in composition.RICH_FORMATS[_format].keys(): | 168 for key in composition.RICH_FORMATS[_format].keys(): |
141 self.addToolbarButton(toolbar, _format, key) | 169 self.addToolbarButton(toolbar, _format, key) |
142 label = Label("Format: %s" % _format) | 170 label = Label("Format: %s" % _format) |
143 label.addStyleName("richTextFormatLabel") | 171 label.addStyleName("richTextFormatLabel") |
144 toolbar.add(label) | 172 toolbar.add(label) |
175 button.addClickListener(button_callback) | 203 button.addClickListener(button_callback) |
176 | 204 |
177 def syncFromUniBox(self): | 205 def syncFromUniBox(self): |
178 """Synchronize from unibox.""" | 206 """Synchronize from unibox.""" |
179 data, target = self.host.uni_box.getTargetAndData() | 207 data, target = self.host.uni_box.getTargetAndData() |
180 self.recipient.setContacts({"To": [target]} if target else {}) | 208 if hasattr(self, 'recipient'): |
209 self.recipient.setContacts({"To": [target]} if target else {}) | |
181 self.textarea.setText(data if data else "") | 210 self.textarea.setText(data if data else "") |
182 | 211 |
183 def syncToUniBox(self, recipients=None, emptyText=False): | 212 def syncToUniBox(self, recipients=None, emptyText=False): |
184 """Synchronize to unibox if a maximum of one recipient is set. | 213 """Synchronize to unibox if a maximum of one recipient is set. |
185 @return True if the sync could be done, False otherwise""" | 214 @return True if the sync could be done, False otherwise""" |
215 def setText(): | |
216 self.host.uni_box.setText("" if emptyText else self.textarea.getText()) | |
217 | |
218 if not hasattr(self, 'recipient'): | |
219 setText() | |
220 return True | |
186 if recipients is None: | 221 if recipients is None: |
187 recipients = self.recipient.getContacts() | 222 recipients = self.recipient.getContacts() |
188 target = "" | 223 target = "" |
189 # we could eventually allow more in the future | 224 # we could eventually allow more in the future |
190 allowed = 1 | 225 allowed = 1 |
195 allowed -= count | 230 allowed -= count |
196 if allowed < 0: | 231 if allowed < 0: |
197 return False | 232 return False |
198 # TODO: change this if later more then one recipients are allowed | 233 # TODO: change this if later more then one recipients are allowed |
199 target = recipients[key][0] | 234 target = recipients[key][0] |
200 self.host.uni_box.setText("" if emptyText else self.textarea.getText()) | 235 setText() |
201 from panels import ChatPanel, MicroblogPanel | 236 from panels import ChatPanel, MicroblogPanel |
202 if target == "": | 237 if target == "": |
203 return True | 238 return True |
204 if target.startswith("@"): | 239 if target.startswith("@"): |
205 _class = MicroblogPanel | 240 _class = MicroblogPanel |
209 self.host.getOrCreateLiberviaWidget(_class, target) | 244 self.host.getOrCreateLiberviaWidget(_class, target) |
210 return True | 245 return True |
211 | 246 |
212 def cancelWithoutSaving(self): | 247 def cancelWithoutSaving(self): |
213 """Ask for confirmation before closing the dialog.""" | 248 """Ask for confirmation before closing the dialog.""" |
249 if self.update_msg and self.original_text and self.textarea.getText() == self.original_text: | |
250 self.__close(CANCEL) | |
251 return | |
252 | |
214 def confirm_cb(answer): | 253 def confirm_cb(answer): |
215 if answer: | 254 if answer: |
216 self.__close() | 255 self.__close(CANCEL) |
217 | 256 |
218 _dialog = ConfirmDialog(confirm_cb, text="Do you really want to cancel this message?") | 257 _dialog = ConfirmDialog(confirm_cb, text="Do you really want to %s?" % ("cancel your changes" if self.update_msg else "cancel this message")) |
219 _dialog.show() | 258 _dialog.show() |
220 | 259 |
221 def closeAndSave(self): | 260 def closeAndSave(self): |
222 """Synchronize to unibox and close the dialog afterward. Display | 261 """Synchronize to unibox and close the dialog afterward. Display |
223 a message and leave the dialog open if the sync was not possible.""" | 262 a message and leave the dialog open if the sync was not possible.""" |
224 if self.syncToUniBox(): | 263 if self.syncToUniBox(): |
225 self.__close() | 264 self.__close(SYNC_NOT_SAVE) |
226 return | 265 return |
227 InfoDialog("Too many recipients", | 266 InfoDialog("Too many recipients", |
228 "A message with more than one direct recipient (To)," + | 267 "A message with more than one direct recipient (To)," + |
229 " or with any special recipient (Cc or Bcc), could not be" + | 268 " or with any special recipient (Cc or Bcc), could not be" + |
230 " stored in the quick box.\n\nPlease finish your composing" + | 269 " stored in the quick box.\n\nPlease finish your composing" + |
247 InfoDialog("Missing information", | 286 InfoDialog("Missing information", |
248 "Some information are missing and the message hasn't been sent.", Width="400px").center() | 287 "Some information are missing and the message hasn't been sent.", Width="400px").center() |
249 return | 288 return |
250 self.syncToUniBox(recipients, emptyText=True) | 289 self.syncToUniBox(recipients, emptyText=True) |
251 self.host.send(targets, text, extra={'rich': text}) | 290 self.host.send(targets, text, extra={'rich': text}) |
252 self.__close() | 291 self.__close(SAVE) |
253 | 292 |
254 | 293 |
255 class RecipientManager(ListManager): | 294 class RecipientManager(ListManager): |
256 """A manager for sub-panels to set the recipients for each recipient type.""" | 295 """A manager for sub-panels to set the recipients for each recipient type.""" |
257 | 296 |