Mercurial > libervia-web
view browser_side/dialog.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 | 19153af4f327 |
children | da0487f0a2e7 |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- """ Libervia: a Salut à Toi frontend Copyright (C) 2011, 2012, 2013 Jérôme Poisson <goffi@goffi.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 pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.PopupPanel import PopupPanel from pyjamas.ui.DialogBox import DialogBox from pyjamas.ui.ListBox import ListBox from pyjamas.ui.Button import Button from pyjamas.ui.TextBox import TextBox from pyjamas.ui.Label import Label from pyjamas.ui.HTML import HTML from pyjamas.ui.Frame import Frame from pyjamas.ui import HasAlignment from pyjamas.ui.KeyboardListener import KEY_ESCAPE, KEY_ENTER from pyjamas.ui.MouseListener import MouseWheelHandler from pyjamas import DOM from pyjamas import Window # List here the patterns that are not allowed in contact group names FORBIDDEN_PATTERNS_IN_GROUP = () class ContactsChooser(DialogBox): def __init__(self, host, callback, nb_contact=None, text='Please select contacts'): """ ContactsChooser allow to select one or several connected contacts @param host: SatWebFrontend instance @param callback: method to call when contacts have been choosed @param nb_contact: number of contacts that have to be selected, None for no limit If a tuple is given instead of an integer, nb_contact[0] is the minimal and nb_contact[1] is the maximal number of contacts to be chosen. """ self.host = host self.callback = callback if isinstance(nb_contact, tuple): if len(nb_contact) == 0: nb_contact = None elif len(nb_contact) == 1: nb_contact = (nb_contact[0], nb_contact[0]) elif nb_contact is not None: nb_contact = (nb_contact, nb_contact) if nb_contact is None: print "Need to select as many contacts as you want" else: print "Need to select between %d and %d contacts" % nb_contact self.nb_contact = nb_contact DialogBox.__init__(self, centered=True) content = VerticalPanel() content.setWidth('100%') self.contacts_list = ListBox() self.contacts_list.setVisibleItemCount(10) self.contacts_list.setMultipleSelect(True) self.contacts_list.setWidth("100%") self.contacts_list.setStyleName('contactsChooser') self.contacts_list.addChangeListener(self.onChange) content.add(self.contacts_list) button_panel = HorizontalPanel() button_panel.addStyleName("marginAuto") self.choose_button = Button("Choose", self.onChoose) if nb_contact and nb_contact[0] > 0: self.choose_button.setEnabled(False) button_panel.add(self.choose_button) button_panel.add(Button("Cancel", self.onCancel)) content.add(button_panel) self.setHTML(text) self.setWidget(content) def onChange(self, sender): if self.nb_contact: selected = len(self.contacts_list.getSelectedValues()) if selected >= self.nb_contact[0] and selected <= self.nb_contact[1]: self.choose_button.setEnabled(True) else: self.choose_button.setEnabled(False) def getContacts(self): """ Actually ask to choose the contacts """ self.contacts_list.clear() for contact in self.host.contact_panel.getConnected(): if contact not in [room.bare for room in self.host.room_list]: self.contacts_list.addItem(contact) self.show() def onChoose(self, sender): self.hide() self.callback(self.contacts_list.getSelectedValues()) def onCancel(self, sender): self.hide() class GenericConfirmDialog(DialogBox): def __init__(self, widgets, callback, title='Confirmation', **kwargs): """ Dialog to confirm an action @param callback: method to call when contacts have been choosed """ self.callback = callback DialogBox.__init__(self, centered=True, **kwargs) content = VerticalPanel() content.setWidth('100%') for wid in widgets: content.add(wid) button_panel = HorizontalPanel() button_panel.addStyleName("marginAuto") self.confirm_button = Button("OK", self.onConfirm) button_panel.add(self.confirm_button) button_panel.add(Button("Cancel", self.onCancel)) content.add(button_panel) self.setHTML(title) self.setWidget(content) def onConfirm(self, sender): self.hide() self.callback(True) def onCancel(self, sender): self.hide() self.callback(False) class ConfirmDialog(GenericConfirmDialog): def __init__(self, callback, text='Are you sure ?', title='Confirmation', **kwargs): GenericConfirmDialog.__init__(self, [HTML(text)], callback, title, **kwargs) class GenericDialog(DialogBox): """Dialog which just show a widget and a close button""" def __init__(self, title, main_widget, callback=None, options=None, **kwargs): """Simple notice dialog box @param title: HTML put in the header @param main_widget: widget put in the body @param callback: method to call on closing @param options: one or more of the following options: - NO_CLOSE: don't add a close button""" DialogBox.__init__(self, centered=True, **kwargs) self.callback = callback if not options: options = [] _body = VerticalPanel() _body.setSize('100%','100%') _body.setSpacing(4) _body.add(main_widget) _body.setCellWidth(main_widget, '100%') _body.setCellHeight(main_widget, '100%') if not 'NO_CLOSE' in options: _close_button = Button("Close", self.onClose) _body.add(_close_button) _body.setCellHorizontalAlignment(_close_button, HasAlignment.ALIGN_CENTER) self.setHTML(title) self.setWidget(_body) self.panel.setSize('100%', '100%') #Need this hack to have correct size in Gecko & Webkit def close(self): """Same effect as clicking the close button""" self.onClose(None) def onClose(self, sender): self.hide() if self.callback: self.callback() class InfoDialog(GenericDialog): def __init__(self, title, body, callback=None, options=None, **kwargs): GenericDialog.__init__(self, title, HTML(body), callback, options, **kwargs) class PopupPanelWrapper(PopupPanel): """This wrapper catch Escape event to avoid request cancellation by Firefox""" def onEventPreview(self, event): if event.type in ["keydown", "keypress", "keyup"] and event.keyCode == KEY_ESCAPE: #needed to prevent request cancellation in Firefox event.preventDefault() return PopupPanel.onEventPreview(self, event) class ExtTextBox(TextBox): """Extended TextBox""" def __init__(self, *args, **kwargs): if 'enter_cb' in kwargs: self.enter_cb = kwargs['enter_cb'] del kwargs['enter_cb'] TextBox.__init__(self, *args, **kwargs) self.addKeyboardListener(self) def onKeyUp(self, sender, keycode, modifiers): pass def onKeyDown(self, sender, keycode, modifiers): pass def onKeyPress(self, sender, keycode, modifiers): if self.enter_cb and keycode == KEY_ENTER: self.enter_cb(self) class GroupSelector(DialogBox): def __init__(self, top_widgets, initial_groups, selected_groups, ok_title="OK", ok_cb=None, cancel_cb=None): DialogBox.__init__(self, centered=True) main_panel = VerticalPanel() self.ok_cb = ok_cb self.cancel_cb = cancel_cb for wid in top_widgets: main_panel.add(wid) main_panel.add(Label('Select in which groups your contact is:')) self.list_box = ListBox() self.list_box.setMultipleSelect(True) self.list_box.setVisibleItemCount(5) self.setAvailableGroups(initial_groups) self.setGroupsSelected(selected_groups) main_panel.add(self.list_box) def cb(text): self.list_box.addItem(text) self.list_box.setItemSelected(self.list_box.getItemCount() - 1, "selected") main_panel.add(AddGroupPanel(initial_groups, cb)) button_panel = HorizontalPanel() button_panel.addStyleName("marginAuto") button_panel.add(Button(ok_title, self.onOK)) button_panel.add(Button("Cancel", self.onCancel)) main_panel.add(button_panel) self.setWidget(main_panel) def getSelectedGroups(self): """Return a list of selected groups""" return self.list_box.getSelectedValues() def setAvailableGroups(self, groups): _groups = list(set(groups)) _groups.sort() self.list_box.clear() for group in _groups: self.list_box.addItem(group) def setGroupsSelected(self, selected_groups): self.list_box.setItemTextSelection(selected_groups) def onOK(self, sender): self.hide() if self.ok_cb: self.ok_cb(self) def onCancel(self, sender): self.hide() if self.cancel_cb: self.cancel_cb(self) class AddGroupPanel(HorizontalPanel): def __init__(self, groups, cb=None): """ @param groups: list of the already existing groups """ HorizontalPanel.__init__(self) self.groups = groups self.add(Label('Add group:')) textbox = ExtTextBox(enter_cb=self.onGroupInput) self.add(textbox) self.add(Button("add", lambda sender: self.onGroupInput(textbox))) self.cb = cb def onGroupInput(self, sender): text = sender.getText() for group in self.groups: if text == group: Window.alert("The group '%s' already exists." % text) return for pattern in FORBIDDEN_PATTERNS_IN_GROUP: if pattern in text: Window.alert("The pattern '%s' is not allowed in group names." % pattern) return sender.setText('') self.groups.append(text) if self.cb is not None: self.cb(text) class WheelTextBox(TextBox, MouseWheelHandler): def __init__(self, *args, **kwargs): TextBox.__init__(self, *args, **kwargs) MouseWheelHandler.__init__(self) class IntSetter(HorizontalPanel): """This class show a bar with button to set an int value""" def __init__(self, label, value=0, value_max=None, visible_len=3): """initialize the intSetter @param label: text shown in front of the setter @param value: initial value @param value_max: limit value, None or 0 for unlimited""" HorizontalPanel.__init__(self) self.value = value self.value_max = value_max _label = Label(label) self.add(_label) self.setCellWidth(_label, "100%") minus_button = Button("-", self.onMinus) self.box = WheelTextBox() self.box.setVisibleLength(visible_len) self.box.setText(str(value)) self.box.addInputListener(self) self.box.addMouseWheelListener(self) plus_button = Button("+", self.onPlus) self.add(minus_button) self.add(self.box) self.add(plus_button) self.valueChangedListener = [] def addValueChangeListener(self, listener): self.valueChangedListener.append(listener) def removeValueChangeListener(self, listener): if listener in self.valueChangedListener: self.valueChangedListener.remove(listener) def _callListeners(self): for listener in self.valueChangedListener: listener(self.value) def setValue(self, value): """Change the value and fire valueChange listeners""" self.value = value self.box.setText(str(value)) self._callListeners() def onMinus(self, sender, step=1): self.value = max(0, self.value - step) self.box.setText(str(self.value)) self._callListeners() def onPlus(self, sender, step=1): self.value += step if self.value_max: self.value = min(self.value, self.value_max) self.box.setText(str(self.value)) self._callListeners() def onInput(self, sender): """Accept only valid integer && normalize print (no leading 0)""" try: self.value = int(self.box.getText()) if self.box.getText() else 0 except ValueError: pass if self.value_max: self.value = min(self.value, self.value_max) self.box.setText(str(self.value)) self._callListeners() def onMouseWheel(self, sender, velocity): if velocity > 0: self.onMinus(sender, 10) else: self.onPlus(sender, 10)