
# TODO: cleanup, eliminate unused methods

from __future__ import division
from wxGUI2 import Preferences, utils
import wxGUI2
from SimpleXMLRPCServer import SimpleXMLRPCServer
import phenix.tracking.program_info
from phenix.utilities import get_doc_path
from libtbx.utils import Sorry, Abort, import_python_object, getcwd_safe
from libtbx import object_oriented_patterns as oop
from libtbx import easy_pickle
from libtbx import adopt_init_args
import libtbx.utils
import wx
import threading
import warnings
import random
import time
import sys
import os

class PhenixApp (wx.App) :
  def __init__ (self, args, postpone_gui_startup=False) :
    self.args                  = args
    self.postpone_gui_startup  = postpone_gui_startup
    self.is_initialized        = False
    self.offline               = False
    self.projects              = None
    self.use_projects          = True
    self.debug                 = False
    self.test_bug_report       = False
    self.use_custom_excepthook = True
    self.start_dir = None
    if ("TERM" in os.environ) :
      self.start_dir             = os.getcwd()
    self.auto_run              = False
    self.close_after_run       = False
    self.skip_version_check    = \
      os.environ.get("PHENIX_SUPPRESS_VERSION_CHECK", False)
    self.quiet                 = False
    self.log                   = None
    self.exit_on_close         = True
    self.main_window           = None
    self.home_window           = None
    self.help_window           = None
    self.run_manager           = oop.null()
    self.app_frames            = {}
    self.app_info              = {}
    self._running_processes    = []
    self._queue_jobs           = []
    self.homedir               = wxGUI2.HOMEDIR
    self.xmlrpc_port           = None
    self.xmlrpc_server         = None
    self.xmlrpc_thread         = None
    self.icon                  = None
    self.phenix_icon           = None
    self.coot_server           = None
    self.pymol_server          = None
    self._default_browser_dir  = None
    self.recipe_list           = None
    wx.App.__init__(self, 0)

  def OnInit (self) :
    i = 0
    while i < len(self.args) :
      arg = self.args[i]
      if arg.startswith("--home-dir=") :
        base_arg, home_dir = arg.split("=")
        wxGUI2.HOMEDIR = home_dir
        wxGUI2.PHENIX_USER_DIR = os.path.join(home_dir, ".phenix")
        self.args.pop(i)
      else :
        i += 1
    cwd = os.getcwd()
    if ((os.path.basename(cwd) == "bin") and
        (os.path.basename(os.path.dirname(cwd)) == "build")) :
      os.chdir(wxGUI2.HOMEDIR)
    self.SetAppName("Phenix")
    self.locale = wx.Locale(wx.LANGUAGE_ENGLISH)
    wx.SystemOptions.SetOptionInt("osx.openfiledialog.always-show-types", 1)
    self.setup_user_dir()
    self.prefs = Preferences.PhenixPrefs(self)
    wxGUI2.prefs_manager = self.prefs
    self.update_settings(modify_windows=False)
    self.process_args(self.args)
    if self.use_custom_excepthook :
      sys.excepthook       = _excepthook
      warnings.showwarning = self.show_warning
    if self.test_bug_report :
      raise Exception("Testing automatic bug report system (-T or "+
        "--test-bug-report in command-line arguments).")
    if not self.quiet and not self.prefs.get_root().gui.disable_splash_screen :
      show_splash_screen(timeout=2000)
    if (os.environ.get("PHENIX_MTYPE") == "intel-windows-x86_64") :
      pass
    if (not self.postpone_gui_startup) :
      self.start_gui()
    return True

  def start_gui (self, exit_on_close=True) :
    if (self.is_initialized) :
      if (self.home_window is None) :
        self.launch_main()
      return False
    # XXX this will only be modified if the main GUI wasn't started first
    self.exit_on_close = exit_on_close
    self.checkout_lock()
    from wxGUI2.Programs import gui_modules
    for category in gui_modules.category :
      for app in category.app :
        self.app_info[app.app_id] = app
    self.get_screen_params()
    from wxGUI2 import ProcessControl, Tracking
    self.run_manager = ProcessControl.manager(self)
    #ProcessControl.setup_stdout_logging_event(self, self.OnLog)
    ProcessControl.setup_process_gui_events(window=self,
      OnExcept=self.OnProcessError,
      OnAbort=self.OnProcessAbort,
      OnComplete=self.OnProcessComplete)
    self.log = oop.null()
    if self.use_projects :
      try :
        self.projects = Tracking.start_tracking(
          tracking_dir=wxGUI2.PHENIX_USER_DIR,
          callback_on_import=self.show_message)
      except RuntimeError, e :
        raise Sorry("There was a fatal error loading your projects database. "+
          " This can happen if the file was tampered with or corrupted and no"+
          " backups could be located.  Please save a copy of the file "+
          "~/.phenix/project_db.phil, remove the original, and restart "+
          "PHENIX.  (Original error: %s)" % str(e))
      wxGUI2.project_manager = self.projects
      if (self.projects is None) :
        raise Sorry("Couldn't import projects database!")
    self._default_browser_dir = getcwd_safe()
    self._timer = wx.Timer(owner=self)
    self._timer_functions = []
    self.Bind(wx.EVT_TIMER, self.OnTimer)
    self._timer.Start(250)
    if (sys.platform in ["darwin"]) : # FIXME
      if not self.prefs.get_root().graphics.disable_xmlrpc_server :
        try :
          self.start_xmlrpc_server()
        except Exception, e :
          print "Error starting XML-RPC server:"
          print str(e)
          print "Input from Coot will be disabled."
    if (self.prefs.new_user) :
      from wxGUI2 import Dialogs, Base
      dlg = Dialogs.ExtendedMessageDialog(
        parent=None,
        title = "Welcome to PHENIX",
        message = \
  """Welcome to the PHENIX graphical interface.  By default, all configurable \
parameters are displayed in dialog windows, including advanced options that \
are rarely changed.  We recommend that you set the user level to 'Basic' or \
'Intermediate' to simplify the interface; you can switch this at any time \
via the Preferences dialog.  All parameters can still be viewed in their \
current state by selecting "View all settings" from the Settings menu.""",
        dialog_name = "expert_level_info",
        prefs_handler = self.prefs,
        message_title = "Configuring the PHENIX GUI",
        image = Base.Icon("apps/Session Manager.png", icon_size=64)
      )
      dlg.run()
    self.set_icon()
    if self.auto_launch_main and not (self.auto_run or self.offline) :
      self.launch_main()
    if ((not self.prefs.get_root().gui.disable_version_check) and
        (not self.skip_version_check)) :
      self.home_window.check_for_updates(background=True)
    self.is_initialized = True

  def process_args (self, args) :
    for arg in self.args :
      if arg == "--auto_run" or arg == "--run_test" :
        self.auto_run = True
        self.quiet = True
        self.use_projects = False
        self.use_custom_excepthook = False
        if arg == "--run_test" or "PHENIX_GUI_TEST" in os.environ :
          self.close_after_run = True
      elif arg == "--offline" :
        self.offline = True
        self.quiet = True
        self.use_projects = False
        self.use_custom_excepthook = False
      elif arg in ["-q", "--quiet"] :
        self.quiet = True
      elif arg in ["-P", "--no-project"] :
        self.use_projects = False
      elif arg in ["-E", "--no-print-exceptions"] :
        self.use_custom_excepthook = False
      elif arg in ["-D", "--debug"] :
        self.use_custom_excepthook = False
        self.debug = True
        #import wx.lib.inspection
        #wx.lib.inspection.InspectionTool().Show()
      elif arg in ["--coot"] :
        self.auto_launch_coot = True
      elif arg in ["-M", "--no-launch-main"] :
        self.auto_launch_main = False
      elif arg in ["-T", "--test-bug-report"] :
        self.test_bug_report = True
      elif arg in ["--skip-version-check"] :
        self.skip_version_check = True

  def get_screen_params (self) :
    (x, y, w, h) = tuple(wx.GetClientDisplayRect())
    wxGUI2.screen_dimensions = (w - 32, h - 72)
    self.screen_width = w
    wxGUI2.screen_width = w
    self.screen_height = h
    wxGUI2.screen_height = h
    self.max_size = (w - 20, h - 80)
    wxGUI2.max_size = (w - 20, h - 80)
    if (sys.platform == "darwin") :
      self.screen_height -= 72 # account for Dock height
    self.max_dialog_height = self.screen_height - 40
    self.set_window_height(self.prefs.get_root().gui.max_dialog_height)

  def set_window_height (self, max_height) :
    if (max_height < self.screen_height) :
      self.max_dialog_height = max_height

  def get_app_info (self, app_id) :
    return self.app_info.get(app_id)

  def get_program_info (self, program_name) :
    return phenix.program_db.get_program_info(program_name)

  def update_settings (self, modify_windows=True) :
    preferences = self.prefs.get_root()
    if getattr(preferences.gui, "base_font_size", None) is not None :
      wxGUI2.BASE_FONT_SIZE = int(preferences.gui.base_font_size)
    self.auto_launch_coot = preferences.graphics.launch_coot_on_startup
    self.auto_launch_main = preferences.gui.auto_start_main
    self.expert_level = int(preferences.gui.user_level)
    wxGUI2.ALPHA_MODE = preferences.gui.enable_alpha
    if (preferences.wizards.rosetta_path is not None) :
      os.environ["PHENIX_ROSETTA_PATH"] = preferences.wizards.rosetta_path
    if modify_windows :
      from wxGUI2 import Windows
      Windows._SizerContainer.font_size = wxGUI2.BASE_FONT_SIZE
    time_format = preferences.gui.time_format
    if time_format == "12" :
      pass
    else :
      utils.time_converter = utils.time_converter_24_hour
      utils.simple_time_converter = utils.simple_time_converter_24_hour

  def setup_http_proxy (self) :
    preferences = self.prefs.get_root()
    http_proxy = preferences.user.http_proxy
    if (http_proxy is None) and ("PHENIX_HTTP_PROXY" in os.environ) :
      http_proxy = os.environ.get("PHENIX_HTTP_PROXY", None)
    user = preferences.user.proxy_user
    port = preferences.user.proxy_port
    password = None
    from wxtbx.phil_controls import simple_dialogs
    dlg = simple_dialogs.HTTPProxyDialog(
      parent=None,
      title="HTTP proxy setup")
    dlg.SetProxyServer(http_proxy)
    dlg.SetProxyPort(port)
    dlg.SetProxyUser(user)
    if (dlg.ShowModal() == wx.ID_OK) :
      user = dlg.GetProxyUser()
      password = dlg.GetProxyPassword()
      port = dlg.GetProxyPort()
    else :
      raise Abort()
    if (user is not None) and (poassword is None) :
      raise Sorry("Both the user name and password must be defined if your "+
        "HTTP proxy requires authentication.")
    os.environ["CCTBX_HTTP_PROXY_SERVER"] = http_proxy
    os.environ["CCTBX_HTTP_PROXY_PORT"] = port
    if (user is not None) :
      os.environ["CCTBX_HTTP_PROXY_USER"] = user
      os.environ["CCTBX_HTTP_PROXY_PASSWORD"] = password
    import libtbx.utils
    libtbx.utils.install_urllib_http_proxy(
      server=http_proxy,
      port=port,
      user=user,
      password=password)
    Windows.MessageBox("Proxy successfully configured.")

  def show_message (self, message, raise_abort_if_cancel=True) :
    from wxGUI2 import Windows
    confirm = Windows.MessageBox(message=message,
      style=wx.OK|wx.CANCEL)
    if (confirm == wx.CANCEL) :
      raise Abort("canceled by user.")
    return True

  def show_warning (self, message, category, *args, **kwds) :
    if ("DeprecationWarning" in category.__name__) :
      print "%s: %s" % (category.__name__, message)
    else :
      from wxGUI2 import Windows
      Windows.MessageBox(message="WARNING: %s" % message,
        style=wx.OK)

  def get_user_interface_level (self) :
    return int(self.prefs.get_root().gui.user_level)

  def switch_project (self, project_object, chdir=True) :
    if chdir :
      self.chdir(project_object.get_directory())

  def project_is_idle (self, project_id) :
    for frame in self.get_frames() :
      if type(frame).__name__ == "_wxPyDeadObject" :
        continue
      if (frame.project.get_id() == project_id) :
        return False
    return True

  def chdir (self, dirname, update=True) :
    if not os.path.exists(dirname) :
      utils.safe_makedirs(dirname)
    os.chdir(dirname)
    self._default_browser_dir = dirname
    if update :
      if (self.home_window is not None) :
        self.home_window.set_working_dir(dirname)

  def setup_user_dir (self) :
    if (not os.path.isdir(wxGUI2.PHENIX_USER_DIR)) :
      config_dir = wx.StandardPaths.Get().GetUserConfigDir()
      if (config_dir is not None) and (config_dir != "") :
        wxGUI2.PHENIX_USER_DIR = os.path.join(config_dir, "Phenix")
    print "Phenix data directory:", wxGUI2.PHENIX_USER_DIR
    if (not os.path.isdir(wxGUI2.PHENIX_USER_DIR)) :
      try :
        utils.safe_makedirs(wxGUI2.PHENIX_USER_DIR)
      except Exception :
        raise Sorry("Could not create the tracking directory '%s'!" %
          wxGUI2.PHENIX_USER_DIR)
    os.environ['PHENIX_DATA'] = wxGUI2.PHENIX_USER_DIR
    tmp_dir = os.path.join(wxGUI2.PHENIX_USER_DIR, "tmp")
    if (not os.path.isdir(tmp_dir)) :
      utils.safe_makedirs(tmp_dir)
    os.environ['PHENIX_TMP'] = tmp_dir # for Coot, etc.

  def get_preferences (self) :
    return self.prefs.get_root()

  def save_preferences (self) :
    self.prefs.update_phil()
    self.prefs.save_settings()

  def load_recipes (self) :
    if (self.recipe_list is None) :
      from phenix.tracking import recipes
      self.recipe_list = recipes.recipe_list(
        file_name=os.path.join(wxGUI2.PHENIX_USER_DIR, "templates.phil"),
        recipe_dir=os.path.join(wxGUI2.PHENIX_USER_DIR, "config"))
    return self.recipe_list

  def update_project_info (self, project_id) :
    if (self.home_window is not None) :
      self.home_window.update_project_info(project_id)

  def get_frames (self) :
    frames = []
    for app_id, frame_list in self.app_frames.iteritems() :
      frames.extend(frame_list)
    return frames

  def get_frame (self, app_id=None, index=0) :
    if app_id is None :
      return self.home_window
    elif app_id in self.app_frames :
      return self.app_frames[app_id][index]
    else :
      raise Sorry("Couldn't find window for application %s!" % app_id)

  def get_last_frame (self, app_id) :
    return self.get_frame(app_id, index=-1)

  def set_icon (self) :
    if (sys.platform in ["darwin", "win32"]) and (self.icon is None) :
      import wxtbx.bitmaps
      icon_path = wxtbx.bitmaps.find_custom_icon("phenix", ext=".ico")
      if icon_path is not None :
        self.phenix_icon = wx.Icon(icon_path, wx.BITMAP_TYPE_ICO)
        if ((wx.Platform == '__WXMAC__') and (wx.VERSION >= (2,9)) and
            (hasattr(wx, "TBI_DOCK"))) :
          self.icon = wx.TaskBarIcon(wx.TBI_DOCK)
        else :
          self.icon = wx.TaskBarIcon()
        self.icon.SetIcon(self.phenix_icon, "PHENIX")

  def OnAbout (self, event) :
    show_splash_screen()

  def splash_screen (self, event=None) :
    if self.quiet :
      return
    elif not self.prefs.get_root().gui.disable_splash_screen :
      show_splash_screen()

  def checkout_lock (self) :
    lock_file = os.path.join(wxGUI2.PHENIX_USER_DIR, "LOCK")
    (host, pid) = self.get_lock_session()
    if (host == wxGUI2.HOST) and (pid == wxGUI2.PID) :
      pass
    else :
      from wxGUI2 import Windows
      override = Windows.MessageBox(message=("You appear to have another "+
        "session of PHENIX running already (hostname = '%s', PID = '%s'), "
        "which has locked your project database to prevent corruption.  "+
        "If this is the result of crashing or killing the GUI, you can "+
        "continue, but only one session should be run at a time.  Do you "+
        "want to override the lock?") % (host, pid),
        style=wx.YES|wx.NO)
      if (override == wx.NO) :
        raise Abort("locked by session on %s (%s)." % (host, pid))
        #return False
    try :
      open(lock_file, "w").write("%s %s" % (wxGUI2.HOST, wxGUI2.PID))
    except OSError, e :
      if (e.errno == 13) :
        raise Sorry(("PHENIX was unable to create a lock file in your home "+
          "directory (full path: %s) because of a permissions error.  Please "+
          "check that you have write access to this path and restart the "+
          "GUI.") % e.filename)
      else :
        raise e

  def get_lock_session (self) :
    lock_file = os.path.join(wxGUI2.PHENIX_USER_DIR, "LOCK")
    if os.path.exists(lock_file) :
      try :
        (host, pid) = open(lock_file, "r").read().split()
      except ValueError :
        (host, pid) = (wxGUI2.HOST, wxGUI2.PID)
    else :
      (host, pid) = (wxGUI2.HOST, wxGUI2.PID)
    return (host, pid)

  def get_lock (self) :
    (host, pid) = self.get_lock_session()
    if (host == wxGUI2.HOST) and (pid == wxGUI2.PID) :
      return True
    return False

  def clear_lock (self) :
    lock_file = os.path.join(wxGUI2.PHENIX_USER_DIR, "LOCK")
    if os.path.exists(lock_file) :
      try :
        os.remove(lock_file)
      except OSError :
        open(lock_file, "w").write("")

  def close_home_window (self) :
    if (self.home_window is not None) :
      # FIXME should really destroy - need to check if this is safe
      #self.home_window.Destroy()
      self.home_window.Hide()
      #if self.home_window.jobs_frame is not None :
      #  self.home_window.jobs_frame.Close()
      self.home_window = None
    if (len(self.app_frames) == 0) and (self.exit_on_close) :
      self.ExitApp()

  def launch_main (self, args=None) :
    if self.home_window is None :
      from wxGUI2.Home.Main import PhenixGUI
      frame = PhenixGUI()
      self.home_window = frame
      if self.main_window is None :
        self.main_window = self.home_window
      frame.Show()
      frame.Raise()
      frame.process_cmdline_args(args)
    else :
      self.home_window.Raise()

  def close_app (self, frame) :
    app_id = frame.get_app_id()
    if app_id in self.app_frames :
      for i, stored_frame in enumerate(self.app_frames[app_id]) :
        if stored_frame is frame :
          self.app_frames[app_id].pop(i)
          if len(self.app_frames[app_id]) == 0 :
            self.app_frames.pop(app_id)
      wxGUI2.DEBUG2("destroying frame for %s" % app_id)
    frame.Hide()
    if (wx.Platform == '__WXMAC__') and (wx.VERSION >= (2,9)) :
      self.SafeYield(self.home_window, True)
    wxGUI2.DEBUG2("frame hidden")
    #wx.CallAfter(frame.Destroy)
    wxGUI2.DEBUG2("%d frames remaining" % len(self.app_frames))
    if len(self.app_frames) == 0 and self.home_window is None :
      self.ExitApp()

  def launch_phenix_app (self, app_id, args=(), restore_mode=False,
      use_project=None, parent=None) :
    args = list(args)
    app_info = self.get_app_info(app_id)
    if app_info is None :
      raise Sorry("The requested program was not found - it may have "+
        "been removed or replaced.")
    if (app_info.alternate_run_method is not None) :
      run_method = import_python_object(
        import_path=app_info.alternate_run_method,
        error_prefix="",
        target_must_be="",
        where_str="").object
      return run_method(use_project, args, restore_mode=restore_mode)
    module_path = "wxGUI2.Programs.%s" % app_id
    if (app_info is not None) and (app_info.path_to_import is not None) :
      module_path = app_info.path_to_import
    class_name = app_id + "GUI"
    frame_class = import_python_object(
      import_path = "%s.%s" % (module_path, class_name),
      error_prefix = "",
      target_must_be = "",
      where_str = "").object
    if frame_class is None :
      raise Sorry("Can't find class %s.%s" % (module_path, class_name))
    else :
      return self.launch_app_frame(
        app_id=app_id,
        frame_class=frame_class,
        args=args,
        restore_mode=restore_mode,
        use_project=use_project,
        parent=parent)

  def setup_app (self, app_id, args=()) :
    app_info = self.get_app_info(app_id)
    program_info = phenix.program_db.get_program_info(app_info.command)
    module_path = "wxGUI2.Programs.%s" % app_id
    if (app_info is not None) and (app_info.path_to_import is not None) :
      module_path = app_info.path_to_import
    if (app_info.master_phil_path is not None) :
      import iotbx.phil
      setup_app = iotbx.phil.setup_app_generic(app_info.master_phil_path)
    elif (program_info.master_phil_path is not None) :
      import iotbx.phil
      setup_app = iotbx.phil.setup_app_generic(program_info.master_phil_path)
    else :
      setup_path = module_path + ".setup_app"
      setup_app = import_python_object(
        import_path=setup_path,
        error_prefix="",
        target_must_be="",
        where_str="").object
    return setup_app(args)

  def launch_app_frame (self, app_id, frame_class, args, **kwds) :
    (master_phil, working_phil, options, unused_args) = self.setup_app(app_id,
      args)
    app_info = self.get_app_info(app_id)
    program_info = phenix.program_db.get_program_info(app_info.command)
    if hasattr(frame_class, "app_id") :
      assert (frame_class.app_id == app_id)
    kwds = dict(kwds)
    kwds['title'] = app_info.label
    kwds['phil_handler'] = None
    kwds['program_info'] = program_info
    kwds['master_phil'] = master_phil
    kwds['working_phil'] = working_phil
    kwds['options'] = options
    kwds['args'] = unused_args
    frame = frame_class(**kwds)
    if (self.main_window is None) and (kwds.get("parent") is None) :
      self.main_window = frame
      self.SetTopWindow(frame)
    self.save_app_frame(frame, app_id)
    frame.Show()
    frame.Raise()
    if (wx.Platform == '__WXMAC__') and (wx.VERSION >= (2,9)) :
      self.SafeYield(frame, True)
    if self.coot_server is not None :
      frame.coot_server = self.coot_server
    elif self.auto_launch_coot :
      frame.launch_coot()
    if self.pymol_server is not None :
      frame.pymol_server = self.pymol_server
    if self.auto_run :
      frame.auto_run()
    return frame

  def save_app_frame (self, frame, app_id) :
    if app_id in self.app_frames :
      self.app_frames[app_id].append(frame)
    else :
      self.app_frames[app_id] = [frame]

  def restore_session (self, app_id, output_dir, config_file=None,
      gui_type="standard") :
    assert (gui_type in ["standard", "applet"])
    args = []
    if config_file is not None :
      args.append(config_file)
    if (gui_type == "applet") :
      self.home_window.run_simple_tool(
        dialog_name=app_id,
        config_file=config_file)
    else :
      frame = self.launch_phenix_app(app_id, args, restore_mode=True)
      frame.prime_modules()
      pkl_file = config_file[:-4] + ".pkl"
      result = None
      try :
        if os.path.isfile(pkl_file) :
          result = easy_pickle.load(pkl_file)
      except Exception, e :
        print e
      try :
        frame.restore_session(
          output_dir=output_dir,
          config_file=config_file,
          result=result)
      finally :
        frame._restore_mode = False

  def resume_job (self, project_id, job) :
    phenix_process = self.run_manager.find_process(project_id, job.job_id)
    if phenix_process is not None :
      if not phenix_process.backgrounded :
        print type(phenix_process.window)
        raise Sorry("This job is already open in another window.")
    elif not getattr(job, "process_type", 0) > 0 :
      return False
    frame = self.launch_phenix_app(app_id=job.app_id,
      args=[job.config_file],
      restore_mode=True)
    if phenix_process is None :
      phenix_process = self.resume_backgrounded_job(project_id, job)
    frame.reattach_job(phenix_process, job)
    return True

  def resume_backgrounded_job (self, project_id, job) :
    from wxGUI2 import ProcessControl
    project = self.projects.load_project(project_id)
    if (project is None) :
      raise Sorry(("Can't resume this job because the project it was run in "+
        "(%s) could not be loaded.  This can happen if the project was "+
        "deleted, the project directory was moved, or the internal tracking "+
        "files were tampered with.") % project_id)
    phenix_process = ProcessControl.resume_process(
      tmp_dir=project.get_tmp_dir(),
      process_type=job.process_type,
      project_id=project_id,
      job_id=job.job_id,
      queue_job_id=getattr(job, "queue_job_id", None))
    return phenix_process

  def restore_job (self, project_id, job) :
    if ((job.status == "started") and
        ((job.time_finished is None) or
         (getattr(job, "pending", False)))) :
      resumed = self.resume_job(project_id, job)
      if resumed :
        return True
    app_info = self.get_app_info(job.app_id)
    gui_type = "standard"
    if (app_info is None) :
      gui_type = "applet"
    self.restore_session(app_id=job.app_id,
      output_dir=job.result_directory,
      config_file=job.config_file,
      gui_type=gui_type)

  def save_queue_job_id (self, job_id) :
    self._queue_jobs.append(job_id)

  def remove_queue_job_id (self, job_id) :
    self._queue_jobs.remove(job_id)

  def get_queued_job_ids (self) :
    return self._queue_jobs

  def OnProcessAbort (self, event) :
    self.projects.handle_job_abort(event.project_id, event.job_id)

  def OnProcessComplete (self, event) :
    self.projects.handle_job_finish(event.project_id, event.job_id,
      result=event.data)

  def OnProcessError (self, event) :
    self.projects.handle_job_error(event.project_id, event.job_id)

  #---------------------------------------------------------------------
  # help browser (Mac only for now)
  def close_help_window (self) :
    if (self.help_window is not None) :
      self.help_window.Close()
      self.help_window.Destroy()
      self.help_window = None

  def open_help_window (self) :
    assert (sys.platform in ["darwin", "win32"])
    import wxtbx.browser
    if (self.help_window is None) :
      docs_url = get_doc_path()
      self.help_window = wxtbx.browser.browser_frame(parent=None, id=-1,
        title="PHENIX documentation",
        size=(1024,640))
      self.help_window.SetHomepage(docs_url)
      self.Bind(wx.EVT_WINDOW_DESTROY, self.OnCloseHelp, self.help_window)
    self.help_window.Show()
    self.help_window.Raise()

  def open_help_page (self, page_name) :
    doc_url = get_doc_path(page_name)
    self.open_help_window()
    self.help_window.Open(doc_url)

  def OnCloseHelp (self, event) :
    self.help_window = None

  #---------------------------------------------------------------------
  def ExitApp (self, event=None, confirm=True) :
    # XXX don't quit if started from REEL
    if (confirm) and (not self.run_manager.confirm_close()) :
      return False
    self.clear_lock()
    if self.xmlrpc_server is not None :
      self.xmlrpc_server.shutdown()
      self.xmlrpc_thread.join()
    self.run_manager.force_quit()
    for (gfx_program, name) in zip(["coot", "pymol"], ["Coot", "PyMOL"]) :
      server = getattr(self, "%s_server" % gfx_program, None)
      if server is not None and server.is_alive() :
        if self.auto_run :
          close_gfx = True
        else :
          from wxGUI2 import Windows
          close_gfx = (Windows.MessageBox(
            parent=None,
            message=("You appear to have a %s window associated with this "+
              "session of PHENIX.  Do you want to close it now?") % name,
            caption="Exit %s?" % name,
            style=wx.YES_NO) == wx.YES)
        if close_gfx :
          server.quit()
    self._exiting = True
    self.cleanup_on_exit()
    time.sleep(1)
    self.Destroy()
    try :
      sys.exit(0)
    except Exception, e :
      pass
    from phenix.utilities import process
    process.kill(os.getpid())

  # called in MainWindow.py (PhenixAppFrame.ExitApp)
  def cleanup_on_exit (self, event=None) :
    if self.icon is not None :
      self.icon.Destroy()

  def OnTimer (self, event) :
    self.run_manager.update(update_backgrounded=False)
    if self.coot_server is not None :
      self.coot_server.flush_requests()
    if self.pymol_server is not None :
      self.pymol_server.flush_requests()

  #---------------------------------------------------------------------
  # XML-RPC server
  # Unlike the Coot extension, this server is run by a separate thread,
  # which is possible only in a Python-based app.  The disadvantage is
  # that XML-RPC function calls won't have return values, but XML-RPC
  # causes enough other problems that it isn't worth using the timer
  # method.
  def start_xmlrpc_server (self) :
    preferences = self.prefs.get_root()
    timeout = preferences.graphics.xmlrpc_timeout
    valid_ports = [ n for n in range(34981, 36864) ]
    n = 0
    max_tries = 50
    while n < max_tries :
      i = int(random.random() * (len(valid_ports) - 1))
      self.xmlrpc_port = valid_ports[i]
      try :
        self.xmlrpc_server = phenix_xmlrpc_server(("127.0.0.1",
          self.xmlrpc_port), self)
      except Exception, e :
        if "Address already in use" in str(e) :
          n += 1
        else :
          raise
      else :
        break
    if self.xmlrpc_server is None :
      Error.SorryDialog("Could not start the local XML-RPC server.  This is "+
        "not a fatal error, but you will not be able to launch Phenix "+
        "from within Coot.")
    else :
      self.xmlrpc_server.socket.settimeout(0.01)
      self.Connect(-1, -1, XMLRPC_REQUEST_ID, self.OnRequest)
      t = threading.Thread(target=self.xmlrpc_server.serve_forever)
      t.start()
      self.xmlrpc_thread = t
      wxGUI2.DEBUG("XML-RPC server started on port %d" % self.xmlrpc_port)

  def OnRequest (self, event) :
    (method, params) = event.get_request()
    self.handle_method_request(method, params)

  def handle_method_request (self, method, params) :
    method_fields = method.split("_")
    app_name = method_fields[0]
    method_name = "_".join(method_fields[1:])
    print "%s %s (%s)" % (app_name, method_name, " ".join(list(params)))
    return_value = None
    frame = self.launch_phenix_app(app_name, [])
    if frame is not None and hasattr(frame, method_name) :
      print "calling method %s" % method_name
      method = getattr(frame, method_name)
      return_value = method(*params)
    # TODO: callback to Coot or PyMOL?
    #return return_value

  #---------------------------------------------------------------------
  # DEBUGGING
  def reload_module (self, module_path) :
    target_module = import_python_object(
        import_path=module_path,
        error_prefix="",
        target_must_be="",
        where_str="").object
    reload(target_module)

XMLRPC_REQUEST_ID = wx.NewId()
class XMLRPCEvent (wx.PyEvent) :
  def __init__ (self, method, params) :
    adopt_init_args(self, locals())
    wx.PyEvent.__init__(self)
    self.SetEventType(XMLRPC_REQUEST_ID)

  def get_request (self) :
    return (self.method, self.params)

class phenix_xmlrpc_server (SimpleXMLRPCServer) :
  def __init__ (self, addr, app) :
    SimpleXMLRPCServer.__init__(self, addr, logRequests=0)
    self.app = app

  def _dispatch (self, method, params) :
    result = None
    event = XMLRPCEvent(method, params)
    wx.PostEvent(self.app, event)
    return True

# provides friendly dialog with exception info
def _excepthook (type, value, traceback) :
  from wxGUI2 import Error
  if type is Abort :
    pass
  elif (((type is Sorry) or (type.__name__ == "FormatError")) and
        (not libtbx.utils.disable_tracebacklimit)) :
    e = Error.SorryDialog(str(value))
  else :
    Error.show_exception_dialog(type, value, traceback)

########################################################################
# splash screen
#
def show_splash_screen (timeout=None) :
  from phenix.phenix_info import phenix_developers
  from phenix.utilities import citations
  img_path = os.path.join(wxGUI2.IMAGE_PATH, "phenix-logo.jpg")
  img = wx.Image(img_path, type=wx.BITMAP_TYPE_ANY, index=-1)
  bmp = img.ConvertToBitmap()
  dlg = wx.Dialog(None, -1, style=wx.NO_BORDER|wx.OK|wx.CENTRE)
  dlg.SetBackgroundColour((255,255,255))
  ok_btn = wx.Button(dlg, wx.ID_OK)
  ok_btn.SetDefault()
  szr = wx.BoxSizer(wx.VERTICAL)
  btns = wx.StdDialogButtonSizer()
  btns.Add(ok_btn, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_BOTTOM)
  bitmap_widget = wx.StaticBitmap(dlg, -1, bmp)
  szr.Add(bitmap_widget, 0, wx.ALIGN_TOP)
  txt_sizer = wx.BoxSizer(wx.VERTICAL)
  szr.Add(txt_sizer, 0, wx.ALL|wx.EXPAND, 10)
  version = "%s-%s" % (os.environ.get("PHENIX_VERSION", "unknown"),
                       os.environ.get("PHENIX_RELEASE_TAG", "000"))
  title_font = wx.Font(14, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.BOLD)
  version_txt = wx.StaticText(dlg, -1, "Version %s" % version)
  version_txt.SetFont(title_font)
  txt_sizer.Add(version_txt, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5)
  url_txt = wx.StaticText(dlg, -1, "http://www.phenix-online.org")
  url_txt.SetFont(title_font)
  txt_sizer.Add(url_txt, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5)
  email_txt = wx.StaticText(dlg, -1, "Support: help@phenix-online.org")
  email_txt.SetFont(title_font)
  txt_sizer.Add(email_txt, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 5)
  # developer list
  label_font = wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.BOLD)
  label1_txt = wx.StaticText(dlg, -1, "Phenix developers:")
  label1_txt.SetFont(label_font)
  txt_sizer.Add(label1_txt, 0, wx.ALL, 5)
  szr2 = wx.BoxSizer(wx.HORIZONTAL)
  txt_sizer.Add(szr2, 0, wx.ALL, 5)
  szr2.Add((20,1))
  txt = ", ".join(phenix_developers)
  author_list = wx.StaticText(dlg, -1, txt)
  author_list.Wrap(640)
  szr2.Add(author_list)
  # citation
  label2_txt = wx.StaticText(dlg, -1, "Current citation:")
  label2_txt.SetFont(label_font)
  txt_sizer.Add(label2_txt, 0, wx.ALL, 5)
  szr3 = wx.BoxSizer(wx.HORIZONTAL)
  txt_sizer.Add(szr3, 0, wx.ALL, 5)
  szr3.Add((20,1))
  citation_txt = wx.StaticText(dlg,-1,citations.get_citation("phenix").strip())
  citation_txt.Wrap(640)
  szr3.Add(citation_txt)
  szr.Add(btns, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALL, 5)
  dlg.SetSizer(szr)
  szr.Fit(dlg)
  dlg.Bind(wx.EVT_CHAR, lambda evt: dlg.EndModal(wx.ID_OK))
  dlg.Centre(wx.BOTH)
  if (timeout is not None) :
    timer = wx.Timer(owner=dlg)
    def OnTimer (evt) :
      dlg.EndModal(wx.ID_OK)
      timer.Stop()
    dlg.Bind(wx.EVT_TIMER, OnTimer)
    timer.Start(timeout)
  dlg.ShowModal()

#---end
