text: from __future__ import absolute_import, division, print_function
  from six.moves import range
  import six
  
  '''
  Author      : Lyubimov, A.Y.
  Created     : 06/04/2016
  Last Changed: 06/04/2016
  Description : XFEL UI Custom Dialogs
  '''
  
  import os
  import wx
  from wx.lib.mixins.listctrl import TextEditMixin, getListCtrlSelection
  from wx.lib.scrolledpanel import ScrolledPanel
  from xfel.ui.db.task import task_types
  
  import xfel.ui.components.xfel_gui_controls as gctr
  
  icons = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'icons/')
  
  # Platform-specific stuff
  # TODO: Will need to test this on Windows at some point
  if wx.Platform == '__WXGTK__':
    norm_font_size = 10
    button_font_size = 12
    LABEL_SIZE = 14
    CAPTION_SIZE = 12
  elif wx.Platform == '__WXMAC__':
    norm_font_size = 12
    button_font_size = 14
    LABEL_SIZE = 14
    CAPTION_SIZE = 12
  elif (wx.Platform == '__WXMSW__'):
    norm_font_size = 9
    button_font_size = 11
    LABEL_SIZE = 11
    CAPTION_SIZE = 9
  
  
  class EdListCtrl(wx.ListCtrl, TextEditMixin):
    ''' TextEditMixin allows any column to be edited. '''
  
    # ----------------------------------------------------------------------
    def __init__(self, parent, ID=wx.ID_ANY, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=0):
      """Constructor"""
      wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
      TextEditMixin.__init__(self)
      self.curRow = -1
  
  
  class BaseDialog(wx.Dialog):
    def __init__(self, parent,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
      wx.Dialog.__init__(self, parent, *args, **kwargs)
  
      self.envelope = wx.BoxSizer(wx.VERTICAL)
      self.main_sizer = wx.BoxSizer(wx.VERTICAL)
      self.envelope.Add(self.main_sizer, flag=wx.EXPAND | wx.ALL, border=5)
      self.SetSizer(self.envelope)
  
      if label_style == 'normal':
        self.font = wx.Font(norm_font_size, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
      elif label_style == 'bold':
        self.font = wx.Font(norm_font_size, wx.DEFAULT, wx.NORMAL, wx.BOLD)
      elif label_style == 'italic':
        self.font = wx.Font(norm_font_size, wx.DEFAULT, wx.ITALIC, wx.NORMAL)
      elif label_style == 'italic_bold':
        self.font = wx.Font(norm_font_size, wx.DEFAULT, wx.ITALIC, wx.BOLD)
  
      if content_style == 'normal':
        self.cfont = wx.Font(norm_font_size, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
      elif content_style == 'bold':
        self.cfont = wx.Font(norm_font_size, wx.DEFAULT, wx.NORMAL, wx.BOLD)
      elif content_style == 'italic':
        self.cfont = wx.Font(norm_font_size, wx.DEFAULT, wx.ITALIC, wx.NORMAL)
      elif content_style == 'italic_bold':
        self.cfont = wx.Font(norm_font_size, wx.DEFAULT, wx.ITALIC, wx.BOLD)
  
  
  # --------------------------------- Dialogs ---------------------------------- #
  
  class SettingsDialog(BaseDialog):
    ''' Initial settings for cctbx.xfel; accepts DB credentials (may be
    separate dialog), populates experiment name / tag, separate dialog for
    multiprocessing and other settings; starts sentinels on OK '''
  
    def __init__(self, parent, params,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      BaseDialog.__init__(self, parent,
                          label_style=label_style,
                          content_style=content_style,
                          size=(600, -1),
                          *args, **kwargs)
  
      self.params = params
      self.drop_tables = False
  
      self.main_sizer = wx.BoxSizer(wx.VERTICAL)
  
      # Experiment tag and DB Credentials button
      self.db_cred = gctr.TextButtonCtrl(self,
                                         name='db_cred',
                                         label='Experiment Tag',
                                         label_style='bold',
                                         label_size=(150, -1),
                                         big_button=True,
                                         big_button_label='DB Credentials...',
                                         big_button_size=(130, -1),
                                         value=self.params.experiment_tag if self.params.experiment_tag is not None else "")
      self.main_sizer.Add(self.db_cred,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      # Facility control
      self.facility_sizer = wx.BoxSizer(wx.HORIZONTAL)
  
      choices = ['LCLS', 'Standalone']
      lower_choices = [f.lower() for f in choices]
      self.facility = gctr.ChoiceCtrl(self,
                                      name = 'facility',
                                      label='Facility',
                                      label_size=(150, -1),
                                      ctrl_size=(180, -1),
                                      label_style='bold',
                                      choices=choices)
      self.facility_sizer.Add(self.facility, flag=wx.EXPAND | wx.ALL, border=10)
      try:
        self.facility.ctr.SetSelection(lower_choices.index(params.facility.name))
      except ValueError:
        pass
  
      self.btn_facility_options = gctr.Button(self, name = 'btn_facility_options', label='Options...')
      self.facility_sizer.Add(self.btn_facility_options, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.main_sizer.Add(self.facility_sizer, flag=wx.EXPAND | wx.ALL)
  
      # Experiment name control
      experiment = None
      if self.params.facility.name == 'lcls': experiment = self.params.facility.lcls.experiment
      if experiment is None: experiment = ''
      self.experiment = gctr.TextButtonCtrl(self,
                                            name='experiment',
                                            label='Experiment',
                                            label_style='bold',
                                            label_size=(150, -1),
                                            big_button_size=(130, -1),
                                            value=experiment)
      self.main_sizer.Add(self.experiment,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      # Output folder text control w/ Browse / magnifying glass button
      if self.params.output_folder == '':
        current_folder = os.path.abspath(os.curdir)
      else:
        current_folder = self.params.output_folder
      self.output = gctr.TextButtonCtrl(self,
                                        name='output',
                                        label='Output',
                                        label_style='bold',
                                        label_size=(150, -1),
                                        big_button=True,
                                        big_button_label='Browse...',
                                        big_button_size=(120, -1),
                                        value=current_folder)
      self.main_sizer.Add(self.output,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      #self.btn_mp = wx.Button(self, label='Multiprocessing...')
      self.btn_op = gctr.Button(self, name='advanced', label='Advanced Settings...')
      self.btn_OK = wx.Button(self, label="OK", id=wx.ID_OK)
      self.btn_OK.SetDefault()
      self.btn_cancel = wx.Button(self, label="Cancel", id=wx.ID_CANCEL)
  
      button_sizer = wx.FlexGridSizer(1, 4, 0, 10)
      button_sizer.AddMany([#(self.btn_mp),
                            (self.btn_op),
                            (0,0),
                            (self.btn_OK),
                            (self.btn_cancel)])
  
      button_sizer.AddGrowableCol(1)
      self.main_sizer.Add(button_sizer,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
      self.SetSizerAndFit(self.main_sizer)
  
      self.Bind(wx.EVT_BUTTON, self.onDBCredentialsButton, id=self.db_cred.btn_big.GetId())
      self.Bind(wx.EVT_BUTTON, self.onAdvanced, id=self.btn_op.GetId())
      self.Bind(wx.EVT_BUTTON, self.onOK, id=self.btn_OK.GetId())
      self.Bind(wx.EVT_BUTTON, self.onBrowse, id=self.output.btn_big.GetId())
      self.Bind(wx.EVT_CHOICE, self.onFacilityChoice)
      self.Bind(wx.EVT_BUTTON, self.onFacilityOptions, id=self.btn_facility_options.GetId())
  
      self.setup_facility_options()
  
    def onBrowse(self, e):
      dlg = wx.DirDialog(self, "Choose the input directory:",
                         style=wx.DD_DEFAULT_STYLE)
  
      if dlg.ShowModal() == wx.ID_OK:
        self.output.ctr.SetValue(dlg.GetPath())
      dlg.Destroy()
  
    def onFacilityChoice(self, e):
      self.params.facility.name = self.facility.ctr.GetStringSelection().lower()
      self.setup_facility_options()
  
    def setup_facility_options(self):
      if self.params.facility.name in ['lcls']:
        self.experiment.Enable()
      else:
        self.experiment.Disable()
  
    def onFacilityOptions(self, e):
      if self.params.facility.name == 'lcls':
        opts = LCLSFacilityOptions(self, self.params)
        opts.Fit()
        opts.Center()
        opts.ShowModal()
      elif self.params.facility.name == 'standalone':
        opts = StandaloneOptions(self, self.params)
        opts.Fit()
        opts.Center()
        opts.ShowModal()
  
    def onAdvanced(self, e):
      adv = AdvancedSettingsDialog(self, self.params)
      adv.Fit()
      adv.Center()
  
      adv.ShowModal()
  
    def onDBCredentialsButton(self, e):
      creds = DBCredentialsDialog(self, self.params)
      creds.Center()
      if (creds.ShowModal() == wx.ID_OK):
        self.params.db.host     = creds.db_host.ctr.GetValue()
        self.params.db.port     = int(creds.db_port.ctr.GetValue())
        self.params.db.name     = creds.db_name.ctr.GetValue()
        self.params.db.user     = creds.db_user.ctr.GetValue()
        self.params.db.password = creds.db_password.ctr.GetValue()
        if self.params.facility.name == 'lcls':
          self.params.facility.lcls.web.location = creds.web_location.ctr.GetValue()
  
        self.drop_tables = creds.chk_drop_tables.GetValue()
  
    def onOK(self, e):
      self.params.facility.name = self.facility.ctr.GetStringSelection().lower()
      self.params.experiment_tag = self.db_cred.ctr.GetValue()
      self.params.output_folder = self.output.ctr.GetValue()
      if self.params.facility.name == 'lcls':
        self.params.facility.lcls.experiment = self.experiment.ctr.GetValue()
      e.Skip()
  
  
  class DBCredentialsDialog(BaseDialog):
    ''' DB credentials entry '''
  
    def __init__(self, parent, params,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.params = params
      BaseDialog.__init__(self, parent,
                          label_style=label_style,
                          content_style=content_style,
                          style=wx.NO_BORDER,
                          *args, **kwargs)
  
      # Host name
      self.db_host = gctr.TextButtonCtrl(self,
                                         name='db_host',
                                         label='DB Host name',
                                         label_style='bold',
                                         label_size=(150, -1),
                                         big_button_size=(130, -1),
                                         value=params.db.host)
      self.main_sizer.Add(self.db_host, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Host name
      self.db_port = gctr.TextButtonCtrl(self,
                                         name='db_port',
                                         label='DB Port number',
                                         label_style='bold',
                                         label_size=(150, -1),
                                         big_button_size=(130, -1),
                                         value=str(params.db.port))
      self.main_sizer.Add(self.db_port, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Database name
      self.db_name = gctr.TextButtonCtrl(self,
                                         name='db_name',
                                         label='DB name',
                                         label_style='bold',
                                         label_size=(150, -1),
                                         big_button_size=(130, -1),
                                         value=params.db.name)
      self.main_sizer.Add(self.db_name, flag=wx.EXPAND | wx.ALL, border=10)
  
      # User name
      self.db_user = gctr.TextButtonCtrl(self,
                                         name='db_user',
                                         label='DB user name',
                                         label_style='bold',
                                         label_size=(150, -1),
                                         big_button_size=(130, -1),
                                         value=params.db.user)
      self.main_sizer.Add(self.db_user, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Password
      self.db_password = gctr.TextButtonCtrl(self,
                                             name='db_password',
                                             label='DB Password',
                                             label_style='bold',
                                             label_size=(150, -1),
                                             text_style=wx.TE_PASSWORD,
                                             big_button_size=(130, -1),
                                             value=params.db.password)
      self.main_sizer.Add(self.db_password, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Drop tables button
      self.chk_drop_tables = wx.CheckBox(self,
                                         label='Delete and regenerate all tables')
      self.main_sizer.Add(self.chk_drop_tables, flag=wx.ALL, border=10)
  
      if params.facility.name == 'lcls':
        self.main_sizer.Add(wx.StaticLine(self), flag=wx.EXPAND | wx.ALL, border=10)
        # LCLS user name
        self.web_location = gctr.TextButtonCtrl(self,
                                                name='web_location',
                                                label='XTC stream location\n(SLAC or NERSC)',
                                                label_style='bold',
                                                label_size=(150, -1),
                                                big_button_size=(130, -1),
                                                value=params.facility.lcls.web.location)
        self.main_sizer.Add(self.web_location, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.Bind(wx.EVT_CHECKBOX, self.onDropTables, self.chk_drop_tables)
  
      self.Fit()
      self.SetTitle('Database Credentials')
  
    def onDropTables(self, e):
      if self.chk_drop_tables.GetValue():
        msg = wx.MessageDialog(self,
                               message='Are you sure?',
                               caption='Warning',
                               style=wx.YES_NO |  wx.ICON_EXCLAMATION)
  
        if (msg.ShowModal() == wx.ID_NO):
          self.chk_drop_tables.SetValue(False)
      e.Skip()
  
  class LCLSFacilityOptions(BaseDialog):
    ''' Options settings specific to LCLS'''
    def __init__(self, parent, params,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.params = params
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
  
      self.main_sizer = wx.BoxSizer(wx.VERTICAL)
  
      self.chk_use_ffb = wx.CheckBox(self,
                                     label='Use ffb (fast feedback) file system. Active experiment only, on hiprio or prio queues')
      self.chk_use_ffb.SetValue(params.facility.lcls.use_ffb)
  
      self.chk_dump_shots = wx.CheckBox(self,
                                        label='Dump all images to disk. Useful for tuning spotfinding and indexing parameters')
      self.chk_dump_shots.SetValue(params.facility.lcls.dump_shots)
  
      self.chk_enforce80 = wx.CheckBox(self,
                                       label='Require stream 80 (FEE spectrometer) before processing')
      self.chk_enforce80.SetValue(params.facility.lcls.web.enforce80)
  
  
      self.chk_enforce81 = wx.CheckBox(self,
                                       label='Require stream 81 (FEE spectrometer) before processing')
      self.chk_enforce81.SetValue(params.facility.lcls.web.enforce81)
  
      self.main_sizer.Add(self.chk_use_ffb, flag=wx.ALL, border=10)
      self.main_sizer.Add(self.chk_dump_shots, flag=wx.ALL, border=10)
      self.main_sizer.Add(self.chk_enforce80, flag=wx.ALL, border=10)
      self.main_sizer.Add(self.chk_enforce81, flag=wx.ALL, border=10)
      self.SetSizer(self.main_sizer)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.SetTitle('LCLS Settings')
  
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
    def onOK(self, e):
      self.params.facility.lcls.use_ffb = bool(self.chk_use_ffb.GetValue())
      self.params.facility.lcls.dump_shots = bool(self.chk_dump_shots.GetValue())
      self.params.facility.lcls.web.enforce80 = bool(self.chk_enforce80.GetValue())
      self.params.facility.lcls.web.enforce81 = bool(self.chk_enforce81.GetValue())
      e.Skip()
  
  class StandaloneOptions(BaseDialog):
    ''' Options settings specific to standalone GUI '''
    def __init__(self, parent, params,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.params = params
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
  
      self.main_sizer = wx.BoxSizer(wx.VERTICAL)
  
      # Output folder text control w/ Browse / magnifying glass button
      if self.params.facility.standalone.data_dir is None:
        current_folder = os.path.abspath(os.curdir)
      else:
        current_folder = self.params.facility.standalone.data_dir
      self.data_dir = gctr.TextButtonCtrl(self,
                                          name='data_dir',
                                          label='Folder to monitor',
                                          label_style='bold',
                                          label_size=(300, -1),
                                          big_button=True,
                                          big_button_label='Browse...',
                                          big_button_size=(120, -1),
                                          value=current_folder)
      self.main_sizer.Add(self.data_dir,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.SetSizer(self.main_sizer)
  
      # Raw image options
      self.monitor_for = gctr.RadioCtrl(self,
                                        name='monitor_for',
                                        label='Monitor for',
                                        label_style='bold',
                                        label_size=(-1, -1),
                                        direction='horizontal',
                                        items={'files':'files',
                                              'folders':'folders'})
      getattr(self.monitor_for, self.params.facility.standalone.monitor_for).SetValue(1)
  
      self.main_sizer.Add(self.monitor_for, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.folders_options = gctr.RadioCtrl(self,
                                            name='folders_options',
                                            label='Run complete criteria',
                                            label_style='bold',
                                            label_size=(-1, -1),
                                            direction='horizontal',
                                            items={'status_file':'Status file',
                                              'n_files':'Number of files'})
      getattr(self.folders_options, self.params.facility.standalone.folders.method).SetValue(1)
  
      self.main_sizer.Add(self.folders_options, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.n_files_needed = gctr.TextButtonCtrl(self,
                                                name='n_files_needed',
                                                label='Number of files per run',
                                                label_style='normal',
                                                label_size=(-1, -1),
                                                value=str(self.params.facility.standalone.folders.n_files_needed))
      self.main_sizer.Add(self.n_files_needed, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.last_modified = gctr.TextButtonCtrl(self,
                                               name='last_modified',
                                               label='Minimum time since last modified\n(in seconds)',
                                               label_style='normal',
                                               label_size=(-1, -1),
                                               value=str(self.params.facility.standalone.files.last_modified))
      self.main_sizer.Add(self.last_modified, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.minimum_file_size = gctr.TextButtonCtrl(self,
                                                   name='minimum_file_size',
                                                   label='Minimum file size\n(in bytes)',
                                                   label_style='normal',
                                                   label_size=(-1, -1),
                                                   value=str(self.params.facility.standalone.files.minimum_file_size))
      self.main_sizer.Add(self.minimum_file_size, flag=wx.EXPAND | wx.ALL, border=10)
  
      # File matching template control
      if self.params.facility.standalone.template is None:
        self.params.facility.standalone.template = ''
      self.template = gctr.TextButtonCtrl(self,
                                          name='template',
                                          label='File matching template\n(example *.h5)',
                                          label_style='bold',
                                          label_size=(300, -1),
                                          value=self.params.facility.standalone.template)
      self.main_sizer.Add(self.template,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      # Composite check
      self.chk_composite = wx.CheckBox(self,
                                     label='Files are composite (like HDF5, files are submitted as individual runs.\nOtherwise, groups of files are submitted as single runs)')
      self.chk_composite.SetValue(params.facility.standalone.composite_files)
      self.main_sizer.Add(self.chk_composite,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.SetTitle('Standalone settings')
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
      self.Bind(wx.EVT_BUTTON, self.onBrowse, id=self.data_dir.btn_big.GetId())
      self.Bind(wx.EVT_RADIOBUTTON, self.onOptionsChanged, self.monitor_for.files)
      self.Bind(wx.EVT_RADIOBUTTON, self.onOptionsChanged, self.monitor_for.folders)
      self.Bind(wx.EVT_RADIOBUTTON, self.onOptionsChanged, self.folders_options.status_file)
      self.Bind(wx.EVT_RADIOBUTTON, self.onOptionsChanged, self.folders_options.n_files)
      self.Bind(wx.EVT_CHECKBOX, self.onOptionsChanged, self.chk_composite)
      self.update_options()
  
    def onOK(self, e):
      self.params.facility.standalone.data_dir = self.data_dir.ctr.GetValue()
      if self.monitor_for.files.GetValue():
        self.params.facility.standalone.monitor_for = 'files'
        self.params.facility.standalone.files.last_modified = float(self.last_modified.ctr.GetValue())
        self.params.facility.standalone.files.minimum_file_size = int(self.minimum_file_size.ctr.GetValue())
      else:
        self.params.facility.standalone.monitor_for = 'folders'
      if self.folders_options.status_file.GetValue():
        self.params.facility.standalone.folders.method = 'status_file'
      elif self.folders_options.n_files.GetValue():
        self.params.facility.standalone.folders.method = 'n_files'
        self.params.facility.standalone.folders.n_files_needed = int(self.n_files_needed.ctr.GetValue())
      self.params.facility.standalone.template = self.template.ctr.GetValue()
      self.params.facility.standalone.composite_files = self.chk_composite.GetValue()
      e.Skip()
  
    def onBrowse(self, e):
      dlg = wx.DirDialog(self, "Choose the input directory:",
                         style=wx.DD_DEFAULT_STYLE)
  
      if dlg.ShowModal() == wx.ID_OK:
        self.data_dir.ctr.SetValue(dlg.GetPath())
      dlg.Destroy()
  
    def onOptionsChanged(self, e):
      self.update_options()
  
    def update_options(self):
      if self.monitor_for.files.GetValue():
        self.folders_options.Disable()
        self.n_files_needed.Disable()
        self.last_modified.Enable()
        self.minimum_file_size.Enable()
      else:
        self.folders_options.Enable()
        if self.folders_options.status_file.GetValue():
          self.n_files_needed.Disable()
        else:
          self.n_files_needed.Enable()
        self.last_modified.Disable()
        self.minimum_file_size.Disable()
  
  class AdvancedSettingsDialog(BaseDialog):
    ''' Advanced settings for the cctbx.xfel front end '''
    def __init__(self, parent, params,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.params = params
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
  
      # Multiprocessing settings
      mp_box = wx.StaticBox(self, label='Multiprocessing Options')
      self.mp_sizer = wx.StaticBoxSizer(mp_box, wx.VERTICAL)
  
      choices = ['local', 'lsf', 'slurm', 'shifter', 'sge', 'pbs', 'htcondor', 'custom']
      self.mp_option = gctr.ChoiceCtrl(self,
                                       name='mp_option',
                                       label='Multiprocessing:',
                                       label_size=(200, -1),
                                       label_style='bold',
                                       choices=choices)
      self.mp_sizer.Add(self.mp_option, flag=wx.EXPAND | wx.ALL, border=10)
      try:
        self.mp_option.ctr.SetSelection(choices.index(params.mp.method))
      except ValueError:
        pass
      self.Bind(wx.EVT_CHOICE, self.onMultiprocessingChoice, self.mp_option.ctr)
  
      # Queue
      queues = ['psanaq', 'psanaq', 'psdebugq','psanaidleq', 'psnehhiprioq',
                'psnehprioq', 'psnehq', 'psfehhiprioq', 'psfehprioq', 'psfehq']
      self.queue_choice = gctr.ChoiceCtrl(self,
                                          name='queue',
                                          label='Queue:',
                                          label_size=(200, -1),
                                          label_style='bold',
                                          choices=queues)
      self.Bind(wx.EVT_CHOICE, self.onQueueChoice, self.queue_choice.ctr)
      self.mp_sizer.Add(self.queue_choice, flag=wx.EXPAND | wx.ALL, border=10)
      try:
        self.queue_choice.ctr.SetSelection(queues.index(params.mp.queue))
      except ValueError:
        pass
  
  
      self.queue_text = gctr.TextButtonCtrl(self,
                                            name='queue',
                                            label='Queue:',
                                            label_style='bold',
                                            label_size=(200, -1),
                                            value=self.params.mp.queue \
                                                  if params.mp.queue is not None else '')
      self.mp_sizer.Add(self.queue_text, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.nproc = gctr.SpinCtrl(self,
                                 name='nproc',
                                 label='Total number of processors:',
                                 label_size=(240, -1),
                                 label_style='normal',
                                 ctrl_size=(100, -1),
                                 ctrl_value='%d'%params.mp.nproc,
                                 ctrl_min=1,
                                 ctrl_max=1000)
      self.mp_sizer.Add(self.nproc, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.nnodes = gctr.SpinCtrl(self,
                                  name='nnodes',
                                  label='Total number of nodes:',
                                  label_size=(240, -1),
                                  label_style='normal',
                                  ctrl_size=(100, -1),
                                  ctrl_value='%d'%params.mp.nnodes,
                                  ctrl_min=1,
                                  ctrl_max=1000)
      self.mp_sizer.Add(self.nnodes, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.nppn_box = gctr.CtrlBase(self)
      nppn_txt = wx.StaticText(self.nppn_box, label="Number of processors per node:")
      self.chk_auto_nproc_per_node = wx.CheckBox(self.nppn_box, label='Auto')
      self.chk_auto_nproc_per_node.SetValue(params.mp.nproc_per_node is None)
      self.nproc_per_node = gctr.SpinCtrl(self.nppn_box,
                                          name='nproc_per_node',
                                          ctrl_value='%d'%params.mp.nproc_per_node if params.mp.nproc_per_node else 1,
                                          ctrl_min = 1, ctrl_max = 1000, label_size=(-1,-1))
      if not params.mp.nproc_per_node: self.nproc_per_node.Disable()
  
      nppn_sizer = wx.FlexGridSizer(1, 3, 0, 10)
      nppn_sizer.Add(nppn_txt, flag=wx.ALL, border=10)
      nppn_sizer.Add(self.chk_auto_nproc_per_node, flag=wx.ALL, border=10)
      nppn_sizer.Add(self.nproc_per_node, flag=wx.EXPAND | wx.ALL, border=10)
      self.nppn_box.SetSizer(nppn_sizer)
      self.mp_sizer.Add(self.nppn_box, flag=wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM, border=10)
  
      self.wall_time = gctr.SpinCtrl(self,
                                     name='wall_time',
                                     label='Max Walltime (mins):',
                                     label_size=(240, -1),
                                     label_style='normal',
                                     ctrl_size=(100, -1),
                                     ctrl_value='%d'%params.mp.wall_time if params.mp.wall_time is not None else 1,
                                     ctrl_min=1,
                                     ctrl_max=2880)
      self.mp_sizer.Add(self.wall_time, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.mpi_command = gctr.TextButtonCtrl(self,
                                             name='mpi_command',
                                             label='MPI command:',
                                             label_style='bold',
                                             label_size=(200, -1),
                                             value=self.params.mp.mpi_command)
      self.mp_sizer.Add(self.mpi_command, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.env_script = gctr.TextButtonCtrl(self,
                                            name='env_script',
                                            label='Environment setup script:',
                                            label_style='bold',
                                            label_size=(200, -1),
                                            value=params.mp.env_script[0] \
                                                  if len(params.mp.env_script) > 0 and \
                                                  params.mp.env_script[0] is not None else '')
      self.mp_sizer.Add(self.env_script, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.phenix_script = gctr.TextButtonCtrl(self,
                                               name='phenix_script',
                                               label='Phenix setup script:',
                                               label_style='bold',
                                               label_size=(200, -1),
                                               value=params.mp.phenix_script[0] \
                                                     if len(params.mp.phenix_script) > 0 and \
                                                     params.mp.phenix_script[0] is not None else '')
      self.mp_sizer.Add(self.phenix_script, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.htcondor_executable_path = gctr.TextButtonCtrl(self,
                                                          name='htcondor_executable_path',
                                                          label='MPI executable path (mp2script or openmpiscript):',
                                                          label_style='bold',
                                                          label_size=(200, -1),
                                                          value=params.mp.htcondor.executable_path \
                                                          if params.mp.htcondor.executable_path is not None else '')
      self.mp_sizer.Add(self.htcondor_executable_path, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.htcondor_filesystemdomain = gctr.TextButtonCtrl(self,
                                                          name='htcondor_filesystemdomain',
                                                          label='Shared filesystem domain:',
                                                          label_style='bold',
                                                          label_size=(200, -1),
                                                          value=params.mp.htcondor.filesystemdomain \
                                                          if params.mp.htcondor.filesystemdomain is not None else '')
      self.mp_sizer.Add(self.htcondor_filesystemdomain, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.main_sizer.Add(self.mp_sizer, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Different nnodes per job type. Implemented for shifter and slurm
  
      self.jobtype_nnodes_box = wx.StaticBox(self, label='Nodes per job')
      self.jobtype_nnodes_sizer = wx.StaticBoxSizer(self.jobtype_nnodes_box, wx.HORIZONTAL)
  
      self.nnodes_index = gctr.SpinCtrl(self,
                                        name='nnodes_index',
                                        label='Indexing:',
                                        label_size=(80, -1),
                                        label_style='normal',
                                        ctrl_size=(100, -1),
                                        ctrl_value='%d'%(params.mp.nnodes_index or 1),
                                        ctrl_min=1,
                                        ctrl_max=1000)
      self.jobtype_nnodes_sizer.Add(self.nnodes_index, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.nnodes_scale = gctr.SpinCtrl(self,
                                        name='nnodes_scale',
                                        label='Scaling:',
                                        label_size=(80, -1),
                                        label_style='normal',
                                        ctrl_size=(100, -1),
                                        ctrl_value='%d'%(params.mp.nnodes_scale or 1),
                                        ctrl_min=1,
                                        ctrl_max=1000)
      self.jobtype_nnodes_sizer.Add(self.nnodes_scale, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.nnodes_merge = gctr.SpinCtrl(self,
                                        name='nnodes_merge',
                                        label='Merging:',
                                        label_size=(80, -1),
                                        label_style='normal',
                                        ctrl_size=(100, -1),
                                        ctrl_value='%d'%(params.mp.nnodes_merge or 1),
                                        ctrl_min=1,
                                        ctrl_max=1000)
      self.jobtype_nnodes_sizer.Add(self.nnodes_merge, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.mp_sizer.Add(self.jobtype_nnodes_sizer, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.extra_box = gctr.CtrlBase(self)
      extra_txt = wx.StaticText(self.extra_box, label="Extra submission arguments")
      self.extra_options = gctr.RichTextCtrl(self.extra_box,
                                             name='extra_options',
                                             size=(-1, 60),
                                             style=wx.VSCROLL,
                                             value="\n".join(self.params.mp.extra_options))
      extra_sizer = wx.FlexGridSizer(1, 2, 0, 10)
      extra_sizer.Add(extra_txt, flag=wx.ALL, border=10)
      extra_sizer.Add(self.extra_options, flag=wx.EXPAND | wx.ALL, border=10)
      extra_sizer.AddGrowableCol(1)
      self.extra_box.SetSizer(extra_sizer)
      self.mp_sizer.Add(self.extra_box, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Shifter-specific settings
  
      self.shifter_image = gctr.TextButtonCtrl(self,
                                               name='shifter_image',
                                               label='Shifter image:',
                                               label_style='bold',
                                               label_size=(200, -1),
                                               value=params.mp.shifter.shifter_image \
                                               if params.mp.shifter.shifter_image is not None else '')
      self.mp_sizer.Add(self.shifter_image, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.shifter_srun_template = gctr.TextButtonCtrl(self,
                                                       name='shifter_srun_template',
                                                       label='Srun Script Template Path:',
                                                       label_style='bold',
                                                       label_size=(200, -1),
                                                       value=params.mp.shifter.srun_script_template \
                                                       if params.mp.shifter.srun_script_template is not None else '')
      self.mp_sizer.Add(self.shifter_srun_template, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.shifter_sbatch_template = gctr.TextButtonCtrl(self,
                                                         name='shifter_sbatch_template',
                                                         label='Sbatch Script Template Path:',
                                                         label_style='bold',
                                                         label_size=(200, -1),
                                                         value=params.mp.shifter.sbatch_script_template \
                                                         if params.mp.shifter.sbatch_script_template is not None else '')
      self.mp_sizer.Add(self.shifter_sbatch_template, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.shifter_jobname = gctr.TextButtonCtrl(self,
                                                 name='shifter_jobname',
                                                 label='Job Name:',
                                                 label_style='bold',
                                                 label_size=(200, -1),
                                                 value=params.mp.shifter.jobname \
                                                 if params.mp.shifter.jobname is not None else '')
      self.mp_sizer.Add(self.shifter_jobname, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.shifter_project = gctr.TextButtonCtrl(self,
                                                 name='shifter_project',
                                                 label='NERSC Project (-A):',
                                                 label_style='bold',
                                                 label_size=(200, -1),
                                                 value=params.mp.shifter.project \
                                                 if params.mp.shifter.project is not None else '')
      self.mp_sizer.Add(self.shifter_project, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.shifter_reservation = gctr.TextButtonCtrl(self,
                                                     name='shifter_reservation',
                                                     label='NERSC Reservation:',
                                                     label_style='bold',
                                                     label_size=(200, -1),
                                                     value=params.mp.shifter.reservation \
                                                     if params.mp.shifter.reservation is not None else '')
      self.mp_sizer.Add(self.shifter_reservation, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.shifter_constraint = gctr.TextButtonCtrl(self,
                                                    name='shifter_constraint',
                                                    label='Job Constraint:',
                                                    label_style='bold',
                                                    label_size=(200, -1),
                                                    value=params.mp.shifter.constraint \
                                                    if params.mp.shifter.constraint is not None else '')
      self.mp_sizer.Add(self.shifter_constraint, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.staging_methods = ["DataWarp", "None"]
      self.staging_descriptions = [
          'Stage logs to the DataWarp burst buffer. WARNING: Only when writing to Cori cscratch. Otherwise logs will be lost.',
          'Write logs directly to disk.']
      self.log_staging = gctr.ChoiceCtrl(self,
                                         name='staging',
                                         label="Log staging",
                                         label_size=(240, -1),
                                         label_style='bold',
                                         ctrl_size=(-1, -1),
                                         choices=self.staging_methods)
      self.log_staging.ctr.SetSelection(
          self.staging_methods.index(params.mp.shifter.staging))
      self.Bind(wx.EVT_CHOICE, self.onStagingChoice, self.log_staging.ctr)
      self.mp_sizer.Add(self.log_staging, flag=wx.EXPAND | wx.ALL, border=10)
      self.staging_help = wx.StaticText(self, label=self.staging_descriptions[self.log_staging.ctr.GetSelection()], size=(600,30))
      self.staging_help.Wrap(600)
      self.mp_sizer.Add(self.staging_help, flag=wx.EXPAND | wx.ALL, border=10)
  
  
      # Data analysis settings
      analysis_box = wx.StaticBox(self, label='Data Analysis Options')
      self.analysis_sizer = wx.StaticBoxSizer(analysis_box, wx.VERTICAL)
  
      # Processing back-ends
      self.dispatchers_sizer = wx.BoxSizer(wx.HORIZONTAL)
      self.back_ends = ['cctbx.xfel (LCLS mode)', 'cctbx.xfel (standalone mode)', 'Ha14', 'Small cell', 'custom']
      self.dispatchers = ['cctbx.xfel.xtc_process', 'cctbx.xfel.process', 'cxi.xtc_process', 'cctbx.xfel.small_cell_process', 'custom']
      self.dispatcher_descriptions = [
        'Process the data according to Brewster 2018, using DIALS for indexing, refinement and integration, with stills-specific defaults. Converts XTC into CBF in memory and optionally provides dumping of CBFs.',
        'Process the data according to Brewster 2018, using DIALS for indexing, refinement and integration, with stills-specific defaults. Reads image files directly.',
        'Process the data according to Hattne 2014, using LABELIT for initial indexing and stills-specific refinement and integration code implemented in the package cctbx.rstbx.',
        'Process the data according to Brewster 2015, using small cell for initial indexing and using DIALS for refinement and integration, with stills-specific defaults.',
        'Provide a custom program. See authors for details.']
  
      self.back_end = gctr.ChoiceCtrl(self,
                                      name='back_end',
                                      label='Processing back end:',
                                      label_size=(240, -1),
                                      label_style='bold',
                                      ctrl_size=(-1, -1),
                                      choices=self.back_ends)
      self.Bind(wx.EVT_CHOICE, self.onBackendChoice, self.back_end.ctr)
      self.dispatchers_sizer.Add(self.back_end, flag=wx.ALIGN_LEFT)
  
      self.custom_dispatcher = gctr.PanelTextCtrl(self,
                                                  ctrl_size=(300, -1),
                                                  value="")
      self.dispatchers_sizer.Add(self.custom_dispatcher, flag=wx.EXPAND | wx.ALL)
  
      try:
        self.back_end.ctr.SetSelection(self.dispatchers.index(params.dispatcher))
        self.custom_dispatcher.Hide()
      except ValueError:
        self.back_end.ctr.SetSelection(len(self.dispatchers)-1)
        self.custom_dispatcher.ctr.SetValue(params.dispatcher)
  
      self.analysis_sizer.Add(self.dispatchers_sizer, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.dispatcher_help = wx.StaticText(self, label=self.dispatcher_descriptions[self.back_end.ctr.GetSelection()], size=(600,80))
      self.dispatcher_help.Wrap(600)
      self.analysis_sizer.Add(self.dispatcher_help, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.main_sizer.Add(self.analysis_sizer, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.SetTitle('Advanced Settings')
  
      self.Bind(wx.EVT_CHECKBOX, self.onChkNppnAuto, self.chk_auto_nproc_per_node)
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
      self.updateMultiprocessing()
  
    def onChkNppnAuto(self, e):
      if self.chk_auto_nproc_per_node.GetValue():
        self.nproc_per_node.Disable()
      else:
        self.nproc_per_node.Enable()
  
    def onMultiprocessingChoice(self, e):
      self.updateMultiprocessing()
  
    def updateMultiprocessing(self):
      if self.mp_option.ctr.GetStringSelection() == 'local':
        self.queue_choice.Hide()
        self.queue_text.Show()
        self.nnodes.Hide()
        self.nproc.Show()
        self.nppn_box.Hide()
        self.wall_time.Hide()
        self.mpi_command.Hide()
        self.env_script.Hide()
        self.phenix_script.Hide()
        self.htcondor_executable_path.Hide()
        self.htcondor_filesystemdomain.Hide()
        self.jobtype_nnodes_box.Hide()
        self.nnodes_index.Hide()
        self.nnodes_scale.Hide()
        self.nnodes_merge.Hide()
        self.extra_box.Hide()
        self.shifter_image.Hide()
        self.shifter_srun_template.Hide()
        self.shifter_sbatch_template.Hide()
        self.shifter_jobname.Hide()
        self.shifter_project.Hide()
        self.shifter_reservation.Hide()
        self.shifter_constraint.Hide()
        self.log_staging.Hide()
        self.staging_help.Hide()
      elif self.mp_option.ctr.GetStringSelection() == 'shifter':
        self.queue_choice.Hide()
        self.queue_text.Show()
        self.nproc.Hide()
        self.nnodes.Show()
        self.nppn_box.Show()
        self.wall_time.Show()
        self.mpi_command.Hide()
        self.env_script.Hide()
        self.phenix_script.Hide()
        self.htcondor_executable_path.Hide()
        self.htcondor_filesystemdomain.Hide()
        self.nnodes_index.Show()
        self.nnodes_scale.Show()
        self.nnodes_merge.Show()
        self.extra_box.Show()
        self.jobtype_nnodes_box.Show()
        self.shifter_image.Show()
        self.shifter_srun_template.Show()
        self.shifter_sbatch_template.Show()
        self.shifter_jobname.Show()
        self.shifter_project.Show()
        self.shifter_reservation.Show()
        self.shifter_constraint.Show()
        self.log_staging.Show()
        self.staging_help.Show()
      elif self.mp_option.ctr.GetStringSelection() == 'htcondor':
        self.queue_choice.Hide()
        self.queue_text.Show()
        self.nproc.Show()
        self.nnodes.Hide()
        self.nppn_box.Hide()
        self.wall_time.Hide()
        self.mpi_command.Hide()
        self.env_script.Show()
        self.phenix_script.Show()
        self.htcondor_executable_path.Show()
        self.htcondor_filesystemdomain.Show()
        self.nnodes_index.Hide()
        self.nnodes_scale.Hide()
        self.nnodes_merge.Hide()
        self.extra_box.Hide()
        self.jobtype_nnodes_box.Hide()
        self.shifter_image.Hide()
        self.shifter_srun_template.Hide()
        self.shifter_sbatch_template.Hide()
        self.shifter_jobname.Hide()
        self.shifter_project.Hide()
        self.shifter_reservation.Hide()
        self.shifter_constraint.Hide()
        self.log_staging.Hide()
        self.staging_help.Hide()
      elif self.mp_option.ctr.GetStringSelection() == 'slurm':
        self.queue_choice.Hide()
        self.queue_text.Show()
        self.nproc.Hide()
        self.nnodes.Hide()
        self.nppn_box.Show()
        self.wall_time.Hide()
        self.mpi_command.Show()
        self.env_script.Show()
        self.phenix_script.Show()
        self.htcondor_executable_path.Hide()
        self.htcondor_filesystemdomain.Hide()
        self.nnodes_index.Show()
        self.nnodes_scale.Show()
        self.nnodes_merge.Show()
        self.extra_box.Show()
        self.jobtype_nnodes_box.Show()
        self.shifter_image.Hide()
        self.shifter_srun_template.Hide()
        self.shifter_sbatch_template.Hide()
        self.shifter_jobname.Hide()
        self.shifter_project.Hide()
        self.shifter_reservation.Hide()
        self.shifter_constraint.Hide()
        self.log_staging.Hide()
        self.staging_help.Hide()
      else :
        if self.params.facility.name == 'lcls' and self.mp_option.ctr.GetStringSelection() == 'lsf':
          self.queue_choice.Show()
          self.queue_text.Hide()
        else:
          self.queue_choice.Hide()
          self.queue_text.Show()
        self.nproc.Show()
        self.nnodes.Hide()
        self.nppn_box.Hide()
        self.wall_time.Hide()
        self.mpi_command.Show()
        self.env_script.Show()
        self.phenix_script.Show()
        self.htcondor_executable_path.Hide()
        self.htcondor_filesystemdomain.Hide()
        self.nnodes_index.Hide()
        self.nnodes_scale.Hide()
        self.nnodes_merge.Hide()
        self.extra_box.Show()
        self.jobtype_nnodes_box.Hide()
        self.shifter_image.Hide()
        self.shifter_srun_template.Hide()
        self.shifter_sbatch_template.Hide()
        self.shifter_jobname.Hide()
        self.shifter_project.Hide()
        self.shifter_reservation.Hide()
        self.shifter_constraint.Hide()
        self.log_staging.Hide()
        self.staging_help.Hide()
  
      self.Fit()
  
    def onQueueChoice(self, e):
      queue = self.queue_choice.ctr.GetString(self.queue_choice.ctr.GetSelection())
      if 'neh' in queue or 'feh' in queue:
        self.nproc.ctr.SetValue(16)
        self.nproc.ctr.SetIncrement(16)
      elif 'psana' in queue or 'debug' in queue:
        self.nproc.ctr.SetValue(12)
        self.nproc.ctr.SetIncrement(12)
      else:
        self.nproc.ctr.SetValue(1)
        self.nproc.ctr.SetIncrement(1)
  
    def onStagingChoice(self, e):
      self.params.mp.shifter.staging = self.staging_methods[self.log_staging.ctr.GetSelection()]
      self.staging_help.SetLabel(self.staging_descriptions[self.log_staging.ctr.GetSelection()])
      self.staging_help.Wrap(600)
  
  
    def onBackendChoice(self, e):
      self.params.dispatcher = self.dispatchers[self.back_end.ctr.GetSelection()]
      self.dispatcher_help.SetLabel(self.dispatcher_descriptions[self.back_end.ctr.GetSelection()])
      self.dispatcher_help.Wrap(600)
      if self.params.dispatcher == 'custom':
        self.custom_dispatcher.Show()
        self.Layout()
      else:
        self.custom_dispatcher.Hide()
        self.Layout()
  
  
    def onOK(self, e):
      self.params.dispatcher = self.dispatchers[self.back_end.ctr.GetSelection()]
  
      if self.params.dispatcher == 'custom':
        self.params.dispatcher = self.custom_dispatcher.ctr.GetValue()
  
      self.params.mp.method = self.mp_option.ctr.GetStringSelection()
      self.params.mp.nproc = int(self.nproc.ctr.GetValue())
  
      if self.params.facility.name == 'lcls' and self.params.mp.method == "lsf":
        self.params.mp.queue = self.queue_choice.ctr.GetStringSelection()
      else:
        if self.chk_auto_nproc_per_node.GetValue():
          self.params.mp.nproc_per_node = None
        else:
          self.params.mp.nproc_per_node = int(self.nproc_per_node.ctr.GetValue())
        self.params.mp.queue = self.queue_text.ctr.GetValue()
        if self.mp_option.ctr.GetStringSelection() in ['shifter', 'slurm']:
          self.params.mp.nnodes_index = int(self.nnodes_index.ctr.GetValue())
          self.params.mp.nnodes_scale = int(self.nnodes_scale.ctr.GetValue())
          self.params.mp.nnodes_merge = int(self.nnodes_merge.ctr.GetValue())
        if self.mp_option.ctr.GetStringSelection() == 'shifter':
          self.params.mp.nnodes = int(self.nnodes.ctr.GetValue())
          self.params.mp.wall_time = int(self.wall_time.ctr.GetValue())
        else:
          self.params.mp.env_script = [self.env_script.ctr.GetValue()]
          self.params.mp.phenix_script = [self.phenix_script.ctr.GetValue()]
          self.params.mp.nproc = int(self.nproc.ctr.GetValue())
  
      self.params.mp.mpi_command = self.mpi_command.ctr.GetValue() \
        if len(self.mpi_command.ctr.GetValue()) > 0 else None
      if len(self.extra_options.GetValue()) > 0:
        self.params.mp.extra_options = self.extra_options.GetValue().split('\n')
      else:
        self.params.mp.extra_options = []
  
      # Copy htcondor settings into the htcondor phil
      self.params.mp.htcondor.executable_path = self.htcondor_executable_path.ctr.GetValue() \
        if len(self.htcondor_executable_path.ctr.GetValue()) > 0 else None
      self.params.mp.htcondor.filesystemdomain = self.htcondor_filesystemdomain.ctr.GetValue() \
        if len(self.htcondor_filesystemdomain.ctr.GetValue()) > 0 else None
  
      # Copy shfiter settings into the shifter phil
      self.params.mp.shifter.sbatch_script_template = self.shifter_sbatch_template.ctr.GetValue() \
        if len(self.shifter_sbatch_template.ctr.GetValue()) > 0 else None
      self.params.mp.shifter.shifter_image = self.shifter_image.ctr.GetValue() \
        if len(self.shifter_image.ctr.GetValue()) > 0 else None
      self.params.mp.shifter.srun_script_template = self.shifter_srun_template.ctr.GetValue() \
        if len(self.shifter_srun_template.ctr.GetValue()) > 0 else None
      self.params.mp.shifter.jobname=self.shifter_jobname.ctr.GetValue() \
        if len(self.shifter_jobname.ctr.GetValue()) > 0 else None
      self.params.mp.shifter.project=self.shifter_project.ctr.GetValue() \
        if len(self.shifter_project.ctr.GetValue()) > 0 else None
      self.params.mp.shifter.reservation=self.shifter_reservation.ctr.GetValue() \
        if len(self.shifter_reservation.ctr.GetValue()) > 0 else None
      self.params.mp.shifter.constraint =self.shifter_constraint.ctr.GetValue() \
        if len(self.shifter_constraint.ctr.GetValue()) > 0 else None
  
      e.Skip()
  
  
  class CalibrationDialog(BaseDialog):
    def __init__(self, parent,
                 label_style='bold',
                 content_style='normal',
                 db=None,
                 *args, **kwargs):
  
      self.parent = parent
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
  
      self.db = db
  
      # Metrology version name
      self.version_name = gctr.TextButtonCtrl(self,
                                              label='Metrology Version Name:',
                                              label_size=(180, -1),
                                              label_style='bold',
                                              value='cspad')
      self.main_sizer.Add(self.version_name, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Reflection type and number of images in subset
      self.top_sizer = wx.FlexGridSizer(1, 2, 0, 20)
      self.top_sizer.AddGrowableCol(1, 1)
      choices = ['indexed', 'reindexed strong', 'integrated']
      self.reflections = gctr.ChoiceCtrl(self,
                                         label='Reflections:',
                                         label_size=(100, -1),
                                         label_style='bold',
                                         choices=choices)
      self.n_subset = gctr.SpinCtrl(self,
                                    label='Images in subset:',
                                    label_size=(120, -1),
                                    label_style='normal',
                                    ctrl_value='1000',
                                    ctrl_max=10000,
                                    ctrl_min=10)
      self.top_sizer.Add(self.reflections)
      self.top_sizer.Add(self.n_subset, wx.ALIGN_RIGHT)
  
      #Trial & runs
      self.trial_sizer = wx.FlexGridSizer(1, 2, 0, 20)
      self.trial_sizer.AddGrowableCol(1, 1)
      trials = [str(i.trial) for i in self.db.get_all_trials()]
      self.trial_number = gctr.ChoiceCtrl(self,
                                          label='Trial:',
                                          label_size=(40, -1),
                                          label_style='normal',
                                          ctrl_size=(200, -1),
                                          choices=trials)
      self.trial_runs = gctr.CheckListCtrl(self,
                                           label='Runs:',
                                           label_size=(40, -1),
                                           label_style='normal',
                                           ctrl_size=(200, -1),
                                           choices=[])
      self.trial_sizer.Add(self.trial_number)
      self.trial_sizer.Add(self.trial_runs, wx.ALIGN_RIGHT)
  
      #Phil blob
      self.phil_text = gctr.RichTextCtrl(self, size=(550, 300), style=wx.VSCROLL)
      self.phil_path = gctr.TwoButtonCtrl(self,
                                          label='PHIL path:',
                                          label_size=(80, -1),
                                          label_style='normal',
                                          button1=True,
                                          button1_label='Browse...',
                                          button2=True,
                                          button2_label='Default PHIL')
  
      self.main_sizer.Add(self.top_sizer, flag=wx.EXPAND | wx.ALL, border=10)
      self.main_sizer.Add(self.trial_sizer, flag=wx.EXPAND | wx.ALL, border=10)
      self.main_sizer.Add(self.phil_text, flag=wx.EXPAND | wx.ALL, border=10)
      self.main_sizer.Add(self.phil_path, flag=wx.EXPAND | wx.ALL, border=10)
  
      self.chk_split_dataset = wx.CheckBox(self,
                                           label='Split dataset into 2 halves (outputs statistics, double runtime, uses 2x number of images')
      self.chk_split_dataset.SetValue(True)
      self.main_sizer.Add(self.chk_split_dataset, flag=wx.ALL, border=10)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
      # Bindings
      self.Bind(wx.EVT_CHOICE, self.onTrialChoice,
                id=self.trial_number.ctr.GetId())
      self.Bind(wx.EVT_BUTTON, self.onBrowse, self.phil_path.button1)
      self.Bind(wx.EVT_BUTTON, self.onDefault, self.phil_path.button2)
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
      self.Bind(wx.EVT_BUTTON, self.onCancel, id=wx.ID_CANCEL)
  
      self.SetTitle('Calibration Settings')
      self.find_runs()
  
      self.frames_sentinel = None
      self.start_frames_sentinel()
  
    def start_frames_sentinel(self):
      if self.frames_sentinel is None:
        from xfel.ui.components.xfel_gui_init import FramesSentinel
        self.frames_sentinel = FramesSentinel(self)
      self.frames_sentinel.active = True
      self.frames_sentinel.start()
  
    def stop_frames_sentinel(self):
      if self.frames_sentinel is not None:
        self.frames_sentinel.active = False
        self.frames_sentinel.join()
  
    def onOK(self, e):
      from xfel.ui.db import get_run_path
      from xfel.util.mp import get_lsf_submit_command
      from xfel.ui import settings_dir
      from libtbx import easy_run
      import copy
      params = copy.deepcopy(self.parent.params)
      params.mp.nproc = 1
  
      version_str = self.version_name.ctr.GetValue()
      working_dir = os.path.join(params.output_folder, "metrology", version_str)
      if os.path.exists(working_dir):
        wx.MessageBox('Version name %s already used'%version_str, 'Warning!',
                      wx.ICON_EXCLAMATION)
        return
  
      self.stop_frames_sentinel()
  
      runs = [self.db.get_run(run_number=int(r)) for r in self.trial_runs.ctr.GetCheckedStrings()]
      run_ids = [r.id for r in runs]
      command = "cspad.cbf_metrology reflections=%s tag=%s split_dataset=%s n_subset=%d "% (
        self.reflections.ctr.GetStringSelection(), self.version_name.ctr.GetValue(),
        self.chk_split_dataset.GetValue(), self.n_subset.ctr.GetValue())
  
      phil_file = os.path.join(settings_dir, "cfgs", "%s.phil"%version_str)
      f = open(phil_file, 'w')
      f.write(self.phil_text.GetValue())
      f.close()
      command += phil_file + " "
  
      trial = self.db.get_trial(trial_number=int(self.trial_number.ctr.GetStringSelection()))
      runs_found = []
      run_paths = []
      for rungroup in trial.rungroups:
        for run in rungroup.runs:
          if run.id in runs_found:
            continue
          if run.id in run_ids:
            run_ids.pop(run_ids.index(run.id))
            runs_found.append(run.id)
            run_paths.append(os.path.join(get_run_path(params.output_folder, trial, rungroup, run), 'out'))
      assert len(run_ids) == 0
  
      command += " ".join(run_paths)
  
      submit_path = os.path.join(settings_dir, "%s.sh"%version_str)
      command = str(get_lsf_submit_command(command, submit_path, working_dir, params.mp)()) # comes back as unicode which throws off easy_run
      cwd = os.getcwd()
      os.makedirs(working_dir)
      os.chdir(working_dir)
  
      print("Submitting metrology refinement. Command:")
      print(command)
      try:
        results = easy_run.fully_buffered(command=command)
        results.show_stdout()
        results.show_stderr()
        results.raise_if_errors()
      except Exception as exc:
        if not "Warning: job being submitted without an AFS token." in str(exc):
          raise exc
      os.chdir(cwd)
      print("Output will be in", working_dir)
  
      e.Skip()
  
    def onTrialChoice(self, e):
      self.find_runs()
  
    def find_runs(self):
      self.trial_runs.ctr.Clear()
      trial = self.db.get_all_trials()[self.trial_number.ctr.GetSelection()]
      runs = [str(i.run) for i in trial.runs]
      if runs:
        self.trial_runs.ctr.InsertItems(items=runs, pos=0)
  
    def onBrowse(self, e):
      ''' Open dialog for selecting PHIL file '''
      load_dlg = wx.FileDialog(self,
                               message="Load PHIL file",
                               defaultDir=os.curdir,
                               defaultFile="*.phil",
                               wildcard="*.phil",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                               )
      if load_dlg.ShowModal() == wx.ID_OK:
        target_file = load_dlg.GetPaths()[0]
        with open(target_file, 'r') as phil_file:
          phil_file_contents = phil_file.read()
        self.phil_text.SetValue(phil_file_contents)
        self.phil_path.ctr.SetValue(target_file)
      load_dlg.Destroy()
  
    def onDefault(self, e):
      # TODO: Generate default PHIL parameters
      pass
  
    def onCancel(self, e):
      self.stop_frames_sentinel()
      e.Skip()
  
  class AveragingDialog(BaseDialog):
    def __init__(self, parent, run, params,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.run = run
      self.params = params
  
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
  
      # Raw image option
      self.raw_toggle = gctr.RadioCtrl(self,
                                       label='',
                                       label_style='normal',
                                       label_size=(-1, -1),
                                       direction='horizontal',
                                       items={'corrected':'corrected',
                                              'raw':'raw'})
      self.raw_toggle.corrected.SetValue(1)
      self.main_sizer.Add(self.raw_toggle, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
    def onOK(self, e):
      from xfel.ui.components.averaging import AveragingCommand
      from libtbx import easy_run
      raw = self.raw_toggle.raw.GetValue() == 1
      average_command = AveragingCommand(self.run, self.params, raw)()
      print("executing", average_command)
      result = easy_run.fully_buffered(command=average_command)
      result.show_stdout()
      e.Skip()
  
  class TrialTagSelectionDialog(BaseDialog):
    def __init__(self, parent,
                 label_style='bold',
                 content_style='normal',
                 db=None,
                 *args, **kwargs):
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
      self.db = db
      self.parent = parent
      self.run_paths = []
      self.tags = []
      self.tag_names = []
  
      self.trials = db.get_all_trials()
      trial_numbers = ["%d"%trial.trial for trial in self.trials]
      self.trials_ctrl = gctr.ChoiceCtrl(self,
                                         label='Trial number:',
                                         label_size=(120, -1),
                                         label_style='bold',
                                         choices=trial_numbers)
      self.main_sizer.Add(self.trials_ctrl, flag=wx.EXPAND | wx.ALL, border=10)
  
  
      self.trial_tags = gctr.CheckListCtrl(self,
                                           label='Tags (optional):',
                                           label_size=(40, -1),
                                           label_style='normal',
                                           ctrl_size=(200, -1),
                                           choices=[])
      self.main_sizer.Add(self.trial_tags, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.SetTitle('Pick a trial and optionally some tags from that trial')
  
      self.Bind(wx.EVT_CHOICE, self.onTrialSelect, id=self.trials_ctrl.ctr.GetId())
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
      wx.CallAfter(self.refresh_tags)
  
    def onTrialSelect(self, e):
      self.refresh_tags()
  
    def refresh_tags(self):
      self.trial_tags.ctr.Clear()
      trial = self.trials[self.trials_ctrl.ctr.GetSelection()]
      self.tags = []
      self.tag_names = []
      tag_ids = []
      for run in trial.runs:
        for tag in run.tags:
          if tag.id not in tag_ids:
            self.tags.append(tag)
            tag_ids.append(tag.id)
            self.tag_names.append(tag.name)
  
      if self.tag_names:
        self.trial_tags.ctr.InsertItems(items=self.tag_names, pos=0)
  
    def onOK(self, e):
      from xfel.ui.db import get_run_path
      self.run_paths = []
  
      trial = self.trials[self.trials_ctrl.ctr.GetSelection()]
      tags = [self.tags[self.tag_names.index(t)] for t in self.trial_tags.ctr.GetCheckedStrings()]
      tag_ids = [t.id for t in tags]
  
      run_ids = []
      for rungroup in trial.rungroups:
        for run in rungroup.runs:
          if run.id not in run_ids:
            if len(tags) == 0:
              self.run_paths.append(os.path.join(
                get_run_path(self.parent.main.params.output_folder, trial, rungroup, run),
                'out'))
            else:
              run_tag_ids = [t.id for t in run.tags]
              for tag_id in tag_ids:
                if tag_id in run_tag_ids:
                  run_ids.append(run.id)
                  self.run_paths.append(os.path.join(
                    get_run_path(self.parent.main.params.output_folder, trial, rungroup, run),
                    'out'))
                  break
      e.Skip()
  
  class MultiRunTagDialog(BaseDialog):
    def __init__(self, parent,
                 label_style='bold',
                 content_style='normal',
                 db=None,
                 *args, **kwargs):
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
      self.parent = parent
      self.db = db
      self.db_tags = self.db.get_all_tags()
      self.db_tag_names = [t.name for t in self.db_tags]
      self.db_tags_selected_sort = self.db_tag_names
      self.db_runs = self.db.get_all_runs()
      self.db_run_numbers = [str(r.run) for r in self.db_runs]
      self.runs_selected = []
      self.buttons_for_runs_selected = []
  
      self.multiruntag_sizer = wx.BoxSizer(wx.VERTICAL)
  
      self.select_runs_panel = wx.Panel(self)
      self.select_runs_sizer = wx.BoxSizer(wx.HORIZONTAL)
      self.select_runs =  gctr.CheckListCtrl(self,
                                             label='Selected runs:',
                                             label_size=(200, -1),
                                             label_style='normal',
                                             ctrl_size=(240, 300),
                                             direction='vertical',
                                             choices=self.db_run_numbers)
      self.select_runs_sizer.Add(self.select_runs,
                                 flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP,
                                 border=10)
      self.select_runs_panel.SetSizer(self.select_runs_sizer)
  
      self.sort_type_panel = wx.Panel(self)
      self.sort_type_sizer = wx.BoxSizer(wx.VERTICAL)
      self.radio_sort = gctr.RadioCtrl(self.sort_type_panel,
                                       label='Sort tags by',
                                       label_style='normal',
                                       label_size=(80, -1),
                                       direction='horizontal',
                                       items={'id':'DB ID',
                                              'alpha':'name'})
      self.sort_type_sizer.Add(self.radio_sort,
                               flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP,
                               border=5)
      self.sort_type_panel.SetSizer(self.sort_type_sizer)
  
      self.button_panel = wx.Panel(self)
      self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
      self.btn_add_tags    = wx.Button(self.button_panel, size=(115, -1),
                                       label='Add tags')
      self.btn_remove_tags = wx.Button(self.button_panel, size=(115, -1),
                                       label='Remove tags')
      self.button_sizer.Add(self.btn_add_tags,
                            flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP,
                            border=5)
      self.button_sizer.Add(self.btn_remove_tags,
                            flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP,
                            border=5)
      self.button_panel.SetSizer(self.button_sizer)
  
      # Add panels to main sizer
      self.multiruntag_sizer.Add(self.select_runs_panel,
                                 flag=wx.EXPAND | wx.LEFT | wx.RIGHT)
      self.multiruntag_sizer.Add(self.sort_type_panel,
                                 flag=wx.EXPAND | wx.BOTTOM | wx.TOP)
      self.multiruntag_sizer.Add(self.button_panel,
                                 flag=wx.EXPAND | wx.BOTTOM | wx.TOP)
      self.main_sizer.Add(self.multiruntag_sizer,
                          flag=wx.EXPAND | wx.RIGHT | wx.LEFT)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.Layout()
      self.SetTitle('Manage multiple runs')
  
      self.Bind(wx.EVT_CHECKLISTBOX, self.onRunChoice, self.select_runs.ctr)
      self.Bind(wx.EVT_RADIOBUTTON, self.onSortDefault, self.radio_sort.id)
      self.Bind(wx.EVT_RADIOBUTTON, self.onSortAlphanum, self.radio_sort.alpha)
      self.Bind(wx.EVT_BUTTON, self.onAddTags, self.btn_add_tags)
      self.Bind(wx.EVT_BUTTON, self.onRemoveTags, self.btn_remove_tags)
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
    def onRunChoice(self, e):
      run_numbers_selected = self.select_runs.ctr.GetCheckedStrings()
      self.selected = {}
      for r in self.db_runs:
        if r.run in run_numbers_selected:
          self.selected[r.run] = [r]
      for b in self.parent.all_tag_buttons:
        if b.run.run in self.selected:
          self.selected[b.run.run].append(b)
  
    def onSortDefault(self, e):
      self.db_tags_selected_sort = self.db_tag_names
  
    def onSortAlphanum(self, e):
      self.db_tags_selected_sort = sorted(self.db_tag_names)
  
    def onAddTags(self, e):
      tag_dlg = wx.MultiChoiceDialog(self,
                                     message='Add these tags to selected runs',
                                     caption='Add tags to multiple runs',
                                     choices=self.db_tags_selected_sort)
      tag_dlg.Fit()
  
      if (tag_dlg.ShowModal() == wx.ID_OK):
        tag_indices = tag_dlg.GetSelections()
        add_tags = [t for t in self.db_tags if self.db_tag_names.index(t.name) in tag_indices]
  
        runs_with_tags_buttons = self.parent.all_tag_buttons
  
        for tag in add_tags:
          for run_number in self.selected.keys():
            run, button = self.selected[run_number]
            run_tags = run.app.get_run_tags(run.run_id)
            run_tag_ids = [t.tag_id for t in run_tags]
            if tag.tag_id not in run_tag_ids:
              run.add_tag(tag)
              button.tags.append(tag)
              button.update_label()
  
  
    def onRemoveTags(self, e):
      tag_dlg = wx.MultiChoiceDialog(self,
                                     message='Remove these tags from selected runs',
                                     caption='Remove tags from multiple runs',
                                     choices=self.db_tags_selected_sort)
      tag_dlg.Fit()
  
      if (tag_dlg.ShowModal() == wx.ID_OK):
        tag_indices = tag_dlg.GetSelections()
        remove_tags = [t for t in self.db_tags if self.db_tag_names.index(t.name) in tag_indices]
  
        for tag in remove_tags:
          for run_number in self.selected.keys():
            run, button = self.selected[run_number]
            run_tags = run.app.get_run_tags(run.run_id)
            run_tag_ids = [t.tag_id for t in run_tags]
            if tag.tag_id in run_tag_ids:
              run.remove_tag(tag)
              tag_on_button = [t for t in button.tags if t.tag_id == tag.tag_id][0] # proxy tag =/= other proxy tag
              button.tags.remove(tag_on_button)
              button.update_label()
  
    def onOK(self, e):
      e.Skip()
  
  class TagDialog(BaseDialog):
    def __init__(self, parent,
                 label_style='bold',
                 content_style='normal',
                 db=None,
                 *args, **kwargs):
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
  
      self.db = db
      self.db_tags = self.db.get_all_tags()
      self.deleted_tags = []
      self.new_tags = []
      self.edited_tags =[]
      self.index = 0
  
      self.top_sizer = wx.BoxSizer(wx.HORIZONTAL)
  
      self.button_panel = wx.Panel(self)
      self.button_sizer = wx.BoxSizer(wx.VERTICAL)
      self.btn_add = wx.Button(self.button_panel, size=(120, -1),
                               label='Add Tag')
      self.btn_rmv = wx.Button(self.button_panel, size=(120, -1),
                               label='Remove Tags')
      self.btn_clr = wx.Button(self.button_panel, size=(120, -1),
                               label='Clear All')
      self.button_sizer.Add(self.btn_add)
      self.button_sizer.Add(self.btn_rmv)
      self.button_sizer.Add(self.btn_clr)
      self.button_panel.SetSizer(self.button_sizer)
  
      self.tag_panel = ScrolledPanel(self, size=(500, 400))
      self.tag_list = EdListCtrl(self.tag_panel,
                                 style=wx.LC_REPORT | wx.SUNKEN_BORDER)
      self.tag_sizer = wx.BoxSizer(wx.VERTICAL)
      self.tag_panel.SetSizer(self.tag_sizer)
  
      self.tag_list.InsertColumn(0, 'Sample Tag', width=200)
      self.tag_list.InsertColumn(1, 'Comments', width=300)
  
      # Populate tags with current values from db
      if len(self.db_tags) > 0:
        for tag in self.db_tags:
          self.tag_list.InsertItem(self.index, str(tag.name))
          self.tag_list.SetItem(self.index, 1, str(tag.comment))
          self.tag_list.SetItemData(self.index, tag.tag_id)
          self.index += 1
  
      self.tag_sizer.Add(self.tag_list, 1, flag=wx.EXPAND)
  
      # Add panels to main sizer
      self.top_sizer.Add(self.button_panel,
                         flag=wx.LEFT, border=10)
      self.top_sizer.Add(self.tag_panel,
                         flag=wx.EXPAND | wx.RIGHT | wx.LEFT, border=10)
      self.main_sizer.Add(self.top_sizer,
                          flag=wx.EXPAND| wx.TOP | wx.BOTTOM, border=10)
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                     flag=wx.EXPAND | wx.ALL,
                     border=10)
  
      self.Layout()
      self.SetTitle('Manage Tags')
  
      # Button bindings
      self.Bind(wx.EVT_BUTTON, self.onAdd, self.btn_add)
      self.Bind(wx.EVT_BUTTON, self.onRemove, self.btn_rmv)
      self.Bind(wx.EVT_BUTTON, self.onClear, self.btn_clr)
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
    def onAdd(self, e):
      ''' Add a string item to list; focus on item & provide default tag name'''
      new_tag = ('default tag {}'.format(self.index), '', self.index)
      self.new_tags.append(new_tag)
      self.tag_list.InsertItem(self.index, new_tag[0])
      self.tag_list.SetItem(self.index, 1, new_tag[1])
      self.tag_list.SetItemData(self.index, -1)
      #self.tag_list.Select(self.index)
      #self.tag_list.Focus(self.index)
      self.index += 1
  
    def onRemove(self, e):
      selected_indices = getListCtrlSelection(self.tag_list)
      tag_ids = [self.tag_list.GetItemData(i) for i in selected_indices]
      self.deleted_tags = [i for i in self.db_tags if i.tag_id in tag_ids]
  
      for i in range(len(selected_indices)):
        to_delete = getListCtrlSelection(self.tag_list)
        self.tag_list.DeleteItem(to_delete[0])
  
    def onClear(self, e):
      warning = wx.MessageDialog(self,
                                 message='Are you sure you want to delete all '
                                         'tags? \n This cannot be reversed!',
                                 caption='Warning',
                                 style=wx.YES_NO | wx.ICON_EXCLAMATION)
      if (warning.ShowModal() == wx.ID_YES):
        self.deleted_tags = self.db_tags
        self.tag_list.DeleteAllItems()
        self.index = 0
        self.db_tags = []
  
    def onOK(self, e):
      try:
        # Delete tags from DB
        for tag in self.deleted_tags:
          self.db.delete_tag(tag=tag)
  
        # Update names for edited tags
        all_items = [(self.tag_list.GetItemData(i),
                      self.tag_list.GetItem(itemIdx=i, col=0),
                      self.tag_list.GetItem(itemIdx=i, col=1))
                      for i in range(self.tag_list.GetItemCount())]
  
        self.db_tags = self.db.get_all_tags()
        tag_ids = [i.tag_id for i in self.db_tags]
        new_tag_names = [i[1].GetText() for i in all_items]
  
        if len([i for i in new_tag_names if new_tag_names.count(i) > 1]) != 0:
          wx.MessageBox('Need a unique tag name!', 'Warning',
                         wx.ICON_EXCLAMATION)
        else:
          for item in all_items:
            if item[0] in tag_ids:
              tag = self.db.get_tag(tag_id=item[0])
              tag.name = item[1].GetText()
              tag.comment = item[2].GetText()
            elif item[0] == -1:
              self.db.create_tag(name=item[1].GetText(), comment=item[2].GetText())
  
      except Exception as exception:
        print(str(exception))
  
      e.Skip()
  
  
  class RunBlockDialog(BaseDialog):
    ''' Comes up when individual run block button is clicked; allows for run
    block settings to be manipulated by user '''
  
    def __init__(self, parent, db,
                 block=None, trial=None,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.parent = parent
      self.block = block
      self.all_blocks = []
      self.db = db
      self.use_ids = db.params.facility.name not in ['lcls']
      self.is_lcls = db.params.facility.name == 'lcls'
  
      all_runs = db.get_all_runs()
      if self.use_ids:
        runs_available = sorted([i.id for i in all_runs])
      else:
        runs_available = sorted([int(i.run) for i in all_runs])
      self.first_avail = min(runs_available)
      self.last_avail = max(runs_available)
  
      if block is None:
        runs = self.db.get_all_runs()
        if self.use_ids:
          run_numbers = [r.id for r in runs]
        else:
          run_numbers = list(sorted([int(r.run) for r in runs]))
        assert len(set(run_numbers)) == len(run_numbers)
  
        if trial is not None:
          self.all_blocks = trial.rungroups
  
        if len(runs) == 0:
          wx.MessageBox("No runs found", "Error", wx.OK | wx.ICON_EXCLAMATION)
          assert False # Close and destroy dialog properly here
  
        self.first_run = min(run_numbers)
        self.last_run = None
  
        class defaults(object):
          def __getattr__(self, item):
            if item == "detector_address":
              return 'CxiDs2.0:Cspad.0'
            elif item == "detz_parameter":
              return 580
            elif item == "format":
              return "cbf"
            elif item == "two_theta_low":
              return 12.5 # Defaults are from kapton tape experiments (this is kapton ring)
            elif item == "two_theta_high":
              return 22.8 # Defaults are from kapton tape experiments (this is water ring)
            elif item in ["extra_phil_str", "calib_dir", "dark_avg_path", "dark_stddev_path",
              "gain_map_path", "beamx", "beamy", "gain_mask_level", "untrusted_pixel_mask_path",
              "binning", "energy", "wavelength_offset", "spectrum_eV_per_pixel", "spectrum_eV_offset",
              "comment", "config_str", "extra_format_str"]:
              return None
            else:
              raise AttributeError(item)
        block = defaults()
  
      else:
        db = block.app
        self.first_run, self.last_run = block.get_first_and_last_runs()
        if self.first_run is None:
          self.first_run = self.first_avail
        else:
          if self.use_ids:
            if self.first_run is not None: self.first_run = self.first_run.id
            if self.last_run is not None: self.last_run = self.last_run.id
          else:
            if self.first_run is not None: self.first_run = int(self.first_run.run)
            if self.last_run is not None: self.last_run = int(self.last_run.run)
  
      self.orig_first_run = self.first_run
      self.orig_last_run = self.last_run
  
      BaseDialog.__init__(self, parent,
                          label_style=label_style,
                          content_style=content_style,
                          size=(600, 600),
                          *args, **kwargs)
  
      # Run block start / end points (choice widgets)
  
      if self.is_lcls:
        self.config_panel = wx.Panel(self)
        config_box = wx.StaticBox(self.config_panel, label='Configuration')
        self.config_sizer = wx.StaticBoxSizer(config_box)
        self.config_panel.SetSizer(self.config_sizer)
        self.config_panel.Hide()
  
      self.phil_panel = wx.Panel(self)
      phil_box = wx.StaticBox(self.phil_panel, label='Extra phil parameters')
      self.phil_sizer = wx.StaticBoxSizer(phil_box)
      self.phil_panel.SetSizer(self.phil_sizer)
  
      if self.is_lcls:
        self.format_panel = wx.Panel(self)
        format_box = wx.StaticBox(self.format_panel, label='Extra XTC format parameters')
        self.format_sizer = wx.StaticBoxSizer(format_box)
        self.format_panel.SetSizer(self.format_sizer)
  
      runblock_box = wx.StaticBox(self, label='Options')
      self.runblock_box_sizer = wx.StaticBoxSizer(runblock_box, wx.VERTICAL)
      self.runblock_panel = ScrolledPanel(self, size=(550, 225))
      self.runblock_sizer = wx.BoxSizer(wx.VERTICAL)
      self.runblock_panel.SetSizer(self.runblock_sizer)
  
      if self.is_lcls:
        # Configuration text ctrl (user can put in anything they want)
        self.config = gctr.PHILBox(self.config_panel,
                                   btn_import=True,
                                   btn_import_label='Import Config',
                                   btn_export=False,
                                   btn_default=True,
                                   btn_default_label='Default Config',
                                   ctr_size=(-1, 100),
                                   ctr_value=str(block.config_str))
        self.config_sizer.Add(self.config, 1, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Extra phil
      self.phil = gctr.PHILBox(self.phil_panel,
                               btn_import=True,
                               btn_import_label='Import PHIL',
                               btn_export=False,
                               btn_default=True,
                               btn_default_label='Default PHIL',
                               ctr_size=(-1, 100),
                               ctr_value=str(block.extra_phil_str))
      self.phil_sizer.Add(self.phil, 1, flag=wx.EXPAND | wx.ALL, border=10)
  
      if self.is_lcls:
        # Extra format options text ctrl (user can put in anything they want)
        self.format = gctr.PHILBox(self.format_panel,
                                   btn_import=True,
                                   btn_import_label='Import PHIL',
                                   btn_export=False,
                                   btn_default=False,
                                   ctr_size=(-1, 100),
                                   ctr_value=str(block.extra_format_str))
        self.format_sizer.Add(self.format, 1, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Image format choice
      if self.is_lcls and self.parent.trial.app.params.dispatcher in \
          ["cxi.xtc_process", "cctbx.xfel.xtc_process"]:
        if self.parent.trial.app.params.dispatcher == "cxi.xtc_process":
          image_choices = ['pickle']
        else:
          image_choices = ['cbf','pickle']
        self.img_format = gctr.ChoiceCtrl(self.runblock_panel,
                                          label='Image Format:',
                                          label_size=(100, -1),
                                          ctrl_size=(150, -1),
                                          choices=image_choices)
        if block.format:
          try:
            format_idx = image_choices.index(block.format)
          except ValueError:
            format_idx = 0 #in case of selecting an unavailable default
        else:
          format_idx = 0
        self.img_format.ctr.SetSelection(format_idx)
        self.runblock_sizer.Add(self.img_format, flag=wx.TOP | wx.LEFT, border=10)
  
      self.start_stop_sizer = wx.FlexGridSizer(1, 3, 60, 20)
  
      self.runblocks_start = gctr.SpinCtrl(self.runblock_panel,
                                     label='Start run:',
                                     label_style='bold',
                                     label_size=(100, -1),
                                     ctrl_value=(self.last_run or self.last_avail),
                                     ctrl_min=self.first_avail,
                                     ctrl_max=self.last_avail)
      self.runblocks_end = gctr.SpinCtrl(self.runblock_panel,
                                     label='End run:',
                                     label_style='bold',
                                     label_size=(100, -1),
                                     ctrl_value=(self.last_run or self.last_avail),
                                     ctrl_min=self.first_avail,
                                     ctrl_max=self.last_avail)
      self.end_type = gctr.RadioCtrl(self.runblock_panel,
                                     name='rg_end_type',
                                     label='',
                                     label_style='normal',
                                     label_size=(100, -1),
                                     direction='vertical',
                                     items={'auto':'Auto add runs',
                                            'specify':'Specify end run'})
      self.start_stop_sizer.AddMany([(self.runblocks_start),
                                     (self.runblocks_end),
                                     (self.end_type)])
      self.runblock_sizer.Add(self.start_stop_sizer, flag=wx.EXPAND | wx.ALL, border=10)
  
      if self.is_lcls:
        # Detector address
        self.address = gctr.TextButtonCtrl(self.runblock_panel,
                                           name='rg_address',
                                           label='Detector Address:',
                                           label_style='bold',
                                           label_size=(100, -1),
                                           value=block.detector_address)
        self.runblock_sizer.Add(self.address, flag=wx.EXPAND | wx.ALL, border=10)
  
  
        # Beam XYZ (X, Y - pickle only)
        self.beam_xyz = gctr.OptionCtrl(self.runblock_panel,
                                        name='rg_beam_xyz',
                                        label='Beam:',
                                        label_style='bold',
                                        label_size=(100, -1),
                                        ctrl_size=(60, -1),
                                        items=[('X', block.beamx),
                                               ('Y', block.beamy),
                                               ('DetZ', block.detz_parameter)])
        self.runblock_sizer.Add(self.beam_xyz, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Binning, energy, gain mask level
      if self.is_lcls:
        items = [('binning', block.binning),
                 ('energy', block.energy)]
        if self.parent.trial.app.params.dispatcher == "cctbx.xfel.xtc_process":
          items.append(('gain_mask_level', block.gain_mask_level))
  
        self.bin_nrg_gain = gctr.OptionCtrl(self.runblock_panel,
                                            name='rg_bin_nrg_gain',
                                            ctrl_size=(80, -1),
                                            items=items)
        self.runblock_sizer.Add(self.bin_nrg_gain, flag=wx.EXPAND | wx.ALL, border=10)
        self.wavelength_offset = gctr.OptionCtrl(self.runblock_panel,
                                                 name='rg_wavelength_offset',
                                                 ctrl_size=(80, -1),
                                                 items=[('wavelength_offset', block.wavelength_offset)])
        self.runblock_sizer.Add(self.wavelength_offset, flag=wx.EXPAND | wx.ALL, border=10)
        self.spectrum_calibration = gctr.OptionCtrl(self.runblock_panel,
                                                    name='rg_spectrum_calibration',
                                                    ctrl_size=(80, -1),
                                                    items=[('spectrum_eV_per_pixel', block.spectrum_eV_per_pixel),
                                                           ('spectrum_eV_offset', block.spectrum_eV_offset)])
        self.runblock_sizer.Add(self.spectrum_calibration, flag=wx.EXPAND | wx.ALL, border=10)
      else:
        self.energy = gctr.TextButtonCtrl(self.runblock_panel,
                                          name='rg_energy',
                                          label='Energy override',
                                          label_size=(150, -1))
        self.energy.ctr.SetValue(str(block.energy))
        self.runblock_sizer.Add(self.energy, flag=wx.EXPAND | wx.ALL, border=10)
  
      # Two theta values for droplet hit finding
      self.two_thetas = gctr.OptionCtrl(self.runblock_panel,
                                        name='rg_two_thetas',
                                        ctrl_size=(80, -1),
                                        items=[('two_theta_low', block.two_theta_low),
                                               ('two_theta_high', block.two_theta_high)])
      self.runblock_sizer.Add(self.two_thetas, flag=wx.EXPAND | wx.ALL, border=10)
  
  
      # Untrusted pixel mask path
      self.untrusted_path = gctr.TextButtonCtrl(self.runblock_panel,
                                                label='Untrusted Pixel Mask:',
                                                label_style='normal',
                                                label_size=(180, -1),
                                                big_button=True,
                                                value=str(block.untrusted_pixel_mask_path))
      self.runblock_sizer.Add(self.untrusted_path, flag=wx.EXPAND | wx.ALL,
                              border=10)
  
      if self.is_lcls and self.parent.trial.app.params.dispatcher in \
          ["cxi.xtc_process", "cctbx.xfel.xtc_process"]:
        # Calibration folder
        self.calib_dir = gctr.TextButtonCtrl(self.runblock_panel,
                                             label='Calibration:',
                                             label_style='normal',
                                             label_size=(100, -1),
                                             big_button=True,
                                             value=str(block.calib_dir))
        self.runblock_sizer.Add(self.calib_dir, flag=wx.EXPAND | wx.ALL,
                                border=10)
  
        # Dark average path (pickle only)
        self.dark_avg_path = gctr.TextButtonCtrl(self.runblock_panel,
                                                 label='Dark Average:',
                                                 label_style='normal',
                                                 label_size=(100, -1),
                                                 big_button=True,
                                                 value=str(block.dark_avg_path))
        self.runblock_sizer.Add(self.dark_avg_path, flag=wx.EXPAND | wx.ALL,
                                border=10)
  
        # Dark stddev path (pickle only)
        self.dark_stddev_path = gctr.TextButtonCtrl(self.runblock_panel,
                                                    label='Dark StdDev:',
                                                    label_style='normal',
                                                    label_size=(100, -1),
                                                    big_button=True,
                                                    value=str(block.dark_stddev_path))
        self.runblock_sizer.Add(self.dark_stddev_path, flag=wx.EXPAND | wx.ALL,
                                border=10)
  
        # Dark map path (pickle only)
        self.gain_map_path = gctr.TextButtonCtrl(self.runblock_panel,
                                                 label='Gain Map:',
                                                 label_style='normal',
                                                 label_size=(100, -1),
                                                 big_button=True,
                                                 value=str(block.gain_map_path))
        self.runblock_sizer.Add(self.gain_map_path, flag=wx.EXPAND | wx.ALL,
                                border=10)
  
      # Comment
      self.comment = gctr.TextButtonCtrl(self.runblock_panel,
                                         label='Comment:',
                                         label_style='normal',
                                         label_size=(100, -1))
      self.comment.ctr.SetValue(str(block.comment))
      self.runblock_sizer.Add(self.comment, flag=wx.EXPAND | wx.ALL,
                              border=10)
  
      self.main_sizer.Add(self.phil_panel, flag=wx.EXPAND | wx.ALL, border=10)
      if self.is_lcls:
        self.main_sizer.Add(self.config_panel, flag=wx.EXPAND | wx.ALL, border=10)
        self.main_sizer.Add(self.format_panel, flag=wx.EXPAND | wx.ALL, border=10)
      self.runblock_box_sizer.Add(self.runblock_panel)
      self.main_sizer.Add(self.runblock_box_sizer, flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.Bind(wx.EVT_RADIOBUTTON, self.onAutoEnd, self.end_type.auto)
      self.Bind(wx.EVT_RADIOBUTTON, self.onSpecifyEnd, self.end_type.specify)
      self.Bind(wx.EVT_BUTTON, self.onImportPhil, self.phil.btn_import)
      if self.is_lcls:
        self.Bind(wx.EVT_BUTTON, self.onImportFormat, self.format.btn_import)
      self.Bind(wx.EVT_BUTTON, self.onUntrustedBrowse,
                id=self.untrusted_path.btn_big.GetId())
      if self.is_lcls and self.parent.trial.app.params.dispatcher in \
          ["cxi.xtc_process", "cctbx.xfel.xtc_process"]:
        self.Bind(wx.EVT_BUTTON, self.onDarkAvgBrowse,
                  id=self.dark_avg_path.btn_big.GetId())
        self.Bind(wx.EVT_BUTTON, self.onImportConfig, self.config.btn_import)
        self.Bind(wx.EVT_BUTTON, self.onDefaultConfig, self.config.btn_default)
        self.Bind(wx.EVT_BUTTON, self.onDarkMapBrowse,
                  id=self.gain_map_path.btn_big.GetId())
        self.Bind(wx.EVT_BUTTON, self.onDarkStdBrowse,
                  id=self.dark_stddev_path.btn_big.GetId())
        self.Bind(wx.EVT_BUTTON, self.onCalibDirBrowse,
                  id=self.calib_dir.btn_big.GetId())
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
  
      self.fill_in_fields()
      self.configure_controls()
      self.Layout()
      self.runblock_panel.SetupScrolling()
      self.SetTitle('Run Block Settings')
  
    def onAutoEnd(self, e):
      self.last_run = None
      self.runblocks_end.Disable()
  
    def onSpecifyEnd(self, e):
      self.runblocks_end.Enable()
  
    def onImportConfig(self, e):
      cfg_dlg = wx.FileDialog(self,
                              message="Load configuration file",
                              defaultDir=os.curdir,
                              defaultFile="*",
                              wildcard="*",
                              style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                              )
      if cfg_dlg.ShowModal() == wx.ID_OK:
        config_file = cfg_dlg.GetPaths()[0]
        with open(config_file, 'r') as cfg:
          cfg_contents = cfg.read()
        self.config.ctr.SetValue(cfg_contents)
      cfg_dlg.Destroy()
  
    def onImportPhil(self, e):
      phil_dlg = wx.FileDialog(self,
                               message="Load phil file",
                               defaultDir=os.curdir,
                               defaultFile="*",
                               wildcard="*.phil",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                               )
      if phil_dlg.ShowModal() == wx.ID_OK:
        phil_file = phil_dlg.GetPaths()[0]
        with open(phil_file, 'r') as phil:
          phil_contents = phil.read()
        self.phil.ctr.SetValue(phil_contents)
      phil_dlg.Destroy()
  
    def onImportFormat(self, e):
      phil_dlg = wx.FileDialog(self,
                               message="Load phil file",
                               defaultDir=os.curdir,
                               defaultFile="*",
                               wildcard="*.phil",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                               )
      if phil_dlg.ShowModal() == wx.ID_OK:
        phil_file = phil_dlg.GetPaths()[0]
        with open(phil_file, 'r') as phil:
          phil_contents = phil.read()
        self.format.ctr.SetValue(phil_contents)
      phil_dlg.Destroy()
  
    def onDefaultConfig(self, e):
      # TODO: Generate default config parameters (re-do based on pickle / CBF)
      pass
  
    def onOK(self, e):
      try:
        first = int(self.runblocks_start.ctr.GetValue())
        assert first > 0 and first >= self.first_avail
        self.first_run = first
      except (ValueError, AssertionError) as e:
        print("Please select a run between %d and %d." % (self.first_avail, self.last_avail))
        raise e
      if self.end_type.specify.GetValue() == 1:
        try:
          last = int(self.runblocks_end.ctr.GetValue())
          assert last > 0 and last <= self.last_avail and last >= first
          self.last_run = last
        except (ValueError, AssertionError) as e:
          print("Please select a run between %d and %d." % (self.first_run, self.last_avail))
          raise e
      elif self.end_type.specify.GetValue() == 0:
        self.last_run = None
      else:
        assert False
      rg_open = self.last_run is None
  
      rg_dict = dict(active=True,
                     open=rg_open,
                     extra_phil_str=self.phil.ctr.GetValue(),
                     untrusted_pixel_mask_path=self.untrusted_path.ctr.GetValue().strip(),
                     two_theta_low=self.two_thetas.two_theta_low.GetValue(),
                     two_theta_high=self.two_thetas.two_theta_high.GetValue(),
                     comment=self.comment.ctr.GetValue())
  
      if self.is_lcls:
        rg_dict['detz_parameter']=self.beam_xyz.DetZ.GetValue()
        rg_dict['beamx']=self.beam_xyz.X.GetValue()
        rg_dict['beamy']=self.beam_xyz.Y.GetValue()
        rg_dict['energy']=self.bin_nrg_gain.energy.GetValue()
        rg_dict['wavelength_offset']=self.wavelength_offset.wavelength_offset.GetValue()
        if self.parent.trial.app.params.dispatcher in \
            ["cxi.xtc_process", "cctbx.xfel.xtc_process"]:
          rg_dict['format']=self.img_format.ctr.GetStringSelection()
          rg_dict['dark_avg_path']=self.dark_avg_path.ctr.GetValue()
          rg_dict['dark_stddev_path']=self.dark_stddev_path.ctr.GetValue()
          rg_dict['gain_map_path']=self.gain_map_path.ctr.GetValue()
          rg_dict['gain_mask_level']=self.bin_nrg_gain.gain_mask_level.GetValue()
          rg_dict['calib_dir']=self.calib_dir.ctr.GetValue()
        rg_dict['binning']=self.bin_nrg_gain.binning.GetValue()
        rg_dict['detector_address']=self.address.ctr.GetValue()
        rg_dict['config_str']=self.config.ctr.GetValue()
        rg_dict['extra_format_str']=self.format.ctr.GetValue()
        rg_dict['spectrum_eV_per_pixel']=self.spectrum_calibration.spectrum_eV_per_pixel.GetValue()
        rg_dict['spectrum_eV_offset']=self.spectrum_calibration.spectrum_eV_offset.GetValue()
      else:
        rg_dict['energy']=self.energy.ctr.GetValue()
  
      for key, value in six.iteritems(rg_dict):
        if str(value) == 'None' or str(value) == '':
          rg_dict[key] = None
        elif type(value) == bool:
          rg_dict[key] = int(value)
  
      if self.block is None:
        self.block = self.db.create_rungroup(**rg_dict)
        self.block.sync_runs(self.first_run, self.last_run, self.use_ids)
        self.parent.trial.add_rungroup(self.block)
      else:
        # if all the parameters are unchanged, do nothing
        all_the_same = [str(rg_dict[key]).strip() == str(getattr(self.block, key)).strip() for key in rg_dict].count(False) == 0
        all_the_same &= self.first_run == self.orig_first_run and self.last_run == self.orig_last_run
        if not all_the_same:
          # if all the parameters except open and comment are the same,
          # only update those fields
          keep_old_run_group = [str(rg_dict[key]).strip() == str(getattr(self.block, key)).strip() for key in rg_dict \
                                if key not in ['open', 'comment']].count(False) == 0
          if keep_old_run_group:
            main = self.parent.parent.GetParent().main
            running = main.job_sentinel is not None and main.job_sentinel.active
            if running:
              main.stop_job_sentinel()
  
            self.block.open = rg_open
            self.block.comment = rg_dict['comment']
            self.block.sync_runs(self.first_run, self.last_run, self.use_ids)
  
            if running:
              main.start_job_sentinel()
  
          else:
            # enough parameters have changed to warrant creating a new run group
            self.block.active = False
            self.block = self.db.create_rungroup(**rg_dict)
            self.block.sync_runs(self.first_run, self.last_run, self.use_ids)
            self.parent.trial.add_rungroup(self.block)
  
      e.Skip()
  
    def fill_in_fields(self):
      ''' If previous rungroups exist in trial, fill in fields in nascent block '''
      if len(self.all_blocks) > 0:
        last = self.all_blocks[-1]
        self.phil.ctr.SetValue(str(last.extra_phil_str))
        if self.is_lcls:
          self.address.ctr.SetValue(str(last.detector_address))
          self.config.ctr.SetValue(str(last.config_str))
          self.format.ctr.SetValue(str(last.extra_format_str))
          self.beam_xyz.DetZ.SetValue(str(last.detz_parameter))
          self.beam_xyz.X.SetValue(str(last.beamx))
          self.beam_xyz.Y.SetValue(str(last.beamy))
          self.bin_nrg_gain.binning.SetValue(str(last.binning))
          self.bin_nrg_gain.energy.SetValue(str(last.energy))
          self.wavelength_offset.wavelength_offset.SetValue(str(last.wavelength_offset))
          self.spectrum_calibration.spectrum_eV_per_pixel.SetValue(str(last.spectrum_eV_per_pixel))
          self.spectrum_calibration.spectrum_eV_offset.SetValue(str(last.spectrum_eV_offset))
          if self.parent.trial.app.params.dispatcher in \
              ["cxi.xtc_process", "cctbx.xfel.xtc_process"]:
            self.dark_avg_path.ctr.SetValue(str(last.dark_avg_path))
            self.dark_stddev_path.ctr.SetValue(str(last.dark_stddev_path))
            self.gain_map_path.ctr.SetValue(str(last.gain_map_path))
            self.bin_nrg_gain.gain_mask_level.SetValue(str(last.gain_mask_level))
            self.calib_dir.ctr.SetValue(str(last.calib_dir))
        self.two_thetas.two_theta_low.SetValue(str(last.two_theta_low))
        self.two_thetas.two_theta_high.SetValue(str(last.two_theta_high))
        self.untrusted_path.ctr.SetValue(str(last.untrusted_pixel_mask_path))
        self.comment.ctr.SetValue(str(last.comment))
  
    def configure_controls(self):
      if self.last_run is None:
        self.runblocks_end.Disable()
        self.end_type.auto.SetValue(1)
      else:
        self.runblocks_end.Enable()
        self.end_type.specify.SetValue(1)
  
    def onDarkAvgBrowse(self, e):
      dark_dlg = wx.FileDialog(self,
                               message="Load dark average file",
                               defaultDir=os.curdir,
                               defaultFile="*.cbf",
                               wildcard="*.cbf",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                               )
  
      if dark_dlg.ShowModal() == wx.ID_OK:
        self.dark_avg_path.ctr.SetValue(dark_dlg.GetPaths()[0])
      dark_dlg.Destroy()
  
    def onDarkMapBrowse(self, e):
      dark_dlg = wx.FileDialog(self,
                               message="Load dark map file",
                               defaultDir=os.curdir,
                               defaultFile="*.cbf",
                               wildcard="*.cbf",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                               )
  
      if dark_dlg.ShowModal() == wx.ID_OK:
        self.gain_map_path.ctr.SetValue(dark_dlg.GetPaths()[0])
      dark_dlg.Destroy()
  
    def onDarkStdBrowse(self, e):
      dark_dlg = wx.FileDialog(self,
                               message="Load dark stddev file",
                               defaultDir=os.curdir,
                               defaultFile="*.cbf",
                               wildcard="*.cbf",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                               )
  
      if dark_dlg.ShowModal() == wx.ID_OK:
        self.dark_stddev_path.ctr.SetValue(dark_dlg.GetPaths()[0])
      dark_dlg.Destroy()
  
    def onUntrustedBrowse(self, e):
      dlg = wx.FileDialog(self,
                               message="Load untrusted pixel mask",
                               defaultDir=os.curdir,
                               defaultFile="*.mask",
                               wildcard="*.mask",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                               )
  
      if dlg.ShowModal() == wx.ID_OK:
        self.untrusted_path.ctr.SetValue(dlg.GetPaths()[0])
      dlg.Destroy()
  
    def onCalibDirBrowse(self, e):
      dlg = wx.DirDialog(self, "Choose calibration directory:",
                         style=wx.DD_DEFAULT_STYLE)
  
      if dlg.ShowModal() == wx.ID_OK:
        self.calib_dir.ctr.SetValue(dlg.GetPath())
      dlg.Destroy()
      e.Skip()
  
  class SelectRunBlocksDialog(BaseDialog):
    def __init__(self, parent, trial,
                 label_style='bold',
                 content_style='normal',
                 db=None,
                 *args, **kwargs):
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
  
      self.db = db
      self.trial = trial
  
      self.top_sizer = wx.BoxSizer(wx.HORIZONTAL)
  
      self.button_panel = wx.Panel(self)
      self.button_sizer = wx.BoxSizer(wx.VERTICAL)
      self.button_panel.SetSizer(self.button_sizer)
  
      self.runblocks_panel = ScrolledPanel(self, size=(500, 400))
  
      # Populate rungroups with current values from db
      self.trial_rungroups = [t.id for t in trial.rungroups]
      self.all_rungroups = self.db.get_all_rungroups()
      choices = []
      selected = []
      use_ids = db.params.facility.name not in ['lcls']
      for rungroup in self.all_rungroups:
        selected.append(rungroup.id in self.trial_rungroups)
        first_run, last_run = rungroup.get_first_and_last_runs()
        if last_run is None:
          if first_run is None:
            desc = "[%d]"%(rungroup.id)
          else:
            desc = "[%d] %d+"%(rungroup.id, int(first_run.id) if use_ids else int(first_run.run))
        else:
          desc = "[%d] %d-%d"%(rungroup.id, int(first_run.id) if use_ids else int(first_run.run), \
                                            int(last_run.id) if use_ids else int(last_run.run))
        if rungroup.comment is not None:
          desc += " " + rungroup.comment
  
        choices.append(desc)
  
      self.runblocks_list = gctr.CheckListCtrl(self.runblocks_panel,
                                               label='Select runblocks',
                                               label_size=(40, -1),
                                               label_style='normal',
                                               ctrl_size=(450, 350),
                                               direction='vertical',
                                               choices=choices)
      for i in range(len(selected)):
        self.runblocks_list.ctr.Check(i, selected[i])
  
      self.runblocks_sizer = wx.BoxSizer(wx.VERTICAL)
      self.runblocks_panel.SetSizer(self.runblocks_sizer)
  
      self.runblocks_sizer.Add(self.runblocks_list, 1, flag=wx.EXPAND)
  
      # Add panels to main sizer
      self.top_sizer.Add(self.button_panel,
                         flag=wx.LEFT, border=10)
      self.top_sizer.Add(self.runblocks_panel,
                         flag=wx.EXPAND | wx.RIGHT | wx.LEFT, border=10)
      self.main_sizer.Add(self.top_sizer,
                          flag=wx.EXPAND| wx.TOP | wx.BOTTOM, border=10)
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                     flag=wx.EXPAND | wx.ALL,
                     border=10)
  
      self.Layout()
      self.SetTitle('Select run blocks')
  
      # Button bindings
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
    def onOK(self, e):
      trial_rungroups = [t.id for t in self.db.get_trial_rungroups(self.trial.id, only_active=False)]
      trials = self.db.get_all_trials()
  
      for i, rungroup in enumerate(self.all_rungroups):
        if self.runblocks_list.ctr.IsChecked(i):
          if not rungroup.id in trial_rungroups:
            self.trial.add_rungroup(rungroup)
          rungroup.active = True
        else:
          if rungroup.id in trial_rungroups:
            self.trial.remove_rungroup(rungroup)
          if not any([rungroup.id in [rg.id for rg in t.rungroups] for t in trials]):
            rungroup.active = False
  
      e.Skip()
  
  class TrialDialog(BaseDialog):
    def __init__(self, parent, db,
                 new=True,
                 trial=None,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.db = db
      self.new = new
      self.trial = trial
      self.all_trials = db.get_all_trials()
  
      BaseDialog.__init__(self, parent,
                          label_style=label_style,
                          content_style=content_style,
                          size=(600, 600),
                          *args, **kwargs)
  
      if trial is None:
        trials = [t.trial for t in self.all_trials]
        if len(trials) == 0:
          trial_number = 0
        else:
          trial_number = max(trials) + 1
        d_min = 1.5
      else:
        trial_number = trial.trial
        d_min = trial.d_min if trial.d_min is not None else 1.5
  
      self.trial_info = gctr.TwoButtonCtrl(self,
                                           label='Trial number:',
                                           label_size=(100, -1),
                                           label_style='bold',
                                           button1=True,
                                           button1_label='Import PHIL',
                                           button1_size=(120, -1),
                                           button2=True,
                                           button2_label='Default PHIL',
                                           button2_size=(120, -1),
                                           value="{}".format(trial_number))
      self.trial_comment = gctr.TextButtonCtrl(self,
                                               label='Comment:',
                                               label_size=(100, -1),
                                               label_style='bold',
                                               ghost_button=False)
  
      self.phil_box = gctr.RichTextCtrl(self, style=wx.VSCROLL, size=(-1, 400))
  
      choices = [('None', None)] + \
                [('Trial {}'.format(t.trial), t.trial) for t in self.all_trials]
      self.copy_runblocks = gctr.ChoiceCtrl(self,
                                            label='Copy runblocks from',
                                            label_style='normal',
                                            label_size=(180, -1),
                                            ctrl_size=(80, -1),
                                            choices=choices)
      self.copy_runblocks.ctr.SetSelection(0)
      self.throttle = gctr.SpinCtrl(self,
                                    name='trial_throttle',
                                    label='Percent events processed:',
                                    label_size=(180, -1),
                                    label_style='bold',
                                    ctrl_size=(80, -1),
                                    ctrl_value='100',
                                    ctrl_min=1,
                                    ctrl_max=100)
      self.num_bins = gctr.SpinCtrl(self,
                                    name='trial_num_bins',
                                    label='Number of bins:',
                                    label_size=(180, -1),
                                    label_style='bold',
                                    ctrl_size=(80, -1),
                                    ctrl_value='20',
                                    ctrl_min=1,
                                    ctrl_max=100,
                                    ctrl_step=1)
      self.d_min = gctr.SpinCtrl(self,
                                 name='trial_d_min',
                                 label='High res. limit ({}):'
                                 ''.format(u'\N{ANGSTROM SIGN}'.encode('utf-8')),
                                 label_size=(180, -1),
                                 label_style='bold',
                                 ctrl_size=(80, -1),
                                 ctrl_value=str(d_min),
                                 ctrl_min=0.1,
                                 ctrl_max=100,
                                 ctrl_step=0.1,
                                 ctrl_digits=1)
  
      self.option_sizer = wx.FlexGridSizer(3, 2, 10, 20)
      self.option_sizer.AddMany([(self.copy_runblocks),
                                 (0, 0),
                                 (self.throttle),
                                 (0, 0),
                                 (self.num_bins),
                                 (self.d_min)])
  
      self.main_sizer.Add(self.trial_info,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
      self.main_sizer.Add(self.trial_comment,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
      self.main_sizer.Add(self.phil_box, 1,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
      self.main_sizer.Add(self.option_sizer, flag=wx.EXPAND | wx.ALL, border=10)
  
  
      # Dialog control
      if self.new:
        dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      else:
        dialog_box = self.CreateSeparatedButtonSizer(wx.OK)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.Layout()
  
      if self.new:
        self.SetTitle('New Trial Settings')
  
        # If previous trials exist, propagate previous settings from them
        if len(self.all_trials) > 0:
          self.last_trial = self.all_trials[-1]
          target_phil_str = self.last_trial.target_phil_str
          self.trial_comment.ctr.SetValue(str(self.last_trial.comment))
          process_percent = self.last_trial.process_percent
  
        else:
          target_phil_str = ''
          process_percent = 100
  
      else:
        self.SetTitle('Trial {}'.format(self.trial.trial))
        target_phil_str = self.trial.target_phil_str
        self.trial_comment.ctr.SetValue(str(self.trial.comment))
        process_percent = self.trial.process_percent
  
        # Disable controls for viewing
        self.trial_info.button1.Disable()
        self.trial_info.button2.Disable()
        self.trial_info.ctr.SetEditable(False)
        self.copy_runblocks.Hide()
        self.phil_box.SetEditable(False)
        self.throttle.ctr.Disable()
        self.num_bins.ctr.Disable()
        self.d_min.ctr.Disable()
  
      if target_phil_str is None:
        target_phil_str = ""
      if process_percent is None:
        process_percent = 100
      self.phil_box.SetValue(target_phil_str)
      self.throttle.ctr.SetValue(process_percent)
  
      # Bindings
      self.Bind(wx.EVT_BUTTON, self.onBrowse, self.trial_info.button1)
      self.Bind(wx.EVT_BUTTON, self.onDefault, self.trial_info.button2)
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
    def onBrowse(self, e):
      ''' Open dialog for selecting PHIL file '''
  
      load_dlg = wx.FileDialog(self,
                               message="Load PHIL file",
                               defaultDir=os.curdir,
                               defaultFile="*.phil",
                               wildcard="*.phil",
                               style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,
                               )
      if load_dlg.ShowModal() == wx.ID_OK:
        target_file = load_dlg.GetPaths()[0]
        with open(target_file, 'r') as phil_file:
          phil_file_contents = phil_file.read()
        self.phil_box.SetValue(phil_file_contents)
      load_dlg.Destroy()
  
    def onDefault(self, e):
      # TODO: Generate default PHIL parameters
      pass
  
    def onOK(self, e):
      if self.new:
        target_phil_str = self.phil_box.GetValue()
  
        # Parameter validation
        dispatcher = self.db.params.dispatcher
        if dispatcher == 'cxi.xtc_process': #LABELIT
          from spotfinder.applications.xfel import cxi_phil
          phil_scope = cxi_phil.cxi_versioned_extract().persist.phil_scope
        else:
          from xfel.ui import load_phil_scope_from_dispatcher
          phil_scope = load_phil_scope_from_dispatcher(dispatcher)
  
        from iotbx.phil import parse
        msg = None
        try:
          trial_params, unused = phil_scope.fetch(parse(target_phil_str), track_unused_definitions = True)
        except Exception as e:
          msg = '\nParameters incompatible with %s dispatcher:\n%s\n' % (dispatcher, str(e))
        else:
          if len(unused) > 0:
            msg = [str(item) for item in unused]
            msg = '\n'.join(['  %s' % line for line in msg])
            msg = 'The following definitions were not recognized:\n%s\n' % msg
  
          try:
            params = trial_params.extract()
          except Exception as e:
            if msg is None: msg = ""
            msg += '\nOne or more values could not be parsed:\n%s\n' % str(e)
  
        if msg is not None:
          msg += '\nFix the parameters and press OK again'
          msgdlg = wx.MessageDialog(self,
                                    message=msg,
                                    caption='Warning',
                                    style=wx.OK |  wx.ICON_EXCLAMATION)
          msgdlg.ShowModal()
          return
  
        comment = self.trial_comment.ctr.GetValue()
        process_percent = int(self.throttle.ctr.GetValue())
        d_min = float(self.d_min.ctr.GetValue())
        n_bins = int(self.num_bins.ctr.GetValue())
        if process_percent == 100:
          process_percent = None
  
        if self.trial is None:
          self.db.create_trial(
            trial = int(self.trial_info.ctr.GetValue()),
            active = False,
            target_phil_str = target_phil_str,
            comment = comment,
            process_percent = process_percent,
            d_min = d_min,
            n_bins = n_bins)
          self.trial = self.db.get_trial(trial_number=int(self.trial_info.ctr.GetValue()))
  
          template_trial_number = self.copy_runblocks.ctr.GetClientData(
            self.copy_runblocks.ctr.GetSelection())
  
          if template_trial_number is not None:
            template_trial = self.db.get_trial(trial_number=template_trial_number)
            for block in template_trial.rungroups:
              self.trial.add_rungroup(block)
  
        else:
          self.trial.target_phil_str = target_phil_str
          self.trial.comment = comment
      else:
        if self.trial.comment != self.trial_comment.ctr.GetValue():
          self.trial.comment = self.trial_comment.ctr.GetValue()
      e.Skip()
  
  class DatasetDialog(BaseDialog):
    def __init__(self, parent, db,
                 new=True,
                 dataset=None,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.db = db
      self.new = new
      self.dataset = dataset
  
      BaseDialog.__init__(self, parent,
                          label_style=label_style,
                          content_style=content_style,
                          size=(600, 600),
                          *args, **kwargs)
  
      self.name = gctr.TextButtonCtrl(self,
                                      label='Name:',
                                      label_size=(100, -1),
                                      label_style='bold',
                                      ghost_button=False)
  
      self.comment = gctr.TextButtonCtrl(self,
                                         label='Comment:',
                                         label_size=(100, -1),
                                         label_style='bold',
                                         ghost_button=False)
  
      self.tag_checklist = gctr.CheckListCtrl(self,
                                          label='Tags:',
                                          label_size=(200, -1),
                                          label_style='normal',
                                          ctrl_size=(150, 100),
                                          direction='vertical',
                                          choices=[])
  
      self.selection_type_radio = gctr.RadioCtrl(self,
                                             label='',
                                             label_style='normal',
                                             label_size=(-1, -1),
                                             direction='horizontal',
                                             items={'inter':'intersection',
                                                    'union':'union'})
  
      self.main_sizer.Add(self.name,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
      self.main_sizer.Add(self.comment,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
      self.main_sizer.Add(self.tag_checklist,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
      self.main_sizer.Add(self.selection_type_radio,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.Layout()
  
      if self.new:
        self.SetTitle('New Dataset Settings')
        self.selection_type_radio.inter.SetValue(1)
      else:
        self.SetTitle('Dataset Settings')
        self.name.ctr.SetValue(str(self.dataset.name))
        self.comment.ctr.SetValue(str(self.dataset.comment))
        if self.dataset.tag_operator == "union":
          self.selection_type_radio.union.SetValue(1)
        elif self.dataset.tag_operator == "intersection":
          self.selection_type_radio.inter.SetValue(1)
        else:
          assert False
  
      # Bindings
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
      # Initialize tag list
      self.all_tags = db.get_all_tags()
      tag_names = [t.name for t in self.all_tags]
      self.dataset_tagnames = [t.name for t in self.dataset.tags] if self.dataset is not None else []
      if tag_names:
        self.tag_checklist.ctr.InsertItems(items=tag_names, pos=0)
        checked = [tag_idx for tag_idx, tag_name in enumerate(tag_names) if tag_name in self.dataset_tagnames]
        self.tag_checklist.ctr.SetCheckedItems(checked)
  
    def onOK(self, e):
      name = self.name.ctr.GetValue()
      comment = self.comment.ctr.GetValue()
      if self.selection_type_radio.union.GetValue() == 1:
        mode = 'union'
      else:
        mode = 'intersection'
  
      if self.new:
        self.dataset = self.db.create_dataset(name = name,
                                              comment = comment,
                                              active = False,
                                              tag_operator = mode)
      else:
        self.dataset.name = name
        self.dataset.comment = comment
  
      checked = self.tag_checklist.ctr.GetCheckedItems()
      for tag_idx, tag in enumerate(self.all_tags):
        if tag_idx in checked:
          if tag.name not in self.dataset_tagnames:
            self.dataset.add_tag(tag)
        else:
          if tag.name in self.dataset_tagnames:
            self.dataset.remove_tag(tag)
  
      e.Skip()
  
  class TaskDialog(BaseDialog):
    def __init__(self, parent, db,
                 dataset=None,
                 task=None,
                 label_style='bold',
                 content_style='normal',
                 *args, **kwargs):
  
      self.db = db
      self.dataset = dataset
      self.task = task
      self.all_trials = db.get_all_trials()
      self.all_trial_numbers = [str(t.trial) for t in self.all_trials]
  
      BaseDialog.__init__(self, parent,
                          label_style=label_style,
                          content_style=content_style,
                          size=(600, 600),
                          *args, **kwargs)
  
      self.type = gctr.ChoiceCtrl(self,
                                  label='Task type:',
                                  label_size=(100, -1),
                                  ctrl_size=(150, -1),
                                  choices=task_types)
  
      self.trial = gctr.ChoiceCtrl(self,
                                   label='Trial:',
                                   label_size=(100, -1),
                                   ctrl_size=(150, -1),
                                   choices=self.all_trial_numbers)
  
      self.phil_box = gctr.RichTextCtrl(self, style=wx.VSCROLL, size=(550, 400))
  
      self.main_sizer.Add(self.type,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
      self.main_sizer.Add(self.trial,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
      self.main_sizer.Add(self.phil_box, 1,
                          flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT,
                          border=10)
  
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                          flag=wx.EXPAND | wx.ALL,
                          border=10)
  
      self.Layout()
  
      if task is None:
        self.SetTitle('New Task Settings')
  
        # If previous tasks exist, propagate previous settings from them
        # XXX
        self.type.ctr.SetSelection(0)
        self.trial.ctr.SetSelection(0)
      else:
        self.SetTitle('Task Settings')
        self.type.ctr.SetSelection(task_types.index(self.task.type))
        self.trial.ctr.SetSelection(self.all_trial_numbers.index(str(self.task.trial.trial)))
        if self.task.parameters is not None:
          self.phil_box.SetValue(self.task.parameters)
  
      # Bindings
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
    def onOK(self, e):
      task_type = self.type.ctr.GetStringSelection()
      # Remember, a trial number doesn't necessarily match its id and they are not guaranteed to be consecutive
      trial = self.all_trials[self.all_trial_numbers.index(self.trial.ctr.GetStringSelection())]
      parameters = self.phil_box.GetValue()
  
      # Parameter validation
      from xfel.ui.db.task import Task
      from iotbx.phil import parse
      dispatcher, phil_scope = Task.get_phil_scope(self.db, task_type)
  
      if phil_scope is not None:
        msg = None
        try:
          task_params, unused = phil_scope.fetch(parse(parameters), track_unused_definitions = True)
        except Exception as e:
          msg = '\nParameters incompatible with %s dispatcher:\n%s\n' % (dispatcher, str(e))
        else:
          if len(unused) > 0:
            msg = [str(item) for item in unused]
            msg = '\n'.join(['  %s' % line for line in msg])
            msg = 'The following definitions were not recognized:\n%s\n' % msg
  
          try:
            params = task_params.extract()
          except Exception as e:
            if msg is None: msg = ""
            msg += '\nOne or more values could not be parsed:\n%s\n' % str(e)
  
        if msg is not None:
          msg += '\nFix the parameters and press OK again'
          msgdlg = wx.MessageDialog(self,
                                    message=msg,
                                    caption='Warning',
                                    style=wx.OK |  wx.ICON_EXCLAMATION)
          msgdlg.ShowModal()
          return
  
      if self.task is None:
        task = self.db.create_task(type = task_type,
                                   trial_id = trial.id,
                                   parameters = parameters)
        self.dataset.add_task(task)
      else:
        self.task.type = task_type
        self.task.trial_id = trial.id
        self.task.parameters = parameters
  
      e.Skip()
  
  class SelectTasksDialog(BaseDialog):
    def __init__(self, parent, dataset,
                 label_style='bold',
                 content_style='normal',
                 db=None,
                 *args, **kwargs):
      BaseDialog.__init__(self, parent, label_style=label_style,
                          content_style=content_style, *args, **kwargs)
  
      self.db = db
      self.dataset = dataset
  
      self.top_sizer = wx.BoxSizer(wx.HORIZONTAL)
  
      self.button_panel = wx.Panel(self)
      self.button_sizer = wx.BoxSizer(wx.VERTICAL)
      self.button_panel.SetSizer(self.button_sizer)
  
      self.tasks_panel = ScrolledPanel(self, size=(500, 400))
  
      # Populate tasks with current values from db
      self.dataset_tasks = [t for t in dataset.tasks]
      self.dataset_task_ids = [t.id for t in self.dataset_tasks]
      self.all_tasks = self.db.get_all_tasks()
      choices = []
      selected = []
      for task in self.all_tasks:
        selected.append(task.id in self.dataset_task_ids)
        desc = "[%d] %s"%(task.id, task.type)
        if task.trial is not None:
          desc += " (trial %d)"%task.trial.trial
  
        choices.append(desc)
  
      self.tasks_list = gctr.CheckListCtrl(self.tasks_panel,
                                           label='Select tasks',
                                           label_size=(40, -1),
                                           label_style='normal',
                                           ctrl_size=(450, 350),
                                           direction='vertical',
                                           choices=choices)
      for i in range(len(selected)):
        self.tasks_list.ctr.Check(i, selected[i])
  
      self.tasks_sizer = wx.BoxSizer(wx.VERTICAL)
      self.tasks_panel.SetSizer(self.tasks_sizer)
  
      self.tasks_sizer.Add(self.tasks_list, 1, flag=wx.EXPAND)
  
      # Add panels to main sizer
      self.top_sizer.Add(self.button_panel,
                         flag=wx.LEFT, border=10)
      self.top_sizer.Add(self.tasks_panel,
                         flag=wx.EXPAND | wx.RIGHT | wx.LEFT, border=10)
      self.main_sizer.Add(self.top_sizer,
                          flag=wx.EXPAND| wx.TOP | wx.BOTTOM, border=10)
      # Dialog control
      dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL)
      self.main_sizer.Add(dialog_box,
                     flag=wx.EXPAND | wx.ALL,
                     border=10)
  
      self.Layout()
      self.SetTitle('Select tasks')
  
      # Button bindings
      self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK)
  
    def onOK(self, e):
      for task in self.dataset_tasks:
        self.dataset.remove_task(task)
  
      for i, task in enumerate(self.all_tasks):
        if self.tasks_list.ctr.IsChecked(i):
          self.dataset.add_task(task)
  
      e.Skip()
  