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

Source Code for Package gui.analyses

  1  ############################################################################### 
  2  #                                                                             # 
  3  # Copyright (C) 2010-2012 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 import Relax_data_store; ds = Relax_data_store() 
 33  from data.gui import Gui 
 34  import dep_check 
 35  from generic_fns import pipes 
 36  from generic_fns.reset import reset 
 37  from relax_errors import RelaxError 
 38  from status import Status; status = Status() 
 39   
 40  # relax GUI module imports. 
 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  # The package contents. 
 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   
62 -class Analysis_controller:
63 """Class for controlling all aspects of analyses.""" 64
65 - def __init__(self, gui):
66 """Initialise the analysis controller. 67 68 @param gui: The gui object. 69 @type gui: wx object 70 """ 71 72 # Store the args. 73 self.gui = gui 74 75 # Initialise some variables. 76 self.init_state = True 77 self._current = None 78 self._num_analyses = 0 79 self._switch_flag = True 80 81 # The analyses page objects. 82 self._analyses = [] 83 84 # Register the page switch method for pipe switches. 85 self.name = 'notebook page switcher' 86 status.observers.pipe_alteration.register(self.name, self.pipe_switch, method_name='pipe_switch') 87 88 # Register a method for removing analyses if the associated pipe is deleted. 89 status.observers.pipe_alteration.register('notebook pipe deletion', self.pipe_deletion, method_name='pipe_deletion') 90 91 # Register the deletion of all analyses for the reset status observer. 92 status.observers.reset.register('gui analyses', self.post_reset, method_name='post_reset') 93 94 # Register state loading. 95 status.observers.state_load.register('gui analyses', self.load_from_store, method_name='load_from_store')
96 97
98 - def analysis_data_loop(self):
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 # Loop over the analyses. 106 for i in range(self._num_analyses): 107 yield ds.relax_gui.analyses[i]
108 109
110 - def analysis_loop(self):
111 """Loop over the analyses, yielding the page objects. 112 113 @return: The page object. 114 @rtype: wx.Frame object 115 """ 116 117 # Loop over the analyses. 118 for i in range(self._num_analyses): 119 yield self._analyses[i]
120 121
122 - def current_data(self):
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 # No current page. 130 if self._current == None: 131 return 132 133 # Return the name. 134 return ds.relax_gui.analyses[self._current]
135 136
137 - def current_analysis_name(self):
138 """Return the name of the current analysis. 139 140 @return: The name of the current analysis. 141 @rtype: str 142 """ 143 144 # No current page. 145 if self._current == None: 146 return 147 148 # Return the name. 149 return ds.relax_gui.analyses[self._current].analysis_name
150 151
152 - def current_analysis_type(self):
153 """Return the type of the current analysis. 154 155 @return: The type of the current analysis. 156 @rtype: str 157 """ 158 159 # No current page. 160 if self._current == None: 161 return 162 163 # Return the name. 164 return ds.relax_gui.analyses[self._current].analysis_type
165 166
167 - def delete_all(self):
168 """Remove all analyses.""" 169 170 # Debugging set up. 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 # Unregister all observer objects prior to analysis deletion. This is to prevent queued wx events being sent to dead or non-existent objects. 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 # Delete the current tabs. 186 while self._num_analyses: 187 # Flush all pending events (bug fix for MS Windows). 188 #wx.Yield() 189 190 # Remove the last analysis, until there is nothing left. 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 # Notify the observers of the change. 196 if status.debug: 197 print("debug> %s: All analyses now deleted." % full_name) 198 status.observers.gui_analysis.notify()
199 200
201 - def delete_analysis(self, index):
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 # Debugging set up. 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 # Decrement the number of analyses. 221 self._num_analyses -= 1 222 223 # Shift the current page back one if necessary. 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 # Execute the analysis delete method, if it exists. 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 # Delete the tab. 236 if status.debug: 237 print("debug> %s: Deleting the notebook page." % full_name) 238 self.notebook.DeletePage(index) 239 240 # Delete the tab object. 241 if status.debug: 242 print("debug> %s: Deleting the analysis GUI object." % full_name) 243 self._analyses.pop(index) 244 245 # The current page has been deleted, so switch one back (if possible). 246 if index == self._current and self._current != 0: 247 if status.debug: 248 print("debug> %s: Switching to page %s." % (full_name, self._current-1)) 249 self.switch_page(self._current-1) 250 251 # No more analyses, so in the initial state. 252 if self._num_analyses == 0: 253 if status.debug: 254 print("debug> %s: Setting the initial state." % full_name) 255 self.set_init_state() 256 257 # Notify the observers of the change. 258 status.observers.gui_analysis.notify() 259 260 # Store the pipe bundle. 261 pipe_bundle = ds.relax_gui.analyses[index].pipe_bundle 262 263 # Delete the data store object. 264 if status.debug: 265 print("debug> %s: Deleting the data store object." % full_name) 266 ds.relax_gui.analyses.pop(index) 267 268 # Delete all data pipes associated with the analysis. 269 for pipe in pipes.pipe_names(): 270 if pipes.get_bundle(pipe) == pipe_bundle: 271 if status.debug: 272 print("debug> %s: Deleting the data pipe '%s' from the '%s' bundle." % (full_name, pipe, pipe_bundle)) 273 pipes.delete(pipe)
274 275
276 - def get_page_from_name(self, name):
277 """Return the page corresponding to the given name. 278 279 @return: The page which matches the given name, or nothing otherwise. 280 @rtype: wx.Frame object or None 281 """ 282 283 # Determine the analysis index. 284 found = False 285 for index in range(self._num_analyses): 286 # Match. 287 if name == ds.relax_gui.analyses[index].analysis_name: 288 found = True 289 break 290 291 # No analysis found, so return nothing. 292 if not found: 293 return 294 295 # Return the analysis page. 296 return self._analyses[index]
297 298
299 - def load_from_store(self):
300 """Recreate the analyses from the relax data store.""" 301 302 # No relax_gui data store structure, so do nothing. 303 if not hasattr(ds, 'relax_gui'): 304 return 305 306 # A remapping table. 307 map = {'NOE': 'noe', 308 'R1': 'r1', 309 'R2': 'r2', 310 'model-free': 'mf'} 311 312 # Loop over each analysis. 313 for i in range(len(ds.relax_gui.analyses)): 314 # The analysis name. 315 if hasattr(ds.relax_gui.analyses[i], 'analysis_name'): 316 analysis_name = ds.relax_gui.analyses[i].analysis_name 317 elif ds.relax_gui.analyses[i].analysis_type == 'NOE': 318 analysis_name = 'Steady-state NOE' 319 elif ds.relax_gui.analyses[i].analysis_type == 'R1': 320 analysis_name = 'R1 relaxation' 321 elif ds.relax_gui.analyses[i].analysis_type == 'R2': 322 analysis_name = 'R2 relaxation' 323 elif ds.relax_gui.analyses[i].analysis_type == 'model-free': 324 analysis_name = 'Model-free' 325 326 # Compatibility with old save files. 327 if not hasattr(ds.relax_gui.analyses[i], 'pipe_bundle'): 328 # First alias the pipe name as the bundle name. 329 ds.relax_gui.analyses[i].pipe_bundle = ds.relax_gui.analyses[i].pipe_name 330 331 # Then bundle the associated pipe into a bundle with the same name. 332 self.gui.interpreter.apply('pipe.bundle', pipe=ds.relax_gui.analyses[i].pipe_name, bundle=ds.relax_gui.analyses[i].pipe_name) 333 334 # Set up the analysis. 335 self._switch_flag = False 336 self.new_analysis(map[ds.relax_gui.analyses[i].analysis_type], analysis_name, index=i) 337 338 # Switch to the page of the current data pipe. 339 self.pipe_switch() 340 341 # Reset the switching flag. 342 self._switch_flag = True 343 344 # Notify the observers of the change. 345 status.observers.gui_analysis.notify()
346 347
348 - def menu_close(self, event):
349 """Close the currently opened analysis. 350 351 @param event: The wx event. 352 @type event: wx event 353 """ 354 355 # Notebook not created yet, so skip. 356 if not hasattr(self, 'notebook'): 357 return 358 359 # Execution lock. 360 if status.exec_lock.locked(): 361 return 362 363 # Get the current analysis index. 364 index = self.notebook.GetSelection() 365 366 # Ask if this should be done. 367 msg = "Are you sure you would like to close the current %s analysis tab?" % ds.relax_gui.analyses[index].analysis_type 368 if status.show_gui and Question(msg, title="Close current analysis", size=(350, 140), default=False).ShowModal() == wx.ID_NO: 369 return 370 371 # Delete. 372 self.delete_analysis(index)
373 374
375 - def menu_close_all(self, event):
376 """Close all analyses. 377 378 @param event: The wx event. 379 @type event: wx event 380 """ 381 382 # Notebook not created yet, so skip. 383 if not hasattr(self, 'notebook'): 384 return 385 386 # Execution lock. 387 if status.exec_lock.locked(): 388 return 389 390 # Ask if this should be done. 391 msg = "Are you sure you would like to close all analyses? All data will be erased and the relax data store reset." 392 if status.show_gui and Question(msg, title="Close all analyses", size=(350, 150), default=False).ShowModal() == wx.ID_NO: 393 return 394 395 # Delete. 396 self.delete_all() 397 398 # Reset relax. 399 reset()
400 401
402 - def menu_new(self, event):
403 """Launch a wizard to select the new analysis. 404 405 @param event: The wx event. 406 @type event: wx event 407 """ 408 409 # Execution lock. 410 if status.exec_lock.locked(): 411 return 412 413 # Initialise the analysis wizard, and obtain the user specified data. 414 self.new_wizard = Analysis_wizard() 415 data = self.new_wizard.run() 416 417 # Failure, so do nothing. 418 if data == None: 419 return 420 421 # Unpack the data. 422 analysis_type, analysis_name, pipe_name, pipe_bundle = data 423 424 # Initialise the new analysis. 425 self.new_analysis(analysis_type, analysis_name, pipe_name, pipe_bundle) 426 427 # Delete the wizard data. 428 del self.new_wizard
429 430
431 - def new_analysis(self, analysis_type=None, analysis_name=None, pipe_name=None, pipe_bundle=None, index=None):
432 """Initialise a new analysis. 433 434 @keyword analysis_type: The type of analysis to initialise. This can be one of 'noe', 'r1', 'r2', or 'mf'. 435 @type analysis_type: str 436 @keyword analysis_name: The name of the analysis to initialise. 437 @type analysis_name: str 438 @keyword pipe_name: The name of the original data pipe to create for the analysis. 439 @type pipe_name: str 440 @keyword pipe_bundle: The name of the data pipe bundle to associate with this analysis. 441 @type pipe_bundle: str 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, 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