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