[Dev] Selecting time in event detail view

Ted Leung twl at osafoundation.org
Tue Nov 1 14:08:13 PST 2005


Hi Davor,

Thanks for the code!   I tried running it on Mac and Linux.  It works  
fine on Linux, but on the Mac I get this:

Traceback (most recent call last):
   File "/Users/twl/davor.py", line 33, in OnShowClock
     win = ClockPopup(self, wx.SIMPLE_BORDER)
   File "/Users/twl/davor.py", line 55, in __init__
     wx.PopupTransientWindow.__init__(self, parent, style)
   File "/Users/twl/work/osaf/binaries/release/Library/Frameworks/ 
Python.framework/Versions/2.4/lib/python2.4/site-packages/wx/ 
_windows.py", line 1814, in __init__
     newobj = _windows_.new_PopupTransientWindow(*args, **kwargs)
NotImplementedError

Ted

On Nov 1, 2005, at 1:45 PM, Davor Cubranic wrote:

> I'm a big fan of Ecco PIM/outliner, and one of very useful features  
> in its UI is the way it allows selecting time by clicking in an  
> analog clock dropdown. (See attachment for a screenshot of Ecco's  
> event properties dialog, both with and without the clock dropdown.)  
> So having to enter times in Chandler by typing them in a text  
> control felt like a step backward and I tried to recreate Ecco's  
> clock dropdown in wxPython. I'm attaching the code with this email,  
> in case you guys are interested in adding it to Chandler at some  
> point. The core functionality is in class ClockPopup, which also  
> uses CancelButton and ClockLabel. ClockDemoPanel illustrates how to  
> set up the popup and hook to its selection notification. The rest  
> is just the required stuff to run a wx application.
>
> To use the popup, run the application and click on the button with  
> the clock icon to the right of the text control (apologies for the  
> ugly icon, it's just for illustration). The clock popup will show  
> up, and you can left-click on any of the hours labels to select the  
> hours value. The popup will go away and the text control will show  
> the selected time. If you want to set a time value that isn't on  
> the hour, *right-click* on the hour label. The hour value will be  
> selected and the clock face will change to minutes -- left-click on  
> the minute label now and the popup will again go away and the text  
> control updated with the selected time. Click outside the popup or  
> on the red "X" button in the middle of the clock face to cancel the  
> selection at any time.
>
> I tried to recreate Ecco's look and functionality as closely as I  
> could. The only difference is that in my version both hours an  
> minutes can be selected from a single popup using the right-click  
> mechanism. I think it saves a bit of time compared to Ecco's  
> separate dropdowns for hours and minutes. The drawback of my  
> version is that the user now has to remember the left/right-click  
> distinction, but I find it to be very natural and not inappropriate  
> to such a frequently used feature.
>
> Davor
>
> P.S. I'm sorry if this is not the right place to post this  
> suggestion. I sent it to the dev list rather than design because I  
> also included working code. I also considered posting on Bugzilla,  
> but the mailing list allows more people to discuss the proposal. I  
> can open an enhancement request in Bugzilla if that's the preferred  
> way.
> P.P.S. The code has been tested on Windows and wxPython 2.6.1.0.
> <ecco-event-dialog.png>
> <ecco-event-dropdown.png>
> <python-clock-hours.png>
> <python-clock-minutes.png>
> import wx
> import wx.lib.buttons as buttons
> import wx.lib.masked as masked
>
> class ClockDemoPanel(wx.Panel):
>     """
>     A demo panel illustrating how to set up the popup and hook it  
> up to a display, like a text control
>     """
>     def __init__(self, parent):
>         wx.Panel.__init__(self, parent, -1)
>
>         l = wx.StaticText(self, -1, "Select time")
>         tc = wx.TextCtrl(self, -1, "12:00", size=(50, -1))
>         wx.CallAfter(tc.SetInsertionPoint, 0)
>         self.tc = tc
>
>         clockimage = wx.BitmapFromImage(wx.ImageFromStream(stream))
>         clockmask = wx.Mask(clockimage, wx.BLUE)
>         clockimage.SetMask(clockmask)
>         b = wx.BitmapButton(self, -1, clockimage, None,  
> (clockimage.GetWidth()+8, clockimage.GetHeight()+8))
>         self.Bind(wx.EVT_BUTTON, self.OnShowClock, b)
>
>         space=5
>         sizer = wx.FlexGridSizer(cols=3, hgap=space, vgap=space)
>         sizer.AddMany([ l, tc, b,
>                         ])
>         border = wx.BoxSizer(wx.VERTICAL)
>         border.Add(sizer, 0, wx.ALL, 25)
>         self.SetSizer(border)
>
>
>     def OnShowClock(self, evt):
>         win = ClockPopup(self, wx.SIMPLE_BORDER)
>         self.Bind(masked.EVT_TIMEUPDATE, self.OnTimeSelected, win)
>
>         # Show the popup right below or above the button
>         # depending on available screen space...
>         btn = evt.GetEventObject()
>         pos = btn.ClientToScreen( (0,0) )
>         sz =  btn.GetSize()
>         win.Position(pos, (0, sz[1]))
>
>         win.Popup()
>
>     def OnTimeSelected(self, evt):
>         self.tc.SetValue('%02d:%02d' % evt.GetValue())
>
> ####################
>
> class ClockPopup(wx.PopupTransientWindow):
>     """
>     A popup to select a time value using a clock-like interface.
>     """
>     def __init__(self, parent, style):
>         wx.PopupTransientWindow.__init__(self, parent, style)
>         self.hours = self.minutes = None
>         self.SetBackgroundColour('white')
>         self.makeClockFace(self)
>         self.SetSize( (77, 77) )
>
>     def GetValue(self):
>         """Returns the popup's selected time as a hour-minutes  
> tuple."""
>         return (self.hours, self.minutes)
>
>     def OnTimeSelected(self, evt):
>         """Fires a TIMEUPDATED event to notify listeners a time  
> value has been selected in the popup."""
>         if not self.hours:
>             self.hours = int(evt.GetEventObject().text)
>             self.minutes = 0
>         else:
>             self.minutes = int(evt.GetEventObject().text)
>         evt = masked.TimeUpdatedEvent(self.GetId(), (self.hours,  
> self.minutes))
>         self.GetEventHandler().ProcessEvent(evt)
>
>         self.Show(False)
>         self.Destroy()
>
>     def OnClockRightClick(self, evt):
>         """Remembers the selected hour value and switches to  
> selecting minutes."""
>         if self.hours: return                   # No right click if  
> showing minutes
>         self.hours = int(evt.GetEventObject().text)
>         hoursLabel = wx.StaticText(self, -1, str(self.hours), (25,  
> 16), (23, 14), style=wx.ALIGN_CENTER_HORIZONTAL)
>         hoursLabel.SetBackgroundColour(wx.Colour(200, 200, 200))
>         it = ['55', '00', '05', '50', '10', '45', '15', '40', '20',  
> '35', '30', '25'].__iter__()
>         for label in self.labels:
>             label.SetLabel(it.next())
>
>     def OnClockCancel(self, evt):
>         """Closes the popup without selecting a time value."""
>         self.Show(False)
>         self.Destroy()
>
>     def makeClockFace(self, win):
>         """A helper function that creates the contents of the popup  
> window and arranges them into the UI."""
>
>         self.labels = [
>             ClockLabel(win, '11', (2, 2), wx.ALIGN_RIGHT),
>             ClockLabel(win, '12', (25, 2),  
> wx.ALIGN_CENTER_HORIZONTAL),
>             ClockLabel(win, ' 1', (48, 2)),
>             ClockLabel(win, '10', (2, 16), wx.ALIGN_CENTER),
>             ClockLabel(win, ' 2', (48, 16), wx.ALIGN_CENTER),
>             ClockLabel(win, ' 9', (2, 30), wx.ALIGN_LEFT),
>             ClockLabel(win, ' 3', (48, 30), wx.ALIGN_RIGHT),
>             ClockLabel(win, ' 8', (2, 44), wx.ALIGN_CENTER),
>             ClockLabel(win, ' 4', (48, 44), wx.ALIGN_CENTER),
>             ClockLabel(win, ' 7', (2, 58), wx.ALIGN_RIGHT),
>             ClockLabel(win, ' 6', (25, 58), wx.ALIGN_CENTER),
>             ClockLabel(win, ' 5', (48, 58)),
>         ]
>         CancelButton(win, (28, 30))
>
>
> class CancelButton(buttons.GenButton):
>     """
>     Button for cancelling time selection (red X in the middle of  
> the clock face)
>     """
>     def __init__(self, parent, pos):
>         buttons.GenButton.__init__(self, parent, -1, 'x', pos=pos,  
> style=wx.SIMPLE_BORDER)
>         self.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD, False))
>         self.SetSize((17, 16))
>         self.SetForegroundColour(wx.RED)
>         self.Bind(wx.EVT_LEFT_UP, parent.OnClockCancel)
>
>
> class ClockLabel(wx.PyWindow):
>     """
>     A simple window that is used as sizer items in the tests below to
>     show how the various sizers work.
>     """
>     def __init__(self, parent, text, pos=wx.DefaultPosition,  
> style=wx.NO_BORDER, size=wx.DefaultSize):
>         wx.PyWindow.__init__(self, parent, pos=pos, style=style)
>
>         self.text = text
>         if size != wx.DefaultSize:
>             self.size = size
>         else:
>             self.size = (23, 14)
>         self.SetSize(self.size)
>         self.style = style
>         self.selected = False
>
>         self.Bind(wx.EVT_PAINT, self.OnPaint)
>         self.Bind(wx.EVT_ENTER_WINDOW, self.OnEnter)
>         self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeave)
>         self.Bind(wx.EVT_LEFT_UP, parent.OnTimeSelected)
>         self.Bind(wx.EVT_RIGHT_UP, parent.OnClockRightClick)
>
>     def OnLeave(self, evt):
>         self.selected = False
>         self.Refresh()
>
>     def OnEnter(self, evt):
>         self.selected = True
>         self.Refresh()
>
>     def OnPaint(self, evt):
>         sz = self.GetSize()
>         dc = wx.PaintDC(self)
>         font = dc.GetFont()
>         font.SetPointSize(8)
>         if int(self.text) % 3 != 0:
>             font.SetWeight(wx.FONTWEIGHT_NORMAL)
>             dc.SetTextForeground(wx.BLACK)
>         else:
>             dc.SetTextForeground(wx.RED)
>             font.SetWeight(wx.FONTWEIGHT_BOLD)
>         dc.SetFont(font)
>         if self.selected:
>             background = wx.BLUE_BRUSH
>             dc.SetTextForeground(wx.WHITE)
>         else:
>             background = wx.WHITE_BRUSH
>         dc.SetBackground(background)
>         dc.Clear()
>         w,h = dc.GetTextExtent(self.text)
>         if self.style & wx.ALIGN_RIGHT:
>             x = sz.width - w
>         elif self.style & wx.ALIGN_CENTER_HORIZONTAL:
>             x = (sz.width-w)/2
>         else:
>             x = 0
>         dc.DrawText(self.text, x, (sz.height-h)/2)
>
>     def GetLabel(self):
>         return self.text
>
>     def SetLabel(self, text):
>         self.text = text
>         self.Refresh()
>
> ####################################3
>
>
> class ClockDemoFrame(wx.Frame):
>     def __init__(self, parent, title, pos=wx.DefaultPosition,
>                  size=wx.DefaultSize, style=wx.CLOSE_BOX|wx.CAPTION| 
> wx.SYSTEM_MENU):
>         wx.Frame.__init__(self, parent, -1, title, pos, size, style)
>         panel = ClockDemoPanel(self)
>
>
> class ClockDemoApp(wx.App):
>     """Represents the timer application to the wx framework. Its  
> only task is to create
>     the window and enter the event loop."""
>     def OnInit(self):
>         frame = ClockDemoFrame(None, "timer")
>         self.SetTopWindow(frame)
>         frame.Show()
>
>         return True
>
>
> import cStringIO
> clockbmpdata = \
> '\x42\x4d\xf6\x00\x00\x00\x00\x00\x00\x00\x76\x00\x00\x00\x28\x00\
> \x00\x00\x10\x00\x00\x00\x10\x00\x00\x00\x01\x00\x04\x00\x00\x00\
> \x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x80\
> \x00\x00\x00\x80\x80\x00\x80\x00\x00\x00\x80\x00\x80\x00\x80\x80\
> \x00\x00\x80\x80\x80\x00\xc0\xc0\xc0\x00\x00\x00\xff\x00\x00\xff\
> \x00\x00\x00\xff\xff\x00\xff\x00\x00\x00\xff\x00\xff\x00\xff\xff\
> \x00\x00\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\
> \x00\x00\x00\x0f\xff\xff\xff\x00\x00\x00\x00\x00\x0f\xff\xf0\x00\
> \x0f\xff\xff\x00\x00\xff\xf0\x0f\xff\xff\xff\xff\x00\xff\x00\xff\
> \xff\xff\xff\x0f\xf0\x0f\x00\xff\xff\xff\xf0\xff\xf0\x0f\x00\xff\
> \xff\xff\x0f\xff\xf0\x0f\x00\xff\xff\xf0\xff\xff\xf0\x0f\x00\xff\
> \xff\xf0\xff\xff\xf0\x0f\x00\xff\xff\xf0\xff\xff\xf0\x0f\x00\xff\
> \xff\xf0\xff\xff\xf0\x0f\xf0\x0f\xff\xff\xff\xff\x00\xff\xf0\x00\
> \x0f\xff\xff\x00\x00\xff\xff\x00\x00\x00\x00\x00\x0f\xff\xff\xff\
> \x00\x00\x00\x0f\xff\xff'
> stream = cStringIO.StringIO(clockbmpdata)
> clockimage = None
> clockmask = None
>
> def main():
>     app = ClockDemoApp(False)
>     app.MainLoop()
>
> if __name__ == '__main__':
>     __name__ = 'Main'
>     main()
> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
>
> Open Source Applications Foundation "Dev" mailing list
> http://lists.osafoundation.org/mailman/listinfo/dev

----
Ted Leung                 Open Source Applications Foundation (OSAF)
PGP Fingerprint: 1003 7870 251F FA71 A59A  CEE3 BEBA 2B87 F5FC 4B42




More information about the Dev mailing list