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