#!/usr/bin/env python ''' HOOKE - A force spectroscopy review & analysis tool Copyright (C) 2008-2010 Massimo Sandal (University of Bologna, Italy). Rolf Schmidt (Concordia University, Canada). This program is released under the GNU General Public License version 2. ''' from libhooke import WX_GOOD import wxversion wxversion.select(WX_GOOD) import copy import cStringIO import os import os.path import sys import glob import time import imp import wx import wx.html import wx.aui import wxmpl import wx.lib.agw.aui as aui import wx.propgrid as wxpg import libhooke as lh from config import config import drivers import plugins import hookecommands import hookeplaylist import hookepropertyeditor import hookeresults import playlist global __version__ __version__ = lh.HOOKE_VERSION[0] __release_name__ = lh.HOOKE_VERSION[1] #TODO: order menu items, get rid of all unused IDs ID_ExportText = wx.NewId() ID_ExportImage = wx.NewId() ID_Config = wx.NewId() ID_About = wx.NewId() ID_Next = wx.NewId() ID_Previous = wx.NewId() ID_ViewAssistant = wx.NewId() ID_ViewCommands = wx.NewId() ID_ViewFolders = wx.NewId() ID_ViewOutput = wx.NewId() ID_ViewPlaylists = wx.NewId() ID_ViewProperties = wx.NewId() ID_ViewResults = wx.NewId() ID_CommandsList = wx.NewId() ID_CommandsListBox = wx.NewId() ID_TextContent = wx.NewId() ID_TreeContent = wx.NewId() ID_HTMLContent = wx.NewId() ID_SizeReportContent = wx.NewId() ID_DeletePerspective = wx.NewId() ID_SavePerspective = wx.NewId() ID_FirstPerspective = ID_SavePerspective + 1000 #I hope we'll never have more than 1000 perspectives ID_FirstPlot = ID_SavePerspective + 2000 class Hooke(wx.App): def OnInit(self): self.SetAppName('Hooke') self.SetVendorName('') #set the Hooke directory lh.hookeDir = os.path.abspath(os.path.dirname(__file__)) windowPosition = (config['main']['left'], config['main']['top']) windowSize = (config['main']['width'], config['main']['height']) #setup the splashscreen if config['splashscreen']['show']: filename = lh.get_file_path('hooke.jpg', ['resources']) if os.path.isfile(filename): bitmap = wx.Image(filename).ConvertToBitmap() splashStyle = wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT splashDuration = config['splashscreen']['duration'] wx.SplashScreen(bitmap, splashStyle, splashDuration, None, -1) wx.Yield() ''' we need for the splash screen to disappear for whatever reason splashDuration and sleep do not correspond to each other at least not on Windows maybe it's because duration is in milliseconds and sleep in seconds thus we need to increase the sleep time a bit a factor of 1.2 seems to work quite well ''' sleepFactor = 1.2 time.sleep(sleepFactor * splashDuration / 1000) plugin_objects = [] for plugin in config['plugins']: if config['plugins'][plugin]: filename = ''.join([plugin, '.py']) path = lh.get_file_path(filename, ['plugins']) if os.path.isfile(path): #get the corresponding filename and path plugin_name = ''.join(['plugins.', plugin]) module = __import__(plugin_name) #get the file that contains the plugin class_file = getattr(plugins, plugin) #get the class that contains the commands class_object = getattr(class_file, plugin + 'Commands') plugin_objects.append(class_object) def make_command_class(*bases): #create metaclass with plugins and plotmanipulators return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {}) frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=windowPosition, size=windowSize) frame.Show(True) self.SetTopWindow(frame) return True def OnExit(self): #TODO: write values to ini file if necessary return True class HookeFrame(wx.Frame): def __init__(self, parent, id=-1, title='', pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN): #call parent constructor wx.Frame.__init__(self, parent, id, title, pos, size, style) self.config = config self.CreateApplicationIcon() #self.configs contains: {the name of the Commands file: corresponding ConfigObj} self.configs = {} ##self.playlists contains: {the name of the playlist: [playlist, tabIndex, plotID]} #self.playlists = {} #self.plugins contains: {the name of the plugin: [caption, function]} self.plugins = {} #self.plotmanipulators list contains: [the name of the plotmanip, function, name of the module] self.plotmanipulators = [] #tell FrameManager to manage this frame self._mgr = aui.AuiManager() self._mgr.SetManagedWindow(self) #set the gradient style self._mgr.GetArtProvider().SetMetric(aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE) #set transparent drag self._mgr.SetFlags(self._mgr.GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG) # set up default notebook style self._notebook_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER self._notebook_theme = 0 #holds the perspectives: {name, [index, perspectiveStr]} self._perspectives = {} # min size for the frame itself isn't completely done. # see the end up FrameManager::Update() for the test # code. For now, just hard code a frame minimum size self.SetMinSize(wx.Size(400, 300)) #create panels here self.panelAssistant = self.CreatePanelAssistant() self.panelCommands = self.CreatePanelCommands() self.panelFolders = self.CreatePanelFolders() self.panelPlaylists = self.CreatePanelPlaylists() self.panelProperties = self.CreatePanelProperties() self.panelOutput = self.CreatePanelOutput() self.panelResults = self.CreatePanelResults() self.plotNotebook = self.CreateNotebook() #self.textCtrlCommandLine=self.CreateCommandLine() # add panes self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False)) self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.panelAssistant, aui.AuiPaneInfo().Name('Assistant').Caption('Assistant').Right().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.panelOutput, aui.AuiPaneInfo().Name('Output').Caption('Output').Bottom().CloseButton(True).MaximizeButton(False)) self._mgr.AddPane(self.panelResults, aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().CloseButton(True).MaximizeButton(False)) #self._mgr.AddPane(self.textCtrlCommandLine, aui.AuiPaneInfo().Name('CommandLine').CaptionVisible(False).Fixed().Bottom().Layer(2).CloseButton(False).MaximizeButton(False)) #self._mgr.AddPane(panelBottom, aui.AuiPaneInfo().Name("panelCommandLine").Bottom().Position(1).CloseButton(False).MaximizeButton(False)) # add the toolbars to the manager self.toolbar=self.CreateToolBar() self.toolbarNavigation=self.CreateToolBarNavigation() self._mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name('toolbar').Caption('Toolbar').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False)) self._mgr.AddPane(self.toolbarNavigation, aui.AuiPaneInfo().Name('toolbarNavigation').Caption('Navigation').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False)) # "commit" all changes made to FrameManager self._mgr.Update() #create the menubar after the panes so that the default perspective #is created with all panes open self.CreateMenuBar() self.statusbar = self.CreateStatusBar() self._BindEvents() #TODO: select item on startup (whatever item) #self.listCtrlCommands.Select(0) #self.OnListboxSelect(None) name = self.config['perspectives']['active'] menu_item = self.GetPerspectiveMenuItem(name) self.OnRestorePerspective(menu_item) self.playlists = self.panelPlaylists.Playlists #define the list of active drivers self.drivers = [] for driver in self.config['drivers']: if self.config['drivers'][driver]: #get the corresponding filename and path filename = ''.join([driver, '.py']) path = lh.get_file_path(filename, ['drivers']) #the driver is active for driver[1] == 1 if os.path.isfile(path): #driver files are located in the 'drivers' subfolder driver_name = ''.join(['drivers.', driver]) module = __import__(driver_name) class_file = getattr(drivers, driver) for command in dir(class_file): if command.endswith('Driver'): self.drivers.append(getattr(class_file, command)) #import all active plugins and plotmanips #the plotmanip_functions contains: {the name of the plotmanip: [method, class_object]} plotmanip_functions = {} #add 'general.ini' to self.configs (this is not a plugin and thus must be imported seperately) ini_path = lh.get_file_path('general.ini', ['plugins']) plugin_config = ConfigObj(ini_path) #self.config.merge(plugin_config) self.configs['general'] = plugin_config #make sure we execute _plug_init() for every command line plugin we import for plugin in self.config['plugins']: if self.config['plugins'][plugin]: filename = ''.join([plugin, '.py']) path = lh.get_file_path(filename, ['plugins']) if os.path.isfile(path): #get the corresponding filename and path plugin_name = ''.join(['plugins.', plugin]) try: #import the module module = __import__(plugin_name) #prepare the ini file for inclusion ini_path = path.replace('.py', '.ini') #include ini file plugin_config = ConfigObj(ini_path) #self.config.merge(plugin_config) self.configs[plugin] = plugin_config #add to plugins commands = eval('dir(module.' + plugin+ '.' + plugin + 'Commands)') #keep only commands (ie names that start with 'do_') commands = [command for command in commands if command.startswith('do_')] if commands: self.plugins[plugin] = commands try: #initialize the plugin eval('module.' + plugin+ '.' + plugin + 'Commands._plug_init(self)') except AttributeError: pass except ImportError: pass #initialize the commands tree commands = dir(HookeFrame) commands = [command for command in commands if command.startswith('do_')] if commands: self.plugins['general'] = commands self.panelCommands.Initialize(self.plugins) for command in dir(self): if command.startswith('plotmanip_'): plotmanip_functions[command] = [command, getattr(self, command)] for name in self.config['plotmanipulators']['names']: if self.config['plotmanipulators'].as_bool(name): command_name = ''.join(['plotmanip_', name]) if command_name in plotmanip_functions: self.plotmanipulators.append(plotmanip_functions[command_name]) #load default list, if possible self.do_loadlist(self.config['general']['list']) def _BindEvents(self): self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_CLOSE, self.OnClose) # Show How To Use The Closing Panes Event self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose) self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnNotebookPageClose) #menu self.Bind(wx.EVT_MENU, self.OnClose, id=wx.ID_EXIT) self.Bind(wx.EVT_MENU, self.OnAbout, id=wx.ID_ABOUT) #view self.Bind(wx.EVT_MENU_RANGE, self.OnView, id=ID_ViewAssistant, id2=ID_ViewResults) #perspectives self.Bind(wx.EVT_MENU, self.OnDeletePerspective, id=ID_DeletePerspective) self.Bind(wx.EVT_MENU, self.OnSavePerspective, id=ID_SavePerspective) self.Bind(wx.EVT_MENU_RANGE, self.OnRestorePerspective, id=ID_FirstPerspective, id2=ID_FirstPerspective+1000) #toolbar self.Bind(wx.EVT_TOOL, self.OnExportImage, id=ID_ExportImage) self.Bind(wx.EVT_TOOL, self.OnNext, id=ID_Next) self.Bind(wx.EVT_TOOL, self.OnPrevious, id=ID_Previous) #self.Bind(.EVT_AUITOOLBAR_TOOL_DROPDOWN, self.OnDropDownToolbarItem, id=ID_DropDownToolbarItem) #dir control treeCtrl = self.panelFolders.GetTreeCtrl() #tree.Bind(wx.EVT_LEFT_UP, self.OnDirCtrl1LeftUp) #tree.Bind(wx.EVT_LEFT_DOWN, self.OnGenericDirCtrl1LeftDown) treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self.OnDirCtrlLeftDclick) #playlist tree self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DOWN, self.OnPlaylistsLeftDown) self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DCLICK, self.OnPlaylistsLeftDclick) #commands tree self.panelCommands.ExecuteButton.Bind(wx.EVT_BUTTON, self.OnExecute) self.panelCommands.CommandsTree.Bind(wx.EVT_LEFT_DOWN, self.OnTreeCtrlCommandsLeftDown) #property editor self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged) self.panelProperties.pg.Bind(wxpg.EVT_PG_SELECTED, self.OnPropGridSelect) #results panel self.panelResults.results_list.OnCheckItem = self.OnResultsCheck def _GetActiveCurveIndex(self): playlist = self.GetActivePlaylist() #get the selected item from the tree selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() #test if a playlist or a curve was double-clicked if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): return -1 else: count = 0 selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) while selected_item.IsOk(): count += 1 selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) return count def _GetActivePlaylistName(self): #get the selected item from the tree selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() #test if a playlist or a curve was double-clicked if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): playlist_item = selected_item else: #get the name of the playlist playlist_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) #now we have a playlist return self.panelPlaylists.PlaylistsTree.GetItemText(playlist_item) def _GetPlaylistTab(self, name): for index, page in enumerate(self.plotNotebook._tabs._pages): if page.caption == name: return index return -1 def _GetUniquePlaylistName(self, name): playlist_name = name count = 1 while playlist_name in self.playlists: playlist_name = ''.join([name, str(count)]) count += 1 return playlist_name def _SavePerspectiveToFile(self, name, perspective): filename = ''.join([name, '.txt']) filename = lh.get_file_path(filename, ['perspectives']) perspectivesFile = open(filename, 'w') perspectivesFile.write(perspective) perspectivesFile.close() def AddPlaylist(self, playlist=None, name='Untitled'): #TODO: change cursor or progressbar (maybe in statusbar) #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) if playlist and playlist.count > 0: playlist.name = self._GetUniquePlaylistName(name) playlist.reset() self.AddToPlaylists(playlist) def AddPlaylistFromFiles(self, files=[], name='Untitled'): #TODO: change cursor or progressbar (maybe in statusbar) #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) if files: playlist = Playlist.Playlist(self.drivers) for item in files: playlist.add_curve(item) if playlist.count > 0: playlist.name = self._GetUniquePlaylistName(name) playlist.reset() self.AddToPlaylists(playlist) def AddToPlaylists(self, playlist): if playlist.has_curves: #setup the playlist in the Playlist tree tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem() playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0) #add all curves to the Playlist tree curves = {} for index, curve in enumerate(playlist.curves): ##remove the extension from the name of the curve ##TODO: optional? #item_text, extension = os.path.splitext(curve.name) #curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, item_text, 1) curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, curve.name, 1) if index == playlist.index: self.panelPlaylists.PlaylistsTree.SelectItem(curve_ID) playlist.reset() #create the plot tab and add playlist to the dictionary plotPanel = wxmpl.PlotPanel(self, ID_FirstPlot + len(self.playlists)) notebook_tab = self.plotNotebook.AddPage(plotPanel, playlist.name, True) tab_index = self.plotNotebook.GetSelection() figure = plotPanel.get_figure() self.playlists[playlist.name] = [playlist, figure] self.panelPlaylists.PlaylistsTree.Expand(playlist_root) self.statusbar.SetStatusText(playlist.get_status_string(), 0) self.UpdatePlot() def AppendToOutput(self, text): self.panelOutput.AppendText(''.join([text, '\n'])) def CreateApplicationIcon(self): iconFile = 'resources' + os.sep + 'microscope.ico' icon = wx.Icon(iconFile, wx.BITMAP_TYPE_ICO) self.SetIcon(icon) def CreateCommandLine(self): return wx.TextCtrl(self, -1, '', style=wx.NO_BORDER|wx.EXPAND) def CreatePanelAssistant(self): panel = wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE) panel.SetEditable(False) return panel def CreatePanelCommands(self): return hookecommands.Commands(self) def CreatePanelFolders(self): #set file filters filters = self.config['folders']['filters'] index = self.config['folders'].as_int('filterindex') #set initial directory folder = self.config['general']['workdir'] return wx.GenericDirCtrl(self, -1, dir=folder, size=(200, 250), style=wx.DIRCTRL_SHOW_FILTERS, filter=filters, defaultFilter=index) def CreatePanelOutput(self): return wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE) def CreatePanelPlaylists(self): return hookeplaylist.Playlists(self) def CreatePanelProperties(self): return hookepropertyeditor.PropertyEditor(self) def CreatePanelResults(self): return hookeresults.Results(self) def CreatePanelWelcome(self): ctrl = wx.html.HtmlWindow(self, -1, wx.DefaultPosition, wx.Size(400, 300)) introStr = '
See the DocumentationIndex for more information
' ctrl.SetPage(introStr) return ctrl def CreateMenuBar(self): menu_bar = wx.MenuBar() #file file_menu = wx.Menu() file_menu.Append(wx.ID_OPEN, '&Open playlist\tCtrl-O') file_menu.Append(wx.ID_SAVE, 'Save playlist\tCtrl-S') file_menu.AppendSeparator() file_menu.Append(wx.ID_EXIT, 'Exit\tCtrl-Q') #edit edit_menu = wx.Menu() edit_menu.Append(ID_ExportText, 'Export text...') edit_menu.Append(ID_ExportImage, 'Export image...') edit_menu.AppendSeparator(); edit_menu.Append(ID_Config, 'Preferences') #view view_menu = wx.Menu() view_menu.AppendCheckItem(ID_ViewFolders, 'Folders\tF5') view_menu.AppendCheckItem(ID_ViewPlaylists, 'Playlists\tF6') view_menu.AppendCheckItem(ID_ViewCommands, 'Commands\tF7') view_menu.AppendCheckItem(ID_ViewProperties, 'Properties\tF8') view_menu.AppendCheckItem(ID_ViewAssistant, 'Assistant\tF9') view_menu.AppendCheckItem(ID_ViewResults, 'Results\tF10') view_menu.AppendCheckItem(ID_ViewOutput, 'Output\tF11') #perspectives self._perspectives_menu = self.CreatePerspectivesMenu() #help help_menu = wx.Menu() help_menu.Append(wx.ID_ABOUT, 'About Hooke') #put it all together menu_bar.Append(file_menu, 'File') menu_bar.Append(edit_menu, 'Edit') menu_bar.Append(view_menu, 'View') menu_bar.Append(self._perspectives_menu, "Perspectives") menu_bar.Append(help_menu, 'Help') self.SetMenuBar(menu_bar) def CreateNotebook(self): # create the notebook off-window to avoid flicker client_size = self.GetClientSize() ctrl = aui.AuiNotebook(self, -1, wx.Point(client_size.x, client_size.y), wx.Size(430, 200), self._notebook_style) arts = [aui.AuiDefaultTabArt, aui.AuiSimpleTabArt, aui.VC71TabArt, aui.FF2TabArt, aui.VC8TabArt, aui.ChromeTabArt] art = arts[self._notebook_theme]() ctrl.SetArtProvider(art) #uncomment if we find a nice icon #page_bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)) ctrl.AddPage(self.CreatePanelWelcome(), "Welcome", False) return ctrl def CreatePerspectivesMenu(self): menu = wx.Menu() menu.Append(ID_SavePerspective, "Save Perspective") menu.Append(ID_DeletePerspective, "Delete Perspective") menu.AppendSeparator() #add perspectives to menubar and _perspectives perspectivesDirectory = os.path.join(lh.hookeDir, 'perspectives') if os.path.isdir(perspectivesDirectory): perspectiveFileNames = os.listdir(perspectivesDirectory) for perspectiveFilename in perspectiveFileNames: filename = lh.get_file_path(perspectiveFilename, ['perspectives']) if os.path.isfile(filename): perspectiveFile = open(filename, 'rU') perspective = perspectiveFile.readline() perspectiveFile.close() if perspective != '': name, extension = os.path.splitext(perspectiveFilename) if extension == '.txt': menuItem = menu.AppendRadioItem(ID_FirstPerspective + len(self._perspectives), name) self._perspectives[name] = [len(self._perspectives), perspective] if self.config['perspectives']['active'] == name: menuItem.Check() #in case there are no perspectives if not self._perspectives: perspective = self._mgr.SavePerspective() self.config['perspectives']['default'] = 'Default' self._perspectives['Default'] = [0, perspective] menuItem = menu.AppendRadioItem(ID_FirstPerspective, 'Default') menuItem.Check() self._SavePerspectiveToFile('Default', perspective) return menu def CreateStatusbar(self): statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP) statusbar.SetStatusWidths([-2, -3]) statusbar.SetStatusText('Ready', 0) welcomeString=u'Welcome to Hooke (version '+__version__+', '+__release_name__+')!' statusbar.SetStatusText(welcomeString, 1) return statusbar def CreateToolBar(self): toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER) toolbar.SetToolBitmapSize(wx.Size(16,16)) toolbar_bmp1 = wx.ArtProvider_GetBitmap(wx.ART_QUESTION, wx.ART_OTHER, wx.Size(16, 16)) toolbar_bmpOpen = wx.ArtProvider_GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, wx.Size(16, 16)) toolbar_bmpSave = wx.ArtProvider_GetBitmap(wx.ART_FILE_SAVE, wx.ART_OTHER, wx.Size(16, 16)) toolbar_bmpExportText = wx.ArtProvider_GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16)) toolbar_bmpExportImage = wx.ArtProvider_GetBitmap(wx.ART_MISSING_IMAGE, wx.ART_OTHER, wx.Size(16, 16)) toolbar.AddLabelTool(101, 'Open', toolbar_bmpOpen) toolbar.AddLabelTool(102, 'Save', toolbar_bmpSave) toolbar.AddSeparator() toolbar.AddLabelTool(ID_ExportText, 'Export text...', toolbar_bmpExportText) toolbar.AddLabelTool(ID_ExportImage, 'Export image...', toolbar_bmpExportImage) toolbar.Realize() return toolbar def CreateToolBarNavigation(self): toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER) toolbar.SetToolBitmapSize(wx.Size(16,16)) toolbar_bmpBack = wx.ArtProvider_GetBitmap(wx.ART_GO_BACK, wx.ART_OTHER, wx.Size(16, 16)) toolbar_bmpForward = wx.ArtProvider_GetBitmap(wx.ART_GO_FORWARD, wx.ART_OTHER, wx.Size(16, 16)) toolbar.AddLabelTool(ID_Previous, 'Previous', toolbar_bmpBack, shortHelp='Previous curve') toolbar.AddLabelTool(ID_Next, 'Next', toolbar_bmpForward, shortHelp='Next curve') toolbar.Realize() return toolbar def DeleteFromPlaylists(self, name): if name in self.playlists: del self.playlists[name] tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem() item, cookie = self.panelPlaylists.PlaylistsTree.GetFirstChild(tree_root) while item.IsOk(): playlist_name = self.panelPlaylists.PlaylistsTree.GetItemText(item) if playlist_name == name: try: self.panelPlaylists.PlaylistsTree.Delete(item) except: pass item = self.panelPlaylists.PlaylistsTree.GetNextSibling(item) self.OnPlaylistsLeftDclick(None) def DeletePlotPage(self, name): index = self._GetPlaylistTab(name) plot = self.playlists[name][1] plot = None self.plotNotebook.RemovePage(index) self.DeleteFromPlaylists(name) def GetActiveCurve(self): playlist = self.GetActivePlaylist() if playlist is not None: return playlist.get_active_curve() return None def GetActivePlaylist(self): playlist_name = self._GetActivePlaylistName() if playlist_name in self.playlists: return self.playlists[playlist_name][0] return None def GetActivePlot(self): curve = self.GetActiveCurve() if curve is not None: return curve.plots[0] return None def GetDockArt(self): return self._mgr.GetArtProvider() def GetBoolFromConfig(self, *args): if len(args) == 2: plugin = args[0] section = args[0] key = args[1] elif len(args) == 3: plugin = args[0] section = args[1] key = args[2] if self.configs.has_key(plugin): config = self.configs[plugin] return config[section][key].as_bool('value') return None def GetFloatFromConfig(self, *args): if len(args) == 2: plugin = args[0] section = args[0] key = args[1] elif len(args) == 3: plugin = args[0] section = args[1] key = args[2] if self.configs.has_key(plugin): config = self.configs[plugin] return config[section][key].as_float('value') return None def GetIntFromConfig(self, *args): if len(args) == 2: plugin = args[0] section = args[0] key = args[1] elif len(args) == 3: plugin = args[0] section = args[1] key = args[2] if self.configs.has_key(plugin): config = self.configs[plugin] return config[section][key].as_int('value') return None def GetStringFromConfig(self, *args): if len(args) == 2: plugin = args[0] section = args[0] key = args[1] elif len(args) == 3: plugin = args[0] section = args[1] key = args[2] if self.configs.has_key(plugin): config = self.configs[plugin] return config[section][key]['value'] return None def GetPerspectiveMenuItem(self, name): index = self._perspectives[name][0] perspective_Id = ID_FirstPerspective + index menu_item = self.MenuBar.FindItemById(perspective_Id) return menu_item def HasPlotmanipulator(self, name): ''' returns True if the plotmanipulator 'name' is loaded, False otherwise ''' for plotmanipulator in self.plotmanipulators: if plotmanipulator[0] == name: return True return False def OnAbout(self, event): msg = 'Hooke\n\n'+\ 'A free, open source data analysis platform\n'+\ '(c) 2006-2008 Massimo Sandal\n\n'+\ '(c) 2009 Dr. Rolf Schmidt\n\n'+\ 'Released under the GNU GPL v2' dialog = wx.MessageDialog(self, msg, "About Hooke", wx.OK | wx.ICON_INFORMATION) dialog.ShowModal() dialog.Destroy() def OnClose(self, event): #apply changes self.config['main']['height'] = str(self.GetSize().GetHeight()) self.config['main']['left'] = str(self.GetPosition()[0]) self.config['main']['top'] = str(self.GetPosition()[1]) self.config['main']['width'] = str(self.GetSize().GetWidth()) # Writing the configuration file to 'hooke.ini' self.config.write() self._mgr.UnInit() del self._mgr self.Destroy() def OnDeletePerspective(self, event): pass def OnDirCtrlLeftDclick(self, event): file_path = self.panelFolders.GetPath() if os.path.isfile(file_path): if file_path.endswith('.hkp'): self.do_loadlist(file_path) else: pass event.Skip() def OnEraseBackground(self, event): event.Skip() def OnExecute(self, event): item = self.panelCommands.CommandsTree.GetSelection() if item.IsOk(): if self.panelCommands.CommandsTree.ItemHasChildren(item): pass else: #get the plugin parent = self.panelCommands.CommandsTree.GetItemParent(item) if not self.panelCommands.CommandsTree.ItemHasChildren(item): parent_text = self.panelCommands.CommandsTree.GetItemText(parent) item_text = self.panelCommands.CommandsTree.GetItemText(item) if item_text in ['genlist', 'loadlist', 'savelist']: property_values = self.panelProperties.GetPropertyValues() arg_str = '' for item in property_values: arg_str = ''.join([arg_str, item, '=r"', str(property_values[item]), '", ']) command = ''.join(['self.do_', item_text, '(', arg_str, ')']) else: command = ''.join(['self.do_', item_text, '()']) exec(command) pass def OnExit(self, event): self.Close() def OnExportImage(self, event): pass def OnNext(self, event): ''' NEXT Go to the next curve in the playlist. If we are at the last curve, we come back to the first. ----- Syntax: next, n ''' selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): #GetFirstChild returns a tuple #we only need the first element next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(selected_item)[0] else: next_item = self.panelPlaylists.PlaylistsTree.GetNextSibling(selected_item) if not next_item.IsOk(): parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) #GetFirstChild returns a tuple #we only need the first element next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(parent_item)[0] self.panelPlaylists.PlaylistsTree.SelectItem(next_item, True) playlist = self.playlists[self._GetActivePlaylistName()][0] if playlist.count > 1: playlist.next() self.statusbar.SetStatusText(playlist.get_status_string(), 0) self.UpdatePlot() def OnNotebookPageClose(self, event): ctrl = event.GetEventObject() playlist_name = ctrl.GetPageText(ctrl._curpage) self.DeleteFromPlaylists(playlist_name) def OnPaneClose(self, event): event.Skip() def OnPlaylistsLeftDclick(self, event): playlist_name = self._GetActivePlaylistName() #if that playlist already exists #we check if it is the active playlist (ie selected in panelPlaylists) #and switch to it if necessary if playlist_name in self.playlists: index = self.plotNotebook.GetSelection() current_playlist = self.plotNotebook.GetPageText(index) #new_playlist = self.playlists[playlist_name][0] #if current_playlist != new_playlist: if current_playlist != playlist_name: index = self._GetPlaylistTab(playlist_name) self.plotNotebook.SetSelection(index) #if a curve was double-clicked item = self.panelPlaylists.PlaylistsTree.GetSelection() #TODO: fix with get_active_curve if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item): index = self._GetActiveCurveIndex() else: index = 0 if index >= 0: playlist = self.playlists[playlist_name][0] playlist.index = index self.statusbar.SetStatusText(playlist.get_status_string(), 0) self.UpdatePlot() #if you uncomment the following line, the tree will collapse/expand as well #event.Skip() def OnPlaylistsLeftDown(self, event): hit_item, hit_flags = self.panelPlaylists.PlaylistsTree.HitTest(event.GetPosition()) if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: #self.SetFocus() self.panelPlaylists.PlaylistsTree.SelectItem(hit_item) playlist_name = self._GetActivePlaylistName() playlist = self.playlists[playlist_name][0] #if a curve was clicked item = self.panelPlaylists.PlaylistsTree.GetSelection() if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item): #TODO: fix with get_active_curve index = self._GetActiveCurveIndex() if index >= 0: #playlist = self.playlists[playlist_name][0] playlist.index = index #self.playlists[playlist_name][0].index = index #else: ##self.playlists[playlist_name][0].index = 0 #playlist.index = index self.playlists[playlist_name][0] = playlist event.Skip() def OnPrevious(self, event): ''' PREVIOUS Go to the previous curve in the playlist. If we are at the first curve, we jump to the last. ------- Syntax: previous, p ''' #playlist = self.playlists[self._GetActivePlaylistName()][0] #select the previous curve and tell the user if we wrapped around #self.AppendToOutput(playlist.previous()) selected_item = self.panelPlaylists.PlaylistsTree.GetSelection() if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item): previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(selected_item) else: previous_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item) if not previous_item.IsOk(): parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item) previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(parent_item) self.panelPlaylists.PlaylistsTree.SelectItem(previous_item, True) playlist = self.playlists[self._GetActivePlaylistName()][0] if playlist.count > 1: playlist.previous() self.statusbar.SetStatusText(playlist.get_status_string(), 0) self.UpdatePlot() def OnPropGridChanged (self, event): prop = event.GetProperty() if prop: item_section = self.panelProperties.SelectedTreeItem item_plugin = self.panelCommands.CommandsTree.GetItemParent(item_section) plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin) config = self.configs[plugin] property_section = self.panelCommands.CommandsTree.GetItemText(item_section) property_key = prop.GetName() property_value = prop.GetValue() config[property_section][property_key]['value'] = property_value def OnPropGridSelect(self, event): pass def OnRestorePerspective(self, event): name = self.MenuBar.FindItemById(event.GetId()).GetLabel() self._mgr.LoadPerspective(self._perspectives[name][1]) self.config['perspectives']['active'] = name self._mgr.Update() all_panes = self._mgr.GetAllPanes() for pane in all_panes: if not pane.name.startswith('toolbar'): if pane.name == 'Assistant': self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown()) if pane.name == 'Folders': self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown()) if pane.name == 'Playlists': self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown()) if pane.name == 'Commands': self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown()) if pane.name == 'Properties': self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown()) if pane.name == 'Output': self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown()) if pane.name == 'Results': self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown()) def OnResultsCheck(self, index, flag): curve = self.GetActiveCurve() result = curve.data['results'][index]['visible'] = flag self.UpdatePlot() def OnSavePerspective(self, event): def nameExists(name): for item in self._perspectives_menu.GetMenuItems(): if item.GetText() == name: return True return False done = False while not done: dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective') dialog.SetValue('New perspective') if dialog.ShowModal() != wx.ID_OK: return else: name = dialog.GetValue() if nameExists(name): dialogConfirm = wx.MessageDialog(self, 'A file with this name already exists.\n\nDo you want to replace it?', 'Confirm', wx.YES_NO|wx.ICON_QUESTION|wx.CENTER) if dialogConfirm.ShowModal() == wx.ID_YES: done = True else: done = True perspective = self._mgr.SavePerspective() if nameExists(name): #check the corresponding menu item menuItem = self.GetPerspectiveMenuItem(name) #replace the perspectiveStr in _pespectives index = self._perspectives[name][0] self._perspectives[name] = [index, perspective] else: #simply add the new perspective to _perspectives index = len(self._perspectives) self._perspectives[name] = [len(self._perspectives), perspective] menuItem = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + len(self._perspectives), name) menuItem.Check() self._SavePerspectiveToFile(name, perspective) #uncheck all perspective menu items #as these are radio items, one item has to be checked at all times #the line 'menuItem.Check()' above actually checks a second item #but does not toggle #so we need to uncheck all other items afterwards #weirdly enough, menuitem.Toggle() doesn't do this properly either for item in self._perspectives_menu.GetMenuItems(): if item.IsCheckable(): if item.GetLabel() != name: item.Check(False) def OnView(self, event): menu_id = event.GetId() menu_item = self.MenuBar.FindItemById(menu_id) menu_label = menu_item.GetLabel() pane = self._mgr.GetPane(menu_label) pane.Show(not pane.IsShown()) #if we don't do the following, the Folders pane does not resize properly on hide/show if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked(): #folders_size = pane.GetSize() self.panelFolders.Fit() self._mgr.Update() def OnSize(self, event): event.Skip() def OnTreeCtrlCommandsLeftDown(self, event): hit_item, hit_flags = self.panelCommands.CommandsTree.HitTest(event.GetPosition()) if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0: self.panelCommands.CommandsTree.SelectItem(hit_item) self.panelProperties.SelectedTreeItem = hit_item #if a command was clicked properties = [] if not self.panelCommands.CommandsTree.ItemHasChildren(hit_item): item_plugin = self.panelCommands.CommandsTree.GetItemParent(hit_item) plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin) if self.configs.has_key(plugin): #config = self.panelCommands.CommandsTree.GetPyData(item_plugin) config = self.configs[plugin] section = self.panelCommands.CommandsTree.GetItemText(hit_item) #display docstring in help window doc_string = eval('self.do_' + section + '.__doc__') if section in config: for option in config[section]: properties.append([option, config[section][option]]) else: module = self.panelCommands.CommandsTree.GetItemText(hit_item) if module != 'general': doc_string = eval('plugins.' + module + '.' + module + 'Commands.__doc__') else: doc_string = 'The module "general" contains Hooke core functionality' if doc_string is not None: self.panelAssistant.ChangeValue(doc_string) hookepropertyeditor.PropertyEditor.Initialize(self.panelProperties, properties) event.Skip() def UpdatePlaylists(self): #setup the playlist in the Playlist tree tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem() playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0) #add all curves to the Playlist tree curves = {} for index, curve in enumerate(playlist.curves): ##remove the extension from the name of the curve ##TODO: optional? #item_text, extension = os.path.splitext(curve.name) #curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, item_text, 1) curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, curve.name, 1) if index == playlist.index: self.panelPlaylists.PlaylistsTree.SelectItem(curve_ID) #create the plot tab and add playlist to the dictionary plotPanel = wxmpl.PlotPanel(self, ID_FirstPlot + len(self.playlists)) notebook_tab = self.plotNotebook.AddPage(plotPanel, playlist.name, True) #tab_index = self.plotNotebook.GetSelection() figure = plotPanel.get_figure() #self.playlists[playlist.name] = [playlist, tab_index, figure] self.playlists[playlist.name] = [playlist, figure] self.panelPlaylists.PlaylistsTree.Expand(playlist_root) self.statusbar.SetStatusText(playlist.get_status_string(), 0) self.UpdatePlot() #HELPER FUNCTIONS #Everything sending an event should be here def _measure_N_points(self, N, whatset=1): ''' general helper function for N-points measures ''' wx.PostEvent(self.frame,self.list_of_events['measure_points'](num_of_points=N, set=whatset)) while 1: try: points=self.frame.events_from_gui.get() break except Empty: pass return points def _clickize(self, xvector, yvector, index): ''' returns a ClickedPoint() object from an index and vectors of x, y coordinates ''' point = lh.ClickedPoint() point.index = index point.absolute_coords = xvector[index], yvector[index] point.find_graph_coords(xvector, yvector) return point #PLAYLIST INTERACTION COMMANDS #------------------------------- def do_genlist(self, folder=lh.hookeDir, filemask='*.*'): ''' GENLIST Generates a file playlist. Note it doesn't *save* it: see savelist for this. If [input files] is a directory, it will use all files in the directory for playlist. So: genlist dir genlist dir/ genlist dir/*.* are all equivalent syntax. ------------ Syntax: genlist [input files] ''' #args list is: input folder, file mask if os.path.isdir(folder): path = os.path.join(folder, filemask) #expanding correctly the input list with the glob module :) files = glob.glob(path) files.sort() #TODO: change cursor or progressbar (maybe in statusbar) #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) playlist = playlist.Playlist(self.drivers) for item in files: curve = playlist.add_curve(item) plot = copy.deepcopy(curve.plots[0]) #add the 'raw' data curve.add_data('raw', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') curve.add_data('raw', plot.vectors[1][0], plot.vectors[1][1], color=plot.colors[1], style='plot') #apply all active plotmanipulators and add the 'manipulated' data for plotmanipulator in self.plotmanipulators: plot = plotmanipulator[1](plot, curve) curve.set_data('manipulated', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') curve.add_data('manipulated', plot.vectors[1][0], plot.vectors[1][1], color=plot.colors[1], style='plot') if playlist.count > 0: playlist.name = self._GetUniquePlaylistName(os.path.basename(folder)) playlist.reset() self.AddToPlaylists(playlist) self.AppendToOutput(playlist.get_status_string()) else: self.AppendToOutput(''.join(['Cannot find folder ', folder])) def do_loadlist(self, filename): ''' LOADLIST Loads a file playlist ----------- Syntax: loadlist [playlist file] ''' #TODO: check for duplicate playlists, ask the user for a unique name #if self.playlist_name in self.playlists: #add hkp extension if necessary if not filename.endswith('.hkp'): filename = ''.join([filename, '.hkp']) #prefix with 'hookeDir' if just a filename or a relative path filename = lh.get_file_path(filename) if os.path.isfile(filename): #TODO: change cursor #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) playlist_new = playlist.Playlist(self.drivers) playlist_new.load(filename) if playlist_new.count > 0: for curve in playlist_new.curves: plot = copy.deepcopy(curve.plots[0]) for plotmanip in self.plotmanipulators: #to_plot = plotmanip[1](to_plot, curve) plot = plotmanip[1](plot, curve) curve.set_data('manipulated', plot.vectors[0][0], plot.vectors[0][1], color=plot.colors[0], style='plot') curve.add_data('manipulated', plot.vectors[1][0], plot.vectors[1][1], color=plot.colors[1], style='plot') self.AddToPlaylists(playlist_new) #else: ##TODO: display dialog self.AppendToOutput(playlist_new.get_status_string()) #TODO: change cursor #self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) else: #TODO: display dialog self.AppendToOutput(''.join['File ', filename, ' not found.\n']) pass def do_savelist(self, filename): ''' SAVELIST Saves the current file playlist on disk. ------------ Syntax: savelist [filename] ''' #self.playlist_generics['pointer'] = self._GetActiveCurveIndex pointer = self._GetActiveCurveIndex() #autocomplete filename if not specified if not filename.endswith('.hkp'): filename = filename.join(['.hkp']) playlist = self.GetActivePlaylist() playlist.set_XML() playlist.save(filename) #PLOT INTERACTION COMMANDS #------------------------------- def UpdatePlot(self): def add_plot(plot): if plot['visible'] and plot['x'] and plot['y']: color = plot['color'] style = plot['style'] if style == 'plot': axes.plot(plot['x'], plot['y'], color=color, zorder=1) if style == 'scatter': axes.scatter(plot['x'], plot['y'], color=color, s=0.5, zorder=2) def add_plot2(plot): if plot.visible and plot.x and plot.y: if plot.style == 'plot': axes.plot(plot.x, plot.y, color=plot.color, zorder=1) if plot.style == 'scatter': axes.scatter(plot.x, plot.y, color=plot.color, s=0.5, zorder=2) playlist_name = self._GetActivePlaylistName() index = self._GetActiveCurveIndex() playlist = self.playlists[playlist_name][0] curve = playlist.get_active_curve() plot = playlist.get_active_plot() figure = self.playlists[playlist_name][1] figure.clf() exclude = None if curve.data.has_key('manipulated'): exclude = 'raw' elif curve.data.has_key('raw'): exclude = 'manipulated' if exclude is not None: #TODO: what is this good for? if not hasattr(self, 'subplot'): axes = figure.add_subplot(111) axes.set_title(plot.title) axes.set_xlabel(plot.units[0]) axes.set_ylabel(plot.units[1]) for set_of_plots in curve.data: if set_of_plots != exclude: plots = curve.data[set_of_plots] for each_plot in plots: add_plot(each_plot) #TODO: add multiple results support #for fit in curve.fits: if curve.fits.has_key('wlc'): for plot in curve.fits['wlc'].results: add_plot2(plot) self.panelResults.DisplayResults(curve.fits['wlc']) else: self.panelResults.ClearResults() axes.figure.canvas.draw() else: self.AppendToOutput('Not able to plot.') ID_PaneBorderSize = wx.ID_HIGHEST + 1 ID_SashSize = ID_PaneBorderSize + 1 ID_CaptionSize = ID_PaneBorderSize + 2 ID_BackgroundColor = ID_PaneBorderSize + 3 ID_SashColor = ID_PaneBorderSize + 4 ID_InactiveCaptionColor = ID_PaneBorderSize + 5 ID_InactiveCaptionGradientColor = ID_PaneBorderSize + 6 ID_InactiveCaptionTextColor = ID_PaneBorderSize + 7 ID_ActiveCaptionColor = ID_PaneBorderSize + 8 ID_ActiveCaptionGradientColor = ID_PaneBorderSize + 9 ID_ActiveCaptionTextColor = ID_PaneBorderSize + 10 ID_BorderColor = ID_PaneBorderSize + 11 ID_GripperColor = ID_PaneBorderSize + 12 #---------------------------------------------------------------------- if __name__ == '__main__': ## now, silence a deprecation warning for py2.3 #import warnings #warnings.filterwarnings("ignore", "integer", DeprecationWarning, "wxPython.gdi") redirect=True if __debug__: redirect=False app = Hooke(redirect=redirect) app.MainLoop()