Exemplo n.º 1
0
class Presenter(object):
  def __init__(self, view, model):
    self.view = view
    self.model = model

    self.client_runner = ProcessController(
      self.model.progress_regex,
      self.model.progress_expr
    )

    pub.subscribe(self.on_cancel, events.WINDOW_CANCEL)
    pub.subscribe(self.on_stop, events.WINDOW_STOP)
    pub.subscribe(self.on_start, events.WINDOW_START)
    pub.subscribe(self.on_restart,  events.WINDOW_RESTART)
    pub.subscribe(self.on_edit, events.WINDOW_EDIT)
    pub.subscribe(self.on_close, events.WINDOW_CLOSE)

    # console statuses from the other thread
    pub.subscribe(self.on_new_message, 'console_update')
    pub.subscribe(self.on_progress_change, 'progress_update')
    pub.subscribe(self.on_client_done, 'execution_complete')


    pub.subscribe(self.on_selection_change, events.LIST_BOX)


  def on_selection_change(self, selection):
    self.update_model()
    self.model.active_group = selection
    self.redraw_from_model()
    self.syncronize_from_model()

  def initialize_view(self):
    self.view.window_title = self.model.program_name
    self.view.window_size = self.model.default_size

    self.view.required_section.clear()
    self.view.optional_section.clear()
    self.view.required_section.populate(self.model.required_args, self.model.num_required_cols)
    self.view.optional_section.populate(self.model.optional_args, self.model.num_optional_cols)

    if self.model.use_monospace_font:
      self.view.set_display_font_style('monospace')

    if self.should_disable_stop_button():
      self.view.disable_stop_button()
    else:
      self.view.enable_stop_button()

    if self.model.layout_type == layouts.COLUMN:
      self.view.set_list_contents(self.model.argument_groups.keys())

    if self.model.auto_start:
      self.model.update_state(States.RUNNNING)
      self.on_start()
    self.syncronize_from_model()

  def update_model(self):
    self.update_list(self.model.required_args, self.view.required_section.get_values())
    self.update_list(self.model.optional_args, self.view.optional_section.get_values())
    self.syncronize_from_model()

  def syncronize_from_model(self):
    #TODO move this out of the presenter
    #TODO Make all view interactions thread safe
    wx.CallAfter(self.syncronize_from_model_async)

  def syncronize_from_model_async(self):
    # update heading titles
    self.view.heading_title = self.model.heading_title
    self.view.heading_subtitle = self.model.heading_subtitle

    # refresh the widgets
    for index, widget in enumerate(self.view.required_section):
      widget.set_value(self.model.required_args[index]._value)
    for index, widget in enumerate(self.view.optional_section):
      widget.set_value(self.model.optional_args[index]._value)

    # swap the views
    getattr(self, self.model.current_state)()

  def redraw_from_model(self):
    self.view.freeze()
    self.view.required_section.clear()
    self.view.optional_section.clear()
    self.view.required_section.populate(self.model.required_args, self.model.num_required_cols)
    self.view.optional_section.populate(self.model.optional_args, self.model.num_optional_cols)
    getattr(self, self.model.current_state)()
    self.view.thaw()

  def should_disable_stop_button(self):
    return self.model.stop_button_disabled

  def on_start(self):
    self.update_model()
    if not self.model.is_valid():
      return self.view.show_missing_args_dialog()
    command = self.model.build_command_line_string()
    self.client_runner.run(command)
    self.model.update_state(States.RUNNNING)
    self.syncronize_from_model()

  def on_stop(self):
    self.ask_stop()

  def on_edit(self):
    self.model.update_state(States.CONFIGURING)
    self.syncronize_from_model()

  def on_restart(self):
    self.on_start()

  def on_cancel(self):
    if self.view.confirm_exit_dialog():
      self.view.Destroy()
      sys.exit()

  def on_close(self):
    self.view.Destroy()
    sys.exit()

  def on_new_message(self, msg):
    # observes changes coming from the subprocess
    self.view.update_console_async(msg)

  def on_progress_change(self, progress):
    # observes changes coming from the subprocess
    self.view.update_progress_aync(progress, self.model.disable_progress_bar_animation)

  def on_client_done(self):
    if self.client_runner.was_success():
      self.model.update_state(States.SUCCESS)
    else:
      self.model.update_state(States.ERROR)
    self.syncronize_from_model()

  def ask_stop(self):
    if self.view.confirm_stop_dialog():
      self.stop()
      return True
    return False

  def stop(self):
    self.client_runner.stop()

  def configuring(self):
    self.view.hide_all_buttons()
    self.view.hide('check_mark', 'running_img', 'error_symbol', 'runtime_display')
    self.view.show('settings_img', 'cancel_button', 'start_button', 'config_panel')
    self.view.Layout()

  def running(self):
    self.view.hide_all_buttons()
    self.view.hide('check_mark', 'settings_img', 'error_symbol', 'config_panel')
    self.view.show('running_img', 'stop_button', 'progress_bar', 'runtime_display')
    self.view.progress_bar.Pulse()
    self.view.Layout()

  def success(self):
    self.view.hide_all_buttons()
    self.view.hide('running_img', 'progress_bar', 'config_panel')
    self.view.show('check_mark', 'edit_button', 'restart_button', 'close_button', 'runtime_display')
    self.view.Layout()

  def error(self):
    self.view.hide_all_buttons()
    self.view.hide('running_img', 'progress_bar', 'config_panel')
    self.view.show('error_symbol', 'edit_button', 'restart_button', 'close_button', 'runtime_display')
    self.view.Layout()

  @staticmethod
  def partition(collection, condition):
    return filter(condition, collection), filter(lambda x: not condition(x), collection)

  def update_list(self, collection, new_values):
    # convenience method for syncronizing the model -> widget list collections
    for index, val in enumerate(new_values):
      collection[index].value = val
Exemplo n.º 2
0
class GooeyApplication(wx.Frame):
    """
    Main window for Gooey.
    """
    def __init__(self, buildSpec, *args, **kwargs):
        super(GooeyApplication, self).__init__(None, *args, **kwargs)
        self._state = {}
        self.buildSpec = buildSpec

        self.header = FrameHeader(self, buildSpec)
        self.configs = self.buildConfigPanels(self)
        self.navbar = self.buildNavigation()
        self.footer = Footer(self, buildSpec)
        self.console = Console(self, buildSpec)
        self.layoutComponent()

        self.clientRunner = ProcessController(
            self.buildSpec.get('progress_regex'),
            self.buildSpec.get('progress_expr'),
            self.buildSpec.get('encoding'))

        pub.subscribe(events.WINDOW_START, self.onStart)
        pub.subscribe(events.WINDOW_RESTART, self.onStart)
        pub.subscribe(events.WINDOW_STOP, self.onStopExecution)
        pub.subscribe(events.WINDOW_CLOSE, self.onClose)
        pub.subscribe(events.WINDOW_CANCEL, self.onCancel)
        pub.subscribe(events.WINDOW_EDIT, self.onEdit)
        pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput)
        pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete)
        pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar)
        # Top level wx close event
        self.Bind(wx.EVT_CLOSE, self.onClose)

        if self.buildSpec['poll_external_updates']:
            self.fetchExternalUpdates()

        if self.buildSpec.get('auto_start', False):
            self.onStart()

    def onStart(self, *args, **kwarg):
        """
        Verify user input and kick off the client's program if valid
        """
        with transactUI(self):
            config = self.navbar.getActiveConfig()
            config.resetErrors()
            if config.isValid():
                self.clientRunner.run(self.buildCliString())
                self.showConsole()
            else:
                config.displayErrors()
                self.Layout()

    def onEdit(self):
        """Return the user to the settings screen for further editing"""
        with transactUI(self):
            if self.buildSpec['poll_external_updates']:
                self.fetchExternalUpdates()
            self.showSettings()

    def buildCliString(self):
        """
        Collect all of the required information from the config screen and
        build a CLI string which can be used to invoke the client program
        """
        config = self.navbar.getActiveConfig()
        group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()]
        positional = config.getPositionalArgs()
        optional = config.getOptionalArgs()
        print(
            cli.buildCliString(self.buildSpec['target'], group['command'],
                               positional, optional))
        return cli.buildCliString(self.buildSpec['target'], group['command'],
                                  positional, optional)

    def onComplete(self, *args, **kwargs):
        """
        Display the appropriate screen based on the success/fail of the
        host program
        """
        with transactUI(self):
            if self.clientRunner.was_success():
                if self.buildSpec.get('return_to_config', False):
                    self.showSettings()
                else:
                    self.showSuccess()
                    if self.buildSpec.get('show_success_modal', True):
                        wx.CallAfter(modals.showSuccess)
            else:
                if self.clientRunner.wasForcefullyStopped:
                    self.showForceStopped()
                else:
                    self.showError()
                    wx.CallAfter(modals.showFailure)

    def onStopExecution(self):
        """Displays a scary message and then force-quits the executing
        client code if the user accepts"""
        if self.buildSpec['show_stop_warning'] and modals.confirmForceStop():
            self.clientRunner.stop()

    def fetchExternalUpdates(self):
        """
        !Experimental!
        Calls out to the client code requesting seed values to use in the UI
        !Experimental!
        """
        seeds = seeder.fetchDynamicProperties(self.buildSpec['target'],
                                              self.buildSpec['encoding'])
        for config in self.configs:
            config.seedUI(seeds)

    def onCancel(self):
        """Close the program after confirming"""
        if modals.confirmExit():
            self.onClose()

    def onClose(self, *args, **kwargs):
        """Cleanup the top level WxFrame and shutdown the process"""
        self.Destroy()
        sys.exit()

    def layoutComponent(self):
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.header, 0, wx.EXPAND)
        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)

        sizer.Add(self.navbar, 1, wx.EXPAND)
        sizer.Add(self.console, 1, wx.EXPAND)
        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)
        sizer.Add(self.footer, 0, wx.EXPAND)
        self.SetMinSize((400, 300))
        self.SetSize(self.buildSpec['default_size'])
        self.SetSizer(sizer)
        self.console.Hide()
        self.Layout()
        self.SetIcon(
            wx.Icon(self.buildSpec['images']['programIcon'],
                    wx.BITMAP_TYPE_ICO))

    def buildNavigation(self):
        """
        Chooses the appropriate layout navigation component based on user prefs
        """
        if self.buildSpec['navigation'] == constants.TABBED:
            navigation = Tabbar(self, self.buildSpec, self.configs)
        else:
            navigation = Sidebar(self, self.buildSpec, self.configs)
            if self.buildSpec['navigation'] == constants.HIDDEN:
                navigation.Hide()
        return navigation

    def buildConfigPanels(self, parent):
        page_class = TabbedConfigPage if self.buildSpec[
            'tabbed_groups'] else ConfigPage

        return [
            page_class(parent, widgets)
            for widgets in self.buildSpec['widgets'].values()
        ]

    def showSettings(self):
        self.navbar.Show(True)
        self.console.Show(False)
        self.header.setImage('settings_img')
        self.header.setTitle(_("settings_title"))
        self.header.setSubtitle(self.buildSpec['program_description'])
        self.footer.showButtons('cancel_button', 'start_button')
        self.footer.progress_bar.Show(False)

    def showConsole(self):
        self.navbar.Show(False)
        self.console.Show(True)
        self.header.setImage('running_img')
        self.header.setTitle(_("running_title"))
        self.header.setSubtitle(_('running_msg'))
        self.footer.showButtons('stop_button')
        self.footer.progress_bar.Show(True)
        if not self.buildSpec['progress_regex']:
            self.footer.progress_bar.Pulse()

    def showComplete(self):
        self.navbar.Show(False)
        self.console.Show(True)
        self.footer.showButtons('edit_button', 'restart_button',
                                'close_button')
        self.footer.progress_bar.Show(False)

    def showSuccess(self):
        self.showComplete()
        self.header.setImage('check_mark')
        self.header.setTitle(_('finished_title'))
        self.header.setSubtitle(_('finished_msg'))
        self.Layout()

    def showError(self):
        self.showComplete()
        self.header.setImage('error_symbol')
        self.header.setTitle(_('finished_title'))
        self.header.setSubtitle(_('finished_error'))

    def showForceStopped(self):
        self.showComplete()
        if self.buildSpec.get('force_stop_is_error', True):
            self.showError()
        else:
            self.showSuccess()
        self.header.setSubtitle(_('finished_forced_quit'))
Exemplo n.º 3
0
class GooeyApplication(wx.Frame):
    """
    Main window for Gooey.
    """
    def __init__(self, buildSpec, *args, **kwargs):
        super(GooeyApplication, self).__init__(None, *args, **kwargs)
        self._state = {}
        self.buildSpec = buildSpec

        self.applyConfiguration()
        self.menu = MenuBar(buildSpec)
        self.SetMenuBar(self.menu)
        self.header = FrameHeader(self, buildSpec)
        self.configs = self.buildConfigPanels(self)
        self.navbar = self.buildNavigation()
        self.footer = Footer(self, buildSpec)
        self.console = Console(self, buildSpec)
        self.layoutComponent()
        self.timer = Timing(self)

        self.clientRunner = ProcessController(
            self.buildSpec.get('progress_regex'),
            self.buildSpec.get('progress_expr'),
            self.buildSpec.get('hide_progress_msg'),
            self.buildSpec.get('encoding'),
            self.buildSpec.get('requires_shell'),
        )

        pub.subscribe(events.WINDOW_START, self.onStart)
        pub.subscribe(events.WINDOW_RESTART, self.onStart)
        pub.subscribe(events.WINDOW_STOP, self.onStopExecution)
        pub.subscribe(events.WINDOW_CLOSE, self.onClose)
        pub.subscribe(events.WINDOW_CANCEL, self.onCancel)
        pub.subscribe(events.WINDOW_EDIT, self.onEdit)
        pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput)
        pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete)
        pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar)
        pub.subscribe(events.TIME_UPDATE, self.footer.updateTimeRemaining)
        # Top level wx close event
        self.Bind(wx.EVT_CLOSE, self.onClose)

        if self.buildSpec['poll_external_updates']:
            self.fetchExternalUpdates()

        if self.buildSpec.get('auto_start', False):
            self.onStart()

    def applyConfiguration(self):
        self.SetTitle(self.buildSpec['program_name'])
        self.SetBackgroundColour(self.buildSpec.get('body_bg_color'))

    def onStart(self, *args, **kwarg):
        """
        Verify user input and kick off the client's program if valid
        """
        with transactUI(self):
            config = self.navbar.getActiveConfig()
            config.resetErrors()
            if config.isValid():
                if self.buildSpec['clear_before_run']:
                    self.console.clear()
                self.clientRunner.run(self.buildCliString())
                self.showConsole()
            else:
                config.displayErrors()
                self.Layout()

    def onEdit(self):
        """Return the user to the settings screen for further editing"""
        with transactUI(self):
            if self.buildSpec['poll_external_updates']:
                self.fetchExternalUpdates()
            self.showSettings()

    def buildCliString(self):
        """
        Collect all of the required information from the config screen and
        build a CLI string which can be used to invoke the client program
        """
        config = self.navbar.getActiveConfig()
        group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()]
        positional = config.getPositionalArgs()
        optional = config.getOptionalArgs()
        return cli.buildCliString(
            self.buildSpec['target'],
            group['command'],
            positional,
            optional,
            suppress_gooey_flag=self.buildSpec['suppress_gooey_flag'])

    def onComplete(self, *args, **kwargs):
        """
        Display the appropriate screen based on the success/fail of the
        host program
        """
        with transactUI(self):
            if self.clientRunner.was_success():
                if self.buildSpec.get('return_to_config', False):
                    self.showSettings()
                else:
                    self.showSuccess()
                    if self.buildSpec.get('show_success_modal', True):
                        wx.CallAfter(modals.showSuccess)
            else:
                if self.clientRunner.wasForcefullyStopped:
                    self.showForceStopped()
                else:
                    self.showError()
                    if self.buildSpec.get('show_failure_modal'):
                        wx.CallAfter(modals.showFailure)

    def onCancel(self):
        """Close the program after confirming

        We treat the behavior of the "cancel" button slightly
        differently than the general window close X button only
        because this is 'part of' the form.
        """
        if modals.confirmExit():
            self.onClose()

    def onStopExecution(self):
        """Displays a scary message and then force-quits the executing
        client code if the user accepts"""
        if self.shouldStopExecution():
            self.clientRunner.stop()

    def onClose(self, *args, **kwargs):
        """Stop any actively running client program, cleanup the top
        level WxFrame and shutdown the current process"""
        # issue #592 - we need to run the same onStopExecution machinery
        # when the exit button is clicked to ensure everything is cleaned
        # up correctly.
        if self.clientRunner.running():
            if self.shouldStopExecution():
                self.clientRunner.stop()
                self.destroyGooey()
        else:
            self.destroyGooey()

    def shouldStopExecution(self):
        return not self.buildSpec[
            'show_stop_warning'] or modals.confirmForceStop()

    def destroyGooey(self):
        self.Destroy()
        sys.exit()

    def fetchExternalUpdates(self):
        """
        !Experimental!
        Calls out to the client code requesting seed values to use in the UI
        !Experimental!
        """
        seeds = seeder.fetchDynamicProperties(self.buildSpec['target'],
                                              self.buildSpec['encoding'])
        for config in self.configs:
            config.seedUI(seeds)

    def layoutComponent(self):
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.header, 0, wx.EXPAND)
        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)

        sizer.Add(self.navbar, 1, wx.EXPAND)
        sizer.Add(self.console, 1, wx.EXPAND)
        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)
        sizer.Add(self.footer, 0, wx.EXPAND)
        self.SetMinSize((400, 300))
        self.SetSize(self.buildSpec['default_size'])
        self.SetSizer(sizer)
        self.console.Hide()
        self.Layout()
        if self.buildSpec.get('fullscreen', True):
            self.ShowFullScreen(True)
        # Program Icon (Windows)
        icon = wx.Icon(self.buildSpec['images']['programIcon'],
                       wx.BITMAP_TYPE_PNG)
        self.SetIcon(icon)
        if sys.platform != 'win32':
            # OSX needs to have its taskbar icon explicitly set
            # bizarrely, wx requires the TaskBarIcon to be attached to the Frame
            # as instance data (self.). Otherwise, it will not render correctly.
            self.taskbarIcon = TaskBarIcon(iconType=wx.adv.TBI_DOCK)
            self.taskbarIcon.SetIcon(icon)

    def buildNavigation(self):
        """
        Chooses the appropriate layout navigation component based on user prefs
        """
        if self.buildSpec['navigation'] == constants.TABBED:
            navigation = Tabbar(self, self.buildSpec, self.configs)
        else:
            navigation = Sidebar(self, self.buildSpec, self.configs)
            if self.buildSpec['navigation'] == constants.HIDDEN:
                navigation.Hide()
        return navigation

    def buildConfigPanels(self, parent):
        page_class = TabbedConfigPage if self.buildSpec[
            'tabbed_groups'] else ConfigPage

        return [
            page_class(parent, widgets, self.buildSpec)
            for widgets in self.buildSpec['widgets'].values()
        ]

    def showSettings(self):
        self.navbar.Show(True)
        self.console.Show(False)
        self.header.setImage('settings_img')
        self.header.setTitle(_("settings_title"))
        self.header.setSubtitle(self.buildSpec['program_description'])
        self.footer.showButtons('cancel_button', 'start_button')
        self.footer.progress_bar.Show(False)
        self.footer.time_remaining_text.Show(False)

    def showConsole(self):
        self.navbar.Show(False)
        self.console.Show(True)
        self.header.setImage('running_img')
        self.header.setTitle(_("running_title"))
        self.header.setSubtitle(_('running_msg'))
        self.footer.showButtons('stop_button')
        self.footer.progress_bar.Show(True)
        self.footer.time_remaining_text.Show(False)
        if self.buildSpec.get('timing_options')['show_time_remaining']:
            self.timer.start()
            self.footer.time_remaining_text.Show(True)
        if not self.buildSpec['progress_regex']:
            self.footer.progress_bar.Pulse()

    def showComplete(self):
        self.navbar.Show(False)
        self.console.Show(True)
        buttons = (['edit_button', 'restart_button', 'close_button']
                   if self.buildSpec.get('show_restart_button', True) else
                   ['edit_button', 'close_button'])
        self.footer.showButtons(*buttons)
        self.footer.progress_bar.Show(False)
        if self.buildSpec.get('timing_options')['show_time_remaining']:
            self.timer.stop()
        self.footer.time_remaining_text.Show(True)
        if self.buildSpec.get(
                'timing_options')['hide_time_remaining_on_complete']:
            self.footer.time_remaining_text.Show(False)

    def showSuccess(self):
        self.showComplete()
        self.header.setImage('check_mark')
        self.header.setTitle(_('finished_title'))
        self.header.setSubtitle(_('finished_msg'))
        self.Layout()

    def showError(self):
        self.showComplete()
        self.header.setImage('error_symbol')
        self.header.setTitle(_('finished_title'))
        self.header.setSubtitle(_('finished_error'))

    def showForceStopped(self):
        self.showComplete()
        if self.buildSpec.get('force_stop_is_error', True):
            self.showError()
        else:
            self.showSuccess()
        self.header.setSubtitle(_('finished_forced_quit'))
Exemplo n.º 4
0
class Presenter(object):
    def __init__(self, view, model):
        self.view = view
        self.model = model

        self.client_runner = ProcessController(self.model.progress_regex,
                                               self.model.progress_expr)

        pub.subscribe(self.on_cancel, events.WINDOW_CANCEL)
        pub.subscribe(self.on_stop, events.WINDOW_STOP)
        pub.subscribe(self.on_start, events.WINDOW_START)
        pub.subscribe(self.on_restart, events.WINDOW_RESTART)
        pub.subscribe(self.on_edit, events.WINDOW_EDIT)
        pub.subscribe(self.on_close, events.WINDOW_CLOSE)

        # console statuses from the other thread
        pub.subscribe(self.on_new_message, 'console_update')
        pub.subscribe(self.on_progress_change, 'progress_update')
        pub.subscribe(self.on_client_done, 'execution_complete')

        pub.subscribe(self.on_selection_change, events.LIST_BOX)

    def on_selection_change(self, selection):
        self.update_model()
        self.model.active_group = selection
        self.redraw_from_model()
        self.syncronize_from_model()

    def initialize_view(self):
        self.view.window_title = self.model.program_name
        self.view.window_size = self.model.default_size

        self.view.required_section.clear()
        self.view.optional_section.clear()
        self.view.required_section.populate(self.model.required_args,
                                            self.model.num_required_cols)
        self.view.optional_section.populate(self.model.optional_args,
                                            self.model.num_optional_cols)

        if self.model.use_monospace_font:
            self.view.set_display_font_style('monospace')

        if self.should_disable_stop_button():
            self.view.disable_stop_button()
        else:
            self.view.enable_stop_button()

        if self.model.layout_type == layouts.COLUMN:
            self.view.set_list_contents(list(
                self.model.argument_groups.keys()))

        if self.model.auto_start:
            self.model.update_state(States.RUNNNING)
            self.on_start()
        self.syncronize_from_model()

    def update_model(self):
        self.update_list(self.model.required_args,
                         self.view.required_section.get_values())
        self.update_list(self.model.optional_args,
                         self.view.optional_section.get_values())
        self.syncronize_from_model()

    def syncronize_from_model(self):
        #TODO move this out of the presenter
        #TODO Make all view interactions thread safe
        wx.CallAfter(self.syncronize_from_model_async)

    def syncronize_from_model_async(self):
        # update heading titles
        self.view.heading_title = self.model.heading_title
        self.view.heading_subtitle = self.model.heading_subtitle

        # refresh the widgets
        for index, widget in enumerate(self.view.required_section):
            widget.set_value(self.model.required_args[index]._value)
        for index, widget in enumerate(self.view.optional_section):
            widget.set_value(self.model.optional_args[index]._value)

        # swap the views
        getattr(self, self.model.current_state)()

    def redraw_from_model(self):
        self.view.freeze()
        self.view.required_section.clear()
        self.view.optional_section.clear()
        self.view.required_section.populate(self.model.required_args,
                                            self.model.num_required_cols)
        self.view.optional_section.populate(self.model.optional_args,
                                            self.model.num_optional_cols)
        getattr(self, self.model.current_state)()
        self.view.thaw()

    def should_disable_stop_button(self):
        return self.model.stop_button_disabled

    def on_start(self):
        self.update_model()
        if not self.model.is_valid():
            return self.view.show_missing_args_dialog()
        command = self.model.build_command_line_string()
        self.client_runner.run(command)
        self.model.update_state(States.RUNNNING)
        self.syncronize_from_model()

    def on_stop(self):
        self.ask_stop()

    def on_edit(self):
        self.model.update_state(States.CONFIGURING)
        self.syncronize_from_model()

    def on_restart(self):
        self.on_start()

    def on_cancel(self):
        if self.view.confirm_exit_dialog():
            self.view.Destroy()
            sys.exit()

    def on_close(self):
        self.view.Destroy()
        sys.exit()

    def on_new_message(self, msg):
        # observes changes coming from the subprocess
        self.view.update_console_async(msg)

    def on_progress_change(self, progress):
        # observes changes coming from the subprocess
        self.view.update_progress_aync(
            progress, self.model.disable_progress_bar_animation)

    def on_client_done(self):
        if self.client_runner.was_success():
            self.model.update_state(States.SUCCESS)
        else:
            self.model.update_state(States.ERROR)
        self.syncronize_from_model()

    def ask_stop(self):
        if self.view.confirm_stop_dialog():
            self.stop()
            return True
        return False

    def stop(self):
        self.client_runner.stop()

    def configuring(self):
        self.view.hide_all_buttons()
        self.view.hide('check_mark', 'running_img', 'error_symbol',
                       'runtime_display')
        self.view.show('settings_img', 'cancel_button', 'start_button',
                       'config_panel')
        self.view.Layout()

    def running(self):
        self.view.hide_all_buttons()
        self.view.hide('check_mark', 'settings_img', 'error_symbol',
                       'config_panel')
        self.view.show('running_img', 'stop_button', 'progress_bar',
                       'runtime_display')
        self.view.progress_bar.Pulse()
        self.view.Layout()

    def success(self):
        self.view.hide_all_buttons()
        self.view.hide('running_img', 'progress_bar', 'config_panel')
        self.view.show('check_mark', 'edit_button', 'restart_button',
                       'close_button', 'runtime_display')
        self.view.Layout()

    def error(self):
        self.view.hide_all_buttons()
        self.view.hide('running_img', 'progress_bar', 'config_panel')
        self.view.show('error_symbol', 'edit_button', 'restart_button',
                       'close_button', 'runtime_display')
        self.view.Layout()

    @staticmethod
    def partition(collection, condition):
        return list(
            filter(condition,
                   collection)), [x for x in collection if not condition(x)]

    def update_list(self, collection, new_values):
        # convenience method for syncronizing the model -> widget list collections
        for index, val in enumerate(new_values):
            collection[index].value = val
Exemplo n.º 5
0
class GooeyApplication(wx.Frame):
    """
    Main window for Gooey.
    """

    def __init__(self, buildSpec, *args, **kwargs):
        super(GooeyApplication, self).__init__(None, *args, **kwargs)
        self._state = {}
        self.buildSpec = buildSpec

        self.header = FrameHeader(self, buildSpec)
        self.configs = self.buildConfigPanels(self)
        self.navbar = self.buildNavigation()
        self.footer = Footer(self, buildSpec)
        self.console = Console(self, buildSpec)
        self.layoutComponent()

        self.clientRunner = ProcessController(
            self.buildSpec.get('progress_regex'),
            self.buildSpec.get('progress_expr'),
            self.buildSpec.get('encoding')
        )

        pub.subscribe(events.WINDOW_START, self.onStart)
        pub.subscribe(events.WINDOW_RESTART, self.onStart)
        pub.subscribe(events.WINDOW_STOP, self.onStopExecution)
        pub.subscribe(events.WINDOW_CLOSE, self.onClose)
        pub.subscribe(events.WINDOW_CANCEL, self.onCancel)
        pub.subscribe(events.WINDOW_EDIT, self.onEdit)
        pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput)
        pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete)
        pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar)
        # Top level wx close event
        self.Bind(wx.EVT_CLOSE, self.onClose)

        if self.buildSpec['poll_external_updates']:
            self.fetchExternalUpdates()

        if self.buildSpec.get('auto_start', False):
            self.onStart()


    def onStart(self, *args, **kwarg):
        """
        Verify user input and kick off the client's program if valid
        """
        with transactUI(self):
            config = self.navbar.getActiveConfig()
            config.resetErrors()
            if config.isValid():
                self.clientRunner.run(self.buildCliString())
                self.showConsole()
            else:
                config.displayErrors()
                self.Layout()


    def onEdit(self):
        """Return the user to the settings screen for further editing"""
        with transactUI(self):
            if self.buildSpec['poll_external_updates']:
                self.fetchExternalUpdates()
            self.showSettings()


    def buildCliString(self):
        """
        Collect all of the required information from the config screen and
        build a CLI string which can be used to invoke the client program
        """
        config = self.navbar.getActiveConfig()
        group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()]
        positional = config.getPositionalArgs()
        optional = config.getOptionalArgs()
        print(cli.buildCliString(
            self.buildSpec['target'],
            group['command'],
            positional,
            optional
        ))
        return cli.buildCliString(
            self.buildSpec['target'],
            group['command'],
            positional,
            optional
        )


    def onComplete(self, *args, **kwargs):
        """
        Display the appropriate screen based on the success/fail of the
        host program
        """
        with transactUI(self):
            if self.clientRunner.was_success():
                if self.buildSpec.get('return_to_config', False):
                    self.showSettings()
                else:
                    self.showSuccess()
                    if self.buildSpec.get('show_success_modal', True):
                        wx.CallAfter(modals.showSuccess)
            else:
                if self.clientRunner.wasForcefullyStopped:
                    self.showForceStopped()
                else:
                    self.showError()
                    wx.CallAfter(modals.showFailure)


    def onStopExecution(self):
        """Displays a scary message and then force-quits the executing
        client code if the user accepts"""
        if self.buildSpec['show_stop_warning'] and modals.confirmForceStop():
            self.clientRunner.stop()


    def fetchExternalUpdates(self):
        """
        !Experimental!
        Calls out to the client code requesting seed values to use in the UI
        !Experimental!
        """
        seeds = seeder.fetchDynamicProperties(
            self.buildSpec['target'],
            self.buildSpec['encoding']
        )
        for config in self.configs:
            config.seedUI(seeds)


    def onCancel(self):
        """Close the program after confirming"""
        if modals.confirmExit():
            self.onClose()


    def onClose(self, *args, **kwargs):
        """Cleanup the top level WxFrame and shutdown the process"""
        self.Destroy()
        sys.exit()


    def layoutComponent(self):
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.header, 0, wx.EXPAND)
        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)

        sizer.Add(self.navbar, 1, wx.EXPAND)
        sizer.Add(self.console, 1, wx.EXPAND)
        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)
        sizer.Add(self.footer, 0, wx.EXPAND)
        self.SetMinSize((400, 300))
        self.SetSize(self.buildSpec['default_size'])
        self.SetSizer(sizer)
        self.console.Hide()
        self.Layout()
        self.SetIcon(wx.Icon(self.buildSpec['images']['programIcon'], wx.BITMAP_TYPE_ICO))



    def buildNavigation(self):
        """
        Chooses the appropriate layout navigation component based on user prefs
        """
        if self.buildSpec['navigation'] == constants.TABBED:
            navigation = Tabbar(self, self.buildSpec, self.configs)
        else:
            navigation = Sidebar(self, self.buildSpec, self.configs)
            if self.buildSpec['navigation'] == constants.HIDDEN:
                navigation.Hide()
        return navigation


    def buildConfigPanels(self, parent):
        page_class = TabbedConfigPage if self.buildSpec['tabbed_groups'] else ConfigPage

        return [page_class(parent, widgets)
                for widgets in self.buildSpec['widgets'].values()]


    def showSettings(self):
        self.navbar.Show(True)
        self.console.Show(False)
        self.header.setImage('settings_img')
        self.header.setTitle(_("settings_title"))
        self.header.setSubtitle(self.buildSpec['program_description'])
        self.footer.showButtons('cancel_button', 'start_button')
        self.footer.progress_bar.Show(False)


    def showConsole(self):
        self.navbar.Show(False)
        self.console.Show(True)
        self.header.setImage('running_img')
        self.header.setTitle(_("running_title"))
        self.header.setSubtitle(_('running_msg'))
        self.footer.showButtons('stop_button')
        self.footer.progress_bar.Show(True)
        if not self.buildSpec['progress_regex']:
            self.footer.progress_bar.Pulse()


    def showComplete(self):
        self.navbar.Show(False)
        self.console.Show(True)
        self.footer.showButtons('edit_button', 'restart_button', 'close_button')
        self.footer.progress_bar.Show(False)


    def showSuccess(self):
        self.showComplete()
        self.header.setImage('check_mark')
        self.header.setTitle(_('finished_title'))
        self.header.setSubtitle(_('finished_msg'))
        self.Layout()


    def showError(self):
        self.showComplete()
        self.header.setImage('error_symbol')
        self.header.setTitle(_('finished_title'))
        self.header.setSubtitle(_('finished_error'))


    def showForceStopped(self):
        self.showComplete()
        if self.buildSpec.get('force_stop_is_error', True):
            self.showError()
        else:
            self.showSuccess()
        self.header.setSubtitle(_('finished_forced_quit'))
Exemplo n.º 6
0
class GooeyApplication(wx.Frame):
    """
    Main window for Gooey.
    """
    def __init__(self, buildSpec, *args, **kwargs):
        super(GooeyApplication, self).__init__(None, *args, **kwargs)
        self._state = {}
        self.buildSpec = buildSpec

        self.applyConfiguration()
        self.menu = MenuBar(buildSpec)
        self.SetMenuBar(self.menu)
        self.header = FrameHeader(self, buildSpec)
        self.configs = self.buildConfigPanels(self)
        self.navbar = self.buildNavigation()
        self.footer = Footer(self, buildSpec)
        self.console = Console(self, buildSpec)

        self.props = {
            'background_color': self.buildSpec['header_bg_color'],
            'title': self.buildSpec['program_name'],
            'subtitle': self.buildSpec['program_description'],
            'height': self.buildSpec['header_height'],
            'image_uri': self.buildSpec['images']['configIcon'],
            'image_size': (six.MAXSIZE, self.buildSpec['header_height'] - 10)
        }

        state = form_page(initial_state(self.buildSpec))

        self.fprops = {
            'buttons': state['buttons'],
            'progress': state['progress'],
            'timing': state['timing'],
            'bg_color': self.buildSpec['footer_bg_color']
        }

        # self.hhh = render(create_element(RHeader, self.props), self)
        # self.fff = render(create_element(RFooter, self.fprops), self)
        # patch(self.hhh, create_element(RHeader, {**self.props, 'image_uri': self.buildSpec['images']['runningIcon']}))
        self.layoutComponent()
        self.timer = Timing(self)

        self.clientRunner = ProcessController(
            self.buildSpec.get('progress_regex'),
            self.buildSpec.get('progress_expr'),
            self.buildSpec.get('hide_progress_msg'),
            self.buildSpec.get('encoding'),
            self.buildSpec.get('requires_shell'),
            self.buildSpec.get('shutdown_signal', signal.SIGTERM))

        pub.subscribe(events.WINDOW_START, self.onStart)
        pub.subscribe(events.WINDOW_RESTART, self.onStart)
        pub.subscribe(events.WINDOW_STOP, self.onStopExecution)
        pub.subscribe(events.WINDOW_CLOSE, self.onClose)
        pub.subscribe(events.WINDOW_CANCEL, self.onCancel)
        pub.subscribe(events.WINDOW_EDIT, self.onEdit)
        pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput)
        pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete)
        pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar)
        pub.subscribe(events.TIME_UPDATE, self.footer.updateTimeRemaining)
        # Top level wx close event
        # self.Bind(wx.EVT_CLOSE, self.onClose)

        # TODO: handle child focus for per-field level validation.
        # self.Bind(wx.EVT_CHILD_FOCUS, self.handleFocus)

        if self.buildSpec.get('auto_start', False):
            self.onStart()

    def applyConfiguration(self):
        self.SetTitle(self.buildSpec['program_name'])
        self.SetBackgroundColour(self.buildSpec.get('body_bg_color'))

    def onStart(self, *args, **kwarg):
        """
        Verify user input and kick off the client's program if valid
        """
        # navigates away from the button because a
        # disabled focused button still looks enabled.
        self.footer.cancel_button.Disable()
        self.footer.start_button.Disable()
        self.footer.start_button.Navigate()
        if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []):
            # TODO: make this wx thread safe so that it can
            # actually run asynchronously
            Thread(target=self.onStartAsync).run()
        else:
            Thread(target=self.onStartAsync).run()

    def onStartAsync(self, *args, **kwargs):
        with transactUI(self):
            try:
                errors = self.validateForm().getOrThrow()
                if errors:  # TODO
                    config = self.navbar.getActiveConfig()
                    config.setErrors(errors)
                    self.Layout()
                    # TODO: account for tabbed layouts
                    # TODO: scroll the first error into view
                    # TODO: rather than just snapping to the top
                    self.configs[0].Scroll(0, 0)
                else:
                    if self.buildSpec['clear_before_run']:
                        self.console.clear()
                    self.clientRunner.run(self.buildCliString())
                    self.showConsole()
            except CalledProcessError as e:
                self.showError()
                self.console.appendText(str(e))
                self.console.appendText(
                    '\n\nThis failure happens when Gooey tries to invoke your '
                    'code for the VALIDATE_FORM event and receives an expected '
                    'error code in response.')
                wx.CallAfter(modals.showFailure)
            except JSONDecodeError as e:
                self.showError()
                self.console.appendText(str(e))
                self.console.appendText(
                    '\n\nGooey was unable to parse the response to the VALIDATE_FORM event. '
                    'This can happen if you have additional logs to stdout beyond what Gooey '
                    'expects.')
                wx.CallAfter(modals.showFailure)
            # for some reason, we have to delay the re-enabling of
            # the buttons by a few ms otherwise they pickup pending
            # events created while they were disabled. Trial and error
            # let to this solution.
            wx.CallLater(20, self.footer.start_button.Enable)
            wx.CallLater(20, self.footer.cancel_button.Enable)

    def onEdit(self):
        """Return the user to the settings screen for further editing"""
        with transactUI(self):
            for config in self.configs:
                config.resetErrors()
            self.showSettings()

    def onComplete(self, *args, **kwargs):
        """
        Display the appropriate screen based on the success/fail of the
        host program
        """
        with transactUI(self):
            if self.clientRunner.was_success():
                if self.buildSpec.get('return_to_config', False):
                    self.showSettings()
                else:
                    self.showSuccess()
                    if self.buildSpec.get('show_success_modal', True):
                        wx.CallAfter(modals.showSuccess)
            else:
                if self.clientRunner.wasForcefullyStopped:
                    self.showForceStopped()
                else:
                    self.showError()
                    if self.buildSpec.get('show_failure_modal'):
                        wx.CallAfter(modals.showFailure)

    def onCancel(self):
        """Close the program after confirming

        We treat the behavior of the "cancel" button slightly
        differently than the general window close X button only
        because this is 'part of' the form.
        """
        if modals.confirmExit():
            self.onClose()

    def onStopExecution(self):
        """Displays a scary message and then force-quits the executing
        client code if the user accepts"""
        if self.shouldStopExecution():
            self.clientRunner.stop()

    def onClose(self, *args, **kwargs):
        """Stop any actively running client program, cleanup the top
        level WxFrame and shutdown the current process"""
        # issue #592 - we need to run the same onStopExecution machinery
        # when the exit button is clicked to ensure everything is cleaned
        # up correctly.
        if self.clientRunner.running():
            if self.shouldStopExecution():
                self.clientRunner.stop()
                self.destroyGooey()
        else:
            self.destroyGooey()

    def buildCliString(self) -> str:
        """
        Collect all of the required information from the config screen and
        build a CLI string which can be used to invoke the client program
        """
        cmd = self.getCommandDetails()
        return cli.cliCmd(
            cmd.target,
            cmd.subcommand,
            cmd.positionals,
            cmd.optionals,
            suppress_gooey_flag=self.buildSpec['suppress_gooey_flag'])

    def validateForm(self) -> Try[Mapping[str, str]]:
        config = self.navbar.getActiveConfig()
        localErrors: Mapping[str, str] = config.getErrors()
        dynamicResult: Try[Mapping[str, str]] = self.fetchDynamicValidations()

        combineErrors = lambda m: merge(localErrors, m)
        return dynamicResult.map(combineErrors)

    def fetchDynamicValidations(self) -> Try[Mapping[str, str]]:
        # only run the dynamic validation if the user has
        # specifically subscribed to that event
        if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []):
            cmd = self.getCommandDetails()
            return seeder.communicate(
                cli.formValidationCmd(cmd.target, cmd.subcommand,
                                      cmd.positionals, cmd.optionals),
                self.buildSpec['encoding'])
        else:
            # shim response if nothing to do.
            return Success({})

    def getCommandDetails(self) -> CommandDetails:
        """
        Temporary helper for getting the state of the current Config.

        To be deprecated upon (the desperately needed) refactor.
        """
        config = self.navbar.getActiveConfig()
        group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()]
        return CommandDetails(
            self.buildSpec['target'],
            group['command'],
            config.getPositionalValues(),
            config.getOptionalValues(),
        )

    def shouldStopExecution(self):
        return not self.buildSpec[
            'show_stop_warning'] or modals.confirmForceStop()

    def destroyGooey(self):
        self.Destroy()
        sys.exit()

    def block(self, **kwargs):
        pass

    def layoutComponent(self):
        sizer = wx.BoxSizer(wx.VERTICAL)
        # sizer.Add(self.hhh, 0, wx.EXPAND)
        sizer.Add(self.header, 0, wx.EXPAND)
        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)

        sizer.Add(self.navbar, 1, wx.EXPAND)
        sizer.Add(self.console, 1, wx.EXPAND)
        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)
        # sizer.Add(self.fff, 0, wx.EXPAND)
        sizer.Add(self.footer, 0, wx.EXPAND)
        self.SetMinSize((400, 300))
        self.SetSize(self.buildSpec['default_size'])
        self.SetSizer(sizer)
        self.console.Hide()
        self.Layout()
        if self.buildSpec.get('fullscreen', True):
            self.ShowFullScreen(True)
        # Program Icon (Windows)
        icon = wx.Icon(self.buildSpec['images']['programIcon'],
                       wx.BITMAP_TYPE_PNG)
        self.SetIcon(icon)
        if sys.platform != 'win32':
            # OSX needs to have its taskbar icon explicitly set
            # bizarrely, wx requires the TaskBarIcon to be attached to the Frame
            # as instance data (self.). Otherwise, it will not render correctly.
            self.taskbarIcon = TaskBarIcon(iconType=wx.adv.TBI_DOCK)
            self.taskbarIcon.SetIcon(icon)

    def buildNavigation(self):
        """
        Chooses the appropriate layout navigation component based on user prefs
        """
        if self.buildSpec['navigation'] == constants.TABBED:
            navigation = Tabbar(self, self.buildSpec, self.configs)
        else:
            navigation = Sidebar(self, self.buildSpec, self.configs)
            if self.buildSpec['navigation'] == constants.HIDDEN:
                navigation.Hide()
        return navigation

    def buildConfigPanels(self, parent):
        page_class = TabbedConfigPage if self.buildSpec[
            'tabbed_groups'] else ConfigPage

        return [
            page_class(parent, widgets, self.buildSpec)
            for widgets in self.buildSpec['widgets'].values()
        ]

    def showSettings(self):
        self.navbar.Show(True)
        self.console.Show(False)
        self.header.setImage('settings_img')
        self.header.setTitle(_("settings_title"))
        self.header.setSubtitle(self.buildSpec['program_description'])
        self.footer.showButtons('cancel_button', 'start_button')
        self.footer.progress_bar.Show(False)
        self.footer.time_remaining_text.Show(False)

    def showConsole(self):
        self.navbar.Show(False)
        self.console.Show(True)
        self.header.setImage('running_img')
        self.header.setTitle(_("running_title"))
        self.header.setSubtitle(_('running_msg'))
        self.footer.showButtons('stop_button')
        if not self.buildSpec.get('disable_progress_bar_animation', False):
            self.footer.progress_bar.Show(True)
        self.footer.time_remaining_text.Show(False)
        if self.buildSpec.get('timing_options')['show_time_remaining']:
            self.timer.start()
            self.footer.time_remaining_text.Show(True)
        if not self.buildSpec['progress_regex']:
            self.footer.progress_bar.Pulse()

    def showComplete(self):
        self.navbar.Show(False)
        self.console.Show(True)
        buttons = (['edit_button', 'restart_button', 'close_button']
                   if self.buildSpec.get('show_restart_button', True) else
                   ['edit_button', 'close_button'])
        self.footer.showButtons(*buttons)
        self.footer.progress_bar.Show(False)
        if self.buildSpec.get('timing_options')['show_time_remaining']:
            self.timer.stop()
        self.footer.time_remaining_text.Show(True)
        if self.buildSpec.get(
                'timing_options')['hide_time_remaining_on_complete']:
            self.footer.time_remaining_text.Show(False)

    def showSuccess(self):
        self.showComplete()
        self.header.setImage('check_mark')
        self.header.setTitle(_('finished_title'))
        self.header.setSubtitle(_('finished_msg'))
        self.Layout()

    def showError(self):
        self.showComplete()
        self.header.setImage('error_symbol')
        self.header.setTitle(_('finished_title'))
        self.header.setSubtitle(_('finished_error'))

    def showForceStopped(self):
        self.showComplete()
        if self.buildSpec.get('force_stop_is_error', True):
            self.showError()
        else:
            self.showSuccess()
        self.header.setSubtitle(_('finished_forced_quit'))