Attachment 'datectrl_1.5.py'

Download

   1 #!/usr/bin/env python
   2 # -*- coding: utf-8 -*-
   3 
   4 import sys
   5 import wx
   6 import wx.combo
   7 import wx.calendar
   8 from datetime import date as dt
   9 
  10 class DateCtrl(wx.combo.ComboCtrl):
  11     INPUT_FORMAT = 0
  12     DISPLAY_FORMAT = 1
  13 
  14     def __init__(self, parent, size, pos, input_format, display_format,
  15             title, default_to_today, allow_null):
  16         wx.combo.ComboCtrl.__init__(self, parent, size=size, pos=pos)
  17 
  18         self.input_format = input_format
  19         self.display_format = display_format
  20         self.title = title
  21         self.default_to_today = default_to_today
  22         self.allow_null = allow_null
  23 
  24         self.TextCtrl.Bind(wx.EVT_SET_FOCUS, self.on_got_focus)
  25         self.TextCtrl.Bind(wx.EVT_CHAR, self.on_char)
  26         self.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter)
  27         self.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave)
  28 
  29         self.nav = False  # force navigation after selecting date
  30         self.is_valid = True  # unlike IsValid(), a blank date can be valid
  31         self.current_format = self.DISPLAY_FORMAT
  32         self.date = wx.DateTime()
  33         self.setup_button()  # create a custom button for popup
  34         (self.blank_string, self.yr_pos, self.mth_pos, self.day_pos,
  35             self.literal_pos) = self.setup_input_format()
  36 
  37         # set up button coords for mouse hit-test
  38         self.b_x1 = self.TextRect[2] - 2
  39         self.b_y1 = self.TextRect[1] - 1
  40         self.b_x2 = self.b_x1 + self.ButtonSize[0] + 3
  41         self.b_y2 = self.b_y1 + self.ButtonSize[1] + 1
  42         self.on_button = False
  43 
  44         self.timer = wx.Timer(self)
  45         self.Bind(wx.EVT_TIMER, self.show_tooltip)
  46 
  47     def on_mouse_enter(self, evt):
  48         if self.b_x1 <= evt.X <= self.b_x2:
  49             if self.b_y1 <= evt.Y <= self.b_y2:
  50                 self.on_button = True
  51                 self.timer.Start(500, oneShot=True)
  52         evt.Skip()
  53 
  54     def on_mouse_leave(self, evt):
  55         if self.on_button:
  56             self.on_button = False
  57             self.timer.Stop()
  58         evt.Skip()
  59 
  60     def show_tooltip(self, evt):
  61         abs_x, abs_y = self.ScreenPosition
  62         rect = wx.Rect(abs_x+self.b_x1, abs_y+self.b_y1,
  63             self.b_x2-self.b_x1+1, self.b_y2-self.b_y1+1)
  64         tip = wx.TipWindow(self, 'Show calendar\n(F4 or space)')
  65         # tip will be destroyed when mouse leaves this rect
  66         tip.SetBoundingRect(rect)
  67 
  68     def setup_button(self):  # copied directly from demo
  69         # make a custom bitmap showing "..."
  70         bw, bh = 14, 16
  71         bmp = wx.EmptyBitmap(bw, bh)
  72         dc = wx.MemoryDC(bmp)
  73 
  74         # clear to a specific background colour
  75         bgcolor = wx.Colour(255, 254, 255)
  76         dc.SetBackground(wx.Brush(bgcolor))
  77         dc.Clear()
  78 
  79         # draw the label onto the bitmap
  80         label = u'\u2026'  # unicode ellipsis
  81         font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
  82         font.SetWeight(wx.FONTWEIGHT_BOLD)
  83         dc.SetFont(font)
  84         tw, th = dc.GetTextExtent(label)
  85         dc.DrawText(label, (bw-tw)/2, (bw-tw)/2)
  86         del dc
  87 
  88         # now apply a mask using the bgcolor
  89         bmp.SetMaskColour(bgcolor)
  90 
  91         # and tell the ComboCtrl to use it
  92         self.SetButtonBitmaps(bmp, True)
  93 
  94     def setup_input_format(self):
  95         """
  96         Modify the defined input format to a string where each character
  97         represents one character of the input string.
  98         Generate and return a blank string to fill in the control.
  99         Return positions within the string of yr, mth, day and literals.
 100         """
 101         format = self.input_format
 102         blank_string = format
 103 
 104         yr_pos = format.find('%y')
 105         if yr_pos > -1:
 106             blank_string = blank_string[:yr_pos]+'  '+blank_string[yr_pos+2:]
 107             yr_pos = (yr_pos, yr_pos+2)
 108         else:
 109             yr_pos = format.find('%Y')
 110             if yr_pos > -1:
 111                 blank_string = blank_string[:yr_pos]+'    '+blank_string[yr_pos+2:]
 112                 format = format[:yr_pos+2]+'YY'+format[yr_pos+2:]
 113                 yr_pos = (yr_pos, yr_pos+4)
 114 
 115         mth_pos = format.find('%m')
 116         if mth_pos > -1:
 117             blank_string = blank_string[:mth_pos]+'  '+blank_string[mth_pos+2:]
 118             mth_pos = (mth_pos, mth_pos+2)
 119 
 120         day_pos = format.find('%d')
 121         if day_pos > -1:
 122             blank_string = blank_string[:day_pos]+'  '+blank_string[day_pos+2:]
 123             day_pos = (day_pos, day_pos+2)
 124 
 125         literal_pos = [i for (i, ch) in enumerate(blank_string)
 126             if blank_string[i] == format[i]]
 127 
 128         return blank_string, yr_pos, mth_pos, day_pos, literal_pos
 129 
 130     # Overridden from ComboCtrl, called when the combo button is clicked
 131     def OnButtonClick(self):
 132         self.SetFocus()  # in case we do not have focus
 133         dlg = CalendarDlg(self)
 134         dlg.CentreOnScreen()
 135         if dlg.ShowModal() == wx.ID_OK:
 136             self.date = dlg.cal.Date
 137             self.Value = self.date.Format(self.display_format)
 138             self.current_format = self.DISPLAY_FORMAT
 139             self.nav = True  # force navigation to next control
 140         dlg.Destroy()
 141 
 142     # Overridden from ComboCtrl to avoid assert since there is no ComboPopup
 143     def DoSetPopupControl(self, popup):
 144         pass
 145 
 146     def on_got_focus(self, evt):
 147         if self.nav:  # user has made a selection, so move on
 148             self.nav = False
 149             wx.CallAfter(self.Navigate)
 150         else:
 151             text_ctrl = self.TextCtrl
 152             if not self.is_valid:  # re-focus after error
 153                 pass  # leave Value alone
 154             elif self.date.IsValid():
 155                 text_ctrl.Value = self.date.Format(self.input_format)
 156             elif self.default_to_today:
 157                 self.date = wx.DateTime.Today()
 158                 text_ctrl.Value = self.date.Format(self.input_format)
 159             else:
 160                 text_ctrl.Value = self.blank_string
 161             self.current_format = self.INPUT_FORMAT
 162             text_ctrl.InsertionPoint = 0
 163             text_ctrl.SetSelection(-1, -1)
 164             text_ctrl.pos = 0
 165         evt.Skip()
 166 
 167     def convert_to_wx_date(self):  # conversion and validation method
 168         self.is_valid = True
 169 
 170         value = self.Value
 171         if value in (self.blank_string, ''):
 172             if self.default_to_today:
 173                 self.date = wx.DateTime.Today()
 174                 self.Value = self.date.Format(self.display_format)
 175             elif self.allow_null:
 176                 self.date = wx.DateTime()
 177                 self.Value = ''
 178             else:
 179                 wx.CallAfter(self.display_error, 'Date is required')
 180             return
 181 
 182         if self.current_format == self.DISPLAY_FORMAT:  # no validation reqd
 183             self.TextCtrl.SetSelection(0, 0)
 184             return
 185 
 186         today = dt.today()
 187 
 188         if self.yr_pos == -1:  # 'yr' not an element of input_format
 189             year = today.year
 190         else:
 191             year = value[self.yr_pos[0]:self.yr_pos[1]].strip()
 192             if year == '':
 193                 year = today.year
 194             elif len(year) == 2:
 195                 # assume year is in range (today-90) to (today+10)
 196                 year = int(year) + int(today.year/100)*100
 197                 if year - today.year > 10:
 198                     year -= 100
 199                 elif year - today.year < -90:
 200                     year += 100
 201             else:
 202                 year = int(year)
 203 
 204         if self.mth_pos == -1:  # 'mth' not an element of input_format
 205             month = today.month
 206         else:
 207             month = value[self.mth_pos[0]:self.mth_pos[1]].strip()
 208             if month == '':
 209                 month = today.month
 210             else:
 211                 month = int(month)
 212 
 213         if self.day_pos == -1:  # 'day' not an element of input_format
 214             day = today.day
 215         else:
 216             day = value[self.day_pos[0]:self.day_pos[1]].strip()
 217             if day == '':
 218                 day = today.day
 219             else:
 220                 day = int(day)
 221 
 222         try:
 223             date = dt(year, month, day)  # validate using python datetime
 224         except ValueError as error:  # gives a meaningful error message
 225             wx.CallAfter(self.display_error, error.args[0])
 226         else:  # date is valid
 227             self.date = wx.DateTimeFromDMY(day, month-1, year)
 228             self.Value = self.date.Format(self.display_format)
 229             self.current_format = self.DISPLAY_FORMAT
 230 
 231     def display_error(self, errmsg):
 232         self.is_valid = False
 233         self.SetFocus()
 234         dlg = wx.MessageDialog(self, errmsg,
 235             self.title, wx.OK | wx.ICON_INFORMATION)
 236         dlg.ShowModal()
 237         dlg.Destroy()
 238 
 239     def on_char(self, evt):
 240         text_ctrl = self.TextCtrl
 241         code = evt.KeyCode
 242         if code in (wx.WXK_SPACE, wx.WXK_F4) and not evt.AltDown():
 243             self.OnButtonClick()
 244             return
 245         max = len(self.blank_string)
 246         if code in (wx.WXK_LEFT, wx.WXK_RIGHT, wx.WXK_HOME, wx.WXK_END):
 247             if text_ctrl.Selection == (0, max):
 248                 text_ctrl.SetSelection(0, 0)
 249             if code == wx.WXK_LEFT:
 250                 if text_ctrl.pos > 0:
 251                     text_ctrl.pos -= 1
 252                     while text_ctrl.pos in self.literal_pos:
 253                         text_ctrl.pos -= 1
 254             elif code == wx.WXK_RIGHT:
 255                 if text_ctrl.pos < max:
 256                     text_ctrl.pos += 1
 257                     while text_ctrl.pos in self.literal_pos:
 258                         text_ctrl.pos += 1
 259             elif code == wx.WXK_HOME:
 260                 text_ctrl.pos = 0
 261             elif code == wx.WXK_END:
 262                 text_ctrl.pos = max
 263             text_ctrl.InsertionPoint = text_ctrl.pos
 264             return
 265         if code in (wx.WXK_BACK, wx.WXK_DELETE):
 266             if text_ctrl.Selection == (0, max):
 267                 text_ctrl.Value = self.blank_string
 268                 text_ctrl.SetSelection(0, 0)
 269             if code == wx.WXK_BACK:
 270                 if text_ctrl.pos == 0:
 271                     return
 272                 text_ctrl.pos -= 1
 273                 while text_ctrl.pos in self.literal_pos:
 274                     text_ctrl.pos -= 1
 275             elif code == wx.WXK_DELETE:
 276                 if text_ctrl.pos == max:
 277                     return
 278             curr_val = text_ctrl.Value
 279             text_ctrl.Value = curr_val[:text_ctrl.pos]+' '+curr_val[text_ctrl.pos+1:]
 280             text_ctrl.InsertionPoint = text_ctrl.pos
 281             return
 282         if code in (wx.WXK_TAB, wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) or code > 255:
 283             evt.Skip()
 284             return
 285         if text_ctrl.pos == max:
 286             wx.Bell()
 287             return
 288         ch = chr(code)
 289         if ch not in ('0123456789'):
 290             wx.Bell()
 291             return
 292         if text_ctrl.Selection == (0, max):
 293             curr_val = self.blank_string
 294         else:
 295             curr_val = text_ctrl.Value
 296         text_ctrl.Value = curr_val[:text_ctrl.pos]+ch+curr_val[text_ctrl.pos+1:]
 297         text_ctrl.pos += 1
 298         while text_ctrl.pos in self.literal_pos:
 299             text_ctrl.pos += 1
 300         text_ctrl.InsertionPoint = text_ctrl.pos
 301 
 302 class CalendarDlg(wx.Dialog):
 303     def __init__(self, parent):
 304 
 305         wx.Dialog.__init__(self, parent, title=parent.title)
 306         panel = wx.Panel(self, -1)
 307 
 308         sizer = wx.BoxSizer(wx.VERTICAL)
 309         panel.SetSizer(sizer)
 310 
 311         cal = wx.calendar.CalendarCtrl(panel, date=parent.date)
 312 
 313         if sys.platform != 'win32':
 314             # gtk truncates the year - this fixes it
 315             w, h = cal.Size
 316             cal.Size = (w+25, h)
 317             cal.MinSize = cal.Size
 318 
 319         sizer.Add(cal, 0)
 320 
 321         button_sizer = wx.BoxSizer(wx.HORIZONTAL)
 322         button_sizer.Add((0, 0), 1)
 323         btn_ok = wx.Button(panel, wx.ID_OK)
 324         btn_ok.SetDefault()
 325         button_sizer.Add(btn_ok, 0, wx.ALL, 2)
 326         button_sizer.Add((0, 0), 1)
 327         btn_can = wx.Button(panel, wx.ID_CANCEL)
 328         button_sizer.Add(btn_can, 0, wx.ALL, 2)
 329         button_sizer.Add((0, 0), 1)
 330         sizer.Add(button_sizer, 1, wx.EXPAND | wx.ALL, 10)
 331         sizer.Fit(panel)
 332         self.ClientSize = panel.Size
 333 
 334         cal.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
 335         cal.SetFocus()
 336         self.cal = cal
 337 
 338     def on_key_down(self, evt):
 339         code = evt.KeyCode
 340         if code == wx.WXK_TAB:
 341             self.cal.Navigate()
 342         elif code in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER):
 343             self.EndModal(wx.ID_OK)
 344         elif code == wx.WXK_ESCAPE:
 345             self.EndModal(wx.ID_CANCEL)
 346         else:
 347             evt.Skip()
 348 
 349 class Panel(wx.Panel):
 350     def __init__(self, parent):
 351         wx.Panel.__init__(self, parent, -1)
 352 
 353         wx.StaticText(self, -1, 'Field1', pos=(50, 30))
 354         t1 = wx.TextCtrl(self, -1, '', size=(130, -1), pos=(150, 30))
 355 
 356         input_format = '%d-%m-%Y'
 357         display_format = '%a %d %b %Y'
 358 #       display_format = '%d-%m-%Y'
 359 
 360         wx.StaticText(self, -1, 'Invoice date', pos=(50, 80))
 361         self.d = DateCtrl(self, size=(130, -1), pos=(150, 80),
 362             input_format=input_format, display_format=display_format,
 363             title='Invoice date', default_to_today=False, allow_null=False)
 364 
 365         wx.StaticText(self, -1, 'Field3', pos=(50, 130))
 366         t3 = wx.TextCtrl(self, -1, '', size=(130, -1), pos=(150, 130))
 367 
 368         t1.Bind(wx.EVT_SET_FOCUS, self.on_t_got_focus)
 369         t3.Bind(wx.EVT_SET_FOCUS, self.on_t_got_focus)
 370 
 371         self.first_time = True  # don't validate date first time
 372         self.SetFocus()
 373 
 374     def on_t_got_focus(self, evt):
 375         if self.first_time:
 376             self.first_time = False
 377         else:
 378             self.d.convert_to_wx_date()
 379         evt.Skip()
 380 
 381 class Frame(wx.Frame):
 382     def __init__(self):
 383         wx.Frame.__init__(self, None, -1, "Date Picker Ctrl test", size=(400, 240))
 384         panel = Panel(self)
 385         self.CentreOnScreen()
 386 
 387 class App(wx.App):
 388     def OnInit(self):
 389         frame = Frame()
 390         frame.Show(True)
 391         return True
 392 
 393 app = App(False)
 394 app.MainLoop()

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2010-11-07 09:16:19, 13.7 KB) [[attachment:datectrl_1.5.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.