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