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