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 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) 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) 90 91 # Register the deletion of all analyses for the reset status observer. 92 status.observers.reset.register('gui analyses', self.post_reset)
93 94
95 - def analysis_data_loop(self):
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 # Loop over the analyses. 103 for i in range(self._num_analyses): 104 yield ds.relax_gui.analyses[i]
105 106
107 - def analysis_loop(self):
108 """Loop over the analyses, yielding the page objects. 109 110 @return: The page object. 111 @rtype: wx.Frame object 112 """ 113 114 # Loop over the analyses. 115 for i in range(self._num_analyses): 116 yield self._analyses[i]
117 118
119 - def current_data(self):
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 # No current page. 127 if self._current == None: 128 return 129 130 # Return the name. 131 return ds.relax_gui.analyses[self._current]
132 133
134 - def current_analysis_name(self):
135 """Return the name of the current analysis. 136 137 @return: The name of the current analysis. 138 @rtype: str 139 """ 140 141 # No current page. 142 if self._current == None: 143 return 144 145 # Return the name. 146 return ds.relax_gui.analyses[self._current].analysis_name
147 148
149 - def current_analysis_type(self):
150 """Return the type of the current analysis. 151 152 @return: The type of the current analysis. 153 @rtype: str 154 """ 155 156 # No current page. 157 if self._current == None: 158 return 159 160 # Return the name. 161 return ds.relax_gui.analyses[self._current].analysis_type
162 163
164 - def delete_all(self):
165 """Remove all analyses.""" 166 167 # Debugging set up. 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 # Unregister all observer objects prior to analysis deletion. This is to prevent queued wx events being sent to dead or non-existent objects. 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 # Delete the current tabs. 183 while self._num_analyses: 184 # Flush all pending events (bug fix for MS Windows). 185 #wx.Yield() 186 187 # Remove the last analysis, until there is nothing left. 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 # Notify the observers of the change. 193 if status.debug: 194 print("debug> %s: All analyses now deleted." % full_name) 195 status.observers.gui_analysis.notify()
196 197
198 - def delete_analysis(self, index):
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 # Debugging set up. 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 # Decrement the number of analyses. 218 self._num_analyses -= 1 219 220 # Shift the current page back one if necessary. 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 # Execute the analysis delete method, if it exists. 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 # Delete the tab. 233 if status.debug: 234 print("debug> %s: Deleting the notebook page." % full_name) 235 self.notebook.DeletePage(index) 236 237 # Delete the tab object. 238 if status.debug: 239 print("debug> %s: Deleting the analysis GUI object." % full_name) 240 self._analyses.pop(index) 241 242 # The current page has been deleted, so switch one back (if possible). 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 # No more analyses, so in the initial state. 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 # Notify the observers of the change. 255 status.observers.gui_analysis.notify() 256 257 # Store the pipe name. 258 pipe_name = ds.relax_gui.analyses[index].pipe_name 259 260 # Delete the data store object. 261 if status.debug: 262 print("debug> %s: Deleting the data store object." % full_name) 263 ds.relax_gui.analyses.pop(index) 264 265 # Delete all data pipes associated with the analysis. 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 # Determine the analysis index. 280 found = False 281 for index in range(self._num_analyses): 282 # Match. 283 if name == ds.relax_gui.analyses[index].analysis_name: 284 found = True 285 break 286 287 # No analysis found, so return nothing. 288 if not found: 289 return 290 291 # Return the analysis page. 292 return self._analyses[index]
293 294
295 - def load_from_store(self):
296 """Recreate the analyses from the relax data store.""" 297 298 # No relax_gui data store structure, so do nothing. 299 if not hasattr(ds, 'relax_gui'): 300 return 301 302 # A remapping table. 303 map = {'NOE': 'noe', 304 'R1': 'r1', 305 'R2': 'r2', 306 'model-free': 'mf'} 307 308 # Loop over each analysis. 309 for i in range(len(ds.relax_gui.analyses)): 310 # The analysis name. 311 if hasattr(ds.relax_gui.analyses[i], 'analysis_name'): 312 analysis_name = ds.relax_gui.analyses[i].analysis_name 313 elif ds.relax_gui.analyses[i].analysis_type == 'NOE': 314 analysis_name = 'Steady-state NOE' 315 elif ds.relax_gui.analyses[i].analysis_type == 'R1': 316 analysis_name = 'R1 relaxation' 317 elif ds.relax_gui.analyses[i].analysis_type == 'R2': 318 analysis_name = 'R2 relaxation' 319 elif ds.relax_gui.analyses[i].analysis_type == 'model-free': 320 analysis_name = 'Model-free' 321 322 # Set up the analysis. 323 self._switch_flag = False 324 self.new_analysis(map[ds.relax_gui.analyses[i].analysis_type], analysis_name, index=i) 325 326 # Switch to the page of the current data pipe. 327 self.pipe_switch() 328 329 # Reset the switching flag. 330 self._switch_flag = True 331 332 # Notify the observers of the change. 333 status.observers.gui_analysis.notify()
334 335
336 - def menu_close(self, event):
337 """Close the currently opened analysis. 338 339 @param event: The wx event. 340 @type event: wx event 341 """ 342 343 # Notebook not created yet, so skip. 344 if not hasattr(self, 'notebook'): 345 return 346 347 # Execution lock. 348 if status.exec_lock.locked(): 349 return 350 351 # Get the current analysis index. 352 index = self.notebook.GetSelection() 353 354 # Ask if this should be done. 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 # Delete. 360 self.delete_analysis(index)
361 362
363 - def menu_close_all(self, event):
364 """Close all analyses. 365 366 @param event: The wx event. 367 @type event: wx event 368 """ 369 370 # Notebook not created yet, so skip. 371 if not hasattr(self, 'notebook'): 372 return 373 374 # Execution lock. 375 if status.exec_lock.locked(): 376 return 377 378 # Ask if this should be done. 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 # Delete. 384 self.delete_all()
385 386
387 - def menu_new(self, event):
388 """Launch a wizard to select the new analysis. 389 390 @param event: The wx event. 391 @type event: wx event 392 """ 393 394 # Execution lock. 395 if status.exec_lock.locked(): 396 return 397 398 # Initialise the analysis wizard, and obtain the user specified data. 399 self.new_wizard = Analysis_wizard() 400 data = self.new_wizard.run() 401 402 # Failure, so do nothing. 403 if data == None: 404 return 405 406 # Unpack the data. 407 analysis_type, analysis_name, pipe_name = data 408 409 # Initialise the new analysis. 410 self.new_analysis(analysis_type, analysis_name, pipe_name) 411 412 # Delete the wizard data. 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 # Check the C modules. 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 # Freeze the GUI. 433 wx.Yield() 434 wx.BeginBusyCursor() 435 self.gui.Freeze() 436 437 # Starting from the initial state. 438 if self.init_state: 439 # A new sizer for the notebook (to replace the current sizer). 440 sizer = wx.BoxSizer(wx.VERTICAL) 441 442 # Create a notebook and add it to the sizer. 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 # Bind changing events. 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 # Delete the previous sizer. 451 old_sizer = self.gui.GetSizer() 452 old_sizer.DeleteWindows() 453 454 # Add the new sizer to the main window. 455 self.gui.SetSizer(sizer) 456 sizer.Layout() 457 458 # The analysis classes. 459 classes = {'noe': Auto_noe, 460 'r1': Auto_r1, 461 'r2': Auto_r2, 462 'mf': Auto_model_free} 463 464 # Bad analysis type. 465 if analysis_type not in classes.keys(): 466 raise RelaxError("The analysis '%s' is unknown." % analysis_type) 467 468 # Initialise the class. 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 # Failure. 472 if not analysis.init_flag: 473 # Reset. 474 if self.init_state: 475 self.set_init_state() 476 477 # Stop operation. 478 return 479 480 # Append the class object to the analysis window object. 481 self._analyses.append(analysis) 482 483 # Add to the notebook. 484 self.notebook.AddPage(self._analyses[-1], analysis_name) 485 486 # Increment the number of analyses. 487 self._num_analyses += 1 488 489 # Switch to the new page. 490 if self._switch_flag: 491 self.switch_page(self._num_analyses-1) 492 493 # Set the initialisation flag. 494 self.init_state = False 495 496 # Reset the main window layout. 497 self.gui.Layout() 498 499 # Thaw the GUI. 500 self.gui.Thaw() 501 if wx.IsBusy(): 502 wx.EndBusyCursor() 503 504 # Notify the observers of the change. 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 # Execution lock. 516 if status.exec_lock.locked(): 517 # Show an error message. 518 error_message("Cannot change analyses, relax is currently executing.", "relax execution lock") 519 520 # Veto the event. 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 # The index. 532 self._current = event.GetSelection() 533 534 # Handel calls to the reset user function! 535 if not hasattr(ds, 'relax_gui'): 536 return 537 538 # Switch to the major data pipe of that page if not the current one. 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 # Normal operation. 543 event.Skip() 544 545 # Notify the observers of the change. 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 # Find the index. 559 index = None 560 for i in range(self._num_analyses): 561 # Matching page. 562 if ds.relax_gui.analyses[i].pipe_name == pipe: 563 index = i 564 break 565 566 # Return the index. 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 # Find the index. 580 index = self.page_index_from_pipe(pipe) 581 582 # No matching page. 583 if index == None: 584 return 585 586 # Return the page name. 587 return ds.relax_gui.analyses[index].analysis_name
588 589
590 - def pipe_deletion(self):
591 """Remove analysis tabs for which the associated data pipe has been deleted.""" 592 593 # Loop over the analyses, noting which no longer have a data pipe. 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 # Reverse the order of the list so the removal works correctly. 600 del_list.reverse() 601 602 # Delete the analyses. 603 for index in del_list: 604 self.delete_analysis(index)
605 606
607 - def pipe_switch(self, pipe=None):
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 # The data pipe. 615 if pipe == None: 616 pipe = pipes.cdp_name() 617 618 # Find the page. 619 index = self.page_index_from_pipe(pipe) 620 621 # No matching page. 622 if index == None: 623 return 624 625 # The page is already active, so do nothing. 626 if self._current == index: 627 return 628 629 # Switch to the page. 630 self.switch_page(index) 631 632 # Notify the observers of the change. 633 status.observers.gui_analysis.notify()
634 635
636 - def post_reset(self):
637 """Post relax data store reset event handler.""" 638 639 # Delete all tabs. 640 while self._num_analyses: 641 # The index of the tab to remove. 642 index = self._num_analyses - 1 643 644 # Delete the tab. 645 if hasattr(self, 'notebook'): 646 self.notebook.DeletePage(index) 647 648 # Delete the tab object. 649 self._analyses.pop(index) 650 651 # Decrement the number of analyses. 652 self._num_analyses -= 1 653 654 # Set the initial state. 655 self.set_init_state()
656 657
658 - def set_init_state(self):
659 """Revert to the initial state.""" 660 661 # Reset the flag. 662 self.init_state = True 663 self._current = None 664 665 # Delete the previous sizer. 666 old_sizer = self.gui.GetSizer() 667 old_sizer.DeleteWindows() 668 669 # Delete the notebook. 670 if hasattr(self, 'notebook'): 671 del self.notebook 672 673 # Recreate the GUI data store object (needed if the reset user function is called). 674 if not hasattr(ds, 'relax_gui'): 675 ds.relax_gui = Gui() 676 677 # Recreate the start screen. 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 # Set the current page number. 689 self._current = index 690 691 # Switch to the page. 692 wx.CallAfter(self.notebook.SetSelection, self._current) 693 694 # Notify the observers of the change. 695 wx.CallAfter(status.observers.gui_analysis.notify)
696