1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 """Package for the automatic and custom analysis GUI elements."""
25
26
27 import inspect
28 import sys
29 import wx
30 from types import ListType
31
32
33 from data import Relax_data_store; ds = Relax_data_store()
34 from data.gui import Gui
35 import dep_check
36 from generic_fns import pipes
37 from relax_errors import RelaxError
38 from status import Status; status = Status()
39
40
41 from gui.analyses.auto_model_free import Auto_model_free
42 from gui.analyses.auto_noe import Auto_noe
43 from gui.analyses.auto_r1 import Auto_r1
44 from gui.analyses.auto_r2 import Auto_r2
45 from gui.analyses.wizard import Analysis_wizard
46 from gui.message import error_message, Question
47
48
49
50 __all__ = ['auto_model_free',
51 'auto_noe',
52 'auto_r1',
53 'auto_r2',
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)
87
88
89 status.observers.pipe_alteration.register('notebook pipe deletion', self.pipe_deletion)
90
91
92 status.observers.reset.register('gui analyses', self.post_reset)
93
94
96 """Loop over the analyses, yielding the data objects.
97
98 @return: The analysis data object from the relax data store.
99 @rtype: data.gui.Analyses instance
100 """
101
102
103 for i in range(self._num_analyses):
104 yield ds.relax_gui.analyses[i]
105
106
108 """Loop over the analyses, yielding the page objects.
109
110 @return: The page object.
111 @rtype: wx.Frame object
112 """
113
114
115 for i in range(self._num_analyses):
116 yield self._analyses[i]
117
118
120 """Return the data container of the current analysis from the relax data store.
121
122 @return: The data container of the current analysis.
123 @rtype: str
124 """
125
126
127 if self._current == None:
128 return
129
130
131 return ds.relax_gui.analyses[self._current]
132
133
135 """Return the name of the current analysis.
136
137 @return: The name of the current analysis.
138 @rtype: str
139 """
140
141
142 if self._current == None:
143 return
144
145
146 return ds.relax_gui.analyses[self._current].analysis_name
147
148
150 """Return the type of the current analysis.
151
152 @return: The type of the current analysis.
153 @rtype: str
154 """
155
156
157 if self._current == None:
158 return
159
160
161 return ds.relax_gui.analyses[self._current].analysis_type
162
163
165 """Remove all analyses."""
166
167
168 if status.debug:
169 fn_name = sys._getframe().f_code.co_name
170 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__
171 class_name = self.__class__.__name__
172 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name)
173 print("\n\n")
174 print("debug> %s: Deleting all analyses." % full_name)
175
176
177 if status.debug:
178 print("debug> %s: Unregistering all methods with the observer objects." % full_name)
179 for i in range(self._num_analyses):
180 self._analyses[i].observer_register(remove=True)
181
182
183 while self._num_analyses:
184
185
186
187
188 if status.debug:
189 print("debug> %s: Deleting the analysis at index %s." % (full_name, self._num_analyses-1))
190 self.delete_analysis(self._num_analyses-1)
191
192
193 if status.debug:
194 print("debug> %s: All analyses now deleted." % full_name)
195 status.observers.gui_analysis.notify()
196
197
199 """Delete the analysis tab and data store corresponding to the index.
200
201 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 print outs at each stage will occur to allow the following of the code and observer object notifications.
202
203
204 @param index: The index of the analysis to delete.
205 @type index: int
206 """
207
208
209 if status.debug:
210 fn_name = sys._getframe().f_code.co_name
211 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__
212 class_name = self.__class__.__name__
213 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name)
214 print("\n\n")
215 print("debug> %s: Deleting the analysis at index %s." % (full_name, index))
216
217
218 self._num_analyses -= 1
219
220
221 if self._current > index:
222 self._current -= 1
223 if status.debug:
224 print("debug> %s: Switching the current analysis to index %s." % (full_name, self._current))
225
226
227 if hasattr(self._analyses[index], 'delete'):
228 if status.debug:
229 print("debug> %s: Executing the analysis specific delete() method." % full_name)
230 self._analyses[index].delete()
231
232
233 if status.debug:
234 print("debug> %s: Deleting the notebook page." % full_name)
235 self.notebook.DeletePage(index)
236
237
238 if status.debug:
239 print("debug> %s: Deleting the analysis GUI object." % full_name)
240 self._analyses.pop(index)
241
242
243 if index == self._current and self._current != 0:
244 if status.debug:
245 print("debug> %s: Switching to page %s." % (full_name, self._current-1))
246 self.switch_page(self._current-1)
247
248
249 if self._num_analyses == 0:
250 if status.debug:
251 print("debug> %s: Setting the initial state." % full_name)
252 self.set_init_state()
253
254
255 status.observers.gui_analysis.notify()
256
257
258 pipe_name = ds.relax_gui.analyses[index].pipe_name
259
260
261 if status.debug:
262 print("debug> %s: Deleting the data store object." % full_name)
263 ds.relax_gui.analyses.pop(index)
264
265
266 if pipes.has_pipe(pipe_name):
267 if status.debug:
268 print("debug> %s: Deleting the data pipe '%s'." % (full_name, pipe_name))
269 pipes.delete(pipe_name)
270
271
272 - def get_page_from_name(self, name):
273 """Return the page corresponding to the given name.
274
275 @return: The page which matches the given name, or nothing otherwise.
276 @rtype: wx.Frame object or None
277 """
278
279
280 found = False
281 for index in range(self._num_analyses):
282
283 if name == ds.relax_gui.analyses[index].analysis_name:
284 found = True
285 break
286
287
288 if not found:
289 return
290
291
292 return self._analyses[index]
293
294
334
335
337 """Close the currently opened analysis.
338
339 @param event: The wx event.
340 @type event: wx event
341 """
342
343
344 if not hasattr(self, 'notebook'):
345 return
346
347
348 if status.exec_lock.locked():
349 return
350
351
352 index = self.notebook.GetSelection()
353
354
355 msg = "Are you sure you would like to close the current %s analysis tab?" % ds.relax_gui.analyses[index].analysis_type
356 if status.show_gui and Question(msg, title="Close current analysis", size=(350, 140), default=False).ShowModal() == wx.ID_NO:
357 return
358
359
360 self.delete_analysis(index)
361
362
364 """Close all analyses.
365
366 @param event: The wx event.
367 @type event: wx event
368 """
369
370
371 if not hasattr(self, 'notebook'):
372 return
373
374
375 if status.exec_lock.locked():
376 return
377
378
379 msg = "Are you sure you would like to close all analyses? All data will be erased and the relax data store reset."
380 if status.show_gui and Question(msg, title="Close all analyses", size=(350, 150), default=False).ShowModal() == wx.ID_NO:
381 return
382
383
384 self.delete_all()
385
386
388 """Launch a wizard to select the new analysis.
389
390 @param event: The wx event.
391 @type event: wx event
392 """
393
394
395 if status.exec_lock.locked():
396 return
397
398
399 self.new_wizard = Analysis_wizard()
400 data = self.new_wizard.run()
401
402
403 if data == None:
404 return
405
406
407 analysis_type, analysis_name, pipe_name = data
408
409
410 self.new_analysis(analysis_type, analysis_name, pipe_name)
411
412
413 del self.new_wizard
414
415
416 - def new_analysis(self, analysis_type=None, analysis_name=None, pipe_name=None, index=None):
417 """Initialise a new analysis.
418
419 @keyword analysis_type: The type of analysis to initialise. This can be one of 'noe', 'r1', 'r2', or 'mf'.
420 @type analysis_type: str
421 @keyword analysis_name: The name of the analysis to initialise.
422 @type analysis_name: str
423 @keyword index: The index of the analysis in the relax data store (set to None if no data currently exists).
424 @type index: None or int
425 """
426
427
428 if analysis_type in ['r1', 'r2'] and not dep_check.C_module_exp_fn:
429 error_message("Relaxation curve fitting is not available. Try compiling the C modules on your platform.")
430 return
431
432
433 wx.Yield()
434 wx.BeginBusyCursor()
435 self.gui.Freeze()
436
437
438 if self.init_state:
439
440 sizer = wx.BoxSizer(wx.VERTICAL)
441
442
443 self.notebook = wx.Notebook(self.gui, -1, style=wx.NB_TOP)
444 sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 0)
445
446
447 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.on_page_changing)
448 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed)
449
450
451 old_sizer = self.gui.GetSizer()
452 old_sizer.DeleteWindows()
453
454
455 self.gui.SetSizer(sizer)
456 sizer.Layout()
457
458
459 classes = {'noe': Auto_noe,
460 'r1': Auto_r1,
461 'r2': Auto_r2,
462 'mf': Auto_model_free}
463
464
465 if analysis_type not in classes.keys():
466 raise RelaxError("The analysis '%s' is unknown." % analysis_type)
467
468
469 analysis = classes[analysis_type](parent=self.notebook, id=-1, gui=self.gui, analysis_name=analysis_name, pipe_name=pipe_name, data_index=index)
470
471
472 if not analysis.init_flag:
473
474 if self.init_state:
475 self.set_init_state()
476
477
478 return
479
480
481 self._analyses.append(analysis)
482
483
484 self.notebook.AddPage(self._analyses[-1], analysis_name)
485
486
487 self._num_analyses += 1
488
489
490 if self._switch_flag:
491 self.switch_page(self._num_analyses-1)
492
493
494 self.init_state = False
495
496
497 self.gui.Layout()
498
499
500 self.gui.Thaw()
501 if wx.IsBusy():
502 wx.EndBusyCursor()
503
504
505 status.observers.gui_analysis.notify()
506
507
508 - def on_page_changing(self, event):
509 """Block page changing if needed.
510
511 @param event: The wx event.
512 @type event: wx event
513 """
514
515
516 if status.exec_lock.locked():
517
518 error_message("Cannot change analyses, relax is currently executing.", "relax execution lock")
519
520
521 event.Veto()
522
523
524 - def on_page_changed(self, event):
525 """Handle page changes.
526
527 @param event: The wx event.
528 @type event: wx event
529 """
530
531
532 self._current = event.GetSelection()
533
534
535 if not hasattr(ds, 'relax_gui'):
536 return
537
538
539 if self._switch_flag and pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name:
540 self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name)
541
542
543 event.Skip()
544
545
546 status.observers.gui_analysis.notify()
547
548
549 - def page_index_from_pipe(self, pipe):
550 """Find the page holding the data pipe and return its page index.
551
552 @param pipe: The data pipe to find the page of.
553 @type pipe: str
554 @return: The page index.
555 @rtype: int or None
556 """
557
558
559 index = None
560 for i in range(self._num_analyses):
561
562 if ds.relax_gui.analyses[i].pipe_name == pipe:
563 index = i
564 break
565
566
567 return index
568
569
570 - def page_name_from_pipe(self, pipe):
571 """Find the page holding the data pipe and return its name.
572
573 @param pipe: The data pipe to find the page of.
574 @type pipe: str
575 @return: The page name.
576 @rtype: str or None
577 """
578
579
580 index = self.page_index_from_pipe(pipe)
581
582
583 if index == None:
584 return
585
586
587 return ds.relax_gui.analyses[index].analysis_name
588
589
591 """Remove analysis tabs for which the associated data pipe has been deleted."""
592
593
594 del_list = []
595 for i in range(self._num_analyses):
596 if not pipes.has_pipe(ds.relax_gui.analyses[i].pipe_name):
597 del_list.append(i)
598
599
600 del_list.reverse()
601
602
603 for index in del_list:
604 self.delete_analysis(index)
605
606
608 """Switch the page to the given or current data pipe.
609
610 @keyword pipe: The pipe associated with the page to switch to. If not supplied, the current data pipe will be used.
611 @type pipe: str or None
612 """
613
614
615 if pipe == None:
616 pipe = pipes.cdp_name()
617
618
619 index = self.page_index_from_pipe(pipe)
620
621
622 if index == None:
623 return
624
625
626 if self._current == index:
627 return
628
629
630 self.switch_page(index)
631
632
633 status.observers.gui_analysis.notify()
634
635
636 - def post_reset(self):
637 """Post relax data store reset event handler."""
638
639
640 while self._num_analyses:
641
642 index = self._num_analyses - 1
643
644
645 if hasattr(self, 'notebook'):
646 self.notebook.DeletePage(index)
647
648
649 self._analyses.pop(index)
650
651
652 self._num_analyses -= 1
653
654
655 self.set_init_state()
656
657
659 """Revert to the initial state."""
660
661
662 self.init_state = True
663 self._current = None
664
665
666 old_sizer = self.gui.GetSizer()
667 old_sizer.DeleteWindows()
668
669
670 if hasattr(self, 'notebook'):
671 del self.notebook
672
673
674 if not hasattr(ds, 'relax_gui'):
675 ds.relax_gui = Gui()
676
677
678 self.gui.add_start_screen()
679
680
681 - def switch_page(self, index):
682 """Switch to the given page.
683
684 @param index: The index of the page to switch to.
685 @type index: int
686 """
687
688
689 self._current = index
690
691
692 wx.CallAfter(self.notebook.SetSelection, self._current)
693
694
695 wx.CallAfter(status.observers.gui_analysis.notify)
696