###################################################################### # Copyright (c) 2007, Petteri Aimonen # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright # notice and this list of conditions. # * Redistributions in binary form must reproduce the above copyright # notice and this list of conditions in the documentation and/or other # materials provided with the distribution. '''Main application window''' # ====================xxx # |_ToolBar_______________| # | | | # | Animal| Graph / event | # | Tree | Panel | # |_______|_______________| # | TimePanel | # |_______________________| import wx import datetime import settings import events import logpanel from pymisc import _, date_fromwx, date_towx from dataaccess import getanimaltree, getlastdate from multigraph import MultiDataGraph from animalview import AnimalView from guiimport import do_guiimport from querypanel import QueryPanel from groupfeedpanel import GroupFeedPanel from images import iconbundle class Navigator(wx.EvtHandler): '''A class to manage navigation toolbar and tree''' def __init__(self, toolbar, treectrl): wx.EvtHandler.__init__(self) self.location = None self.tb = toolbar self.tc = treectrl self.toolbar_addbuttons() self.update_tree() self.tc.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeActivate) self.tb.Bind(wx.EVT_TOOL, self.OnGoPrev, id = self.prevbutton.GetId()) self.tb.Bind(wx.EVT_TOOL, self.OnGoNext, id = self.nextbutton.GetId()) self.tb.Bind(wx.EVT_TOOL, self.OnGoToParent, id = self.upbutton.GetId()) self.tb.Bind(wx.EVT_TOOL, self.OnFind, id = self.findbutton.GetId()) self.tb.Bind(wx.EVT_TOOL, self.OnReload, id = self.reloadbutton.GetId()) # Toolbar events def OnGoPrev(self, evt): self.goto(self.location.get_globalsibling(-1)) def OnGoNext(self, evt): self.goto(self.location.get_globalsibling(1)) def OnGoToParent(self, evt): self.goto(self.location.parent) def OnFind(self, evt): dialog = wx.TextEntryDialog(None, _('Type identification number of animal to search for.'), _('Find animal')) if dialog.ShowModal() != wx.ID_OK: return # User cancelled rawvalue = dialog.GetValue() try: animalid = int(rawvalue) except ValueError: animalid = None node = self.tree.find_node(('animal', animalid)) if not node: dialog = wx.MessageDialog(None, _("Animal with id '%s' was not found.") % rawvalue, _('Animal not found'), wx.OK | wx.ICON_INFORMATION) dialog.ShowModal() else: self.goto(node) def OnReload(self, evt): evt = events.DataReloadEvent() wx.PostEvent(self, evt) # Tree events def OnTreeActivate(self, evt): self.goto(self.tc.GetPyData(evt.GetItem())) # Use backreference in node # General functions def goto(self, new, firsttime = False): '''Go to "new" or do nothing if "new" is None.''' if new is None or (not firsttime and self.location == new): return if not firsttime: self.tc.SetItemBold(self.location.guiid, False) self.location = new self.tc.EnsureVisible(new.guiid) self.tc.SelectItem(new.guiid) self.tc.SetItemBold(new.guiid) self.toolbar_update() wx.PostEvent(self, events.GraphUpdateEvent()) # Toolbar helper functions def toolbar_addbuttons(self): '''Add buttons to toolbar for navigation''' toolbar_buttons = [ ('prevbutton', wx.ART_GO_BACK, _('Previous'), ''), ('upbutton', wx.ART_GO_TO_PARENT, _('Up'), ''), ('nextbutton', wx.ART_GO_FORWARD, _('Next'), ''), None, ('findbutton', wx.ART_FIND, _('Find'), _('Find animal')), None, ('reloadbutton', wx.ART_FILE_OPEN, _('Reload'), _('Reload new data files')) ] for button in toolbar_buttons: if button is None: self.tb.AddSeparator() else: buttonname, pictureid, label, help = button picture = wx.ArtProvider.GetBitmap(pictureid, wx.ART_TOOLBAR) ref = self.tb.AddLabelTool(-1, label, picture, shortHelp = label, longHelp = help) setattr(self, buttonname, ref) # store self.prevbutton etc. self.tb.Realize() def toolbar_update(self): '''Update toolbar button captions and enabled-states''' toolbar_helpmsgs = [ (['section', 'inactive'], _('Previous section'), _('Next section'), _('Sections'), _('Show all sections')), (['group'], _('Previous group'), _('Next group'), _('Groups'), _('Show all groups in current section')), (['animal'], _('Previous animal'), _('Next animal'), _('Animals'), _('Show all animals in current group')) ] self.tb.EnableTool(self.prevbutton.GetId(), self.location.get_globalsibling(-1) is not None) self.tb.EnableTool(self.nextbutton.GetId(), self.location.get_globalsibling(1) is not None) self.tb.EnableTool(self.upbutton.GetId(), self.location.parent is not None) if self.location.userdata is None: # Root node self.upbutton.SetLabel(_('Up')) else: for types, prevh, nexth, uplabel, uph in toolbar_helpmsgs: if self.location.userdata[0] not in types: continue self.prevbutton.SetLongHelp(prevh) self.nextbutton.SetLongHelp(nexth) self.upbutton.SetLabel(uplabel) self.upbutton.SetLongHelp(uph) break self.tb.Realize() def nodetext(self, node): if node.userdata[0] == 'animal': return _('Animal %d') % node.userdata[1] elif node.userdata[0] == 'group': return _('Group %d') % node.userdata[1] elif node.userdata[0] == 'section': return _('Section %d') % node.userdata[1] elif node.userdata[0] == 'inactive': return _('Inactive animals') else: raise ValueError def update_tree(self): def add_to_tree(node): if node.parent: parentid = node.parent.guiid node.guiid = self.tc.AppendItem(parentid, self.nodetext(node)) else: node.guiid = self.tc.AddRoot(_("All animals")) self.tc.SetPyData(node.guiid, node) # Store backreference if self.location: oldsel = self.location.userdata else: oldsel = None self.tree = getanimaltree() self.tc.DeleteAllItems() self.tree.traverse_tree(add_to_tree) self.tc.Expand(self.tree.guiid) self.goto(self.tree.find_node(oldsel), True) class TimePanel(wx.Panel): '''Timeframe selection panel''' def __init__(self, parent): wx.Panel.__init__(self, parent) self.fromdate = wx.DatePickerCtrl(self, size = (140, 24), style = wx.DP_DROPDOWN) self.todate = wx.DatePickerCtrl(self, size = (140, 24), style = wx.DP_DROPDOWN) self.weekbutton = wx.Button(self, label = _('Last two weeks')) self.monthbutton = wx.Button(self, label = _('Last month')) self.sizer = wx.BoxSizer(wx.HORIZONTAL) self.sizer.Add(wx.StaticText(self, label = _('Display events from ')), flag = wx.LEFT | wx.RIGHT | wx.CENTER, border = 5) self.sizer.Add(self.fromdate, flag = wx.CENTER) self.sizer.Add(wx.StaticText(self, label = _(' to ')), flag = wx.LEFT | wx.RIGHT | wx.CENTER, border = 5) self.sizer.Add(self.todate, flag = wx.CENTER) if wx.Platform == '__WXGTK__': # Datepicker events seem broken on GTK self.applybutton = wx.Button(self, wx.ID_APPLY, label = _('Apply')) self.sizer.Add(self.applybutton, flag = wx.LEFT, border = 5) self.applybutton.Bind(wx.EVT_BUTTON, self.OnChange) self.sizer.Add(self.weekbutton, flag = wx.LEFT, border = 15) self.sizer.Add(self.monthbutton) self.weekbutton.Bind(wx.EVT_BUTTON, self.SetLastWeek) self.monthbutton.Bind(wx.EVT_BUTTON, self.SetLastMonth) self.fromdate.Bind(wx.EVT_DATE_CHANGED, self.OnChange) self.todate.Bind(wx.EVT_DATE_CHANGED, self.OnChange) self.SetAutoLayout(True) self.SetSizer(self.sizer) self.Layout() self.SetLastWeek() def SetLastWeek(self, evt = None): '''Set last week as the timeframe''' todate = getlastdate() fromdate = todate + datetime.timedelta(-14) self.SetTimeframe(fromdate, todate) self.OnChange() self.weekbutton.Enable(False) def SetLastMonth(self, evt = None): '''Set last month as the timeframe''' todate = getlastdate() fromdate = todate + datetime.timedelta(-31) self.SetTimeframe(fromdate, todate) self.OnChange() self.monthbutton.Enable(False) def OnChange(self, evt = None): self.weekbutton.Enable(True) self.monthbutton.Enable(True) wx.PostEvent(self, events.TimeframeEvent()) def SetTimeframe(self, fromdate, todate): '''Set displayed timeframe''' self.fromdate.SetValue(date_towx(fromdate)) self.todate.SetValue(date_towx(todate)) def GetTimeframe(self): '''Get displayed timeframe as fromdate, todate (datetime.date)''' fromdate = date_fromwx(self.fromdate.GetValue()) todate = date_fromwx(self.todate.GetValue()) return fromdate, todate class MainWindow(wx.Frame): def __init__(self, title = _("Feed monitoring application")): wx.Frame.__init__(self, None, title = title) self.SetIcons(iconbundle) self.importlog = None self.tb = self.CreateToolBar(style = wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_TEXT) self.CreateStatusBar() self.topsplitter = wx.SplitterWindow(self) self.animaltree = wx.TreeCtrl(self.topsplitter, style = wx.TR_HAS_BUTTONS | wx.TR_NO_LINES | wx.TR_FULL_ROW_HIGHLIGHT) self.tabswitch = wx.Notebook(self.topsplitter) self.timepanel = TimePanel(self) self.navigator = Navigator(self.tb, self.animaltree) self.topsplitter.SetMinimumPaneSize(100) self.topsplitter.SplitVertically(self.animaltree, self.tabswitch) self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.topsplitter, flag = wx.EXPAND, proportion = 1) self.sizer.Add(wx.StaticLine(self), flag = wx.EXPAND) self.sizer.Add(self.timepanel, flag = wx.EXPAND) # We need to switch between animalview and multigraph on the first page self.graphpage = wx.Panel(self.tabswitch) self.graphpage.Bind(wx.EVT_SIZE, self.graphpage_OnSize) self.graphpanel = MultiDataGraph(self.graphpage) self.animalview = AnimalView(self.graphpage) self.animalview.Show(False) self.tabswitch.AddPage(self.graphpage, _('Graphs')) self.importlogpage = logpanel.ImportLogPanel(self.tabswitch) self.tabswitch.AddPage(self.importlogpage, _('Import log')) self.gfeedpanel = GroupFeedPanel(self.tabswitch) self.tabswitch.AddPage(self.gfeedpanel, _('Groupfeeds')) self.querypanel = QueryPanel(self.tabswitch) self.tabswitch.AddPage(self.querypanel, _('SQL-Queries')) self.SetAutoLayout(True) self.SetSizer(self.sizer) self.SetMinSize((700,600)) self.RestoreSize() self.Layout() self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(events.EVT_NODESELECT, self.OnNodeSelect) self.Bind(events.EVT_TREECHANGE, self.OnTreeChange) self.Bind(events.EVT_TIMEFRAME, self.OnTimeframeChanged) self.navigator.Bind(events.EVT_GRAPHUPDATE, self.updategraph) self.navigator.Bind(events.EVT_DATARELOAD, self.OnReload) def graphpage_OnSize(self, evt = None): self.animalview.SetSize(self.graphpage.GetSize()) self.graphpanel.SetSize(self.graphpage.GetSize()) def OnTreeChange(self, evt = None): self.navigator.update_tree() def OnClose(self, evt = None): '''Save window size and exit''' width, height = self.GetSize() sashpos = self.topsplitter.GetSashPosition() maximized = self.IsMaximized() settings.parser.set("GUI", "mainwindow_w", width) settings.parser.set("GUI", "mainwindow_h", height) settings.parser.set("GUI", "mainwindow_sash", sashpos) settings.parser.set("GUI", "mainwindow_max", maximized) settings.saveconfig() raise SystemExit def RestoreSize(self): '''Restore window size''' width = settings.parser.getint("GUI", "mainwindow_w") height = settings.parser.getint("GUI", "mainwindow_h") sash = settings.parser.getint("GUI", "mainwindow_sash") maximized = settings.parser.getboolean("GUI", "mainwindow_max") if maximized: self.Maximize() self.SetSize((width, height)) self.topsplitter.SetSashPosition(sash) def OnNodeSelect(self, evt): '''Log or graph link has been clicked''' self.tabswitch.SetSelection(0) node = self.navigator.tree.find_node(evt.userdata) if not node: print "Invalid link", repr(evt.userdata) else: self.navigator.goto(node) def OnTimeframeChanged(self, evt = None): self.updategraph() def OnReload(self, evt = None): '''Reload data files, update display''' do_guiimport() self.navigator.update_tree() self.updategraph() self.importlogpage.refresh() self.gfeedpanel.update() def updategraph(self, evt = None): fromdate, todate = self.timepanel.GetTimeframe() node = self.navigator.location try: wx.BeginBusyCursor() showmulti = True # Animalview or multigraph if node.userdata is None: # Root node self.graphpanel.plot_all(fromdate, todate) elif node.userdata[0] == 'group': self.graphpanel.plot_group(node.userdata[1], fromdate, todate) elif node.userdata[0] == 'section': self.graphpanel.plot_section(node.userdata[1], fromdate, todate) elif node.userdata[0] == 'animal': self.animalview.showanimal(node.userdata[1], fromdate, todate) showmulti = False else: self.graphpanel.clear() self.graphpanel.Show(showmulti) self.animalview.Show(not showmulti) finally: wx.EndBusyCursor() if __name__ == '__main__': print "Unit testing" class MyApp(wx.App): def OnInit(self): w = MainWindow() w.Show() return True app = MyApp(0) app.MainLoop()