Gnumed.timelinelib.wxgui.components.wxdatetimepicker

Package Gnumed :: Package timelinelib :: Package wxgui :: Package components :: Module wxdatetimepicker |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.wxgui.components.wxdatetimepicker

      1  # Copyright (C) 2009, 2010, 2011  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.config.paths import ICONS_DIR
     24  from timelinelib.time import try_to_create_wx_date_time_from_dmy
     25  from timelinelib.time import WxTimeType
     26  from timelinelib.wxgui.utils import _display_error_message
     27
     28
     29 -class WxDateTimePicker(wx.Panel):
     30
     31 -    def __init__(self, parent, show_time=True, config=None):
     32          wx.Panel.__init__(self, parent)
     33          self.config = config
     34          self._create_gui()
     35          self.controller = WxDateTimePickerController(
     36              self.date_picker, self.time_picker, wx.DateTime.Now)
     37          self.show_time(show_time)
     38
     39 -    def show_time(self, show=True):
     40          self.time_picker.Show(show)
     41          self.GetSizer().Layout()
     42
     43 -    def set_value(self, value):
     44          self.controller.set_value(value)
     45
     46 -    def get_value(self):
     47          return self.controller.get_value()
     48
     49 -    def _create_gui(self):
     50          self.date_picker = WxDatePicker(self)
     51          self.date_button = self._create_date_button()
     52          self.time_picker = WxTimePicker(self)
     53          self._layout_controls()
     54
     55 -    def _create_date_button(self):
     56          image = wx.Bitmap(os.path.join(ICONS_DIR, "calendar.png"))
     57          date_button = wx.BitmapButton(self, bitmap=image)
     58          self.Bind(wx.EVT_BUTTON, self._date_button_on_click, date_button)
     59          return date_button
     60
     61 -    def _layout_controls(self):
     62          sizer = wx.BoxSizer(wx.HORIZONTAL)
     63          flags = wx.ALIGN_CENTER_VERTICAL
     64          sizer.Add(self.date_picker, proportion=1, flag=flags)
     65          sizer.Add(self.date_button, proportion=0, flag=flags)
     66          sizer.Add(self.time_picker, proportion=0, flag=flags)
     67          self.SetSizerAndFit(sizer)
     68
     69 -    def _date_button_on_click(self, evt):
     70          try:
     71              self.calendar_popup = self._create_calendar_popup()
     72              self._position_calendar_popup(evt)
     73              self.calendar_popup.Popup()
     74          except:
     75              _display_error_message(_("Invalid date"))
     76
     77 -    def _create_calendar_popup(self):
     78          wx_date = self.controller.get_value()
     79          calendar_popup = CalendarPopup(self, wx_date, self.config)
     80          calendar_popup.Bind(wx.calendar.EVT_CALENDAR_SEL_CHANGED,
     81                              self._calendar_on_date_changed)
     82          calendar_popup.Bind(wx.calendar.EVT_CALENDAR,
     83                              self._calendar_on_date_changed_dclick)
     84          return calendar_popup
     85
     86 -    def _position_calendar_popup(self, evt):
     87          btn = evt.GetEventObject()
     88          pos = btn.ClientToScreen((0,0))
     89          size = btn.GetSize()
     90          self.calendar_popup.Position(pos, (0, size[1]))
     91
     92 -    def _calendar_on_date_changed(self, evt):
     93          wx_date = evt.GetEventObject().GetDate()
     94          self.date_picker.set_date(wx_date)
     95
     96 -    def _calendar_on_date_changed_dclick(self, evt):
     97          self.time_picker.SetFocus()
     98          self.calendar_popup.Dismiss()
     99
    100
    101 -class WxDateTimePickerController(object):
    102
    103 -    def __init__(self, date_picker, time_picker, now_fn):
    104          self.date_picker = date_picker
    105          self.time_picker = time_picker
    106          self.now_fn = now_fn
    107
    108 -    def set_value(self, date_time):
    109          if date_time == None:
    110              date_time = self.now_fn()
    111          self.date_picker.set_date(date_time)
    112          self.time_picker.set_time(date_time)
    113
    114 -    def get_value(self):
    115          wx_date = self.date_picker.get_date()
    116          if self.time_picker.IsShown():
    117              wx_time = self.time_picker.get_time()
    118              wx_date.SetHour(wx_time.GetHour())
    119              wx_date.SetMinute(wx_time.GetMinute())
    120          return wx_date
    121
    122
    123 -class CalendarPopup(wx.PopupTransientWindow):
    124
    125 -    def __init__(self, parent, wx_date, config):
    126          self.config = config
    127          self.controller = CalendarPopupController(self)
    128          wx.PopupTransientWindow.__init__(self, parent, style=wx.BORDER_NONE)
    129          border = 2
    130          style = self._get_cal_style()
    131          self.cal = wx.calendar.CalendarCtrl(self, -1, wx_date,
    132                                              pos=(border,border), style=style)
    133          self._set_cal_range()
    134          self._set_size(border)
    135          self._bind_events()
    136
    137 -    def _get_cal_style(self):
    138          style = (wx.calendar.CAL_SHOW_HOLIDAYS |
    139                   wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
    140          if self.config.week_start == "monday":
    141              style |= wx.calendar.CAL_MONDAY_FIRST
    142          else:
    143              style |= wx.calendar.CAL_SUNDAY_FIRST
    144          return style
    145
    146 -    def _set_cal_range(self):
    147          min_date, msg = WxTimeType().get_min_time()
    148          max_date, msg = WxTimeType().get_max_time()
    149          max_date -= wx.DateSpan.Day()
    150          self.cal.SetDateRange(min_date, max_date)
    151
    152 -    def _set_size(self, border):
    153          size = self.cal.GetBestSize()
    154          self.SetSize((size.width + border * 2, size.height + border * 2))
    155
    156 -    def _bind_events(self):
    157          def on_month(evt):
    158              self.controller.on_month()
    159          def on_day(evt):
    160              self.controller.on_day()
    161          self.cal.Bind(wx.calendar.EVT_CALENDAR_MONTH, on_month)
    162          self.cal.Bind(wx.calendar.EVT_CALENDAR_DAY, on_day)
    163
    164 -    def OnDismiss(self):
    165          self.controller.on_dismiss()
    166
    167
    168 -class CalendarPopupController(object):
    169
    170 -    def __init__(self, calendar_popup):
    171          self.calendar_popup = calendar_popup
    172          self.repop = False
    173          self.repoped = False
    174
    175 -    def on_month(self):
    176          self.repop = True
    177
    178 -    def on_day(self):
    179          self.repop = True
    180
    181 -    def on_dismiss(self):
    182          # This funny code makes the calender control stay open when you change
    183          # month or day. The control is closed on a double-click on a day or
    184          # a single click outside of the control
    185          if self.repop and not self.repoped:
    186              self.calendar_popup.Popup()
    187              self.repoped = True
    188
    189
    190 -class WxDatePicker(wx.TextCtrl):
    191
    192 -    def __init__(self, parent):
    193          wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER)
    194          self.controller = WxDatePickerController(self)
    195          self._bind_events()
    196          self._resize_to_fit_text()
    197
    198 -    def get_date(self):
    199          return self.controller.get_date()
    200
    201 -    def set_date(self, wx_date):
    202          self.controller.set_date(wx_date)
    203
    204 -    def get_date_string(self):
    205          return self.GetValue()
    206
    207 -    def set_date_string(self, date_string):
    208          return self.SetValue(date_string)
    209
    210 -    def _bind_events(self):
    211          self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
    212          self.Bind(wx.EVT_KILL_FOCUS, self._on_kill_focus)
    213          self.Bind(wx.EVT_CHAR, self._on_char)
    214          self.Bind(wx.EVT_TEXT, self._on_text)
    215          self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
    216
    217 -    def _on_set_focus(self, evt):
    218          # CallAfter is a trick to prevent default behavior of selecting all
    219          # text when a TextCtrl is given focus
    220          wx.CallAfter(self.controller.on_set_focus)
    221
    222 -    def _on_kill_focus(self, evt):
    223          # Trick to not make selection text disappear when focus is lost (we
    224          # remove the selection instead)
    225          self.controller.on_kill_focus()
    226          self.SetSelection(0, 0)
    227
    228 -    def _on_char(self, evt):
    229          if evt.GetKeyCode() == wx.WXK_TAB:
    230              if evt.ShiftDown():
    231                  skip = self.controller.on_shift_tab()
    232              else:
    233                  skip = self.controller.on_tab()
    234          else:
    235              skip = True
    236          evt.Skip(skip)
    237
    238 -    def _on_text(self, evt):
    239          self.controller.on_text_changed()
    240
    241 -    def _on_key_down(self, evt):
    242          if evt.GetKeyCode() == wx.WXK_UP:
    243              self.controller.on_up()
    244          elif evt.GetKeyCode() == wx.WXK_DOWN:
    245              self.controller.on_down()
    246          else:
    247              evt.Skip()
    248
    249 -    def _resize_to_fit_text(self):
    250          w, h = self.GetTextExtent("0000-00-00")
    251          width = w + 20
    252          self.SetMinSize((width, -1))
    253
    254
    255 -class WxDatePickerController(object):
    256
    257 -    def __init__(self, py_date_picker, error_bg="pink"):
    258          self.wx_date_picker = py_date_picker
    259          self.error_bg = error_bg
    260          self.original_bg = self.wx_date_picker.GetBackgroundColour()
    261          self.separator = WxTimeType().event_date_string(WxTimeType().now())[4]
    262          self.region_year = 0
    263          self.region_month = 1
    264          self.region_day = 2
    265          self.region_siblings = ((self.region_year, self.region_month),
    266                                  (self.region_month, self.region_day))
    267          self.preferred_day = None
    268          self.save_preferred_day = True
    269          self.last_selection = None
    270
    271 -    def get_date(self):
    272          try:
    273              (year, month, day) = self._parse_year_month_day()
    274              if year == 0:
    275                  raise ValueError("Invalid date.")
    276              if year < 0:
    277                  year +=1
    278              wx_date = try_to_create_wx_date_time_from_dmy(day, month - 1, year)
    279              self._ensure_date_within_allowed_period(wx_date)
    280              return wx_date
    281          except ValueError:
    282              raise ValueError("Invalid date.")
    283
    284 -    def set_date(self, wx_date):
    285          bc_year = wx.DateTime.ConvertYearToBC(wx_date.Year)
    286          tmp_date = wx.DateTimeFromDMY(wx_date.Day, wx_date.Month, bc_year)
    287          date_string = WxTimeType().event_date_string(tmp_date)
    288          self.wx_date_picker.set_date_string(date_string)
    289
    290 -    def on_set_focus(self):
    291          if self.last_selection:
    292              start, end = self.last_selection
    293              self.wx_date_picker.SetSelection(start, end)
    294          else:
    295              self._select_region_if_possible(self.region_year)
    296              self.last_selection = self.wx_date_picker.GetSelection()
    297
    298 -    def on_kill_focus(self):
    299          if self.last_selection:
    300              self.last_selection = self.wx_date_picker.GetSelection()
    301
    302 -    def on_tab(self):
    303          for (left_region, right_region) in self.region_siblings:
    304              if self._insertion_point_in_region(left_region):
    305                  self._select_region_if_possible(right_region)
    306                  return False
    307          return True
    308
    309 -    def on_shift_tab(self):
    310          for (left_region, right_region) in self.region_siblings:
    311              if self._insertion_point_in_region(right_region):
    312                  self._select_region_if_possible(left_region)
    313                  return False
    314          return True
    315
    316 -    def on_text_changed(self):
    317          self._change_background_depending_on_date_validity()
    318          if self._current_date_is_valid():
    319              current_date = self.get_date()
    320              # To prevent saving of preferred day when year or month is changed
    321              # in on_up() and on_down()...
    322              # Save preferred day only when text is entered in the date text
    323              # control and not when up or down keys has been used.
    324              # When up and down keys are used, the preferred day is saved in
    325              # on_up() and on_down() only when day is changed.
    326              if self.save_preferred_day:
    327                  self._save_preferred_day(current_date)
    328
    329 -    def on_up(self):
    330          if not self._current_date_is_valid():
    331              return
    332          if self._insertion_point_in_region(self.region_year):
    333              new_date = self._increment_year()
    334          elif self._insertion_point_in_region(self.region_month):
    335              new_date = self._increment_month()
    336          else:
    337              new_date = self._increment_day()
    338              self._save_preferred_day(new_date)
    339          self._set_new_date_and_restore_selection(new_date)
    340
    341 -    def on_down(self):
    342          if not self._current_date_is_valid():
    343              return
    344          if self._insertion_point_in_region(self.region_year):
    345              new_date = self._decrement_year()
    346          elif self._insertion_point_in_region(self.region_month):
    347              new_date = self._decrement_month()
    348          else:
    349              new_date = self._decrement_day()
    350              self._save_preferred_day(new_date)
    351          self._set_new_date_and_restore_selection(new_date)
    352
    353 -    def _increment_year(self):
    354          date = self.get_date()
    355          if date.Year < WxTimeType().get_max_time()[0].Year - 1:
    356              date = self._set_valid_day(date.Year + 1, date.Month, date.Day)
    357          return date
    358
    359 -    def _increment_month(self):
    360          date = self.get_date()
    361          if date.Month < 11:
    362              date = self._set_valid_day(date.Year, date.Month + 1, date.Day)
    363          elif date.Year < WxTimeType().get_max_time()[0].Year - 1:
    364              date = self._set_valid_day(date.Year + 1, 0, date.Day)
    365          return date
    366
    367 -    def _increment_day(self):
    368          date = self.get_date()
    369          if date <  WxTimeType().get_max_time()[0] - wx.TimeSpan.Day():
    370              date = date + wx.DateSpan.Day()
    371          return date
    372
    373 -    def _decrement_year(self):
    374          date = self.get_date()
    375          if date.Year > WxTimeType().get_min_time()[0].Year:
    376              date = self._set_valid_day(date.Year - 1, date.Month, date.Day)
    377          return date
    378
    379 -    def _decrement_month(self):
    380          date = self.get_date()
    381          if date.Month > 0:
    382              date = self._set_valid_day(date.Year, date.Month - 1, date.Day)
    383          elif date.Year > WxTimeType().get_min_time()[0].Year:
    384              date = self._set_valid_day(date.Year - 1, 11, date.Day)
    385          return date
    386
    387 -    def _decrement_day(self):
    388          date = self.get_date()
    389          if date.Day > 1:
    390              date.SetDay(date.Day - 1)
    391          elif date.Month > 0:
    392              date = self._set_valid_day(date.Year, date.Month - 1, 31)
    393          elif date.Year > WxTimeType().get_min_time()[0].Year:
    394              date = self._set_valid_day(date.Year - 1, 11, 31)
    395          return date
    396
    397 -    def _change_background_depending_on_date_validity(self):
    398          if self._current_date_is_valid():
    399              color = self.original_bg
    400          else:
    401              color = self.error_bg
    402          self.wx_date_picker.SetBackgroundColour(color)
    403          self.wx_date_picker.SetFocus()
    404          self.wx_date_picker.Refresh()
    405
    406 -    def _parse_year_month_day(self):
    407          date_string = self.wx_date_picker.get_date_string()
    408          date_bc = False
    409          if (date_string[0:1] == self.separator):
    410              date_string = date_string[1:]
    411              date_bc = True
    412          components = date_string.split(self.separator)
    413          if len(components) != 3:
    414              raise ValueError()
    415          year  = int(components[self.region_year])
    416          if date_bc:
    417              year = -year
    418          month = int(components[self.region_month])
    419          day   = int(components[self.region_day])
    420          return (year, month, day)
    421
    422 -    def _ensure_date_within_allowed_period(self, wx_date):
    423          if (wx_date >= WxTimeType().get_max_time()[0] or
    424              wx_date <  WxTimeType().get_min_time()[0]):
    425              raise ValueError()
    426
    427 -    def _set_new_date_and_restore_selection(self, new_date):
    428          def restore_selection(selection):
    429              self.wx_date_picker.SetSelection(selection[0], selection[1])
    430          selection = self.wx_date_picker.GetSelection()
    431          self.save_preferred_day = False
    432          if self.preferred_day != None:
    433              new_date = self._set_valid_day(new_date.Year, new_date.Month,
    434                                             self.preferred_day)
    435          self.set_date(new_date)
    436          restore_selection(selection)
    437          self.save_preferred_day = True
    438
    439 -    def _set_valid_day(self, new_year, new_month, new_day):
    440          while True:
    441              try:
    442                  return try_to_create_wx_date_time_from_dmy(new_day, new_month, new_year)
    443              except ValueError:
    444                  new_day -= 1
    445
    446 -    def _save_preferred_day(self, date):
    447          day = date.Day
    448          if day > 28:
    449              self.preferred_day = day
    450          else:
    451              self.preferred_day = None
    452
    453 -    def _current_date_is_valid(self):
    454          try:
    455              self.get_date()
    456          except ValueError:
    457              return False
    458          return True
    459
    460 -    def _select_region_if_possible(self, region):
    461          region_range = self._get_region_range(region)
    462          if region_range:
    463              self.wx_date_picker.SetSelection(region_range[0], region_range[-1])
    464
    465 -    def _insertion_point_in_region(self, n):
    466          region_range = self._get_region_range(n)
    467          if region_range:
    468              return self.wx_date_picker.GetInsertionPoint() in region_range
    469
    470 -    def _get_region_range(self, n):
    471          # Returns a range of valid cursor positions for a valid region year,
    472          # month or day.
    473          def region_is_not_valid(region):
    474              return region not in (self.region_year, self.region_month,
    475                                    self.region_day)
    476          def date_has_exactly_two_seperators(datestring):
    477              return len(datestring.split(self.separator)) == 3
    478          def calculate_pos_range(region, datestring):
    479              pos_of_separator1 = datestring.find(self.separator)
    480              pos_of_separator2 = datestring.find(self.separator,
    481                                                  pos_of_separator1 + 1)
    482              if region == self.region_year:
    483                  return range(0, pos_of_separator1 + 1)
    484              elif region == self.region_month:
    485                  return range(pos_of_separator1 + 1, pos_of_separator2 + 1)
    486              else:
    487                  return range(pos_of_separator2 + 1, len(datestring) + 1)
    488          if region_is_not_valid(n):
    489              return None
    490          date = self.wx_date_picker.get_date_string()
    491          if not date_has_exactly_two_seperators(date):
    492              return None
    493          pos_range = calculate_pos_range(n, date)
    494          return pos_range
    495
    496
    497 -class WxTimePicker(wx.TextCtrl):
    498
    499 -    def __init__(self, parent):
    500          wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER)
    501          self.controller = WxTimePickerController(self)
    502          self._bind_events()
    503          self._resize_to_fit_text()
    504
    505 -    def get_time(self):
    506          return self.controller.get_time()
    507
    508 -    def set_time(self, wx_time):
    509          self.controller.set_time(wx_time)
    510
    511 -    def get_time_string(self):
    512          return self.GetValue()
    513
    514 -    def set_time_string(self, time_string):
    515          self.SetValue(time_string)
    516
    517 -    def _bind_events(self):
    518          self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
    519          self.Bind(wx.EVT_KILL_FOCUS, self._on_kill_focus)
    520          self.Bind(wx.EVT_CHAR, self._on_char)
    521          self.Bind(wx.EVT_TEXT, self._on_text)
    522          self.Bind(wx.EVT_KEY_DOWN, self._on_key_down)
    523
    524 -    def _on_set_focus(self, evt):
    525          # CallAfter is a trick to prevent default behavior of selecting all
    526          # text when a TextCtrl is given focus
    527          wx.CallAfter(self.controller.on_set_focus)
    528
    529 -    def _on_kill_focus(self, evt):
    530          # Trick to not make selection text disappear when focus is lost (we
    531          # remove the selection instead)
    532          self.controller.on_kill_focus()
    533          self.SetSelection(0, 0)
    534
    535 -    def _on_char(self, evt):
    536          if evt.GetKeyCode() == wx.WXK_TAB:
    537              if evt.ShiftDown():
    538                  skip = self.controller.on_shift_tab()
    539              else:
    540                  skip = self.controller.on_tab()
    541          else:
    542              skip = True
    543          evt.Skip(skip)
    544
    545 -    def _on_text(self, evt):
    546          self.controller.on_text_changed()
    547
    548 -    def _on_key_down(self, evt):
    549          if evt.GetKeyCode() == wx.WXK_UP:
    550              self.controller.on_up()
    551          elif evt.GetKeyCode() == wx.WXK_DOWN:
    552              self.controller.on_down()
    553          else:
    554              evt.Skip()
    555
    556 -    def _resize_to_fit_text(self):
    557          w, h = self.GetTextExtent("00:00")
    558          width = w + 20
    559          self.SetMinSize((width, -1))
    560
    561
    562 -class WxTimePickerController(object):
    563
    564 -    def __init__(self, wx_time_picker):
    565          self.wx_time_picker = wx_time_picker
    566          self.original_bg = self.wx_time_picker.GetBackgroundColour()
    567          self.separator = WxTimeType().event_time_string(WxTimeType().now())[2]
    568          self.hour_part = 0
    569          self.minute_part = 1
    570          self.last_selection = None
    571
    572 -    def get_time(self):
    573          try:
    574              split = self.wx_time_picker.get_time_string().split(self.separator)
    575              if len(split) != 2:
    576                  raise ValueError()
    577              hour_string, minute_string = split
    578              hour = int(hour_string)
    579              minute = int(minute_string)
    580              return wx.DateTimeFromHMS(hour, minute, 0)
    581          except ValueError:
    582              raise ValueError("Invalid time.")
    583
    584 -    def set_time(self, wx_time):
    585          time_string = WxTimeType().event_time_string(wx_time)
    586          self.wx_time_picker.set_time_string(time_string)
    587
    588 -    def on_set_focus(self):
    589          if self.last_selection:
    590              start, end = self.last_selection
    591              self.wx_time_picker.SetSelection(start, end)
    592          else:
    593              self._select_part(self.hour_part)
    594
    595 -    def on_kill_focus(self):
    596          self.last_selection = self.wx_time_picker.GetSelection()
    597
    598 -    def on_tab(self):
    599          if self._in_minute_part():
    600              return True
    601          self._select_part(self.minute_part)
    602          return False
    603
    604 -    def on_shift_tab(self):
    605          if self._in_hour_part():
    606              return True
    607          self._select_part(self.hour_part)
    608          return False
    609
    610 -    def on_text_changed(self):
    611          try:
    612              self.get_time()
    613              self.wx_time_picker.SetBackgroundColour(self.original_bg)
    614          except ValueError:
    615              self.wx_time_picker.SetBackgroundColour("pink")
    616          self.wx_time_picker.Refresh()
    617
    618 -    def on_up(self):
    619          if not self._time_is_valid():
    620              return
    621          if self._in_hour_part():
    622              new_time = self._increment_hour()
    623          else:
    624              new_time = self._increment_minutes()
    625          self._set_new_time_and_restore_selection(new_time)
    626
    627 -    def on_down(self):
    628          if not self._time_is_valid():
    629              return
    630          if self._in_hour_part():
    631              new_time = self._decrement_hour()
    632          else:
    633              new_time = self._decrement_minutes()
    634          self._set_new_time_and_restore_selection(new_time)
    635
    636 -    def _increment_hour(self):
    637          time = self.get_time()
    638          new_hour = time.Hour + 1
    639          if new_hour > 23:
    640              new_hour = 0
    641          time.SetHour(new_hour)
    642          return time
    643
    644 -    def _increment_minutes(self):
    645          time = self.get_time()
    646          new_hour = time.Hour
    647          new_minute = time.Minute + 1
    648          if new_minute > 59:
    649              new_minute = 0
    650              new_hour = time.Hour + 1
    651              if new_hour > 23:
    652                  new_hour = 0
    653          time.SetHour(new_hour)
    654          time.SetMinute(new_minute)
    655          return time
    656
    657 -    def _decrement_hour(self):
    658          time = self.get_time()
    659          new_hour = time.Hour - 1
    660          if new_hour < 0:
    661              new_hour = 23
    662          time.SetHour(new_hour)
    663          return time
    664
    665 -    def _decrement_minutes(self):
    666          time = self.get_time()
    667          new_hour = time.Hour
    668          new_minute = time.Minute - 1
    669          if new_minute < 0:
    670              new_minute = 59
    671              new_hour = time.Hour - 1
    672              if new_hour < 0:
    673                  new_hour = 23
    674          time.SetHour(new_hour)
    675          time.SetMinute(new_minute)
    676          return time
    677
    678 -    def _set_new_time_and_restore_selection(self, new_time):
    679          def restore_selection(selection):
    680              self.wx_time_picker.SetSelection(selection[0], selection[1])
    681          selection = self.wx_time_picker.GetSelection()
    682          self.set_time(new_time)
    683          restore_selection(selection)
    684
    685 -    def _time_is_valid(self):
    686          try:
    687              self.get_time()
    688          except ValueError:
    689              return False
    690          return True
    691
    692 -    def _select_part(self, part):
    693          if self._separator_pos() == -1:
    694              return
    695          if part == self.hour_part:
    696              self.wx_time_picker.SetSelection(0, self._separator_pos())
    697          else:
    698              time_string_len = len(self.wx_time_picker.get_time_string())
    699              self.wx_time_picker.SetSelection(self._separator_pos() + 1, time_string_len)
    700          self.preferred_part = part
    701
    702 -    def _in_hour_part(self):
    703          if self._separator_pos() == -1:
    704              return
    705          return self.wx_time_picker.GetInsertionPoint() <= self._separator_pos()
    706
    707 -    def _in_minute_part(self):
    708          if self._separator_pos() == -1:
    709              return
    710          return self.wx_time_picker.GetInsertionPoint() > self._separator_pos()
    711
    712 -    def _separator_pos(self):
    713          return self.wx_time_picker.get_time_string().find(self.separator)
    714