view browser_side/dialog.py @ 230:266e9678eec0

browser_side: added the flag REUSE_EXISTING_LIBERVIA_WIDGETS - do not create a new chat panel for contacts/groups if a similar one is found in the same tab - this goes with a modification of the methods to create new panels, especially arguments handling - improvement of the accepted groups list for MicroblogPanel (remove duplicates and keep sorted) Details for the new flag: # Set to true to not create a new LiberviaWidget when a similar one # already exist (i.e. a chat panel with the same target). Instead # the existing widget will be eventually removed from its parent # and added to new WidgetsPanel, or replaced to the expected # position if the previous and the new parent are the same.
author souliane <souliane@mailoo.org>
date Tue, 08 Oct 2013 13:57:35 +0200
parents 67e24c342e7f
children fab7aa366576
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
        """
        self.host = host
        self.callback = callback
        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()
        self.choose_button = Button("Choose", self.onChoose)
        if (nb_contact):
            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:
            if len(self.contacts_list.getSelectedValues()) == self.nb_contact:
                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'):
        """
        Dialog to confirm an action
        @param callback: method to call when contacts have been choosed
        """
        self.callback = callback
        DialogBox.__init__(self, centered=True)

        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'):
        GenericConfirmDialog.__init__(self, [HTML(text)], callback, title)


class GenericDialog(DialogBox):
    """Dialog which just show a widget and a close button"""

    def __init__(self, title, main_widget, callback=None, options=None):
        """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)
        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):
        GenericDialog.__init__(self, title, HTML(body), callback, options)

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)

        add_group_panel = HorizontalPanel()
        add_group_lb = Label('Add group:')
        add_group_tb = ExtTextBox(enter_cb = self.onGroupInput)
        add_group_bt = Button("add", lambda sender: self.onGroupInput(add_group_tb))
        add_group_panel.add(add_group_lb)
        add_group_panel.add(add_group_tb)
        add_group_panel.add(add_group_bt)
        main_panel.add(add_group_panel)

        button_panel = HorizontalPanel()
        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)

    def onGroupInput(self, sender):
        text = sender.getText()
        for index in xrange(0, self.list_box.getItemCount()):
            if text == self.list_box.getValue(index):
                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
        self.list_box.addItem(text)
        sender.setText('')
        self.list_box.setItemSelected(self.list_box.getItemCount()-1, "selected")


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)