# GNU Enterprise Forms - UI Driver - Base class for user interfaces
#
# 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: UIdriver.py 9975 2009-10-16 10:05:21Z reinhard $

import sys, os, dircache

from gnue.common import events
from gnue.common.definitions.GObjects import *
from gnue.common.definitions.GRootObj import GRootObj
from gnue.common.utils.FileUtils import dyn_import
from gnue.common.apps import errors

from gnue.forms.GFForm import *
from gnue.forms import VERSION

# =============================================================================
# Exceptions
# =============================================================================

class ImplementationError (errors.SystemError):
  def __init__ (self, drivername, method):
    msg = u_("The UI-Driver %(name)s has no implementation of %(method)s") \
          % {'name': drivername,
             'method': method}
    errors.SystemError.__init__ (self, msg)


# -----------------------------------------------------------------------------
# Guess a default iconset for the current environment
# -----------------------------------------------------------------------------

def guessDefaultIconset ():
  """
  Guess the default iconset to use in this environment
  """

  # TODO: This is *very* crude logic
  try:
    import mac
    return 'macosx'
  except ImportError:
    try:
      winver = sys.getwindowsversion()
      if winver[0]>5 or (winver[0]==5 and winver[1]>=1 and winver[3]==2):
        # TODO: we have no winxp iconset yet
        # return 'winxp'
        return 'default'
      else:
        return 'default'
    except AttributeError:
      try:
        import posix
        if os.environ.get('KDE_FULL_SESSION',''):
          return 'kde3'
        ds = os.environ.get('DESKTOP_SESSION','')
        if ds[:3] == 'kde':
          return 'kde3'
        elif ds[:5] == 'gnome':
          return 'gnome'
        else:
          return 'default'
      except ImportError:
        return 'default'



# =============================================================================
# Base class for UI drivers
# =============================================================================

class GFUserInterfaceBase (GRootObj, events.EventAware):

  default_iconset = guessDefaultIconset ()

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

  def __init__(self, eventHandler, name = "Undefined", disableSplash = None,
               parentContainer = None, moduleName = None):
    """
    """
    # moduleName is here only for Designer to be able to pass it in
    #when debugging a form.

    GRootObj.__init__(self, 'uiDriver', None, None)

    self.name           = name
    self._type          = 'UIDriver'
    self._disableSplash = disableSplash

    # Used when forms are embedded in navigator. What parentContainer is
    # depends on the individual UIdriver and Navigator.
    self._parentContainer = parentContainer

    events.EventAware.__init__(self, eventHandler)

    # So other modules can get to the ui
    __builtins__ ['forms_ui']  = self

    # Multi-form support
    self._formNameToUIForm = {}             # Holds links the to the top level
                                            # UIForm objects held in memory
    self._formNameToGFForm = {}             # Holds links the to the top level
                                            # UIForm objects held in memory
    self._gfObjToUIWidget = {}              # dictionary of driver specific
                                            # UIfoo widgets with the GFObj used
                                            # as the key

    # Import and register supported widgets in UI driver
    self._supportedWidgets = {}

    if moduleName:  #We are within Designer, running a form
      basedir = os.path.dirname(sys.modules[moduleName].__file__)
    else:   #Normal case
      basedir  = os.path.dirname (sys.modules [self.__module__].__file__)

    uiDriver = os.path.basename (basedir)
    basedir = os.path.join(basedir, "widgets")
    for widgetName in dircache.listdir (basedir):
        # _xxx are abstract classes, .xxx are hidden dirs (like ".svn")
        if widgetName [0] != '_' and widgetName [0] != '.':
          if os.path.isdir(os.path.join(basedir, widgetName)):
            # Directories: import as module
            widget = dyn_import ('gnue.forms.uidrivers.%s.widgets.%s' \
                                 % (uiDriver, widgetName))
          elif os.path.isfile (os.path.join(basedir, widgetName)):
            # Files: import only .py (not .pyc or .pyo or anything else)
            (widgetName, ext) = os.path.splitext (widgetName)
            if ext == '.py':
              widget = dyn_import ('gnue.forms.uidrivers.%s.widgets.%s' \
                                   % (uiDriver, widgetName))

          try:
            self._supportedWidgets [widget.configuration ['provides']] = widget
          except Exception, mesg:
            raise ImportError, mesg


  # ---------------------------------------------------------------------------
  # Build the user interface
  # ---------------------------------------------------------------------------

  def _buildUI (self, object, formName):
      try:
          supported = self._supportedWidgets [object._type]
      except KeyError:   
          assert gDebug(4, "No UI Widget provided for type '%s' in _buildUI" % object._type)
      else:
              
          # Look up the parent GObj of the ojbect
          if object._type == "GFPage":
              parent = object.findParentOfType (None)
          else:
              parent = object.getParent ()

          # find the ui widget that corrosponds with that parent
          uiParent = self._gfObjToUIWidget.get(parent, self)
      
          # Build widget
          event = events.Event ('CreateUIWidget', None,
                        eventHandler = self.dispatchEvent,
                        object       = object,
                        parent       = uiParent,
                        #container   = self.currentWidget[0],
                        textWidth    = self.textWidth,
                        textHeight   = self.textHeight,
                        widgetWidth  = self.widgetWidth,
                        widgetHeight = self.widgetHeight,
                        interface    = self,
                        initialize   = 1)
          uiWidget  = supported.configuration ['baseClass'] (event)
          
          # Store parent GFForm reference in widget
          uiWidget._form = self._form
          
          # Store UIWidget reference with GFObj based object that created it
          object.uiWidget = uiWidget

          # Assign to cross refernce dicts as needed 
          self._gfObjToUIWidget [object] = uiWidget
          
          if object._type == 'GFForm':
            self._formNameToUIForm [formName] = uiWidget
            self._formNameToGFForm [formName] = object


  #############################################################################
  #
  # Public Interface
  #
  # The interface exposed to the forms backend
  #
  #


  # ---------------------------------------------------------------------------
  # Build a form from a GObj tree
  # ---------------------------------------------------------------------------

  def buildForm (self, form, formName):

    self._form = form

    # Create the UI from the GFForm passed in
    form.walk (self._buildUI, formName = formName)
    
    self._gfObjToUIWidget [form].phaseInit ()

    self._formNameToUIForm [formName]._gfObjToUIWidget = self._gfObjToUIWidget
    self._formNameToUIForm [formName]._form = form


  #############################################################################
  #
  # Optional Functions
  #
  # UIDrivers can override the following functions
  #


  # ---------------------------------------------------------------------------
  # Get an input dialog for a given set of fields
  # ---------------------------------------------------------------------------

  def getInput (self, title, fields, cancel = True):
    """
    Prompt the user for a given number of fields and return the result as
    dictionary.

    @param title: title of the input dialog
    @param fields: field definition as described below
    @param cancel: if True the dialog has a cancel button, otherwise not

    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.
    """

    return self._getInput (title, fields, cancel)


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

  def _getInput (self, title, fields, cancel):

    raise ImplementationError, (self.name, '_getInput')
