From 369b7898b0ad864b64ba6c0205a51802e575c5c2 Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Fri, 23 Oct 2015 18:08:59 +0300 Subject: [PATCH 01/10] 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): From b40bc521608bca9ea74eb878e9258649c78ae133 Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Fri, 23 Oct 2015 18:14:25 +0300 Subject: [PATCH 02/10] implement progress bar --- gooey/gui/controller.py | 35 +++++++++++++++++++++++ gooey/gui/windows/base_window.py | 7 +++++ gooey/gui/windows/footer.py | 19 ++++++------ gooey/python_bindings/config_generator.py | 4 ++- gooey/python_bindings/gooey_decorator.py | 4 ++- 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/gooey/gui/controller.py b/gooey/gui/controller.py index 2262ea3..c9905d3 100644 --- a/gooey/gui/controller.py +++ b/gooey/gui/controller.py @@ -6,6 +6,7 @@ Created on Dec 22, 2013 import wx import os +import re import sys import subprocess @@ -113,8 +114,42 @@ class Controller(object): if not line: break wx.CallAfter(self.core_gui.PublishConsoleMsg, line) + progress = self.progress_from_line(line) + if progress is not None: + wx.CallAfter(self.core_gui.UpdateProgressBar, progress) wx.CallAfter(callback, process) + def progress_from_line(self, text): + progress_regex = self.build_spec['progress_regex'] + if not progress_regex: + return None + match = re.search(progress_regex, text.strip()) + if not match: + return None + progress_expr = self.build_spec['progress_expr'] + if progress_expr: + def safe_float(x): + try: + return float(x) + except ValueError: + return x + eval_locals = {k: safe_float(v) for k, v in match.groupdict().items()} + if "x" not in eval_locals: + eval_locals["x"] = [safe_float(x) for x in match.groups()] + try: + value = eval(progress_expr, {}, eval_locals) + except: + return None + else: + try: + value = match.group(1) + except IndexError: + return None + try: + return int(value) + except ValueError: + return None + def process_result(self, process): _stdout, _ = process.communicate() if process.returncode == 0: diff --git a/gooey/gui/windows/base_window.py b/gooey/gui/windows/base_window.py index 0b7f14e..34872ab 100644 --- a/gooey/gui/windows/base_window.py +++ b/gooey/gui/windows/base_window.py @@ -153,6 +153,13 @@ class BaseWindow(wx.Frame): def PublishConsoleMsg(self, text): self.runtime_display.cmd_textbox.AppendText(text) + def UpdateProgressBar(self, value): + pb = self.foot_panel.progress_bar + if value < 0: + pb.Pulse() + else: + pb.SetValue(min(value, pb.GetRange())) + if __name__ == '__main__': pass diff --git a/gooey/gui/windows/footer.py b/gooey/gui/windows/footer.py index b7f22a3..d32c8d3 100644 --- a/gooey/gui/windows/footer.py +++ b/gooey/gui/windows/footer.py @@ -10,7 +10,7 @@ import wx.animate from gooey.gui.pubsub import pub from gooey.gui.lang import i18n -from gooey.gui import imageutil, image_repository, events +from gooey.gui import imageutil, events class AbstractFooter(wx.Panel): @@ -27,7 +27,7 @@ class AbstractFooter(wx.Panel): # components self.cancel_button = None self.start_button = None - self.running_animation = None + self.progress_bar = None self.close_button = None self.stop_button = None self.restart_button = None @@ -50,7 +50,7 @@ class AbstractFooter(wx.Panel): self.restart_button = self.button(i18n._('restart'), wx.ID_OK, event_id=int(events.WINDOW_RESTART)) self.edit_button = self.button(i18n._('edit'), wx.ID_OK, event_id=int(events.WINDOW_EDIT)) - self.running_animation = wx.animate.GIFAnimationCtrl(self, -1, image_repository.loader) + self.progress_bar = wx.Gauge(self, range=100) self.buttons = [self.cancel_button, self.start_button, self.stop_button, self.close_button, self.restart_button, self.edit_button] @@ -59,20 +59,18 @@ class AbstractFooter(wx.Panel): self.hide_all_buttons() self.cancel_button.Show() self.start_button.Show() - self.running_animation.Stop() self.Layout() def running(): self.hide_all_buttons() self.stop_button.Show() - self.running_animation.Show() - self.running_animation.Play() + self.progress_bar.Show() + self.progress_bar.Pulse() self.Layout() def success(): self.hide_all_buttons() - self.running_animation.Stop() - self.running_animation.Hide() + self.progress_bar.Hide() self.edit_button.Show() self.restart_button.Show() self.close_button.Show() @@ -97,6 +95,9 @@ class AbstractFooter(wx.Panel): v_sizer = wx.BoxSizer(wx.VERTICAL) h_sizer = wx.BoxSizer(wx.HORIZONTAL) + h_sizer.Add(self.progress_bar, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 20) + self.progress_bar.Hide() + 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) @@ -104,8 +105,6 @@ class AbstractFooter(wx.Panel): v_sizer.AddStretchSpacer(1) v_sizer.Add(h_sizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.EXPAND) - v_sizer.Add(self.running_animation, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.RIGHT, 20) - self.running_animation.Hide() h_sizer.Add(self.edit_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10) h_sizer.Add(self.restart_button, 0, wx.ALIGN_RIGHT | wx.RIGHT, 10) diff --git a/gooey/python_bindings/config_generator.py b/gooey/python_bindings/config_generator.py index fc61514..4a51daa 100644 --- a/gooey/python_bindings/config_generator.py +++ b/gooey/python_bindings/config_generator.py @@ -25,7 +25,9 @@ def create_from_parser(parser, source_path, **kwargs): 'num_optional_cols': kwargs.get('optional_cols', 3), 'manual_start': False, 'layout_type': 'column', - 'monospace_display': kwargs.get('monospace_display', False) + 'monospace_display': kwargs.get('monospace_display', False), + 'progress_regex': kwargs.get('progress_regex'), + 'progress_expr': kwargs.get('progress_expr'), } if show_config: diff --git a/gooey/python_bindings/gooey_decorator.py b/gooey/python_bindings/gooey_decorator.py index 91f4210..73405b5 100644 --- a/gooey/python_bindings/gooey_decorator.py +++ b/gooey/python_bindings/gooey_decorator.py @@ -33,7 +33,9 @@ def Gooey(f=None, optional_cols=2, dump_build_config=False, load_build_config=None, - monospace_display=False): + monospace_display=False, + progress_regex=None, + progress_expr=None): ''' Decorator for client code's main function. Serializes argparse data to JSON for use with the Gooey front end From ee1ed8abacaeba24709e82a8c2e89e07953f6040 Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Fri, 23 Oct 2015 18:37:01 +0300 Subject: [PATCH 03/10] add progress bar examples --- gooey/_tmp/example_progress_bar_1.py | 23 +++++++++++++++++++++++ gooey/_tmp/example_progress_bar_2.py | 26 ++++++++++++++++++++++++++ gooey/_tmp/example_progress_bar_3.py | 26 ++++++++++++++++++++++++++ gooey/languages/english.json | 2 +- 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 gooey/_tmp/example_progress_bar_1.py create mode 100644 gooey/_tmp/example_progress_bar_2.py create mode 100644 gooey/_tmp/example_progress_bar_3.py diff --git a/gooey/_tmp/example_progress_bar_1.py b/gooey/_tmp/example_progress_bar_1.py new file mode 100644 index 0000000..7969ea3 --- /dev/null +++ b/gooey/_tmp/example_progress_bar_1.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals +from __future__ import print_function +import sys +from time import sleep +from gooey import Gooey, GooeyParser + + +@Gooey(progress_regex=r"^progress: (\d+)%$") +def main(): + parser = GooeyParser(prog="example_progress_bar_1") + _ = parser.parse_args(sys.argv[1:]) + + for i in range(100): + print("progress: {}%".format(i+1)) + sys.stdout.flush() + sleep(0.1) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/gooey/_tmp/example_progress_bar_2.py b/gooey/_tmp/example_progress_bar_2.py new file mode 100644 index 0000000..30e0b60 --- /dev/null +++ b/gooey/_tmp/example_progress_bar_2.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals +from __future__ import print_function +import sys +from time import sleep +from gooey import Gooey, GooeyParser + + +@Gooey(progress_regex=r"^progress: (\d+)/(\d+)$", + progress_expr="x[0] / x[1] * 100") +def main(): + parser = GooeyParser(prog="example_progress_bar_2") + parser.add_argument("steps", type=int, default=15) + parser.add_argument("delay", type=int, default=1) + args = parser.parse_args(sys.argv[1:]) + + for i in range(args.steps): + print("progress: {}/{}".format(i+1, args.steps)) + sys.stdout.flush() + sleep(args.delay) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/gooey/_tmp/example_progress_bar_3.py b/gooey/_tmp/example_progress_bar_3.py new file mode 100644 index 0000000..e3d062f --- /dev/null +++ b/gooey/_tmp/example_progress_bar_3.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals +from __future__ import print_function +import sys +from time import sleep +from gooey import Gooey, GooeyParser + + +@Gooey(progress_regex=r"^progress: (?P\d+)/(?P\d+)$", + progress_expr="current / total * 100") +def main(): + parser = GooeyParser(prog="example_progress_bar_3") + parser.add_argument("steps", type=int, default=15) + parser.add_argument("delay", type=int, default=1) + args = parser.parse_args(sys.argv[1:]) + + for i in range(args.steps): + print("progress: {}/{}".format(i+1, args.steps)) + sys.stdout.flush() + sleep(args.delay) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/gooey/languages/english.json b/gooey/languages/english.json index 136190c..a389ca2 100644 --- a/gooey/languages/english.json +++ b/gooey/languages/english.json @@ -22,7 +22,7 @@ "edit": "Edit", "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!!", + "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" } From 254dbd1a6bfaf6ea4bef52ab45990f7fe909009f Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Sat, 31 Oct 2015 19:45:28 +0300 Subject: [PATCH 04/10] add option to disable progress bar animation on Windows 7 --- gooey/_tmp/example_progress_bar_2.py | 3 +- gooey/_tmp/example_progress_bar_4.py | 39 +++++++++++++++++++++++ gooey/gui/windows/base_window.py | 14 +++++++- gooey/python_bindings/config_generator.py | 1 + gooey/python_bindings/gooey_decorator.py | 3 +- 5 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 gooey/_tmp/example_progress_bar_4.py diff --git a/gooey/_tmp/example_progress_bar_2.py b/gooey/_tmp/example_progress_bar_2.py index 30e0b60..ae70555 100644 --- a/gooey/_tmp/example_progress_bar_2.py +++ b/gooey/_tmp/example_progress_bar_2.py @@ -9,7 +9,8 @@ from gooey import Gooey, GooeyParser @Gooey(progress_regex=r"^progress: (\d+)/(\d+)$", - progress_expr="x[0] / x[1] * 100") + progress_expr="x[0] / x[1] * 100", + progress_animation=False) def main(): parser = GooeyParser(prog="example_progress_bar_2") parser.add_argument("steps", type=int, default=15) diff --git a/gooey/_tmp/example_progress_bar_4.py b/gooey/_tmp/example_progress_bar_4.py new file mode 100644 index 0000000..c322ffa --- /dev/null +++ b/gooey/_tmp/example_progress_bar_4.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals +from __future__ import print_function +import sys +from time import sleep +from gooey import Gooey, GooeyParser + + +@Gooey(progress_regex=r"^progress: (-?\d+)%$", + progress_animation=False) +def main(): + parser = GooeyParser(prog="example_progress_bar_1") + _ = parser.parse_args(sys.argv[1:]) + + print("Step 1") + + for i in range(1, 101): + print("progress: {}%".format(i)) + sys.stdout.flush() + sleep(0.05) + + print("Step 2") + + print("progress: -1%") # pulse + sys.stdout.flush() + sleep(3) + + print("Step 3") + + for i in range(1, 101): + print("progress: {}%".format(i)) + sys.stdout.flush() + sleep(0.05) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/gooey/gui/windows/base_window.py b/gooey/gui/windows/base_window.py index 34872ab..647e241 100644 --- a/gooey/gui/windows/base_window.py +++ b/gooey/gui/windows/base_window.py @@ -3,6 +3,8 @@ Created on Jan 19, 2014 @author: Chris ''' +import sys + import wx from gooey.gui.pubsub import pub @@ -158,7 +160,17 @@ class BaseWindow(wx.Frame): if value < 0: pb.Pulse() else: - pb.SetValue(min(value, pb.GetRange())) + value = min(int(value), pb.GetRange()) + if pb.GetValue() != value: + # Windows 7 progress bar animation hack + if not self.build_spec["progress_animation"] \ + and sys.platform.startswith("win"): + if pb.GetRange() == value: + pb.SetValue(value) + pb.SetValue(value-1) + else: + pb.SetValue(value+1) + pb.SetValue(value) if __name__ == '__main__': diff --git a/gooey/python_bindings/config_generator.py b/gooey/python_bindings/config_generator.py index 4a51daa..06d8e93 100644 --- a/gooey/python_bindings/config_generator.py +++ b/gooey/python_bindings/config_generator.py @@ -28,6 +28,7 @@ def create_from_parser(parser, source_path, **kwargs): 'monospace_display': kwargs.get('monospace_display', False), 'progress_regex': kwargs.get('progress_regex'), 'progress_expr': kwargs.get('progress_expr'), + 'progress_animation': kwargs.get('progress_animation'), } if show_config: diff --git a/gooey/python_bindings/gooey_decorator.py b/gooey/python_bindings/gooey_decorator.py index 73405b5..f048459 100644 --- a/gooey/python_bindings/gooey_decorator.py +++ b/gooey/python_bindings/gooey_decorator.py @@ -35,7 +35,8 @@ def Gooey(f=None, load_build_config=None, monospace_display=False, progress_regex=None, - progress_expr=None): + progress_expr=None, + progress_animation=True): ''' Decorator for client code's main function. Serializes argparse data to JSON for use with the Gooey front end From 1095dca00eb91a27a8e034496c11c42d80e69c2b Mon Sep 17 00:00:00 2001 From: Shura1oplot Date: Sun, 8 Nov 2015 12:09:23 +0300 Subject: [PATCH 05/10] revert "make quoting implicit" --- gooey/gui/util/quoting.py | 5 ++++ gooey/gui/widgets/components.py | 1 - gooey/gui/widgets/widget_pack.py | 39 ++++++++++++-------------------- gooey/gui/windows/layouts.py | 2 +- 4 files changed, 20 insertions(+), 27 deletions(-) diff --git a/gooey/gui/util/quoting.py b/gooey/gui/util/quoting.py index ae6710b..ab845be 100644 --- a/gooey/gui/util/quoting.py +++ b/gooey/gui/util/quoting.py @@ -1,4 +1,5 @@ import sys +import re if sys.platform.startswith("win"): @@ -7,3 +8,7 @@ if sys.platform.startswith("win"): else: # POSIX shell def quote(value): return "'{}'".format('{}'.format(value).replace("'", "'\\''")) + + +def maybe_quote(string): + return '"{}"'.format(string) if not re.match(r'^".*"$', string) else string diff --git a/gooey/gui/widgets/components.py b/gooey/gui/widgets/components.py index 6f09999..95deb3b 100644 --- a/gooey/gui/widgets/components.py +++ b/gooey/gui/widgets/components.py @@ -234,7 +234,6 @@ DirChooser = lambda data: BaseGuiComponent(data=data, widget_pack=widget_ FileSaver = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.FileSaverPayload()) DateChooser = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.DateChooserPayload()) TextField = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.TextInputPayload()) -CommandField = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.TextInputPayload(no_qouting=True)) Dropdown = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.DropdownPayload()) Counter = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.CounterPayload()) MultiDirChooser = lambda data: BaseGuiComponent(data=data, widget_pack=widget_pack.MultiDirChooserPayload()) diff --git a/gooey/gui/widgets/widget_pack.py b/gooey/gui/widgets/widget_pack.py index df837de..232ae6f 100644 --- a/gooey/gui/widgets/widget_pack.py +++ b/gooey/gui/widgets/widget_pack.py @@ -1,7 +1,8 @@ from functools import partial +import re from gooey.gui.lang import i18n from gooey.gui.util.filedrop import FileDrop -from gooey.gui.util.quoting import quote +from gooey.gui.util.quoting import maybe_quote __author__ = 'Chris' @@ -68,9 +69,9 @@ class BaseChooser(WidgetPack): def getValue(self): value = self.text_box.GetValue() if self.option_string and value: - return '{0} {1}'.format(self.option_string, quote(value)) + return '{0} {1}'.format(self.option_string, maybe_quote(value)) else: - return quote(value) if value else '' + return maybe_quote(value) if value else '' def onButton(self, evt): raise NotImplementedError @@ -85,13 +86,6 @@ class BaseFileChooser(BaseChooser): BaseChooser.__init__(self) self.dialog = dialog - def getValue(self): - value = ' '.join(quote(x) for x in self.text_box.GetValue().split(os.pathsep)) - if self.option_string and value: - return '{} {}'.format(self.option_string, value) - else: - return value or '' - def onButton(self, evt): dlg = self.dialog(self.parent) result = (self.get_path(dlg) @@ -102,9 +96,10 @@ class BaseFileChooser(BaseChooser): def get_path(self, dlg): if isinstance(dlg, wx.DirDialog) or isinstance(dlg, CalendarDlg): - return dlg.GetPath() + return maybe_quote(dlg.GetPath()) else: - return os.pathsep.join(dlg.GetPaths()) + paths = dlg.GetPaths() + return maybe_quote(paths[0]) if len(paths) < 2 else ' '.join(map(maybe_quote, paths)) class MyMultiDirChooser(MDD.MultiDirDialog): def __init(self, *args, **kwargs): @@ -128,10 +123,9 @@ DateChooserPayload = partial(BaseFileChooser, dialog=CalendarDlg) MultiDirChooserPayload = partial(BaseFileChooser, dialog=lambda parent: MyMultiDirChooser(parent, title="Select Directories", defaultPath=os.getcwd(), agwStyle=MDD.DD_MULTIPLE|MDD.DD_DIR_MUST_EXIST)) class TextInputPayload(WidgetPack): - def __init__(self, no_quoting=False): + def __init__(self): self.widget = None self.option_string = None - self.no_quoting = no_quoting def build(self, parent, data): self.option_string = self.get_command(data) @@ -144,15 +138,11 @@ class TextInputPayload(WidgetPack): return self.widget def getValue(self): - if self.no_quoting: - _quote = lambda value: value - else: - _quote = lambda value: quote(value) value = self.widget.GetValue() if value and self.option_string: - return '{} {}'.format(self.option_string, _quote(value)) + return '{} {}'.format(self.option_string, value) else: - return _quote(value) if value else '' + return '"{}"'.format(value) if value else '' def _SetValue(self, text): # used for testing @@ -178,13 +168,12 @@ class DropdownPayload(WidgetPack): return self.widget def getValue(self): - value = self.widget.GetValue() - if value == self.default_value: + if self.widget.GetValue() == self.default_value: return '' - elif value and self.option_string: - return '{} {}'.format(self.option_string, quote(value)) + elif self.widget.GetValue() and self.option_string: + return '{} {}'.format(self.option_string, self.widget.GetValue()) else: - return quote(value) if value else '' + return self.widget.GetValue() def _SetValue(self, text): # used for testing diff --git a/gooey/gui/windows/layouts.py b/gooey/gui/windows/layouts.py index d528141..67393fe 100644 --- a/gooey/gui/windows/layouts.py +++ b/gooey/gui/windows/layouts.py @@ -9,7 +9,7 @@ from gooey.gui.util import wx_util basic_config = { 'required': [{ - 'type': 'CommandField', + 'type': 'TextField', 'data': { 'display_name': 'Enter Commands', 'help': 'Enter command line arguments', From 39d804fd2847380cd3c6a2cd0c9e79d7d9d980a3 Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Sat, 19 Dec 2015 16:27:31 +0300 Subject: [PATCH 06/10] option to disable stop button --- gooey/_tmp/example_progress_bar_2.py | 2 +- gooey/_tmp/example_progress_bar_4.py | 2 +- gooey/gui/windows/base_window.py | 9 ++++++++- gooey/python_bindings/config_generator.py | 3 ++- gooey/python_bindings/gooey_decorator.py | 3 ++- 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/gooey/_tmp/example_progress_bar_2.py b/gooey/_tmp/example_progress_bar_2.py index ae70555..776f8f2 100644 --- a/gooey/_tmp/example_progress_bar_2.py +++ b/gooey/_tmp/example_progress_bar_2.py @@ -10,7 +10,7 @@ from gooey import Gooey, GooeyParser @Gooey(progress_regex=r"^progress: (\d+)/(\d+)$", progress_expr="x[0] / x[1] * 100", - progress_animation=False) + disable_progress_bar_animation=False) def main(): parser = GooeyParser(prog="example_progress_bar_2") parser.add_argument("steps", type=int, default=15) diff --git a/gooey/_tmp/example_progress_bar_4.py b/gooey/_tmp/example_progress_bar_4.py index c322ffa..7cd2792 100644 --- a/gooey/_tmp/example_progress_bar_4.py +++ b/gooey/_tmp/example_progress_bar_4.py @@ -9,7 +9,7 @@ from gooey import Gooey, GooeyParser @Gooey(progress_regex=r"^progress: (-?\d+)%$", - progress_animation=False) + disable_progress_bar_animation=False) def main(): parser = GooeyParser(prog="example_progress_bar_1") _ = parser.parse_args(sys.argv[1:]) diff --git a/gooey/gui/windows/base_window.py b/gooey/gui/windows/base_window.py index 647e241..732c733 100644 --- a/gooey/gui/windows/base_window.py +++ b/gooey/gui/windows/base_window.py @@ -63,6 +63,12 @@ class BaseWindow(wx.Frame): self.runtime_display = RuntimeDisplay(self, self.build_spec) self.foot_panel = footer.Footer(self) + + if self.build_spec['disable_stop_button']: + self.foot_panel.stop_button.Disable() + else: + self.foot_panel.stop_button.Enable() + self.panels = [self.head_panel, self.config_panel, self.foot_panel] def _do_layout(self): @@ -163,7 +169,8 @@ class BaseWindow(wx.Frame): value = min(int(value), pb.GetRange()) if pb.GetValue() != value: # Windows 7 progress bar animation hack - if not self.build_spec["progress_animation"] \ + # http://stackoverflow.com/questions/5332616/disabling-net-progressbar-animation-when-changing-value + if not self.build_spec["disable_progress_bar_animation"] \ and sys.platform.startswith("win"): if pb.GetRange() == value: pb.SetValue(value) diff --git a/gooey/python_bindings/config_generator.py b/gooey/python_bindings/config_generator.py index 06d8e93..1918596 100644 --- a/gooey/python_bindings/config_generator.py +++ b/gooey/python_bindings/config_generator.py @@ -28,7 +28,8 @@ def create_from_parser(parser, source_path, **kwargs): 'monospace_display': kwargs.get('monospace_display', False), 'progress_regex': kwargs.get('progress_regex'), 'progress_expr': kwargs.get('progress_expr'), - 'progress_animation': kwargs.get('progress_animation'), + 'disable_progress_bar_animation': kwargs.get('disable_progress_bar_animation'), + 'disable_stop_button': kwargs.get('disable_stop_button'), } if show_config: diff --git a/gooey/python_bindings/gooey_decorator.py b/gooey/python_bindings/gooey_decorator.py index f048459..dbe942d 100644 --- a/gooey/python_bindings/gooey_decorator.py +++ b/gooey/python_bindings/gooey_decorator.py @@ -36,7 +36,8 @@ def Gooey(f=None, monospace_display=False, progress_regex=None, progress_expr=None, - progress_animation=True): + disable_progress_bar_animation=False, + disable_stop_button=False): ''' Decorator for client code's main function. Serializes argparse data to JSON for use with the Gooey front end From 345b4bb4afc3baa4ca1bbfe596b2054cf358ecd8 Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Sat, 19 Dec 2015 17:07:13 +0300 Subject: [PATCH 07/10] fix window close button --- gooey/gui/controller.py | 12 +++++++++--- gooey/gui/windows/base_window.py | 13 ++----------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/gooey/gui/controller.py b/gooey/gui/controller.py index c9905d3..c38b56c 100644 --- a/gooey/gui/controller.py +++ b/gooey/gui/controller.py @@ -51,8 +51,9 @@ class Controller(object): pub.send_message(events.WINDOW_CHANGE, view_name=views.CONFIG_SCREEN) def on_close(self): - self.core_gui.Destroy() - sys.exit() + if self.ask_stop(): + self.core_gui.Destroy() + sys.exit() def on_restart(self): self.on_start() @@ -75,13 +76,18 @@ class Controller(object): return self.show_dialog(i18n._('error_title'), i18n._('error_required_fields'), wx.ICON_ERROR) cmd_line_args = self.core_gui.GetOptions() - command = '{0} --ignore-gooey {1}'.format(self.build_spec['target'], cmd_line_args) + command = '{} --ignore-gooey {}'.format(self.build_spec['target'], cmd_line_args) pub.send_message(events.WINDOW_CHANGE, view_name=views.RUNNING_SCREEN) self.run_client_code(command) def on_stop(self): + self.ask_stop() + + def ask_stop(self): if not self.running(): return True + if self.build_spec['disable_stop_button']: + return False msg = i18n._('sure_you_want_to_stop') dlg = wx.MessageDialog(None, msg, i18n._('stop_task'), wx.YES_NO) result = dlg.ShowModal() diff --git a/gooey/gui/windows/base_window.py b/gooey/gui/windows/base_window.py index 732c733..b03fec7 100644 --- a/gooey/gui/windows/base_window.py +++ b/gooey/gui/windows/base_window.py @@ -44,8 +44,6 @@ class BaseWindow(wx.Frame): 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))) - def _init_properties(self): self.SetTitle(self.build_spec['program_name']) self.SetSize(self.build_spec['default_size']) @@ -148,15 +146,8 @@ class BaseWindow(wx.Frame): 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() + evt.Veto() + pub.send_message(str(events.WINDOW_CLOSE)) def PublishConsoleMsg(self, text): self.runtime_display.cmd_textbox.AppendText(text) From 90eee6e2d1c6b831116859dc7a2ee0fab05f7fc7 Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Sat, 19 Dec 2015 17:08:13 +0300 Subject: [PATCH 08/10] add disable close button example --- gooey/_tmp/example_disable_stop.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 gooey/_tmp/example_disable_stop.py diff --git a/gooey/_tmp/example_disable_stop.py b/gooey/_tmp/example_disable_stop.py new file mode 100644 index 0000000..b6c12e2 --- /dev/null +++ b/gooey/_tmp/example_disable_stop.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals +from __future__ import print_function +import sys +from time import sleep +from gooey import Gooey, GooeyParser + + +@Gooey(progress_regex=r"^progress: (\d+)%$", + disable_stop_button=True) +def main(): + parser = GooeyParser(prog="example_progress_bar_1") + _ = parser.parse_args(sys.argv[1:]) + + for i in range(100): + print("progress: {}%".format(i+1)) + sys.stdout.flush() + sleep(0.1) + + +if __name__ == "__main__": + sys.exit(main()) From 463696b1f555ab90d1c0b829a8164304e2e2f1ed Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Sat, 19 Dec 2015 17:29:47 +0300 Subject: [PATCH 09/10] refactor progress_from_line() --- gooey/gui/controller.py | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/gooey/gui/controller.py b/gooey/gui/controller.py index c38b56c..c0cb18c 100644 --- a/gooey/gui/controller.py +++ b/gooey/gui/controller.py @@ -134,26 +134,28 @@ class Controller(object): return None progress_expr = self.build_spec['progress_expr'] if progress_expr: - def safe_float(x): - try: - return float(x) - except ValueError: - return x - eval_locals = {k: safe_float(v) for k, v in match.groupdict().items()} - if "x" not in eval_locals: - eval_locals["x"] = [safe_float(x) for x in match.groups()] - try: - value = eval(progress_expr, {}, eval_locals) - except: - return None + return self._eval_progress(match, progress_expr) else: + return self._search_progress(match) + + def _search_progress(self, match): + try: + return int(float(match.group(1))) + except: + return None + + def _eval_progress(self, match, eval_expr): + def safe_float(x): try: - value = match.group(1) - except IndexError: - return None + return float(x) + except ValueError: + return x + _locals = {k: safe_float(v) for k, v in match.groupdict().items()} + if "x" not in _locals: + _locals["x"] = [safe_float(x) for x in match.groups()] try: - return int(value) - except ValueError: + return int(float(eval(eval_expr, {}, _locals))) + except: return None def process_result(self, process): From 59b70a814b8a4fec903ac7c978bc4a09846e741f Mon Sep 17 00:00:00 2001 From: Alexander Gordeyev Date: Tue, 22 Dec 2015 11:08:40 +0300 Subject: [PATCH 10/10] fix disable_progress_bar_animation inverted meaning --- gooey/_tmp/example_progress_bar_2.py | 2 +- gooey/_tmp/example_progress_bar_4.py | 2 +- gooey/gui/windows/base_window.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gooey/_tmp/example_progress_bar_2.py b/gooey/_tmp/example_progress_bar_2.py index 776f8f2..6ead7e2 100644 --- a/gooey/_tmp/example_progress_bar_2.py +++ b/gooey/_tmp/example_progress_bar_2.py @@ -10,7 +10,7 @@ from gooey import Gooey, GooeyParser @Gooey(progress_regex=r"^progress: (\d+)/(\d+)$", progress_expr="x[0] / x[1] * 100", - disable_progress_bar_animation=False) + disable_progress_bar_animation=True) def main(): parser = GooeyParser(prog="example_progress_bar_2") parser.add_argument("steps", type=int, default=15) diff --git a/gooey/_tmp/example_progress_bar_4.py b/gooey/_tmp/example_progress_bar_4.py index 7cd2792..fca9b99 100644 --- a/gooey/_tmp/example_progress_bar_4.py +++ b/gooey/_tmp/example_progress_bar_4.py @@ -9,7 +9,7 @@ from gooey import Gooey, GooeyParser @Gooey(progress_regex=r"^progress: (-?\d+)%$", - disable_progress_bar_animation=False) + disable_progress_bar_animation=True) def main(): parser = GooeyParser(prog="example_progress_bar_1") _ = parser.parse_args(sys.argv[1:]) diff --git a/gooey/gui/windows/base_window.py b/gooey/gui/windows/base_window.py index b03fec7..1c05fb2 100644 --- a/gooey/gui/windows/base_window.py +++ b/gooey/gui/windows/base_window.py @@ -161,7 +161,7 @@ class BaseWindow(wx.Frame): if pb.GetValue() != value: # Windows 7 progress bar animation hack # http://stackoverflow.com/questions/5332616/disabling-net-progressbar-animation-when-changing-value - if not self.build_spec["disable_progress_bar_animation"] \ + if self.build_spec["disable_progress_bar_animation"] \ and sys.platform.startswith("win"): if pb.GetRange() == value: pb.SetValue(value)