Package gui :: Module relax_gui
[hide private]
[frames] | no frames]

Source Code for Module gui.relax_gui

   1  ############################################################################### 
   2  #                                                                             # 
   3  # Copyright (C) 2009-2011 Michael Bieri                                       # 
   4  # Copyright (C) 2009-2014,2019 Edward d'Auvergne                              # 
   5  # Copyright (C) 2016 Troels Schwarz-Linnet                                    # 
   6  #                                                                             # 
   7  # This file is part of the program relax (http://www.nmr-relax.com).          # 
   8  #                                                                             # 
   9  # This program is free software: you can redistribute it and/or modify        # 
  10  # it under the terms of the GNU General Public License as published by        # 
  11  # the Free Software Foundation, either version 3 of the License, or           # 
  12  # (at your option) any later version.                                         # 
  13  #                                                                             # 
  14  # This program is distributed in the hope that it will be useful,             # 
  15  # but WITHOUT ANY WARRANTY; without even the implied warranty of              # 
  16  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               # 
  17  # GNU General Public License for more details.                                # 
  18  #                                                                             # 
  19  # You should have received a copy of the GNU General Public License           # 
  20  # along with this program.  If not, see <http://www.gnu.org/licenses/>.       # 
  21  #                                                                             # 
  22  ############################################################################### 
  23   
  24  # Module docstring. 
  25  """Main module for the relax graphical user interface.""" 
  26   
  27  # Python module imports. 
  28  from os import F_OK, access, getcwd, sep 
  29  import platform 
  30  from re import search 
  31  import sys 
  32  from time import sleep 
  33  from warnings import warn 
  34  import webbrowser 
  35  import wx 
  36   
  37  # relax module imports. 
  38  from data_store import Relax_data_store; ds = Relax_data_store() 
  39  from data_store.gui import Gui 
  40  import dep_check 
  41  from graphics import IMAGE_PATH, fetch_icon 
  42  from gui.about import About_relax 
  43  from gui.analyses import Analysis_controller 
  44  from gui.spin_viewer.frame import Spin_view_window 
  45  from gui.controller import Controller 
  46  from gui.export_bmrb import Export_bmrb_window 
  47  from gui.filedialog import RelaxDirDialog, RelaxFileDialog 
  48  from gui.fonts import font 
  49  from gui.icons import Relax_icons 
  50  from gui.interpreter import Interpreter 
  51  from gui.menu import Menu 
  52  from gui.message import error_message, Question 
  53  from gui.misc import bitmap_setup, gui_raise, open_file, protected_exec 
  54  from gui.pipe_editor import Pipe_editor 
  55  from gui.references import References 
  56  from gui.relax_prompt import Prompt 
  57  from gui.results_viewer import Results_viewer 
  58  from gui.components.free_file_format import Free_file_format_window 
  59  from gui.string_conv import gui_to_str 
  60  from gui.uf_objects import Uf_storage; uf_store = Uf_storage() 
  61  from info import Info_box 
  62  from lib.errors import RelaxNoPipeError 
  63  from lib.warnings import RelaxWarning 
  64  from lib.io import io_streams_restore 
  65  from pipe_control import state 
  66  from pipe_control.pipes import cdp_name 
  67  from pipe_control.reset import reset 
  68  from pipe_control.system import pwd 
  69  from status import Status; status = Status() 
  70  from version import repo_head, version 
  71   
  72   
  73  # wx IDs for the toolbar. 
  74  TB_FILE_NEW = wx.NewId() 
  75  TB_FILE_CLOSE = wx.NewId() 
  76  TB_FILE_CLOSE_ALL = wx.NewId() 
  77  TB_FILE_CWD = wx.NewId() 
  78  TB_FILE_OPEN = wx.NewId() 
  79  TB_FILE_SAVE = wx.NewId() 
  80  TB_FILE_SAVE_AS = wx.NewId() 
  81  TB_VIEW_CONTROLLER = wx.NewId() 
  82  TB_VIEW_SPIN_VIEW = wx.NewId() 
  83  TB_VIEW_RESULTS = wx.NewId() 
  84  TB_VIEW_PIPE_EDIT = wx.NewId() 
  85  TB_VIEW_PROMPT = wx.NewId() 
  86   
  87   
  88   
89 -class Main(wx.Frame):
90 """The main GUI class.""" 91 92 # Hard coded variables. 93 min_width = 1000 94 min_height = 600 95
96 - def __init__(self, parent=None, id=-1, title=""):
97 """Initialise the main relax GUI frame.""" 98 99 # Store the wxPython info for os/machine/version specific hacks. 100 status.wx_info = {} 101 status.wx_info["version"] = wx.__version__.split('.') 102 status.wx_info["minor"] = "%s.%s" % (status.wx_info["version"][0], status.wx_info["version"][1]) 103 status.wx_info["os"] = sys.platform 104 status.wx_info["build"] = None 105 if search('gtk2', wx.version()): 106 status.wx_info["build"] = 'gtk' 107 elif search('cocoa', wx.version()): 108 status.wx_info["build"] = 'cocoa' 109 elif search('mac-unicode', wx.version()): 110 status.wx_info["build"] = 'carbon' 111 status.wx_info["full"] = None 112 if status.wx_info["build"]: 113 status.wx_info["full"] = "%s-%s" % (status.wx_info["os"], status.wx_info["build"]) 114 115 # Some internal variables. 116 self.test_suite_flag = False 117 118 # The main window style. 119 style = wx.DEFAULT_FRAME_STYLE 120 if not status.debug and status.wx_info["os"] != 'darwin': 121 style = style | wx.MAXIMIZE 122 123 # Execute the base class __init__ method. 124 super(Main, self).__init__(parent=parent, id=id, title=title, style=style) 125 126 # Force the main window to start maximised (needed for MS Windows). 127 if not status.debug and status.wx_info["os"] != 'darwin': 128 self.Maximize() 129 130 # Set up some standard interface-wide fonts. 131 font.setup() 132 133 # Set up the relax icons. 134 relax_icons = Relax_icons() 135 relax_icons.setup() 136 self.SetIcons(relax_icons) 137 138 # Initialise some variables for the GUI. 139 self.launch_dir = getcwd() 140 141 # Set up the frame. 142 self.Layout() 143 self.SetSize((self.min_width, self.min_height)) 144 self.SetMinSize((self.min_width, self.min_height)) 145 self.Centre() 146 147 # The analysis window object storage. 148 self.analysis = Analysis_controller(self) 149 150 # The calculation threads list. 151 self.calc_threads = [] 152 153 # Initialise the GUI data. 154 self.init_data() 155 156 # Build the menu bar. 157 self.menu = Menu(self) 158 159 # Build the toolbar. 160 self.build_toolbar() 161 162 # Build the controller, but don't show it. 163 self.controller = Controller(self) 164 165 # Set the window title. 166 if version == "repository commit": 167 win_title = "relax %s %s" % (version, repo_head) 168 else: 169 win_title = "relax %s" % version 170 self.SetTitle(win_title) 171 172 # Set up the status bar. 173 self.status_bar = self.CreateStatusBar(4, 0) 174 self.status_bar.SetStatusWidths([-4, -4, -1, -2]) 175 self.update_status_bar() 176 177 # Add the start screen. 178 self.add_start_screen() 179 180 # Close Box event. 181 self.Bind(wx.EVT_CLOSE, self.exit_gui) 182 183 # Initialise the special interpreter thread object. 184 self.interpreter = Interpreter() 185 186 # Register functions with the observer objects. 187 status.observers.pipe_alteration.register('status bar', self.update_status_bar, method_name='update_status_bar') 188 status.observers.system_cwd_path.register('status bar', self.update_status_bar, method_name='update_status_bar') 189 status.observers.result_file.register('gui', self.show_results_viewer_no_warn, method_name='show_results_viewer_no_warn') 190 status.observers.exec_lock.register('gui', self.enable, method_name='enab') 191 192 # Assume a script has been run and there is data in the store. 193 self.analysis.load_from_store()
194 195
196 - def about_relax(self, event=None):
197 """The about message for relax. 198 199 @keyword event: The wx event. 200 @type event: wx event 201 """ 202 203 # Build the dialog. 204 dialog = About_relax(None, -1) 205 206 # The dialog. 207 if status.show_gui: 208 dialog.Show()
209 210
211 - def action_export_bmrb(self, event=None):
212 """Export the contents of the current data pipe for BMRB deposition. 213 214 @keyword event: The wx event. 215 @type event: wx event 216 """ 217 218 # No current data pipe. 219 if not cdp_name(): 220 gui_raise(RelaxNoPipeError()) 221 return 222 223 # Open the export window. 224 Export_bmrb_window(self)
225 226
227 - def action_state_save(self, event=None):
228 """Save the program state. 229 230 @keyword event: The wx event. 231 @type event: wx event 232 """ 233 234 # Not saved yet, therefore pass execution to state_save_as(). 235 if not self.save_file: 236 self.action_state_save_as(event) 237 return 238 239 # Save. 240 self.state_save()
241 242
243 - def action_state_save_as(self, event=None):
244 """Save the program state with file name selection. 245 246 @keyword event: The wx event. 247 @type event: wx event 248 """ 249 250 # The dialog. 251 dialog = RelaxFileDialog(parent=self, message='Select the relax save state file', defaultFile='state.bz2', wildcard='relax save file (*.bz2)|*.bz2', style=wx.FD_SAVE) 252 253 # Show the dialog and catch if no file has been selected. 254 if status.show_gui and dialog.ShowModal() != wx.ID_OK: 255 # Don't do anything. 256 return 257 258 # The file. 259 file_name = dialog.get_file() 260 261 # Set the file name. 262 self.save_file = file_name 263 264 # Save. 265 self.state_save()
266 267
268 - def add_start_screen(self):
269 """Create a start screen for the main window when no analyses exist.""" 270 271 # The sizer for the main GUI window. 272 sizer = wx.BoxSizer(wx.VERTICAL) 273 self.SetSizer(sizer) 274 275 # The relax icon. 276 image = wx.StaticBitmap(self, -1, bitmap_setup(IMAGE_PATH+'ulysses_shadowless_400x168.png')) 277 sizer.AddStretchSpacer() 278 sizer.Add(image, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) 279 sizer.AddStretchSpacer() 280 281 # wxPython-Phoenix instability warning text. 282 if not dep_check.wx_stable: 283 text = [ 284 "wxPython-Phoenix version %s.%s.%s detected." % (wx.VERSION[0], wx.VERSION[1], wx.VERSION[2]), 285 "This version of Phoenix is not stable and relax support is experimental.", 286 "Not all features of the GUI may be available or functional.", 287 "Please use wxPython-Phoenix version >= 4.1.0 or \"Classic\" instead - otherwise use at your own risk." 288 ] 289 for i in range(len(text)): 290 element = wx.StaticText(self, -1, text[i]) 291 element.SetFont(font.roman_font_18) 292 element.SetForegroundColour("red") 293 sizer.Add(element, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0) 294 sizer.AddStretchSpacer() 295 warn(RelaxWarning(" ".join(text))) 296 297 # Re-perform the layout of the GUI elements, and refresh. 298 self.Layout() 299 self.Refresh()
300 301
302 - def build_toolbar(self):
303 """Create the toolbar.""" 304 305 # Init. 306 self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL|wx.TB_FLAT) 307 308 # The new analysis button. 309 if dep_check.wx_classic: 310 self.toolbar.AddLabelTool(TB_FILE_NEW, "New analysis", wx.Bitmap(fetch_icon('oxygen.actions.document-new', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="New analysis") 311 else: 312 self.toolbar.AddTool(TB_FILE_NEW, "New analysis", wx.Bitmap(fetch_icon('oxygen.actions.document-new', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="New analysis") 313 self.Bind(wx.EVT_TOOL, self.analysis.menu_new, id=TB_FILE_NEW) 314 315 # The close analysis button. 316 if dep_check.wx_classic: 317 self.toolbar.AddLabelTool(TB_FILE_CLOSE, "Close analysis", wx.Bitmap(fetch_icon('oxygen.actions.document-close', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Close analysis") 318 else: 319 self.toolbar.AddTool(TB_FILE_CLOSE, "Close analysis", wx.Bitmap(fetch_icon('oxygen.actions.document-close', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Close analysis") 320 self.Bind(wx.EVT_TOOL, self.analysis.menu_close, id=TB_FILE_CLOSE) 321 322 # The close all analyses button. 323 if dep_check.wx_classic: 324 self.toolbar.AddLabelTool(TB_FILE_CLOSE_ALL, "Close all analyses", wx.Bitmap(fetch_icon('oxygen.actions.dialog-close', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Close all analyses") 325 else: 326 self.toolbar.AddTool(TB_FILE_CLOSE_ALL, "Close all analyses", wx.Bitmap(fetch_icon('oxygen.actions.dialog-close', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Close all analyses") 327 self.Bind(wx.EVT_TOOL, self.analysis.menu_close_all, id=TB_FILE_CLOSE_ALL) 328 329 # A separator. 330 self.toolbar.AddSeparator() 331 332 # The change working directory button. 333 if dep_check.wx_classic: 334 self.toolbar.AddLabelTool(TB_FILE_CWD, "Change working directory", wx.Bitmap(fetch_icon('oxygen.places.folder-favorites', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Change working directory") 335 else: 336 self.toolbar.AddTool(TB_FILE_CWD, "Change working directory", wx.Bitmap(fetch_icon('oxygen.places.folder-favorites', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Change working directory") 337 self.Bind(wx.EVT_TOOL, self.system_cwd, id=TB_FILE_CWD) 338 339 # The open state button. 340 if dep_check.wx_classic: 341 self.toolbar.AddLabelTool(TB_FILE_OPEN, "Open relax state", wx.Bitmap(fetch_icon('oxygen.actions.document-open', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Open relax state") 342 else: 343 self.toolbar.AddTool(TB_FILE_OPEN, "Open relax state", wx.Bitmap(fetch_icon('oxygen.actions.document-open', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Open relax state") 344 self.Bind(wx.EVT_TOOL, self.state_load, id=TB_FILE_OPEN) 345 346 # The save state button. 347 if dep_check.wx_classic: 348 self.toolbar.AddLabelTool(TB_FILE_SAVE, "Save relax state", wx.Bitmap(fetch_icon('oxygen.actions.document-save', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Save relax state") 349 else: 350 self.toolbar.AddTool(TB_FILE_SAVE, "Save relax state", wx.Bitmap(fetch_icon('oxygen.actions.document-save', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Save relax state") 351 self.Bind(wx.EVT_TOOL, self.action_state_save, id=TB_FILE_SAVE) 352 353 # The save as button. 354 if dep_check.wx_classic: 355 self.toolbar.AddLabelTool(TB_FILE_SAVE_AS, "Save as", wx.Bitmap(fetch_icon('oxygen.actions.document-save-as', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Save as") 356 else: 357 self.toolbar.AddTool(TB_FILE_SAVE_AS, "Save as", wx.Bitmap(fetch_icon('oxygen.actions.document-save-as', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Save as") 358 self.Bind(wx.EVT_TOOL, self.action_state_save_as, id=TB_FILE_SAVE_AS) 359 360 # A separator. 361 self.toolbar.AddSeparator() 362 363 # The relax controller button. 364 if dep_check.wx_classic: 365 self.toolbar.AddLabelTool(TB_VIEW_CONTROLLER, "Controller", wx.Bitmap(fetch_icon('oxygen.apps.preferences-system-performance', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="relax controller") 366 else: 367 self.toolbar.AddTool(TB_VIEW_CONTROLLER, "Controller", wx.Bitmap(fetch_icon('oxygen.apps.preferences-system-performance', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="relax controller") 368 self.Bind(wx.EVT_TOOL, self.show_controller, id=TB_VIEW_CONTROLLER) 369 370 # The spin viewer button. 371 if dep_check.wx_classic: 372 self.toolbar.AddLabelTool(TB_VIEW_SPIN_VIEW, "Spin viewer", wx.Bitmap(fetch_icon('relax.spin', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Spin viewer window") 373 else: 374 self.toolbar.AddTool(TB_VIEW_SPIN_VIEW, "Spin viewer", wx.Bitmap(fetch_icon('relax.spin', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Spin viewer window") 375 self.Bind(wx.EVT_TOOL, self.show_tree, id=TB_VIEW_SPIN_VIEW) 376 377 # The results viewer button. 378 if dep_check.wx_classic: 379 self.toolbar.AddLabelTool(TB_VIEW_RESULTS, "Results viewer", wx.Bitmap(fetch_icon('oxygen.actions.view-statistics', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Results viewer window") 380 else: 381 self.toolbar.AddTool(TB_VIEW_RESULTS, "Results viewer", wx.Bitmap(fetch_icon('oxygen.actions.view-statistics', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Results viewer window") 382 self.Bind(wx.EVT_TOOL, self.show_results_viewer, id=TB_VIEW_RESULTS) 383 384 # The data pipe editor button. 385 if dep_check.wx_classic: 386 self.toolbar.AddLabelTool(TB_VIEW_PIPE_EDIT, "Data pipe editor", wx.Bitmap(fetch_icon('relax.pipe', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Data pipe editor") 387 else: 388 self.toolbar.AddTool(TB_VIEW_PIPE_EDIT, "Data pipe editor", wx.Bitmap(fetch_icon('relax.pipe', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="Data pipe editor") 389 self.Bind(wx.EVT_TOOL, self.show_pipe_editor, id=TB_VIEW_PIPE_EDIT) 390 391 # The relax prompt button. 392 if dep_check.wx_classic: 393 self.toolbar.AddLabelTool(TB_VIEW_PROMPT, "relax prompt", wx.Bitmap(fetch_icon('oxygen.mimetypes.application-x-executable-script', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="The relax prompt GUI window") 394 else: 395 self.toolbar.AddTool(TB_VIEW_PROMPT, "relax prompt", wx.Bitmap(fetch_icon('oxygen.mimetypes.application-x-executable-script', "22x22"), wx.BITMAP_TYPE_ANY), shortHelp="The relax prompt GUI window") 396 self.Bind(wx.EVT_TOOL, self.show_prompt, id=TB_VIEW_PROMPT) 397 398 # Build the toolbar. 399 self.toolbar.Realize()
400 401
402 - def close_windows(self):
403 """Throw a warning to close all of the non-essential windows when execution is locked. 404 405 This is to speed up the calculations by avoiding window updates. 406 """ 407 408 # Init the window list. 409 win_list = [] 410 411 # Is the spin viewer window open? 412 if hasattr(self, 'spin_viewer') and self.spin_viewer.IsShown(): 413 win_list.append('The spin viewer window') 414 415 # Is the pipe editor window open? 416 if hasattr(self, 'pipe_editor') and self.pipe_editor.IsShown(): 417 win_list.append('The data pipe editor window') 418 419 # Is the results viewer window open? 420 if hasattr(self, 'results_viewer') and self.results_viewer.IsShown(): 421 win_list.append('The results viewer window') 422 423 # The windows are not open, so quit. 424 if not len(win_list): 425 return 426 427 # The text. 428 text = "The following windows are currently open:\n\n" 429 for win in win_list: 430 text = "%s\t%s.\n" % (text, win) 431 text = text + "\nClosing these will significantly speed up the calculations." 432 433 # Display the error message dialog. 434 dlg = wx.MessageDialog(self, text, caption="Close windows", style=wx.OK|wx.ICON_EXCLAMATION|wx.STAY_ON_TOP) 435 if status.show_gui: 436 dlg.ShowModal() 437 438 # Otherwise output to stderr. 439 else: 440 sys.stderr.write(text) 441 sys.stderr.flush()
442 443
444 - def contact_relax(self, event=None):
445 """Write an email to the relax mailing-list using the standard mailing program. 446 447 @keyword event: The wx event. 448 @type event: wx event 449 """ 450 451 webbrowser.open_new('mailto:relax-users@gna.org')
452 453
454 - def enable(self):
455 """Enable and disable certain parts of the main window with the execution lock.""" 456 457 # Flag for enabling or disabling the elements. 458 enable = False 459 if not status.exec_lock.locked(): 460 enable = True 461 462 # The toolbar. 463 wx.CallAfter(self.toolbar.EnableTool, TB_FILE_NEW, enable) 464 wx.CallAfter(self.toolbar.EnableTool, TB_FILE_CLOSE, enable) 465 wx.CallAfter(self.toolbar.EnableTool, TB_FILE_CLOSE_ALL, enable) 466 wx.CallAfter(self.toolbar.EnableTool, TB_FILE_CWD, enable) 467 wx.CallAfter(self.toolbar.EnableTool, TB_FILE_OPEN, enable) 468 wx.CallAfter(self.toolbar.EnableTool, TB_FILE_SAVE, enable) 469 wx.CallAfter(self.toolbar.EnableTool, TB_FILE_SAVE_AS, enable)
470 471
472 - def exit_gui(self, event=None):
473 """Catch the main window closure and perform the exit procedure. 474 475 @keyword event: The wx event. 476 @type event: wx event 477 """ 478 479 # Ask if the user is sure they would like to exit. 480 doexit = wx.ID_YES 481 if status.show_gui and not ds.is_empty(): 482 doexit = Question('Are you sure you would like to quit relax? All unsaved data will be lost.', title='Exit relax', default=True).ShowModal() 483 484 # Exit. 485 if doexit == wx.ID_YES: 486 # Restore the IO streams. 487 io_streams_restore(verbosity=0) 488 489 # The relax information box. 490 info = Info_box() 491 492 # The width of the printout. 493 if platform.uname()[0] in ['Windows', 'Microsoft']: 494 width = 80 495 else: 496 width = 100 497 498 # Remove the Mac OS X task bar icon. 499 if hasattr(self, 'taskbar_icon'): 500 self.taskbar_icon.Destroy() 501 502 # Terminate the interpreter thread to allow for a cleaner exit. 503 self.interpreter.exit() 504 505 # End the GUI main loop. 506 app = wx.GetApp() 507 app.ExitMainLoop()
508 509
510 - def init_data(self):
511 """Initialise the data used by the GUI interface.""" 512 513 # Temporary data: the save file. 514 self.save_file = None 515 self.system_cwd_path = pwd(verbose=False) 516 517 # Add the GUI object to the data store, if not present. 518 if not hasattr(ds, 'relax_gui'): 519 ds.relax_gui = Gui()
520 521
522 - def free_file_format_settings(self, event=None):
523 """Open the free file format settings window. 524 525 @keyword event: The wx event. 526 @type event: wx event 527 """ 528 529 # Build the window. 530 win = Free_file_format_window() 531 532 # Show the window. 533 if status.show_gui: 534 win.Show()
535 536
537 - def references(self, event=None):
538 """Display the references relevant for relax. 539 540 @keyword event: The wx event. 541 @type event: wx event 542 """ 543 544 # Build and show the references window. 545 self.references = References(self) 546 if status.show_gui: 547 self.references.Show()
548 549
550 - def relax_manual(self, event=None):
551 """Display the relax manual. 552 553 @keyword event: The wx event. 554 @type event: wx event 555 """ 556 557 # The PDF manual. 558 file = status.install_path + sep+"docs"+sep+"relax.pdf" 559 560 # Test if it exists. 561 if not access(file, F_OK): 562 error_message("The relax manual '%s' cannot be found. Please compile using the scons program." % file) 563 return 564 565 # Open the relax PDF manual using the native PDF reader. 566 open_file(file)
567 568
569 - def reset(self):
570 """Reset the GUI.""" 571 572 # Close some GUI windows, if open. 573 windows = ['pipe_editor', 'relax_prompt', 'results_viewer', 'spin_viewer'] 574 for window in windows: 575 if hasattr(self, window): 576 # Get the object. 577 win_obj = getattr(self, window) 578 579 # Close the window. 580 win_obj.Close() 581 582 # Flush all wx events to make sure the GUI is ready for the next test. 583 wx.Yield() 584 585 # Reset the relax controller. 586 self.controller.reset()
587 588
589 - def run_test_suite(self, event=None, categories=['system', 'unit', 'gui', 'verification']):
590 """Execute the full test suite. 591 592 @keyword event: The wx event. 593 @type event: wx event 594 @keyword categories: The list of test categories to run, for example ['system', 'unit', 'gui', 'verification'] for all tests. 595 @type categories: list of str 596 """ 597 598 # Ask if this should be done. 599 msg = "In running the test suite, relax will be reset and all data lost. Are you sure you would like to run the test suite?" 600 if Question(msg, parent=self, size=(400, 150), default=False).ShowModal() == wx.ID_NO: 601 return 602 603 # Set the test suite flag. 604 self.test_suite_flag = True 605 606 # Change the cursor to waiting. 607 wx.BeginBusyCursor() 608 609 # Set a new style to stay on top, refreshing to update the style (needed for Mac OS X and MS Windows). 610 orig_style = self.controller.GetWindowStyle() 611 self.controller.SetWindowStyle(orig_style | wx.STAY_ON_TOP) 612 self.controller.Refresh() 613 614 # Make the relax controller modal so that all other windows are deactivated (to stop users from clicking on things). 615 self.controller.MakeModal(True) 616 617 # Close all open windows. 618 if hasattr(self, 'spin_viewer'): 619 self.spin_viewer.Close() 620 if hasattr(self, 'pipe_editor'): 621 self.pipe_editor.Close() 622 if hasattr(self, 'results_viewer'): 623 self.results_viewer.Close() 624 if hasattr(self, 'relax_prompt'): 625 self.relax_prompt.Close() 626 627 # Reset relax. 628 reset() 629 630 # Show the relax controller. 631 self.show_controller(event) 632 633 # Yield 634 wx.GetApp().Yield(True) 635 636 # Prevent all new GUI elements from being shown. 637 status.show_gui = False 638 639 # Run the tests (with the import here to break a nasty circular import). 640 import test_suite.test_suite_runner 641 runner = test_suite.test_suite_runner.Test_suite_runner([], from_gui=True, categories=categories) 642 runner.run_all_tests() 643 644 # Reactive the GUI. 645 status.show_gui = True 646 647 # Turn off the busy cursor. 648 if wx.IsBusy(): 649 wx.EndBusyCursor() 650 651 # Restore the controller. 652 self.controller.SetWindowStyle(orig_style) 653 self.controller.MakeModal(False) 654 self.controller.Refresh() 655 656 # Unset the test suite flag. 657 self.test_suite_flag = False 658 659 # Set the controller main gauge to 100%. 660 wx.CallAfter(self.controller.main_gauge.SetValue, 100)
661 662
663 - def run_test_suite_gui(self, event=None):
664 """Execute the GUI tests. 665 666 @keyword event: The wx event. 667 @type event: wx event 668 """ 669 670 # Forward the call. 671 self.run_test_suite(event, categories=['gui'])
672 673
674 - def run_test_suite_sys(self, event=None):
675 """Execute the system tests. 676 677 @keyword event: The wx event. 678 @type event: wx event 679 """ 680 681 # Forward the call. 682 self.run_test_suite(event, categories=['system'])
683 684
685 - def run_test_suite_unit(self, event=None):
686 """Execute the unit tests. 687 688 @keyword event: The wx event. 689 @type event: wx event 690 """ 691 692 # Forward the call. 693 self.run_test_suite(event, categories=['unit'])
694 695
696 - def run_test_suite_verification(self, event=None):
697 """Execute the verification tests. 698 699 @keyword event: The wx event. 700 @type event: wx event 701 """ 702 703 # Forward the call. 704 self.run_test_suite(event, categories=['verification'])
705 706
707 - def show_controller(self, event=None):
708 """Display the relax controller window. 709 710 @keyword event: The wx event. 711 @type event: wx event 712 """ 713 714 # Bring the window to the front. 715 if self.controller.IsShown(): 716 self.controller.Raise() 717 return 718 719 # Open the window. 720 if status.show_gui: 721 self.controller.Show()
722 723
724 - def show_pipe_editor(self, event=None):
725 """Display the data pipe editor window. 726 727 @keyword event: The wx event. 728 @type event: wx event 729 """ 730 731 # Throw a warning if the execution lock is on. 732 if status.exec_lock.locked(): 733 dlg = wx.MessageDialog(self, "Leaving the pipe editor window open will slow down the calculations.", caption="Warning", style=wx.OK|wx.ICON_EXCLAMATION|wx.STAY_ON_TOP) 734 if status.show_gui: 735 dlg.ShowModal() 736 737 # Build the pipe editor if needed. 738 if not hasattr(self, 'pipe_editor'): 739 self.pipe_editor = Pipe_editor(gui=self) 740 741 # Bring the window to the front. 742 if self.pipe_editor.IsShown(): 743 self.pipe_editor.Raise() 744 return 745 746 # Open the window. 747 if status.show_gui and not self.pipe_editor.IsShown(): 748 self.pipe_editor.Show() 749 750 # Update the grid. 751 self.pipe_editor.update_grid() 752 self.pipe_editor.activate() 753 754 # Register the grid for updating when a user function completes or when the GUI analysis tabs change (needed here for the window hiding and associated unregistering). 755 self.pipe_editor.observer_setup(register=True)
756 757
758 - def show_prompt(self, event=None):
759 """Display the relax prompt window. 760 761 @keyword event: The wx event. 762 @type event: wx event 763 """ 764 765 # Build the relax prompt if needed. 766 if not hasattr(self, 'relax_prompt'): 767 self.relax_prompt = Prompt(None, -1, "", parent=self) 768 769 # Bring the window to the front. 770 if self.relax_prompt.IsShown(): 771 self.relax_prompt.Raise() 772 return 773 774 # Open the window. 775 if status.show_gui: 776 self.relax_prompt.Show()
777 778
779 - def show_results_viewer(self, event=None):
780 """Display the analysis results. 781 782 @keyword event: The wx event. 783 @type event: wx event 784 """ 785 786 # Show the results viewer in a thread safe way. 787 wx.CallAfter(self.show_results_viewer_safe, warn=True)
788 789
790 - def show_results_viewer_safe(self, warn=False):
791 """Display the analysis results in a thread safe wx.CallAfter call. 792 793 @keyword warn: A flag which if True will cause a message dialog to appear warning about keeping the window open with the execution lock. 794 @type warn: bool 795 """ 796 797 # Throw a warning if the execution lock is on. 798 if warn and status.exec_lock.locked(): 799 dlg = wx.MessageDialog(self, "Leaving the results viewer window open will slow down the calculations.", caption="Warning", style=wx.OK|wx.ICON_EXCLAMATION|wx.STAY_ON_TOP) 800 if status.show_gui: 801 wx.CallAfter(dlg.ShowModal) 802 803 # Create the results viewer window if needed. 804 if not hasattr(self, 'results_viewer'): 805 self.results_viewer = Results_viewer(self) 806 807 # Bring the window to the front. 808 if self.results_viewer.IsShown(): 809 self.results_viewer.Raise() 810 return 811 812 # Open the window. 813 if status.show_gui and not self.results_viewer.IsShown(): 814 self.results_viewer.Show()
815 816
818 """Display the analysis results.""" 819 820 # Show the results viewer in a thread safe way with no warning dialog. 821 wx.CallAfter(self.show_results_viewer_safe, warn=False)
822 823
824 - def show_tree(self, event=None):
825 """Display the molecule, residue, and spin tree window. 826 827 @keyword event: The wx event. 828 @type event: wx event 829 """ 830 831 # Throw a warning if the execution lock is on. 832 if status.exec_lock.locked(): 833 dlg = wx.MessageDialog(self, "Leaving the spin viewer window open will slow down the calculations.", caption="Warning", style=wx.OK|wx.ICON_EXCLAMATION|wx.STAY_ON_TOP) 834 if status.show_gui: 835 dlg.ShowModal() 836 837 # Build the spin view window. 838 if not hasattr(self, 'spin_viewer'): 839 self.spin_viewer = Spin_view_window(None, -1, "", parent=self) 840 841 # Bring the window to the front. 842 if self.spin_viewer.IsShown(): 843 self.spin_viewer.Raise() 844 return 845 846 # Open the window (the GUI flag check is inside the Show method). 847 if not self.spin_viewer.IsShown(): 848 self.spin_viewer.Show(show=status.show_gui)
849 850
851 - def state_load(self, event=None, file_name=None):
852 """Load the program state. 853 854 @keyword event: The wx event. 855 @type event: wx event 856 @keyword file_name: The name of the file to load (for dialogless operation). 857 @type file_name: str 858 """ 859 860 # Execution lock. 861 if status.exec_lock.locked(): 862 return 863 864 # Warning. 865 if not self.analysis.init_state or not ds.is_empty(): 866 # The message. 867 msg = "Loading a saved relax state file will cause all unsaved data to be lost. Are you sure you would to open a save file?" 868 869 # The dialog. 870 if status.show_gui and Question(msg, default=True, size=(400, 150)).ShowModal() == wx.ID_NO: 871 return 872 873 # Open the dialog. 874 if not file_name: 875 dialog = RelaxFileDialog(parent=self, message='Select the relax save state file', defaultFile='state.bz2', wildcard='relax save files (*.bz2;*.gz)|*.bz2;*.gz|All files (*)|*', style=wx.FD_OPEN) 876 877 # Show the dialog and catch if no file has been selected. 878 if status.show_gui and dialog.ShowModal() != wx.ID_OK: 879 # Don't do anything. 880 return 881 882 # The file. 883 file_name = gui_to_str(dialog.get_file()) 884 885 # Yield to allow the cursor to be changed. 886 wx.Yield() 887 888 # Change the cursor to waiting, and freeze the GUI. 889 wx.BeginBusyCursor() 890 self.Freeze() 891 892 # Make sure the GUI returns to normal if a failure occurs. 893 try: 894 # Delete the current tabs. 895 self.analysis.delete_all() 896 897 # Reset the relax data store. 898 reset() 899 900 # The new save file name. 901 self.save_file = file_name 902 903 # Load the relax state. 904 if protected_exec(state.load_state, file_name, verbosity=0): 905 # Update the core of the GUI to match the new data store. 906 self.sync_ds(upload=False) 907 908 # File loading failure. 909 else: 910 # Reset relax to clear any partially loaded data. 911 reset() 912 913 # Reinitialise the GUI data store structure. 914 self.init_data() 915 916 # Reset the cursor, and thaw the GUI. 917 finally: 918 self.Thaw() 919 920 # Turn off the busy cursor. 921 if wx.IsBusy(): 922 wx.EndBusyCursor()
923 924
925 - def state_save(self):
926 """Save the program state.""" 927 928 # Update the data store to match the GUI. 929 self.sync_ds(upload=True) 930 931 # Save the relax state (with save user feedback). 932 try: 933 wx.BeginBusyCursor() 934 state.save_state(self.save_file, verbosity=0, force=True) 935 936 # Sleep a little so the user sees the busy cursor and knows that a save has occurred! 937 sleep(1) 938 939 # Turn off the user feedback. 940 finally: 941 if wx.IsBusy(): 942 wx.EndBusyCursor()
943 944
945 - def sync_ds(self, upload=False):
946 """Synchronise the GUI and the relax data store, both ways. 947 948 This method allows the GUI information to be uploaded into the relax data store, or for the information in the relax data store to be downloaded by the GUI. 949 950 @keyword upload: A flag which if True will cause the GUI to send data to the relax data store. If False, data will be downloaded from the relax data store to update the GUI. 951 @type upload: bool 952 """ 953 954 # Loop over each analysis. 955 for page in self.analysis.analysis_loop(): 956 # Execute the analysis page specific update methods. 957 if hasattr(page, 'sync_ds'): 958 page.sync_ds(upload)
959 960
961 - def system_cwd(self, event=None):
962 """Change the system current working directory. 963 964 @keyword event: The wx event. 965 @type event: wx event 966 """ 967 968 # The dialog. 969 dialog = RelaxDirDialog(parent=self, message="Select working directory", defaultPath=wx.EmptyString, style=wx.DD_CHANGE_DIR) 970 971 # Show the dialog and catch if no directory has been selected. 972 if status.show_gui and dialog.ShowModal() != wx.ID_OK: 973 # Don't do anything. 974 return 975 976 # Call the get_path function to get the directory name and change path. 977 self.system_cwd_path = dialog.get_path() 978 979 # Update the status bar. 980 self.update_status_bar() 981 982 # Change the directory 983 try: 984 wx.BeginBusyCursor() 985 986 # Sleep a little so the user sees the busy cursor and knows that the directory changes has occurred. 987 sleep(1) 988 989 # Turn off the user feedback. 990 finally: 991 if wx.IsBusy(): 992 wx.EndBusyCursor()
993 994
995 - def uf_call(self, event=None):
996 """Catch the user function call to properly specify the parent window. 997 998 @keyword event: The wx event. 999 @type event: wx event 1000 """ 1001 1002 # The user function ID. 1003 uf_id = event.GetId() 1004 1005 # Get the user function name. 1006 name = uf_store.get_uf(uf_id) 1007 1008 # Call the user function GUI object. 1009 uf_store[name](event=event, wx_parent=self)
1010 1011
1012 - def update_status_bar(self):
1013 """Update the status bar info.""" 1014 1015 # Set the current data pipe info. 1016 pipe = cdp_name() 1017 1018 # No data pipe. 1019 if pipe == None: 1020 pipe = '' 1021 1022 # Get the current working directory 1023 self.system_cwd_path = pwd(verbose=False) 1024 1025 # The relax information box. 1026 info = Info_box() 1027 1028 # Set the status. 1029 wx.CallAfter(self.status_bar.SetStatusText, info.copyright_short, 0) 1030 wx.CallAfter(self.status_bar.SetStatusText, self.system_cwd_path, 1) 1031 wx.CallAfter(self.status_bar.SetStatusText, "Data pipe:", 2) 1032 wx.CallAfter(self.status_bar.SetStatusText, pipe, 3)
1033