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