Added modular directory structure.
[hooke.git] / hooke / hooke.py
1 #!/usr/bin/env python\r
2 \r
3 '''\r
4 HOOKE - A force spectroscopy review & analysis tool\r
5 \r
6 (C) 2008 Massimo Sandal\r
7 \r
8 Copyright (C) 2008 Massimo Sandal (University of Bologna, Italy).\r
9 \r
10 This program is released under the GNU General Public License version 2.\r
11 '''\r
12 \r
13 from .libhooke import HOOKE_VERSION, WX_GOOD\r
14 \r
15 import os\r
16 \r
17 import wxversion\r
18 wxversion.select(WX_GOOD)\r
19 import wx\r
20 import wxmpl\r
21 from wx.lib.newevent import NewEvent\r
22 \r
23 import matplotlib.numerix as nx\r
24 import scipy as sp\r
25 \r
26 from threading import Thread\r
27 import Queue\r
28 \r
29 from .hooke_cli import HookeCli\r
30 from .libhooke import HookeConfig, ClickedPoint\r
31 from . import libhookecurve as lhc\r
32 \r
33 #import file versions, just to know with what we're working...\r
34 from hooke_cli import __version__ as hookecli_version\r
35 \r
36 global __version__\r
37 global events_from_gui\r
38 global config\r
39 global CLI_PLUGINS\r
40 global GUI_PLUGINS\r
41 global LOADED_PLUGINS\r
42 global PLOTMANIP_PLUGINS\r
43 global FILE_DRIVERS\r
44 \r
45 __version__=HOOKE_VERSION[0]\r
46 __release_name__=HOOKE_VERSION[1]\r
47 \r
48 events_from_gui=Queue.Queue() #GUI ---> CLI COMMUNICATION\r
49 \r
50 print 'Starting Hooke.'\r
51 #CONFIGURATION FILE PARSING\r
52 config_obj=HookeConfig()\r
53 config=config_obj.load_config('hooke.conf')\r
54 \r
55 #IMPORTING PLUGINS\r
56 \r
57 CLI_PLUGINS=[]\r
58 GUI_PLUGINS=[]\r
59 PLOTMANIP_PLUGINS=[]\r
60 LOADED_PLUGINS=[]\r
61 \r
62 plugin_commands_namespaces=[]\r
63 plugin_gui_namespaces=[]\r
64 for plugin_name in config['plugins']:\r
65     try:\r
66         hooke_module=__import__('hooke.plugin.'+plugin_name)\r
67         plugin = getattr(hooke_module.plugin, plugin_name)\r
68         try:\r
69             #take Command plugin classes\r
70             commands = getattr(plugin, plugin_name+'Commands')\r
71             CLI_PLUGINS.append(commands)\r
72             plugin_commands_namespaces.append(dir(commands))\r
73         except:\r
74             pass\r
75         try:\r
76             #take Gui plugin classes\r
77             gui = getattr(plugin, plugin_name+'Gui')\r
78             GUI_PLUGINS.append(gui)\r
79             plugin_gui_namespaces.append(dir(gui))\r
80         except:\r
81             pass\r
82     except ImportError:\r
83         print 'Cannot find plugin ',plugin_name\r
84     else:\r
85         LOADED_PLUGINS.append(plugin_name)\r
86         print 'Imported plugin ',plugin_name\r
87 \r
88 #eliminate names common to all namespaces\r
89 for i,namespace in enumerate(plugin_commands_namespaces):\r
90     plugin_commands_namespaces[i] = \\r
91         filter(lambda item : not (item.startswith('__')\r
92                                   or item == '_plug_init'),\r
93                namespace)\r
94 #check for conflicts in namespaces between plugins\r
95 #FIXME: only in commands now, because I don't have Gui plugins to check\r
96 #FIXME: how to check for plugin-defined variables (self.stuff) ??\r
97 plugin_commands_names=[]\r
98 whatplugin_defines=[]\r
99 plugin_gui_names=[]\r
100 for namespace,plugin_name in zip(plugin_commands_namespaces, config['plugins']):\r
101     for item in namespace:\r
102         if item in plugin_commands_names:\r
103             i=plugin_commands_names.index(item) #we exploit the fact index gives the *first* occurrence of a name...\r
104             print 'Error. Plugin %s defines a function %s already defined by %s!' \\r
105                 % (plugin_name, item, whatplugin_defines[i])\r
106             print 'This should not happen. Please disable one or both plugins and contact the plugin authors to solve the conflict.'\r
107             print 'Hooke cannot continue.'\r
108             exit()\r
109         else:\r
110             plugin_commands_names.append(item)\r
111             whatplugin_defines.append(plugin_name)\r
112 \r
113 \r
114 config['loaded_plugins']=LOADED_PLUGINS #FIXME: kludge -this should be global but not in config!\r
115 #IMPORTING DRIVERS\r
116 #FIXME: code duplication\r
117 FILE_DRIVERS=[]\r
118 LOADED_DRIVERS=[]\r
119 for driver_name in config['drivers']:\r
120     try:\r
121         hooke_module=__import__('hooke.driver.'+driver_name)\r
122         driver = getattr(hooke_module.driver, driver_name)\r
123         try:\r
124             FILE_DRIVERS.append(getattr(driver, driver_name+'Driver'))\r
125         except:\r
126             pass\r
127     except ImportError:\r
128         print 'Cannot find driver ',driver_name\r
129     else:\r
130         LOADED_DRIVERS.append(driver_name)\r
131         print 'Imported driver ',driver_name\r
132 config['loaded_drivers']=LOADED_DRIVERS\r
133 \r
134 #LIST OF CUSTOM WX EVENTS FOR CLI ---> GUI COMMUNICATION\r
135 #FIXME: do they need to be here?\r
136 list_of_events={}\r
137 \r
138 plot_graph, EVT_PLOT = NewEvent()\r
139 list_of_events['plot_graph']=plot_graph\r
140 \r
141 plot_contact, EVT_PLOT_CONTACT = NewEvent()\r
142 list_of_events['plot_contact']=plot_contact\r
143 \r
144 measure_points, EVT_MEASURE_POINTS = NewEvent()\r
145 list_of_events['measure_points']=measure_points\r
146 \r
147 export_image, EVT_EXPORT_IMAGE = NewEvent()\r
148 list_of_events['export_image']=export_image\r
149 \r
150 close_plot, EVT_CLOSE_PLOT = NewEvent()\r
151 list_of_events['close_plot'] = close_plot\r
152 \r
153 show_plots, EVT_SHOW_PLOTS = NewEvent()\r
154 list_of_events['show_plots'] = show_plots\r
155 \r
156 get_displayed_plot, EVT_GET_DISPLAYED_PLOT = NewEvent()\r
157 list_of_events['get_displayed_plot'] = get_displayed_plot\r
158 #------------\r
159 \r
160 class CliThread(Thread):\r
161 \r
162     def __init__(self,frame,list_of_events):\r
163         Thread.__init__(self)\r
164 \r
165         #here we have to put temporary references to pass to the cli object.\r
166         self.frame=frame\r
167         self.list_of_events=list_of_events\r
168 \r
169         self.debug=0 #to be used in the future\r
170 \r
171     def run(self):\r
172         print '\n\nThis is Hooke, version',__version__ , __release_name__\r
173         print\r
174         print '(c) Massimo Sandal & others, 2006-2008. Released under the GNU Lesser General Public License Version 3'\r
175         print 'Hooke is Free software.'\r
176         print '----'\r
177         print ''\r
178 \r
179         def make_command_class(*bases):\r
180             #FIXME: perhaps redundant\r
181             return type(HookeCli)("HookeCliPlugged", bases + (HookeCli,), {})\r
182         cli = make_command_class(*CLI_PLUGINS)(self.frame,self.list_of_events,events_from_gui,config,FILE_DRIVERS)\r
183         cli.cmdloop()\r
184 \r
185 '''\r
186 GUI CODE\r
187 \r
188 FIXME: put it in a separate module in the future?\r
189 '''\r
190 class MainMenuBar(wx.MenuBar):\r
191     '''\r
192     Creates the menu bar\r
193     '''\r
194     def __init__(self):\r
195         wx.MenuBar.__init__(self)\r
196         '''the menu description. the key of the menu is XX&Menu, where XX is a number telling\r
197         the order of the menus on the menubar.\r
198         &Menu is the Menu text\r
199         the corresponding argument is ('&Item', 'itemname'), where &Item is the item text and itemname\r
200         the inner reference to use in the self.menu_items dictionary.\r
201 \r
202         See create_menus() to see how it works\r
203 \r
204         Note: the mechanism on page 124 of "wxPython in Action" is less awkward, maybe, but I want\r
205         binding to be performed later. Perhaps I'm wrong :)\r
206         ''' \r
207 \r
208         self.menu_desc={'00&File':[('&Open playlist','openplaymenu'),('&Exit','exitmenu')], \r
209                         '01&Edit':[('&Export text...','exporttextmenu'),('&Export image...','exportimagemenu')],\r
210                         '02&Help':[('&About Hooke','aboutmenu')]}\r
211         self.create_menus()\r
212 \r
213     def create_menus(self):\r
214         '''\r
215         Smartish routine to create the menu from the self.menu_desc dictionary\r
216         Hope it's a workable solution for the future.\r
217         '''\r
218         self.menus=[] #the menu objects to append to the menubar\r
219         self.menu_items={} #the single menu items dictionary, to bind to events\r
220 \r
221         names=self.menu_desc.keys() #we gotta sort, because iterating keys goes in odd order\r
222         names.sort()\r
223 \r
224         for name in names:\r
225             self.menus.append(wx.Menu())\r
226             for menu_item in self.menu_desc[name]:\r
227                 self.menu_items[menu_item[1]]=self.menus[-1].Append(-1, menu_item[0])\r
228 \r
229         for menu,name in zip(self.menus,names):\r
230             self.Append(menu,name[2:])\r
231 \r
232 class MainPanel(wx.Panel):\r
233     def __init__(self,parent,id):  \r
234 \r
235         wx.Panel.__init__(self,parent,id)\r
236         self.splitter = wx.SplitterWindow(self)\r
237 \r
238 ID_FRAME=100        \r
239 class MainWindow(wx.Frame):\r
240     '''we make a frame inheriting wx.Frame and setting up things on the init'''\r
241     def __init__(self,parent,id,title):\r
242 \r
243         #-----------------------------\r
244         #WX WIDGETS INITIALIZATION\r
245 \r
246         wx.Frame.__init__(self,parent,ID_FRAME,title,size=(800,600),style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)\r
247 \r
248         self.mainpanel=MainPanel(self,-1)\r
249         self.cpanels=[]\r
250 \r
251         self.cpanels.append(wx.Panel(self.mainpanel.splitter,-1))\r
252         self.cpanels.append(wx.Panel(self.mainpanel.splitter,-1))\r
253 \r
254         self.statusbar=wx.StatusBar(self,-1)\r
255         self.SetStatusBar(self.statusbar)\r
256 \r
257         self.mainmenubar=MainMenuBar()\r
258         self.SetMenuBar(self.mainmenubar)\r
259 \r
260         self.controls=[]\r
261         self.figures=[]\r
262         self.axes=[]\r
263 \r
264         #This is our matplotlib plot\r
265         self.controls.append(wxmpl.PlotPanel(self.cpanels[0],-1))\r
266         self.controls.append(wxmpl.PlotPanel(self.cpanels[1],-1))\r
267         #These are our figure and axes, so to have easy references\r
268         #Also, we initialize\r
269         self.figures=[control.get_figure() for control in self.controls]\r
270         self.axes=[figure.gca() for figure in self.figures]\r
271 \r
272         self.cpanels[1].Hide()\r
273         self.mainpanel.splitter.Initialize(self.cpanels[0])\r
274 \r
275         self.sizer_dance() #place/size the widgets\r
276 \r
277         self.controls[0].SetSize(self.cpanels[0].GetSize())\r
278         self.controls[1].SetSize(self.cpanels[1].GetSize())\r
279 \r
280         #resize the frame to properly draw on Windows\r
281         frameSize=self.GetSize()\r
282         frameSize.DecBy(1, 1)\r
283         self.SetSize(frameSize)\r
284         '''\r
285         #if you need the exact same size as before DecBy, uncomment this block\r
286         frameSize.IncBy(1, 1)\r
287         self.SetSize(frameSize)\r
288         '''\r
289 \r
290         #-------------------------------------------\r
291         #NON-WX WIDGETS INITIALIZATION\r
292 \r
293         #Flags.\r
294         self.click_plot=0\r
295 \r
296         #FIXME: These could become a single flag with different (string?) values\r
297         #self.on_measure_distance=False\r
298         #self.on_measure_force=False\r
299 \r
300         self.plot_fit=False\r
301 \r
302         #Number of points to be clicked\r
303         self.num_of_points = 2\r
304 \r
305         #Data.\r
306         '''\r
307             self.current_x_ext=[[],[]]\r
308             self.current_y_ext=[[],[]]\r
309             self.current_x_ret=[[],[]]\r
310             self.current_y_ret=[[],[]]\r
311 \r
312 \r
313             self.current_x_unit=[None,None]\r
314             self.current_y_unit=[None,None]\r
315             '''\r
316 \r
317         #Initialize xaxes, yaxes\r
318         #FIXME: should come from config\r
319         self.current_xaxes=0\r
320         self.current_yaxes=0\r
321 \r
322         #Other\r
323 \r
324 \r
325         self.index_buffer=[]\r
326 \r
327         self.clicked_points=[]\r
328 \r
329         self.measure_set=None\r
330 \r
331         self.events_from_gui = events_from_gui\r
332 \r
333         '''\r
334             This dictionary keeps all the flags and the relative functon names that\r
335             have to be called when a point is clicked.\r
336             That is:\r
337             - if point is clicked AND foo_flag=True\r
338             - foo()\r
339 \r
340             Conversely, foo_flag is True if a corresponding event is launched by the CLI.\r
341 \r
342             self.ClickedPoints() takes care of handling this\r
343             '''\r
344 \r
345         self.click_flags_functions={'measure_points':[False, 'MeasurePoints']}\r
346 \r
347         #Binding of custom events from CLI --> GUI functions!                       \r
348         #FIXME: Should use the self.Bind() syntax\r
349         EVT_PLOT(self, self.PlotCurve)\r
350         EVT_PLOT_CONTACT(self, self.PlotContact)\r
351         EVT_GET_DISPLAYED_PLOT(self, self.OnGetDisplayedPlot)\r
352         EVT_MEASURE_POINTS(self, self.OnMeasurePoints)\r
353         EVT_EXPORT_IMAGE(self,self.ExportImage)\r
354         EVT_CLOSE_PLOT(self, self.OnClosePlot)\r
355         EVT_SHOW_PLOTS(self, self.OnShowPlots)\r
356 \r
357         #This event and control decide what happens when I click on the plot 0.\r
358         wxmpl.EVT_POINT(self, self.controls[0].GetId(), self.ClickPoint0)\r
359         wxmpl.EVT_POINT(self, self.controls[1].GetId(), self.ClickPoint1)\r
360 \r
361         #RUN PLUGIN-SPECIFIC INITIALIZATION\r
362         #make sure we execute _plug_init() for every command line plugin we import\r
363         for plugin_name in config['plugins']:\r
364             try:\r
365                 hooke_module=__import__('hooke.plugin.'+plugin_name)\r
366                 plugin = getattr(hooke_module.plugin, plugin_name)\r
367                 try:\r
368                     eval('plugin.'+plugin_name+'Gui._plug_init(self)')\r
369                     pass\r
370                 except AttributeError:\r
371                     pass\r
372             except ImportError:\r
373                 pass\r
374 \r
375 \r
376 \r
377     #WX-SPECIFIC FUNCTIONS\r
378     def sizer_dance(self):\r
379         '''\r
380             adjust size and placement of wxpython widgets.\r
381             '''\r
382         self.splittersizer = wx.BoxSizer(wx.VERTICAL)\r
383         self.splittersizer.Add(self.mainpanel.splitter, 1, wx.EXPAND)\r
384 \r
385         self.plot1sizer = wx.BoxSizer()\r
386         self.plot1sizer.Add(self.controls[0], 1, wx.EXPAND)\r
387 \r
388         self.plot2sizer = wx.BoxSizer()\r
389         self.plot2sizer.Add(self.controls[1], 1, wx.EXPAND)\r
390 \r
391         self.panelsizer=wx.BoxSizer()\r
392         self.panelsizer.Add(self.mainpanel, -1, wx.EXPAND)\r
393 \r
394         self.cpanels[0].SetSizer(self.plot1sizer)\r
395         self.cpanels[1].SetSizer(self.plot2sizer)\r
396 \r
397         self.mainpanel.SetSizer(self.splittersizer)\r
398         self.SetSizer(self.panelsizer)\r
399 \r
400     def binding_dance(self):\r
401         self.Bind(wx.EVT_MENU, self.OnOpenPlayMenu, self.menubar.menu_items['openplaymenu'])\r
402         self.Bind(wx.EVT_MENU, self.OnExitMenu, self.menubar.menu_items['exitmenu'])\r
403         self.Bind(wx.EVT_MENU, self.OnExportText, self.menubar.menu_items['exporttextmenu'])\r
404         self.Bind(wx.EVT_MENU, self.OnExportImage, self.menubar.menu_items['exportimagemenu'])\r
405         self.Bind(wx.EVT_MENU, self.OnAboutMenu, self.menubar.menu_items['aboutmenu'])\r
406 \r
407     # DOUBLE PLOT MANAGEMENT\r
408     #----------------------\r
409     def show_both(self):\r
410         '''\r
411             Shows both plots.\r
412             '''\r
413         self.mainpanel.splitter.SplitHorizontally(self.cpanels[0],self.cpanels[1])\r
414         self.mainpanel.splitter.SetSashGravity(0.5)\r
415         self.mainpanel.splitter.SetSashPosition(300) #FIXME: we should get it and restore it\r
416         self.mainpanel.splitter.UpdateSize()\r
417 \r
418     def close_plot(self,plot):\r
419         '''\r
420             Closes one plot - only if it's open\r
421             '''\r
422         if not self.cpanels[plot].IsShown():\r
423             return\r
424         if plot != 0:\r
425             self.current_plot_dest = 0\r
426         else:\r
427             self.current_plot_dest = 1\r
428         self.cpanels[plot].Hide()\r
429         self.mainpanel.splitter.Unsplit(self.cpanels[plot])\r
430         self.mainpanel.splitter.UpdateSize()\r
431 \r
432 \r
433     def OnClosePlot(self,event):\r
434         self.close_plot(event.to_close)       \r
435 \r
436     def OnShowPlots(self,event):\r
437         self.show_both()\r
438 \r
439 \r
440     #FILE MENU FUNCTIONS\r
441     #--------------------\r
442     def OnOpenPlayMenu(self, event):\r
443         pass \r
444 \r
445     def OnExitMenu(self,event):\r
446         pass\r
447 \r
448     def OnExportText(self,event):\r
449         pass\r
450 \r
451     def OnExportImage(self,event):\r
452         pass\r
453 \r
454     def OnAboutMenu(self,event):\r
455         pass\r
456 \r
457     #PLOT INTERACTION    \r
458     #----------------                        \r
459     def PlotCurve(self,event):\r
460         '''\r
461             plots the current ext,ret curve.\r
462             '''\r
463         dest=0\r
464 \r
465         #FIXME: BAD kludge following. There should be a well made plot queue mechanism, with replacements etc.\r
466         #---\r
467         #If we have only one plot in the event, we already have one in self.plots and this is a secondary plot,\r
468         #do not erase self.plots but append the new plot to it.\r
469         if len(event.plots) == 1 and event.plots[0].destination != 0 and len(self.plots) == 1:\r
470             self.plots.append(event.plots[0])\r
471         #if we already have two plots and a new secondary plot comes, we substitute the previous\r
472         if len(event.plots) == 1 and event.plots[0].destination != 0 and len(self.plots) > 1:\r
473             self.plots[1] = event.plots[0]\r
474         else:\r
475             self.plots = event.plots\r
476 \r
477         #FIXME. Should be in PlotObject, somehow\r
478         c=0\r
479         for plot in self.plots:\r
480             if self.plots[c].styles==[]:\r
481                 self.plots[c].styles=[None for item in plot.vectors] \r
482             if self.plots[c].colors==[]:\r
483                 self.plots[c].colors=[None for item in plot.vectors] \r
484 \r
485         for plot in self.plots:\r
486             '''\r
487             MAIN LOOP FOR ALL PLOTS (now only 2 are allowed but...)\r
488             '''\r
489             if 'destination' in dir(plot):\r
490                 dest=plot.destination\r
491 \r
492             #if the requested panel is not shown, show it\r
493             if not ( self.cpanels[dest].IsShown() ):\r
494                 self.show_both()\r
495 \r
496             self.axes[dest].hold(False)\r
497             self.current_vectors=plot.vectors\r
498             self.current_title=plot.title\r
499             self.current_plot_dest=dest #let's try this way to take into account the destination plot...\r
500 \r
501             c=0\r
502 \r
503             if len(plot.colors)==0:\r
504                 plot.colors=[None] * len(plot.vectors)\r
505             if len(plot.styles)==0:\r
506                 plot.styles=[None] * len(plot.vectors)     \r
507 \r
508             for vectors_to_plot in self.current_vectors: \r
509                 if plot.styles[c]=='scatter':\r
510                     if plot.colors[c]==None:\r
511                         self.axes[dest].scatter(vectors_to_plot[0], vectors_to_plot[1])\r
512                     else:\r
513                         self.axes[dest].scatter(vectors_to_plot[0], vectors_to_plot[1],color=plot.colors[c])\r
514                 else:\r
515                     if plot.colors[c]==None:\r
516                         self.axes[dest].plot(vectors_to_plot[0], vectors_to_plot[1])\r
517                     else:\r
518                         self.axes[dest].plot(vectors_to_plot[0], vectors_to_plot[1], color=plot.colors[c])\r
519                 self.axes[dest].hold(True)\r
520                 c+=1\r
521 \r
522             '''\r
523                 for vectors_to_plot in self.current_vectors:\r
524                     if len(vectors_to_plot)==2: #3d plots are to come...\r
525                         if len(plot.styles) > 0 and plot.styles[c] == 'scatter':\r
526                             self.axes[dest].scatter(vectors_to_plot[0],vectors_to_plot[1])\r
527                         elif len(plot.styles) > 0 and plot.styles[c] == 'scatter_red':\r
528                             self.axes[dest].scatter(vectors_to_plot[0],vectors_to_plot[1],color='red')\r
529                         else:\r
530                             self.axes[dest].plot(vectors_to_plot[0],vectors_to_plot[1])\r
531 \r
532                         self.axes[dest].hold(True)\r
533                         c+=1\r
534                     else:\r
535                         pass\r
536                 '''               \r
537             #FIXME: tackles only 2d plots\r
538             self.axes[dest].set_xlabel(plot.units[0])\r
539             self.axes[dest].set_ylabel(plot.units[1])\r
540 \r
541             #FIXME: set smaller fonts\r
542             self.axes[dest].set_title(plot.title)\r
543 \r
544             if plot.xaxes: \r
545                 #swap X axis\r
546                 xlim=self.axes[dest].get_xlim()\r
547                 self.axes[dest].set_xlim((xlim[1],xlim[0])) \r
548             if plot.yaxes:\r
549                 #swap Y axis\r
550                 ylim=self.axes[dest].get_ylim()        \r
551                 self.axes[dest].set_ylim((ylim[1],ylim[0])) \r
552 \r
553             self.controls[dest].draw()\r
554 \r
555 \r
556     def PlotContact(self,event):\r
557         '''\r
558             plots the contact point\r
559             DEPRECATED!\r
560             '''\r
561         self.axes[0].hold(True)\r
562         self.current_contact_index=event.contact_index\r
563 \r
564         #now we fake a clicked point \r
565         self.clicked_points.append(ClickedPoint())\r
566         self.clicked_points[-1].absolute_coords=self.current_x_ret[dest][self.current_contact_index], self.current_y_ret[dest][self.current_contact_index]\r
567         self.clicked_points[-1].is_marker=True    \r
568 \r
569         self._replot()\r
570         self.clicked_points=[]\r
571 \r
572     def OnMeasurePoints(self,event):\r
573         '''\r
574             trigger flags to measure N points\r
575             '''\r
576         self.click_flags_functions['measure_points'][0]=True\r
577         if 'num_of_points' in dir(event):\r
578             self.num_of_points=event.num_of_points\r
579         if 'set' in dir(event):    \r
580             self.measure_set=event.set            \r
581 \r
582     def ClickPoint0(self,event):\r
583         self.current_plot_dest=0\r
584         self.ClickPoint(event)\r
585     def ClickPoint1(self,event):\r
586         self.current_plot_dest=1\r
587         self.ClickPoint(event)\r
588 \r
589     def ClickPoint(self,event):\r
590         '''\r
591             this function decides what to do when we receive a left click on the axes.\r
592             We trigger other functions:\r
593             - the action chosen by the CLI sends an event\r
594             - the event raises a flag : self.click_flags_functions['foo'][0]\r
595             - the raised flag wants the function in self.click_flags_functions[1] to be called after a click\r
596             '''\r
597         for key, value in self.click_flags_functions.items():\r
598             if value[0]:\r
599                 eval('self.'+value[1]+'(event)')\r
600 \r
601 \r
602 \r
603     def MeasurePoints(self,event,current_set=1):\r
604         dest=self.current_plot_dest\r
605         try:\r
606             current_set=self.measure_set\r
607         except AttributeError:\r
608             pass\r
609 \r
610         #find the current plot matching the clicked destination\r
611         plot=self._plot_of_dest()\r
612         if len(plot.vectors)-1 < current_set: #what happens if current_set is 1 and we have only 1 vector?\r
613             current_set=current_set-len(plot.vectors)\r
614 \r
615         xvector=plot.vectors[current_set][0]\r
616         yvector=plot.vectors[current_set][1]\r
617 \r
618         self.clicked_points.append(ClickedPoint())            \r
619         self.clicked_points[-1].absolute_coords=event.xdata, event.ydata\r
620         self.clicked_points[-1].find_graph_coords(xvector,yvector)\r
621         self.clicked_points[-1].is_marker=True    \r
622         self.clicked_points[-1].is_line_edge=True\r
623         self.clicked_points[-1].dest=dest                \r
624 \r
625         self._replot()\r
626 \r
627         if len(self.clicked_points)==self.num_of_points:\r
628             self.events_from_gui.put(self.clicked_points)\r
629             #restore to default state:\r
630             self.clicked_points=[]\r
631             self.click_flags_functions['measure_points'][0]=False    \r
632 \r
633 \r
634     def OnGetDisplayedPlot(self,event):\r
635         if 'dest' in dir(event):\r
636             self.GetDisplayedPlot(event.dest)\r
637         else:\r
638             self.GetDisplayedPlot(self.current_plot_dest)\r
639 \r
640     def GetDisplayedPlot(self,dest):\r
641         '''\r
642             returns to the CLI the currently displayed plot for the given destination\r
643             '''\r
644         displayed_plot=self._plot_of_dest(dest)\r
645         events_from_gui.put(displayed_plot)\r
646 \r
647     def ExportImage(self,event):\r
648         '''\r
649             exports an image as a file.\r
650             Current supported file formats: png, eps\r
651             (matplotlib docs say that jpeg should be supported too, but with .jpg it doesn't work for me!)\r
652             '''\r
653         #dest=self.current_plot_dest\r
654         dest=event.dest\r
655         filename=event.name\r
656         self.figures[dest].savefig(filename)\r
657 \r
658     '''\r
659         def _find_nearest_point(self, mypoint, dataset=1):\r
660 \r
661             #Given a clicked point on the plot, finds the nearest point in the dataset (in X) that\r
662             #corresponds to the clicked point.\r
663 \r
664             dest=self.current_plot_dest\r
665 \r
666             xvector=plot.vectors[dataset][0]\r
667             yvector=plot.vectors[dataset][1]\r
668 \r
669             #Ye Olde sorting algorithm...\r
670             #FIXME: is there a better solution?\r
671             index=0\r
672             best_index=0\r
673             best_diff=10^9 #hope we never go over this magic number :(\r
674             for point in xvector:\r
675                 diff=abs(point-mypoint)\r
676                 if diff<best_diff:\r
677                     best_index=index\r
678                     best_diff=diff\r
679                 index+=1\r
680 \r
681             return best_index,xvector[best_index],yvector[best_index]\r
682          '''   \r
683 \r
684     def _plot_of_dest(self,dest=None):\r
685         '''\r
686             returns the plot that has the current destination\r
687             '''\r
688         if dest==None:\r
689             dest=self.current_plot_dest\r
690 \r
691         plot=None\r
692         for aplot in self.plots:\r
693             if aplot.destination == dest:\r
694                 plot=aplot\r
695         return plot\r
696 \r
697     def _replot(self):\r
698         '''\r
699             this routine is needed for a fresh clean-and-replot of interface\r
700             otherwise, refreshing works very badly :(\r
701 \r
702             thanks to Ken McIvor, wxmpl author!\r
703             '''\r
704         dest=self.current_plot_dest\r
705         #we get current zoom limits\r
706         xlim=self.axes[dest].get_xlim()\r
707         ylim=self.axes[dest].get_ylim()           \r
708         #clear axes\r
709         self.axes[dest].cla()\r
710 \r
711         #Plot curve:         \r
712         #find the current plot matching the clicked destination\r
713         plot=self._plot_of_dest()\r
714         #plot all superimposed plots \r
715         c=0 \r
716         if len(plot.colors)==0:\r
717             plot.colors=[None] * len(plot.vectors)\r
718         if len(plot.styles)==0:\r
719             plot.styles=[None] * len(plot.vectors)     \r
720         for plotset in plot.vectors: \r
721             if plot.styles[c]=='scatter':\r
722                 if plot.colors[c]==None:\r
723                     self.axes[dest].scatter(plotset[0], plotset[1])\r
724                 else:\r
725                     self.axes[dest].scatter(plotset[0], plotset[1],color=plot.colors[c])\r
726             else:\r
727                 if plot.colors[c]==None:\r
728                     self.axes[dest].plot(plotset[0], plotset[1])\r
729                 else:\r
730                     self.axes[dest].plot(plotset[0], plotset[1], color=plot.colors[c])\r
731             '''    \r
732                 if len(plot.styles) > 0 and plot.styles[c]=='scatter':\r
733                     self.axes[dest].scatter(plotset[0], plotset[1],color=plot.colors[c])\r
734                 elif len(plot.styles) > 0 and plot.styles[c] == 'scatter_red':\r
735                     self.axes[dest].scatter(plotset[0],plotset[1],color='red')\r
736                 else:\r
737                     self.axes[dest].plot(plotset[0], plotset[1])\r
738                 '''\r
739             c+=1\r
740         #plot points we have clicked\r
741         for item in self.clicked_points:\r
742             if item.is_marker:\r
743                 if item.graph_coords==(None,None): #if we have no graph coords, we display absolute coords\r
744                     self.axes[dest].scatter([item.absolute_coords[0]],[item.absolute_coords[1]])\r
745                 else:\r
746                     self.axes[dest].scatter([item.graph_coords[0]],[item.graph_coords[1]])               \r
747 \r
748         if self.plot_fit:\r
749             print 'DEBUGGING WARNING: use of self.plot_fit is deprecated!'\r
750             self.axes[dest].plot(self.plot_fit[0],self.plot_fit[1])\r
751 \r
752         self.axes[dest].hold(True)      \r
753         #set old axes again\r
754         self.axes[dest].set_xlim(xlim)\r
755         self.axes[dest].set_ylim(ylim)\r
756         #set title and names again...\r
757         self.axes[dest].set_title(self.current_title)           \r
758         self.axes[dest].set_xlabel(plot.units[0])\r
759         self.axes[dest].set_ylabel(plot.units[1])\r
760         #and redraw!\r
761         self.controls[dest].draw()\r
762 \r
763 \r
764 class MySplashScreen(wx.SplashScreen):\r
765     """\r
766     Create a splash screen widget.\r
767     That's just a fancy addition... every serious application has a splash screen!\r
768     """\r
769     def __init__(self, frame):\r
770         # This is a recipe to a the screen.\r
771         # Modify the following variables as necessary.\r
772         #aBitmap = wx.Image(name = "wxPyWiki.jpg").ConvertToBitmap()\r
773         aBitmap=wx.Image(name=os.path.join(\r
774                 config['install']['docpath'],\r
775                 'hooke.jpg')).ConvertToBitmap()\r
776         splashStyle = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT\r
777         splashDuration = 2000 # milliseconds\r
778         splashCallback = None\r
779         # Call the constructor with the above arguments in exactly the\r
780         # following order.\r
781         wx.SplashScreen.__init__(self, aBitmap, splashStyle,\r
782                                  splashDuration, None, -1)\r
783         wx.EVT_CLOSE(self, self.OnExit)\r
784         self.frame=frame\r
785         wx.Yield()\r
786 \r
787     def OnExit(self, evt):\r
788         self.Hide()\r
789 \r
790         self.frame.Show()\r
791         # The program will freeze without this line.\r
792         evt.Skip()  # Make sure the default handler runs too...\r
793 \r
794 \r
795 #------------------------------------------------------------------------------\r
796 \r
797 def main():\r
798     app=wx.PySimpleApp()\r
799 \r
800     def make_gui_class(*bases):\r
801         return type(MainWindow)("MainWindowPlugged", bases + (MainWindow,), {})\r
802 \r
803     main_frame = make_gui_class(*GUI_PLUGINS)(None, -1, ('Hooke '+__version__))\r
804 \r
805     #FIXME. The frame.Show() is called by the splashscreen here! Ugly as hell.\r
806 \r
807     mysplash=MySplashScreen(main_frame)\r
808     mysplash.Show()\r
809 \r
810     my_cmdline=CliThread(main_frame, list_of_events)\r
811     my_cmdline.start()\r
812 \r
813     app.MainLoop()\r
814 \r
815 if __name__ == '__main__':\r
816     main()\r