1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20   
 21   
 22   
 23  """Package for the automatic and custom analysis GUI elements.""" 
 24   
 25   
 26  import inspect 
 27  import sys 
 28  import wx 
 29  from types import ListType 
 30   
 31   
 32  from data_store import Relax_data_store; ds = Relax_data_store() 
 33  from data_store.gui import Gui 
 34  import dep_check 
 35  from gui.analyses.auto_model_free import Auto_model_free 
 36  from gui.analyses.auto_noe import Auto_noe 
 37  from gui.analyses.auto_r1 import Auto_r1 
 38  from gui.analyses.auto_r2 import Auto_r2 
 39  from gui.analyses.auto_relax_disp import Auto_relax_disp 
 40  from gui.analyses.wizard import Analysis_wizard 
 41  from gui.message import error_message, Question 
 42  from lib.errors import RelaxError 
 43  from pipe_control import pipes 
 44  from pipe_control.reset import reset 
 45  from status import Status; status = Status() 
 46   
 47   
 48   
 49  __all__ = ['auto_model_free', 
 50             'auto_noe', 
 51             'auto_r1', 
 52             'auto_r2', 
 53             'auto_relax_disp', 
 54             'auto_rx_base', 
 55             'base', 
 56             'elements', 
 57             'relax_control', 
 58             'results_analysis', 
 59             'results'] 
 60   
 61   
 63      """Class for controlling all aspects of analyses.""" 
 64   
 66          """Initialise the analysis controller. 
 67   
 68          @param gui:         The gui object. 
 69          @type gui:          wx object 
 70          """ 
 71   
 72           
 73          self.gui = gui 
 74   
 75           
 76          self.init_state = True 
 77          self._current = None 
 78          self._num_analyses = 0 
 79          self._switch_flag = True 
 80   
 81           
 82          self._analyses = [] 
 83   
 84           
 85          self.name = 'notebook page switcher' 
 86          status.observers.pipe_alteration.register(self.name, self.pipe_switch, method_name='pipe_switch') 
 87   
 88           
 89          status.observers.pipe_alteration.register('notebook pipe deletion', self.pipe_deletion, method_name='pipe_deletion') 
 90   
 91           
 92          status.observers.reset.register('gui analyses', self.post_reset, method_name='post_reset') 
 93   
 94           
 95          status.observers.state_load.register('gui analyses', self.load_from_store, method_name='load_from_store') 
  96   
 97   
 99          """Loop over the analyses, yielding the data objects. 
100   
101          @return:    The analysis data object from the relax data store. 
102          @rtype:     data.gui.Analyses instance 
103          """ 
104   
105           
106          for i in range(self._num_analyses): 
107              yield ds.relax_gui.analyses[i] 
 108   
109   
111          """Loop over the analyses, yielding the page objects. 
112   
113          @return:    The page object. 
114          @rtype:     wx.Frame object 
115          """ 
116   
117           
118          for i in range(self._num_analyses): 
119              yield self._analyses[i] 
 120   
121   
123          """Return the data container of the current analysis from the relax data store. 
124   
125          @return:    The data container of the current analysis. 
126          @rtype:     str 
127          """ 
128   
129           
130          if self._current == None: 
131              return 
132          if len(ds.relax_gui.analyses) == 0: 
133              return 
134   
135           
136          return ds.relax_gui.analyses[self._current] 
 137   
138   
140          """Return the name of the current analysis. 
141   
142          @return:    The name of the current analysis. 
143          @rtype:     str 
144          """ 
145   
146           
147          if self._current == None: 
148              return 
149          if len(ds.relax_gui.analyses) == 0: 
150              return 
151   
152           
153          return ds.relax_gui.analyses[self._current].analysis_name 
 154   
155   
157          """Return the type of the current analysis. 
158   
159          @return:    The type of the current analysis. 
160          @rtype:     str 
161          """ 
162   
163           
164          if self._current == None: 
165              return 
166          if len(ds.relax_gui.analyses) == 0: 
167              return 
168   
169           
170          return ds.relax_gui.analyses[self._current].analysis_type 
 171   
172   
174          """Remove all analyses.""" 
175   
176           
177          if status.debug: 
178              fn_name = sys._getframe().f_code.co_name 
179              mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 
180              class_name = self.__class__.__name__ 
181              full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 
182              print("\n\n") 
183              print("debug> %s:  Deleting all analyses." % full_name) 
184   
185           
186          if status.debug: 
187              print("debug> %s:  Unregistering all methods with the observer objects." % full_name) 
188          for i in range(self._num_analyses): 
189              self._analyses[i].observer_register(remove=True) 
190   
191           
192          while self._num_analyses: 
193               
194               
195   
196               
197              if status.debug: 
198                  print("debug> %s:  Deleting the analysis at index %s." % (full_name, self._num_analyses-1)) 
199              self.delete_analysis(self._num_analyses-1, reset=reset) 
200   
201           
202          if status.debug: 
203              print("debug> %s:  All analyses now deleted." % full_name) 
204          status.observers.gui_analysis.notify() 
 205   
206   
208          """Delete the analysis tab and data store corresponding to the index. 
209   
210          The order of these operations is very important due to the notification of observer objects and the updates, synchronisations, etc. that follow.  If the program debugging mode is on, then printouts at each stage will occur to allow the following of the code and observer object notifications. 
211   
212   
213          @param index:   The index of the analysis to delete. 
214          @type index:    int 
215          """ 
216   
217           
218          if status.debug: 
219              fn_name = sys._getframe().f_code.co_name 
220              mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 
221              class_name = self.__class__.__name__ 
222              full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 
223              print("\n\n") 
224              print("debug> %s:  Deleting the analysis at index %s." % (full_name, index)) 
225   
226           
227          self._num_analyses -= 1 
228   
229           
230          if self._current > index: 
231              self._current -= 1 
232              if status.debug: 
233                  print("debug> %s:  Switching the current analysis to index %s." % (full_name, self._current)) 
234   
235           
236          if hasattr(self._analyses[index], 'delete'): 
237              if status.debug: 
238                  print("debug> %s:  Executing the analysis specific delete() method." % full_name) 
239              self._analyses[index].delete() 
240          wx.Yield() 
241   
242           
243          if status.debug: 
244              print("debug> %s:  Deleting the notebook page." % full_name) 
245          self.notebook.DeletePage(index) 
246   
247           
248          if status.debug: 
249              print("debug> %s:  Deleting the analysis GUI object." % full_name) 
250          self._analyses.pop(index) 
251   
252           
253          if not reset: 
254               
255              pipe_bundle = ds.relax_gui.analyses[index].pipe_bundle 
256   
257               
258              if status.debug: 
259                  print("debug> %s:  Deleting the data store object." % full_name) 
260              ds.relax_gui.analyses.pop(index) 
261   
262               
263              for pipe in pipes.pipe_names(): 
264                  if pipes.get_bundle(pipe) == pipe_bundle: 
265                      if status.debug: 
266                          print("debug> %s:  Deleting the data pipe '%s' from the '%s' bundle." % (full_name, pipe, pipe_bundle)) 
267                      pipes.delete(pipe) 
268   
269           
270          if self._num_analyses == 0: 
271              if status.debug: 
272                  print("debug> %s:  Setting the initial state." % full_name) 
273              self.set_init_state() 
274   
275           
276          elif index == self._current: 
277               
278              page_index = self._current 
279   
280               
281              if self._num_analyses <= self._current: 
282                  page_index = self._current - 1 
283   
284               
285              if status.debug: 
286                  print("debug> %s:  Switching to page %s." % (full_name, page_index)) 
287              self.switch_page(page_index) 
288   
289           
290          status.observers.gui_analysis.notify() 
 291   
292   
293 -    def get_page_from_name(self, name): 
 294          """Return the page corresponding to the given name. 
295   
296          @return:    The page which matches the given name, or nothing otherwise. 
297          @rtype:     wx.Frame object or None 
298          """ 
299   
300           
301          found = False 
302          for index in range(self._num_analyses): 
303               
304              if name == ds.relax_gui.analyses[index].analysis_name: 
305                  found = True 
306                  break 
307   
308           
309          if not found: 
310              return 
311   
312           
313          return self._analyses[index] 
 314   
315   
317          """Recreate the analyses from the relax data store.""" 
318   
319           
320          if not hasattr(ds, 'relax_gui'): 
321              return 
322   
323           
324          map = { 
325              'NOE': 'noe', 
326              'R1': 'r1', 
327              'R2': 'r2', 
328              'model-free': 'mf', 
329              'Relax-disp': 'relax_disp' 
330          } 
331   
332           
333          for i in range(len(ds.relax_gui.analyses)): 
334               
335              if hasattr(ds.relax_gui.analyses[i], 'analysis_name'): 
336                  analysis_name = ds.relax_gui.analyses[i].analysis_name 
337              elif ds.relax_gui.analyses[i].analysis_type == 'NOE': 
338                  analysis_name = 'Steady-state NOE' 
339              elif ds.relax_gui.analyses[i].analysis_type == 'R1': 
340                  analysis_name = 'R1 relaxation' 
341              elif ds.relax_gui.analyses[i].analysis_type == 'R2': 
342                  analysis_name = 'R2 relaxation' 
343              elif ds.relax_gui.analyses[i].analysis_type == 'model-free': 
344                  analysis_name = 'Model-free' 
345              elif ds.relax_gui.analyses[i].analysis_type == 'Relax-disp': 
346                  analysis_name = 'Relaxation dispersion' 
347   
348               
349              if not hasattr(ds.relax_gui.analyses[i], 'pipe_bundle'): 
350                   
351                  ds.relax_gui.analyses[i].pipe_bundle = ds.relax_gui.analyses[i].pipe_name 
352   
353                   
354                  self.gui.interpreter.apply('pipe.bundle', pipe=ds.relax_gui.analyses[i].pipe_name, bundle=ds.relax_gui.analyses[i].pipe_name) 
355   
356               
357              self._switch_flag = False 
358              self.new_analysis(map[ds.relax_gui.analyses[i].analysis_type], analysis_name, index=i) 
359   
360           
361          self.pipe_switch() 
362   
363           
364          self._switch_flag = True 
365   
366           
367          status.observers.gui_analysis.notify() 
 368   
369   
371          """Close the currently opened analysis. 
372   
373          @param event:   The wx event. 
374          @type event:    wx event 
375          """ 
376   
377           
378          if not hasattr(self, 'notebook'): 
379              return 
380   
381           
382          if status.exec_lock.locked(): 
383              return 
384   
385           
386          index = self.notebook.GetSelection() 
387   
388           
389          msg = "Are you sure you would like to close the current %s analysis tab?" % ds.relax_gui.analyses[index].analysis_type 
390          if status.show_gui and Question(msg, title="Close current analysis", size=(350, 140), default=False).ShowModal() == wx.ID_NO: 
391              return 
392   
393           
394          self.delete_analysis(index) 
 395   
396   
398          """Close all analyses. 
399   
400          @param event:   The wx event. 
401          @type event:    wx event 
402          """ 
403   
404           
405          if not hasattr(self, 'notebook'): 
406              return 
407   
408           
409          if status.exec_lock.locked(): 
410              return 
411   
412           
413          msg = "Are you sure you would like to close all analyses?  All data will be erased and the relax data store reset." 
414          if status.show_gui and Question(msg, title="Close all analyses", size=(350, 150), default=False).ShowModal() == wx.ID_NO: 
415              return 
416   
417           
418          self.delete_all() 
419   
420           
421          reset() 
 422   
423   
425          """Launch a wizard to select the new analysis. 
426   
427          @param event:       The wx event. 
428          @type event:        wx event 
429          @keyword destroy:   A flag which if True will cause the analysis wizard to be destroyed.  This is used for the test suite. 
430          @type destroy:      bool 
431          """ 
432   
433           
434          if status.exec_lock.locked(): 
435              return 
436   
437           
438          self.new_wizard = Analysis_wizard() 
439          data = self.new_wizard.run() 
440   
441           
442          if destroy: 
443              wx.Yield() 
444              self.new_wizard.Destroy() 
445   
446           
447          if data == None: 
448              return 
449   
450           
451          analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec = data 
452   
453           
454          self.new_analysis(analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec) 
 455   
456   
457 -    def new_analysis(self, analysis_type=None, analysis_name=None, pipe_name=None, pipe_bundle=None, uf_exec=[], index=None): 
 458          """Initialise a new analysis. 
459   
460          @keyword analysis_type: The type of analysis to initialise.  This can be one of 'noe', 'r1', 'r2', 'mf' or 'relax_disp'. 
461          @type analysis_type:    str 
462          @keyword analysis_name: The name of the analysis to initialise. 
463          @type analysis_name:    str 
464          @keyword pipe_name:     The name of the original data pipe to create for the analysis. 
465          @type pipe_name:        str 
466          @keyword pipe_bundle:   The name of the data pipe bundle to associate with this analysis. 
467          @type pipe_bundle:      str 
468          @keyword uf_exec:       The list of user function on_execute methods returned from the new analysis wizard. 
469          @type uf_exec:          list of methods 
470          @keyword index:         The index of the analysis in the relax data store (set to None if no data currently exists). 
471          @type index:            None or int 
472          """ 
473   
474           
475          if analysis_type in ['r1', 'r2'] and not dep_check.C_module_exp_fn: 
476              error_message("Relaxation curve fitting is not available.  Try compiling the C modules on your platform.") 
477              return 
478   
479           
480          if analysis_type == 'relax_disp' and not dep_check.C_module_exp_fn: 
481              error_message("Relaxation curve fitting will not available for this dispersion analysis.  Try compiling the C modules on your platform if you have measured full exponential curves.") 
482   
483           
484          wx.Yield() 
485          wx.BeginBusyCursor() 
486          self.gui.Freeze() 
487   
488           
489          if self.init_state: 
490               
491              sizer = wx.BoxSizer(wx.VERTICAL) 
492   
493               
494              self.notebook = wx.Notebook(self.gui, -1, style=wx.NB_TOP) 
495              sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 0) 
496   
497               
498              self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.on_page_changing) 
499              self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed) 
500   
501               
502              old_sizer = self.gui.GetSizer() 
503              old_sizer.DeleteWindows() 
504   
505               
506              self.gui.SetSizer(sizer) 
507              sizer.Layout() 
508   
509           
510          classes = { 
511              'noe': Auto_noe, 
512              'r1':  Auto_r1, 
513              'r2':  Auto_r2, 
514              'mf':  Auto_model_free, 
515              'relax_disp':  Auto_relax_disp 
516          } 
517   
518           
519          if analysis_type not in classes: 
520              raise RelaxError("The analysis '%s' is unknown." % analysis_type) 
521   
522           
523          analysis = classes[analysis_type](parent=self.notebook, id=-1, gui=self.gui, analysis_name=analysis_name, pipe_name=pipe_name, pipe_bundle=pipe_bundle, uf_exec=uf_exec, data_index=index) 
524   
525           
526          if not analysis.init_flag: 
527               
528              if self.init_state: 
529                  self.set_init_state() 
530   
531               
532              return 
533   
534           
535          self._analyses.append(analysis) 
536   
537           
538          self.notebook.AddPage(self._analyses[-1], analysis_name) 
539   
540           
541          self._num_analyses += 1 
542   
543           
544          if self._switch_flag: 
545              self.switch_page(self._num_analyses-1) 
546   
547           
548          self.init_state = False 
549   
550           
551          self.gui.Layout() 
552   
553           
554          self.gui.Thaw() 
555          if wx.IsBusy(): 
556              wx.EndBusyCursor() 
557   
558           
559          status.observers.gui_analysis.notify() 
 560   
561   
562 -    def on_page_changing(self, event): 
 563          """Block page changing if needed. 
564   
565          @param event:   The wx event. 
566          @type event:    wx event 
567          """ 
568   
569           
570          if status.exec_lock.locked(): 
571               
572              error_message("Cannot change analyses, relax is currently executing.", "relax execution lock") 
573   
574               
575              event.Veto() 
 576   
577   
578 -    def on_page_changed(self, event): 
 579          """Handle page changes. 
580   
581          @param event:   The wx event. 
582          @type event:    wx event 
583          """ 
584   
585           
586          self._current = event.GetSelection() 
587   
588           
589          if ds.is_empty(): 
590              return 
591   
592           
593          if self._switch_flag and pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name: 
594              self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name) 
595   
596           
597          event.Skip() 
598   
599           
600          status.observers.gui_analysis.notify() 
 601   
602   
603 -    def page_index_from_bundle(self, bundle): 
 604          """Find the analysis associated with the data pipe bundle and return its page index. 
605   
606          @param bundle:  The data pipe bundle to find the page of. 
607          @type bundle:   str 
608          @return:        The page index. 
609          @rtype:         int or None 
610          """ 
611   
612           
613          index = None 
614          for i in range(self._num_analyses): 
615               
616              if ds.relax_gui.analyses[i].pipe_bundle == bundle: 
617                  index = i 
618                  break 
619   
620           
621          return index 
 622   
623   
624 -    def page_name_from_bundle(self, bundle): 
 625          """Find the analysis associated with the bundle and return its name. 
626   
627          @param bundle:  The data pipe bundle to find the page of. 
628          @type bundle:   str 
629          @return:        The page name. 
630          @rtype:         str or None 
631          """ 
632   
633           
634          index = self.page_index_from_bundle(bundle) 
635   
636           
637          if index == None: 
638              return 
639   
640           
641          return ds.relax_gui.analyses[index].analysis_name 
 642   
643   
659   
660   
662          """Switch the page to the given or current data pipe. 
663   
664          @keyword pipe:  The pipe associated with the page to switch to.  If not supplied, the current data pipe will be used. 
665          @type pipe:     str or None 
666          """ 
667   
668           
669          if pipe == None: 
670              pipe = pipes.cdp_name() 
671   
672           
673          if pipe == None: 
674              return 
675   
676           
677          index = self.page_index_from_bundle(pipes.get_bundle(pipe)) 
678   
679           
680          if index == None: 
681              return 
682   
683           
684          if self._current == index: 
685              return 
686   
687           
688          self.switch_page(index) 
689   
690           
691          status.observers.gui_analysis.notify() 
 692   
693   
694 -    def post_reset(self): 
 695          """Post relax data store reset event handler.""" 
696   
697           
698          self.delete_all(reset=True) 
 699   
700   
702          """Revert to the initial state.""" 
703   
704           
705          self.init_state = True 
706          self._current = None 
707   
708           
709          old_sizer = self.gui.GetSizer() 
710          old_sizer.DeleteWindows() 
711   
712           
713          if hasattr(self, 'notebook'): 
714              del self.notebook 
715   
716           
717          if not hasattr(ds, 'relax_gui'): 
718              ds.relax_gui = Gui() 
719   
720           
721          self.gui.add_start_screen() 
 722   
723   
724 -    def switch_page(self, index): 
 725          """Switch to the given page. 
726   
727          @param index:   The index of the page to switch to. 
728          @type index:    int 
729          """ 
730   
731           
732          self._current = index 
733   
734           
735          if pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name: 
736              self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name) 
737   
738           
739          wx.CallAfter(self.notebook.SetSelection, self._current) 
740   
741           
742          wx.CallAfter(status.observers.gui_analysis.notify) 
  743