Browse Source

Merge branch 'Shura1oplot-progress_bar'

pull/136/head
chriskiehl 9 years ago
parent
commit
d980155842
19 changed files with 319 additions and 52 deletions
  1. 24
      gooey/_tmp/example_disable_stop.py
  2. 23
      gooey/_tmp/example_progress_bar_1.py
  3. 27
      gooey/_tmp/example_progress_bar_2.py
  4. 26
      gooey/_tmp/example_progress_bar_3.py
  5. 39
      gooey/_tmp/example_progress_bar_4.py
  6. 89
      gooey/gui/controller.py
  7. 5
      gooey/gui/util/quoting.py
  8. 11
      gooey/gui/util/taskkill.py
  9. 1
      gooey/gui/widgets/components.py
  10. 2
      gooey/gui/widgets/widget_pack.py
  11. 34
      gooey/gui/windows/base_window.py
  12. 21
      gooey/gui/windows/footer.py
  13. 2
      gooey/gui/windows/layouts.py
  14. 17
      gooey/languages/eng.py
  15. 30
      gooey/languages/english.json
  16. 5
      gooey/python_bindings/config_generator.py
  17. 4
      gooey/python_bindings/docopt_to_json.py
  18. 10
      gooey/python_bindings/gooey_decorator.py
  19. 1
      gooey/python_bindings/gooey_parser.py

24
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())

23
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())

27
gooey/_tmp/example_progress_bar_2.py

@ -0,0 +1,27 @@
#!/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",
disable_progress_bar_animation=True)
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())

26
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<current>\d+)/(?P<total>\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())

39
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+)%$",
disable_progress_bar_animation=True)
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())

89
gooey/gui/controller.py

@ -5,6 +5,8 @@ Created on Dec 22, 2013
'''
import wx
import os
import re
import sys
import subprocess
@ -14,12 +16,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 +37,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)
@ -46,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()
@ -70,12 +76,41 @@ 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()
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))
@ -85,16 +120,52 @@ 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:
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:
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(float(eval(eval_expr, {}, _locals)))
except:
return None
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 +179,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)

5
gooey/gui/util/quoting.py

@ -1,5 +1,6 @@
import re
import sys
import re
if sys.platform.startswith("win"):
@ -8,3 +9,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

11
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)

1
gooey/gui/widgets/components.py

@ -243,7 +243,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())

2
gooey/gui/widgets/widget_pack.py

@ -65,7 +65,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

34
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
@ -40,8 +42,7 @@ class BaseWindow(wx.Frame):
self._init_controller()
self.registerControllers()
self.Bind(wx.EVT_SIZE, self.onResize)
self.Bind(wx.EVT_CLOSE, lambda x: pub.send_message(str(events.WINDOW_CLOSE)))
self.Bind(wx.EVT_CLOSE, self.onClose)
def _init_properties(self):
self.SetTitle(self.build_spec['program_name'])
@ -60,6 +61,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):
@ -137,9 +144,32 @@ class BaseWindow(wx.Frame):
def onResize(self, evt):
evt.Skip()
def onClose(self, evt):
if evt.CanVeto():
evt.Veto()
pub.send_message(str(events.WINDOW_CLOSE))
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:
value = min(int(value), pb.GetRange())
if pb.GetValue() != value:
# Windows 7 progress bar animation hack
# http://stackoverflow.com/questions/5332616/disabling-net-progressbar-animation-when-changing-value
if self.build_spec["disable_progress_bar_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__':
pass

21
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.loading_icon)
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,19 +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.running_animation.Show()
self.running_animation.Play()
self.stop_button.Show()
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()
@ -96,14 +95,16 @@ 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)
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)
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)

2
gooey/gui/windows/layouts.py

@ -11,7 +11,7 @@ from gooey.gui.util.quoting import quote
basic_config = {
'required': [{
'type': 'CommandField',
'type': 'TextField',
'data': {
'display_name': 'Enter Commands',
'help': 'Enter command line arguments',

17
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!',
}

30
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"
}

5
gooey/python_bindings/config_generator.py

@ -28,7 +28,10 @@ def create_from_parser(parser, source_path, **kwargs):
'monospace_display': kwargs.get('monospace_display', False),
'image_dir': kwargs.get('image_dir'),
'language_dir': kwargs.get('language_dir'),
'progress_regex': kwargs.get('progress_regex'),
'progress_expr': kwargs.get('progress_expr'),
'disable_progress_bar_animation': kwargs.get('disable_progress_bar_animation'),
'disable_stop_button': kwargs.get('disable_stop_button'),
}
if show_config:

4
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())

10
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
@ -35,7 +35,11 @@ def Gooey(f=None,
load_build_config=None,
monospace_display=False,
image_dir='default',
language_dir=get_resource_path('languages')):
language_dir=get_resource_path('languages'),
progress_regex=None,
progress_expr=None,
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
@ -60,7 +64,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)

1
gooey/python_bindings/gooey_parser.py

@ -1,4 +1,5 @@
from argparse import ArgumentParser, _SubParsersAction, _MutuallyExclusiveGroup
from gooey.gui.lang.i18n import _
class GooeySubParser(_SubParsersAction):

Loading…
Cancel
Save