Package Gnumed :: Package timelinelib :: Package calendar :: Package gregorian :: Package timepicker :: Module datetime |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.calendar.gregorian.timepicker.datetime

      1  # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
      2  #
      3  # This file is part of Timeline.
      4  #
      5  # Timeline is free software: you can redistribute it and/or modify
      6  # it under the terms of the GNU General Public License as published by
      7  # the Free Software Foundation, either version 3 of the License, or
      8  # (at your option) any later version.
      9  #
     10  # Timeline is distributed in the hope that it will be useful,
     11  # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  # GNU General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU General Public License
     16  # along with Timeline.  If not, see <http://www.gnu.org/licenses/>.
     17
     18
     19  import os.path
     20
     21  import wx.calendar
     22
     23  from timelinelib.calendar.gregorian.gregorian import GregorianDateTime
     24  from timelinelib.calendar.gregorian.time import GregorianTime
     25  from timelinelib.calendar.gregorian.timepicker.date import GregorianDatePicker
     26  from timelinelib.calendar.gregorian.timepicker.time import GregorianTimePicker
     27  from timelinelib.calendar.gregorian.timetype import GregorianTimeType
     28  from timelinelib.config.paths import ICONS_DIR
     29  from timelinelib.wxgui.utils import display_information_message
     30
     31
     32  ERROR_MESSAGE = _("The date control can't handle the given date")
     33
     34
     35 -class GregorianDateTimePicker(wx.Panel):
     36
     37 -    def __init__(self, parent, show_time=True, config=None, on_change=None):
     38          wx.Panel.__init__(self, parent)
     39          self.config = config
     40          self._create_gui()
     41          self.controller = GregorianDateTimePickerController(self,
     42                                                              self.date_picker,
     43                                                              self.time_picker,
     44                                                              GregorianTimeType().now, on_change)
     45          self.show_time(show_time)
     46          self.parent = parent
     47
     48 -    def PopupCalendar(self, evt, wx_date):
     49          calendar_popup = CalendarPopup(self, wx_date, self.config)
     50          calendar_popup.Bind(wx.calendar.EVT_CALENDAR_SEL_CHANGED,
     51                              self._calendar_on_date_changed)
     52          calendar_popup.Bind(wx.calendar.EVT_CALENDAR,
     53                              self._calendar_on_date_changed_dclick)
     54          btn = evt.GetEventObject()
     55          pos = btn.ClientToScreen((0, 0))
     56          sz = btn.GetSize()
     57          calendar_popup.Position(pos, (0, sz[1]))
     58          calendar_popup.Popup()
     59          self.calendar_popup = calendar_popup
     60
     61 -    def on_return(self):
     62          try:
     63              self.parent.on_return()
     64          except AttributeError:
     65              pass
     66
     67 -    def on_escape(self):
     68          try:
     69              self.parent.on_escape()
     70          except AttributeError:
     71              pass
     72
     73 -    def show_time(self, show=True):
     74          self.time_picker.Show(show)
     75          self.GetSizer().Layout()
     76
     77 -    def get_value(self):
     78          try:
     79              return self.controller.get_value()
     80          except ValueError:
     81              pass
     82
     83 -    def set_value(self, value):
     84          self.controller.set_value(value)
     85
     86 -    def _create_gui(self):
     87          self.date_picker = self._create_date_picker()
     88          image = wx.Bitmap(os.path.join(ICONS_DIR, "calendar.png"))
     89          self.date_button = wx.BitmapButton(self, bitmap=image)
     90          self.Bind(wx.EVT_BUTTON, self._date_button_on_click, self.date_button)
     91          self.time_picker = GregorianTimePicker(self)
     92          # Layout
     93          sizer = wx.BoxSizer(wx.HORIZONTAL)
     94          sizer.Add(self.date_picker, proportion=1,
     95                    flag=wx.ALIGN_CENTER_VERTICAL)
     96          sizer.Add(self.date_button, proportion=0,
     97                    flag=wx.ALIGN_CENTER_VERTICAL)
     98          sizer.Add(self.time_picker, proportion=0,
     99                    flag=wx.ALIGN_CENTER_VERTICAL)
    100          self.SetSizerAndFit(sizer)
    101
    102 -    def _create_date_picker(self):
    103          return GregorianDatePicker(self, self.config.get_date_formatter())
    104
    105 -    def _date_button_on_click(self, evt):
    106          self.controller.date_button_on_click(evt)
    107
    108 -    def _out_of_date_range(self, wx_date):
    109          """It's is a limitation in the wx.calendar.CalendarCtrl class
    110          that has this date limit."""
    111          return str(wx_date) < '1601-01-01 00:00:00'
    112
    113 -    def _calendar_on_date_changed(self, evt):
    114          wx_date = evt.GetEventObject().GetDate()
    115          date = self.controller.wx_date_to_date_tuple(wx_date)
    116          self.date_picker.SetGregorianDate(date)
    117
    118 -    def _calendar_on_date_changed_dclick(self, evt):
    119          self.time_picker.SetFocus()
    120          self.calendar_popup.Dismiss()
    121
    122
    123 -class GregorianDateTimePickerController(object):
    124
    125 -    def __init__(self, view, date_picker, time_picker, now_fn, on_change):
    126          self._view = view
    127          self.date_picker = date_picker
    128          self.time_picker = time_picker
    129          self.now_fn = now_fn
    130          self.on_change = on_change
    131
    132 -    def get_value(self):
    133          if self.time_picker.IsShown():
    134              hour, minute, second = self.time_picker.GetGregorianTime()
    135          else:
    136              hour, minute, second = (0, 0, 0)
    137          year, month, day = self.date_picker.GetGregorianDate()
    138          return GregorianDateTime(year, month, day, hour, minute, second).to_time()
    139
    140 -    def set_value(self, time):
    141          if time is None:
    142              time = self.now_fn()
    143          self.date_picker.SetGregorianDate(GregorianDateTime.from_time(time).to_date_tuple())
    144          self.time_picker.SetGregorianTime(GregorianDateTime.from_time(time).to_time_tuple())
    145          if self.on_change is not None:
    146              self.on_change()
    147
    148 -    def date_tuple_to_wx_date(self, date):
    149          year, month, day = date
    150          return wx.DateTimeFromDMY(day, month - 1, year, 0, 0, 0)
    151
    152 -    def wx_date_to_date_tuple(self, wx_date):
    153          return (wx_date.Year, wx_date.Month + 1, wx_date.Day)
    154
    155 -    def date_button_on_click(self, evt):
    156          try:
    157              dt = self.date_picker.GetGregorianDate()
    158              wx_date = self.date_tuple_to_wx_date(dt)
    159          except ValueError:
    160              wx_date = wx.DateTime.Now()
    161          except wx._core.PyAssertionError:
    162              display_information_message('wx.DateTime limitation', ERROR_MESSAGE)
    163          else:
    164              try:
    165                  self._view.PopupCalendar(evt, wx_date)
    166              except wx._core.PyAssertionError:
    167                  display_information_message('GUI control limitation', ERROR_MESSAGE)
    168
    169
    170 -class CalendarPopup(wx.PopupTransientWindow):
    171
    172 -    def __init__(self, parent, wx_date, config):
    173          self.config = config
    174          wx.PopupTransientWindow.__init__(self, parent, style=wx.BORDER_NONE)
    175          self._create_gui(wx_date)
    176          self.controller = CalendarPopupController(self)
    177          self._bind_events()
    178
    179 -    def _create_gui(self, wx_date):
    180          BORDER = 2
    181          self.cal = self._create_calendar_control(wx_date, BORDER)
    182          size = self.cal.GetBestSize()
    183          self.SetSize((size.width + BORDER * 2, size.height + BORDER * 2))
    184
    185 -    def _create_calendar_control(self, wx_date, border):
    186          style = self._get_cal_style()
    187          cal = wx.calendar.CalendarCtrl(self, -1, wx_date,
    188                                         pos=(border, border), style=style)
    189          self._set_cal_range(cal)
    190          return cal
    191
    192 -    def _get_cal_style(self):
    193          style = (wx.calendar.CAL_SHOW_HOLIDAYS |
    194                   wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
    195          if self.config.get_week_start() == "monday":
    196              style |= wx.calendar.CAL_MONDAY_FIRST
    197          else:
    198              style |= wx.calendar.CAL_SUNDAY_FIRST
    199          return style
    200
    201 -    def _set_cal_range(self, cal):
    202          min_date = GregorianTimeType().get_min_time()
    203          max_date = GregorianTimeType().get_max_time()
    204          min_date = self.time_to_wx_date(min_date)
    205          max_date = self.time_to_wx_date(max_date) - wx.DateSpan.Day()
    206          cal.SetDateRange(min_date, max_date)
    207
    208 -    def time_to_wx_date(self, time):
    209          year, month, day = GregorianDateTime.from_time(time).to_date_tuple()
    210          try:
    211              return wx.DateTimeFromDMY(day, month - 1, year, 0, 0, 0)
    212          except OverflowError:
    213              if year < 0:
    214                  year, month, day = GregorianDateTime.from_time(GregorianTime(0, 0)).to_date_tuple()
    215                  return wx.DateTimeFromDMY(day, month - 1, year, 0, 0, 0)
    216
    217 -    def _bind_events(self):
    218          def on_month(evt):
    219              self.controller.on_month()
    220
    221          def on_day(evt):
    222              self.controller.on_day()
    223
    224          self.cal.Bind(wx.calendar.EVT_CALENDAR_MONTH, on_month)
    225          self.cal.Bind(wx.calendar.EVT_CALENDAR_DAY, on_day)
    226
    227 -    def OnDismiss(self):
    228          self.controller.on_dismiss()
    229
    230
    231 -class CalendarPopupController(object):
    232
    233 -    def __init__(self, calendar_popup):
    234          self.calendar_popup = calendar_popup
    235          self.repop = False
    236          self.repoped = False
    237
    238 -    def on_month(self):
    239          self.repop = True
    240
    241 -    def on_day(self):
    242          self.repop = True
    243
    244 -    def on_dismiss(self):
    245          # This funny code makes the calender control stay open when you change
    246          # month or day. The control is closed on a double-click on a day or
    247          # a single click outside of the control
    248          if self.repop and not self.repoped:
    249              try:
    250                  self.calendar_popup.Popup()
    251              except wx.PyAssertionError:
    252                  # This happens if you open the calendar popup, clik and hold
    253                  # down the mouse on a day and thereafter drag the mouse outside
    254                  # of the calendar control, release the mouse, and click outside
    255                  # the clandar control.
    256                  pass
    257              self.repoped = True
    258