From 369b7898b0ad864b64ba6c0205a51802e575c5c2 Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Fri, 23 Oct 2015 18:08:59 +0300 Subject: [PATCH] implement Stop button --- gooey/gui/controller.py | 40 ++++++++++++++++++++---- gooey/gui/util/taskkill.py | 11 +++++++ gooey/gui/widgets/widget_pack.py | 2 +- gooey/gui/windows/base_window.py | 13 ++++++++ gooey/gui/windows/footer.py | 2 ++ gooey/languages/eng.py | 17 +++++----- gooey/languages/english.json | 30 +++++++++--------- gooey/python_bindings/docopt_to_json.py | 4 +-- gooey/python_bindings/gooey_decorator.py | 4 +-- gooey/python_bindings/gooey_parser.py | 1 + 10 files changed, 91 insertions(+), 33 deletions(-) create mode 100644 gooey/gui/util/taskkill.py diff --git a/gooey/gui/controller.py b/gooey/gui/controller.py index e5a5647..2262ea3 100644 --- a/gooey/gui/controller.py +++ b/gooey/gui/controller.py @@ -5,6 +5,7 @@ Created on Dec 22, 2013 ''' import wx +import os import sys import subprocess @@ -14,12 +15,13 @@ from multiprocessing.dummy import Pool from gooey.gui import events from gooey.gui.lang import i18n from gooey.gui.windows import views +from gooey.gui.util.taskkill import taskkill + YES = 5103 NO = 5104 - class Controller(object): ''' Main controller for the gui. @@ -34,9 +36,11 @@ class Controller(object): ''' self.core_gui = base_frame self.build_spec = build_spec + self._process = None # wire up all the observers 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_close, events.WINDOW_CLOSE) @@ -74,8 +78,32 @@ class Controller(object): pub.send_message(events.WINDOW_CHANGE, view_name=views.RUNNING_SCREEN) self.run_client_code(command) + def on_stop(self): + if not self.running(): + return True + msg = i18n._('sure_you_want_to_stop') + dlg = wx.MessageDialog(None, msg, i18n._('stop_task'), wx.YES_NO) + result = dlg.ShowModal() + dlg.Destroy() + if result == YES: + self.stop() + return True + return False + + def stop(self): + if self.running(): + taskkill(self._process.pid) + + def running(self): + return self._process and self._process.poll() is None + def run_client_code(self, command): - p = subprocess.Popen(command, bufsize=1, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + env = os.environ.copy() + env["GOOEY"] = "1" + print "run command:", command + p = subprocess.Popen(command, bufsize=1, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, shell=True, env=env) + self._process = p pool = Pool(1) pool.apply_async(self.read_stdout, (p, self.process_result)) @@ -88,13 +116,13 @@ class Controller(object): wx.CallAfter(callback, process) def process_result(self, process): - _stdout, _stderr = process.communicate() + _stdout, _ = process.communicate() if process.returncode == 0: pub.send_message(events.WINDOW_CHANGE, view_name=views.SUCCESS_SCREEN) self.success_dialog() else: pub.send_message(events.WINDOW_CHANGE, view_name=views.ERROR_SCREEN) - self.error_dialog(_stderr) + self.error_dialog() def skipping_config(self): return self.build_spec['manual_start'] @@ -108,8 +136,8 @@ class Controller(object): def success_dialog(self): self.show_dialog(i18n._("execution_finished"), i18n._('success_message'), wx.ICON_INFORMATION) - def error_dialog(self, error_msg): - self.show_dialog(i18n._('error_title'), i18n._('uh_oh').format(error_msg), wx.ICON_ERROR) + def error_dialog(self): + self.show_dialog(i18n._('error_title'), i18n._('uh_oh'), wx.ICON_ERROR) def show_dialog(self, title, content, style): a = wx.MessageDialog(None, content, title, style) diff --git a/gooey/gui/util/taskkill.py b/gooey/gui/util/taskkill.py new file mode 100644 index 0000000..a48f676 --- /dev/null +++ b/gooey/gui/util/taskkill.py @@ -0,0 +1,11 @@ +import sys +import os +import signal + + +if sys.platform.startswith("win"): + def taskkill(pid): + os.system('taskkill /F /PID {:d} /T >NUL 2>NUL'.format(pid)) +else: # POSIX + def taskkill(pid): + os.kill(pid, signal.SIGTERM) diff --git a/gooey/gui/widgets/widget_pack.py b/gooey/gui/widgets/widget_pack.py index 43ff219..df837de 100644 --- a/gooey/gui/widgets/widget_pack.py +++ b/gooey/gui/widgets/widget_pack.py @@ -60,7 +60,7 @@ class BaseChooser(WidgetPack): widget_sizer = wx.BoxSizer(wx.HORIZONTAL) widget_sizer.Add(self.text_box, 1, wx.EXPAND) widget_sizer.AddSpacer(10) - widget_sizer.Add(self.button, 0) + widget_sizer.Add(self.button, 0, wx.ALIGN_CENTER_VERTICAL) parent.Bind(wx.EVT_BUTTON, self.onButton, self.button) return widget_sizer diff --git a/gooey/gui/windows/base_window.py b/gooey/gui/windows/base_window.py index de41592..0b7f14e 100644 --- a/gooey/gui/windows/base_window.py +++ b/gooey/gui/windows/base_window.py @@ -40,6 +40,7 @@ class BaseWindow(wx.Frame): self._init_controller() self.registerControllers() self.Bind(wx.EVT_SIZE, self.onResize) + self.Bind(wx.EVT_CLOSE, self.onClose) self.Bind(wx.EVT_CLOSE, lambda x: pub.send_message(str(events.WINDOW_CLOSE))) @@ -137,6 +138,18 @@ class BaseWindow(wx.Frame): def onResize(self, evt): evt.Skip() + def onClose(self, evt): + if evt.CanVeto(): + if self._controller.on_stop(): + self._controller.on_close() + else: + evt.Veto() + return + else: + self._controller.stop() + self._controller.on_close() + evt.Skip() + def PublishConsoleMsg(self, text): self.runtime_display.cmd_textbox.AppendText(text) diff --git a/gooey/gui/windows/footer.py b/gooey/gui/windows/footer.py index e22f525..b7f22a3 100644 --- a/gooey/gui/windows/footer.py +++ b/gooey/gui/windows/footer.py @@ -64,6 +64,7 @@ class AbstractFooter(wx.Panel): def running(): self.hide_all_buttons() + self.stop_button.Show() self.running_animation.Show() self.running_animation.Play() self.Layout() @@ -99,6 +100,7 @@ class AbstractFooter(wx.Panel): h_sizer.AddStretchSpacer(1) h_sizer.Add(self.cancel_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 20) h_sizer.Add(self.start_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 20) + h_sizer.Add(self.stop_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 20) v_sizer.AddStretchSpacer(1) v_sizer.Add(h_sizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) diff --git a/gooey/languages/eng.py b/gooey/languages/eng.py index c79a2c7..04ef61c 100644 --- a/gooey/languages/eng.py +++ b/gooey/languages/eng.py @@ -25,17 +25,18 @@ if __name__ == '__main__': 'required_args_msg': 'Required Arguments', 'optional_args_msg': 'Optional Arguments', # popup dialogs "sure_you_want_to_exit": "Are you sure you want to exit?", - 'close_program': 'Close Program?', + 'close_program': 'Close program?', + 'sure_you_want_to_stop': 'Are you sure you want to stop the task? ' + + '\nInterruption can corrupt your data!', + 'stop_task': 'Stop task?', 'status': 'Status', 'uh_oh': ''' -Uh oh! Looks like there was a problem. -Copy the below error to let your developer know what went wrong. - -{} - ''', +Uh oh! Looks like there was a problem. +Copy the text from status window to let your developer know what went wrong. +''', 'error_title': "Error", - 'execution_finished': 'Execution Finished', - 'success_message': 'Program completed Sucessfully!', + 'execution_finished': 'Execution finished', + 'success_message': 'Program completed sucessfully!', } diff --git a/gooey/languages/english.json b/gooey/languages/english.json index a393ee7..136190c 100644 --- a/gooey/languages/english.json +++ b/gooey/languages/english.json @@ -1,17 +1,18 @@ { - "cancel": "Cancel", - "close": "Close", - "close_program": "Close Program?", - "error_title": "Error", - "execution_finished": "Execution Finished", + "cancel": "Cancel", + "close": "Close", + "close_program": "Close program?", + "stop_task": "Stop task?", + "error_title": "Error", + "execution_finished": "Execution finished", "finished_msg": "All done! You may now safely close the program.", "finished_error": "An error has occurred.", - "finished_title": "Finished", - "optional_args_msg": "Optional Arguments", - "required_args_msg": "Required Arguments", - "running_msg": "Please wait while the application performs its tasks. \nThis may take a few moments", - "running_title": "Running", - "settings_title": "Settings", + "finished_title": "Finished", + "optional_args_msg": "Optional Arguments", + "required_args_msg": "Required Arguments", + "running_msg": "Please wait while the application performs its tasks. \nThis may take a few moments", + "running_title": "Running", + "settings_title": "Settings", "simple_config": "Enter Command Line Arguments", "error_required_fields": "Must fill in all fields in the Required section!", "start": "Start", @@ -19,8 +20,9 @@ "status": "Status", "restart": "Restart", "edit": "Edit", - "success_message": "Program completed Sucessfully!\nPress the OK button to exit", - "sure_you_want_to_exit": "Are you sure you want to exit?", - "uh_oh": "\nUh oh! Looks like there was a problem. \nCopy the below error to let your developer know what went wrong.\n\n{} \t\t\n\t\t", + "success_message": "Program completed sucessfully!", + "sure_you_want_to_exit": "Are you sure you want to exit?", + "sure_you_want_to_stop": "Are you sure you want to stop the task? \nInterruption can corrupt your data!!", + "uh_oh": "\nUh oh! Looks like there was a problem. \nCopy the text from status window to let your developer know what went wrong.\n", "browse": "Browse" } diff --git a/gooey/python_bindings/docopt_to_json.py b/gooey/python_bindings/docopt_to_json.py index fb0c763..93868d3 100644 --- a/gooey/python_bindings/docopt_to_json.py +++ b/gooey/python_bindings/docopt_to_json.py @@ -28,7 +28,7 @@ Options: # types? - +import re from docopt import docopt, Option, Argument @@ -51,7 +51,7 @@ class MyOption(Option): else: argcount = 1 if argcount: - matched = re.findall('\[default: (.*)\]', description, flags=re.I) + matched = re.findall(r'\[default: (.*)\]', description, flags=re.I) value = matched[0] if matched else None return class_(short, long, argcount, value, description=description.strip()) diff --git a/gooey/python_bindings/gooey_decorator.py b/gooey/python_bindings/gooey_decorator.py index 122d668..91f4210 100644 --- a/gooey/python_bindings/gooey_decorator.py +++ b/gooey/python_bindings/gooey_decorator.py @@ -6,6 +6,7 @@ Created on Jan 24, 2014 TODO: this ''' +import sys import os import json import atexit @@ -13,7 +14,6 @@ import tempfile from . import source_parser from . import config_generator -import sys from gooey.gui import application @@ -58,7 +58,7 @@ def Gooey(f=None, if dump_build_config: config_path = os.path.join(os.getcwd(), 'gooey_config.json') - print( 'Writing Build Config to: {}'.format(config_path)) + print 'Writing Build Config to: {}'.format(config_path) with open(config_path, 'w') as f: f.write(json.dumps(build_spec, indent=2)) application.run(build_spec) diff --git a/gooey/python_bindings/gooey_parser.py b/gooey/python_bindings/gooey_parser.py index 9fe18e8..93b0568 100644 --- a/gooey/python_bindings/gooey_parser.py +++ b/gooey/python_bindings/gooey_parser.py @@ -1,4 +1,5 @@ from argparse import ArgumentParser, _SubParsersAction +from gooey.gui.lang.i18n import _ class GooeySubParser(_SubParsersAction):