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