Browse Source

Elapsed / Remaining Time on Progress Bar

parent 00e0f3e7c6
author Jack McKew <jackmckew2@gmail.com> 1594344614 +1000
committer Jack McKew <jackmckew2@gmail.com> 1594346638 +1000

Elapsed / Remaining Time on Progress Bar

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Fizban <jackmckew2@gmail.com> 1593182730 +1000
committer Fizban <jackmckew2@gmail.com> 1593246420 +1000

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Fizban <jackmckew2@gmail.com> 1593182730 +1000
committer Fizban <jackmckew2@gmail.com> 1593246359 +1000

Elapsed / Remaining Time on Progress Bar

Elapsed / Remaining Time on Progress Bar

Time Remaining Text for Progress

Time remaining somewhat working

Time format working

Add tests and pythn 2.7 compat

Incase python 2 import perf counter in func

Fix flickering text and align

Remove transactUI

Add new decorators

Update time remaining notes in README

Amend tests with new arguments

Remove unused import

Separate into time module

Move to dictionary structure options

Explicitly show time text

Amend tests for separate module

Integration test - missing on complete tests

Remove old code

Elapsed / Remaining Time on Progress Bar

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Fizban <jackmckew2@gmail.com> 1593182730 +1000
committer Fizban <jackmckew2@gmail.com> 1593246420 +1000

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Fizban <jackmckew2@gmail.com> 1593182730 +1000
committer Fizban <jackmckew2@gmail.com> 1593246359 +1000

Elapsed / Remaining Time on Progress Bar

Elapsed / Remaining Time on Progress Bar

Time Remaining Text for Progress

Time remaining somewhat working

Time format working

Add tests and pythn 2.7 compat

Incase python 2 import perf counter in func

Fix flickering text and align

Remove transactUI

Add new decorators

Update time remaining notes in README

Amend tests with new arguments

Remove unused import

Separate into time module

Move to dictionary structure options

Explicitly show time text

Amend tests for separate module

Remove old code

Pass testdata as dict instead of kwargs

Merge dictionaries for defaults

Test almost working

Delete settings.json

Revert "Test almost working"

This reverts commit f17d50681cae664719f67a7e8cc0b1feaf1ac4c7.

Back to working state

Remove unhelpful test

Update docs

Working without updated test

Add tests for time remaining text

Elapsed / Remaining Time on Progress Bar

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Jack McKew <jackmckew2@gmail.com> 1594344614 +1000
committer Jack McKew <jackmckew2@gmail.com> 1594346638 +1000

Elapsed / Remaining Time on Progress Bar

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Fizban <jackmckew2@gmail.com> 1593182730 +1000
committer Fizban <jackmckew2@gmail.com> 1593246420 +1000

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Fizban <jackmckew2@gmail.com> 1593182730 +1000
committer Fizban <jackmckew2@gmail.com> 1593246359 +1000

Elapsed / Remaining Time on Progress Bar

Elapsed / Remaining Time on Progress Bar

Time Remaining Text for Progress

Time remaining somewhat working

Time format working

Add tests and pythn 2.7 compat

Incase python 2 import perf counter in func

Fix flickering text and align

Remove transactUI

Add new decorators

Update time remaining notes in README

Amend tests with new arguments

Remove unused import

Separate into time module

Move to dictionary structure options

Explicitly show time text

Amend tests for separate module

Integration test - missing on complete tests

Remove old code

Elapsed / Remaining Time on Progress Bar

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Fizban <jackmckew2@gmail.com> 1593182730 +1000
committer Fizban <jackmckew2@gmail.com> 1593246420 +1000

parent 00e0f3e7c6434240ffe02522a0be2daf8c5b456c
author Fizban <jackmckew2@gmail.com> 1593182730 +1000
committer Fizban <jackmckew2@gmail.com> 1593246359 +1000

Elapsed / Remaining Time on Progress Bar

Elapsed / Remaining Time on Progress Bar

Time Remaining Text for Progress

Time remaining somewhat working

Time format working

Add tests and pythn 2.7 compat

Incase python 2 import perf counter in func

Fix flickering text and align

Remove transactUI

Add new decorators

Update time remaining notes in README

Amend tests with new arguments

Remove unused import

Separate into time module

Move to dictionary structure options

Explicitly show time text

Amend tests for separate module

Remove old code

Pass testdata as dict instead of kwargs

Merge dictionaries for defaults

Test almost working

Delete settings.json

Revert "Test almost working"

This reverts commit f17d50681cae664719f67a7e8cc0b1feaf1ac4c7.

Back to working state

Remove unhelpful test

Update docs

Working without updated test

Add tests for time remaining text

Remove artifact from squashing

Amend artifacts

Handle no progress and tidy up footer label
1.0.5-release-candidate
Jack McKew 4 years ago
committed by Chris
parent
commit
116771edd2
12 changed files with 329 additions and 57 deletions
  1. 118
      README.md
  2. 19
      gooey/gui/components/footer.py
  3. 15
      gooey/gui/containers/application.py
  4. 1
      gooey/gui/events.py
  5. 4
      gooey/gui/processor.py
  6. 24
      gooey/gui/util/functional.py
  7. 96
      gooey/gui/util/time.py
  8. 3
      gooey/python_bindings/config_generator.py
  9. 4
      gooey/python_bindings/gooey_decorator.py
  10. 7
      gooey/tests/test_processor.py
  11. 63
      gooey/tests/test_time_remaining.py
  12. 32
      gooey/tests/test_util.py

118
README.md

@ -18,32 +18,33 @@ Table of Contents
-----------------
- [Gooey](#gooey)
- [Table of contents](#table-of-contents)
- [Latest Update](#latest-update)
- [Quick Start](#quick-start)
- [Installation Instructions](#installation-instructions)
- [Table of Contents](#table-of-contents)
- [Quick Start](#quick-start)
- [Installation instructions](#installation-instructions)
- [Usage](#usage)
- [Examples](#examples)
- [What It Is](#what-is-it)
- [Why Is It](#why)
- [Who is this for](#who-is-this-for)
- [How does it work](#how-does-it-work)
- [Internationalization](#internationalization)
- [Global Configuration](#global-configuration)
- [Layout Customization](#layout-customization)
- [Run Modes](#run-modes)
- [Full/Advanced](#advanced)
- [What is it?](#what-is-it)
- [Why?](#why)
- [Who is this for?](#who-is-this-for)
- [How does it work?](#how-does-it-work)
- [Mappings:](#mappings)
- [GooeyParser](#gooeyparser)
- [Internationalization](#internationalization)
- [Global Configuration](#global-configuration)
- [Layout Customization](#layout-customization)
- [Run Modes](#run-modes)
- [Advanced](#advanced)
- [Basic](#basic)
- [No Config](#no-config)
- [Menus](#menus)
- [Input Validation](#input-validation)
- [Using Dynamic Values](#using-dynamic-values)
- [Showing Progress](#showing-progress)
- [Customizing Icons](#customizing-icons)
- [Packaging](#packaging)
- [Screenshots](#screenshots)
- [Contributing](#wanna-help)
- [Image Credits](#image-credits)
- [Menus](#menus)
- [Input Validation](#input-validation)
- [Using Dynamic Values](#using-dynamic-values)
- [Showing Progress](#showing-progress)
- [Elapsed / Remaining Time](#elapsed--remaining-time)
- [Customizing Icons](#customizing-icons)
- [Packaging](#packaging)
- [Screenshots](#screenshots)
- [Wanna help?](#wanna-help)
@ -156,16 +157,16 @@ At run-time, it parses your Python script for all references to `ArgumentParser`
Gooey does its best to choose sensible defaults based on the options it finds. Currently, `ArgumentParser._actions` are mapped to the following `WX` components.
| Parser Action | Widget | Example |
|:----------------------|-----------|------|
| store | TextCtrl | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e9f5e-07c5-11e5-86e5-82f011c538cf.png"/>|
| store_const | CheckBox | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/>|
| store_true| CheckBox | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/>|
| store_False | CheckBox| <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/> |
| append | TextCtrl | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e9f5e-07c5-11e5-86e5-82f011c538cf.png"/> |
| count| DropDown &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f53ccbe4-07c5-11e5-80e5-510e2aa22922.png"/> |
| Mutually Exclusive Group | RadioGroup | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f553feb8-07c5-11e5-9d5b-eaa4772075a9.png"/>
|choice &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| DropDown | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e4da6-07c5-11e5-9e66-d8e6d7f18ac6.png"/> |
| Parser Action | Widget | Example |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| store | TextCtrl | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e9f5e-07c5-11e5-86e5-82f011c538cf.png"/> |
| store_const | CheckBox | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/> |
| store_true | CheckBox | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/> |
| store_False | CheckBox | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png"/> |
| append | TextCtrl | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e9f5e-07c5-11e5-86e5-82f011c538cf.png"/> |
| count | DropDown &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f53ccbe4-07c5-11e5-80e5-510e2aa22922.png"/> |
| Mutually Exclusive Group | RadioGroup | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f553feb8-07c5-11e5-9d5b-eaa4772075a9.png"/> |
| choice &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | DropDown | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e4da6-07c5-11e5-9e66-d8e6d7f18ac6.png"/> |
### GooeyParser
@ -199,14 +200,14 @@ However, by dropping in `GooeyParser` and supplying a `widget` name, you can dis
**Custom Widgets:**
| Widget | Example |
|----------------|------------------------------|
| DirChooser, FileChooser, MultiFileChooser, FileSaver, MultiFileSaver | <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f5483b28-07c5-11e5-9d01-1935635fc22d.gif" width="400"></p> |
| DateChooser/TimeChooser &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f544756a-07c5-11e5-86d6-862ac146ad35.gif" width="400"></p> <p>Please note that for both of these widgets the values passed to the application will always be in [ISO format](https://www.wxpython.org/Phoenix/docs/html/wx.DateTime.html#wx.DateTime.FormatISOTime) while localized values may appear in some parts of the GUI depending on end-user settings.</p> |
| PasswordField | <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/28953722-eae72cca-788e-11e7-8fa1-9a1ef332a053.png" width="400"></p> |
| Listbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/31590191-fadd06f2-b1c0-11e7-9a49-7cbf0c6d33d1.png) |
| BlockCheckbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/46922288-9296f200-cfbb-11e8-8b0d-ddde08064247.png) <br/> The default InlineCheck box can look less than ideal if a large help text block is present. `BlockCheckbox` moves the text block to the normal position and provides a short-form `block_label` for display next to the control. Use `gooey_options.checkbox_label` to control the label text |
| ColourChooser &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;| <p align="center"><img src="https://user-images.githubusercontent.com/21027844/72672451-0752aa80-3a0f-11ea-86ed-8303bd3e54b5.gif" width="400"></p> |
| Widget | Example |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| DirChooser, FileChooser, MultiFileChooser, FileSaver, MultiFileSaver | <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f5483b28-07c5-11e5-9d01-1935635fc22d.gif" width="400"></p> |
| DateChooser/TimeChooser &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f544756a-07c5-11e5-86d6-862ac146ad35.gif" width="400"></p> <p>Please note that for both of these widgets the values passed to the application will always be in [ISO format](https://www.wxpython.org/Phoenix/docs/html/wx.DateTime.html#wx.DateTime.FormatISOTime) while localized values may appear in some parts of the GUI depending on end-user settings.</p> |
| PasswordField | <p align="center"><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/28953722-eae72cca-788e-11e7-8fa1-9a1ef332a053.png" width="400"></p> |
| Listbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/31590191-fadd06f2-b1c0-11e7-9a49-7cbf0c6d33d1.png) |
| BlockCheckbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/46922288-9296f200-cfbb-11e8-8b0d-ddde08064247.png) <br/> The default InlineCheck box can look less than ideal if a large help text block is present. `BlockCheckbox` moves the text block to the normal position and provides a short-form `block_label` for display next to the control. Use `gooey_options.checkbox_label` to control the label text |
| ColourChooser &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | <p align="center"><img src="https://user-images.githubusercontent.com/21027844/72672451-0752aa80-3a0f-11ea-86ed-8303bd3e54b5.gif" width="400"></p> |
@ -271,6 +272,9 @@ Just about everything in Gooey's overall look and feel can be customized by pass
| progress_expr | A python expression applied to any matches found via the `progress_regex`. See: [Showing Progress](#showing-progress) for a detailed how-to |
| hide_progress_msg | Option to hide textual progress updates which match the `progress_regex`. See: [Showing Progress](#showing-progress) for a detailed how-to |
| disable_progress_bar_animation | Disable the progress bar |
| timing_options | This contains the options for displaying time remaining and elapsed time, to be used with `progress_regex` and `progress_expr`. [Elapsed / Remaining Time](#elapsed--remaining-time). Contained as a dictionary with the options `show_time_remaining` and `hide_time_remaining_on_complete`. Eg: `timing_options={'show_time_remaining':True,'hide_time_remaining_on_complete':True}` |
| show_time_remaining | Disable the time remaining text see [Elapsed / Remaining Time](#elapsed--remaining-time) |
| hide_time_remaining_on_complete | Hide time remaining on complete screen see [Elapsed / Remaining Time](#elapsed--remaining-time) |
| requires_shell | Controls whether or not the `shell` argument is used when invoking your program. [More info here](https://stackoverflow.com/questions/3172470/actual-meaning-of-shell-true-in-subprocess#3172488) |
| navigation | Sets the "navigation" style of Gooey's top level window. <br>Options: <table> <thead> <tr><th>TABBED</th><th>SIDEBAR</th></tr></thead> <tbody> <tr> <td><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464826-2a946ba2-ee47-11e7-92a4-4afeb49dc9ca.png" width="200" height="auto"></td><td><img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464847-9918fbb0-ee47-11e7-8d5f-0d42631c2bc0.png" width="200" height="auto"></td></tr></tbody></table>|
| sidebar_title | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34472159-1bfedbd0-ef10-11e7-8bc3-b6d69febb8c3.png" width="250" height="auto" align="right"> Controls the heading title above the SideBar's navigation pane. Defaults to: "Actions" |
@ -302,9 +306,9 @@ You can achieve fairly flexible layouts with Gooey by using a few simple customi
At the highest level, you have several overall layout options controllable via various arguments to the Gooey decorator.
| `show_sidebar=True` | `show_sidebar=False` | `navigation='TABBED'` | `tabbed_groups=True` |
|---------------------|----------------------|----------------------|------------------------|
|<img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464847-9918fbb0-ee47-11e7-8d5f-0d42631c2bc0.png" width="400"> |<img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/35487799-762aa308-0434-11e8-8eb3-1e9fab2d13ae.png" width="400"> |<img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464835-5ba9b0e4-ee47-11e7-9561-55e3647c2165.png" width="400"> |<img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464826-2a946ba2-ee47-11e7-92a4-4afeb49dc9ca.png" width="400"> |
| `show_sidebar=True` | `show_sidebar=False` | `navigation='TABBED'` | `tabbed_groups=True` |
| -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464847-9918fbb0-ee47-11e7-8d5f-0d42631c2bc0.png" width="400"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/35487799-762aa308-0434-11e8-8eb3-1e9fab2d13ae.png" width="400"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464835-5ba9b0e4-ee47-11e7-9561-55e3647c2165.png" width="400"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464826-2a946ba2-ee47-11e7-92a4-4afeb49dc9ca.png" width="400"> |
**Grouping Inputs**
@ -690,6 +694,22 @@ progress: 3/100
There are lots of options for telling Gooey about progress as your program is running. Checkout the [Gooey Examples](https://github.com/chriskiehl/GooeyExamples) repository for more detailed usage and examples!
### Elapsed / Remaining Time
Gooey also supports tracking elapsed / remaining time when progress is used! This is done in a similar manner to that of the project [tqdm](https://github.com/tqdm/tqdm). This can be enabled with `timing_options`, the `timing_options` argument takes in a dictionary with the keys `show_time_remaining` and `hide_time_remaining_on_complete`. The default behavior is True for `show_time_remaining` and False for `hide_time_remaining_on_complete`. This will only work when `progress_regex` and `progress_expr` are used.
```python
@Gooey(progress_regex=r"^progress: (?P<current>\d+)/(?P<total>\d+)$",
progress_expr="current / total * 100",
timing_options = {
'show_time_remaining':True,
'hide_time_remaining_on_complete':True,
})
```
![Elapsed/Remaining Time](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/gooey-estimated-finish.gif)
--------------------------------------
@ -723,13 +743,13 @@ Detailed step by step instructions can be found [here](http://chriskiehl.com/art
Screenshots
------------
| Flat Layout | Column Layout |Success Screen | Error Screen | Warning Dialog |
|-------------|---------------|---------------|--------------|----------------|
| <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/4414e54e-0965-11e5-964b-f717a7adaac6.jpg"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/4411b824-0965-11e5-905a-3a2b5df0efb3.jpg"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/44165442-0965-11e5-8edf-b8305353285f.jpg"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/4410dcce-0965-11e5-8243-c1d832c05887.jpg"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/4415432c-0965-11e5-9190-17f55460faf3.jpg"> |
| Flat Layout | Column Layout | Success Screen | Error Screen | Warning Dialog |
| ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/4414e54e-0965-11e5-964b-f717a7adaac6.jpg"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/4411b824-0965-11e5-905a-3a2b5df0efb3.jpg"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/44165442-0965-11e5-8edf-b8305353285f.jpg"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/4410dcce-0965-11e5-8243-c1d832c05887.jpg"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/4415432c-0965-11e5-9190-17f55460faf3.jpg"> |
| Custom Groups | Tabbed Groups | Tabbed Navigation | Sidebar Navigation | Input Validation |
|-------------|---------------|---------------|--------------|----------------|
| <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464824-c044d57a-ee46-11e7-9c35-6e701a7c579a.png"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464826-2a946ba2-ee47-11e7-92a4-4afeb49dc9ca.png"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464835-5ba9b0e4-ee47-11e7-9561-55e3647c2165.png"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464847-9918fbb0-ee47-11e7-8d5f-0d42631c2bc0.png"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464861-0e82c214-ee48-11e7-8f4a-a8e00721efef.png"> |
| Custom Groups | Tabbed Groups | Tabbed Navigation | Sidebar Navigation | Input Validation |
| -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464824-c044d57a-ee46-11e7-9c35-6e701a7c579a.png"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464826-2a946ba2-ee47-11e7-92a4-4afeb49dc9ca.png"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464835-5ba9b0e4-ee47-11e7-9561-55e3647c2165.png"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464847-9918fbb0-ee47-11e7-8d5f-0d42631c2bc0.png"> | <img src="https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464861-0e82c214-ee48-11e7-8f4a-a8e00721efef.png"> |

19
gooey/gui/components/footer.py

@ -18,6 +18,7 @@ class Footer(wx.Panel):
self.buildSpec = buildSpec
self.SetMinSize((30, 53))
self.SetDoubleBuffered(True)
# components
self.cancel_button = None
self.start_button = None
@ -39,6 +40,18 @@ class Footer(wx.Panel):
self.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)
def updateTimeRemaining(self,*args,**kwargs):
estimate_time_remaining = kwargs.get('estimatedRemaining')
elapsed_time_value = kwargs.get('elapsed_time')
if elapsed_time_value is None:
return
elif estimate_time_remaining is not None:
self.time_remaining_text.SetLabel(f"{elapsed_time_value}<{estimate_time_remaining}")
return
else:
self.time_remaining_text.SetLabel(f"{elapsed_time_value}")
def updateProgressBar(self, *args, **kwargs):
'''
value, disable_animation=False
@ -85,6 +98,8 @@ class Footer(wx.Panel):
self.progress_bar = wx.Gauge(self, range=100)
self.time_remaining_text = wx.StaticText(self)
self.buttons = [self.cancel_button, self.start_button,
self.stop_button, self.close_button,
self.restart_button, self.edit_button]
@ -103,7 +118,8 @@ class Footer(wx.Panel):
h_sizer.Add(self.progress_bar, 1,
wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 20)
self.progress_bar.Hide()
h_sizer.Add(self.time_remaining_text,0,wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 20)
h_sizer.AddStretchSpacer(1)
h_sizer.Add(self.cancel_button, 0,wx.RIGHT, 20)
@ -119,6 +135,7 @@ class Footer(wx.Panel):
self.edit_button.Hide()
self.restart_button.Hide()
self.close_button.Hide()
self.progress_bar.Hide()
v_sizer.AddStretchSpacer(1)
self.SetSizer(v_sizer)

15
gooey/gui/containers/application.py

@ -20,6 +20,7 @@ from gooey.gui.components.sidebar import Sidebar
from gooey.gui.components.tabbar import Tabbar
from gooey.gui.lang.i18n import _
from gooey.gui.processor import ProcessController
from gooey.gui.util.time import Timing
from gooey.gui.pubsub import pub
from gooey.gui.util import wx_util
from gooey.gui.util.wx_util import transactUI
@ -45,6 +46,7 @@ class GooeyApplication(wx.Frame):
self.footer = Footer(self, buildSpec)
self.console = Console(self, buildSpec)
self.layoutComponent()
self.timer = Timing(self)
self.clientRunner = ProcessController(
self.buildSpec.get('progress_regex'),
@ -63,6 +65,7 @@ class GooeyApplication(wx.Frame):
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)
@ -91,6 +94,7 @@ class GooeyApplication(wx.Frame):
else:
config.displayErrors()
self.Layout()
def onEdit(self):
@ -236,6 +240,7 @@ class GooeyApplication(wx.Frame):
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):
@ -246,6 +251,10 @@ class GooeyApplication(wx.Frame):
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()
@ -258,6 +267,12 @@ class GooeyApplication(wx.Frame):
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):

1
gooey/gui/events.py

@ -21,6 +21,7 @@ LIST_BOX = wx.Window.NewControlId()
CONSOLE_UPDATE = wx.Window.NewControlId()
EXECUTION_COMPLETE = wx.Window.NewControlId()
PROGRESS_UPDATE = wx.Window.NewControlId()
TIME_UPDATE = wx.Window.NewControlId()
USER_INPUT = wx.Window.NewControlId()

4
gooey/gui/processor.py

@ -13,8 +13,7 @@ from gooey.util.functional import unit, bind
class ProcessController(object):
def __init__(self, progress_regex, progress_expr, hide_progress_msg,
encoding, shell=True):
def __init__(self, progress_regex, progress_expr, hide_progress_msg,encoding, shell=True):
self._process = None
self.progress_regex = progress_regex
self.progress_expr = progress_expr
@ -69,6 +68,7 @@ class ProcessController(object):
if not line:
break
_progress = self._extract_progress(line)
pub.send_message(events.PROGRESS_UPDATE, progress=_progress)
if _progress is None or self.hide_progress_msg is False:
pub.send_message(events.CONSOLE_UPDATE,

24
gooey/gui/util/functional.py

@ -1,6 +1,30 @@
'''
Utils for functional methodologies throughout Gooey
'''
def merge_dictionaries(x,y):
"""
Merge 2 dictionaries with y taking overwriting x if a key collision is found
This is mainly useful for maintaining the dictionary arguments to allow for more expressive & extensible arguments.
https://stackoverflow.com/questions/38987/how-do-i-merge-two-dictionaries-in-a-single-expression-in-python-taking-union-o
Args:
x (dict): Input dictionary
y (dict): Input dictionary
Returns:
The combined dictionary of x & y with y taking preference on the occasion of key collision
"""
if x is None:
x = {}
if y is None:
y = {}
try:
return {**x,**y}
except:
z = x.copy()
z.update(y)
return z

96
gooey/gui/util/time.py

@ -0,0 +1,96 @@
"""
Module for evaluating time elapsed & time remaining from progress
"""
import wx
from gooey.gui.pubsub import pub
from gooey.gui import events
class Timing(object):
def __init__(self, parent):
self.startTime = 0
self.estimatedRemaining = None
self.wxTimer = wx.Timer(parent)
self.parent = parent
parent.Bind(wx.EVT_TIMER, self.publishTime, self.wxTimer)
pub.subscribe(events.PROGRESS_UPDATE, self._updateEstimate)
def _updateEstimate(self, *args, **kwargs):
prog = kwargs.get('progress')
if(not prog):
self.estimatedRemaining = None
return
if(prog > 0):
self.estimatedRemaining = estimate_time_remaining(prog,self.startTime)
def publishTime(self, *args, **kwargs):
pub.send_message(
events.TIME_UPDATE,
start=self.startTime,
current=get_current_time(),
elapsed_time=format_interval(get_elapsed_time(self.startTime)),
estimatedRemaining=format_interval(self.estimatedRemaining))
def start(self):
self.startTime = get_current_time()
self.estimatedRemaining = None
self.wxTimer.Start()
def stop(self):
self.wxTimer.Stop()
def format_interval(timeValue):
"""
Formats a number of seconds as a clock time, [H:]MM:SS
Parameters
----------
t : int
Number of seconds.
Returns
-------
out : str
[H:]MM:SS
"""
# https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L228
try:
mins, s = divmod(int(timeValue), 60)
h, m = divmod(mins, 60)
if h:
return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s)
else:
return '{0:02d}:{1:02d}'.format(m, s)
except:
return None
def get_elapsed_time(startTime):
"""
Get elapsed time in form of seconds. Provide a start time in seconds as float.
Args:
startTime (float): Start time to compare against in seconds.
Returns:
float: Time between start time and now
"""
return get_current_time() - startTime
def estimate_time_remaining(progress,startTime):
# https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L392
# https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L417
_rate = progress / get_elapsed_time(startTime)
return ((100 - progress) / _rate)
def get_current_time():
"""
Returns a float of the current time in seconds. Attempt to import perf_counter (more accurate in 3.4+), otherwise utilise timeit.
Returns:
float: Current time in seconds from performance counter.
"""
try:
from time import perf_counter
return perf_counter()
except:
import timeit
return timeit.default_timer()

3
gooey/python_bindings/config_generator.py

@ -5,6 +5,8 @@ import textwrap
from gooey.python_bindings import argparse_to_json
from gooey.gui.util.quoting import quote
from gooey.python_bindings import constants
from gooey.python_bindings import gooey_decorator
from gooey.gui.util.functional import merge_dictionaries
default_layout = {
'widgets': [{
@ -69,6 +71,7 @@ def create_from_parser(parser, source_path, **kwargs):
'progress_regex': kwargs.get('progress_regex'),
'progress_expr': kwargs.get('progress_expr'),
'hide_progress_msg': kwargs.get('hide_progress_msg', False),
'timing_options': merge_dictionaries(gooey_decorator.defaults.get('timing_options'),kwargs.get('timing_options')),
'disable_progress_bar_animation': kwargs.get('disable_progress_bar_animation'),
'disable_stop_button': kwargs.get('disable_stop_button'),

4
gooey/python_bindings/gooey_decorator.py

@ -48,6 +48,10 @@ defaults = {
'navigation': 'SIDEBAR', # TODO: add this to the docs
'tabbed_groups': False,
'use_cmd_args': False,
'timing_options': {
'show_time_remaining': False,
'hide_time_remaining_on_complete': True
}
}
# TODO: kwargs all the things

7
gooey/tests/test_processor.py

@ -15,7 +15,6 @@ class TestProcessor(unittest.TestCase):
processor = ProcessController(r"total: (\d+)%$", None, False, 'utf-8')
self.assertEqual(processor._extract_progress(b'my cool total: 100%'), 100)
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%'))
@ -29,14 +28,12 @@ class TestProcessor(unittest.TestCase):
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, 'utf-8')
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)
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, 'utf-8')
processor = ProcessController(regex, r'x[0] *^/* x[1]', False, False,'utf-8')
match = re.search(regex, '50/50')
self.assertIsNone(processor._eval_progress(match))

63
gooey/tests/test_time_remaining.py

@ -0,0 +1,63 @@
import time
import unittest
from argparse import ArgumentParser
from itertools import *
from tests.harness import instrumentGooey
class TestFooterTimeRemaining(unittest.TestCase):
def make_parser(self):
parser = ArgumentParser(description='description')
return parser
def test_time_remaining_visibility(self):
for testdata in self.testcases():
with self.subTest(testdata):
with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, gooeyApp):
gooeyApp.showConsole()
footer = gooeyApp.footer
self.assertEqual(
footer.time_remaining_text.Shown,
testdata.get('show_time_remaining',False)
)
def test_time_remaining_visibility_on_complete(self):
for testdata in self.testcases():
with self.subTest(testdata):
with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, gooeyApp):
gooeyApp.showComplete()
footer = gooeyApp.footer
if not testdata.get('show_time_remaining') and testdata:
self.assertEqual(
footer.time_remaining_text.Shown,
testdata.get('hide_time_remaining_on_complete',True)
)
else:
return True
def testcases(self):
"""
Generate a powerset of all possible combinations of
the header parameters (empty, some present, all present, all combos)
"""
iterable = product(['show_time_remaining', 'hide_time_remaining_on_complete'], [True, False])
allCombinations = list(powerset(iterable))
return [{k: v for k,v in args}
for args in allCombinations]
def powerset(iterable):
"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)"
s = list(iterable)
return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))
if __name__ == '__main__':
unittest.main()

32
gooey/tests/test_util.py

@ -0,0 +1,32 @@
import re
import unittest
from gooey.gui.util.time import get_current_time,get_elapsed_time,estimate_time_remaining,format_interval
class TestTimeUtil(unittest.TestCase):
def test_time_elapsed(self):
# Check that time elapsed is greater than zero
_start_time = get_current_time()
elapsed = get_elapsed_time(_start_time)
self.assertGreater(elapsed,0)
def test_time_remaining(self):
# Check that time elapsed is greater than zero
_start_time = get_current_time()
remaining = estimate_time_remaining(30,_start_time)
self.assertGreater(remaining,0)
def test_current_time(self):
# Test that current time is greater than zero
_start_time = get_current_time()
self.assertGreater(_start_time,0)
def test_format_interval(self):
# Test same as TQDM https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/tests/tests_tqdm.py#L234
# but in unittest form
self.assertEqual(format_interval(60), '01:00')
self.assertEqual(format_interval(6160), '1:42:40')
self.assertEqual(format_interval(238113), '66:08:33')
Loading…
Cancel
Save