1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import os.path
20 import datetime
21
22 import wx.calendar
23
24 from timelinelib.config.paths import ICONS_DIR
25 from timelinelib.time import PyTimeType
26 from timelinelib.wxgui.utils import _display_error_message
27
28
30
31 - def __init__(self, parent, show_time=True, config=None):
38
40 self.time_picker.Show(show)
41 self.GetSizer().Layout()
42
45
48
50 self.date_picker = PyDatePicker(self)
51 image = wx.Bitmap(os.path.join(ICONS_DIR, "calendar.png"))
52 self.date_button = wx.BitmapButton(self, bitmap=image)
53 self.Bind(wx.EVT_BUTTON, self._date_button_on_click, self.date_button)
54 self.time_picker = PyTimePicker(self)
55
56 sizer = wx.BoxSizer(wx.HORIZONTAL)
57 sizer.Add(self.date_picker, proportion=1,
58 flag=wx.ALIGN_CENTER_VERTICAL)
59 sizer.Add(self.date_button, proportion=0,
60 flag=wx.ALIGN_CENTER_VERTICAL)
61 sizer.Add(self.time_picker, proportion=0,
62 flag=wx.ALIGN_CENTER_VERTICAL)
63 self.SetSizerAndFit(sizer)
64
81
82
84 wx_date = evt.GetEventObject().GetDate()
85 py_date = datetime.datetime(wx_date.Year, wx_date.Month+1, wx_date.Day)
86 self.date_picker.set_py_date(py_date)
87
89 self.time_picker.SetFocus()
90 self.calendar_popup.Dismiss()
91
93 return wx.DateTimeFromDMY(py_date.day, py_date.month-1, py_date.year,
94 0, 0, 0)
95
96
98
99 - def __init__(self, date_picker, time_picker, now_fn):
100 self.date_picker = date_picker
101 self.time_picker = time_picker
102 self.now_fn = now_fn
103
109
111 if py_date_time == None:
112 py_date_time = self.now_fn()
113 self.date_picker.set_py_date(py_date_time.date())
114 self.time_picker.set_py_time(py_date_time.time())
115
116
118
125
127 BORDER = 2
128 self.cal = self._create_calendar_control(wx_date, BORDER)
129 size = self.cal.GetBestSize()
130 self.SetSize((size.width + BORDER * 2, size.height + BORDER * 2))
131
133 style = self._get_cal_style()
134 cal = wx.calendar.CalendarCtrl(self, -1, wx_date,
135 pos=(border,border), style=style)
136 self._set_cal_range(cal)
137 return cal
138
140 style = (wx.calendar.CAL_SHOW_HOLIDAYS |
141 wx.calendar.CAL_SEQUENTIAL_MONTH_SELECTION)
142 if self.config.week_start == "monday":
143 style |= wx.calendar.CAL_MONDAY_FIRST
144 else:
145 style |= wx.calendar.CAL_SUNDAY_FIRST
146 return style
147
149 min_date, msg = PyTimeType().get_min_time()
150 max_date, msg = PyTimeType().get_max_time()
151 min_date = self._py_date_to_wx_date(min_date)
152 max_date = self._py_date_to_wx_date(max_date) - wx.DateSpan.Day()
153 cal.SetDateRange(min_date, max_date)
154
156 return wx.DateTimeFromDMY(py_date.day, py_date.month - 1, py_date.year,
157 0, 0, 0)
158
160 def on_month(evt):
161 self.controller.on_month()
162 def on_day(evt):
163 self.controller.on_day()
164 self.cal.Bind(wx.calendar.EVT_CALENDAR_MONTH, on_month)
165 self.cal.Bind(wx.calendar.EVT_CALENDAR_DAY, on_day)
166
169
170
172
174 self.calendar_popup = calendar_popup
175 self.repop = False
176 self.repoped = False
177
180
183
185
186
187
188 if self.repop and not self.repoped:
189 self.calendar_popup.Popup()
190 self.repoped = True
191
192
194
196 wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER)
197 self.controller = PyDatePickerController(self)
198 self._bind_events()
199 self._resize_to_fit_text()
200
203
206
209
212
214 def on_set_focus(evt):
215
216
217 wx.CallAfter(self.controller.on_set_focus)
218 self.Bind(wx.EVT_SET_FOCUS, on_set_focus)
219 def on_kill_focus(evt):
220
221
222 self.controller.on_kill_focus()
223 self.SetSelection(0, 0)
224 self.Bind(wx.EVT_KILL_FOCUS, on_kill_focus)
225 def on_char(evt):
226 if evt.GetKeyCode() == wx.WXK_TAB:
227 if evt.ShiftDown():
228 skip = self.controller.on_shift_tab()
229 else:
230 skip = self.controller.on_tab()
231 else:
232 skip = True
233 evt.Skip(skip)
234 self.Bind(wx.EVT_CHAR, on_char)
235 def on_text(evt):
236 self.controller.on_text_changed()
237 self.Bind(wx.EVT_TEXT, on_text)
238 def on_key_down(evt):
239 if evt.GetKeyCode() == wx.WXK_UP:
240 self.controller.on_up()
241 elif evt.GetKeyCode() == wx.WXK_DOWN:
242 self.controller.on_down()
243 else:
244 evt.Skip()
245 self.Bind(wx.EVT_KEY_DOWN, on_key_down)
246
248 w, h = self.GetTextExtent("0000-00-00")
249 width = w + 20
250 self.SetMinSize((width, -1))
251
252
254
255 - def __init__(self, py_date_picker, error_bg="pink"):
256 self.py_date_picker = py_date_picker
257 self.error_bg = error_bg
258 self.original_bg = self.py_date_picker.GetBackgroundColour()
259 self.separator = PyTimeType().event_date_string(PyTimeType().now())[4]
260 self.region_year = 0
261 self.region_month = 1
262 self.region_day = 2
263 self.region_siblings = ((self.region_year, self.region_month),
264 (self.region_month, self.region_day))
265 self.preferred_day = None
266 self.save_preferred_day = True
267 self.last_selection = None
268
270 try:
271 (year, month, day) = self._parse_year_month_day()
272 py_date = datetime.date(year, month, day)
273 self._ensure_within_allowed_period(py_date)
274 return py_date
275 except ValueError:
276 raise ValueError("Invalid date.")
277
281
283 if self.last_selection:
284 start, end = self.last_selection
285 self.py_date_picker.SetSelection(start, end)
286 else:
287 self._select_region_if_possible(self.region_year)
288 self.last_selection = self.py_date_picker.GetSelection()
289
291 if self.last_selection:
292 self.last_selection = self.py_date_picker.GetSelection()
293
295 for (left_region, right_region) in self.region_siblings:
296 if self._insertion_point_in_region(left_region):
297 self._select_region_if_possible(right_region)
298 return False
299 return True
300
302 for (left_region, right_region) in self.region_siblings:
303 if self._insertion_point_in_region(right_region):
304 self._select_region_if_possible(left_region)
305 return False
306 return True
307
308 - def on_text_changed(self):
309 self._change_background_depending_on_date_validity()
310 if self._current_date_is_valid():
311 current_date = self.get_py_date()
312
313
314
315
316
317
318 if self.save_preferred_day:
319 self._save_preferred_day(current_date)
320
326 def increment_month(date):
327 if date.month < 12:
328 return self._set_valid_day(date.year, date.month + 1,
329 date.day)
330 elif date.year < PyTimeType().get_max_time()[0].year - 1:
331 return self._set_valid_day(date.year + 1, 1, date.day)
332 return date
333 def increment_day(date):
334 if date < PyTimeType().get_max_time()[0].date() - datetime.timedelta(days=1):
335 return date + datetime.timedelta(days=1)
336 return date
337 if not self._current_date_is_valid():
338 return
339 selection = self.py_date_picker.GetSelection()
340 current_date = self.get_py_date()
341 if self._insertion_point_in_region(self.region_year):
342 new_date = increment_year(current_date)
343 elif self._insertion_point_in_region(self.region_month):
344 new_date = increment_month(current_date)
345 else:
346 new_date = increment_day(current_date)
347 self._save_preferred_day(new_date)
348 if current_date != new_date:
349 self._set_new_date_and_restore_selection(new_date, selection)
350
356 def decrement_month(date):
357 if date.month > 1:
358 return self._set_valid_day(date.year, date.month - 1, date.day)
359 elif date.year > PyTimeType().get_min_time()[0].year:
360 return self._set_valid_day(date.year - 1, 12, date.day)
361 return date
362 def decrement_day(date):
363 if date.day > 1:
364 return date.replace(day=date.day - 1)
365 elif date.month > 1:
366 return self._set_valid_day(date.year, date.month - 1, 31)
367 elif date.year > PyTimeType().get_min_time()[0].year:
368 return self._set_valid_day(date.year - 1, 12, 31)
369 return date
370 if not self._current_date_is_valid():
371 return
372 selection = self.py_date_picker.GetSelection()
373 current_date = self.get_py_date()
374 if self._insertion_point_in_region(self.region_year):
375 new_date = decrement_year(current_date)
376 elif self._insertion_point_in_region(self.region_month):
377 new_date = decrement_month(current_date)
378 else:
379 new_date = decrement_day(current_date)
380 self._save_preferred_day(new_date)
381 if current_date != new_date:
382 self._set_new_date_and_restore_selection(new_date, selection)
383
385 if self._current_date_is_valid():
386 self.py_date_picker.SetBackgroundColour(self.original_bg)
387 else:
388 self.py_date_picker.SetBackgroundColour(self.error_bg)
389 self.py_date_picker.SetFocus()
390 self.py_date_picker.Refresh()
391
400
406
408 def restore_selection(selection):
409 self.py_date_picker.SetSelection(selection[0], selection[1])
410 self.save_preferred_day = False
411 if self.preferred_day != None:
412 new_date = self._set_valid_day(new_date.year, new_date.month,
413 self.preferred_day)
414 self.set_py_date(new_date)
415 restore_selection(selection)
416 self.save_preferred_day = True
417
419 done = False
420 while not done:
421 try:
422 date = datetime.date(year=new_year, month=new_month, day=new_day)
423 done = True
424 except Exception, ex:
425 new_day -= 1
426 return date
427
429 if date.day > 28:
430 self.preferred_day = date.day
431 else:
432 self.preferred_day = None
433
435 try:
436 self.get_py_date()
437 except ValueError:
438 return False
439 return True
440
442 region_range = self._get_region_range(region)
443 if region_range:
444 self.py_date_picker.SetSelection(region_range[0], region_range[-1])
445
447 region_range = self._get_region_range(n)
448 if region_range:
449 return self.py_date_picker.GetInsertionPoint() in region_range
450
452
453
454 def region_is_not_valid(region):
455 return region not in (self.region_year, self.region_month,
456 self.region_day)
457 def date_has_exactly_two_seperators(datestring):
458 return len(datestring.split(self.separator)) == 3
459 def calculate_pos_range(region, datestring):
460 pos_of_separator1 = datestring.find(self.separator)
461 pos_of_separator2 = datestring.find(self.separator,
462 pos_of_separator1 + 1)
463 if region == self.region_year:
464 return range(0, pos_of_separator1 + 1)
465 elif region == self.region_month:
466 return range(pos_of_separator1 + 1, pos_of_separator2 + 1)
467 else:
468 return range(pos_of_separator2 + 1, len(datestring) + 1)
469 if region_is_not_valid(n):
470 return None
471 date = self.py_date_picker.get_date_string()
472 if not date_has_exactly_two_seperators(date):
473 return None
474 pos_range = calculate_pos_range(n, date)
475 return pos_range
476
477
479
481 wx.TextCtrl.__init__(self, parent, style=wx.TE_PROCESS_ENTER)
482 self.controller = PyTimePickerController(self)
483 self._bind_events()
484 self._resize_to_fit_text()
485
488
491
494
497
499 def on_set_focus(evt):
500
501
502 wx.CallAfter(self.controller.on_set_focus)
503 self.Bind(wx.EVT_SET_FOCUS, on_set_focus)
504 def on_kill_focus(evt):
505
506
507 self.controller.on_kill_focus()
508 self.SetSelection(0, 0)
509 self.Bind(wx.EVT_KILL_FOCUS, on_kill_focus)
510 def on_char(evt):
511 if evt.GetKeyCode() == wx.WXK_TAB:
512 if evt.ShiftDown():
513 skip = self.controller.on_shift_tab()
514 else:
515 skip = self.controller.on_tab()
516 else:
517 skip = True
518 evt.Skip(skip)
519 self.Bind(wx.EVT_CHAR, on_char)
520 def on_text(evt):
521 self.controller.on_text_changed()
522 self.Bind(wx.EVT_TEXT, on_text)
523 def on_key_down(evt):
524 if evt.GetKeyCode() == wx.WXK_UP:
525 self.controller.on_up()
526 elif evt.GetKeyCode() == wx.WXK_DOWN:
527 self.controller.on_down()
528 else:
529 evt.Skip()
530 self.Bind(wx.EVT_KEY_DOWN, on_key_down)
531
533 w, h = self.GetTextExtent("00:00")
534 width = w + 20
535 self.SetMinSize((width, -1))
536
537
539
541 self.py_time_picker = py_time_picker
542 self.original_bg = self.py_time_picker.GetBackgroundColour()
543 self.separator = PyTimeType().event_time_string(PyTimeType().now())[2]
544 self.hour_part = 0
545 self.minute_part = 1
546 self.last_selection = None
547
549 try:
550 split = self.py_time_picker.get_time_string().split(self.separator)
551 if len(split) != 2:
552 raise ValueError()
553 hour_string, minute_string = split
554 hour = int(hour_string)
555 minute = int(minute_string)
556 return datetime.time(hour, minute)
557 except ValueError:
558 raise ValueError("Invalid time.")
559
563
565 if self.last_selection:
566 start, end = self.last_selection
567 self.py_time_picker.SetSelection(start, end)
568 else:
569 self._select_part(self.hour_part)
570
572 self.last_selection = self.py_time_picker.GetSelection()
573
575 if self._in_minute_part():
576 return True
577 self._select_part(self.minute_part)
578 return False
579
581 if self._in_hour_part():
582 return True
583 self._select_part(self.hour_part)
584 return False
585
586 - def on_text_changed(self):
587 try:
588 self.get_py_time()
589 self.py_time_picker.SetBackgroundColour(self.original_bg)
590 except ValueError:
591 self.py_time_picker.SetBackgroundColour("pink")
592 self.py_time_picker.Refresh()
593
595 def increment_hour(time):
596 new_hour = time.hour + 1
597 if new_hour > 23:
598 new_hour = 0
599 return time.replace(hour=new_hour)
600 def increment_minutes(time):
601 new_hour = time.hour
602 new_minute = time.minute + 1
603 if new_minute > 59:
604 new_minute = 0
605 new_hour = time.hour + 1
606 if new_hour > 23:
607 new_hour = 0
608 return time.replace(hour=new_hour, minute=new_minute)
609 if not self._time_is_valid():
610 return
611 selection = self.py_time_picker.GetSelection()
612 current_time = self.get_py_time()
613 if self._in_hour_part():
614 new_time = increment_hour(current_time)
615 else:
616 new_time = increment_minutes(current_time)
617 if current_time != new_time:
618 self._set_new_time_and_restore_selection(new_time, selection)
619
621 def decrement_hour(time):
622 new_hour = time.hour - 1
623 if new_hour < 0:
624 new_hour = 23
625 return time.replace(hour=new_hour)
626 def decrement_minutes(time):
627 new_hour = time.hour
628 new_minute = time.minute - 1
629 if new_minute < 0:
630 new_minute = 59
631 new_hour = time.hour - 1
632 if new_hour < 0:
633 new_hour = 23
634 return time.replace(hour=new_hour, minute=new_minute)
635 if not self._time_is_valid():
636 return
637 selection = self.py_time_picker.GetSelection()
638 current_time = self.get_py_time()
639 if self._in_hour_part():
640 new_time = decrement_hour(current_time)
641 else:
642 new_time = decrement_minutes(current_time)
643 if current_time != new_time:
644 self._set_new_time_and_restore_selection(new_time, selection)
645
647 def restore_selection(selection):
648 self.py_time_picker.SetSelection(selection[0], selection[1])
649 self.set_py_time(new_time)
650 restore_selection(selection)
651
653 try:
654 self.get_py_time()
655 except ValueError:
656 return False
657 return True
658
660 if self._separator_pos() == -1:
661 return
662 if part == self.hour_part:
663 self.py_time_picker.SetSelection(0, self._separator_pos())
664 else:
665 time_string_len = len(self.py_time_picker.get_time_string())
666 self.py_time_picker.SetSelection(self._separator_pos() + 1, time_string_len)
667 self.preferred_part = part
668
670 if self._separator_pos() == -1:
671 return
672 return self.py_time_picker.GetInsertionPoint() <= self._separator_pos()
673
675 if self._separator_pos() == -1:
676 return
677 return self.py_time_picker.GetInsertionPoint() > self._separator_pos()
678
681