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