[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