# GNU Enterprise Forms - QT3 UI Driver - UI specific dialogs
#
# Copyright 2001-2009 Free Software Foundation
#
# This file is part of GNU Enterprise
#
# GNU Enterprise is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 3, or (at your option) any later version.
#
# GNU Enterprise 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with program; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
# $Id: dialogs.py 9956 2009-10-11 18:54:57Z reinhard $
"""
Implementation of some common dialogs for the QT3 driver
"""

import qt

from gnue.forms import VERSION

__all__ = ['ExceptionDialog', 'InputDialog', 'AboutBox']

# =============================================================================
# Exception display dialog
# =============================================================================

class ExceptionDialog(qt.QDialog):
    """
    Dialog for displaying exceptions.  The traceback of the exception is
    available via a detail button.
    """

    _TITLE = {'system'     : u_("GNUe Internal System Error"),
              'admin'      : u_("GNUe Unexpected Error"),
              'application': u_("GNUe Application Error")}

    _FORMAT = {
       'system': u_("An unexpected internal error has occured:\n%s.\n"
                    "This means you have found a bug in GNU Enterprise. "
                    "Please report it to gnue-dev@gnu.org"),
       'admin': u_("An unexpected error has occured:\n%s.\n"
                   "Please contact your system administrator."),
       'application': u_("An unexpected error has occured:\n%s.\n"
                         "Please contact your system administrator.")}

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__ (self, group, name, message, detail):
        
        qt.QDialog.__init__(self)

        self.vbox = qt.QVBoxLayout(self, 4)

        hbox = qt.QHBoxLayout(5)
        self.vbox.addLayout(hbox, 0)

        pxm = qt.QMessageBox.standardIcon(qt.QMessageBox.Critical)
        self.icon = qt.QLabel('', self)
        self.icon.setPixmap(pxm)
        hbox.addWidget(self.icon, 0)

        self.msg = qt.QLabel(self._FORMAT.get(group) % message, self)
        hbox.addWidget(self.msg, 1)

        self.ext = qt.QMultiLineEdit(self)
        self.ext.setText(detail)
        self.ext.setReadOnly(True)

        self._ext_visible = False
        self.setExtension(self.ext)
        self.setOrientation(qt.Qt.Vertical)

        sep = qt.QFrame(self, "separatorline")
        sep.setFrameStyle(qt.QFrame.HLine | qt.QFrame.Sunken)
        self.vbox.addWidget(sep, 0)

        bbox = qt.QHBoxLayout()
        bbox.addStretch(1)

        self.cbtn = qt.QPushButton(u_("Close"), self)
        self.connect(self.cbtn, qt.SIGNAL('clicked()'),
                self.reject)
        bbox.addWidget(self.cbtn, 0)

        self.det = qt.QPushButton(u_(">> Detail"), self)
        self.connect(self.det, qt.SIGNAL('clicked()'),
                self.__toggle_detail)
        bbox.addWidget(self.det, 0)

        self.vbox.addLayout(bbox, 0)

        self.setCaption(self._TITLE.get(group, u'Error'))

    # -------------------------------------------------------------------------

    def __toggle_detail(self):

        view = not self._ext_visible
        self._ext_visible = view
        if view:
            self.det.setText (u_('<< Detail'))
        else:
            self.det.setText (u_('>> Detail'))

        self.showExtension(view)


# =============================================================================
# AboutDialog
# =============================================================================

class AboutBox(qt.QDialog):
    """
    Displays an about dialog for the current application
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self, parent, name, version, author, descr, icon):
        """
        @param parent: QT widget to be the parent of the dialog
        @param name: name of the application
        @param version: version of the application
        @param author: author of the application
        @param description: text describing the form
        @param icon: path to the appication's icon
        """

        qt.QDialog.__init__(self, parent)

        current = self.font()
        small = qt.QFont(current.family(), current.pointSize()-1)
        bold = qt.QFont(current.family(), current.pointSize(), qt.QFont.Bold)

        self.setCaption(u_("About %s") % name)

        vbox = qt.QVBoxLayout(self, 4)
        vbox.setMargin(12)

        if icon:
            label = qt.QLabel(self)
            label.setPixmap(qt.QPixmap(icon))
            vbox.addWidget(label, 0, qt.Qt.AlignHCenter)

        label = qt.QLabel(name, self)
        label.setFont(bold)
        vbox.addWidget(label, 0, qt.Qt.AlignHCenter)

        if version:
            label = qt.QLabel(u_("Version: %s") % version, self)
            label.setFont(small)
            vbox.addWidget(label, 0, qt.Qt.AlignHCenter)

        if author:
            label = qt.QLabel(author, self)
            vbox.addWidget(label, 0, qt.Qt.AlignHCenter)

        if descr:
            label = qt.QLabel(descr, self)
            vbox.addWidget(label, 1, qt.Qt.AlignHCenter | qt.Qt.WordBreak)

        sep = qt.QFrame(self, "separatorline")
        sep.setFrameStyle(qt.QFrame.HLine | qt.QFrame.Sunken)
        vbox.addWidget(sep, 0)

        label = qt.QLabel("GNU Enterprise Forms", self)
        label.setFont(bold)
        vbox.addWidget(label, 0, qt.Qt.AlignHCenter)

        vers = "Version %s / %s %s" % (VERSION, 'QT', qt.qVersion())
        label = qt.QLabel(vers, self)
        label.setFont(small)
        vbox.addWidget(label, 0, qt.Qt.AlignHCenter)

        hbox = qt.QHBoxLayout(4)
        hbox.addStretch(1)

        ok = qt.QPushButton(u_("Ok"), self)
        self.connect(ok, qt.SIGNAL('clicked()'), self.accept)
        hbox.addWidget(ok, 0)

        hbox.addStretch(1)

        vbox.addLayout(hbox)



# =============================================================================
# LineEdit widget for InputDialog
# =============================================================================

class IDLineEdit(qt.QLineEdit):
    """
    LineEdit control for InputDialogs which automatically updates the input
    data dictionary on changes of the text.

    Signals:

    on_enter(QWidget): Emitted when the Enter/Return key was hit
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self, name, default, elements, password, ddict, parent):

        qt.QLineEdit.__init__(self, default or '', parent, str(name))

        if password:
            self.setEchoMode(qt.QLineEdit.Password)

        if elements and elements [0][0]:
            qt.QToolTip.add(self, elements[0][0])

        self.ddict = ddict
        self.ddict[name] = default or ''

        self.connect(self, qt.SIGNAL('textChanged(const QString &)'),
                self.__on_text_change)
        self.connect(self, qt.SIGNAL('returnPressed()'),
                self.__on_return_pressed)

    # -------------------------------------------------------------------------
    # Event Slots
    # -------------------------------------------------------------------------

    def __on_text_change(self, text):
        self.ddict[self.name()] = unicode(text)

    # -------------------------------------------------------------------------

    def __on_return_pressed(self):
        self.emit(qt.PYSIGNAL('on_enter(QWidget)'), (self,))


# =============================================================================
# Combobox Widget for InputDialog
# =============================================================================

class IDComboBox(qt.QComboBox):
    """
    A ComboBox Widget for InputDialogs.

    Such a ComboBox automatically manages the allowed values as defined by the
    input data dictionary.

    Signals:

    on_enter(QWidget):  Emitted when Enter/Return-Key was hit
    update_depending(QWidget, int): Emitted when a value has been selected.
        The int argument is the index of the selected value.
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__(self, name, master, allowed, default, tip, ddict, parent):

        qt.QComboBox.__init__(self, False, parent, str(name))

        self.ddict = ddict
        self._master  = master
        self._allowed = allowed
        self._default = default
        self._keys = []

        if tip:
            qt.QToolTip.add(self, tip)

        self.connect(self, qt.SIGNAL('activated(int)'), self.__on_selected)

        self.update_widget()


    # -------------------------------------------------------------------------
    # Rebuild the contents of the combobox
    # -------------------------------------------------------------------------

    def update_widget(self):
        """
        Reset the allowed values of the ComboBox depending on the master-value
        or (if no master is available) the predefined allowed values.
        """

        name = self.name()
        self.clear()

        if self._master:
            values = self._allowed.get(self.ddict.get(self._master), {})
        else:
            values = self._allowed

        if values:
            self._keys = []
            for (k, v) in values.items():
                self._keys.append(k)
                self.insertItem(unicode(v))

            if self._default in values:
                self.setCurrentItem(self._keys.index(self._default))
                self.ddict[name] = self._default
            else:
                self.ddict[name] = self._keys[0]
                self.setCurrentItem(0)

        else:
            if name in self.ddict:
                del self.ddict[name]

        self.setEnabled(len(values) > 0)


    # -------------------------------------------------------------------------
    # Event-Handlers
    # -------------------------------------------------------------------------

    def keyPressEvent(self, event):
        """
        If the Enter- or Return-Key is pressed the KeyEvent is consumed and an
        on_enter signal is emitted.
        """
        if event.key() in [qt.Qt.Key_Return, qt.Qt.Key_Enter]:
            self.emit(qt.PYSIGNAL('on_enter(QWidget)'), (self,))
        else:
            qt.QComboBox.keyPressEvent(self, event)


    # -------------------------------------------------------------------------
    # Slots
    # -------------------------------------------------------------------------

    def __on_selected(self, index):

        new = self._keys[index]
        self.ddict[self.name()] = new
        self.emit(qt.PYSIGNAL('update_depending(QWidget, int)'), (self,index))



# =============================================================================
# Class implementing a versatile input dialog
# =============================================================================

class InputDialog (qt.QDialog):
    """
    Dialog class prompting the user for a given number of fields. These field
    definitions are specified as follows:

    A field definition is a tuple having these elements:
    - fieldlabel: This text will be used as label in the left column
    - fieldname: This is the key in the result-dictionary to contain the value
        entered by the user
    - fieldtype: Currently these types are supported:
        - label: The contents of 'fieldlabel' as static text
        - warning: The contents of 'fieldlabel' as static text, formatted as
            warning
        - string: A text entry control
        - password: A text entry control with obscured characters
        - dropdown: Foreach element given in 'elements' a separate ComboBox
            control will be created, where each one has it's own dictionary of
            allowed values. If a value is selected in one control, all others
            are synchronized to represent the same key-value.
    - default: Default value to use
    - masterfield: Used for 'dropdowns'. This item specifies another field
        definition acting as master field. If this master field is changed, the
        allowedValues of this dropdown will be changed accordingly. If a
        masterfield is specified the 'allowedValues' dictionaries are built
        like {master1: {key: value, key: value, ...}, master2: {key: value,
        ...}}
    - elements: sequence of input element tuples (label, allowedValues). This
        is used for dropdowns only. 'label' will be used as ToolTip for the
        control and 'allowedValues' gives a dictionary with all valid keys to
        be selected in the dropdown.

    @return: If closed by 'Ok' the result is a dictionary with all values
        entered by the user, where the "fieldname"s will be used as keys. If
        the user has not selected a value from a dropdown (i.e. it has no
        values to select) there will be no such key in the result dictionary.
        If the dialog is canceled ('Cancel'-Button) the result will be None.
    """

    # -------------------------------------------------------------------------
    # Constructor
    # -------------------------------------------------------------------------

    def __init__ (self, title, fields, cancel = True):
        """
        Create a new input dialog

        @param title: Dialog title
        @param fields: sequence of field definition tuples
        @param cancel: If True add a Cancel button to the dialog
        """

        qt.QDialog.__init__ (self)

        self.setCaption(title)
        self.setIcon(qt.QMessageBox.standardIcon(qt.QMessageBox.Question))

        self.top_sizer = qt.QVBoxLayout(self, 8)

        self.grid = qt.QGridLayout()
        bbox = qt.QHBoxLayout()
        bbox.addStretch(1)

        self.top_sizer.addLayout(self.grid)

        sep = qt.QFrame(self, "separatorline")
        sep.setFrameStyle(qt.QFrame.HLine | qt.QFrame.Sunken)
        self.top_sizer.addWidget(sep)

        self.top_sizer.addLayout(bbox)

        cancel = qt.QPushButton(u_('Cancel'), self)
        cancel.setDefault(False)
        cancel.setAutoDefault(False)
        self.connect(cancel, qt.SIGNAL('clicked()'), self.reject)
        bbox.addWidget(cancel)

        ok = qt.QPushButton(u_('Ok'), self)
        ok.setDefault(False)
        ok.setAutoDefault(False)
        self.connect(ok, qt.SIGNAL('clicked()'), qt.SLOT('accept()'))
        bbox.addWidget(ok)

        self.inputData   = {}
        self.__dropdowns = {}
        self.__entries = []

        row = 0
        for (label, name, fieldtype, default, master, elements) in fields:
            ftp = fieldtype.lower()

            if ftp in ['label', 'warning']:
                self.__addText(row, label, ftp == 'warning')

            elif ftp == 'image':
                self.__addImage(row, name)

            elif ftp in ['string', 'password']:
                self.__addString(row, label, name, default, elements, ftp !=
                        'string')

            elif ftp == 'dropdown':
                self.__addChoice(row, label, name, default, master, elements)

            row += 1

        if self.__entries:
            self.__focus(self.__entries[0])


    # -------------------------------------------------------------------------
    # If the dialog is dismissed, clear the input data
    # -------------------------------------------------------------------------

    def reject(self):
        """
        Cancel the dialog and clear the input data dictionary
        """

        self.inputData = None
        qt.QDialog.reject(self)


    # -------------------------------------------------------------------------
    # Add a centered, static label or warning
    # -------------------------------------------------------------------------

    def __addText(self, row, label, warning=False):

        text = qt.QLabel(label, self)
        text.setAlignment(qt.Qt.AlignHCenter)
        self.grid.addMultiCellWidget(text, row, row, 0, 1)

        if warning:
            text.setPaletteForegroundColor(qt.QColor('red'))


    # -------------------------------------------------------------------------
    # Add a text control for a string or a password
    # -------------------------------------------------------------------------

    def __addString(self, row, label, name, default, elements, pwd=False):

        text = qt.QLabel(label, self)
        self.grid.addWidget(text, row, 0)

        entry = IDLineEdit(name, default, elements, pwd, self.inputData, self)
        self.grid.addWidget(entry, row, 1)

        self.connect(entry, qt.PYSIGNAL('on_enter(QWidget)'),
                self.__on_enter)

        self.__entries.append(entry)


    # -------------------------------------------------------------------------
    # Add a series of dropdowns into a single row
    # -------------------------------------------------------------------------

    def __addChoice(self, row, label, name, default, master, elements):

        text = qt.QLabel(label, self)
        self.grid.addWidget(text, row, 0)

        hbox = qt.QHBoxLayout()
        self.grid.addLayout(hbox, row, 1)

        perMaster = self.__dropdowns.setdefault(master, {})
        perRow    = perMaster.setdefault(name, [])

        border = 0
        for (tip, allowedValues) in elements:
            widget = IDComboBox(name, master, allowedValues, default, tip,
                    self.inputData, self)
            self.connect(widget, qt.PYSIGNAL('update_depending(QWidget, int)'),
                    self.__update_depending)
            self.connect(widget, qt.PYSIGNAL('on_enter(QWidget)'),
                    self.__on_enter)

            perRow.append(widget)

            hbox.addWidget(widget)
            self.__entries.append(widget)


    # -------------------------------------------------------------------------
    # Add a centered image to the dialog
    # -------------------------------------------------------------------------

    def __addImage (self, row, imageURL):

        image = qt.QLabel('', self)
        image.setAlignment(qt.Qt.AlignHCenter)
        image.setPixmap(qt.QPixmap(imageURL))
        self.grid.addMultiCellWidget(image, row, row, 0, 1)


    # -------------------------------------------------------------------------
    # If <Enter> is pressed within a text control, move the focus
    # -------------------------------------------------------------------------
  
    def __on_enter(self, entry):

        next = None
        start = self.__entries.index(entry)+1
        if start < len(self.__entries):
            for i in range(start, len(self.__entries)):
                if self.__entries[i].isEnabled():
                    next = self.__entries[i]
                    break

        if next is not None:
            self.__focus(next)
        else:
            self.accept()

    # -------------------------------------------------------------------------
    # Focus a given widget
    # -------------------------------------------------------------------------

    def __focus(self, widget):
        widget.setFocus()

        if isinstance(widget, qt.QLineEdit):
            widget.selectAll()

    # -------------------------------------------------------------------------
    # Update all depending combo boxes
    # -------------------------------------------------------------------------

    def __update_depending(self, widget, index):

        for item in self.__dropdowns[widget._master][widget.name()]:
          item.setCurrentItem(index)

        master = widget.name()

        if master in self.__dropdowns:
            for name in self.__dropdowns[master].keys():
                drops = self.__dropdowns[master][name]
                for i in drops:
                    i.update_widget()


# =============================================================================
# Module Self Test
# =============================================================================

if __name__ == '__main__':
    import sys

    cname = {'c1': 'demoa', 'c2': 'demob'}
    ckey  = {'c1': 'ck-A' , 'c2': 'ck-B'}

    wija = {'c1': {'04': '2004', '05': '2005'},
            'c2': {'24': '2024', '25': '2025', '26': '2026'}}

    codes = {'24': {'241': 'c-24-1', '242': 'c-24-2'},
             '25': {'251': 'c-25-1'}}

    fields = [('Foo!', '/home/johannes/gnue/share/gnue/images/gnue.png', 'image',
             None, None, []),
            ('Username', u'_username', 'string', None, None, \
              [('Name of the user', None)]),
            ('Password', u'_password', 'password', 'foo', None, [('yeah',1)]),
            ('Foobar', u'_foobar', 'dropdown', 'frob', None, \
                [('single', {'trash': 'Da Trash', 'frob': 'Frob'})]),
            ('Multi', u'_multi', 'dropdown', '100', None, \
                [('name', {'50': 'A 50', '100': 'B 100', '9': 'C 9'}),
                ('sepp', {'50': 'se 50', '100': 'se 100', '9': 'se 9'})]),
            ('Noe', u'_depp', 'label', 'furz', None, []),
            ('Das ist jetzt ein Fehler', None, 'warning', None, None, []),

            ('Firma', u'company', 'dropdown', 'c1', None,
                [('Name', cname), ('Code', ckey)]),
            ('Wirtschaftsjahr', u'wija', 'dropdown', '05', 'company',
                [('Jahr', wija)]),
            ('Codes', u'codes', 'dropdown', None, 'wija',
                [('Code', codes)])
                ]

    app = qt.QApplication( sys.argv )
    app.connect(app, qt.SIGNAL("lastWindowClosed()"), app, qt.SLOT("quit()"))
    
    dialog = InputDialog ('Foobar', fields)
    try:
        dialog.show()
        print "Result:", dialog.inputData

    finally:
        app.quit()

    app.exec_loop()
