commit-gnue
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[gnue] r9332 - in trunk/gnue-forms/src: GFObjects input/displayHandlers


From: johannes
Subject: [gnue] r9332 - in trunk/gnue-forms/src: GFObjects input/displayHandlers
Date: Fri, 26 Jan 2007 04:08:59 -0600 (CST)

Author: johannes
Date: 2007-01-26 04:08:58 -0600 (Fri, 26 Jan 2007)
New Revision: 9332

Modified:
   trunk/gnue-forms/src/GFObjects/GFField.py
   trunk/gnue-forms/src/input/displayHandlers/Cursor.py
   trunk/gnue-forms/src/input/displayHandlers/datehandler.py
Log:
Auto-completion and display string parsing for date fields


Modified: trunk/gnue-forms/src/GFObjects/GFField.py
===================================================================
--- trunk/gnue-forms/src/GFObjects/GFField.py   2007-01-26 08:01:55 UTC (rev 
9331)
+++ trunk/gnue-forms/src/GFObjects/GFField.py   2007-01-26 10:08:58 UTC (rev 
9332)
@@ -249,7 +249,7 @@
     # Autocomplete a value for this field
     # -------------------------------------------------------------------------
 
-    def autocomplete(self, value):
+    def autocomplete(self, value, cursor):
         """
         Return the first valid user value that starts with the provided part.
 
@@ -257,19 +257,22 @@
 
         @param value: User entered string.
         @type value: unicode
-        @returns: Autocompleted string.
-        @rtype: unicode
+        @param cursor: Position of the current insertion point
+        @type cursor: integer
+        @returns: tuple of autocompleted string and the new position of the
+            insertion point
+        @rtype: tuple of (unicode, integer)
         """
 
         if not self.__is_lookup:
-            return value
+            return (value, cursor)
 
         for allowed in self.__lookup_list:
             if allowed.startswith(value):
-                return allowed
+                return (allowed, cursor)
 
         # Nothing found, return original user input.
-        return value
+        return (value, cursor)
 
 
     # -------------------------------------------------------------------------

Modified: trunk/gnue-forms/src/input/displayHandlers/Cursor.py
===================================================================
--- trunk/gnue-forms/src/input/displayHandlers/Cursor.py        2007-01-26 
08:01:55 UTC (rev 9331)
+++ trunk/gnue-forms/src/input/displayHandlers/Cursor.py        2007-01-26 
10:08:58 UTC (rev 9332)
@@ -311,12 +311,12 @@
 
         if new_text.startswith(self.display[:ende]) and len(new_text) > ende:
             start_sel = len(new_text)
-            new_text = self._autocomplete_(new_text)
-            new_text = self.field.autocomplete(new_text)
+            (new_text, start_sel) = self._autocomplete_(new_text, start_sel)
             if len(new_text) > start_sel:
                 new_selection = (start_sel, len(new_text))
             else:
                 new_selection = (None, None)
+            new_cursor = start_sel
         else:
             new_selection = (None, None)
 
@@ -336,7 +336,7 @@
     # Do autocompletion
     # -------------------------------------------------------------------------
 
-    def _autocomplete_(self, new_value):
+    def _autocomplete_(self, new_value, new_cursor):
         """
         Descandants can override this method to introduce autocompletion.  This
         method get's called from __change_text() if the current cursor is at
@@ -344,7 +344,7 @@
 
         @returns: the new (autocompleted) value to use
         """
-        return new_value
+        return self.field.autocomplete(new_value, new_cursor)
 
     # =========================================================================
     # Cursor movement functions

Modified: trunk/gnue-forms/src/input/displayHandlers/datehandler.py
===================================================================
--- trunk/gnue-forms/src/input/displayHandlers/datehandler.py   2007-01-26 
08:01:55 UTC (rev 9331)
+++ trunk/gnue-forms/src/input/displayHandlers/datehandler.py   2007-01-26 
10:08:58 UTC (rev 9332)
@@ -1,5 +1,9 @@
-# This file is part of GNU Enterprise.
+# GNU Enterprise Forms - Display handler - Date related display handlers
 #
+# Copyright 2001-2007 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
@@ -15,18 +19,16 @@
 # write to the Free Software Foundation, Inc., 59 Temple Place
 # - Suite 330, Boston, MA 02111-1307, USA.
 #
-# Copyright 2002-2007 Free Software Foundation
-#
-# FILE:
-# GFDisplayHandler.py
-#
-# $Id$
+# $Id: $
 """
-DisplayHandler classes for Forms input validation
+DisplayHandler classes for input validation of date, time and datetime values
 """
 __revision__ = "$Id:$"
 
-import sys, time, datetime
+import sys
+import time
+import datetime
+import re
 
 from gnue.common.apps import errors
 from gnue.forms.input.displayHandlers.Cursor import BaseCursor
@@ -36,88 +38,293 @@
 # =============================================================================
 
 class InvalidDateLiteral (errors.UserError):
-  def __init__ (self, value):
-    msg = u_("'%(value)s' is not a valid date-literal") % {'value': value}
-    errors.UserError.__init__ (self, msg)
+    def __init__ (self, value):
+        msg = u_("'%(value)s' is not a valid date-literal") % {'value': value}
+        errors.UserError.__init__ (self, msg)
 
-
 # =============================================================================
-# Display handler for Date and Time values
+# Base class for date related handlers
 # =============================================================================
 
-class Date(BaseCursor):
-    pass
+class DateRelated(BaseCursor):
 
+    def get_date_order(self, format):
+
+        sample = datetime.date(1978, 3, 21)
+        text = sample.strftime(str(format))
+        order = []
+        inter = []
+        pat = re.compile('^(\d+)(\D*)')
+
+        match = pat.match(text)
+        while match:
+            part, rest = match.groups()
+            if part in ['1978', '78']:
+                if len(part) == 2:
+                    order.append('year')
+                else:
+                    order.append('Year')
+            elif part in ['03', '3']:
+                order.append('month')
+            elif part == '21':
+                order.append('day')
+            else:
+                order.append('?')
+
+            if rest:
+                inter.append(rest)
+
+            text = text[len(part + rest):]
+            if not text:
+                break
+
+            match = pat.match(text)
+
+        return (order, inter)
+
+
+
+
 class Time(BaseCursor):
     pass
 
-class DateTime(BaseCursor):
-  """
-  Class to handle the display and entry of date based fields.
-  """
-  def __init__(self, entry, eventHandler, subEventHandler, displayMask,
-               inputMask):
-    BaseCursor.__init__(self, entry, eventHandler, subEventHandler,
-            displayMask, inputMask)
-    self.__displayMask = displayMask
-    self.__inputMask = inputMask
+class Date(DateRelated):
 
+    # -------------------------------------------------------------------------
+    # Constructor
+    # -------------------------------------------------------------------------
 
-  def build_display(self, value, editing):
-    if editing:
-      if self.__inputMask:
-        format = self.__inputMask
-      else:
-        format = "%m/%d/%Y"
-    else:
-      if self.__displayMask:
-        format = self.__displayMask
-      else:
-        format = "%m/%d/%y"
+    def __init__(self, entry, eventHandler, subEventHandler, display_mask,
+                 input_mask):
 
-    if value in (None, ""):
-      return ""
-    try:
-      return value.strftime (str (format))
-    except AttributeError:
-      return str(value)
+        DateRelated.__init__(self, entry, eventHandler, subEventHandler,
+                display_mask, input_mask)
 
+        self.__input_mask = input_mask or '%x'
+        self.__display_mask = display_mask or '%x'
 
-  def parse_display(self, display):
-    if not len(display):
-        return None
+        self.__order, self.__inter = self.get_date_order(self.__input_mask)
 
-    # TODO: Replace with format mask
-    if self.__inputMask:
-      try:
-        tempVal = time.strptime (display, self.__inputMask)
-        return datetime.datetime(*tempVal[0:6])
-      except ValueError:
+
+    # -------------------------------------------------------------------------
+    # Create a display string for the current value
+    # -------------------------------------------------------------------------
+
+    def build_display(self, value, editing):
+        """
+        """
+
+        if editing:
+            mask = self.__input_mask
+        else:
+            mask = self.__display_mask
+
+        if value in (None, ""):
+            return ""
+
+        try:
+            return value.strftime(str(mask))
+
+        except AttributeError:
+            return str(value)
+
+
+    # -------------------------------------------------------------------------
+    # Try to figure out which date the user meant
+    # -------------------------------------------------------------------------
+
+    def parse_display(self, display):
+        """
+        """
+
+        try:
+            # First have a look wether the input follows the requested format 
+            temp = time.strptime(display, self.__input_mask)
+            return datetime.date(*temp[0:3])
+
+        except ValueError:
+            pass
+
+        today = datetime.date.today()
+
+        # Ok, now let's do some guessing.
+
+        # If the input is a number of length 2 we treat it as day
+        if display.isdigit() and len(display) <= 2:
+            return today.replace(day=int(display))
+
+        # If the input is a 4-digit number or a string with two numbers
+        # separated by a non-digit string we treat it as "day and month"
+        # according to the order of the original input mask
+        match = re.match('^(\d+)\D+(\d+)\s*$', display)
+        if (display.isdigit() and len(display) == 4) or match is not None:
+            if match:
+                (val1, val2) = match.groups()
+            else:
+                val1, val2 = display[:2], display[2:]
+
+            if self.__order.index('day') < self.__order.index('month'):
+                day = int(val1)
+                month = int(val2)
+            else:
+                day = int(val2)
+                month = int(val1)
+
+            return today.replace(day=day, month=month)
+
+        # If the input is a 6-digit number or a triple of numeric values
+        # separated by non-digit characters it is likely a complete date
+        match = re.match('^(\d+)\D+(\d+)\D+(\d+).*$', display)
+        if (display.isdigit() and len(display) == 6) or match is not None:
+            if match:
+                values = match.groups()
+            else:
+                values = display[:2], display[2:4], display[4:]
+
+            kw = {}
+            for index, item in enumerate(values):
+                value = int(item)
+                if index == 2 and value < 100:
+                    # FIXME: need a better algorithm here
+                    if value > 50:
+                        value += 1900
+                    else:
+                        value += 2000
+                kw[self.__order[index].lower()] = value
+
+            return datetime.date(**kw)
+
+        # If the input is a 8-digit number it should be a complete date.  We
+        # derive the order of the elements from the order as given in the input
+        # mask.
+        if display.isdigit() and len(display) == 8:
+            for item in self.__order:
+                if item.lower() == 'year':
+                    year = int(display[:4])
+                    display = display[4:]
+                elif item == 'month':
+                    month = int(display[:2])
+                    display = display[2:]
+                elif item == 'day':
+                    day = int(display[:2])
+                    display = display[2:]
+
+            return datetime.date(day=day, month=month, year=year)
+                
         raise InvalidDateLiteral, display
-    
-    # TODO: Candidate for maketrans?
-    # TODO: This logic does not work for timestamps
-    #       as it skips the hour,minute, second
-    value = display.replace('.','/').replace('-','/')
-    
-    # Support for quick entry like '123102' for '12/31/02'
-    if len(value) in (6, 8) and value.find('/') == -1:
-      month = value[:2]
-      day = value[2:4]
-      year = value[4:]
 
-    elif value.find ('/') == -1:
-      raise InvalidDateLiteral, value
 
-    else:
-      month, day, year = value.split('/')
+    # -------------------------------------------------------------------------
+    # Autocomplete a user input
+    # -------------------------------------------------------------------------
 
-    # TODO: Shouldn't the 50 be a config option
-    year = int(year)
-    if year < 100:
-      if year > 50:
-        year = year + 1900
-      else:
-        year = year + 2000
+    def _autocomplete_(self, new_text, new_cursor):
 
-    return datetime.datetime(year, int(month), int(day))
+        # We do not autocomplete dates starting with the year
+        if self.__order[0].lower() == 'year':
+            return new_text, new_cursor
+
+        today = datetime.date.today().strftime(str(self.__input_mask))
+
+        # If the text is a number it must be the first component
+        if new_text.isdigit():
+            if self.__order[0] != 'month':
+                new_text = new_text + today[today.index(self.__inter[0]):]
+            return new_text, new_cursor
+
+        else:
+            parts = new_text.split(self.__inter[0], 1)
+            if len(parts) <> 2:
+                return new_text, new_cursor
+
+            lead = parts[0] + self.__inter[0]
+            rest = parts[1]
+            # If we have nothing after the first intersection symbol, we have
+            # to complete the rest
+            if not rest:
+                if self.__order[0] <> 'month':
+                    new_text = parts[0] + today[today.index(self.__inter[0]):]
+                return new_text, new_cursor
+
+            parts = rest.split(self.__inter[1], 1)
+            # If we have the first and the second component, stick it together
+            if len(parts) == 1:
+                lead += parts[0]
+                new_cursor = len(lead)
+                new_text = lead + today[today.rindex(self.__inter[1]):]
+
+                return new_text, new_cursor
+
+        return new_text, new_cursor
+
+
+class DateTime(BaseCursor):
+    """
+    Class to handle the display and entry of date based fields.
+    """
+    def __init__(self, entry, eventHandler, subEventHandler, displayMask,
+                 inputMask):
+        BaseCursor.__init__(self, entry, eventHandler, subEventHandler,
+                displayMask, inputMask)
+        self.__displayMask = displayMask
+        self.__inputMask = inputMask
+
+
+    def build_display(self, value, editing):
+        if editing:
+            if self.__inputMask:
+                format = self.__inputMask
+            else:
+                format = "%m/%d/%Y"
+        else:
+            if self.__displayMask:
+                format = self.__displayMask
+            else:
+                format = "%m/%d/%y"
+
+        if value in (None, ""):
+            return ""
+        try:
+            return value.strftime (str (format))
+        except AttributeError:
+            return str(value)
+
+
+    def parse_display(self, display):
+        if not len(display):
+            return None
+
+        # TODO: Replace with format mask
+        if self.__inputMask:
+            try:
+                tempVal = time.strptime (display, self.__inputMask)
+                return datetime.datetime(*tempVal[0:6])
+            except ValueError:
+                raise InvalidDateLiteral, display
+
+        # TODO: Candidate for maketrans?
+        # TODO: This logic does not work for timestamps
+        #       as it skips the hour,minute, second
+        value = display.replace('.','/').replace('-','/')
+
+        # Support for quick entry like '123102' for '12/31/02'
+        if len(value) in (6, 8) and value.find('/') == -1:
+            month = value[:2]
+            day = value[2:4]
+            year = value[4:]
+
+        elif value.find ('/') == -1:
+            raise InvalidDateLiteral, value
+
+        else:
+            month, day, year = value.split('/')
+
+        # TODO: Shouldn't the 50 be a config option
+        year = int(year)
+        if year < 100:
+            if year > 50:
+                year = year + 1900
+            else:
+                year = year + 2000
+
+        return datetime.datetime(year, int(month), int(day))





reply via email to

[Prev in Thread] Current Thread [Next in Thread]