dabo Commit Revision 6730 Date: 2011-08-05 09:01:02 -0700 (Fri, 05 Aug 2011) Author: Jacekk Trac: http://trac.dabodev.com/changeset/6730
Changed: U trunk/dabo/ui/uiwx/__init__.py A trunk/dabo/ui/uiwx/dDatePicker.py Log: Added cDatePicker control based on native wx.DatePickerCtrl. Warning! This control wasn't tested on OSX platform. Also color related properties may not work. Diff: Modified: trunk/dabo/ui/uiwx/__init__.py =================================================================== --- trunk/dabo/ui/uiwx/__init__.py 2011-07-26 14:03:02 UTC (rev 6729) +++ trunk/dabo/ui/uiwx/__init__.py 2011-08-05 16:01:02 UTC (rev 6730) @@ -102,6 +102,7 @@ from dCheckList import dCheckList from dColorDialog import dColorDialog from dComboBox import dComboBox +from dDatePicker import dDatePicker from dDateTextBox import dDateTextBox from dDropdownList import dDropdownList from dDialog import dDialog Added: trunk/dabo/ui/uiwx/dDatePicker.py =================================================================== --- trunk/dabo/ui/uiwx/dDatePicker.py (rev 0) +++ trunk/dabo/ui/uiwx/dDatePicker.py 2011-08-05 16:01:02 UTC (rev 6730) @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +""" +@author: Jacek Ka³ucki <[email protected]> +@note: Color setting doesn't work for this control. It's a wx issue. +""" +import datetime +import wx +import dabo +if __name__ == "__main__": + dabo.ui.loadUI("wx") +import dabo.dEvents as dEvents +import dDataControlMixin as dcm +from time import mktime +from dabo.dLocalize import _ +from dabo.lib.utils import ustr +from dabo.ui import makeDynamicProperty + + +def dateTimePy2Wx(date): + if isinstance(date, (datetime.datetime, datetime.date)): + retVal = wx.DateTimeFromTimeT(mktime(date.timetuple())) + if isinstance(date, datetime.datetime): + retVal.SetMillisecond(date.microsecond) + else: + retVal = date + return retVal + + +def dateTimeWx2Py(date): + if date.IsValid(): + retVal = datetime.datetime( + date.GetYear(), + date.GetMonth() + 1, + date.GetDay(), + date.GetHour(), + date.GetMinute(), + date.GetSecond(), + date.GetMillisecond() + ) + else: + retVal = None + return retVal + + +class dDatePicker(dcm.dDataControlMixin, wx.DatePickerCtrl): + """ + Creates a DatePicker control. + Control purpose is to maintain Date field types, but it can + be used for Timestamp data field types too. + It's behavior is similar to dDateTextBox control. + """ + + def __init__(self, parent, properties=None, attProperties=None, *args, **kwargs): + self._invalidBackColor = "Yellow" + self._valueMode = "d" + self._timePart = [0, 0, 0, 0] + self._lastWasNone = True + self._baseClass = dDatePicker + preClass = wx.PreDatePickerCtrl + pickerMode = self._extractKey((properties, attProperties, kwargs), + "PickerMode", "Dropdown")[:1].lower() + if pickerMode not in "ds": + pickerMode = "d" + kwargs["style"] = kwargs.get("style", 0) | \ + {"d": wx.DP_DROPDOWN, "s": wx.DP_SPIN}[pickerMode] + if self._extractKey((properties, attProperties, kwargs), "AllowNullDate", False): + kwargs["style"] |= wx.DP_ALLOWNONE + if self._extractKey((properties, attProperties, kwargs), "ForceShowCentury", False): + kwargs["style"] |= wx.DP_SHOWCENTURY + dcm.dDataControlMixin.__init__(self, preClass, parent, + properties, attProperties, *args, **kwargs) + self._bindKeys() + + def _initEvents(self): + super(dDatePicker, self)._initEvents() + self.Bind(wx.EVT_DATE_CHANGED, self._onWxHit) + + def _onWxHit(self, evt): + self._userChanged = True + self._lastWasNone = False + self.flushValue() + super(dDatePicker, self)._onWxHit(evt) + + def dayInterval(self, days): + """Adjusts the date by the given number of days; negative + values move backwards. + """ + self.Value += datetime.timedelta(days) + + def monthInterval(self, months): + """Adjusts the date by the given number of months; negative + values move backwards. + """ + val = self.Value + mn = val.month + months + yr = val.year + dy = val.day + while mn < 1: + yr -= 1 + mn += 12 + while mn > 12: + yr += 1 + mn -= 12 + # May still be an invalid day for the selected month + ok = False + while not ok: + try: + val = val.replace(year=yr, month=mn, day=dy) + ok = True + except ValueError: + dy -= 1 + self.Value = val + + def setCurrentDate(self): + if self._valueMode == "d": + val = datetime.date.today() + else: + val = datetime.datetime.now() + self.Value = val + + def setToMonthDay(self, day): + val = self.Value + if isinstance(day, basestring): + if day[:1].lower() == "f": + val = val.replace(day=1) + elif day[:1].lower() == "l": + mn = val.month + td = datetime.timedelta(1) + while mn == val.month: + val += td + # We're now at the first of the next month. Go back one. + val -= td + else: + val = val.replace(day=day) + self.Value = val + + def setToYearDay(self, day): + val = self.Value + if isinstance(day, basestring): + if day[:1].lower() == "f": + val = val.replace(month=1, day=1) + elif day[:1].lower() == "l": + val = val.replace(month=12, day=31) + self.Value = val + + def _processKey(self, evt): + key = evt.EventData["keyCode"] + if key == 43: # + + self.dayInterval(1) + elif key == 45: # - + self.dayInterval(-1) + elif key == 116: # T + self.setCurrentDate() + elif key == 91: # [ + self.monthInterval(-1) + elif key == 93: # ] + self.monthInterval(1) + elif key == 109: # m + self.setToMonthDay("First") + elif key == 104: # h + self.setToMonthDay("Last") + elif key == 121: # y + self.setToYearDay("First") + elif key == 114: # r + self.setToYearDay("Last") + elif key == 100: # d + self._setCustomDate() + else: + print key + + def _setCustomDate(self): + days = dabo.ui.getInt( + message=_("Day shift:"), caption=_("Reschedule day"), Min= -365, Max=365) + if days: + self.dayInterval(days) + + def _bindKeys(self): + # It seems that on Windows platform there is a bug in + # control key handling implementation, because '=' key + # is recognized as '+' key. + # On Linux, '+' key seems to be unsupported. + self.bindKey("+", self._processKey) + self.bindKey("-", self._processKey) + self.bindKey("d", self._processKey) + self.bindKey("t", self._processKey) + self.bindKey("[", self._processKey) + self.bindKey("]", self._processKey) + self.bindKey("m", self._processKey) + self.bindKey("h", self._processKey) + self.bindKey("y", self._processKey) + self.bindKey("r", self._processKey) + self.bindKey("=", self._processKey) + + def _getPyValue(self, val): + if self._lastWasNone: + val = None + elif val: + if self._valueMode == "d": + val = datetime.date(val.year, val.month, val.day) + else: + val = val.combine(val, datetime.time( + self._timePart[0], + self._timePart[1], + self._timePart[2], + self._timePart[3] + ) + ) + return val + + def _getWxValue(self, val): + if isinstance(val, basestring): + val = datetime.datetime.strptime(val, "%Y-%m-%d") + elif isinstance(val, tuple): + val = datetime.datetime(*val) + elif isinstance(val, wx.DateTime): + return val + if val is not None: + self._valueMode = "d" if type(val) == datetime.date else "t" + if self._valueMode == "t": + if val is None: + self._timePart = [0, 0, 0, 0] + else: + self._timePart[0] = val.hour + self._timePart[1] = val.minute + self._timePart[2] = val.second + self._timePart[3] = val.microsecond + if val is None: + self._lastWasNone = True + if self.AllowNullDate: + val = wx.DateTime() + else: + val = self.GetLowerLimit() + else: + self._lastWasNone = False + val = dateTimePy2Wx(val) + return val + + def setInvalidDate(self): + self.Value = wx.DefaultDateTime + + def GetValue(self): + try: + val = dateTimeWx2Py(super(dDatePicker, self).GetValue()) + except wx.PyAssertionError: + val = None + return self._getPyValue(val) + + def SetValue(self, val): + val = self._getWxValue(val) + try: + super(dDatePicker, self).SetValue(val) + except ValueError, e: + dabo.errorLog.write(_(u"Object '%s' has the following error: %s") % (self.Name, ustr(e))) + + def _getAllowNullDate(self): + return self._hasWindowStyleFlag(wx.DP_ALLOWNONE) + + def _setAllowNullDate(self, val): + if val: + self._addWindowStyleFlag(wx.DP_ALLOWNONE) + else: + self._delWindowStyleFlag(wx.DP_ALLOWNONE) + + def _getForceShowCentury(self): + return self._hasWindowStyleFlag(wx.DP_SHOWCENTURY) + + def _setForceShowCentury(self): + if val: + self._addWindowStyleFlag(wx.DP_SHOWCENTURY) + else: + self._delWindowStyleFlag(wx.DP_SHOWCENTURY) + + def _getInvalidBackColor(self): + return self._invalidBackColor + + def _setInvalidBackColor(self, val): + self._invalidBackColor = val + + def _getIsDateValid(self): + return self.Value is not None + + def _getMaxValue(self): + return self._getPyValue(dateTimeWx2Py(self.UpperLimit)) + + def _setMaxValue(self, val): + if self._constructed(): + val = self._getWxValue(val) + self.SetRange(self.LowerLimit, val) + else: + self._properties["MinValue"] = val + + def _getMinValue(self): + return self._getPyValue(dateTimeWx2Py(self.LowerLimit)) + + def _setMinValue(self, val): + if self._constructed(): + val = self._getWxValue(val) + self.SetRange(val, self.UpperLimit) + else: + self._properties["MinValue"] = val + + def _getPickerMode(self): + if self._hasWindowStyleFlag(wx.DP_DROPDOWN): + mode = "Dropdown" + else: + mode = "Spin" + return mode + + def _setPickerMode(self, val): + mode = val[:1].lower() + if mode in "ds": + self._addWindowStyleFlag({"d": wx.DP_DROPDOWN, "s": wx.DP_SPIN}[mode]) + else: + raise ValueError(_("The only allowed values are: 'Dropdown', 'Spin'.")) + + def _getValueMode(self): + return {"d": "Date", "t": "Timestamp"}[self._valueMode] + + def _setValueMode(self, val): + val = val[:1].lower() + if val in "dt": + self._valueMode = val + else: + raise ValueError(_("The only allowed values are: 'Date', 'Timestamp'.")) + + # Property definitions: + + AllowNullDate = property(_getAllowNullDate, _setAllowNullDate, None, + _("""If True enable Null vale in date. (bool)(Default=False)""")) + + ForceShowCentury = property(_getForceShowCentury, _setForceShowCentury, None, + _("""Regardless of locale setting, century is shown if True. (bool) + (Default=False)""")) + + IsDateValid = property(_getIsDateValid, None, None, + _("""Read-only property tells if Value holds valid date type value.""")) + + InvalidBackColor = property(_getInvalidBackColor, _setInvalidBackColor, None, + _("""Color value used for illegal values or values out-of-teh bounds. (str) + (Default="Yellow")""")) + + MaxValue = property(_getMaxValue, _setMaxValue, None, + _("""Holds upper value limit. (date, tuple, str)(Default=None)""")) + + MinValue = property(_getMinValue, _setMinValue, None, + _("""Holds lower value limit. (date, tuple, str)(Default=None)""")) + + PickerMode = property(_getPickerMode, _setPickerMode, None, + _("""Creates control with spin or dropdown calendar. (str) + Available values are: + - Spin + - Dropdown (default)""")) + + ValueMode = property(_getValueMode, _setValueMode, None, + _("""Enables handling Timestamp type. (str)(Default="Date")""")) + + DynamicMaxValue = makeDynamicProperty(MaxValue) + DynamicMinValue = makeDynamicProperty(MinValue) + + +if __name__ == "__main__": + import test + + class TestBase(dDatePicker): + + def onValueChanged(self, evt): + print "onValueChanged" + + test.Test().runTest(TestBase, AllowNullDate=True) + test.Test().runTest(TestBase, BackColor="orange", PickerMode="Spin", AllowNullDate=True)
_______________________________________________ Post Messages to: [email protected] Subscription Maintenance: http://leafe.com/mailman/listinfo/dabo-dev Searchable Archives: http://leafe.com/archives/search/dabo-dev This message: http://leafe.com/archives/byMID/[email protected]
