示例#1
0
    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()
示例#2
0
    def test_extract_progress(self):
        # should pull out a number based on the supplied
        # regex and expression
        processor = ProcessController("^progress: (\d+)%$", None, 'utf-8')
        self.assertEqual(processor._extract_progress(b'progress: 50%'), 50)

        processor = ProcessController("total: (\d+)%$", None,  'utf-8')
        self.assertEqual(processor._extract_progress(b'my cool total: 100%'), 100)
示例#3
0
    def test_extract_progress(self):
        # should pull out a number based on the supplied
        # regex and expression
        processor = ProcessController(r"^progress: (\d+)%$", None, False,
                                      'utf-8')
        self.assertEqual(processor._extract_progress(b'progress: 50%'), 50)

        processor = ProcessController(r"total: (\d+)%$", None, False, 'utf-8')
        self.assertEqual(processor._extract_progress(b'my cool total: 100%'),
                         100)
示例#4
0
    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()
示例#5
0
 def test_eval_progress_returns_none_on_failure(self):
     # given a match in the string, should eval the result
     regex = r'(\d+)/(\d+)$'
     processor = ProcessController(regex, r'x[0] *^/* x[1]', False, False,
                                   'utf-8')
     match = re.search(regex, '50/50')
     self.assertIsNone(processor._eval_progress(match))
示例#6
0
 def test_eval_progress(self):
     # given a match in the string, should eval the result
     regex = r'(\d+)/(\d+)$'
     processor = ProcessController(regex, r'x[0] / x[1]', False, False,
                                   'utf-8')
     match = re.search(regex, '50/50')
     self.assertEqual(processor._eval_progress(match), 1.0)
示例#7
0
    def __init__(self, props):
        super().__init__(props)
        self.frameRef = Ref()
        self.consoleRef = Ref()
        self.configRef = Ref()

        self.buildSpec = props
        self.state = initial_state(props)
        self.headerprops = lambda state: {
            'background_color': self.buildSpec['header_bg_color'],
            'title': state['title'],
            'show_title': state['header_show_title'],
            'subtitle': state['subtitle'],
            'show_subtitle': state['header_show_subtitle'],
            'flag': wx.EXPAND,
            'height': self.buildSpec['header_height'],
            'image_uri': state['image'],
            'image_size': (six.MAXSIZE, self.buildSpec['header_height'] - 10)
        }

        self.fprops = lambda state: {
            'buttons': state['buttons'],
            'progress': state['progress'],
            'timing': state['timing'],
            'bg_color': self.buildSpec['footer_bg_color'],
            'flag': wx.EXPAND,
        }
        self.clientRunner = ProcessController.of(self.buildSpec)
        self.timer = None
示例#8
0
    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)
示例#9
0
def test_extract_progress():
  # should pull out a number based on the supplied
  # regex and expression
  processor = ProcessController(r"^progress: (\d+)%$", None)
  assert processor._extract_progress('progress: 50%') == 50

  processor = ProcessController(r"total: (\d+)%$", None)
  assert processor._extract_progress('my cool total: 100%') == 100
示例#10
0
    def test_ignore_sigint_family_signals(self):
        try:
            import _winapi
            signals = [signal.CTRL_BREAK_EVENT, signal.CTRL_C_EVENT]
            programs = ['ignore_break.py', 'ignore_interrupt.py']
        except ModuleNotFoundError:
            signals = [signal.SIGINT]
            programs = ['ignore_interrupt.py']

        for program, sig in zip(programs, signals):
            cmd = sys.executable + ' ' + os.path.join(os.getcwd(), 'files',
                                                      program)
            process = processor = ProcessController(None,
                                                    None,
                                                    False,
                                                    'utf-8',
                                                    True,
                                                    shutdown_signal=sig,
                                                    testmode=True)
            process.run(cmd)
            # super-duper important sleep so that the
            # signal is actually received by the child process
            # see: https://stackoverflow.com/questions/32023719/how-to-simulate-a-terminal-ctrl-c-event-from-a-unittest
            time.sleep(1)
            process.send_shutdown_signal()
            # wait to give stdout enough time to write
            time.sleep(1)
            # now our signal should have been received, but rejected.
            self.assertTrue(processor.running())
            # so we sigterm to actually shut down the process.
            process._send_signal(signal.SIGTERM)
            # sanity wait
            max_wait = time.time() + 2
            while processor.running() and time.time() < max_wait:
                time.sleep(0.1)
            # now we should be shut down due to killing the process.
            self.assertFalse(processor.running())
            # and we'll see in the stdout out from the process that our
            # interrupt was received
            output = process._process.stdout.read().decode('utf-8')
            self.assertIn("INTERRUPT", str(output))
            # but indeed ignored. It continued running and writing to stdout after
            # receiving the signal
            self.assertTrue(output.index("INTERRUPT") < len(output))
示例#11
0
    def test_all_interrupts_halt_process(self):
        """
        TODO: These tests are hella flaky. I'm confident that the feature works. However, getting
        signals, subprocesses and unittest to all play together reliably is proving tricky. It
        primarily seems to come down to how long the time.sleep() is before sending the shutdown
        signal.
        """

        cmd = 'python ' + os.path.join(os.getcwd(), 'files',
                                       'infinite_loop.py')

        try:
            import _winapi
            signals = [
                signal.SIGTERM, signal.CTRL_BREAK_EVENT, signal.CTRL_C_EVENT
            ]
        except ModuleNotFoundError:
            signals = [signal.SIGTERM, signal.SIGINT]
        try:
            for sig in signals:
                print('sig', sig)
                processor = ProcessController(None,
                                              None,
                                              False,
                                              'utf-8',
                                              True,
                                              shutdown_signal=sig)

                processor.run(cmd)
                self.assertTrue(processor.running())

                # super-duper important sleep so that the
                # signal is actually received by the child process
                # see: https://stackoverflow.com/questions/32023719/how-to-simulate-a-terminal-ctrl-c-event-from-a-unittest
                time.sleep(1)
                processor.stop()
                max_wait = time.time() + 4
                while processor.running() and time.time() < max_wait:
                    time.sleep(0.1)
                self.assertFalse(processor.running())
        except KeyboardInterrupt:
            pass
示例#12
0
文件: presenter.py 项目: dtbinh/Gooey
  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)
示例#13
0
 def test_eval_progress_returns_none_on_failure(self):
     # given a match in the string, should eval the result
     regex = r'(\d+)/(\d+)$'
     processor = ProcessController(regex, r'x[0] *^/* x[1]', 'utf-8')
     match = re.search(regex, '50/50')
     self.assertIsNone(processor._eval_progress(match))
示例#14
0
 def test_extract_progress_returns_none_if_no_regex_supplied(self):
     processor = ProcessController(None, None, False, 'utf-8')
     self.assertIsNone(processor._extract_progress(b'Total progress: 100%'))
示例#15
0
 def test_extract_progress_returns_none_if_no_match_found(self):
     processor = ProcessController(r'(\d+)%$', None, 'utf-8')
     self.assertIsNone(processor._extract_progress(b'No match in dis string'))
示例#16
0
 def test_eval_progress(self):
     # given a match in the string, should eval the result
     regex = r'(\d+)/(\d+)$'
     processor = ProcessController(regex, r'x[0] / x[1]', 'utf-8')
     match = re.search(regex, '50/50')
     self.assertEqual(processor._eval_progress(match), 1.0)
示例#17
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'))
示例#18
0
 def test_extract_progress_returns_none_if_no_regex_supplied(self):
     processor = ProcessController(None, None, 'utf-8')
     self.assertIsNone(processor._extract_progress(b'Total progress: 100%'))
示例#19
0
def test_eval_progress():
  # given a match in the string, should eval the result
  regex = r'(\d+)/(\d+)$'
  processor = ProcessController(regex, r'x[0] / x[1]')
  match = re.search(regex, '50/50')
  assert processor._eval_progress(match) == 1.0
示例#20
0
def test_eval_progress_returns_none_on_failure():
  # given a match in the string, should eval the result
  regex = r'(\d+)/(\d+)$'
  processor = ProcessController(regex, r'x[0] *^/* x[1]')
  match = re.search(regex, '50/50')
  assert processor._eval_progress(match) == None
示例#21
0
def test_extract_progress_returns_none_if_no_regex_supplied():
  processor = ProcessController(None, None)
  assert processor._extract_progress('Total progress: 100%') == None
示例#22
0
def test_extract_progress_returns_none_if_no_match_found():
  processor = ProcessController(r'(\d+)%$', None)
  assert processor._extract_progress('No match in dis string') == None
示例#23
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
示例#24
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'))
示例#25
0
    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()
示例#26
0
文件: presenter.py 项目: dtbinh/Gooey
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
示例#27
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'))
示例#28
0
 def test_extract_progress_returns_none_if_no_match_found(self):
     processor = ProcessController(r'(\d+)%$', None, False, 'utf-8')
     self.assertIsNone(
         processor._extract_progress(b'No match in dis string'))
示例#29
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'))