Hack merge illysam's hooke.py into hooke/ui/gui/__init__.py
[hooke.git] / hooke / ui / gui / __init__.py
1 # Copyright\r
2 \r
3 """Defines :class:`GUI` providing a wxWindows interface to Hooke.\r
4 """\r
5 \r
6 import lib.libhooke as lh\r
7 import wxversion\r
8 wxversion.select(lh.WX_GOOD)\r
9 \r
10 from configobj import ConfigObj\r
11 import copy\r
12 import os.path\r
13 import platform\r
14 import shutil\r
15 import time\r
16 \r
17 import wx.html\r
18 import wx.lib.agw.aui as aui\r
19 import wx.lib.evtmgr as evtmgr\r
20 import wx.propgrid as wxpg\r
21 \r
22 from matplotlib.ticker import FuncFormatter\r
23 \r
24 from configobj import __version__ as configobj_version\r
25 from matplotlib import __version__ as mpl_version\r
26 from numpy import __version__ as numpy_version\r
27 from scipy import __version__ as scipy_version\r
28 from sys import version as python_version\r
29 from wx import __version__ as wx_version\r
30 from wx.propgrid import PROPGRID_MAJOR\r
31 from wx.propgrid import PROPGRID_MINOR\r
32 from wx.propgrid import PROPGRID_RELEASE\r
33 \r
34 try:\r
35     from agw import cubecolourdialog as CCD\r
36 except ImportError: # if it's not there locally, try the wxPython lib.\r
37     import wx.lib.agw.cubecolourdialog as CCD\r
38 \r
39 #set the Hooke directory\r
40 lh.hookeDir = os.path.abspath(os.path.dirname(__file__))\r
41 from config.config import config\r
42 import drivers\r
43 import lib.clickedpoint\r
44 import lib.curve\r
45 import lib.delta\r
46 import lib.playlist\r
47 import lib.plotmanipulator\r
48 import lib.prettyformat\r
49 import panels.commands\r
50 import panels.note\r
51 import panels.perspectives\r
52 import panels.playlist\r
53 import panels.plot\r
54 import panels.propertyeditor\r
55 import panels.results\r
56 import plugins\r
57 \r
58 global __version__\r
59 global __codename__\r
60 global __releasedate__\r
61 __version__ = lh.HOOKE_VERSION[0]\r
62 __codename__ = lh.HOOKE_VERSION[1]\r
63 __releasedate__ = lh.HOOKE_VERSION[2]\r
64 __release_name__ = lh.HOOKE_VERSION[1]\r
65 \r
66 ID_About = wx.NewId()\r
67 ID_Next = wx.NewId()\r
68 ID_Previous = wx.NewId()\r
69 \r
70 ID_ViewAssistant = wx.NewId()\r
71 ID_ViewCommands = wx.NewId()\r
72 ID_ViewFolders = wx.NewId()\r
73 ID_ViewNote = wx.NewId()\r
74 ID_ViewOutput = wx.NewId()\r
75 ID_ViewPlaylists = wx.NewId()\r
76 ID_ViewProperties = wx.NewId()\r
77 ID_ViewResults = wx.NewId()\r
78 \r
79 ID_DeletePerspective = wx.NewId()\r
80 ID_SavePerspective = wx.NewId()\r
81 \r
82 ID_FirstPerspective = ID_SavePerspective + 1000\r
83 #I hope we'll never have more than 1000 perspectives\r
84 ID_FirstPlot = ID_SavePerspective + 2000\r
85 \r
86 class Hooke(wx.App):\r
87 \r
88     def OnInit(self):\r
89         self.SetAppName('Hooke')\r
90         self.SetVendorName('')\r
91 \r
92         window_height = config['main']['height']\r
93         window_left= config['main']['left']\r
94         window_top = config['main']['top']\r
95         window_width = config['main']['width']\r
96 \r
97         #sometimes, the ini file gets confused and sets 'left'\r
98         #and 'top' to large negative numbers\r
99         #let's catch and fix this\r
100         #keep small negative numbers, the user might want those\r
101         if window_left < -window_width:\r
102             window_left = 0\r
103         if window_top < -window_height:\r
104             window_top = 0\r
105         window_position = (window_left, window_top)\r
106         window_size = (window_width, window_height)\r
107 \r
108         #setup the splashscreen\r
109         if config['splashscreen']['show']:\r
110             filename = lh.get_file_path('hooke.jpg', ['resources'])\r
111             if os.path.isfile(filename):\r
112                 bitmap = wx.Image(filename).ConvertToBitmap()\r
113                 splashStyle = wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT\r
114                 splashDuration = config['splashscreen']['duration']\r
115                 wx.SplashScreen(bitmap, splashStyle, splashDuration, None, -1)\r
116                 wx.Yield()\r
117                 '''\r
118                 we need for the splash screen to disappear\r
119                 for whatever reason splashDuration and sleep do not correspond to each other\r
120                 at least not on Windows\r
121                 maybe it's because duration is in milliseconds and sleep in seconds\r
122                 thus we need to increase the sleep time a bit\r
123                 a factor of 1.2 seems to work quite well\r
124                 '''\r
125                 sleepFactor = 1.2\r
126                 time.sleep(sleepFactor * splashDuration / 1000)\r
127 \r
128         plugin_objects = []\r
129         for plugin in config['plugins']:\r
130             if config['plugins'][plugin]:\r
131                 filename = ''.join([plugin, '.py'])\r
132                 path = lh.get_file_path(filename, ['plugins'])\r
133                 if os.path.isfile(path):\r
134                     #get the corresponding filename and path\r
135                     plugin_name = ''.join(['plugins.', plugin])\r
136                     #import the module\r
137                     __import__(plugin_name)\r
138                     #get the file that contains the plugin\r
139                     class_file = getattr(plugins, plugin)\r
140                     #get the class that contains the commands\r
141                     class_object = getattr(class_file, plugin + 'Commands')\r
142                     plugin_objects.append(class_object)\r
143 \r
144         def make_command_class(*bases):\r
145             #create metaclass with plugins and plotmanipulators\r
146             return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {})\r
147         frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=window_position, size=window_size)\r
148         frame.Show(True)\r
149         self.SetTopWindow(frame)\r
150 \r
151         return True\r
152 \r
153     def OnExit(self):\r
154         return True\r
155 \r
156 \r
157 class HookeFrame(wx.Frame):\r
158 \r
159     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
160         #call parent constructor\r
161         wx.Frame.__init__(self, parent, id, title, pos, size, style)\r
162         self.config = config\r
163         self.CreateApplicationIcon()\r
164         #self.configs contains: {the name of the Commands file: corresponding ConfigObj}\r
165         self.configs = {}\r
166         #self.displayed_plot holds the currently displayed plot\r
167         self.displayed_plot = None\r
168         #self.playlists contains: {the name of the playlist: [playlist, tabIndex, plotID]}\r
169         self.playlists = {}\r
170         #list of all plotmanipulators\r
171         self.plotmanipulators = []\r
172         #self.plugins contains: {the name of the plugin: [caption, function]}\r
173         self.plugins = {}\r
174         #self.results_str contains the type of results we want to display\r
175         self.results_str = 'wlc'\r
176 \r
177         #tell FrameManager to manage this frame\r
178         self._mgr = aui.AuiManager()\r
179         self._mgr.SetManagedWindow(self)\r
180         #set the gradient style\r
181         self._mgr.GetArtProvider().SetMetric(aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE)\r
182         #set transparent drag\r
183         self._mgr.SetFlags(self._mgr.GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG)\r
184 \r
185         # set up default notebook style\r
186         self._notebook_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER\r
187         self._notebook_theme = 0\r
188 \r
189         #holds the perspectives: {name, perspective_str}\r
190         self._perspectives = {}\r
191 \r
192         # min size for the frame itself isn't completely done.\r
193         # see the end up FrameManager::Update() for the test\r
194         # code. For now, just hard code a frame minimum size\r
195         self.SetMinSize(wx.Size(500, 500))\r
196         #define the list of active drivers\r
197         self.drivers = []\r
198         for driver in self.config['drivers']:\r
199             if self.config['drivers'][driver]:\r
200                 #get the corresponding filename and path\r
201                 filename = ''.join([driver, '.py'])\r
202                 path = lh.get_file_path(filename, ['drivers'])\r
203                 #the driver is active for driver[1] == 1\r
204                 if os.path.isfile(path):\r
205                     #driver files are located in the 'drivers' subfolder\r
206                     driver_name = ''.join(['drivers.', driver])\r
207                     __import__(driver_name)\r
208                     class_file = getattr(drivers, driver)\r
209                     for command in dir(class_file):\r
210                         if command.endswith('Driver'):\r
211                             self.drivers.append(getattr(class_file, command))\r
212         #import all active plugins and plotmanips\r
213         #add 'core.ini' to self.configs (this is not a plugin and thus must be imported separately)\r
214         ini_path = lh.get_file_path('core.ini', ['plugins'])\r
215         plugin_config = ConfigObj(ini_path)\r
216         #self.config.merge(plugin_config)\r
217         self.configs['core'] = plugin_config\r
218         #existing_commands contains: {command: plugin}\r
219         existing_commands = {}\r
220         #make sure we execute _plug_init() for every command line plugin we import\r
221         for plugin in self.config['plugins']:\r
222             if self.config['plugins'][plugin]:\r
223                 filename = ''.join([plugin, '.py'])\r
224                 path = lh.get_file_path(filename, ['plugins'])\r
225                 if os.path.isfile(path):\r
226                     #get the corresponding filename and path\r
227                     plugin_name = ''.join(['plugins.', plugin])\r
228                     try:\r
229                         #import the module\r
230                         module = __import__(plugin_name)\r
231                         #prepare the ini file for inclusion\r
232                         ini_path = path.replace('.py', '.ini')\r
233                         #include ini file\r
234                         plugin_config = ConfigObj(ini_path)\r
235                         #self.config.merge(plugin_config)\r
236                         self.configs[plugin] = plugin_config\r
237                         #add to plugins\r
238                         commands = eval('dir(module.' + plugin+ '.' + plugin + 'Commands)')\r
239                         #keep only commands (ie names that start with 'do_')\r
240                         commands = [command for command in commands if command.startswith('do_')]\r
241                         if commands:\r
242                             for command in commands:\r
243                                 if existing_commands.has_key(command):\r
244                                     message_str = 'Adding "' + command + '" in plugin "' + plugin + '".\n\n'\r
245                                     message_str += '"' + command + '" already exists in "' + str(existing_commands[command]) + '".\n\n'\r
246                                     message_str += 'Only "' + command + '" in "' + str(existing_commands[command]) + '" will work.\n\n'\r
247                                     message_str += 'Please rename one of the commands in the source code and restart Hooke or disable one of the plugins.'\r
248                                     dialog = wx.MessageDialog(self, message_str, 'Warning', wx.OK|wx.ICON_WARNING|wx.CENTER)\r
249                                     dialog.ShowModal()\r
250                                     dialog.Destroy()\r
251                                 existing_commands[command] = plugin\r
252                             self.plugins[plugin] = commands\r
253                         try:\r
254                             #initialize the plugin\r
255                             eval('module.' + plugin+ '.' + plugin + 'Commands._plug_init(self)')\r
256                         except AttributeError:\r
257                             pass\r
258                     except ImportError:\r
259                         pass\r
260         #add commands from hooke.py i.e. 'core' commands\r
261         commands = dir(HookeFrame)\r
262         commands = [command for command in commands if command.startswith('do_')]\r
263         if commands:\r
264             self.plugins['core'] = commands\r
265         #create panels here\r
266         self.panelAssistant = self.CreatePanelAssistant()\r
267         self.panelCommands = self.CreatePanelCommands()\r
268         self.panelFolders = self.CreatePanelFolders()\r
269         self.panelPlaylists = self.CreatePanelPlaylists()\r
270         self.panelProperties = self.CreatePanelProperties()\r
271         self.panelNote = self.CreatePanelNote()\r
272         self.panelOutput = self.CreatePanelOutput()\r
273         self.panelResults = self.CreatePanelResults()\r
274         self.plotNotebook = self.CreateNotebook()\r
275 \r
276         # add panes\r
277         self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False))\r
278         self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False))\r
279         self._mgr.AddPane(self.panelNote, aui.AuiPaneInfo().Name('Note').Caption('Note').Left().CloseButton(True).MaximizeButton(False))\r
280         self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False))\r
281         self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False))\r
282         self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False))\r
283         self._mgr.AddPane(self.panelAssistant, aui.AuiPaneInfo().Name('Assistant').Caption('Assistant').Right().CloseButton(True).MaximizeButton(False))\r
284         self._mgr.AddPane(self.panelOutput, aui.AuiPaneInfo().Name('Output').Caption('Output').Bottom().CloseButton(True).MaximizeButton(False))\r
285         self._mgr.AddPane(self.panelResults, aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().CloseButton(True).MaximizeButton(False))\r
286         #self._mgr.AddPane(self.textCtrlCommandLine, aui.AuiPaneInfo().Name('CommandLine').CaptionVisible(False).Fixed().Bottom().Layer(2).CloseButton(False).MaximizeButton(False))\r
287         #self._mgr.AddPane(panelBottom, aui.AuiPaneInfo().Name("panelCommandLine").Bottom().Position(1).CloseButton(False).MaximizeButton(False))\r
288 \r
289         # add the toolbars to the manager\r
290         #self.toolbar=self.CreateToolBar()\r
291         self.toolbarNavigation=self.CreateToolBarNavigation()\r
292         #self._mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name('toolbar').Caption('Toolbar').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))\r
293         self._mgr.AddPane(self.toolbarNavigation, aui.AuiPaneInfo().Name('toolbarNavigation').Caption('Navigation').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))\r
294         # "commit" all changes made to FrameManager\r
295         self._mgr.Update()\r
296         #create the menubar after the panes so that the default perspective\r
297         #is created with all panes open\r
298         self.CreateMenuBar()\r
299         self.statusbar = self.CreateStatusbar()\r
300         self._BindEvents()\r
301 \r
302         name = self.config['perspectives']['active']\r
303         menu_item = self.GetPerspectiveMenuItem(name)\r
304         if menu_item is not None:\r
305             self.OnRestorePerspective(menu_item)\r
306             #TODO: config setting to remember playlists from last session\r
307         self.playlists = self.panelPlaylists.Playlists\r
308         #initialize the commands tree\r
309         self.panelCommands.Initialize(self.plugins)\r
310         for command in dir(self):\r
311             if command.startswith('plotmanip_'):\r
312                 self.plotmanipulators.append(lib.plotmanipulator.Plotmanipulator(method=getattr(self, command), command=command))\r
313 \r
314         #load default list, if possible\r
315         self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlist'))\r
316 \r
317     def _BindEvents(self):\r
318         #TODO: figure out if we can use the eventManager for menu ranges\r
319         #and events of 'self' without raising an assertion fail error\r
320         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)\r
321         self.Bind(wx.EVT_SIZE, self.OnSize)\r
322         self.Bind(wx.EVT_CLOSE, self.OnClose)\r
323         # Show How To Use The Closing Panes Event\r
324         self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)\r
325         self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnNotebookPageClose)\r
326         #menu\r
327         evtmgr.eventManager.Register(self.OnAbout, wx.EVT_MENU, win=self, id=wx.ID_ABOUT)\r
328         evtmgr.eventManager.Register(self.OnClose, wx.EVT_MENU, win=self, id=wx.ID_EXIT)\r
329         #view\r
330         self.Bind(wx.EVT_MENU_RANGE, self.OnView, id=ID_ViewAssistant, id2=ID_ViewResults)\r
331         #perspectives\r
332         self.Bind(wx.EVT_MENU, self.OnDeletePerspective, id=ID_DeletePerspective)\r
333         self.Bind(wx.EVT_MENU, self.OnSavePerspective, id=ID_SavePerspective)\r
334         self.Bind(wx.EVT_MENU_RANGE, self.OnRestorePerspective, id=ID_FirstPerspective, id2=ID_FirstPerspective+1000)\r
335         #toolbar\r
336         evtmgr.eventManager.Register(self.OnNext, wx.EVT_TOOL, win=self, id=ID_Next)\r
337         evtmgr.eventManager.Register(self.OnPrevious, wx.EVT_TOOL, win=self, id=ID_Previous)\r
338         #self.Bind(.EVT_AUITOOLBAR_TOOL_DROPDOWN, self.OnDropDownToolbarItem, id=ID_DropDownToolbarItem)\r
339         #dir control\r
340         treeCtrl = self.panelFolders.GetTreeCtrl()\r
341         #tree.Bind(wx.EVT_LEFT_UP, self.OnDirCtrl1LeftUp)\r
342         #tree.Bind(wx.EVT_LEFT_DOWN, self.OnGenericDirCtrl1LeftDown)\r
343         treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self.OnDirCtrlLeftDclick)\r
344         #playlist tree\r
345         self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DOWN, self.OnPlaylistsLeftDown)\r
346         self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DCLICK, self.OnPlaylistsLeftDclick)\r
347         #commands tree\r
348         evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self.panelCommands.ExecuteButton)\r
349         evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree)\r
350         evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self.panelCommands.CommandsTree)\r
351         evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton)\r
352         #property editor\r
353         self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)\r
354         #results panel\r
355         self.panelResults.results_list.OnCheckItem = self.OnResultsCheck\r
356 \r
357     def _GetActiveFileIndex(self):\r
358         lib.playlist.Playlist = self.GetActivePlaylist()\r
359         #get the selected item from the tree\r
360         selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
361         #test if a playlist or a curve was double-clicked\r
362         if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
363             return -1\r
364         else:\r
365             count = 0\r
366             selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)\r
367             while selected_item.IsOk():\r
368                 count += 1\r
369                 selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)\r
370             return count\r
371 \r
372     def _GetPlaylistTab(self, name):\r
373         for index, page in enumerate(self.plotNotebook._tabs._pages):\r
374             if page.caption == name:\r
375                 return index\r
376         return -1\r
377 \r
378     def _GetUniquePlaylistName(self, name):\r
379         playlist_name = name\r
380         count = 1\r
381         while playlist_name in self.playlists:\r
382             playlist_name = ''.join([name, str(count)])\r
383             count += 1\r
384         return playlist_name\r
385 \r
386     def _RestorePerspective(self, name):\r
387         self._mgr.LoadPerspective(self._perspectives[name])\r
388         self.config['perspectives']['active'] = name\r
389         self._mgr.Update()\r
390         all_panes = self._mgr.GetAllPanes()\r
391         for pane in all_panes:\r
392             if not pane.name.startswith('toolbar'):\r
393                 if pane.name == 'Assistant':\r
394                     self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown())\r
395                 if pane.name == 'Folders':\r
396                     self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown())\r
397                 if pane.name == 'Playlists':\r
398                     self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown())\r
399                 if pane.name == 'Commands':\r
400                     self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown())\r
401                 if pane.name == 'Note':\r
402                     self.MenuBar.FindItemById(ID_ViewNote).Check(pane.window.IsShown())\r
403                 if pane.name == 'Properties':\r
404                     self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown())\r
405                 if pane.name == 'Output':\r
406                     self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown())\r
407                 if pane.name == 'Results':\r
408                     self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown())\r
409 \r
410     def _SavePerspectiveToFile(self, name, perspective):\r
411         filename = ''.join([name, '.txt'])\r
412         filename = lh.get_file_path(filename, ['perspectives'])\r
413         perspectivesFile = open(filename, 'w')\r
414         perspectivesFile.write(perspective)\r
415         perspectivesFile.close()\r
416 \r
417     def _UnbindEvents(self):\r
418         #menu\r
419         evtmgr.eventManager.DeregisterListener(self.OnAbout)\r
420         evtmgr.eventManager.DeregisterListener(self.OnClose)\r
421         #toolbar\r
422         evtmgr.eventManager.DeregisterListener(self.OnNext)\r
423         evtmgr.eventManager.DeregisterListener(self.OnPrevious)\r
424         #commands tree\r
425         evtmgr.eventManager.DeregisterListener(self.OnExecute)\r
426         evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)\r
427         evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlItemActivated)\r
428         evtmgr.eventManager.DeregisterListener(self.OnUpdateNote)\r
429 \r
430     def AddPlaylist(self, playlist=None, name='Untitled'):\r
431         if playlist and playlist.count > 0:\r
432             playlist.name = self._GetUniquePlaylistName(name)\r
433             playlist.reset()\r
434             self.AddToPlaylists(playlist)\r
435 \r
436     def AddPlaylistFromFiles(self, files=[], name='Untitled'):\r
437         if files:\r
438             playlist = lib.playlist.Playlist(self, self.drivers)\r
439             for item in files:\r
440                 playlist.add_curve(item)\r
441         if playlist.count > 0:\r
442             playlist.name = self._GetUniquePlaylistName(name)\r
443             playlist.reset()\r
444             self.AddTayliss(playlist)\r
445 \r
446     def AddToPlaylists(self, playlist):\r
447         if playlist.count > 0:\r
448             #setup the playlist in the Playlist tree\r
449             tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem()\r
450             playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0)\r
451             #add all files to the Playlist tree\r
452 #            files = {}\r
453             hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
454             for index, file_to_add in enumerate(playlist.files):\r
455                 #optionally remove the extension from the name of the curve\r
456                 if hide_curve_extension:\r
457                     file_to_add.name = lh.remove_extension(file_to_add.name)\r
458                 file_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, file_to_add.name, 1)\r
459                 if index == playlist.index:\r
460                     self.panelPlaylists.PlaylistsTree.SelectItem(file_ID)\r
461             playlist.reset()\r
462             #create the plot tab and add playlist to the dictionary\r
463             plotPanel = panels.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))\r
464             notebook_tab = self.plotNotebook.AddPage(plotPanel, playlist.name, True)\r
465             #tab_index = self.plotNotebook.GetSelection()\r
466             playlist.figure = plotPanel.get_figure()\r
467             self.playlists[playlist.name] = playlist\r
468             #self.playlists[playlist.name] = [playlist, figure]\r
469             self.panelPlaylists.PlaylistsTree.Expand(playlist_root)\r
470             self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
471             self.UpdateNote()\r
472             self.UpdatePlot()\r
473 \r
474     def AppendToOutput(self, text):\r
475         self.panelOutput.AppendText(''.join([text, '\n']))\r
476 \r
477     def AppliesPlotmanipulator(self, name):\r
478         '''\r
479         Returns True if the plotmanipulator 'name' is applied, False otherwise\r
480         name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')\r
481         '''\r
482         return self.GetBoolFromConfig('core', 'plotmanipulators', name)\r
483 \r
484     def ApplyPlotmanipulators(self, plot, plot_file):\r
485         '''\r
486         Apply all active plotmanipulators.\r
487         '''\r
488         if plot is not None and plot_file is not None:\r
489             manipulated_plot = copy.deepcopy(plot)\r
490             for plotmanipulator in self.plotmanipulators:\r
491                 if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):\r
492                     manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)\r
493             return manipulated_plot\r
494 \r
495     def CreateApplicationIcon(self):\r
496         iconFile = 'resources' + os.sep + 'microscope.ico'\r
497         icon = wx.Icon(iconFile, wx.BITMAP_TYPE_ICO)\r
498         self.SetIcon(icon)\r
499 \r
500     def CreateCommandLine(self):\r
501         return wx.TextCtrl(self, -1, '', style=wx.NO_BORDER|wx.EXPAND)\r
502 \r
503     def CreatePanelAssistant(self):\r
504         panel = wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE)\r
505         panel.SetEditable(False)\r
506         return panel\r
507 \r
508     def CreatePanelCommands(self):\r
509         return panels.commands.Commands(self)\r
510 \r
511     def CreatePanelFolders(self):\r
512         #set file filters\r
513         filters = self.config['folders']['filters']\r
514         index = self.config['folders'].as_int('filterindex')\r
515         #set initial directory\r
516         folder = self.GetStringFromConfig('core', 'preferences', 'workdir')\r
517         return wx.GenericDirCtrl(self, -1, dir=folder, size=(200, 250), style=wx.DIRCTRL_SHOW_FILTERS, filter=filters, defaultFilter=index)\r
518 \r
519     def CreatePanelNote(self):\r
520         return panels.note.Note(self)\r
521 \r
522     def CreatePanelOutput(self):\r
523         return wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE)\r
524 \r
525     def CreatePanelPlaylists(self):\r
526         return panels.playlist.Playlists(self)\r
527 \r
528     def CreatePanelProperties(self):\r
529         return panels.propertyeditor.PropertyEditor(self)\r
530 \r
531     def CreatePanelResults(self):\r
532         return panels.results.Results(self)\r
533 \r
534     def CreatePanelWelcome(self):\r
535         #TODO: move into panels.welcome\r
536         ctrl = wx.html.HtmlWindow(self, -1, wx.DefaultPosition, wx.Size(400, 300))\r
537         introStr = '<h1>Welcome to Hooke</h1>' + \\r
538                  '<h3>Features</h3>' + \\r
539                  '<ul>' + \\r
540                  '<li>View, annotate, measure force files</li>' + \\r
541                  '<li>Worm-like chain fit of force peaks</li>' + \\r
542                  '<li>Automatic convolution-based filtering of empty files</li>' + \\r
543                  '<li>Automatic fit and measurement of multiple force peaks</li>' + \\r
544                  '<li>Handles force-clamp force experiments (experimental)</li>' + \\r
545                  '<li>It is extensible by users by means of plugins and drivers</li>' + \\r
546                  '</ul>' + \\r
547                  '<p>See the <a href="http://code.google.com/p/hooke/wiki/DocumentationIndex">DocumentationIndex</a> for more information</p>'\r
548         ctrl.SetPage(introStr)\r
549         return ctrl\r
550 \r
551     def CreateMenuBar(self):\r
552         menu_bar = wx.MenuBar()\r
553         self.SetMenuBar(menu_bar)\r
554         #file\r
555         file_menu = wx.Menu()\r
556         file_menu.Append(wx.ID_EXIT, 'Exit\tCtrl-Q')\r
557 #        edit_menu.AppendSeparator();\r
558 #        edit_menu.Append(ID_Config, 'Preferences')\r
559         #view\r
560         view_menu = wx.Menu()\r
561         view_menu.AppendCheckItem(ID_ViewFolders, 'Folders\tF5')\r
562         view_menu.AppendCheckItem(ID_ViewPlaylists, 'Playlists\tF6')\r
563         view_menu.AppendCheckItem(ID_ViewCommands, 'Commands\tF7')\r
564         view_menu.AppendCheckItem(ID_ViewProperties, 'Properties\tF8')\r
565         view_menu.AppendCheckItem(ID_ViewAssistant, 'Assistant\tF9')\r
566         view_menu.AppendCheckItem(ID_ViewResults, 'Results\tF10')\r
567         view_menu.AppendCheckItem(ID_ViewOutput, 'Output\tF11')\r
568         view_menu.AppendCheckItem(ID_ViewNote, 'Note\tF12')\r
569         #perspectives\r
570         perspectives_menu = wx.Menu()\r
571 \r
572         #help\r
573         help_menu = wx.Menu()\r
574         help_menu.Append(wx.ID_ABOUT, 'About Hooke')\r
575         #put it all together\r
576         menu_bar.Append(file_menu, 'File')\r
577         menu_bar.Append(view_menu, 'View')\r
578         menu_bar.Append(perspectives_menu, "Perspectives")\r
579         self.UpdatePerspectivesMenu()\r
580         menu_bar.Append(help_menu, 'Help')\r
581 \r
582     def CreateNotebook(self):\r
583         # create the notebook off-window to avoid flicker\r
584         client_size = self.GetClientSize()\r
585         ctrl = aui.AuiNotebook(self, -1, wx.Point(client_size.x, client_size.y), wx.Size(430, 200), self._notebook_style)\r
586         arts = [aui.AuiDefaultTabArt, aui.AuiSimpleTabArt, aui.VC71TabArt, aui.FF2TabArt, aui.VC8TabArt, aui.ChromeTabArt]\r
587         art = arts[self._notebook_theme]()\r
588         ctrl.SetArtProvider(art)\r
589         #uncomment if we find a nice icon\r
590         #page_bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16))\r
591         ctrl.AddPage(self.CreatePanelWelcome(), "Welcome", False)\r
592         return ctrl\r
593 \r
594     def CreateStatusbar(self):\r
595         statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)\r
596         statusbar.SetStatusWidths([-2, -3])\r
597         statusbar.SetStatusText('Ready', 0)\r
598         welcomeString=u'Welcome to Hooke (version '+__version__+', '+__release_name__+')!'\r
599         statusbar.SetStatusText(welcomeString, 1)\r
600         return statusbar\r
601 \r
602     def CreateToolBarNavigation(self):\r
603         toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER)\r
604         toolbar.SetToolBitmapSize(wx.Size(16,16))\r
605         toolbar_bmpBack = wx.ArtProvider_GetBitmap(wx.ART_GO_BACK, wx.ART_OTHER, wx.Size(16, 16))\r
606         toolbar_bmpForward = wx.ArtProvider_GetBitmap(wx.ART_GO_FORWARD, wx.ART_OTHER, wx.Size(16, 16))\r
607         toolbar.AddLabelTool(ID_Previous, 'Previous', toolbar_bmpBack, shortHelp='Previous curve')\r
608         toolbar.AddLabelTool(ID_Next, 'Next', toolbar_bmpForward, shortHelp='Next curve')\r
609         toolbar.Realize()\r
610         return toolbar\r
611 \r
612     def DeleteFromPlaylists(self, name):\r
613         if name in self.playlists:\r
614             del self.playlists[name]\r
615         tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem()\r
616         item, cookie = self.panelPlaylists.PlaylistsTree.GetFirstChild(tree_root)\r
617         while item.IsOk():\r
618             playlist_name = self.panelPlaylists.PlaylistsTree.GetItemText(item)\r
619             if playlist_name == name:\r
620                 try:\r
621                     self.panelPlaylists.PlaylistsTree.Delete(item)\r
622                 except:\r
623                     pass\r
624             item = self.panelPlaylists.PlaylistsTree.GetNextSibling(item)\r
625 \r
626     def GetActiveFigure(self):\r
627         playlist_name = self.GetActivePlaylistName()\r
628         figure = self.playlists[playlist_name].figure\r
629         if figure is not None:\r
630             return figure\r
631         return None\r
632 \r
633     def GetActiveFile(self):\r
634         playlist = self.GetActivePlaylist()\r
635         if playlist is not None:\r
636             return playlist.get_active_file()\r
637         return None\r
638 \r
639     def GetActivePlaylist(self):\r
640         playlist_name = self.GetActivePlaylistName()\r
641         if playlist_name in self.playlists:\r
642             return self.playlists[playlist_name]\r
643         return None\r
644 \r
645     def GetActivePlaylistName(self):\r
646         #get the selected item from the tree\r
647         selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
648         #test if a playlist or a curve was double-clicked\r
649         if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
650             playlist_item = selected_item\r
651         else:\r
652             #get the name of the playlist\r
653             playlist_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)\r
654         #now we have a playlist\r
655         return self.panelPlaylists.PlaylistsTree.GetItemText(playlist_item)\r
656 \r
657     def GetActivePlot(self):\r
658         playlist = self.GetActivePlaylist()\r
659         if playlist is not None:\r
660             return playlist.get_active_file().plot\r
661         return None\r
662 \r
663     def GetDisplayedPlot(self):\r
664         plot = copy.deepcopy(self.displayed_plot)\r
665         #plot.curves = []\r
666         #plot.curves = copy.deepcopy(plot.curves)\r
667         return plot\r
668 \r
669     def GetDisplayedPlotCorrected(self):\r
670         plot = copy.deepcopy(self.displayed_plot)\r
671         plot.curves = []\r
672         plot.curves = copy.deepcopy(plot.corrected_curves)\r
673         return plot\r
674 \r
675     def GetDisplayedPlotRaw(self):\r
676         plot = copy.deepcopy(self.displayed_plot)\r
677         plot.curves = []\r
678         plot.curves = copy.deepcopy(plot.raw_curves)\r
679         return plot\r
680 \r
681     def GetDockArt(self):\r
682         return self._mgr.GetArtProvider()\r
683 \r
684     def GetBoolFromConfig(self, *args):\r
685         if len(args) == 2:\r
686             plugin = args[0]\r
687             section = args[0]\r
688             key = args[1]\r
689         elif len(args) == 3:\r
690             plugin = args[0]\r
691             section = args[1]\r
692             key = args[2]\r
693         if self.configs.has_key(plugin):\r
694             config = self.configs[plugin]\r
695             return config[section][key].as_bool('value')\r
696         return None\r
697 \r
698     def GetColorFromConfig(self, *args):\r
699         if len(args) == 2:\r
700             plugin = args[0]\r
701             section = args[0]\r
702             key = args[1]\r
703         elif len(args) == 3:\r
704             plugin = args[0]\r
705             section = args[1]\r
706             key = args[2]\r
707         if self.configs.has_key(plugin):\r
708             config = self.configs[plugin]\r
709             color_tuple = eval(config[section][key]['value'])\r
710             color = [value / 255.0 for value in color_tuple]\r
711             return color\r
712         return None\r
713 \r
714     def GetFloatFromConfig(self, *args):\r
715         if len(args) == 2:\r
716             plugin = args[0]\r
717             section = args[0]\r
718             key = args[1]\r
719         elif len(args) == 3:\r
720             plugin = args[0]\r
721             section = args[1]\r
722             key = args[2]\r
723         if self.configs.has_key(plugin):\r
724             config = self.configs[plugin]\r
725             return config[section][key].as_float('value')\r
726         return None\r
727 \r
728     def GetIntFromConfig(self, *args):\r
729         if len(args) == 2:\r
730             plugin = args[0]\r
731             section = args[0]\r
732             key = args[1]\r
733         elif len(args) == 3:\r
734             plugin = args[0]\r
735             section = args[1]\r
736             key = args[2]\r
737         if self.configs.has_key(plugin):\r
738             config = self.configs[plugin]\r
739             return config[section][key].as_int('value')\r
740         return None\r
741 \r
742     def GetStringFromConfig(self, *args):\r
743         if len(args) == 2:\r
744             plugin = args[0]\r
745             section = args[0]\r
746             key = args[1]\r
747         elif len(args) == 3:\r
748             plugin = args[0]\r
749             section = args[1]\r
750             key = args[2]\r
751         if self.configs.has_key(plugin):\r
752             config = self.configs[plugin]\r
753             return config[section][key]['value']\r
754         return None\r
755 \r
756     def GetPlotmanipulator(self, name):\r
757         '''\r
758         Returns a plot manipulator function from its name\r
759         '''\r
760         for plotmanipulator in self.plotmanipulators:\r
761             if plotmanipulator.name == name:\r
762                 return plotmanipulator\r
763         return None\r
764 \r
765     def GetPerspectiveMenuItem(self, name):\r
766         if self._perspectives.has_key(name):\r
767             perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
768             perspectives_list.sort()\r
769             index = perspectives_list.index(name)\r
770             perspective_Id = ID_FirstPerspective + index\r
771             menu_item = self.MenuBar.FindItemById(perspective_Id)\r
772             return menu_item\r
773         else:\r
774             return None\r
775 \r
776     def HasPlotmanipulator(self, name):\r
777         '''\r
778         returns True if the plotmanipulator 'name' is loaded, False otherwise\r
779         '''\r
780         for plotmanipulator in self.plotmanipulators:\r
781             if plotmanipulator.command == name:\r
782                 return True\r
783         return False\r
784 \r
785     def OnAbout(self, event):\r
786         message = 'Hooke\n\n'+\\r
787             'A free, open source data analysis platform\n\n'+\\r
788             'Copyright 2006-2008 by Massimo Sandal\n'+\\r
789             'Copyright 2010 by Dr. Rolf Schmidt\n\n'+\\r
790             'Hooke is released under the GNU General Public License version 2.'\r
791         dialog = wx.MessageDialog(self, message, 'About Hooke', wx.OK | wx.ICON_INFORMATION)\r
792         dialog.ShowModal()\r
793         dialog.Destroy()\r
794 \r
795     def OnClose(self, event):\r
796         #apply changes\r
797         self.config['main']['height'] = str(self.GetSize().GetHeight())\r
798         self.config['main']['left'] = str(self.GetPosition()[0])\r
799         self.config['main']['top'] = str(self.GetPosition()[1])\r
800         self.config['main']['width'] = str(self.GetSize().GetWidth())\r
801         #save the configuration file to 'config/hooke.ini'\r
802         self.config.write()\r
803         #save all plugin config files\r
804         for config in self.configs:\r
805             plugin_config = self.configs[config]\r
806             plugin_config.write()\r
807         self._UnbindEvents()\r
808         self._mgr.UnInit()\r
809         del self._mgr\r
810         self.Destroy()\r
811 \r
812     def OnDeletePerspective(self, event):\r
813         dialog = panels.perspectives.Perspectives(self, -1, 'Delete perspective(s)')\r
814         dialog.CenterOnScreen()\r
815         dialog.ShowModal()\r
816         dialog.Destroy()\r
817         self.UpdatePerspectivesMenu()\r
818         #unfortunately, there is a bug in wxWidgets (Ticket #3258) that\r
819         #makes the radio item indicator in the menu disappear\r
820         #the code should be fine once this issue is fixed\r
821 \r
822     def OnDirCtrlLeftDclick(self, event):\r
823         file_path = self.panelFolders.GetPath()\r
824         if os.path.isfile(file_path):\r
825             if file_path.endswith('.hkp'):\r
826                 self.do_loadlist(file_path)\r
827         event.Skip()\r
828 \r
829     def OnEraseBackground(self, event):\r
830         event.Skip()\r
831 \r
832     def OnExecute(self, event):\r
833         item = self.panelCommands.CommandsTree.GetSelection()\r
834         if item.IsOk():\r
835             if not self.panelCommands.CommandsTree.ItemHasChildren(item):\r
836                 item_text = self.panelCommands.CommandsTree.GetItemText(item)\r
837                 command = ''.join(['self.do_', item_text, '()'])\r
838                 #self.AppendToOutput(command + '\n')\r
839                 exec(command)\r
840 \r
841     def OnExit(self, event):\r
842         self.Close()\r
843 \r
844     def OnNext(self, event):\r
845         '''\r
846         NEXT\r
847         Go to the next curve in the playlist.\r
848         If we are at the last curve, we come back to the first.\r
849         -----\r
850         Syntax: next, n\r
851         '''\r
852         selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
853         if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
854             #GetFirstChild returns a tuple\r
855             #we only need the first element\r
856             next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(selected_item)[0]\r
857         else:\r
858             next_item = self.panelPlaylists.PlaylistsTree.GetNextSibling(selected_item)\r
859             if not next_item.IsOk():\r
860                 parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)\r
861                 #GetFirstChild returns a tuple\r
862                 #we only need the first element\r
863                 next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(parent_item)[0]\r
864         self.panelPlaylists.PlaylistsTree.SelectItem(next_item, True)\r
865         if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
866             playlist = self.GetActivePlaylist()\r
867             if playlist.count > 1:\r
868                 playlist.next()\r
869                 self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
870                 self.UpdateNote()\r
871                 self.UpdatePlot()\r
872 \r
873     def OnNotebookPageClose(self, event):\r
874         ctrl = event.GetEventObject()\r
875         playlist_name = ctrl.GetPageText(ctrl._curpage)\r
876         self.DeleteFromPlaylists(playlist_name)\r
877 \r
878     def OnPaneClose(self, event):\r
879         event.Skip()\r
880 \r
881     def OnPlaylistsLeftDclick(self, event):\r
882         if self.panelPlaylists.PlaylistsTree.Count > 0:\r
883             playlist_name = self.GetActivePlaylistName()\r
884             #if that playlist already exists\r
885             #we check if it is the active playlist (ie selected in panelPlaylists)\r
886             #and switch to it if necessary\r
887             if playlist_name in self.playlists:\r
888                 index = self.plotNotebook.GetSelection()\r
889                 current_playlist = self.plotNotebook.GetPageText(index)\r
890                 if current_playlist != playlist_name:\r
891                     index = self._GetPlaylistTab(playlist_name)\r
892                     self.plotNotebook.SetSelection(index)\r
893                 #if a curve was double-clicked\r
894                 item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
895                 if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item):\r
896                     index = self._GetActiveFileIndex()\r
897                 else:\r
898                     index = 0\r
899                 if index >= 0:\r
900                     playlist = self.GetActivePlaylist()\r
901                     playlist.index = index\r
902                     self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
903                     self.UpdateNote()\r
904                     self.UpdatePlot()\r
905             #if you uncomment the following line, the tree will collapse/expand as well\r
906             #event.Skip()\r
907 \r
908     def OnPlaylistsLeftDown(self, event):\r
909         hit_item, hit_flags = self.panelPlaylists.PlaylistsTree.HitTest(event.GetPosition())\r
910         if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
911             self.panelPlaylists.PlaylistsTree.SelectItem(hit_item)\r
912             playlist_name = self.GetActivePlaylistName()\r
913             playlist = self.GetActivePlaylist()\r
914             #if a curve was clicked\r
915             item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
916             if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item):\r
917                 index = self._GetActiveFileIndex()\r
918                 if index >= 0:\r
919                     playlist.index = index\r
920             self.playlists[playlist_name] = playlist\r
921         event.Skip()\r
922 \r
923     def OnPrevious(self, event):\r
924         '''\r
925         PREVIOUS\r
926         Go to the previous curve in the playlist.\r
927         If we are at the first curve, we jump to the last.\r
928         -------\r
929         Syntax: previous, p\r
930         '''\r
931         #playlist = self.playlists[self.GetActivePlaylistName()][0]\r
932         #select the previous curve and tell the user if we wrapped around\r
933         #self.AppendToOutput(playlist.previous())\r
934         selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
935         if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
936             previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(selected_item)\r
937         else:\r
938             previous_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)\r
939             if not previous_item.IsOk():\r
940                 parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)\r
941                 previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(parent_item)\r
942         self.panelPlaylists.PlaylistsTree.SelectItem(previous_item, True)\r
943         playlist = self.GetActivePlaylist()\r
944         if playlist.count > 1:\r
945             playlist.previous()\r
946             self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
947             self.UpdateNote()\r
948             self.UpdatePlot()\r
949 \r
950     def OnPropGridChanged (self, event):\r
951         prop = event.GetProperty()\r
952         if prop:\r
953             item_section = self.panelProperties.SelectedTreeItem\r
954             item_plugin = self.panelCommands.CommandsTree.GetItemParent(item_section)\r
955             plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin)\r
956             config = self.configs[plugin]\r
957             property_section = self.panelCommands.CommandsTree.GetItemText(item_section)\r
958             property_key = prop.GetName()\r
959             property_value = prop.GetDisplayedString()\r
960 \r
961             config[property_section][property_key]['value'] = property_value\r
962 \r
963     def OnRestorePerspective(self, event):\r
964         name = self.MenuBar.FindItemById(event.GetId()).GetLabel()\r
965         self._RestorePerspective(name)\r
966 \r
967     def OnResultsCheck(self, index, flag):\r
968         results = self.GetActivePlot().results\r
969         if results.has_key(self.results_str):\r
970             results[self.results_str].results[index].visible = flag\r
971             results[self.results_str].update()\r
972             self.UpdatePlot()\r
973 \r
974     def OnSavePerspective(self, event):\r
975 \r
976         def nameExists(name):\r
977             menu_position = self.MenuBar.FindMenu('Perspectives')\r
978             menu = self.MenuBar.GetMenu(menu_position)\r
979             for item in menu.GetMenuItems():\r
980                 if item.GetText() == name:\r
981                     return True\r
982             return False\r
983 \r
984         done = False\r
985         while not done:\r
986             dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective')\r
987             dialog.SetValue('New perspective')\r
988             if dialog.ShowModal() != wx.ID_OK:\r
989                 return\r
990             else:\r
991                 name = dialog.GetValue()\r
992 \r
993             if nameExists(name):\r
994                 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
995                 if dialogConfirm.ShowModal() == wx.ID_YES:\r
996                     done = True\r
997             else:\r
998                 done = True\r
999 \r
1000         perspective = self._mgr.SavePerspective()\r
1001         self._SavePerspectiveToFile(name, perspective)\r
1002         self.config['perspectives']['active'] = name\r
1003         self.UpdatePerspectivesMenu()\r
1004 #        if nameExists(name):\r
1005 #            #check the corresponding menu item\r
1006 #            menu_item = self.GetPerspectiveMenuItem(name)\r
1007 #            #replace the perspectiveStr in _pespectives\r
1008 #            self._perspectives[name] = perspective\r
1009 #        else:\r
1010 #            #because we deal with radio items, we need to do some extra work\r
1011 #            #delete all menu items from the perspectives menu\r
1012 #            for item in self._perspectives_menu.GetMenuItems():\r
1013 #                self._perspectives_menu.DeleteItem(item)\r
1014 #            #recreate the perspectives menu\r
1015 #            self._perspectives_menu.Append(ID_SavePerspective, 'Save Perspective')\r
1016 #            self._perspectives_menu.Append(ID_DeletePerspective, 'Delete Perspective')\r
1017 #            self._perspectives_menu.AppendSeparator()\r
1018 #            #convert the perspectives dictionary into a list\r
1019 #            # the list contains:\r
1020 #            #[0]: name of the perspective\r
1021 #            #[1]: perspective\r
1022 #            perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
1023 #            perspectives_list.append(name)\r
1024 #            perspectives_list.sort()\r
1025 #            #add all previous perspectives\r
1026 #            for index, item in enumerate(perspectives_list):\r
1027 #                menu_item = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + index, item)\r
1028 #                if item == name:\r
1029 #                    menu_item.Check()\r
1030 #            #add the new perspective to _perspectives\r
1031 #            self._perspectives[name] = perspective\r
1032 \r
1033     def OnSize(self, event):\r
1034         event.Skip()\r
1035 \r
1036     def OnTreeCtrlCommandsSelectionChanged(self, event):\r
1037         selected_item = event.GetItem()\r
1038         if selected_item is not None:\r
1039             plugin = ''\r
1040             section = ''\r
1041             #deregister/register the listener to avoid infinite loop\r
1042             evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)\r
1043             self.panelCommands.CommandsTree.SelectItem(selected_item)\r
1044             evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree)\r
1045             self.panelProperties.SelectedTreeItem = selected_item\r
1046             #if a command was clicked\r
1047             properties = []\r
1048             if not self.panelCommands.CommandsTree.ItemHasChildren(selected_item):\r
1049                 item_plugin = self.panelCommands.CommandsTree.GetItemParent(selected_item)\r
1050                 plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin)\r
1051                 if self.configs.has_key(plugin):\r
1052                     #config = self.panelCommands.CommandsTree.GetPyData(item_plugin)\r
1053                     config = self.configs[plugin]\r
1054                     section = self.panelCommands.CommandsTree.GetItemText(selected_item)\r
1055                     #display docstring in help window\r
1056                     doc_string = eval('self.do_' + section + '.__doc__')\r
1057                     if section in config:\r
1058                         for option in config[section]:\r
1059                             properties.append([option, config[section][option]])\r
1060             else:\r
1061                 plugin = self.panelCommands.CommandsTree.GetItemText(selected_item)\r
1062                 if plugin != 'core':\r
1063                     doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')\r
1064                 else:\r
1065                     doc_string = 'The module "core" contains Hooke core functionality'\r
1066             if doc_string is not None:\r
1067                 self.panelAssistant.ChangeValue(doc_string)\r
1068             else:\r
1069                 self.panelAssistant.ChangeValue('')\r
1070             panels.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)\r
1071             #save the currently selected command/plugin to the config file\r
1072             self.config['command']['command'] = section\r
1073             self.config['command']['plugin'] = plugin\r
1074 \r
1075     def OnTreeCtrlItemActivated(self, event):\r
1076         self.OnExecute(event)\r
1077 \r
1078     def OnUpdateNote(self, event):\r
1079         '''\r
1080         Saves the note to the active file.\r
1081         '''\r
1082         active_file = self.GetActiveFile()\r
1083         active_file.note = self.panelNote.Editor.GetValue()\r
1084 \r
1085     def OnView(self, event):\r
1086         menu_id = event.GetId()\r
1087         menu_item = self.MenuBar.FindItemById(menu_id)\r
1088         menu_label = menu_item.GetLabel()\r
1089 \r
1090         pane = self._mgr.GetPane(menu_label)\r
1091         pane.Show(not pane.IsShown())\r
1092         #if we don't do the following, the Folders pane does not resize properly on hide/show\r
1093         if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():\r
1094             #folders_size = pane.GetSize()\r
1095             self.panelFolders.Fit()\r
1096         self._mgr.Update()\r
1097 \r
1098     def _clickize(self, xvector, yvector, index):\r
1099         '''\r
1100         Returns a ClickedPoint() object from an index and vectors of x, y coordinates\r
1101         '''\r
1102         point = lib.clickedpoint.ClickedPoint()\r
1103         point.index = index\r
1104         point.absolute_coords = xvector[index], yvector[index]\r
1105         point.find_graph_coords(xvector, yvector)\r
1106         return point\r
1107 \r
1108     def _delta(self, message='Click 2 points', whatset=lh.RETRACTION):\r
1109         '''\r
1110         Calculates the difference between two clicked points\r
1111         '''\r
1112         clicked_points = self._measure_N_points(N=2, message=message, whatset=whatset)\r
1113 \r
1114         plot = self.GetDisplayedPlotCorrected()\r
1115         curve = plot.curves[whatset]\r
1116 \r
1117         delta = lib.delta.Delta()\r
1118         delta.point1.x = clicked_points[0].graph_coords[0]\r
1119         delta.point1.y = clicked_points[0].graph_coords[1]\r
1120         delta.point2.x = clicked_points[1].graph_coords[0]\r
1121         delta.point2.y = clicked_points[1].graph_coords[1]\r
1122         delta.units.x = curve.units.x\r
1123         delta.units.y = curve.units.y\r
1124 \r
1125         return delta\r
1126 \r
1127     def _measure_N_points(self, N, message='', whatset=lh.RETRACTION):\r
1128         '''\r
1129         General helper function for N-points measurements\r
1130         By default, measurements are done on the retraction\r
1131         '''\r
1132         if message:\r
1133             dialog = wx.MessageDialog(None, message, 'Info', wx.OK)\r
1134             dialog.ShowModal()\r
1135 \r
1136         figure = self.GetActiveFigure()\r
1137 \r
1138         xvector = self.displayed_plot.curves[whatset].x\r
1139         yvector = self.displayed_plot.curves[whatset].y\r
1140 \r
1141         clicked_points = figure.ginput(N, timeout=-1, show_clicks=True)\r
1142 \r
1143         points = []\r
1144         for clicked_point in clicked_points:\r
1145             point = lib.clickedpoint.ClickedPoint()\r
1146             point.absolute_coords = clicked_point[0], clicked_point[1]\r
1147             point.dest = 0\r
1148             #TODO: make this optional?\r
1149             #so far, the clicked point is taken, not the corresponding data point\r
1150             point.find_graph_coords(xvector, yvector)\r
1151             point.is_line_edge = True\r
1152             point.is_marker = True\r
1153             points.append(point)\r
1154         return points\r
1155 \r
1156     def do_copylog(self):\r
1157         '''\r
1158         Copies all files in the current playlist that have a note to the destination folder.\r
1159         destination: select folder where you want the files to be copied\r
1160         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
1161         '''\r
1162         playlist = self.GetActivePlaylist()\r
1163         if playlist is not None:\r
1164             destination = self.GetStringFromConfig('core', 'copylog', 'destination')\r
1165             if not os.path.isdir(destination):\r
1166                 os.makedirs(destination)\r
1167             for current_file in playlist.files:\r
1168                 if current_file.note:\r
1169                     shutil.copy(current_file.filename, destination)\r
1170                     if current_file.driver.filetype == 'mfp1d':\r
1171                         filename = current_file.filename.replace('deflection', 'LVDT', 1)\r
1172                         path, name = os.path.split(filename)\r
1173                         filename = os.path.join(path, 'lvdt', name)\r
1174                         use_LVDT_folder = self.GetBoolFromConfig('core', 'copylog', 'use_LVDT_folder')\r
1175                         if use_LVDT_folder:\r
1176                             destination = os.path.join(destination, 'LVDT')\r
1177                         shutil.copy(filename, destination)\r
1178 \r
1179     def do_plotmanipulators(self):\r
1180         '''\r
1181         Please select the plotmanipulators you would like to use\r
1182         and define the order in which they will be applied to the data.\r
1183 \r
1184         Click 'Execute' to apply your changes.\r
1185         '''\r
1186         self.UpdatePlot()\r
1187 \r
1188     def do_preferences(self):\r
1189         '''\r
1190         Please set general preferences for Hooke here.\r
1191         hide_curve_extension: hides the extension of the force curve files.\r
1192                               not recommended for 'picoforce' files\r
1193         '''\r
1194         pass\r
1195 \r
1196     def do_test(self):\r
1197         '''\r
1198         Use this command for testing purposes. You find do_test in hooke.py.\r
1199         '''\r
1200         pass\r
1201 \r
1202     def do_version(self):\r
1203         '''\r
1204         VERSION\r
1205         ------\r
1206         Prints the current version and codename, plus library version. Useful for debugging.\r
1207         '''\r
1208         self.AppendToOutput('Hooke ' + __version__ + ' (' + __codename__ + ')')\r
1209         self.AppendToOutput('Released on: ' + __releasedate__)\r
1210         self.AppendToOutput('---')\r
1211         self.AppendToOutput('Python version: ' + python_version)\r
1212         self.AppendToOutput('WxPython version: ' + wx_version)\r
1213         self.AppendToOutput('Matplotlib version: ' + mpl_version)\r
1214         self.AppendToOutput('SciPy version: ' + scipy_version)\r
1215         self.AppendToOutput('NumPy version: ' + numpy_version)\r
1216         self.AppendToOutput('ConfigObj version: ' + configobj_version)\r
1217         self.AppendToOutput('wxPropertyGrid version: ' + '.'.join([str(PROPGRID_MAJOR), str(PROPGRID_MINOR), str(PROPGRID_RELEASE)]))\r
1218         self.AppendToOutput('---')\r
1219         self.AppendToOutput('Platform: ' + str(platform.uname()))\r
1220         self.AppendToOutput('******************************')\r
1221         self.AppendToOutput('Loaded plugins')\r
1222         self.AppendToOutput('---')\r
1223 \r
1224         #sort the plugins into alphabetical order\r
1225         plugins_list = [key for key, value in self.plugins.iteritems()]\r
1226         plugins_list.sort()\r
1227         for plugin in plugins_list:\r
1228             self.AppendToOutput(plugin)\r
1229 \r
1230     def UpdateNote(self):\r
1231         #update the note for the active file\r
1232         active_file = self.GetActiveFile()\r
1233         if active_file is not None:\r
1234             self.panelNote.Editor.SetValue(active_file.note)\r
1235 \r
1236     def UpdatePerspectivesMenu(self):\r
1237         #add perspectives to menubar and _perspectives\r
1238         perspectivesDirectory = os.path.join(lh.hookeDir, 'perspectives')\r
1239         self._perspectives = {}\r
1240         if os.path.isdir(perspectivesDirectory):\r
1241             perspectiveFileNames = os.listdir(perspectivesDirectory)\r
1242             for perspectiveFilename in perspectiveFileNames:\r
1243                 filename = lh.get_file_path(perspectiveFilename, ['perspectives'])\r
1244                 if os.path.isfile(filename):\r
1245                     perspectiveFile = open(filename, 'rU')\r
1246                     perspective = perspectiveFile.readline()\r
1247                     perspectiveFile.close()\r
1248                     if perspective:\r
1249                         name, extension = os.path.splitext(perspectiveFilename)\r
1250                         if extension == '.txt':\r
1251                             self._perspectives[name] = perspective\r
1252 \r
1253         #in case there are no perspectives\r
1254         if not self._perspectives:\r
1255             perspective = self._mgr.SavePerspective()\r
1256             self._perspectives['Default'] = perspective\r
1257             self._SavePerspectiveToFile('Default', perspective)\r
1258 \r
1259         selected_perspective = self.config['perspectives']['active']\r
1260         if not self._perspectives.has_key(selected_perspective):\r
1261             self.config['perspectives']['active'] = 'Default'\r
1262             selected_perspective = 'Default'\r
1263 \r
1264         perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
1265         perspectives_list.sort()\r
1266 \r
1267         #get the Perspectives menu\r
1268         menu_position = self.MenuBar.FindMenu('Perspectives')\r
1269         menu = self.MenuBar.GetMenu(menu_position)\r
1270         #delete all menu items\r
1271         for item in menu.GetMenuItems():\r
1272             menu.DeleteItem(item)\r
1273         #rebuild the menu by adding the standard menu items\r
1274         menu.Append(ID_SavePerspective, 'Save Perspective')\r
1275         menu.Append(ID_DeletePerspective, 'Delete Perspective')\r
1276         menu.AppendSeparator()\r
1277         #add all previous perspectives\r
1278         for index, label in enumerate(perspectives_list):\r
1279             menu_item = menu.AppendRadioItem(ID_FirstPerspective + index, label)\r
1280             if label == selected_perspective:\r
1281                 self._RestorePerspective(label)\r
1282                 menu_item.Check(True)\r
1283 \r
1284     def UpdatePlaylistsTreeSelection(self):\r
1285         playlist = self.GetActivePlaylist()\r
1286         if playlist is not None:\r
1287             if playlist.index >= 0:\r
1288                 self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
1289                 self.UpdateNote()\r
1290                 self.UpdatePlot()\r
1291 \r
1292     def UpdatePlot(self, plot=None):\r
1293 \r
1294         def add_to_plot(curve, set_scale=True):\r
1295             if curve.visible and curve.x and curve.y:\r
1296                 #get the index of the subplot to use as destination\r
1297                 destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1\r
1298                 #set all parameters for the plot\r
1299                 axes_list[destination].set_title(curve.title)\r
1300                 if set_scale:\r
1301                     axes_list[destination].set_xlabel(curve.prefix.x + curve.units.x)\r
1302                     axes_list[destination].set_ylabel(curve.prefix.y + curve.units.y)\r
1303                     #set the formatting details for the scale\r
1304                     formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero)\r
1305                     formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.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
1313                 if curve.legend:\r
1314                     axes_list[destination].legend()\r
1315 \r
1316         if plot is None:\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.prefix.x = self.GetStringFromConfig('core', 'preferences', 'x_prefix')\r
1326                     curve.prefix.y = self.GetStringFromConfig('core', 'preferences', 'y_prefix')\r
1327             if active_file.driver is None:\r
1328                 self.AppendToOutput('Invalid file: ' + active_file.filename)\r
1329                 return\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
1337         else:\r
1338             active_file = None\r
1339             self.displayed_plot = copy.deepcopy(plot)\r
1340 \r
1341         figure = self.GetActiveFigure()\r
1342         figure.clear()\r
1343 \r
1344         #use '0' instead of e.g. '0.00' for scales\r
1345         use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero')\r
1346         #optionally remove the extension from the title of the plot\r
1347         hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
1348         if hide_curve_extension:\r
1349             title = lh.remove_extension(self.displayed_plot.title)\r
1350         else:\r
1351             title = self.displayed_plot.title\r
1352         figure.suptitle(title, fontsize=14)\r
1353         #create the list of all axes necessary (rows and columns)\r
1354         axes_list =[]\r
1355         number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves])\r
1356         number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves])\r
1357         for index in range(number_of_rows * number_of_columns):\r
1358             axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1))\r
1359 \r
1360         #add all curves to the corresponding plots\r
1361         for curve in self.displayed_plot.curves:\r
1362             add_to_plot(curve)\r
1363 \r
1364         #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot'\r
1365         figure.subplots_adjust(hspace=0.3)\r
1366 \r
1367         #display results\r
1368         self.panelResults.ClearResults()\r
1369         if self.displayed_plot.results.has_key(self.results_str):\r
1370             for curve in self.displayed_plot.results[self.results_str].results:\r
1371                 add_to_plot(curve, set_scale=False)\r
1372             self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str])\r
1373         else:\r
1374             self.panelResults.ClearResults()\r
1375         #refresh the plot\r
1376         figure.canvas.draw()\r
1377 \r
1378 if __name__ == '__main__':\r
1379 \r
1380     ## now, silence a deprecation warning for py2.3\r
1381     import warnings\r
1382     warnings.filterwarnings("ignore", "integer", DeprecationWarning, "wxPython.gdi")\r
1383 \r
1384     redirect = True\r
1385     if __debug__:\r
1386         redirect=False\r
1387 \r
1388     app = Hooke(redirect=redirect)\r
1389 \r
1390     app.MainLoop()\r
1391 \r
1392 from ..command import CommandExit, Exit, Command, Argument, StoreValue\r
1393 from ..interaction import Request, BooleanRequest, ReloadUserInterfaceConfig\r
1394 from ..ui import UserInterface, CommandMessage\r
1395 from ..util.encoding import get_input_encoding, get_output_encoding\r
1396 \r
1397 \r
1398 class GUI (UserInterface):\r
1399     """wxWindows graphical user interface.\r
1400     """\r
1401     def __init__(self):\r
1402         super(GUI, self).__init__(name='gui')\r
1403 \r
1404     def default_settings(self):\r
1405         """Return a list of :class:`hooke.config.Setting`\s for any\r
1406         configurable UI settings.\r
1407 \r
1408         The suggested section setting is::\r
1409 \r
1410             Setting(section=self.setting_section, help=self.__doc__)\r
1411         """\r
1412         return []\r
1413 \r
1414     def reload_config(self):\r
1415         pass\r
1416 \r
1417     def run(self, commands, ui_to_command_queue, command_to_ui_queue):\r
1418         self._initialize()\r
1419         cmd = self._cmd(commands, ui_to_command_queue, command_to_ui_queue)\r
1420         cmd.cmdloop(self._splash_text())\r
1421 \r
1422     def run_lines(self, commands, ui_to_command_queue, command_to_ui_queue,\r
1423                   lines):\r
1424         raise NotImplementedError(\r
1425             'Use the command line interface for run_lines()')\r