# GNU Enterprise Forms - GTK 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 9956 2009-10-11 18:54:57Z reinhard $

import gtk
import gobject
import math

from gnue.common import events
from gnue.forms.uidrivers.gtk2.widgets._base import UIHelper


# =============================================================================
# This class implements the UI layer for entry widgets
# =============================================================================

class UIEntry (UIHelper):

  def _create_widget (self, event, spacer):
    gfObject = event.object
    style    = gfObject.style

    if style == 'dropdown':
      newWidget = self.__createDropDown (gfObject, event)

    elif style == 'label':
      newWidget = self.__createLabel (gfObject, event)

    elif style == 'checkbox':
      newWidget = self.__createCheckbox (gfObject, event)

    elif style == 'listbox':
      newWidget = self.__createListBox (gfObject, event)

    else:
      if hasattr (gfObject, 'Char__height') and gfObject.Char__height > 1:
        newWidget = self.__createTextView (gfObject, event)

      else:
        newWidget = self.__createEntry (gfObject, event)

    event.container.show_all ()
    return newWidget


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

  def __createDropDown (self, gfObject, event):
    """
    """

    # return self.__createOldDropDown (gfObject, event)
    newWidget = gtk.combo_box_entry_new_text ()
    
    # Enter does NOT open the popup list
    # newWidget.set_activate (False)

    newWidget.set_size_request (self.itemWidth, self.itemHeight)
    event.container.put (newWidget, self.itemX, self.itemY)
    newWidget.show ()

    if event.initialize:
      self._addDefaultEventHandler (newWidget)
      self._addFocusHandler (newWidget.child, newWidget)
      self._addDefaultEventHandler (newWidget.child, newWidget)

      entry = newWidget.child
      entry._insert_handler = entry.connect ('insert-text',
                                             self.insertTextHandler,
                                             gfObject)

      entry._delete_handler = entry.connect ('delete-text',
                                             self.deleteTextHandler,
                                             gfObject)
      entry.connect ('button-release-event', self._buttonRelease)

    newWidget._sfc_handler = newWidget.connect ('set-focus-child',
                                                self._setFocusChild,
                                                gfObject)

    return newWidget


  # ---------------------------------------------------------------------------
  # Create a label widget
  # ---------------------------------------------------------------------------

  def __createLabel (self, gfObject, event):
    newWidget = gtk.Label ("")
    # Place a label deeper than the input widgets, so the baselines are ok
    event.container.put (newWidget, self.itemX, self.itemY + 4)
    newWidget.show ()

    return newWidget


  # ---------------------------------------------------------------------------
  # Create a checkbox widget
  # ---------------------------------------------------------------------------

  def __createCheckbox (self, gfObject, event):
    label = len (gfObject.label) and gfObject.label or None
    newWidget = gtk.CheckButton (label)

    add = int ((self.itemHeight - newWidget.size_request () [1]) / 2)
    event.container.put (newWidget, self.itemX, self.itemY + add)
    newWidget.show ()

    newWidget._clickedHandler = newWidget.connect ('toggled',
                                                   self.checkboxHandler)
    if event.initialize:
      self._addDefaultEventHandler (newWidget)
      self._addFocusHandler (newWidget)

    return newWidget


  # ---------------------------------------------------------------------------
  # Create a text view widget
  # ---------------------------------------------------------------------------

  def __createTextView (self, gfObject, event):

    newWidget = gtk.TextView ()
    # newWidget.set_wrap_mode (gtk.WRAP_CHAR)
    newWidget.set_pixels_above_lines (2)
    newWidget.set_left_margin (2)

    if gfObject.readonly:
      newWidget.set_editable (False)

    viewport  = gtk.ScrolledWindow ()
    viewport.set_shadow_type (gtk.SHADOW_IN)
    viewport.add (newWidget)
    viewport.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
    viewport.set_size_request (self.itemWidth, self.itemHeight)

    tBuffer = newWidget.get_buffer ()
    tBuffer._insert_handler = tBuffer.connect ('insert-text',
                                               self.insertBufferHandler,
                                               newWidget)

    tBuffer._delete_handler = tBuffer.connect ('delete-range',
                                               self.deleteBufferHandler,
                                               newWidget)

    newWidget.connect ('key-press-event', self.textViewKeyPress)
    newWidget.connect ('button-release-event', self._buttonRelease)

    if event.initialize:
      self._addFocusHandler (newWidget)
      self._addDefaultEventHandler (newWidget)

    event.container.put (viewport, self.itemX, self.itemY)
    newWidget.show ()

    return newWidget


  # ---------------------------------------------------------------------------
  # Create a new text entry widget
  # ---------------------------------------------------------------------------

  def __createEntry (self, gfObject, event):

    newWidget = gtk.Entry ()
    if gfObject.style == 'password':
        newWidget.set_visibility(False)
    newWidget.set_size_request (self.itemWidth, self.itemHeight)

    newWidget._insert_handler = newWidget.connect ('insert-text',
                                                   self.insertTextHandler,
                                                   gfObject)

    newWidget._delete_handler = newWidget.connect ('delete-text',
                                                   self.deleteTextHandler,
                                                   gfObject)
    newWidget.connect ('button-release-event', self._buttonRelease)

    if event.initialize:
      self._addFocusHandler (newWidget)
      self._addDefaultEventHandler (newWidget)

    event.container.put (newWidget, self.itemX, self.itemY)
    newWidget.show ()

    return newWidget


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

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

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

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


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

  def _ui_set_editable_(self, index, editable):

    # FIXME: grey out entry, disallow changes if possible
    pass


  # ---------------------------------------------------------------------------
  # Set the cursor position in a widget to position
  # ---------------------------------------------------------------------------

  def _ui_set_cursor_position_ (self, index, position):

    widget = self.widgets [index]

    entry = isinstance (widget, gtk.ComboBox) and widget.child or widget

    assert gDebug (6, "set_cursor_position to %s in %s [%s] (%s)" % \
        (position, entry, index, widget))

    if isinstance (entry, gtk.Entry):
      entry.set_position (position)

    elif isinstance (entry, gtk.TextView):
      entry.set_cursor_visible (True)

      tBuffer = widget.get_buffer ()
      tBuffer.place_cursor (tBuffer.get_iter_at_offset (position))
      entry.scroll_mark_onscreen (tBuffer.get_insert ())


  # ---------------------------------------------------------------------------
  # set the selected area
  # ---------------------------------------------------------------------------

  def _ui_set_selected_area_ (self, index, selection1, selection2):

    assert gDebug (6, "Set Selected Area %s/%s in %s [%s]" % \
        (selection1, selection2, self, index))

    widget = self.widgets [index]

    if isinstance (widget, gtk.Entry):
      widget.select_region (selection1, selection2)

    elif isinstance (widget, gtk.TextView):
      tBuffer = widget.get_buffer ()
      selbound = tBuffer.get_selection_bound ()
      insert   = tBuffer.get_insert ()
      left  = min (selection1, selection2)
      right = max (selection1, selection2)

      tBuffer.move_mark (insert, tBuffer.get_iter_at_offset (left))
      tBuffer.move_mark (selbound, tBuffer.get_iter_at_offset (right))

    elif isinstance (widget, gtk.ComboBox):
      widget.child.select_region (selection1, selection2)


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

  def _ui_cut_(self, index):

        widget = self.widgets[index]

        if hasattr(widget, 'cut_clipboard'):
            widget.cut_clipboard()

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

  def _ui_copy_(self, index):

        widget = self.widgets[index]

        if hasattr(widget, 'copy_clipboard'):
            widget.copy_clipboard()

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

  def _ui_paste_(self, index):

        widget = self.widgets[index]

        if hasattr(widget, 'paste_clipboard'):
            widget.paste_clipboard()

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

  def _ui_select_all_(self, index):

        widget = self.widgets[index]

        if hasattr(widget, 'select_region'):
            widget.set_position(-1)
            pos = widget.get_position()
            widget.select_region(0, -1)
            bounds = widget.get_selection_bounds()
            # There seems to be no event that is fired when the selection of an
            # entry changes, so we must notify the GF layer ourselves.
            # FIXME: This also means that selecting "Select All" from the
            # context menu of the entry doesn't work.
            self._request('SELECTWITHMOUSE', position1=bounds[0],
                    position2=bounds[1], cursor=pos)

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

  def _ui_set_choices_(self, index, choices):
    widget = self.widgets[index]

    self.items = choices[:]
    if isinstance (widget, gtk.ComboBox):
        widget.get_model ().clear ()
        map (widget.append_text, choices)

    elif isinstance (widget, gtk.TreeView):
        self.listStore.clear ()

        for item in choices:
          self.listStore.append([item])

  # ---------------------------------------------------------------------------
  # insert text into gtk.Entry widgets
  # ---------------------------------------------------------------------------

  def insertTextHandler (self, widget, newtext, length, pos, gfObject):

    assert gDebug (6, "insert gtk.Entry () '%s' at %d (%s) into %s" % \
               (newtext, widget.get_position (), pos, widget))

    text   = unicode (newtext, 'utf-8')
    action = events.Event ('requestKEYPRESS', text = text,
                           _form = gfObject._form)

    widget.stop_emission ('insert-text')
    gobject.idle_add (self._eventHandler, action)


  # ---------------------------------------------------------------------------
  # delete a portion of text from a gtk.Entry () widget
  # ---------------------------------------------------------------------------

  def deleteTextHandler (self, widget, start_pos, end_pos, gfObject):

    assert gDebug (6, "Delete %s to %s in %s" % (start_pos, end_pos, widget))

    action = events.Event ('requestDELETERANGE',
                            start_pos = start_pos,
                            end_pos   = end_pos,
                            position  = widget.get_position (),
                            _form     = gfObject._form)

    widget.stop_emission ('delete-text')
    self._eventHandler (action)


  # ---------------------------------------------------------------------------
  # Handle text inserts in a text view widget (textBuffer)
  # ---------------------------------------------------------------------------

  def insertBufferHandler (self, tBuffer, iterator, newtext, length, widget):

    position = iterator.get_offset ()
    gfObject = self._uiDriver._WidgetToGFObj [widget]

    assert gDebug (6, "gtk.TextBuffer insert '%s' at %s" % \
               (newtext, position))

    text = unicode (newtext, 'utf-8')
    tBuffer.stop_emission ('insert-text')

    self._request ('INSERTAT', text = text, position = position)


  # ---------------------------------------------------------------------------
  # Handle deletes in a textView widget
  # ---------------------------------------------------------------------------

  def deleteBufferHandler (self, tBuffer, start, end, widget):

    assert gDebug (6, "gtk.TextBuffer delete %s to %s in %s" \
                % (start.get_offset (), end.get_offset (), widget))

    gfObject = self._uiDriver._WidgetToGFObj [widget]

    action = events.Event ('requestDELETERANGE',
                            start_pos = start.get_offset (),
                            end_pos   = end.get_offset (),
                            position  = tBuffer.get_iter_at_mark ( \
                                        tBuffer.get_insert ()).get_offset (),
                            _form     = gfObject._form)

    tBuffer.stop_emission ('delete-range')
    self._eventHandler (action)


  # ---------------------------------------------------------------------------
  # Handle the tab-key in a textView widget to move the focus
  # ---------------------------------------------------------------------------

  def textViewKeyPress (self, widget, event):
    """
    This handler reacts on up and down keys. While not in the first or last row
    both keys move the cursor in the text view widget. Up- and down-keys in the
    first or last row moves the focus out of the widget.
    """
    if event.keyval == gtk.keysyms.Down:
      tBuffer = widget.get_buffer ()

      cIter = tBuffer.get_iter_at_mark (tBuffer.get_insert ())
      if widget.forward_display_line (cIter):
        self._request ('CURSORMOVE', position = cIter.get_offset ())
      else:
        self._request ('NEXTENTRY')

      return True

    elif event.keyval == gtk.keysyms.Up:
      tBuffer = widget.get_buffer ()
      cIter = tBuffer.get_iter_at_mark (tBuffer.get_insert ())
      if widget.backward_display_line (cIter):
        self._request ('CURSORMOVE', position = cIter.get_offset ())
      else:
        self._request ('PREVENTRY')

      return True

    # Key not handled, leave it up to the common handler
    return False


  # ---------------------------------------------------------------------------
  # Handle the toggled event on a checkbox
  # ---------------------------------------------------------------------------

  def checkboxHandler (self, widget):
    """
    This function fires a 'requestTOGGLECHECKBOX' event if the state of a
    checkbutton widget changes.
    """
    buttonStatus = widget.get_active ()

    gfObject  = self._uiDriver._WidgetToGFObj [widget]
    eventdata = [gfObject, buttonStatus]
    action = events.Event ('requestTOGGLECHKBOX', data = eventdata,
                           _form = gfObject._form)

    widget.stop_emission ('toggled')

    self._eventHandler (action)

    return True


  # ---------------------------------------------------------------------------
  # Create a listbox entry
  # ---------------------------------------------------------------------------

  def __createListBox (self, gfObject, event):
    self.listStore = gtk.ListStore (str)

    hbox = gtk.HBox (False, 0)
    hbox.set_size_request (self.itemWidth, self.itemHeight)

    frame = gtk.Frame ()
    frame.set_shadow_type (gtk.SHADOW_IN)

    hbox.pack_start (frame)
    frame.show ()

    newWidget = gtk.TreeView (self.listStore)
    newWidget.set_headers_visible (False)

    if newWidget.get_vadjustment () is not None:
      adjustment = newWidget.get_vadjustment ()
      scroll = gtk.VScrollbar (adjustment)
      scroll.set_size_request (-1, self.itemHeight)
      hbox.pack_start (scroll, False, False)
      newWidget._focusOnScroll = adjustment.connect ('value-changed',
                                           self._focusOnScroll, newWidget)
      scroll.show ()

    self.selection = newWidget.get_selection ()
    self.selection.set_mode (gtk.SELECTION_SINGLE)
    self.selection._changedHandler = self.selection.connect ('changed',
        self._selectionChanged, gfObject, newWidget)

    tvCol = gtk.TreeViewColumn ()
    r = newWidget.append_column (tvCol)

    cell = gtk.CellRendererText ()
    tvCol.pack_start (cell, False)
    tvCol.add_attribute (cell, 'text', 0)

    frame.add (newWidget)
    if event.initialize:
      self._addFocusHandler (newWidget, newWidget, True)
      self._addDefaultEventHandler (newWidget)

    newWidget.show ()

    event.container.put (hbox, self.itemX, self.itemY)

    return newWidget


  # ---------------------------------------------------------------------------
  # Handle changes of selection in the listbox
  # ---------------------------------------------------------------------------

  def _selectionChanged (self, treeSelection, gfObject, widget):

    (model, tIter) = treeSelection.get_selected ()
    if tIter is not None:
      desc = model.get_value (tIter, 0)

    fRef = gfObject._form

    if tIter is not None:
      # Make sure the GF layer knows which widget has the focus.
      gfObject._event_set_focus(self.widgets.index(widget))

      action = events.Event ('requestREPLACEVALUE', text = desc, _form = fRef)
      self._eventHandler (action)


  # ---------------------------------------------------------------------------
  # Move ui-focus to the corresponding tree view if a slider gets changed
  # ---------------------------------------------------------------------------

  def _focusOnScroll (self, adjustment, treeView):

    assert gDebug (6, "grabing focus to %s on adjustment-change %s" % (treeView,
                                                                adjustment))
    self._blockHandler (treeView, '_focusHandler')
    treeView.grab_focus ()
    self._blockHandler (treeView, '_focusHandler', True)


  # ---------------------------------------------------------------------------
  # Handle the release of a mouse button
  # ---------------------------------------------------------------------------

  def _buttonRelease (self, widget, event):
    """
    This function handles release of the left mouse button. If the current
    widget has a selection requestSELECTWITHMOUSE otherwise requestCURSORMOVE
    will be fired.
    """
    if isinstance (widget, gtk.TextView):
      tBuffer = widget.get_buffer ()
      cPos = tBuffer.get_iter_at_mark (tBuffer.get_insert ()).get_offset ()
      bMarks = tBuffer.get_selection_bounds ()
      if len (bMarks):
        bounds = [it.get_offset () for it in bMarks]
      else:
        bounds = []

    elif isinstance (widget, gtk.Entry):
      cPos   = widget.get_position ()
      bounds = widget.get_selection_bounds ()

    assert gDebug (6, "Button-Release: %s (%s) %s %s" % (widget, self._gfObject.name,
                                                  cPos, bounds))
    if len (bounds):
      self._request ('SELECTWITHMOUSE', position1 = bounds [0],
                     position2 = bounds [1], cursor = cPos)
    else:
      self._request ('CURSORMOVE', position = cPos)


  # ---------------------------------------------------------------------------
  # Set the focus child for a combo box entry
  # ---------------------------------------------------------------------------

  def _setFocusChild (self, widget, child, gfObject):

    self._blockHandler (widget, '_sfc_handler')

    if child is not None:
      # Move the focus to the entry of the dropdown. This implies a
      # focus-change in the GF-layer too
      if not widget.is_focus ():
        widget.child.grab_focus ()

    self._blockHandler (widget, '_sfc_handler', True)


# -----------------------------------------------------------------------------
# Determine the minimum size of a button widget
# -----------------------------------------------------------------------------

def size_request ():
  """
  This function returns a tuple (width, height) describing the minimum size of
  entry widgets. Actually only an entry and a combo box is used here, where
  only the height portion get's returned.
  """

  e = gtk.Entry ()
  (w, eHeight) = e.size_request ()
  c = gtk.ComboBoxEntry ()
  (w, cHeight) = c.size_request ()

  return (None, max (eHeight, cHeight))


# =============================================================================
# Base configuration data
# =============================================================================

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