[Dev] Selecting time in event detail view
Alec Flett
alecf at osafoundation.org
Tue Nov 1 14:08:25 PST 2005
Davor,
This is great stuff! Thanks for throwing together the wxPython code.
Personally I think this would be interesting to try out in chandler and
see what people think of it.
We're in the process of wrapping up 0.6, which means no new "features"
but perhaps when we begin work on 0.7, we can try to integrate your code
into the detail view...
Alec
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.
>
> ------------------------------------------------------------------------
>
>
> ------------------------------------------------------------------------
>
>
> ------------------------------------------------------------------------
>
>
> ------------------------------------------------------------------------
>
> ------------------------------------------------------------------------
>
> 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
>
More information about the Dev
mailing list