1 #!/usr/bin/env python
\r
4 HOOKE - A force spectroscopy review & analysis tool
\r
6 Copyright 2008 by Massimo Sandal (University of Bologna, Italy)
\r
7 Copyright 2010 by Rolf Schmidt (Concordia University, Canada)
\r
9 This program is released under the GNU General Public License version 2.
\r
12 import lib.libhooke as lh
\r
14 wxversion.select(lh.WX_GOOD)
\r
16 from configobj import ConfigObj
\r
24 import wx.lib.agw.aui as aui
\r
25 import wx.lib.evtmgr as evtmgr
\r
26 import wx.propgrid as wxpg
\r
28 from matplotlib import __version__ as mpl_version
\r
29 from numpy import __version__ as numpy_version
\r
30 from scipy import __version__ as scipy_version
\r
31 from sys import version as python_version
\r
32 from wx import __version__ as wx_version
\r
33 from matplotlib.ticker import FuncFormatter
\r
36 from agw import cubecolourdialog as CCD
\r
37 except ImportError: # if it's not there locally, try the wxPython lib.
\r
38 import wx.lib.agw.cubecolourdialog as CCD
\r
40 #set the Hooke directory
\r
41 lh.hookeDir = os.path.abspath(os.path.dirname(__file__))
\r
42 from config.config import config
\r
44 import lib.clickedpoint
\r
48 import lib.plotmanipulator
\r
49 import lib.prettyformat
\r
50 import panels.commands
\r
52 import panels.perspectives
\r
53 import panels.playlist
\r
55 import panels.propertyeditor
\r
56 import panels.results
\r
63 global __releasedate__
\r
64 __version__ = lh.HOOKE_VERSION[0]
\r
65 __codename__ = lh.HOOKE_VERSION[1]
\r
66 __releasedate__ = lh.HOOKE_VERSION[2]
\r
67 __release_name__ = lh.HOOKE_VERSION[1]
\r
69 ID_About = wx.NewId()
\r
70 ID_Next = wx.NewId()
\r
71 ID_Previous = wx.NewId()
\r
73 ID_ViewAssistant = wx.NewId()
\r
74 ID_ViewCommands = wx.NewId()
\r
75 ID_ViewFolders = wx.NewId()
\r
76 ID_ViewNote = wx.NewId()
\r
77 ID_ViewOutput = wx.NewId()
\r
78 ID_ViewPlaylists = wx.NewId()
\r
79 ID_ViewProperties = wx.NewId()
\r
80 ID_ViewResults = wx.NewId()
\r
82 ID_DeletePerspective = wx.NewId()
\r
83 ID_SavePerspective = wx.NewId()
\r
85 ID_FirstPerspective = ID_SavePerspective + 1000
\r
86 #I hope we'll never have more than 1000 perspectives
\r
87 ID_FirstPlot = ID_SavePerspective + 2000
\r
89 class Hooke(wx.App):
\r
92 self.SetAppName('Hooke')
\r
93 self.SetVendorName('')
\r
95 window_height = config['main']['height']
\r
96 window_left= config['main']['left']
\r
97 window_top = config['main']['top']
\r
98 window_width = config['main']['width']
\r
100 #sometimes, the ini file gets confused and sets 'left'
\r
101 #and 'top' to large negative numbers
\r
102 #let's catch and fix this
\r
103 #keep small negative numbers, the user might want those
\r
104 if window_left < -window_width:
\r
106 if window_top < -window_height:
\r
108 window_position = (window_left, window_top)
\r
109 window_size = (window_width, window_height)
\r
111 #setup the splashscreen
\r
112 if config['splashscreen']['show']:
\r
113 filename = lh.get_file_path('hooke.jpg', ['resources'])
\r
114 if os.path.isfile(filename):
\r
115 bitmap = wx.Image(filename).ConvertToBitmap()
\r
116 splashStyle = wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT
\r
117 splashDuration = config['splashscreen']['duration']
\r
118 wx.SplashScreen(bitmap, splashStyle, splashDuration, None, -1)
\r
121 we need for the splash screen to disappear
\r
122 for whatever reason splashDuration and sleep do not correspond to each other
\r
123 at least not on Windows
\r
124 maybe it's because duration is in milliseconds and sleep in seconds
\r
125 thus we need to increase the sleep time a bit
\r
126 a factor of 1.2 seems to work quite well
\r
129 time.sleep(sleepFactor * splashDuration / 1000)
\r
131 plugin_objects = []
\r
132 for plugin in config['plugins']:
\r
133 if config['plugins'][plugin]:
\r
134 filename = ''.join([plugin, '.py'])
\r
135 path = lh.get_file_path(filename, ['plugins'])
\r
136 if os.path.isfile(path):
\r
137 #get the corresponding filename and path
\r
138 plugin_name = ''.join(['plugins.', plugin])
\r
140 __import__(plugin_name)
\r
141 #get the file that contains the plugin
\r
142 class_file = getattr(plugins, plugin)
\r
143 #get the class that contains the commands
\r
144 class_object = getattr(class_file, plugin + 'Commands')
\r
145 plugin_objects.append(class_object)
\r
147 def make_command_class(*bases):
\r
148 #create metaclass with plugins and plotmanipulators
\r
149 return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {})
\r
150 frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=window_position, size=window_size)
\r
152 self.SetTopWindow(frame)
\r
160 class HookeFrame(wx.Frame):
\r
162 def __init__(self, parent, id=-1, title='', pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN):
\r
163 #call parent constructor
\r
164 wx.Frame.__init__(self, parent, id, title, pos, size, style)
\r
165 self.config = config
\r
166 self.CreateApplicationIcon()
\r
167 #self.configs contains: {the name of the Commands file: corresponding ConfigObj}
\r
169 #self.displayed_plot holds the currently displayed plot
\r
170 self.displayed_plot = None
\r
171 #self.playlists contains: {the name of the playlist: [playlist, tabIndex, plotID]}
\r
172 self.playlists = {}
\r
173 #list of all plotmanipulators
\r
174 self.plotmanipulators = []
\r
175 #self.plugins contains: {the name of the plugin: [caption, function]}
\r
177 #self.results_str contains the type of results we want to display
\r
178 self.results_str = 'wlc'
\r
180 #tell FrameManager to manage this frame
\r
181 self._mgr = aui.AuiManager()
\r
182 self._mgr.SetManagedWindow(self)
\r
183 #set the gradient style
\r
184 self._mgr.GetArtProvider().SetMetric(aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE)
\r
185 #set transparent drag
\r
186 self._mgr.SetFlags(self._mgr.GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG)
\r
188 # set up default notebook style
\r
189 self._notebook_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER
\r
190 self._notebook_theme = 0
\r
192 #holds the perspectives: {name, perspective_str}
\r
193 self._perspectives = {}
\r
195 # min size for the frame itself isn't completely done.
\r
196 # see the end up FrameManager::Update() for the test
\r
197 # code. For now, just hard code a frame minimum size
\r
198 self.SetMinSize(wx.Size(500, 500))
\r
199 #create panels here
\r
200 self.panelAssistant = self.CreatePanelAssistant()
\r
201 self.panelCommands = self.CreatePanelCommands()
\r
202 self.panelFolders = self.CreatePanelFolders()
\r
203 self.panelPlaylists = self.CreatePanelPlaylists()
\r
204 self.panelProperties = self.CreatePanelProperties()
\r
205 self.panelNote = self.CreatePanelNote()
\r
206 self.panelOutput = self.CreatePanelOutput()
\r
207 self.panelResults = self.CreatePanelResults()
\r
208 self.plotNotebook = self.CreateNotebook()
\r
211 self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False))
\r
212 self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False))
\r
213 self._mgr.AddPane(self.panelNote, aui.AuiPaneInfo().Name('Note').Caption('Note').Left().CloseButton(True).MaximizeButton(False))
\r
214 self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False))
\r
215 self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False))
\r
216 self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False))
\r
217 self._mgr.AddPane(self.panelAssistant, aui.AuiPaneInfo().Name('Assistant').Caption('Assistant').Right().CloseButton(True).MaximizeButton(False))
\r
218 self._mgr.AddPane(self.panelOutput, aui.AuiPaneInfo().Name('Output').Caption('Output').Bottom().CloseButton(True).MaximizeButton(False))
\r
219 self._mgr.AddPane(self.panelResults, aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().CloseButton(True).MaximizeButton(False))
\r
220 #self._mgr.AddPane(self.textCtrlCommandLine, aui.AuiPaneInfo().Name('CommandLine').CaptionVisible(False).Fixed().Bottom().Layer(2).CloseButton(False).MaximizeButton(False))
\r
221 #self._mgr.AddPane(panelBottom, aui.AuiPaneInfo().Name("panelCommandLine").Bottom().Position(1).CloseButton(False).MaximizeButton(False))
\r
223 # add the toolbars to the manager
\r
224 #self.toolbar=self.CreateToolBar()
\r
225 self.toolbarNavigation=self.CreateToolBarNavigation()
\r
226 #self._mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name('toolbar').Caption('Toolbar').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))
\r
227 self._mgr.AddPane(self.toolbarNavigation, aui.AuiPaneInfo().Name('toolbarNavigation').Caption('Navigation').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))
\r
228 # "commit" all changes made to FrameManager
\r
230 #create the menubar after the panes so that the default perspective
\r
231 #is created with all panes open
\r
232 self.CreateMenuBar()
\r
233 self.statusbar = self.CreateStatusbar()
\r
236 name = self.config['perspectives']['active']
\r
237 menu_item = self.GetPerspectiveMenuItem(name)
\r
238 if menu_item is not None:
\r
239 self.OnRestorePerspective(menu_item)
\r
240 #TODO: config setting to remember playlists from last session
\r
241 self.playlists = self.panelPlaylists.Playlists
\r
242 #define the list of active drivers
\r
244 for driver in self.config['drivers']:
\r
245 if self.config['drivers'][driver]:
\r
246 #get the corresponding filename and path
\r
247 filename = ''.join([driver, '.py'])
\r
248 path = lh.get_file_path(filename, ['drivers'])
\r
249 #the driver is active for driver[1] == 1
\r
250 if os.path.isfile(path):
\r
251 #driver files are located in the 'drivers' subfolder
\r
252 driver_name = ''.join(['drivers.', driver])
\r
253 __import__(driver_name)
\r
254 class_file = getattr(drivers, driver)
\r
255 for command in dir(class_file):
\r
256 if command.endswith('Driver'):
\r
257 self.drivers.append(getattr(class_file, command))
\r
258 #import all active plugins and plotmanips
\r
259 #add 'core.ini' to self.configs (this is not a plugin and thus must be imported separately)
\r
260 ini_path = lh.get_file_path('core.ini', ['plugins'])
\r
261 plugin_config = ConfigObj(ini_path)
\r
262 #self.config.merge(plugin_config)
\r
263 self.configs['core'] = plugin_config
\r
264 #existing_commands contains: {command: plugin}
\r
265 existing_commands = {}
\r
266 #make sure we execute _plug_init() for every command line plugin we import
\r
267 for plugin in self.config['plugins']:
\r
268 if self.config['plugins'][plugin]:
\r
269 filename = ''.join([plugin, '.py'])
\r
270 path = lh.get_file_path(filename, ['plugins'])
\r
271 if os.path.isfile(path):
\r
272 #get the corresponding filename and path
\r
273 plugin_name = ''.join(['plugins.', plugin])
\r
276 module = __import__(plugin_name)
\r
277 #prepare the ini file for inclusion
\r
278 ini_path = path.replace('.py', '.ini')
\r
280 plugin_config = ConfigObj(ini_path)
\r
281 #self.config.merge(plugin_config)
\r
282 self.configs[plugin] = plugin_config
\r
284 commands = eval('dir(module.' + plugin+ '.' + plugin + 'Commands)')
\r
285 #keep only commands (ie names that start with 'do_')
\r
286 commands = [command for command in commands if command.startswith('do_')]
\r
288 for command in commands:
\r
289 if existing_commands.has_key(command):
\r
290 message_str = 'Adding "' + command + '" in plugin "' + plugin + '".\n\n'
\r
291 message_str += '"' + command + '" already exists in "' + str(existing_commands[command]) + '".\n\n'
\r
292 message_str += 'Only "' + command + '" in "' + str(existing_commands[command]) + '" will work.\n\n'
\r
293 message_str += 'Please rename one of the commands in the source code and restart Hooke or disable one of the plugins.'
\r
294 dialog = wx.MessageDialog(self, message_str, 'Warning', wx.OK|wx.ICON_WARNING|wx.CENTER)
\r
297 existing_commands[command] = plugin
\r
298 self.plugins[plugin] = commands
\r
300 #initialize the plugin
\r
301 eval('module.' + plugin+ '.' + plugin + 'Commands._plug_init(self)')
\r
302 except AttributeError:
\r
304 except ImportError:
\r
306 #add commands from hooke.py i.e. 'core' commands
\r
307 commands = dir(HookeFrame)
\r
308 commands = [command for command in commands if command.startswith('do_')]
\r
310 self.plugins['core'] = commands
\r
311 #initialize the commands tree
\r
312 self.panelCommands.Initialize(self.plugins)
\r
313 for command in dir(self):
\r
314 if command.startswith('plotmanip_'):
\r
315 self.plotmanipulators.append(lib.plotmanipulator.Plotmanipulator(method=getattr(self, command), command=command))
\r
317 #load default list, if possible
\r
318 self.do_loadlist(self.config['core']['list'])
\r
320 def _BindEvents(self):
\r
321 #TODO: figure out if we can use the eventManager for menu ranges
\r
322 #and events of 'self' without raising an assertion fail error
\r
323 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
\r
324 self.Bind(wx.EVT_SIZE, self.OnSize)
\r
325 self.Bind(wx.EVT_CLOSE, self.OnClose)
\r
326 # Show How To Use The Closing Panes Event
\r
327 self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)
\r
328 self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnNotebookPageClose)
\r
330 evtmgr.eventManager.Register(self.OnAbout, wx.EVT_MENU, win=self, id=wx.ID_ABOUT)
\r
331 evtmgr.eventManager.Register(self.OnClose, wx.EVT_MENU, win=self, id=wx.ID_EXIT)
\r
333 self.Bind(wx.EVT_MENU_RANGE, self.OnView, id=ID_ViewAssistant, id2=ID_ViewResults)
\r
335 self.Bind(wx.EVT_MENU, self.OnDeletePerspective, id=ID_DeletePerspective)
\r
336 self.Bind(wx.EVT_MENU, self.OnSavePerspective, id=ID_SavePerspective)
\r
337 self.Bind(wx.EVT_MENU_RANGE, self.OnRestorePerspective, id=ID_FirstPerspective, id2=ID_FirstPerspective+1000)
\r
339 evtmgr.eventManager.Register(self.OnNext, wx.EVT_TOOL, win=self, id=ID_Next)
\r
340 evtmgr.eventManager.Register(self.OnPrevious, wx.EVT_TOOL, win=self, id=ID_Previous)
\r
341 #self.Bind(.EVT_AUITOOLBAR_TOOL_DROPDOWN, self.OnDropDownToolbarItem, id=ID_DropDownToolbarItem)
\r
343 treeCtrl = self.panelFolders.GetTreeCtrl()
\r
344 #tree.Bind(wx.EVT_LEFT_UP, self.OnDirCtrl1LeftUp)
\r
345 #tree.Bind(wx.EVT_LEFT_DOWN, self.OnGenericDirCtrl1LeftDown)
\r
346 treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self.OnDirCtrlLeftDclick)
\r
348 self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DOWN, self.OnPlaylistsLeftDown)
\r
349 self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DCLICK, self.OnPlaylistsLeftDclick)
\r
351 evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self.panelCommands.ExecuteButton)
\r
352 evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree)
\r
353 evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self.panelCommands.CommandsTree)
\r
354 evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton)
\r
356 self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)
\r
358 self.panelResults.results_list.OnCheckItem = self.OnResultsCheck
\r
360 def _GetActiveFileIndex(self):
\r
361 lib.playlist.Playlist = self.GetActivePlaylist()
\r
362 #get the selected item from the tree
\r
363 selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()
\r
364 #test if a playlist or a curve was double-clicked
\r
365 if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):
\r
369 selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)
\r
370 while selected_item.IsOk():
\r
372 selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)
\r
375 def _GetPlaylistTab(self, name):
\r
376 for index, page in enumerate(self.plotNotebook._tabs._pages):
\r
377 if page.caption == name:
\r
381 def _GetUniquePlaylistName(self, name):
\r
382 playlist_name = name
\r
384 while playlist_name in self.playlists:
\r
385 playlist_name = ''.join([name, str(count)])
\r
387 return playlist_name
\r
389 def _RestorePerspective(self, name):
\r
390 self._mgr.LoadPerspective(self._perspectives[name])
\r
391 self.config['perspectives']['active'] = name
\r
393 all_panes = self._mgr.GetAllPanes()
\r
394 for pane in all_panes:
\r
395 if not pane.name.startswith('toolbar'):
\r
396 if pane.name == 'Assistant':
\r
397 self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown())
\r
398 if pane.name == 'Folders':
\r
399 self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown())
\r
400 if pane.name == 'Playlists':
\r
401 self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown())
\r
402 if pane.name == 'Commands':
\r
403 self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown())
\r
404 if pane.name == 'Note':
\r
405 self.MenuBar.FindItemById(ID_ViewNote).Check(pane.window.IsShown())
\r
406 if pane.name == 'Properties':
\r
407 self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown())
\r
408 if pane.name == 'Output':
\r
409 self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown())
\r
410 if pane.name == 'Results':
\r
411 self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown())
\r
413 def _SavePerspectiveToFile(self, name, perspective):
\r
414 filename = ''.join([name, '.txt'])
\r
415 filename = lh.get_file_path(filename, ['perspectives'])
\r
416 perspectivesFile = open(filename, 'w')
\r
417 perspectivesFile.write(perspective)
\r
418 perspectivesFile.close()
\r
420 def _UnbindEvents(self):
\r
422 evtmgr.eventManager.DeregisterListener(self.OnAbout)
\r
423 evtmgr.eventManager.DeregisterListener(self.OnClose)
\r
425 evtmgr.eventManager.DeregisterListener(self.OnNext)
\r
426 evtmgr.eventManager.DeregisterListener(self.OnPrevious)
\r
428 evtmgr.eventManager.DeregisterListener(self.OnExecute)
\r
429 evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)
\r
430 evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlItemActivated)
\r
431 evtmgr.eventManager.DeregisterListener(self.OnUpdateNote)
\r
433 def AddPlaylist(self, playlist=None, name='Untitled'):
\r
434 if playlist and playlist.count > 0:
\r
435 playlist.name = self._GetUniquePlaylistName(name)
\r
437 self.AddToPlaylists(playlist)
\r
439 def AddPlaylistFromFiles(self, files=[], name='Untitled'):
\r
441 playlist = lib.playlist.Playlist(self, self.drivers)
\r
443 playlist.add_curve(item)
\r
444 if playlist.count > 0:
\r
445 playlist.name = self._GetUniquePlaylistName(name)
\r
447 self.AddTayliss(playlist)
\r
449 def AddToPlaylists(self, playlist):
\r
450 if playlist.count > 0:
\r
451 #setup the playlist in the Playlist tree
\r
452 tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem()
\r
453 playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0)
\r
454 #add all files to the Playlist tree
\r
456 hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')
\r
457 for index, file_to_add in enumerate(playlist.files):
\r
458 #optionally remove the extension from the name of the curve
\r
459 if hide_curve_extension:
\r
460 file_to_add.name = lh.remove_extension(file_to_add.name)
\r
461 file_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, file_to_add.name, 1)
\r
462 if index == playlist.index:
\r
463 self.panelPlaylists.PlaylistsTree.SelectItem(file_ID)
\r
465 #create the plot tab and add playlist to the dictionary
\r
466 plotPanel = panels.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))
\r
467 notebook_tab = self.plotNotebook.AddPage(plotPanel, playlist.name, True)
\r
468 #tab_index = self.plotNotebook.GetSelection()
\r
469 playlist.figure = plotPanel.get_figure()
\r
470 self.playlists[playlist.name] = playlist
\r
471 #self.playlists[playlist.name] = [playlist, figure]
\r
472 self.panelPlaylists.PlaylistsTree.Expand(playlist_root)
\r
473 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
477 def AppendToOutput(self, text):
\r
478 self.panelOutput.AppendText(''.join([text, '\n']))
\r
480 def AppliesPlotmanipulator(self, name):
\r
482 Returns True if the plotmanipulator 'name' is applied, False otherwise
\r
483 name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')
\r
485 return self.GetBoolFromConfig('core', 'plotmanipulators', name)
\r
487 def ApplyPlotmanipulators(self, plot, plot_file):
\r
489 Apply all active plotmanipulators.
\r
491 if plot is not None and plot_file is not None:
\r
492 manipulated_plot = copy.deepcopy(plot)
\r
493 for plotmanipulator in self.plotmanipulators:
\r
494 if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):
\r
495 manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)
\r
496 return manipulated_plot
\r
498 def CreateApplicationIcon(self):
\r
499 iconFile = 'resources' + os.sep + 'microscope.ico'
\r
500 icon = wx.Icon(iconFile, wx.BITMAP_TYPE_ICO)
\r
503 def CreateCommandLine(self):
\r
504 return wx.TextCtrl(self, -1, '', style=wx.NO_BORDER|wx.EXPAND)
\r
506 def CreatePanelAssistant(self):
\r
507 panel = wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE)
\r
508 panel.SetEditable(False)
\r
511 def CreatePanelCommands(self):
\r
512 return panels.commands.Commands(self)
\r
514 def CreatePanelFolders(self):
\r
516 filters = self.config['folders']['filters']
\r
517 index = self.config['folders'].as_int('filterindex')
\r
518 #set initial directory
\r
519 folder = self.config['core']['workdir']
\r
520 return wx.GenericDirCtrl(self, -1, dir=folder, size=(200, 250), style=wx.DIRCTRL_SHOW_FILTERS, filter=filters, defaultFilter=index)
\r
522 def CreatePanelNote(self):
\r
523 return panels.note.Note(self)
\r
525 def CreatePanelOutput(self):
\r
526 return wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE)
\r
528 def CreatePanelPlaylists(self):
\r
529 return panels.playlist.Playlists(self)
\r
531 def CreatePanelProperties(self):
\r
532 return panels.propertyeditor.PropertyEditor(self)
\r
534 def CreatePanelResults(self):
\r
535 return panels.results.Results(self)
\r
537 def CreatePanelWelcome(self):
\r
538 #TODO: move into panels.welcome
\r
539 ctrl = wx.html.HtmlWindow(self, -1, wx.DefaultPosition, wx.Size(400, 300))
\r
540 introStr = '<h1>Welcome to Hooke</h1>' + \
\r
541 '<h3>Features</h3>' + \
\r
543 '<li>View, annotate, measure force files</li>' + \
\r
544 '<li>Worm-like chain fit of force peaks</li>' + \
\r
545 '<li>Automatic convolution-based filtering of empty files</li>' + \
\r
546 '<li>Automatic fit and measurement of multiple force peaks</li>' + \
\r
547 '<li>Handles force-clamp force experiments (experimental)</li>' + \
\r
548 '<li>It is extensible by users by means of plugins and drivers</li>' + \
\r
550 '<p>See the <a href="http://code.google.com/p/hooke/wiki/DocumentationIndex">DocumentationIndex</a> for more information</p>'
\r
551 ctrl.SetPage(introStr)
\r
554 def CreateMenuBar(self):
\r
555 menu_bar = wx.MenuBar()
\r
556 self.SetMenuBar(menu_bar)
\r
558 file_menu = wx.Menu()
\r
559 file_menu.Append(wx.ID_EXIT, 'Exit\tCtrl-Q')
\r
560 # edit_menu.AppendSeparator();
\r
561 # edit_menu.Append(ID_Config, 'Preferences')
\r
563 view_menu = wx.Menu()
\r
564 view_menu.AppendCheckItem(ID_ViewFolders, 'Folders\tF5')
\r
565 view_menu.AppendCheckItem(ID_ViewPlaylists, 'Playlists\tF6')
\r
566 view_menu.AppendCheckItem(ID_ViewCommands, 'Commands\tF7')
\r
567 view_menu.AppendCheckItem(ID_ViewProperties, 'Properties\tF8')
\r
568 view_menu.AppendCheckItem(ID_ViewAssistant, 'Assistant\tF9')
\r
569 view_menu.AppendCheckItem(ID_ViewResults, 'Results\tF10')
\r
570 view_menu.AppendCheckItem(ID_ViewOutput, 'Output\tF11')
\r
571 view_menu.AppendCheckItem(ID_ViewNote, 'Note\tF12')
\r
573 perspectives_menu = wx.Menu()
\r
576 help_menu = wx.Menu()
\r
577 help_menu.Append(wx.ID_ABOUT, 'About Hooke')
\r
578 #put it all together
\r
579 menu_bar.Append(file_menu, 'File')
\r
580 menu_bar.Append(view_menu, 'View')
\r
581 menu_bar.Append(perspectives_menu, "Perspectives")
\r
582 self.UpdatePerspectivesMenu()
\r
583 menu_bar.Append(help_menu, 'Help')
\r
585 def CreateNotebook(self):
\r
586 # create the notebook off-window to avoid flicker
\r
587 client_size = self.GetClientSize()
\r
588 ctrl = aui.AuiNotebook(self, -1, wx.Point(client_size.x, client_size.y), wx.Size(430, 200), self._notebook_style)
\r
589 arts = [aui.AuiDefaultTabArt, aui.AuiSimpleTabArt, aui.VC71TabArt, aui.FF2TabArt, aui.VC8TabArt, aui.ChromeTabArt]
\r
590 art = arts[self._notebook_theme]()
\r
591 ctrl.SetArtProvider(art)
\r
592 #uncomment if we find a nice icon
\r
593 #page_bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16))
\r
594 ctrl.AddPage(self.CreatePanelWelcome(), "Welcome", False)
\r
597 def CreateStatusbar(self):
\r
598 statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)
\r
599 statusbar.SetStatusWidths([-2, -3])
\r
600 statusbar.SetStatusText('Ready', 0)
\r
601 welcomeString=u'Welcome to Hooke (version '+__version__+', '+__release_name__+')!'
\r
602 statusbar.SetStatusText(welcomeString, 1)
\r
605 def CreateToolBarNavigation(self):
\r
606 toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER)
\r
607 toolbar.SetToolBitmapSize(wx.Size(16,16))
\r
608 toolbar_bmpBack = wx.ArtProvider_GetBitmap(wx.ART_GO_BACK, wx.ART_OTHER, wx.Size(16, 16))
\r
609 toolbar_bmpForward = wx.ArtProvider_GetBitmap(wx.ART_GO_FORWARD, wx.ART_OTHER, wx.Size(16, 16))
\r
610 toolbar.AddLabelTool(ID_Previous, 'Previous', toolbar_bmpBack, shortHelp='Previous curve')
\r
611 toolbar.AddLabelTool(ID_Next, 'Next', toolbar_bmpForward, shortHelp='Next curve')
\r
615 def DeleteFromPlaylists(self, name):
\r
616 if name in self.playlists:
\r
617 del self.playlists[name]
\r
618 tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem()
\r
619 item, cookie = self.panelPlaylists.PlaylistsTree.GetFirstChild(tree_root)
\r
621 playlist_name = self.panelPlaylists.PlaylistsTree.GetItemText(item)
\r
622 if playlist_name == name:
\r
624 self.panelPlaylists.PlaylistsTree.Delete(item)
\r
627 item = self.panelPlaylists.PlaylistsTree.GetNextSibling(item)
\r
629 def GetActiveFigure(self):
\r
630 playlist_name = self.GetActivePlaylistName()
\r
631 figure = self.playlists[playlist_name].figure
\r
632 if figure is not None:
\r
636 def GetActiveFile(self):
\r
637 playlist = self.GetActivePlaylist()
\r
638 if playlist is not None:
\r
639 return playlist.get_active_file()
\r
642 def GetActivePlaylist(self):
\r
643 playlist_name = self.GetActivePlaylistName()
\r
644 if playlist_name in self.playlists:
\r
645 return self.playlists[playlist_name]
\r
648 def GetActivePlaylistName(self):
\r
649 #get the selected item from the tree
\r
650 selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()
\r
651 #test if a playlist or a curve was double-clicked
\r
652 if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):
\r
653 playlist_item = selected_item
\r
655 #get the name of the playlist
\r
656 playlist_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)
\r
657 #now we have a playlist
\r
658 return self.panelPlaylists.PlaylistsTree.GetItemText(playlist_item)
\r
660 def GetActivePlot(self):
\r
661 playlist = self.GetActivePlaylist()
\r
662 if playlist is not None:
\r
663 return playlist.get_active_file().plot
\r
666 def GetDisplayedPlot(self):
\r
667 plot = copy.deepcopy(self.displayed_plot)
\r
669 #plot.curves = copy.deepcopy(plot.curves)
\r
672 def GetDisplayedPlotCorrected(self):
\r
673 plot = copy.deepcopy(self.displayed_plot)
\r
675 plot.curves = copy.deepcopy(plot.corrected_curves)
\r
678 def GetDisplayedPlotRaw(self):
\r
679 plot = copy.deepcopy(self.displayed_plot)
\r
681 plot.curves = copy.deepcopy(plot.raw_curves)
\r
684 def GetDockArt(self):
\r
685 return self._mgr.GetArtProvider()
\r
687 def GetBoolFromConfig(self, *args):
\r
692 elif len(args) == 3:
\r
696 if self.configs.has_key(plugin):
\r
697 config = self.configs[plugin]
\r
698 return config[section][key].as_bool('value')
\r
701 def GetColorFromConfig(self, *args):
\r
706 elif len(args) == 3:
\r
710 if self.configs.has_key(plugin):
\r
711 config = self.configs[plugin]
\r
712 color_tuple = eval(config[section][key]['value'])
\r
713 color = [value / 255.0 for value in color_tuple]
\r
717 def GetFloatFromConfig(self, *args):
\r
722 elif len(args) == 3:
\r
726 if self.configs.has_key(plugin):
\r
727 config = self.configs[plugin]
\r
728 return config[section][key].as_float('value')
\r
731 def GetIntFromConfig(self, *args):
\r
736 elif len(args) == 3:
\r
740 if self.configs.has_key(plugin):
\r
741 config = self.configs[plugin]
\r
742 return config[section][key].as_int('value')
\r
745 def GetStringFromConfig(self, *args):
\r
750 elif len(args) == 3:
\r
754 if self.configs.has_key(plugin):
\r
755 config = self.configs[plugin]
\r
756 return config[section][key]['value']
\r
759 def GetPlotmanipulator(self, name):
\r
761 Returns a plot manipulator function from its name
\r
763 for plotmanipulator in self.plotmanipulators:
\r
764 if plotmanipulator.name == name:
\r
765 return plotmanipulator
\r
768 def GetPerspectiveMenuItem(self, name):
\r
769 if self._perspectives.has_key(name):
\r
770 perspectives_list = [key for key, value in self._perspectives.iteritems()]
\r
771 perspectives_list.sort()
\r
772 index = perspectives_list.index(name)
\r
773 perspective_Id = ID_FirstPerspective + index
\r
774 menu_item = self.MenuBar.FindItemById(perspective_Id)
\r
779 def HasPlotmanipulator(self, name):
\r
781 returns True if the plotmanipulator 'name' is loaded, False otherwise
\r
783 for plotmanipulator in self.plotmanipulators:
\r
784 if plotmanipulator.command == name:
\r
788 def OnAbout(self, event):
\r
789 message = 'Hooke\n\n'+\
\r
790 'A free, open source data analysis platform\n\n'+\
\r
791 'Copyright 2006-2008 by Massimo Sandal\n'+\
\r
792 'Copyright 2010 by Dr. Rolf Schmidt\n\n'+\
\r
793 'Hooke is released under the GNU General Public License version 2.'
\r
794 dialog = wx.MessageDialog(self, message, 'About Hooke', wx.OK | wx.ICON_INFORMATION)
\r
798 def OnClose(self, event):
\r
800 self.config['main']['height'] = str(self.GetSize().GetHeight())
\r
801 self.config['main']['left'] = str(self.GetPosition()[0])
\r
802 self.config['main']['top'] = str(self.GetPosition()[1])
\r
803 self.config['main']['width'] = str(self.GetSize().GetWidth())
\r
804 #save the configuration file to 'config/hooke.ini'
\r
805 self.config.write()
\r
806 #save all plugin config files
\r
807 for config in self.configs:
\r
808 plugin_config = self.configs[config]
\r
809 plugin_config.write()
\r
810 self._UnbindEvents()
\r
815 def OnDeletePerspective(self, event):
\r
816 dialog = panels.perspectives.Perspectives(self, -1, 'Delete perspective(s)')
\r
817 dialog.CenterOnScreen()
\r
820 self.UpdatePerspectivesMenu()
\r
821 #unfortunately, there is a bug in wxWidgets (Ticket #3258) that
\r
822 #makes the radio item indicator in the menu disappear
\r
823 #the code should be fine once this issue is fixed
\r
825 def OnDirCtrlLeftDclick(self, event):
\r
826 file_path = self.panelFolders.GetPath()
\r
827 if os.path.isfile(file_path):
\r
828 if file_path.endswith('.hkp'):
\r
829 self.do_loadlist(file_path)
\r
832 def OnEraseBackground(self, event):
\r
835 def OnExecute(self, event):
\r
836 item = self.panelCommands.CommandsTree.GetSelection()
\r
838 if not self.panelCommands.CommandsTree.ItemHasChildren(item):
\r
839 item_text = self.panelCommands.CommandsTree.GetItemText(item)
\r
840 command = ''.join(['self.do_', item_text, '()'])
\r
841 #self.AppendToOutput(command + '\n')
\r
844 def OnExit(self, event):
\r
847 def OnNext(self, event):
\r
850 Go to the next curve in the playlist.
\r
851 If we are at the last curve, we come back to the first.
\r
855 selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()
\r
856 if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):
\r
857 #GetFirstChild returns a tuple
\r
858 #we only need the first element
\r
859 next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(selected_item)[0]
\r
861 next_item = self.panelPlaylists.PlaylistsTree.GetNextSibling(selected_item)
\r
862 if not next_item.IsOk():
\r
863 parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)
\r
864 #GetFirstChild returns a tuple
\r
865 #we only need the first element
\r
866 next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(parent_item)[0]
\r
867 self.panelPlaylists.PlaylistsTree.SelectItem(next_item, True)
\r
868 if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):
\r
869 playlist = self.GetActivePlaylist()
\r
870 if playlist.count > 1:
\r
872 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
876 def OnNotebookPageClose(self, event):
\r
877 ctrl = event.GetEventObject()
\r
878 playlist_name = ctrl.GetPageText(ctrl._curpage)
\r
879 self.DeleteFromPlaylists(playlist_name)
\r
881 def OnPaneClose(self, event):
\r
884 def OnPlaylistsLeftDclick(self, event):
\r
885 if self.panelPlaylists.PlaylistsTree.Count > 0:
\r
886 playlist_name = self.GetActivePlaylistName()
\r
887 #if that playlist already exists
\r
888 #we check if it is the active playlist (ie selected in panelPlaylists)
\r
889 #and switch to it if necessary
\r
890 if playlist_name in self.playlists:
\r
891 index = self.plotNotebook.GetSelection()
\r
892 current_playlist = self.plotNotebook.GetPageText(index)
\r
893 if current_playlist != playlist_name:
\r
894 index = self._GetPlaylistTab(playlist_name)
\r
895 self.plotNotebook.SetSelection(index)
\r
896 #if a curve was double-clicked
\r
897 item = self.panelPlaylists.PlaylistsTree.GetSelection()
\r
898 if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item):
\r
899 index = self._GetActiveFileIndex()
\r
903 playlist = self.GetActivePlaylist()
\r
904 playlist.index = index
\r
905 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
908 #if you uncomment the following line, the tree will collapse/expand as well
\r
911 def OnPlaylistsLeftDown(self, event):
\r
912 hit_item, hit_flags = self.panelPlaylists.PlaylistsTree.HitTest(event.GetPosition())
\r
913 if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:
\r
914 self.panelPlaylists.PlaylistsTree.SelectItem(hit_item)
\r
915 playlist_name = self.GetActivePlaylistName()
\r
916 playlist = self.GetActivePlaylist()
\r
917 #if a curve was clicked
\r
918 item = self.panelPlaylists.PlaylistsTree.GetSelection()
\r
919 if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item):
\r
920 index = self._GetActiveFileIndex()
\r
922 playlist.index = index
\r
923 self.playlists[playlist_name] = playlist
\r
926 def OnPrevious(self, event):
\r
929 Go to the previous curve in the playlist.
\r
930 If we are at the first curve, we jump to the last.
\r
932 Syntax: previous, p
\r
934 #playlist = self.playlists[self.GetActivePlaylistName()][0]
\r
935 #select the previous curve and tell the user if we wrapped around
\r
936 #self.AppendToOutput(playlist.previous())
\r
937 selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()
\r
938 if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):
\r
939 previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(selected_item)
\r
941 previous_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)
\r
942 if not previous_item.IsOk():
\r
943 parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)
\r
944 previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(parent_item)
\r
945 self.panelPlaylists.PlaylistsTree.SelectItem(previous_item, True)
\r
946 playlist = self.GetActivePlaylist()
\r
947 if playlist.count > 1:
\r
948 playlist.previous()
\r
949 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
953 def OnPropGridChanged (self, event):
\r
954 prop = event.GetProperty()
\r
956 item_section = self.panelProperties.SelectedTreeItem
\r
957 item_plugin = self.panelCommands.CommandsTree.GetItemParent(item_section)
\r
958 plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin)
\r
959 config = self.configs[plugin]
\r
960 property_section = self.panelCommands.CommandsTree.GetItemText(item_section)
\r
961 property_key = prop.GetName()
\r
962 property_value = prop.GetDisplayedString()
\r
964 config[property_section][property_key]['value'] = property_value
\r
966 def OnRestorePerspective(self, event):
\r
967 name = self.MenuBar.FindItemById(event.GetId()).GetLabel()
\r
968 self._RestorePerspective(name)
\r
970 def OnResultsCheck(self, index, flag):
\r
971 results = self.GetActivePlot().results
\r
972 if results.has_key(self.results_str):
\r
973 results[self.results_str].results[index].visible = flag
\r
974 results[self.results_str].update()
\r
977 def OnSavePerspective(self, event):
\r
979 def nameExists(name):
\r
980 menu_position = self.MenuBar.FindMenu('Perspectives')
\r
981 menu = self.MenuBar.GetMenu(menu_position)
\r
982 for item in menu.GetMenuItems():
\r
983 if item.GetText() == name:
\r
989 dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective')
\r
990 dialog.SetValue('New perspective')
\r
991 if dialog.ShowModal() != wx.ID_OK:
\r
994 name = dialog.GetValue()
\r
996 if nameExists(name):
\r
997 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)
\r
998 if dialogConfirm.ShowModal() == wx.ID_YES:
\r
1003 perspective = self._mgr.SavePerspective()
\r
1004 self._SavePerspectiveToFile(name, perspective)
\r
1005 self.config['perspectives']['active'] = name
\r
1006 self.UpdatePerspectivesMenu()
\r
1007 # if nameExists(name):
\r
1008 # #check the corresponding menu item
\r
1009 # menu_item = self.GetPerspectiveMenuItem(name)
\r
1010 # #replace the perspectiveStr in _pespectives
\r
1011 # self._perspectives[name] = perspective
\r
1013 # #because we deal with radio items, we need to do some extra work
\r
1014 # #delete all menu items from the perspectives menu
\r
1015 # for item in self._perspectives_menu.GetMenuItems():
\r
1016 # self._perspectives_menu.DeleteItem(item)
\r
1017 # #recreate the perspectives menu
\r
1018 # self._perspectives_menu.Append(ID_SavePerspective, 'Save Perspective')
\r
1019 # self._perspectives_menu.Append(ID_DeletePerspective, 'Delete Perspective')
\r
1020 # self._perspectives_menu.AppendSeparator()
\r
1021 # #convert the perspectives dictionary into a list
\r
1022 # # the list contains:
\r
1023 # #[0]: name of the perspective
\r
1024 # #[1]: perspective
\r
1025 # perspectives_list = [key for key, value in self._perspectives.iteritems()]
\r
1026 # perspectives_list.append(name)
\r
1027 # perspectives_list.sort()
\r
1028 # #add all previous perspectives
\r
1029 # for index, item in enumerate(perspectives_list):
\r
1030 # menu_item = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + index, item)
\r
1031 # if item == name:
\r
1032 # menu_item.Check()
\r
1033 # #add the new perspective to _perspectives
\r
1034 # self._perspectives[name] = perspective
\r
1036 def OnSize(self, event):
\r
1039 def OnTreeCtrlCommandsSelectionChanged(self, event):
\r
1040 selected_item = event.GetItem()
\r
1041 if selected_item is not None:
\r
1044 #deregister/register the listener to avoid infinite loop
\r
1045 evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)
\r
1046 self.panelCommands.CommandsTree.SelectItem(selected_item)
\r
1047 evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree)
\r
1048 self.panelProperties.SelectedTreeItem = selected_item
\r
1049 #if a command was clicked
\r
1051 if not self.panelCommands.CommandsTree.ItemHasChildren(selected_item):
\r
1052 item_plugin = self.panelCommands.CommandsTree.GetItemParent(selected_item)
\r
1053 plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin)
\r
1054 if self.configs.has_key(plugin):
\r
1055 #config = self.panelCommands.CommandsTree.GetPyData(item_plugin)
\r
1056 config = self.configs[plugin]
\r
1057 section = self.panelCommands.CommandsTree.GetItemText(selected_item)
\r
1058 #display docstring in help window
\r
1059 doc_string = eval('self.do_' + section + '.__doc__')
\r
1060 if section in config:
\r
1061 for option in config[section]:
\r
1062 properties.append([option, config[section][option]])
\r
1064 plugin = self.panelCommands.CommandsTree.GetItemText(selected_item)
\r
1065 if plugin != 'core':
\r
1066 doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')
\r
1068 doc_string = 'The module "core" contains Hooke core functionality'
\r
1069 if doc_string is not None:
\r
1070 self.panelAssistant.ChangeValue(doc_string)
\r
1072 self.panelAssistant.ChangeValue('')
\r
1073 panels.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)
\r
1074 #save the currently selected command/plugin to the config file
\r
1075 self.config['command']['command'] = section
\r
1076 self.config['command']['plugin'] = plugin
\r
1078 def OnTreeCtrlItemActivated(self, event):
\r
1079 self.OnExecute(event)
\r
1081 def OnUpdateNote(self, event):
\r
1083 Saves the note to the active file.
\r
1085 active_file = self.GetActiveFile()
\r
1086 active_file.note = self.panelNote.Editor.GetValue()
\r
1088 def OnView(self, event):
\r
1089 menu_id = event.GetId()
\r
1090 menu_item = self.MenuBar.FindItemById(menu_id)
\r
1091 menu_label = menu_item.GetLabel()
\r
1093 pane = self._mgr.GetPane(menu_label)
\r
1094 pane.Show(not pane.IsShown())
\r
1095 #if we don't do the following, the Folders pane does not resize properly on hide/show
\r
1096 if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():
\r
1097 #folders_size = pane.GetSize()
\r
1098 self.panelFolders.Fit()
\r
1099 self._mgr.Update()
\r
1101 def _clickize(self, xvector, yvector, index):
\r
1103 Returns a ClickedPoint() object from an index and vectors of x, y coordinates
\r
1105 point = lib.clickedpoint.ClickedPoint()
\r
1106 point.index = index
\r
1107 point.absolute_coords = xvector[index], yvector[index]
\r
1108 point.find_graph_coords(xvector, yvector)
\r
1111 def _delta(self, message='Click 2 points', whatset=lh.RETRACTION):
\r
1113 Calculates the difference between two clicked points
\r
1115 clicked_points = self._measure_N_points(N=2, message=message, whatset=whatset)
\r
1117 plot = self.GetDisplayedPlotCorrected()
\r
1118 curve = plot.curves[whatset]
\r
1120 delta = lib.delta.Delta()
\r
1121 delta.point1.x = clicked_points[0].graph_coords[0]
\r
1122 delta.point1.y = clicked_points[0].graph_coords[1]
\r
1123 delta.point2.x = clicked_points[1].graph_coords[0]
\r
1124 delta.point2.y = clicked_points[1].graph_coords[1]
\r
1125 delta.units.x = curve.units.x
\r
1126 delta.units.y = curve.units.y
\r
1130 def _measure_N_points(self, N, message='', whatset=lh.RETRACTION):
\r
1132 General helper function for N-points measurements
\r
1133 By default, measurements are done on the retraction
\r
1136 dialog = wx.MessageDialog(None, message, 'Info', wx.OK)
\r
1137 dialog.ShowModal()
\r
1139 figure = self.GetActiveFigure()
\r
1141 xvector = self.displayed_plot.curves[whatset].x
\r
1142 yvector = self.displayed_plot.curves[whatset].y
\r
1144 clicked_points = figure.ginput(N, timeout=-1, show_clicks=True)
\r
1147 for clicked_point in clicked_points:
\r
1148 point = lib.clickedpoint.ClickedPoint()
\r
1149 point.absolute_coords = clicked_point[0], clicked_point[1]
\r
1151 #TODO: make this optional?
\r
1152 #so far, the clicked point is taken, not the corresponding data point
\r
1153 point.find_graph_coords(xvector, yvector)
\r
1154 point.is_line_edge = True
\r
1155 point.is_marker = True
\r
1156 points.append(point)
\r
1159 def do_copylog(self):
\r
1161 Copies all files in the current playlist that have a note to the destination folder.
\r
1162 destination: select folder where you want the files to be copied
\r
1163 use_LVDT_folder: when checked, the files will be copied to a folder called 'LVDT' in the destination folder (for MFP-1D files only)
\r
1165 playlist = self.GetActivePlaylist()
\r
1166 if playlist is not None:
\r
1167 destination = self.GetStringFromConfig('core', 'copylog', 'destination')
\r
1168 if not os.path.isdir(destination):
\r
1169 os.makedirs(destination)
\r
1170 for current_file in playlist.files:
\r
1171 if current_file.note:
\r
1172 shutil.copy(current_file.filename, destination)
\r
1173 if current_file.driver.filetype == 'mfp1d':
\r
1174 filename = current_file.filename.replace('deflection', 'LVDT', 1)
\r
1175 path, name = os.path.split(filename)
\r
1176 filename = os.path.join(path, 'lvdt', name)
\r
1177 use_LVDT_folder = self.GetBoolFromConfig('core', 'copylog', 'use_LVDT_folder')
\r
1178 if use_LVDT_folder:
\r
1179 destination = os.path.join(destination, 'LVDT')
\r
1180 shutil.copy(filename, destination)
\r
1182 def do_plotmanipulators(self):
\r
1184 Please select the plotmanipulators you would like to use
\r
1185 and define the order in which they will be applied to the data.
\r
1187 Click 'Execute' to apply your changes.
\r
1191 def do_preferences(self):
\r
1193 Please set general preferences for Hooke here.
\r
1194 hide_curve_extension: hides the extension of the force curve files.
\r
1195 not recommended for 'picoforce' files
\r
1199 def do_test(self):
\r
1201 Use this command for testing purposes. You find do_test in hooke.py.
\r
1205 def do_version(self):
\r
1209 Prints the current version and codename, plus library version. Useful for debugging.
\r
1211 self.AppendToOutput('Hooke ' + __version__ + ' (' + __codename__ + ')')
\r
1212 self.AppendToOutput('Released on: ' + __releasedate__)
\r
1213 self.AppendToOutput('---')
\r
1214 self.AppendToOutput('Python version: ' + python_version)
\r
1215 self.AppendToOutput('WxPython version: ' + wx_version)
\r
1216 self.AppendToOutput('Matplotlib version: ' + mpl_version)
\r
1217 self.AppendToOutput('SciPy version: ' + scipy_version)
\r
1218 self.AppendToOutput('NumPy version: ' + numpy_version)
\r
1219 self.AppendToOutput('---')
\r
1220 self.AppendToOutput('Platform: ' + str(platform.uname()))
\r
1221 self.AppendToOutput('******************************')
\r
1222 self.AppendToOutput('Loaded plugins')
\r
1223 self.AppendToOutput('---')
\r
1225 #sort the plugins into alphabetical order
\r
1226 plugins_list = [key for key, value in self.plugins.iteritems()]
\r
1227 plugins_list.sort()
\r
1228 for plugin in plugins_list:
\r
1229 self.AppendToOutput(plugin)
\r
1231 def UpdateNote(self):
\r
1232 #update the note for the active file
\r
1233 active_file = self.GetActiveFile()
\r
1234 if active_file is not None:
\r
1235 self.panelNote.Editor.SetValue(active_file.note)
\r
1237 def UpdatePerspectivesMenu(self):
\r
1238 #add perspectives to menubar and _perspectives
\r
1239 perspectivesDirectory = os.path.join(lh.hookeDir, 'perspectives')
\r
1240 self._perspectives = {}
\r
1241 if os.path.isdir(perspectivesDirectory):
\r
1242 perspectiveFileNames = os.listdir(perspectivesDirectory)
\r
1243 for perspectiveFilename in perspectiveFileNames:
\r
1244 filename = lh.get_file_path(perspectiveFilename, ['perspectives'])
\r
1245 if os.path.isfile(filename):
\r
1246 perspectiveFile = open(filename, 'rU')
\r
1247 perspective = perspectiveFile.readline()
\r
1248 perspectiveFile.close()
\r
1250 name, extension = os.path.splitext(perspectiveFilename)
\r
1251 if extension == '.txt':
\r
1252 self._perspectives[name] = perspective
\r
1254 #in case there are no perspectives
\r
1255 if not self._perspectives:
\r
1256 perspective = self._mgr.SavePerspective()
\r
1257 self._perspectives['Default'] = perspective
\r
1258 self._SavePerspectiveToFile('Default', perspective)
\r
1260 selected_perspective = self.config['perspectives']['active']
\r
1261 if not self._perspectives.has_key(selected_perspective):
\r
1262 self.config['perspectives']['active'] = 'Default'
\r
1263 selected_perspective = 'Default'
\r
1265 perspectives_list = [key for key, value in self._perspectives.iteritems()]
\r
1266 perspectives_list.sort()
\r
1268 #get the Perspectives menu
\r
1269 menu_position = self.MenuBar.FindMenu('Perspectives')
\r
1270 menu = self.MenuBar.GetMenu(menu_position)
\r
1271 #delete all menu items
\r
1272 for item in menu.GetMenuItems():
\r
1273 menu.DeleteItem(item)
\r
1274 #rebuild the menu by adding the standard menu items
\r
1275 menu.Append(ID_SavePerspective, 'Save Perspective')
\r
1276 menu.Append(ID_DeletePerspective, 'Delete Perspective')
\r
1277 menu.AppendSeparator()
\r
1278 #add all previous perspectives
\r
1279 for index, label in enumerate(perspectives_list):
\r
1280 menu_item = menu.AppendRadioItem(ID_FirstPerspective + index, label)
\r
1281 if label == selected_perspective:
\r
1282 self._RestorePerspective(label)
\r
1283 menu_item.Check(True)
\r
1285 def UpdatePlaylistsTreeSelection(self):
\r
1286 playlist = self.GetActivePlaylist()
\r
1287 if playlist is not None:
\r
1288 if playlist.index >= 0:
\r
1289 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
1293 def UpdatePlot(self, plot=None):
\r
1295 def add_to_plot(curve):
\r
1296 if curve.visible and curve.x and curve.y:
\r
1297 #get the index of the subplot to use as destination
\r
1298 destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1
\r
1299 #set all parameters for the plot
\r
1300 axes_list[destination].set_title(curve.title)
\r
1301 axes_list[destination].set_xlabel(curve.multiplier.x + curve.units.x)
\r
1302 axes_list[destination].set_ylabel(curve.multiplier.y + curve.units.y)
\r
1303 #set the formatting details for the scale
\r
1304 formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.multiplier.x, use_zero)
\r
1305 formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.multiplier.y, use_zero)
\r
1306 axes_list[destination].xaxis.set_major_formatter(formatter_x)
\r
1307 axes_list[destination].yaxis.set_major_formatter(formatter_y)
\r
1308 if curve.style == 'plot':
\r
1309 axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, lw=curve.linewidth, zorder=1)
\r
1310 if curve.style == 'scatter':
\r
1311 axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2)
\r
1312 #add the legend if necessary
\r
1314 axes_list[destination].legend()
\r
1317 active_file = self.GetActiveFile()
\r
1318 if not active_file.driver:
\r
1319 #the first time we identify a file, the following need to be set
\r
1320 active_file.identify(self.drivers)
\r
1321 for curve in active_file.plot.curves:
\r
1322 curve.decimals.x = self.GetIntFromConfig('core', 'preferences', 'x_decimals')
\r
1323 curve.decimals.y = self.GetIntFromConfig('core', 'preferences', 'y_decimals')
\r
1324 curve.legend = self.GetBoolFromConfig('core', 'preferences', 'legend')
\r
1325 curve.multiplier.x = self.GetStringFromConfig('core', 'preferences', 'x_multiplier')
\r
1326 curve.multiplier.y = self.GetStringFromConfig('core', 'preferences', 'y_multiplier')
\r
1327 if active_file.driver is None:
\r
1328 self.AppendToOutput('Invalid file: ' + active_file.filename)
\r
1330 self.displayed_plot = copy.deepcopy(active_file.plot)
\r
1331 #add raw curves to plot
\r
1332 self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves)
\r
1333 #apply all active plotmanipulators
\r
1334 self.displayed_plot = self.ApplyPlotmanipulators(self.displayed_plot, active_file)
\r
1335 #add corrected curves to plot
\r
1336 self.displayed_plot.corrected_curves = copy.deepcopy(self.displayed_plot.curves)
\r
1338 active_file = None
\r
1339 self.displayed_plot = copy.deepcopy(plot)
\r
1341 figure = self.GetActiveFigure()
\r
1343 #use '0' instead of e.g. '0.00' for scales
\r
1344 use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero')
\r
1345 #optionally remove the extension from the title of the plot
\r
1346 hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')
\r
1347 if hide_curve_extension:
\r
1348 title = lh.remove_extension(self.displayed_plot.title)
\r
1350 title = self.displayed_plot.title
\r
1351 figure.suptitle(title, fontsize=14)
\r
1352 #create the list of all axes necessary (rows and columns)
\r
1354 number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves])
\r
1355 number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves])
\r
1356 for index in range(number_of_rows * number_of_columns):
\r
1357 axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1))
\r
1358 #add all curves to the corresponding plots
\r
1359 for curve in self.displayed_plot.curves:
\r
1360 add_to_plot(curve)
\r
1362 #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot'
\r
1363 figure.subplots_adjust(hspace=0.3)
\r
1366 self.panelResults.ClearResults()
\r
1367 if self.displayed_plot.results.has_key(self.results_str):
\r
1368 for curve in self.displayed_plot.results[self.results_str].results:
\r
1369 add_to_plot(curve)
\r
1370 self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str])
\r
1372 self.panelResults.ClearResults()
\r
1374 figure.canvas.draw()
\r
1376 if __name__ == '__main__':
\r
1378 ## now, silence a deprecation warning for py2.3
\r
1380 warnings.filterwarnings("ignore", "integer", DeprecationWarning, "wxPython.gdi")
\r
1386 app = Hooke(redirect=redirect)
\r