"Fossies" - the Fresh Open Source Software Archive

Member "relax-5.0.0/gui/relax_gui.py" (9 Dec 2019, 37732 Bytes) of package /linux/privat/relax-5.0.0.src.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "relax_gui.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 4.1.3_vs_5.0.0.

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