Package gui :: Package analyses
[hide private]
[frames] | no frames]

Source Code for Package gui.analyses

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2010-2013 Edward d'Auvergne                                   # 
  4  #                                                                             # 
  5  # This file is part of the program relax (http://www.nmr-relax.com).          # 
  6  #                                                                             # 
  7  # This program is free software: you can redistribute it and/or modify        # 
  8  # it under the terms of the GNU General Public License as published by        # 
  9  # the Free Software Foundation, either version 3 of the License, or           # 
 10  # (at your option) any later version.                                         # 
 11  #                                                                             # 
 12  # This program is distributed in the hope that it will be useful,             # 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of              # 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               # 
 15  # GNU General Public License for more details.                                # 
 16  #                                                                             # 
 17  # You should have received a copy of the GNU General Public License           # 
 18  # along with this program.  If not, see <http://www.gnu.org/licenses/>.       # 
 19  #                                                                             # 
 20  ############################################################################### 
 21   
 22  # Package docstring. 
 23  """Package for the automatic and custom analysis GUI elements.""" 
 24   
 25  # Python module imports. 
 26  import inspect 
 27  import sys 
 28  import wx 
 29  from types import ListType 
 30   
 31  # relax module imports. 
 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.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  # The package contents. 
 48  __all__ = ['auto_model_free', 
 49             'auto_noe', 
 50             'auto_r1', 
 51             'auto_r2', 
 52             'auto_rx_base', 
 53             'base', 
 54             'elements', 
 55             'relax_control', 
 56             'results_analysis', 
 57             'results'] 
 58   
 59   
60 -class Analysis_controller:
61 """Class for controlling all aspects of analyses.""" 62
63 - def __init__(self, gui):
64 """Initialise the analysis controller. 65 66 @param gui: The gui object. 67 @type gui: wx object 68 """ 69 70 # Store the args. 71 self.gui = gui 72 73 # Initialise some variables. 74 self.init_state = True 75 self._current = None 76 self._num_analyses = 0 77 self._switch_flag = True 78 79 # The analyses page objects. 80 self._analyses = [] 81 82 # Register the page switch method for pipe switches. 83 self.name = 'notebook page switcher' 84 status.observers.pipe_alteration.register(self.name, self.pipe_switch, method_name='pipe_switch') 85 86 # Register a method for removing analyses if the associated pipe is deleted. 87 status.observers.pipe_alteration.register('notebook pipe deletion', self.pipe_deletion, method_name='pipe_deletion') 88 89 # Register the deletion of all analyses for the reset status observer. 90 status.observers.reset.register('gui analyses', self.post_reset, method_name='post_reset') 91 92 # Register state loading. 93 status.observers.state_load.register('gui analyses', self.load_from_store, method_name='load_from_store')
94 95
96 - def analysis_data_loop(self):
97 """Loop over the analyses, yielding the data objects. 98 99 @return: The analysis data object from the relax data store. 100 @rtype: data.gui.Analyses instance 101 """ 102 103 # Loop over the analyses. 104 for i in range(self._num_analyses): 105 yield ds.relax_gui.analyses[i]
106 107
108 - def analysis_loop(self):
109 """Loop over the analyses, yielding the page objects. 110 111 @return: The page object. 112 @rtype: wx.Frame object 113 """ 114 115 # Loop over the analyses. 116 for i in range(self._num_analyses): 117 yield self._analyses[i]
118 119
120 - def current_data(self):
121 """Return the data container of the current analysis from the relax data store. 122 123 @return: The data container of the current analysis. 124 @rtype: str 125 """ 126 127 # No current page. 128 if self._current == None: 129 return 130 131 # Return the name. 132 return ds.relax_gui.analyses[self._current]
133 134
135 - def current_analysis_name(self):
136 """Return the name of the current analysis. 137 138 @return: The name of the current analysis. 139 @rtype: str 140 """ 141 142 # No current page. 143 if self._current == None: 144 return 145 146 # Return the name. 147 return ds.relax_gui.analyses[self._current].analysis_name
148 149
150 - def current_analysis_type(self):
151 """Return the type of the current analysis. 152 153 @return: The type of the current analysis. 154 @rtype: str 155 """ 156 157 # No current page. 158 if self._current == None: 159 return 160 161 # Return the name. 162 return ds.relax_gui.analyses[self._current].analysis_type
163 164
165 - def delete_all(self):
166 """Remove all analyses.""" 167 168 # Debugging set up. 169 if status.debug: 170 fn_name = sys._getframe().f_code.co_name 171 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 172 class_name = self.__class__.__name__ 173 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 174 print("\n\n") 175 print("debug> %s: Deleting all analyses." % full_name) 176 177 # Unregister all observer objects prior to analysis deletion. This is to prevent queued wx events being sent to dead or non-existent objects. 178 if status.debug: 179 print("debug> %s: Unregistering all methods with the observer objects." % full_name) 180 for i in range(self._num_analyses): 181 self._analyses[i].observer_register(remove=True) 182 183 # Delete the current tabs. 184 while self._num_analyses: 185 # Flush all pending events (bug fix for MS Windows). 186 #wx.Yield() 187 188 # Remove the last analysis, until there is nothing left. 189 if status.debug: 190 print("debug> %s: Deleting the analysis at index %s." % (full_name, self._num_analyses-1)) 191 self.delete_analysis(self._num_analyses-1) 192 193 # Notify the observers of the change. 194 if status.debug: 195 print("debug> %s: All analyses now deleted." % full_name) 196 status.observers.gui_analysis.notify()
197 198
199 - def delete_analysis(self, index):
200 """Delete the analysis tab and data store corresponding to the index. 201 202 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. 203 204 205 @param index: The index of the analysis to delete. 206 @type index: int 207 """ 208 209 # Debugging set up. 210 if status.debug: 211 fn_name = sys._getframe().f_code.co_name 212 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 213 class_name = self.__class__.__name__ 214 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 215 print("\n\n") 216 print("debug> %s: Deleting the analysis at index %s." % (full_name, index)) 217 218 # Decrement the number of analyses. 219 self._num_analyses -= 1 220 221 # Shift the current page back one if necessary. 222 if self._current > index: 223 self._current -= 1 224 if status.debug: 225 print("debug> %s: Switching the current analysis to index %s." % (full_name, self._current)) 226 227 # Execute the analysis delete method, if it exists. 228 if hasattr(self._analyses[index], 'delete'): 229 if status.debug: 230 print("debug> %s: Executing the analysis specific delete() method." % full_name) 231 self._analyses[index].delete() 232 233 # Delete the tab. 234 if status.debug: 235 print("debug> %s: Deleting the notebook page." % full_name) 236 self.notebook.DeletePage(index) 237 238 # Delete the tab object. 239 if status.debug: 240 print("debug> %s: Deleting the analysis GUI object." % full_name) 241 self._analyses.pop(index) 242 243 # The current page has been deleted, so switch one back (if possible). 244 if index == self._current and self._current != 0: 245 if status.debug: 246 print("debug> %s: Switching to page %s." % (full_name, self._current-1)) 247 self.switch_page(self._current-1) 248 249 # No more analyses, so in the initial state. 250 if self._num_analyses == 0: 251 if status.debug: 252 print("debug> %s: Setting the initial state." % full_name) 253 self.set_init_state() 254 255 # Notify the observers of the change. 256 status.observers.gui_analysis.notify() 257 258 # Store the pipe bundle. 259 pipe_bundle = ds.relax_gui.analyses[index].pipe_bundle 260 261 # Delete the data store object. 262 if status.debug: 263 print("debug> %s: Deleting the data store object." % full_name) 264 ds.relax_gui.analyses.pop(index) 265 266 # Delete all data pipes associated with the analysis. 267 for pipe in pipes.pipe_names(): 268 if pipes.get_bundle(pipe) == pipe_bundle: 269 if status.debug: 270 print("debug> %s: Deleting the data pipe '%s' from the '%s' bundle." % (full_name, pipe, pipe_bundle)) 271 pipes.delete(pipe)
272 273
274 - def get_page_from_name(self, name):
275 """Return the page corresponding to the given name. 276 277 @return: The page which matches the given name, or nothing otherwise. 278 @rtype: wx.Frame object or None 279 """ 280 281 # Determine the analysis index. 282 found = False 283 for index in range(self._num_analyses): 284 # Match. 285 if name == ds.relax_gui.analyses[index].analysis_name: 286 found = True 287 break 288 289 # No analysis found, so return nothing. 290 if not found: 291 return 292 293 # Return the analysis page. 294 return self._analyses[index]
295 296
297 - def load_from_store(self):
298 """Recreate the analyses from the relax data store.""" 299 300 # No relax_gui data store structure, so do nothing. 301 if not hasattr(ds, 'relax_gui'): 302 return 303 304 # A remapping table. 305 map = {'NOE': 'noe', 306 'R1': 'r1', 307 'R2': 'r2', 308 'model-free': 'mf'} 309 310 # Loop over each analysis. 311 for i in range(len(ds.relax_gui.analyses)): 312 # The analysis name. 313 if hasattr(ds.relax_gui.analyses[i], 'analysis_name'): 314 analysis_name = ds.relax_gui.analyses[i].analysis_name 315 elif ds.relax_gui.analyses[i].analysis_type == 'NOE': 316 analysis_name = 'Steady-state NOE' 317 elif ds.relax_gui.analyses[i].analysis_type == 'R1': 318 analysis_name = 'R1 relaxation' 319 elif ds.relax_gui.analyses[i].analysis_type == 'R2': 320 analysis_name = 'R2 relaxation' 321 elif ds.relax_gui.analyses[i].analysis_type == 'model-free': 322 analysis_name = 'Model-free' 323 324 # Compatibility with old save files. 325 if not hasattr(ds.relax_gui.analyses[i], 'pipe_bundle'): 326 # First alias the pipe name as the bundle name. 327 ds.relax_gui.analyses[i].pipe_bundle = ds.relax_gui.analyses[i].pipe_name 328 329 # Then bundle the associated pipe into a bundle with the same name. 330 self.gui.interpreter.apply('pipe.bundle', pipe=ds.relax_gui.analyses[i].pipe_name, bundle=ds.relax_gui.analyses[i].pipe_name) 331 332 # Set up the analysis. 333 self._switch_flag = False 334 self.new_analysis(map[ds.relax_gui.analyses[i].analysis_type], analysis_name, index=i) 335 336 # Switch to the page of the current data pipe. 337 self.pipe_switch() 338 339 # Reset the switching flag. 340 self._switch_flag = True 341 342 # Notify the observers of the change. 343 status.observers.gui_analysis.notify()
344 345
346 - def menu_close(self, event):
347 """Close the currently opened analysis. 348 349 @param event: The wx event. 350 @type event: wx event 351 """ 352 353 # Notebook not created yet, so skip. 354 if not hasattr(self, 'notebook'): 355 return 356 357 # Execution lock. 358 if status.exec_lock.locked(): 359 return 360 361 # Get the current analysis index. 362 index = self.notebook.GetSelection() 363 364 # Ask if this should be done. 365 msg = "Are you sure you would like to close the current %s analysis tab?" % ds.relax_gui.analyses[index].analysis_type 366 if status.show_gui and Question(msg, title="Close current analysis", size=(350, 140), default=False).ShowModal() == wx.ID_NO: 367 return 368 369 # Delete. 370 self.delete_analysis(index)
371 372
373 - def menu_close_all(self, event):
374 """Close all analyses. 375 376 @param event: The wx event. 377 @type event: wx event 378 """ 379 380 # Notebook not created yet, so skip. 381 if not hasattr(self, 'notebook'): 382 return 383 384 # Execution lock. 385 if status.exec_lock.locked(): 386 return 387 388 # Ask if this should be done. 389 msg = "Are you sure you would like to close all analyses? All data will be erased and the relax data store reset." 390 if status.show_gui and Question(msg, title="Close all analyses", size=(350, 150), default=False).ShowModal() == wx.ID_NO: 391 return 392 393 # Delete. 394 self.delete_all() 395 396 # Reset relax. 397 reset()
398 399
400 - def menu_new(self, event):
401 """Launch a wizard to select the new analysis. 402 403 @param event: The wx event. 404 @type event: wx event 405 """ 406 407 # Execution lock. 408 if status.exec_lock.locked(): 409 return 410 411 # Initialise the analysis wizard, and obtain the user specified data. 412 self.new_wizard = Analysis_wizard() 413 data = self.new_wizard.run() 414 415 # Failure, so do nothing. 416 if data == None: 417 return 418 419 # Unpack the data. 420 analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec = data 421 422 # Initialise the new analysis. 423 self.new_analysis(analysis_type, analysis_name, pipe_name, pipe_bundle, uf_exec) 424 425 # Delete the wizard data. 426 del self.new_wizard
427 428
429 - def new_analysis(self, analysis_type=None, analysis_name=None, pipe_name=None, pipe_bundle=None, uf_exec=[], index=None):
430 """Initialise a new analysis. 431 432 @keyword analysis_type: The type of analysis to initialise. This can be one of 'noe', 'r1', 'r2', or 'mf'. 433 @type analysis_type: str 434 @keyword analysis_name: The name of the analysis to initialise. 435 @type analysis_name: str 436 @keyword pipe_name: The name of the original data pipe to create for the analysis. 437 @type pipe_name: str 438 @keyword pipe_bundle: The name of the data pipe bundle to associate with this analysis. 439 @type pipe_bundle: str 440 @keyword uf_exec: The list of user function on_execute methods returned from the new analysis wizard. 441 @type uf_exec: list of methods 442 @keyword index: The index of the analysis in the relax data store (set to None if no data currently exists). 443 @type index: None or int 444 """ 445 446 # Check the C modules. 447 if analysis_type in ['r1', 'r2'] and not dep_check.C_module_exp_fn: 448 error_message("Relaxation curve fitting is not available. Try compiling the C modules on your platform.") 449 return 450 451 # Freeze the GUI. 452 wx.Yield() 453 wx.BeginBusyCursor() 454 self.gui.Freeze() 455 456 # Starting from the initial state. 457 if self.init_state: 458 # A new sizer for the notebook (to replace the current sizer). 459 sizer = wx.BoxSizer(wx.VERTICAL) 460 461 # Create a notebook and add it to the sizer. 462 self.notebook = wx.Notebook(self.gui, -1, style=wx.NB_TOP) 463 sizer.Add(self.notebook, 1, wx.ALL|wx.EXPAND, 0) 464 465 # Bind changing events. 466 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.on_page_changing) 467 self.gui.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed) 468 469 # Delete the previous sizer. 470 old_sizer = self.gui.GetSizer() 471 old_sizer.DeleteWindows() 472 473 # Add the new sizer to the main window. 474 self.gui.SetSizer(sizer) 475 sizer.Layout() 476 477 # The analysis classes. 478 classes = {'noe': Auto_noe, 479 'r1': Auto_r1, 480 'r2': Auto_r2, 481 'mf': Auto_model_free} 482 483 # Bad analysis type. 484 if analysis_type not in classes.keys(): 485 raise RelaxError("The analysis '%s' is unknown." % analysis_type) 486 487 # Initialise the class. 488 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) 489 490 # Failure. 491 if not analysis.init_flag: 492 # Reset. 493 if self.init_state: 494 self.set_init_state() 495 496 # Stop operation. 497 return 498 499 # Append the class object to the analysis window object. 500 self._analyses.append(analysis) 501 502 # Add to the notebook. 503 self.notebook.AddPage(self._analyses[-1], analysis_name) 504 505 # Increment the number of analyses. 506 self._num_analyses += 1 507 508 # Switch to the new page. 509 if self._switch_flag: 510 self.switch_page(self._num_analyses-1) 511 512 # Set the initialisation flag. 513 self.init_state = False 514 515 # Reset the main window layout. 516 self.gui.Layout() 517 518 # Thaw the GUI. 519 self.gui.Thaw() 520 if wx.IsBusy(): 521 wx.EndBusyCursor() 522 523 # Notify the observers of the change. 524 status.observers.gui_analysis.notify()
525 526
527 - def on_page_changing(self, event):
528 """Block page changing if needed. 529 530 @param event: The wx event. 531 @type event: wx event 532 """ 533 534 # Execution lock. 535 if status.exec_lock.locked(): 536 # Show an error message. 537 error_message("Cannot change analyses, relax is currently executing.", "relax execution lock") 538 539 # Veto the event. 540 event.Veto()
541 542
543 - def on_page_changed(self, event):
544 """Handle page changes. 545 546 @param event: The wx event. 547 @type event: wx event 548 """ 549 550 # The index. 551 self._current = event.GetSelection() 552 553 # Handel calls to the reset user function! 554 if ds.is_empty(): 555 return 556 557 # Switch to the major data pipe of that page if not the current one. 558 if self._switch_flag and pipes.cdp_name() != ds.relax_gui.analyses[self._current].pipe_name: 559 self.gui.interpreter.apply('pipe.switch', ds.relax_gui.analyses[self._current].pipe_name) 560 561 # Normal operation. 562 event.Skip() 563 564 # Notify the observers of the change. 565 status.observers.gui_analysis.notify()
566 567
568 - def page_index_from_bundle(self, bundle):
569 """Find the analysis associated with the data pipe bundle and return its page index. 570 571 @param bundle: The data pipe bundle to find the page of. 572 @type bundle: str 573 @return: The page index. 574 @rtype: int or None 575 """ 576 577 # Find the index. 578 index = None 579 for i in range(self._num_analyses): 580 # Matching page. 581 if ds.relax_gui.analyses[i].pipe_bundle == bundle: 582 index = i 583 break 584 585 # Return the index. 586 return index
587 588
589 - def page_name_from_bundle(self, bundle):
590 """Find the analysis associated with the bundle and return its name. 591 592 @param bundle: The data pipe bundle to find the page of. 593 @type bundle: str 594 @return: The page name. 595 @rtype: str or None 596 """ 597 598 # Find the index. 599 index = self.page_index_from_bundle(bundle) 600 601 # No matching page. 602 if index == None: 603 return 604 605 # Return the page name. 606 return ds.relax_gui.analyses[index].analysis_name
607 608
609 - def pipe_deletion(self):
610 """Remove analysis tabs for which the associated data pipe has been deleted.""" 611 612 # Loop over the analyses, noting which no longer have an associated data pipe bundle. 613 del_list = [] 614 for i in range(self._num_analyses): 615 if not pipes.has_bundle(ds.relax_gui.analyses[i].pipe_bundle): 616 del_list.append(i) 617 618 # Reverse the order of the list so the removal works correctly. 619 del_list.reverse() 620 621 # Delete the analyses. 622 for index in del_list: 623 self.delete_analysis(index)
624 625
626 - def pipe_switch(self, pipe=None):
627 """Switch the page to the given or current data pipe. 628 629 @keyword pipe: The pipe associated with the page to switch to. If not supplied, the current data pipe will be used. 630 @type pipe: str or None 631 """ 632 633 # The data pipe. 634 if pipe == None: 635 pipe = pipes.cdp_name() 636 637 # No pipes to switch to. 638 if pipe == None: 639 return 640 641 # Find the page. 642 index = self.page_index_from_bundle(pipes.get_bundle(pipe)) 643 644 # No matching page. 645 if index == None: 646 return 647 648 # The page is already active, so do nothing. 649 if self._current == index: 650 return 651 652 # Switch to the page. 653 self.switch_page(index) 654 655 # Notify the observers of the change. 656 status.observers.gui_analysis.notify()
657 658
659 - def post_reset(self):
660 """Post relax data store reset event handler.""" 661 662 # Debugging set up. 663 if status.debug: 664 fn_name = sys._getframe().f_code.co_name 665 mod_name = inspect.getmodule(inspect.stack()[1][0]).__name__ 666 class_name = self.__class__.__name__ 667 full_name = "%s.%s.%s" % (mod_name, class_name, fn_name) 668 print("\n\n") 669 print("debug> %s: Deleting all analyses." % full_name) 670 671 # Unregister all observer objects prior to analysis deletion. This is to prevent queued wx events being sent to dead or non-existent objects. 672 if status.debug: 673 print("debug> %s: Unregistering all methods with the observer objects." % full_name) 674 for i in range(self._num_analyses): 675 self._analyses[i].observer_register(remove=True) 676 677 # Delete all tabs. 678 while self._num_analyses: 679 # The index of the tab to remove. 680 index = self._num_analyses - 1 681 682 # Delete the tab. 683 if hasattr(self, 'notebook'): 684 self.notebook.DeletePage(index) 685 686 # Delete the tab object. 687 self._analyses.pop(index) 688 689 # Decrement the number of analyses. 690 self._num_analyses -= 1 691 692 # Set the initial state. 693 self.set_init_state()
694 695
696 - def set_init_state(self):
697 """Revert to the initial state.""" 698 699 # Reset the flag. 700 self.init_state = True 701 self._current = None 702 703 # Delete the previous sizer. 704 old_sizer = self.gui.GetSizer() 705 old_sizer.DeleteWindows() 706 707 # Delete the notebook. 708 if hasattr(self, 'notebook'): 709 del self.notebook 710 711 # Recreate the GUI data store object (needed if the reset user function is called). 712 if not hasattr(ds, 'relax_gui'): 713 ds.relax_gui = Gui() 714 715 # Recreate the start screen. 716 self.gui.add_start_screen()
717 718
719 - def switch_page(self, index):
720 """Switch to the given page. 721 722 @param index: The index of the page to switch to. 723 @type index: int 724 """ 725 726 # Set the current page number. 727 self._current = index 728 729 # Switch to the page. 730 wx.CallAfter(self.notebook.SetSelection, self._current) 731 732 # Notify the observers of the change. 733 wx.CallAfter(status.observers.gui_analysis.notify)
734