# GNU Enterprise Forms - QT3 UI driver - Entry widgets
#
# 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: entry.py 10012 2009-10-27 16:00:31Z reinhard $
"""
Entry widget
"""

import qt

from gnue.forms.input import GFKeyMapper
from gnue.forms.input.GFKeyMapper import vk
from gnue.forms.uidrivers.qt3.widgets._base import UIHelper

# =============================================================================
# Interface class for entry widgets
# =============================================================================

class UIEntry(UIHelper):
    """
    Wraps an <entry> tag
    """

    # -------------------------------------------------------------------------
    # Create a new widget
    # -------------------------------------------------------------------------

    def _create_widget_(self, event, spacer):

        style = self._gfObject.style.lower()
        self._block_change_ = False
        owner = self.getParent()

        if self.in_grid:
            parent = owner._get_cell(self, spacer)
        else:
            parent = event.container

        func = getattr(self, '_UIEntry__build_%s' % style, None)
        if func:
            (self.label, self.widget) = func(parent)
        else:
            (self.label, self.widget) = self.__build_default(parent)

        if not self.managed:
            self.widget.set_default_size()

        if self.in_grid:
            self.widget._gnue_label_ = self.label

        owner.add_widgets(self, spacer)

        return self.widget

    # -------------------------------------------------------------------------
    # Create the variouse entry widgets
    # -------------------------------------------------------------------------

    def __build_default(self, parent, password=False, multiline=False):

        label = self.__add_entry_label(parent)
        if multiline:
            self.growable = True
            return [label, MultiLineEdit(parent, self)]
        else:
            return [label, LineEdit(parent, self, password)]

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

    def __build_password(self, parent):

        return self.__build_default(parent, True)

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

    def __build_multiline(self, parent):

        return self.__build_default(parent, False, True)

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

    def __build_label(self, parent):

        ctrl = Label(parent, self)
        return [self.__add_entry_label(parent), ctrl]

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

    def __build_checkbox(self, parent):
        
        result = CheckBox(parent, self)
        return [None, result]

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

    def __build_dropdown(self, parent):

        result = ComboBox(parent, self)
        return [self.__add_entry_label(parent), result]

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

    def __build_listbox(self, parent):

        self.growable = True
        result = ListBox(parent, self)
        return [self.__add_entry_label(parent), result]


    # -------------------------------------------------------------------------
    # Create a label for a given entry
    # -------------------------------------------------------------------------

    def __add_entry_label(self, parent):

        if self.in_grid:
            result = GridLabel(parent, self)

        elif self._gfObject.label:
            result = qt.QLabel(self._gfObject.label, parent)
        else:
            result = None

        return result


    # -------------------------------------------------------------------------
    # Enable/disable this entry
    # -------------------------------------------------------------------------

    def _ui_enable_(self, index):
        self.widgets[index].setEnabled(True)

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

    def _ui_disable_(self, index):
        self.widgets[index].setEnabled(False)


    # -------------------------------------------------------------------------
    # Set "editable" status for this widget
    # -------------------------------------------------------------------------

    def _ui_set_editable_(self, index, editable):

        # TODO: grey out entry, disallow changes if possible.
        pass


    # -------------------------------------------------------------------------
    # Set value and cursor position
    # -------------------------------------------------------------------------

    def _ui_set_value_(self, index, value):
        """
        This function sets the value of a widget.
        """

        widget = self.widgets[index]

        self._block_change_ = True
        try:
            method = getattr(widget, '_ui_set_value_', None)
            if method:
                method(value)
        finally:
            if self.in_grid and widget._gnue_label_:
                if isinstance(widget._gnue_label_, qt.QLabel):
                    widget._gnue_label_.setText(value)

            self._block_change_ = False

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

    def _ui_set_cursor_position_(self, index, position):
        """
        Set the cursor position to the given location inside a capable widget.

        @param position: new position of the insertion point
        @param index: index of the widget to be changed (if rows > 0)
        """

        widget = self.widgets[index]
        method = getattr(widget, '_ui_set_cursor_position_', None)
        if method:
            method(position)

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

    def _ui_set_selected_area_(self, index, selection1, selection2):
        """
        Sets the selection start/end inside a capable widget.

        @param selection1: start position of the selection
        @param selection2: end position of the selection
        @param index: index of the widget to be changed
        """
        widget = self.widgets[index]

        method = getattr(widget, '_ui_set_selected_area_', None)
        if method:
            method(selection1, selection2)

    # -------------------------------------------------------------------------
    # Clipboard and selection
    # -------------------------------------------------------------------------

    def _ui_cut_(self, index):

        widget = self.widgets[index]
        if hasattr(widget, '_ui_cut_'):
            widget._ui_cut_()

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

    def _ui_copy_(self, index):

        widget = self.widgets[index]
        if hasattr(widget, '_ui_copy_'):
            widget._ui_copy_()

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

    def _ui_paste_(self, index):

        widget = self.widgets[index]
        if hasattr(widget, '_ui_paste_'):
            widget._ui_paste_()

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

    def _ui_select_all_(self, index):

        widget = self.widgets[index]
        if hasattr(widget, '_ui_select_all_'):
            widget._ui_select_all_()

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

    def _ui_set_choices_(self, index, choices):

        widget = self.widgets[index]
        if hasattr(widget, '_ui_set_choices_'):
            widget._ui_set_choices_(choices)


# =============================================================================
# Base class for entry widgets
# =============================================================================

class BaseEntry(object):

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

    def __init__(self, ui_widget, qt_class):
        self.ui_widget = ui_widget
        self.qt_class = qt_class
        self.lookup = self

    # -------------------------------------------------------------------------
    # Set the default size for a widget
    # -------------------------------------------------------------------------

    def set_default_size(self):
        """
        Set the fixed size for this widget
        """
        cellw = self.ui_widget._uiDriver.cell_width
        cellh = self.ui_widget._uiDriver.cell_height

        self.setFixedSize(cellw * self.ui_widget.chr_w, cellh *
                self.ui_widget.chr_h)


    # -------------------------------------------------------------------------
    # User-Feedback methods
    # -------------------------------------------------------------------------

    def _ui_set_cursor_position_(self, position):

        if hasattr(self, 'setCursorPosition'):
            self.setCursorPosition(position)

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

    def _ui_set_selected_area_(self, selection1, selection2):

        if hasattr(self, 'setSelection'):
            self.setSelection(selection1, selection2-selection1)

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

    def _ui_cut_(self):

        if hasattr(self, 'cut'):
            self.cut()

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

    def _ui_copy_(self):

        if hasattr(self, 'copy'):
            self.copy()

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

    def _ui_paste_(self):

        if hasattr(self, 'paste'):
            self.paste()

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

    def _ui_select_all_(self):

        if hasattr(self, 'selectAll'):
            self.selectAll()


    # -------------------------------------------------------------------------
    # Capture handling of focus movement
    # -------------------------------------------------------------------------

    def focusNextPrevChild(self, next):
        """
        Send a next- or prev-entry request to the GFForm
        """
        if next:
            self.ui_widget._request('NEXTENTRY')
        else:
            self.ui_widget._request('PREVENTRY')

        return True


    # -------------------------------------------------------------------------
    # Event-Handler
    # -------------------------------------------------------------------------

    def keyPressEvent(self, event):
        """
        Handle key press events by searching for an apropriate command.  If no
        command was found a keypress event is fired on the bound UI widget.
        """

        keycode = event.key()
        state = event.state()

        # Handle cursor up/down and page up/down.
        if state == 0:
            if keycode == qt.Qt.Key_Up:
                self.ui_widget._gfObject._event_line_up()
            elif keycode == qt.Qt.Key_Down:
                self.ui_widget._gfObject._event_line_down()
            elif keycode == qt.Qt.Key_Prior:
                self.ui_widget._gfObject._event_page_up()
            elif keycode == qt.Qt.Key_Next:
                self.ui_widget._gfObject._event_page_down()

        # We will ask the Key-Mapper for a command only for Tab- and
        # Return-Keys.  Everything else should be handled by the widget
        if keycode in [qt.Qt.Key_Tab, qt.Qt.Key_Return]:
            (command, args) = GFKeyMapper.KeyMapper.getEvent(keycode,
                    state & qt.QKeyEvent.ShiftButton > 0,
                    state & qt.QKeyEvent.ControlButton > 0,
                    state & qt.QKeyEvent.AltButton > 0)

            if command is not None:
                if command == 'NEWLINE':
                    self.ui_widget._request(command, text = '\n')
                else:
                    self.ui_widget._request(command, triggerName = args)

        else:
            # TODO: is there another way to find the qt widget class which
            # is implemented by the class of this Mixin ?
            self.qt_class.keyPressEvent(self, event)


    # -------------------------------------------------------------------------
    # Keep the GF-Focus in sync
    # -------------------------------------------------------------------------

    def focusInEvent(self, event):
        """
        Keep the GF-Focus in sync with the QT3-focus
        """

        self.ui_widget._gfObject._event_set_focus(self.ui_widget.widgets.index(
                self.lookup))

        self.qt_class.focusInEvent(self, event)


# =============================================================================
# Label widgets
# =============================================================================

class Label(BaseEntry, qt.QLabel):
    """
    Label widgets
    """

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

    def __init__(self, parent, ui_widget):
        qt.QLabel.__init__(self, parent)
        BaseEntry.__init__(self, ui_widget, qt.QLineEdit)

    # -------------------------------------------------------------------------
    # UI-Slots
    # -------------------------------------------------------------------------

    def _ui_set_value_(self, value):

        self.setText(value)

# =============================================================================
# GridLabel
# =============================================================================

class GridLabel(qt.QLabel):
    """
    Implements a Label widget used within grid controls which is capable of
    activating the corresponding entry widget on a mouse click.
    """

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

    def __init__(self, parent, ui_widget):

        self.ui_widget = ui_widget
        qt.QLabel.__init__(self, parent)


    # -------------------------------------------------------------------------
    # Event-handler
    # -------------------------------------------------------------------------

    def mousePressEvent(self, event):

        # Find the widget this label belongs to
        for (index, item) in enumerate(self.ui_widget.widgets):
            if item._gnue_label_ == self:
                if item.isEnabled() and \
                        self.ui_widget._gfObject.style.lower() != 'label':
                    # This replaces the label with the actual entry and sets
                    # the focus on the entry
                    self.ui_widget._gfObject.set_focus(index)
                    self.ui_widget._ui_set_focus_(index)
                else:
                    # This entry is not focusable, so we at least move the
                    # database cursor to the record behind this entry.
                    self.ui_widget._gfObject._event_jump_records(index)
                break


# =============================================================================
# LineEdit widgets
# =============================================================================

class LineEdit(BaseEntry, qt.QLineEdit):
    """
    Single line text entry widget
    """

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

    def __init__(self, parent, ui_widget, password=False):
        qt.QLineEdit.__init__(self, parent)
        BaseEntry.__init__(self, ui_widget, qt.QLineEdit)

        self.connect(self, qt.SIGNAL('textChanged(const QString &)'),
                self.__on_text_changed)

        # Right-align entries bound to numeric fields
        if ui_widget._gfObject._field.datatype == 'number':
            self.setAlignment(qt.Qt.AlignRight)

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


    # -------------------------------------------------------------------------
    # Qt-Signals
    # -------------------------------------------------------------------------

    def __on_text_changed(self, value):

        if not self.ui_widget._block_change_:
            if not self.hasFocus():
                self.setFocus()
            self.ui_widget._request('REPLACEVALUE', text=unicode(value),
                    position=self.cursorPosition())


    # -------------------------------------------------------------------------
    # Release of a mouse button
    # -------------------------------------------------------------------------

    def mouseReleaseEvent(self, event):
        """
        After releasing the left mouse button, make sure to update the
        insertion point on the GF layer.
        """

        self.qt_class.mouseReleaseEvent(self, event)

        if event.button() == qt.Qt.LeftButton:
            left = self.cursorPosition()
            if self.hasSelectedText():
                right = left + len(self.selectedText())
                self.ui_widget._request('SELECTWITHMOUSE', position1=left,
                        position2=right)
            else:
                self.ui_widget._request('CURSORMOVE', position=left)


    # -------------------------------------------------------------------------
    # UI-Slots
    # -------------------------------------------------------------------------

    def _ui_set_value_(self, value):

        self.setText(value)



# =============================================================================
# Multiline text entry widget
# =============================================================================

class MultiLineEdit(BaseEntry, qt.QTextEdit):

    def __init__(self, parent, ui_widget):
        qt.QLineEdit.__init__(self, parent)
        BaseEntry.__init__(self, ui_widget, qt.QTextEdit)

        self.__last_para = 0
        self.__last_pos = 0

        self.setTextFormat(qt.Qt.PlainText)

        self.connect(self, qt.SIGNAL('textChanged()'),
                self.__on_text_changed)
        self.connect(self, qt.SIGNAL('cursorPositionChanged(int, int)'),
                self.__on_cursor_changed)

    # -------------------------------------------------------------------------
    # UI slots
    # -------------------------------------------------------------------------

    def _ui_set_value_(self, value):

        self.setText(value)

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

    def _ui_set_cursor_position_(self, position):

        (para, offset) = self.__position_to_qt(position)
        self.setCursorPosition(para, offset)

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

    def _ui_set_selected_area_(self, selection1, selection2):

        (para1, offs1) = self.__position_to_qt(selection1)
        (para2, offs2) = self.__position_to_qt(selection2)

        self.setSelection(para1, offs1, para2, offs2)


    # -------------------------------------------------------------------------
    # Qt-Signals
    # -------------------------------------------------------------------------

    def __on_text_changed(self):

        # FIXME: This signal is fired twice when a selection get's replaced by
        # a new text.  By doing this and the lack of a working
        # getCursorPosition() replacing selected text does not work properly.
        if not self.ui_widget._block_change_:
            self.ui_widget._request('REPLACEVALUE', text=unicode(self.text()),
                    position=self.__qt_to_position() + 1)

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

    def __on_cursor_changed(self, para, pos):

        # pyQT has a broken getCursorPosition(), so we have no chance to find
        # out where the cursor is currently located.  That's the reason for
        # doing it this way.
        self.__last_para = para
        self.__last_pos = pos


    # -------------------------------------------------------------------------
    # Map a QT position into a GF position
    # -------------------------------------------------------------------------

    def __qt_to_position(self):

        result = self.__last_pos + self.__last_para
        for i in range(self.__last_para):
            result += self.paragraphLength(i)

        return result


    # -------------------------------------------------------------------------
    # Map a GF position into a QT position
    # -------------------------------------------------------------------------

    def __position_to_qt(self, position):

        val = unicode(self.text())[:position]
        para = val.count('\n')
        offset = position
        if para > 0:
            offset = len(val.split('\n')[-1])

        return (para, offset)


# =============================================================================
# Checkbox (TriState)
# =============================================================================

class CheckBox(BaseEntry, qt.QCheckBox):
    """
    Implementation of a Tri-State-Checkbox
    """

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

    def __init__(self, parent, ui_widget):

        qt.QCheckBox.__init__(self, ui_widget._gfObject.label or '', parent)
        BaseEntry.__init__(self, ui_widget, qt.QCheckBox)
        self.setTristate(True)
        # We have to set both TabFocus and ClickFocus for this widget,
        # otherwise the checkbox won't request the focus-change on clicking
        # into it using the mouse.  This is needed to enable the GFInstance to
        # process the TOGGLECHKBOX request
        self.setFocusPolicy(qt.QWidget.StrongFocus)

        self._blocked_ = False
        self.connect(self, qt.SIGNAL('toggled(bool)'), self.__on_toggled)


    # -------------------------------------------------------------------------
    # Event-Handler
    # -------------------------------------------------------------------------

    def __on_toggled(self, state):

        if not self._blocked_:
            self.ui_widget._request('TOGGLECHKBOX')

    # -------------------------------------------------------------------------
    # UI-Slots
    # -------------------------------------------------------------------------

    def _ui_set_value_(self, value):

        self._blocked_ = True
        try:
            if value is None:
                self.setState(qt.QButton.NoChange)
            elif value:
                self.setState(qt.QButton.On)
            else:
                self.setState(qt.QButton.Off)

        finally:
            self._blocked_ = False


# =============================================================================
# Base class for widgets having a set of allowed values
# =============================================================================

class ChoiceEntry(BaseEntry):
    """
    """

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

    def __init__(self, ui_widget, qt_class):

        BaseEntry.__init__(self, ui_widget, qt_class)

        self.ui_widget = ui_widget
        self.qt_class = qt_class

    # -------------------------------------------------------------------------
    # Implementation of virtual methods
    # -------------------------------------------------------------------------

    def _ui_set_choices_(self, choices):

        # Make sure to block any TextChanged events while recreating the list
        # of available choices.  This would set the foucs into control as well
        # as having an invalid value set.
        old = self.ui_widget._block_change_
        try:
            self.ui_widget._block_change_ = True
            self.clear()

            for item in choices:
                self.insertItem(item)

        finally:
            self.ui_widget._block_change_ = old


# =============================================================================
# Dropdown widgets
# =============================================================================

class ComboBox(ChoiceEntry, qt.QComboBox):
    """
    Dropdown widgets
    """

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

    def __init__(self, parent, ui_widget):

        qt.QComboBox.__init__(self, True, parent)
        ChoiceEntry.__init__(self, ui_widget, qt.QComboBox)

        self.setDuplicatesEnabled(False)

        self.__lineEdit = LineEdit(parent, ui_widget)
        self.__lineEdit.lookup = self
        self.setLineEdit(self.__lineEdit)

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


    # -------------------------------------------------------------------------
    # Event-Handler
    # -------------------------------------------------------------------------

    def __on_activated(self, item_index):

        if not self.ui_widget._block_change_:
            selected = unicode(self.currentText())
            if not self.hasFocus():
                self.setFocus()

            self.ui_widget._request('REPLACEVALUE', text=selected)


    # -------------------------------------------------------------------------
    # Implementation of virtual methods
    # -------------------------------------------------------------------------

    def _ui_set_value_(self, value):

        self.setCurrentText(value)

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

    def _ui_set_cursor_position_(self, position):

        self.lineEdit().setCursorPosition(position)

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

    def _ui_set_selected_area_(self, position1, position2):

        self.lineEdit().setSelection(position1, position2-position1)

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

    def _ui_cut_(self):

        self.lineEdit().cut()

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

    def _ui_copy_(self):

        self.lineEdit().copy()

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

    def _ui_paste_(self):

        self.lineEdit().paste()

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

    def _ui_select_all_(self):

        self.lineEdit().selectAll()


# =============================================================================
# Listbox widgets
# =============================================================================

class ListBox(ChoiceEntry, qt.QListBox):
    """
    Listbox widgets
    """

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

    def __init__(self, parent, ui_widget):

        qt.QListBox.__init__(self, parent)
        ChoiceEntry.__init__(self, ui_widget, qt.QListBox)

        self.connect(self, qt.SIGNAL('highlighted(int)'), self.__on_highlighted)


    # -------------------------------------------------------------------------
    # Event-Handler
    # -------------------------------------------------------------------------

    def __on_highlighted(self, index):
        self.ui_widget._request('REPLACEVALUE',
                text=unicode(self.currentText()))

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

    def _ui_set_value_(self, value):

        self.setCurrentItem(self.findItem(value))




# =============================================================================
# Keymapper configuration: Translate from QT to our virtual keystrokes
# =============================================================================

qtKeyTranslations = {
    vk.A      : qt.Qt.Key_A,         vk.C         : qt.Qt.Key_C,
    vk.V      : qt.Qt.Key_V,         vk.X         : qt.Qt.Key_X,
    vk.F1     : qt.Qt.Key_F1,        vk.F2        : qt.Qt.Key_F2,
    vk.F3     : qt.Qt.Key_F3,        vk.F4        : qt.Qt.Key_F4,
    vk.F5     : qt.Qt.Key_F5,        vk.F6        : qt.Qt.Key_F6,
    vk.F7     : qt.Qt.Key_F7,        vk.F8        : qt.Qt.Key_F8,
    vk.F9     : qt.Qt.Key_F9,        vk.F10       : qt.Qt.Key_F10,
    vk.F11    : qt.Qt.Key_F11,       vk.F12       : qt.Qt.Key_F12,
    vk.INSERT : qt.Qt.Key_Insert,    vk.DELETE    : qt.Qt.Key_Delete,
    vk.HOME   : qt.Qt.Key_Home,      vk.END       : qt.Qt.Key_End,
    vk.PAGEUP : qt.Qt.Key_Prior,     vk.PAGEDOWN  : qt.Qt.Key_Next,
    vk.UP     : qt.Qt.Key_Up,        vk.DOWN      : qt.Qt.Key_Down,
    vk.LEFT   : qt.Qt.Key_Left,      vk.RIGHT     : qt.Qt.Key_Right,
    vk.TAB    : qt.Qt.Key_Tab,
    vk.RETURN : qt.Qt.Key_Return,    vk.BACKSPACE : qt.Qt.Key_Backspace }

GFKeyMapper.KeyMapper.setUIKeyMap(qtKeyTranslations)


# =============================================================================
# Configuration
# =============================================================================

configuration = {
  'baseClass': UIEntry,
  'provides' : 'GFEntry',
  'container': False,
}
