view frontends/src/wix/xmlui.py @ 1106:e2e1e27a3680

frontends: XMLUI refactoring + dialogs: - there are now XMLUIPanel and XMLUIDialog both inheriting from XMLUIBase - following dialogs are managed: - MessageDialog - NoteDialog - ConfirmDialog - FileDialog - XMLUI creation is now made using xmlui.create(...) instead of instanciating directly XMLUI - classes must be registed in frontends - "parent" attribute renamed to "_xmlui_parent" to avoid name conflicts with frontends toolkits
author Goffi <goffi@goffi.org>
date Wed, 13 Aug 2014 14:48:49 +0200
parents 8e0072754413
children f91e7028e2c3
line wrap: on
line source

#!/usr/bin/python
# -*- coding: utf-8 -*-

# wix: a SAT frontend
# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 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 sat.core.i18n import _
import wx
from sat.core.log import getLogger
log = getLogger(__name__)
from sat_frontends.tools import xmlui
from sat_frontends.constants import Const as C


class EventWidget(object):
    """ Used to manage change event of  widgets """

    def _xmluiOnChange(self, callback):
        """ Call callback with widget as only argument """
        def change_cb(event):
            callback(self)
        self.Bind(self._xmlui_change_event, change_cb)


class WixWidget(object):
    _xmlui_proportion = 0


class ValueWidget(WixWidget):

    def _xmluiSetValue(self, value):
        self.SetValue(value)

    def _xmluiGetValue(self):
        return self.GetValue()


class EmptyWidget(WixWidget, xmlui.EmptyWidget, wx.Window):

    def __init__(self, _xmlui_parent):
        wx.Window.__init__(self, _xmlui_parent, -1)


class TextWidget(WixWidget, xmlui.TextWidget, wx.StaticText):

    def __init__(self, _xmlui_parent, value):
        wx.StaticText.__init__(self, _xmlui_parent, -1, value)


class LabelWidget(xmlui.LabelWidget, TextWidget):

    def __init__(self, _xmlui_parent, value):
        super(LabelWidget, self).__init__(_xmlui_parent, value+": ")


class JidWidget(xmlui.JidWidget, TextWidget):
    pass


class DividerWidget(WixWidget, xmlui.DividerWidget, wx.StaticLine):

    def __init__(self, _xmlui_parent, style='line'):
        wx.StaticLine.__init__(self, _xmlui_parent, -1)


class StringWidget(EventWidget, ValueWidget, xmlui.StringWidget, wx.TextCtrl):
    _xmlui_change_event = wx.EVT_TEXT

    def __init__(self, _xmlui_parent, value, read_only=False):
        style = wx.TE_READONLY if read_only else 0
        wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style)
        self._xmlui_proportion = 1


class PasswordWidget(EventWidget, ValueWidget, xmlui.PasswordWidget, wx.TextCtrl):
    _xmlui_change_event = wx.EVT_TEXT

    def __init__(self, _xmlui_parent, value, read_only=False):
        style = wx.TE_PASSWORD
        if read_only:
            style |= wx.TE_READONLY
        wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style)
        self._xmlui_proportion = 1


class TextBoxWidget(EventWidget, ValueWidget, xmlui.TextBoxWidget, wx.TextCtrl):
    _xmlui_change_event = wx.EVT_TEXT

    def __init__(self, _xmlui_parent, value, read_only=False):
        style = wx.TE_MULTILINE
        if read_only:
            style |= wx.TE_READONLY
        wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style)
        self._xmlui_proportion = 1


class BoolWidget(EventWidget, ValueWidget, xmlui.BoolWidget, wx.CheckBox):
    _xmlui_change_event = wx.EVT_CHECKBOX

    def __init__(self, _xmlui_parent, state, read_only=False):
        style = wx.CHK_2STATE
        if read_only:
            style |= wx.TE_READONLY
        wx.CheckBox.__init__(self, _xmlui_parent, -1, "", style=wx.CHK_2STATE)
        self.SetValue(state)
        self._xmlui_proportion = 1

    def _xmluiSetValue(self, value):
        self.SetValue(value == 'true')

    def _xmluiGetValue(self):
        return "true" if self.GetValue() else "false"


class ButtonWidget(EventWidget, WixWidget, xmlui.ButtonWidget, wx.Button):
    _xmlui_change_event = wx.EVT_BUTTON

    def __init__(self, _xmlui_parent, value, click_callback):
        wx.Button.__init__(self, _xmlui_parent, -1, value)
        self._xmlui_click_callback = click_callback
        _xmlui_parent.Bind(wx.EVT_BUTTON, lambda evt: click_callback(evt.GetEventObject()), self)
        self._xmlui_parent = _xmlui_parent

    def _xmluiOnClick(self, callback):
        self._xmlui_parent.Bind(wx.EVT_BUTTON, lambda evt: callback(evt.GetEventObject()), self)


class ListWidget(EventWidget, WixWidget, xmlui.ListWidget, wx.ListBox):
    _xmlui_change_event = wx.EVT_LISTBOX

    def __init__(self, _xmlui_parent, options, selected, flags):
        styles = wx.LB_MULTIPLE if not 'single' in flags else wx.LB_SINGLE
        wx.ListBox.__init__(self, _xmlui_parent, -1, choices=[option[1] for option in options], style=styles)
        self._xmlui_attr_map = {label: value for value, label in options}
        self._xmlui_proportion = 1
        self._xmluiSelectValues(selected)

    def _xmluiSelectValue(self, value):
        try:
            label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0]
        except IndexError:
            log.warning(_("Can't find value [%s] to select" % value))
            return
        for idx in xrange(self.GetCount()):
            self.SetSelection(idx, self.GetString(idx) == label)

    def _xmluiSelectValues(self, values):
        labels = [label for label, _value in self._xmlui_attr_map.items() if _value in values]
        for idx in xrange(self.GetCount()):
            self.SetSelection(idx, self.GetString(idx) in labels)

    def _xmluiGetSelectedValues(self):
        ret = []
        labels = [self.GetString(idx) for idx in self.GetSelections()]
        for label in labels:
            ret.append(self._xmlui_attr_map[label])
        return ret

    def _xmluiAddValues(self, values, select=True):
        selected = self._xmluiGetSelectedValues()
        for value in values:
            if value not in self._xmlui_attr_map.values():
                wx.ListBox.Append(self, value)
                self._xmlui_attr_map[value] = value
            if value not in selected:
                selected.append(value)
        self._xmluiSelectValues(selected)


class WixContainer(object):
    _xmlui_proportion = 1

    def _xmluiAppend(self, widget):
        self.sizer.Add(widget, self._xmlui_proportion, flag=wx.EXPAND)


class AdvancedListContainer(WixContainer, xmlui.AdvancedListContainer, wx.ScrolledWindow):

    def __init__(self, _xmlui_parent, columns, selectable='no'):
        wx.ScrolledWindow.__init__(self, _xmlui_parent)
        self._xmlui_selectable = selectable != 'no'
        if selectable:
            columns += 1
        self.sizer = wx.FlexGridSizer(cols=columns)
        self.SetSizer(self.sizer)
        self._xmlui_select_cb = None
        self._xmlui_select_idx = None
        self._xmlui_select_widgets = []

    def _xmluiAddRow(self, idx):
        # XXX: select_button is a Q&D way to implement row selection
        # FIXME: must be done properly
        if not self._xmlui_selectable:
            return
        select_button = wx.Button(self, wx.ID_OK, label=_("select"))
        self.sizer.Add(select_button)
        def click_cb(event, idx=idx):
            cb = self._xmlui_select_cb
            self._xmlui_select_idx = idx
            # TODO: fill self._xmlui_select_widgets
            if cb is not None:
                cb(self)
            event.Skip()
        self.Bind(wx.EVT_BUTTON, click_cb)

    def _xmluiGetSelectedWidgets(self):
        return self._xmlui_select_widgets

    def _xmluiGetSelectedIndex(self):
        return self._xmlui_select_idx

    def _xmluiOnSelect(self, callback):
        self._xmlui_select_cb = callback

class PairsContainer(WixContainer, xmlui.PairsContainer, wx.Panel):

    def __init__(self, _xmlui_parent):
        wx.Panel.__init__(self, _xmlui_parent)
        self.sizer = wx.FlexGridSizer(cols=2)
        self.sizer.AddGrowableCol(1) #The growable column need most of time to be the right one in pairs
        self.SetSizer(self.sizer)


class TabsContainer(WixContainer, xmlui.TabsContainer, wx.Notebook):

    def __init__(self, _xmlui_parent):
        wx.Notebook.__init__(self, _xmlui_parent, -1, style=wx.NB_LEFT if self._xmlui_main.type=='param' else 0)

    def _xmluiAddTab(self, label):
        tab_panel = wx.Panel(self, -1)
        tab_panel.sizer = wx.BoxSizer(wx.VERTICAL)
        tab_panel.SetSizer(tab_panel.sizer)
        self.AddPage(tab_panel, label)
        VerticalContainer._xmluiAdapt(tab_panel)
        return tab_panel


class VerticalContainer(WixContainer, xmlui.VerticalContainer, wx.Panel):

    def __init__(self, _xmlui_parent):
        wx.Panel.__init__(self, _xmlui_parent)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.sizer)


## Dialogs ##


class WixDialog(object):

    def __init__(self, _xmlui_parent, level):
        self.host = _xmlui_parent.host
        self.ok_cb = None
        self.cancel_cb = None
        if level == C.XMLUI_DATA_LVL_INFO:
            self.flags = wx.ICON_INFORMATION
        elif level == C.XMLUI_DATA_LVL_ERROR:
            self.flags = wx.ICON_ERROR
        else:
            self.flags = wx.ICON_INFORMATION
            log.warning(_("Unmanaged dialog level: %s") % level)

    def _xmluiShow(self):
        answer = self.ShowModal()
        if answer == wx.ID_YES or answer == wx.ID_OK:
            self._xmluiValidated()
        else:
            self._xmluiCancelled()

    def _xmluiClose(self):
        self.Destroy()


class MessageDialog(WixDialog, xmlui.MessageDialog, wx.MessageDialog):

    def __init__(self, _xmlui_parent, title, message, level):
        WixDialog.__init__(self, _xmlui_parent, level)
        xmlui.MessageDialog.__init__(self, _xmlui_parent)
        self.flags |= wx.OK
        wx.MessageDialog.__init__(self, _xmlui_parent.host, message, title, style = self.flags)


class NoteDialog(xmlui.NoteDialog, MessageDialog):
    # TODO: separate NoteDialog
    pass


class ConfirmDialog(WixDialog, xmlui.ConfirmDialog, wx.MessageDialog):

    def __init__(self, _xmlui_parent, title, message, level, buttons_set):
        WixDialog.__init__(self, _xmlui_parent, level)
        xmlui.ConfirmDialog.__init__(self, _xmlui_parent)
        if buttons_set == C.XMLUI_DATA_BTNS_SET_YESNO:
            self.flags |= wx.YES_NO
        else:
            self.flags |= wx.OK | wx.CANCEL
        wx.MessageDialog.__init__(self, _xmlui_parent.host, message, title, style = self.flags)


class FileDialog(WixDialog, xmlui.FileDialog, wx.FileDialog):

    def __init__(self, _xmlui_parent, title, message, level, filetype):
        # TODO: message and filetype are not managed yet
        WixDialog.__init__(self, _xmlui_parent, level)
        self.flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT # FIXME: use the legacy flags, but must manage cases like dir or open
        xmlui.FileDialog.__init__(self, _xmlui_parent)
        wx.FileDialog.__init__(self, _xmlui_parent.host, title, style = self.flags)

    def _xmluiShow(self):
        answer = self.ShowModal()
        if answer == wx.ID_OK:
            self._xmluiValidated({'path': self.GetPath()})
        else:
            self._xmluiCancelled()


class GenericFactory(object):

    def __getattr__(self, attr):
        if attr.startswith("create"):
            cls = globals()[attr[6:]]
            return cls


class WidgetFactory(GenericFactory):

    def __getattr__(self, attr):
        if attr.startswith("create"):
            cls = GenericFactory.__getattr__(self, attr)
            cls._xmlui_main = self._xmlui_main
            return cls


class XMLUIPanel(xmlui.XMLUIPanel, wx.Frame):
    """Create an user interface from a SàT XML"""
    widget_factory = WidgetFactory()

    def __init__(self, host, parsed_xml, title=None, flags = None,):
        self.widget_factory._xmlui_main = self
        xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags)

    def constructUI(self, parsed_dom):
        style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in self.flags else wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, None, style=style)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(self.sizer)

        def postTreat():
            if self.title:
                self.SetTitle(self.title)

            if self.type == 'form':
                dialogButtons = wx.StdDialogButtonSizer()
                submitButton = wx.Button(self.main_cont,wx.ID_OK, label=_("Submit"))
                dialogButtons.AddButton(submitButton)
                self.main_cont.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton)
                if not 'NO_CANCEL' in self.flags:
                    cancelButton = wx.Button(self.main_cont,wx.ID_CANCEL)
                    dialogButtons.AddButton(cancelButton)
                    self.main_cont.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton)
                dialogButtons.Realize()
                self.main_cont.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL)

            self.sizer.Add(self.main_cont, 1, flag=wx.EXPAND)
            self.sizer.Fit(self)
            self.Show()

        super(XMLUIPanel, self).constructUI(parsed_dom, postTreat)
        if not 'NO_CANCEL' in self.flags:
            self.Bind(wx.EVT_CLOSE, self.onClose, self)
        self.MakeModal()

    def _xmluiClose(self):
        self.MakeModal(False)
        self.Destroy()

    ###events

    def onParamChange(self, ctrl):
        super(XMLUIPanel, self).onParamChange(ctrl)

    def onFormSubmitted(self, event):
        """Called when submit button is clicked"""
        button = event.GetEventObject()
        super(XMLUIPanel, self).onFormSubmitted(button)

    def onClose(self, event):
        """Close event: we have to send the form."""
        log.debug(_("close"))
        if self.type == 'param':
            self.onSaveParams()
        else:
            self._xmluiClose()
        event.Skip()


class XMLUIDialog(xmlui.XMLUIDialog):
    dialog_factory = WidgetFactory()


xmlui.registerClass(xmlui.CLASS_PANEL, XMLUIPanel)
xmlui.registerClass(xmlui.CLASS_DIALOG, XMLUIDialog)
create = xmlui.create