Mercurial > libervia-web
view browser_side/richtext.py @ 254:28d3315a8003
browser_side: isolate the basic stuff of RecipientManager in a new class ListManager:
- renamed most occurences of "recipient" to "contact" and "recipient type" to "contact key" or "list"
- data to represent the lists and autocomplete values are parametrized
- UI elements styles are set by default but can be ovewritten by a sub-class
- popup menu for the list Button element has to be set with registerPopupMenuPanel
- richtext UI uses the definitions from sat.tool.frontends.composition
Know issues:
- drag and drop AutoCompleteTextBox corrupts the list of remaining autocomplete values
- selecting an autocomplete value with the mouse and not keybord is not working properly
author | souliane <souliane@mailoo.org> |
---|---|
date | Sat, 09 Nov 2013 09:38:17 +0100 |
parents | d4e73d9140af |
children | d3c734669577 |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- """ Libervia: a Salut à Toi frontend Copyright (C) 2013 Adrien Cossa <souliane@mailoo.org> This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ from dialog import ConfirmDialog from pyjamas.ui.TextArea import TextArea from pyjamas.ui.Button import Button from dialog import InfoDialog from pyjamas.ui.DialogBox import DialogBox from pyjamas.ui.Label import Label from pyjamas.ui.FlexTable import FlexTable from pyjamas.ui.HorizontalPanel import HorizontalPanel from list_manager import ListManager from sat.tools.frontends import composition class RichTextEditor(FlexTable): """Panel for the rich text editor.""" def __init__(self, host, parent, onCloseCallback=None): """Fill the editor with recipients panel, toolbar, text area...""" # TODO: don't forget to comment this before commit self._debug = False # This must be done before FlexTable.__init__ because it is used by setVisible self.host = host offset1 = len(composition.RECIPIENT_TYPES) offset2 = len(composition.RICH_FORMATS) if self._debug else 1 FlexTable.__init__(self, offset1 + offset2 + 2, 2) self.addStyleName('richTextEditor') self._parent = parent self._on_close_callback = onCloseCallback # recipient types sub-panels are automatically added by the manager self.recipient = RecipientManager(self) self.recipient.createWidgets(title_format="%s: ") # Rich text tool bar is automatically added by setVisible self.textarea = TextArea() self.textarea.addStyleName('richTextArea') self.command = HorizontalPanel() self.command.addStyleName("marginAuto") self.command.add(Button("Cancel", listener=self.cancelWithoutSaving)) self.command.add(Button("Back to quick box", listener=self.closeAndSave)) self.command.add(Button("Send message", listener=self.sendMessage)) self.getFlexCellFormatter().setColSpan(offset1 + offset2, 0, 2) self.getFlexCellFormatter().setColSpan(offset1 + offset2 + 1, 0, 2) self.setWidget(offset1 + offset2, 0, self.textarea) self.setWidget(offset1 + offset2 + 1, 0, self.command) @classmethod def getOrCreate(cls, host, parent=None, onCloseCallback=None): """Get or create the richtext editor associated to that host. Add it to parent if parent is not None, otherwise display it in a popup dialog. Information are saved for later the widget to be also automatically removed from its parent, or the popup to be closed. @param host: the host @param parent: parent panel (or None to display in a popup). @return: the RichTextEditor instance if parent is not None, otherwise a popup DialogBox containing the RichTextEditor. """ if not hasattr(host, 'richtext'): host.richtext = RichTextEditor(host, parent, onCloseCallback) def add(widget, parent): if widget.getParent() is not None: if widget.getParent() != parent: widget.removeFromParent() parent.add(widget) else: parent.add(widget) widget.setVisible(True) if parent is None: if not hasattr(host.richtext, 'popup'): host.richtext.popup = DialogBox(autoHide=False, centered=True) host.richtext.popup.setHTML("Compose your message") host.richtext.popup.add(host.richtext) add(host.richtext, host.richtext.popup) host.richtext.popup.center() else: add(host.richtext, parent) host.richtext.syncFromUniBox() return host.richtext.popup if parent is None else host.richtext def setVisible(self, kwargs): """Called each time the widget is displayed, after creation or after having been hidden.""" self.host.bridge.call('asyncGetParamA', self.setToolBar, composition.PARAM_NAME_SYNTAX, composition.PARAM_KEY_COMPOSITION) or self.setToolBar(None) FlexTable.setVisible(self, kwargs) def __close(self): """Remove the widget from parent or close the popup.""" if self._parent is None: self.popup.hide() else: self.setVisible(False) if self._on_close_callback is not None: self._on_close_callback() def setToolBar(self, _format): """This method is called asynchronously after the parameter holding the rich text format is retrieved. It is called at each opening of the rich text editor because the user may have change his setting since the last time.""" if _format is None or _format not in composition.RICH_FORMATS.keys(): _format = composition.RICH_FORMATS.keys()[0] if hasattr(self, "_format") and self._format == _format: return self._format = _format offset1 = len(composition.RECIPIENT_TYPES) count = 0 for _format in composition.RICH_FORMATS.keys() if self._debug else [self._format]: toolbar = HorizontalPanel() toolbar.addStyleName('richTextToolbar') for key in composition.RICH_FORMATS[_format].keys(): self.addToolbarButton(toolbar, _format, key) label = Label("Format: %s" % _format) label.addStyleName("richTextFormatLabel") toolbar.add(label) self.getFlexCellFormatter().setColSpan(offset1 + count, 0, 2) self.setWidget(offset1 + count, 0, toolbar) count += 1 def addToolbarButton(self, toolbar, _format, key): """Add a button with the defined parameters.""" button = Button('<img src="%s" class="richTextIcon" />' % composition.RICH_BUTTONS[key]["icon"]) button.setTitle(composition.RICH_BUTTONS[key]["tip"]) button.addStyleName('richTextToolButton') toolbar.add(button) def button_callback(): """Generic callback for a toolbar button.""" text = self.textarea.getText() cursor_pos = self.textarea.getCursorPos() selection_length = self.textarea.getSelectionLength() infos = composition.RICH_FORMATS[_format][key] if selection_length == 0: middle_text = infos[1] else: middle_text = text[cursor_pos:cursor_pos + selection_length] self.textarea.setText(text[:cursor_pos] + infos[0] + middle_text + infos[2] + text[cursor_pos + selection_length:]) self.textarea.setCursorPos(cursor_pos + len(infos[0]) + len(middle_text)) self.textarea.setFocus(True) button.addClickListener(button_callback) def syncFromUniBox(self): """Synchronize from unibox.""" data, target = self.host.uni_box.getTargetAndData() self.recipient.setContacts({"To": [target]} if target else {}) self.textarea.setText(data if data else "") def syncToUniBox(self, recipients=None): """Synchronize to unibox if a maximum of one recipient is set, and it is not set to for optional recipient type. That means synchronization is not done if more then one recipients are set or if a recipient is set to an optional type (Cc, Bcc). @return True if the sync could be done, False otherwise""" if recipients is None: recipients = self.recipient.getContacts() target = "" # we could eventually allow more in the future allowed = 1 for key in recipients: count = len(recipients[key]) if count == 0: continue allowed -= count if allowed < 0 or composition.RECIPIENT_TYPES[key]["optional"]: return False # TODO: change this if later more then one recipients are allowed target = recipients[key][0] self.host.uni_box.setText(self.textarea.getText()) from panels import ChatPanel, MicroblogPanel if target == "": return True if target.startswith("@"): _class = MicroblogPanel target = None if target == "@@" else target[1:] else: _class = ChatPanel self.host.getOrCreateLiberviaWidget(_class, target) return True def cancelWithoutSaving(self): """Ask for confirmation before closing the dialog.""" def confirm_cb(answer): if answer: self.__close() _dialog = ConfirmDialog(confirm_cb, text="Do you really want to cancel this message?") _dialog.show() def closeAndSave(self): """Synchronize to unibox and close the dialog afterward. Display a message and leave the dialog open if the sync was not possible.""" if self.syncToUniBox(): self.__close() return InfoDialog("Too many recipients", "A message with more than one direct recipient (To)," + " or with any special recipient (Cc or Bcc), could not be" + " stored in the quick box.\n\nPlease finish your composing" + " in the rich text editor, and send your message directly" + " from here.", Width="400px").center() def sendMessage(self): """Send the message.""" recipients = self.recipient.getContacts() if self.syncToUniBox(recipients): # also check that we actually have a message target and data if len(recipients["To"]) > 0 and self.textarea.getText() != "": from pyjamas.ui.KeyboardListener import KEY_ENTER self.host.uni_box.onKeyPress(self.host.uni_box, KEY_ENTER, None) self.__close() else: InfoDialog("Missing information", "Some information are missing and the message hasn't been sent," + " but it has been stored to the quick box instead.", Width="400px").center() return InfoDialog("Feature in development", "Sending a message to more the one recipient," + " to Cc or Bcc is not implemented yet!", Width="400px").center() class RecipientManager(ListManager): """A manager for sub-panels to set the recipients for each recipient type.""" def __init__(self, parent): # TODO: be sure we also display empty groups and disconnected contacts + their groups # store the full list of potential recipients (groups and contacts) list_ = [] list_.extend("@%s" % group for group in parent.host.contact_panel.getGroups()) list_.extend(contact for contact in parent.host.contact_panel.getContacts()) ListManager.__init__(self, parent, composition.RECIPIENT_TYPES, list_) self.registerPopupMenuPanel(entries=composition.RECIPIENT_TYPES, hide=lambda sender, key: self.__children[key]["panel"].isVisible(), callback=self.setContactPanelVisible)