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
133
134 return ds.relax_gui.analyses[self._current]
135
136
138 """Return the name of the current analysis.
139
140 @return: The name of the current analysis.
141 @rtype: str
142 """
143
144
145 if self._current == None:
146 return
147
148
149 return ds.relax_gui.analyses[self._current].analysis_name
150
151
153 """Return the type of the current analysis.
154
155 @return: The type of the current analysis.
156 @rtype: str
157 """
158
159
160 if self._current == None:
161 return
162
163
164 return ds.relax_gui.analyses[self._current].analysis_type
165
166
168 """Remove all analyses."""
169
170
171 if status.debug:
172 fn_name = sys._getframe().f_code.co_name
173 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__
174 class_name = self.__class__.__name__
175 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name)
176 print("\n\n")
177 print("debug> %s: Deleting all analyses." % full_name)
178
179
180 if status.debug:
181 print("debug> %s: Unregistering all methods with the observer objects." % full_name)
182 for i in range(self._num_analyses):
183 self._analyses[i].observer_register(remove=True)
184
185
186 while self._num_analyses:
187
188
189
190
191 if status.debug:
192 print("debug> %s: Deleting the analysis at index %s." % (full_name, self._num_analyses-1))
193 self.delete_analysis(self._num_analyses-1)
194
195
196 if status.debug:
197 print("debug> %s: All analyses now deleted." % full_name)
198 status.observers.gui_analysis.notify()
199
200
202 """Delete the analysis tab and data store corresponding to the index.
203
204 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.
205
206
207 @param index: The index of the analysis to delete.
208 @type index: int
209 """
210
211
212 if status.debug:
213 fn_name = sys._getframe().f_code.co_name
214 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__
215 class_name = self.__class__.__name__
216 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name)
217 print("\n\n")
218 print("debug> %s: Deleting the analysis at index %s." % (full_name, index))
219
220
221 self._num_analyses -= 1
222
223
224 if self._current > index:
225 self._current -= 1
226 if status.debug:
227 print("debug> %s: Switching the current analysis to index %s." % (full_name, self._current))
228
229
230 if hasattr(self._analyses[index], 'delete'):
231 if status.debug:
232 print("debug> %s: Executing the analysis specific delete() method." % full_name)
233 self._analyses[index].delete()
234
235
236 if status.debug:
237 print("debug> %s: Deleting the notebook page." % full_name)
238 self.notebook.DeletePage(index)
239
240
241 if status.debug:
242 print("debug> %s: Deleting the analysis GUI object." % full_name)
243 self._analyses.pop(index)
244
245
246 pipe_bundle = ds.relax_gui.analyses[index].pipe_bundle
247
248
249 if status.debug:
250 print("debug> %s: Deleting the data store object." % full_name)
251 ds.relax_gui.analyses.pop(index)
252
253
254 for pipe in pipes.pipe_names():
255 if pipes.get_bundle(pipe) == pipe_bundle:
256 if status.debug:
257 print("debug> %s: Deleting the data pipe '%s' from the '%s' bundle." % (full_name, pipe, pipe_bundle))
258 pipes.delete(pipe)
259
260
261 if self._num_analyses == 0:
262 if status.debug:
263 print("debug> %s: Setting the initial state." % full_name)
264 self.set_init_state()
265
266
267 elif index == self._current:
268
269 page_index = self._current
270
271
272 if self._num_analyses <= self._current:
273 page_index = self._current - 1
274
275
276 if status.debug:
277 print("debug> %s: Switching to page %s." % (full_name, page_index))
278 self.switch_page(page_index)
279
280
281 status.observers.gui_analysis.notify()
282
283
284 - def get_page_from_name(self, name):
285 """Return the page corresponding to the given name.
286
287 @return: The page which matches the given name, or nothing otherwise.
288 @rtype: wx.Frame object or None
289 """
290
291
292 found = False
293 for index in range(self._num_analyses):
294
295 if name == ds.relax_gui.analyses[index].analysis_name:
296 found = True
297 break
298
299
300 if not found:
301 return
302
303
304 return self._analyses[index]
305
306
308 """Recreate the analyses from the relax data store."""
309
310
311 if not hasattr(ds, 'relax_gui'):
312 return
313
314
315 map = {
316 'NOE': 'noe',
317 'R1': 'r1',
318 'R2': 'r2',
319 'model-free': 'mf',
320 'Relax-disp': 'relax_disp'
321 }
322
323
324 for i in range(len(ds.relax_gui.analyses)):
325
326 if hasattr(ds.relax_gui.analyses[i], 'analysis_name'):
327 analysis_name = ds.relax_gui.analyses[i].analysis_name
328 elif ds.relax_gui.analyses[i].analysis_type == 'NOE':
329 analysis_name = 'Steady-state NOE'
330 elif ds.relax_gui.analyses[i].analysis_type == 'R1':
331 analysis_name = 'R1 relaxation'
332 elif ds.relax_gui.analyses[i].analysis_type == 'R2':
333 analysis_name = 'R2 relaxation'
334 elif ds.relax_gui.analyses[i].analysis_type == 'model-free':
335 analysis_name = 'Model-free'
336 elif ds.relax_gui.analyses[i].analysis_type == 'Relax-disp':
337 analysis_name = 'Relaxation dispersion'
338
339
340 if not hasattr(ds.relax_gui.analyses[i], 'pipe_bundle'):
341
342 ds.relax_gui.analyses[i].pipe_bundle = ds.relax_gui.analyses[i].pipe_name
343
344
345 self.gui.interpreter.apply('pipe.bundle', pipe=ds.relax_gui.analyses[i].pipe_name, bundle=ds.relax_gui.analyses[i].pipe_name)
346
347
348 self._switch_flag = False
349 self.new_analysis(map[ds.relax_gui.analyses[i].analysis_type], analysis_name, index=i)
350
351
352 self.pipe_switch()
353
354
355 self._switch_flag = True
356
357
358 status.observers.gui_analysis.notify()
359
360
362 """Close the currently opened analysis.
363
364 @param event: The wx event.
365 @type event: wx event
366 """
367
368
369 if not hasattr(self, 'notebook'):
370 return
371
372
373 if status.exec_lock.locked():
374 return
375
376
377 index = self.notebook.GetSelection()
378
379
380 msg = "Are you sure you would like to close the current %s analysis tab?" % ds.relax_gui.analyses[index].analysis_type
381 if status.show_gui and Question(msg, title="Close current analysis", size=(350, 140), default=False).ShowModal() == wx.ID_NO:
382 return
383
384
385 self.delete_analysis(index)
386
387
389 """Close all analyses.
390
391 @param event: The wx event.
392 @type event: wx event
393 """
394
395
396 if not hasattr(self, 'notebook'):
397 return
398
399
400 if status.exec_lock.locked():
401 return
402
403
404 msg = "Are you sure you would like to close all analyses? All data will be erased and the relax data store reset."
405 if status.show_gui and Question(msg, title="Close all analyses", size=(350, 150), default=False).ShowModal() == wx.ID_NO:
406 return
407
408
409 self.delete_all()
410
411
412 reset()
413
414
442
443
444 - def new_analysis(self, analysis_type=None, analysis_name=None, pipe_name=None, pipe_bundle=None, uf_exec=[], index=None):
445 """Initialise a new analysis.
446
447 @keyword analysis_type: The type of analysis to initialise. This can be one of 'noe', 'r1', 'r2', 'mf' or 'relax_disp'.
448 @type analysis_type: str
449 @keyword analysis_name: The name of the analysis to initialise.
450 @type analysis_name: str
451 @keyword pipe_name: The name of the original data pipe to create for the analysis.
452 @type pipe_name: str
453 @keyword pipe_bundle: The name of the data pipe bundle to associate with this analysis.
454 @type pipe_bundle: str
455 @keyword uf_exec: The list of user function on_execute methods returned from the new analysis wizard.
456 @type uf_exec: list of methods
457 @keyword index: The index of the analysis in the relax data store (set to None if no data currently exists).
458 @type index: None or int
459 """
460
461
462 if analysis_type in ['r1', 'r2'] and not dep_check.C_module_exp_fn:
463 error_message("Relaxation curve fitting is not available. Try compiling the C modules on your platform.")
464 return
465
466
467 if analysis_type == 'relax_disp' and not dep_check.C_module_exp_fn:
468 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.")
469
470
471 wx.Yield()
472 wx.BeginBusyCursor()
473 self.gui.Freeze()
474
475
476 if self.init_state:
477
478 sizer = wx.BoxSizer(wx.VERTICAL)
479
480
481 self.notebook = wx.Notebook(self.gui, -1, style=wx.NB_TOP)
482 sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 0)
483
484
485 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.on_page_changing)
486 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed)
487
488
489 old_sizer = self.gui.GetSizer()
490 old_sizer.DeleteWindows()
491
492
493 self.gui.SetSizer(sizer)
494 sizer.Layout()
495
496
497 classes = {
498 'noe': Auto_noe,
499 'r1': Auto_r1,
500 'r2': Auto_r2,
501 'mf': Auto_model_free,
502 'relax_disp': Auto_relax_disp
503 }
504
505
506 if analysis_type not in classes.keys():
507 raise RelaxError("The analysis '%s' is unknown." % analysis_type)
508
509
510 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)
511
512
513 if not analysis.init_flag:
514
515 if self.init_state:
516 self.set_init_state()
517
518
519 return
520
521
522 self._analyses.append(analysis)
523
524
525 self.notebook.AddPage(self._analyses[-1], analysis_name)
526
527
528 self._num_analyses += 1
529
530
531 if self._switch_flag:
532 self.switch_page(self._num_analyses-1)
533
534
535 self.init_state = False
536
537
538 self.gui.Layout()
539
540
541 self.gui.Thaw()
542 if wx.IsBusy():
543 wx.EndBusyCursor()
544
545
546 status.observers.gui_analysis.notify()
547
548
549 - def on_page_changing(self, event):
550 """Block page changing if needed.
551
552 @param event: The wx event.
553 @type event: wx event
554 """
555
556
557 if status.exec_lock.locked():
558
559 error_message("Cannot change analyses, relax is currently executing.", "relax execution lock")
560
561
562 event.Veto()
563
564
565 - def on_page_changed(self, event):
566 """Handle page changes.
567
568 @param event: The wx event.
569 @type event: wx event
570 """
571
572
573 self._current = event.GetSelection()
574
575
576 if ds.is_empty():
577 return
578
579
580 if self._switch_flag and pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name:
581 self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name)
582
583
584 event.Skip()
585
586
587 status.observers.gui_analysis.notify()
588
589
590 - def page_index_from_bundle(self, bundle):
591 """Find the analysis associated with the data pipe bundle and return its page index.
592
593 @param bundle: The data pipe bundle to find the page of.
594 @type bundle: str
595 @return: The page index.
596 @rtype: int or None
597 """
598
599
600 index = None
601 for i in range(self._num_analyses):
602
603 if ds.relax_gui.analyses[i].pipe_bundle == bundle:
604 index = i
605 break
606
607
608 return index
609
610
611 - def page_name_from_bundle(self, bundle):
612 """Find the analysis associated with the bundle and return its name.
613
614 @param bundle: The data pipe bundle to find the page of.
615 @type bundle: str
616 @return: The page name.
617 @rtype: str or None
618 """
619
620
621 index = self.page_index_from_bundle(bundle)
622
623
624 if index == None:
625 return
626
627
628 return ds.relax_gui.analyses[index].analysis_name
629
630
646
647
649 """Switch the page to the given or current data pipe.
650
651 @keyword pipe: The pipe associated with the page to switch to. If not supplied, the current data pipe will be used.
652 @type pipe: str or None
653 """
654
655
656 if pipe == None:
657 pipe = pipes.cdp_name()
658
659
660 if pipe == None:
661 return
662
663
664 index = self.page_index_from_bundle(pipes.get_bundle(pipe))
665
666
667 if index == None:
668 return
669
670
671 if self._current == index:
672 return
673
674
675 self.switch_page(index)
676
677
678 status.observers.gui_analysis.notify()
679
680
681 - def post_reset(self):
682 """Post relax data store reset event handler."""
683
684
685 if status.debug:
686 fn_name = sys._getframe().f_code.co_name
687 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__
688 class_name = self.__class__.__name__
689 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name)
690 print("\n\n")
691 print("debug> %s: Deleting all analyses." % full_name)
692
693
694 if status.debug:
695 print("debug> %s: Unregistering all methods with the observer objects." % full_name)
696 for i in range(self._num_analyses):
697 self._analyses[i].observer_register(remove=True)
698
699
700 while self._num_analyses:
701
702 index = self._num_analyses - 1
703
704
705 if hasattr(self, 'notebook'):
706 self.notebook.DeletePage(index)
707
708
709 self._analyses.pop(index)
710
711
712 self._num_analyses -= 1
713
714
715 self.set_init_state()
716
717
719 """Revert to the initial state."""
720
721
722 self.init_state = True
723 self._current = None
724
725
726 old_sizer = self.gui.GetSizer()
727 old_sizer.DeleteWindows()
728
729
730 if hasattr(self, 'notebook'):
731 del self.notebook
732
733
734 if not hasattr(ds, 'relax_gui'):
735 ds.relax_gui = Gui()
736
737
738 self.gui.add_start_screen()
739
740
741 - def switch_page(self, index):
742 """Switch to the given page.
743
744 @param index: The index of the page to switch to.
745 @type index: int
746 """
747
748
749 self._current = index
750
751
752 if pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name:
753 self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name)
754
755
756 wx.CallAfter(self.notebook.SetSelection, self._current)
757
758
759 wx.CallAfter(status.observers.gui_analysis.notify)
760