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