[
  {
    "path": ".gitignore",
    "content": "*.py[cod]\n\n# C extensions\n*.so\n\n# Packages\n*.egg\n*.egg-info\ndist\nbuild\neggs\nparts\nvar\nsdist\ndevelop-eggs\n.installed.cfg\nlib\nlib64\n__pycache__\n\n# Installer logs\npip-log.txt\n\n# Unit test / coverage reports\n.coverage\nhtmlcov\n.tox\nnosetests.xml\n.cache\n\n# Translations\n*.mo\n\n# Mr Developer\n.mr.developer.cfg\n.project\n.pydevproject\n*.sublime*\n\n#\n.idea\n.settings\nMANIFEST\n\nresources/\ngooey/examples/\ngooey/_tmp/\n\nvenv*/\n\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"images\"]\n\tpath = images\n\turl = https://github.com/chriskiehl/GooeyImages.git\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# PLEASE STOP IGNORING THE ISSUE AND PR TEMPLATES\n\n\n## How to Contribute \n\nAll contributions are welcome! This guide will get you up to speed with the contribution process for Gooey. \n\nSome Caveats Up Front: \n\n* Opening a PR does not guarantee it will be merged \n* Feedback may take time\n* Merges may take time \n\n**--> The current release branch is [1.2.1](https://github.com/chriskiehl/Gooey/tree/1.2.1-release) <--**. All PRs should be opened against this branch. \n\n\n### Getting Started: \n\nAll bugs and non-trivial changes must have an associated [issue](https://github.com/chriskiehl/Gooey/issues/new). So, step one should be making sure that your [issue doesn't already exist](https://github.com/chriskiehl/Gooey/issues?utf8=%E2%9C%93&q=is%3Aissue). If you find a relevant issue, feel free to add a comment with any additional details or problems specific to your use case. Otherwise, open a new issue and fill out the template in its entirety. \n\nAn exception to this rule is for any \"trivial\" change such as language additions, documentation fixes, typo corrections, etc.. no issue is required for these. Just include a good description / overview in your PR. \n\n  \n### Development Overview\n\nAll development and pull requests should be made against the **current release branch**. Master is reserved for the last stable working version of the code. As such, it will often be outdated.\n\nRelease branches take the form of `{semvar}-release`. For example:\n\n* `1.0.2-release`  \n* `2.0.0-release` \n\nYou can find the current release branch by checking out the [branches page](https://github.com/chriskiehl/Gooey/branches). \n\n\n**Making Changes:**\n\n* Create a branch for your changes\n\t* Use the current release branch\n\t* Don't branch from `master`! This will cause you pain! \n\t* Ideal branch naming would reference the issue number it is resolving (e.g. `issue-xxx-enabling-cool-feature` ). \n* Group your commits into coarse feature-level chunks (preferably one) and reference the issue number in the message (e.g. `\"closes #322 - added cool feature XXX\"`)\n\t* Make your commits about One Thing. \n\t* Avoid stream of consciousness style commits as they'll just be asked to be cleaned up during code review\n* Make sure you've added tests for your feature / bug fix\n* Make sure it works on both Python 2.7 and Python 3.x (this is often overlooked!) \n* Backwards compatibility must be honored \n\n**When to PEP8:**\n\nThe vast majority of Gooey's code does _not_ follow PEP8. This is because the vast majority of Gooey's code is build on top of WxPython code, which does not follow PEP8. Everything in Gooey's core honors the general camelCase style used throughout Wx. \n\nThe exception to this rule is for everything in the `python_bindings/` package. This package holds the public API for Gooey, and thus honors PEP8. So the general rule is that if you're making a change to the public bindings: use PEP8. For all other internal Gooey code, honor the house style you find. \n\n\n\n## Pull Request Process\n\nPull Requests should be made against the **current release branch**. You can find the current release branch [here](https://github.com/chriskiehl/Gooey/branches).\n\nA good PR should hit these essentials.\n\nBasic Checklist: \n - [ ] Works on both Python 2.7 & Python 3.x \n - [ ] Commit message includes the relevant issue number\n - [ ] Pull request description contains link to relevant issue\n - [ ] Bug fix / feature has associated tests\n - [ ] README.md is updated (if relevant)\n - [ ] PR has summary of the change and links to the detailed issue.  \n\nSuper Cool Person Above and Beyond Checklist Additions:\n - [ ] A sister commit in the [Examples Repo](https://github.com/chriskiehl/GooeyExamples) was created demonstrating your new feature \n\n\n## Why is master the default branch when you don't want people submitting PRs to it? \n\nIn an ideal world, Github would give fine control over the semantics of what branches means what. I'd love to be able to say branch-xxx is for releases, branch-yyy is for staging, and branch-zzz for development. However, all we've got with tools of today is `default branch`. This default branch is what you get if you clone the library or pip install from source, and what you see when you land on the page. Personal preference is that this always give the healthiest view of the project possible. As such, the default branch tracks master, which houses the latest stable release, and mirrors what's in PyPi. \n\n\n## Code of Conduct\n\nNone. Use your best judgement. \n\n\n## Grumpy Stuff:\n\n* Please do not email me directly to ask why your PR hasn't been merged \n* Please do not email me directly to ask why your issue hasn't been addressed. \n\nThe answer will always be some stock variant of (1) I'm just _a_ guy, (2) I work on this for free (3) It's not a priority at the moment, (4) yes, I feel guilty all the time, (5) some weekends I just want to play a video game or something. \n\n[Worth a read.](https://gist.github.com/richhickey/1563cddea1002958f96e7ba9519972d9)\n\n\n\n"
  },
  {
    "path": "ISSUE_TEMPLATE.md",
    "content": "Hello There, Future Issue Creator! \n\n>README: Are you suddenly seeing errors related to Alignment flags when starting Gooey? Upgrade your gooey installation to the latest version (`pip install -U gooey`) to resolve the errors! See [this issue](https://github.com/chriskiehl/Gooey/issues/549) for additional information. \n\nFound a bug? Just a friendly heads up, _debugging it requires information from you!_ Make sure the template below is filled out in its entirety. \n\n - [ ] OS\n - [ ] Python Version \n - [ ] Gooey Version \n - [ ] Thorough description of problem \n     - [ ] Expected Behavior \n     - [ ] Actual Behavior \n - [ ] A minimal code example -- preferably copy/pastable in the issue itself (less time figuring out how to run your code == more time debugging!) \n - [ ] Screenshot (if visual quirk) \n - [ ] Anything else you may think will be helpful \n \n\nThanks! ^_^ \n \n \n\n\n\n\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\r\n\r\nCopyright (c) 2013-2017 Chris\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy of\r\nthis software and associated documentation files (the \"Software\"), to deal in\r\nthe Software without restriction, including without limitation the rights to\r\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\r\nthe Software, and to permit persons to whom the Software is furnished to do so,\r\nsubject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\r\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\r\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.md\r\ninclude LICENSE.txt\r\ninclude MANIFEST.in\r\nrecursive-include gooey/images *\r\nrecursive-include gooey/languages *\r\nrecursive-exclude * __pycache__\r\nrecursive-exclude * *.py[co]\r\n"
  },
  {
    "path": "README.md",
    "content": "# Gooey \n  \n\nTurn (almost) any Python 3 Console Program into a GUI application with one line\n\n<p align=\"center\">\n    <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/1-0-4-title-card.png\" />\n</p>\n\n\nTable of Contents\n-----------------  \n\n- [Gooey](#gooey)\n- [Table of contents](#table-of-contents)\n- [Latest Update](#latest-update)\n- [Quick Start](#quick-start)\n    - [Installation Instructions](#installation-instructions)\n    - [Usage](#usage)\n    - [Examples](#examples)\n- [What It Is](#what-is-it)\n- [Why Is It](#why)\n- [Who is this for](#who-is-this-for)\n- [How does it work](#how-does-it-work)\n- [Internationalization](#internationalization)\n- [Global Configuration](#global-configuration)\n- [Layout Customization](#layout-customization)\n- [Run Modes](#run-modes)\n    - [Full/Advanced](#advanced)\n    - [Basic](#basic)\n    - [No Config](#no-config)\n- [Menus](#menus)    \n- [Dynamic Validation](#dynamic-validation)\n- [Lifecycle Events and UI control](#lifecycle-events-and-ui-control)\n- [Showing Progress](#showing-progress)\n    - [Elapsed / Remaining Time](#elapsed--remaining-time)\n- [Customizing Icons](#customizing-icons)\n- [Packaging](#packaging)\n- [Screenshots](#screenshots)\n- [Contributing](#wanna-help)\n- [Image Credits](#image-credits)\n\n\n\n----------------  \n\n\n## Quick Start\n\n\n### Installation instructions\n\n\nThe easiest way to install Gooey is via `pip`\n\n    pip install Gooey \n\nAlternatively, you can install Gooey by cloning the project to your local directory\n\n    git clone https://github.com/chriskiehl/Gooey.git\n\nrun `setup.py` \n\n    python setup.py install\n    \n\n\n### Usage  \n\nGooey is attached to your code via a simple decorator on whichever method has your `argparse` declarations (usually `main`).\n\n    from gooey import Gooey\n\n    @Gooey      <--- all it takes! :)\n    def main():\n      parser = ArgumentParser(...)\n      # rest of code\n\nDifferent styling and functionality can be configured by passing arguments into the decorator.\n\n    # options\n    @Gooey(advanced=Boolean,          # toggle whether to show advanced config or not \n           language=language_string,  # Translations configurable via json\n           auto_start=True,           # skip config screens all together\n           target=executable_cmd,     # Explicitly set the subprocess executable arguments\n           program_name='name',       # Defaults to script name\n           program_description,       # Defaults to ArgParse Description\n           default_size=(610, 530),   # starting size of the GUI\n           required_cols=1,           # number of columns in the \"Required\" section\n           optional_cols=2,           # number of columns in the \"Optional\" section\n           dump_build_config=False,   # Dump the JSON Gooey uses to configure itself\n           load_build_config=None,    # Loads a JSON Gooey-generated configuration\n           monospace_display=False)   # Uses a mono-spaced font in the output screen\n    )\n    def main():\n      parser = ArgumentParser(...)\n      # rest of code\n            \nSee: [How does it Work](#how-does-it-work) section for details on each option.\n\nGooey will do its best to choose sensible widget defaults to display in the GUI. However, if more fine tuning is desired, you can use the drop-in replacement `GooeyParser` in place of `ArgumentParser`. This lets you control which widget displays in the GUI. See: [GooeyParser](#gooeyparser)\n\n    from gooey import Gooey, GooeyParser\n\n    @Gooey\n    def main():\n      parser = GooeyParser(description=\"My Cool GUI Program!\") \n      parser.add_argument('Filename', widget=\"FileChooser\")\n      parser.add_argument('Date', widget=\"DateChooser\")\n      ...\n\n### Examples\n\nGooey downloaded and installed? Great! Wanna see it in action? Head over the the [Examples Repository](https://github.com/chriskiehl/GooeyExamples) to download a few ready-to-go example scripts. They'll give you a quick tour of all Gooey's various layouts, widgets, and features. \n\n[Direct Download](https://github.com/chriskiehl/GooeyExamples/archive/master.zip)\n\n\n    \nWhat is it? \n-----------  \n\nGooey converts your Console Applications into end-user-friendly GUI applications. It lets you focus on building robust, configurable programs in a familiar way, all without having to worry about how it will be presented to and interacted with by your average user. \n\nWhy?\n---  \n\nBecause as much as we love the command prompt, the rest of the world looks at it like an ugly relic from the early '80s. On top of that, more often than not programs need to do more than just one thing, and that means giving options, which previously meant either building a GUI, or trying to explain how to supply arguments to a Console Application. Gooey was made to (hopefully) solve those problems. It makes programs easy to use, and pretty to look at! \n\nWho is this for?\n----------------  \n\nIf you're building utilities for yourself, other programmers, or something which produces a result that you want to capture and pipe over to another console application (e.g. *nix philosophy utils), Gooey probably isn't the tool for you. However, if you're building 'run and done,' around-the-office-style scripts, things that shovel bits from point A to point B, or simply something that's targeted at a non-programmer, Gooey is the perfect tool for the job. It lets you build as complex of an application as your heart desires all while getting the GUI side for free. \n\n\nHow does it work?\n-----------------\n\nGooey is attached to your code via a simple decorator on whichever method has your `argparse` declarations.\n\n    @Gooey\n    def my_run_func():\n      parser = ArgumentParser(...)\n      # rest of code\n\nAt run-time, it parses your Python script for all references to `ArgumentParser`. (The older `optparse` is currently not supported.) These references are then extracted, assigned a `component type` based on the `'action'` they provide, and finally used to assemble the GUI.  \n\n#### Mappings: \n\nGooey does its best to choose sensible defaults based on the options it finds. Currently, `ArgumentParser._actions` are mapped to the following `WX` components. \n\n| Parser Action    | Widget    | Example |\n|:----------------------|-----------|------|\n| store  |  TextCtrl |  <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e9f5e-07c5-11e5-86e5-82f011c538cf.png\"/>|\n| store_const | CheckBox |<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png\"/>|\n| store_true | CheckBox | <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png\"/>|\n| store_False | CheckBox|  <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png\"/>   |\n| version | CheckBox|  <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f538c850-07c5-11e5-8cbe-864badfa54a9.png\"/>   |\n| append | TextCtrl |  <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54e9f5e-07c5-11e5-86e5-82f011c538cf.png\"/>  | \n| 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\"/> | \n| Mutually Exclusive Group | RadioGroup | <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f553feb8-07c5-11e5-9d5b-eaa4772075a9.png\"/>\n|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\"/> |\n\n### GooeyParser\n\nIf the above defaults aren't cutting it, you can control the exact widget type by using the drop-in `ArgumentParser` replacement `GooeyParser`. This gives you the additional keyword argument `widget`, to which you can supply the name of the component you want to display. Best part? You don't have to change any of your `argparse` code to use it. Drop it in, and you're good to go. \n\n**Example:**\n\n    from argparse import ArgumentParser\n    ....\n    \n    def main(): \n        parser = ArgumentParser(description=\"My Cool Gooey App!\")\n        parser.add_argument('filename', help=\"name of the file to process\") \n\nGiven then above, Gooey would select a normal `TextField` as the widget type like this: \n<p align=\"center\">\n    <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f5393e20-07c5-11e5-88e9-c153fc3ecfaa.PNG\">\n</p>\n\nHowever, by dropping in `GooeyParser` and supplying a `widget` name, you can display a much more user friendly `FileChooser`\n\n\n    from gooey import GooeyParser\n    ....\n    \n    def main(): \n        parser = GooeyParser(description=\"My Cool Gooey App!\")\n        parser.add_argument('filename', help=\"name of the file to process\", widget='FileChooser') \n        \n<p align=\"center\"><img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f53ae23e-07c5-11e5-8757-c8aa6f3013b5.PNG\"></p>\n\n**Custom Widgets:**\n\n| Widget         |           Example            | \n|----------------|------------------------------| \n| 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> | \n| 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> |\n| 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> |\n| Listbox | ![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/31590191-fadd06f2-b1c0-11e7-9a49-7cbf0c6d33d1.png) |\n| 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 | \n|  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> |\n| FilterableDropdown | <p align=\"center\"><img src=\"https://raw.githubusercontent.com/chriskiehl/GooeyImages/images/readme-images/filterable-dropdown.gif\" width=\"400\"></p> |\n| IntegerField | <p align=\"center\"><img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/integer-field.PNG\" width=\"400\"></p> |\n| DecimalField | <p align=\"center\"><img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/decimal-field.PNG\" width=\"400\"></p> |\n| Slider | <p align=\"center\"><img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/slider.PNG\" width=\"400\"></p> |\n\n\n\n \n  \nInternationalization\n-------------------- \n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f52e9f1a-07c5-11e5-8f31-36a8fc14ac02.jpg\" align=\"right\" />\n\nGooey is international ready and easily ported to your host language. Languages are controlled via an argument to the `Gooey` decorator. \n\n    @Gooey(language='russian')\n    def main(): \n        ... \n\nAll program text is stored externally in `json` files. So adding new language support is as easy as pasting a few key/value pairs in the `gooey/languages/` directory. \n\nThanks to some awesome [contributors](https://github.com/chriskiehl/Gooey/graphs/contributors), Gooey currently comes pre-stocked with over 18 different translations! \n\nWant to add another one? Submit a [pull request!](https://github.com/chriskiehl/Gooey/compare)\n\n\n-------------------------------------------    \n\n\n\nGlobal Configuration \n--------------------\n\nJust about everything in Gooey's overall look and feel can be customized by passing arguments to the decorator. \n\n| Parameter | Summary | \n|-----------|---------|\n| encoding | Text encoding to use when displaying characters (default: 'utf-8') | \n| use_legacy_titles | Rewrites the default argparse group name from \"Positional\" to \"Required\". This is primarily for retaining backward compatibility with previous versions of Gooey (which had poor support/awareness of groups and did its own naive bucketing of arguments). |\n| advanced | Toggles whether to show the 'full' configuration screen, or a simplified version |\n| auto_start | Skips the configuration all together and runs the program immediately |\n| language | Tells Gooey which language set to load from the `gooey/languages` directory.|\n| target | Tells Gooey how to re-invoke itself. By default Gooey will find python, but this allows you to specify the program (and arguments if supplied).|\n| suppress_gooey_flag | Should be set when using a custom `target`. Prevent Gooey from injecting additional CLI params |\n|program_name | The name displayed in the title bar of the GUI window. If not supplied, the title defaults to the script name pulled from `sys.argv[0]`. |\n| program_description | Sets the text displayed in the top panel of the `Settings` screen. Defaults to the description pulled from `ArgumentParser`. |\n| default_size | Initial size of the window | \n| fullscreen | start Gooey in fullscreen mode |\n| required_cols | Controls how many columns are in the Required Arguments section <br> :warning: **Deprecation notice:** See [Layout Customization](https://github.com/chriskiehl/Gooey#layout-customization) for modern layout controls|\n| optional_cols | Controls how many columns are in the Optional Arguments section <br> :warning: **Deprecation notice:** See [Layout Customization](https://github.com/chriskiehl/Gooey#layout-customization) for modern layout controls|\n| dump_build_config | Saves a `json` copy of its build configuration on disk for reuse/editing | \n| load_build_config | Loads a `json` copy of its build configuration from disk | \n| monospace_display | Uses a mono-spaced font in the output screen <br> :warning: **Deprecation notice:** See [Layout Customization](https://github.com/chriskiehl/Gooey#layout-customization) for modern font configuration| \n| image_dir | Path to the directory in which Gooey should look for custom images/icons |\n| language_dir | Path to the directory in which Gooey should look for custom languages files |\n| disable_stop_button | Disable the `Stop` button when running |\n| show_stop_warning | Displays a warning modal before allowing the user to force termination of your program |\n| force_stop_is_error | Toggles whether an early termination by the shows the success or error screen |\n| show_success_modal | Toggles whether or not to show a summary modal after a successful run |\n| show_failure_modal | Toggles whether or not to show a summary modal on failure |\n| show_restart_button | Toggles whether or not to show the restart button at the end of execution |\n| run_validators | Controls whether or not to have Gooey perform validation before calling your program |\n| poll_external_updates | (Experimental!) When True, Gooey will call your code with a `gooey-seed-ui` CLI argument and use the response to fill out dynamic values in the UI (See: [Using Dynamic Values](#using-dynamic-values))|\n| use_cmd_args | Substitute any command line arguments provided at run time for the default values specified in the Gooey configuration |\n| return_to_config | When True, Gooey will return to the configuration settings window upon successful run |\n| progress_regex | A text regex used to pattern match runtime progress information. See: [Showing Progress](#showing-progress) for a detailed how-to | \n| progress_expr | A python expression applied to any matches found via the `progress_regex`. See: [Showing Progress](#showing-progress) for a detailed how-to |\n| hide_progress_msg | Option to hide textual progress updates which match the `progress_regex`. See: [Showing Progress](#showing-progress) for a detailed how-to |\n| disable_progress_bar_animation | Disable the progress bar |\n| 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}` |\n| show_time_remaining | Disable the time remaining text see [Elapsed / Remaining Time](#elapsed--remaining-time) |\n| hide_time_remaining_on_complete | Hide time remaining on complete screen see [Elapsed / Remaining Time](#elapsed--remaining-time) |\n| 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) |\n| shutdown_signal | Specifies the `signal` to send to the child process when the `stop` button is pressed. See [Gracefully Stopping](https://github.com/chriskiehl/Gooey/tree/master/docs) in the docs for more info. | \n| 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>|\n| 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\" |\n| show_sidebar | Show/Hide the sidebar in when navigation mode == `SIDEBAR` |\n| body_bg_color | HEX value of the main Gooey window |\n| header_bg_color | HEX value of the header background | \n| header_height | height in pixels of the header | \n| header_show_title | Show/Hide the header title | \n| header_show_subtitle | Show/Hide the header subtitle | \n| footer_bg_color | HEX value of the Footer background | \n| sidebar_bg_color | HEX value of the Sidebar's background | \n| terminal_panel_color | HEX value of the terminal's panel | \n| terminal_font_color | HEX value of the font displayed in Gooey's terminal | \n| terminal_font_family | Name of the Font Family to use in the terminal | \n| terminal_font_weight | Weight of the font (`constants.FONTWEIGHT_NORMAL`, `constants.FONTWEIGHT_XXX`) | \n| terminal_font_size | Point size of the font displayed in the terminal | \n| error_color | HEX value of the text displayed when a validation error occurs |\n| richtext_controls | Switch on/off the console support for terminal control sequences (limited support for font weight and color). Defaults to : False. See [docs](https://github.com/chriskiehl/Gooey/tree/master/docs) for additional details |\n| menus | Show custom menu groups and items (see: [Menus](#menus) |\n| clear_before_run | When true, previous output will be cleared from the terminal when running program again |\n\n\n\nLayout Customization\n--------------------\n\nYou can achieve fairly flexible layouts with Gooey by using a few simple customizations. \n\nAt the highest level, you have several overall layout options controllable via various arguments to the Gooey decorator.\n\n\n| `show_sidebar=True` | `show_sidebar=False` | `navigation='TABBED'` |  `tabbed_groups=True` |\n|---------------------|----------------------|----------------------|------------------------|\n|<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\"> |\n\n\n**Grouping Inputs**\n\nBy default, if you're using Argparse with Gooey, your inputs will be split into two buckets: `positional` and `optional`. However, these aren't always the most descriptive groups to present to your user. You can arbitrarily bucket inputs into logic groups and customize the layout of each. \n\nWith `argparse` this is done via `add_argument_group()`\n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/35487956-a4c9915e-0436-11e8-8a11-fd21528aedf0.png\" align=\"right\" width=\"410\">\n\n```\nparser = ArgumentParser()\nsearch_group = parser.add_argument_group(\n    \"Search Options\", \n    \"Customize the search options\"\n)\n```\n\nYou can add arguments to the group as normal \n\n```\nsearch_group.add_argument(\n    '--query', \n    help='Base search string'\n) \n```\n\nWhich will display them as part of the group within the UI. \n\n\n\n\nRun Modes\n---------\n\nGooey has a handful of presentation modes so you can tailor its layout to your content type and user's level or experience. \n\n\n\n\n### Advanced \n\n\n\n\nThe default view is the \"full\" or \"advanced\" configuration screen. It has two different layouts depending on the type of command line interface it's wrapping. For most applications, the flat layout will be the one to go with, as its layout matches best to the familiar CLI schema of a primary command followed by many options (e.g. Curl, FFMPEG). \n\nOn the other side is the Column Layout. This one is best suited for CLIs that have multiple paths or are made up of multiple little tools each with their own arguments and options (think: git). It displays the primary paths along the left column, and their corresponding arguments in the right. This is a great way to package a lot of varied functionality into a single app. \n\n<p align=\"center\">\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f06a36cc-08ad-11e5-843e-9322df96d4d6.png\">\n</p>\n\nBoth views present each action in the `Argument Parser` as a unique GUI component. It makes it ideal for presenting the program to users which are unfamiliar with command line options and/or Console Programs in general. Help messages are displayed along side each component to make it as clear as possible which each widget does.\n\n**Setting the layout style:**\n\nCurrently, the layouts can't be explicitly specified via a parameter (on the TODO!). The layouts are built depending on whether or not there are `subparsers` used in your code base. So, if you want to trigger the `Column Layout`, you'll need to add a `subparser` to your `argparse` code. \n\nIt can be toggled via the `advanced` parameter in the `Gooey` decorator. \n\n\n    @gooey(advanced=True)\n    def main():\n        # rest of code   \n        \n\n\n--------------------------------------------  \n\n\n\n### Basic  \n\nThe basic view is best for times when the user is familiar with Console Applications, but you still want to present something a little more polished than a simple terminal. The basic display is accessed by setting the `advanced` parameter in the `gooey` decorator to `False`. \n\n    @gooey(advanced=False)\n    def main():\n        # rest of code  \n\n<p align=\"center\">\n    <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f53a4306-07c5-11e5-8e63-b510d6db9953.png\">\n</p>\n\n\n----------------------------------------------  \n\n### No Config\n\nNo Config pretty much does what you'd expect: it doesn't show a configuration screen. It hops right to the `display` section and begins execution of the host program. This is the one for improving the appearance of little one-off scripts. \n\nTo use this mode, set `auto_start=True` in the Gooey decorator. \n\n```python\n@Gooey(auto_start=True) \ndef main (): \n    ... \n```\n\n<p align=\"center\">\n    <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/f54fe6f2-07c5-11e5-92e4-f72a2ae12862.png\">\n</p>\n\n\n--------------------------------------\n\n\n### Menus \n\n\n![image](https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/47250909-74782a00-d3df-11e8-88ac-182d06c4435a.png)\n\n>Added 1.0.2\n\nYou can add a Menu Bar to the top of Gooey with customized menu groups and items.\n\nMenus are specified on the main `@Gooey` decorator as a list of maps. \n\n```\n@Gooey(menu=[{}, {}, ...])\n```\n\nEach map is made up of two key/value pairs \n\n1. `name` - the name for this menu group\n2. `items` - the individual menu items within this group \n\nYou can have as many menu groups as you want. They're passed as a list to the `menu` argument on the `@Gooey` decorator.\n\n```\n@Gooey(menu=[{'name': 'File', 'items: []},\n             {'name': 'Tools', 'items': []},\n             {'name': 'Help', 'items': []}])\n```\n\nIndividual menu items in a group are also just maps of key / value pairs. Their exact key set varies based on their `type`, but two keys will always be present: \n\n* `type` - this controls the behavior that will be attached to the menu item as well as the keys it needs specified\n* `menuTitle` - the name for this MenuItem  \n\n\nCurrently, three types of menu options are supported: \n\n * AboutDialog \n * MessageDialog\n * Link\n * HtmlDialog\n \n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/47251026-9ffc1400-d3e1-11e8-9095-982a6367561b.png\" width=\"400\" height=\"auto\" align=\"right\" />\n\n**About Dialog** is your run-of-the-mill About Dialog. It displays program information such as name, version, and license info in a standard native AboutBox.\n\nSchema \n\n * `name` - (_optional_) \n * `description` - (_optional_) \n * `version` - (_optional_)  \n * `copyright` - (_optional_) \n * `license` - (_optional_)\n * `website` - (_optional_)\n * `developer` - (_optional_)\n\nExample: \n\n```\n{\n    'type': 'AboutDialog',\n    'menuTitle': 'About',\n    'name': 'Gooey Layout Demo',\n    'description': 'An example of Gooey\\'s layout flexibility',\n    'version': '1.2.1',\n    'copyright': '2018',\n    'website': 'https://github.com/chriskiehl/Gooey',\n    'developer': 'http://chriskiehl.com/',\n    'license': 'MIT'\n}\n```\n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/47250925-bbfeb600-d3df-11e8-88a8-5ba838e9466d.png\" width=\"400\" height=\"auto\" align=\"right\" />\n\n**MessageDialog** is a generic informational dialog box. You can display anything from small alerts, to long-form informational text to the user.\n\nSchema: \n\n * `message` - (_required_) the text to display in the body of the modal \n * `caption` - (_optional_) the caption in the title bar of the modal    \n\nExample: \n\n```python\n{\n    'type': 'MessageDialog',\n    'menuTitle': 'Information',\n    'message': 'Hey, here is some cool info for ya!',\n    'caption': 'Stuff you should know'\n}\n```\n\n**Link** is for sending the user to an external website. This will spawn their default browser at the URL you specify. \n\nSchema: \n\n * `url` - (_required_) - the fully qualified URL to visit\n\nExample:\n\n```python\n{\n    'type': 'Link',\n    'menuTitle': 'Visit Out Site',\n    'url': 'http://www.example.com'\n}\n```\n\n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/docs/menus/html-dialog.PNG\" width=\"400\" height=\"auto\" align=\"right\" />\n\n**HtmlDialog** gives you full control over what's displayed in the message dialog (bonus: people can copy/paste text from this one!). \n\n\n\nSchema: \n\n * `caption` - (_optional_) the caption in the title bar of the modal   \n * `html` - (_required_) the html you want displayed in the dialog. Note: only a small subset of HTML is supported. [See the WX docs for more info](https://wxpython.org/Phoenix/docs/html/html_overview.html). \n\nExample: \n\n```python\n{\n    'type': 'HtmlDialog',\n    'menuTitle': 'Fancy Dialog!',\n    'caption': 'Demo of the HtmlDialog',\n    'html': '''\n    <body bgcolor=\"white\">\n        <img src=/path/to/your/image.png\" /> \n        <h1>Hello world!</h1> \n        <p><font color=\"red\">Lorem ipsum dolor sit amet, consectetur</font></p>\n    </body>\n    '''\n}\n\n```\n\n**A full example:**\n\nTwo menu groups (\"File\" and \"Help\") with four menu items between them. \n\n```python\n@Gooey(\n    program_name='Advanced Layout Groups',\n    menu=[{\n        'name': 'File',\n        'items': [{\n                'type': 'AboutDialog',\n                'menuTitle': 'About',\n                'name': 'Gooey Layout Demo',\n                'description': 'An example of Gooey\\'s layout flexibility',\n                'version': '1.2.1',\n                'copyright': '2018',\n                'website': 'https://github.com/chriskiehl/Gooey',\n                'developer': 'http://chriskiehl.com/',\n                'license': 'MIT'\n            }, {\n                'type': 'MessageDialog',\n                'menuTitle': 'Information',\n                'caption': 'My Message',\n                'message': 'I am demoing an informational dialog!'\n            }, {\n                'type': 'Link',\n                'menuTitle': 'Visit Our Site',\n                'url': 'https://github.com/chriskiehl/Gooey'\n            }]\n        },{\n        'name': 'Help',\n        'items': [{\n            'type': 'Link',\n            'menuTitle': 'Documentation',\n            'url': 'https://www.readthedocs.com/foo'\n        }]\n    }]\n)\n```\n\n\n---------------------------------------  \n\n\n### Dynamic Validation \n\n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/34464861-0e82c214-ee48-11e7-8f4a-a8e00721efef.png\" width=\"400\" height=\"auto\" align=\"right\" />\n\n>:warning: \n>Note! This functionality is experimental and likely to be unstable. Its API may be changed or removed altogether. Feedback/thoughts on this feature is welcome and encouraged!\n \n>:warning: \n>See [Release Notes]() for guidance on upgrading from 1.0.8 to 1.2.0 \n\n\nBefore passing the user's inputs to your program, Gooey can optionally run a special pre-flight validation to check that all arguments pass your specified validations.  \n\n**How does it work?**   \n\nGooey piggy backs on the `type` parameter available to most Argparse Argument types. \n\n```python\nparser.add_argument('--some-number', type=int)\nparser.add_argument('--some-number', type=float)\n```\n\nIn addition to simple builtins like `int` and `float`, you can supply your own function to the `type` parameter to vet the incoming values. \n\n```python\ndef must_be_exactly_ten(value): \n    number = int(value) \n    if number == 10:\n        return number\n    else: \n        raise TypeError(\"Hey! you need to provide exactly the number 10!\")\n        \n        \ndef main(): \n    parser = ArgumentParser()\n    parser.add_argument('--ten', type=must_be_exactly_ten)\n```\n\n**How to enable the pre-flight validation**\n\nBy default, Gooey won't run the validation. Why? This feature is fairly experimental and does a lot of intense Monkey Patching behind the scenes. As such, it's currently opt-in. \n\nYou enable to validation by telling Gooey you'd like to subscribe to the `VALIDATE_FORM` event. \n\n```python\nfrom gooey import Gooey, Events \n\n@Gooey(use_events=[Events.VALIDATE_FORM])\ndef main(): \n    ... \n```\n\n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/dynamic-validation-1-2-0.JPG\" />\n\nNow, when you run Gooey, before it invokes your main program, it'll send a separate pre-validation check and record any issues raised from your `type` functions.  \n\n\n**Full Code Example**\n\n```\nfrom gooey import Gooey, Events\nfrom argparse import ArgumentParser\n\ndef must_be_exactly_ten(value):\n    number = int(value)\n    if number == 10:\n        return number\n    else:\n        raise TypeError(\"Hey! you need to provide exactly the number 10!\")\n\n@Gooey(program_name='Validation Example', use_events=[Events.VALIDATE_FORM])\ndef main():\n    parser = ArgumentParser(description=\"Checkout this validation!\")\n    parser.add_argument('--ten', metavar='This field should be 10', type=must_be_exactly_ten)\n    args = parser.parse_args()\n    print(args)\n```\n\n\n\n\n---------------------------------------\n  \n\n## Lifecycle Events and UI control\n\n>:warning: \n>Note! This functionality is experimental. Its API may be changed or removed altogether. Feedback on this feature is welcome and encouraged! \n\nAs of 1.2.0, Gooey now exposes coarse grain lifecycle hooks to your program. This means you can now take additional follow-up actions in response to successful runs or failures and even control the current state of the UI itself! \n\nCurrently, two primary hooks are exposed: \n\n* `on_success`\n* `on_error`\n\nThese fire exactly when you'd expect: after your process has completed. \n\n\n**Anatomy of an lifecycle handler**:\n\nBoth `on_success` and `on_error` have the same type signature. \n\n```python\nfrom typing import Mapping, Any, Optional\nfrom gooey.types import PublicGooeyState  \n\ndef on_success(args: Mapping[str, Any], state: PublicGooeyState) -> Optional[PublicGooeyState]:\n    \"\"\"\n    You can do anything you want in the handler including \n    returning an updated UI state for your next run!   \n    \"\"\" \n    return state\n    \ndef on_error(args: Mapping[str, Any], state: PublicGooeyState) -> Optional[PublicGooeyState]:\n    \"\"\"\n    You can do anything you want in the handler including \n    returning an updated UI state for your next run!   \n    \"\"\" \n    return state    \n```\n\n* **args** This is the parsed Argparse object (e.g. the output of `parse_args()`). This will be a mapping of the user's arguments as existed when your program was invoked.\n* **state** This is the current state of Gooey's UI. If your program uses subparsers, this currently just lists the state of the active parser/form. Whatever updated version of this state you return will be reflected in the UI!    \n\n\n**Attaching the handlers:**\n\nHandlers are attached when instantiating the `GooeyParser`.\n\n```python\nparser = GooeyParser(\n    on_success=my_success_handler,\n    on_failure=my_failure_handler)\n``` \n\n\n**Subscribing to the lifecycle events**\n\nJust like [Validation](#dynamic-validation), these lifecycle events are opt-in. Pass the event you'd like to subscribe to into the `use_events` Gooey decorator argument. \n\n```python\nfrom gooey import Gooey, Events \n\n@Gooey(use_events=[Events.ON_SUCCESS, Events.ON_ERROR])\ndef main(): \n    ... \n```\n\n\n\n-------------------------------------\n\n## Showing Progress\n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/readme-images/45590349-55bbda80-b8eb-11e8-9aed-b4fe377756ac.png\" align=\"right\" width=\"420\"/>\n\nGiving visual progress feedback with Gooey is easy! If you're already displaying textual progress updates, you can tell Gooey to hook into that existing output in order to power its Progress Bar. \n\nFor simple cases, output strings which resolve to a numeric representation of the completion percentage (e.g. `Progress 83%`) can be pattern matched and turned into a progress bar status with a simple regular expression (e.g. `@Gooey(progress_regex=r\"^progress: (\\d+)%$\")`). \n\nFor more complicated outputs, you can pass in a custom evaluation expression (`progress_expr`) to transform regular expression matches as needed. \n\nOutput strings which satisfy the regular expression can be hidden from the console via the `hide_progress_msg` parameter (e.g. `@Gooey(progress_regex=r\"^progress: (\\d+)%$\", hide_progress_msg=True)`.\n\n**Regex and Processing Expression**\n\n```python\n@Gooey(progress_regex=r\"^progress: (?P<current>\\d+)/(?P<total>\\d+)$\",\n       progress_expr=\"current / total * 100\")\n```\n\n**Program Output:**\n\n```\nprogress: 1/100\nprogress: 2/100\nprogress: 3/100\n...\n```\n\nThere 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! \n\n### Elapsed / Remaining Time\n\nGooey 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.\n\n```python\n@Gooey(progress_regex=r\"^progress: (?P<current>\\d+)/(?P<total>\\d+)$\",\n       progress_expr=\"current / total * 100\",\n       timing_options = {\n        'show_time_remaining':True,\n        'hide_time_remaining_on_complete':True,\n    })\n```\n\n--------------------------------------\n\n\n## Customizing Icons\n\nGooey comes with a set of six default icons. These can be overridden with your own custom images/icons by telling Gooey to search additional directories when initializing. This is done via the `image_dir` argument to the `Gooey` decorator. \n\n    @Gooey(program_name='Custom icon demo', image_dir='/path/to/my/image/directory')\n    def main():\n        # rest of program\n        \nImages are discovered by Gooey based on their _filenames_. So, for example, in order to supply a custom configuration icon, simply place an image with the filename `config_icon.png` in your images directory. These are the filenames which can be overridden:\n\n* program_icon.png\n* success_icon.png\n* running_icon.png\n* loading_icon.gif\n* config_icon.png\n* error_icon.png\n\n\n## Packaging\n\nThanks to some [awesome contributors](https://github.com/chriskiehl/Gooey/issues/58), packaging Gooey as an executable is super easy. \n\nThe tl;dr [pyinstaller](https://github.com/pyinstaller/pyinstaller) version is to drop this [build.spec](https://raw.githubusercontent.com/chriskiehl/Gooey/master/docs/packaging/build-win.spec) into the root directory of your application. Edit its contents so that the `APPPNAME` and `name` are relevant to your project and the `pathex` value points to your applications root, then execute `pyinstaller -F --windowed build.spec` to bundle your app into a ready-to-go executable. \n\nDetailed step by step instructions can be found [here](https://github.com/chriskiehl/Gooey/blob/master/docs/packaging/Packaging-Gooey.md). \n\n\nScreenshots\n------------  \n\n| Flat Layout | Column Layout |Success Screen | Error Screen | Warning Dialog |\n|-------------|---------------|---------------|--------------|----------------|\n| <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\"> | \n\n| Custom Groups | Tabbed Groups | Tabbed Navigation | Sidebar Navigation | Input Validation |\n|-------------|---------------|---------------|--------------|----------------|\n| <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\"> | \n\n\n\n\n\n----------------------------------------------  \n\n\n\n\n\n\nWanna help?\n-----------  \n\nCode, translation, documentation, or graphics? All pull requests are welcome. Just make sure to checkout [the contributing guidelines](https://github.com/chriskiehl/Gooey/blob/master/CONTRIBUTING.md) first.\n\n\n\n\n"
  },
  {
    "path": "TODO.md",
    "content": "Release TODO\n============\n\n\n - [ ] Fix user supplied directory path when packaged. Currently gives super cryptic failures\n - [X] need ability to call out to external seed function for dynamic defaults\n    - [ ] update readme (SavingOverIt could be example use case)\n    - [ ] extend this to all types (currently only works for Dropdowns)\n    - [ ] think about stuff. Need a friendly way to specify mappings that's more\n          flexible than options_strings\n\n - [X] success/error screen after a ForceStop should be configurable. Stopping early does not necessarily error\n - [ ] customizable button text\n - [X] text encoding\n - [X] pass down the font info to the console\n - [X] pass down the style info to the console\n - [X] MUST add new entries to all language files\n\n\nIssue #234\n - allow general options\n\nREADME:\n\n - update README with all the things\n - [ ] RadioGroup\n    - [ ] `initial_selection` option\n    - [ ] group name options\n - [ ] force_stop_is_error\n - [X] validation howto\n - [ ] advanced layout howto\n - [ ] turning on/off dialog options\n - [X] full list of custom widgets and their options\n - [ ] progress bar management\n\n\nCustom Validation:\n\n - [X] make sure user supplied validators fail gracefully and report something useful\n - [ ] validator should be able to call outside itself -- either to a separate cmdline util, or a subset of the host prog\n\n\nLater TODO:\n - overview of Gooey for peeps who wanna dev against it\n"
  },
  {
    "path": "docs/Gooey-Options.md",
    "content": "# Gooey Options \n\nUsing `GooeyParser` we can extend the API of `argparse` to support lots of cool additional functionality. \n\nThe main addition to the top-level `argparse` API is that we pick up extra keywords: `widget` and `gooey_options`. `widget` is used to specified which UI element to provide for the argument, i.e., a listbox or a file browser. `gooey_options` accepts a dictionary of configuration parameters that lets you specify things like custom validators, style overrides, and a bunch of behavioral extensions for the various widget classes.   \n\n`GooeyParser` is a drop-in replacement for `argparse`. You can import it from the root Gooey namespace like this: \n\n```python\nfrom gooey import GooeyParser\n```\n\nand replace `ArgumentParser` with `GooeyParser`\n\n```python\n# parser = ArgumentParser()   # old busted\nparser = GooeyParser()        # new hotness\n```\n\nand with that, you're ready to rock. \n\n\n## Overview\n\n* Global Style/Layout Options \n* Global Config Options \n* Custom Widget Options\n    * Textarea\n    * BlockCheckbox  \n    * Listbox\n    * RadioGroups\n* Argument Group Options  \n\n\n## Global Style / Layout Options     \n\nAll widgets in Gooey (with the exception of RadioGroups) are made up of three basic components. \n\n1. Label \n2. Help Text \n3. Input Control\n\n![image](https://user-images.githubusercontent.com/1408720/56450719-cfca9c80-62dc-11e9-93ec-6ad56810e79a.png)\n\nThe following options apply to all Widget types in Gooey. \n\n```python\nparser.add_argument('-my-arg', gooey_options={\n    'label_color': '#ffffff',\n    'label_bg_color': '#ffffff', \n    'help_color': '#ffffff',\n    'help_bg_color': '#ffffff',\n    'error_color': '#ffffff',\n    'error_bg_color': '#ffffff',\n    'show_label': bool,\n    'show_help': bool, \n    'visible': bool,\n    'full_width': bool\n})\n``` \n\n| Keyword | Type | Description | \n|---------|------|-------------|\n| label_color | hex string | The foreground color of the label text (e.g. `#ff0000`) |\n| label_bg_color | hex string | The background color of the label text. |\n| help_color | hex string | The foreground color of the help text. |\n| help_bg_color | hex string | The background color of the help text. |\n| error_color | hex string | The foreground color of the error text (when visible). |\n| error_bg_color | hex string | The background color of the error text (when visible). |\n| show_label | bool | Toggles whether or not to display the label text |\n| show_help | bool | Toggles whether or not to display the help text |\n| visible | bool | Hides the entire widget when `False`. Note: the widget is still present in the UI and will still send along any default values that have been provided in code. This option is here for when you want to hide certain advanced / dangerous inputs from your GUI users. |\n| full_width | bool | This is a layout hint for this widget. When `True` the widget will fill the entire available space within a given row. Otherwise, it will be sized based on the column rules provided elsewhere. | \n\n\n## Global Config Options \n\n> new in 1.0.8\n\nAll widgets in Gooey accept an `initial_value` option to seed the UI. \n\n```python\nparser.add_argument('-my-arg', widget='Textarea', gooey_options={\n    'initial_value': 'Hello world!'  \n})\n```\n\n## Individual Widget Options\n\nA few widgets have additional options for controlling their layout and behavior. \n\n### Textarea\n\n```python\nparser.add_argument('-my-arg', widget='Textarea', gooey_options={\n    # height of the text area in pixels\n    'height': int,    \n    # prevents the user from editing when true\n    'readonly': bool  \n})\n``` \n\n### IntegerField\n\n```python\nparser.add_argument('-my-arg', widget='IntegerField', gooey_options={\n    'min': int, \n    'max': int, \n    'increment': int  \n})\n``` \n\n\n### DecimalField\n\n```python\nparser.add_argument('-my-arg', widget='IntegerField', gooey_options={\n    'min': float, \n    'max': float, \n    'increment': float,\n    'precision': int  # 0 - 20\n})\n``` \n\n### Slider\n\nThe Slider is just a reskinned IntegerField, so it has the same options\n \n```python\nparser.add_argument('-my-arg', widget='Slider', gooey_options={\n    'min': int, \n    'max': int, \n    'increment': int  \n})\n``` \n\n\n### BlockCheckbox\n\n```python\nparser.add_argument('-my-arg', widget='BlockCheckbox', gooey_options={\n    # allows customizing the checkbox's label\n    'checkbox_label': str  \n})\n```\n \n### Listbox\n\n```python\nparser.add_argument('-my-arg', widget='Listbox', gooey_options={\n    # height of the listbox in pixels\n    'height': int\n})\n```\n\n### Radio Group  \n\n```python\nparser.add_mutually_exclusive_group(gooey_options={\n    # Pre-select a specific option within a mutually exclusive group. \n    # default behavior is to have all options unselected by default.  \n    'initial_selection': int\n})\n```\n\n\n## Argument Groups\n\nArgument Groups take a number of `gooey_options` to help control layout. \n\n```python\nparser.add_argument_group('MyGroup', desription='my cool group', gooey_options={\n    'show_border': bool,\n    'show_underline': bool,\n    'label_color': '#FF9900',\n    'columns': int,\n    'margin_top': int\n})\n``` \n  \n| Keyword | Type | Description | \n|---------|------|-------------|\n| show_border | bool | When `True` a labeled border will surround all widgets added to this group. |\n| show_underline | bool | Controls whether or not to display the underline when using the default border style |\n| label_color | hex string | The foreground color for the group name |\n| columns | int | Controls the number of widgets on each row | \n| margin_top | int | specifies the top margin in pixels for this group |\n\n![image](https://user-images.githubusercontent.com/1408720/57576112-9c77bb00-740d-11e9-9dac-4e798699a35c.png)\n\n\n\n## File and Folder choosers\n\nFile and Folder Choosers Groups take a number of `gooey_options` to help control default values. \n\n```python\nparser.add_argument(\"FileChooser\", widget=\"FileChooser\",\n                            gooey_options={\n                                'wildcard':\n                                    \"Comma separated file (*.csv)|*.csv|\"\n                                    \"All files (*.*)|*.*\",\n                                'default_dir': \"c:/batch\",\n                                'default_file': \"def_file.csv\",\n                                'message': \"pick me\"\n                            }\n                            )\nparser.add_argument(\"DirectoryChooser\", widget=\"DirChooser\",\n                            gooey_options={\n                                'wildcard':\n                                    \"Comma separated file (*.csv)|*.csv|\"\n                                    \"All files (*.*)|*.*\",\n                                'message': \"pick folder\",\n                                'default_path': \"c:/batch/stuff\"\n                            }\n                            )\nparser.add_argument(\"FileSaver\", widget=\"FileSaver\",\n                            gooey_options={\n                                'wildcard':\n                                    \"JPG (*.jpg)|*.jpg|\"\n                                    \"All files (*.*)|*.*\",\n                                'message': \"pick folder\",\n                                'default_dir': \"c:/projects\",\n                                'default_file': \"def_file.csv\"\n                            }\n                            )\nparser.add_argument(\"MultiFileSaver\", widget=\"MultiFileChooser\",\n                            gooey_options={\n                                'wildcard':\n                                    \"Comma separated file (*.csv)|*.csv|\"\n                                    \"All files (*.*)|*.*\",\n                                'message': \"pick folder\",\n                                'default_dir': \"c:/temp\",\n                                'default_file': \"def_file.csv\"\n                            }\n                            )\n``` \n  \n| Keyword | Type | Description | \n|---------|------|-------------|\n| wildcard | string | Sets the wildcard, which can contain multiple file types, for example: \"BMP files (.bmp)&#124;.bmp&#124;GIF files (.gif)&#124;.gif\" |\n| message | string | \tSets the message that will be displayed on the dialog. |\n| default_dir | string | The default directory |\n| default_file | string | The default filename | \n| default_path | string | The default path |\n\n\n  \n"
  },
  {
    "path": "docs/Gracefully-Stopping.md",
    "content": "# Gracefully Stopping a Running Process\n\n>New in v1.0.9!\n\n<p align=\"center\">\n  <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/docs/graceful-stopping/screenshot.PNG\"/>\n</p>\n\n**Contents:**\n\n* [How to tell Gooey which shutdown signal to use](#how-to-tell-gooey-which-signal-to-use)\n* [How to catch KeyboardInterrupts](#How-to-catch-KeyboardInterrupts)\n* [How to catch general interrupt signals](#How-to-catch-general-interrupt-signals)\n\nBy default, Gooey will kill the child process without any chance for cleanup. This guide will explain how to adjust that behavior so that you can detect when Gooey is attempting to close your process and use that signal to shutdown gracefully.   \n\n### Basics: How to tell Gooey which shutdown signal to use: \n\nYou can control the signal Gooey sends while stopping your process via `shutdown_signal` decorator argument. Signal values come from the builtin `signal` python module. On linux, any of the available constants may be used as a value. However, on Windows, only `CTRL_BREAK_EVENT`, `CTRL_C_EVENT` and `SIGTERM` are supported by the OS.   \n \n \n```python\nimport signal \n@Gooey(shutdown_signal=signal.CTRL_C_EVENT)\ndef main():\n    ...\n```\n\n\n### How to catch KeyboardInterrupts:\n\nKeyboard interrupts are triggered in response to the `CTRL_C_EVENT` signal.\n\n```python\nimport signal \n@Gooey(shutdown_signal=signal.CTRL_C_EVENT)\ndef main():\n    ...\n``` \n\nCatching them in your code is really easy! They conveniently show up as top-level Exceptions. Just wrap your main logic in a try/except and you'll be able to catch when Gooey tries to shut down your process.   \n\n```python\ntry\n   # your code here\nexcept KeyboardInterrupt: \n   # cleanup and shutdown or ignore \n``` \n\n### How to catch general interrupt signals\n\nHandling other signals is only slightly more involved than the `CTRL_C_EVENT` one. You need to install a handler via the `signal` module and tie it to the specific signal you want to handle. Let's use the `CTRL_BREAK_EVENT` signal as example. \n\n```python\nimport signal\n\n# (1)\ndef handler(*args): \n    print(\"I am called in response to an external signal!\")\n    raise Exception(\"Kaboom!\")\n\n# (2) \nsignal.signal(signal.SIGBREAK, handler)\n\n# (3)\n@Gooey(shutdown_signal=signal.CTRL_BREAK_EVENT)\ndef main():\n    # your code here \n    # ... \n```    \n\nHere we setup a handler called `handler` (1). This function can do anything you want in response to the signal including ignoring it entirely. Next we tie the signal we're interested in to the handler (2). Finally, we tell Gooey to send the `BREAK` signal(3) when the stop button is clicked. \n\n> Note: pay close attention to the different constants used while specifying a handler (e.g. `SIGBREAK`) versus specifying which signal will be sent (e.g. `CTRL_BREAK_SIGNAL`).   \n\n\n"
  },
  {
    "path": "docs/Using-Richtext-Controls.md",
    "content": "# Using the Richtext Controls\n\n<p align=\"center\">\n    <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/docs/richtext-controls/richtext-screen.png\">\n</p>\n\nGooey has a support for basic terminal control sequences. These let you control weight and color of the output font. For a full runnable example, checkout the code [from the examples repository](https://github.com/chriskiehl/GooeyExamples/blob/master/examples/richtext_demo.py)\n\nYou can enable this mode by passing the `richtext_controls` option to the Gooey decorator. \n\n```python\n@Gooey(richtext_controls=True)\ndef main():\n   ...\n```\n\n### Usage\n\nThe [colored library](https://pypi.org/project/colored/) provides functions for adding markup controls to your text. Gooey supports the foreground (`fg`) and attributes (`attr`).\n\n```python\nfrom colored import stylize, attr, fg\n```\n\nYou can modify your text's output by wrapping it in the `stylize` function provided by colored.py. For instance, this line will be displayed as bold (`attr(1)`) and red (`fg('red')`) when rendered by Gooey.  \n\n```python\nprint(stylize('Hello world!', fg('red') + attr('bold')))\n```\n\n> Note that you combine the foreground and attribute functions with the `+` operator.   \n\n### Attributes: \n\nRichtext currently supports the following attributes:\n\n\n|Code | Description      |\n|:----|------------------|\n|  1  | bold             |\n|  4  | underlined       |\n|  0  | reset            |\n|  21 | res_bold         |\n|  24 | res_underlined   |\n\nThese can be used either via their code (`attr(1)`) or their verbose form (`attr('bold')`)\n\n\n### Foreground Colors:\n\nSimilar to the Attributes, foreground colors can be specified either by their code (`fg(2)`) or verbose form (`fg('green')`)\n\n|Code | Description         |\n|:----|---------------------|\n| 0   | black               |\n| 1   | red                 |\n| 2   | green               |\n| 3   | yellow              |\n| 4   | blue                |\n| 5   | magenta             |\n| 6   | cyan                |\n| 7   | light_gray          |\n| 8   | dark_gray           |\n| 9   | light_red           |\n| 10  | light_green         |\n| 11  | light_yellow        |\n| 12  | light_blue          |\n| 13  | light_magenta       |\n| 14  | light_cyan          |\n| 15  | white               |\n| 16  | grey_0              |\n| 17  | navy_blue           |\n| 18  | dark_blue           |\n| 19  | blue_3a             |\n| 20  | blue_3b             |\n| 21  | blue_1              |\n| 22  | dark_green          |\n| 23  | deep_sky_blue_4a    |\n| 24  | deep_sky_blue_4b    |\n| 25  | deep_sky_blue_4c    |\n| 26  | dodger_blue_3       |\n| 27  | dodger_blue_2       |\n| 28  | green_4             |\n| 29  | spring_green_4      |\n| 30  | turquoise_4         |\n| 31  | deep_sky_blue_3a    |\n| 32  | deep_sky_blue_3b    |\n| 33  | dodger_blue_1       |\n| 34  | green_3a            |\n| 35  | spring_green_3a     |\n| 36  | dark_cyan           |\n| 37  | light_sea_green     |\n| 38  | deep_sky_blue_2     |\n| 39  | deep_sky_blue_1     |\n| 40  | green_3b            |\n| 41  | spring_green_3b     |\n| 42  | spring_green_2a     |\n| 43  | cyan_3              |\n| 44  | dark_turquoise      |\n| 45  | turquoise_2         |\n| 46  | green_1             |\n| 47  | spring_green_2b     |\n| 48  | spring_green_1      |\n| 49  | medium_spring_green |\n| 50  | cyan_2              |\n| 51  | cyan_1              |\n| 52  | dark_red_1          |\n| 53  | deep_pink_4a        |\n| 54  | purple_4a           |\n| 55  | purple_4b           |\n| 56  | purple_3            |\n| 57  | blue_violet         |\n| 58  | orange_4a           |\n| 59  | grey_37             |\n| 60  | medium_purple_4     |\n| 61  | slate_blue_3a       |\n| 62  | slate_blue_3b       |\n| 63  | royal_blue_1        |\n| 64  | chartreuse_4        |\n| 65  | dark_sea_green_4a   |\n| 66  | pale_turquoise_4    |\n| 67  | steel_blue          |\n| 68  | steel_blue_3        |\n| 69  | cornflower_blue     |\n| 70  | chartreuse_3a       |\n| 71  | dark_sea_green_4b   |\n| 72  | cadet_blue_2        |\n| 73  | cadet_blue_1        |\n| 74  | sky_blue_3          |\n| 75  | steel_blue_1a       |\n| 76  | chartreuse_3b       |\n| 77  | pale_green_3a       |\n| 78  | sea_green_3         |\n| 79  | aquamarine_3        |\n| 80  | medium_turquoise    |\n| 81  | steel_blue_1b       |\n| 82  | chartreuse_2a       |\n| 83  | sea_green_2         |\n| 84  | sea_green_1a        |\n| 85  | sea_green_1b        |\n| 86  | aquamarine_1a       |\n| 87  | dark_slate_gray_2   |\n| 88  | dark_red_2          |\n| 89  | deep_pink_4b        |\n| 90  | dark_magenta_1      |\n| 91  | dark_magenta_2      |\n| 92  | dark_violet_1a      |\n| 93  | purple_1a           |\n| 94  | orange_4b           |\n| 95  | light_pink_4        |\n| 96  | plum_4              |\n| 97  | medium_purple_3a    |\n| 98  | medium_purple_3b    |\n| 99  | slate_blue_1        |\n| 100 | yellow_4a           |\n| 101 | wheat_4             |\n| 102 | grey_53             |\n| 103 | light_slate_grey    |\n| 104 | medium_purple       |\n| 105 | light_slate_blue    |\n| 106 | yellow_4b           |\n| 107 | dark_olive_green_3a |\n| 108 | dark_green_sea      |\n| 109 | light_sky_blue_3a   |\n| 110 | light_sky_blue_3b   |\n| 111 | sky_blue_2          |\n| 112 | chartreuse_2b       |\n| 113 | dark_olive_green_3b |\n| 114 | pale_green_3b       |\n| 115 | dark_sea_green_3a   |\n| 116 | dark_slate_gray_3   |\n| 117 | sky_blue_1          |\n| 118 | chartreuse_1        |\n| 119 | light_green_2       |\n| 120 | light_green_3       |\n| 121 | pale_green_1a       |\n| 122 | aquamarine_1b       |\n| 123 | dark_slate_gray_1   |\n| 124 | red_3a              |\n| 125 | deep_pink_4c        |\n| 126 | medium_violet_red   |\n| 127 | magenta_3a          |\n| 128 | dark_violet_1b      |\n| 129 | purple_1b           |\n| 130 | dark_orange_3a      |\n| 131 | indian_red_1a       |\n| 132 | hot_pink_3a         |\n| 133 | medium_orchid_3     |\n| 134 | medium_orchid       |\n| 135 | medium_purple_2a    |\n| 136 | dark_goldenrod      |\n| 137 | light_salmon_3a     |\n| 138 | rosy_brown          |\n| 139 | grey_63             |\n| 140 | medium_purple_2b    |\n| 141 | medium_purple_1     |\n| 142 | gold_3a             |\n| 143 | dark_khaki          |\n| 144 | navajo_white_3      |\n| 145 | grey_69             |\n| 146 | light_steel_blue_3  |\n| 147 | light_steel_blue    |\n| 148 | yellow_3a           |\n| 149 | dark_olive_green_3  |\n| 150 | dark_sea_green_3b   |\n| 151 | dark_sea_green_2    |\n| 152 | light_cyan_3        |\n| 153 | light_sky_blue_1    |\n| 154 | green_yellow        |\n| 155 | dark_olive_green_2  |\n| 156 | pale_green_1b       |\n| 157 | dark_sea_green_5b   |\n| 158 | dark_sea_green_5a   |\n| 159 | pale_turquoise_1    |\n| 160 | red_3b              |\n| 161 | deep_pink_3a        |\n| 162 | deep_pink_3b        |\n| 163 | magenta_3b          |\n| 164 | magenta_3c          |\n| 165 | magenta_2a          |\n| 166 | dark_orange_3b      |\n| 167 | indian_red_1b       |\n| 168 | hot_pink_3b         |\n| 169 | hot_pink_2          |\n| 170 | orchid              |\n| 171 | medium_orchid_1a    |\n| 172 | orange_3            |\n| 173 | light_salmon_3b     |\n| 174 | light_pink_3        |\n| 175 | pink_3              |\n| 176 | plum_3              |\n| 177 | violet              |\n| 178 | gold_3b             |\n| 179 | light_goldenrod_3   |\n| 180 | tan                 |\n| 181 | misty_rose_3        |\n| 182 | thistle_3           |\n| 183 | plum_2              |\n| 184 | yellow_3b           |\n| 185 | khaki_3             |\n| 186 | light_goldenrod_2a  |\n| 187 | light_yellow_3      |\n| 188 | grey_84             |\n| 189 | light_steel_blue_1  |\n| 190 | yellow_2            |\n| 191 | dark_olive_green_1a |\n| 192 | dark_olive_green_1b |\n| 193 | dark_sea_green_1    |\n| 194 | honeydew_2          |\n| 195 | light_cyan_1        |\n| 196 | red_1               |\n| 197 | deep_pink_2         |\n| 198 | deep_pink_1a        |\n| 199 | deep_pink_1b        |\n| 200 | magenta_2b          |\n| 201 | magenta_1           |\n| 202 | orange_red_1        |\n| 203 | indian_red_1c       |\n| 204 | indian_red_1d       |\n| 205 | hot_pink_1a         |\n| 206 | hot_pink_1b         |\n| 207 | medium_orchid_1b    |\n| 208 | dark_orange         |\n| 209 | salmon_1            |\n| 210 | light_coral         |\n| 211 | pale_violet_red_1   |\n| 212 | orchid_2            |\n| 213 | orchid_1            |\n| 214 | orange_1            |\n| 215 | sandy_brown         |\n| 216 | light_salmon_1      |\n| 217 | light_pink_1        |\n| 218 | pink_1              |\n| 219 | plum_1              |\n| 220 | gold_1              |\n| 221 | light_goldenrod_2b  |\n| 222 | light_goldenrod_2c  |\n| 223 | navajo_white_1      |\n| 224 | misty_rose1         |\n| 225 | thistle_1           |\n| 226 | yellow_1            |\n| 227 | light_goldenrod_1   |\n| 228 | khaki_1             |\n| 229 | wheat_1             |\n| 230 | cornsilk_1          |\n| 231 | grey_100            |\n| 232 | grey_3              |\n| 233 | grey_7              |\n| 234 | grey_11             |\n| 235 | grey_15             |\n| 236 | grey_19             |\n| 237 | grey_23             |\n| 238 | grey_27             |\n| 239 | grey_30             |\n| 240 | grey_35             |\n| 241 | grey_39             |\n| 242 | grey_42             |\n| 243 | grey_46             |\n| 244 | grey_50             |\n| 245 | grey_54             |\n| 246 | grey_58             |\n| 247 | grey_62             |\n| 248 | grey_66             |\n| 249 | grey_70             |\n| 250 | grey_74             |\n| 251 | grey_78             |\n| 252 | grey_82             |\n| 253 | grey_85             |\n| 254 | grey_89             |\n| 255 | grey_93             |\n| 256 | default             |\n\n \n"
  },
  {
    "path": "docs/packaging/Packaging-Custom-Images.md",
    "content": "# Using Custom Images while Packaging  \n\n> Note: if you're new to packaging Gooey, checkout the main [Packaging Guide](https://github.com/chriskiehl/Gooey/blob/doc-improvements/docs/packaging/Packaging-Gooey.md) first!  \n\nGooey comes with a set of six default icons. These can be overridden with your own custom images/icons by telling Gooey to search additional directories when initializing. This is done via the `image_dir` argument to the `Gooey` decorator. \n\n```python\n@Gooey(program_name='Custom icon demo', image_dir='/path/to/images')\ndef main():\n    # rest of program\n```\n\nWhile this works for regular executions, a little additional work is required to make sure that your images will actually be available when running as a stand alone executable. \n    \nTo make your custom images available after packaging, you have to do two things. \n\n**Step 1:** wrap the path to your image directory in the `local_resource_path()` function provided by Gooey. When PyInstaller runs your application, it decompresses all the contents to a random temp directory. This function will handle the logic of resolving that directory and fetching your resources from it. \n\n```python\nfrom gooey import Gooey, local_resource_path\n\n@Gooey(image_dir=local_resource_path('relative/path/to/images'))\ndef main():\n   ...\n```\n\n**Step 2:** Update `build.spec` to include the image directory during bundling. This is done by giving the path to your Images as a Tree object to Pyinstaller's `EXE` section. \n\n```\n# -*- mode: python ; coding: utf-8 -*-\n\nimport os\n...\n# LOOK AT ME! I AM A TREE OBJECT \nimage_overrides = Tree('path/to/images', prefix='path/to/images')\n\n...\n\nexe = EXE(pyz,\n          a.scripts,\n          a.binaries,\n          a.zipfiles,\n          a.datas,\n          options,\n          image_overrides,  # <-- NEW \n          name='APPNAME',\n          debug=False,\n          strip=None,\n          upx=True,\n          console=False,\n          icon=os.path.join(gooey_root, 'images', 'program_icon.ico'))\n``` \n\nAnd then build via PyInstaller as usual. \n\n```\npyinstaller -F --windowed build.spec\n``` \n\nPyInstaller will now include your images in its bundle.   \n\n\n\n\n"
  },
  {
    "path": "docs/packaging/Packaging-Gooey.md",
    "content": "# Packaging Gooey as a Stand Alone Application\n\n<p align=\"center\">\n    <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/docs/packaging/packaged-application.png\" />\n</p>\n\n\n>:warning: Packaging Gooey is an ongoing science. Gooey currently runs on all the major platforms, can be installed in a bajillion different ways, and has several active versions in wide usage. In short, edge cases abound. If you run into any problems, hit up [this issue](https://github.com/chriskiehl/Gooey/issues/259).\n\nYou can package all of your programs files into a single easy to distribute executable using PyInstaller.  \n\nPacking Gooey into a standalone executable is super straight forward thanks to [PyInstaller](http://www.pyinstaller.org/). It is the only dependency you'll need and can be installed via the following. \n\n```\npip install pyinstaller\n```\n\n**Setting up the build:**\n\nPyInstaller uses [spec files](http://pythonhosted.org/PyInstaller/#using-spec-files) to determine how to bundle the project. These are a bit like `setup.py` files, but contain rules for how PyInstaller should bundle your whole application as a stand alone executable.    \n\nThis file is usually placed in the root of your project. e.g.  \n\n```\nMyProject/\n   - src/\n   - build.spec  # <-- goes here!\n   - LICENCE.txt\n   - README.md\n```\n\n**Download Spec Files**\n\n* Windows users can grab a pre-built spec file [here](https://raw.githubusercontent.com/chriskiehl/Gooey/master/docs/packaging/build-win.spec).\n* For OSX users, you'll want [this one](https://raw.githubusercontent.com/chriskiehl/Gooey/master/docs/packaging/build-osx.spec).\n\nThe exact contents of the spec files will vary based on your OS, but at a high level, they'll share the same core pieces: `Analysis`, `EXE`, and, if you're on OSX, `BUNDLE` \n\n\n```\n# -*- mode: python ; coding: utf-8 -*-\n\nblock_cipher = None\n\na = Analysis(\n    ['main.py'],  # replace me with the main entry point \n    pathex=['/path/to/main.py'],  # replace me with the appropriate path\n    ...\n    )\n\npyz = PYZ(a.pure)\n\noptions = [('u', None, 'OPTION'), ('v', None, 'OPTION'), ('w', None, 'OPTION')]\n\nexe = EXE(pyz,\n       ...\n       name='MyCoolApplication'  # replace me with exe name\n       console=False)\n       \n## OSX only below!       \napp = BUNDLE(exe,\n             name='APPNAME.app',  # osx users replace me!\n             bundle_identifier=None,\n             info_plist=info_plist\n            )\n```\n\nThe `Analysis` section is where you'll tell PyInstaller about your program. Using the build.spec from above, you'll need to make two edits to this section. \n\n1. replace `APPNAME` in the `Analysis()` section with the name of _your_ application\n2. replace the `pathex` value in the `Analysis()` section with the path to your application's root\n\n\n> note: If you use additional data resources (e.g. images, data, etc..) you'll also need to explicitly add them to the EXE section. See [packaging custom images] for more info. \n\nNext is `EXE`. In this section you'll replace the `name` argument with what you'd like the final `.exe` to be named.\n\n>Note: if you're providing your own icon file, EXE is where you'll provide it. If you're on Windows, you must provide an .ico file (not PNG).\n\nIf you're on OSX, you'll have an additional `BUNDLE` section. You'll need to make one final edit here as well to control the name of the `.app` bundle that PyInstaller produces. Additionally, if you're customizing the bundle's icon, this is where you would supply the override (versus Windows, which places it in the EXE section). \n\nOnce you've updated the `.spec` to reflect your program's details. All that's left to do is build the executable! \n\n### Running the .spec file \n\nFrom the command line, run \n\n```\npyinstaller -F --windowed build.spec\n```\n\n* `-F` tells PyInstaller to create a single bundled output file\n* `--windowed` disables the terminal which would otherwise launch when you opened your app. \n\nAnd that's it. Inside of the `dist/` directory, you'll find a beautiful stand-alone executable that you can distribute to your users. \n\n\n## Troubleshooting\n\n**PROBLEM: My bundled Application won't work!** \n\nFirst things first: _See if you can package your application **without** Gooey!_\n\nRead and understand all of the PyInstaller docs. If you're referencing binaries or external data files, you may have to do a little extra work in your `.spec` to get PyInstaller to understand all of your dependencies. \n\nRebuild your bundle with `debug=True` set in the `.spec` file. This will give lots of useful output when your application bootstraps which can make pinning down the problem much easier. \n\nRebuild your bundle without the `-F` flag (e.g. just `pyinstaller build.spec`). This will build a directory with all of your dependencies. This can make it easier to poke around and see what PyInstaller's view of your project actually is.  \n\n**PROBLEM: I'm seeing the wrong icon on my executable** \n\nFirst things first: Is Windows gas lighting you? \n\nWindows caches icons semi-aggressively. This can lead to it showing an icon in the file explorer that doesn't actually reflect reality. \n\n![image](https://github.com/chriskiehl/GooeyImages/raw/images/docs/packaging/cached-icon.png)\n\nRight-click on the executable and select \"properties.\" This will show you the icon that's actually associated with file. As long as everything looks good there, you're golden. Windows will catch up... _eventually_.  \n\n\n**PROBLEM: Exception: This program needs access to the screen. Please run with a Framework build of python, and only when you are logged in on the main display of your Mac.**\n\nThis happens on OSX when you neglect the `--windowed` flag during your build step. \n\nwrong:\n```\npyinstaller build.spec  ## oops! forgot the required flags    \n```\n\nCorrect:\n```\npyinstaller --windowed build.spec \n```\n \nCheckout the [Pyinstaller Manual](https://github.com/pyinstaller/pyinstaller/wiki/FAQ) for more details. \n\n"
  },
  {
    "path": "docs/packaging/build-osx.spec",
    "content": "# -*- mode: python ; coding: utf-8 -*-\n\"\"\"\nExample build.spec file\n\nThis hits most of the major notes required for\nbuilding a stand alone version of your Gooey application.\n\"\"\"\n\n\nimport os\nimport platform\nimport gooey\ngooey_root = os.path.dirname(gooey.__file__)\ngooey_languages = Tree(os.path.join(gooey_root, 'languages'), prefix = 'gooey/languages')\ngooey_images = Tree(os.path.join(gooey_root, 'images'), prefix = 'gooey/images')\n\nfrom PyInstaller.building.api import EXE, PYZ, COLLECT\nfrom PyInstaller.building.build_main import Analysis\nfrom PyInstaller.building.datastruct import Tree\nfrom PyInstaller.building.osx import BUNDLE\n\nblock_cipher = None\n\na = Analysis(['APPNAME.py'],  # replace me with your path\n             pathex=['/path/to/APP.py'],\n             hiddenimports=[],\n             hookspath=None,\n             runtime_hooks=None,\n             )\npyz = PYZ(a.pure)\n\noptions = [('u', None, 'OPTION'), ('v', None, 'OPTION'), ('w', None, 'OPTION')]\n\n\nexe = EXE(pyz,\n          a.scripts,\n          a.binaries,\n          a.zipfiles,\n          a.datas,\n          options,\n          gooey_languages,\n          gooey_images,\n          name='APPNAME',\n          debug=False,\n          strip=False,\n          upx=True,\n          console=False,\n          icon=os.path.join(gooey_root, 'images', 'program_icon.ico'))\n\ninfo_plist = {'addition_prop': 'additional_value'}\napp = BUNDLE(exe,\n             name='APPNAME.app',\n             bundle_identifier=None,\n             info_plist=info_plist\n            )\n"
  },
  {
    "path": "docs/packaging/build-win.spec",
    "content": "# -*- mode: python ; coding: utf-8 -*-\n\nimport gooey\ngooey_root = os.path.dirname(gooey.__file__)\n\nblock_cipher = None\n\na = Analysis(['APPNAME.py'],  # replace me with your path\n             pathex=['/path/to/APP.py'],\n             binaries=[],\n             datas=[],\n             hiddenimports=[],\n             hookspath=[],\n             runtime_hooks=[],\n             excludes=[],\n             win_no_prefer_redirects=False,\n             win_private_assemblies=False,\n             cipher=block_cipher,\n             noarchive=False)\npyz = PYZ(a.pure, a.zipped_data,\n             cipher=block_cipher)\nexe = EXE(pyz,\n          a.scripts,\n          a.binaries,\n          a.zipfiles,\n          a.datas,\n          [],\n          name='main',\n          debug=False,\n          bootloader_ignore_signals=False,\n          strip=False,\n          upx=True,\n          upx_exclude=[],\n          runtime_tmpdir=None,\n          console=False,\n          icon=os.path.join(gooey_root, 'images', 'program_icon.ico'))\n"
  },
  {
    "path": "docs/pull_request_template.md",
    "content": "Hello there! \n\nMake sure you've followed the [Contributing](https://github.com/chriskiehl/Gooey/blob/master/CONTRIBUTING.md) guidelines before finalizing your pull request. \n\nTL;DR: \n\n - [ ] You're opening this PR against the current [release branch](https://github.com/chriskiehl/Gooey/blob/master/CONTRIBUTING.md#development-overview)\n - [ ] Works on both Python 2.7 & Python 3.x\n - [ ] Commits have been squashed and includes the relevant issue number\n - [ ] Pull request description contains link to relevant issue or detailed notes on changes\n  - [ ] This **must** include example code demonstrating your feature or the bug being fixed\n - [ ] All existing tests pass \n - [ ] Your bug fix / feature has associated test coverage \n - [ ] README.md is updated (if relevant)\n"
  },
  {
    "path": "docs/releases/1.0.3-release-notes.md",
    "content": "# Gooey 1.0.3 Released!\n\n![title card](https://github.com/chriskiehl/GooeyImages/blob/images/docs/releases/1.0.3/release-title-card.png)\n\nAfter cooking for far too long, **Gooey 1.0.3 is released!**\n\n\n\nGrab the latest version:\n\n* [github](https://github.com/chriskiehl/Gooey)\n* [PyPi](TODO)\n\n\nRunnable demos for all the new features can be found in the [Examples repo](https://github.com/chriskiehl/GooeyExamples).\n\n## Overview:\n\n\nA lot of focus was put on settling Gooey down into a more stable mature project. In addition to all of the new features, a lot of time was spent writing documentation, stamping down cross platform issues / quirks, and making numerous tweaks and additions to enable a smoother experience when packaging Gooey for distribution.    \n\n\n## What's new\n\n\n### Fancy Layout controls!\n\n\n![advanced layout](https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.3/advanced-layout.png)\n\nThe main goal of this release was enabling more complex real-world layouts and more customization of Gooey's UI. As of 1.1.0, you now have have control over every color, font, and display status within the application. You can now brand Gooey to your organization's colors, logically group related items under a central heading, and optionally show/hide all the individual components that make up an input widget.\n\n\n### Menu Bars\n\nGooey now includes a simple declarative system for creating top level menu bars and items.\n\n![menu bar](https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.3/menu.png)\n\n\n\nThe menu option currently supports three flavors:\n\n\n\n**AboutDialog**\n\n\nThis is an AboutDialog as rendered natively by your OS. It's a good place to show standard info like version info, descriptions, licenses, etc.. in a standard way across platforms.\n\n\n\n**MessageDialogs**\n\n\nNext up are general message dialogs. You can display any informational text inside of these.\n\n\n\n**Link**\n\n\nFinally, you can create fixed menu items that simply link to external resources, for instance, your site, documentation, pdfs, etc.. \n\n\n## Rich Text Controls\n\nThanks to @NathanRichard, Gooey can now optionally honor terminal control sequences and display Rich Text in the output panel.\n\n![rich text](https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.3/rich-text.png)\n\n\n### New Gooey Program Icon\n\n<img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/logo/gooey.png\" width=\"200\" height=\"auto\">\n\nNew icon provided by professional cool guy and crazy talented UX designer [Justin Rhee](https://www.linkedin.com/in/justinrhee/).   \n\n \n\n\n## Additional features\n\n* OSX now shows program Icon in Dock \n* `show_error_modal` option to toggle whether or not failures additionally raise alert modals.\n* `BlockCheckbox` widget.\n* Hyperlinks written to the console appear as such and will launch a browser on click\n* `clear_before_run` option lets you control whether or not subsequent program runs start from a fresh terminal or preserve the previous output.\n* Conditionally show/hide restart button\n* `requires_shell` option - controls how `Popen` spawns your program. By default (and historically), this value is False.\n* Optionally silence textual progress updates when using the Progress widget (via @conradhilley)\n* Multi-Directory Choosers - these were accidentally dropped from the project. @HenrykHaniewicz added them back!\n* Additional explicit wx imports to make packaging on OSX easier\n* Textfields can now be made Readonly for informational purposes\n* better custom target support via `suppress_gooey_flag` which prevents the `--ignore-gooey` flag from being injected\n\n\n\n\n## Breaking Changes\n\nNo breaking changes between `1.0.0` and `1.1.0`!\n\n## Language Additions / Improvements\n\n* Completed Italian translation - @gison93\n\n* Updated French translation - @NathanRichard\n* Updated Hebrew translation - @eturkes\n\n\n\n## Bug Fixes\n\n* Fixed 5 year old bug(!) where an errant lambda function wasn't passing through all of its arguments which caused frustratingly opaque failures under specific conditions.\n* Fixed bug where external updates weren't applied to `ListBox`\n* Fix bug where tuples weren't coerced to List which causes concatenation errors\n* Fixed bug where string coercion in `argparse_to_json` was too broad and caused type errors\n* Fixed bug where wrong validator was applied to Dropdown type causing preflight checks to always fail\n* Fixed bug where Radio Groups would apply too much vertical spacing between components\n* Fixed bug where subgroups with single items were attached to the wrong UI parent\n* Fixed bug where legacy default groups weren't being translated\n* Fixed bug where certain languages would sometimes cause components to be rendered off screen\n\n\n"
  },
  {
    "path": "docs/releases/1.0.4-release-notes.md",
    "content": "# Gooey 1.0.4 Released!\n\nGooey picked up some cool new widget types thanks to awesome contributions from @NathanRichard and @conradhilley. \n\nThe rest of this release was focused on bug fixes and quality improvements. My commitment is to having Gooey be a stable, reliable project. This has required slowly shedding it's fast and loose hobby project past. Test coverage more than doubled between 1.0.3 and 1.0.4 and several bugs were fixed along the way as a result of this. The next few releases of Gooey will be similarly focused on bringing its codebase up to snuff so that wider changes can be made without introducing unexpected regressions.  \n\n\n## Upgrading from 1.0.3 to 1.0.4\n\nTranslation notice! Yes/No options in confirmation modals are now backed by the language files. Previously, they were fixed to english regardless of the selected language. If the new language options aren't configured for your language, you will now see a translation request in the button label! \n\n\n## What's new\n\n\n### Widgets: TimeChooser\n\n<p align=\"center\">\n    <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.4/time-chooser-demo.JPG\">\n</p>\n\nUsage: \n\n```python\nparser = GooeyParser()\nparser.add_argument('--time', widget='TimeChooser')\n``` \n\n@NathanRichard added this one after an excellent deep dive into the complexities of dealing with time inside of WX. See the README for notes on usage. \n\n\n### Widgets: ColourChooser\n\n<p align=\"center\">\n    <img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.4/color-chooser-demo.jpg\">\n</p>\n\nUsage: \n\n```python\nparser = GooeyParser()\nparser.add_argument('--color', widget='ColourChooser')\n``` \n\n@conradhilley brought this one to life. You can now select colors from an awesome little chooser widget.  \n\n\n### CLI based defaults \n\n@jschultz added the ability to use arguments passed on the command line as defaults in Gooey.  Enable this functionality by passing `use_cmd_args` to the Gooey decorator. \n\n```python\n@Gooey(use_cmd_args=True)\ndef main():\n    parser = ArgumentParser()\n    parser.add_argument('--foo')\n```\n\nNow any CLI args you pass when invoking your program will show up as defaults in Gooey. \n\n```\npython my_program.py --foo \"hello!\" \n```\n\n### Additional features\n\n - Added option to start Gooey in full screen mode\n\n\n## Language Additions / Improvements\n\n * @foben - documentation fixes \n * @gediz - turkish translations \n * @dsardelic - bosnian & Croatian translations\n * @partrita - Korean translations \n\n\n## Bug Fixes \n\n - Main README image had a typo \"Command Lines Applications\" \n - Truthy values \n - Fixed bug where nargs in textfields weren't being mapped correctly\n - Fixed bug where argparse's SUPPRESS flag was showing in the UI\n - Fixed missing i18n capabilities in modals\n - Fixed bug where program_description wasn't being honored\n - Fixed bug where gooey_options weren't being honored in the Header\n - Fixed bug where RadioGroup wasn't enabling it's child widget when `initial_selection` was set\n - Fixed bug where checkboxes weren't honoring visibility options\n - Fixed bug where gooey_options weren't being passed to footer \n    \n \n\n"
  },
  {
    "path": "docs/releases/1.0.5-release-notes.md",
    "content": "## Gooey 1.0.5 Released! \n\nGooey is now using WX 4.1.0!\n\nThis change should resolve several issues in Ubuntu as well as the numerous other quirks which have been reported.\n\n## Thank you to the current Patreon supporters! \n\n* Qteal\n* Joseph Rhodes\n\n\n# New widgets: \n\n### FilterableDropdown\n\n![Filterable Dropdown](https://user-images.githubusercontent.com/1408720/97120143-6649fc00-16d2-11eb-95a9-f8c49cae055f.gif)\n\nYou can checkout a runnable example in the GooeyExamples repo [here](https://github.com/chriskiehl/GooeyExamples/blob/1.0.5-release/examples/FilterableDropdown.py)\n\nExample Code: \n\n```python\nadd_argument(\n    choices=['a', 'b', 'c'],\n    widget='FilterableDropdown',\n    gooey_options={\n        'no_match': 'No results found!',\n        'placeholder': 'Type something!'\n})\n```\n\nThis introduces a new language translation key: \"no_matches_found\" to handle the case where the user's input doesn't match any of the choices. This is used by default, but can be overridden via gooey options\n\n### Elapsed Time / Estimated time remaining\n\n![fbHcpCAGD8](https://user-images.githubusercontent.com/19178331/85913252-592d1580-b876-11ea-8def-25b12732b9cb.gif)\n\n@JackMcKew put in a herculean effort and introduced a new feature where elapsed and estimated remaining time can be shown in addition to the standard progress bar. \n\nYou can checkout an example [here](https://github.com/chriskiehl/GooeyExamples/blob/master/examples/example_time_remaining.py)\n\nExample Code: \n\n```python\n@Gooey(timing_options={\n    'show_time_remaining':True,\n    'hide_time_remaining_on_complete':True\n})\n```   \n\n\n## Breaking Changes \n\n* (documentation breaking)`terminal_font_weight`'s public documented API allowed the strings \"NORMAL\" and \"BOLD\" while its internal implementation relied on numeric font weights (light=200, normal=300, etc..). The documentation was updated to show the correct usage and a constants file was added to the public API.   \n\n\n## Functionality\n\n* @neonbunny enabled Parsers to use configuration from parents. \n* @eladeyal-intel updated `RichTextConsole` to allow control+scrollwheel to zoom the text \n \n \n\n## Language Additions / Improvements\n\n* @soleil0-0 - Additional Chinese translation\n* @dancergraham - Additional French translation \n* @ajvirSingh1313 - Hindi translation \n \n\n## Bug Fixes \n\n* Fixed bug where dynamic updates to a Dropdown would cause the selection to be lost \n* Fixed performance issues where dynamic updates with large items would cause Gooey to hang\n* @rotu fixed a bug in dynamic updates related to `Popen` usage.\n* @neonbunny - resolved warning cause by missing return statement\n* Fixed bug where terminal font and colors were not being set correctly\n* Fixed mysterious RadioGroup issue where underlying WxWidgets would 'forget' the current selection under certain circumstances \n  \n\n"
  },
  {
    "path": "docs/releases/1.0.6-release-notes.md",
    "content": "## Gooey 1.0.6 Released! \n\n\nThis is a minor release beefing up the new FilterableDropdown's search capabilities and performance. In the previous release, the dropdown was backed by WX's `ListBox` widget. 1.0.6 replaces this for a fully virtualized version which allows Gooey to operate on massive datasets without taking a hit to UI performance. Additionally, how Gooey internally filters for matches has also been updated. Choice are now backed by a trie for super fast lookup even against large data sets. Tokenization and match strategies can be customized to support just about any lookup style.      \n\nHead over to the [Examples Repo](https://github.com/chriskiehl/GooeyExamples) to see the updated demo which now uses a dataset consisting of about 25k unique items.  \n\n\n**New Gooey Options:**\n\n`FilterableDropdown` now takes a `search_strategy` in its `gooey_options`.   \n\n```python\nfrom gooey import Gooey, GooeyParser, PrefixTokenizers\n\ngooey_options={\n    'label_color': (255, 100, 100),\n    'placeholder': 'Start typing to view suggestions',\n    'search_strategy': {\n        'type': 'PrefixFilter',\n        'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE,\n        'input_tokenizer': PrefixTokenizers.REGEX('\\s'),\n        'ignore_case': True,\n        'operator': 'AND',\n        'index_suffix': False\n    }\n})\n```\n\nThis gives control over how the choices and user input get tokenized, as well as how those tokenized matches get treated (ANDed together vs ORd). Want to match on any part of any word? Enable the `index_suffix` option to index all of your candidate words by their individual parts. e.g. \n\n```\nWord: 'Banana' \nSuffixes: ['Banana', 'anana', 'nana', 'ana']\n```\n\nThese all get loaded into a trie for super fast lookup. Combine this with the `WORDs` tokenizer, and you get really fine grained search though your options! \n\n\n## Thank you to the current Patreon supporters! \n\n* Qteal\n* Joseph Rhodes\n\n\n## Breaking Changes \n\nNo breaking changes from 1.0.5.\n\n \n\n"
  },
  {
    "path": "docs/releases/1.0.7-release-notes.md",
    "content": "## Gooey 1.0.7 Released! \n\nLots of new stuff this release! We've got 3 new widget types, new gooey_options, as well as some quality of Life improvements for using Gooey Options. \n\n\n### New Widgets: IntegerField, DecimalField, and Slider\n\n<p align=\"center\"><img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.7/numeric-inputs.gif\" ></p>\n\n\nGooey now has 3 inputs specifically geared towards accepting numeric inputs. Previously, all Gooey had were text fields which you could add `validators` to in order to enforce only numbers were entered, but now we have top level widgets which do all of that out of the box! \n  \n**Important Usage Note:** since these numeric inputs don't allow any non-numeric characters to be entered, they do **not** give you the ability to blank them out. Unlike a `TextField` which can be left empty and thus have its value not passed to your program, the numeric inputs will always send a value. Thus, you have to have sane handling in user-land.   \n\nCheckout the [Options docs](https://github.com/chriskiehl/Gooey/blob/master/docs/Gooey-Options.md) for more details. \n\n \n### New Gooey Options: placeholder \n\n<p align=\"center\"><img src=\"https://github.com/chriskiehl/GooeyImages/raw/images/docs/releases/1.0.7/placeholders.gif\" ></p>\n\nWidgets with text inputs now all accept a `placeholder` Gooey option. \n\n```python\nadd_argument('--foo', widget='TextField', gooey_options=options.TextField(\n    placeholder='Type some text here!'\n)\n\n# or without the options helper \nadd_argument('--foo', widget='TextField', gooey_options={\n    'placeholder': 'Type some text here!'\n})\n```\n\n\n### New Validator option: RegexValidator\n\n```python\nadd_argument('--foo', widget='TextField', gooey_options=options.TextField(\n    placeholder='Type some text here!',\n    validator=options.RegexValidator(\n        test='\\d{4}',\n        message='Must be exactly 4 digits long!'\n    )\n)\n\n# or without the options helper \nadd_argument('--foo', widget='TextField', gooey_options={\n    'placeholder': 'Type some text here!',\n    'validator': {\n        'type': 'RegexValidator',\n        'test': '\\d{4}',\n        'message': 'Must be exactly 4 digits long!'\n    }\n})\n```\n\n \n### New feature: Options helpers \n\nGooey now has a top-level `options` module which can be imported. Previously, Gooey Options have been an opaque map. While great for openness / extensibility, it's pretty terrible from a discoverability / \"what does this actually take again..?\" perspective. The new `options` module aims to make using `gooey_options` easier and more discoverable. \n\n```python\nfrom gooey import options\n```\n\nThe goal is to enable IDE's to provide better auto-completion help as well as more REPL driven usefulness via help() and docstrings. \n\n```python\nfrom gooey import options\n\nparser.add_argument(\n    '--foo', \n    help='Some foo thing',\n    widget='FilterableDropdown',\n    gooey_options=options.FilterableDropdown(\n        placeholder='Search for a Foo',\n        search_strategy=options.PrefixSearchStrategy(\n            ignore_case=True \n        )\n    ))\n``` \n\nNote that these are _just_ helpers for generating the right data shapes. They're still generating plain data behind the scenes and thus all existing `gooey_options` code remains 100% compatible. \n\n**Better Docs:**\n\nWhich is to say, documentation which actually exists rather than _not_ exist. You can inspect the docs live in the REPL or by hopping to the symbol in editors which support such things. \n\n```\n>>> from gooey import options \n>>> help(options.RadioGroup) \nHelp on function FileChooser in module __main__:\n\nFileChooser(wildcard=None, default_dir=None, default_file=None, message=None, **layout_options)\n    :param wildcard: Sets the wildcard, which can contain multiple file types, for \n                     example: \"BMP files (.bmp)|.bmp|GIF files (.gif)|.gif\"\n    :param message:  Sets the message that will be displayed on the dialog.\n    :param default_dir: The default directory selected when the dialog spawns \n    :param default_file: The default filename used in the dialog\n    \n    Layout Options:\n    ---------------\n    \n    Color options can be passed either as a hex string ('#ff0000') or as\n    a collection of RGB values (e.g. `[255, 0, 0]` or `(255, 0, 0)`)\n    \n    :param label_color:    The foreground color of the label text\n    :param label_bg_color: The background color of the label text.\n    :param help_color:     The foreground color of the help text.\n    :param help_bg_color:  The background color of the help text.\n    :param error_color:    The foreground color of the error text (when visible).\n    :param error_bg_color: The background color of the error text (when visible).\n    :param show_label:     Toggles whether or not to display the label text\n    :param show_help:      Toggles whether or not to display the help text\n    :param visible:        Hides the entire widget when False. Note: the widget\n                           is still present in the UI and will still send along any\n                           default values that have been provided in code. This option\n                           is here for when you want to hide certain advanced / dangerous\n                           inputs from your GUI users.\n    :param full_width:     This is a layout hint for this widget. When True the widget\n                           will fill the entire available space within a given row.\n                           Otherwise, it will be sized based on the column rules\n                           provided elsewhere. \n```\n\nIdeally, and eventually, we'll be able to completely type these options to increase visibility / usability even more. However, for backwards compatibility reasons, Gooey will continue to be sans anything more than the most basic of type hinting for the time being.\n\n\n## Breaking Changes \n\n**No breaking API changes from 1.0.6 to 1.0.7.** However, the _strictness_ of existing Gooey Options has been increased, which _could_ result in issues when upgrading from 1.0.6. In an attempt to be helpful, Gooey now throws an exception if invalid Gooey Options are supplied. This is to catch things like invalid types or ill-formed data. If you were passing bad data in 1.0.6, it will now be flagged as such in 1.0.7.   \n\n\n## Thank you to the current [Patreon supporters](https://www.patreon.com/chriskiehl)! \n\n* Sponsors: \n    * Qteal\n* Individuals: \n    * Joseph Rhodes\n    * Nicholas \n    \n\n    \n    "
  },
  {
    "path": "docs/releases/1.0.8-release-notes.md",
    "content": "## Gooey 1.0.8 Released! \n\n\nAnother minor Gooey release! This one brings a new global Gooey Option for setting initial values in the UI, support for `version` action types, plus a few bug/linting fixes.\n\nAdditionally, I continue to plug away at getting the test coverage to useful levels. We're now pushing 80% coverage which is making working on Gooey with confidence much easier!   \n\n\n### New Gooey Options: initial_value \n\nThis option lets you specify the value present in the widget when Gooey starts. \n\n```python\nparser.add_argument('-my-arg', widget='Textarea', gooey_options={\n    'initial_value': 'Hello world!'  \n})\n```\n\nOr, using the new `options` helpers: \n\n```python\nfrom gooey import options \nparser.add_argument('-my-arg', widget='Textarea', gooey_options=options.Textarea(\n    initial_value='Hello World!'\n))\n```\n\nIf you've been using Gooey awhile, you'll recognize that this overlaps with the current behavior of `default`. The new `initial_value` enables you to supply a truly optional seed value to the UI. When using `default`, even if the user clears your value out of the UI, argparse will add it back in when it parses the CLI string. While this is often useful behavior, it prevents certain workflows from being possible. `initial_value` let's you control the UI independent of argparse. This means you can now, for instance, set a checkbox to be checked by default in the UI, but optionally allow the user to deselect it without having argprase re-populate the 'checked' state (a behavior which comes up frequently in the issue tracker due to it being technically correct, but also very confusing!). \n\n\n### action=version support \n\nWhen using `action='version'` Gooey will now map it a CheckBox widget type. \n\n\n### Other Fixes / Changes: \n\n * Bug fix: add missing translation step for tabbed group titles (@neonbunny)\n * Linting: swap `is not` for `!=` (@DrStrinky) \n\n\n## Breaking Changes \n\n**No breaking API changes from 1.0.7 to 1.0.8**   \n\n\n## Thank you to the current [Patreon supporters](https://www.patreon.com/chriskiehl)! \n\n* Sponsors: \n    * Qteal\n* Individuals: \n    * Joseph Rhodes\n    * Nicholas \n    * Joey\n    \n\n    \n    "
  },
  {
    "path": "docs/releases/1.0.8.1-release-notes.md",
    "content": "## Gooey 1.0.8.1 Released! \n\n\nThis is a tiny intermediate release which just loosen Gooey's WxPython dependency from `=4.1.0` to `>=4.1.0` in `setup.py`. The strict version requirement was causing numerous installation issues across environments.\n\n  "
  },
  {
    "path": "docs/releases/1.2.0-ALPHA-release-notes.md",
    "content": "# Gooey 1.2.0-ALPHA Released! \n\n### Warning: \n\n>**Upgrade with caution!** 1.2.0 removes the experimental Dynamic Updates feature and replaces it with a _new_ experimental Dynamic Updates feature! The two APIs are incompatible.    \n\nThis release brings a whole host of new features to Gooey. Chief among them are the new Dynamic Updates and Validation functionality. This was effectively a rebuild of a substantial portion of Gooey's internal to enable a more client/server style functionality. This means that you have more control over the gooey's lifecycle, and can subscribe to high level events. Currently, FormSubmit, OnComplete, and OnError are supported, but more are on their way! Soon you'll be able to have fine grained control over the UI and its presentation, and still without having to write a single line of traditional GUI code! \n\n\n### Breaking Changes (1.0.8 -> 1.2.0) \n\n * **Validation** - the validation mechanism available via gooey_options has been removed entirely in favor of the new API.   \n * **Dynamic Updates** - there was previously minimal support for loading new data at run time. This has been revomed in favor of a new system which gives advanced control over the state of the UI. \n\n### New Features\n\n* **Dynamic Updates and Validation** - Checkout the [README](https://github.com/chriskiehl/Gooey/blob/master/README.md) for details on how to get started. This feature is really hairy behind the scenes and involves all kinds of crazy monkey patching in order to work. Odds of encountering a bug or scenario that doesn't work for your use case is high in this initial release. Please fill out an issue if any problems pop up! Checkout [the examples repo](https://github.com/chriskiehl/GooeyExamples/blob/master/examples/lifecycle_hooks.py) to see the new lifecycle hooks in action. \n* **Graceful Shutdown control** - Gooey previously would `SIGTERM` your application when you tried to halt it while running. However, with 1.2.0, you have control over which signal Gooey sends when you request a shutdown. This gives you a chance to catch that signal and clean up and resources currently un use before shutting down.\n* **Better sys.argv handling** - Gooey no longer mutates the global sys.argv variable. This caused people all kinds of problems -- most frequent being Gooey spawning multiple windows. This is now removed, and hopefully all the pain stemming from it as well. \n     \n\n\n \n  \n  \n "
  },
  {
    "path": "docs/releases/pypi-distribution.md",
    "content": "# Testing PyPi distribution before upload\n\nThe 1.0.4 release was botched when uploading to PyPi as it pulled in the 1.0.3 artifacts sitting in my dev directory.  This meant that the 1.0.4 version was now clobbered on PyPi and could no longer be used. More care is needed when deploying. \n\n\n### How to test locally before uploading\n\n1\\. build the wheel\n\n```\npython pip_build_wheel.py\n``` \n\nthis will output the wheel to the `dist/` directory. \n\n2/. Copy the file location. \n\nCopy the absolute path to the .gz output file. It will look something like this:  \n\n```\ndist/Gooey-1.0.4.tar.gz\n```\n\n3\\. In a different virtual environment, install the local wheel \n\n```\ncd ~/projects/GooeyExamples\nvirtualenv venv \nsource ./venv/Scripts/activate\npip install /path/to/local/dist/Gooey-1.0.4.tar.gz\n``` \n\nIf everything installs OK, you're good to upload.\n\n```\npython pip_deploy.py\n```\n\n "
  },
  {
    "path": "docs/releases/release-checklist.md",
    "content": "# Release Checklist \n\n\n - [ ] Release commit is tagged \n - [ ] The next release-branch is created \n - [ ] CONTRIBUTING.md has been updated to point at the next release branch\n - [ ] Release is created on Github \n - [ ] All tests pass on 2.7 and 3.x \n - [ ] All warnings are resolved (run tests with `PYTHONWARNINGS=default`)\n - [ ] All Gooey Examples run and work as expected \n - [ ] All new features have corresponding examples \n - [ ] All new features have README updates \n - [ ] Wx Inspection tool is removed from the runner\n - [ ] all debug prints removed  \n - [ ] setup.py version is updated \n - [ ] __init__.py version is updated\n - [ ] types check (for the most part) `./venv/Scripts/python.exe -m mypy /path/to/python_bindings/types.py`\n - [ ] pip install of release branch works.   \n     - [ ] All Gooey Examples run and work as expected\n - [ ] pypi is updated \n - [ ] pypi pip install tested 2.7 & 3.x\n     - [ ] All Gooey Examples run and work as expected\n - [ ] Release notes written: \n     - [ ] major features \n     - [ ] bug fixes\n     - [ ] language additions\n     - [ ] breaking changes  \n     - [ ] contributors \n  \n\n\n \n"
  },
  {
    "path": "gooey/__init__.py",
    "content": "import os\r\nfrom gooey.python_bindings.gooey_decorator import Gooey\r\nfrom gooey.python_bindings.gooey_parser import GooeyParser\r\nfrom gooey.gui.util.freeze import localResourcePath as local_resource_path\r\nfrom gooey.python_bindings import constants\r\nfrom gooey.python_bindings.constants import Events\r\nfrom gooey.gui.components.filtering.prefix_filter import PrefixTokenizers\r\nfrom gooey.gui.components.options import options\r\nfrom gooey.python_bindings import types\r\ntypes = types\r\n__version__ = '1.2.0-ALPHA'\r\n"
  },
  {
    "path": "gooey/__main__.py",
    "content": "# '''\r\n# Delegates arguments to the main Gooey runner\r\n#\r\n# For use when run directly from command line with the -m (module) flag:\r\n#\r\n#   e.g. $ python -m gooey\r\n#\r\n# '''\r\n#\r\n# from gooey import application\r\n#\r\n# application.main()\r\n"
  },
  {
    "path": "gooey/gui/__init__.py",
    "content": "__author__ = 'Chris'\r\n"
  },
  {
    "path": "gooey/gui/application/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/gui/application/application.py",
    "content": "import sys\nfrom json import JSONDecodeError\n\nimport six\nimport wx  # type: ignore\n\nfrom gooey import Events\nfrom gooey.gui import events\nfrom gooey.gui import host\nfrom gooey.gui import state as s\nfrom gooey.gui.application.components import RHeader, ProgressSpinner, ErrorWarning, RTabbedLayout, \\\n    RSidebar, RFooter\nfrom gooey.gui.components import modals\nfrom gooey.gui.components.config import ConfigPage\nfrom gooey.gui.components.config import TabbedConfigPage\nfrom gooey.gui.components.console import Console\nfrom gooey.gui.components.menubar import MenuBar\nfrom gooey.gui.lang.i18n import _\nfrom gooey.gui.processor import ProcessController\nfrom gooey.gui.pubsub import pub\nfrom gooey.gui.state import FullGooeyState\nfrom gooey.gui.state import initial_state, ProgressEvent, TimingEvent\nfrom gooey.gui.util.wx_util import transactUI, callafter\nfrom gooey.python_bindings import constants\nfrom gooey.python_bindings.dynamics import unexpected_exit_explanations, \\\n    deserialize_failure_explanations\nfrom gooey.python_bindings.types import PublicGooeyState\nfrom gooey.python_bindings.types import Try\nfrom gooey.util.functional import assoc\nfrom gooey.gui.util.time import Timing\nfrom rewx import components as c  # type: ignore\nfrom rewx import wsx  # type: ignore\nfrom rewx.core import Component, Ref  # type: ignore\n\n\nclass RGooey(Component):\n    \"\"\"\n    Main Application container for Gooey.\n\n    State Management\n    ----------------\n\n    Pending further refactor, state is tracked in two places:\n    1. On this instance (React style)\n    2. In the WX Form Elements themselves[0]\n\n    As needed, these two states are merged to form the `FullGooeyState`, which\n    is the canonical state object against which all logic runs.\n\n\n    Dynamic Updates\n    ---------------\n\n\n\n\n    [0] this is legacy and will (eventually) be refactored away\n\n    \"\"\"\n    def __init__(self, props):\n        super().__init__(props)\n        self.frameRef = Ref()\n        self.consoleRef = Ref()\n        self.configRef = Ref()\n\n        self.buildSpec = props\n        self.state = initial_state(props)\n        self.headerprops = lambda state: {\n            'background_color': self.buildSpec['header_bg_color'],\n            'title': state['title'],\n            'show_title': state['header_show_title'],\n            'subtitle': state['subtitle'],\n            'show_subtitle': state['header_show_subtitle'],\n            'flag': wx.EXPAND,\n            'height': self.buildSpec['header_height'],\n            'image_uri': state['image'],\n            'image_size': (six.MAXSIZE, self.buildSpec['header_height'] - 10)}\n\n        self.fprops = lambda state: {\n            'buttons': state['buttons'],\n            'progress': state['progress'],\n            'timing': state['timing'],\n            'bg_color': self.buildSpec['footer_bg_color'],\n            'flag': wx.EXPAND,\n        }\n        self.clientRunner = ProcessController.of(self.buildSpec)\n        self.timer = None\n\n\n    def component_did_mount(self):\n        pub.subscribe(events.WINDOW_START, self.onStart)\n        pub.subscribe(events.WINDOW_RESTART, self.onStart)\n        pub.subscribe(events.WINDOW_STOP, self.handleInterrupt)\n        pub.subscribe(events.WINDOW_CLOSE, self.handleClose)\n        pub.subscribe(events.WINDOW_CANCEL, self.handleCancel)\n        pub.subscribe(events.WINDOW_EDIT, self.handleEdit)\n        pub.subscribe(events.CONSOLE_UPDATE, self.consoleRef.instance.logOutput)\n        pub.subscribe(events.EXECUTION_COMPLETE, self.handleComplete)\n        pub.subscribe(events.PROGRESS_UPDATE, self.updateProgressBar)\n        pub.subscribe(events.TIME_UPDATE, self.updateTime)\n        # # Top level wx close event\n        frame: wx.Frame = self.frameRef.instance\n        frame.Bind(wx.EVT_CLOSE, self.handleClose)\n        frame.SetMenuBar(MenuBar(self.buildSpec))\n        self.timer = Timing(frame)\n\n        if self.state['fullscreen']:\n            frame.ShowFullScreen(True)\n\n        if self.state['show_preview_warning'] and not 'unittest' in sys.modules.keys():\n            wx.MessageDialog(None, caption='YOU CAN DISABLE THIS MESSAGE',\n                             message=\"\"\"\n                This is a preview build of 1.2.0! There may be instability or \n                broken functionality. If you encounter any issues, please open an issue \n                here: https://github.com/chriskiehl/Gooey/issues \n                \n                The current stable version is 1.0.8. \n                \n                NOTE! You can disable this message by setting `show_preview_warning` to False. \n                \n                e.g. \n                `@Gooey(show_preview_warning=False)`\n                \"\"\").ShowModal()\n\n    def getActiveConfig(self):\n        return [item\n                for child in self.configRef.instance.Children\n                # we descend down another level of children  to account\n                # for Notebook layouts (which have wrapper objects)\n                for item in [child] + list(child.Children)\n                if isinstance(item, ConfigPage)\n                or isinstance(item, TabbedConfigPage)][self.state['activeSelection']]\n\n    def getActiveFormState(self):\n        \"\"\"\n        This boiler-plate and manual interrogation of the UIs\n        state is required until we finish porting the Config Form\n        over to rewx (which is a battle left for another day given\n        its complexity)\n        \"\"\"\n        return self.getActiveConfig().getFormState()\n\n\n    def fullState(self):\n        \"\"\"\n        Re: final porting is a to do. For now we merge the UI\n        state into the main tracked state.\n        \"\"\"\n        formState = self.getActiveFormState()\n        return s.combine(self.state, self.props, formState)\n\n\n    def onStart(self, *args, **kwargs):\n        \"\"\"\n        Dispatches the start behavior.\n        \"\"\"\n        if Events.VALIDATE_FORM in self.state['use_events']:\n            self.runAsyncValidation()\n        else:\n            self.startRun()\n\n\n    def startRun(self):\n        \"\"\"\n        Kicks off a run by invoking the host's code\n        and pumping its stdout to Gooey's Console window.\n        \"\"\"\n        state = self.fullState()\n        if state['clear_before_run']:\n            self.consoleRef.instance.Clear()\n        self.set_state(s.consoleScreen(_, state))\n        self.clientRunner.run(s.buildInvocationCmd(state))\n        self.timer.start()\n        self.frameRef.instance.Layout()\n        for child in self.frameRef.instance.Children:\n            child.Layout()\n\n\n    def syncExternalState(self, state: FullGooeyState):\n        \"\"\"\n        Sync the UI's state to what the host program has requested.\n        \"\"\"\n        self.getActiveConfig().syncFormState(s.activeFormState(state))\n        self.frameRef.instance.Layout()\n        for child in self.frameRef.instance.Children:\n            child.Layout()\n\n\n    def handleInterrupt(self, *args, **kwargs):\n        if self.shouldStopExecution():\n            self.clientRunner.stop()\n\n    def handleComplete(self, *args, **kwargs):\n        self.timer.stop()\n        if self.clientRunner.was_success():\n            self.handleSuccessfulRun()\n            if Events.ON_SUCCESS in self.state['use_events']:\n                self.runAsyncExternalOnCompleteHandler(was_success=True)\n        else:\n            self.handleErrantRun()\n            if Events.ON_ERROR in self.state['use_events']:\n                self.runAsyncExternalOnCompleteHandler(was_success=False)\n\n    def handleSuccessfulRun(self):\n        if self.state['return_to_config']:\n            self.set_state(s.editScreen(_, self.state))\n        else:\n            self.set_state(s.successScreen(_, self.state))\n            if self.state['show_success_modal']:\n                wx.CallAfter(modals.showSuccess)\n\n\n    def handleErrantRun(self):\n        if self.clientRunner.wasForcefullyStopped:\n            self.set_state(s.interruptedScreen(_, self.state))\n        else:\n            self.set_state(s.errorScreen(_, self.state))\n            if self.state['show_failure_modal']:\n                wx.CallAfter(modals.showFailure)\n\n\n    def successScreen(self):\n        strings = {'title': _('finished_title'), 'subtitle': _('finished_msg')}\n        self.set_state(s.success(self.state, strings, self.buildSpec))\n\n\n    def handleEdit(self, *args, **kwargs):\n        self.set_state(s.editScreen(_, self.state))\n\n    def handleCancel(self, *args, **kwargs):\n        if modals.confirmExit():\n            self.handleClose()\n\n    def handleClose(self, *args, **kwargs):\n        \"\"\"Stop any actively running client program, cleanup the top\n        level WxFrame and shutdown the current process\"\"\"\n        # issue #592 - we need to run the same onStopExecution machinery\n        # when the exit button is clicked to ensure everything is cleaned\n        # up correctly.\n        frame: wx.Frame = self.frameRef.instance\n        if self.clientRunner.running():\n            if self.shouldStopExecution():\n                self.clientRunner.stop()\n                frame.Destroy()\n                # TODO: NOT exiting here would allow\n                # spawing the gooey to input params then\n                # returning control to the CLI\n                sys.exit()\n        else:\n            frame.Destroy()\n            sys.exit()\n\n    def shouldStopExecution(self):\n        return not self.state['show_stop_warning'] or modals.confirmForceStop()\n\n    def updateProgressBar(self, *args, progress=None):\n        self.set_state(s.updateProgress(self.state, ProgressEvent(progress=progress)))\n\n    def updateTime(self, *args, elapsed_time=None, estimatedRemaining=None, **kwargs):\n        event = TimingEvent(elapsed_time=elapsed_time, estimatedRemaining=estimatedRemaining)\n        self.set_state(s.updateTime(self.state, event))\n\n    def handleSelectAction(self, event):\n        self.set_state(assoc(self.state, 'activeSelection', event.Selection))\n\n\n    def runAsyncValidation(self):\n        def handleHostResponse(hostState: PublicGooeyState):\n            self.set_state(s.finishUpdate(self.state))\n            currentState = self.fullState()\n            self.syncExternalState(s.mergeExternalState(currentState, hostState))\n            if not s.has_errors(self.fullState()):\n                self.startRun()\n            else:\n                self.set_state(s.editScreen(_, s.show_alert(self.fullState())))\n\n        def onComplete(result: Try[PublicGooeyState]):\n            result.onSuccess(handleHostResponse)\n            result.onError(self.handleHostError)\n\n        self.set_state(s.beginUpdate(self.state))\n        fullState = self.fullState()\n        host.communicateFormValidation(fullState, callafter(onComplete))\n\n\n    def runAsyncExternalOnCompleteHandler(self, was_success):\n        def handleHostResponse(hostState):\n            if hostState:\n                self.syncExternalState(s.mergeExternalState(self.fullState(), hostState))\n\n        def onComplete(result: Try[PublicGooeyState]):\n            result.onError(self.handleHostError)\n            result.onSuccess(handleHostResponse)\n\n        if was_success:\n            host.communicateSuccessState(self.fullState(), callafter(onComplete))\n        else:\n            host.communicateErrorState(self.fullState(), callafter(onComplete))\n\n\n    def handleHostError(self, ex):\n        \"\"\"\n        All async errors get pumped here where we dump out the\n        error and they hopefully provide a lot of helpful debugging info\n        for the user.\n        \"\"\"\n        try:\n            self.set_state(s.errorScreen(_, self.state))\n            self.consoleRef.instance.appendText(str(ex))\n            self.consoleRef.instance.appendText(str(getattr(ex, 'output', '')))\n            self.consoleRef.instance.appendText(str(getattr(ex, 'stderr', '')))\n            raise ex\n        except JSONDecodeError as e:\n            self.consoleRef.instance.appendText(deserialize_failure_explanations)\n        except Exception as e:\n            self.consoleRef.instance.appendText(unexpected_exit_explanations)\n        finally:\n            self.set_state({**self.state, 'fetchingUpdate': False})\n\n\n    def render(self):\n        return wsx(\n            [c.Frame, {'title': self.buildSpec['program_name'],\n                       'background_color': self.buildSpec['body_bg_color'],\n                       'double_buffered': True,\n                       'min_size': (400, 300),\n                       'icon_uri': self.state['images']['programIcon'],\n                       'size': self.buildSpec['default_size'],\n                       'ref': self.frameRef},\n             [c.Block, {'orient': wx.VERTICAL},\n              [RHeader, self.headerprops(self.state)],\n              [c.StaticLine, {'style': wx.LI_HORIZONTAL, 'flag': wx.EXPAND}],\n              [ProgressSpinner, {'show': self.state['fetchingUpdate']}],\n              [ErrorWarning, {'show': self.state['show_error_alert'],\n                              'uri': self.state['images']['errorIcon']}],\n              [Console, {**self.buildSpec,\n                         'flag': wx.EXPAND,\n                         'proportion': 1,\n                         'show': self.state['screen'] == 'CONSOLE',\n                         'ref': self.consoleRef}],\n              [RTabbedLayout if self.buildSpec['navigation'] == constants.TABBED else RSidebar,\n               {'bg_color': self.buildSpec['sidebar_bg_color'],\n                'label': 'Some Action!',\n                'tabbed_groups': self.buildSpec['tabbed_groups'],\n                'show_sidebar': self.state['show_sidebar'],\n                'ref': self.configRef,\n                'show': self.state['screen'] == 'FORM',\n                'activeSelection': self.state['activeSelection'],\n                'options': list(self.buildSpec['widgets'].keys()),\n                'on_change': self.handleSelectAction,\n                'config': self.buildSpec['widgets'],\n                'flag': wx.EXPAND,\n                'proportion': 1}],\n              [c.StaticLine, {'style': wx.LI_HORIZONTAL, 'flag': wx.EXPAND}],\n              [RFooter, self.fprops(self.state)]]]\n        )\n\n\n\n\n"
  },
  {
    "path": "gooey/gui/application/components.py",
    "content": "\"\"\"\nHouses all the supporting rewx components for\nthe main application window.\n\"\"\"\nimport wx  # type: ignore\nfrom typing_extensions import TypedDict\n\nfrom gooey.gui.components.config import ConfigPage, TabbedConfigPage\nfrom gooey.gui.components.console import Console\nfrom gooey.gui.components.mouse import notifyMouseEvent\nfrom gooey.gui.components.sidebar import Sidebar\nfrom gooey.gui.components.tabbar import Tabbar\nfrom gooey.gui.lang.i18n import _\nfrom gooey.gui.pubsub import pub\nfrom gooey.gui.state import present_time\nfrom gooey.gui.three_to_four import Constants\nfrom gooey.python_bindings import constants\nfrom rewx import components as c  # type: ignore\nfrom rewx import wsx, mount, update  # type: ignore\nfrom rewx.core import Component, Ref  # type: ignore\nfrom rewx.widgets import set_basic_props  # type: ignore\n\n\ndef attach_notifier(parent):\n    \"\"\"\n    Recursively attaches the mouseEvent notifier\n    to all elements in the tree\n    \"\"\"\n    parent.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\n    for child in parent.Children:\n        attach_notifier(child)\n\n\nclass HeaderProps(TypedDict):\n    background_color: str\n    title: str\n    show_title: bool\n    subtitle: str\n    show_subtitle: bool\n\n\nclass RHeader(Component):\n    def __init__(self, props):\n        super().__init__(props)\n        self.parentRef = Ref()\n\n    def component_did_mount(self):\n        attach_notifier(self.parentRef.instance)\n\n    def render(self):\n        if 'running' not in self.props['image_uri']:\n            imageProps = {\n                'uri': self.props['image_uri'],\n                'size': self.props['image_size'],\n                'flag': wx.RIGHT,\n                'border': 10}\n        else:\n            imageProps = {\n                'size': self.props['image_size'],\n                'flag': wx.RIGHT,\n                'border': 10}\n        return wsx(\n            [c.Block, {'orient': wx.HORIZONTAL,\n                       'ref': self.parentRef,\n                       'min_size': (120, self.props['height']),\n                       'background_color': self.props['background_color']},\n             [c.Block, {'orient': wx.VERTICAL,\n                        'flag': wx.ALIGN_CENTER_VERTICAL | wx.ALL,\n                        'proportion': 1,\n                        'border': 10},\n              [TitleText, {'label': self.props['title'],\n                           'show': self.props['show_title'],\n                           'wx_name': 'header_title'}],\n              [c.StaticText, {'label': self.props['subtitle'],\n                              'show': self.props['show_subtitle'],\n                              'wx_name': 'header_subtitle'}]],\n             [c.StaticBitmap, imageProps]]\n        )\n\n\n\nclass RFooter(Component):\n    def __init__(self, props):\n        super().__init__(props)\n        self.ref = Ref()\n\n    def component_did_mount(self):\n        \"\"\"\n        We have to manually wire up LEFT_DOWN handlers\n        for every component due to wx limitations.\n        See: mouse.py docs for background.\n        \"\"\"\n        block: wx.BoxSizer = self.ref.instance\n        attach_notifier(block)\n\n    def handle(self, btn):\n        def inner(*args, **kwargs):\n            pub.send_message(btn['id'])\n        return inner\n\n    def render(self):\n        return wsx(\n            [c.Block, {'orient': wx.VERTICAL,\n                       'min_size': (30, 53),\n                       'background_color': self.props['bg_color']},\n             [c.Block, {'orient': wx.VERTICAL, 'proportion': 1}],\n             [c.Block, {'orient': wx.HORIZONTAL,\n                        'border': 20,\n                        'flag': wx.EXPAND | wx.LEFT | wx.RIGHT,\n                        'ref': self.ref},\n              [c.Gauge, {'range': 100,\n                         'proportion': 1,\n                         'value': self.props['progress']['value'],\n                         'show': self.props['progress']['show']}],\n              [c.StaticText, {'label': present_time(self.props['timing']),\n                              'flag': wx.LEFT,\n                              'wx_name': 'timing',\n                              'show': self.props['timing']['show'],\n                              'border': 20}],\n              [c.Block, {'orient': wx.HORIZONTAL, 'proportion': 1}],\n              *[[c.Button, {**btn,\n                            'label': _(btn['label_id']),\n                            'min_size': (90, 23),\n                            'flag': wx.LEFT,\n                            'border': 10,\n                            'on_click': self.handle(btn)\n                            }]\n                for btn in self.props['buttons']]],\n             [c.Block, {'orient': wx.VERTICAL, 'proportion': 1}]]\n        )\n\n\nclass RNavbar(Component):\n    def __init__(self, props):\n        super().__init__(props)\n\n    # if self.buildSpec['navigation'] == constants.TABBED:\n    #     navigation = Tabbar(self, self.buildSpec, self.configs)\n    # else:\n    #     navigation = Sidebar(self, self.buildSpec, self.configs)\n    #     if self.buildSpec['navigation'] == constants.HIDDEN:\n    #         navigation.Hide()\n    def render(self):\n        return wsx(\n\n        )\n\ndef VerticalSpacer(props):\n    return wsx([c.Block, {'orient': wx.VERTICAL, 'min_size': (-1, props['height'])}])\n\ndef SidebarControls(props):\n    return wsx(\n        [c.Block, {'orient': wx.VERTICAL,\n                   'min_size': (180, 0),\n                   'size': (180, 0),\n                   'show': props.get('show', True),\n                   'flag': wx.EXPAND,\n                   'proportion': 0,\n                   'background_color': props['bg_color']},\n         [c.Block, {'orient': wx.VERTICAL,\n                    'min_size': (180, 0),\n                    'size': (180, 0),\n                    'flag': wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM,\n                    'border': 10,\n                    'proportion': 1,\n                    'background_color': props['bg_color']},\n          [VerticalSpacer, {'height': 15}],\n          [TitleText, {'label': props['label']}],\n          [VerticalSpacer, {'height': 5}],\n          [c.ListBox, {'choices': props['options'],\n                       'value': props['activeSelection'],\n                       'proportion': 1,\n                       'on_change': props['on_change'],\n                       'flag': wx.EXPAND}],\n          [VerticalSpacer, {'height': 10}]]]\n    )\n\n\ndef ProgressSpinner(props):\n    return wsx(\n        [c.Block, {'flag': wx.EXPAND, 'show': props['show']},\n         [c.Gauge, {'flag': wx.EXPAND,\n                    'value': -1,\n                    'size': (-1, 4)}],\n         [c.StaticLine, {'style': wx.LI_HORIZONTAL,\n                         'flag': wx.EXPAND}]]\n    )\n\n\ndef ErrorWarning(props):\n    return wsx(\n        [c.Block, {'orient': wx.HORIZONTAL,\n                   'background_color': '#fdeded',\n                   'style': wx.SIMPLE_BORDER,\n                   'flag': wx.EXPAND | wx.ALL,\n                   'proportion': 0,\n                   'border': 5,\n                   'min_size': (-1, 45),\n                   'show': props.get('show', True)},\n         [c.StaticBitmap, {'size': (24, 24),\n                           'flag': wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL,\n                           'border': 6,\n                           'uri': props['uri']}],\n         [c.StaticText, {'label': 'Whoops! You have some errors which must be corrected',\n                         'flag': wx.ALIGN_CENTER_VERTICAL}]]\n    )\n\ndef RSidebar(props):\n    return wsx(\n        [c.Block, {'orient': wx.HORIZONTAL,\n                   'show': props.get('show', True),\n                   'flag': props['flag'],\n                   'proportion': props['proportion'],\n                   'ref': props['ref']},\n         [SidebarControls, {**props, 'show': props['show_sidebar']}],\n         [c.StaticLine, {'style': wx.LI_VERTICAL,\n                         'flag': wx.EXPAND,\n                         'min_size': (1, -1)}],\n         *[[TabbedConfigPage if props['tabbed_groups'] else ConfigPage,\n            {'flag': wx.EXPAND,\n             'proportion': 3,\n             'config': config,\n             'show': i == props['activeSelection']}]\n           for i, config in enumerate(props['config'].values())]\n         ]\n    )\n\n\ndef RTabbedLayout(props):\n    return wsx(\n        [c.Notebook, {'flag': wx.EXPAND | wx.ALL,\n                      'show': props.get('show', True),\n                      'proportion': 1,\n                      'on_change': props['on_change'],\n                      'ref': props['ref']},\n         *[[c.NotebookItem,\n            {'title': props['options'][i], 'selected': props['activeSelection'] == i},\n            [TabbedConfigPage if props['tabbed_groups'] else ConfigPage,\n             {'flag': wx.EXPAND,\n              'proportion': 3,\n              'config': config,\n              'show': i == props['activeSelection']}]]\n           for i, config in enumerate(props['config'].values())]]\n    )\n\n\n\ndef layout_choose():\n    def buildNavigation(self):\n        \"\"\"\n        Chooses the appropriate layout navigation component based on user prefs\n        \"\"\"\n        if self.buildSpec['navigation'] == constants.TABBED:\n            navigation = Tabbar(self, self.buildSpec, self.configs)\n        else:\n            navigation = Sidebar(self, self.buildSpec, self.configs)\n            if self.buildSpec['navigation'] == constants.HIDDEN:\n                navigation.Hide()\n        return navigation\n\n\n    def buildConfigPanels(self, parent):\n        page_class = TabbedConfigPage if self.buildSpec['tabbed_groups'] else ConfigPage\n\n        return [page_class(parent, widgets, self.buildSpec)\n                for widgets in self.buildSpec['widgets'].values()]\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nclass TitleText(Component):\n    def __init__(self, props):\n        super().__init__(props)\n        self.ref = Ref()\n\n    def component_did_mount(self):\n        text: wx.StaticText = self.ref.instance\n        font_size = text.GetFont().GetPointSize()\n        text.SetFont(wx.Font(\n            int(font_size * 1.2),\n            wx.FONTFAMILY_DEFAULT,\n            Constants.WX_FONTSTYLE_NORMAL,\n            wx.FONTWEIGHT_BOLD,\n            False\n        ))\n\n    def render(self):\n        return wsx([c.StaticText, {**self.props, 'label': self.props['label'], 'ref': self.ref}])\n\n\n##\n## REWX definitions:\n##\n\n@mount.register(ConfigPage)  # type: ignore\ndef config(element, parent):\n    return update(element, ConfigPage(parent, element['props']['config'], {'contents': []}))\n\n@update.register(ConfigPage)  # type: ignore\ndef config(element, instance: ConfigPage):\n    set_basic_props(instance, element['props'])\n    return instance\n\n@mount.register(TabbedConfigPage)  # type: ignore\ndef tabbedconfig(element, parent):\n    return update(element, TabbedConfigPage(parent, element['props']['config'], {'contents': []}))\n\n@update.register(TabbedConfigPage)  # type: ignore\ndef tabbedconfig(element, instance: TabbedConfigPage):\n    set_basic_props(instance, element['props'])\n    return instance\n\n@mount.register(Console)  # type: ignore\ndef console(element, parent):\n    return update(element, Console(parent, element['props']))\n\n@update.register(Console)  # type: ignore\ndef console(element, instance: Console):\n    set_basic_props(instance, element['props'])\n    if 'show' in element['props']:\n        instance.Show(element['props']['show'])\n    return instance\n\n"
  },
  {
    "path": "gooey/gui/bootstrap.py",
    "content": "'''\nMain runner entry point for Gooey.\n'''\nfrom typing import Any, Tuple\n\nimport wx  # type: ignore\n# wx.html and wx.xml imports required here to make packaging with\n# pyinstaller on OSX possible without manually specifying `hidden_imports`\n# in the build.spec\nimport wx.html  # type: ignore\nimport wx.lib.inspection  # type: ignore\nimport wx.richtext  # type: ignore\nimport wx.xml  # type: ignore\n\nfrom gooey.gui import image_repository\nfrom gooey.gui.application.application import RGooey\nfrom gooey.gui.lang import i18n\nfrom gooey.util.functional import merge\nfrom rewx import render, create_element  # type: ignore\n\n\ndef run(build_spec):\n    app, _ = build_app(build_spec)\n    app.MainLoop()\n\n\ndef build_app(build_spec):\n    app = wx.App(False)\n    return _build_app(build_spec, app)\n\n\ndef _build_app(build_spec, app) -> Tuple[Any, wx.Frame]:\n    \"\"\"\n    Note: this method is broken out with app as\n    an argument to facilitate testing.\n    \"\"\"\n    # use actual program name instead of script file name in macOS menu\n    app.SetAppDisplayName(build_spec['program_name'])\n\n    i18n.load(build_spec['language_dir'], build_spec['language'], build_spec['encoding'])\n    imagesPaths = image_repository.loadImages(build_spec['image_dir'])\n    gapp2 = render(create_element(RGooey, merge(build_spec, imagesPaths)), None)\n    # wx.lib.inspection.InspectionTool().Show()\n    # gapp.Show()\n    gapp2.Show()\n    return (app, gapp2)\n"
  },
  {
    "path": "gooey/gui/cli.py",
    "content": "import json\r\nfrom itertools import chain\r\n\r\nfrom copy import deepcopy\r\n\r\nfrom gooey.util.functional import compact\r\nfrom typing import List, Optional\r\n\r\nfrom gooey.gui.constants import VALUE_PLACEHOLDER\r\nfrom gooey.gui.formatters import formatArgument\r\nfrom gooey.python_bindings.types import FieldValue, Group, Item\r\nfrom gooey.util.functional import merge  # type: ignore\r\nfrom gooey.gui.state import FullGooeyState\r\n\r\n'''\r\nprimary :: Target -> Command -> Array Arg -> Array Arg -> Boolean -> CliString\r\nvalidateForm :: Target -> Command -> Array Arg -> Array Arg -> CliString\r\nvalidateField :: Target -> Command -> Array Arg -> Array Arg -> ArgId -> CliString\r\ncompleted :: Target -> Command -> FromState -> CliString\r\nfailed :: Target -> Command -> FromState -> CliString\r\nfieldAction :: Target -> Command ->   \r\n\r\n'''\r\n\r\n\r\ndef buildSuccessCmd(state: FullGooeyState):\r\n    subcommand = state['subcommands'][state['activeSelection']]\r\n    widgets = state['widgets'][subcommand]\r\n\r\n\r\n\r\n\r\ndef onSuccessCmd(target: str, subCommand: str, formState: List[str]) -> str:\r\n    command = subCommand if not subCommand == '::gooey/default' else ''\r\n    return f'{target} {command} --gooey-on-success {json.dumps(formState)}'\r\n\r\n\r\ndef onErrorCmd(target: str, subCommand: str, formState: List[str]) -> str:\r\n    command = subCommand if not subCommand == '::gooey/default' else ''\r\n    return f'{target} {command} --gooey-on-error {json.dumps(formState)}'\r\n\r\n\r\ndef formValidationCmd(target: str, subCommand: str, positionals: List[FieldValue], optionals: List[FieldValue]) -> str:\r\n    positional_args = [cmdOrPlaceholderOrNone(x) for x in positionals]\r\n    optional_args = [cmdOrPlaceholderOrNone(x) for x in optionals]\r\n    command = subCommand if not subCommand == '::gooey/default' else ''\r\n    return u' '.join(compact([\r\n        target,\r\n        command,\r\n        *optional_args,\r\n        '--gooey-validate-form',\r\n        '--' if positional_args else '',\r\n        *positional_args]))\r\n\r\n\r\ndef cliCmd(target: str,\r\n           subCommand: str,\r\n           positionals: List[FieldValue],\r\n           optionals: List[FieldValue],\r\n           suppress_gooey_flag=False) -> str:\r\n    positional_args = [arg['cmd'] for arg in positionals]\r\n    optional_args = [arg['cmd'] for arg in optionals]\r\n    command = subCommand if not subCommand == '::gooey/default' else ''\r\n    ignore_flag = '' if suppress_gooey_flag else '--ignore-gooey'\r\n    return u' '.join(compact([\r\n        target,\r\n        command,\r\n        *optional_args,\r\n        ignore_flag,\r\n        '--' if positional_args else '',\r\n        *positional_args]))\r\n\r\n\r\ndef cmdOrPlaceholderOrNone(field: FieldValue) -> Optional[str]:\r\n    # Argparse has a fail-fast-and-exit behavior for any missing\r\n    # values. This poses a problem for dynamic validation, as we\r\n    # want to collect _all_ errors to be more useful to the user.\r\n    # As such, if there is no value currently available, we pass\r\n    # through a stock placeholder values which allows GooeyParser\r\n    # to handle it being missing without Argparse exploding due to\r\n    # it actually being missing.\r\n    if field['clitype'] == 'positional':\r\n        return field['cmd'] or VALUE_PLACEHOLDER\r\n    elif field['clitype'] != 'positional' and field['meta']['required']:\r\n        # same rationale applies here. We supply the argument\r\n        # along with a fixed placeholder (when relevant i.e. `store`\r\n        # actions)\r\n        return field['cmd'] or formatArgument(field['meta'], VALUE_PLACEHOLDER)\r\n    else:\r\n        # Optional values are, well, optional. So, like usual, we send\r\n        # them if present or drop them if not.\r\n        return field['cmd']\r\n\r\n\r\ndef buildCliString(target, subCommand, positional, optional, suppress_gooey_flag=False):\r\n    positionals = deepcopy(positional)\r\n    if positionals:\r\n        positionals.insert(0, \"--\")\r\n\r\n    arguments = ' '.join(compact(chain(optional, positionals)))\r\n\r\n    if subCommand != '::gooey/default':\r\n        arguments = u'{} {}'.format(subCommand, arguments)\r\n\r\n    ignore_flag = '' if suppress_gooey_flag else '--ignore-gooey'\r\n    return u'{} {} {}'.format(target, ignore_flag, arguments)\r\n"
  },
  {
    "path": "gooey/gui/components/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/gui/components/config.py",
    "content": "from typing import Mapping, List\r\n\r\nimport wx  # type: ignore\r\nfrom wx.lib.scrolledpanel import ScrolledPanel  # type: ignore\r\n\r\nfrom gooey.gui.components.mouse import notifyMouseEvent\r\nfrom gooey.gui.components.util.wrapped_static_text import AutoWrappedStaticText\r\nfrom gooey.gui.lang.i18n import _\r\nfrom gooey.gui.util import wx_util\r\nfrom gooey.python_bindings.types import FormField\r\nfrom gooey.util.functional import getin, flatmap, indexunique\r\n\r\n\r\nclass ConfigPage(ScrolledPanel):\r\n    self_managed = True\r\n\r\n    def __init__(self, parent, rawWidgets, buildSpec,  *args, **kwargs):\r\n        super(ConfigPage, self).__init__(parent, *args, **kwargs)\r\n\r\n        self.SetupScrolling(scroll_x=False, scrollToTop=False)\r\n        self.rawWidgets = rawWidgets\r\n        self.buildSpec = buildSpec\r\n        self.reifiedWidgets = []\r\n        self.layoutComponent()\r\n        self.Layout()\r\n        self.widgetsMap = indexunique(lambda x: x._id, self.reifiedWidgets)\r\n        self.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n        ## TODO: need to rethink what uniquely identifies an argument.\r\n        ## Out-of-band IDs, while simple, make talking to the client program difficult\r\n        ## unless they're agreed upon before hand. Commands, as used here, have the problem\r\n        ## of (a) not being nearly granular enough (for instance,  `-v` could represent totally different\r\n        ## things given context/parser position), and (b) cannot identify positional args.\r\n\r\n    def getName(self, group):\r\n        \"\"\"\r\n        retrieve the group name from the group object while accounting for\r\n        legacy fixed-name manual translation requirements.\r\n        \"\"\"\r\n        name = group['name']\r\n        return (_(name)\r\n                if name in {'optional_args_msg', 'required_args_msg'}\r\n                else name)\r\n\r\n\r\n    def firstCommandIfPresent(self, widget):\r\n        commands = widget._meta['commands']\r\n        return commands[0] if commands else ''\r\n\r\n    def getPositionalArgs(self):\r\n        return [widget.getValue()['cmd'] for widget in self.reifiedWidgets\r\n                if widget.info['cli_type'] == 'positional']\r\n\r\n    def getOptionalArgs(self):\r\n        return [widget.getValue()['cmd'] for widget in self.reifiedWidgets\r\n                if widget.info['cli_type'] != 'positional']\r\n\r\n\r\n    def getPositionalValues(self):\r\n        return [widget.getValue() for widget in self.reifiedWidgets\r\n                if widget.info['cli_type'] == 'positional']\r\n\r\n\r\n    def getOptionalValues(self):\r\n        return [widget.getValue() for widget in self.reifiedWidgets\r\n                if widget.info['cli_type'] != 'positional']\r\n\r\n\r\n    def getFormState(self) -> List[FormField]:\r\n        return [widget.getUiState()\r\n                for widget in self.reifiedWidgets]\r\n\r\n\r\n    def syncFormState(self, formState: List[FormField]):\r\n        for item in formState:\r\n            self.widgetsMap[item['id']].syncUiState(item)\r\n\r\n    def isValid(self):\r\n        return not any(self.getErrors())\r\n\r\n    def getErrors(self):\r\n        states = [widget.getValue() for widget in self.reifiedWidgets]\r\n        return {state['meta']['dest']: state['error'] for state in states\r\n                if state['error']}\r\n\r\n    def seedUI(self, seeds):\r\n        radioWidgets = self.indexInternalRadioGroupWidgets()\r\n        for id, values in seeds.items():\r\n            if id in self.widgetsMap:\r\n                self.widgetsMap[id].setOptions(values)\r\n            if id in radioWidgets:\r\n                radioWidgets[id].setOptions(values)\r\n\r\n\r\n    def setErrors(self, errorMap: Mapping[str, str]):\r\n        self.resetErrors()\r\n        radioWidgets = self.indexInternalRadioGroupWidgets()\r\n        widgetsByDest = {v._meta['dest']: v for k,v in self.widgetsMap.items()\r\n                         if v.info['type'] != 'RadioGroup'}\r\n\r\n        # if there are any errors, then all error blocks should\r\n        # be displayed so that the UI elements remain inline with\r\n        # each other.\r\n        if errorMap:\r\n            for widget in self.widgetsMap.values():\r\n                widget.showErrorString(True)\r\n\r\n        for id, message in errorMap.items():\r\n            if id in widgetsByDest:\r\n                widgetsByDest[id].setErrorString(message)\r\n                widgetsByDest[id].showErrorString(True)\r\n            if id in radioWidgets:\r\n                radioWidgets[id].setErrorString(message)\r\n                radioWidgets[id].showErrorString(True)\r\n\r\n\r\n    def indexInternalRadioGroupWidgets(self):\r\n        groups = filter(lambda x: x.info['type'] == 'RadioGroup', self.reifiedWidgets)\r\n        widgets = flatmap(lambda group: group.widgets, groups)\r\n        return indexunique(lambda x: x._meta['dest'], widgets)\r\n\r\n\r\n    def displayErrors(self):\r\n        states = [widget.getValue() for widget in self.reifiedWidgets]\r\n        errors = [state for state in states if state['error']]\r\n        for error in errors:\r\n            widget = self.widgetsMap[error['id']]\r\n            widget.setErrorString(error['error'])\r\n            widget.showErrorString(True)\r\n            while widget.GetParent():\r\n                widget.Layout()\r\n                widget = widget.GetParent()\r\n\r\n    def resetErrors(self):\r\n        for widget in self.reifiedWidgets:\r\n            widget.setErrorString('')\r\n            widget.showErrorString(False)\r\n\r\n    def hideErrors(self):\r\n        for widget in self.reifiedWidgets:\r\n            widget.hideErrorString()\r\n\r\n\r\n    def layoutComponent(self):\r\n        sizer = wx.BoxSizer(wx.VERTICAL)\r\n        for item in self.rawWidgets['contents']:\r\n            self.makeGroup(self, sizer, item, 0, wx.EXPAND | wx.ALL, 10)\r\n        self.SetSizer(sizer)\r\n\r\n    def makeGroup(self, parent, thissizer, group, *args):\r\n        '''\r\n        Messily builds the (potentially) nested and grouped layout\r\n\r\n        Note! Mutates `self.reifiedWidgets` in place with the widgets as they're\r\n        instantiated! I cannot figure out how to split out the creation of the\r\n        widgets from their styling without WxPython violently exploding\r\n\r\n        TODO: sort out the WX quirks and clean this up.\r\n        '''\r\n\r\n        # determine the type of border , if any, the main sizer will use\r\n        if getin(group, ['options', 'show_border'], False):\r\n            boxDetails = wx.StaticBox(parent, -1, self.getName(group) or '')\r\n            boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL)\r\n        else:\r\n            boxSizer = wx.BoxSizer(wx.VERTICAL)\r\n            boxSizer.AddSpacer(10)\r\n            if group['name']:\r\n                groupName = wx_util.h1(parent, self.getName(group) or '')\r\n                groupName.SetForegroundColour(getin(group, ['options', 'label_color']))\r\n                groupName.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n                boxSizer.Add(groupName, 0, wx.TOP | wx.BOTTOM | wx.LEFT, 8)\r\n\r\n        group_description = getin(group, ['description'])\r\n        if group_description:\r\n            description = AutoWrappedStaticText(parent, label=group_description, target=boxSizer)\r\n            description.SetForegroundColour(getin(group, ['options', 'description_color']))\r\n            description.SetMinSize((0, -1))\r\n            description.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n            boxSizer.Add(description, 1,  wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)\r\n\r\n        # apply an underline when a grouping border is not specified\r\n        # unless the user specifically requests not to show it\r\n        if not getin(group, ['options', 'show_border'], False) and group['name'] \\\r\n                and getin(group, ['options', 'show_underline'], True):\r\n            boxSizer.Add(wx_util.horizontal_rule(parent), 0, wx.EXPAND | wx.LEFT, 10)\r\n\r\n        ui_groups = self.chunkWidgets(group)\r\n\r\n        for uigroup in ui_groups:\r\n            sizer = wx.BoxSizer(wx.HORIZONTAL)\r\n            for item in uigroup:\r\n                widget = self.reifyWidget(parent, item)\r\n                if not getin(item, ['options', 'visible'], True):\r\n                    widget.Hide()\r\n                # !Mutate the reifiedWidgets instance variable in place\r\n                self.reifiedWidgets.append(widget)\r\n                sizer.Add(widget, 1, wx.ALL | wx.EXPAND, 5)\r\n            boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5)\r\n\r\n        # apply the same layout rules recursively for subgroups\r\n        hs = wx.BoxSizer(wx.HORIZONTAL)\r\n        for e, subgroup in enumerate(group['groups']):\r\n            self.makeGroup(parent, hs, subgroup, 1, wx.EXPAND)\r\n            if len(group['groups']) != e:\r\n                hs.AddSpacer(5)\r\n\r\n            # self.makeGroup(parent, hs, subgroup, 1, wx.ALL | wx.EXPAND, 5)\r\n            itemsPerColumn = getin(group, ['options', 'columns'], 2)\r\n            if e % itemsPerColumn or (e + 1) == len(group['groups']):\r\n                boxSizer.Add(hs, *args)\r\n                hs = wx.BoxSizer(wx.HORIZONTAL)\r\n\r\n\r\n        group_top_margin = getin(group, ['options', 'margin_top'], 1)\r\n\r\n        marginSizer = wx.BoxSizer(wx.VERTICAL)\r\n        marginSizer.Add(boxSizer, 1, wx.EXPAND | wx.TOP, group_top_margin)\r\n\r\n        thissizer.Add(marginSizer, *args)\r\n\r\n\r\n    def chunkWidgets(self, group):\r\n        ''' chunk the widgets up into groups based on their sizing hints '''\r\n        ui_groups = []\r\n        subgroup = []\r\n        for index, item in enumerate(group['items']):\r\n            if getin(item, ['options', 'full_width'], False):\r\n                ui_groups.append(subgroup)\r\n                ui_groups.append([item])\r\n                subgroup = []\r\n            else:\r\n                subgroup.append(item)\r\n            if len(subgroup) == getin(group, ['options', 'columns'], 2) \\\r\n                    or item == group['items'][-1]:\r\n                ui_groups.append(subgroup)\r\n                subgroup = []\r\n        return ui_groups\r\n\r\n\r\n    def reifyWidget(self, parent, item):\r\n        ''' Convert a JSON description of a widget into a WxObject '''\r\n        from gooey.gui.components import widgets\r\n        widgetClass = getattr(widgets, item['type'])\r\n        return widgetClass(parent, item)\r\n\r\n\r\n\r\nclass TabbedConfigPage(ConfigPage):\r\n    \"\"\"\r\n    Splits top-level groups across tabs\r\n    \"\"\"\r\n\r\n    def layoutComponent(self):\r\n        # self.rawWidgets['contents'] = self.rawWidgets['contents'][1:2]\r\n        self.notebook = wx.Notebook(self, style=wx.BK_DEFAULT)\r\n\r\n        panels = [wx.Panel(self.notebook) for _ in self.rawWidgets['contents']]\r\n        sizers = [wx.BoxSizer(wx.VERTICAL) for _ in panels]\r\n\r\n        for group, panel, sizer in zip(self.rawWidgets['contents'], panels, sizers):\r\n            self.makeGroup(panel, sizer, group, 0, wx.EXPAND)\r\n            panel.SetSizer(sizer)\r\n            panel.Layout()\r\n            self.notebook.AddPage(panel, self.getName(group))\r\n            self.notebook.Layout()\r\n\r\n\r\n        _sizer = wx.BoxSizer(wx.VERTICAL)\r\n        _sizer.Add(self.notebook, 1, wx.EXPAND)\r\n        self.SetSizer(_sizer)\r\n        self.Layout()\r\n\r\n\r\n    def snapToErrorTab(self):\r\n        pass\r\n\r\n"
  },
  {
    "path": "gooey/gui/components/console.py",
    "content": "import webbrowser\r\n\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui.lang.i18n import _\r\nfrom .widgets.basictextconsole import BasicTextConsole\r\n\r\n\r\nclass Console(wx.Panel):\r\n    '''\r\n    Textbox console/terminal displayed during the client program's execution.\r\n    '''\r\n    self_managed = True\r\n\r\n    def __init__(self, parent, buildSpec, **kwargs):\r\n        wx.Panel.__init__(self, parent, name='console', **kwargs)\r\n        self.buildSpec = buildSpec\r\n\r\n        self.text = wx.StaticText(self, label=_(\"status\"))\r\n        if buildSpec[\"richtext_controls\"]:\r\n            from .widgets.richtextconsole import RichTextConsole\r\n            self.textbox = RichTextConsole(self)\r\n        else:\r\n            self.textbox = BasicTextConsole(self)\r\n\r\n        self.defaultFont = self.textbox.GetFont()\r\n\r\n        self.textbox.SetFont(wx.Font(\r\n            self.buildSpec['terminal_font_size'] or self.defaultFont.GetPointSize(),\r\n            self.getFontStyle(),\r\n            wx.NORMAL,\r\n            self.buildSpec['terminal_font_weight'] or wx.NORMAL,\r\n            False,\r\n            self.getFontFace(),\r\n        ))\r\n        self.textbox.SetForegroundColour(self.buildSpec['terminal_font_color'])\r\n         \r\n        self.layoutComponent()\r\n        self.Layout()\r\n        self.Bind(wx.EVT_TEXT_URL, self.evtUrl, self.textbox)\r\n\r\n    def evtUrl(self, event):\r\n        if event.MouseEvent.LeftUp():\r\n            # The rich console provides the embedded URL via GetString()\r\n            # but the basic console does not\r\n            webbrowser.open(\r\n                event.GetString() or\r\n                self.textbox.GetRange(event.URLStart,event.URLEnd))\r\n        event.Skip()\r\n\r\n\r\n    def getFontStyle(self):\r\n        \"\"\"\r\n        Force wx.Modern style to support legacy\r\n        monospace_display param when present\r\n        \"\"\"\r\n        return (wx.MODERN\r\n                if self.buildSpec['monospace_display']\r\n                else wx.DEFAULT)\r\n\r\n\r\n    def getFontFace(self):\r\n        \"\"\"Choose the best font face available given the user options\"\"\"\r\n        userFace = self.buildSpec['terminal_font_family'] or self.defaultFont.GetFaceName()\r\n        return (''\r\n                if self.buildSpec['monospace_display']\r\n                else userFace)\r\n\r\n\r\n    def logOutput(self, *args, **kwargs):\r\n        \"\"\"Event Handler for console updates coming from the client's program\"\"\"\r\n        self.appendText(kwargs.get('msg'))\r\n\r\n\r\n    def appendText(self, txt):\r\n        \"\"\"\r\n        Append the text to the main TextCtrl.\r\n\r\n        Note! Must be called from a Wx specific thread handler to avoid\r\n        multi-threaded explosions (e.g. wx.CallAfter)\r\n        \"\"\"\r\n        self.textbox.AppendText(txt)\r\n\r\n    def clear(self):\r\n        \"\"\"\r\n            Clear the the main TextCtrl.\r\n        \"\"\"\r\n        self.textbox.Clear()\r\n\r\n\r\n    def getText(self):\r\n        return self.textbox.GetValue()\r\n\r\n    def layoutComponent(self):\r\n        self.SetBackgroundColour(self.buildSpec.get('terminal_panel_color', '#F0F0F0'))\r\n        sizer = wx.BoxSizer(wx.VERTICAL)\r\n        sizer.AddSpacer(10)\r\n        sizer.Add(self.text, 0, wx.LEFT, 20)\r\n        sizer.AddSpacer(10)\r\n        sizer.Add(self.textbox, 1, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, 20)\r\n        sizer.AddSpacer(20)\r\n        self.SetSizer(sizer)\r\n\r\n\r\n"
  },
  {
    "path": "gooey/gui/components/dialogs.py",
    "content": "import rewx.components as c  # type: ignore\nimport wx  # type: ignore\nimport wx.html2  # type: ignore\nfrom rewx import wsx, render  # type: ignore\n\n\ndef _html_window(html):\n    return wsx(\n        [c.Block, {'orient': wx.VERTICAL, 'flag': wx.EXPAND},\n         [c.HtmlWindow, {'style': wx.TE_READONLY, 'flag': wx.EXPAND | wx.ALL,\n                         'proportion': 1, 'value': html}]]\n    )\n\n\nclass HtmlDialog(wx.Dialog):\n    \"\"\"\n    A MessageDialog where the central contents are an HTML window\n    customizable by the user.\n    \"\"\"\n    def __init__(self, *args, **kwargs):\n        caption = kwargs.pop('caption', '')\n        html = kwargs.pop('html', '')\n        super(HtmlDialog, self).__init__(None, *args, **kwargs)\n\n        wx.InitAllImageHandlers()\n\n        self.SetTitle(caption)\n        sizer = wx.BoxSizer(wx.VERTICAL)\n        sizer.Add(render(_html_window(html), self), 1, wx.EXPAND)\n\n        # in addition to creating the sizer, this actually attached\n        # a few common handlers which makes it feel more dialog-y. Thus\n        # it being done here rather than in rewx\n        btnSizer = self.CreateStdDialogButtonSizer(wx.OK)\n        sizer.Add(btnSizer, 0, wx.ALL | wx.EXPAND, 9)\n        self.SetSizer(sizer)\n        self.Layout()\n\n\n\n"
  },
  {
    "path": "gooey/gui/components/filtering/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/gui/components/filtering/prefix_filter.py",
    "content": "import re\n\nimport pygtrie as trie  # type: ignore\nfrom functools import reduce\n\n__ALL__ = ('PrefixTokenizers', 'PrefixSearch')\n\n\n\nclass PrefixTokenizers:\n    # This string here is just an arbitrary long string so that\n    # re.split finds no matches and returns the entire phrase\n    ENTIRE_PHRASE = '::gooey/tokenization/entire-phrase'\n    # \\s == any whitespace character\n    WORDS = r'\\s'\n\n    @classmethod\n    def REGEX(cls, expression):\n        return expression\n\nclass OperatorType:\n    AND = 'AND'\n    OR = 'OR'\n\nclass SearchOptions:\n    def __init__(self,\n                 choice_tokenizer=PrefixTokenizers.ENTIRE_PHRASE,\n                 input_tokenizer=PrefixTokenizers.ENTIRE_PHRASE,\n                 ignore_case=True,\n                 operator='AND',\n                 index_suffix= False,\n                 **kwargs):\n        self.choice_tokenizer = choice_tokenizer\n        self.input_tokenizer = input_tokenizer\n        self.ignore_case = ignore_case\n        self.operator = operator\n        self.index_suffix = index_suffix\n\n\n\nclass PrefixSearch(object):\n    \"\"\"\n    A trie backed index for quickly finding substrings\n    in a list of options.\n    \"\"\"\n\n    def __init__(self, choices, options={}, *args, **kwargs):\n        self.choices = sorted(filter(None, choices))\n        self.options: SearchOptions = SearchOptions(**options)\n        self.searchtree = self.buildSearchTrie(choices)\n\n    def updateChoices(self, choices):\n        self.choices = sorted(filter(None, choices))\n        self.searchtree = self.buildSearchTrie(choices)\n\n    def findMatches(self, token):\n        if not token:\n            return sorted(self.choices)\n        tokens = self.tokenizeInput(token)\n        matches = [set(flatten(self._vals(self.searchtree, prefix=t))) for t in tokens]\n        op = intersection if self.options.operator == 'AND' else union\n        return sorted(reduce(op, matches))\n\n    def tokenizeInput(self, token):\n        \"\"\"\n        Cleans and tokenizes the user's input.\n\n        empty characters and spaces are trimmed to prevent\n        matching all paths in the index.\n        \"\"\"\n        return list(filter(None, re.split(self.options.input_tokenizer, self.clean(token))))\n\n    def tokenizeChoice(self, choice):\n        \"\"\"\n        Splits the `choice` into a series of tokens based on\n        the user's criteria.\n\n        If suffix indexing is enabled, the individual tokens\n        are further broken down and indexed by their suffix offsets. e.g.\n\n            'Banana', 'anana', 'nana', 'ana'\n        \"\"\"\n        choice_ = self.clean(choice)\n        tokens = re.split(self.options.choice_tokenizer, choice_)\n        if self.options.index_suffix:\n            return [token[i:]\n                    for token in tokens\n                    for i in range(len(token) - 2)]\n        else:\n            return tokens\n\n    def clean(self, text):\n        return text.lower() if self.options.ignore_case else text\n\n    def buildSearchTrie(self, choices):\n        searchtrie = trie.Trie()\n        for choice in choices:\n            for token in self.tokenizeChoice(choice):\n                if not searchtrie.has_key(token):\n                    searchtrie[token] = []\n                searchtrie[token].append(choice)\n        return searchtrie\n\n    def _vals(self, searchtrie, **kwargs):\n        try:\n            return searchtrie.values(**kwargs)\n        except KeyError:\n            return []\n\n\ndef intersection(a, b):\n    return a.intersection(b)\n\n\ndef union(a, b):\n    return a.union(b)\n\n\ndef flatten(xs):\n    return [item for x in xs for item in x]\n"
  },
  {
    "path": "gooey/gui/components/footer.py",
    "content": "import sys\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui import events\r\nfrom gooey.gui.lang.i18n import _\r\nfrom gooey.gui.pubsub import pub\r\nfrom gooey.gui.components.mouse import notifyMouseEvent\r\n\r\n\r\nclass Footer(wx.Panel):\r\n    '''\r\n    Footer section used on the configuration\r\n    screen of the application\r\n    '''\r\n\r\n    def __init__(self, parent, buildSpec, **kwargs):\r\n        wx.Panel.__init__(self, parent, **kwargs)\r\n        self.buildSpec = buildSpec\r\n\r\n        self.SetMinSize((30, 53))\r\n        # TODO: The was set to True for the timer addition\r\n        #       however, it leads to 'tearing' issues when resizing\r\n        #       the GUI in windows. Disabling until I can dig into it.\r\n        self.SetDoubleBuffered(False)\r\n        # components\r\n        self.cancel_button = None\r\n        self.start_button = None\r\n        self.progress_bar = None\r\n        self.close_button = None\r\n        self.stop_button = None\r\n        self.restart_button = None\r\n        self.edit_button = None\r\n        self.buttons = []\r\n\r\n        self.layouts = {}\r\n\r\n        self._init_components()\r\n        self._do_layout()\r\n\r\n        for button in self.buttons:\r\n            self.Bind(wx.EVT_BUTTON, self.dispatch_click, button)\r\n            self.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent, button)\r\n        self.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n\r\n\r\n    def updateTimeRemaining(self,*args,**kwargs):\r\n        estimate_time_remaining = kwargs.get('estimatedRemaining')\r\n        elapsed_time_value = kwargs.get('elapsed_time')\r\n        if elapsed_time_value is None:\r\n            return\r\n        elif estimate_time_remaining is not None:\r\n            self.time_remaining_text.SetLabel(f\"{elapsed_time_value}<{estimate_time_remaining}\")\r\n            return\r\n        else:\r\n            self.time_remaining_text.SetLabel(f\"{elapsed_time_value}\")\r\n\r\n\r\n    def updateProgressBar(self, *args, **kwargs):\r\n        '''\r\n         value, disable_animation=False\r\n        :param args:\r\n        :param kwargs:\r\n        :return:\r\n        '''\r\n        value = kwargs.get('progress')\r\n        pb = self.progress_bar\r\n        if value is None:\r\n            return\r\n        if value < 0:\r\n            pb.Pulse()\r\n        else:\r\n            value = min(int(value), pb.GetRange())\r\n            if pb.GetValue() != value:\r\n                # Windows 7 progress bar animation hack\r\n                # http://stackoverflow.com/questions/5332616/disabling-net-progressbar-animation-when-changing-value\r\n                if self.buildSpec['disable_progress_bar_animation'] \\\r\n                        and sys.platform.startswith(\"win\"):\r\n                    if pb.GetRange() == value:\r\n                        pb.SetValue(value)\r\n                        pb.SetValue(value - 1)\r\n                    else:\r\n                        pb.SetValue(value + 1)\r\n                pb.SetValue(value)\r\n\r\n\r\n    def showButtons(self, *buttonsToShow):\r\n        for button in self.buttons:\r\n            button.Show(False)\r\n        for button in buttonsToShow:\r\n            getattr(self, button).Show(True)\r\n        self.Layout()\r\n\r\n\r\n    def _init_components(self):\r\n        self.cancel_button = self.button(_('cancel'), wx.ID_CANCEL, event_id=events.WINDOW_CANCEL)\r\n        self.stop_button = self.button(_('stop'), wx.ID_OK, event_id=events.WINDOW_STOP)\r\n        self.start_button = self.button(_('start'), wx.ID_OK, event_id=int(events.WINDOW_START))\r\n        self.close_button = self.button(_(\"close\"), wx.ID_OK, event_id=int(events.WINDOW_CLOSE))\r\n        self.restart_button = self.button(_('restart'), wx.ID_OK, event_id=int(events.WINDOW_RESTART))\r\n        self.edit_button = self.button(_('edit'), wx.ID_OK, event_id=int(events.WINDOW_EDIT))\r\n\r\n        self.progress_bar = wx.Gauge(self, range=100)\r\n\r\n        self.time_remaining_text = wx.StaticText(self)\r\n\r\n        self.buttons = [self.cancel_button, self.start_button,\r\n                        self.stop_button, self.close_button,\r\n                        self.restart_button, self.edit_button]\r\n\r\n        if self.buildSpec['disable_stop_button']:\r\n            self.stop_button.Enable(False)\r\n\r\n\r\n    def _do_layout(self):\r\n        self.SetBackgroundColour(self.buildSpec['footer_bg_color'])\r\n        self.stop_button.Hide()\r\n        self.restart_button.Hide()\r\n\r\n        v_sizer = wx.BoxSizer(wx.VERTICAL)\r\n        h_sizer = wx.BoxSizer(wx.HORIZONTAL)\r\n\r\n        h_sizer.Add(self.progress_bar, 1,\r\n                    wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 20)\r\n        \r\n        h_sizer.Add(self.time_remaining_text,0,wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 20)\r\n\r\n        h_sizer.AddStretchSpacer(1)\r\n        h_sizer.Add(self.cancel_button, 0,wx.RIGHT, 20)\r\n        h_sizer.Add(self.start_button, 0, wx.RIGHT, 20)\r\n        h_sizer.Add(self.stop_button, 0, wx.RIGHT, 20)\r\n\r\n        v_sizer.AddStretchSpacer(1)\r\n        v_sizer.Add(h_sizer, 0, wx.EXPAND)\r\n\r\n        h_sizer.Add(self.edit_button, 0, wx.RIGHT, 10)\r\n        h_sizer.Add(self.restart_button, 0, wx.RIGHT, 10)\r\n        h_sizer.Add(self.close_button, 0, wx.RIGHT, 20)\r\n        self.edit_button.Hide()\r\n        self.restart_button.Hide()\r\n        self.close_button.Hide()\r\n        # self.progress_bar.Hide()\r\n\r\n        v_sizer.AddStretchSpacer(1)\r\n        self.SetSizer(v_sizer)\r\n\r\n    def button(self, label=None, style=None, event_id=-1):\r\n        return wx.Button(\r\n            parent=self,\r\n            id=event_id,\r\n            size=(90, -1),\r\n            label=label,\r\n            style=style)\r\n\r\n    def dispatch_click(self, event):\r\n        if event.EventObject.Enabled:\r\n            pub.send_message(event.GetId())\r\n\r\n    def hide_all_buttons(self):\r\n        for button in self.buttons:\r\n            button.Hide()\r\n"
  },
  {
    "path": "gooey/gui/components/header.py",
    "content": "'''\r\nCreated on Dec 23, 2013\r\n\r\n@author: Chris\r\n'''\r\n\r\nimport wx  # type: ignore\r\nfrom rewx import wsx\r\nimport rewx.components as c\r\n\r\nfrom gooey.gui import imageutil, image_repository\r\nfrom gooey.gui.util import wx_util\r\nfrom gooey.gui.three_to_four import bitmapFromImage\r\nfrom gooey.util.functional import getin\r\nfrom gooey.gui.components.mouse import notifyMouseEvent\r\n\r\nPAD_SIZE = 10\r\n\r\n\r\n\r\n\r\n\r\nclass FrameHeader(wx.Panel):\r\n    def __init__(self, parent, buildSpec, **kwargs):\r\n        wx.Panel.__init__(self, parent, **kwargs)\r\n        self.SetDoubleBuffered(True)\r\n\r\n        self.buildSpec = buildSpec\r\n\r\n        self._header = None\r\n        self._subheader = None\r\n        self.settings_img = None\r\n        self.running_img = None\r\n        self.check_mark = None\r\n        self.error_symbol = None\r\n\r\n        self.images = []\r\n\r\n        self.layoutComponent()\r\n        self.bindMouseEvents()\r\n\r\n\r\n\r\n    def setTitle(self, title):\r\n        self._header.SetLabel(title)\r\n\r\n    def setSubtitle(self, subtitle):\r\n        self._subheader.SetLabel(subtitle)\r\n\r\n    def setImage(self, image):\r\n        for img in self.images:\r\n            img.Show(False)\r\n        getattr(self, image).Show(True)\r\n        self.Layout()\r\n\r\n\r\n    def layoutComponent(self):\r\n        self.SetBackgroundColour(self.buildSpec['header_bg_color'])\r\n        self.SetSize((30, self.buildSpec['header_height']))\r\n        self.SetMinSize((120, self.buildSpec['header_height']))\r\n\r\n        self._header = wx_util.h1(self, label=self.buildSpec['program_name'])\r\n        self._subheader = wx.StaticText(self, label=self.buildSpec['program_description'])\r\n\r\n        images = self.buildSpec['images']\r\n        targetHeight = self.buildSpec['header_height'] - 10\r\n        self.settings_img = self._load_image(images['configIcon'], targetHeight)\r\n        self.running_img = self._load_image(images['runningIcon'], targetHeight)\r\n        self.check_mark = self._load_image(images['successIcon'], targetHeight)\r\n        self.error_symbol = self._load_image(images['errorIcon'], targetHeight)\r\n\r\n        self.images = [\r\n            self.settings_img,\r\n            self.running_img,\r\n            self.check_mark,\r\n            self.error_symbol\r\n        ]\r\n\r\n        vsizer = wx.BoxSizer(wx.VERTICAL)\r\n        sizer = wx.BoxSizer(wx.HORIZONTAL)\r\n        headings_sizer = self.build_heading_sizer()\r\n        sizer.Add(headings_sizer, 1,\r\n                  wx.ALIGN_LEFT | wx.EXPAND | wx.LEFT,\r\n                  PAD_SIZE)\r\n        sizer.Add(self.settings_img, 0, wx.EXPAND | wx.RIGHT, PAD_SIZE)\r\n        sizer.Add(self.running_img, 0, wx.EXPAND | wx.RIGHT, PAD_SIZE)\r\n        sizer.Add(self.check_mark, 0, wx.EXPAND | wx.RIGHT, PAD_SIZE)\r\n        sizer.Add(self.error_symbol, 0, wx.EXPAND | wx.RIGHT, PAD_SIZE)\r\n        self.running_img.Hide()\r\n        self.check_mark.Hide()\r\n        self.error_symbol.Hide()\r\n        vsizer.Add(sizer, 1, wx.EXPAND)\r\n        self.SetSizer(vsizer)\r\n\r\n\r\n    def _load_image(self, imgPath, targetHeight):\r\n        rawImage = imageutil.loadImage(imgPath)\r\n        sizedImage = imageutil.resizeImage(rawImage, targetHeight)\r\n        return imageutil.wrapBitmap(sizedImage, self)\r\n\r\n\r\n    def build_heading_sizer(self):\r\n        sizer = wx.BoxSizer(wx.VERTICAL)\r\n        sizer.AddStretchSpacer(1)\r\n        if self.buildSpec['header_show_title']:\r\n            sizer.Add(self._header, 0)\r\n        else:\r\n            self._header.Hide()\r\n\r\n        if self.buildSpec['header_show_subtitle']:\r\n            sizer.Add(self._subheader, 0)\r\n        else:\r\n            self._subheader.Hide()\r\n        sizer.AddStretchSpacer(1)\r\n        return sizer\r\n\r\n    def bindMouseEvents(self):\r\n        \"\"\"\r\n        Manually binding all LEFT_DOWN events.\r\n        See: gooey.gui.mouse for background.\r\n        \"\"\"\r\n        self.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n        self._header.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n        self._subheader.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n        for image in self.images:\r\n            image.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)"
  },
  {
    "path": "gooey/gui/components/layouts/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/gui/components/layouts/layouts.py",
    "content": "import wx  # type: ignore\r\n\r\n\r\ndef standard_layout(title, subtitle, widget):\r\n    container = wx.BoxSizer(wx.VERTICAL)\r\n\r\n    container.Add(title)\r\n    container.AddSpacer(2)\r\n\r\n    if subtitle:\r\n        container.Add(subtitle, 1, wx.EXPAND)\r\n        container.AddSpacer(2)\r\n    else:\r\n        container.AddStretchSpacer(1)\r\n\r\n    container.Add(widget, 0, wx.EXPAND)\r\n    return container\r\n"
  },
  {
    "path": "gooey/gui/components/menubar.py",
    "content": "import webbrowser\r\nfrom functools import partial\r\n\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui import three_to_four\r\nfrom gooey.gui.components.dialogs import HtmlDialog\r\n\r\n\r\nclass MenuBar(wx.MenuBar):\r\n    \"\"\"\r\n    Wx.MenuBar handles converting the users list of Menu Groups into\r\n    concrete wx.Menu instances.\r\n    \"\"\"\r\n\r\n    def __init__(self, buildSpec, *args, **kwargs):\r\n        super(MenuBar,self).__init__(*args, **kwargs)\r\n        self.buildSpec = buildSpec\r\n        self.makeMenuItems(buildSpec.get('menu', []))\r\n\r\n\r\n    def makeMenuItems(self, menuGroups):\r\n        \"\"\"\r\n        Assign the menu groups list to wx.Menu instances\r\n        and bind the appropriate handlers.\r\n        \"\"\"\r\n        for menuGroup in menuGroups:\r\n            menu = wx.Menu()\r\n            for item in menuGroup.get('items'):\r\n                option = menu.Append(wx.NewId(), item.get('menuTitle', ''))\r\n                self.Bind(wx.EVT_MENU, self.handleMenuAction(item), option)\r\n            self.Append(menu, '&' + menuGroup.get('name'))\r\n\r\n\r\n    def handleMenuAction(self, item):\r\n        \"\"\"\r\n        Dispatch based on the value of the type field.\r\n        \"\"\"\r\n        handlers = {\r\n            'Link': self.openBrowser,\r\n            'AboutDialog': self.spawnAboutDialog,\r\n            'MessageDialog': self.spawnMessageDialog,\r\n            'HtmlDialog': self.spawnHtmlDialog\r\n        }\r\n        f = handlers[item['type']]\r\n        return partial(f, item)\r\n\r\n\r\n    def openBrowser(self, item, *args, **kwargs):\r\n        \"\"\"\r\n        Open the supplied URL in the user's default browser.\r\n        \"\"\"\r\n        webbrowser.open(item.get('url'))\r\n\r\n\r\n    def spawnMessageDialog(self, item, *args, **kwargs):\r\n        \"\"\"\r\n        Show a simple message dialog with the user's message and caption.\r\n        \"\"\"\r\n        wx.MessageDialog(self, item.get('message', ''),\r\n                               caption=item.get('caption', '')).ShowModal()\r\n\r\n\r\n    def spawnHtmlDialog(self, item, *args, **kwargs):\r\n        HtmlDialog(caption=item.get('caption', ''), html=item.get('html')).ShowModal()\r\n\r\n\r\n    def spawnAboutDialog(self, item, *args, **kwargs):\r\n        \"\"\"\r\n        Fill the wx.AboutBox with any relevant info the user provided\r\n        and launch the dialog\r\n        \"\"\"\r\n        aboutOptions = {\r\n            'name': 'SetName',\r\n            'version': 'SetVersion',\r\n            'description': 'SetDescription',\r\n            'copyright': 'SetCopyright',\r\n            'website': 'SetWebSite',\r\n            'developer': 'AddDeveloper',\r\n            'license': 'SetLicense'\r\n        }\r\n        about = three_to_four.AboutDialog()\r\n        for field, method in aboutOptions.items():\r\n            if field in item:\r\n                getattr(about, method)(item[field])\r\n\r\n        three_to_four.AboutBox(about)\r\n\r\n\r\n"
  },
  {
    "path": "gooey/gui/components/modals.py",
    "content": "\"\"\"\r\nAll of the dialogs used throughout Gooey\r\n\"\"\"\r\nfrom collections import namedtuple\r\n\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui.lang.i18n import _\r\n\r\n\r\n# These don't seem to be specified anywhere in WX for some reason\r\nDialogConstants = namedtuple('DialogConstants', 'YES NO')(5103, 5104)  # type: ignore\r\n\r\n\r\ndef showDialog(title, content, style):\r\n    dlg = wx.MessageDialog(None, content, title, style)\r\n    dlg.SetYesNoLabels(_('dialog_button_yes'), _('dialog_button_no'))\r\n    dlg.SetOKLabel(_('dialog_button_ok'))\r\n    result = dlg.ShowModal()\r\n    dlg.Destroy()\r\n    return result\r\n\r\n\r\ndef missingArgsDialog():\r\n    showDialog(_('error_title'), _('error_required_fields'), wx.ICON_ERROR)\r\n\r\n\r\ndef validationFailure():\r\n    showDialog(_('error_title'), _('validation_failed'), wx.ICON_WARNING)\r\n\r\n\r\ndef showSuccess():\r\n    showDialog(_('execution_finished'), _('success_message'), wx.ICON_INFORMATION)\r\n\r\n\r\ndef showFailure():\r\n    showDialog(_('execution_finished'), _('uh_oh'), wx.ICON_ERROR)\r\n\r\n\r\ndef confirmExit():\r\n    result = showDialog(_('sure_you_want_to_exit'), _('close_program'), wx.YES_NO | wx.ICON_INFORMATION)\r\n    return result == DialogConstants.YES\r\n\r\n\r\ndef confirmForceStop():\r\n    result = showDialog(_('stop_task'), _('sure_you_want_to_stop'), wx.YES_NO | wx.ICON_WARNING)\r\n    return result == DialogConstants.YES\r\n\r\n"
  },
  {
    "path": "gooey/gui/components/mouse.py",
    "content": "\"\"\"\nWxPython lacks window level event hooks. Meaning, there's no\ngeneral way to subscribe to every mouse event that goes on within\nthe application.\n\nTo implement features which respond to clicks outside of their\nimmediate scope, for instance, dropdowns, a workaround in the form\nof manually binding all mouse events, for every component, to a single\ntop level handler needs to be done.\n\nNormally, this type of functionality would be handled by wx.PopupTransientWindow.\nHowever, there's a long standing bug with it and the ListBox/Ctrl\nclasses which prevents its usage and thus forcing this garbage.\n\nSee: https://github.com/wxWidgets/Phoenix/blob/705aa63d75715f8abe484f4559a37cb6b09decb3/demo/PopupWindow.py\n\"\"\"\n\n\nfrom gooey.gui.pubsub import pub\nimport gooey.gui.events as events\n\ndef notifyMouseEvent(event):\n    \"\"\"\n    Notify interested listeners of the LEFT_DOWN mouse event\n    \"\"\"\n    # TODO: is there ever a situation where this wouldn't be skipped..?\n    event.Skip()\n    pub.send_message_sync(events.LEFT_DOWN, wxEvent=event)"
  },
  {
    "path": "gooey/gui/components/options/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/gui/components/options/options.py",
    "content": "from gooey.gui.components.filtering.prefix_filter import PrefixTokenizers\n\n\n\ndef _include_layout_docs(f):\n    \"\"\"\n    Combines the layout_options docsstring with the\n    wrapped function's doc string.\n    \"\"\"\n    f.__doc__ = (f.__doc__ or '') + (LayoutOptions.__doc__ or '')\n    return f\n\n\ndef _include_global_option_docs(f):\n    \"\"\"\n    Combines docstrings for options available to\n    all widget types.\n    \"\"\"\n    _doc = \"\"\":param initial_value:  Sets the initial value in the UI. \n    \"\"\"\n    f.__doc__ = (f.__doc__ or '') + _doc\n    return f\n\ndef _include_chooser_msg_wildcard_docs(f):\n    \"\"\"\n    Combines the basic Chooser options (wildard, message) docsstring\n    with the wrapped function's doc string.\n    \"\"\"\n    _doc = \"\"\":param wildcard: Sets the wildcard, which can contain multiple file types, for \n                     example: \"BMP files (.bmp)|.bmp|GIF files (.gif)|.gif\"\n    :param message:  Sets the message that will be displayed on the dialog.\n    \"\"\"\n    f.__doc__ = (f.__doc__ or '') + _doc\n    return f\n\ndef _include_choose_dir_file_docs(f):\n    \"\"\"\n        Combines the basic Chooser options (wildard, message) docsstring\n        with the wrapped function's doc string.\n        \"\"\"\n    _doc = \"\"\":param default_dir: The default directory selected when the dialog spawns \n    :param default_file: The default filename used in the dialog\n    \"\"\"\n    f.__doc__ = (f.__doc__ or '') + _doc\n    return f\n\n\n\ndef LayoutOptions(label_color=None,\n                  label_bg_color=None,\n                  help_color=None,\n                  help_bg_color=None,\n                  error_color=None,\n                  error_bg_color=None,\n                  show_label=True,\n                  show_help=True,\n                  visible=True,\n                  full_width=False):\n    \"\"\"\n    Layout Options:\n    ---------------\n\n    Color options can be passed either as a hex string ('#ff0000') or as\n    a collection of RGB values (e.g. `[255, 0, 0]` or `(255, 0, 0)`)\n\n    :param label_color:    The foreground color of the label text\n    :param label_bg_color: The background color of the label text.\n    :param help_color:     The foreground color of the help text.\n    :param help_bg_color:  The background color of the help text.\n    :param error_color:    The foreground color of the error text (when visible).\n    :param error_bg_color: The background color of the error text (when visible).\n    :param show_label:     Toggles whether or not to display the label text\n    :param show_help:      Toggles whether or not to display the help text\n    :param visible:        Hides the entire widget when False. Note: the widget\n                           is still present in the UI and will still send along any\n                           default values that have been provided in code. This option\n                           is here for when you want to hide certain advanced / dangerous\n                           inputs from your GUI users.\n    :param full_width:     This is a layout hint for this widget. When True the widget\n                           will fill the entire available space within a given row.\n                           Otherwise, it will be sized based on the column rules\n                           provided elsewhere.\n    \"\"\"\n    return _clean(locals())\n\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef TextField(initial_value=None, validator=None, **layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef PasswordField(initial_value=None, validator=None, **layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef IntegerField(initial_value=None, validator=None, min=0, max=100, increment=1, **layout_options):\n    \"\"\"\n    :param min: The minimum value allowed\n    :param max: The maximum value allowed\n    :param increment: The step size of the spinner\n    \"\"\"\n    return _clean(locals())\n\n@_include_layout_docs\n@_include_global_option_docs\ndef Slider(initial_value=None, validator=None, min=0, max=100, increment=1, **layout_options):\n    \"\"\"\n    :param min: The minimum value allowed\n    :param max: The maximum value allowed\n    :param increment: The step size of the slider\n    \"\"\"\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef DecimalField(validator=None,\n                 initial_value=None,\n                 min=0.0,\n                 max=1.0,\n                 increment=0.01,\n                 precision=2,\n                 **layout_options):\n    \"\"\"\n    :param min: The minimum value allowed\n    :param max: The maximum value allowed\n    :param increment: The step size of the spinner\n    :param precision: The precision of the decimal (0-20)\n    \"\"\"\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef TextArea(initial_value=None, height=None, readonly=False, validator=None, **layout_options):\n    \"\"\"\n    :param height:   The height of the TextArea.\n    :param readonly: Controls whether or not user's may modify the contents\n    \"\"\"\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef RichTextConsole(**layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef ListBox(initial_value=None, height=None, **layout_options):\n    \"\"\"\n    :param height: The height of the Listbox\n    \"\"\"\n    return _clean(locals())\n\n# TODO: what are this guy's layout options..?\ndef MutexGroup(initial_selection=None, title=None, **layout_options):\n    \"\"\"\n    :param initial_selection: The index of the option which should be initially selected.\n    :param title:             Adds the supplied title above the RadioGroup options (when present)\n    \"\"\"\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef Dropdown(initial_value=None, **layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef Counter(initial_value=None, **layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef CheckBox(initial_value=None, **layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef BlockCheckBox(initial_value=None, checkbox_label=None, **layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\ndef FilterableDropdown(placeholder=None,\n                       empty_message=None,\n                       max_size=80,\n                       search_strategy=None,\n                       initial_value=None,\n                       **layout_options):\n    \"\"\"\n    :param placeholder:     Text to display when the user has provided no input\n    :param empty_message:   Text to display if the user's query doesn't match anything\n    :param max_size:        maximum height of the dropdown\n    :param search_strategy: see: PrefixSearchStrategy\n    \"\"\"\n    return _clean(locals())\n\n\ndef PrefixSearchStrategy(\n                   choice_tokenizer=PrefixTokenizers.WORDS,\n                   input_tokenizer=PrefixTokenizers.REGEX('\\s'),\n                   ignore_case=True,\n                   operator='AND',\n                   index_suffix=False):\n    \"\"\"\n    :param choice_tokenizer: See: PrefixTokenizers - sets the tokenization strategy\n                             for the `choices`\n    :param input_tokenizer:  See: PrefixTokenizers sets how the users's `input` get tokenized.\n    :param ignore_case:      Controls whether or not to honor case while searching\n    :param operator:         see: `OperatorType` - controls whether or not individual\n                             search tokens\n                             get `AND`ed or `OR`d together when evaluating a match.\n    :param index_suffix:     When enabled, generates a suffix-tree to enable efficient\n                             partial-matching against any of the tokens.\n    \"\"\"\n    return {**_clean(locals()), 'type': 'PrefixFilter'}\n\n\n@_include_layout_docs\n@_include_global_option_docs\n@_include_choose_dir_file_docs\n@_include_chooser_msg_wildcard_docs\ndef FileChooser(wildcard=None,\n                default_dir=None,\n                default_file=None,\n                message=None,\n                initial_value=None,\n                **layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\n@_include_chooser_msg_wildcard_docs\ndef DirectoryChooser(wildcard=None,\n                    default_path=None,\n                    message=None,\n                    initial_value=None,\n                    **layout_options):\n    \"\"\"\n    :param default_path: The default path selected when the dialog spawns\n    \"\"\"\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\n@_include_choose_dir_file_docs\n@_include_chooser_msg_wildcard_docs\ndef FileSaver(wildcard=None,\n              default_dir=None,\n              default_file=None,\n              message=None,\n              initial_value=None,\n              **layout_options):\n    return _clean(locals())\n\n\n@_include_layout_docs\n@_include_global_option_docs\n@_include_choose_dir_file_docs\n@_include_chooser_msg_wildcard_docs\ndef MultiFileSaver(wildcard=None,\n              default_dir=None,\n              default_file=None,\n              message=None,\n              initial_value=None,\n              **layout_options):\n    return _clean(locals())\n\n\ndef ExpressionValidator(test=None, message=None):\n    \"\"\"\n    Creates the data for a basic expression validator.\n\n    Your test function can be made up of any valid Python expression.\n    It receives the variable user_input as an argument against which to\n    perform its validation. Note that all values coming from Gooey\n    are in the form of a string, so you'll have to cast as needed\n    in order to perform your validation.\n    \"\"\"\n    return {**_clean(locals()), 'type': 'ExpressionValidator'}\n\n\ndef RegexValidator(test=None, message=None):\n    \"\"\"\n    Creates the data for a basic RegexValidator.\n\n    :param test:    the regex expression. This should be the expression\n                    directly (i.e. `test='\\d+'`). Gooey will test\n                    that the user's input satisfies this expression.\n    :param message: The message to display if the input doesn't match\n                    the regex\n    \"\"\"\n    return {**_clean(locals()), 'type': 'RegexValidator'}\n\n\ndef ArgumentGroup(show_border=False,\n                  show_underline=True,\n                  label_color=None,\n                  columns=None,\n                  margin_top=None):\n    \"\"\"\n    :param show_border:    When True a labeled border will surround all widgets added to this group.\n    :param show_underline: Controls whether or not to display the underline when using the default border style\n    :param label_color:    The foreground color for the group name\n    :param columns:        Controls the number of widgets on each row\n    :param margin_top:     specifies the top margin in pixels for this group\n    \"\"\"\n    return _clean(locals())\n\n\n\n\n\ndef _clean(options):\n    cleaned = {k: v for k, v in options.items()\n               if v is not None and k != \"layout_options\"}\n    return {**options.get('layout_options', {}), **cleaned}\n\n"
  },
  {
    "path": "gooey/gui/components/options/validators.py",
    "content": "import re\nfrom functools import wraps\n\nfrom gooey.gui.components.filtering.prefix_filter import OperatorType\n\n\nclass SuperBool(object):\n    \"\"\"\n    A boolean which keeps with it the rationale\n    for when it is false.\n    \"\"\"\n    def __init__(self, value, rationale):\n        self.value = value\n        self.rationale = rationale\n\n    def __bool__(self):\n        return self.value\n\n    __nonzero__ = __bool__\n\n    def __str__(self):\n        return str(self.value)\n\n\ndef lift(f):\n    \"\"\"\n    Lifts a basic predicate to the SuperBool type\n    stealing the docstring as the rationale message.\n\n    This is largely just goofing around and experimenting\n    since it's a private internal API.\n    \"\"\"\n    @wraps(f)\n    def inner(value):\n        result = f(value)\n        return SuperBool(result, f.__doc__) if not isinstance(result, SuperBool) else result\n    return inner\n\n\n@lift\ndef is_tuple_or_list(value):\n    \"\"\"Must be either a list or tuple\"\"\"\n    return isinstance(value, list) or isinstance(value, tuple)\n\n\n@lift\ndef is_str(value):\n    \"\"\"Must be of type `str`\"\"\"\n    return isinstance(value, str)\n\n@lift\ndef is_str_or_coll(value):\n    \"\"\"\n    Colors must be either a hex string or collection of RGB values.\n    e.g.\n        Hex string: #fff0ce\n        RGB Collection: [0, 255, 128] or (0, 255, 128)\n    \"\"\"\n    return bool(is_str(value)) or bool(is_tuple_or_list(value))\n\n\n@lift\ndef has_valid_channel_values(rgb_coll):\n    \"\"\"Colors in an RGB collection must all be in the range 0-255\"\"\"\n    return all([is_0to255(c) and is_int(c) for c in rgb_coll])\n\n\n@lift\ndef is_three_channeled(value):\n    \"\"\"Missing channels! Colors in an RGB collection should be of the form [R,G,B] or (R,G,B)\"\"\"\n    return len(value) == 3\n\n@lift\ndef is_hex_string(value: str):\n    \"\"\"Invalid hexadecimal format. Expected: \"#FFFFFF\" \"\"\"\n    return isinstance(value, str) and bool(re.match('^#[\\dABCDEF]{6}$', value, flags=2))\n\n\n@lift\ndef is_bool(value):\n    \"\"\"Must be of type Boolean\"\"\"\n    return isinstance(value, bool)\n\n@lift\ndef non_empty_string(value):\n    \"\"\"Must be a non-empty non-blank string\"\"\"\n    return bool(value) and bool(value.strip())\n\n@lift\ndef is_tokenization_operator(value):\n    \"\"\"Operator must be a valid OperatorType i.e. one of: (AND, OR)\"\"\"\n    return bool(value) in (OperatorType.AND, OperatorType.OR)\n\n@lift\ndef is_tokenizer(value):\n    \"\"\"Tokenizers must be valid Regular expressions. see: options.PrefixTokenizers\"\"\"\n    return bool(non_empty_string(value))\n\n\n@lift\ndef is_int(value):\n    \"\"\"Invalid type. Expected `int`\"\"\"\n    return isinstance(value, int)\n\n@lift\ndef is_0to255(value):\n    \"\"\"RGB values must be in the range 0 - 255 (inclusive)\"\"\"\n    return 0 <= value <= 255\n\n\ndef is_0to20(value):\n    \"\"\"Precision values must be in the range 0 - 20 (inclusive)\"\"\"\n    return 0 <= value <= 20\n\n@lift\ndef is_valid_color(value):\n    \"\"\"Must be either a valid hex string or RGB list\"\"\"\n    if is_str(value):\n        return is_hex_string(value)\n    elif is_tuple_or_list(value):\n        return (is_tuple_or_list(value)\n                and is_three_channeled(value)\n                and has_valid_channel_values(value))\n    else:\n        return is_str_or_coll(value)\n\n\nvalidators = {\n    'label_color': is_valid_color,\n    'label_bg_color': is_valid_color,\n    'help_color': is_valid_color,\n    'help_bg_color': is_valid_color,\n    'error_color': is_valid_color,\n    'error_bg_color': is_valid_color,\n    'show_label': is_bool,\n    'show_help': is_bool,\n    'visible': is_bool,\n    'full_width': is_bool,\n    'height': is_int,\n    'readonly': is_bool,\n    'initial_selection': is_int,\n    'title': non_empty_string,\n    'checkbox_label': non_empty_string,\n    'placeholder': non_empty_string,\n    'empty_message': non_empty_string,\n    'max_size': is_int,\n    'choice_tokenizer': is_tokenizer,\n    'input_tokenizer': is_tokenizer,\n    'ignore_case': is_bool,\n    'operator': is_tokenization_operator,\n    'index_suffix': is_bool,\n    'wildcard': non_empty_string,\n    'default_dir': non_empty_string,\n    'default_file': non_empty_string,\n    'default_path': non_empty_string,\n    'message': non_empty_string,\n    'precision': is_0to20\n}\n\n\n\ndef collect_errors(predicates, m):\n    return {\n        k:predicates[k](v).rationale\n        for k,v in m.items()\n        if k in predicates and not predicates[k](v)}\n\n\ndef validate(pred, value):\n    result = pred(value)\n    if not result:\n        raise ValueError(result.rationale)\n\n\n\nif __name__ == '__main__':\n    # TODO: there should be tests\n    pass\n    # print(validateColor((1, 'ergerg', 1234)))\n    # print(validateColor(1234))\n    # print(validateColor(123.234))\n    # print(validateColor('123.234'))\n    # print(validateColor('FFFAAA'))\n    # print(validateColor('#FFFAAA'))\n    # print(validateColor([]))\n    # print(validateColor(()))\n    # print(validateColor((1, 2)))\n    # print(validateColor((1, 2, 1234)))\n    # print(is_lifted(lift(is_int)))\n    # print(is_lifted(is_int))\n    # print(OR(is_poop, is_int)('poop'))\n    # print(AND(is_poop, is_lower, is_lower)('pooP'))\n    # print(OR(is_poop, is_int))\n    # print(is_lifted(OR(is_poop, is_int)))\n    # print(validate(is_valid_color, [255, 255, 256]))\n    # print(is_valid_color('#fff000'))\n    # print(is_valid_color([255, 244, 256]))\n    # print(non_empty_string('asdf') and non_empty_string('asdf'))\n    # validate(is_valid_color, 1234)\n\n\n"
  },
  {
    "path": "gooey/gui/components/sidebar.py",
    "content": "import wx  # type: ignore\r\n\r\nfrom gooey.gui.util import wx_util\r\n\r\n\r\nclass Sidebar(wx.Panel):\r\n    \"\"\"\r\n    Sidebar handles the show/hide logic so that it mirrors the functionality\r\n    of the wx.Notebook class (which wants to control everything)\r\n    \"\"\"\r\n    def __init__(self, parent, buildSpec, configPanels, *args, **kwargs):\r\n        super(Sidebar, self).__init__(parent, *args, **kwargs)\r\n        self._parent = parent\r\n        self.buildSpec = buildSpec\r\n        self.configPanels = configPanels\r\n        self.activeSelection = 0\r\n        self.options = list(self.buildSpec['widgets'].keys())\r\n        self.leftPanel = wx.Panel(self)\r\n        self.label = wx_util.h1(self.leftPanel, self.buildSpec.get('sidebar_title'))\r\n        self.listbox = wx.ListBox(self.leftPanel, -1, choices=self.options)\r\n        self.Bind(wx.EVT_LISTBOX, self.swapConfigPanels, self.listbox)\r\n        self.layoutComponent()\r\n        self.listbox.SetSelection(0)\r\n\r\n\r\n    def getSelectedGroup(self):\r\n        \"\"\"Return the currently active 'group' i.e. the root SubParser \"\"\"\r\n        return self.options[self.activeSelection]\r\n\r\n\r\n    def getActiveConfig(self):\r\n        \"\"\"Return the currently visible config screen\"\"\"\r\n        return self.configPanels[self.activeSelection]\r\n\r\n\r\n    def swapConfigPanels(self, event):\r\n        \"\"\"Hide/show configuration panels based on the currently selected\r\n         option in the sidebar \"\"\"\r\n        for id, panel in enumerate(self.configPanels):\r\n            panel.Hide()\r\n        self.activeSelection = event.Selection\r\n        self.configPanels[event.Selection].Show()\r\n        self._parent.Layout()\r\n\r\n\r\n    def layoutComponent(self):\r\n        left = self.layoutLeftSide()\r\n\r\n        hsizer = wx.BoxSizer(wx.HORIZONTAL)\r\n        hsizer.Add(left, 0, wx.EXPAND)\r\n\r\n        if not self.buildSpec['tabbed_groups']:\r\n            # only add it for non-tabbed layouts as it looks\r\n            # weird against the tabbed ones\r\n            hsizer.Add(wx_util.vertical_rule(self), 0, wx.EXPAND)\r\n\r\n        for body in self.configPanels:\r\n            body.Reparent(self)\r\n            hsizer.Add(body, 1, wx.EXPAND)\r\n            body.Hide()\r\n        self.configPanels[0].Show()\r\n        self.SetSizer(hsizer)\r\n\r\n        if not self.buildSpec['show_sidebar']:\r\n            left.Show(False)\r\n\r\n        self.Layout()\r\n\r\n\r\n    def layoutLeftSide(self):\r\n        self.leftPanel.SetBackgroundColour(self.buildSpec['sidebar_bg_color'])\r\n        self.leftPanel.SetSize((180, 0))\r\n        self.leftPanel.SetMinSize((180, 0))\r\n\r\n        container = wx.BoxSizer(wx.VERTICAL)\r\n        container.AddSpacer(15)\r\n        container.Add(self.label, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, 10)\r\n        container.AddSpacer(5)\r\n\r\n        container.Add(self.listbox, 1, wx.LEFT | wx.RIGHT | wx.EXPAND, 10)\r\n        container.AddSpacer(20)\r\n        self.leftPanel.SetSizer(container)\r\n        return self.leftPanel\r\n\r\n\r\n\r\n"
  },
  {
    "path": "gooey/gui/components/tabbar.py",
    "content": "import wx  # type: ignore\r\n\r\nfrom gooey.gui import events\r\nfrom gooey.gui.pubsub import pub\r\nfrom gooey.gui.util import wx_util\r\n\r\n\r\nclass Tabbar(wx.Panel):\r\n    def __init__(self, parent, buildSpec, configPanels, *args, **kwargs):\r\n        super(Tabbar, self).__init__(parent, *args, **kwargs)\r\n        self._parent = parent\r\n        self.notebook = wx.Notebook(self, style=wx.BK_DEFAULT)\r\n        self.buildSpec = buildSpec\r\n        self.configPanels = configPanels\r\n        self.options = list(self.buildSpec['widgets'].keys())\r\n        self.layoutComponent()\r\n\r\n\r\n    def layoutComponent(self):\r\n        for group, panel in zip(self.options, self.configPanels):\r\n            panel.Reparent( self.notebook)\r\n            self.notebook.AddPage(panel, group)\r\n            self.notebook.Layout()\r\n\r\n        sizer = wx.BoxSizer(wx.VERTICAL)\r\n        sizer.Add(self.notebook, 1, wx.EXPAND)\r\n        self.SetSizer(sizer)\r\n        self.Layout()\r\n\r\n    def getSelectedGroup(self):\r\n        return self.options[self.notebook.Selection]\r\n\r\n    def getActiveConfig(self):\r\n        return self.configPanels[self.notebook.Selection]\r\n\r\n    def show(self, b):\r\n        self.Show(b)\r\n"
  },
  {
    "path": "gooey/gui/components/util/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/gui/components/util/wrapped_static_text.py",
    "content": "import wx  # type: ignore\r\nfrom wx.lib.wordwrap import wordwrap  # type: ignore\r\n\r\n\r\n\r\nclass AutoWrappedStaticText(wx.StaticText):\r\n    \"\"\"\r\n    Copy/pasta of wx.lib.agw.infobar.AutoWrapStaticText with 3 modifications:\r\n\r\n        1. Extends wx.StaticText rather than GenStaticText\r\n        2. Does not set the fore/background colors to sys defaults\r\n        3. takes an optional `target` parameter for sizing info\r\n\r\n    The behavior of GenStaticText's background color is pretty buggy cross-\r\n    platform. It doesn't reliably match its parent components background\r\n    colors[0] (for instance when rendered inside of a Notebook) which leads to\r\n    ugly 'boxing' around the text components.\r\n\r\n    There is either a bug in WX, or or human error on my end, which causes\r\n    EVT_SIZE events to continuously spawn from this (and AutoWrapStaticText) but\r\n    with ever decreasing widths (in response to the SetLabel action in the\r\n    wrap handler). The end result is a single skinny column of letters.\r\n\r\n    The work around is to respond the EVT_SIZE event, but follow the size of the\r\n    `target` component rather than relying on the size of the event.\r\n\r\n    [0] more specifically, they'll match 1:1 on paper, but still ultimately\r\n    render differently.\r\n    \"\"\"\r\n\r\n    def __init__(self, parent, *args, **kwargs):\r\n        self.target = kwargs.pop('target', None)\r\n        super(AutoWrappedStaticText, self).__init__(parent, *args, **kwargs)\r\n        self.label = kwargs.get('label')\r\n        self.Bind(wx.EVT_SIZE, self.OnSize)\r\n        self.parent = parent\r\n\r\n\r\n    def OnSize(self, event):\r\n        \"\"\"\r\n        Handles the ``wx.EVT_SIZE`` event for :class:`AutoWrapStaticText`.\r\n\r\n        :param `event`: a :class:`wx.SizeEvent` event to be processed.\r\n        \"\"\"\r\n\r\n        event.Skip()\r\n        if self.target:\r\n            self.Wrap(self.target.GetSize().width)\r\n        else:\r\n            self.Wrap(self.parent.GetSize()[0])\r\n\r\n    def Wrap(self, width):\r\n        \"\"\"\r\n        This functions wraps the controls label so that each of its lines becomes at\r\n        most `width` pixels wide if possible (the lines are broken at words boundaries\r\n        so it might not be the case if words are too long).\r\n\r\n        If `width` is negative, no wrapping is done.\r\n\r\n        :param integer `width`: the maximum available width for the text, in pixels.\r\n\r\n        :note: Note that this `width` is not necessarily the total width of the control,\r\n         since a few pixels for the border (depending on the controls border style) may be added.\r\n        \"\"\"\r\n\r\n        if width < 0:\r\n            return\r\n\r\n        self.Freeze()\r\n\r\n        dc = wx.ClientDC(self)\r\n        dc.SetFont(self.GetFont())\r\n        text = wordwrap(self.label, width, dc)\r\n        self.SetLabel(text, wrapped=True)\r\n\r\n        self.Thaw()\r\n\r\n    def SetLabel(self, label, wrapped=False):\r\n        \"\"\"\r\n        Sets the :class:`AutoWrapStaticText` label.\r\n\r\n        All \"&\" characters in the label are special and indicate that the following character is\r\n        a mnemonic for this control and can be used to activate it from the keyboard (typically\r\n        by using ``Alt`` key in combination with it). To insert a literal ampersand character, you\r\n        need to double it, i.e. use \"&&\". If this behaviour is undesirable, use :meth:`~Control.SetLabelText` instead.\r\n\r\n        :param string `label`: the new :class:`AutoWrapStaticText` text label;\r\n        :param bool `wrapped`: ``True`` if this method was called by the developer using :meth:`~AutoWrapStaticText.SetLabel`,\r\n         ``False`` if it comes from the :meth:`~AutoWrapStaticText.OnSize` event handler.\r\n\r\n        :note: Reimplemented from :class:`wx.Control`.\r\n        \"\"\"\r\n\r\n        if not wrapped:\r\n            self.label = label\r\n\r\n        wx.StaticText.SetLabel(self, label)\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/__init__.py",
    "content": "from __future__ import absolute_import\r\n\r\nfrom .textfield import TextField\r\nfrom .textarea import Textarea\r\nfrom .password import PasswordField\r\nfrom .command import CommandField\r\nfrom .dropdown import Dropdown\r\nfrom .listbox import Listbox\r\nfrom .checkbox import CheckBox\r\nfrom .checkbox import BlockCheckbox\r\nfrom .counter import Counter\r\nfrom .radio_group import RadioGroup\r\nfrom .choosers import *\r\nfrom .dropdown_filterable import FilterableDropdown\r\nfrom .numeric_fields import IntegerField, DecimalField\r\nfrom .slider import Slider\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/bases.py",
    "content": "import re\r\nfrom functools import reduce\r\nfrom typing import Optional, Callable, Any, Type, Union\r\n\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui import formatters, events\r\nfrom gooey.gui.util import wx_util\r\nfrom gooey.python_bindings.types import FormField\r\nfrom gooey.util.functional import getin, ifPresent\r\nfrom gooey.gui.validators import runValidator\r\nfrom gooey.gui.components.util.wrapped_static_text import AutoWrappedStaticText\r\nfrom gooey.gui.components.mouse import notifyMouseEvent\r\nfrom gooey.python_bindings import types as t\r\n\r\n\r\nclass BaseWidget(wx.Panel):\r\n    widget_class: Any\r\n\r\n    def arrange(self, label, text):\r\n        raise NotImplementedError\r\n\r\n    def getWidget(self, parent: wx.Window, **options):\r\n        return self.widget_class(parent, **options)\r\n\r\n    def connectSignal(self):\r\n        raise NotImplementedError\r\n\r\n    def getSublayout(self, *args, **kwargs):\r\n        raise NotImplementedError\r\n\r\n    def setValue(self, value):\r\n        raise NotImplementedError\r\n\r\n    def setPlaceholder(self, value):\r\n        raise NotImplementedError\r\n\r\n    def receiveChange(self, *args, **kwargs):\r\n        raise NotImplementedError\r\n\r\n    def dispatchChange(self, value, **kwargs):\r\n        raise NotImplementedError\r\n\r\n    def formatOutput(self, metatdata, value):\r\n        raise NotImplementedError\r\n\r\n\r\nclass TextContainer(BaseWidget):\r\n    # TODO: fix this busted-ass inheritance hierarchy.\r\n    # Cracking at the seems for more advanced widgets\r\n    # problems:\r\n    #   - all the usual textbook problems of inheritance\r\n    #   - assumes there will only ever be ONE widget created\r\n    #   - assumes those widgets are all created in `getWidget`\r\n    #   - all the above make for extremely awkward lifecycle management\r\n    #      - no clear point at which binding is correct.\r\n    #   - I think the core problem here is that I couple the interface\r\n    #     for shared presentation layout with the specification of\r\n    #     a behavioral interface\r\n    #     - This should be broken apart.\r\n    #     - presentation can be ad-hoc or composed\r\n    #     - behavioral just needs a typeclass of get/set/format for Gooey's purposes\r\n    widget_class = None  # type: ignore\r\n\r\n    def __init__(self, parent, widgetInfo, *args, **kwargs):\r\n        super(TextContainer, self).__init__(parent, *args, **kwargs)\r\n\r\n        self.info = widgetInfo\r\n        self._id = widgetInfo['id']\r\n        self.widgetInfo = widgetInfo\r\n        self._meta = widgetInfo['data']\r\n        self._options = widgetInfo['options']\r\n        self.label = wx.StaticText(self, label=widgetInfo['data']['display_name'])\r\n        self.help_text = AutoWrappedStaticText(self, label=widgetInfo['data']['help'] or '')\r\n        self.error = AutoWrappedStaticText(self, label='')\r\n        self.error.Hide()\r\n        self.widget = self.getWidget(self)\r\n        self.layout = self.arrange(*args, **kwargs)\r\n        self.setColors()\r\n        self.SetSizer(self.layout)\r\n        self.bindMouseEvents()\r\n        self.Bind(wx.EVT_SIZE, self.onSize)\r\n\r\n        # 1.0.7 initial_value should supersede default when both are present\r\n        if self._options.get('initial_value') is not None:\r\n            self.setValue(self._options['initial_value'])\r\n        # Checking for None instead of truthiness means False-evaluaded defaults can be used.\r\n        elif self._meta['default'] is not None:\r\n            self.setValue(self._meta['default'])\r\n\r\n        if self._options.get('placeholder'):\r\n            self.setPlaceholder(self._options.get('placeholder'))\r\n\r\n        self.onComponentInitialized()\r\n\r\n    def onComponentInitialized(self):\r\n        pass\r\n\r\n    def bindMouseEvents(self):\r\n        \"\"\"\r\n        Send any LEFT DOWN mouse events to interested\r\n        listeners via pubsub. see: gooey.gui.mouse for background.\r\n        \"\"\"\r\n        self.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n        self.label.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n        self.help_text.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n        self.error.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n        self.widget.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n\r\n    def arrange(self, *args, **kwargs):\r\n        wx_util.make_bold(self.label)\r\n        wx_util.withColor(self.label, self._options['label_color'])\r\n        wx_util.withColor(self.help_text, self._options['help_color'])\r\n        wx_util.withColor(self.error, self._options['error_color'])\r\n\r\n        self.help_text.SetMinSize((0,-1))\r\n\r\n        layout = wx.BoxSizer(wx.VERTICAL)\r\n\r\n        if self._options.get('show_label', True):\r\n            layout.Add(self.label, 0, wx.EXPAND)\r\n        else:\r\n            self.label.Show(False)\r\n            layout.AddStretchSpacer(1)\r\n\r\n        layout.AddSpacer(2)\r\n        if self.help_text and self._options.get('show_help', True):\r\n            layout.Add(self.help_text, 1, wx.EXPAND)\r\n            layout.AddSpacer(2)\r\n        else:\r\n            self.help_text.Show(False)\r\n            layout.AddStretchSpacer(1)\r\n        layout.Add(self.getSublayout(), 0, wx.EXPAND)\r\n        layout.Add(self.error, 1, wx.EXPAND)\r\n\r\n        # self.error.SetLabel(\"HELLOOOOO??\")\r\n        # self.error.Show()\r\n        # print(self.error.Shown)\r\n        return layout\r\n\r\n\r\n    def setColors(self):\r\n        wx_util.make_bold(self.label)\r\n        wx_util.withColor(self.label, self._options['label_color'])\r\n        wx_util.withColor(self.help_text, self._options['help_color'])\r\n        wx_util.withColor(self.error, self._options['error_color'])\r\n        if self._options.get('label_bg_color'):\r\n            self.label.SetBackgroundColour(self._options.get('label_bg_color'))\r\n        if self._options.get('help_bg_color'):\r\n            self.help_text.SetBackgroundColour(self._options.get('help_bg_color'))\r\n        if self._options.get('error_bg_color'):\r\n            self.error.SetBackgroundColour(self._options.get('error_bg_color'))\r\n\r\n    def getWidget(self, *args, **options):\r\n        return self.widget_class(*args, **options)\r\n\r\n    def getWidgetValue(self):\r\n        raise NotImplementedError\r\n\r\n    def getSublayout(self, *args, **kwargs):\r\n        layout = wx.BoxSizer(wx.HORIZONTAL)\r\n        layout.Add(self.widget, 1, wx.EXPAND)\r\n        return layout\r\n\r\n    def onSize(self, event):\r\n        # print(self.GetSize())\r\n        # self.error.Wrap(self.GetSize().width)\r\n        # self.help_text.Wrap(500)\r\n        # self.Layout()\r\n        event.Skip()\r\n\r\n    def getUiState(self) -> t.FormField:\r\n        return t.TextField(\r\n            id=self._id,\r\n            type=self.widgetInfo['type'],\r\n            value=self.getWidgetValue(),\r\n            placeholder=self.widget.widget.GetHint(),\r\n            error=self.error.GetLabel().replace('\\n', ' '),\r\n            enabled=self.IsEnabled(),\r\n            visible=self.IsShown()\r\n        )\r\n\r\n    def syncUiState(self, state: FormField):  # type: ignore\r\n        self.widget.setValue(state['value'])  # type: ignore\r\n        self.error.SetLabel(state['error'] or '')\r\n        self.error.Show(state['error'] is not None and state['error'] is not '')\r\n\r\n\r\n    def getValue(self) -> t.FieldValue:\r\n        regexFunc: Callable[[str], bool] = lambda x: bool(re.match(userValidator, x))\r\n\r\n        userValidator = getin(self._options, ['validator', 'test'], 'True')\r\n        message = getin(self._options, ['validator', 'message'], '')\r\n        testFunc = regexFunc \\\r\n                   if getin(self._options, ['validator', 'type'], None) == 'RegexValidator'\\\r\n                   else eval('lambda user_input: bool(%s)' % userValidator)\r\n        satisfies = testFunc if self._meta['required'] else ifPresent(testFunc)\r\n        value = self.getWidgetValue()\r\n\r\n        return t.FieldValue(  # type: ignore\r\n            id=self._id,\r\n            cmd=self.formatOutput(self._meta, value),\r\n            meta=self._meta,\r\n            rawValue= value,\r\n            # type=self.info['type'],\r\n            enabled=self.IsEnabled(),\r\n            visible=self.IsShown(),\r\n            test= runValidator(satisfies, value),\r\n            error=None if runValidator(satisfies, value) else message,\r\n            clitype=('positional'\r\n                        if self._meta['required'] and not self._meta['commands']\r\n                        else 'optional')\r\n        )\r\n\r\n    def setValue(self, value):\r\n        self.widget.SetValue(value)\r\n\r\n    def setPlaceholder(self, value):\r\n        if getattr(self.widget, 'SetHint', None):\r\n            self.widget.SetHint(value)\r\n\r\n    def setErrorString(self, message):\r\n        self.error.SetLabel(message)\r\n        self.error.Wrap(self.Size.width)\r\n        self.Layout()\r\n\r\n    def showErrorString(self, b):\r\n        self.error.Wrap(self.Size.width)\r\n        self.error.Show(b)\r\n\r\n    def setOptions(self, values):\r\n        return None\r\n\r\n    def receiveChange(self, metatdata, value):\r\n        raise NotImplementedError\r\n\r\n    def dispatchChange(self, value, **kwargs):\r\n        raise NotImplementedError\r\n\r\n    def formatOutput(self, metadata, value) -> str:\r\n        raise NotImplementedError\r\n\r\n\r\n\r\n\r\nclass BaseChooser(TextContainer):\r\n    \"\"\" Base Class for the Chooser widget types \"\"\"\r\n\r\n    def setValue(self, value):\r\n        self.widget.setValue(value)\r\n\r\n    def setPlaceholder(self, value):\r\n        self.widget.SetHint(value)\r\n\r\n    def getWidgetValue(self):\r\n        return self.widget.getValue()\r\n\r\n    def formatOutput(self, metatdata, value):\r\n        return formatters.general(metatdata, value)\r\n\r\n    def getUiState(self) -> t.FormField:\r\n        btn: wx.Button = self.widget.button  # type: ignore\r\n        return t.Chooser(\r\n            id=self._id,\r\n            type=self.widgetInfo['type'],\r\n            value=self.widget.getValue(),\r\n            btn_label=btn.GetLabel(),\r\n            error=self.error.GetLabel() or None,\r\n            enabled=self.IsEnabled(),\r\n            visible=self.IsShown()\r\n        )\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/basictextconsole.py",
    "content": "import wx  # type: ignore\n\nclass BasicTextConsole(wx.TextCtrl):\n    def __init__(self, parent):\n        super(BasicTextConsole, self).__init__(parent, -1, \"\", style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH | wx.TE_AUTO_URL )\n"
  },
  {
    "path": "gooey/gui/components/widgets/checkbox.py",
    "content": "import wx  # type: ignore\r\n\r\nfrom gooey.gui import formatters\r\nfrom gooey.gui.components.widgets.bases import TextContainer\r\nfrom gooey.gui.lang.i18n import _\r\nfrom gooey.gui.util import wx_util\r\nfrom gooey.python_bindings import types as t\r\n\r\n\r\nclass CheckBox(TextContainer):\r\n\r\n    widget_class = wx.CheckBox\r\n\r\n    def arrange(self, *args, **kwargs):\r\n        wx_util.make_bold(self.label)\r\n        wx_util.withColor(self.label, self._options['label_color'])\r\n        wx_util.withColor(self.help_text, self._options['help_color'])\r\n        wx_util.withColor(self.error, self._options['error_color'])\r\n        self.error.Hide()\r\n\r\n        self.help_text.SetMinSize((0,-1))\r\n\r\n        layout = wx.BoxSizer(wx.VERTICAL)\r\n        if self._options.get('show_label', True):\r\n            layout.Add(self.label, 0, wx.EXPAND)\r\n        else:\r\n            self.label.Show(False)\r\n            layout.AddStretchSpacer(1)\r\n\r\n        layout.AddSpacer(2)\r\n        if self.help_text:\r\n            hsizer = wx.BoxSizer(wx.HORIZONTAL)\r\n            hsizer.Add(self.widget, 0)\r\n            hsizer.Add(self.help_text, 1)\r\n            layout.Add(hsizer, 1, wx.EXPAND)\r\n            layout.AddSpacer(2)\r\n        else:\r\n            layout.Add(self.widget, 0, wx.EXPAND)\r\n            layout.AddStretchSpacer(1)\r\n        return layout\r\n\r\n\r\n    def setValue(self, value):\r\n        self.widget.SetValue(value)\r\n\r\n    def getWidgetValue(self):\r\n        return self.widget.GetValue()\r\n\r\n\r\n    def formatOutput(self, metatdata, value):\r\n        return formatters.checkbox(metatdata, value)\r\n\r\n\r\n    def hideInput(self):\r\n        self.widget.Hide()\r\n\r\n\r\n    def getUiState(self) -> t.FormField:\r\n        return t.Checkbox(\r\n            id=self._id,\r\n            type='Checkbox',\r\n            checked=self.widget.GetValue(),\r\n            error=self.error.GetLabel() or None,  # type: ignore\r\n            enabled=self.IsEnabled(),\r\n            visible=self.IsShown()\r\n        )\r\n\r\n    def syncUiState(self, state: t.Checkbox):  # type: ignore\r\n        checkbox: wx.CheckBox = self.widget\r\n        checkbox.SetValue(state['checked'])\r\n        checkbox.Enable(state['enabled'])\r\n        self.Show(state['visible'])\r\n        self.error.SetLabel(state['error'] or '')\r\n        self.error.Show(state['error'] is not None and state['error'] is not '')\r\n\r\n\r\n\r\n\r\n\r\nclass BlockCheckbox(CheckBox):\r\n    \"\"\"\r\n    A block style layout which places the help text in the normal\r\n    location rather than inline next to the checkbox. A replacement label\r\n    called `block_label` is shown next to the checkbox control.\r\n\r\n         +-----------------+\r\n         |label            |\r\n         |help_text        |\r\n         |[ ] block_label  |\r\n         +-----------------+\r\n    This option tends to look better when there is a large amount of\r\n    help text.\r\n    \"\"\"\r\n\r\n\r\n    def arrange(self, *args, **kwargs):\r\n        wx_util.make_bold(self.label)\r\n        wx_util.withColor(self.label, self._options['label_color'])\r\n        wx_util.withColor(self.help_text, self._options['help_color'])\r\n        wx_util.withColor(self.error, self._options['error_color'])\r\n        self.error.Hide()\r\n\r\n        self.help_text.SetMinSize((0,-1))\r\n\r\n        layout = wx.BoxSizer(wx.VERTICAL)\r\n\r\n        if self._options.get('show_label', True):\r\n            layout.Add(self.label, 0, wx.EXPAND)\r\n        else:\r\n            layout.AddStretchSpacer(1)\r\n\r\n        layout.AddSpacer(2)\r\n        if self.help_text and self._options.get('show_help', True):\r\n            layout.Add(self.help_text, 1, wx.EXPAND)\r\n            layout.AddSpacer(2)\r\n        else:\r\n            layout.AddStretchSpacer(1)\r\n\r\n        layout.AddSpacer(2)\r\n\r\n        block_label = self._options.get('checkbox_label', _('checkbox_label'))\r\n        hsizer = wx.BoxSizer(wx.HORIZONTAL)\r\n        hsizer.Add(self.widget, 0)\r\n        hsizer.Add(wx.StaticText(self, label=block_label), 1)\r\n        layout.Add(hsizer, 1, wx.EXPAND)\r\n        layout.AddSpacer(2)\r\n\r\n        return layout"
  },
  {
    "path": "gooey/gui/components/widgets/choosers.py",
    "content": "from gooey.gui import formatters\r\nfrom gooey.gui.components.widgets import core\r\nfrom gooey.gui.components.widgets.bases import TextContainer, BaseChooser\r\n\r\n\r\n__ALL__ = [\r\n    'FileChooser',\r\n    'MultiFileChooser',\r\n    'FileSaver',\r\n    'DirChooser',\r\n    'MultiDirChooser',\r\n    'DateChooser',\r\n    'ColourChooser',\r\n    'TimeChooser'\r\n]\r\n\r\n\r\nclass FileChooser(BaseChooser):\r\n    widget_class = core.FileChooser\r\n\r\n\r\nclass MultiFileChooser(BaseChooser):\r\n    widget_class = core.MultiFileChooser\r\n\r\n    def formatOutput(self, metatdata, value):\r\n        return formatters.multiFileChooser(metatdata, value)\r\n\r\n\r\nclass FileSaver(BaseChooser):\r\n    widget_class = core.FileSaver\r\n\r\n\r\nclass DirChooser(BaseChooser):\r\n    widget_class = core.DirChooser\r\n\r\n\r\nclass MultiDirChooser(BaseChooser):\r\n    widget_class = core.MultiDirChooser\r\n\r\n    def formatOutput(self, metadata, value):\r\n        return formatters.multiFileChooser(metadata, value)\r\n\r\n\r\nclass DateChooser(BaseChooser):\r\n    widget_class = core.DateChooser\r\n\r\n\r\nclass ColourChooser(BaseChooser):\r\n    widget_class = core.ColourChooser\r\n\r\n\r\nclass TimeChooser(BaseChooser):\r\n    widget_class = core.TimeChooser\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/command.py",
    "content": "from gooey.gui.components.widgets.textfield import TextField\r\nfrom gooey.python_bindings import types as t\r\n\r\n\r\n\r\n__ALL__ = ('CommandField',)\r\n\r\nclass CommandField(TextField):\r\n\r\n    def getUiState(self) -> t.FormField:\r\n        return t.Command(**super().getUiState())  # type: ignore\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/core/__init__.py",
    "content": "from . chooser import Chooser, FileChooser, FileSaver, DirChooser, DateChooser, TimeChooser, MultiFileChooser, MultiDirChooser, ColourChooser\r\nfrom . text_input import PasswordInput, MultilineTextInput, TextInput\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/core/chooser.py",
    "content": "import wx  # type: ignore\r\nimport wx.lib.agw.multidirdialog as MDD  # type: ignore\r\nimport os\r\nimport re\r\n\r\nfrom gooey.gui.components.widgets.core.text_input import TextInput\r\nfrom gooey.gui.components.widgets.dialogs.calender_dialog import CalendarDlg\r\nfrom gooey.gui.components.widgets.dialogs.time_dialog import TimeDlg\r\nfrom gooey.gui.lang.i18n import _\r\nfrom gooey.util.functional import merge\r\nfrom gooey.gui.util.filedrop import FileDrop\r\n\r\n\r\nclass Chooser(wx.Panel):\r\n    \"\"\"\r\n    TODO: Tests!\r\n    TODO: Document GooeyOptions!\r\n    Base 'Chooser' type.\r\n\r\n    Launches a Dialog box that allows the user to pick files, directories,\r\n    dates, etc.. and places the result into a TextInput in the UI\r\n\r\n    TODO: oh, young me. DRY != Good Abstraction\r\n    TODO: this is another weird inheritance hierarchy that's hard\r\n          to follow. Why do subclasses reach into, not their parent\r\n          class, but their _physical_ UI parent to grab the Gooey Options?\r\n          All this could be simplified to make the data flow\r\n          more apparent.\r\n    \"\"\"\r\n    _gooey_options = {\r\n        'pathsep': str\r\n    }\r\n    def __init__(self, parent, *args, **kwargs):\r\n        super(Chooser, self).__init__(parent)\r\n        self.options = parent._options\r\n        buttonLabel = kwargs.pop('label', _('browse'))\r\n        self.widget = TextInput(self, *args, **kwargs)\r\n        self.button = wx.Button(self, label=buttonLabel)\r\n        self.button.Bind(wx.EVT_BUTTON, self.spawnDialog)\r\n        self.dropTarget = FileDrop(self.widget, self.dropHandler)\r\n        self.widget.SetDropTarget(self.dropTarget)\r\n        self.layout()\r\n\r\n    def dropHandler(self, x, y, filenames):\r\n        sep = self.options.get('pathsep', os.pathsep)\r\n        self.widget.setValue(sep.join(filenames))\r\n        return True\r\n\r\n    def layout(self):\r\n        layout = wx.BoxSizer(wx.HORIZONTAL)\r\n        layout.Add(self.widget, 1, wx.EXPAND | wx.TOP, 2)\r\n        layout.Add(self.button, 0, wx.LEFT, 10)\r\n\r\n        v = wx.BoxSizer(wx.VERTICAL)\r\n        v.Add(layout, 1, wx.EXPAND, wx.TOP, 1)\r\n        self.SetSizer(v)\r\n\r\n    def spawnDialog(self, event):\r\n        fd = self.getDialog()\r\n        if fd.ShowModal() == wx.ID_CANCEL:\r\n            return\r\n        self.processResult(self.getResult(fd))\r\n\r\n    def getDialog(self):\r\n        return wx.FileDialog(self, _('open_file'))\r\n\r\n    def getResult(self, dialog):\r\n        return dialog.GetPath()\r\n\r\n    def processResult(self, result):\r\n        self.setValue(result)\r\n\r\n    def setValue(self, value):\r\n        self.widget.setValue(value)\r\n\r\n    def SetHint(self, value):\r\n        self.widget.SetHint(value)\r\n\r\n    def getValue(self):\r\n        return self.widget.getValue()\r\n\r\n\r\nclass FileChooser(Chooser):\r\n    \"\"\" Retrieve an existing file from the system \"\"\"\r\n    def getDialog(self):\r\n        options = self.Parent._options\r\n        return wx.FileDialog(self, message=options.get('message', _('open_file')),\r\n                             style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST,\r\n                             defaultFile=options.get('default_file', _(\"enter_filename\")),\r\n                             defaultDir=options.get('default_dir', _('')),\r\n                             wildcard=options.get('wildcard', wx.FileSelectorDefaultWildcardStr))\r\n\r\n\r\nclass MultiFileChooser(Chooser):\r\n    \"\"\" Retrieve an multiple files from the system \"\"\"\r\n    def getDialog(self):\r\n        options = self.Parent._options\r\n        return wx.FileDialog(self, message=options.get('message', _('open_files')),\r\n                             style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE,\r\n                             defaultFile=options.get('default_file', _(\"enter_filename\")),\r\n                             defaultDir=options.get('default_dir', _('')),\r\n                             wildcard=options.get('wildcard', wx.FileSelectorDefaultWildcardStr))\r\n\r\n    def getResult(self, dialog):\r\n        return os.pathsep.join(dialog.GetPaths())\r\n\r\n\r\nclass FileSaver(Chooser):\r\n    \"\"\" Specify the path to save a new file \"\"\"\r\n    def getDialog(self):\r\n        options = self.Parent._options\r\n        return wx.FileDialog(\r\n            self,\r\n            style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT,\r\n            defaultFile=options.get('default_file', _(\"enter_filename\")),\r\n            defaultDir=options.get('default_dir', _('')),\r\n            message=options.get('message', _('choose_file')),\r\n            wildcard=options.get('wildcard', wx.FileSelectorDefaultWildcardStr)\r\n        )\r\n\r\n\r\nclass DirChooser(Chooser):\r\n    \"\"\" Retrieve a path to the supplied directory \"\"\"\r\n    def getDialog(self):\r\n        options = self.Parent._options\r\n        return wx.DirDialog(self, message=options.get('message', _('choose_folder')),\r\n                            defaultPath=options.get('default_path', os.getcwd()))\r\n\r\nclass MultiDirChooser(Chooser):\r\n    \"\"\" Retrieve multiple directories from the system \"\"\"\r\n    def getDialog(self):\r\n        options = self.Parent._options\r\n        return MDD.MultiDirDialog(self,\r\n                                  message=options.get('message', _('choose_folders')),\r\n                                  title=_('choose_folders_title'),\r\n                                  defaultPath=options.get('default_path', os.getcwd()),\r\n                                  agwStyle=MDD.DD_MULTIPLE | MDD.DD_DIR_MUST_EXIST)\r\n    def getResult(self, dialog):\r\n        paths = dialog.GetPaths()\r\n        # Remove volume labels from Windows paths\r\n        if 'nt' == os.name:\r\n            for i, path in enumerate(paths):\r\n                if path:\r\n                    parts = path.split(os.sep)\r\n                    vol = parts[0]\r\n                    drives = re.match(r'.*\\((?P<drive>\\w:)\\)', vol)\r\n                    paths[i] = os.sep.join([drives.group('drive')] + parts[1:])\r\n\r\n        return os.pathsep.join(paths)\r\n\r\n\r\nclass DateChooser(Chooser):\r\n    \"\"\" Launches a date picker which returns an ISO Date \"\"\"\r\n    def __init__(self, *args, **kwargs):\r\n        defaults = {'label': _('choose_date')}\r\n        super(DateChooser, self).__init__(*args, **merge(kwargs, defaults))\r\n\r\n    def getDialog(self):\r\n        return CalendarDlg(self)\r\n\r\n\r\nclass TimeChooser(Chooser):\r\n    \"\"\" Launches a time picker which returns and ISO Time \"\"\"\r\n    def __init__(self, *args, **kwargs):\r\n        defaults = {'label': _('choose_time')}\r\n        super(TimeChooser, self).__init__(*args, **merge(kwargs, defaults))\r\n\r\n    def getDialog(self):\r\n        return TimeDlg(self)\r\n\r\n\r\nclass ColourChooser(Chooser):\r\n    \"\"\" Launches a color picker which returns a hex color code\"\"\"\r\n    def __init__(self, *args, **kwargs):\r\n        defaults = {'label': _('choose_colour'),\r\n                    'style': wx.TE_RICH}\r\n        super(ColourChooser, self).__init__(*args, **merge(kwargs, defaults))\r\n\r\n    def setValue(self, value):\r\n        colour = wx.Colour(value)\r\n        self.widget.widget.SetForegroundColour(colour)\r\n        self.widget.widget.SetBackgroundColour(colour)\r\n        self.widget.setValue(value)\r\n\r\n    def getResult(self, dialog):\r\n        colour = dialog.GetColourData().GetColour()\r\n\r\n        # Set text box back/foreground to selected colour\r\n        self.widget.widget.SetForegroundColour(colour)\r\n        self.widget.widget.SetBackgroundColour(colour)\r\n\r\n        return colour.GetAsString(wx.C2S_HTML_SYNTAX)\r\n\r\n    def getDialog(self):\r\n        return wx.ColourDialog(self)\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/core/text_input.py",
    "content": "import wx  # type: ignore\r\n\r\nfrom gooey.gui.util.filedrop import FileDrop\r\nfrom gooey.util.functional import merge\r\nfrom gooey.gui.components.mouse import notifyMouseEvent\r\n\r\n\r\nclass TextInput(wx.Panel):\r\n    def __init__(self, parent, *args, **kwargs):\r\n        super(TextInput, self).__init__(parent)\r\n        self.widget = wx.TextCtrl(self, *args, **kwargs)\r\n        dt = FileDrop(self.widget)\r\n        self.widget.SetDropTarget(dt)\r\n        self.widget.SetMinSize((0, -1))\r\n        self.widget.SetDoubleBuffered(True)\r\n        self.widget.AppendText('')\r\n        self.layout()\r\n\r\n        self.widget.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\r\n\r\n    def layout(self):\r\n        sizer = wx.BoxSizer(wx.VERTICAL)\r\n        sizer.Add(self.widget, 0, wx.EXPAND)\r\n        self.SetSizer(sizer)\r\n\r\n\r\n    def setValue(self, value):\r\n        self.widget.Clear()\r\n        self.widget.AppendText(str(value))\r\n        self.widget.SetInsertionPoint(0)\r\n\r\n    def getValue(self):\r\n        return self.widget.GetValue()\r\n\r\n    def SetHint(self, value):\r\n        self.widget.SetHint(value)\r\n\r\n    def SetDropTarget(self, target):\r\n        self.widget.SetDropTarget(target)\r\n\r\n\r\n\r\ndef PasswordInput(_, parent, *args, **kwargs):\r\n    style = {'style': wx.TE_PASSWORD}\r\n    return TextInput(parent, *args, **merge(kwargs, style))\r\n\r\n\r\ndef MultilineTextInput(_, parent, *args, **kwargs):\r\n    style = {'style': wx.TE_MULTILINE}\r\n    return TextInput(parent, *args, **merge(kwargs, style))\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/counter.py",
    "content": "import wx  # type: ignore\r\n\r\nfrom gooey.gui.components.widgets.dropdown import Dropdown\r\nfrom gooey.python_bindings import types as t\r\nfrom gooey.gui import formatters\r\n\r\n\r\nclass Counter(Dropdown):\r\n\r\n    def setValue(self, value):\r\n        index = self._meta['choices'].index(value) + 1\r\n        self.widget.SetSelection(index)\r\n\r\n    def getUiState(self) -> t.FormField:\r\n        widget: wx.ComboBox = self.widget\r\n        return t.Counter(\r\n            id=self._id,\r\n            type=self.widgetInfo['type'],\r\n            selected=self.getWidgetValue(),\r\n            choices=widget.GetStrings(),\r\n            error=self.error.GetLabel() or None,\r\n            enabled=self.IsEnabled(),\r\n            visible=self.IsShown()\r\n        )\r\n\r\n\r\n\r\n    def formatOutput(self, metadata, value):\r\n        return formatters.counter(metadata, value)\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/dialogs/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/gui/components/widgets/dialogs/base_dialog.py",
    "content": "from gooey.gui.lang.i18n import _\r\n\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui.three_to_four import Constants\r\n\r\n\r\nclass BaseDialog(wx.Dialog):\r\n  \"\"\"\r\n    Common base for CalendarDlg and TimeDlg.\r\n  \"\"\"\r\n  def __init__(self, parent, pickerClass, pickerGetter, localizedPickerLabel):\r\n    wx.Dialog.__init__(self, parent, title=localizedPickerLabel)\r\n\r\n    self.SetBackgroundColour('#ffffff')\r\n\r\n    self.ok_button = wx.Button(self, wx.ID_OK, label=_('ok'))\r\n    self.picker = pickerClass(self, style=Constants.WX_DP_DROPDOWN)\r\n    self.pickerGetter = pickerGetter\r\n\r\n    vertical_container = wx.BoxSizer(wx.VERTICAL)\r\n    vertical_container.AddSpacer(10)\r\n    vertical_container.Add(self.picker, 0, wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER, 15)\r\n\r\n    vertical_container.AddSpacer(10)\r\n    button_sizer = wx.BoxSizer(wx.HORIZONTAL)\r\n    button_sizer.AddStretchSpacer(1)\r\n    button_sizer.Add(self.ok_button, 0)\r\n\r\n    vertical_container.Add(button_sizer, 0, wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER, 15)\r\n    vertical_container.AddSpacer(20)\r\n    self.SetSizerAndFit(vertical_container)\r\n\r\n    self.Bind(wx.EVT_BUTTON, self.onOkButton, self.ok_button)\r\n\r\n  def onOkButton(self, event):\r\n    self.EndModal(wx.ID_OK)\r\n    event.Skip()\r\n\r\n  def onCancelButton(self, event):\r\n    try:\r\n      return None\r\n    except:\r\n      self.Close()\r\n\r\n  def GetPath(self):\r\n    \"\"\"\r\n      Return the value chosen in the picker.\r\n      The method is called GetPath() instead of getPath() to emulate the WX Pickers API.\r\n      This allows the Chooser class to work same way with native WX dialogs or children of BaseDialog.\r\n    \"\"\"\r\n\r\n    return self.pickerGetter(self.picker)\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/dialogs/calender_dialog.py",
    "content": "\r\n\r\nfrom .base_dialog import BaseDialog\r\nfrom gooey.gui.three_to_four import Classes\r\nfrom gooey.gui.lang.i18n import _\r\n\r\n\r\nclass CalendarDlg(BaseDialog):\r\n    def __init__(self, parent):\r\n        super(CalendarDlg, self).__init__(parent, \r\n            pickerClass=Classes.DatePickerCtrl,\r\n            pickerGetter=lambda datepicker: datepicker.GetValue().FormatISODate(),\r\n            localizedPickerLabel=_('select_date'))"
  },
  {
    "path": "gooey/gui/components/widgets/dialogs/time_dialog.py",
    "content": "\nfrom .base_dialog import BaseDialog\nfrom gooey.gui.three_to_four import Classes\nfrom gooey.gui.lang.i18n import _\n\n\nclass TimeDlg(BaseDialog):\n\tdef __init__(self, parent):\n\t\tsuper(TimeDlg, self).__init__(parent, \n\t\t\tpickerClass=Classes.TimePickerCtrl,\n\t\t\tpickerGetter=lambda datepicker: datepicker.GetValue().FormatISOTime(),\n\t\t\tlocalizedPickerLabel=_('select_time'))\n"
  },
  {
    "path": "gooey/gui/components/widgets/dropdown.py",
    "content": "from contextlib import contextmanager\r\n\r\nfrom gooey.gui.components.widgets.bases import TextContainer\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui import formatters\r\nfrom gooey.gui.lang.i18n import _\r\nfrom gooey.python_bindings import types as t\r\nfrom gooey.python_bindings.types import FormField\r\n\r\n\r\nclass Dropdown(TextContainer):\r\n    _gooey_options = {\r\n        'placeholder': str,\r\n        'readonly': bool,\r\n        'enable_autocomplete': bool\r\n    }\r\n    def getWidget(self, parent, *args, **options):\r\n        default = _('select_option')\r\n        return wx.ComboBox(\r\n            parent=parent,\r\n            id=-1,\r\n            # str conversion allows using stringyfiable values in addition to pure strings\r\n            value=str(default),\r\n            choices=[str(default)] + [str(choice) for choice in self._meta['choices']],\r\n            style=wx.CB_DROPDOWN)\r\n\r\n    def setOptions(self, options):\r\n        with self.retainSelection():\r\n            self.widget.Clear()\r\n            self.widget.SetItems([_('select_option')] + options)\r\n\r\n    def setValue(self, value):\r\n        ## +1 to offset the default placeholder value\r\n        index = self._meta['choices'].index(value) + 1\r\n        self.widget.SetSelection(index)\r\n\r\n    def getWidgetValue(self):\r\n        value = self.widget.GetValue()\r\n        # filter out the extra default option that's\r\n        # appended during creation\r\n        if value == _('select_option'):\r\n            return None\r\n        return value\r\n\r\n    def formatOutput(self, metadata, value):\r\n        return formatters.dropdown(metadata, value)\r\n\r\n\r\n    def syncUiState(self, state: FormField):\r\n        self.setOptions(state['choices'])  # type: ignore\r\n        if state['selected'] is not None:  # type: ignore\r\n            self.setValue(state['selected'])  # type: ignore\r\n        self.error.SetLabel(state['error'] or '')\r\n        self.error.Show(state['error'] is not None and state['error'] is not '')\r\n\r\n    def getUiState(self) -> t.FormField:\r\n        widget: wx.ComboBox = self.widget\r\n        return t.Dropdown(\r\n            id=self._id,\r\n            type=self.widgetInfo['type'],\r\n            selected=self.getWidgetValue(),\r\n            choices=widget.GetStrings(),\r\n            error=self.error.GetLabel() or None,\r\n            enabled=self.IsEnabled(),\r\n            visible=self.IsShown()\r\n        )\r\n\r\n    @contextmanager\r\n    def retainSelection(self):\r\n        \"\"\"\"\r\n        Retains the selected dropdown option (when possible)\r\n        across mutations due to dynamic updates.\r\n        \"\"\"\r\n        prevSelection = self.widget.GetSelection()\r\n        prevValue = self.widget.GetValue()\r\n        try:\r\n            yield\r\n        finally:\r\n            current_at_index = self.widget.GetString(prevSelection)\r\n            if prevValue == current_at_index:\r\n                self.widget.SetSelection(prevSelection)\r\n            else:\r\n                self.widget.SetSelection(0)\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/dropdown_filterable.py",
    "content": "from contextlib import contextmanager\n\nimport wx  # type: ignore\nimport wx.html  # type: ignore\n\nimport gooey.gui.events as events\nfrom gooey.gui.components.filtering.prefix_filter import PrefixSearch\nfrom gooey.gui.components.mouse import notifyMouseEvent\nfrom gooey.gui.components.widgets.dropdown import Dropdown\nfrom gooey.gui.lang.i18n import _\nfrom gooey.gui.pubsub import pub\nfrom gooey.python_bindings import types as t\n\n__ALL__ = ('FilterableDropdown',)\n\n\nclass FilterableDropdown(Dropdown):\n    \"\"\"\n    TODO: tests for gooey_options\n    TODO: documentation\n    A dropdown with auto-complete / filtering behaviors.\n\n    This is largely a recreation of the `AutoComplete` functionality baked\n    into WX itself.\n\n    Background info:\n    The Dropdown's listbox and its Autocomplete dialog are different components.\n    This means that if the former is open, the latter cannot be used. Additionally,\n    this leads to annoying UX quirks like the boxes having different styles and sizes.\n    If we ignore the UX issues, a possible solution for still leveraging the current built-in\n    AutoComplete functionality would have been to capture EVT_TEXT and conditionally\n    close the dropdown while spawning the AutoComplete dialog, but due to\n    (a) non-overridable behaviors and (b) lack a fine grained events, this cannot be\n    done in a seamless manner.\n\n    FAQ:\n    Q: Why does it slide down rather than hover over elements like the native ComboBox?\n    A: The only mechanism for layering in WX is the wx.PopupTransientWindow. There's a long\n       standing issue in wxPython which prevents Listbox/Ctrl from capturing events when\n       inside of a PopupTransientWindow (see: https://tinyurl.com/y28ngh7v)\n\n    Q: Why is visibility handled by changing its size rather than using Show/Hide?\n    A: WX's Layout engine is strangely quirky when it comes to toggling visibility.\n       Repeated calls to Layout() after the first show/hide cycle no longer produce\n       the same results. I have no idea why. I keep checking it thinking I'm crazy, but\n       alas... seems to be the case.\n    \"\"\"\n    gooey_options = {\n        'placeholder': str,\n        'empty_message': str,\n        'max_size': str\n    }\n    def __init__(self, *args, **kwargs):\n        # these are declared here and created inside\n        # of getWidget() because the structure of all\n        # this inheritance garbage is broken.\n        self.listbox = None\n        self.model = None\n        super(FilterableDropdown, self).__init__(*args, **kwargs)\n        self.SetDoubleBuffered(True)\n\n    def interpretState(self, model):\n        \"\"\"\n        Updates the UI to reflect the current state of the model.\n        \"\"\"\n        if self.widget.GetValue() != self.model.displayValue:\n            self.widget.ChangeValue(model.displayValue)\n\n        self.listbox.Clear()\n        self.listbox.SetItemCount(len(self.model.suggestions))\n        if len(self.model.suggestions) == 1:\n            # I have no clue why this is required, but without\n            # manually flicking the virtualized listbox off/on\n            # it won't paint the update when there's only a single\n            # item being displayed\n            self.listbox.Show(False)\n            self.listbox.Show(self.model.suggestionsVisible)\n        if model.selectedSuggestion > -1:\n            self.listbox.SetSelection(model.selectedSuggestion)\n            self.widget.SetInsertionPoint(-1)\n            self.widget.SetSelection(999, -1)\n        else:\n            self.listbox.SetSelection(-1)\n        self.estimateBestSize()\n        self.listbox.Show(self.model.suggestionsVisible)\n        self.Layout()\n        self.GetParent().Layout()\n\n    def onComponentInitialized(self):\n        self.widget.GetTextCtrl().Bind(wx.EVT_TEXT, self.onTextInput)\n        self.widget.GetTextCtrl().Bind(wx.EVT_CHAR_HOOK, self.onKeyboardControls)\n        self.widget.Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\n        self.widget.GetTextCtrl().Bind(wx.EVT_LEFT_DOWN, notifyMouseEvent)\n        self.listbox.Bind(wx.EVT_LISTBOX, self.onClickSuggestion)\n        pub.subscribe(events.LEFT_DOWN, self.onMouseClick)\n        self.widget.SetHint(self._options.get('placeholder', ''))\n\n    def getWidget(self, parent, *args, **options):\n        # self.widget = wx.ComboCtrl(parent)\n        self.comboCtrl = wx.ComboCtrl(parent)\n        self.comboCtrl.OnButtonClick = self.onButton\n        self.foo = ListCtrlComboPopup()\n        self.comboCtrl.SetPopupControl(self.foo)\n        self.listbox = VirtualizedListBox(self)\n        self.listbox.OnGetItem = self.OnGetItem\n        # model is created here because the design of these widget\n        # classes is broken.\n        self.model = FilterableDropdownModel(self._meta['choices'], self._options, listeners=[self.interpretState])\n        # overriding this to false removes it from tab behavior.\n        # and keeps the tabbing at the top-level widget level\n        self.listbox.AcceptsFocusFromKeyboard = lambda *args, **kwargs: False\n        return self.comboCtrl\n\n    def getUiState(self) -> t.FormField:\n        widget: wx.ComboBox = self.widget\n        return t.DropdownFilterable(\n            id=self._id,\n            type=self.widgetInfo['type'],\n            value=self.model.actualValue,\n            choices=self.model.choices,\n            error=self.error.GetLabel() or None,\n            enabled=self.IsEnabled(),\n            visible=self.IsShown()\n        )\n\n    def syncUiState(self, state: t.DropdownFilterable):  # type: ignore\n        self.setOptions(state['choices'])\n        if state['value'] is not None:\n            self.setValue(state['value'])\n        self.error.SetLabel(state['error'] or '')\n        self.error.Show(state['error'] is not None and state['error'] is not '')\n\n    def OnGetItem(self, n):\n        return self.model.suggestions[n]\n\n    def getSublayout(self, *args, **kwargs):\n        verticalSizer = wx.BoxSizer(wx.VERTICAL)\n        layout = wx.BoxSizer(wx.HORIZONTAL)\n        layout.Add(self.widget, 1, wx.EXPAND)\n        verticalSizer.Add(layout, 0, wx.EXPAND)\n        verticalSizer.Add(self.listbox, 0, wx.EXPAND)\n        self.listbox.SetMaxSize(self.model.maxSize)\n        self.listbox.Hide()\n        self.Layout()\n        return verticalSizer\n\n    def setOptions(self, options):\n        self.model.updateChoices(options)\n        if not self.model.actualValue in options:\n            self.model.updateActualValue('')\n\n    def setValue(self, value):\n        self.model.updateActualValue(value)\n\n    def onButton(self):\n        if self.model.suggestionsVisible:\n            self.model.hideSuggestions()\n        else:\n            self.model.showSuggestions()\n\n    def onClickSuggestion(self, event):\n        self.model.acceptSuggestion(self.model.suggestions[event.Selection])\n        event.Skip()\n\n    def onMouseClick(self, wxEvent):\n        \"\"\"\n        Closes the suggestions when the user clicks anywhere\n        outside of the current widget.\n        \"\"\"\n        if wxEvent.EventObject not in (self.widget, self.widget.GetTextCtrl()):\n            self.model.hideSuggestions()\n            wxEvent.Skip()\n        else:\n            wxEvent.Skip()\n\n    def onTextInput(self, event):\n        \"\"\"Processes the user's input and show relevant suggestions\"\"\"\n        self.model.handleTextInput(event.GetString())\n\n    def onKeyboardControls(self, event):\n        \"\"\"\n        Handles any keyboard events relevant to the\n        control/navigation of the suggestion box.\n        All other events are passed through via `Skip()`\n        and bubble up to `onTextInput` to be handled.\n        \"\"\"\n        if event.GetKeyCode() == wx.WXK_ESCAPE:\n            self.model.ignoreSuggestions()\n        elif event.GetKeyCode() in (wx.WXK_TAB, wx.WXK_RETURN):\n            self.model.acceptSuggestion(self.model.displayValue)\n            event.Skip()\n        elif event.GetKeyCode() in (wx.WXK_DOWN, wx.WXK_UP):\n            if not self.model.suggestionsVisible:\n                self.model.generateSuggestions(self.model.displayValue)\n                self.model.showSuggestions()\n            else:\n                if self.listbox.OnGetItem(0) != self.model.noMatch:\n                    self.ignore = True\n                    if event.GetKeyCode() == wx.WXK_DOWN:\n                        self.model.incSelectedSuggestion()\n                    else:\n                        self.model.decSelectedSuggestion()\n        else:\n            # for some reason deleting text doesn't\n            # trigger the usual evt_text event, even though\n            # it IS a modification of the text... so handled here.\n            if event.GetKeyCode() == wx.WXK_DELETE:\n                self.model.handleTextInput('')\n            event.Skip()\n\n    def estimateBestSize(self):\n        \"\"\"\n        Restricts the size of the dropdown based on the number\n        of items within it. This is a rough estimate based on the\n        current font size.\n        \"\"\"\n        padding = 11\n        rowHeight = self.listbox.GetFont().GetPixelSize()[1] + padding\n        maxHeight = self.model.maxSize[1]\n        self.listbox.SetMaxSize((-1, min(maxHeight, len(self.model.suggestions) * rowHeight)))\n        self.listbox.SetMinSize((-1, min(maxHeight, len(self.model.suggestions) * rowHeight)))\n        self.listbox.SetSize((-1, -1))\n\n\n\nclass VirtualizedListBox(wx.html.HtmlListBox):\n    def __init__(self, *args, **kwargs):\n        super(VirtualizedListBox, self).__init__(*args, **kwargs)\n        self.SetItemCount(1)\n\n    def OnGetItem(self, n):\n        return ''\n\n\n\n\nclass FilterableDropdownModel(object):\n    \"\"\"\n    The model/state for the FilterableDropdown. While this is still one\n    big ball of mutation (hard to get away from in WX), it serves the purpose\n    of keeping data transforms independent of presentation concerns.\n    \"\"\"\n    gooey_options = {\n        'placeholder': str,\n        'empty_message': str,\n        'max_size': str\n    }\n    def __init__(self, choices, options, listeners=[], *args, **kwargs):\n        self.listeners = listeners\n        self.actualValue = ''\n        self.displayValue = ''\n        self.dropEvent = False\n        self.suggestionsVisible = False\n        self.noMatch = options.get('no_matches', _('dropdown.no_matches'))\n        self.choices = choices\n        self.suggestions = choices\n        self.selectedSuggestion = -1\n        self.suggestionsVisible = False\n        self.maxSize = (-1, options.get('max_size', 80))\n        self.strat = PrefixSearch(choices, options.get('search_strategy', {}))\n\n    def __str__(self):\n        return str(vars(self))\n\n    @contextmanager\n    def notify(self):\n        try:\n            yield\n        finally:\n            for listener in self.listeners:\n                listener(self)\n\n    def updateChoices(self, choices):\n        \"\"\"Update the available choices in response\n        to a dynamic update\"\"\"\n        self.choices = choices\n        self.strat.updateChoices(choices)\n\n    def handleTextInput(self, value):\n        if self.dropEvent:\n            self.dropEvent = False\n        else:\n            with self.notify():\n                self.actualValue = value\n                self.displayValue = value\n                self.selectedSuggestion = -1\n                self.generateSuggestions(value)\n                self.suggestionsVisible = True\n\n    def updateActualValue(self, value):\n        with self.notify():\n            self.actualValue = value\n            self.displayValue = value\n\n    def acceptSuggestion(self, suggestion):\n        \"\"\"Accept the currently selected option as the user's input\"\"\"\n        with self.notify():\n            self.actualValue = suggestion\n            self.displayValue = suggestion\n            self.suggestionsVisible = False\n            self.selectedSuggestion = -1\n\n    def ignoreSuggestions(self):\n        \"\"\"\n        Ignore the suggested values and replace the\n        user's original input.\n        \"\"\"\n        with self.notify():\n            self.displayValue = self.actualValue\n            self.suggestionsVisible = False\n            self.selectedSuggestion = -1\n\n    def generateSuggestions(self, prompt):\n        suggestions = self.strat.findMatches(prompt)\n        final_suggestions = suggestions if suggestions else [self.noMatch]\n        self.suggestions = final_suggestions\n\n    def incSelectedSuggestion(self):\n        with self.notify():\n            nextIndex = (self.selectedSuggestion + 1) % len(self.suggestions)\n            suggestion = self.suggestions[nextIndex]\n            self.selectedSuggestion = nextIndex\n            self.displayValue = suggestion\n            self.dropEvent = True\n\n    def decSelectedSuggestion(self):\n        with self.notify():\n            currentIndex = max(-1, self.selectedSuggestion - 1)\n            nextIndex = currentIndex % len(self.suggestions)\n            nextDisplay = self.suggestions[nextIndex]\n            self.displayValue = nextDisplay\n            self.selectedSuggestion = nextIndex\n            self.dropEvent = True\n\n    def hideSuggestions(self):\n        with self.notify():\n            self.suggestionsVisible = False\n\n    def showSuggestions(self):\n        with self.notify():\n            self.generateSuggestions(self.displayValue)\n            self.suggestionsVisible = True\n\n    def isShowingSuggestions(self):\n        \"\"\"\n        Check if we're currently showing the suggestion dropdown\n        by checking if we've made it's height non-zero.\n        \"\"\"\n        return self.suggestionsVisible\n\n\nclass ListCtrlComboPopup(wx.ComboPopup):\n    \"\"\"\n    This is an empty placeholder to satisfy the interface of\n    the ComboCtrl which uses it. All Popup behavior is handled\n    inside of `FilterableDropdown`. See its docs for additional\n    details.\n    \"\"\"\n    def __init__(self):\n        wx.ComboPopup.__init__(self)\n        self.lc = None\n\n    def Create(self, parent):\n        # this ComboCtrl requires a real Wx widget be created\n        # thus creating a blank static text object\n        self.lc = wx.StaticText(parent)\n        return True\n\n    def GetControl(self):\n        return self.lc\n\n"
  },
  {
    "path": "gooey/gui/components/widgets/listbox.py",
    "content": "import wx  # type: ignore\r\n\r\nfrom gooey.gui import formatters\r\nfrom gooey.gui.components.widgets.bases import TextContainer\r\nfrom gooey.python_bindings import types as t\r\n\r\n\r\nclass Listbox(TextContainer):\r\n\r\n    def getWidget(self, parent, *args, **options):\r\n        height = self._options.get('height', 60)\r\n        return wx.ListBox(\r\n            parent=parent,\r\n            choices=self._meta['choices'],\r\n            size=(-1, height),\r\n            style=wx.LB_MULTIPLE\r\n        )\r\n\r\n    def setOptions(self, options):\r\n        self.widget.Clear()\r\n        for option in options:\r\n            self.widget.Append(option)\r\n\r\n    def setValue(self, values):\r\n        for string in values:\r\n            self.widget.SetStringSelection(string)\r\n\r\n    def getWidgetValue(self):\r\n        return [self.widget.GetString(index)\r\n                for index in self.widget.GetSelections()]\r\n\r\n    def formatOutput(self, metadata, value):\r\n        return formatters.listbox(metadata, value)\r\n\r\n    def getUiState(self) -> t.FormField:\r\n        widget: wx.ComboBox = self.widget\r\n        return t.Listbox(\r\n            id=self._id,\r\n            type=self.widgetInfo['type'],\r\n            selected=self.getWidgetValue(),\r\n            choices=self._meta['choices'],\r\n            error=self.error.GetLabel() or None,\r\n            enabled=self.IsEnabled(),\r\n            visible=self.IsShown()\r\n        )\r\n\r\n    def syncUiState(self, state: t.Listbox):  # type: ignore\r\n        widget: wx.ComboBox = self.widget\r\n        widget.Clear()\r\n        widget.AppendItems(state.get('choices', []))\r\n        for string in state['selected']:\r\n            widget.SetStringSelection(string)\r\n        self.error.SetLabel(state['error'] or '')\r\n        self.error.Show(state['error'] is not None and state['error'] is not '')\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/numeric_fields.py",
    "content": "import wx  # type: ignore\n\nfrom gooey.gui import formatters\nfrom gooey.gui.components.widgets.bases import TextContainer\nfrom gooey.python_bindings import types as t\n\nclass IntegerField(TextContainer):\n    \"\"\"\n    An integer input field\n    \"\"\"\n    widget_class = wx.SpinCtrl\n    def getWidget(self, *args, **options):\n        widget = self.widget_class(self,\n                             value='',\n                             min=self._options.get('min', 0),\n                             max=self._options.get('max', 100))\n        return widget\n\n    def getWidgetValue(self):\n        return self.widget.GetValue()\n\n    def setValue(self, value):\n        self.widget.SetValue(int(value))\n\n    def formatOutput(self, metatdata, value):\n        # casting to string so that the generic formatter\n        # doesn't treat 0 as false/None\n        return formatters.general(metatdata, str(value))\n\n    def getUiState(self) -> t.FormField:\n        widget: wx.SpinCtrl = self.widget\n        return t.IntegerField(\n            id=self._id,\n            type=self.widgetInfo['type'],\n            value=self.getWidgetValue(),\n            min=widget.GetMin(),\n            max=widget.GetMax(),\n            error=self.error.GetLabel() or None,\n            enabled=self.IsEnabled(),\n            visible=self.IsShown()\n        )\n\nclass DecimalField(IntegerField):\n    \"\"\"\n    A decimal input field\n    \"\"\"\n    widget_class = wx.SpinCtrlDouble\n\n    def getWidget(self, *args, **options):\n        widget = self.widget_class(self,\n                             value='',\n                             min=self._options.get('min', 0),\n                             max=self._options.get('max', 100),\n                             inc=self._options.get('increment', 0.01))\n        widget.SetDigits(self._options.get('precision', widget.GetDigits()))\n        return widget\n\n\n    def setValue(self, value):\n        self.widget.SetValue(value)\n\n    def getUiState(self) -> t.FormField:\n        widget: wx.SpinCtrlDouble = self.widget\n        return t.IntegerField(\n            id=self._id,\n            type=self.widgetInfo['type'],\n            value=self.getWidgetValue(),\n            min=widget.GetMin(),\n            max=widget.GetMax(),\n            error=self.error.GetLabel() or None,\n            enabled=self.IsEnabled(),\n            visible=self.IsShown()\n        )\n\n\n"
  },
  {
    "path": "gooey/gui/components/widgets/password.py",
    "content": "from gooey.gui.components.widgets.core.text_input import PasswordInput\r\nfrom gooey.gui.components.widgets.textfield import TextField\r\nfrom gooey.python_bindings import types as t\r\n\r\n__ALL__ = ('PasswordField',)\r\n\r\nclass PasswordField(TextField):\r\n    widget_class = PasswordInput  # type: ignore\r\n\r\n    def __init__(self, *args, **kwargs):\r\n        super(PasswordField, self).__init__(*args, **kwargs)\r\n\r\n    def getUiState(self) -> t.FormField:  # type: ignore\r\n        return t.PasswordField(**super().getUiState())  # type: ignore\r\n\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/radio_group.py",
    "content": "from typing import Optional\r\n\r\nimport wx  # type: ignore\r\nfrom gooey.gui.components.widgets.bases import BaseWidget\r\nfrom gooey.gui.lang.i18n import _\r\nfrom gooey.gui.util import wx_util\r\nfrom gooey.gui.components.widgets import CheckBox\r\nfrom gooey.util.functional import getin, merge\r\nfrom gooey.python_bindings import types as t\r\n\r\n\r\nclass RadioGroup(BaseWidget):\r\n    \"\"\"\r\n    \"\"\"\r\n\r\n    def __init__(self, parent, widgetInfo, *args, **kwargs):\r\n        super(RadioGroup, self).__init__(parent, *args, **kwargs)\r\n        self._parent = parent\r\n        self.info = widgetInfo\r\n        self._id = widgetInfo['id']\r\n        self._options = widgetInfo['options']\r\n        self.widgetInfo = widgetInfo\r\n        self.error = wx.StaticText(self, label='')\r\n        self.radioButtons = self.createRadioButtons()\r\n        self.selected = None\r\n        self.widgets = self.createWidgets()\r\n        self.arrange()\r\n\r\n\r\n        for button in self.radioButtons:\r\n            button.Bind(wx.EVT_LEFT_DOWN, self.handleButtonClick)\r\n\r\n        initialSelection = getin(self.info, ['options', 'initial_selection'], None)\r\n        if initialSelection is not None:\r\n            self.selected = self.radioButtons[initialSelection]\r\n            self.selected.SetValue(True)\r\n        self.handleImplicitCheck()\r\n\r\n        self.applyStyleRules()\r\n\r\n\r\n    def getValue(self):\r\n        for button, widget in zip(self.radioButtons, self.widgets):\r\n            if button.GetValue():  # is Checked\r\n                return merge(widget.getValue(), {'id': self._id})\r\n        else:\r\n            # just return the first widget's value even though it's\r\n            # not active so that the expected interface is satisfied\r\n            return self.widgets[0].getValue()\r\n\r\n\r\n    def syncUiState(self, state: t.RadioGroup):\r\n        if state['selected'] is not None:\r\n            self.radioButtons[state['selected']].SetValue(True)\r\n        for option, widget in zip(state['options'], self.widgets):\r\n            widget.syncUiState(option)\r\n        # Fit required here to force WX to actually\r\n        # show newly Enabled/Shown things for some reason.\r\n        self.Fit()\r\n\r\n    def getUiState(self):\r\n        return t.RadioGroup(\r\n            id=self._id,\r\n            type=self.widgetInfo['type'],\r\n            error=self.error.GetLabel(),\r\n            enabled=self.Enabled,\r\n            visible=self.Shown,\r\n            selected=self.getSelectedIndex(),\r\n            options=[x.getUiState() for x in self.widgets]\r\n        )\r\n\r\n    def getSelectedIndex(self) -> Optional[int]:\r\n        for index, btn in enumerate(self.radioButtons):\r\n            if btn.GetValue():\r\n                return index\r\n        return None\r\n\r\n    def setErrorString(self, message):\r\n        for button, widget in zip(self.radioButtons, self.widgets):\r\n            if button.GetValue():  # is Checked\r\n                widget.setErrorString(message)\r\n        self.Layout()\r\n\r\n    def showErrorString(self, b):\r\n        for button, widget in zip(self.radioButtons, self.widgets):\r\n            if button.GetValue():  # is Checked\r\n                widget.showErrorString(b)\r\n\r\n\r\n    def arrange(self, *args, **kwargs):\r\n        title = getin(self.widgetInfo, ['options', 'title'], _('choose_one'))\r\n        if getin(self.widgetInfo, ['options', 'show_border'], False):\r\n            boxDetails = wx.StaticBox(self, -1, title)\r\n            boxSizer = wx.StaticBoxSizer(boxDetails, wx.VERTICAL)\r\n        else:\r\n            title = wx_util.h1(self, title)\r\n            title.SetForegroundColour(self._options['label_color'])\r\n            boxSizer = wx.BoxSizer(wx.VERTICAL)\r\n            boxSizer.AddSpacer(10)\r\n            boxSizer.Add(title, 0)\r\n\r\n        for btn, widget in zip(self.radioButtons, self.widgets):\r\n            sizer = wx.BoxSizer(wx.HORIZONTAL)\r\n            sizer.Add(btn,0, wx.RIGHT, 4)\r\n            sizer.Add(widget, 1, wx.EXPAND)\r\n            boxSizer.Add(sizer, 0, wx.ALL | wx.EXPAND, 5)\r\n        self.SetSizer(boxSizer)\r\n\r\n\r\n    def handleButtonClick(self, event):\r\n        currentSelection = self.selected\r\n        nextSelection = event.EventObject\r\n\r\n        if not self.isSameRadioButton(currentSelection, nextSelection):\r\n            self.selected = nextSelection\r\n            self.selected.SetValue(True)\r\n        else:\r\n            # user clicked on an already enabled radio button.\r\n            # if it is not in the required section, allow it to be deselected\r\n            if not self.widgetInfo['required']:\r\n                self.selected.SetValue(False)\r\n                self.selected = None\r\n\r\n        self.applyStyleRules()\r\n        self.handleImplicitCheck()\r\n\r\n\r\n    def isSameRadioButton(self, radioButton1, radioButton2):\r\n        return (getattr(radioButton1, 'Id', 'r1-not-found') ==\r\n                getattr(radioButton2, 'Id', 'r2-not-found'))\r\n\r\n\r\n    def applyStyleRules(self):\r\n        \"\"\"\r\n        Conditionally disabled/enables form fields based on the current\r\n        section in the radio group\r\n        \"\"\"\r\n        # for reasons I have been completely unable to figure out\r\n        # or understand, IFF you've interacted with one of the radio Buttons's\r\n        # child components, then the act of disabling that component will\r\n        # reset the state of the radioButtons thus causing it to forget\r\n        # what should be selected. So, that is why we're collected the initial\r\n        # state of all the buttons and resetting each button's state as we go.\r\n        # it's wonky as hell\r\n        states = [x.GetValue() for x in self.radioButtons]\r\n        for widget in self.widgets:\r\n            widget.Enable()\r\n        for button, selected, widget in zip(self.radioButtons, states, self.widgets):\r\n            if isinstance(widget, CheckBox):\r\n                widget.hideInput()\r\n            if not selected: # not checked\r\n                widget.Disable()\r\n            else:\r\n                # More \"I don't understand\" style code\r\n                # Under some conditions, Enable() doesn't cascade\r\n                # as listed in the docs. We have to manually drill\r\n                # into the children to enable everything.\r\n                widget = widget\r\n                while widget:\r\n                    widget.Enable()\r\n                    widget = getattr(widget, 'widget', None)\r\n            button.SetValue(selected)\r\n\r\n    def handleImplicitCheck(self):\r\n        \"\"\"\r\n        Checkboxes are hidden when inside of a RadioGroup as a selection of\r\n        the Radio button is an implicit selection of the Checkbox. As such, we have\r\n        to manually \"check\" any checkbox as needed.\r\n        \"\"\"\r\n        for button, widget in zip(self.radioButtons, self.widgets):\r\n            if isinstance(widget, CheckBox):\r\n                if button.GetValue(): # checked\r\n                    widget.setValue(True)\r\n                else:\r\n                    widget.setValue(False)\r\n\r\n\r\n    def createRadioButtons(self):\r\n        # button groups in wx are statefully determined via a style flag\r\n        # on the first button (what???). All button instances are part of the\r\n        # same group until a new button is created with the style flag RG_GROUP\r\n        # https://wxpython.org/Phoenix/docs/html/wx.RadioButton.html\r\n        # (What???)\r\n        firstButton = wx.RadioButton(self, style=wx.RB_GROUP)\r\n        firstButton.SetValue(False)\r\n        buttons = [firstButton]\r\n\r\n        for _ in getin(self.widgetInfo, ['data','widgets'], [])[1:]:\r\n            buttons.append(wx.RadioButton(self))\r\n        return buttons\r\n\r\n    def createWidgets(self):\r\n        \"\"\"\r\n        Instantiate the Gooey Widgets that are used within the RadioGroup\r\n        \"\"\"\r\n        from gooey.gui.components import widgets\r\n        widgets = [getattr(widgets, item['type'])(self, item)\r\n                   for item in getin(self.widgetInfo, ['data', 'widgets'], [])]\r\n        # widgets should be disabled unless\r\n        # explicitly selected\r\n        for widget in widgets:\r\n            widget.Disable()\r\n        return widgets\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/richtextconsole.py",
    "content": "import wx  # type: ignore\nimport wx.richtext  # type: ignore\nimport colored  # type: ignore\nimport re\nfrom gooey.python_bindings import types as t\n\n\nkColorList = [\"#000000\", \"#800000\", \"#008000\", \"#808000\", \"#000080\", \"#800080\", \"#008080\", \"#c0c0c0\",\n    \"#808080\", \"#ff0000\", \"#00ff00\", \"#ffff00\", \"#0000ff\", \"#ff00ff\", \"#00ffff\", \"#ffffff\", \"#000000\",\n    \"#00005f\", \"#000087\", \"#0000af\", \"#0000d7\", \"#0000ff\", \"#005f00\", \"#005f5f\", \"#005f87\", \"#005faf\",\n    \"#005fd7\", \"#005fff\", \"#008700\", \"#00875f\", \"#008787\", \"#0087af\", \"#0087d7\", \"#0087ff\", \"#00af00\",\n    \"#00af5f\", \"#00af87\", \"#00afaf\", \"#00afd7\", \"#00afff\", \"#00d700\", \"#00d75f\", \"#00d787\", \"#00d7af\",\n    \"#00d7d7\", \"#00d7ff\", \"#00ff00\", \"#00ff5f\", \"#00ff87\", \"#00ffaf\", \"#00ffd7\", \"#00ffff\", \"#5f0000\",\n    \"#5f005f\", \"#5f0087\", \"#5f00af\", \"#5f00d7\", \"#5f00ff\", \"#5f5f00\", \"#5f5f5f\", \"#5f5f87\", \"#5f5faf\",\n    \"#5f5fd7\", \"#5f5fff\", \"#5f8700\", \"#5f875f\", \"#5f8787\", \"#5f87af\", \"#5f87d7\", \"#5f87ff\", \"#5faf00\",\n    \"#5faf5f\", \"#5faf87\", \"#5fafaf\", \"#5fafd7\", \"#5fafff\", \"#5fd700\", \"#5fd75f\", \"#5fd787\", \"#5fd7af\",\n    \"#5fd7d7\", \"#5fd7ff\", \"#5fff00\", \"#5fff5f\", \"#5fff87\", \"#5fffaf\", \"#5fffd7\", \"#5fffff\", \"#870000\",\n    \"#87005f\", \"#870087\", \"#8700af\", \"#8700d7\", \"#8700ff\", \"#875f00\", \"#875f5f\", \"#875f87\", \"#875faf\",\n    \"#875fd7\", \"#875fff\", \"#878700\", \"#87875f\", \"#878787\", \"#8787af\", \"#8787d7\", \"#8787ff\", \"#87af00\",\n    \"#87af5f\", \"#87af87\", \"#87afaf\", \"#87afd7\", \"#87afff\", \"#87d700\", \"#87d75f\", \"#87d787\", \"#87d7af\",\n    \"#87d7d7\", \"#87d7ff\", \"#87ff00\", \"#87ff5f\", \"#87ff87\", \"#87ffaf\", \"#87ffd7\", \"#87ffff\", \"#af0000\",\n    \"#af005f\", \"#af0087\", \"#af00af\", \"#af00d7\", \"#af00ff\", \"#af5f00\", \"#af5f5f\", \"#af5f87\", \"#af5faf\",\n    \"#af5fd7\", \"#af5fff\", \"#af8700\", \"#af875f\", \"#af8787\", \"#af87af\", \"#af87d7\", \"#af87ff\", \"#afaf00\",\n    \"#afaf5f\", \"#afaf87\", \"#afafaf\", \"#afafd7\", \"#afafff\", \"#afd700\", \"#afd75f\", \"#afd787\", \"#afd7af\",\n    \"#afd7d7\", \"#afd7ff\", \"#afff00\", \"#afff5f\", \"#afff87\", \"#afffaf\", \"#afffd7\", \"#afffff\", \"#d70000\",\n    \"#d7005f\", \"#d70087\", \"#d700af\", \"#d700d7\", \"#d700ff\", \"#d75f00\", \"#d75f5f\", \"#d75f87\", \"#d75faf\",\n    \"#d75fd7\", \"#d75fff\", \"#d78700\", \"#d7875f\", \"#d78787\", \"#d787af\", \"#d787d7\", \"#d787ff\", \"#d7af00\",\n    \"#d7af5f\", \"#d7af87\", \"#d7afaf\", \"#d7afd7\", \"#d7afff\", \"#d7d700\", \"#d7d75f\", \"#d7d787\", \"#d7d7af\",\n    \"#d7d7d7\", \"#d7d7ff\", \"#d7ff00\", \"#d7ff5f\", \"#d7ff87\", \"#d7ffaf\", \"#d7ffd7\", \"#d7ffff\", \"#ff0000\",\n    \"#ff005f\", \"#ff0087\", \"#ff00af\", \"#ff00d7\", \"#ff00ff\", \"#ff5f00\", \"#ff5f5f\", \"#ff5f87\", \"#ff5faf\",\n    \"#ff5fd7\", \"#ff5fff\", \"#ff8700\", \"#ff875f\", \"#ff8787\", \"#ff87af\", \"#ff87d7\", \"#ff87ff\", \"#ffaf00\",\n    \"#ffaf5f\", \"#ffaf87\", \"#ffafaf\", \"#ffafd7\", \"#ffafff\", \"#ffd700\", \"#ffd75f\", \"#ffd787\", \"#ffd7af\",\n    \"#ffd7d7\", \"#ffd7ff\", \"#ffff00\", \"#ffff5f\", \"#ffff87\", \"#ffffaf\", \"#ffffd7\", \"#ffffff\", \"#080808\",\n    \"#121212\", \"#1c1c1c\", \"#262626\", \"#303030\", \"#3a3a3a\", \"#444444\", \"#4e4e4e\", \"#585858\", \"#626262\",\n    \"#6c6c6c\", \"#767676\", \"#808080\", \"#8a8a8a\", \"#949494\", \"#9e9e9e\", \"#a8a8a8\", \"#b2b2b2\", \"#bcbcbc\",\n    \"#c6c6c6\", \"#d0d0d0\", \"#dadada\", \"#e4e4e4\", \"#eeeeee\"]\n\nclass RichTextConsole(wx.richtext.RichTextCtrl):\n    \"\"\"\n        An advanced rich test console panel supporting some Xterm control codes.\n    \"\"\"\n\n    def __init__(self, parent):\n        super(wx.richtext.RichTextCtrl, self).__init__(parent, -1, \"\", style=wx.richtext.RE_MULTILINE | wx.richtext.RE_READONLY)\n        self.regex_urls=re.compile(r'\\b((?:file://|https?://|mailto:)[^][\\s<>|]*)')\n        self.url_colour = wx.Colour(0,0,255)\n        self.esc = colored.style.ESC\n        self.end = colored.style.END\n        self.noop = lambda *args, **kwargs: None\n\n        self.actionsMap = {\n            colored.style.BOLD: self.BeginBold,\n            colored.style.RES_BOLD: self.EndBold,\n            colored.style.UNDERLINED: self.BeginUnderline,\n            colored.style.RES_UNDERLINED: self.EndUnderline,\n            colored.style.RESET: self.EndAllStyles,\n        }\n\n        # Actions for coloring text\n        for index, hex in enumerate(kColorList):\n            escSeq = '{}{}{}'.format(colored.fore.ESC, index, colored.fore.END)\n            wxcolor = wx.Colour(int(hex[1:3],16), int(hex[3:5],16), int(hex[5:],16), alpha=wx.ALPHA_OPAQUE)\n            # NB : we use a default parameter to force the evaluation of the binding\n            self.actionsMap[escSeq] = lambda bindedColor=wxcolor: self.BeginTextColour(bindedColor)\n            \n        self.Bind(wx.EVT_MOUSEWHEEL, self.onMouseWheel)\n\n\n    def PreprocessAndWriteText(self, content):\n        \"\"\"Write text into console, while capturing URLs and making \n        them blue, underlined, and clickable.\n        \"\"\"\n        textStream=iter(re.split(self.regex_urls, content))\n        # The odd elements in textStream are plaintext;\n        # the even elements are URLs.\n        for plaintext in textStream:\n            url=next(textStream, None)\n            self.WriteText(plaintext)\n            if url:    \n                self.BeginTextColour(self.url_colour)\n                self.BeginUnderline()\n                self.BeginURL(url)\n                self.WriteText(url)\n                self.EndURL()\n                self.EndUnderline()\n                self.EndTextColour()\n            \n    def AppendText(self, content):\n        \"\"\"\n        wx method overridden to capture the terminal control character and translate them into wx styles.\n        Complexity : o(len(content))\n        \"\"\"\n        self.SetInsertionPointEnd()\n        unprocIndex = 0\n        while True:\n            # Invariant : unprocIndex is the starting index of the unprocessed part of the buffer\n            escPos = content.find(self.esc, unprocIndex)\n            if escPos == -1:\n                break\n            # Invariant : found an escape sequence starting at escPos\n            # NB : we flush all the characters before the escape sequence, if any\n            if content[unprocIndex:escPos]:\n                self.PreprocessAndWriteText(content[unprocIndex:escPos])\n            endEsc = content.find(self.end, escPos)\n            if endEsc == -1:\n                unprocIndex = escPos + len(self.esc)\n                continue\n            # Invariant : end of sequence has been found\n            self.actionsMap.get(content[escPos:endEsc+1], self.noop)()\n            unprocIndex = endEsc + 1\n        # Invariant : unprocessed end of buffer is escape-free, ready to be printed\n        self.PreprocessAndWriteText(content[unprocIndex:])\n        self.ShowPosition(self.GetInsertionPoint())\n\n    def onMouseWheel(self, event):\n        if event.GetModifiers()==2 and event.GetWheelAxis()==wx.MOUSE_WHEEL_VERTICAL:\n            if event.GetWheelRotation() >= event.GetWheelDelta():\n                r=1.1\n            elif event.GetWheelRotation() <= -event.GetWheelDelta():\n                r=1.0/1.1\n            else:\n                return\n            self.SetFontScale(self.GetFontScale() * r, True)\n        else:\n           event.Skip()\n"
  },
  {
    "path": "gooey/gui/components/widgets/slider.py",
    "content": "import wx  # type: ignore\n\nfrom gooey.gui import formatters\nfrom gooey.gui.components.widgets.bases import TextContainer\nfrom gooey.python_bindings import types as t\n\n\nclass Slider(TextContainer):\n    \"\"\"\n    An integer input field\n    \"\"\"\n    widget_class = wx.Slider\n    def getWidget(self, *args, **options):\n        widget = self.widget_class(self,\n                                   minValue=self._options.get('min', 0),\n                                   maxValue=self._options.get('max', 100),\n                                   style=wx.SL_MIN_MAX_LABELS | wx.SL_VALUE_LABEL)\n        return widget\n\n    def getWidgetValue(self):\n        return self.widget.GetValue()\n\n    def setValue(self, value):\n        self.widget.SetValue(value)\n\n    def formatOutput(self, metatdata, value):\n        return formatters.general(metatdata, str(value))\n\n    def getUiState(self) -> t.FormField:\n        widget: wx.Slider = self.widget\n        return t.Slider(\n            id=self._id,\n            type=self.widgetInfo['type'],\n            value=self.getWidgetValue(),\n            min=widget.GetMin(),\n            max=widget.GetMax(),\n            error=self.error.GetLabel() or None,\n            enabled=self.IsEnabled(),\n            visible=self.IsShown()\n        )\n"
  },
  {
    "path": "gooey/gui/components/widgets/textarea.py",
    "content": "import os\r\nimport wx  # type: ignore\r\nfrom functools import reduce\r\n\r\nfrom gooey.gui.components.widgets.core.text_input import MultilineTextInput\r\nfrom gooey.gui.components.widgets.textfield import TextField\r\nfrom gooey.gui.components.widgets.bases import TextContainer\r\nfrom gooey.gui import formatters\r\nfrom gooey.python_bindings import types as t\r\nfrom gooey.python_bindings.types import FormField\r\n\r\n\r\nclass Textarea(TextContainer):\r\n\r\n    def getWidget(self, parent, *args, **options):\r\n        widgetHeight = self._options.get('height', -1)\r\n        return wx.TextCtrl(\r\n            parent=parent,\r\n            size=(-1, widgetHeight),\r\n            style=self.getModifiers()\r\n        )\r\n\r\n    def getModifiers(self):\r\n        readonly = (wx.TE_READONLY\r\n                    if self._options.get('readonly', False)\r\n                    # using TE_MUTLI as a safe OR-able no-op value\r\n                    else wx.TE_MULTILINE)\r\n        return reduce(lambda acc, val: acc | val, [wx.TE_MULTILINE, readonly])\r\n\r\n    def getWidgetValue(self):\r\n        return self.widget.GetValue()\r\n\r\n    def setValue(self, value):\r\n        self.widget.Clear()\r\n        self.widget.AppendText(str(value))\r\n        self.widget.SetInsertionPoint(0)\r\n\r\n    def formatOutput(self, metatdata, value: str):\r\n        return formatters.general(metatdata, value.replace('\\n', os.linesep))\r\n\r\n    def syncUiState(self, state: FormField):\r\n        self.setValue(state['value'])  # type: ignore\r\n        self.error.SetLabel(state['error'] or '')\r\n        self.error.Show(state['error'] is not None and state['error'] is not '')\r\n\r\n    def getUiState(self) -> t.FormField:\r\n        return t.TextField(\r\n            id=self._id,\r\n            type=self.widgetInfo['type'],\r\n            value=self.getWidgetValue(),\r\n            placeholder=self.widget.GetHint(),\r\n            error=self.error.GetLabel().replace('\\n', ' '),\r\n            enabled=self.IsEnabled(),\r\n            visible=self.IsShown()\r\n        )\r\n"
  },
  {
    "path": "gooey/gui/components/widgets/textfield.py",
    "content": "import wx  # type: ignore\r\n\r\nfrom gooey.gui import formatters\r\nfrom gooey.gui.components.widgets.bases import TextContainer\r\nfrom gooey.gui.components.widgets.core.text_input import TextInput\r\nfrom gooey.python_bindings import types as t\r\n\r\nclass TextField(TextContainer):\r\n    widget_class = TextInput\r\n\r\n    def getWidgetValue(self):\r\n        return self.widget.getValue()\r\n\r\n    def setValue(self, value):\r\n        self.widget.setValue(str(value))\r\n\r\n    def setPlaceholder(self, value):\r\n        self.widget.SetHint(value)\r\n\r\n    def formatOutput(self, metatdata, value):\r\n        return formatters.general(metatdata, value)\r\n\r\n    def syncUiState(self, state: t.TextField):  # type: ignore\r\n        textctr: wx.TextCtrl = self.widget.widget\r\n        textctr.SetValue(state['value'])\r\n        textctr.SetHint(state['placeholder'])\r\n        textctr.Enable(state['enabled'])\r\n        self.Show(state['visible'])\r\n        self.error.SetLabel(state['error'] or '')\r\n        self.error.Show(state['error'] is not None and state['error'] is not '')\r\n        self.Layout()\r\n"
  },
  {
    "path": "gooey/gui/constants.py",
    "content": "\nVALUE_PLACEHOLDER = '::gooey/placeholder'\nRADIO_PLACEHOLDER = '::gooey/radio-placeholder'\n\n"
  },
  {
    "path": "gooey/gui/containers/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/gui/containers/application.py",
    "content": "\"\"\"\r\nPrimary orchestration and control point for Gooey.\r\n\"\"\"\r\nimport queue\r\nimport sys\r\nimport threading\r\nfrom contextlib import contextmanager\r\nfrom functools import wraps\r\nfrom json import JSONDecodeError\r\nfrom pprint import pprint\r\nfrom subprocess import CalledProcessError\r\nfrom threading import Thread, get_ident\r\nfrom typing import Mapping, Dict, Type, Iterable\r\n\r\nimport six\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui.state import FullGooeyState\r\nfrom gooey.python_bindings.types import PublicGooeyState\r\nfrom rewx.widgets import set_basic_props\r\n\r\nfrom gooey.gui.components.mouse import notifyMouseEvent\r\nfrom gooey.gui.state import initial_state, present_time, form_page, ProgressEvent, TimingEvent\r\nfrom gooey.gui import state as s\r\nfrom gooey.gui.three_to_four import Constants\r\nfrom rewx.core import Component, Ref, updatewx, patch\r\nfrom typing_extensions import TypedDict\r\n\r\nfrom rewx import wsx, render, create_element, mount, update\r\nfrom rewx import components as c\r\nfrom wx.adv import TaskBarIcon  # type: ignore\r\nimport signal\r\n\r\nfrom gooey import Events\r\nfrom gooey.gui import cli\r\nfrom gooey.gui import events\r\nfrom gooey.gui import seeder\r\nfrom gooey.gui.components import modals\r\nfrom gooey.gui.components.config import ConfigPage, TabbedConfigPage\r\nfrom gooey.gui.components.console import Console\r\nfrom gooey.gui.components.footer import Footer\r\nfrom gooey.gui.components.header import FrameHeader\r\nfrom gooey.gui.components.menubar import MenuBar\r\nfrom gooey.gui.components.sidebar import Sidebar\r\nfrom gooey.gui.components.tabbar import Tabbar\r\nfrom gooey.gui.lang.i18n import _\r\nfrom gooey.gui.processor import ProcessController\r\nfrom gooey.gui.util.time import Timing\r\nfrom gooey.gui.pubsub import pub\r\nfrom gooey.gui.util import wx_util\r\nfrom gooey.gui.util.wx_util import transactUI\r\nfrom gooey.python_bindings import constants\r\nfrom gooey.python_bindings.types import Failure, Success, CommandDetails, Try\r\nfrom gooey.util.functional import merge, associn, assoc\r\nfrom gooey.gui.image_repository import loadImages\r\nfrom gooey.gui import host\r\n\r\n\r\nfrom threading import Lock\r\n\r\nfrom gooey.util.functional import associnMany\r\n\r\nlock = Lock()\r\n\r\nclass GooeyApplication(wx.Frame):\r\n    \"\"\"\r\n    Main window for Gooey.\r\n    \"\"\"\r\n\r\n    def __init__(self, buildSpec, *args, **kwargs):\r\n        super(GooeyApplication, self).__init__(None, *args, **kwargs)\r\n        self._state = {}\r\n        self.buildSpec = buildSpec\r\n\r\n        self.applyConfiguration()\r\n        self.menu = MenuBar(buildSpec)\r\n        self.SetMenuBar(self.menu)\r\n        self.header = FrameHeader(self, buildSpec)\r\n        self.configs = self.buildConfigPanels(self)\r\n        self.navbar = self.buildNavigation()\r\n        self.footer = Footer(self, buildSpec)\r\n        self.console = Console(self, buildSpec)\r\n\r\n        self.props = {\r\n            'background_color': self.buildSpec['header_bg_color'],\r\n            'title': self.buildSpec['program_name'],\r\n            'subtitle': self.buildSpec['program_description'],\r\n            'height': self.buildSpec['header_height'],\r\n            'image_uri': self.buildSpec['images']['configIcon'],\r\n            'image_size': (six.MAXSIZE, self.buildSpec['header_height'] - 10)}\r\n\r\n        state = form_page(initial_state(self.buildSpec))\r\n\r\n        self.fprops = {\r\n            'buttons': state['buttons'],\r\n            'progress': state['progress'],\r\n            'timing': state['timing'],\r\n            'bg_color': self.buildSpec['footer_bg_color']\r\n        }\r\n\r\n        # self.hhh = render(create_element(RHeader, self.props), self)\r\n        # self.fff = render(create_element(RFooter, self.fprops), self)\r\n        # patch(self.hhh, create_element(RHeader, {**self.props, 'image_uri': self.buildSpec['images']['runningIcon']}))\r\n        self.layoutComponent()\r\n        self.timer = Timing(self)\r\n\r\n        self.clientRunner = ProcessController(\r\n            self.buildSpec.get('progress_regex'),\r\n            self.buildSpec.get('progress_expr'),\r\n            self.buildSpec.get('hide_progress_msg'),\r\n            self.buildSpec.get('encoding'),\r\n            self.buildSpec.get('requires_shell'),\r\n            self.buildSpec.get('shutdown_signal', signal.SIGTERM)\r\n        )\r\n\r\n        pub.subscribe(events.WINDOW_START, self.onStart)\r\n        pub.subscribe(events.WINDOW_RESTART, self.onStart)\r\n        pub.subscribe(events.WINDOW_STOP, self.onStopExecution)\r\n        pub.subscribe(events.WINDOW_CLOSE, self.onClose)\r\n        pub.subscribe(events.WINDOW_CANCEL, self.onCancel)\r\n        pub.subscribe(events.WINDOW_EDIT, self.onEdit)\r\n        pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput)\r\n        pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete)\r\n        pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar)\r\n        pub.subscribe(events.TIME_UPDATE, self.footer.updateTimeRemaining)\r\n        # Top level wx close event\r\n        # self.Bind(wx.EVT_CLOSE, self.onClose)\r\n\r\n        # TODO: handle child focus for per-field level validation.\r\n        # self.Bind(wx.EVT_CHILD_FOCUS, self.handleFocus)\r\n\r\n        if self.buildSpec.get('auto_start', False):\r\n            self.onStart()\r\n\r\n\r\n    def applyConfiguration(self):\r\n        self.SetTitle(self.buildSpec['program_name'])\r\n        self.SetBackgroundColour(self.buildSpec.get('body_bg_color'))\r\n\r\n\r\n    def onStart(self, *args, **kwarg):\r\n        \"\"\"\r\n        Verify user input and kick off the client's program if valid\r\n        \"\"\"\r\n        # navigates away from the button because a\r\n        # disabled focused button still looks enabled.\r\n        self.footer.cancel_button.Disable()\r\n        self.footer.start_button.Disable()\r\n        self.footer.start_button.Navigate()\r\n        if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []):\r\n            # TODO: make this wx thread safe so that it can\r\n            # actually run asynchronously\r\n            Thread(target=self.onStartAsync).run()\r\n        else:\r\n            Thread(target=self.onStartAsync).run()\r\n\r\n    def onStartAsync(self, *args, **kwargs):\r\n        with transactUI(self):\r\n            try:\r\n                errors = self.validateForm().getOrThrow()\r\n                if errors:  # TODO\r\n                    config = self.navbar.getActiveConfig()\r\n                    config.setErrors(errors)\r\n                    self.Layout()\r\n                    # TODO: account for tabbed layouts\r\n                    # TODO: scroll the first error into view\r\n                    # TODO: rather than just snapping to the top\r\n                    self.configs[0].Scroll(0, 0)\r\n                else:\r\n                    if self.buildSpec['clear_before_run']:\r\n                        self.console.clear()\r\n                    self.clientRunner.run(self.buildCliString())\r\n                    self.showConsole()\r\n            except CalledProcessError as e:\r\n                self.showError()\r\n                self.console.appendText(str(e))\r\n                self.console.appendText(\r\n                    '\\n\\nThis failure happens when Gooey tries to invoke your '\r\n                    'code for the VALIDATE_FORM event and receives an expected '\r\n                    'error code in response.'\r\n                )\r\n                wx.CallAfter(modals.showFailure)\r\n            except JSONDecodeError as e:\r\n                self.showError()\r\n                self.console.appendText(str(e))\r\n                self.console.appendText(\r\n                    '\\n\\nGooey was unable to parse the response to the VALIDATE_FORM event. '\r\n                    'This can happen if you have additional logs to stdout beyond what Gooey '\r\n                    'expects.'\r\n                )\r\n                wx.CallAfter(modals.showFailure)\r\n            # for some reason, we have to delay the re-enabling of\r\n            # the buttons by a few ms otherwise they pickup pending\r\n            # events created while they were disabled. Trial and error\r\n            # let to this solution.\r\n            wx.CallLater(20, self.footer.start_button.Enable)\r\n            wx.CallLater(20, self.footer.cancel_button.Enable)\r\n\r\n\r\n    def onEdit(self):\r\n        \"\"\"Return the user to the settings screen for further editing\"\"\"\r\n        with transactUI(self):\r\n            for config in self.configs:\r\n                config.resetErrors()\r\n            self.showSettings()\r\n\r\n\r\n    def onComplete(self, *args, **kwargs):\r\n        \"\"\"\r\n        Display the appropriate screen based on the success/fail of the\r\n        host program\r\n        \"\"\"\r\n        with transactUI(self):\r\n            if self.clientRunner.was_success():\r\n                if self.buildSpec.get('return_to_config', False):\r\n                    self.showSettings()\r\n                else:\r\n                    self.showSuccess()\r\n                    if self.buildSpec.get('show_success_modal', True):\r\n                        wx.CallAfter(modals.showSuccess)\r\n            else:\r\n                if self.clientRunner.wasForcefullyStopped:\r\n                    self.showForceStopped()\r\n                else:\r\n                    self.showError()\r\n                    if self.buildSpec.get('show_failure_modal'):\r\n                        wx.CallAfter(modals.showFailure)\r\n\r\n    def onCancel(self):\r\n        \"\"\"Close the program after confirming\r\n\r\n        We treat the behavior of the \"cancel\" button slightly\r\n        differently than the general window close X button only\r\n        because this is 'part of' the form.\r\n        \"\"\"\r\n        if modals.confirmExit():\r\n            self.onClose()\r\n\r\n\r\n    def onStopExecution(self):\r\n        \"\"\"Displays a scary message and then force-quits the executing\r\n        client code if the user accepts\"\"\"\r\n        if self.shouldStopExecution():\r\n            self.clientRunner.stop()\r\n\r\n\r\n    def onClose(self, *args, **kwargs):\r\n        \"\"\"Stop any actively running client program, cleanup the top\r\n        level WxFrame and shutdown the current process\"\"\"\r\n        # issue #592 - we need to run the same onStopExecution machinery\r\n        # when the exit button is clicked to ensure everything is cleaned\r\n        # up correctly.\r\n        if self.clientRunner.running():\r\n            if self.shouldStopExecution():\r\n                self.clientRunner.stop()\r\n                self.destroyGooey()\r\n        else:\r\n            self.destroyGooey()\r\n\r\n    def buildCliString(self) -> str:\r\n        \"\"\"\r\n        Collect all of the required information from the config screen and\r\n        build a CLI string which can be used to invoke the client program\r\n        \"\"\"\r\n        cmd = self.getCommandDetails()\r\n        return cli.cliCmd(\r\n            cmd.target,\r\n            cmd.subcommand,\r\n            cmd.positionals,\r\n            cmd.optionals,\r\n            suppress_gooey_flag=self.buildSpec['suppress_gooey_flag']\r\n        )\r\n\r\n    def validateForm(self) -> Try[Mapping[str, str]]:\r\n        config = self.navbar.getActiveConfig()\r\n        localErrors: Mapping[str, str] = config.getErrors()\r\n        dynamicResult: Try[Mapping[str, str]] = self.fetchDynamicValidations()\r\n\r\n        combineErrors = lambda m: merge(localErrors, m)\r\n        return dynamicResult.map(combineErrors)\r\n\r\n\r\n    def fetchDynamicValidations(self) -> Try[Mapping[str, str]]:\r\n        # only run the dynamic validation if the user has\r\n        # specifically subscribed to that event\r\n        if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []):\r\n            cmd = self.getCommandDetails()\r\n            return seeder.communicate(cli.formValidationCmd(\r\n                cmd.target,\r\n                cmd.subcommand,\r\n                cmd.positionals,\r\n                cmd.optionals\r\n            ), self.buildSpec['encoding'])\r\n        else:\r\n            # shim response if nothing to do.\r\n            return Success({})\r\n\r\n\r\n    def getCommandDetails(self) -> CommandDetails:\r\n        \"\"\"\r\n        Temporary helper for getting the state of the current Config.\r\n\r\n        To be deprecated upon (the desperately needed) refactor.\r\n        \"\"\"\r\n        config = self.navbar.getActiveConfig()\r\n        group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()]\r\n        return CommandDetails(\r\n            self.buildSpec['target'],\r\n            group['command'],\r\n            config.getPositionalValues(),\r\n            config.getOptionalValues(),\r\n        )\r\n\r\n\r\n    def shouldStopExecution(self):\r\n        return not self.buildSpec['show_stop_warning'] or modals.confirmForceStop()\r\n\r\n\r\n    def destroyGooey(self):\r\n        self.Destroy()\r\n        sys.exit()\r\n\r\n    def block(self, **kwargs):\r\n        pass\r\n\r\n\r\n    def layoutComponent(self):\r\n        sizer = wx.BoxSizer(wx.VERTICAL)\r\n        # sizer.Add(self.hhh, 0, wx.EXPAND)\r\n        sizer.Add(self.header, 0, wx.EXPAND)\r\n        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)\r\n\r\n        sizer.Add(self.navbar, 1, wx.EXPAND)\r\n        sizer.Add(self.console, 1, wx.EXPAND)\r\n        sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)\r\n        # sizer.Add(self.fff, 0, wx.EXPAND)\r\n        sizer.Add(self.footer, 0, wx.EXPAND)\r\n        self.SetMinSize((400, 300))\r\n        self.SetSize(self.buildSpec['default_size'])\r\n        self.SetSizer(sizer)\r\n        self.console.Hide()\r\n        self.Layout()\r\n        if self.buildSpec.get('fullscreen', True):\r\n            self.ShowFullScreen(True)\r\n        # Program Icon (Windows)\r\n        icon = wx.Icon(self.buildSpec['images']['programIcon'], wx.BITMAP_TYPE_PNG)\r\n        self.SetIcon(icon)\r\n        if sys.platform != 'win32':\r\n            # OSX needs to have its taskbar icon explicitly set\r\n            # bizarrely, wx requires the TaskBarIcon to be attached to the Frame\r\n            # as instance data (self.). Otherwise, it will not render correctly.\r\n            self.taskbarIcon = TaskBarIcon(iconType=wx.adv.TBI_DOCK)\r\n            self.taskbarIcon.SetIcon(icon)\r\n\r\n\r\n    def buildNavigation(self):\r\n        \"\"\"\r\n        Chooses the appropriate layout navigation component based on user prefs\r\n        \"\"\"\r\n        if self.buildSpec['navigation'] == constants.TABBED:\r\n            navigation = Tabbar(self, self.buildSpec, self.configs)\r\n        else:\r\n            navigation = Sidebar(self, self.buildSpec, self.configs)\r\n            if self.buildSpec['navigation'] == constants.HIDDEN:\r\n                navigation.Hide()\r\n        return navigation\r\n\r\n\r\n    def buildConfigPanels(self, parent):\r\n        page_class = TabbedConfigPage if self.buildSpec['tabbed_groups'] else ConfigPage\r\n\r\n        return [page_class(parent, widgets, self.buildSpec)\r\n                for widgets in self.buildSpec['widgets'].values()]\r\n\r\n\r\n    def showSettings(self):\r\n        self.navbar.Show(True)\r\n        self.console.Show(False)\r\n        self.header.setImage('settings_img')\r\n        self.header.setTitle(_(\"settings_title\"))\r\n        self.header.setSubtitle(self.buildSpec['program_description'])\r\n        self.footer.showButtons('cancel_button', 'start_button')\r\n        self.footer.progress_bar.Show(False)\r\n        self.footer.time_remaining_text.Show(False)\r\n\r\n\r\n    def showConsole(self):\r\n        self.navbar.Show(False)\r\n        self.console.Show(True)\r\n        self.header.setImage('running_img')\r\n        self.header.setTitle(_(\"running_title\"))\r\n        self.header.setSubtitle(_('running_msg'))\r\n        self.footer.showButtons('stop_button')\r\n        if not self.buildSpec.get('disable_progress_bar_animation', False):\r\n            self.footer.progress_bar.Show(True)\r\n        self.footer.time_remaining_text.Show(False)\r\n        if self.buildSpec.get('timing_options')['show_time_remaining']:\r\n            self.timer.start()\r\n            self.footer.time_remaining_text.Show(True)\r\n        if not self.buildSpec['progress_regex']:\r\n            self.footer.progress_bar.Pulse()\r\n\r\n\r\n    def showComplete(self):\r\n        self.navbar.Show(False)\r\n        self.console.Show(True)\r\n        buttons = (['edit_button', 'restart_button', 'close_button']\r\n                   if self.buildSpec.get('show_restart_button', True)\r\n                   else ['edit_button', 'close_button'])\r\n        self.footer.showButtons(*buttons)\r\n        self.footer.progress_bar.Show(False)\r\n        if self.buildSpec.get('timing_options')['show_time_remaining']:\r\n            self.timer.stop()\r\n        self.footer.time_remaining_text.Show(True)\r\n        if self.buildSpec.get('timing_options')['hide_time_remaining_on_complete']:\r\n            self.footer.time_remaining_text.Show(False)\r\n\r\n\r\n    def showSuccess(self):\r\n        self.showComplete()\r\n        self.header.setImage('check_mark')\r\n        self.header.setTitle(_('finished_title'))\r\n        self.header.setSubtitle(_('finished_msg'))\r\n        self.Layout()\r\n\r\n\r\n    def showError(self):\r\n        self.showComplete()\r\n        self.header.setImage('error_symbol')\r\n        self.header.setTitle(_('finished_title'))\r\n        self.header.setSubtitle(_('finished_error'))\r\n\r\n\r\n    def showForceStopped(self):\r\n        self.showComplete()\r\n        if self.buildSpec.get('force_stop_is_error', True):\r\n            self.showError()\r\n        else:\r\n            self.showSuccess()\r\n        self.header.setSubtitle(_('finished_forced_quit'))\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "gooey/gui/events.py",
    "content": "\"\"\"\r\nApp wide event registry\r\n\r\nEverything in the application is communicated via pubsub. These are the events\r\nthat tie everything together.\r\n\"\"\"\r\n\r\nimport wx  # type: ignore\r\n\r\nWINDOW_STOP     = wx.Window.NewControlId()\r\nWINDOW_CANCEL   = wx.Window.NewControlId()\r\nWINDOW_CLOSE    = wx.Window.NewControlId()\r\nWINDOW_START    = wx.Window.NewControlId()\r\nWINDOW_RESTART  = wx.Window.NewControlId()\r\nWINDOW_EDIT     = wx.Window.NewControlId()\r\n\r\nWINDOW_CHANGE   = wx.Window.NewControlId()\r\nPANEL_CHANGE    = wx.Window.NewControlId()\r\nLIST_BOX        = wx.Window.NewControlId()\r\n\r\nCONSOLE_UPDATE  = wx.Window.NewControlId()\r\nEXECUTION_COMPLETE = wx.Window.NewControlId()\r\nPROGRESS_UPDATE = wx.Window.NewControlId()\r\nTIME_UPDATE    = wx.Window.NewControlId()\r\n\r\nUSER_INPUT = wx.Window.NewControlId()\r\n\r\nLEFT_DOWN = wx.Window.NewControlId()\r\n\r\n"
  },
  {
    "path": "gooey/gui/formatters.py",
    "content": "import os\r\n\r\nimport itertools\r\n\r\nfrom gooey.gui.util.quoting import quote\r\nfrom gooey.python_bindings.types import EnrichedItem, FormField\r\nfrom gooey.gui.constants import VALUE_PLACEHOLDER, RADIO_PLACEHOLDER\r\nfrom gooey.util.functional import assoc, associnMany\r\n\r\n\r\ndef value(field: FormField):\r\n    if field['type'] in ['Checkbox', 'BlockCheckbox']:\r\n        return field['checked']   # type: ignore\r\n    elif field['type'] in ['Dropdown', 'Listbox', 'Counter']:\r\n        return field['selected']   # type: ignore\r\n    elif field['type'] == 'RadioGroup':\r\n        if field['selected'] is not None:  # type: ignore\r\n            return value(field['options'][field['selected']])  # type: ignore\r\n        else:\r\n            return None\r\n    else:\r\n        return field['value'] # type: ignore\r\n\r\n\r\ndef add_placeholder(field: FormField, placeholder=VALUE_PLACEHOLDER):\r\n    \"\"\"\r\n    TODO: Docs about placeholders\r\n    \"\"\"\r\n    if field['type'] in ['Checkbox', 'CheckBox', 'BlockCheckbox']:\r\n        # there's no sane placeholder we can make for this one, as\r\n        # it's kind of a nonsensical case: a required optional flag.\r\n        # We set it to True here, which is equally nonsensical, but\r\n        # ultimately will allow the validation to pass. We have no\r\n        # way of passing a placeholder without even MORE monket patching\r\n        # of the user's parser to rewrite the action type\r\n        return assoc(field, 'checked', True)\r\n    elif field['type'] in ['Dropdown', 'Listbox', 'Counter']:\r\n        return assoc(field, 'selected', placeholder)\r\n    elif field['type'] == 'RadioGroup':\r\n        # We arbitrarily attach a placeholder for first RadioGroup option\r\n        # and mark it as the selected one.\r\n        return {\r\n            **field,\r\n            'selected': 0,\r\n            'options': [\r\n                add_placeholder(field['options'][0], placeholder=RADIO_PLACEHOLDER),  # type: ignore\r\n                *field['options'][1:]  # type: ignore\r\n            ]\r\n        }\r\n    else:\r\n        return assoc(field, 'value', placeholder)\r\n\r\n\r\ndef formatArgument(item: EnrichedItem):\r\n    if item['type'] in ['Checkbox', 'CheckBox', 'BlockCheckbox']:\r\n        return checkbox(item['data'], value(item['field']))\r\n    elif item['type'] == 'MultiFileChooser':\r\n        return multiFileChooser(item['data'], value(item['field']))\r\n    elif item['type'] == 'Textarea':\r\n        return textArea(item['data'], value(item['field']))\r\n    elif item['type'] == 'CommandField':\r\n        return textArea(item['data'], value(item['field']))\r\n    elif item['type'] == 'Counter':\r\n        return counter(item['data'], value(item['field']))\r\n    elif item['type'] == 'Dropdown':\r\n        return dropdown(item['data'], value(item['field']))\r\n    elif item['type'] == 'Listbox':\r\n        return listbox(item['data'], value(item['field']))\r\n    elif item['type'] == 'RadioGroup':\r\n        selected = item['field']['selected']  # type: ignore\r\n        if selected is not None:\r\n            formField = item['field']['options'][selected]  # type: ignore\r\n            argparseDefinition = item['data']['widgets'][selected]  # type: ignore\r\n            return formatArgument(assoc(argparseDefinition, 'field', formField))  # type: ignore\r\n        else:\r\n            return None\r\n    else:\r\n        return general(item['data'], value(item['field']))\r\n\r\n\r\ndef placeholder(item: EnrichedItem):\r\n    pass\r\n\r\n\r\ndef checkbox(metadata, value):\r\n    return metadata['commands'][0] if value else None\r\n\r\n\r\ndef multiFileChooser(metadata, value):\r\n    paths = ' '.join(quote(x) for x in value.split(os.pathsep) if x)\r\n    if metadata['commands'] and paths:\r\n        return u'{} {}'.format(metadata['commands'][0], paths)\r\n    return paths or None\r\n\r\n\r\ndef textArea(metadata, value):\r\n    if metadata['commands'] and value:\r\n        return '{} {}'.format(metadata['commands'][0], quote(value.encode('unicode_escape')))\r\n    else:\r\n        return quote(value.encode('unicode_escape')) if value else ''\r\n\r\n\r\ndef commandField(metadata, value):\r\n    if metadata['commands'] and value:\r\n        return u'{} {}'.format(metadata['commands'][0], value)\r\n    else:\r\n        return value or None\r\n\r\n\r\ndef counter(metatdata, value):\r\n    '''\r\n    Returns\r\n      str(option_string * DropDown Value)\r\n      e.g.\r\n      -vvvvv\r\n    '''\r\n    if not str(value).isdigit():\r\n        return None\r\n    command = str(metatdata['commands'][0]).strip()\r\n    return ' '.join(itertools.repeat(command, int(value)))\r\n\r\n\r\ndef dropdown(metadata, value):\r\n    if value == 'Select Option':\r\n        return None\r\n    elif metadata['commands'] and value:\r\n        return u'{} {}'.format(metadata['commands'][0], quote(value))\r\n    else:\r\n        return quote(value) if value else ''\r\n\r\n\r\ndef listbox(meta, value):\r\n    if meta['commands'] and value:\r\n        return u'{} {}'.format(meta['commands'][0], ' '.join(map(quote, value)))\r\n    else:\r\n        return ' '.join(map(quote, value)) if value else ''\r\n\r\n\r\ndef general(metadata, value):\r\n    if metadata.get('commands') and value:\r\n        if not metadata.get('nargs'):\r\n            v = quote(value)\r\n        else:\r\n            v = value\r\n        return u'{0} {1}'.format(metadata['commands'][0], v)\r\n    else:\r\n        if not value:\r\n            return None\r\n        elif not metadata.get('nargs'):\r\n            return quote(value)\r\n        else:\r\n            return value\r\n\r\n"
  },
  {
    "path": "gooey/gui/host.py",
    "content": "from concurrent.futures import ThreadPoolExecutor\nfrom threading import Thread\nfrom typing import Callable, Dict, Any\n\nfrom gooey.gui import seeder\nfrom gooey.gui import state as s\nfrom gooey.gui.state import FullGooeyState\nfrom gooey.python_bindings.types import Try, PublicGooeyState\n\n\ndef communicateFormValidation(state: FullGooeyState, callback: Callable[[Try[Dict[str, str]]], None]) -> None:\n    communicateAsync(s.buildFormValidationCmd(state), state, callback)\n\n\ndef communicateSuccessState(state: FullGooeyState, callback: Callable[[Try[PublicGooeyState]], None]) -> None:\n    communicateAsync(s.buildOnSuccessCmd(state), state, callback)\n\n\ndef communicateErrorState(state: FullGooeyState, callback: Callable[[Try[PublicGooeyState]], None]) -> None:\n    communicateAsync(s.buildOnErrorCmd(state), state, callback)\n\n\ndef fetchFieldValidation():\n    pass\n\n\n\ndef fetchFieldAction():\n    pass\n\ndef fetchFormAction():\n    pass\n\n\ndef communicateAsync(cmd: str, state: FullGooeyState, callback: Callable[[Any], None]):\n    \"\"\"\n    Callable MUST be wrapped in wx.CallAfter if its going to\n    modify the UI.\n    \"\"\"\n    def work():\n        result = seeder.communicate(cmd, state['encoding'])\n        callback(result)\n    thread = Thread(target=work)\n    thread.start()\n\n"
  },
  {
    "path": "gooey/gui/image_repository.py",
    "content": "'''\r\nCollection of the image paths.\r\n\r\nThe module is meant to act as a singleton, hence the globals() abuse.\r\n\r\nImage credit: kidcomic.net\r\n'''\r\nimport os\r\nfrom functools import partial\r\n\r\nfrom gooey.gui.util.freeze import getResourcePath\r\nfrom gooey.util.functional import merge\r\n\r\nfilenames = {\r\n    'programIcon': 'program_icon.png',\r\n    'successIcon': 'success_icon.png',\r\n    'runningIcon': 'running_icon.png',\r\n    'configIcon': 'config_icon.png',\r\n    'errorIcon': 'error_icon.png'\r\n}\r\n\r\n\r\ndef loadImages(targetDir):\r\n    defaultImages = resolvePaths(getResourcePath('images'), filenames)\r\n    return {'images': merge(defaultImages, collectOverrides(targetDir, filenames))}\r\n\r\n\r\ndef getImageDirectory(targetDir):\r\n    return getResourcePath('images') \\\r\n           if targetDir == 'default' \\\r\n           else targetDir\r\n\r\n\r\ndef collectOverrides(targetDir, filenames):\r\n    if targetDir == '::gooey/default':\r\n        return {}\r\n\r\n    pathto = partial(os.path.join, targetDir)\r\n    if not os.path.isdir(targetDir):\r\n        raise IOError('Unable to find the user supplied directory {}'.format(\r\n            targetDir))\r\n\r\n    return {varname: pathto(filename)\r\n            for varname, filename in filenames.items()\r\n            if os.path.exists(pathto(filename))}\r\n\r\n\r\ndef resolvePaths(dirname, filenames):\r\n    return {key:  os.path.join(dirname, filename)\r\n            for key, filename in filenames.items()}\r\n\r\n\r\n"
  },
  {
    "path": "gooey/gui/imageutil.py",
    "content": "'''\r\nUtilities for loading, resizing and converting between PIL and WX image formats\r\n'''\r\n\r\nimport six\r\nfrom PIL import Image  # type: ignore\r\nimport wx  # type: ignore\r\n\r\nfrom gooey.gui.three_to_four import bitmapFromBufferRGBA\r\n\r\n\r\n\r\ndef loadImage(img_path):\r\n    return Image.open(img_path)\r\n\r\n\r\ndef resizeImage(im, targetHeight):\r\n    im.thumbnail((six.MAXSIZE, targetHeight))\r\n    return im\r\n\r\n\r\ndef wrapBitmap(im, parent):\r\n    try:\r\n        rgba = im.convert('RGBA').tobytes()\r\n    except AttributeError:\r\n        rgba = im.convert('RGBA').tostring()\r\n\r\n    bitmapData = bitmapFromBufferRGBA(im, rgba)\r\n    return wx.StaticBitmap(parent, bitmap=bitmapData)\r\n\r\n\r\nif __name__ == '__main__':\r\n    pass\r\n"
  },
  {
    "path": "gooey/gui/lang/__init__.py",
    "content": "__author__ = 'Chris'\r\n"
  },
  {
    "path": "gooey/gui/lang/i18n.py",
    "content": "'''\r\nCreated on Jan 25, 2014\r\n\r\n@author: Chris\r\n\r\nProvides Internationalization for all text within the program.\r\n\r\n'''\r\n\r\nimport io\r\nimport os\r\nimport json\r\n\r\n__all__ = ['load', '_']\r\n\r\n_DICTIONARY = None\r\n\r\ndef load(language_dir, filename, encoding):\r\n  ''' Open and return the supplied json file '''\r\n  global _DICTIONARY\r\n  try:\r\n    json_file = filename + '.json'\r\n    with io.open(os.path.join(language_dir, json_file), 'r', encoding=encoding) as f:\r\n      _DICTIONARY = json.load(f)\r\n  except IOError:\r\n    raise IOError('{0} Language file not found at location {1}. '\r\n                  'Make sure that your translation file is in the '\r\n                  'listed language directory'.format(filename.title(), language_dir))\r\n\r\ndef translate(key):\r\n  return _DICTIONARY.get(key, '(Translate me!) {}'.format(key))\r\n\r\ndef _(key):\r\n  return translate(key)\r\n"
  },
  {
    "path": "gooey/gui/lang/i18n_config.py",
    "content": "\"\"\"\r\nConfiguration settings for the program.\r\nMore or less, this is just used so that the i18n module\r\ncan load the right language file at runtime.\r\n\"\"\"\r\n\r\nLANG = 'english'\r\n"
  },
  {
    "path": "gooey/gui/processor.py",
    "content": "import os\r\nimport re\r\nimport signal\r\nimport subprocess\r\nimport sys\r\nfrom functools import partial\r\nfrom threading import Thread\r\n\r\nimport psutil  # type: ignore\r\n\r\nfrom gooey.gui import events\r\nfrom gooey.gui.pubsub import pub\r\nfrom gooey.gui.util.casting import safe_float\r\nfrom gooey.util.functional import unit, bind\r\nfrom gooey.python_bindings.types import GooeyParams\r\n\r\n\r\ntry:\r\n    import _winapi\r\n    creationflag = subprocess.CREATE_NEW_PROCESS_GROUP\r\nexcept ModuleNotFoundError:\r\n    # default Popen creation flag\r\n    creationflag = 0\r\n\r\n\r\nclass ProcessController(object):\r\n\r\n    @classmethod\r\n    def of(cls, params: GooeyParams):\r\n        return cls(\r\n            params.get('progress_regex'),\r\n            params.get('progress_expr'),\r\n            params.get('hide_progress_msg'),\r\n            params.get('encoding'),\r\n            params.get('requires_shell'),\r\n            params.get('shutdown_signal', signal.SIGTERM)\r\n        )\r\n\r\n    def __init__(self, progress_regex, progress_expr, hide_progress_msg,\r\n                 encoding, shell=True, shutdown_signal=signal.SIGTERM, testmode=False):\r\n        self._process = None\r\n        self.progress_regex = progress_regex\r\n        self.progress_expr = progress_expr\r\n        self.hide_progress_msg = hide_progress_msg\r\n        self.encoding = encoding\r\n        self.wasForcefullyStopped = False\r\n        self.shell_execution = shell\r\n        self.shutdown_signal = shutdown_signal\r\n        self.testMode = testmode\r\n\r\n    def was_success(self):\r\n        self._process.communicate()\r\n        return self._process.returncode == 0\r\n\r\n    def poll(self):\r\n        if not self._process:\r\n            raise Exception('Not started!')\r\n        return self._process.poll()\r\n\r\n    def stop(self):\r\n        \"\"\"\r\n        Sends a signal of the user's choosing (default SIGTERM) to\r\n        the child process.\r\n        \"\"\"\r\n        if self.running():\r\n            self.wasForcefullyStopped = True\r\n            self.send_shutdown_signal()\r\n\r\n    def send_shutdown_signal(self):\r\n        self._send_signal(self.shutdown_signal)\r\n\r\n    def _send_signal(self, sig):\r\n        parent = psutil.Process(self._process.pid)\r\n        for child in parent.children(recursive=True):\r\n            child.send_signal(sig)\r\n        parent.send_signal(sig)\r\n\r\n    def running(self):\r\n        return self._process and self.poll() is None\r\n\r\n    def run(self, command):\r\n        \"\"\"\r\n        Kicks off the user's code in a subprocess.\r\n\r\n        Implementation Note: CREATE_NEW_SUBPROCESS is required to have signals behave sanely\r\n        on windows. See the signal_support module for full background.\r\n        \"\"\"\r\n        self.wasForcefullyStopped = False\r\n        env = os.environ.copy()\r\n        env[\"GOOEY\"] = \"1\"\r\n        env[\"PYTHONIOENCODING\"] = self.encoding\r\n        # TODO: why is this try/catch here..?\r\n        try:\r\n            self._process = subprocess.Popen(\r\n                command.encode(sys.getfilesystemencoding()),\r\n                stdout=subprocess.PIPE, stdin=subprocess.PIPE,\r\n                stderr=subprocess.STDOUT, shell=self.shell_execution, env=env,\r\n                creationflags=creationflag)\r\n        except:\r\n            self._process = subprocess.Popen(\r\n                command,\r\n                stdout=subprocess.PIPE, stdin=subprocess.PIPE,\r\n                stderr = subprocess.STDOUT, shell = self.shell_execution, env=env,\r\n                creationflags=creationflag\r\n            )\r\n\r\n        # the message pump depends on the wx instance being initiated and its\r\n        # mainloop running (to dispatch pubsub messages). This makes testing difficult\r\n        # so we only spin up the thread when we're not testing.\r\n        if not self.testMode:\r\n            t = Thread(target=self._forward_stdout, args=(self._process,))\r\n            t.start()\r\n\r\n    def _forward_stdout(self, process):\r\n        '''\r\n        Reads the stdout of `process` and forwards lines and progress\r\n        to any interested subscribers\r\n        '''\r\n        while True:\r\n            line = process.stdout.readline()\r\n            if not line:\r\n                break\r\n            _progress = self._extract_progress(line)\r\n\r\n            pub.send_message(events.PROGRESS_UPDATE, progress=_progress)\r\n            if _progress is None or self.hide_progress_msg is False:\r\n                pub.send_message(events.CONSOLE_UPDATE,\r\n                                 msg=line.decode(self.encoding))\r\n        pub.send_message(events.EXECUTION_COMPLETE)\r\n\r\n    def _extract_progress(self, text):\r\n        '''\r\n        Finds progress information in the text using the\r\n        user-supplied regex and calculation instructions\r\n        '''\r\n        # monad-ish dispatch to avoid the if/else soup\r\n        find = partial(re.search, string=text.strip().decode(self.encoding))\r\n        regex = unit(self.progress_regex)\r\n        match = bind(regex, find)\r\n        result = bind(match, self._calculate_progress)\r\n        return result\r\n\r\n    def _calculate_progress(self, match):\r\n        '''\r\n        Calculates the final progress value found by the regex\r\n        '''\r\n        if not self.progress_expr:\r\n            return safe_float(match.group(1))\r\n        else:\r\n            return self._eval_progress(match)\r\n\r\n    def _eval_progress(self, match):\r\n        '''\r\n        Runs the user-supplied progress calculation rule\r\n        '''\r\n        _locals = {k: safe_float(v) for k, v in match.groupdict().items()}\r\n        if \"x\" not in _locals:\r\n            _locals[\"x\"] = [safe_float(x) for x in match.groups()]\r\n        try:\r\n            return int(eval(self.progress_expr, {}, _locals))\r\n        except:\r\n            return None\r\n\r\n"
  },
  {
    "path": "gooey/gui/pubsub.py",
    "content": "import wx  # type: ignore\r\nfrom collections import defaultdict\r\n\r\n__ALL__ = ['pub']\r\n\r\n\r\nclass PubSub(object):\r\n    \"\"\"\r\n    A super simplified clone of Wx.lib.pubsub since it doesn't exist on linux\r\n    \"\"\"\r\n\r\n    def __init__(self):\r\n        self.registry = defaultdict(list)\r\n\r\n    def subscribe(self, event, handler):\r\n        self.registry[event].append(handler)\r\n\r\n    def send_message(self, event, **kwargs):\r\n        for event_handler in self.registry.get(event, []):\r\n            wx.CallAfter(event_handler, **kwargs)\r\n\r\n    def send_message_sync(self, event, **kwargs):\r\n        \"\"\"\r\n        ===== THIS IS NOT THREAD SAFE =====\r\n        Synchronously sends the message to all relevant consumers\r\n        and blocks until a response is received.\r\n\r\n        This MUST ONLY be used for communication within\r\n        the same thread! It exists primarily as an escape\r\n        hatch for bubbling up messages (which would be garbage\r\n        collected in the CallAfter form) to interested components\r\n        \"\"\"\r\n        for event_handler in self.registry.get(event, []):\r\n            event_handler(**kwargs)\r\n\r\npub = PubSub()\r\n"
  },
  {
    "path": "gooey/gui/seeder.py",
    "content": "\"\"\"\r\nUtil for talking to the client program in order to retrieve\r\ndynamic defaults for the UI\r\n\"\"\"\r\nimport subprocess\r\nfrom json import JSONDecodeError\r\nfrom subprocess import CalledProcessError\r\n\r\nfrom gooey.python_bindings.types import Try, Success, Failure\r\nfrom gooey.python_bindings.coms import deserialize_inbound\r\n\r\n\r\ndef communicate(cmd, encoding) -> Try:\r\n    \"\"\"\r\n    Invoke the processes specified by `cmd`.\r\n    Assumes that the process speaks JSON over stdout. Non-json response\r\n    are treated as an error.\r\n\r\n    Implementation Note: I don't know why, but `Popen` is like ~5-6x faster\r\n    than `check_output`. in practice, it means waiting for ~1/10th\r\n    of a second rather than ~7/10ths of a second. A\r\n    difference which is pretty weighty when there's a\r\n    user waiting on the other end.\r\n    \"\"\"\r\n    try:\r\n        proc = subprocess.Popen(\r\n            cmd,\r\n            stdout=subprocess.PIPE,\r\n            stderr=subprocess.PIPE\r\n        )\r\n        out, err = proc.communicate()\r\n        if out and proc.poll() == 0:\r\n            return Success(deserialize_inbound(out, encoding))\r\n        else:\r\n            return Failure(CalledProcessError(proc.returncode, cmd, output=out, stderr=err))\r\n    except JSONDecodeError as e:\r\n        return Failure(e)\r\n\r\n"
  },
  {
    "path": "gooey/gui/state.py",
    "content": "import json\nfrom base64 import b64encode\nfrom typing import Optional, List, Dict, Any, Union, Callable\n\nfrom typing_extensions import TypedDict\nimport wx\n\nfrom gooey.gui import events\nfrom gooey.gui.lang.i18n import _\nfrom gooey.python_bindings.types import GooeyParams, Item, Group, TopLevelParser, EnrichedItem, \\\n    FieldValue\nfrom gooey.util.functional import associn, assoc, associnMany, compact\nfrom gooey.gui.formatters import formatArgument\nfrom gooey.python_bindings.types import FormField\nfrom gooey.gui.constants import VALUE_PLACEHOLDER\nfrom gooey.gui.formatters import add_placeholder\nfrom gooey.python_bindings.types import CommandPieces, PublicGooeyState\n\n\nclass TimingEvent(TypedDict):\n    elapsed_time: Optional[str]\n    estimatedRemaining: Optional[str]\n\nclass ProgressEvent(TypedDict):\n    progress: Optional[int]\n\nclass ButtonState(TypedDict):\n    id: str\n    style: str\n    label_id: str\n    show: bool\n    enabled: bool\n\nclass ProgressState(TypedDict):\n    show: bool\n    range: int\n    value: int\n\nclass TimingState(TypedDict):\n    show: bool\n    elapsedTime: Optional[str]\n    estimated_remaining: Optional[str]\n\nclass GooeyState(GooeyParams):\n    fetchingUpdate: bool\n    screen: str\n    title: str\n    subtitle: str\n    images: Dict[str, str]\n    image: str\n    buttons: List[ButtonState]\n    progress: ProgressState\n    timing: TimingState\n    subcommands: List[str]\n    activeSelection: int\n    show_error_alert: bool\n\nclass FullGooeyState(GooeyState):\n    forms: Dict[str, List[FormField]]\n    widgets: Dict[str, Dict[str, Any]]\n\n\n\n\n\ndef extract_items(groups: List[Group]) -> List[Item]:\n    if not groups:\n        return []\n    group = groups[0]\n    return group['items'] \\\n           + extract_items(groups[1:]) \\\n           + extract_items(group['groups'])\n\n\ndef widgets(descriptor: TopLevelParser) -> List[Item]:\n    return extract_items(descriptor['contents'])\n\n\ndef enrichValue(formState: List[FormField], items: List[Item]) -> List[EnrichedItem]:\n    formIndex = {k['id']:k for k in formState}\n    return [EnrichedItem(field=formIndex[item['id']], **item) for item in items]  # type: ignore\n\n\ndef positional(items: List[Union[Item, EnrichedItem]]):\n    return [item for item in items if item['cli_type'] == 'positional']\n\n\ndef optional(items: List[Union[Item, EnrichedItem]]):\n    return [item for item in items if item['cli_type'] != 'positional']\n\n\ndef cli_pieces(state: FullGooeyState, formatter=formatArgument) -> CommandPieces:\n    parserName = state['subcommands'][state['activeSelection']]\n    parserSpec = state['widgets'][parserName]\n    formState = state['forms'][parserName]\n    subcommand = parserSpec['command'] if parserSpec['command'] != '::gooey/default' else ''\n    items = enrichValue(formState, widgets(parserSpec))\n    positional_args = [formatter(item) for item in positional(items)]  # type: ignore\n    optional_args = [formatter(item) for item in optional(items)]      # type: ignore\n    ignoreFlag = '' if state['suppress_gooey_flag'] else '--ignore-gooey'\n    return CommandPieces(\n        target=state['target'],\n        subcommand=subcommand,\n        positionals=compact(positional_args),\n        optionals=compact(optional_args),\n        ignoreFlag=ignoreFlag\n    )\n\n\ndef activeFormState(state: FullGooeyState):\n    subcommand = state['subcommands'][state['activeSelection']]\n    return state['forms'][subcommand]\n\n\ndef buildInvocationCmd(state: FullGooeyState):\n    pieces = cli_pieces(state)\n    return u' '.join(compact([\n        pieces.target,\n        pieces.subcommand,\n        *pieces.optionals,\n        pieces.ignoreFlag,\n        '--' if pieces.positionals else '',\n        *pieces.positionals]))\n\n\ndef buildFormValidationCmd(state: FullGooeyState):\n    pieces = cli_pieces(state, formatter=cmdOrPlaceholderOrNone)\n    serializedForm = json.dumps({'active_form': activeFormState(state)})\n    b64ecoded = b64encode(serializedForm.encode('utf-8'))\n    return ' '.join(compact([\n        pieces.target,\n        pieces.subcommand,\n        *pieces.optionals,\n        '--gooey-validate-form',\n        '--gooey-state ' + b64ecoded.decode('utf-8'),\n        '--' if pieces.positionals else '',\n        *pieces.positionals]))\n\n\ndef buildOnCompleteCmd(state: FullGooeyState, was_success: bool):\n    pieces = cli_pieces(state)\n    serializedForm = json.dumps({'active_form': activeFormState(state)})\n    b64ecoded = b64encode(serializedForm.encode('utf-8'))\n    return u' '.join(compact([\n        pieces.target,\n        pieces.subcommand,\n        *pieces.optionals,\n        '--gooey-state ' + b64ecoded.decode('utf-8'),\n        '--gooey-run-is-success' if was_success else '--gooey-run-is-failure',\n        '--' if pieces.positionals else '',\n        *pieces.positionals]))\n\n\ndef buildOnSuccessCmd(state: FullGooeyState):\n    return buildOnCompleteCmd(state, True)\n\ndef buildOnErrorCmd(state: FullGooeyState):\n    return buildOnCompleteCmd(state, False)\n\n\ndef cmdOrPlaceholderOrNone(item: EnrichedItem) -> Optional[str]:\n    # Argparse has a fail-fast-and-exit behavior for any missing\n    # values. This poses a problem for dynamic validation, as we\n    # want to collect _all_ errors to be more useful to the user.\n    # As such, if there is no value currently available, we pass\n    # through a stock placeholder values which allows GooeyParser\n    # to handle it being missing without Argparse exploding due to\n    # it actually being missing.\n    if item['cli_type'] == 'positional':\n        return formatArgument(item) or VALUE_PLACEHOLDER\n    elif item['cli_type'] != 'positional' and item['required']:\n        # same rationale applies here. We supply the argument\n        # along with a fixed placeholder (when relevant i.e. `store`\n        # actions)\n        return formatArgument(item) or formatArgument(assoc(item, 'field', add_placeholder(item['field'])))\n    else:\n        # Optional values are, well, optional. So, like usual, we send\n        # them if present or drop them if not.\n        return formatArgument(item)\n\n\n\n\ndef combine(state: GooeyState, params: GooeyParams, formState: List[FormField]) -> FullGooeyState:\n    \"\"\"\n    I'm leaving the refactor of the form elements to another day.\n    For now, we'll just merge in the state of the form fields as tracked\n    in the UI into the main state blob as needed.\n    \"\"\"\n    subcommand = list(params['widgets'].keys())[state['activeSelection']]\n    return FullGooeyState(**{\n        **state,\n        **params,\n        'forms': {subcommand: formState}\n    })\n\n\ndef enable_buttons(state, to_enable: List[str]):\n    updated = [{**btn, 'enabled': btn['label_id'] in to_enable}\n               for btn in state['buttons']]\n    return assoc(state, 'buttons', updated)\n\n\n\ndef activeCommand(state, params: GooeyParams):\n    \"\"\"\n    Retrieve the active sub-parser command as determined by the\n    current selection.\n    \"\"\"\n    return list(params['widgets'].keys())[state['activeSelection']]\n\n\ndef mergeExternalState(state: FullGooeyState, extern: PublicGooeyState) -> FullGooeyState:\n    # TODO: insane amounts of helpful validation\n    subcommand = state['subcommands'][state['activeSelection']]\n    formItems: List[FormField] = state['forms'][subcommand]\n    hostForm: List[FormField] = extern['active_form']\n    return associn(state, ['forms', subcommand], hostForm)\n\n\ndef show_alert(state: FullGooeyState):\n    return assoc(state, 'show_error_alert', True)\n\ndef has_errors(state: FullGooeyState):\n    \"\"\"\n    Searches through the form elements (including down into\n    RadioGroup's internal options to find the presence of\n    any errors.\n    \"\"\"\n    return any([item['error'] or any(x['error'] for x in item.get('options', []))\n                for items in state['forms'].values()\n                for item in items])\n\n\ndef initial_state(params: GooeyParams) -> GooeyState:\n    buttons = [\n        ('cancel', events.WINDOW_CANCEL, wx.ID_CANCEL),\n        ('start', events.WINDOW_START, wx.ID_OK),\n        ('stop', events.WINDOW_STOP, wx.ID_OK),\n        ('edit', events.WINDOW_EDIT,wx.ID_OK),\n        ('restart', events.WINDOW_RESTART, wx.ID_OK),\n        ('close', events.WINDOW_CLOSE, wx.ID_OK),\n    ]\n    # helping out the type system\n    params: Dict[str, Any] = params\n    return GooeyState(\n        **params,\n        fetchingUpdate=False,\n        screen='FORM',\n        title=params['program_name'],\n        subtitle=params['program_description'],\n        image=params['images']['configIcon'],\n        buttons=[ButtonState(\n            id=event_id,\n            style=style,\n            label_id=label,\n            show=label in ('cancel', 'start'),\n            enabled=True)\n            for label, event_id, style in buttons],\n        progress=ProgressState(\n            show=False,\n            range=100,\n            value=0 if params['progress_regex'] else -1\n        ),\n        timing=TimingState(\n            show=False,\n            elapsed_time=None,\n            estimatedRemaining=None,\n        ),\n        show_error_alert=False,\n        subcommands=list(params['widgets'].keys()),\n        activeSelection=0\n    )\n\ndef header_props(state, params):\n    return {\n            'background_color': params['header_bg_color'],\n            'title': params['program_name'],\n            'subtitle': params['program_description'],\n            'height': params['header_height'],\n            'image_uri': ims['images']['configIcon'],\n            'image_size': (six.MAXSIZE, params['header_height'] - 10)\n    }\n\n\ndef form_page(state):\n    return {\n        **state,\n        'buttons': [{**btn, 'show': btn['label_id'] in ('start', 'cancel')}\n                    for btn in state['buttons']]\n    }\n\n\ndef consoleScreen(_: Callable[[str], str], state: GooeyState):\n    return {\n        **state,\n        'screen': 'CONSOLE',\n        'title': _(\"running_title\"),\n        'subtitle': _('running_msg'),\n        'image': state['images']['runningIcon'],\n        'buttons': [{**btn,\n                     'show': btn['label_id'] == 'stop',\n                     'enabled': True}\n                    for btn in state['buttons']],\n        'progress': {\n            'show': not state['disable_progress_bar_animation'],\n            'range': 100,\n            'value': 0 if state['progress_regex'] else -1\n        },\n        'timing': {\n            'show': state['timing_options']['show_time_remaining'],\n            'elapsed_time': None,\n            'estimatedRemaining': None\n        },\n        'show_error_alert': False\n     }\n\n\ndef editScreen(_: Callable[[str], str], state: FullGooeyState):\n    use_buttons = ('cancel', 'start')\n    return associnMany(\n        state,\n        ('screen', 'FORM'),\n        ('buttons', [{**btn,\n                      'show': btn['label_id'] in use_buttons,\n                      'enabled': True}\n                     for btn in state['buttons']]),\n        ('image', state['images']['configIcon']),\n        ('title', state['program_name']),\n        ('subtitle', state['program_description']))\n\n\ndef beginUpdate(state: GooeyState):\n    return {\n        **enable_buttons(state, ['cancel']),\n        'fetchingUpdate': True\n    }\n\ndef finishUpdate(state: GooeyState):\n    return {\n        **enable_buttons(state, ['cancel', 'start']),\n        'fetchingUpdate': False\n    }\n\n\ndef finalScreen(_: Callable[[str], str], state: GooeyState) -> GooeyState:\n    use_buttons = ('edit', 'restart', 'close')\n    return associnMany(\n        state,\n        ('screen', 'CONSOLE'),\n        ('buttons', [{**btn,\n                      'show': btn['label_id'] in use_buttons,\n                      'enabled': True}\n                     for btn in state['buttons']]),\n        ('image', state['images']['successIcon']),\n        ('title', _('finished_title')),\n        ('subtitle', _('finished_msg')),\n        ('progress.show', False),\n        ('timing.show', not state['timing_options']['hide_time_remaining_on_complete']))\n\n\ndef successScreen(_: Callable[[str], str], state: GooeyState) -> GooeyState:\n    return associnMany(\n        finalScreen(_, state),\n        ('image', state['images']['successIcon']),\n        ('title', _('finished_title')),\n        ('subtitle', _('finished_msg')))\n\n\ndef errorScreen(_: Callable[[str], str], state: GooeyState) -> GooeyState:\n    return associnMany(\n        finalScreen(_, state),\n        ('image', state['images']['errorIcon']),\n        ('title', _('finished_title')),\n        ('subtitle', _('finished_error')))\n\n\ndef interruptedScreen(_: Callable[[str], str], state: GooeyState):\n    next_state = errorScreen(_, state) if state['force_stop_is_error'] else successScreen(_, state)\n    return assoc(next_state, 'subtitle', _('finished_forced_quit'))\n\n\ndef updateProgress(state, event: ProgressEvent):\n    return associn(state, ['progress', 'value'], event['progress'] or 0)\n\n\ndef updateTime(state, event):\n    return associnMany(\n        state,\n        ('timing.elapsed_time', event['elapsed_time']),\n        ('timing.estimatedRemaining', event['estimatedRemaining'])\n    )\n\n\n\n\n\n\ndef update_time(state, event: TimingEvent):\n    return {\n        **state,\n        'timer': {\n            **state['timer'],\n            'elapsed_time': event['elapsed_time'],\n            'estimatedRemaining': event['estimatedRemaining']\n        }\n    }\n\n\ndef present_time(timer):\n    estimate_time_remaining = timer['estimatedRemaining']\n    elapsed_time_value = timer['elapsed_time']\n    if elapsed_time_value is None:\n        return ''\n    elif estimate_time_remaining is not None:\n        return f'{elapsed_time_value}<{estimate_time_remaining}'\n    else:\n        return f'{elapsed_time_value}'\n"
  },
  {
    "path": "gooey/gui/three_to_four.py",
    "content": "'''\r\nUtil for supporting WxPython 3 & 4\r\n'''\r\n\r\nimport wx  # type: ignore\r\ntry:\r\n    import wx.adv  # type: ignore\r\nexcept ImportError:\r\n    pass\r\n\r\nisLatestVersion = wx.version().startswith('4')\r\n\r\n\r\nclass Constants:\r\n    if isLatestVersion:\r\n        WX_FONTSTYLE_NORMAL = wx.FONTSTYLE_NORMAL\r\n        WX_DP_DROPDOWN = wx.adv.DP_DROPDOWN\r\n    else:\r\n        WX_FONTSTYLE_NORMAL = wx.FONTWEIGHT_NORMAL\r\n        WX_DP_DROPDOWN = wx.DP_DROPDOWN\r\n\r\n\r\nclass Classes:\r\n    if isLatestVersion:\r\n        DatePickerCtrl = wx.adv.DatePickerCtrl\r\n    else:\r\n        DatePickerCtrl = wx.DatePickerCtrl\r\n\r\n    if isLatestVersion:\r\n        TimePickerCtrl = wx.adv.TimePickerCtrl\r\n    else:\r\n        TimePickerCtrl = wx.TimePickerCtrl\r\n\r\n\r\n\r\n\r\ndef imageFromBitmap(bitmap):\r\n    if isLatestVersion:\r\n        return bitmap.ConvertToImage()\r\n    else:\r\n        return wx.ImageFromBitmap(bitmap)\r\n\r\n\r\ndef bitmapFromImage(image):\r\n    if isLatestVersion:\r\n        return wx.Bitmap(image)\r\n    else:\r\n        return wx.BitmapFromImage(image)\r\n\r\n\r\ndef bitmapFromBufferRGBA(im, rgba):\r\n    if isLatestVersion:\r\n        return wx.Bitmap.FromBufferRGBA(im.size[0], im.size[1], rgba)\r\n    else:\r\n        return wx.BitmapFromBufferRGBA(im.size[0], im.size[1], rgba)\r\n\r\ndef AboutDialog():\r\n    if isLatestVersion:\r\n        return wx.adv.AboutDialogInfo()\r\n    else:\r\n        return wx.AboutDialogInfo()\r\n\r\n\r\ndef AboutBox(aboutDialog):\r\n    return (wx.adv.AboutBox(aboutDialog)\r\n            if isLatestVersion\r\n            else wx.AboutBox(aboutDialog))\r\n\r\n"
  },
  {
    "path": "gooey/gui/util/__init__.py",
    "content": "__author__ = 'Chris'\r\n"
  },
  {
    "path": "gooey/gui/util/casting.py",
    "content": "\r\n\r\ndef safe_int(n):\r\n  return _safe_cast(int, n)\r\n\r\ndef safe_float(n):\r\n  return _safe_cast(float, n)\r\n\r\n\r\ndef _safe_cast(_type, val):\r\n  try:\r\n    return _type(val)\r\n  except ValueError:\r\n    return None\r\n\r\n"
  },
  {
    "path": "gooey/gui/util/filedrop.py",
    "content": "import wx  # type: ignore\r\n\r\n\r\nclass FileDrop(wx.FileDropTarget):\r\n    def __init__(self, window, dropStrategy=None):\r\n        wx.FileDropTarget.__init__(self)\r\n        self.window = window\r\n        self.dropHandler = dropStrategy or self._defaultStrategy\r\n\r\n    def OnDropFiles(self, x, y, filenames):\r\n        return self.dropHandler(x, y, filenames)\r\n\r\n    def _defaultStrategy(self, x, y, filenames):\r\n        for name in filenames:\r\n            self.window.WriteText(name)\r\n        return True\r\n"
  },
  {
    "path": "gooey/gui/util/freeze.py",
    "content": "'''\nUtils for retrieving resources when when in a frozen state.\n\nMEIPASS explanation:\nhttps://pythonhosted.org/PyInstaller/#run-time-operation\n'''\nimport os\nimport sys\n\n\ndef is_frozen():\n    return getattr(sys, 'frozen', False)\n\n\ndef getResourcePath(*args):\n    if is_frozen():\n        # MEIPASS explanation:\n        # https://pythonhosted.org/PyInstaller/#run-time-operation\n        basedir = getattr(sys, '_MEIPASS', None)\n        if not basedir:\n            basedir = os.path.dirname(sys.executable)\n        resource_dir = os.path.join(basedir, 'gooey')\n        if not os.path.isdir(resource_dir):\n            raise IOError(\n                (\n                \"Cannot locate Gooey resources. It seems that the program was frozen, \"\n                \"but resource files were not copied into directory of the executable \"\n                \"file. Please copy `languages` and `images` folders from gooey module \"\n                \"directory into `{}{}` directory. Using PyInstaller, a.datas in .spec \"\n                \"file must be specified.\".format(resource_dir, os.sep)))\n    else:\n        resource_dir = os.path.normpath(\n            os.path.join(os.path.dirname(__file__), '..', '..'))\n    return os.path.join(resource_dir, *args)\n\n\ndef localResourcePath(path):\n    \"\"\"\n    A packaging aware util for getting the path to the local working directory.\n    When non-packaged, this is os.getcwd(), when packaged, it will be the local\n    (dynamic) directory where PyInstaller decompresses content.\n    \"\"\"\n    if is_frozen():\n        basedir = getattr(sys, '_MEIPASS', None)\n        return os.path.join(basedir or sys.executable, path)\n    else:\n        return os.path.join(os.getcwd(), path)\n"
  },
  {
    "path": "gooey/gui/util/functional.py",
    "content": "'''\r\nUtils for functional methodologies throughout Gooey\r\n\r\n'''\r\n\r\n\r\ndef merge_dictionaries(x,y):\r\n    \"\"\"\r\n    Merge 2 dictionaries with y taking overwriting x if a key collision is found\r\n\r\n    This is mainly useful for maintaining the dictionary arguments to allow for more expressive & extensible arguments.\r\n    https://stackoverflow.com/questions/38987/how-do-i-merge-two-dictionaries-in-a-single-expression-in-python-taking-union-o\r\n\r\n    Args:\r\n        x (dict): Input dictionary\r\n        y (dict): Input dictionary\r\n\r\n    Returns:\r\n        The combined dictionary of x & y with y taking preference on the occasion of key collision\r\n    \"\"\"\r\n    if x is None:\r\n        x = {}\r\n    if y is None:\r\n        y = {}\r\n    try:\r\n        return {**x,**y}\r\n    except:\r\n        z = x.copy()\r\n        z.update(y)\r\n        return z\r\n"
  },
  {
    "path": "gooey/gui/util/quoting.py",
    "content": "import sys\r\n\r\n\r\nif sys.platform.startswith(\"win\"):\r\n  def quote(value):\r\n    return u'\"{}\"'.format(u'{}'.format(value).replace(u'\"', u'\"\"'))\r\nelse:  # POSIX shell\r\n  def quote(value):\r\n    return u\"'{}'\".format(u'{}'.format(value).replace(u\"'\", u\"'\\\\''\"))\r\n\r\n"
  },
  {
    "path": "gooey/gui/util/time.py",
    "content": "\"\"\"\nModule for evaluating time elapsed & time remaining from progress\n\"\"\"\nimport wx  # type: ignore\nfrom gooey.gui.pubsub import pub\nfrom gooey.gui import events\n\nclass Timing(object):\n\n    def __init__(self, parent):\n        self.startTime = 0\n        self.estimatedRemaining = None\n        self.wxTimer = wx.Timer(parent)\n        self.parent = parent\n        parent.Bind(wx.EVT_TIMER, self.publishTime, self.wxTimer)\n\n        pub.subscribe(events.PROGRESS_UPDATE, self._updateEstimate)\n\n    def _updateEstimate(self, *args, **kwargs):\n        prog = kwargs.get('progress')\n        if(not prog): \n            self.estimatedRemaining = None\n            return\n        if(prog > 0):\n            self.estimatedRemaining = estimate_time_remaining(prog,self.startTime)\n\n    def publishTime(self, *args, **kwargs):\n        pub.send_message(\n            events.TIME_UPDATE,\n            start=self.startTime,\n            current=get_current_time(),\n            elapsed_time=format_interval(get_elapsed_time(self.startTime)),\n            estimatedRemaining=format_interval(self.estimatedRemaining))\n\n    def start(self):\n        self.startTime = get_current_time()\n        self.estimatedRemaining = None\n        self.wxTimer.Start()\n\n    def stop(self):\n        self.wxTimer.Stop()\n\ndef format_interval(timeValue):\n    \"\"\"\n    Formats a number of seconds as a clock time, [H:]MM:SS\n    Parameters\n    ----------\n    t  : int\n        Number of seconds.\n    Returns\n    -------\n    out  : str\n        [H:]MM:SS\n    \"\"\"\n    # https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L228\n    try:\n        mins, s = divmod(int(timeValue), 60)\n        h, m = divmod(mins, 60)\n        if h:\n            return '{0:d}:{1:02d}:{2:02d}'.format(h, m, s)\n        else:\n            return '{0:02d}:{1:02d}'.format(m, s)\n    except:\n        return None\n\ndef get_elapsed_time(startTime):\n    \"\"\"\n    Get elapsed time in form of seconds. Provide a start time in seconds as float.\n\n    Args:\n        startTime (float): Start time to compare against in seconds.\n\n    Returns:\n        float: Time between start time and now\n    \"\"\"\n    return get_current_time() - startTime\n\ndef estimate_time_remaining(progress,startTime):\n    # https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L392\n    # https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/std.py#L417\n    _rate = progress / get_elapsed_time(startTime)\n    return ((100 - progress) / _rate)\n\ndef get_current_time():\n    \"\"\"\n    Returns a float of the current time in seconds. Attempt to import perf_counter (more accurate in 3.4+), otherwise utilise timeit.\n\n    Returns:\n        float: Current time in seconds from performance counter.\n    \"\"\"\n    try:\n        from time import perf_counter\n        return perf_counter()\n    except:\n        import timeit\n        return timeit.default_timer()\n"
  },
  {
    "path": "gooey/gui/util/wx_util.py",
    "content": "\"\"\"\r\nCollection of Utility methods for creating often used, pre-styled wx Widgets\r\n\"\"\"\r\nfrom functools import wraps\r\n\r\nimport wx  # type: ignore\r\nfrom contextlib import contextmanager\r\n\r\nfrom gooey.gui.three_to_four import Constants\r\n\r\n\r\ndef callafter(f):\r\n    \"\"\"\r\n    Wraps the supplied function in a wx.CallAfter\r\n    for Thread-safe interop with WX.\r\n    \"\"\"\r\n    @wraps(f)\r\n    def inner(*args, **kwargs):\r\n        wx.CallAfter(f, *args, **kwargs)\r\n    return inner\r\n\r\n\r\n@contextmanager\r\ndef transactUI(obj):\r\n    \"\"\"\r\n    Coarse grain UI locking to avoid glitchy UI updates\r\n    \"\"\"\r\n    obj.Freeze()\r\n    try:\r\n        yield\r\n    finally:\r\n        obj.Layout()\r\n        obj.Thaw()\r\n\r\n\r\nstyles = {\r\n    'h0': (wx.FONTFAMILY_DEFAULT, Constants.WX_FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False),\r\n    'h1': (wx.FONTFAMILY_DEFAULT, Constants.WX_FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False),\r\n    'h2': (wx.FONTFAMILY_DEFAULT, Constants.WX_FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False),\r\n    'bold': (wx.FONTFAMILY_DEFAULT, Constants.WX_FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False)\r\n}\r\n\r\n\r\n\r\n\r\ndef make_bold(statictext):\r\n    pointsize = statictext.GetFont().GetPointSize()\r\n    font = wx.Font(pointsize, *styles['bold'])\r\n    statictext.SetFont(font)\r\n\r\n\r\ndef dark_grey(statictext):\r\n    return withColor(statictext, (54, 54, 54))\r\n\r\n\r\ndef withColor(statictext, hex):\r\n    statictext.SetForegroundColour(hex)\r\n    return statictext\r\n\r\n\r\ndef h0(parent, label):\r\n    text = wx.StaticText(parent, label=label)\r\n    font_size = text.GetFont().GetPointSize()\r\n    font = wx.Font(int(font_size * 1.4, *styles['h0']))\r\n    text.SetFont(font)\r\n    return text\r\n\r\n\r\ndef h1(parent, label):\r\n    return _header(parent, label, styles['h1'])\r\n\r\n\r\ndef h2(parent, label):\r\n    return _header(parent, label, styles['h2'])\r\n\r\n\r\ndef _header(parent, label, styles):\r\n    text = wx.StaticText(parent, label=label)\r\n    font_size = text.GetFont().GetPointSize()\r\n    font = wx.Font(int(font_size * 1.2), *styles)\r\n    text.SetFont(font)\r\n    return text\r\n\r\n\r\ndef horizontal_rule(parent):\r\n    return _rule(parent, wx.LI_HORIZONTAL)\r\n\r\n\r\ndef vertical_rule(parent):\r\n    return _rule(parent, wx.LI_VERTICAL)\r\n\r\n\r\ndef _rule(parent, direction):\r\n    line = wx.StaticLine(parent, -1, style=direction)\r\n    line.SetSize((10, 10))\r\n    return line\r\n"
  },
  {
    "path": "gooey/gui/validation.py",
    "content": "from typing import Mapping\n\nfrom gooey import Events\nfrom gooey.python_bindings.types import Try\nfrom gooey.util.functional import merge\n\n\ndef validateForm(self) -> Try[Mapping[str, str]]:  # or Exception\n    config = self.navbar.getActiveConfig()\n    localErrors: Mapping[str, str] = config.getErrors()\n    dynamicResult: Try[Mapping[str, str]] = self.fetchDynamicValidations()\n\n    combineErrors = lambda m: merge(localErrors, m)\n    return dynamicResult.map(combineErrors)\n\n\ndef fetchDynamicValidations(self) -> Try[Mapping[str, str]]:\n    # only run the dynamic validation if the user has\n    # specifically subscribed to that event\n    if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []):\n        cmd = self.getCommandDetails()\n        return seeder.communicate2(cli.formValidationCmd(\n            cmd.target,\n            cmd.subcommand + 'baba',\n            cmd.positionals,\n            cmd.optionals\n        ), self.buildSpec['encoding'])\n    else:\n        # shim response if nothing to do.\n        return Success({})"
  },
  {
    "path": "gooey/gui/validators.py",
    "content": "\r\n\r\ndef runValidator(f, value) -> bool:\r\n    \"\"\"\r\n    Attempt to run the user supplied validation function\r\n\r\n    Fall back to False in the even of any errors\r\n    \"\"\"\r\n    try:\r\n        return f(value)\r\n    except:\r\n        return False\r\n\r\n\r\n\r\n"
  },
  {
    "path": "gooey/images/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/languages/Hindi.json",
    "content": "{\n  \"browse\": \"ब्राउज़\",\n  \"cancel\": \"रद्द करना\",\n  \"checkbox_label\": \"सक्षम करें\",\n  \"choose_colour\": \"रंग चुनें\",\n  \"choose_date\": \"तिथि चुनें\",\n  \"choose_time\": \"समय चुनें\",\n  \"choose_file\": \"फ़ाइल चुनें\",\n  \"choose_folder\": \"फोल्डर चुनें\",\n  \"choose_folders_msg\": \"एक या अधिक फ़ोल्डर चुनें:\",\n  \"choose_folders_title\": \"फ़ोल्डर के लिए ब्राउज़ करें\",\n  \"choose_one\": \"एक चुनो\",\n  \"close\": \"बंद करे\",\n  \"close_program\": \"प्रोग्रेम को बंद करें?\",\n  \"edit\": \"संपादित करें\",\n  \"enter_filename\": \"फ़ाइल नाम दर्ज करें\",\n  \"error_required_fields\": \"आवश्यक अनुभाग में सभी क्षेत्रों को भरना चाहिए!\",\n  \"error_title\": \"त्रुटि\",\n  \"execution_finished\": \"निष्पादन समाप्त हो गया\",\n  \"finished_error\": \"एक गलती हुई है।\",\n  \"finished_forced_quit\": \"उपयोगकर्ता द्वारा समाप्त किया गया\",\n  \"finished_msg\": \"सब कुछ कर दिया! अब आप प्रोग्राम को सुरक्षित रूप से बंद कर सकते हैं।\",\n  \"finished_title\": \"ख़त्म होना\",\n  \"ok\": \"ठीक\",\n  \"open_file\": \"खुली फाइल\",\n  \"open_files\": \"खुली फ़ाइलें\",\n  \"optional_args_msg\": \"वैकल्पिक तर्क\",\n  \"required_args_msg\": \"आवश्यक तर्क\",\n  \"restart\": \"पुनर्प्रारंभ करें\",\n  \"running_msg\": \"कृपया प्रतीक्षा करें जब आवेदन अपने कार्य करता है। \\nइसमें कुछ क्षण लग सकते हैं\",\n  \"running_title\": \"चल रहा है\",\n  \"select_date\": \"एक तिथि चुनें\",\n  \"select_time\": \"एक समय का चयन करें\",\n  \"select_option\": \"विकल्प चुनें\",\n  \"settings_title\": \"समायोजन\",\n  \"simple_config\": \"कमांड लाइन तर्क दर्ज करें\",\n  \"start\": \"शुरू\",\n  \"status\": \"स्थिति\",\n  \"stop\": \"रुकें\",\n  \"stop_task\": \"कार्य रोकें?\",\n  \"success_message\": \"कार्यक्रम सफलतापूर्वक पूरा हुआ!\",\n  \"sure_you_want_to_exit\": \"क्या आप वाकई प्रोग्राम से बाहर निकलना चाहते हैं?\",\n  \"sure_you_want_to_stop\": \"क्या आप वाकई कार्य को रोकना चाहते हैं? \\nव्यवधान आपके डेटा को दूषित कर सकता है!\",\n  \"uh_oh\": \"\\nउह ओह! लगता है कि कोई समस्या थी। \\nअपने डेवलपर को क्या गलत हुआ, यह बताने के लिए स्टेटस विंडो से टेक्स्ट कॉपी करें।\\n\",\n  \"validation_failed\": \"एक या अधिक फ़ील्ड सत्यापन विफल हो गए।\",\n  \"dialog_button_yes\": \"हाँ\",\n  \"dialog_button_no\": \"नहीं\",\n  \"dialog_button_ok\": \"हाँ\"\n}\n"
  },
  {
    "path": "gooey/languages/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/languages/bosnian.json",
    "content": "{\r\n  \"browse\": \"Pretraži\",\r\n  \"cancel\": \"Otkaži\",\r\n  \"checkbox_label\": \"Uključi\",\r\n  \"choose_date\": \"Odaberite datum\",\r\n  \"choose_file\": \"Odaberite datoteku\",\r\n  \"choose_folder\": \"Odaberite direktorij\",\r\n  \"choose_folders_msg\": \"Odaberite jedan ili više direktorija:\",\r\n  \"choose_folders_title\": \"Razgledajte direktorije\",\r\n  \"choose_one\": \"Odaberite jedan element\",\r\n  \"close\": \"Zatvori\",\r\n  \"close_program\": \"Zatvori program?\",\r\n  \"edit\": \"Uredi\",\r\n  \"enter_filename\": \"Unesite ime datoteke\",\r\n  \"error_required_fields\": \"Morate popuniti sva polja u obaveznoj sekciji!\",\r\n  \"error_title\": \"Greška\",\r\n  \"execution_finished\": \"Izvršenje završeno\",\r\n  \"finished_error\": \"Greška se pojavila.\",\r\n  \"finished_forced_quit\": \"Prekinuto od strane korisnika\",\r\n  \"finished_msg\": \"Sve gotovo! Sada možete zatvoriti program.\",\r\n  \"finished_title\": \"Završeno\",\r\n  \"ok\": \"Ok\",\r\n  \"open_file\": \"Otvori datoteku\",\r\n  \"open_files\": \"Otvori datoteke\",\r\n  \"optional_args_msg\": \"Opcioni argument\",\r\n  \"required_args_msg\": \"Obavezan argument\",\r\n  \"restart\": \"Ponovno pokretanje\",\r\n  \"running_msg\": \"Molimo sačekajte dok aplikacija izvrši sve zadatke. \\nOvo može potrajati nekoliko trenutaka\",\r\n  \"running_title\": \"Radni\",\r\n  \"select_date\": \"Odaberite datum\",\r\n  \"select_option\": \"Odaberite opciju\",\r\n  \"settings_title\": \"Postavke\",\r\n  \"simple_config\": \"Unesite argumente komandne linije\",\r\n  \"start\": \"Start\",\r\n  \"status\": \"Status\",\r\n  \"stop\": \"Stop\",\r\n  \"stop_task\": \"Zaustavi operaciju?\",\r\n  \"success_message\": \"Program izvršen uspješno\",\r\n  \"sure_you_want_to_exit\": \"Jeste li sigurni da želite da izađete?\",\r\n  \"sure_you_want_to_stop\": \"Jeste li sigurni da želite zaustaviti zadatak? \\nPrekid može pokvariti vaše podatke!\",\r\n  \"uh_oh\": \"\\nUh oh! Izgleda da je došlo do problema. \\nKopirajte tekst iz status prozora kako bi pomogli programeru da shvati šta je pošlo naopako.\\n\",\r\n  \"validation_failed\": \"Jedno ili više polja nisu prošla validaciju.\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/chinese.json",
    "content": "{\r\n  \"browse\": \"浏览\",\r\n  \"cancel\": \"取消\",\r\n  \"checkbox_label\": \"启用\",\r\n  \"choose_colour\": \"选择颜色\",\r\n  \"choose_date\": \"选择日期\",\r\n  \"choose_time\": \"选择时间\",\r\n  \"choose_file\": \"选择文件\",\r\n  \"choose_folder\": \"选择文件目录\",\r\n  \"choose_folders_msg\": \"选择一个或更多文件目录:\",\r\n  \"choose_folders_title\": \"浏览文件目录\",\r\n  \"choose_one\": \"选择一项\",\r\n  \"close\": \"关闭\",\r\n  \"close_program\": \"确认退出?\",\r\n  \"edit\": \"编辑\",\r\n  \"enter_filename\": \"输入文件名\",\r\n  \"error_required_fields\": \"必须填充所有的必填选项!\",\r\n  \"error_title\": \"错误\",\r\n  \"execution_finished\": \"运行完成\",\r\n  \"finished_error\": \"运行出错\",\r\n  \"finished_forced_quit\": \"用户终结运行\",\r\n  \"finished_msg\": \"运行结束! 你现在可以安全的关闭程序。\",\r\n  \"finished_title\": \"运行结束\",\r\n  \"ok\": \"Ok\",\r\n  \"open_file\": \"打开文件\",\r\n  \"open_files\": \"打开多个文件\",\r\n  \"optional_args_msg\": \"可选参数\",\r\n  \"required_args_msg\": \"必填参数\",\r\n  \"restart\": \"重启\",\r\n  \"running_msg\": \"请稍等，程序正在运行。 \\n这可能将需要一定时间。\",\r\n  \"running_title\": \"运行中\",\r\n  \"select_date\": \"选择日期\",\r\n  \"select_time\": \"选择时间\",\r\n  \"select_option\": \"选择选项\",\r\n  \"settings_title\": \"配置\",\r\n  \"simple_config\": \"输入运行参数\",\r\n  \"start\": \"开始\",\r\n  \"status\": \"状态\",\r\n  \"stop\": \"停止\",\r\n  \"stop_task\": \"停止运行当前任务?\",\r\n  \"success_message\": \"程序成功退出!\\n按OK键退出\",\r\n  \"sure_you_want_to_exit\": \"你确定要退出?\",\r\n  \"sure_you_want_to_stop\": \"你确定要停止当前运行的任务? \\n中断有可能会破坏你的数据!\",\r\n  \"uh_oh\": \"\\nUh 糟糕! 程序运行出现故障。 \\n复制下方的错误信息给程序的开发员。\\n\\n{} \\t\\t\\n\\t\\t\",\r\n  \"validation_failed\": \"一个或一个以上的输入数据不符合格式要求\",\r\n  \"dialog_button_yes\": \"是\",\r\n  \"dialog_button_no\": \"否\",\r\n  \"dialog_button_ok\": \"OK\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/croatian.json",
    "content": "{\n  \"browse\": \"Pretraži\",\n  \"cancel\": \"Otkaži\",\n  \"checkbox_label\": \"Uključi\",\n  \"choose_date\": \"Odaberite datum\",\n  \"choose_file\": \"Odaberite datoteku\",\n  \"choose_folder\": \"Odaberite mapu\",\n  \"choose_folders_msg\": \"Odaberite jednu ili više mapa:\",\n  \"choose_folders_title\": \"Razgledajte mape\",\n  \"choose_one\": \"Odaberite jedan element\",\n  \"close\": \"Zatvori\",\n  \"close_program\": \"Zatvoriti program?\",\n  \"edit\": \"Uredi\",\n  \"enter_filename\": \"Unesite ime datoteke\",\n  \"error_required_fields\": \"Moraju biti popunjena sva polja u bloku Obavezno!\",\n  \"error_title\": \"Greška\",\n  \"execution_finished\": \"Izvršavanje završeno\",\n  \"finished_error\": \"Pojavila se greška.\",\n  \"finished_forced_quit\": \"Prekinuto od strane korisnika\",\n  \"finished_msg\": \"Sve završeno! Program sada možete zatvoriti na siguran način.\",\n  \"finished_title\": \"Završeno\",\n  \"ok\": \"Ok\",\n  \"open_file\": \"Otvori datoteku\",\n  \"open_files\": \"Otvori datoteke\",\n  \"optional_args_msg\": \"Neobavezan argument\",\n  \"required_args_msg\": \"Obavezan argument\",\n  \"restart\": \"Ponovno pokretanje\",\n  \"running_msg\": \"Molim pričekajte dok aplikacija obavi svoje zadatke. \\nOvo može potrajati nekoliko trenutaka\",\n  \"running_title\": \"Izvodi se\",\n  \"select_date\": \"Odaberite datum\",\n  \"select_option\": \"Odaberite opciju\",\n  \"settings_title\": \"Postavke\",\n  \"simple_config\": \"Unesite argumente naredbenog retka\",\n  \"start\": \"Početak\",\n  \"status\": \"Status\",\n  \"stop\": \"Stop\",\n  \"stop_task\": \"Zaustaviti zadatak?\",\n  \"success_message\": \"Program uspješno izvršen!\",\n  \"sure_you_want_to_exit\": \"Jeste li sigurni da želite izaći?\",\n  \"sure_you_want_to_stop\": \"Jeste li sigurni da želite zaustaviti zadatak? \\nPrekid može iskvariti vaše podatke!\",\n  \"uh_oh\": \"\\nUh oh! Izgleda da je došlo do problema. \\nKopirajte tekst iz statusnog prozora kako biste vašem programeru dali do znanja što je pošlo krivo.\\n\",\n  \"validation_failed\": \"Jedno ili više polja nije prošlo validaciju.\"\n}\n"
  },
  {
    "path": "gooey/languages/czech.json",
    "content": "{\n  \"browse\": \"Prohlížet\",\n  \"cancel\": \"Storno\",\n  \"choose_date\": \"Zvolte Datum\",\n  \"choose_file\": \"Zvolte Soubor\",\n  \"choose_folder\": \"Zvolte složku\",\n  \"choose_one\": \"Zvolte Jeden\",\n  \"close\": \"Zavřít\",\n  \"close_program\": \"Ukončit Program?\",\n  \"edit\": \"Upravit\",\n  \"enter_filename\": \"Zadejte Název Souboru\",\n  \"error_required_fields\": \"Vyplňte všechna pole!\",\n  \"error_title\": \"Chyba!\",\n  \"execution_finished\": \"Exekuce Programu Dokončena\",\n  \"finished_error\": \"Chyba!\",\n  \"finished_forced_quit\": \"Ukončeno uživatelem\",\n  \"finished_msg\": \"Úloha dokončena! Nyní můžete bezpečně ukončit program.\",\n  \"finished_title\": \"Hotovo\",\n  \"open_file\": \"Otevřít Soubor\",\n  \"optional_args_msg\": \"Volitelné\",\n  \"required_args_msg\": \"Povinné\",\n  \"restart\": \"Restart\",\n  \"running_msg\": \"Prosím čekejte na dokončení úloh aplikace. \\nToto může chvíli trvat\",\n  \"running_title\": \"Běh\",\n  \"select_option\": \"Vyberte Možnost\",\n  \"settings_title\": \"Nastavení\",\n  \"simple_config\": \"Zadejte Argumenty Příkazové Řádky\",\n  \"start\": \"Start\",\n  \"status\": \"Stav\",\n  \"stop\": \"Stop\",\n  \"stop_task\": \"Ukončit úlohu?\",\n  \"success_message\": \"Úloha dokončena!\",\n  \"sure_you_want_to_exit\": \"Opravdu chcete zavřít program?\",\n  \"sure_you_want_to_stop\": \"Opravdu chcete zavřít program? \\nUkončení může způsobit strátu dat!\",\n  \"uh_oh\": \"\\nOops! Vypadá to, že se vyskytl problém. \\nZkopírujte text ze stavového okna a pošlete ho vývojáři programu, aby ho spravil.\\n\",\n  \"validation_failed\": \"Jedno nebo více polí je špatně vyplněno.\"\n}\n"
  },
  {
    "path": "gooey/languages/dutch.json",
    "content": "{\n  \"browse\": \"Bladeren\",\n  \"cancel\": \"Annuleren\",\n  \"checkbox_label\": \"Inschakelen\",\n  \"choose_colour\": \"Kies een kleur\",\n  \"choose_date\": \"Kies een datum\",\n  \"choose_file\": \"Kies een bestand\",\n  \"choose_folder\": \"Kies een map\",\n  \"choose_folders_msg\": \"Kies één of meer mappen:\",\n  \"choose_folders_title\": \"Mappen bladeren\",\n  \"choose_one\": \"Kies één\",\n  \"choose_time\": \"Kies een tijd\",\n  \"close\": \"Afsluiten\",\n  \"close_program\": \"Wilt u het programma afsluiten?\",\n  \"dialog_button_no\": \"Nee\",\n  \"dialog_button_ok\": \"OK\",\n  \"dialog_button_yes\": \"Ja\",\n  \"dropdown\": {\n    \"no_matches\": \"Geen overeenkomsten gevonden\"\n  },\n  \"edit\": \"Bewerken\",\n  \"enter_filename\": \"Voer bestandsnaam in\",\n  \"error_required_fields\": \"Voer alle verplichte velden in!\",\n  \"error_title\": \"Fout\",\n  \"execution_finished\": \"Uitvoering is klaar\",\n  \"finished_error\": \"Er is een fout opgetreden\",\n  \"finished_forced_quit\": \"Afgebroken\",\n  \"finished_msg\": \"Alles is klaar, u kunt het programma veilig afsluiten\",\n  \"finished_title\": \"Klaar\",\n  \"ok\": \"OK\",\n  \"open_file\": \"Bestand openen\",\n  \"open_files\": \"Bestanden openen\",\n  \"optional_args_msg\": \"Optionele velden\",\n  \"required_args_msg\": \"Verplichte velden\",\n  \"restart\": \"Herstarten\",\n  \"running_msg\": \"Wacht alstublieft tot het programma klaar is, dit kan even duren\",\n  \"running_title\": \"Bezig\",\n  \"select_date\": \"Kies een datum\",\n  \"select_option\": \"Selecteer een optie\",\n  \"select_time\": \"Kies een tijd\",\n  \"settings_title\": \"Opties\",\n  \"simple_config\": \"Voer commandoregel-argumenten in\",\n  \"start\": \"Start\",\n  \"status\": \"Status\",\n  \"stop\": \"Afbreken\",\n  \"stop_task\": \"Wilt u het programma onderbreken?\",\n  \"success_message\": \"Het programma is succesvol voltooid!\\nDruk op de OK knop om af te sluiten\",\n  \"sure_you_want_to_exit\": \"Weet u het zeker?\",\n  \"sure_you_want_to_stop\": \"Weet u zeker dat u het programma wilt onderbreken?\\nDit kan leiden tot datacorruptie!\",\n  \"uh_oh\": \"\\nOeps! Er is iets misgegaan!\\nKopieër de foutmelding hieronder om de ontwikkelaar te laten weten wat er mis is gegaan.\\n\",\n  \"validation_failed\": \"Tenminste één veld voldoet niet aan de eisen\"\n}\n"
  },
  {
    "path": "gooey/languages/english.json",
    "content": "{\r\n  \"browse\": \"Browse\",\r\n  \"cancel\": \"Cancel\",\r\n  \"checkbox_label\": \"Enable\",\r\n  \"choose_colour\": \"Choose Colour\",\r\n  \"choose_date\": \"Choose Date\",\r\n  \"choose_time\": \"Choose Time\",\r\n  \"choose_file\": \"Choose File\",\r\n  \"choose_folder\": \"Choose Folder\",\r\n  \"choose_folders_msg\": \"Choose one or more folders:\",\r\n  \"choose_folders_title\": \"Browse For Folders\",\r\n  \"choose_one\": \"Choose One\",\r\n  \"close\": \"Close\",\r\n  \"close_program\": \"Close program?\",\r\n  \"edit\": \"Edit\",\r\n  \"enter_filename\": \"Enter Filename\",\r\n  \"error_required_fields\": \"Must fill in all fields in the Required section!\",\r\n  \"error_title\": \"Error\",\r\n  \"execution_finished\": \"Execution finished\",\r\n  \"finished_error\": \"An error has occurred.\",\r\n  \"finished_forced_quit\": \"Terminated by user\",\r\n  \"finished_msg\": \"All done! You may now safely close the program.\",\r\n  \"finished_title\": \"Finished\",\r\n  \"dropdown.no_matches\": \"No matches found\",\r\n  \"ok\": \"Ok\",\r\n  \"open_file\": \"Open File\",\r\n  \"open_files\": \"Open Files\",\r\n  \"optional_args_msg\": \"Optional Arguments\",\r\n  \"required_args_msg\": \"Required Arguments\",\r\n  \"restart\": \"Restart\",\r\n  \"running_msg\": \"Please wait while the application performs its tasks. \\nThis may take a few moments\",\r\n  \"running_title\": \"Running\",\r\n  \"select_date\": \"Select a Date\",\r\n  \"select_time\": \"Select a Time\",\r\n  \"select_option\": \"Select Option\",\r\n  \"settings_title\": \"Settings\",\r\n  \"simple_config\": \"Enter Command Line Arguments\",\r\n  \"start\": \"Start\",\r\n  \"status\": \"Status\",\r\n  \"stop\": \"Stop\",\r\n  \"stop_task\": \"Stop task?\",\r\n  \"success_message\": \"Program completed successfully!\",\r\n  \"sure_you_want_to_exit\": \"Are you sure you want to exit?\",\r\n  \"sure_you_want_to_stop\": \"Are you sure you want to stop the task? \\nInterruption can corrupt your data!\",\r\n  \"uh_oh\": \"\\nUh oh! Looks like there was a problem. \\nCopy the text from the status window to let your developer know what went wrong.\\n\",\r\n  \"validation_failed\": \"One or more fields failed validation.\",\r\n  \"dialog_button_yes\": \"Yes\",\r\n  \"dialog_button_no\": \"No\",\r\n  \"dialog_button_ok\": \"OK\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/french.json",
    "content": "{\r\n  \"browse\": \"Naviguer\",\r\n  \"cancel\": \"Annuler\",\r\n  \"checkbox_label\": \"Activer\",\r\n  \"choose_colour\": \"Choisir une couleur\",\r\n  \"choose_date\": \"Choisir une date\",\r\n  \"choose_time\": \"Choisir une heure\",\r\n  \"choose_file\": \"Choisir un fichier\",\r\n  \"choose_folder\": \"Choisir un dossier\",\r\n  \"choose_folders_msg\": \"Choisir un ou plusieurs dossiers:\",\r\n  \"choose_folders_title\": \"Parcourir les dossiers\",\r\n  \"choose_one\": \"Choisir entre :\",\r\n  \"close\": \"Fermer\",\r\n  \"close_program\": \"Fermer le programme ?\",\r\n  \"edit\": \"Éditer\",\r\n  \"enter_filename\": \"Entrer un nom de fichier\",\r\n  \"error_required_fields\": \"Tous les champs dans la section obligatoire doivent être remplis !\",\r\n  \"error_title\": \"Erreur\",\r\n  \"execution_finished\": \"Exécution terminée\",\r\n  \"finished_error\": \"Une erreur s'est produite.\",\r\n  \"finished_forced_quit\": \"Stoppé par l'utilisateur\",\r\n  \"finished_msg\": \"Terminé ! Vous pouvez maintenant fermer le programme.\",\r\n  \"finished_title\": \"Terminé\",\r\n  \"ok\": \"Ok\",\r\n  \"open_file\": \"Ouvrir fichier\",\r\n  \"open_files\": \"Ouvrir fichiers\",\r\n  \"optional_args_msg\": \"Arguments optionnels\",\r\n  \"required_args_msg\": \"Arguments obligatoires\",\r\n  \"restart\": \"Redémarrer\",\r\n  \"running_msg\": \"Veuillez attendre la fin de l'exécution. \\nCela peut prendre quelques instants.\",\r\n  \"running_title\": \"Exécution en cours.\",\r\n  \"select_date\": \"Sélectionner une date\",\r\n  \"select_time\": \"Sélectionner une heure\",\r\n  \"select_option\": \"Sélectionner une option\",\r\n  \"settings_title\": \"Paramètres\",\r\n  \"simple_config\": \"Entrer les arguments de ligne de commande\",\r\n  \"start\": \"Démarrer\",\r\n  \"status\": \"Statut\",\r\n  \"stop\": \"Arrêter\",\r\n  \"stop_task\": \"Arrêter la tâche ?\",\r\n  \"success_message\": \"Programme terminé avec succès !\\nAppuyez sur le bouton OK pour fermer.\",\r\n  \"sure_you_want_to_exit\": \"Êtes vous sûr de vouloir quitter ?\",\r\n  \"sure_you_want_to_stop\": \"Êtes vous sûr de vouloir arrêter le traitement ? \\nUne interruption peut corrompre les données !\",\r\n  \"uh_oh\": \"\\nOups ! Il semble qu'une erreur soit survenue. \\nCopiez le message d'erreur ci-dessous pour en informer le développeur :\\n\\n{} \\t\\t\\n\\t\\t\",\r\n  \"validation_failed\": \"Un ou plusieurs champs ne passent pas la validation.\",\r\n  \"dialog_button_yes\": \"Oui\",\r\n  \"dialog_button_no\": \"Non\",\r\n  \"dialog_button_ok\": \"Ok\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/german.json",
    "content": "{\r\n  \"browse\": \"Durchsuchen\",\r\n  \"cancel\": \"Abbrechen\",\r\n  \"choose_date\": \"Datum auswählen\",\r\n  \"choose_file\": \"Datei auswählen\",\r\n  \"choose_folder\": \"Ordner auswählen\",\r\n  \"choose_one\": \"Auswählen\",\r\n  \"close\": \"Schließen\",\r\n  \"close_program\": \"Programm Schließen?\",\r\n  \"edit\": \"Bearbeiten\",\r\n  \"enter_filename\": \"Dateinamen eingeben\",\r\n  \"error_required_fields\": \"Bitte alle benötigten Argumente ausfüllen!\",\r\n  \"error_title\": \"Fehler\",\r\n  \"execution_finished\": \"Die Ausführung war erfolgreich\",\r\n  \"finished_error\": \"Es ist ein Fehler aufgetreten.\",\r\n  \"finished_forced_quit\": \"Die Ausführung wurde abgebrochen.\",\r\n  \"finished_msg\": \"Fertig! Sie können das Programm jetzt schließen\",\r\n  \"finished_title\": \"Fertig\",\r\n  \"open_file\": \"Datei öffnen\",\r\n  \"optional_args_msg\": \"optionale Argumente\",\r\n  \"required_args_msg\": \"benötigte Argumente\",\r\n  \"restart\": \"Neustart\",\r\n  \"running_msg\": \"Bitte warten Sie auf die Anwendung \\nDies kann einen Moment dauern.\",\r\n  \"running_title\": \"Läuft\",\r\n  \"select_option\": \"Option wählen\",\r\n  \"settings_title\": \"Einstellungen\",\r\n  \"simple_config\": \"Befehlszeilenargumente eingeben\",\r\n  \"start\": \"Start\",\r\n  \"status\": \"Status\",\r\n  \"stop\": \"Stop\",\r\n  \"stop_task\": \"Ausführung abbrechen?\",\r\n  \"success_message\": \"Das Programm wurde erfolgreich ausgeführt!\",\r\n  \"sure_you_want_to_exit\": \"Wollen Sie das Programm wirklich beenden?\",\r\n  \"sure_you_want_to_stop\": \"Wollen Sie die Ausführung wirklich abbrechen? \\nDies kann zu Datenverlust führen!\",\r\n  \"uh_oh\": \"\\nUpps ! Irgendwas ist schief gelaufen. \\nKopieren Sie den Text aus diesem Fenster und senden ihn an den Entwickler.\\n\\n{} \\t\\t\\n\\t\\t\",\r\n  \"validation_failed\": \"Ein oder mehrere Felder konnten nicht validiert werden\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/greek.json",
    "content": "{\r\n  \"browse\": \"Περιήγηση\",\r\n  \"cancel\": \"Ακύρωση\",\r\n  \"choose_date\": \"(translate me) Choose Date\",\r\n  \"choose_file\": \"(translate me) Choose file\",\r\n  \"choose_folder\": \"(translate me) Choose folder\",\r\n  \"choose_one\": \"(translate me) Choose One\",\r\n  \"close\": \"Κλείσιμο\",\r\n  \"close_program\": \"Κλείσιμο προγράμματος;\",\r\n  \"edit\": \"Επεξεργασία\",\r\n  \"enter_filename\": \"(translate me) Enter filename\",\r\n  \"error_required_fields\": \"Πρέπει να συμπληρώσετε όλα τα απαραίτητα πεδία!\",\r\n  \"error_title\": \"Σφάλμα\",\r\n  \"execution_finished\": \"Η εκτέλεση ολοκληρώθηκε\",\r\n  \"finished_error\": \"Ένα σφάλμα συνέβη.\",\r\n  \"finished_forced_quit\": \"(translate me) Terminated by user\",\r\n  \"finished_msg\": \"Τελείωσε! Μπορείς να τερματίσεις το πρόγραμμα με ασφάλεια.\",\r\n  \"finished_title\": \"Τελείωσε\",\r\n  \"open_file\": \"(translate me) Open File\",\r\n  \"optional_args_msg\": \"Προαιρετικοί Παράμετροι\",\r\n  \"required_args_msg\": \"Απαραίτητοι Παράμετροι\",\r\n  \"restart\": \"Επανεκκίνηση\",\r\n  \"running_msg\": \"Παρακαλώ περιμένετε όσο η εφαρμογή λειτουργεί. \\nΊσως διαρκέσει λίγη ώρα\",\r\n  \"running_title\": \"Εκτελείτε\",\r\n  \"select_option\": \"(translate me) Select Option\",\r\n  \"settings_title\": \"Επιλογές\",\r\n  \"simple_config\": \"Εισάγετε Παράμετρους Γραμμής Εντολών\",\r\n  \"start\": \"Έναρξη\",\r\n  \"status\": \"Κατάσταση\",\r\n  \"stop\": \"Διακοπή\",\r\n  \"stop_task\": \"Διακοπή διαδικασίας;\",\r\n  \"success_message\": \"Το πρόγραμμα εκτελέστηκε με επιτυχία!\",\r\n  \"sure_you_want_to_exit\": \"Θέλετε σίγουρα να το κλείσετε;\",\r\n  \"sure_you_want_to_stop\": \"Θέλετε σίγουρα να διακόψετε την εργασία; \\nΗ διακοπή μπορεί να καταστρέψει τα δεδομένα σας!\",\r\n  \"uh_oh\": \"\\nΟυπς! Φαίνεται πως υπάρχει κάποιο πρόβλημα. \\nΑντιγράψτε το κείμενο από το παράθυρο κατάστασης για να ενημερώσετε τον προγραμματιστή τι πήγε λάθος.\\n\",\r\n  \"validation_failed\": \"(translate me)One or more fields failed validation\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/hebrew.json",
    "content": "{\n  \"browse\": \"בחירה\",\n  \"cancel\": \"בטל\",\n  \"choose_date\": \"בחר תאריך\",\n  \"choose_file\": \"בחר קובץ\",\n  \"choose_folder\": \"בחר תיקייה\",\n  \"choose_one\": \"בחר פריט\",\n  \"close\": \"סגור\",\n  \"close_program\": \"לסגור את התוכנית?\",\n  \"edit\": \"ערוך\",\n  \"enter_filename\": \"נא להזין שם קובץ\",\n  \"error_required_fields\": \"יש למלא את כל השדות שנמצאים באזור החובה\",\n  \"error_title\": \"שגיאה\",\n  \"execution_finished\": \"הפעולה הסתיימה\",\n  \"finished_error\": \"אירעה תקלה\",\n  \"finished_forced_quit\": \"הופסק על יד המשתמש\",\n  \"finished_msg\": \"סיימנו! ניתן לסגור את התוכנית בבטחה\",\n  \"finished_title\": \"סיום\",\n  \"open_file\": \"פתיחת קובץ\",\n  \"optional_args_msg\": \"נתונים אפשריים\",\n  \"required_args_msg\": \"נתוני חובה\",\n  \"restart\": \"התחל מחדש\",\n  \"running_msg\": \"נא להמתין בזמן שהתוכנית מבצעת את משימותיה. זה עשוי להימשך מספר רגעים\",\n  \"running_title\": \"בריצה\",\n  \"select_option\": \"נא לבחור אפשרות\",\n  \"settings_title\": \"אפשרויות\",\n  \"simple_config\": \"נא להזין נתוני שורת פקודות\",\n  \"start\": \"התחל\",\n  \"status\": \"סטטוס\",\n  \"stop\": \"עצור\",\n  \"stop_task\": \"לעצור את הפעולה?\",\n  \"success_message\": \"התוכנית הסתיימה בהצלחה\",\n  \"sure_you_want_to_exit\": \"האם לצאת?\",\n  \"sure_you_want_to_stop\": \"להפסיק את הפעולה? הפסקה מוקדמת עלולה להשחית נתונים\",\n  \"uh_oh\": \"\\nאוי ואבוי! נראה שאירעה תקלה. \\nניתן להעתיק את הטקסט מחלון הסטטוס כדי להודיע למפתחי/ות התוכנה מה השתבש.\",\n  \"validation_failed\": \"חלק מהשדות מכילים נתונים לא חוקיים\"\n}\n"
  },
  {
    "path": "gooey/languages/italian.json",
    "content": "{\r\n  \"browse\": \"Apri\",\r\n  \"cancel\": \"Annulla\",\r\n  \"choose_date\": \"Seleziona data\",\r\n  \"choose_file\": \"Seleziona file\",\r\n  \"choose_folder\": \"Seleziona cartella\",\r\n  \"choose_one\": \"Scegline una\",\r\n  \"close\": \"Chiudi\",\r\n  \"close_program\": \"Chiudere l'applicazione?\",\r\n  \"edit\": \"Modifica\",\r\n  \"enter_filename\": \"Inserisci filename\",\r\n  \"error_required_fields\": \"Devi compilare tutti i campi della sezione Richiesti!\",\r\n  \"error_title\": \"Errore\",\r\n  \"execution_finished\": \"Esecuzione finita\",\r\n  \"finished_error\": \"Si è verificato un errore.\",\r\n  \"finished_forced_quit\": \"Terminato dall'utente\",\r\n  \"finished_msg\": \"Tutto fatto! Ora puoi chiudere il programma.\",\r\n  \"finished_title\": \"Finito\",\r\n  \"open_file\": \"Apri file\",\r\n  \"optional_args_msg\": \"Parametri opzionali\",\r\n  \"required_args_msg\": \"Parametri richiesti\",\r\n  \"restart\": \"Restart\",\r\n  \"running_msg\": \"Attendi finchè l'applicazione non avrà finito. \\nQuest'operazione potrebbe durare qualche minuto\",\r\n  \"running_title\": \"In esecuzione\",\r\n  \"select_option\": \"Seleziona opzione\",\r\n  \"settings_title\": \"Opzioni\",\r\n  \"simple_config\": \"Inserici i parametri di linea di comando\",\r\n  \"start\": \"Start\",\r\n  \"status\": \"Stato\",\r\n  \"stop\": \"Stop\",\r\n  \"stop_task\": \"Fermare il processo?\",\r\n  \"success_message\": \"Programma eseguito con successo!\",\r\n  \"sure_you_want_to_exit\": \"Sei sicuro di voler uscire?\",\r\n  \"sure_you_want_to_stop\": \"Sei sicuro di voler interrompere il processo? \\nL'interruzione potrebbe corrempere i dati!\",\r\n  \"uh_oh\": \"\\nOh oh! Sembra che ci sia stato un problema. \\nCopia il testo dalla finestra di stato per far sapere allo sviluppatore cosa è andato storto.\\n\",\r\n  \"validation_failed\": \"Validazione fallita in uno o più campi\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/japanese.json",
    "content": "{\r\n  \"browse\": \"ブラウズ\",\r\n  \"cancel\": \"キャンセル\",\r\n  \"choose_date\": \"日付を選択\",\r\n  \"choose_file\": \"ファイルを選ぶ\",\r\n  \"choose_folder\": \"フォルダーを選択\",\r\n  \"choose_one\": \"いずれかを選択\",\r\n  \"close\": \"終了\",\r\n  \"close_program\": \"終了しますか？\",\r\n  \"edit\": \"修正\",\r\n  \"enter_filename\": \"ファイル名入力\",\r\n  \"error_required_fields\": \"必須引数を全部入力してください。\",\r\n  \"error_title\": \"エラー\",\r\n  \"execution_finished\": \"実行完了\",\r\n  \"finished_error\": \"エラーが発生しました。\",\r\n  \"finished_forced_quit\": \"ユーザーによる終了\",\r\n  \"finished_msg\": \"完了しました。もうアップリを閉じても大丈夫です。\",\r\n  \"finished_title\": \"完了\",\r\n  \"open_file\": \"ファイルを開く\",\r\n  \"optional_args_msg\": \"オプション引数\",\r\n  \"required_args_msg\": \"必須引数\",\r\n  \"restart\": \"再実行\",\r\n  \"running_msg\": \"アップリが作業を終えるまで待ってください。\\n完了までしばらくかかります\",\r\n  \"running_title\": \"実行中\",\r\n  \"select_option\": \"(オプションの選択\",\r\n  \"settings_title\": \"[設定]\",\r\n  \"simple_config\": \"コマンドライン引数を入力してください。\",\r\n  \"start\": \"実行\",\r\n  \"status\": \"状態\",\r\n  \"stop\": \"中止\",\r\n  \"stop_task\": \"作業を中断しますか？\",\r\n  \"success_message\": \"完了しました。\\n完了ボタンを押してアップリを終了します。\",\r\n  \"sure_you_want_to_exit\": \"本当に終了しますか？\",\r\n  \"sure_you_want_to_stop\": \"本当に中断しますか？\\nデータに問題が発生する恐れがあります！\",\r\n  \"uh_oh\": \"\\n問題が発生したようです。\\n下のエラーメッセージを開発者に送って何の問題か教えてください。\",\r\n  \"validation_failed\": \"フィールドの検証に失敗しました\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/korean.json",
    "content": "{\r\n  \"browse\": \"찾기\",\r\n  \"cancel\": \"취소\",\r\n  \"choose_date\": \"날짜 선택\",\r\n  \"choose_file\": \"파일 선택\",\r\n  \"choose_folder\": \"폴더 선택\",\r\n  \"choose_one\": \"하나만 선택\",\r\n  \"close\": \"종료\",\r\n  \"close_program\": \"종료하시겠습니까?\",\r\n  \"edit\": \"편집\",\r\n  \"enter_filename\": \"파일 이름을 입력하세요\",\r\n  \"error_required_fields\": \"필수 인자들을 모두 입력해주세요.\",\r\n  \"error_title\": \"오류\",\r\n  \"execution_finished\": \"실행 완료\",\r\n  \"finished_error\": \"오류가 발생했습니다.\",\r\n  \"finished_forced_quit\": \"사용자에 의해 종료\",\r\n  \"finished_msg\": \"모두 완료되었습니다! 이제 종료하셔도 좋습니다.\",\r\n  \"finished_title\": \"완료\",\r\n  \"open_file\": \"파일 열기\",\r\n  \"optional_args_msg\": \"선택적인 입력\",\r\n  \"required_args_msg\": \"필수적인 입력\",\r\n  \"restart\": \"다시 시작하기\",\r\n  \"running_msg\": \"작업을 완료될때까지 기다려주세요. \\n 시간이 필요합니다.\",\r\n  \"running_title\": \"실행 중\",\r\n  \"select_option\": \"옵션 선택\",\r\n  \"settings_title\": \"설정\",\r\n  \"simple_config\": \"커맨드 라인 인자를 입력해주세요.\",\r\n  \"start\": \"시작\",\r\n  \"status\": \"상태\",\r\n  \"stop\": \"중지\",\r\n  \"stop_task\": \"작업을 중지하시겠습니까?\",\r\n  \"success_message\": \"프로그램이 성공적으로 완료되었습니다.\\n완료 버튼을 누르면 종료됩니다.\",\r\n  \"sure_you_want_to_exit\": \"정말로 종료하시겠습니까?\",\r\n  \"sure_you_want_to_stop\": \"정말로 작업을 중지하시겠습니까? \\n데이터에 문제가 발생할 수 있습니다!\",\r\n  \"uh_oh\": \"\\n이런! 문제가 발생한 것 같군요\\n아래의 에러를 복사해 개발자에게 보내 무엇이 문제인지 알려주세요.\\n\",\r\n  \"validation_failed\": \"하나 이상의 입력값이 올바르지 않습니다.\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/polish.json",
    "content": "{\n  \"browse\": \"Przeglądaj\",\n  \"cancel\": \"Anuluj\",\n  \"choose_date\": \"Wybierz datę\",\n  \"choose_file\": \"Wybierz plik\",\n  \"choose_folder\": \"Wybierz katalog\",\n  \"choose_one\": \"Wybierz wariant\",\n  \"close\": \"Zamknij\",\n  \"close_program\": \"Zamknąć program?\",\n  \"edit\": \"Edytuj\",\n  \"enter_filename\": \"Wprowadź nazwę pliku\",\n  \"error_required_fields\": \"Trzeba wypełnić wszystkie pola z grupy wymaganych!\",\n  \"error_title\": \"Błąd\",\n  \"execution_finished\": \"Wykonano\",\n  \"finished_error\": \"Wystąpił błąd.\",\n  \"finished_forced_quit\": \"Zakończone przez użytkownika\",\n  \"finished_msg\": \"Wszystko gotowe! Można teraz bezpiecznie zamknąć program.\",\n  \"finished_title\": \"Skończone\",\n  \"open_file\": \"Otwórz plik\",\n  \"optional_args_msg\": \"Argumenty opcjonalne\",\n  \"required_args_msg\": \"Argumenty wymagane\",\n  \"restart\": \"Uruchom ponownie\",\n  \"running_msg\": \"Proszę poczekać na wykonanie zadań przez program. \\nTo może trochę potrwać\",\n  \"running_title\": \"Wykonuje się\",\n  \"select_option\": \"Wybierz opcję\",\n  \"settings_title\": \"Ustawienia\",\n  \"simple_config\": \"Wprowadź argumenty linii poleceń\",\n  \"start\": \"Start\",\n  \"status\": \"Status\",\n  \"stop\": \"Stop\",\n  \"stop_task\": \"Zatrzymać zadanie?\",\n  \"success_message\": \"Program udanie zakończył działanie!\",\n  \"sure_you_want_to_exit\": \"Potwierdzasz zamiar opuszczenia?\",\n  \"sure_you_want_to_stop\": \"Potwierdzasz zamiar przerwania zadania? \\nPrzerwanie może uszkokdzić Twoje dane!\",\n  \"uh_oh\": \"\\nOch, ach! Wydaje się, że wystąpił jakiś problem. \\nSkopiuj tekst z okna statusu - w celu powiadomienia autora programu o tym, co poszło źle.\\n\",\n  \"validation_failed\": \"Jedno lub więcej pól nie przeszło weryfikacji.\"\n}\n"
  },
  {
    "path": "gooey/languages/portuguese.json",
    "content": "{\r\n  \"browse\": \"Procurar\",\r\n  \"cancel\": \"Cancelar\",\r\n  \"checkbox_label\": \"Habilitar\",\r\n  \"choose_colour\": \"Escolha uma cor\",\r\n  \"choose_date\": \"Escolha uma data\",\r\n  \"choose_time\": \"Escolha um horário\",\r\n  \"choose_file\": \"Escolha um arquivo\",\r\n  \"choose_folder\": \"Escolha uma pasta\",\r\n  \"choose_folders_msg\": \"Escolha uma ou mais pastas:\",\r\n  \"choose_folders_title\": \"Procurar por pastas\",\r\n  \"choose_one\": \"Escolha um\",\r\n  \"close\": \"Fechar\",\r\n  \"close_program\": \"Fechar o programa?\",\r\n  \"edit\": \"Editar\",\r\n  \"enter_filename\": \"Entre com o nome do arquivo\",\r\n  \"error_required_fields\": \"Você deve preencher todos os campos na seção obrigatória!\",\r\n  \"error_title\": \"Erro\",\r\n  \"execution_finished\": \"Execução Finalizada\",\r\n  \"finished_error\": \"Ocorreu um erro.\",\r\n  \"finished_forced_quit\": \"Terminado pelo usuário\",\r\n  \"finished_msg\": \"Acabou! Você agora pode fechar o programa com segurança.\",\r\n  \"finished_title\": \"Finalizado\",\r\n  \"dropdown.no_matches\": \"Nenhum resultado encontrado\",\r\n  \"ok\": \"Ok\",\r\n  \"open_file\": \"Abrir arquivo\",\r\n  \"open_files\": \"Abrir arquivos\",\r\n  \"optional_args_msg\": \"Argumentos Opcionais\",\r\n  \"required_args_msg\": \"Argumentos Obrigatórios\",\r\n  \"restart\": \"Reiniciar\",\r\n  \"running_msg\": \"Aguarde enquanto a aplicação executa suas tarefas. \\nIsto pode demorar alguns instantes\",\r\n  \"running_title\": \"Em Execução\",\r\n  \"select_date\": \"Selecione uma data\",\r\n  \"select_time\": \"Selecione um horário\",\r\n  \"select_option\": \"Selecione uma opção\",\r\n  \"settings_title\": \"Configurações\",\r\n  \"simple_config\": \"Insira os argumentos da linha de comando\",\r\n  \"start\": \"Iniciar\",\r\n  \"status\": \"Status\",\r\n  \"stop\": \"Parar\",\r\n  \"stop_task\": \"Parar a execução?\",\r\n  \"success_message\": \"Programa finalizado com sucesso!\\nPressione OK para sair\",\r\n  \"sure_you_want_to_exit\": \"Você tem certeza que deseja sair?\",\r\n  \"sure_you_want_to_stop\": \"Você tem certeza que deseja interromper a tarefa?\\nA interrupção pode corromper os seus dados!\",\r\n  \"uh_oh\": \"\\nOps! Parece que ocorreu um problmea. \\nCopie o texto da janela de status e envie para o desenvolvedor para que ele saiba o que deu errado.\\n\",\r\n  \"validation_failed\": \"Um ou mais campos não puderam ser validados\",\r\n  \"dialog_button_yes\": \"Sim\",\r\n  \"dialog_button_no\": \"Não\",\r\n  \"dialog_button_ok\": \"OK\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/russian.json",
    "content": "{\r\n  \"browse\": \"просматривать\",\r\n  \"cancel\": \"Отмена\",\r\n  \"choose_date\": \"Выбор даты\",\r\n  \"choose_file\": \"Выбор файла\",\r\n  \"choose_folder\": \"Выбор директории\",\r\n  \"choose_one\": \"Выберите один вариант\",\r\n  \"close\": \"Закрыть\",\r\n  \"close_program\": \"Закрыть программу?\",\r\n  \"edit\": \"редактировать\",\r\n  \"enter_filename\": \"Введите имя файла\",\r\n  \"error_required_fields\": \"Нужно заполнить все поля в разделе Требуется !\",\r\n  \"error_title\": \"Ошибка\",\r\n  \"execution_finished\": \"Выполнено\",\r\n  \"finished_error\": \"Произошла ошибка.\",\r\n  \"finished_forced_quit\": \"Завершено пользователем\",\r\n  \"finished_msg\": \"Готово! Теперь вы можете закрыть программу.\",\r\n  \"finished_title\": \"Готово\",\r\n  \"open_file\": \"Открыть файл\",\r\n  \"optional_args_msg\": \"Необязательные аргументы (опции)\",\r\n  \"required_args_msg\": \"Обязательные аргументы\",\r\n  \"restart\": \"перезапуск\",\r\n  \"running_msg\": \"Пожалуйста, подождите, пока приложение выполняет задачи. \\nЭто может занять пару минут.\",\r\n  \"running_title\": \"Выполняется\",\r\n  \"select_option\": \"Выберите вариант\",\r\n  \"settings_title\": \"Настройки\",\r\n  \"simple_config\": \"Введите аргументы командной строки\",\r\n  \"start\": \"Запуск\",\r\n  \"status\": \"Статус\",\r\n  \"stop\": \"стоп\",\r\n  \"stop_task\": \"Остановить задание?\",\r\n  \"success_message\": \"Программа выполнена успешно!\\nНажмите OK для выхода\",\r\n  \"sure_you_want_to_exit\": \"Вы уверены, что хотите выйти?\",\r\n  \"sure_you_want_to_stop\": \"Вы уверены, что хотите остановить задание? \\nОстановка может повредить ваши данные!\",\r\n  \"uh_oh\": \"\\nБеда! Кажется, случилось что-то плохое. \\nОтправьте ошибку разработчику чтобы узнать, что пошло не так.\\n\\n{} \\t\\t\\n\\t\\t\",\r\n  \"validation_failed\": \"Одно или несколько полей не прошли валидацию\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/serbian.json",
    "content": "{\n  \"browse\": \"Pretraži\",\n  \"cancel\": \"Otkaži\",\n  \"checkbox_label\": \"Uključi\",\n  \"choose_colour\": \"Odaberite boju\",\n  \"choose_date\": \"Odaberite datum\",\n  \"choose_file\": \"Odaberite datoteku\",\n  \"choose_folder\": \"Odaberite direktorijum\",\n  \"choose_folders_msg\": \"Odaberite jedan ili više direktorijuma:\",\n  \"choose_folders_title\": \"Pregled direktorijuma\",\n  \"choose_one\": \"Odaberite jedan element\",\n  \"close\": \"Zatvori\",\n  \"close_program\": \"Zatvori program?\",\n  \"edit\": \"Uredi\",\n  \"enter_filename\": \"Unesite ime datoteke\",\n  \"error_required_fields\": \"Morate popuniti sva polja u obaveznoj sekciji!\",\n  \"error_title\": \"Greška\",\n  \"execution_finished\": \"Izvršavanje završeno\",\n  \"finished_error\": \"Greška se desila prilikom izvršavanja.\",\n  \"finished_forced_quit\": \"Izvršavanje prekinuto od strane korisnika\",\n  \"finished_msg\": \"Izvršavanje uspešno! Sada možete zatvoriti program.\",\n  \"finished_title\": \"Završeno\",\n  \"dropdown.no_matches\": \"Nisu pronađeni elementi koji zadovoljavaju kriterijum\",\n  \"ok\": \"Ok\",\n  \"open_file\": \"Otvori datoteku\",\n  \"open_files\": \"Otvori datoteke\",\n  \"optional_args_msg\": \"Opcioni argument\",\n  \"required_args_msg\": \"Obavezan argument\",\n  \"restart\": \"Ponovno pokretanje\",\n  \"running_msg\": \"Molimo sačekajte dok aplikacija izvrši sve zadatke. \\nOvo može potrajati nekoliko trenutaka\",\n  \"running_title\": \"Radni\",\n  \"select_date\": \"Odaberite datum\",\n  \"select_option\": \"Odaberite opciju\",\n  \"settings_title\": \"Podešavanja\",\n  \"simple_config\": \"Unesite argumente komandne linije\",\n  \"start\": \"Start\",\n  \"status\": \"Status\",\n  \"stop\": \"Stop\",\n  \"stop_task\": \"Zaustavi operaciju?\",\n  \"success_message\": \"Program izvršen uspešno!\",\n  \"sure_you_want_to_exit\": \"Jeste li sigurni da želite da izađete?\",\n  \"sure_you_want_to_stop\": \"Jeste li sigurni da želite zaustaviti izvršavanje zadatka? \\nPrekid može učiniti vaše podatke neupotrebljivima!\",\n  \"uh_oh\": \"\\nOh ne! Izgleda da je došlo do problema. \\nKopirajte tekst iz status prozora kako bi pomogli autoru da uvidi i ispravi grešku.\\n\",\n  \"validation_failed\": \"Jedno ili više polja nisu u skladu sa validacijom.\",\n  \"dialog_button_yes\": \"Da\",\n  \"dialog_button_no\": \"Ne\",\n  \"dialog_button_ok\": \"OK\"\n}\n"
  },
  {
    "path": "gooey/languages/spanish.json",
    "content": "{\r\n  \"browse\": \"Examinar\",\r\n  \"cancel\": \"Cancelar\",\r\n  \"choose_date\": \"Escoger fecha\",\r\n  \"choose_file\": \"Escoger archivo\",\r\n  \"choose_folder\": \"Escoger carpeta\",\r\n  \"choose_one\": \"Escoger uno(a)\",\r\n  \"close\": \"Cerrar\",\r\n  \"close_program\": \"¿Cerrar programa?\",\r\n  \"edit\": \"Editar\",\r\n  \"enter_filename\": \"Ingrese nombre de archivo\",\r\n  \"error_required_fields\": \"¡Debe rellenar todos los campos de la sección obligatoria!\",\r\n  \"error_title\": \"Error\",\r\n  \"execution_finished\": \"Ejecución completada\",\r\n  \"finished_error\": \"Ha ocurrido un error.\",\r\n  \"finished_forced_quit\": \"Programa cerrado por usuario\",\r\n  \"finished_msg\": \"¡Terminado! Ya puede cerrar el programa de forma segura.\",\r\n  \"finished_title\": \"Terminado\",\r\n  \"open_file\": \"Abrir archivo\",\r\n  \"optional_args_msg\": \"Parámetros opcionales\",\r\n  \"required_args_msg\": \"Parámetros obligatorios\",\r\n  \"restart\": \"Reiniciar\",\r\n  \"running_msg\": \"Por favor espera mientras la aplicación realiza sus tareas \\nPuede tardar unos instantes\",\r\n  \"running_title\": \"Ejecutando\",\r\n  \"select_option\": \"Seleccione opción\",\r\n  \"settings_title\": \"Ajustes\",\r\n  \"simple_config\": \"Introduzca parámetros de línea de comandos\",\r\n  \"start\": \"Empezar\",\r\n  \"status\": \"Estado\",\r\n  \"stop\": \"Parar\",\r\n  \"stop_task\": \"¿Detener tarea?\",\r\n  \"success_message\": \"¡EL programa terminó con éxito!\\nPresione el botón OK para salir\",\r\n  \"sure_you_want_to_exit\": \"¿Está seguro de que desea salir?\",\r\n  \"sure_you_want_to_stop\": \"¿Está seguro(a) de que desea detener la tarea? \\nLa interrupción puede corromper la data\",\r\n  \"uh_oh\": \"\\n¡Ops! Parece que hubo un problema. \\nEnvíe el error que aparece debajo a su desarrollador para comunicar el problema.\\n\",\r\n  \"validation_failed\": \"Uno o más campos fallaron en la validación\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/tamil.json",
    "content": "{\n  \"browse\": \"உலாவு\",\n  \"cancel\": \"ரத்துசெய்\",\n  \"checkbox_label\": \"இயக்கு\",\n  \"choose_colour\": \"வண்ணத்தைத் தேர்வுசெய்க\",\n  \"choose_date\": \"தேதியைத் தேர்வுசெய்க\",\n  \"choose_time\": \"நேரத்தைத் தேர்வுசெய்க\",\n  \"choose_file\": \"கோப்பை தேர்வுசெய்க\",\n  \"choose_folder\": \"கோப்புறையைத் தேர்வுசெய்க\",\n  \"choose_folders_msg\": \"ஒன்று அல்லது அதற்கு மேற்பட்ட கோப்புறைகளைத் தேர்வுசெய்க:\",\n  \"choose_folders_title\": \"கோப்புறைகளுக்கு உலாவுக\",\n  \"choose_one\": \"ஒன்றை தேர்ந்தெடு\",\n  \"close\": \"மூடு\",\n  \"close_program\": \"நிரலை மூடவா?\",\n  \"edit\": \"திருத்து\",\n  \"enter_filename\": \"கோப்பு பெயரை உள்ளிடவும்\",\n  \"error_required_fields\": \"தேவையான பிரிவில் உள்ள அனைத்து உரைப்பகுதியும் நிரப்ப வேண்டும்!\",\n  \"error_title\": \"பிழை\",\n  \"execution_finished\": \"செயல்படுத்தல் முடிந்தது\",\n  \"finished_error\": \"தவறு நிகழ்ந்துவிட்டது.\",\n  \"finished_forced_quit\": \"பயனரால் நிறுத்தப்பட்டது\",\n  \"finished_msg\": \"அனைத்தும் முடிந்தது! நீங்கள் இப்போது நிரலை பாதுகாப்பாக மூடலாம்.\",\n  \"finished_title\": \"முடிந்தது\",\n  \"dropdown.no_matches\": \"பொருத்தங்கள் எதுவும் கிடைக்கவில்லை\",\n  \"ok\": \"சரி\",\n  \"open_file\": \"கோப்பைத் திறக்க\",\n  \"open_files\": \"கோப்புகளைத் திறக்க\",\n  \"optional_args_msg\": \"விருப்ப வாதங்கள்\",\n  \"required_args_msg\": \"தேவையான வாதங்கள்\",\n  \"restart\": \"மறுதொடக்கம்\",\n  \"running_msg\": \"பயன்பாடு அதன் பணிகளைச் செய்யும்போது காத்திருக்கவும். \\nஇதற்கு சில தருணங்கள் ஆகலாம்\",\n  \"running_title\": \"இயங்குகிறது\",\n  \"select_date\": \"தேதியைத் தேர்ந்தெடுக்கவும்\",\n  \"select_time\": \"நேரத்தைத் தேர்ந்தெடுக்கவும்\",\n  \"select_option\": \"விருப்பத்தைத் தேர்ந்தெடுக்கவும்\",\n  \"settings_title\": \"அமைப்புகள்\",\n  \"simple_config\": \"கட்டளை வரி வாதங்களை உள்ளிடவும்\",\n  \"start\": \"தொடங்கு\",\n  \"status\": \"நிலை\",\n  \"stop\": \"நிறுத்து\",\n  \"stop_task\": \"பணியை நிறுத்தவா?\",\n  \"success_message\": \"நிரல் வெற்றிகரமாக முடிந்தது!\",\n  \"sure_you_want_to_exit\": \"நிச்சயமாக நீங்கள் வெளியேற வேண்டுமா?\",\n  \"sure_you_want_to_stop\": \"நீங்கள் நிச்சயமாக பணியை நிறுத்த விரும்புகிறீர்களா? \\nதடங்கல் உங்கள் தரவை சிதைக்கும்!\",\n  \"uh_oh\": \"\\nஅட டா! சிக்கல் இருப்பதாகத் தெரிகிறது. \\nதவறு என்ன என்பதை உங்கள் படைப்பாளருக்கு தெரியப்படுத்த, நிலை சாளரத்திலிருந்து உரையை நகலெடுக்கவும்.\\n\",\n  \"validation_failed\": \"ஒன்று அல்லது அதற்கு மேற்பட்ட புலங்கள் சரிபார்ப்பில் தோல்வியுற்றன.\",\n  \"dialog_button_yes\": \"ஆம்\",\n  \"dialog_button_no\": \"இல்லை\",\n  \"dialog_button_ok\": \"சரி\"\n}\n"
  },
  {
    "path": "gooey/languages/traditional-chinese.json",
    "content": "{\r\n  \"browse\": \"瀏覽\",\r\n  \"cancel\": \"取消\",\r\n  \"choose_date\": \"(translate me) Choose Date\",\r\n  \"choose_file\": \"(translate me) Choose file\",\r\n  \"choose_folder\": \"(translate me) Choose folder\",\r\n  \"choose_one\": \"(translate me) Choose One\",\r\n  \"close\": \"關閉\",\r\n  \"close_program\": \"確認退出?\",\r\n  \"edit\": \"編輯\",\r\n  \"enter_filename\": \"(translate me) Enter filename\",\r\n  \"error_required_fields\": \"必須填充所有的必填選項!\",\r\n  \"error_title\": \"錯誤\",\r\n  \"execution_finished\": \"運行完成\",\r\n  \"finished_error\": \"運行出錯\",\r\n  \"finished_forced_quit\": \"(translate me) Terminated by user\",\r\n  \"finished_msg\": \"運行結束! 您現在可以安全的關閉程序。\",\r\n  \"finished_title\": \"運行結束\",\r\n  \"open_file\": \"(translate me) Open File\",\r\n  \"optional_args_msg\": \"可選參數\",\r\n  \"required_args_msg\": \"必填參數\",\r\n  \"restart\": \"重啟\",\r\n  \"running_msg\": \"請稍等，程序正在運行。 \\n這可能將需要一定時間。\",\r\n  \"running_title\": \"運行中\",\r\n  \"select_option\": \"(translate me) Select Option\",\r\n  \"settings_title\": \"設置\",\r\n  \"simple_config\": \"輸入運行參數\",\r\n  \"start\": \"開始\",\r\n  \"status\": \"狀態\",\r\n  \"stop\": \"停止\",\r\n  \"stop_task\": \"您確定要停止任務？\",\r\n  \"success_message\": \"程序成功退出!\\n按OK鍵退出\",\r\n  \"sure_you_want_to_exit\": \"您確定要退出?\",\r\n  \"sure_you_want_to_stop\": \"您確定您想要停止任務？ \\n這可能會損壞您的數據！\",\r\n  \"uh_oh\": \"\\nUh 糟糕! 程序出現了故障。 \\n覆制下方的錯誤信息給開發者。\\n\\n{} \\t\\t\\n\\t\\t\",\r\n  \"validation_failed\": \"(translate me) One or more fields failed validation\"\r\n}\r\n"
  },
  {
    "path": "gooey/languages/turkish.json",
    "content": "{\n  \"browse\": \"Göz at\",\n  \"cancel\": \"İptal\",\n  \"checkbox_label\": \"Etkinleştir\",\n  \"choose_date\": \"Tarih seç\",\n  \"choose_file\": \"Dosya seç\",\n  \"choose_folder\": \"Klasör seç\",\n  \"choose_folders_msg\": \"Bir veya daha çok klasör seçiniz:\",\n  \"choose_folders_title\": \"Klasörlere göz at\",\n  \"choose_one\": \"Birini seç\",\n  \"close\": \"Kapat\",\n  \"close_program\": \"Program kapatılsın mı?\",\n  \"edit\": \"Düzenle\",\n  \"enter_filename\": \"Dosya adı giriniz\",\n  \"error_required_fields\": \"Gerekli olarak işaretlenen tüm alanları doldurunuz!\",\n  \"error_title\": \"Hata\",\n  \"execution_finished\": \"Çalıştırma sona erdi\",\n  \"finished_error\": \"Bir hata oluştu.\",\n  \"finished_forced_quit\": \"Kullanıcı tarafından sonlandırıldı\",\n  \"finished_msg\": \"Her şey tamamlandı! Şimdi programı güvenle kapatabilirsiniz.\",\n  \"finished_title\": \"Tamamlandı\",\n  \"ok\": \"Tamam\",\n  \"open_file\": \"Dosya aç\",\n  \"open_files\": \"Dosyaları aç\",\n  \"optional_args_msg\": \"İsteğe bağlı argümanlar\",\n  \"required_args_msg\": \"Gerekli argümanlar\",\n  \"restart\": \"Yeniden başlat\",\n  \"running_msg\": \"Lütfen uygulama görevleri yerine getirirken bekleyiniz. \\nBu işlem biraz vakit alabilir\",\n  \"running_title\": \"Çalışıyor\",\n  \"select_date\": \"Tarih seçiniz\",\n  \"select_option\": \"Seçim yapınız\",\n  \"settings_title\": \"Ayarlar\",\n  \"simple_config\": \"Komut satırı argümanlarını giriniz\",\n  \"start\": \"Başlat\",\n  \"status\": \"Durum\",\n  \"stop\": \"Durdur\",\n  \"stop_task\": \"Görev durdurulsun mu?\",\n  \"success_message\": \"Program başarıyla tamamlandı!\",\n  \"sure_you_want_to_exit\": \"Çıkmak istediğinizden emin misiniz?\",\n  \"sure_you_want_to_stop\": \"Görevi durdurmak istediğinizden emin misiniz? \\nÇalışmanın yarıda kesilmesi veri kaybına sebep olabilir!\",\n  \"uh_oh\": \"\\nHay aksi! Bir sorun oluşmuş gibi gözüküyor. \\nGeliştiriciyi yanlış giden durumdan haberdar etmek için durum penceresindeki metni kopyalayınız.\\n\",\n  \"validation_failed\": \"Bir veya birden fazla alanda geçersiz veri bulunuyor.\"\n}\n"
  },
  {
    "path": "gooey/languages/vietnamese.json",
    "content": "{\r\n  \"browse\": \"Duyệt\",\r\n  \"cancel\": \"Hủy\",\r\n  \"choose_date\": \"(translate me) Choose Date\",\r\n  \"choose_file\": \"(translate me) Choose file\",\r\n  \"choose_folder\": \"(translate me) Choose folder\",\r\n  \"choose_one\": \"(translate me) Choose One\",\r\n  \"close\": \"Đóng\",\r\n  \"close_program\": \"Có đóng cửa sổ?\",\r\n  \"edit\": \"Hiệu chỉnh\",\r\n  \"enter_filename\": \"(translate me) Enter filename\",\r\n  \"error_required_fields\": \"Bạn phải nhập các tham số bắt buộc\",\r\n  \"error_title\": \"Lỗi\",\r\n  \"execution_finished\": \"Kết thúc thực thi\",\r\n  \"finished_error\": \"Đã xảy ra một lỗi.\",\r\n  \"finished_forced_quit\": \"(translate me) Terminated by user\",\r\n  \"finished_msg\": \"Hoàn thành! Bạn có thể yên tâm đóng cửa sổ này\",\r\n  \"finished_title\": \"Kết thúc\",\r\n  \"open_file\": \"(translate me) Open File\",\r\n  \"optional_args_msg\": \"Các tham số tùy chọn\",\r\n  \"required_args_msg\": \"Các tham số bắt buộc\",\r\n  \"restart\": \"Chạy lại\",\r\n  \"running_msg\": \"Hãy đợi chương trình thực hiện các tác vụ. \\nCó thể kéo dài một lát.\",\r\n  \"running_title\": \"Đang chạy\",\r\n  \"select_option\": \"(translate me) Select Option\",\r\n  \"settings_title\": \"Cấu hình\",\r\n  \"simple_config\": \"Hãy nhập các tham số dòng lệnh!\",\r\n  \"start\": \"Bắt đầu\",\r\n  \"status\": \"Trạng thái\",\r\n  \"stop\": \"Kết thúc\",\r\n  \"stop_task\": \"Có dừng tác vụ?\",\r\n  \"success_message\": \"Chương trình kết thúc thành công.\",\r\n  \"sure_you_want_to_exit\": \"Bạn có thực sự muốn thoát?\",\r\n  \"sure_you_want_to_stop\": \"Bạn có thực sự muốn dừng tác vụ? \\nNgừng giữa chừng có thể làm hỏng dữ liệu của bạn!\",\r\n  \"uh_oh\": \"\\nỒ! Có vấn đề. \\nHãy sao chép các văn bản trong cửa sổ trạng thái và gửi cho tác giả phần mềm!\\n\",\r\n  \"validation_failed\": \"(translate me) One or more fields failed validation\"\r\n}\r\n"
  },
  {
    "path": "gooey/python_bindings/__init__.py",
    "content": "__author__ = 'Chris'\r\n"
  },
  {
    "path": "gooey/python_bindings/argparse_to_json.py",
    "content": "\"\"\"\r\nConverts argparse parser actions into json \"Build Specs\"\r\n\"\"\"\r\nimport argparse\r\nimport json\r\nimport os\r\nimport sys\r\nfrom argparse import (\r\n    _CountAction,\r\n    _HelpAction,\r\n    _StoreConstAction,\r\n    _StoreFalseAction,\r\n    _StoreTrueAction,\r\n    _StoreAction,\r\n    _SubParsersAction,\r\n    _VersionAction, _MutuallyExclusiveGroup)\r\nfrom collections import OrderedDict\r\nfrom functools import partial\r\nfrom uuid import uuid4\r\n\r\nfrom gooey.python_bindings.gooey_parser import GooeyParser\r\nfrom gooey.util.functional import merge, getin, identity, assoc\r\nfrom gooey.gui.components.options.validators import validators\r\nfrom gooey.gui.components.options.validators import collect_errors\r\n\r\nVALID_WIDGETS = (\r\n    'FileChooser',\r\n    'MultiFileChooser',\r\n    'FileSaver',\r\n    'DirChooser',\r\n    'DateChooser',\r\n    'TimeChooser',\r\n    'TextField',\r\n    'Dropdown',\r\n    'Counter',\r\n    'RadioGroup',\r\n    'CheckBox',\r\n    'BlockCheckbox',\r\n    'MultiDirChooser',\r\n    'Textarea',\r\n    'PasswordField',\r\n    'Listbox',\r\n    'FilterableDropdown',\r\n    'IntegerField',\r\n    'DecimalField',\r\n    'Slider'\r\n)\r\n\r\n\r\n# TODO: validate Listbox. When required, nargs must be +\r\n\r\nclass UnknownWidgetType(Exception):\r\n    pass\r\n\r\n\r\nclass UnsupportedConfiguration(Exception):\r\n    pass\r\n\r\n\r\n\r\n# TODO: merge the default foreground and bg colors from the\r\n# baseline build_spec\r\nitem_default = {\r\n    'error_color': '#ea7878',\r\n    'label_color': '#000000',\r\n    'help_color': '#363636',\r\n    'full_width': False,\r\n    'validator': {\r\n        'type': 'local',\r\n        'test': 'lambda x: True',\r\n        'message': ''\r\n    },\r\n    'external_validator': {\r\n        'cmd': '',\r\n    }\r\n}\r\n\r\n\r\ndef convert(parser, **kwargs):\r\n    \"\"\"\r\n    Converts a parser into a JSON representation\r\n\r\n    TODO:\r\n    This is in desperate need of refactor. It wasn't build with supporting\r\n    all (or any) of this configuration in mind. The use of global defaults\r\n    are actively getting in the way of easily adding more configuration options.\r\n\r\n    Pain points:\r\n        - global data sprinkled throughout the calls\r\n        - local data threaded through calls\r\n        - totally unclear what the data structures even hold\r\n        - everything is just mushed together and gross. unwinding argparse also\r\n          builds validators, handles coercion, and so on...\r\n        - converts to an entirely bespoke json mini-language that mirrors\r\n          the internal structure of argparse.\r\n    Refactor plan:\r\n        - Investigate restructuring the core data representation. As is, it is ad-hoc\r\n          and largely tied to argparse's goofy internal structure. May be worth moving to\r\n          something \"standard.\" Though, not sure what the options are.\r\n        - standardize how these things read from the environment. No global in some local in others.\r\n        - Investigate splitting the whole thing into phases (ala Ring). Current thinking is that\r\n          a lot of this stuff could be modelled more like pluggable upgrades to the base structure.\r\n        - I want to add a helpful validation stage to catch user errors like invalid gooey_options\r\n    \"\"\"\r\n\r\n    group_defaults = {\r\n        'legacy': {\r\n            'required_cols': kwargs['required_cols'],\r\n            'optional_cols': kwargs['optional_cols']\r\n        },\r\n        'columns': 2,\r\n        'padding': 10,\r\n        'show_border': False\r\n    }\r\n\r\n    assert_subparser_constraints(parser)\r\n    x = {\r\n        'layout': 'standard',\r\n        'widgets': OrderedDict(\r\n            (choose_name(name, sub_parser), {\r\n                'command': name,\r\n                'name': choose_name(name, sub_parser),\r\n                'help': get_subparser_help(sub_parser),\r\n                'description': '',\r\n                'contents': process(sub_parser,\r\n                                    getattr(sub_parser, 'widgets', {}),\r\n                                    getattr(sub_parser, 'options', {}),\r\n                                    group_defaults)\r\n            }) for name, sub_parser in iter_parsers(parser))\r\n    }\r\n\r\n    if kwargs.get('use_legacy_titles'):\r\n        return apply_default_rewrites(x)\r\n    return x\r\n\r\n\r\ndef process(parser, widget_dict, options, group_defaults):\r\n    mutex_groups = parser._mutually_exclusive_groups\r\n    raw_action_groups = [extract_groups(group, group_defaults) for group in parser._action_groups\r\n                         if group._group_actions]\r\n    corrected_action_groups = reapply_mutex_groups(mutex_groups, raw_action_groups)\r\n\r\n    return categorize2(strip_empty(corrected_action_groups), widget_dict, options)\r\n\r\n\r\ndef strip_empty(groups):\r\n    return [group for group in groups if group['items']]\r\n\r\n\r\ndef assert_subparser_constraints(parser):\r\n    if has_subparsers(parser._actions):\r\n        if has_required(parser._actions):\r\n            raise UnsupportedConfiguration(\r\n                \"Gooey doesn't currently support top level required arguments \"\r\n                \"when subparsers are present.\")\r\n\r\n\r\ndef iter_parsers(parser):\r\n    ''' Iterate over name, parser pairs '''\r\n    try:\r\n        return get_subparser(parser._actions).choices.items()\r\n    except:\r\n        return iter([('::gooey/default', parser)])\r\n\r\n\r\ndef get_subparser_help(parser):\r\n    if isinstance(parser, GooeyParser):\r\n        return getattr(parser.parser, 'usage', '')\r\n    else:\r\n        return getattr(parser, 'usage', '')\r\n\r\n\r\ndef extract_groups(action_group, group_defaults):\r\n    '''\r\n    Recursively extract argument groups and associated actions\r\n    from ParserGroup objects\r\n    '''\r\n    return {\r\n        'name': action_group.title,\r\n        'description': action_group.description,\r\n        'items': [action for action in action_group._group_actions\r\n                  if not is_help_message(action)],\r\n        'groups': [extract_groups(group, group_defaults)\r\n                   for group in action_group._action_groups],\r\n        'options': handle_option_merge(\r\n            group_defaults,\r\n            getattr(action_group, 'gooey_options', {}),\r\n            action_group.title)\r\n    }\r\n\r\n\r\ndef handle_option_merge(group_defaults, incoming_options, title):\r\n    \"\"\"\r\n    Merges a set of group defaults with incoming options.\r\n\r\n    A bunch of ceremony here is to ensure backwards compatibility with the old\r\n    num_required_cols and num_optional_cols decorator args. They are used as\r\n    the seed values for the new group defaults which keeps the old behavior\r\n    _mostly_ in tact.\r\n\r\n    Known failure points:\r\n        * Using custom groups / names. No 'positional arguments' group\r\n          means no required_cols arg being honored\r\n        * Non-positional args marked as required. It would take group\r\n          shuffling along the lines of that required to make\r\n          mutually exclusive groups show in the correct place. In short, not\r\n          worth the complexity for a legacy feature that's been succeeded by\r\n          a much more powerful alternative.\r\n    \"\"\"\r\n    if title == 'positional arguments':\r\n        # the argparse default 'required' bucket\r\n        req_cols = getin(group_defaults, ['legacy', 'required_cols'], 2)\r\n        new_defaults = assoc(group_defaults, 'columns', req_cols)\r\n        return merge(new_defaults, incoming_options)\r\n    else:\r\n        opt_cols = getin(group_defaults, ['legacy', 'optional_cols'], 2)\r\n        new_defaults = assoc(group_defaults, 'columns', opt_cols)\r\n        return merge(new_defaults, incoming_options)\r\n\r\n\r\n\r\ndef apply_default_rewrites(spec):\r\n    top_level_subgroups = list(spec['widgets'].keys())\r\n\r\n    for subgroup in top_level_subgroups:\r\n        path = ['widgets', subgroup, 'contents']\r\n        contents = getin(spec, path)\r\n        for group in contents:\r\n            if group['name'] == 'positional arguments':\r\n                group['name'] = 'required_args_msg'\r\n            if group['name'] == 'optional arguments':\r\n                group['name'] = 'optional_args_msg'\r\n    return spec\r\n\r\n\r\ndef contains_actions(a, b):\r\n    ''' check if any actions(a) are present in actions(b) '''\r\n    return set(a).intersection(set(b))\r\n\r\n\r\ndef reapply_mutex_groups(mutex_groups, action_groups):\r\n    # argparse stores mutually exclusive groups independently\r\n    # of all other groups. So, they must be manually re-combined\r\n    # with the groups/subgroups to which they were originally declared\r\n    # in order to have them appear in the correct location in the UI.\r\n    #\r\n    # Order is attempted to be preserved by inserting the MutexGroup\r\n    # into the _actions list at the first occurrence of any item\r\n    # where the two groups intersect\r\n    def swap_actions(actions):\r\n        for mutexgroup in mutex_groups:\r\n            mutex_actions = mutexgroup._group_actions\r\n            if contains_actions(mutex_actions, actions):\r\n                # make a best guess as to where we should store the group\r\n                targetindex = actions.index(mutexgroup._group_actions[0])\r\n                # insert the _ArgumentGroup container\r\n                actions[targetindex] = mutexgroup\r\n                # remove the duplicated individual actions\r\n                actions = [action for action in actions\r\n                        if action not in mutex_actions]\r\n        return actions\r\n\r\n    return [group.update({'items': swap_actions(group['items'])}) or group\r\n            for group in action_groups]\r\n\r\n\r\ndef categorize2(groups, widget_dict, options):\r\n    defaults = {'label_color': '#000000', 'description_color': '#363636'}\r\n    return [{\r\n        'name': group['name'],\r\n        'items': list(categorize(group['items'], widget_dict, options)),\r\n        'groups': categorize2(group['groups'], widget_dict, options),\r\n        'description': group['description'],\r\n        'options': merge(defaults ,group['options'])\r\n    } for group in groups]\r\n\r\n\r\ndef categorize(actions, widget_dict, options):\r\n    _get_widget = partial(get_widget, widget_dict)\r\n    for action in actions:\r\n        if is_version(action):\r\n            yield action_to_json(action, _get_widget(action, 'CheckBox'), options)\r\n\r\n        elif is_mutex(action):\r\n            yield build_radio_group(action, widget_dict, options)\r\n\r\n        elif is_standard(action):\r\n            yield action_to_json(action, _get_widget(action, 'TextField'), options)\r\n        \r\n        elif is_writemode_file(action):\r\n            yield action_to_json(action, _get_widget(action, 'FileSaver'), options)\r\n\r\n        elif is_readmode_file(action):\r\n            yield action_to_json(action, _get_widget(action, 'FileChooser'), options)\r\n\r\n        elif is_choice(action):\r\n            yield action_to_json(action, _get_widget(action, 'Dropdown'), options)\r\n\r\n        elif is_flag(action):\r\n            yield action_to_json(action, _get_widget(action, 'CheckBox'), options)\r\n\r\n        elif is_counter(action):\r\n            _json = action_to_json(action, _get_widget(action, 'Counter'), options)\r\n            # pre-fill the 'counter' dropdown\r\n            _json['data']['choices'] = list(map(str, range(0, 11)))\r\n            yield _json\r\n        else:\r\n            raise UnknownWidgetType(action)\r\n\r\n\r\ndef get_widget(widgets, action, default):\r\n    supplied_widget = widgets.get(action.dest, None)\r\n    return supplied_widget or default\r\n\r\n\r\ndef is_required(action):\r\n    '''\r\n    _actions possessing the `required` flag and not implicitly optional\r\n    through `nargs` being '*' or '?'\r\n    '''\r\n    return not isinstance(action, _SubParsersAction) and (\r\n    action.required == True and action.nargs not in ['*', '?'])\r\n\r\n\r\ndef is_mutex(action):\r\n    return isinstance(action, argparse._MutuallyExclusiveGroup)\r\n\r\n\r\ndef has_required(actions):\r\n    return list(filter(None, list(filter(is_required, actions))))\r\n\r\n\r\ndef is_subparser(action):\r\n    return isinstance(action, _SubParsersAction)\r\n\r\n\r\ndef has_subparsers(actions):\r\n    return list(filter(is_subparser, actions))\r\n\r\n\r\ndef get_subparser(actions):\r\n    return list(filter(is_subparser, actions))[0]\r\n\r\n\r\ndef is_optional(action):\r\n    '''\r\n    _actions either not possessing the `required` flag or implicitly optional\r\n    through `nargs` being '*' or '?'\r\n    '''\r\n    return (not action.required) or action.nargs in ['*', '?']\r\n\r\n\r\ndef is_choice(action):\r\n    ''' action with choices supplied '''\r\n    return action.choices\r\n\r\ndef is_file(action):\r\n    ''' action with FileType '''\r\n    return isinstance(action.type, argparse.FileType)\r\n\r\ndef is_readmode_file(action):\r\n    return is_file(action) and 'r' in action.type._mode\r\n\r\ndef is_writemode_file(action):\r\n    # FileType uses the same modes as the builtin `open`\r\n    # as such, all modes that aren't explicitly `r` (which is\r\n    # also the default) are writable or read/writable, thus\r\n    # making a FileChooser a good choice.\r\n    return is_file(action) and 'r' not in action.type._mode\r\n\r\ndef is_version(action):\r\n    return isinstance(action, _VersionAction) or issubclass(type(action), _VersionAction)\r\n\r\n\r\ndef is_standard(action):\r\n    \"\"\" actions which are general \"store\" instructions.\r\n    e.g. anything which has an argument style like:\r\n       $ script.py -f myfilename.txt\r\n    \"\"\"\r\n    boolean_actions = (\r\n        _StoreConstAction, _StoreFalseAction,\r\n        _StoreTrueAction\r\n    )\r\n    return (not action.choices\r\n            and not isinstance(action.type, argparse.FileType)\r\n            and not isinstance(action, (_CountAction, _HelpAction))\r\n            # subclass checking is to handle the GooeyParser case\r\n            # where Action get wrapped in a custom class\r\n            and not issubclass(type(action), boolean_actions)\r\n            and type(action) not in boolean_actions)\r\n\r\n\r\ndef is_flag(action):\r\n    \"\"\" _actions which are either storeconst, store_bool, etc.. \"\"\"\r\n    # TODO: refactor to isinstance tuple form\r\n    action_types = [_StoreTrueAction, _StoreFalseAction, _StoreConstAction]\r\n    return (any(list(map(lambda Action: isinstance(action, Action), action_types)))\r\n            or issubclass(type(action), (_StoreTrueAction, _StoreFalseAction, _StoreConstAction)))\r\n\r\n\r\n\r\ndef is_counter(action):\r\n    \"\"\" _actions which are of type _CountAction \"\"\"\r\n    return isinstance(action, _CountAction)\r\n\r\n\r\ndef is_default_progname(name, subparser):\r\n    return subparser.prog == '{} {}'.format(os.path.split(sys.argv[0])[-1], name)\r\n\r\n\r\ndef is_help_message(action):\r\n    return isinstance(action, _HelpAction)\r\n\r\n\r\ndef choose_name(name, subparser):\r\n    return name if is_default_progname(name, subparser) else subparser.prog\r\n\r\n\r\ndef build_radio_group(mutex_group, widget_group, options):\r\n  dests = [action.dest for action in mutex_group._group_actions]\r\n  return {\r\n    'id': 'group_' + '_'.join(dests),\r\n    'type': 'RadioGroup',\r\n    'cli_type': 'optional',\r\n    'group_name': 'Choose Option',\r\n    'required': mutex_group.required,\r\n    'options': merge(item_default, getattr(mutex_group, 'gooey_options', {})),\r\n    'data': {\r\n      'commands': [action.option_strings for action in mutex_group._group_actions],\r\n      'widgets': list(categorize(mutex_group._group_actions, widget_group, options))\r\n    }\r\n  }\r\n\r\n\r\ndef action_to_json(action, widget, options):\r\n    dropdown_types = {'Listbox', 'Dropdown', 'Counter'}\r\n    if action.required:\r\n        # Text fields get a default check that user input is present\r\n        # and not just spaces, dropdown types get a simplified\r\n        # is-it-present style check\r\n        validator = ('user_input and not user_input.isspace()'\r\n                     if widget not in dropdown_types\r\n                     else 'user_input')\r\n        error_msg = 'This field is required'\r\n    else:\r\n        # not required; do nothing;\r\n        validator = 'True'\r\n        error_msg = ''\r\n\r\n    base = merge(item_default, {\r\n        'validator': {\r\n            'type': 'ExpressionValidator',\r\n            'test': validator,\r\n            'message': error_msg\r\n        },\r\n    })\r\n\r\n    if (options.get(action.dest) or {}).get('initial_value') != None:\r\n        value = options[action.dest]['initial_value']\r\n        options[action.dest]['initial_value'] = handle_initial_values(action, widget, value)\r\n    default = handle_initial_values(action, widget, action.default)\r\n    if default == argparse.SUPPRESS:\r\n        default = None\r\n\r\n\r\n\r\n    final_options = merge(base, options.get(action.dest) or {})\r\n    validate_gooey_options(action, widget, final_options)\r\n\r\n    return {\r\n        'id': action.dest,\r\n        'type': widget,\r\n        'cli_type': choose_cli_type(action),\r\n        'required': action.required,\r\n        'data': {\r\n            'display_name': action.metavar or action.dest,\r\n            'help': (action.help or '').replace('%%', '%'),\r\n            'required': action.required,\r\n            'nargs': action.nargs or '',\r\n            'commands': action.option_strings,\r\n            'choices': list(map(str, action.choices)) if action.choices else [],\r\n            'default': default,\r\n            'dest': action.dest,\r\n        },\r\n        'options': final_options\r\n    }\r\n\r\n\r\ndef validate_gooey_options(action, widget, options):\r\n    \"\"\"Very basic field validation / sanity checking for\r\n    the time being.\r\n\r\n    Future plans are to assert against the options and actions together\r\n    to facilitate checking that certain options like `initial_selection` in\r\n    RadioGroups map to a value which actually exists (rather than exploding\r\n    at runtime with an unhelpful error)\r\n\r\n    Additional problems with the current approach is that no feedback is given\r\n    as to WHERE the issue took place (in terms of stacktrace). Which means we should\r\n    probably explode in GooeyParser proper rather than trying to collect all the errors here.\r\n    It's not super ideal in that the user will need to run multiple times to\r\n    see all the issues, but, ultimately probably less annoying that trying to\r\n    debug which `gooey_option` key had an issue in a large program.\r\n\r\n    That said \"better is the enemy of done.\" This is good enough for now. It'll be\r\n    a TODO: better validation \r\n    \"\"\"\r\n    errors = collect_errors(validators, options)\r\n    if errors:\r\n        from pprint import pformat\r\n        raise ValueError(str(action.dest) + str(pformat(errors)))\r\n\r\n\r\ndef choose_cli_type(action):\r\n    return 'positional' \\\r\n            if action.required and not action.option_strings \\\r\n            else 'optional'\r\n\r\n\r\ndef coerce_default(default, widget):\r\n    \"\"\"coerce a default value to the best appropriate type for\r\n    ingestion into wx\"\"\"\r\n    dispatcher = {\r\n        'Listbox': clean_list_defaults,\r\n        'Dropdown': safe_string,\r\n        'Counter': safe_string\r\n    }\r\n    # Issue #321:\r\n    # Defaults for choice types must be coerced to strings\r\n    # to be able to match the stringified `choices` used by `wx.ComboBox`\r\n    cleaned = clean_default(default)\r\n\r\n    # dispatch to the appropriate cleaning function, or return the value\r\n    # as is if no special handler is present\r\n    return dispatcher.get(widget, identity)(cleaned)\r\n\r\n\r\ndef handle_initial_values(action, widget, value):\r\n    handlers = [\r\n        [textinput_with_nargs_and_list_default, coerse_nargs_list],\r\n        [is_widget('Listbox'), clean_list_defaults],\r\n        [is_widget('Dropdown'), coerce_str],\r\n        [is_widget('Counter'), safe_string]\r\n    ]\r\n    for matches, apply_coercion in handlers:\r\n        if matches(action, widget):\r\n            return apply_coercion(value)\r\n    return clean_default(value)\r\n\r\n\r\ndef coerse_nargs_list(default):\r\n    \"\"\"\r\n    nargs=* and defaults which are collection types\r\n    must be transformed into a CLI equivalent form. So, for\r\n    instance, ['one two', 'three'] => \"one two\" \"three\"\r\n\r\n    This only applies when the target widget is a text input. List\r\n    based widgets such as Listbox should keep their defaults in list form\r\n\r\n    Without this transformation, `add_arg('--foo', default=['a b'], nargs='*')` would show up in\r\n    the UI as the literal string `['a b']` brackets and all.\r\n    \"\"\"\r\n    return ' '.join('\"{}\"'.format(x) for x in default)\r\n\r\ndef is_widget(name):\r\n    def equals(action, widget):\r\n        return widget == name\r\n    return equals\r\n\r\n\r\ndef textinput_with_nargs_and_list_default(action, widget):\r\n    \"\"\"\r\n    Vanilla TextInputs which have nargs options which produce lists (i.e.\r\n    nargs +, *, N, or REMAINDER) need to have their default values transformed\r\n    into CLI style space-separated entries when they're supplied as a list of values\r\n    on the Python side.\r\n    \"\"\"\r\n    return (\r\n        widget in {'TextField', 'Textarea', 'PasswordField'}\r\n        and (isinstance(action.default, list) or isinstance(action.default, tuple))\r\n        and is_list_based_nargs(action))\r\n\r\n\r\ndef is_list_based_nargs(action):\r\n    \"\"\" \"\"\"\r\n    return isinstance(action.nargs, int) or action.nargs in {'*', '+', '...'}\r\n\r\n\r\n\r\ndef clean_list_defaults(default_values):\r\n    \"\"\"\r\n    Listbox's default's can be passed as a single value\r\n    or a collection of values (due to multiple selections). The list interface\r\n    is standardized on for ease.\r\n    \"\"\"\r\n    wrapped_values = ([default_values]\r\n                      if isinstance(default_values, str)\r\n                      else default_values)\r\n    return [safe_string(value) for value in wrapped_values or []]\r\n\r\n\r\ndef clean_default(default):\r\n    \"\"\"\r\n    Attempts to safely coalesce the default value down to\r\n    a valid JSON type.\r\n    \"\"\"\r\n    try:\r\n        json.dumps(default)\r\n        return default\r\n    except TypeError as e:\r\n        # see: Issue #377\r\n        # if is isn't json serializable (i.e. primitive data) there's nothing\r\n        # useful for Gooey to do with it (since Gooey deals in primitive data\r\n        # types). So the correct behavior is dropping them. This affects ONLY\r\n        # gooey, not the client code.\r\n        return None\r\n\r\n\r\ndef safe_string(value):\r\n    \"\"\"\r\n    Coerce a type to string as long as it isn't None or Boolean\r\n    TODO: why do I have this special boolean case..?\r\n    \"\"\"\r\n    if value is None or isinstance(value, bool):\r\n        return value\r\n    else:\r\n        return str(value)\r\n\r\n\r\ndef coerce_str(value):\r\n    \"\"\"\r\n    Coerce the incoming type to string as long as it isn't None\r\n    \"\"\"\r\n    return str(value) if value is not None else value\r\n\r\n\r\n\r\ndef this_is_a_comment(action, widget):\r\n    \"\"\"\r\n    TODO:\r\n     - better handling of nargs.\r\n     - allowing a class of \"Narg'able\" widget variants that allow dynamically adding options.\r\n    Below are some rough notes on the current widgets and their nargs behavior (or lack of)\r\n    \"\"\"\r\n\r\n    asdf = [\r\n        # choosers are all currently treated as\r\n        # singular inputs regardless of nargs status.\r\n        'FileChooser',\r\n        'MultiFileChooser',\r\n        'FileSaver',\r\n        'DirChooser',\r\n        'DateChooser',\r\n        'TimeChooser',\r\n\r\n        # radiogroup requires no special logic. Delegates to internal widgets\r\n        'RadioGroup',\r\n        # nargs invalid\r\n        'CheckBox',\r\n        # nargs invalid\r\n        'BlockCheckbox',\r\n\r\n        # currently combines everything into a single, system _sep separated string\r\n        # potential nargs behavior\r\n        #   input: - each item gets a readonly textbox?\r\n        #          - each item is its own editable widget?\r\n        #          - problem with this idea is selected a ton of files would break the UI.\r\n        #            maybe a better option would be to expose what's been added as a small\r\n        #            list view? That way its a fixed size even if they choose 100s of files.\r\n        #\r\n        'MultiDirChooser',\r\n        # special case. convert default to list of strings\r\n        'Listbox',\r\n\r\n        # special cases. coerce default to string\r\n        'Dropdown',\r\n        'Counter',\r\n\r\n        # nargs behavior:\r\n        #   - convert to space separated list of strings\r\n        'TextField',\r\n        'Textarea',\r\n        'PasswordField',\r\n    ]"
  },
  {
    "path": "gooey/python_bindings/cmd_args.py",
    "content": "'''\r\nCreated on Jan 15 2019\r\n\r\n@author: Jonathan Schultz\r\n\r\nThis file contains code that allows the default argument values to be specified\r\non the command line.\r\n'''\r\n\r\nfrom argparse import _SubParsersAction\r\n\r\ndef parse_cmd_args(self, args=None):\r\n\r\n  def prepare_to_read_cmd_args(item):\r\n    '''\r\n    Before reading the command-line arguments, we need to fudge a few things:\r\n      1. If there are subparsers, we need a dest in order to know in which\r\n         subparser the command-line values should be stored.\r\n      2. Any required argument or mutex group needs to be made not required,\r\n         otherwise it will be compulsory to enter those values on the command\r\n         line.\r\n    We save the everything as it was before the fudge, so we can restore later.\r\n    '''\r\n    for action in item._actions:\r\n      if isinstance(action, _SubParsersAction):\r\n        action.save_dest = action.dest\r\n        if not action.dest:\r\n          action.dest = '_subparser'\r\n      else:\r\n        action.save_required = action.required\r\n        action.required = False\r\n        action.save_nargs = action.nargs\r\n        if action.nargs == '+':\r\n          action.nargs = '*'\r\n        elif action.nargs is None:\r\n          action.nargs = '?'\r\n\r\n    for mutex_group in item._mutually_exclusive_groups:\r\n      mutex_group.save_required = mutex_group.required\r\n      mutex_group.required = False\r\n\r\n  def overwrite_default_values(item, cmd_args):\r\n    '''\r\n    Subsistute arguments provided on the command line in the place of the\r\n    default values provided to argparse.\r\n    '''\r\n    for action in item._actions:\r\n      if isinstance(action, _SubParsersAction):\r\n        subparser_arg = getattr(cmd_args, action.dest, None)\r\n        if subparser_arg:\r\n          overwrite_default_values(action.choices[subparser_arg], cmd_args)\r\n      else:\r\n        dest = getattr(action, 'dest', None)\r\n        if dest:\r\n          cmd_arg = getattr(cmd_args, dest, None)\r\n          if cmd_arg:\r\n            action.default = cmd_arg\r\n\r\n  def restore_original_configuration(item):\r\n    '''\r\n    Restore the old values as they were to start with.\r\n    '''\r\n    for action in item._actions:\r\n      if isinstance(action, _SubParsersAction):\r\n        action.dest = action.save_dest\r\n        del action.save_dest\r\n      else:\r\n        action.required = action.save_required\r\n        del action.save_required\r\n        action.nargs = action.save_nargs\r\n        del action.save_nargs\r\n\r\n    for mutex_group in item._mutually_exclusive_groups:\r\n      mutex_group.required = mutex_group.save_required\r\n      del mutex_group.save_required\r\n\r\n  prepare_to_read_cmd_args(self)\r\n  overwrite_default_values(self, self.original_parse_args(args))\r\n  restore_original_configuration(self)\r\n"
  },
  {
    "path": "gooey/python_bindings/coms.py",
    "content": "\"\"\"\nBecause Gooey communicates with the host program\nover stdin/out, we have to be able to differentiate what's\ncoming from gooey and structured, versus what is arbitrary\njunk coming from the host's own logging.\n\nTo do this, we just prefix all written by gooey with the\nliteral string 'gooey::'. This lets us dig through all the\nnoisy stdout to find just the structured Gooey data we're\nafter.\n\"\"\"\n\nimport json\nfrom base64 import b64decode\nfrom typing import Dict, Any\n\nfrom gooey.python_bindings.schema import validate_public_state\nfrom gooey.python_bindings.types import PublicGooeyState\n\nprefix = 'gooey::'\n\n\ndef serialize_outbound(out: PublicGooeyState):\n    \"\"\"\n    Attaches a prefix to whatever is about to be written\n    to stdout so that we can differentiate it in the\n    sea of other stdout writes\n    \"\"\"\n    return prefix + json.dumps(out)\n\n\ndef deserialize_inbound(stdout: bytes, encoding):\n    \"\"\"\n    Deserializes the incoming stdout payload after\n    finding the relevant sections give the gooey prefix.\n    e.g.\n    std='foo\\nbar\\nstarting run\\ngooey::{active_form: [...]}\\n'\n    => {active_form: [...]}\n    \"\"\"\n    data = json.loads(stdout.decode(encoding).split(prefix)[-1])\n    return validate_public_state(data)\n\n\ndef decode_payload(x):\n    \"\"\"\n    To avoid quoting shenanigans, the json state sent from\n    Gooey is b64ecoded for ease of CLI transfer. Argparse will\n    usually barf when trying to parse json directly\n    \"\"\"\n    return json.loads(b64decode(x))\n"
  },
  {
    "path": "gooey/python_bindings/config_generator.py",
    "content": "import os\r\nimport sys\r\nimport signal\r\nimport warnings\r\nimport textwrap\r\nfrom gooey.python_bindings import argparse_to_json\r\nfrom gooey.gui.util.quoting import quote\r\nfrom gooey.python_bindings import constants\r\nfrom gooey.python_bindings import gooey_decorator\r\nfrom gooey.gui.util.functional import merge_dictionaries\r\n\r\ndefault_layout = {\r\n    'widgets': [{\r\n      'type': 'CommandField',\r\n      'required': True,\r\n      'data': {\r\n        'display_name': 'Enter Commands',\r\n        'help': 'Enter command line arguments',\r\n        'nargs': '',\r\n        'commands': '',\r\n        'choices': [],\r\n        'default': None,\r\n      }\r\n    }],\r\n}\r\n\r\n# TODO: deprecate me\r\ndef create_from_parser(parser, source_path, **kwargs):\r\n\r\n  run_cmd = kwargs.get('target')\r\n  if run_cmd is None:\r\n    if hasattr(sys, 'frozen'):\r\n      run_cmd = quote(source_path)\r\n    else:\r\n      run_cmd = '{} -u {}'.format(quote(sys.executable), quote(source_path))\r\n\r\n  build_spec = {**kwargs, 'target': run_cmd}\r\n\r\n  if build_spec['monospace_display']:\r\n      warnings.warn('Gooey Option `monospace_display` is a legacy option.\\n'\r\n                    'See the terminal_font_x options for more flexible control '\r\n                    'over Gooey\\'s text formatting')\r\n\r\n\r\n  build_spec['program_description'] = build_spec['program_description'] or parser.description or ''\r\n\r\n  layout_data = (argparse_to_json.convert(parser, **build_spec)\r\n                   if build_spec['advanced']\r\n                   else default_layout.items())\r\n\r\n  build_spec.update(layout_data)\r\n\r\n  if len(build_spec['widgets']) > 1:\r\n    # there are subparsers involved\r\n    build_spec['show_sidebar'] = True\r\n\r\n  return build_spec\r\n\r\n\r\n\r\n"
  },
  {
    "path": "gooey/python_bindings/constants.py",
    "content": "from collections import namedtuple\r\n\r\nSIDEBAR = 'SIDEBAR'\r\nTABBED = 'TABBED'\r\nINLINE = 'INLINE'\r\nHIDDEN = 'HIDDEN'\r\n\r\nFONTWEIGHT_THIN = 100\r\nFONTWEIGHT_EXTRALIGHT = 200\r\nFONTWEIGHT_LIGHT = 300\r\nFONTWEIGHT_NORMAL = 400\r\nFONTWEIGHT_MEDIUM = 500\r\nFONTWEIGHT_SEMIBOLD = 600\r\nFONTWEIGHT_BOLD = 700\r\nFONTWEIGHT_EXTRABOLD = 800\r\nFONTWEIGHT_HEAVY = 900\r\nFONTWEIGHT_EXTRAHEAVY = 1000\r\n\r\n\r\nEvents = namedtuple('Events', [\r\n    'VALIDATE_FORM', 'ON_SUCCESS', 'ON_ERROR'\r\n])('VALIDATE_FORM', 'ON_SUCCESS', 'ON_ERROR')\r\n\r\n# class Events:\r\n#     VALIDATE_FORM = 'VALIDATE_FORM'\r\n#     ON_SUCCESS = 'ON_SUCCESS'\r\n#     ON_ERROR = 'ON_ERROR'\r\n\r\n"
  },
  {
    "path": "gooey/python_bindings/constraints.py",
    "content": "\"\"\"\nBasic constraints to ensure GooeyParser is fed all the info it needs\nfor various widget classes.\n\nTODO: this should all live in the build_config stage here where it is used\nwithin the GooeyParser directly. As is, logic is fragmented across files. Some\nassertions happen in argparse_to_json, while others happen in GooeyParser.\n\nWhenever refactoring happens, these should be removed from GooeyParser.\n\"\"\"\nfrom textwrap import dedent\n\ndef is_required(action):\n    return action.required\n\ndef is_hidden(options):\n    return not options.get('visible', True)\n\ndef has_validator(options):\n    return bool(options.get('validator'))\n\ndef has_default(action):\n    return bool(action.default)\n\ndef assert_visibility_requirements(action, options):\n    if action.required and is_hidden(options) \\\n            and not (has_validator(options) or has_default(action)):\n        raise ValueError(dedent(\n            '''\n            When using Gooey's hidden field functionality, you must either '\n      \n              (a) provide a default value, or '\n              (b) provide a custom validator'\n              \n            Without one of those, your users will be unable to advance past \n            the configuration screen as they cannot interact with your \n            hidden field, and the default validator requires something to \n            be present for fields marked as `required`.  \n            '''\n        ))\n\ndef assert_listbox_constraints(widget, **kwargs):\n    if widget and widget == 'Listbox':\n        if not 'nargs' in kwargs or kwargs['nargs'] not in ['*', '+']:\n            raise ValueError(\n                'Gooey\\'s Listbox widget requires that nargs be specified.\\n'\n                'Nargs must be set to either `*` or `+` (e.g. nargs=\"*\")'\n            )\n\n"
  },
  {
    "path": "gooey/python_bindings/control.py",
    "content": "\"\"\"\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n!!!!!!!!!!!DEBUGGING NOTE!!!!!!!!!!!\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\nPyCharm will inject addition params into stdout when starting\na new process. This can make debugging VERY VERY CONFUSING as\nthe thing being injected starts complaining about unknown\narguments...\n\nTL;DR: disable the \"Attaach to subprocess automatically\" option\nin the Debugger settings, and the world will be sane again.\n\nSee: https://youtrack.jetbrains.com/issue/PY-24929\nand: https://www.jetbrains.com/help/pycharm/2017.1/python-debugger.html\n\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n!!!!!!!!!!!DEBUGGING NOTE!!!!!!!!!!!\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\"\"\"\nimport json\nimport os\nimport sys\nimport traceback\nfrom argparse import ArgumentParser\nfrom copy import deepcopy\nfrom typing import List, Dict\n\nfrom gooey.python_bindings.dynamics import monkey_patch_for_form_validation\nfrom gooey.python_bindings.dynamics import patch_argument, collect_errors\nfrom gooey.python_bindings.types import GooeyParams\nfrom gooey.python_bindings.coms import serialize_outbound, decode_payload\nfrom gooey.python_bindings.types import PublicGooeyState\nfrom . import cmd_args\nfrom . import config_generator\n\n\ndef noop(*args, **kwargs):\n    \"\"\"\n    No-op for dev/null-ing handlers which\n    haven't been specified by the user.\n    \"\"\"\n    return None\n\n\ndef bypass_gooey(params):\n    \"\"\"\n    Bypasses all the Gooey machinery and runs the user's code directly.\n    \"\"\"\n    def parse_args(self: ArgumentParser, args=None, namespace=None):\n        # We previously mutated sys.argv directly to remove\n        # the --ignore-gooey flag. But this caused lots of issues\n        # See: https://github.com/chriskiehl/Gooey/issues/686\n        # So, we instead modify the parser to transparently\n        # consume the extra token.\n        patched_parser = patch_argument(self, '--ignore-gooey', action='store_true')\n        args = patched_parser.original_parse_args(args, namespace)  # type: ignore\n        # removed from the arg object so the user doesn't have\n        # to deal with it or be confused by it\n        del args.ignore_gooey\n        return args\n    return parse_args\n\n\ndef boostrap_gooey(params: GooeyParams):\n    \"\"\"Bootstraps the Gooey UI.\"\"\"\n    def parse_args(self: ArgumentParser, args=None, namespace=None):\n        # This import is delayed so it is not in the --ignore-gooey codepath.\n        from gooey.gui import bootstrap\n        source_path = sys.argv[0]\n\n        build_spec = None\n        if params['load_build_config']:\n            try:\n                exec_dir = os.path.dirname(sys.argv[0])\n                open_path = os.path.join(exec_dir, params['load_build_config'])  # type: ignore\n                build_spec = json.load(open(open_path, \"r\"))\n            except Exception as e:\n                print('Exception loading Build Config from {0}: {1}'.format(params['load_build_config'], e))\n                sys.exit(1)\n\n        if not build_spec:\n            if params['use_cmd_args']:\n                cmd_args.parse_cmd_args(self, args)\n\n            build_spec = config_generator.create_from_parser(\n                self,\n                source_path,\n                **params)\n\n        if params['dump_build_config']:\n            config_path = os.path.join(os.path.dirname(sys.argv[0]), 'gooey_config.json')\n            print('Writing Build Config to: {}'.format(config_path))\n            with open(config_path, 'w') as f:\n                f.write(json.dumps(build_spec, indent=2))\n        bootstrap.run(build_spec)\n    return parse_args\n\n\ndef validate_form(params: GooeyParams, write=print, exit=sys.exit):\n    \"\"\"\n    Validates the user's current form.\n    \"\"\"\n    def merge_errors(state: PublicGooeyState, errors: Dict[str, str]) -> PublicGooeyState:\n        changes = deepcopy(state['active_form'])\n        for item in changes:\n            if item['type'] == 'RadioGroup':\n                for subitem in item['options']:  # type: ignore\n                    subitem['error'] = errors.get(subitem['id'], None)\n                item['error'] = any(x['error'] for x in item['options'])   # type: ignore\n            else:\n                item['error'] = errors.get(item['id'], None)  # type: ignore\n\n        return PublicGooeyState(active_form=changes)\n\n    def parse_args(self: ArgumentParser, args=None, namespace=None):\n        error_registry: Dict[str, Exception] = {}\n        patched_parser = monkey_patch_for_form_validation(error_registry, self)\n        try:\n            args = patched_parser.original_parse_args(args, namespace)  # type: ignore\n            state = args.gooey_state\n            next_state = merge_errors(state, collect_errors(patched_parser, error_registry, vars(args)))\n            write(serialize_outbound(next_state))\n            exit(0)\n        except Exception as e:\n            write(e)\n            exit(1)\n    return parse_args\n\n\ndef validate_field(params):\n    def parse_args(self: ArgumentParser, args=None, namespace=None):\n        raise NotImplementedError\n    return parse_args\n\n\ndef handle_completed_run(params, write=print, exit=sys.exit):\n    def parse_args(self: ArgumentParser, args=None, namespace=None):\n        # because we're running under the context of a successful\n        # invocation having just completed, the arguments supplied to\n        # the parser to trigger it are thus, by definition, safe to parse.\n        # So, we don't need any error patching monkey business and just need\n        # to attach our specific arg to parse the extra option Gooey passes\n\n        patch_argument(self, '--gooey-state', action='store', type=decode_payload)\n        patch_argument(self, '--gooey-run-is-success', default=False, action='store_true')\n        patch_argument(self, '--gooey-run-is-failure', default=False, action='store_true')\n\n        try:\n            args = self.original_parse_args(args, namespace)  # type: ignore\n            form_state = args.gooey_state\n            was_success = args.gooey_run_is_success\n            # removing the injected gooey value so as not\n            # to clutter the user's object\n            del args.gooey_state\n            del args.gooey_run_is_success\n            del args.gooey_run_is_failure\n            if was_success:\n                next_state = getattr(self, 'on_gooey_success', noop)(args, form_state)  # type: ignore\n            else:\n                next_state = getattr(self, 'on_gooey_error', noop)(args, form_state)  # type: ignore\n            write(serialize_outbound(next_state))\n            exit(0)\n        except Exception as e:\n            write(''.join(traceback.format_stack()))\n            write(e)\n            exit(1)\n    return parse_args\n\n\ndef handle_error(params):\n    def parse_args(self: ArgumentParser, args=None, namespace=None):\n        raise NotImplementedError\n    return parse_args\n\n\ndef handle_field_update(params):\n    def parse_args(self: ArgumentParser, args=None, namespace=None):\n        raise NotImplementedError\n    return parse_args\n\n\ndef handle_submit(params):\n    def parse_args(self: ArgumentParser, args=None, namespace=None):\n        raise NotImplementedError\n    return parse_args\n\n\ndef choose_hander(params: GooeyParams, cliargs: List[str]):\n    \"\"\"\n    Dispatches to the appropriate handler based on values\n    found in the CLI arguments\n    \"\"\"\n    with open('tmp.txt', 'w') as f:\n        f.write(str(sys.argv))\n    if '--gooey-validate-form' in cliargs:\n        return validate_form(params)\n    elif '--gooey-run-is-success' in cliargs or '--gooey-run-is-failure' in cliargs:\n        return handle_completed_run(params)\n    elif '--ignore-gooey' in cliargs:\n        return bypass_gooey(params)\n    else:\n        return boostrap_gooey(params)\n\n\n\n"
  },
  {
    "path": "gooey/python_bindings/dynamics.py",
    "content": "\"\"\"\nAll things Dynamic Updates & Validation.\n\nHear me all ye who enter!\n=========================\n\nThis is a module of disgusting hacks and monkey patching. Control flow\nis all over the place and a comprised of hodgepodge of various strategies.\nThis is all because Argparse's internal parsing design (a) really,\nreally, REALLY wants to fail and sys.exit at the first error it\nfinds, and (b) does these program ending validations at seemingly random\npoints throughout its code base. Meaning, there is no single centralized\nvalidation module, class, or function which could be overridden in order to\nachieve the desired behavior.\n\nAll that means is that it takes a fair amount of indirect, non-standard, and\ngross monkey-patching to get Argparse to collect all its errors as it parses\nrather than violently explode each time it finds one.\n\nFor additional background, see the original design here:\nhttps://github.com/chriskiehl/Gooey/issues/755\n\n\n\"\"\"\nfrom argparse import ArgumentParser, _SubParsersAction, _MutuallyExclusiveGroup\nfrom functools import wraps\nfrom typing import Union, Any, Mapping, Dict, Callable\n\nfrom gooey.python_bindings.types import Success, Failure, Try, InvalidChoiceException\nfrom gooey.python_bindings.argparse_to_json import is_subparser\nfrom gooey.util.functional import lift, identity, merge\nfrom gooey.gui.constants import VALUE_PLACEHOLDER\nfrom gooey.python_bindings.constants import Events\nfrom gooey.python_bindings.coms import decode_payload\nfrom gooey.gui.constants import RADIO_PLACEHOLDER\n\nunexpected_exit_explanations = f'''\n+=======================+\n|Gooey Unexpected Error!|\n+=======================+\n\nGooey encountered an unexpected error while trying to communicate \nwith your program to process one of the {Events._fields} events.\n\nThese features are new and experimental! You may have encountered a bug! \n\nYou can open a ticket with a small reproducible example here\nhttps://github.com/chriskiehl/Gooey/issues\n'''  # type: ignore\n\n\ndeserialize_failure_explanations = f'''\n+==================================+\n|Gooey Event Deserialization Error!|\n+==================================+\n\nGooey was unable to deserialize the payload returned from your \nprogram when processing one of the {Events._fields} events. \n\nThe payload *MUST* be in the `GooeyPublicState` schema. You can \nview the type information in `gooey.python_bindings.types.py`\n\nNote, these features are new an experimental. This may be a bug on \nGooey's side! \n\nYou can open a ticket with a small reproducible example here:  \nhttps://github.com/chriskiehl/Gooey/issues\n'''\n\n\ndef check_value(registry: Dict[str, Exception], original_fn):\n    \"\"\"\n    A Monkey Patch for `Argparse._check_value` which changes its\n    behavior from one which throws an exception, to one which swallows\n    the exception and silently records the failure.\n\n    For certain argument types, Argparse calls a\n    one-off `check_value` method. This method is inconvenient for us\n    as it either returns nothing or throws an ArgumentException (thus leading\n    to a sys.exit). Because our goal is to collect ALL\n    errors for the entire parser, we must patch around this behavior.\n    \"\"\"\n    @wraps(original_fn)\n    def inner(self, action, value: Union[Any, Success, Failure]):\n        def update_reg(_self, _action, _value):\n            try:\n                original_fn(_action, _value)\n            except Exception as e:\n                # check_value exclusively handles validating that the\n                # supplied argument is a member of the `choices` set.\n                # by default, it pops an exception containing all of the\n                # available choices. However, since we're in a UI environment\n                # all of that is redundant information. It's also *way too much*\n                # information for things like FilterableDropdown. Thus we just\n                # remap it to a 'simple' exception here.\n                error = InvalidChoiceException(\"Selected option is not a valid choice\")\n                # IMPORTANT! note that this mutates the\n                # reference that is passed in!\n                registry[action.dest] = error\n\n        # Inside of Argparse, `type_func` gets applied before the calls\n        # to `check_value`. A such, depending on the type, this may already\n        # be a lifted value.\n        if isinstance(value, Success) and not isinstance(value, Failure):\n            update_reg(self, action, value.value)\n        elif isinstance(value, list) and all(x.isSuccess() for x in value):\n            update_reg(self, action, [x.value for x in value])\n        else:\n            update_reg(self, action, value)\n    return inner\n\n\ndef patch_args(*args, **kwargs):\n    def inner(parser):\n        return patch_argument(parser, *args, **kwargs)\n    return inner\n\ndef patch_argument(parser, *args, **kwargs):\n    \"\"\"\n    Mutates the supplied parser to append the arguments (args, kwargs) to\n    the root parser and all subparsers.\n\n    Example: `patch_argument(parser, '--ignore-gooey', action='store_true')\n\n    This is used to punch additional cli arguments into the user's\n    existing parser definition. By adding our arguments everywhere it allows\n    us to use the `parse_args` machinery 'for free' without needing to\n    worry about context shifts (like a repeated `dest` between subparsers).\n    \"\"\"\n    parser.add_argument(*args, **kwargs)\n    subparsers = list(filter(is_subparser, parser._actions))\n    if subparsers:\n        for sub in subparsers[0].choices.values():  # type: ignore\n            patch_argument(sub, *args, **kwargs)\n    return parser\n\n\ndef patch_all_parsers(patch_fn: Callable[[ArgumentParser], None], parser):\n    subparsers = list(filter(is_subparser, parser._actions))\n    if subparsers:\n        for sub in subparsers[0].choices.values():  # type: ignore\n            patch_all_parsers(patch_fn, sub)\n    return parser\n\n\ndef recursively_patch_parser(parser, fn, *args):\n    fn(parser, *args)\n    subparsers = list(filter(is_subparser, parser._actions))\n    if subparsers:\n        for sub in subparsers[0].choices.values():  # type: ignore\n            recursively_patch_parser(sub, fn, *args)\n    return parser\n\n\ndef recursively_patch_actions(parser, fn):\n    for action in parser._actions:\n        if issubclass(type(action), _SubParsersAction):\n            for subparser in action.choices.values():\n                recursively_patch_actions(subparser, fn)\n        else:\n            fn(action)\n\ndef lift_action_type(action):\n    \"\"\"\"\"\"\n    action.type = lift(action.type or identity)\n\ndef lift_actions_mutating(parser):\n    \"\"\"\n    Mutates the supplied parser to lift all of its (likely) partial\n    functions into total functions. See module docs for additional\n    background. TL;DR: we have to \"trick\" Argparse into thinking\n    every value is valid so that it doesn't short-circuit and sys.exit\n    when it encounters a validation error. As such, we wrap everything\n    in an Either/Try, and defer deciding the actual success/failure of\n    the type transform until later in the execution when we have control.\n    \"\"\"\n    recursively_patch_actions(parser, lift_action_type)\n    # for action in parser._actions:\n    #     if issubclass(type(action), _SubParsersAction):\n    #         for subparser in action.choices.values():\n    #             lift_actions_mutating(subparser)\n    #     else:\n    #         action.type = lift(action.type or identity)\n\n\ndef collect_errors(parser, error_registry: Dict[str, Exception], args: Dict[str, Try]) -> Dict[str, str]:\n    \"\"\"\n    Merges all the errors from the Args mapping and error registry\n    into a final dict.\n    \"\"\"\n    # As is a theme throughout this module, to avoid Argparse\n    # short-circuiting during parse-time, we pass a placeholder string\n    # for required positional arguments which haven't yet been provided\n    # by the user. So, what's happening here is that we're now collecting\n    # all the args which have the placeholders so that we can flag them\n    # all as required and missing.\n    # Again, to be hyper clear, this is about being able to collect ALL\n    # errors, versus just ONE error (Argparse default).\n    required_but_missing = {k: 'This field is required'\n                            for k, v in args.items()\n                            if isinstance(v, Success) and v.value == VALUE_PLACEHOLDER}\n\n    mutexes_required_but_missing = collect_mutex_errors(parser, args)\n\n    errors = {k: str(v.error)\n              for k, v in args.items()\n              if v is not None and isinstance(v, Failure)}\n    # Secondary errors are those which get frustratingly applied by\n    # Argparse in a way which can't be easily tracked with patching\n    # or higher order functions. See: `check_value` for more details.\n    secondary = {k: str(e) for k, e in error_registry.items() if e}\n    return merge(required_but_missing, errors, secondary, mutexes_required_but_missing)\n\n\ndef collect_mutex_errors(parser, args: Dict[str, Try]):\n    \"\"\"\n    RadioGroups / MutuallyExclusiveGroup require extra care.\n    Mutexes are not normal actions. They're not argument targets\n    themselves, they have no `dest`, they're just parse-time containers\n    for arguments. As such, there's no top-level argument destination\n    we can tie a single error to. So, the strategy here is to mark _all_ of\n    a radio group's children with an error if *any* of them are missing.\n\n    It's a bit clunky, but what we've got to work with.\n    \"\"\"\n    def dest_targets(group: _MutuallyExclusiveGroup):\n        return [action.dest for action in group._group_actions]\n\n    mutexes_missing = {dest for dest, v in args.items()\n                     if isinstance(v, Success) and v.value == RADIO_PLACEHOLDER}\n\n    return {dest: 'One of these must be provided'\n            for group in parser._mutually_exclusive_groups\n            for dest in dest_targets(group)\n            # if the group is required and we've got one of its\n            # children marked as missing\n            if group.required and set(dest_targets(group)).intersection(mutexes_missing)}\n\n\n\n\n\n\n\n\n\ndef patch(obj, old_fn, new_fn):\n    setattr(obj, old_fn, new_fn.__get__(obj, ArgumentParser))\n\ndef monkey_patch_check_value(parser, new_fn):\n    parser._check_value = new_fn.__get__(parser, ArgumentParser)\n    return parser\n\ndef monkey_patch(patcher, error_registry: Dict[str, Exception], parser):\n    lift_actions_mutating(parser)\n    patcher(parser)\n    new_check_value = check_value(error_registry, parser._check_value)\n    # https://stackoverflow.com/questions/28127874/monkey-patching-python-an-instance-method\n    # parser._check_value = new_check_value.__get__(parser, ArgumentParser)\n\n    return parser\n\n\n\ndef monkey_patch_for_form_validation(error_registry: Dict[str, Exception], parser):\n    \"\"\"\n    Applies all the crufty monkey patching required to\n    process a validate_form event\n    \"\"\"\n    lift_actions_mutating(parser)\n    patch_argument(parser, '--gooey-validate-form', action='store_true')\n    patch_argument(parser, '--gooey-state', action='store', type=decode_payload)\n    new_check_value = check_value(error_registry, parser._check_value)\n    recursively_patch_parser(parser, monkey_patch_check_value, new_check_value)\n    # https://stackoverflow.com/questions/28127874/monkey-patching-python-an-instance-method\n    # patch(parser, '_check_value', new_check_value)\n    # parser._check_value = new_check_value.__get__(parser, ArgumentParser)\n    return monkey_patch_check_value(parser, new_check_value)\n\n"
  },
  {
    "path": "gooey/python_bindings/gooey_decorator.py",
    "content": "\"\"\"\r\nCreated on Jan 24, 2014  <-- so long ago!\r\n\"\"\"\r\nimport sys\r\nfrom argparse import ArgumentParser\r\nfrom functools import wraps\r\n\r\nfrom gooey.python_bindings.control import choose_hander\r\nfrom gooey.python_bindings.parameters import gooey_params\r\nfrom gooey.python_bindings.types import GooeyParams\r\n\r\nIGNORE_COMMAND = '--ignore-gooey'\r\n\r\n\r\ndef Gooey(f=None, **gkwargs):\r\n    \"\"\"\r\n    Decoration entry point for the Gooey process.\r\n    See types.GooeyParams for kwargs options\r\n    \"\"\"\r\n    params: GooeyParams = gooey_params(**gkwargs)\r\n\r\n    @wraps(f)\r\n    def inner(*args, **kwargs):\r\n        parser_handler = choose_hander(params, gkwargs.get('cli', sys.argv))\r\n        # monkey patch parser\r\n        ArgumentParser.original_parse_args = ArgumentParser.parse_args\r\n        ArgumentParser.parse_args = parser_handler\r\n        # return the wrapped, now monkey-patched, user function\r\n        # to be later invoked\r\n        return f(*args, **kwargs)\r\n\r\n    def thunk(func):\r\n        \"\"\"\r\n        This just handles the case where the decorator is called\r\n        with arguments (i.e. @Gooey(foo=bar) rather than @Gooey).\r\n\r\n        Cause python is weird, when a decorator is called (e.g. @decorator())\r\n        rather than just declared (e.g. @decorator), in complete and utter\r\n        defiance of what your lying eyes see, it changes from a higher order\r\n        function, to a function that takes an arbitrary argument *and then*\r\n        returns a higher order function. i.e.\r\n\r\n        decorate :: (a -> b) -> (a -> b)\r\n        decorate() :: c -> (a -> b) -> (a -> b)\r\n\r\n        wat.\r\n        \"\"\"\r\n        return Gooey(func, **params)\r\n\r\n    return inner if callable(f) else thunk\r\n\r\n"
  },
  {
    "path": "gooey/python_bindings/gooey_parser.py",
    "content": "from argparse import ArgumentParser, _SubParsersAction\r\nfrom argparse import _MutuallyExclusiveGroup, _ArgumentGroup\r\n\r\n\r\nclass GooeySubParser(_SubParsersAction):\r\n    def __init__(self, *args, **kwargs):\r\n        super(GooeySubParser, self).__init__(*args, **kwargs)\r\n\r\n\r\n# TODO: figure out how to correctly dispatch all of these\r\n#       so that the individual wrappers aren't needed\r\nclass GooeyArgumentGroup(_ArgumentGroup):\r\n    def __init__(self, parser, widgets, options, *args, **kwargs):\r\n        self.parser = parser\r\n        self.widgets = widgets\r\n        self.options = options\r\n        super(GooeyArgumentGroup, self).__init__(self.parser, *args, **kwargs)\r\n\r\n    def add_argument(self, *args, **kwargs):\r\n        widget = kwargs.pop('widget', None)\r\n        metavar = kwargs.pop('metavar', None)\r\n        options = kwargs.pop('gooey_options', None)\r\n\r\n        action = super(GooeyArgumentGroup, self).add_argument(*args, **kwargs)\r\n        self.parser._actions[-1].metavar = metavar\r\n        self.widgets[self.parser._actions[-1].dest] = widget\r\n        self.options[self.parser._actions[-1].dest] = options\r\n        return action\r\n\r\n    def add_argument_group(self, *args, **kwargs):\r\n        options = kwargs.pop('gooey_options', {})\r\n        group = GooeyArgumentGroup(self.parser, self.widgets, self.options, *args, **kwargs)\r\n        group.gooey_options = options\r\n        self._action_groups.append(group)\r\n        return group\r\n\r\n    def add_mutually_exclusive_group(self, *args, **kwargs):\r\n        options = kwargs.pop('gooey_options', {})\r\n        container = self\r\n        group = GooeyMutuallyExclusiveGroup(container, self.parser, self.widgets, self.options, *args, **kwargs)\r\n        group.gooey_options = options\r\n        self.parser._mutually_exclusive_groups.append(group)\r\n        return group\r\n\r\n\r\nclass GooeyMutuallyExclusiveGroup(_MutuallyExclusiveGroup):\r\n    def __init__(self, container, parser, widgets, options, *args, **kwargs):\r\n        self.parser = parser\r\n        self.widgets = widgets\r\n        self.options = options\r\n        super(GooeyMutuallyExclusiveGroup, self).__init__(container, *args, **kwargs)\r\n\r\n    def add_argument(self, *args, **kwargs):\r\n        widget = kwargs.pop('widget', None)\r\n        metavar = kwargs.pop('metavar', None)\r\n        options = kwargs.pop('gooey_options', None)\r\n\r\n        super(GooeyMutuallyExclusiveGroup, self).add_argument(*args, **kwargs)\r\n        self.parser._actions[-1].metavar = metavar\r\n        self.widgets[self.parser._actions[-1].dest] = widget\r\n        self.options[self.parser._actions[-1].dest] = options\r\n\r\n\r\nclass MyArgumentParser(ArgumentParser):\r\n    def __init__(self, **kwargs):\r\n        self._errors = []\r\n        super(MyArgumentParser, self).__init__(**kwargs)\r\n\r\n    def error(self, message):\r\n        self._errors.append(message)\r\n\r\n\r\ndef lift_relevant(**kwargs):\r\n    \"\"\"\r\n    Lifts the user's (likely) partial function into\r\n    total one of type `String -> Either Error a`\r\n    \"\"\"\r\n    try:\r\n        # Not all Parser Actions accept a type function. Rather\r\n        # than track what allows what explicitly, we just try to\r\n        # pass the `type` var to constructor. If is doesn't\r\n        # explode, then we're good and we use the lifted type. Otherwise\r\n        # we use the original kwargs\r\n        p = ArgumentParser()\r\n        lifted_kwargs = {**kwargs, 'type': lift(kwargs.get('type', identity))}\r\n        p.add_argument('-a', **lifted_kwargs)\r\n        return lifted_kwargs\r\n    except TypeError as e:\r\n        return kwargs\r\n\r\n\r\ndef cls_wrapper(cls, **options):\r\n    def inner(*args, **kwargs):\r\n        class ActionWrapper(cls):\r\n            def __call__(self, p, namespace, values, option_string, **qkwargs):\r\n                # print('hello from', options, namespace, values, option_string, qkwargs)\r\n                super(ActionWrapper, self).__call__(p, namespace, values, option_string, **qkwargs)\r\n        return ActionWrapper(*args, **kwargs)\r\n    return inner\r\n\r\n\r\nclass GooeyParser(object):\r\n    def __init__(self, **kwargs):\r\n        on_success = kwargs.pop('on_success', None)\r\n        on_error = kwargs.pop('on_error', None)\r\n        self.__dict__['parser'] = ArgumentParser(**kwargs)\r\n        self.widgets = {}\r\n        self.options = {}\r\n        self.on_gooey_success = on_success\r\n        self.on_gooey_error = on_error\r\n        if 'parents' in kwargs:\r\n            for parent in kwargs['parents']:\r\n                if isinstance(parent, self.__class__):\r\n                    self.widgets.update(parent.widgets)\r\n                    self.options.update(parent.options)\r\n\r\n    @property\r\n    def _mutually_exclusive_groups(self):\r\n        return self.parser._mutually_exclusive_groups\r\n\r\n    @property\r\n    def _actions(self):\r\n        return self.parser._actions\r\n\r\n    @property\r\n    def description(self):\r\n        return self.parser.description\r\n\r\n    def add_argument(self, *args, **kwargs):\r\n        widget = kwargs.pop('widget', None)\r\n        metavar = kwargs.pop('metavar', None)\r\n        options = kwargs.pop('gooey_options', None)\r\n\r\n        # TODO: move this to the control module. No need to do it\r\n        #       at creation time.\r\n        # lifted_kwargs = lift_relevant(**kwargs)\r\n        #\r\n        # action_cls = self.parser._pop_action_class(kwargs)\r\n        # enhanced_action = cls_wrapper(action_cls, **(options if options else {}))\r\n        #\r\n        # action = self.parser.add_argument(*args, **{**lifted_kwargs, 'action': enhanced_action})\r\n\r\n        action = self.parser.add_argument(*args, **kwargs)\r\n\r\n        self.parser._actions[-1].metavar = metavar\r\n\r\n        action_dest = self.parser._actions[-1].dest\r\n        if action_dest not in self.widgets or self.widgets[action_dest] is None:\r\n            self.widgets[action_dest] = widget\r\n\r\n        if action_dest not in self.options or self.options[action_dest] is None:\r\n            self.options[self.parser._actions[-1].dest] = options\r\n\r\n        self._validate_constraints(\r\n            self.parser._actions[-1],\r\n            widget,\r\n            options or {},\r\n            **kwargs\r\n        )\r\n        return action\r\n\r\n    def add_mutually_exclusive_group(self, *args, **kwargs):\r\n        options = kwargs.pop('gooey_options', {})\r\n        group = GooeyMutuallyExclusiveGroup(self, self.parser, self.widgets, self.options, *args, **kwargs)\r\n        group.gooey_options = options\r\n        self.parser._mutually_exclusive_groups.append(group)\r\n        return group\r\n\r\n    def add_argument_group(self, *args, **kwargs):\r\n        options = kwargs.pop('gooey_options', {})\r\n        group = GooeyArgumentGroup(self.parser, self.widgets, self.options, *args, **kwargs)\r\n        group.gooey_options = options\r\n        self.parser._action_groups.append(group)\r\n        return group\r\n\r\n    def parse_args(self, args=None, namespace=None):\r\n        return self.parser.parse_args(args, namespace)\r\n\r\n    def add_subparsers(self, **kwargs):\r\n        if self._subparsers is not None:\r\n            self.error(_('cannot have multiple subparser arguments'))\r\n\r\n        # add the parser class to the arguments if it's not present\r\n        kwargs.setdefault('parser_class', type(self))\r\n\r\n        if 'title' in kwargs or 'description' in kwargs:\r\n            title = kwargs.pop('title', 'subcommands')\r\n            description = kwargs.pop('description', None)\r\n            self._subparsers = self.add_argument_group(title, description)\r\n        else:\r\n            self._subparsers = self._positionals\r\n\r\n        # prog defaults to the usage message of this parser, skipping\r\n        # optional arguments and with no \"usage:\" prefix\r\n        if kwargs.get('prog') is None:\r\n            formatter = self._get_formatter()\r\n            positionals = self._get_positional_actions()\r\n            groups = self._mutually_exclusive_groups\r\n            formatter.add_usage(self.usage, positionals, groups, '')\r\n            kwargs['prog'] = formatter.format_help().strip()\r\n\r\n        # create the parsers action and add it to the positionals list\r\n        parsers_class = self._pop_action_class(kwargs, 'parsers')\r\n        action = parsers_class(option_strings=[], **kwargs)\r\n        self._subparsers._add_action(action)\r\n\r\n        # return the created parsers action\r\n        return action\r\n\r\n    def _validate_constraints(self, parser_action, widget, options, **kwargs):\r\n        from gooey.python_bindings import constraints\r\n        constraints.assert_listbox_constraints(widget, **kwargs)\r\n        constraints.assert_visibility_requirements(parser_action, options)\r\n\r\n\r\n\r\n    def __getattr__(self, item):\r\n        return getattr(self.parser, item)\r\n\r\n    def __setattr__(self, key, value):\r\n        return setattr(self.parser, key, value)\r\n\r\n"
  },
  {
    "path": "gooey/python_bindings/parameters.py",
    "content": "import signal\nimport sys\nimport textwrap\n\nimport os\nfrom typing import List\n\nfrom gooey.python_bindings.constants import Events\nfrom gooey.python_bindings import constants\nfrom gooey.gui.util.freeze import getResourcePath\nfrom gooey.python_bindings.types import GooeyParams\nfrom gooey.util.functional import merge\n\n\n\ndef _get_font_weight(kwargs):\n    error_msg = textwrap.dedent('''\n    Unknown font weight {}. \n\n    The available weights can be found in the `constants` module. \n    They're prefixed with \"FONTWEIGHT\" (e.g. `FONTWEIGHT_BOLD`)\n\n    example code:    \n\n    ```\n    from gooey import constants\n    @Gooey(terminal_font_weight=constants.FONTWEIGHT_NORMAL)\n    ```   \n    ''')\n    weights = {\n        constants.FONTWEIGHT_THIN,\n        constants.FONTWEIGHT_EXTRALIGHT,\n        constants.FONTWEIGHT_LIGHT,\n        constants.FONTWEIGHT_NORMAL,\n        constants.FONTWEIGHT_MEDIUM,\n        constants.FONTWEIGHT_SEMIBOLD,\n        constants.FONTWEIGHT_BOLD,\n        constants.FONTWEIGHT_EXTRABOLD,\n        constants.FONTWEIGHT_HEAVY,\n        constants.FONTWEIGHT_EXTRAHEAVY\n    }\n    weight = kwargs.get('terminal_font_weight', constants.FONTWEIGHT_NORMAL)\n    if weight not in weights:\n        raise ValueError(error_msg.format(weight))\n    return weight\n\n\n# python can't type kwargs? wtf..\ndef gooey_params(**kwargs) -> GooeyParams:\n    \"\"\"\n    Builds the full GooeyParams object from an arbitrary subset of supplied values\n    \"\"\"\n    return GooeyParams(**{  # type: ignore\n        'show_preview_warning': kwargs.get('show_preview_warning', True),\n        'language': kwargs.get('language', 'english'),\n        'target': kwargs.get('target'),\n\n        'dump_build_config': kwargs.get('dump_build_config', False),\n        'load_build_config': kwargs.get('load_build_config'),\n        'use_cmd_args': kwargs.get('use_cmd_args', False),\n\n        'suppress_gooey_flag': kwargs.get('suppress_gooey_flag') or False,\n        # TODO: I should not read from the environment.\n        # remains here for legacy reasons pending refactor\n        'program_name': kwargs.get('program_name') or os.path.basename(sys.argv[0]).replace('.py', ''),\n        'program_description': kwargs.get('program_description') or '',\n        'sidebar_title': kwargs.get('sidebar_title', 'Actions'),\n        'default_size': kwargs.get('default_size', (610, 530)),\n        'auto_start': kwargs.get('auto_start', False),\n        'advanced': kwargs.get('advanced', True),\n        'run_validators': kwargs.get('run_validators', True),\n        'encoding': kwargs.get('encoding', 'utf-8'),\n        'show_stop_warning': kwargs.get('show_stop_warning', True),\n        'show_success_modal': kwargs.get('show_success_modal', True),\n        'show_failure_modal': kwargs.get('show_failure_modal', True),\n        'force_stop_is_error': kwargs.get('force_stop_is_error', True),\n        'poll_external_updates': kwargs.get('poll_external_updates', False),\n        'return_to_config': kwargs.get('return_to_config', False),\n        'show_restart_button': kwargs.get('show_restart_button', True),\n        'requires_shell': kwargs.get('requires_shell', True),\n        'menu': kwargs.get('menu', []),\n        'clear_before_run': kwargs.get('clear_before_run', False),\n        'fullscreen': kwargs.get('fullscreen', False),\n\n        'use_legacy_titles': kwargs.get('use_legacy_titles', True),\n        'required_cols': kwargs.get('required_cols', 2),\n        'optional_cols': kwargs.get('optional_cols', 2),\n        'manual_start': False,\n        'monospace_display': kwargs.get('monospace_display', False),\n\n        'image_dir': kwargs.get('image_dir', '::gooey/default'),\n        # TODO: this directory resolution shouldn't happen here!\n        # TODO: leaving due to legacy for now\n        'language_dir': kwargs.get('language_dir', getResourcePath('languages')),\n        'progress_regex': kwargs.get('progress_regex'),\n        'progress_expr': kwargs.get('progress_expr'),\n        'hide_progress_msg': kwargs.get('hide_progress_msg', False),\n\n        'timing_options': merge({\n            'show_time_remaining': False,\n            'hide_time_remaining_on_complete': True\n        }, kwargs.get('timing_options', {})),\n        'disable_progress_bar_animation': kwargs.get('disable_progress_bar_animation', False),\n        'disable_stop_button': kwargs.get('disable_stop_button'),\n        'shutdown_signal': kwargs.get('shutdown_signal', signal.SIGTERM),\n        'use_events': parse_events(kwargs.get('use_events', [])),\n\n\n        'navigation': kwargs.get('navigation', constants.SIDEBAR),\n        'show_sidebar': kwargs.get('show_sidebar', False),\n        'tabbed_groups': kwargs.get('tabbed_groups', False),\n        'group_by_type': kwargs.get('group_by_type', True),\n\n\n        'body_bg_color': kwargs.get('body_bg_color', '#f0f0f0'),\n        'header_bg_color': kwargs.get('header_bg_color', '#ffffff'),\n        'header_height': kwargs.get('header_height', 90),\n        'header_show_title': kwargs.get('header_show_title', True),\n        'header_show_subtitle': kwargs.get('header_show_subtitle', True),\n        'header_image_center': kwargs.get('header_image_center', False),\n        'footer_bg_color': kwargs.get('footer_bg_color', '#f0f0f0'),\n        'sidebar_bg_color': kwargs.get('sidebar_bg_color', '#f2f2f2'),\n\n        'terminal_panel_color': kwargs.get('terminal_panel_color', '#F0F0F0'),\n        'terminal_font_color': kwargs.get('terminal_font_color', '#000000'),\n        'terminal_font_family': kwargs.get('terminal_font_family', None),\n        'terminal_font_weight': _get_font_weight(kwargs),\n        'terminal_font_size': kwargs.get('terminal_font_size', None),\n        'richtext_controls': kwargs.get('richtext_controls', False),\n        'error_color': kwargs.get('error_color', '#ea7878'),\n        # TODO: remove. Only useful for testing\n        'cli': kwargs.get('cli', sys.argv),\n    })\n\n\ndef parse_events(events: List[str]) -> List[str]:\n    if not isinstance(events, list):\n        raise TypeError(\n            f\"`use_events` requires a list of events. You provided \"\n            \"{events}. \\n\"\n            \"Example: \\n\"\n            \"\\tfrom gooey import Events\"\n            \"\\t@Gooey(use_events=[Events.VALIDATE_FORM]\")\n\n    unknown_events = set(events) - set(Events)\n    if unknown_events:\n        raise ValueError(\n            f'nrecognized event(s) were passed to `use_events`: {unknown_events}\\n'\n            f'Must be one of {Events._fields}\\n'\n            f'Consider using the `Events` object: `from gooey import Events`')\n    else:\n        return events\n"
  },
  {
    "path": "gooey/python_bindings/parser/gooey_parser.py",
    "content": ""
  },
  {
    "path": "gooey/python_bindings/parser_exceptions.py",
    "content": "'''\r\nCreated on Feb 10, 2014\r\n\r\n@author: Chris\r\n'''\r\n\r\n\r\nclass ParserError(Exception):\r\n  \"\"\"Thrown when the parser can't find argparse functions the client code\"\"\"\r\n  pass\r\n\r\n\r\nclass ArgumentError(Exception):\r\n  \"\"\"Thrown when the parser is supplied with an incorrect argument format\"\"\"\r\n  pass\r\n\r\n\r\nif __name__ == '__main__':\r\n  pass"
  },
  {
    "path": "gooey/python_bindings/schema.py",
    "content": "from typing import Dict, Any\n\nfrom gooey.python_bindings.types import PublicGooeyState\nfrom gooey.python_bindings import types as t\n\n\ndef validate_public_state(state: Dict[str, Any]) -> PublicGooeyState:\n    \"\"\"\n    Very, very minimal validation the shape of the incoming state\n    is inline with the PublicGooeyState type.\n\n    TODO: turn this into something useful.\n    \"\"\"\n    top_level_keys = PublicGooeyState.__annotations__.keys()\n    assert set(top_level_keys) == set(state.keys())\n    for item in state['active_form']:\n        assert 'type' in item\n        expected_keys = getattr(t, item['type']).__annotations__.keys()\n        a = set(expected_keys)\n        b = set(item.keys())\n        assert set(expected_keys) == set(item.keys())\n    return state\n"
  },
  {
    "path": "gooey/python_bindings/signal_support.py",
    "content": "\"\"\"\nUtilities for patching Windows so that CTRL-C signals\ncan be received by process groups.\n\nThe best resource for understanding why this is required is the Python\nIssue here: https://bugs.python.org/issue13368\n\n**The official docs from both Python and Microsoft cannot be trusted due to inaccuracies**\n\nThe two sources directly conflict with regards to what signals are possible on Windows\nunder which circumstances.\n\n\nPython's docs:\n    https://bugs.python.org/issue13368\n    On Windows, SIGTERM is an alias for terminate(). CTRL_C_EVENT and\n    CTRL_BREAK_EVENT can be sent to processes started with a creationflags\n    parameter which includes CREATE_NEW_PROCESS_GROUP.\n\nMicrosoft's docs:\n    https://docs.microsoft.com/en-us/windows/console/generateconsolectrlevent\n    Generates a CTRL+C signal. This signal cannot be generated for process groups.\n\nAnother piece of the puzzle:\n    https://docs.microsoft.com/en-us/windows/console/handlerroutine#remarks\n    Each console process has its own list of HandlerRoutine functions. Initially,\n    this list contains only a default handler function that calls ExitProcess. A\n    console process adds or removes additional handler functions by calling the\n    SetConsoleCtrlHandler function, which does not affect the list of handler\n    functions for other processes.\n\nThe most important line here is: \"Initially, this list contains only the default\nhandler function that calls exit Process\"\n\nSo, despite what Microsoft's docs for ctrlcevent state, it IS possible to send\nthe ctrl+c signal to process groups **IFF** the leader for that process group has\nthe appropriate handler installed.\n\nWe can solve this transparently in Gooey land by installing the handler on behalf\nof the user when required.\n\"\"\"\nimport sys\nimport signal\nfrom textwrap import dedent\n\n\ndef requires_special_handler(platform, requested_signal):\n    \"\"\"\n    Checks whether we need to attach additional handlers\n    to this process to deal with ctrl-C signals\n    \"\"\"\n    return platform.startswith(\"win\") and requested_signal == signal.CTRL_C_EVENT\n\n\ndef install_handler():\n    \"\"\"\n    Adds a Ctrl+C 'handler routine'. This allows the CTRL-C\n    signal to be received even when the process was created in\n    a new process group.\n\n    See module docstring for additional info.\n    \"\"\"\n    assert sys.platform.startswith(\"win\")\n    import ctypes\n\n    help_msg = dedent('''\n    Please open an issue here: https://github.com/chriskiehl/Gooey/issues\n        \n    GOOD NEWS: THERE IS A WORK AROUND: \n    \n    Gooey only needs to attach special Windows handlers for the CTRL-C event. Consider using \n    the `CTRL_BREAK_EVENT` or `SIGTERM` instead to get past this error. \n    \n    See the Graceful Shutdown docs for information on how to catch alternative signals. \n    \n    https://github.com/chriskiehl/Gooey/tree/master/docs \n    ''')\n\n    try:\n        kernel32 = ctypes.WinDLL('kernel32')\n        if kernel32.SetConsoleCtrlHandler(None, 0) == 0:\n            raise Exception(dedent('''\n            Gooey was unable to install the handler required for processing\n            CTRL-C signals. This is a **very** unexpected failure. \n            ''') + help_msg)\n    except OSError as e:\n        raise OSError(dedent('''\n            Gooey failed while trying to find the kernel32 module. \n            Gooey requires this module in order to attach handlers for CTRL-C signal. \n            Not being able to find this is **very** unexpected. \n            ''') + help_msg)\n"
  },
  {
    "path": "gooey/python_bindings/types.py",
    "content": "from typing import Optional, Tuple, List, Union, Mapping, Any, TypeVar, Generic, Dict\n\nfrom dataclasses import dataclass\nfrom typing_extensions import TypedDict\n\n\nclass MenuHtmlDialog(TypedDict):\n    type: str\n    menuTitle: str\n    caption: Optional[str]\n    html: str\n\nclass MenuLink(TypedDict):\n    type: str\n    menuTitle: str\n    url: str\n\n\nclass MenuMessageDialog(TypedDict):\n    type: str\n    menuTitle: str\n    message: str\n    caption: Optional[str]\n\nclass MenuAboutDialog(TypedDict):\n    type: str\n    menuTitle: str\n    name: Optional[str]\n    description: Optional[str]\n    version: Optional[str]\n    copyright: Optional[str]\n    license: Optional[str]\n    website: Optional[str]\n    developer: Optional[str]\n\n\nMenuItem = Union[\n    MenuLink,\n    MenuMessageDialog,\n    MenuAboutDialog,\n    MenuHtmlDialog\n]\n\n\n\nclass TimingOptions(TypedDict):\n    show_time_remaining: bool\n    hide_time_remaining_on_complete: bool\n\n\nclass GooeyParams(TypedDict):\n    # when running with a custom target, there is no need to inject\n    # --ignore-gooey into the CLI args\n    show_preview_warning: bool\n    suppress_gooey_flag: bool\n    advanced: bool\n    language: str\n    target: Optional[str]\n    program_name: Optional[str]\n    program_description: Optional[str]\n    sidebar_title: str\n    default_size: Tuple[int, int]\n    auto_start: bool\n    show_advanced: bool\n    run_validators: bool\n    encoding: str\n    show_stop_warning: bool\n    show_success_modal: bool\n    show_failure_modal: bool\n    force_stop_is_error: bool\n    poll_external_updates: bool  # BEING DEPRECATED\n    return_to_config: bool\n    show_restart_button: bool\n    requires_shell: bool\n    menu: List[MenuItem]\n    clear_before_run: bool\n    fullscreen: bool\n    # Legacy/Backward compatibility interop\n    use_legacy_titles: bool\n    required_cols: int\n    optional_cols: int\n    manual_start: bool\n    monospace_display: bool\n\n    image_dir: str\n    language_dir: str\n    progress_regex: Optional[str]\n    progress_expr: Optional[str]\n    hide_progress_msg: bool\n    timing_options: TimingOptions\n    disable_progress_bar_animation: bool\n    disable_stop_button: bool\n    shutdown_signal: int\n    use_events: List[str]\n\n    # Layouts\n    navigation: str\n    show_sidebar: bool\n    tabbed_groups: bool\n    group_by_type: bool\n\n    # styles\n    body_bg_color: str\n    header_bg_color: str\n    header_height: int\n    header_show_title: bool\n    header_show_subtitle: bool\n    header_image_center: bool\n    footer_bg_color: str\n    sidebar_bg_color: str\n\n    # font family, weight, and size are determined at runtime\n    terminal_panel_color: str\n    terminal_font_color: str\n    terminal_font_family: Optional[str]\n    terminal_font_weight: Optional[int]\n    terminal_font_size: Optional[int]\n    richtext_controls: bool\n    error_color: str\n\n    use_cmd_args: bool\n    dump_build_config: bool\n    load_build_config: Optional[str]\n\n# TODO:\n# use the syntax here rather than inheritance, as the latter is a type error\n# https://jdkandersson.com/2020/01/27/python-typeddict-arbitrary-key-names-with-totality/\n# class BuildSpecification(GooeyParams):\n#     target: str\n#     widgets: str\n\n\nclass BasicField(TypedDict):\n    id: str\n    type: str\n    # required: bool\n    # positional: bool\n    error: Optional[str]\n    enabled: bool\n    visible: bool\n\nclass Dropdown(BasicField):\n    selected: int\n    choices: List[str]\n\nclass Chooser(BasicField):\n    btn_label: str\n    value: str\n\n\nclass MultiFileChooser(Chooser):\n    pass\n\nclass FileChooser(Chooser):\n    pass\n\nclass FileSaver(Chooser):\n    pass\n\nclass DirChooser(Chooser):\n    pass\n\nclass MultiDirChooser(Chooser):\n    pass\n\nclass DateChooser(Chooser):\n    pass\n\nclass TimeChooser(Chooser):\n    pass\n\nclass ColourChooser(Chooser):\n    pass\n\nclass Command(BasicField):\n    value: str\n    placeholder: str\n\nclass Counter(BasicField):\n    selected: int\n    choices: List[str]\n\nclass DropdownFilterable(BasicField):\n    value: str\n    choices: List[str]\n\nclass Listbox(BasicField):\n    selected: List[str]\n    choices: List[str]\n\nclass IntegerField(BasicField):\n    value: str\n    min: int\n    max: int\n\nclass DecimalField(BasicField):\n    value: float\n    min: float\n    max: float\n\nclass Slider(BasicField):\n    value: float\n    min: float\n    max: float\n\nclass Textarea(BasicField):\n    value: float\n    height: int\n\nclass TextField(BasicField):\n    value: str\n    placeholder: str\n\n\nclass PasswordField(TextField):\n    pass\n\n\nclass Checkbox(BasicField):\n    checked: bool\n\n\nclass RadioGroup(BasicField):\n    selected: Optional[int]\n    options: List['FormField']\n\n\nFormField = Union[\n    Textarea,\n    Slider,\n    Command,\n    Counter,\n    Checkbox,\n    TextField,\n    Dropdown,\n    Chooser,\n    RadioGroup,\n    DropdownFilterable,\n    Listbox,\n    IntegerField\n]\n\n\n\n\nclass FieldValue(TypedDict):\n    \"\"\"\n    The current value of a widget in the UI.\n    TODO: Why are things like cmd and cli type tracked IN the\n    UI and returned as part of the getValue() call?\n    What the hell, young me?\n    \"\"\"\n    id: str\n    cmd: Optional[str]\n    rawValue: str\n    placeholder: str\n    positional: bool\n    required: bool\n    enabled: bool\n    visible: bool\n    test: bool\n    error: Optional[str]\n    clitype: str\n    meta: Any\n\n\nclass PublicGooeyState(TypedDict):\n    \"\"\"\n    A minimal representation of Gooey's current UI state\n    \"\"\"\n    active_form: List[FormField]\n\n\nclass Group(TypedDict):\n    name: str\n    items: List['Item']\n    groups: List['Group']\n    description: str\n    options: Dict[Any, Any]\n\n\nclass Item(TypedDict):\n    id: str\n    type: str\n    cli_type: str\n    group_name: str\n    required: bool\n    options: Dict[Any, Any]\n    data: 'ItemData'\n\n\nclass EnrichedItem(Item):\n    \"\"\"\n    An argparse item paired with its associated Gooey form\n    field and current state.\n    \"\"\"\n    field: FormField\n\n\nItemData = Union['StandardData', 'RadioData']\n\nclass StandardData(TypedDict):\n    display_name: str\n    help: str\n    required: bool\n    nargs: str\n    commands: List[str]\n    choices: List[str]\n    default: Union[str, List[str]]\n    dest: str\n\nclass RadioData(TypedDict):\n    commands: List[List[str]]\n    widgets: List[Item]\n\n\nclass TopLevelParser(TypedDict):\n    command: str\n    name: str\n    help: Optional[str]\n    description: str\n    contents: List[Group]\n\nA = TypeVar('A')\n\n\n## TODO: dynamic types\n\n@dataclass(frozen=True, eq=True)\nclass CommandDetails:\n    target: str\n    subcommand: str\n    positionals: List[FieldValue]\n    optionals: List[FieldValue]\n\n@dataclass(frozen=True, eq=True)\nclass CommandPieces:\n    target: str\n    subcommand: str\n    positionals: List[str]\n    optionals: List[str]\n    ignoreFlag: str\n\n@dataclass(frozen=True, eq=True)\nclass Success(Generic[A]):\n    value: A\n\n    def map(self, f):\n        return Success(f(self.value))\n    def flatmap(self, f):\n        return f(self.value)\n    def onSuccess(self, f):\n        f(self.value)\n        return self\n    def onError(self, f):\n        return self\n    def isSuccess(self):\n        return True\n    def getOrThrow(self):\n        return self.value\n\n@dataclass(frozen=True, eq=True)\nclass Failure:\n    error: Exception\n\n    def map(self, f):\n        return Failure(self.error)\n    def flatmap(self, f):\n        return Failure(self.error)\n    def onSuccess(self, f):\n        return self\n    def onError(self, f):\n        f(self.error)\n        return self\n    def isSuccess(self):\n        return False\n    def getOrThrow(self):\n        raise self.error\n\nTry = Union[Success[A], Failure]\n\n\n\nValidationResponse = Mapping[str, str]\n\n\nclass InvalidChoiceException(ValueError):\n    pass\n"
  },
  {
    "path": "gooey/tests/__init__.py",
    "content": "\"\"\"\nThis weirdness exists to work around a very specific problem\nwith testing WX: you can only ever have one App() instance per\nprocess. I've spent hours and hours trying to work around this and\nfigure out how to gracefully destroy and recreate them, but... no dice.\n\nThis is echo'd in the docs: https://wxpython.org/Phoenix/docs/html/wx.App.html\n\nDestroying/recreating causes instability in the tests. We can work around that\nby reusing the same App instance across tests and only destroying the top level\nframe (which is fine). However, this causes a new problem: the last test which\nruns will now always fail, cause we're not Destroying the App instance.\n\nIdeal world: UnitTest would expose a global \"done\" hook regardless of test\ndiscovery type. That doesn't exist, so the best we can do is use the Module cleanup\nmethods. These aren't perfect, but destroying / recreating at the module boundary\ngives slightly more reliable tests. These are picked up by the test runner\nby their existence in the module's globals(). There's no other way to hook\nthings together. We need it in every test, and thus... that's the background\nfor why this weirdness is going on.\n\nIt's a hack around a hack around a problem in Wx.\n\nUsage:\n\nIn any tests which use WX, you must import this module's definitions\ninto the test's global scope\n\n```\nfrom gooey.tests import *\n```\n\"\"\"\nimport wx\nimport locale\nimport platform\n\nclass TestApp(wx.App):\n    \"\"\"\n    Stolen from the mailing list here:\n    https://groups.google.com/g/wxpython-users/c/q5DSyyuKluA\n\n    Wx started randomly exploding with locale issues while running\n    the tests. For whatever reason, manually setting it in InitLocale\n    seems to solve the problem.\n    \"\"\"\n    def __init__(self, with_c_locale=None, **kws):\n        if with_c_locale is None:\n            with_c_locale = (platform.system() == 'Windows')\n        self.with_c_locale = with_c_locale\n        wx.App.__init__(self, **kws)\n\n    def InitLocale(self):\n        \"\"\"over-ride wxPython default initial locale\"\"\"\n        if self.with_c_locale:\n            self._initial_locale = None\n            locale.setlocale(locale.LC_ALL, 'C')\n        else:\n            lang, enc = locale.getdefaultlocale()\n            self._initial_locale = wx.Locale(lang, lang[:2], lang)\n            locale.setlocale(locale.LC_ALL, lang)\n\n    def OnInit(self):\n        self.createApp()\n        return True\n\n    def createApp(self):\n        return True\n\n\napp = None\n\ndef setUpModule():\n    global app\n    app = TestApp()\n\ndef tearDownModule():\n    global app\n    app.Destroy()"
  },
  {
    "path": "gooey/tests/all_widgets.py",
    "content": "\"\"\"\nParser containing all Gooey widgets.\n\"\"\"\n\nfrom gooey import GooeyParser\n\n\nparser = GooeyParser()\n\nparser.add_argument('--textfield', default=2, widget=\"TextField\")\nparser.add_argument('--textarea', default=\"oneline twoline\", widget='Textarea')\nparser.add_argument('--password', default=\"hunter42\", widget='PasswordField')\nparser.add_argument('--commandfield', default=\"cmdr\", widget='CommandField')\nparser.add_argument('--dropdown', choices=[\"one\", \"two\"], default=\"two\", widget='Dropdown')\nparser.add_argument(\n    '--listboxie',\n    nargs='+',\n    default=['three', 'four'],\n    choices=['one', 'two', 'three', 'four'],\n    widget='Listbox',\n    gooey_options={\n        'height': 300,\n        'validate': '',\n        'heading_color': '',\n        'text_color': '',\n        'hide_heading': True,\n        'hide_text': True,\n    }\n)\nparser.add_argument('--counter', default=3, action='count', widget='Counter')\nparser.add_argument(\"--overwrite1\", action=\"store_true\", default=True, widget='CheckBox')\nparser.add_argument(\"--overwrite2\", action=\"store_true\", default=True, widget='BlockCheckbox')\n\nverbosity = parser.add_mutually_exclusive_group(\n    gooey_options={\n        'initial_selection': 0\n    }\n)\nverbosity.add_argument('--mutexone', default='hello')\n\nparser.add_argument('--mutextwo', default='3', widget='Slider')\nparser.add_argument('--mutextwo', default='1', widget='IntegerField')\nparser.add_argument('--mutextwo', default='4', widget='DecimalField')\n\nparser.add_argument(\"--filechooser\", default=\"fc-value\", widget='FileChooser')\nparser.add_argument(\"--filesaver\", default=\"fs-value\", widget='FileSaver')\nparser.add_argument(\"--dirchooser\", default=\"dc-value\", widget='DirChooser')\nparser.add_argument(\"--datechooser\", default=\"2015-01-01\", widget='DateChooser')\nparser.add_argument(\"--colourchooser\", default=\"#000000\", widget='ColourChooser')\n\n\n\n"
  },
  {
    "path": "gooey/tests/all_widgets_subparser.py",
    "content": "\"\"\"\r\nExample program to demonstrate Gooey's presentation of subparsers\r\n\"\"\"\r\n\r\nfrom gooey import Gooey, GooeyParser\r\n\r\nrunning = True\r\n\r\n\r\n@Gooey(\r\n    optional_cols=2,\r\n    program_name=\"Subparser Demo\",\r\n    dump_build_config=True,\r\n    show_success_modal=False)\r\ndef main():\r\n    parser = GooeyParser()\r\n    subs = parser.add_subparsers(help='commands', dest='command')\r\n\r\n    parser_one = subs.add_parser('parser1', prog=\"Parser 1\")\r\n    parser_one.add_argument('--textfield', default=2, widget=\"TextField\")\r\n    parser_one.add_argument('--textarea', default=\"oneline twoline\", widget='Textarea')\r\n    parser_one.add_argument('--password', default=\"hunter42\", widget='PasswordField')\r\n    parser_one.add_argument('--commandfield', default=\"cmdr\", widget='CommandField')\r\n    parser_one.add_argument('--dropdown',\r\n                        choices=[\"one\", \"two\"], default=\"two\", widget='Dropdown')\r\n    parser_one.add_argument('--listboxie',\r\n                        nargs='+',\r\n                        default=['Option three', 'Option four'],\r\n                        choices=['Option one', 'Option two', 'Option three',\r\n                                 'Option four'],\r\n                        widget='Listbox',\r\n                        gooey_options={\r\n                            'height': 300,\r\n                            'validate': '',\r\n                            'heading_color': '',\r\n                            'text_color': '',\r\n                            'hide_heading': True,\r\n                            'hide_text': True,\r\n                        }\r\n                        )\r\n    parser_one.add_argument('-c', '--counter', default=3, action='count',\r\n                        widget='Counter')\r\n    #\r\n    parser_one.add_argument(\"-o\", \"--overwrite\", action=\"store_true\",\r\n                        default=True,\r\n                        widget='CheckBox')\r\n\r\n    ### Mutex Group ###\r\n    verbosity = parser_one.add_mutually_exclusive_group(\r\n        required=True,\r\n        gooey_options={\r\n            'initial_selection': 1\r\n        }\r\n    )\r\n    verbosity.add_argument(\r\n        '--mutexone',\r\n        default=True,\r\n        action='store_true',\r\n        help=\"Show more details\")\r\n\r\n    verbosity.add_argument(\r\n        '--mutextwo',\r\n        default='mut-2',\r\n        widget='TextField')\r\n\r\n    parser_one.add_argument(\"--filechooser\", default=\"fc-value\", widget='FileChooser')\r\n    parser_one.add_argument(\"--filesaver\", default=\"fs-value\", widget='FileSaver')\r\n    parser_one.add_argument(\"--dirchooser\", default=\"dc-value\", widget='DirChooser')\r\n    parser_one.add_argument(\"--datechooser\", default=\"2015-01-01\", widget='DateChooser')\r\n    parser_one.add_argument(\"--colourchooser\", default=\"#000000\", widget='ColourChooser')\r\n\r\n    parser_two = subs.add_parser('parser2', prog=\"parser 2\")\r\n    parser_two.add_argument('--textfield', default=2, widget=\"TextField\")\r\n    parser_two.add_argument('--textarea', default=\"oneline twoline\",\r\n                            widget='Textarea')\r\n    parser_two.add_argument('--password', default=\"hunter42\", widget='PasswordField')\r\n    parser_two.add_argument('--commandfield', default=\"cmdr\", widget='CommandField')\r\n    parser_two.add_argument('--dropdown',\r\n                            choices=[\"one\", \"two\"], default=\"two\", widget='Dropdown')\r\n    parser_two.add_argument('--listboxie',\r\n                            nargs='+',\r\n                            default=['Option three', 'Option four'],\r\n                            choices=['Option one', 'Option two', 'Option three',\r\n                                     'Option four'],\r\n                            widget='Listbox',\r\n                            gooey_options={\r\n                                'height': 300,\r\n                                'validate': '',\r\n                                'heading_color': '',\r\n                                'text_color': '',\r\n                                'hide_heading': True,\r\n                                'hide_text': True,\r\n                            }\r\n                            )\r\n    parser_two.add_argument('-c', '--counter', default=3, action='count',\r\n                            widget='Counter')\r\n    #\r\n    parser_two.add_argument(\"-o\", \"--overwrite\", action=\"store_true\",\r\n                            default=True,\r\n                            widget='CheckBox')\r\n\r\n    ### Mutex Group ###\r\n    verbosity = parser_two.add_mutually_exclusive_group(\r\n        required=True,\r\n        gooey_options={\r\n            'initial_selection': 1\r\n        }\r\n    )\r\n    verbosity.add_argument(\r\n        '--mutexone',\r\n        default=True,\r\n        action='store_true',\r\n        help=\"Show more details\")\r\n\r\n    verbosity.add_argument(\r\n        '--mutextwo',\r\n        default='mut-2',\r\n        widget='TextField')\r\n\r\n    parser_two.add_argument(\"--filechooser\", default=\"fc-value\", widget='FileChooser')\r\n    parser_two.add_argument(\"--filesaver\", default=\"fs-value\", widget='FileSaver')\r\n    parser_two.add_argument(\"--dirchooser\", default=\"dc-value\", widget='DirChooser')\r\n    parser_two.add_argument(\"--datechooser\", default=\"2015-01-01\",widget='DateChooser')\r\n    parser_two.add_argument(\"--colourchooser\", default=\"#000000\", widget='ColourChooser')\r\n\r\n    dest_vars = [\r\n        'textfield',\r\n        'textarea',\r\n        'password',\r\n        'commandfield',\r\n        'dropdown',\r\n        'listboxie',\r\n        'counter',\r\n        'overwrite',\r\n        'mutextwo',\r\n        'filechooser',\r\n        'filesaver',\r\n        'dirchooser',\r\n        'datechooser',\r\n        'colourchooser'\r\n    ]\r\n\r\n    parser.parse_args()\r\n\r\n    args = parser.parse_args()\r\n    import time\r\n    time.sleep(.6)\r\n    for i in dest_vars:\r\n        assert getattr(args, i) is not None\r\n    print(\"Success\")\r\n\r\n\r\nif __name__ == '__main__':\r\n    main()\r\n"
  },
  {
    "path": "gooey/tests/auto_start.py",
    "content": "import sys\r\n\r\nfrom gooey import Gooey\r\nfrom gooey import GooeyParser\r\nfrom argparse import ArgumentParser\r\n\r\n@Gooey(\r\n    progress_regex=r\"^progress: (-?\\d+)%$\",\r\n    disable_progress_bar_animation=True,\r\n    dump_build_config=True,\r\n    show_success_modal=False,\r\n    auto_start=True\r\n)\r\ndef main():\r\n    desc = \"Example application to show Gooey's various widgets\"\r\n    parser = GooeyParser(prog=\"example_progress_bar_1\")\r\n    _ = parser.parse_args(sys.argv[1:])\r\n\r\n    import time\r\n    time.sleep(1)\r\n    print('Success')\r\n\r\nif __name__ == '__main__':\r\n    main()\r\n"
  },
  {
    "path": "gooey/tests/dynamics/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/tests/dynamics/files/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/tests/dynamics/files/basic.py",
    "content": "from argparse import ArgumentParser\n\nfrom gooey import Events, Gooey\n\n\nwith open('tmp.txt', 'w') as f:\n    import sys\n    f.write(str(sys.argv))\n\ndef make_parser():\n    parser = ArgumentParser()\n    parser.add_argument('foo', type=int)\n    return parser\n\n@Gooey(use_events=[Events.VALIDATE_FORM])\ndef main():\n    parser = make_parser()\n    print(parser.parse_args())\n    print('DONE')\n\n\nif __name__ == '__main__':\n    main()\n\n"
  },
  {
    "path": "gooey/tests/dynamics/files/lifecycles.py",
    "content": "from argparse import ArgumentParser\n\nfrom gooey import Events, Gooey, GooeyParser\nfrom gooey import types as t\n\n\nwith open('tmp.txt', 'w') as f:\n    import sys\n    f.write(str(sys.argv))\n\n\n\ndef handle_success(args, state: t.PublicGooeyState):\n    field = state['active_form'][0]\n    field['value'] = 'success'\n    return {**state, 'active_form': [field]}\n\n\ndef handle_error(args, state: t.PublicGooeyState):\n    field = state['active_form'][0]\n    field['value'] = 'error'\n    return {**state, 'active_form': [field]}\n\n\ndef make_parser():\n    parser = GooeyParser(on_error=handle_error, on_success=handle_success)\n    parser.add_argument('foo')\n    return parser\n\n@Gooey(use_events=[Events.ON_ERROR, Events.ON_SUCCESS])\ndef main():\n    parser = make_parser()\n    args = parser.parse_args()\n    if args.foo == 'fail':\n        raise Exception('EXCEPTION')\n    print('DONE')\n\n\nif __name__ == '__main__':\n    main()\n\n"
  },
  {
    "path": "gooey/tests/dynamics/files/tmp.txt",
    "content": "['C:/Users/Chris/Documents/Gooey/gooey/tests/dynamics/files/basic.py', '--ignore-gooey', '--', '1234']"
  },
  {
    "path": "gooey/tests/dynamics/test_dynamics.py",
    "content": "import unittest\nfrom argparse import ArgumentParser\nfrom typing import Dict\nfrom unittest.mock import MagicMock\n\nfrom python_bindings.dynamics import patch_argument, monkey_patch_for_form_validation\n\n\nclass TestDynamicUpdates(unittest.TestCase):\n\n    def tearDown(self):\n        \"\"\"\n        Undoes the monkey patching after every tests\n        \"\"\"\n        if hasattr(ArgumentParser, 'original_parse_args'):\n            ArgumentParser.parse_args = ArgumentParser.original_parse_args\n\n    def test_patch_argument(self):\n        \"\"\"\n        Asserting that regardless of parser complexity, we attach our\n        new argument at every level.\n        \"\"\"\n        parser = ArgumentParser()\n        subparsers = parser.add_subparsers()\n        # multiple subparsers\n        a = subparsers.add_parser('a')\n        b = subparsers.add_parser('b')\n\n        a.add_argument('--level-1')\n        b.add_argument('--level-1')\n\n        # deeply nested subparsers\n        a_subparsers = a.add_subparsers()\n        b_subparsers = b.add_subparsers()\n\n        # nested args:\n        a__nested = a_subparsers.add_parser('a1')\n        b__nested = b_subparsers.add_parser('b1')\n\n        a__nested.add_argument('--level-2')\n        b__nested.add_argument('--level-2')\n\n        # sanity check / showing the parser behavior\n        # we've got two levels of parser nesting, each level\n        # has some options available.\n        mock = MagicMock()\n        ArgumentParser.error = mock\n        assert parser.parse_args('a --level-1 some-value'.split())\n        assert parser.parse_args('b --level-1 some-value'.split())\n        assert parser.parse_args('a a1 --level-2 some-value'.split())\n        assert parser.parse_args('b b1 --level-2 some-value'.split())\n        assert not mock.called\n\n        # if we try passing an arbitrary unknown flag we explode\n        # patching over the `error` method which usually sys.exit's\n        # for any errors.\n        parser.parse_args('a --level-1 some-value --some-flag'.split())\n        assert mock.called\n\n        patch_argument(parser, '--some-flag', action='store_true')\n        mock.reset_mock()\n        # now ever call combination accepts the flag we added\n        assert parser.parse_args('--some-flag'.split())\n        assert parser.parse_args('a --level-1 some-value --some-flag'.split())\n        assert parser.parse_args('b --level-1 some-value --some-flag'.split())\n        assert parser.parse_args('a a1 --level-2 some-value --some-flag'.split())\n        assert parser.parse_args('b b1 --level-2 some-value --some-flag'.split())\n        assert not mock.called\n\n"
  },
  {
    "path": "gooey/tests/dynamics/test_live_updates.py",
    "content": "import sys\nimport unittest\nfrom copy import deepcopy\n\nfrom gooey import Events\nfrom gooey.tests.harness import instrumentGooey\nfrom gooey.tests import *\n\nclass TestLiveDynamicUpdates(unittest.TestCase):\n\n    def test_validate_form(self):\n        \"\"\"\n        Integration testing the Dynamic Validation features.\n        \"\"\"\n        # Because it's a live test, nothing is mocked. This basic.py file\n        # will be called via subprocess as part of the test. As such, we\n        # grab both its path on disk (for use as a target for Gooey) as\n        # well as its parser instance (so that we can bootstrap)\n        from gooey.tests.dynamics.files import basic\n        params = {\n            'target': '{} -u {}'.format(sys.executable, basic.__file__),\n            'use_events': [Events.VALIDATE_FORM],\n        }\n        with instrumentGooey(basic.make_parser(), **params) as (app, frame, gapp):\n            # the parser has a single arg of type int.\n            # We purposefully give it invalid input for the sake of the test.\n            gapp.getActiveConfig().widgetsMap['foo'].setValue('not a number')\n            # and make sure we're not somehow starting with an error\n            self.assertEqual(gapp.getActiveFormState()[0]['error'], '')\n            gapp.onStart()\n\n            # All subprocess calls ultimately pump though wx's event queue\n            # so we have to kick off the mainloop and let it run long enough\n            # to let the subprocess complete and the event queue flush\n            wx.CallLater(2500, app.ExitMainLoop)\n            app.MainLoop()\n            # after the subprocess call is complete, our UI should have\n            # been updated with the data dynamically returned from the\n            # basic.py invocation.\n            self.assertIn('invalid literal', gapp.getActiveFormState()[0]['error'])\n\n\n    def test_validate_form_without_errors(self):\n        from gooey.tests.dynamics.files import basic\n        params = {\n            'target': '{} -u {}'.format(sys.executable, basic.__file__),\n            'use_events': [Events.VALIDATE_FORM],\n            # setting to false because it interferes with the test\n            'show_success_modal': False\n        }\n        with instrumentGooey(basic.make_parser(), **params) as (app, frame, gapp):\n            gapp.getActiveConfig().widgetsMap['foo'].setValue('10')  # valid int\n            self.assertEqual(gapp.getActiveFormState()[0]['error'], '')\n            gapp.onStart()\n\n            wx.CallLater(2500, app.ExitMainLoop)\n            app.MainLoop()\n            # no errors blocked the run, so we should have executed and finished.\n            # we're now on the success screen.\n            self.assertEqual(gapp.state['image'], gapp.state['images']['successIcon'])\n            # and indeed no errors were written to the UI\n            self.assertEqual(gapp.getActiveFormState()[0]['error'], '')\n            # and we find the expected output written to the console\n            # rather than some unexpected error\n            self.assertIn('DONE', frame.FindWindowByName(\"console\").getText())\n\n\n    def test_lifecycle_handlers(self):\n        cases = [\n            {'input': 'happy path', 'expected_stdout': 'DONE', 'expected_update': 'success'},\n            {'input': 'fail', 'expected_stdout': 'EXCEPTION', 'expected_update': 'error'}\n        ]\n        from gooey.tests.dynamics.files import lifecycles\n        params = {\n            'target': '{} -u {}'.format(sys.executable, lifecycles.__file__),\n            'use_events': [Events.ON_SUCCESS, Events.ON_ERROR],\n            'show_success_modal': False,\n            'show_failure_modal': False\n        }\n        for case in cases:\n            with self.subTest(case):\n                with instrumentGooey(lifecycles.make_parser(), **params) as (app, frame, gapp):\n                    gapp.getActiveConfig().widgetsMap['foo'].setValue(case['input'])\n                    gapp.onStart()\n                    # give everything a chance to run\n                    wx.CallLater(2000, app.ExitMainLoop)\n                    app.MainLoop()\n                    # `lifecycle.py` is set up to raise an exception for certain inputs\n                    # so we check that we find our expected stdout here\n                    console = frame.FindWindowByName(\"console\")\n                    self.assertIn(case['expected_stdout'], console.getText())\n\n                    # Now, based on what happened during the run (success/exception) our\n                    # respective lifecycle handler should have been called. These are\n                    # configured to update the form field in the UI with a relevant value.\n                    # Thus we we're checking here to see that out input has changed, and now\n                    # matches the value we expect from the handler\n                    textfield = gapp.getActiveFormState()[0]\n                    print(case['expected_update'], textfield['value'])\n                    self.assertEqual(case['expected_update'], textfield['value'])\n\n"
  },
  {
    "path": "gooey/tests/dynamics/tmp.txt",
    "content": "['C:\\\\Users\\\\Chris\\\\Documents\\\\Gooey\\\\gooey\\\\tests\\\\dynamics\\\\files\\\\basic.py', '--ignore-gooey', '--', '10']"
  },
  {
    "path": "gooey/tests/gooey_config__autostart.json",
    "content": "{\r\n  \"default_size\": [\r\n    610,\r\n    530\r\n  ],\r\n  \"terminal_font_weight\": null,\r\n  \"header_bg_color\": \"#ffffff\",\r\n  \"show_success_modal\": false,\r\n  \"num_optional_cols\": 2,\r\n  \"layout\": \"standard\",\r\n  \"language_dir\": \"F:\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\gooey\\\\languages\",\r\n  \"terminal_panel_color\": \"#F0F0F0\",\r\n  \"footer_bg_color\": \"#f0f0f0\",\r\n  \"image_dir\": \"::gooey/default\",\r\n  \"terminal_font_family\": null,\r\n  \"progress_expr\": null,\r\n  \"header_show_subtitle\": true,\r\n  \"show_sidebar\": false,\r\n  \"progress_regex\": \"^progress: (-?\\\\d+)%$\",\r\n  \"header_height\": 80,\r\n  \"run_validators\": true,\r\n  \"poll_external_updates\": false,\r\n  \"disable_stop_button\": false,\r\n  \"manual_start\": false,\r\n  \"header_show_title\": true,\r\n  \"language\": \"english\",\r\n  \"header_image_center\": false,\r\n  \"tabbed_groups\": false,\r\n  \"program_name\": \"auto_start\",\r\n  \"sidebar_bg_color\": \"#f2f2f2\",\r\n  \"monospace_display\": false,\r\n  \"program_description\": \"\",\r\n  \"terminal_font_color\": \"#000000\",\r\n  \"disable_progress_bar_animation\": true,\r\n  \"group_by_type\": true,\r\n  \"target\": \"\\\"F:\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\venv3\\\\Scripts\\\\python.exe\\\" -u \\\"F:/Dropbox/pretty_gui/Gooey/gooey/tests/auto_start.py\\\"\",\r\n  \"encoding\": \"utf-8\",\r\n  \"auto_start\": true,\r\n  \"error_color\": \"#ea7878\",\r\n  \"show_advanced\": true,\r\n  \"sidebar_title\": \"Actions\",\r\n  \"body_bg_color\": \"#f0f0f0\",\r\n  \"widgets\": {\r\n    \"example_progress_bar_1\": {\r\n      \"command\": \"::gooey/default\",\r\n      \"contents\": []\r\n    }\r\n  },\r\n  \"navigation\": \"SIDEBAR\",\r\n  \"terminal_font_size\": null,\r\n  \"use_legacy_titles\": true,\r\n  \"num_required_cols\": 2,\r\n  \"show_stop_warning\": true\r\n}"
  },
  {
    "path": "gooey/tests/gooey_config__normal.json",
    "content": "{\r\n  \"progress_expr\": null,\r\n  \"disable_stop_button\": false,\r\n  \"header_bg_color\": \"#ffffff\",\r\n  \"progress_regex\": null,\r\n  \"terminal_font_family\": null,\r\n  \"disable_progress_bar_animation\": false,\r\n  \"error_color\": \"#ea7878\",\r\n  \"language\": \"english\",\r\n  \"run_validators\": true,\r\n  \"show_sidebar\": true,\r\n  \"program_description\": \"Example application to show Gooey's various widgets\",\r\n  \"image_dir\": \"::gooey/default\",\r\n  \"auto_start\": false,\r\n  \"target\": \"\\\"F:\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\venv3\\\\Scripts\\\\python.exe\\\" -u \\\"F:/Dropbox/pretty_gui/Gooey/gooey/tests/all_widgets.py\\\"\",\r\n  \"header_image_center\": false,\r\n  \"layout\": \"standard\",\r\n  \"program_name\": \"all_widgets\",\r\n  \"show_success_modal\": false,\r\n  \"monospace_display\": false,\r\n  \"num_optional_cols\": 2,\r\n  \"encoding\": \"utf-8\",\r\n  \"tabbed_groups\": false,\r\n  \"poll_external_updates\": false,\r\n  \"body_bg_color\": \"#f0f0f0\",\r\n  \"show_stop_warning\": true,\r\n  \"widgets\": {\r\n    \"all_widgets.py\": {\r\n      \"contents\": [\r\n        {\r\n          \"name\": \"Optional Arguments\",\r\n          \"items\": [\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"TextField\",\r\n              \"required\": false,\r\n              \"id\": \"--textfield\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": 2,\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--textfield\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"textfield\",\r\n                \"dest\": \"textfield\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"Textarea\",\r\n              \"required\": false,\r\n              \"id\": \"--textarea\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": \"oneline twoline\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--textarea\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"textarea\",\r\n                \"dest\": \"textarea\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"PasswordField\",\r\n              \"required\": false,\r\n              \"id\": \"--password\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": \"hunter42\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--password\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"password\",\r\n                \"dest\": \"password\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"CommandField\",\r\n              \"required\": false,\r\n              \"id\": \"--commandfield\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": \"cmdr\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--commandfield\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"commandfield\",\r\n                \"dest\": \"commandfield\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"Dropdown\",\r\n              \"required\": false,\r\n              \"id\": \"--dropdown\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"one\",\r\n                  \"two\"\r\n                ],\r\n                \"default\": \"two\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--dropdown\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"dropdown\",\r\n                \"dest\": \"dropdown\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validate\": \"\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"hide_text\": true,\r\n                \"heading_color\": \"\",\r\n                \"text_color\": \"\",\r\n                \"error_color\": \"#ea7878\",\r\n                \"height\": 300,\r\n                \"hide_heading\": true,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"Listbox\",\r\n              \"required\": false,\r\n              \"id\": \"--listboxie\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"Option one\",\r\n                  \"Option two\",\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"default\": [\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--listboxie\"\r\n                ],\r\n                \"nargs\": \"+\",\r\n                \"display_name\": \"listboxie\",\r\n                \"dest\": \"listboxie\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"Counter\",\r\n              \"required\": false,\r\n              \"id\": \"-c\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"1\",\r\n                  \"2\",\r\n                  \"3\",\r\n                  \"4\",\r\n                  \"5\",\r\n                  \"6\",\r\n                  \"7\",\r\n                  \"8\",\r\n                  \"9\",\r\n                  \"10\"\r\n                ],\r\n                \"default\": 3,\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"-c\",\r\n                  \"--counter\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"counter\",\r\n                \"dest\": \"counter\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"CheckBox\",\r\n              \"required\": false,\r\n              \"id\": \"-o\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": true,\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"-o\",\r\n                  \"--overwrite\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"overwrite\",\r\n                \"dest\": \"overwrite\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"initial_selection\": 1\r\n              },\r\n              \"type\": \"RadioGroup\",\r\n              \"required\": true,\r\n              \"id\": \"5aa16ff9-9bd8-46b0-a7e4-7c209d34f0bc\",\r\n              \"data\": {\r\n                \"widgets\": [\r\n                  {\r\n                    \"options\": {\r\n                      \"validator\": {\r\n                        \"test\": \"True\",\r\n                        \"message\": \"\"\r\n                      },\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      },\r\n                      \"error_color\": \"#ea7878\"\r\n                    },\r\n                    \"type\": \"CheckBox\",\r\n                    \"required\": false,\r\n                    \"id\": \"--mutexone\",\r\n                    \"cli_type\": \"optional\",\r\n                    \"data\": {\r\n                      \"choices\": [],\r\n                      \"default\": true,\r\n                      \"help\": \"Show more details\",\r\n                      \"required\": false,\r\n                      \"commands\": [\r\n                        \"--mutexone\"\r\n                      ],\r\n                      \"nargs\": \"\",\r\n                      \"display_name\": \"mutexone\",\r\n                      \"dest\": \"mutexone\"\r\n                    }\r\n                  },\r\n                  {\r\n                    \"options\": {\r\n                      \"validator\": {\r\n                        \"test\": \"True\",\r\n                        \"message\": \"\"\r\n                      },\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      },\r\n                      \"error_color\": \"#ea7878\"\r\n                    },\r\n                    \"type\": \"TextField\",\r\n                    \"required\": false,\r\n                    \"id\": \"--mutextwo\",\r\n                    \"cli_type\": \"optional\",\r\n                    \"data\": {\r\n                      \"choices\": [],\r\n                      \"default\": \"mut-2\",\r\n                      \"help\": null,\r\n                      \"required\": false,\r\n                      \"commands\": [\r\n                        \"--mutextwo\"\r\n                      ],\r\n                      \"nargs\": \"\",\r\n                      \"display_name\": \"mutextwo\",\r\n                      \"dest\": \"mutextwo\"\r\n                    }\r\n                  }\r\n                ],\r\n                \"commands\": [\r\n                  [\r\n                    \"--mutexone\"\r\n                  ],\r\n                  [\r\n                    \"--mutextwo\"\r\n                  ]\r\n                ]\r\n              },\r\n              \"cli_type\": \"optional\",\r\n              \"group_name\": \"Choose Option\"\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"FileChooser\",\r\n              \"required\": false,\r\n              \"id\": \"--filechooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": \"fc-value\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--filechooser\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"filechooser\",\r\n                \"dest\": \"filechooser\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"FileSaver\",\r\n              \"required\": false,\r\n              \"id\": \"--filesaver\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": \"fs-value\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--filesaver\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"filesaver\",\r\n                \"dest\": \"filesaver\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"DirChooser\",\r\n              \"required\": false,\r\n              \"id\": \"--dirchooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": \"dc-value\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--dirchooser\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"dirchooser\",\r\n                \"dest\": \"dirchooser\"\r\n              }\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"type\": \"DateChooser\",\r\n              \"required\": false,\r\n              \"id\": \"--datechooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"default\": \"2015-01-01\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"commands\": [\r\n                  \"--datechooser\"\r\n                ],\r\n                \"nargs\": \"\",\r\n                \"display_name\": \"datechooser\",\r\n                \"dest\": \"datechooser\"\r\n              }\r\n            }\r\n          ],\r\n          \"groups\": [],\r\n          \"description\": null,\r\n          \"options\": {\r\n            \"columns\": 2,\r\n            \"show_border\": false,\r\n            \"padding\": 10\r\n          }\r\n        }\r\n      ],\r\n      \"command\": \"::gooey/default\"\r\n    }\r\n  },\r\n  \"terminal_font_weight\": null,\r\n  \"default_size\": [\r\n    610,\r\n    530\r\n  ],\r\n  \"footer_bg_color\": \"#f0f0f0\",\r\n  \"num_required_cols\": 2,\r\n  \"terminal_panel_color\": \"#F0F0F0\",\r\n  \"use_legacy_titles\": true,\r\n  \"group_by_type\": true,\r\n  \"manual_start\": false,\r\n  \"language_dir\": \"F:\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\gooey\\\\languages\",\r\n  \"terminal_font_size\": null,\r\n  \"navigation\": \"SIDEBAR\",\r\n  \"header_height\": 80,\r\n  \"header_show_title\": true,\r\n  \"terminal_font_color\": \"#000000\",\r\n  \"sidebar_bg_color\": \"#f2f2f2\",\r\n  \"show_advanced\": true,\r\n  \"header_show_subtitle\": true,\r\n  \"sidebar_title\": \"Your Custom Title\"\r\n}"
  },
  {
    "path": "gooey/tests/gooey_config__subparser.json",
    "content": "{\r\n  \"default_size\": [\r\n    610,\r\n    530\r\n  ],\r\n  \"header_height\": 80,\r\n  \"language\": \"english\",\r\n  \"terminal_font_weight\": null,\r\n  \"auto_start\": false,\r\n  \"num_optional_cols\": 2,\r\n  \"monospace_display\": false,\r\n  \"progress_expr\": null,\r\n  \"widgets\": {\r\n    \"Parser 1\": {\r\n      \"command\": \"parser1\",\r\n      \"contents\": [\r\n        {\r\n          \"groups\": [],\r\n          \"description\": null,\r\n          \"name\": \"Optional Arguments\",\r\n          \"options\": {\r\n            \"show_border\": false,\r\n            \"columns\": 2,\r\n            \"padding\": 10\r\n          },\r\n          \"items\": [\r\n            {\r\n              \"id\": \"--textfield\",\r\n              \"type\": \"TextField\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"textfield\",\r\n                \"required\": false,\r\n                \"display_name\": \"textfield\",\r\n                \"default\": 2,\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--textfield\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--textarea\",\r\n              \"type\": \"Textarea\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"textarea\",\r\n                \"required\": false,\r\n                \"display_name\": \"textarea\",\r\n                \"default\": \"oneline twoline\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--textarea\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--password\",\r\n              \"type\": \"PasswordField\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"password\",\r\n                \"required\": false,\r\n                \"display_name\": \"password\",\r\n                \"default\": \"hunter42\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--password\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--commandfield\",\r\n              \"type\": \"CommandField\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"commandfield\",\r\n                \"required\": false,\r\n                \"display_name\": \"commandfield\",\r\n                \"default\": \"cmdr\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--commandfield\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--dropdown\",\r\n              \"type\": \"Dropdown\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"one\",\r\n                  \"two\"\r\n                ],\r\n                \"dest\": \"dropdown\",\r\n                \"required\": false,\r\n                \"display_name\": \"dropdown\",\r\n                \"default\": \"two\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--dropdown\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--listboxie\",\r\n              \"type\": \"Listbox\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"Option one\",\r\n                  \"Option two\",\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"dest\": \"listboxie\",\r\n                \"required\": false,\r\n                \"display_name\": \"listboxie\",\r\n                \"default\": [\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"help\": null,\r\n                \"nargs\": \"+\",\r\n                \"commands\": [\r\n                  \"--listboxie\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"height\": 300,\r\n                \"validate\": \"\",\r\n                \"text_color\": \"\",\r\n                \"heading_color\": \"\",\r\n                \"hide_text\": true,\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                },\r\n                \"hide_heading\": true,\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"-c\",\r\n              \"type\": \"Counter\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"1\",\r\n                  \"2\",\r\n                  \"3\",\r\n                  \"4\",\r\n                  \"5\",\r\n                  \"6\",\r\n                  \"7\",\r\n                  \"8\",\r\n                  \"9\",\r\n                  \"10\"\r\n                ],\r\n                \"dest\": \"counter\",\r\n                \"required\": false,\r\n                \"display_name\": \"counter\",\r\n                \"default\": 3,\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"-c\",\r\n                  \"--counter\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"-o\",\r\n              \"type\": \"CheckBox\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"overwrite\",\r\n                \"required\": false,\r\n                \"display_name\": \"overwrite\",\r\n                \"default\": true,\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"-o\",\r\n                  \"--overwrite\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"744eb075-ff4b-4731-9539-795846089b37\",\r\n              \"type\": \"RadioGroup\",\r\n              \"required\": true,\r\n              \"data\": {\r\n                \"widgets\": [\r\n                  {\r\n                    \"id\": \"--mutexone\",\r\n                    \"type\": \"CheckBox\",\r\n                    \"required\": false,\r\n                    \"data\": {\r\n                      \"choices\": [],\r\n                      \"dest\": \"mutexone\",\r\n                      \"required\": false,\r\n                      \"display_name\": \"mutexone\",\r\n                      \"default\": true,\r\n                      \"help\": \"Show more details\",\r\n                      \"nargs\": \"\",\r\n                      \"commands\": [\r\n                        \"--mutexone\"\r\n                      ]\r\n                    },\r\n                    \"options\": {\r\n                      \"error_color\": \"#ea7878\",\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      },\r\n                      \"validator\": {\r\n                        \"message\": \"\",\r\n                        \"test\": \"True\"\r\n                      }\r\n                    },\r\n                    \"cli_type\": \"optional\"\r\n                  },\r\n                  {\r\n                    \"id\": \"--mutextwo\",\r\n                    \"type\": \"TextField\",\r\n                    \"required\": false,\r\n                    \"data\": {\r\n                      \"choices\": [],\r\n                      \"dest\": \"mutextwo\",\r\n                      \"required\": false,\r\n                      \"display_name\": \"mutextwo\",\r\n                      \"default\": \"mut-2\",\r\n                      \"help\": null,\r\n                      \"nargs\": \"\",\r\n                      \"commands\": [\r\n                        \"--mutextwo\"\r\n                      ]\r\n                    },\r\n                    \"options\": {\r\n                      \"error_color\": \"#ea7878\",\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      },\r\n                      \"validator\": {\r\n                        \"message\": \"\",\r\n                        \"test\": \"True\"\r\n                      }\r\n                    },\r\n                    \"cli_type\": \"optional\"\r\n                  }\r\n                ],\r\n                \"commands\": [\r\n                  [\r\n                    \"--mutexone\"\r\n                  ],\r\n                  [\r\n                    \"--mutextwo\"\r\n                  ]\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"initial_selection\": 1\r\n              },\r\n              \"group_name\": \"Choose Option\",\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--filechooser\",\r\n              \"type\": \"FileChooser\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"filechooser\",\r\n                \"required\": false,\r\n                \"display_name\": \"filechooser\",\r\n                \"default\": \"fc-value\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--filechooser\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--filesaver\",\r\n              \"type\": \"FileSaver\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"filesaver\",\r\n                \"required\": false,\r\n                \"display_name\": \"filesaver\",\r\n                \"default\": \"fs-value\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--filesaver\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--dirchooser\",\r\n              \"type\": \"DirChooser\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"dirchooser\",\r\n                \"required\": false,\r\n                \"display_name\": \"dirchooser\",\r\n                \"default\": \"dc-value\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--dirchooser\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--datechooser\",\r\n              \"type\": \"DateChooser\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"datechooser\",\r\n                \"required\": false,\r\n                \"display_name\": \"datechooser\",\r\n                \"default\": \"2015-01-01\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--datechooser\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            }\r\n          ]\r\n        }\r\n      ]\r\n    },\r\n    \"parser 2\": {\r\n      \"command\": \"parser2\",\r\n      \"contents\": [\r\n        {\r\n          \"groups\": [],\r\n          \"description\": null,\r\n          \"name\": \"Optional Arguments\",\r\n          \"options\": {\r\n            \"show_border\": false,\r\n            \"columns\": 2,\r\n            \"padding\": 10\r\n          },\r\n          \"items\": [\r\n            {\r\n              \"id\": \"--textfield\",\r\n              \"type\": \"TextField\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"textfield\",\r\n                \"required\": false,\r\n                \"display_name\": \"textfield\",\r\n                \"default\": 2,\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--textfield\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--textarea\",\r\n              \"type\": \"Textarea\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"textarea\",\r\n                \"required\": false,\r\n                \"display_name\": \"textarea\",\r\n                \"default\": \"oneline twoline\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--textarea\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--password\",\r\n              \"type\": \"PasswordField\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"password\",\r\n                \"required\": false,\r\n                \"display_name\": \"password\",\r\n                \"default\": \"hunter42\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--password\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--commandfield\",\r\n              \"type\": \"CommandField\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"commandfield\",\r\n                \"required\": false,\r\n                \"display_name\": \"commandfield\",\r\n                \"default\": \"cmdr\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--commandfield\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--dropdown\",\r\n              \"type\": \"Dropdown\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"one\",\r\n                  \"two\"\r\n                ],\r\n                \"dest\": \"dropdown\",\r\n                \"required\": false,\r\n                \"display_name\": \"dropdown\",\r\n                \"default\": \"two\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--dropdown\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--listboxie\",\r\n              \"type\": \"Listbox\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"Option one\",\r\n                  \"Option two\",\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"dest\": \"listboxie\",\r\n                \"required\": false,\r\n                \"display_name\": \"listboxie\",\r\n                \"default\": [\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"help\": null,\r\n                \"nargs\": \"+\",\r\n                \"commands\": [\r\n                  \"--listboxie\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"height\": 300,\r\n                \"validate\": \"\",\r\n                \"text_color\": \"\",\r\n                \"heading_color\": \"\",\r\n                \"hide_text\": true,\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                },\r\n                \"hide_heading\": true,\r\n                \"error_color\": \"#ea7878\"\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"-c\",\r\n              \"type\": \"Counter\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [\r\n                  \"1\",\r\n                  \"2\",\r\n                  \"3\",\r\n                  \"4\",\r\n                  \"5\",\r\n                  \"6\",\r\n                  \"7\",\r\n                  \"8\",\r\n                  \"9\",\r\n                  \"10\"\r\n                ],\r\n                \"dest\": \"counter\",\r\n                \"required\": false,\r\n                \"display_name\": \"counter\",\r\n                \"default\": 3,\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"-c\",\r\n                  \"--counter\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"-o\",\r\n              \"type\": \"CheckBox\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"overwrite\",\r\n                \"required\": false,\r\n                \"display_name\": \"overwrite\",\r\n                \"default\": true,\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"-o\",\r\n                  \"--overwrite\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"18fe890f-3df3-43d4-b842-cf9ad3d02178\",\r\n              \"type\": \"RadioGroup\",\r\n              \"required\": true,\r\n              \"data\": {\r\n                \"widgets\": [\r\n                  {\r\n                    \"id\": \"--mutexone\",\r\n                    \"type\": \"CheckBox\",\r\n                    \"required\": false,\r\n                    \"data\": {\r\n                      \"choices\": [],\r\n                      \"dest\": \"mutexone\",\r\n                      \"required\": false,\r\n                      \"display_name\": \"mutexone\",\r\n                      \"default\": true,\r\n                      \"help\": \"Show more details\",\r\n                      \"nargs\": \"\",\r\n                      \"commands\": [\r\n                        \"--mutexone\"\r\n                      ]\r\n                    },\r\n                    \"options\": {\r\n                      \"error_color\": \"#ea7878\",\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      },\r\n                      \"validator\": {\r\n                        \"message\": \"\",\r\n                        \"test\": \"True\"\r\n                      }\r\n                    },\r\n                    \"cli_type\": \"optional\"\r\n                  },\r\n                  {\r\n                    \"id\": \"--mutextwo\",\r\n                    \"type\": \"TextField\",\r\n                    \"required\": false,\r\n                    \"data\": {\r\n                      \"choices\": [],\r\n                      \"dest\": \"mutextwo\",\r\n                      \"required\": false,\r\n                      \"display_name\": \"mutextwo\",\r\n                      \"default\": \"mut-2\",\r\n                      \"help\": null,\r\n                      \"nargs\": \"\",\r\n                      \"commands\": [\r\n                        \"--mutextwo\"\r\n                      ]\r\n                    },\r\n                    \"options\": {\r\n                      \"error_color\": \"#ea7878\",\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      },\r\n                      \"validator\": {\r\n                        \"message\": \"\",\r\n                        \"test\": \"True\"\r\n                      }\r\n                    },\r\n                    \"cli_type\": \"optional\"\r\n                  }\r\n                ],\r\n                \"commands\": [\r\n                  [\r\n                    \"--mutexone\"\r\n                  ],\r\n                  [\r\n                    \"--mutextwo\"\r\n                  ]\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"initial_selection\": 1\r\n              },\r\n              \"group_name\": \"Choose Option\",\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--filechooser\",\r\n              \"type\": \"FileChooser\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"filechooser\",\r\n                \"required\": false,\r\n                \"display_name\": \"filechooser\",\r\n                \"default\": \"fc-value\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--filechooser\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--filesaver\",\r\n              \"type\": \"FileSaver\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"filesaver\",\r\n                \"required\": false,\r\n                \"display_name\": \"filesaver\",\r\n                \"default\": \"fs-value\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--filesaver\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--dirchooser\",\r\n              \"type\": \"DirChooser\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"dirchooser\",\r\n                \"required\": false,\r\n                \"display_name\": \"dirchooser\",\r\n                \"default\": \"dc-value\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--dirchooser\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            },\r\n            {\r\n              \"id\": \"--datechooser\",\r\n              \"type\": \"DateChooser\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"choices\": [],\r\n                \"dest\": \"datechooser\",\r\n                \"required\": false,\r\n                \"display_name\": \"datechooser\",\r\n                \"default\": \"2015-01-01\",\r\n                \"help\": null,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--datechooser\"\r\n                ]\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"validator\": {\r\n                  \"message\": \"\",\r\n                  \"test\": \"True\"\r\n                }\r\n              },\r\n              \"cli_type\": \"optional\"\r\n            }\r\n          ]\r\n        }\r\n      ]\r\n    }\r\n  },\r\n  \"run_validators\": true,\r\n  \"use_legacy_titles\": true,\r\n  \"header_bg_color\": \"#ffffff\",\r\n  \"disable_progress_bar_animation\": false,\r\n  \"terminal_font_size\": null,\r\n  \"target\": \"\\\"F:\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\venv3\\\\Scripts\\\\python.exe\\\" -u \\\"F:/Dropbox/pretty_gui/Gooey/gooey/tests/all_widgets_subparser.py\\\"\",\r\n  \"body_bg_color\": \"#f0f0f0\",\r\n  \"show_stop_warning\": true,\r\n  \"image_dir\": \"::gooey/default\",\r\n  \"progress_regex\": null,\r\n  \"program_description\": \"\",\r\n  \"header_image_center\": false,\r\n  \"header_show_title\": true,\r\n  \"program_name\": \"Subparser Demo\",\r\n  \"terminal_font_family\": null,\r\n  \"footer_bg_color\": \"#f0f0f0\",\r\n  \"poll_external_updates\": false,\r\n  \"sidebar_title\": \"Actions\",\r\n  \"layout\": \"standard\",\r\n  \"navigation\": \"SIDEBAR\",\r\n  \"num_required_cols\": 2,\r\n  \"tabbed_groups\": false,\r\n  \"disable_stop_button\": false,\r\n  \"encoding\": \"utf-8\",\r\n  \"terminal_panel_color\": \"#F0F0F0\",\r\n  \"group_by_type\": true,\r\n  \"terminal_font_color\": \"#000000\",\r\n  \"manual_start\": false,\r\n  \"show_success_modal\": false,\r\n  \"language_dir\": \"F:\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\gooey\\\\languages\",\r\n  \"show_sidebar\": true,\r\n  \"sidebar_bg_color\": \"#f2f2f2\",\r\n  \"error_color\": \"#ea7878\",\r\n  \"show_advanced\": true,\r\n  \"header_show_subtitle\": true\r\n}"
  },
  {
    "path": "gooey/tests/gooey_config__validation.json",
    "content": "{\r\n  \"image_dir\": \"::gooey/default\",\r\n  \"progress_regex\": null,\r\n  \"error_color\": \"#ea7878\",\r\n  \"navigation\": \"SIDEBAR\",\r\n  \"encoding\": \"utf-8\",\r\n  \"run_validators\": true,\r\n  \"default_size\": [\r\n    610,\r\n    530\r\n  ],\r\n  \"num_required_cols\": 2,\r\n  \"program_description\": \"Example application to show Gooey's various widgets\",\r\n  \"poll_external_updates\": false,\r\n  \"header_bg_color\": \"#ffffff\",\r\n  \"footer_bg_color\": \"#f0f0f0\",\r\n  \"sidebar_title\": \"Your Custom Title\",\r\n  \"sidebar_bg_color\": \"#f2f2f2\",\r\n  \"program_name\": \"all_widgets\",\r\n  \"progress_expr\": null,\r\n  \"language\": \"english\",\r\n  \"auto_start\": false,\r\n  \"show_stop_warning\": true,\r\n  \"disable_stop_button\": false,\r\n  \"use_legacy_titles\": true,\r\n  \"terminal_font_family\": null,\r\n  \"show_advanced\": true,\r\n  \"header_height\": 80,\r\n  \"widgets\": {\r\n    \"all_widgets.py\": {\r\n      \"command\": \"::gooey/default\",\r\n      \"contents\": [\r\n        {\r\n          \"items\": [\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--textfield\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"textfield\",\r\n                \"choices\": [],\r\n                \"dest\": \"textfield\",\r\n                \"nargs\": \"\",\r\n                \"default\": 2,\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--textfield\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"int(user_input) > 5\",\r\n                  \"message\": \"number must be greater than 5\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"TextField\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--textarea\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"textarea\",\r\n                \"choices\": [],\r\n                \"dest\": \"textarea\",\r\n                \"nargs\": \"\",\r\n                \"default\": \"oneline twoline\",\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--textarea\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"Textarea\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--password\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"password\",\r\n                \"choices\": [],\r\n                \"dest\": \"password\",\r\n                \"nargs\": \"\",\r\n                \"default\": \"hunter42\",\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--password\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"PasswordField\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--commandfield\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"commandfield\",\r\n                \"choices\": [],\r\n                \"dest\": \"commandfield\",\r\n                \"nargs\": \"\",\r\n                \"default\": \"cmdr\",\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--commandfield\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"CommandField\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--dropdown\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"dropdown\",\r\n                \"choices\": [\r\n                  \"one\",\r\n                  \"two\"\r\n                ],\r\n                \"dest\": \"dropdown\",\r\n                \"nargs\": \"\",\r\n                \"default\": \"two\",\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--dropdown\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"Dropdown\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--listboxie\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"listboxie\",\r\n                \"choices\": [\r\n                  \"Option one\",\r\n                  \"Option two\",\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"dest\": \"listboxie\",\r\n                \"nargs\": \"+\",\r\n                \"default\": [\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--listboxie\",\r\n              \"options\": {\r\n                \"text_color\": \"\",\r\n                \"hide_text\": true,\r\n                \"validate\": \"\",\r\n                \"error_color\": \"#ea7878\",\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"heading_color\": \"\",\r\n                \"height\": 300,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"hide_heading\": true\r\n              },\r\n              \"type\": \"Listbox\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"-c\",\r\n                  \"--counter\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"counter\",\r\n                \"choices\": [\r\n                  \"1\",\r\n                  \"2\",\r\n                  \"3\",\r\n                  \"4\",\r\n                  \"5\",\r\n                  \"6\",\r\n                  \"7\",\r\n                  \"8\",\r\n                  \"9\",\r\n                  \"10\"\r\n                ],\r\n                \"dest\": \"counter\",\r\n                \"nargs\": \"\",\r\n                \"default\": 3,\r\n                \"required\": false\r\n              },\r\n              \"id\": \"-c\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"Counter\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"-o\",\r\n                  \"--overwrite\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"overwrite\",\r\n                \"choices\": [],\r\n                \"dest\": \"overwrite\",\r\n                \"nargs\": \"\",\r\n                \"default\": true,\r\n                \"required\": false\r\n              },\r\n              \"id\": \"-o\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"CheckBox\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"options\": {\r\n                \"initial_selection\": 1\r\n              },\r\n              \"data\": {\r\n                \"commands\": [\r\n                  [\r\n                    \"--mutexone\"\r\n                  ],\r\n                  [\r\n                    \"--mutextwo\"\r\n                  ]\r\n                ],\r\n                \"widgets\": [\r\n                  {\r\n                    \"data\": {\r\n                      \"commands\": [\r\n                        \"--mutexone\"\r\n                      ],\r\n                      \"help\": \"Show more details\",\r\n                      \"display_name\": \"mutexone\",\r\n                      \"choices\": [],\r\n                      \"dest\": \"mutexone\",\r\n                      \"nargs\": \"\",\r\n                      \"default\": true,\r\n                      \"required\": false\r\n                    },\r\n                    \"id\": \"--mutexone\",\r\n                    \"options\": {\r\n                      \"error_color\": \"#ea7878\",\r\n                      \"validator\": {\r\n                        \"test\": \"True\",\r\n                        \"message\": \"\"\r\n                      },\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      }\r\n                    },\r\n                    \"type\": \"CheckBox\",\r\n                    \"cli_type\": \"optional\",\r\n                    \"required\": false\r\n                  },\r\n                  {\r\n                    \"data\": {\r\n                      \"commands\": [\r\n                        \"--mutextwo\"\r\n                      ],\r\n                      \"help\": null,\r\n                      \"display_name\": \"mutextwo\",\r\n                      \"choices\": [],\r\n                      \"dest\": \"mutextwo\",\r\n                      \"nargs\": \"\",\r\n                      \"default\": \"mut-2\",\r\n                      \"required\": false\r\n                    },\r\n                    \"id\": \"--mutextwo\",\r\n                    \"options\": {\r\n                      \"error_color\": \"#ea7878\",\r\n                      \"validator\": {\r\n                        \"test\": \"True\",\r\n                        \"message\": \"\"\r\n                      },\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      }\r\n                    },\r\n                    \"type\": \"TextField\",\r\n                    \"cli_type\": \"optional\",\r\n                    \"required\": false\r\n                  }\r\n                ]\r\n              },\r\n              \"id\": \"08995171-905b-4ec3-9b93-6e91fb1369d7\",\r\n              \"group_name\": \"Choose Option\",\r\n              \"type\": \"RadioGroup\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": true\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--filechooser\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"filechooser\",\r\n                \"choices\": [],\r\n                \"dest\": \"filechooser\",\r\n                \"nargs\": \"\",\r\n                \"default\": \"fc-value\",\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--filechooser\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"FileChooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--filesaver\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"filesaver\",\r\n                \"choices\": [],\r\n                \"dest\": \"filesaver\",\r\n                \"nargs\": \"\",\r\n                \"default\": \"fs-value\",\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--filesaver\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"FileSaver\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--dirchooser\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"dirchooser\",\r\n                \"choices\": [],\r\n                \"dest\": \"dirchooser\",\r\n                \"nargs\": \"\",\r\n                \"default\": \"dc-value\",\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--dirchooser\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"DirChooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            },\r\n            {\r\n              \"data\": {\r\n                \"commands\": [\r\n                  \"--datechooser\"\r\n                ],\r\n                \"help\": null,\r\n                \"display_name\": \"datechooser\",\r\n                \"choices\": [],\r\n                \"dest\": \"datechooser\",\r\n                \"nargs\": \"\",\r\n                \"default\": \"2015-01-01\",\r\n                \"required\": false\r\n              },\r\n              \"id\": \"--datechooser\",\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              },\r\n              \"type\": \"DateChooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false\r\n            }\r\n          ],\r\n          \"name\": \"Optional Arguments\",\r\n          \"groups\": [],\r\n          \"options\": {\r\n            \"show_border\": false,\r\n            \"columns\": 2,\r\n            \"padding\": 10\r\n          },\r\n          \"description\": null\r\n        }\r\n      ]\r\n    }\r\n  },\r\n  \"num_optional_cols\": 2,\r\n  \"tabbed_groups\": false,\r\n  \"terminal_font_weight\": null,\r\n  \"terminal_panel_color\": \"#F0F0F0\",\r\n  \"disable_progress_bar_animation\": false,\r\n  \"target\": \"\\\"F:\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\venv3\\\\Scripts\\\\python.exe\\\" -u \\\"F:/Dropbox/pretty_gui/Gooey/gooey/tests/all_widgets.py\\\"\",\r\n  \"manual_start\": false,\r\n  \"group_by_type\": true,\r\n  \"header_show_title\": true,\r\n  \"show_success_modal\": false,\r\n  \"language_dir\": \"F:\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\gooey\\\\languages\",\r\n  \"header_show_subtitle\": true,\r\n  \"monospace_display\": false,\r\n  \"terminal_font_size\": null,\r\n  \"layout\": \"standard\",\r\n  \"header_image_center\": false,\r\n  \"body_bg_color\": \"#f0f0f0\",\r\n  \"terminal_font_color\": \"#000000\",\r\n  \"show_sidebar\": true\r\n}"
  },
  {
    "path": "gooey/tests/harness.py",
    "content": "from contextlib import contextmanager\n\nimport time\nfrom threading import Thread\nfrom typing import Tuple\n\nimport wx\n\nfrom gooey.gui import bootstrap\nfrom gooey.python_bindings.config_generator import create_from_parser\nfrom gooey.python_bindings.parameters import gooey_params\nfrom gooey.util.functional import merge\nfrom gooey.gui.application.application import RGooey\n\n\n@contextmanager\ndef instrumentGooey(parser, **kwargs) -> Tuple[wx.App, wx.Frame, RGooey]:\n    \"\"\"\n    Context manager used during testing for setup/tear down of the\n    WX infrastructure during subTests.\n\n    Weirdness warning: this uses a globally reused wx.App instance.\n    \"\"\"\n    from gooey.tests import app\n    if app == None:\n        raise Exception(\"App instance has not been created! This is likely due to \"\n                        \"you forgetting to add the magical import which makes all these \"\n                        \"tests work. See the module doc in gooey.tests.__init__ for guidance\")\n    buildspec = create_from_parser(parser, \"\", **gooey_params(**kwargs))\n    app, frame = bootstrap._build_app(buildspec, app)\n    app.SetTopWindow(frame)\n    try:\n        # we need to run the main loop temporarily to get it to\n        # apply any pending updates from the initial creation.\n        # The UI state will be stale otherwise\n        # this works because CallLater just enqueues the message to\n        # be processed. The MainLoop starts running, picks it up, and\n        # then exists\n        wx.CallLater(1, app.ExitMainLoop)\n        app.MainLoop()\n        yield (app, frame, frame._instance)\n    finally:\n        frame.Destroy()\n        del frame\n"
  },
  {
    "path": "gooey/tests/integration/README.md",
    "content": "These integration tests must be run one at a time. I can't figure out how to clear the wx context between runs and Unittest doesn't allow process isolation.. "
  },
  {
    "path": "gooey/tests/integration/__init__.py",
    "content": "\"\"\"\r\nIntegration tests that exercise Gooey's various run modes\r\n\r\nWX Python needs to control the main thread. So, in order to simulate a user\r\nrunning through the system, we have to execute the actual assertions in a\r\ndifferent thread\r\n\"\"\""
  },
  {
    "path": "gooey/tests/integration/integ_autostart.py",
    "content": "import time\r\nimport unittest\r\n\r\nfrom gooey.gui.lang.i18n import _\r\nfrom tests.integration.programs import auto_start as auto_start_module\r\n\r\n\r\nclass TestGooeyIntegration(unittest.TestCase):\r\n\r\n    def test__gooeyAutoStart(self):\r\n        \"\"\"Verifies that issue #201 doesn't regress and auto_start skips the config\r\n        screen and hops right into the client's program\"\"\"\r\n        from gooey.tests.integration import runner\r\n        runner.run_integration(auto_start_module, self.verifyAutoStart, auto_start=True)\r\n\r\n    def verifyAutoStart(self, app, buildSpec):\r\n        \"\"\"\r\n        When the auto_start flag == True Gooey should skip the\r\n        configuration screen\r\n        \"\"\"\r\n        time.sleep(1)\r\n        try:\r\n            # Gooey should NOT be showing the name/description headers\r\n            # present on the config page\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertNotEqual(title, buildSpec['program_name'])\r\n            self.assertNotEqual(subtitle, buildSpec['program_description'])\r\n\r\n            # Gooey should be showing the console messages straight away\r\n            # without manually starting the program\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertEqual(title,_(\"running_title\"))\r\n            self.assertEqual(subtitle, _('running_msg'))\r\n\r\n            # Wait for Gooey to swap the header to the final screen\r\n            while app.TopWindow.header._header.GetLabel() == _(\"running_title\"):\r\n                time.sleep(.1)\r\n\r\n            # verify that we've landed on the success screen\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertEqual(title, _(\"finished_title\"))\r\n            self.assertEqual(subtitle, _('finished_msg'))\r\n\r\n\r\n            # and that output was actually written to the console\r\n            self.assertIn(\"Success\", app.TopWindow.console.textbox.GetValue())\r\n        except:\r\n            app.TopWindow.Destroy()\r\n            raise\r\n        else:\r\n            import wx\r\n            wx.CallAfter(app.TopWindow.Destroy)\r\n            return None\r\n\r\n\r\n\r\n\r\nif __name__ == '__main__':\r\n    unittest.main()\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "gooey/tests/integration/integ_subparser_demo.py",
    "content": "import wx\r\nimport time\r\nimport unittest\r\n\r\nfrom gooey.gui.lang.i18n import _\r\nfrom tests.integration.programs import \\\r\n    all_widgets_subparser  as all_widgets_subparser_module\r\n\r\n\r\nclass TestGooeyIntegration11(unittest.TestCase):\r\n\r\n    def test_gooeySubparserMode(self):\r\n        \"\"\" Tests the happy path through the subparser run mode of Gooey \"\"\"\r\n        from gooey.tests.integration import runner\r\n        runner.run_integration(all_widgets_subparser_module, self.gooeySanityTest)\r\n\r\n    def gooeySanityTest(self, app, buildSpec):\r\n        try:\r\n            # Check out header is present and showing data\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertEqual(title, buildSpec['program_name'])\r\n            self.assertEqual(subtitle, buildSpec['program_description'])\r\n\r\n            # switch to the run screen\r\n            app.TopWindow.onStart()\r\n\r\n            # Should find the expected test in the header\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertEqual(title,_(\"running_title\"))\r\n            self.assertEqual(subtitle, _('running_msg'))\r\n\r\n            # Wait for Gooey to swap the header to the final screen\r\n            while app.TopWindow.header._header.GetLabel() == _(\"running_title\"):\r\n                time.sleep(.1)\r\n\r\n            # verify that we've landed on the success screen\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertEqual(title, _(\"finished_title\"))\r\n            self.assertEqual(subtitle, _('finished_msg'))\r\n\r\n            # and that output was actually written to the console\r\n            self.assertIn(\"Success\", app.TopWindow.console.textbox.GetValue())\r\n            time.sleep(1)\r\n        except:\r\n            wx.CallAfter(app.TopWindow.Destroy)\r\n            raise\r\n        else:\r\n            wx.CallAfter(app.TopWindow.Destroy)\r\n            return None\r\n\r\n\r\nif __name__ == '__main__':\r\n    unittest.main()\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "gooey/tests/integration/integ_validations.py",
    "content": "import time\r\nimport unittest\r\n\r\nfrom tests.integration.programs import validations as validations_module\r\n\r\n\r\nclass TestGooeyIntegration(unittest.TestCase):\r\n    \"\"\"\r\n    A few quick integration tests that exercise Gooey's various run modes\r\n\r\n    WX Python needs to control the main thread. So, in order to simulate a user\r\n    running through the system, we have to execute the actual assertions in a\r\n    different thread\r\n    \"\"\"\r\n\r\n    def test__gooeyValidation(self):\r\n        \"\"\"Verifies that custom validation routines supplied via gooey_options prevents\r\n        the user from advancing past the configuration page when they fail\"\"\"\r\n        from gooey.tests.integration import runner\r\n        runner.run_integration(validations_module, self.verifyValidators)\r\n\r\n\r\n    def verifyValidators(self, app, buildSpec):\r\n        time.sleep(1)\r\n        try:\r\n            app.TopWindow.onStart()\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertNotEqual(title, buildSpec['program_name'])\r\n            self.assertNotEqual(subtitle, buildSpec['program_description'])\r\n        except:\r\n            app.TopWindow.Destroy()\r\n            raise\r\n        else:\r\n            import wx\r\n            wx.CallAfter(app.TopWindow.Destroy)\r\n            return None\r\n\r\n\r\n\r\nif __name__ == '__main__':\r\n    unittest.main()\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "gooey/tests/integration/integ_widget_demo.py",
    "content": "import time\r\nimport unittest\r\n\r\nimport wx\r\nfrom gooey.gui.lang.i18n import _\r\nfrom tests.integration.programs import all_widgets as all_widgets_module\r\n\r\n\r\nclass TestGooeyIntegration99(unittest.TestCase):\r\n\r\n\r\n    def test_gooeyNormalRun(self):\r\n        \"\"\" Tests the happy path through the default run mode of Gooey \"\"\"\r\n        from gooey.tests.integration import runner\r\n        runner.run_integration(all_widgets_module, self.gooeySanityTest)\r\n\r\n\r\n    def gooeySanityTest(self, app, buildSpec):\r\n        time.sleep(1)\r\n        try:\r\n            # Check out header is present and showing data\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertEqual(title, buildSpec['program_name'])\r\n            self.assertEqual(subtitle, buildSpec['program_description'])\r\n\r\n            # switch to the run screen\r\n            app.TopWindow.onStart()\r\n\r\n            # Should find the expected test in the header\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertEqual(title,_(\"running_title\"))\r\n            self.assertEqual(subtitle, _('running_msg'))\r\n\r\n            # Wait for Gooey to swap the header to the final screen\r\n            while app.TopWindow.header._header.GetLabel() == _(\"running_title\"):\r\n                time.sleep(.1)\r\n\r\n            # verify that we've landed on the success screen\r\n            title = app.TopWindow.header._header.GetLabel()\r\n            subtitle = app.TopWindow.header._subheader.GetLabel()\r\n            self.assertEqual(title, _(\"finished_title\"))\r\n            self.assertEqual(subtitle, _('finished_msg'))\r\n\r\n            # and that output was actually written to the console\r\n            self.assertIn(\"Success\", app.TopWindow.console.textbox.GetValue())\r\n            time.sleep(1)\r\n        except:\r\n            app.TopWindow.Destroy()\r\n            raise\r\n        else:\r\n            wx.CallAfter(app.TopWindow.Destroy)\r\n            return None\r\n\r\n\r\nif __name__ == '__main__':\r\n    unittest.main()\r\n\r\n\r\n\r\n\r\n\r\n\r\n"
  },
  {
    "path": "gooey/tests/integration/programs/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/tests/integration/programs/all_widgets.py",
    "content": "from gooey import Gooey\r\nfrom gooey import GooeyParser\r\n\r\n\r\n@Gooey(\r\n    sidebar_title=\"Your Custom Title\",\r\n    show_sidebar=True,\r\n    dump_build_config=True,\r\n    show_success_modal=False,\r\n    force_stop_is_error=False,\r\n    language='chinese'\r\n)\r\ndef main():\r\n    dest_vars = [\r\n        'textfield',\r\n        'textarea',\r\n        'password',\r\n        'commandfield',\r\n        'dropdown',\r\n        'listboxie',\r\n        'counter',\r\n        'overwrite',\r\n        'mutextwo',\r\n        'filechooser',\r\n        'filesaver',\r\n        'dirchooser',\r\n        'datechooser'\r\n\r\n    ]\r\n\r\n    parser = get_parser()\r\n    args = parser.parse_args()\r\n    import time\r\n    for i in dest_vars:\r\n        assert getattr(args, i) is not None\r\n    print(\"Success\")\r\n\r\n\r\n\r\ndef get_parser():\r\n    desc = \"Example application to show Gooey's various widgets\"\r\n    parser = GooeyParser(description=desc, add_help=False)\r\n\r\n    parser.add_argument('--textfield', default=2, widget=\"TextField\")\r\n    parser.add_argument('--textarea', default=\"oneline twoline\", widget='Textarea')\r\n    parser.add_argument('--password', default=\"hunter42\", widget='PasswordField')\r\n    parser.add_argument('--commandfield', default=\"cmdr\", widget='CommandField')\r\n    parser.add_argument('--dropdown', choices=[\"one\", \"two\"], default=\"two\", widget='Dropdown')\r\n    parser.add_argument('--listboxie',\r\n                        nargs='+',\r\n                        default=['Option three', 'Option four'],\r\n                        choices=['Option one', 'Option two', 'Option three',\r\n                                 'Option four'],\r\n                        widget='Listbox',\r\n                        gooey_options={\r\n                            'height': 300,\r\n                            'validate': '',\r\n                            'heading_color': '',\r\n                            'text_color': '',\r\n                            'hide_heading': True,\r\n                            'hide_text': True,\r\n                        })\r\n    parser.add_argument('-c', '--counter', default=3, action='count', widget='Counter')\r\n    parser.add_argument(\"-o\", \"--overwrite\", action=\"store_true\", default=True, widget='CheckBox')\r\n    parser.add_argument(\"-bo\", \"--blockcheckbox\", action=\"store_true\", default=True, widget='BlockCheckbox')\r\n\r\n    ### Mutex Group ###\r\n    verbosity = parser.add_mutually_exclusive_group(\r\n        required=True,\r\n        gooey_options={\r\n            'initial_selection': 1\r\n        }\r\n    )\r\n    verbosity.add_argument(\r\n        '--mutexone',\r\n        default=True,\r\n        action='store_true',\r\n        help=\"Show more details\")\r\n\r\n    verbosity.add_argument(\r\n        '--mutextwo',\r\n        default='mut-2',\r\n        widget='TextField')\r\n\r\n    parser.add_argument(\"--filechooser\", default=\"fc-value\", widget='FileChooser')\r\n    parser.add_argument(\"--filesaver\", default=\"fs-value\", widget='FileSaver')\r\n    parser.add_argument(\"--dirchooser\", default=\"dc-value\", widget='DirChooser')\r\n    parser.add_argument(\"--datechooser\", default=\"2015-01-01\", widget='DateChooser')\r\n    parser.add_argument(\"--multidirchooser\", default=\"2015-01-01\", widget='MultiDirChooser')\r\n\r\n    return parser\r\n\r\nif __name__ == '__main__':\r\n    main()\r\n"
  },
  {
    "path": "gooey/tests/integration/programs/all_widgets_subparser.py",
    "content": "\"\"\"\r\nExample program to demonstrate Gooey's presentation of subparsers\r\n\"\"\"\r\n\r\nfrom gooey import Gooey, GooeyParser\r\n\r\n@Gooey(\r\n    optional_cols=2,\r\n    program_name=\"Subparser Demo\",\r\n    dump_build_config=True,\r\n    show_success_modal=False)\r\ndef main():\r\n    dest_vars = [\r\n        'textfield',\r\n        'textarea',\r\n        'password',\r\n        'commandfield',\r\n        'dropdown',\r\n        'listboxie',\r\n        'counter',\r\n        'overwrite',\r\n        'mutextwo',\r\n        'filechooser',\r\n        'filesaver',\r\n        'dirchooser',\r\n        'datechooser'\r\n    ]\r\n    parser = get_parser()\r\n    args = parser.parse_args()\r\n    import time\r\n    time.sleep(.6)\r\n    for i in dest_vars:\r\n        assert getattr(args, i) is not None\r\n    print(\"Success\")\r\n\r\n\r\ndef get_parser():\r\n    parser = GooeyParser()\r\n    subs = parser.add_subparsers(help='commands', dest='command')\r\n\r\n    parser_one = subs.add_parser('parser1', prog=\"Parser 1\")\r\n    parser_one.add_argument('--textfield', default=2, widget=\"TextField\")\r\n    parser_one.add_argument('--textarea', default=\"oneline twoline\",\r\n                            widget='Textarea')\r\n    parser_one.add_argument('--password', default=\"hunter42\",\r\n                            widget='PasswordField')\r\n    parser_one.add_argument('--commandfield', default=\"cmdr\",\r\n                            widget='CommandField')\r\n    parser_one.add_argument('--dropdown',\r\n                            choices=[\"one\", \"two\"], default=\"two\",\r\n                            widget='Dropdown')\r\n    parser_one.add_argument('--listboxie',\r\n                            nargs='+',\r\n                            default=['Option three', 'Option four'],\r\n                            choices=['Option one', 'Option two', 'Option three',\r\n                                     'Option four'],\r\n                            widget='Listbox',\r\n                            gooey_options={\r\n                                'height': 300,\r\n                                'validate': '',\r\n                                'heading_color': '',\r\n                                'text_color': '',\r\n                                'hide_heading': True,\r\n                                'hide_text': True,\r\n                            }\r\n                            )\r\n    parser_one.add_argument('-c', '--counter', default=3, action='count',\r\n                            widget='Counter')\r\n    #\r\n    parser_one.add_argument(\"-o\", \"--overwrite\", action=\"store_true\",\r\n                            default=True,\r\n                            widget='CheckBox')\r\n\r\n    ### Mutex Group ###\r\n    verbosity = parser_one.add_mutually_exclusive_group(\r\n        required=True,\r\n        gooey_options={\r\n            'initial_selection': 1\r\n        }\r\n    )\r\n    verbosity.add_argument(\r\n        '--mutexone',\r\n        default=True,\r\n        action='store_true',\r\n        help=\"Show more details\")\r\n\r\n    verbosity.add_argument(\r\n        '--mutextwo',\r\n        default='mut-2',\r\n        widget='TextField')\r\n\r\n    parser_one.add_argument(\"--filechooser\", default=\"fc-value\", widget='FileChooser')\r\n    parser_one.add_argument(\"--filesaver\", default=\"fs-value\", widget='FileSaver')\r\n    parser_one.add_argument(\"--dirchooser\", default=\"dc-value\", widget='DirChooser')\r\n    parser_one.add_argument(\"--datechooser\", default=\"2015-01-01\", widget='DateChooser')\r\n\r\n    parser_two = subs.add_parser('parser2', prog=\"parser 2\")\r\n    parser_two.add_argument('--textfield', default=2, widget=\"TextField\")\r\n    parser_two.add_argument('--textarea', default=\"oneline twoline\", widget='Textarea')\r\n    parser_two.add_argument('--password', default=\"hunter42\", widget='PasswordField')\r\n    parser_two.add_argument('--commandfield', default=\"cmdr\", widget='CommandField')\r\n    parser_two.add_argument('--dropdown', choices=[\"one\", \"two\"], default=\"two\", widget='Dropdown')\r\n    parser_two.add_argument('--listboxie',\r\n                            nargs='+',\r\n                            default=['Option three', 'Option four'],\r\n                            choices=['Option one', 'Option two', 'Option three',\r\n                                     'Option four'],\r\n                            widget='Listbox',\r\n                            gooey_options={\r\n                                'height': 300,\r\n                                'validate': '',\r\n                                'heading_color': '',\r\n                                'text_color': '',\r\n                                'hide_heading': True,\r\n                                'hide_text': True,\r\n                            }\r\n                            )\r\n    parser_two.add_argument('-c', '--counter', default=3, action='count', widget='Counter')\r\n    parser_two.add_argument(\"-o\", \"--overwrite\", action=\"store_true\", default=True, widget='CheckBox')\r\n\r\n    ### Mutex Group ###\r\n    verbosity = parser_two.add_mutually_exclusive_group(\r\n        required=True,\r\n        gooey_options={\r\n            'initial_selection': 1\r\n        }\r\n    )\r\n    verbosity.add_argument(\r\n        '--mutexone',\r\n        default=True,\r\n        action='store_true',\r\n        help=\"Show more details\")\r\n\r\n    verbosity.add_argument(\r\n        '--mutextwo',\r\n        default='mut-2',\r\n        widget='TextField')\r\n\r\n    parser_two.add_argument(\"--filechooser\", default=\"fc-value\", widget='FileChooser')\r\n    parser_two.add_argument(\"--filesaver\", default=\"fs-value\", widget='FileSaver')\r\n    parser_two.add_argument(\"--dirchooser\", default=\"dc-value\", widget='DirChooser')\r\n    parser_two.add_argument(\"--datechooser\", default=\"2015-01-01\", widget='DateChooser')\r\n\r\n    return parser\r\n\r\nif __name__ == '__main__':\r\n    main()\r\n"
  },
  {
    "path": "gooey/tests/integration/programs/auto_start.py",
    "content": "import sys\r\n\r\nfrom gooey import Gooey\r\nfrom gooey import GooeyParser\r\nfrom argparse import ArgumentParser\r\n\r\n@Gooey(\r\n    progress_regex=r\"^progress: (-?\\d+)%$\",\r\n    disable_progress_bar_animation=True,\r\n    dump_build_config=True,\r\n    show_success_modal=False,\r\n    auto_start=True\r\n)\r\ndef main():\r\n    parser = get_parser()\r\n    _ = parser.parse_args(sys.argv[1:])\r\n    import time\r\n    time.sleep(2)\r\n    print('Success')\r\n\r\n\r\n\r\ndef get_parser():\r\n    return GooeyParser(prog=\"example_progress_bar_1\")\r\n\r\nif __name__ == '__main__':\r\n    main()\r\n"
  },
  {
    "path": "gooey/tests/integration/programs/gooey_config.json",
    "content": "{\r\n  \"language\": \"chinese\",\r\n  \"target\": \"\\\"C:\\\\Users\\\\Chris\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\venv3\\\\Scripts\\\\python.exe\\\" -u \\\"C:/Users/Chris/Dropbox/pretty_gui/Gooey/gooey/tests/integration/programs/all_widgets.py\\\"\",\r\n  \"program_name\": \"all_widgets\",\r\n  \"program_description\": \"Example application to show Gooey's various widgets\",\r\n  \"sidebar_title\": \"Your Custom Title\",\r\n  \"default_size\": [\r\n    610,\r\n    530\r\n  ],\r\n  \"auto_start\": false,\r\n  \"show_advanced\": true,\r\n  \"run_validators\": true,\r\n  \"encoding\": \"utf-8\",\r\n  \"show_stop_warning\": true,\r\n  \"show_success_modal\": false,\r\n  \"force_stop_is_error\": false,\r\n  \"poll_external_updates\": false,\r\n  \"return_to_config\": false,\r\n  \"show_restart_button\": true,\r\n  \"requires_shell\": true,\r\n  \"menu\": [],\r\n  \"clear_before_run\": false,\r\n  \"use_legacy_titles\": true,\r\n  \"num_required_cols\": 2,\r\n  \"num_optional_cols\": 2,\r\n  \"manual_start\": false,\r\n  \"monospace_display\": false,\r\n  \"image_dir\": \"::gooey/default\",\r\n  \"language_dir\": \"C:\\\\Users\\\\Chris\\\\Dropbox\\\\pretty_gui\\\\Gooey\\\\gooey\\\\languages\",\r\n  \"progress_regex\": null,\r\n  \"progress_expr\": null,\r\n  \"hide_progress_msg\": false,\r\n  \"disable_progress_bar_animation\": false,\r\n  \"disable_stop_button\": false,\r\n  \"navigation\": \"SIDEBAR\",\r\n  \"show_sidebar\": true,\r\n  \"tabbed_groups\": false,\r\n  \"group_by_type\": true,\r\n  \"body_bg_color\": \"#f0f0f0\",\r\n  \"header_bg_color\": \"#ffffff\",\r\n  \"header_height\": 80,\r\n  \"header_show_title\": true,\r\n  \"header_show_subtitle\": true,\r\n  \"header_image_center\": false,\r\n  \"footer_bg_color\": \"#f0f0f0\",\r\n  \"sidebar_bg_color\": \"#f2f2f2\",\r\n  \"terminal_panel_color\": \"#F0F0F0\",\r\n  \"terminal_font_color\": \"#000000\",\r\n  \"terminal_font_family\": null,\r\n  \"terminal_font_weight\": null,\r\n  \"terminal_font_size\": null,\r\n  \"richtext_controls\": false,\r\n  \"error_color\": \"#ea7878\",\r\n  \"layout\": \"standard\",\r\n  \"widgets\": {\r\n    \"all_widgets.py\": {\r\n      \"command\": \"::gooey/default\",\r\n      \"name\": \"all_widgets.py\",\r\n      \"help\": null,\r\n      \"description\": \"\",\r\n      \"contents\": [\r\n        {\r\n          \"name\": \"Optional Arguments\",\r\n          \"items\": [\r\n            {\r\n              \"id\": \"--textfield\",\r\n              \"type\": \"TextField\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"textfield\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--textfield\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": 2,\r\n                \"dest\": \"textfield\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--textarea\",\r\n              \"type\": \"Textarea\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"textarea\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--textarea\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": \"oneline twoline\",\r\n                \"dest\": \"textarea\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--password\",\r\n              \"type\": \"PasswordField\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"password\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--password\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": \"hunter42\",\r\n                \"dest\": \"password\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--commandfield\",\r\n              \"type\": \"CommandField\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"commandfield\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--commandfield\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": \"cmdr\",\r\n                \"dest\": \"commandfield\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--dropdown\",\r\n              \"type\": \"Dropdown\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"dropdown\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--dropdown\"\r\n                ],\r\n                \"choices\": [\r\n                  \"one\",\r\n                  \"two\"\r\n                ],\r\n                \"default\": \"two\",\r\n                \"dest\": \"dropdown\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--listboxie\",\r\n              \"type\": \"Listbox\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"listboxie\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"+\",\r\n                \"commands\": [\r\n                  \"--listboxie\"\r\n                ],\r\n                \"choices\": [\r\n                  \"Option one\",\r\n                  \"Option two\",\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"default\": [\r\n                  \"Option three\",\r\n                  \"Option four\"\r\n                ],\r\n                \"dest\": \"listboxie\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"height\": 300,\r\n                \"validate\": \"\",\r\n                \"heading_color\": \"\",\r\n                \"text_color\": \"\",\r\n                \"hide_heading\": true,\r\n                \"hide_text\": true\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"-c\",\r\n              \"type\": \"Counter\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"counter\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"-c\",\r\n                  \"--counter\"\r\n                ],\r\n                \"choices\": [\r\n                  \"1\",\r\n                  \"2\",\r\n                  \"3\",\r\n                  \"4\",\r\n                  \"5\",\r\n                  \"6\",\r\n                  \"7\",\r\n                  \"8\",\r\n                  \"9\",\r\n                  \"10\"\r\n                ],\r\n                \"default\": \"3\",\r\n                \"dest\": \"counter\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"-o\",\r\n              \"type\": \"CheckBox\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"overwrite\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"-o\",\r\n                  \"--overwrite\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": true,\r\n                \"dest\": \"overwrite\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"-bo\",\r\n              \"type\": \"BlockCheckbox\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"blockcheckbox\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"-bo\",\r\n                  \"--blockcheckbox\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": true,\r\n                \"dest\": \"blockcheckbox\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"94c2fd11-1925-4cc8-8634-44ef0ce07986\",\r\n              \"type\": \"RadioGroup\",\r\n              \"cli_type\": \"optional\",\r\n              \"group_name\": \"Choose Option\",\r\n              \"required\": true,\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"type\": \"local\",\r\n                  \"test\": \"lambda x: True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                },\r\n                \"initial_selection\": 1\r\n              },\r\n              \"data\": {\r\n                \"commands\": [\r\n                  [\r\n                    \"--mutexone\"\r\n                  ],\r\n                  [\r\n                    \"--mutextwo\"\r\n                  ]\r\n                ],\r\n                \"widgets\": [\r\n                  {\r\n                    \"id\": \"--mutexone\",\r\n                    \"type\": \"CheckBox\",\r\n                    \"cli_type\": \"optional\",\r\n                    \"required\": false,\r\n                    \"data\": {\r\n                      \"display_name\": \"mutexone\",\r\n                      \"help\": \"Show more details\",\r\n                      \"required\": false,\r\n                      \"nargs\": \"\",\r\n                      \"commands\": [\r\n                        \"--mutexone\"\r\n                      ],\r\n                      \"choices\": [],\r\n                      \"default\": true,\r\n                      \"dest\": \"mutexone\"\r\n                    },\r\n                    \"options\": {\r\n                      \"error_color\": \"#ea7878\",\r\n                      \"label_color\": \"#000000\",\r\n                      \"help_color\": \"#363636\",\r\n                      \"full_width\": false,\r\n                      \"validator\": {\r\n                        \"test\": \"True\",\r\n                        \"message\": \"\"\r\n                      },\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      }\r\n                    }\r\n                  },\r\n                  {\r\n                    \"id\": \"--mutextwo\",\r\n                    \"type\": \"TextField\",\r\n                    \"cli_type\": \"optional\",\r\n                    \"required\": false,\r\n                    \"data\": {\r\n                      \"display_name\": \"mutextwo\",\r\n                      \"help\": null,\r\n                      \"required\": false,\r\n                      \"nargs\": \"\",\r\n                      \"commands\": [\r\n                        \"--mutextwo\"\r\n                      ],\r\n                      \"choices\": [],\r\n                      \"default\": \"mut-2\",\r\n                      \"dest\": \"mutextwo\"\r\n                    },\r\n                    \"options\": {\r\n                      \"error_color\": \"#ea7878\",\r\n                      \"label_color\": \"#000000\",\r\n                      \"help_color\": \"#363636\",\r\n                      \"full_width\": false,\r\n                      \"validator\": {\r\n                        \"test\": \"True\",\r\n                        \"message\": \"\"\r\n                      },\r\n                      \"external_validator\": {\r\n                        \"cmd\": \"\"\r\n                      }\r\n                    }\r\n                  }\r\n                ]\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--filechooser\",\r\n              \"type\": \"FileChooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"filechooser\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--filechooser\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": \"fc-value\",\r\n                \"dest\": \"filechooser\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--filesaver\",\r\n              \"type\": \"FileSaver\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"filesaver\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--filesaver\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": \"fs-value\",\r\n                \"dest\": \"filesaver\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--dirchooser\",\r\n              \"type\": \"DirChooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"dirchooser\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--dirchooser\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": \"dc-value\",\r\n                \"dest\": \"dirchooser\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--datechooser\",\r\n              \"type\": \"DateChooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"datechooser\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--datechooser\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": \"2015-01-01\",\r\n                \"dest\": \"datechooser\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            },\r\n            {\r\n              \"id\": \"--multidirchooser\",\r\n              \"type\": \"MultiDirChooser\",\r\n              \"cli_type\": \"optional\",\r\n              \"required\": false,\r\n              \"data\": {\r\n                \"display_name\": \"multidirchooser\",\r\n                \"help\": null,\r\n                \"required\": false,\r\n                \"nargs\": \"\",\r\n                \"commands\": [\r\n                  \"--multidirchooser\"\r\n                ],\r\n                \"choices\": [],\r\n                \"default\": \"2015-01-01\",\r\n                \"dest\": \"multidirchooser\"\r\n              },\r\n              \"options\": {\r\n                \"error_color\": \"#ea7878\",\r\n                \"label_color\": \"#000000\",\r\n                \"help_color\": \"#363636\",\r\n                \"full_width\": false,\r\n                \"validator\": {\r\n                  \"test\": \"True\",\r\n                  \"message\": \"\"\r\n                },\r\n                \"external_validator\": {\r\n                  \"cmd\": \"\"\r\n                }\r\n              }\r\n            }\r\n          ],\r\n          \"groups\": [],\r\n          \"description\": null,\r\n          \"options\": {\r\n            \"label_color\": \"#000000\",\r\n            \"description_color\": \"#363636\",\r\n            \"legacy\": {\r\n              \"required_cols\": 2,\r\n              \"optional_cols\": 2\r\n            },\r\n            \"columns\": 2,\r\n            \"padding\": 10,\r\n            \"show_border\": false\r\n          }\r\n        }\r\n      ]\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "gooey/tests/integration/programs/validations.py",
    "content": "import time\r\n\r\nfrom gooey import Gooey\r\nfrom gooey import GooeyParser\r\n\r\n\r\n@Gooey(\r\n    sidebar_title=\"Your Custom Title\",\r\n    show_sidebar=True,\r\n    show_success_modal=False,\r\n    force_stop_is_error=False,\r\n)\r\ndef main():\r\n    parser = get_parser()\r\n    args = parser.parse_args()\r\n    time.sleep(2)\r\n    print(\"Success\")\r\n\r\n\r\ndef get_parser():\r\n    \"\"\"\r\n    A simple parser with a single required argument and no default thus\r\n    ensuring that clicking the start button in the UI will throw\r\n    a validation error.\r\n    \"\"\"\r\n    desc = \"Example application to show Gooey's various widgets\"\r\n    parser = GooeyParser(description=desc, add_help=False)\r\n    parser.add_argument('--textfield', widget=\"TextField\", required=True)\r\n    return parser\r\n\r\n\r\nif __name__ == '__main__':\r\n    main()"
  },
  {
    "path": "gooey/tests/integration/runner.py",
    "content": "import os\r\nimport time\r\nfrom concurrent import futures\r\n\r\nfrom gooey.gui.util.freeze import getResourcePath\r\nfrom gooey.python_bindings import config_generator\r\nfrom gooey.util.functional import merge\r\n\r\n\r\ndef run_integration(module, assertionFunction, **kwargs):\r\n    \"\"\"\r\n    Integration test harness.\r\n\r\n    WXPython is *super* finicky when it comes to integration tests. It needs\r\n    the main Python thread for its app loop, which means we have to integration\r\n    test on a separate thread. The causes further strangeness in how Unittest\r\n    and WXPython interact. In short, each test must be in its own module and\r\n    thus import its own wx instance, and be run in its own \"space.\"\r\n\r\n    So long as the above is satisfied, then integration tests can run reliably.\r\n\r\n    \"\"\"\r\n    from gooey.gui import application\r\n    options = merge({\r\n        'image_dir': '::gooey/default',\r\n        'language_dir': getResourcePath('languages'),\r\n        'show_success_modal': False\r\n    }, kwargs)\r\n    module_path = os.path.abspath(module.__file__)\r\n    parser = module.get_parser()\r\n    build_spec = config_generator.create_from_parser(parser, module_path, **options)\r\n\r\n    time.sleep(2)\r\n    app = application.build_app(build_spec=build_spec)\r\n    executor = futures.ThreadPoolExecutor(max_workers=1)\r\n    # executor runs in parallel and will submit a wx.Destroy request\r\n    # when done making its assertions\r\n    testResult = executor.submit(assertionFunction, app, build_spec)\r\n    # main loop blocks the main thread\r\n    app.MainLoop()\r\n    # .result() blocks as well while we wait for the thread to finish\r\n    # any waiting it may be doing.\r\n    testResult.result()\r\n    del app\r\n\r\n\r\n"
  },
  {
    "path": "gooey/tests/processor/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/tests/processor/files/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/tests/processor/files/ignore_break.py",
    "content": "\"\"\"\nPython file for Processor test suite\n\nShort 1s loop which purposefully ignores\nKeyboard Interrupts in order to continue\nexecuting\n\"\"\"\n\nimport time\nimport signal\n\ndef ignored_it(*args):\n    print(\"INTERRUPT\")\n\nsignal.signal(signal.SIGBREAK, ignored_it)\n\n\nwhile True:\n    print(time.time())\n    time.sleep(0.1)"
  },
  {
    "path": "gooey/tests/processor/files/ignore_interrupt.py",
    "content": "\"\"\"\nPython file for Processor test suite\n\nInfinite loop which purposefully ignores\nKeyboard Interrupts in order to continue\nexecuting. The only way to kill it is via\nSIGTERM family signals.\n\"\"\"\n\nimport time\nimport sys\n\nif sys.platform.startswith('win'):\n    import ctypes\n    kernel32 = ctypes.WinDLL('kernel32')\n    kernel32.SetConsoleCtrlHandler(None, 0)\n\n\nwhile True:\n    try:\n        print(time.time())\n        time.sleep(0.1)\n    except KeyboardInterrupt:\n        # Ignored!\n        print(\"INTERRUPT\")\n"
  },
  {
    "path": "gooey/tests/processor/files/infinite_loop.py",
    "content": "\"\"\"\nPython file for Processor test suite\n\nInfinite loop which would continue forever if not\ninterrupted.\n\"\"\"\nimport time\nimport sys\n\nif sys.platform.startswith('win'):\n    import ctypes\n    kernel32 = ctypes.WinDLL('kernel32')\n    kernel32.SetConsoleCtrlHandler(None, 0)\n\n\nwhile True:\n    print(time.time())\n    time.sleep(0.1)"
  },
  {
    "path": "gooey/tests/processor/test_processor.py",
    "content": "import re\nimport signal\nimport subprocess\nimport sys\nimport unittest\nimport os\nimport time\n\nimport wx\n\nfrom gooey.gui import events, processor\nfrom gooey.gui.pubsub import pub\nfrom gooey.gui.processor import ProcessController\n\n\nclass TestProcessor(unittest.TestCase):\n\n    def test_extract_progress(self):\n        # should pull out a number based on the supplied\n        # regex and expression\n        processor = ProcessController(r\"^progress: (\\d+)%$\", None, False, 'utf-8')\n        self.assertEqual(processor._extract_progress(b'progress: 50%'), 50)\n\n        processor = ProcessController(r\"total: (\\d+)%$\", None, False, 'utf-8')\n        self.assertEqual(processor._extract_progress(b'my cool total: 100%'), 100)\n\n    def test_extract_progress_returns_none_if_no_regex_supplied(self):\n        processor = ProcessController(None, None, False, 'utf-8')\n        self.assertIsNone(processor._extract_progress(b'Total progress: 100%'))\n\n\n    def test_extract_progress_returns_none_if_no_match_found(self):\n        processor = ProcessController(r'(\\d+)%$', None, False, 'utf-8')\n        self.assertIsNone(processor._extract_progress(b'No match in dis string'))\n\n\n    def test_eval_progress(self):\n        # given a match in the string, should eval the result\n        regex = r'(\\d+)/(\\d+)$'\n        processor = ProcessController(regex, r'x[0] / x[1]', False,False, 'utf-8')\n        match = re.search(regex, '50/50')\n        self.assertEqual(processor._eval_progress(match), 1.0)\n    def test_eval_progress_returns_none_on_failure(self):\n        # given a match in the string, should eval the result\n        regex = r'(\\d+)/(\\d+)$'\n        processor = ProcessController(regex, r'x[0] *^/* x[1]', False, False,'utf-8')\n        match = re.search(regex, '50/50')\n        self.assertIsNone(processor._eval_progress(match))\n\n\n    def test_all_interrupts_halt_process(self):\n        \"\"\"\n        TODO: These tests are hella flaky. I'm confident that the feature works. However, getting\n        signals, subprocesses and unittest to all play together reliably is proving tricky. It\n        primarily seems to come down to how long the time.sleep() is before sending the shutdown\n        signal.\n        \"\"\"\n\n        cmd = 'python ' + os.path.join(os.getcwd(), 'files', 'infinite_loop.py')\n\n        try:\n            import _winapi\n            signals = [signal.SIGTERM, signal.CTRL_BREAK_EVENT, signal.CTRL_C_EVENT]\n        except ModuleNotFoundError:\n            signals = [signal.SIGTERM, signal.SIGINT]\n        try:\n            for sig in signals:\n                print('sig', sig)\n                processor = ProcessController(None, None, False, 'utf-8', True, shutdown_signal=sig)\n\n                processor.run(cmd)\n                self.assertTrue(processor.running())\n\n                # super-duper important sleep so that the\n                # signal is actually received by the child process\n                # see: https://stackoverflow.com/questions/32023719/how-to-simulate-a-terminal-ctrl-c-event-from-a-unittest\n                time.sleep(1)\n                processor.stop()\n                max_wait = time.time() + 4\n                while processor.running() and time.time() < max_wait:\n                    time.sleep(0.1)\n                self.assertFalse(processor.running())\n        except KeyboardInterrupt:\n            pass\n\n\n    def test_ignore_sigint_family_signals(self):\n        try:\n            import _winapi\n            signals = [signal.CTRL_BREAK_EVENT, signal.CTRL_C_EVENT]\n            programs = ['ignore_break.py', 'ignore_interrupt.py']\n        except ModuleNotFoundError:\n            signals = [signal.SIGINT]\n            programs = ['ignore_interrupt.py']\n\n\n        for program, sig in zip(programs, signals):\n            cmd = sys.executable + ' ' + os.path.join(os.getcwd(), 'files', program)\n            process = processor = ProcessController(None, None, False, 'utf-8', True, shutdown_signal=sig, testmode=True)\n            process.run(cmd)\n            # super-duper important sleep so that the\n            # signal is actually received by the child process\n            # see: https://stackoverflow.com/questions/32023719/how-to-simulate-a-terminal-ctrl-c-event-from-a-unittest\n            time.sleep(1)\n            process.send_shutdown_signal()\n            # wait to give stdout enough time to write\n            time.sleep(1)\n            # now our signal should have been received, but rejected.\n            self.assertTrue(processor.running())\n            # so we sigterm to actually shut down the process.\n            process._send_signal(signal.SIGTERM)\n            # sanity wait\n            max_wait = time.time() + 2\n            while processor.running() and time.time() < max_wait:\n                time.sleep(0.1)\n            # now we should be shut down due to killing the process.\n            self.assertFalse(processor.running())\n            # and we'll see in the stdout out from the process that our\n            # interrupt was received\n            output = process._process.stdout.read().decode('utf-8')\n            self.assertIn(\"INTERRUPT\", str(output))\n            # but indeed ignored. It continued running and writing to stdout after\n            # receiving the signal\n            self.assertTrue(output.index(\"INTERRUPT\") < len(output))\n\n"
  },
  {
    "path": "gooey/tests/test_application.py",
    "content": "import sys\nimport unittest\nfrom argparse import ArgumentParser\nfrom collections import namedtuple\nfrom pprint import pprint\nfrom unittest.mock import patch\nfrom unittest.mock import MagicMock\n\nfrom python_bindings import constants\nfrom tests.harness import instrumentGooey\n\nfrom gooey.tests import *\n\nclass TestGooeyApplication(unittest.TestCase):\n\n    def testFullscreen(self):\n        parser = self.basicParser()\n        for shouldShow in [True, False]:\n            with self.subTest('Should set full screen: {}'.format(shouldShow)):\n                with instrumentGooey(parser, fullscreen=shouldShow) as (app, frame, gapp):\n                    self.assertEqual(frame.IsFullScreen(), shouldShow)\n\n\n    @patch(\"gui.containers.application.modals.confirmForceStop\")\n    def testGooeyRequestsConfirmationWhenShowStopWarningModalTrue(self, mockModal):\n        \"\"\"\n        When show_stop_warning=False, Gooey should immediately kill the\n        running program without additional user confirmation.\n\n        Otherwise, Gooey should show a confirmation modal and, dependending on the\n        user's choice, either do nothing or kill the running program.\n        \"\"\"\n        Case = namedtuple('Case', ['show_warning', 'shouldSeeConfirm', 'userChooses', 'shouldHaltProgram'])\n        testcases = [\n            Case(show_warning=True, shouldSeeConfirm=True, userChooses=True, shouldHaltProgram=True),\n            Case(show_warning=True, shouldSeeConfirm=True, userChooses=False, shouldHaltProgram=False),\n            Case(show_warning=False, shouldSeeConfirm=False, userChooses='N/A', shouldHaltProgram=True),\n        ]\n\n        for case in testcases:\n            mockModal.reset_mock()\n            parser = self.basicParser()\n            with instrumentGooey(parser, show_stop_warning=case.show_warning) as (app, frame, gapp):\n                mockClientRunner = MagicMock()\n                mockModal.return_value = case.userChooses\n                gapp.clientRunner = mockClientRunner\n\n                gapp.handleInterrupt()\n\n                if case.shouldSeeConfirm:\n                    mockModal.assert_called()\n                else:\n                    mockModal.assert_not_called()\n\n                if case.shouldHaltProgram:\n                    mockClientRunner.stop.assert_called()\n                else:\n                    mockClientRunner.stop.assert_not_called()\n\n    # @patch(\"gui.containers.application.modals.confirmForceStop\")\n    # def testOnCloseShutsDownActiveClients(self, mockModal):\n    #     \"\"\"\n    #     Issue 592: Closing the UI should clean up any actively running programs\n    #     \"\"\"\n    #     parser = self.basicParser()\n    #     with instrumentGooey(parser) as (app, frame):\n    #         frame.clientRunner = MagicMock()\n    #         frame.destroyGooey = MagicMock()\n    #         # mocking that the user clicks \"yes shut down\" in the warning modal\n    #         mockModal.return_value = True\n    #         frame._instance.handleClose()\n    #\n    #         mockModal.assert_called()\n    #         frame.destroyGooey.assert_called()\n\n\n    def testTerminalColorChanges(self):\n        ## Issue #625 terminal panel color wasn't being set due to a typo\n        parser = self.basicParser()\n        expectedColors = [(255, 0, 0, 255), (255, 255, 255, 255), (100, 100, 100,100)]\n        for expectedColor in expectedColors:\n            with instrumentGooey(parser, terminal_panel_color=expectedColor) as (app, frame, gapp):\n                foundColor = gapp.consoleRef.instance.GetBackgroundColour()\n                self.assertEqual(tuple(foundColor), expectedColor)\n\n\n    def testFontWeightsGetSet(self):\n        ## Issue #625 font weight wasn't being correctly passed to the terminal\n        for weight in [constants.FONTWEIGHT_LIGHT, constants.FONTWEIGHT_BOLD]:\n            parser = self.basicParser()\n            with instrumentGooey(parser, terminal_font_weight=weight) as (app, frame, gapp):\n                terminal = gapp.consoleRef.instance.textbox\n                self.assertEqual(terminal.GetFont().GetWeight(), weight)\n\n\n    def testProgressBarHiddenWhenDisabled(self):\n        options = [\n            {'disable_progress_bar_animation': True},\n            {'disable_progress_bar_animation': False},\n            {}\n        ]\n        for kwargs in options:\n            parser = self.basicParser()\n            with instrumentGooey(parser, **kwargs) as (app, frame, gapp):\n                mockClientRunner = MagicMock()\n                frame.clientRunner = mockClientRunner\n\n                # transition's Gooey to the running state using the now mocked processor.\n                # so that we can make assertions about the visibility of footer buttons\n                gapp.onStart()\n\n                # the progress bar flag is awkwardly inverted (is_disabled, rather than\n                # is_enabled). Thus inverting the expectation here. When disabled is true,\n                # shown should be False,\n                expect_shown = not kwargs.get('disable_progress_bar_animation', False)\n                self.assertEqual(gapp.state['progress']['show'], expect_shown)\n\n    def basicParser(self):\n        parser = ArgumentParser()\n        parser.add_argument('--foo')\n        return parser\n\n\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_argparse_to_json.py",
    "content": "import argparse\r\nimport sys\r\nimport unittest\r\nfrom argparse import ArgumentParser, FileType\r\n\r\nfrom gooey import GooeyParser\r\nfrom gooey.python_bindings import argparse_to_json\r\nfrom gooey.util.functional import getin\r\nfrom gooey.tests import *\r\nfrom gui.components.options.options import FileChooser\r\nfrom gui.components.widgets import FileSaver\r\n\r\n\r\nclass TestArgparse(unittest.TestCase):\r\n\r\n    def test_mutex_groups_conversion(self):\r\n        \"\"\"\r\n        Ensure multiple mutex groups are processed correctly.\r\n        \"\"\"\r\n        parser = ArgumentParser()\r\n        g1 = parser.add_mutually_exclusive_group(required=True)\r\n        g1.add_argument('--choose1')\r\n        g1.add_argument('--choose2')\r\n\r\n        g2 = parser.add_mutually_exclusive_group(required=True)\r\n        g2.add_argument('--choose3')\r\n        g2.add_argument('--choose4')\r\n\r\n        output = argparse_to_json.process(parser, {}, {}, {})\r\n\r\n        # assert that we get two groups of two choices back\r\n        items = output[0]['items']\r\n        self.assertTrue(len(items) == 2)\r\n        group1 = items[0]\r\n        group2 = items[1]\r\n        self.assertTrue(['--choose1'] in group1['data']['commands'])\r\n        self.assertTrue(['--choose2'] in group1['data']['commands'])\r\n        self.assertTrue(['--choose3'] in group2['data']['commands'])\r\n        self.assertTrue(['--choose4'] in group2['data']['commands'])\r\n        self.assertTrue(group1['type'] == 'RadioGroup')\r\n        self.assertTrue(group2['type'] == 'RadioGroup')\r\n\r\n    def test_json_iterable_conversion(self):\r\n        \"\"\"\r\n        Issue #312 - tuples weren't being coerced to list during argparse\r\n        conversion causing downstream issues when concatenating\r\n        \"\"\"\r\n        # our original functionality accepted only lists as the choices arg\r\n        parser = ArgumentParser()\r\n        parser.add_argument(\"-foo\", choices=['foo','bar', 'baz'])\r\n        result = argparse_to_json.action_to_json(parser._actions[-1], \"Dropdown\", {})\r\n\r\n        choices = result['data']['choices']\r\n        self.assertTrue(isinstance(choices, list))\r\n        self.assertEqual(choices, ['foo','bar', 'baz'])\r\n\r\n        # Now we allow tuples as well.\r\n        parser = ArgumentParser()\r\n        parser.add_argument(\"-foo\", choices=('foo','bar', 'baz'))\r\n        result = argparse_to_json.action_to_json(parser._actions[-1], \"Dropdown\", {})\r\n\r\n        choices = result['data']['choices']\r\n        self.assertTrue(isinstance(choices, list))\r\n        self.assertEqual(choices, ['foo','bar', 'baz'])\r\n\r\n\r\n    def test_choice_string_cooersion(self):\r\n        \"\"\"\r\n        Issue 321 - must coerce choice types to string to support wx.ComboBox\r\n        \"\"\"\r\n        parser = ArgumentParser()\r\n        parser.add_argument('--foo', default=1, choices=[1, 2, 3])\r\n        choice_action = parser._actions[-1]\r\n        result = argparse_to_json.action_to_json(choice_action, 'Dropdown', {})\r\n        self.assertEqual(getin(result, ['data', 'choices']), ['1', '2', '3'])\r\n        # default value is also converted to a string type\r\n        self.assertEqual(getin(result, ['data', 'default']), '1')\r\n\r\n    def test_choice_string_cooersion_no_default(self):\r\n        \"\"\"\r\n        Make sure that choice types without a default don't create\r\n        the literal string \"None\" but stick with the value None\r\n        \"\"\"\r\n        parser = ArgumentParser()\r\n        parser.add_argument('--foo', choices=[1, 2, 3])\r\n\r\n        choice_action = parser._actions[-1]\r\n        result = argparse_to_json.action_to_json(choice_action, 'Dropdown', {})\r\n        self.assertEqual(getin(result, ['data', 'default']), None)\r\n        \r\n\r\n    def test_listbox_defaults_cast_correctly(self):\r\n        \"\"\"\r\n        Issue XXX - defaults supplied in a list were turned into a string\r\n        wholesale (list and all). The defaults should be stored as a list\r\n        proper with only the _internal_ values coerced to strings.\r\n        \"\"\"\r\n        parser = GooeyParser()\r\n        parser.add_argument('--foo', widget=\"Listbox\", nargs=\"*\", choices=[1, 2, 3], default=[1, 2])\r\n\r\n        choice_action = parser._actions[-1]\r\n        result = argparse_to_json.action_to_json(choice_action, 'Listbox', {})\r\n        self.assertEqual(getin(result, ['data', 'default']), ['1', '2'])\r\n\r\n\r\n    def test_listbox_single_default_cast_correctly(self):\r\n        \"\"\"\r\n        Single arg defaults to listbox should be wrapped in a list and\r\n        their contents coerced as usual.\r\n        \"\"\"\r\n        parser = GooeyParser()\r\n        parser.add_argument('--foo', widget=\"Listbox\",\r\n                            nargs=\"*\", choices=[1, 2, 3], default=\"sup\")\r\n\r\n        choice_action = parser._actions[-1]\r\n        result = argparse_to_json.action_to_json(choice_action, 'Listbox', {})\r\n        self.assertEqual(getin(result, ['data', 'default']), ['sup'])\r\n\r\n    def test_non_data_defaults_are_dropped_entirely(self):\r\n        \"\"\"\r\n        This is a refinement in understanding of Issue #147\r\n\r\n        Caused by Issue 377 - passing arbitrary objects as defaults\r\n        causes failures.\r\n        \"\"\"\r\n        # passing plain data to cleaning function results in plain data\r\n        # being returned\r\n        data = ['abc',\r\n                123,\r\n                ['a', 'b'],\r\n                [1, 2, 3]]\r\n\r\n        for datum in data:\r\n            result = argparse_to_json.clean_default(datum)\r\n            self.assertEqual(result, datum)\r\n\r\n        # passing in complex objects results in None\r\n        objects = [sys.stdout, sys.stdin, object(), max, min]\r\n\r\n        for obj in objects:\r\n            result = argparse_to_json.clean_default(obj)\r\n            self.assertEqual(result, None)\r\n\r\n\r\n    def test_suppress_is_removed_as_default_value(self):\r\n        \"\"\"\r\n        Issue #469\r\n        Argparse uses the literal string ==SUPPRESS== as an internal flag.\r\n        When encountered in Gooey, these should be dropped and mapped to `None`.\r\n        \"\"\"\r\n        parser = ArgumentParser(prog='test_program')\r\n        parser.add_argument(\"--foo\", default=argparse.SUPPRESS)\r\n        parser.add_argument('--version', action='version', version='1.0')\r\n\r\n        result = argparse_to_json.convert(parser, required_cols=2, optional_cols=2)\r\n        groups = getin(result, ['widgets', 'test_program', 'contents'])\r\n        for item in groups[0]['items']:\r\n            self.assertEqual(getin(item, ['data', 'default']), None)\r\n\r\n\r\n    def test_version_maps_to_checkbox(self):\r\n        testcases = [\r\n            [['--version'], {}, 'TextField'],\r\n            # we only remap if the action is version\r\n            # i.e. we don't care about the argument name itself\r\n            [['--version'], {'action': 'store'}, 'TextField'],\r\n            # should get mapped to CheckBox because of the action\r\n            [['--version'], {'action': 'version'}, 'CheckBox'],\r\n            # ditto, even through the 'name' isn't 'version'\r\n            [['--foobar'], {'action': 'version'}, 'CheckBox'],\r\n        ]\r\n        for args, kwargs, expectedType in testcases:\r\n            with self.subTest([args, kwargs]):\r\n                parser = argparse.ArgumentParser(prog='test')\r\n                parser.add_argument(*args, **kwargs)\r\n                result = argparse_to_json.convert(parser, required_cols=2, optional_cols=2)\r\n                contents = getin(result, ['widgets', 'test', 'contents'])[0]\r\n                self.assertEqual(contents['items'][0]['type'], expectedType)\r\n\r\n\r\n    def test_textinput_with_list_default_mapped_to_cli_friendly_value(self):\r\n        \"\"\"\r\n        Issue: #500\r\n\r\n        Using nargs and a `default` value with a list causes the literal list string\r\n        to be put into the UI.\r\n        \"\"\"\r\n        testcases = [\r\n            {'nargs': '+', 'default': ['a b', 'c'], 'gooey_default': '\"a b\" \"c\"', 'w': 'TextField'},\r\n            {'nargs': '*', 'default': ['a b', 'c'], 'gooey_default': '\"a b\" \"c\"', 'w': 'TextField'},\r\n            {'nargs': '...', 'default': ['a b', 'c'], 'gooey_default': '\"a b\" \"c\"', 'w': 'TextField'},\r\n            {'nargs': 2, 'default': ['a b', 'c'], 'gooey_default': '\"a b\" \"c\"', 'w': 'TextField'},\r\n            # TODO: this demos the current nargs behavior for string defaults, but\r\n            # TODO: it is wrong! These should be wrapped in quotes so spaces aren't\r\n            # TODO: interpreted as unique arguments.\r\n            {'nargs': '+', 'default': 'a b', 'gooey_default': 'a b', 'w': 'TextField'},\r\n            {'nargs': '*', 'default': 'a b', 'gooey_default': 'a b', 'w': 'TextField'},\r\n            {'nargs': '...', 'default': 'a b', 'gooey_default': 'a b', 'w': 'TextField'},\r\n            {'nargs': 1, 'default': 'a b', 'gooey_default': 'a b', 'w': 'TextField'},\r\n\r\n            # Listbox has special nargs handling which keeps the list in tact.\r\n            {'nargs': '+', 'default': ['a b', 'c'], 'gooey_default': ['a b', 'c'], 'w': 'Listbox'},\r\n            {'nargs': '*', 'default': ['a b', 'c'], 'gooey_default': ['a b', 'c'], 'w': 'Listbox'},\r\n            {'nargs': '...', 'default': ['a b', 'c'], 'gooey_default': ['a b', 'c'],'w': 'Listbox'},\r\n            {'nargs': 2, 'default': ['a b', 'c'], 'gooey_default': ['a b', 'c'], 'w': 'Listbox'},\r\n            {'nargs': '+', 'default': 'a b', 'gooey_default': ['a b'], 'w': 'Listbox'},\r\n            {'nargs': '*', 'default': 'a b', 'gooey_default': ['a b'], 'w': 'Listbox'},\r\n            {'nargs': '...', 'default': 'a b', 'gooey_default': ['a b'], 'w': 'Listbox'},\r\n            {'nargs': 1, 'default': 'a b', 'gooey_default': ['a b'], 'w': 'Listbox'},\r\n        ]\r\n        for case in testcases:\r\n            with self.subTest(case):\r\n                parser = ArgumentParser(prog='test_program')\r\n                parser.add_argument('--foo', nargs=case['nargs'], default=case['default'])\r\n                action = parser._actions[-1]\r\n                result = argparse_to_json.handle_initial_values(action, case['w'], action.default)\r\n                self.assertEqual(result, case['gooey_default'])\r\n\r\n    def test_nargs(self):\r\n        \"\"\"\r\n        so there are just a few simple rules here:\r\n        if nargs in [*, N, +, remainder]:\r\n            default MUST be a list OR we must map it to one\r\n\r\n        action:_StoreAction\r\n            - nargs '?'\r\n                - default:validate list is invalid\r\n                - default:coerce stringify\r\n            - nargs #{*, N, +, REMAINDER}\r\n                - default:validate None\r\n                - default:coerce\r\n                    if string: stringify\r\n                    if list: convert from list to cli style input string\r\n        action:_StoreConstAction\r\n            - nargs: invalid\r\n            - defaults:stringify\r\n\r\n        action:{_StoreFalseAction, _StoreTrueAction}\r\n            - nargs: invalid\r\n            - defaults:validate: require bool\r\n            - defaults:coerce: no stringify; leave bool\r\n\r\n        action:_CountAction\r\n            - nargs: invalid\r\n            - default:validate: must be numeric index within range OR None\r\n            - default:coerce: integer or None\r\n\r\n        action:_AppendAction\r\n            TODO: NOT CURRENTLY SUPPORTED BY GOOEY\r\n            nargs behavior is weird and needs to be understood.\r\n            - nargs\r\n\r\n        action:CustomUserAction:\r\n            - nargs: no way to know expected behavior. Ignore\r\n            - default: jsonify type if possible.\r\n        \"\"\"\r\n\r\n        parser = ArgumentParser()\r\n        parser.add_argument(\r\n            '--bar',\r\n            nargs='+',\r\n            choices=[\"one\", \"two\"],\r\n            default=\"one\",\r\n        )\r\n\r\n\r\n    def test_filetype_chooses_good_widget(self):\r\n        \"\"\"\r\n        #743 chose the picker type based on the FileType mode\r\n        when available.\r\n        \"\"\"\r\n        cases = [\r\n            (FileType(), 'FileChooser'),\r\n            (FileType('r'), 'FileChooser'),\r\n            (FileType('rb'), 'FileChooser'),\r\n            (FileType('rt'), 'FileChooser'),\r\n            (FileType('w'), 'FileSaver'),\r\n            (FileType('wt'), 'FileSaver'),\r\n            (FileType('wb'), 'FileSaver'),\r\n            (FileType('a'), 'FileSaver'),\r\n            (FileType('x'), 'FileSaver'),\r\n            (FileType('+'), 'FileSaver'),\r\n        ]\r\n\r\n        for filetype, expected_widget in cases:\r\n            with self.subTest(f'expect {filetype} to produce {expected_widget})'):\r\n                parser = ArgumentParser()\r\n                parser.add_argument('foo', type=filetype)\r\n                action = [parser._actions[-1]]\r\n                result = next(argparse_to_json.categorize(action, {}, {}))\r\n                self.assertEqual(result['type'], expected_widget)\r\n\r\n"
  },
  {
    "path": "gooey/tests/test_checkbox.py",
    "content": "import unittest\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\n\n\nclass TestCheckbox(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument(\n            '--widget',\n            action='store_true',\n            **kwargs)\n        return parser\n\n\n    def testInitialValue(self):\n        cases = [\n            # `initial` should supersede `default`\n            {'inputs': {'default': False,\n                        'widget': 'CheckBox',\n                        'gooey_options': {'initial_value': True}},\n             'expect': True},\n\n            {'inputs': {'gooey_options': {'initial_value': True},\n                        'widget': 'CheckBox'},\n             'expect': True},\n\n            {'inputs': {'gooey_options': {'initial_value': False},\n                        'widget': 'CheckBox'},\n             'expect': False},\n\n            {'inputs': {'default': True,\n                        'widget': 'CheckBox',\n                        'gooey_options': {}},\n             'expect': True},\n\n            {'inputs': {'default': True,\n                        'widget': 'CheckBox'},\n             'expect': True},\n\n            {'inputs': {'widget': 'CheckBox'},\n             'expect': False}\n        ]\n        for case in cases:\n            with self.subTest(case):\n                parser = self.makeParser(**case['inputs'])\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    widget = gapp.getActiveConfig().reifiedWidgets[0]\n                    self.assertEqual(widget.getValue()['rawValue'], case['expect'])\n\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_chooser_results.py",
    "content": "import argparse\nimport os\nimport unittest\n\nfrom gooey.gui.components.widgets.core import chooser\nfrom gooey.tests import *\n\n\nclass MockWxMDD:\n    def GetPaths(self):\n        pass\n\nclass TestChooserResults(unittest.TestCase):\n\n    def test_multiDirChooserGetResult(self):\n        expected_outputs = [\n            (None, \"\", [\"\"]),\n\n            # Windows\n            ('nt', \"C:\", [\"OS and System (C:)\"]),\n            ('nt', \"D:\\\\A Folder\\\\Yep Another One\",\n             [\"Other Stuff (D:)\\\\A Folder\\\\Yep Another One\"]),\n            ('nt', \"A:\\\\Wow Remember Floppy Drives;E:\\\\Righto Then\",\n             [\"Flipflop (A:)\\\\Wow Remember Floppy Drives\",\n              \"Elephants Only (E:)\\\\Righto Then\"])\n        ]\n\n        for osname, expected, pathsoutput in expected_outputs:\n            if not osname or osname == os.name:\n                chooser.MDD.MultiDirDialog = MockWxMDD\n                chooser.MDD.MultiDirDialog.GetPaths = lambda self : pathsoutput\n                result = chooser.MultiDirChooser.getResult(None, MockWxMDD())\n                print(result)\n                self.assertEqual(result, expected)\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "gooey/tests/test_cli.py",
    "content": "import unittest\nfrom gooey.gui import cli\n\n\nclass TestCliStringCreation(unittest.TestCase):\n\n    # TODO: exercise the formValidationCase (which will require tedious test data creation)\n    def test_cli(self):\n        print(cli.buildCliString('target', 'cmd', ['pos1', 'pos2'], ['-a 1', '-b 2']))\n\n        positionals = [\n            {'clitype': 'positional', 'cmd': 'pos1', 'required': True},\n            {'clitype': 'positional', 'cmd': 'pos2', 'required': True}\n        ]\n\n        optionals = [\n            {'clitype': 'optional', 'cmd': '-a 1', 'required': False},\n            {'clitype': 'optional', 'cmd': '-b 2', 'required': False},\n        ]\n\n        # print(cli.formValidationCmd('target', 'cmd', positionals, optionals))"
  },
  {
    "path": "gooey/tests/test_cmd_args.py",
    "content": "import unittest\n\nfrom gooey import GooeyParser\nfrom gooey.python_bindings import cmd_args\nfrom argparse import ArgumentParser\nfrom gooey.tests import *\n\n\nclass TextCommandLine(unittest.TestCase):\n\n    def test_default_overwritten(self):\n        parser = GooeyParser()\n        ArgumentParser.original_parse_args = ArgumentParser.parse_args\n\n        parser.add_argument('arg', type=int, default=0)\n\n        # Supply 1 as command line argument, check that it overwrites argparse default\n        cmd_args.parse_cmd_args(parser, ['1'])\n        argdefault = next(action for action in parser._actions if action.dest == 'arg').default\n        self.assertEqual(argdefault, 1)\n\n    def test_required_not_enforced(self):\n        parser = GooeyParser()\n        ArgumentParser.original_parse_args = ArgumentParser.parse_args\n\n        parser.add_argument('--arg', type=int, required=True)\n        parser.add_argument('--argn', type=int, nargs='+')\n        parser.add_argument('argp', type=int)\n        mutex=parser.add_mutually_exclusive_group(required=True)\n        mutex.add_argument('--one', action='store_true')\n        mutex.add_argument('--two', action='store_true')\n\n        # No error when we don't provide required arguments\n        cmd_args.parse_cmd_args(parser)\n\n        # Test that required/argn have been restored in parser\n        argrequired = next(action for action in parser._actions if action.dest == 'arg').required\n        self.assertEqual(argrequired, True)\n        argnnargs = next(action for action in parser._actions if action.dest == 'argn').nargs\n        self.assertEqual(argnnargs, '+')\n        argpnargs = next(action for action in parser._actions if action.dest == 'argp').nargs\n        self.assertEqual(argpnargs, None)\n        mutexrequired = next(mutex for mutex in parser._mutually_exclusive_groups).required\n        self.assertEqual(mutexrequired, True)\n\n    def test_cmd_args_subparser(self):\n        parser = GooeyParser()\n        subparsers = parser.add_subparsers(dest='subparser')\n        subparserA = subparsers.add_parser('A')\n        subparserB = subparsers.add_parser('B')\n        subparserA.add_argument('argA', type=int, default=0)\n        subparserB.add_argument('argB', type=int, default=0)\n\n        ArgumentParser.original_parse_args = ArgumentParser.parse_args\n\n        cmd_args.parse_cmd_args(parser, ['A', '1'])\n\n        # Check that argA is overwritten but not argB\n        subparseraction = next(action for action in parser._actions if action.dest == 'subparser')\n        argAdefault = next(action for action in subparseraction.choices['A']._actions if action.dest == 'argA').default\n        self.assertEqual(argAdefault, 1)\n        argBdefault = next(action for action in subparseraction.choices['B']._actions if action.dest == 'argB').default\n        self.assertEqual(argBdefault, 0)\n"
  },
  {
    "path": "gooey/tests/test_common.py",
    "content": "import unittest\nfrom collections import namedtuple\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\nCase = namedtuple('Case', 'inputs initialExpected')\n\n\nclass TestCommonProperties(unittest.TestCase):\n    \"\"\"\n    Test options and functionality\n    common across all widgets.\n    \"\"\"\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument('--widget', **kwargs)\n        return parser\n\n    def testInitialValue(self):\n        widgets = ['ColourChooser',\n                   'CommandField',\n                   'DateChooser', 'DirChooser', 'FileChooser', 'FileSaver',\n                   'FilterableDropdown',  'MultiDirChooser', 'MultiFileChooser',\n                   'PasswordField',  'TextField', 'Textarea', 'TimeChooser']\n\n        cases = [\n            # initial_value supersedes, default\n            Case(\n                {'default': 'default', 'gooey_options': {'initial_value': 'some val'}},\n                'some val'),\n            Case(\n                {'gooey_options': {'initial_value': 'some val'}},\n                 'some val'),\n            Case(\n                {'default': 'default', 'gooey_options': {}},\n                 'default'),\n            Case({'default': 'default'},\n                 'default')\n        ]\n\n        for widgetName in widgets:\n            with self.subTest(widgetName):\n                for case in cases:\n                    parser = self.makeParser(widget=widgetName, **case.inputs)\n                    with instrumentGooey(parser) as (app, frame, gapp):\n                        widget = gapp.getActiveConfig().reifiedWidgets[0]\n                        self.assertEqual(widget.getValue()['rawValue'], case.initialExpected)\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "gooey/tests/test_config_generator.py",
    "content": "import unittest\nfrom argparse import ArgumentParser\n\nfrom python_bindings import constants\nfrom python_bindings.config_generator import create_from_parser\nfrom gooey.tests import *\nfrom gooey.python_bindings.parameters import gooey_params\n\n\nclass TextConfigGenerator(unittest.TestCase):\n\n    def test_program_description(self):\n        \"\"\"\n        Should use `program_description` if supplied, otherwise\n        fallback to the description on the `parser`\n        \"\"\"\n\n        parser = ArgumentParser(description=\"Parser Description\")\n        # when supplied explicitly, we assign it as the description\n        params = gooey_params(program_description='Custom Description')\n        buildspec = create_from_parser(parser, \"\", **params)\n        self.assertEqual(buildspec['program_description'], 'Custom Description')\n\n        # when no explicit program_definition supplied, we fallback to the parser's description\n        buildspec = create_from_parser(parser, \"\", **gooey_params())\n        self.assertEqual(buildspec['program_description'], 'Parser Description')\n\n        # if no description is provided anywhere, we just set it to be an empty string.\n        blank_parser = ArgumentParser()\n        buildspec = create_from_parser(blank_parser, \"\", **gooey_params())\n        self.assertEqual(buildspec['program_description'], '')\n\n    def test_valid_font_weights(self):\n        \"\"\"\n        Asserting that only valid font-weights are allowable.\n        \"\"\"\n        all_valid_weights = range(100, 1001, 100)\n        for weight in all_valid_weights:\n            parser = ArgumentParser(description=\"test parser\")\n            params = gooey_params(terminal_font_weight=weight)\n            buildspec = create_from_parser(parser, \"\", **params)\n            self.assertEqual(buildspec['terminal_font_weight'], weight)\n\n    def test_font_weight_defaults_to_normal(self):\n        parser = ArgumentParser(description=\"test parser\")\n        # no font_weight explicitly provided\n        buildspec = create_from_parser(parser, \"\", **gooey_params())\n        self.assertEqual(buildspec['terminal_font_weight'], constants.FONTWEIGHT_NORMAL)\n\n\n    def test_invalid_font_weights_throw_error(self):\n        parser = ArgumentParser(description=\"test parser\")\n        with self.assertRaises(ValueError):\n            invalid_weight = 9123\n            params = gooey_params(terminal_font_weight=invalid_weight)\n            buildspec = create_from_parser(parser, \"\", **params)\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_constraints.py",
    "content": "import unittest\n\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\nclass TestConstraints(unittest.TestCase):\n\n    def test_listbox_constraints(self):\n        \"\"\"\n        Listbox widgets must be provided a nargs option\n        \"\"\"\n\n        # Trying to create a listbox widget without specifying nargs\n        # throws an error\n        with self.assertRaises(ValueError):\n            parser = GooeyParser()\n            parser.add_argument('one', choices=['one', 'two'], widget='Listbox')\n\n        # Listbox with an invalid nargs value throws an error\n        with self.assertRaises(ValueError):\n            parser = GooeyParser()\n            parser.add_argument(\n                'one', choices=['one', 'two'], widget='Listbox', nargs='?')\n\n        # Listbox with an invalid nargs value throws an error\n        with self.assertRaises(ValueError):\n            parser = GooeyParser()\n            parser.add_argument(\n                'one', choices=['one', 'two'], widget='Listbox', nargs=3)\n\n        # valid nargs throw no errors\n        for narg in ['*', '+']:\n            parser = GooeyParser()\n            parser.add_argument(\n                'one', choices=['one', 'two'], widget='Listbox', nargs=narg)\n\n\n\n    def test_visibility_constraint(self):\n        \"\"\"\n        When visible=False in Gooey config, the user MUST supply either\n        a custom validator or a default value.\n        \"\"\"\n        # added without issue\n        parser = GooeyParser()\n        parser.add_argument('one')\n\n        # still fine\n        parser = GooeyParser()\n        parser.add_argument('one', gooey_options={'visible': True})\n\n        # trying to hide an input without a default or custom validator\n        # results in an error\n        with self.assertRaises(ValueError):\n            parser = GooeyParser()\n            parser.add_argument('one', gooey_options={'visible': False})\n\n        # explicit default=None; still error\n        with self.assertRaises(ValueError):\n            parser = GooeyParser()\n            parser.add_argument(\n                'one',\n                default=None,\n                gooey_options={'visible': False})\n\n        # default = empty string. Still error\n        with self.assertRaises(ValueError):\n            parser = GooeyParser()\n            parser.add_argument(\n                'one',\n                default='',\n                gooey_options={'visible': False})\n\n        # default = valid string. No Error\n        parser = GooeyParser()\n        parser.add_argument(\n            'one',\n            default='Hello',\n            gooey_options={'visible': False})\n\n        # No default, but custom validator: Success\n        parser = GooeyParser()\n        parser.add_argument(\n            'one',\n            gooey_options={\n                'visible': False,\n                'validator': {'test': 'true'}\n            })\n\n        # default AND validator, still fine\n        parser = GooeyParser()\n        parser.add_argument(\n            'one',\n            default='Hai',\n            gooey_options={\n                'visible': False,\n                'validator': {'test': 'true'}\n            })"
  },
  {
    "path": "gooey/tests/test_control.py",
    "content": "import json\nimport unittest\nfrom argparse import ArgumentParser\nfrom contextlib import contextmanager\nfrom pprint import pprint\nfrom typing import Dict, List\nfrom unittest.mock import MagicMock, patch\n\nimport sys\nimport shlex\n\nfrom wx._core import CommandEvent\n\nfrom gooey import GooeyParser\nfrom python_bindings.coms import decode_payload, deserialize_inbound\nfrom python_bindings.dynamics import patch_argument, check_value\nfrom gooey.python_bindings import control\nfrom gooey.python_bindings.parameters import gooey_params\nfrom gooey.gui import state as s\nfrom gooey.python_bindings.schema import validate_public_state\nfrom python_bindings.types import FormField\n\nfrom tests.harness import instrumentGooey\n\nfrom gooey.tests import *\n\n\ndef custom_type(x):\n    if x == '1234':\n        return x\n    else:\n        raise Exception('KABOOM!')\n\n\nclass TestControl(unittest.TestCase):\n\n    def tearDown(self):\n        \"\"\"\n        Undoes the monkey patching after every tests\n        \"\"\"\n        if hasattr(ArgumentParser, 'original_parse_args'):\n            ArgumentParser.parse_args = ArgumentParser.original_parse_args\n\n    def test_validate_form(self):\n        \"\"\"\n        Testing the major validation cases we support.\n        \"\"\"\n        writer = MagicMock()\n        exit = MagicMock()\n        monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit)\n        ArgumentParser.original_parse_args = ArgumentParser.parse_args\n        ArgumentParser.parse_args = monkey_patch\n\n        parser = GooeyParser()\n        # examples:\n        # ERROR: mismatched builtin type\n        parser.add_argument('a', type=int, gooey_options={'initial_value': 'not-an-int'})\n        # ERROR: mismatched custom type\n        parser.add_argument('b', type=custom_type, gooey_options={'initial_value': 'not-a-float'})\n        # ERROR: missing required positional arg\n        parser.add_argument('c')\n        # ERROR: missing required 'optional' arg\n        parser.add_argument('--oc', required=True)\n        # VALID: This is one of the bizarre cases which are possible\n        # but don't make much sense. It should pass through as valid\n        # because there's no way for us to send a 'not present optional value'\n        parser.add_argument('--bo', action='store_true', required=True)\n        # ERROR: a required mutex group, with no args supplied.\n        # Should flag all as missing.\n        group = parser.add_mutually_exclusive_group(required=True)\n        group.add_argument('--gp1-a', type=str)\n        group.add_argument('--gp1-b', type=str)\n\n        # ERROR: required mutex group with a default option but nothing\n        # selected will still fail\n        group2 = parser.add_mutually_exclusive_group(required=True)\n        group2.add_argument('--gp2-a', type=str)\n        group2.add_argument('--gp2-b', type=str, default='Heeeeyyyyy')\n\n        # VALID: now, same as above, but now the option is actually enabled via\n        # the initial selection. No error.\n        group3 = parser.add_mutually_exclusive_group(required=True, gooey_options={'initial_selection': 1})\n        group3.add_argument('--gp3-a', type=str)\n        group3.add_argument('--gp3-b', type=str, default='Heeeeyyyyy')\n        # VALID: optional mutex.\n        group4 = parser.add_mutually_exclusive_group()\n        group4.add_argument('--gp4-a', type=str)\n        group4.add_argument('--gp4-b', type=str)\n        # VALID: arg present and type satisfied\n        parser.add_argument('ga', type=str, gooey_options={'initial_value': 'whatever'})\n        # VALID: arg present and custom type satisfied\n        parser.add_argument('gb', type=custom_type, gooey_options={'initial_value': '1234'})\n        # VALID: optional\n        parser.add_argument('--gc')\n\n        # now we're adding the same\n        with instrumentGooey(parser, target='test') as (app, frame, gapp):\n            # we start off with no errors\n            self.assertFalse(s.has_errors(gapp.fullState()))\n\n            # now we feed our form-validation\n            cmd = s.buildFormValidationCmd(gapp.fullState())\n            asdf = shlex.split(cmd)[1:]\n            parser.parse_args(shlex.split(cmd)[1:])\n            assert writer.called\n            assert exit.called\n\n\n        result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')\n        # Host->Gooey communication is all done over the PublicGooeyState schema\n        # as such, we coarsely validate it's shape here\n        validate_public_state(result)\n\n        # manually merging the two states back together\n        nextState = s.mergeExternalState(gapp.fullState(), result)\n        # and now we find that we have errors!\n        self.assertTrue(s.has_errors(nextState))\n        items = s.activeFormState(nextState)\n        self.assertIn('invalid literal', get_by_id(items, 'a')['error'])\n        self.assertIn('KABOOM!', get_by_id(items, 'b')['error'])\n        self.assertIn('required', get_by_id(items, 'c')['error'])\n        self.assertIn('required', get_by_id(items, 'oc')['error'])\n        for item in get_by_id(items, 'group_gp1_a_gp1_b')['options']:\n            self.assertIsNotNone(item['error'])\n        for item in get_by_id(items, 'group_gp2_a_gp2_b')['options']:\n            self.assertIsNotNone(item['error'])\n\n        for item in get_by_id(items, 'group_gp3_a_gp3_b')['options']:\n            self.assertIsNone(item['error'])\n        # should be None, since this one was entirely optional\n        for item in get_by_id(items, 'group_gp4_a_gp4_b')['options']:\n            self.assertIsNone(item['error'])\n        self.assertIsNone(get_by_id(items, 'bo')['error'])\n        self.assertIsNone(get_by_id(items, 'ga')['error'])\n        self.assertIsNone(get_by_id(items, 'gb')['error'])\n        self.assertIsNone(get_by_id(items, 'gc')['error'])\n\n\n    def test_subparsers(self):\n        \"\"\"\n        Making sure that subparsers are handled correctly and\n        all validations still work as expected.\n        \"\"\"\n        writer = MagicMock()\n        exit = MagicMock()\n        monkey_patch = control.validate_form(gooey_params(), write=writer, exit=exit)\n        ArgumentParser.original_parse_args = ArgumentParser.parse_args\n        ArgumentParser.parse_args = monkey_patch\n\n        def build_parser():\n            # we build a new parser for each subtest\n            # since we monkey patch the hell out of it\n            # each time\n            parser = GooeyParser()\n            subs = parser.add_subparsers()\n            foo = subs.add_parser('foo')\n            foo.add_argument('a')\n            foo.add_argument('b')\n            foo.add_argument('p')\n\n            bar = subs.add_parser('bar')\n            bar.add_argument('a')\n            bar.add_argument('b')\n            bar.add_argument('z')\n            return parser\n\n        parser = build_parser()\n        with instrumentGooey(parser, target='test') as (app, frame, gapp):\n            with self.subTest('first subparser'):\n                # we start off with no errors\n                self.assertFalse(s.has_errors(gapp.fullState()))\n\n                cmd = s.buildFormValidationCmd(gapp.fullState())\n                parser.parse_args(shlex.split(cmd)[1:])\n                assert writer.called\n                assert exit.called\n\n                result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')\n                nextState = s.mergeExternalState(gapp.fullState(), result)\n                # by default, the subparser defined first, 'foo', is selected.\n                self.assertIn('foo', nextState['forms'])\n                # and we should find its attributes\n                expected = {'a', 'b', 'p'}\n                actual = {x['id'] for x in nextState['forms']['foo']}\n                self.assertEqual(expected, actual)\n\n\n        parser = build_parser()\n        with instrumentGooey(parser, target='test') as (app, frame, gapp):\n            with self.subTest('Second subparser'):\n                # mocking a 'selection change' event to select\n                # the second subparser\n                event = MagicMock()\n                event.Selection = 1\n                gapp.handleSelectAction(event)\n\n                # Flushing our events by running the main loop\n                wx.CallLater(1, app.ExitMainLoop)\n                app.MainLoop()\n\n                cmd = s.buildFormValidationCmd(gapp.fullState())\n                parser.parse_args(shlex.split(cmd)[1:])\n                assert writer.called\n                assert exit.called\n\n                result = deserialize_inbound(writer.call_args[0][0].encode('utf-8'), 'utf-8')\n                nextState = s.mergeExternalState(gapp.fullState(), result)\n                # Now our second subparer, 'bar', should be present.\n                self.assertIn('bar', nextState['forms'])\n                # and we should find its attributes\n                expected = {'a', 'b', 'z'}\n                actual = {x['id'] for x in nextState['forms']['bar']}\n                self.assertEqual(expected, actual)\n\n\n    def test_ignore_gooey(self):\n        parser = GooeyParser()\n        subs = parser.add_subparsers()\n        foo = subs.add_parser('foo')\n        foo.add_argument('a')\n        foo.add_argument('b')\n        foo.add_argument('p')\n\n        bar = subs.add_parser('bar')\n        bar.add_argument('a')\n        bar.add_argument('b')\n        bar.add_argument('z')\n\n        control.bypass_gooey(gooey_params())(parser)\n\ndef get_by_id(items: List[FormField], id: str):\n    return [x for x in items if x['id'] == id][0]\n\n\n\n"
  },
  {
    "path": "gooey/tests/test_counter.py",
    "content": "import unittest\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\n\n\nclass TestCounter(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument(\n            '--widget',\n            action='count',\n            widget=\"Counter\",\n            **kwargs)\n        return parser\n\n\n    def testInitialValue(self):\n        cases = [\n            # `initial` should supersede `default`\n            {'inputs': {'default': 1,\n                        'gooey_options': {'initial_value': 3}},\n             'expect': '3'},\n\n            {'inputs': {'gooey_options': {'initial_value': 1}},\n             'expect': '1'},\n\n            {'inputs': {'default': 2,\n                        'gooey_options': {}},\n             'expect': '2'},\n\n            {'inputs': {'default': 1},\n             'expect': '1'},\n\n            {'inputs': {},\n             'expect': None}\n        ]\n        for case in cases:\n            with self.subTest(case):\n                parser = self.makeParser(**case['inputs'])\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    widget = gapp.getActiveConfig().reifiedWidgets[0]\n                    self.assertEqual(widget.getValue()['rawValue'], case['expect'])\n\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_decoration.py",
    "content": "import unittest\nfrom argparse import ArgumentParser\nfrom functools import wraps\n\nfrom python_bindings.types import TimingOptions\n\n\n# TODO:\n\n# def decor(f=None, *gargs, **gkwargs):\n#     @wraps(f)\n#     def inner(*args, **kwargs):\n#         print('hello from decorator', gargs, gkwargs)\n#         # choose handler\n#         # monkey patch parser\n#\n#         return f(*args, **kwargs)\n#\n#     def inner2(func):\n#         return decor(func, *gargs, **gkwargs)\n#\n#     return inner if callable(f) else inner2\n#\n#\n# def handle_success(params):\n#     def parse_args(self: ArgumentParser, args=None, namespace=None):\n#         return self._original_parse_args()\n#     return parse_args\n#\n#\n# # @decor\n# def main(*args, **kwargs):\n#     \"\"\"Hellow world!!!!!\"\"\"\n#     print('sup from main', args, kwargs)\n#\n#     # ArgumentParser._original_parse_args = ArgumentParser.parse_args\n#     # ArgumentParser.parse_args = handle_success(ArgumentParser.parse_args)\n#\n#     parser = ArgumentParser()\n#     parser.add_argument('-f', '--foo', help='is foo')\n#     subs = parser.add_subparsers()\n#     sp = subs.add_parser('hh')\n#     sp.add_argument('-f', '--foo', help='sp.foo')\n#     print(parser.parse_args(['hh', '-f', 'asdf']))\n#\n#     print(TimingOptions(show_time_remaining=True, hide_time_remaining_on_complete=True).hide_time_remaining_on_complete)\n#\n#\n#\n#\n# class Testie(unittest.TestCase):\n#\n#     def test_thing(self, **kwargs):\n#         print(main(1, 2))\n#         print(help(main))"
  },
  {
    "path": "gooey/tests/test_dropdown.py",
    "content": "import unittest\nfrom argparse import ArgumentParser\nfrom unittest.mock import patch\n\nfrom gooey import GooeyParser\nfrom tests.harness import instrumentGooey\nfrom gooey.tests import *\n\nclass TestGooeyDropdown(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument('--dropdown', **kwargs)\n        return parser\n\n\n    # @patch(\"gui.containers.application.seeder.fetchDynamicProperties\")\n    # def test_dropdown_behavior(self, mock):\n    #     \"\"\"\n    #     Testing that:\n    #         - default values are used as the initial selection (when present)\n    #         - Initial selection defaults to placeholder when no defaults supplied\n    #         - selection is preserved (when possible) across dynamic updates\n    #     \"\"\"\n    #     testcases = [\n    #         # tuples of [choices, default, initalSelection, dynamicUpdate, expectedFinalSelection]\n    #         [['1', '2'], None, 'Select Option', ['1', '2','3'], 'Select Option'],\n    #         [['1', '2'], '2', '2', ['1', '2','3'],  '2'],\n    #         [['1', '2'], '1', '1', ['1', '2','3'], '1'],\n    #         # dynamic updates removed our selected value; defaults back to placeholder\n    #         [['1', '2'], '2', '2', ['1', '3'], 'Select Option'],\n    #         # TODO: this test case is currently passing wrong data for the dynamic\n    #         # TODO: update due to a bug where Gooey doesn't apply the same ingestion\n    #         # TODO: rules for data received dynamically as it does for parsers.\n    #         # TODO: In short, Gooey should be able to handle a list of bools [True, False]\n    #         # TODO: from dynamics just like it does in parser land. It doesn't currently\n    #         # TODO: do this, so I'm manually casting it to strings for now.\n    #         [[True, False], True, 'True', ['True', 'False'], 'True']\n    #     ]\n    #\n    #     for choices, default, initalSelection, dynamicUpdate, expectedFinalSelection in testcases:\n    #         parser = self.makeParser(choices=choices, default=default)\n    #         with instrumentGooey(parser) as (app, frame):\n    #             dropdown = frame.configs[0].reifiedWidgets[0]\n    #             # ensure that default values (when supplied) are selected in the UI\n    #             self.assertEqual(dropdown.widget.GetValue(), initalSelection)\n    #             # fire a dynamic update with the mock values\n    #             mock.return_value = {'--dropdown': dynamicUpdate}\n    #             frame.fetchExternalUpdates()\n    #             # the values in the UI now reflect those returned from the update\n    #             # note: we're appending the ['select option'] bit here as it gets automatically added\n    #             # in the UI.\n    #             expectedValues = ['Select Option'] + dynamicUpdate\n    #             self.assertEqual(dropdown.widget.GetItems(), expectedValues)\n    #             # and our selection is what we expect\n    #             self.assertEqual(dropdown.widget.GetValue(), expectedFinalSelection)\n\n\n    def testInitialValue(self):\n        cases = [\n            # `initial` should supersede `default`\n            {'inputs': {'default': 'b',\n                        'choices': ['a', 'b', 'c'],\n                        'gooey_options': {'initial_value': 'a'}},\n             'expect': 'a'},\n\n            {'inputs': {'choices': ['a', 'b', 'c'],\n                        'gooey_options': {'initial_value': 'a'}},\n             'expect': 'a'},\n\n            {'inputs': {'choices': ['a', 'b', 'c'],\n                        'default': 'b',\n                        'gooey_options': {}},\n             'expect': 'b'},\n\n            {'inputs': {'choices': ['a', 'b', 'c'],\n                        'default': 'b'},\n             'expect': 'b'},\n\n            {'inputs': {'choices': ['a', 'b', 'c']},\n             'expect': None}\n        ]\n        for case in cases:\n            with self.subTest(case):\n                parser = self.makeParser(**case['inputs'])\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    widget = gapp.getActiveConfig().reifiedWidgets[0]\n                    self.assertEqual(widget.getValue()['rawValue'], case['expect'])\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_filterable_dropdown.py",
    "content": "import unittest\nfrom argparse import ArgumentParser\nfrom collections import namedtuple\nfrom unittest.mock import patch\nimport wx\nfrom gooey.tests import *\n\nfrom gooey.tests.harness import instrumentGooey\nfrom gooey import GooeyParser\n\n\nclass TestGooeyFilterableDropdown(unittest.TestCase):\n\n    def make_parser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument('--dropdown', widget='FilterableDropdown', **kwargs)\n        return parser\n\n    def test_input_spawns_popup(self):\n        parser = self.make_parser(choices=['alpha1', 'alpha2', 'beta', 'gamma'])\n        with instrumentGooey(parser) as (app, frame, gapp):\n            dropdown = gapp.getActiveConfig().reifiedWidgets[0]\n\n            event = wx.CommandEvent(wx.wxEVT_TEXT, wx.Window.NewControlId())\n            event.SetEventObject(dropdown.widget.GetTextCtrl())\n\n            dropdown.widget.GetTextCtrl().ProcessEvent(event)\n            self.assertTrue(\n                dropdown.model.suggestionsVisible,\n                dropdown.listbox.IsShown()\n            )\n\n    def test_arrow_key_selection_cycling(self):\n        \"\"\"\n        Testing that the up/down arrow keys spawn the dropdown\n        and cycle through its options wrapping around as needed.\n        \"\"\"\n        Scenario = namedtuple('Scenario', [\n            'key', 'expectVisible', 'expectedSelection', 'expectedDisplayValue'])\n\n        choices = ['alpha', 'beta']\n        # no text entered yet\n        initial = Scenario(None, False, -1, '')\n        scenarios = [\n            # cycling down\n            [\n            Scenario(wx.WXK_DOWN, True, -1, ''),\n            Scenario(wx.WXK_DOWN, True, 0, 'alpha'),\n            Scenario(wx.WXK_DOWN, True, 1, 'beta'),\n            # wraps around to top\n            Scenario(wx.WXK_DOWN, True, 0, 'alpha')\n        ],  # cycling up\n            [\n            Scenario(wx.WXK_UP, True, -1, ''),\n            Scenario(wx.WXK_UP, True, 1, 'beta'),\n            Scenario(wx.WXK_UP, True, 0, 'alpha'),\n            # wraps around to top\n            Scenario(wx.WXK_UP, True, 1, 'beta'),\n        ]]\n\n        for actions in scenarios:\n            parser = self.make_parser(choices=choices)\n            with instrumentGooey(parser) as (app, frame, gapp):\n                dropdown = gapp.getActiveConfig().reifiedWidgets[0]\n                # sanity check we're starting from our known initial state\n                self.assertEqual(dropdown.model.suggestionsVisible, initial.expectVisible)\n                self.assertEqual(dropdown.model.displayValue, initial.expectedDisplayValue)\n                self.assertEqual(dropdown.model.selectedSuggestion, initial.expectedSelection)\n\n                for action in actions:\n                    self.pressButton(dropdown, action.key)\n                    self.assertEqual(\n                        dropdown.model.suggestionsVisible,\n                        dropdown.listbox.IsShown()\n                    )\n                    self.assertEqual(\n                        dropdown.model.displayValue,\n                        action.expectedDisplayValue\n                    )\n                    self.assertEqual(\n                        dropdown.model.selectedSuggestion,\n                        action.expectedSelection\n                    )\n\n\n    def enterText(self, dropdown, text):\n        event = wx.CommandEvent(wx.wxEVT_TEXT, wx.Window.NewControlId())\n        event.SetString(text)\n        dropdown.widget.GetTextCtrl().ProcessEvent(event)\n\n    def pressButton(self, dropdown, keycode):\n        event = mockKeyEvent(keycode)\n        dropdown.onKeyboardControls(event)\n\n\ndef mockKeyEvent(keycode):\n    \"\"\"\n    Manually bypassing the setters as they don't allow\n    the non wx.wxXXX event variants by default.\n    The internal WX post/process machinery doesn't handle key\n    codes well for some reason, thus has to be mocked and\n    manually passed to the relevant handler.\n    \"\"\"\n    event = wx.KeyEvent()\n    event.KeyCode = keycode\n    return event\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "gooey/tests/test_filtering.py",
    "content": "import unittest\n\nfrom gooey import PrefixTokenizers\nfrom gui.components.filtering.prefix_filter import SearchOptions, PrefixSearch\nfrom collections import namedtuple\n\nTestData = namedtuple('TestData', [\n    'options',\n    'input_string',\n    'expected_results',\n])\n\nPlaces = namedtuple('Places', [\n    'kabul',\n    'tirana',\n    'kyoto',\n    'tokyo'\n])\n\nclass TestPrefixFilter(unittest.TestCase):\n\n\n    def setUp(self):\n        self.testdata = Places(\n            'Afghanistan Kabul',\n            'Albania Tirana',\n            'Japan Kyoto',\n            'Japan Tokyo'\n        )\n\n    def test_prefix_searching(self):\n        p = self.testdata\n        cases = [\n            TestData({'ignore_case': True}, 'a', [p.kabul, p.tirana]),\n            TestData({'ignore_case': True}, 'A', [p.kabul, p.tirana]),\n            TestData({'ignore_case': False}, 'a', []),\n            TestData({'ignore_case': False}, 'A', [p.kabul, p.tirana]),\n\n            # when using the phrase tokenizer, the search input must\n            # match starting from the beginning. So we find Afghanistan\n            TestData({'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE}, 'Afghan', [p.kabul]),\n            # but we cannot look up Kyoto because the phrase begins with \"Japan\"\n            TestData({'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE}, 'Kyoto', []),\n            # So if we start with \"Japan K\" it'll be returned\n            TestData({'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE}, 'Japan K', [p.kyoto]),\n\n\n\n            # word tokenizer will split on all whitespace and index\n            # each choice one for each UNIQUE word\n            # so passing in 'a' will match \"Af\" and \"Al\" as usual\n            TestData({'choice_tokenizer': PrefixTokenizers.WORDS}, 'a', [p.kabul, p.tirana]),\n            # but now we can also find Kyoto without prefixing \"japan\" as we'd\n            # need to do with the phrase tokenizer\n            TestData({'choice_tokenizer': PrefixTokenizers.WORDS}, 'kyo', [p.kyoto]),\n\n            # if we tokenize the input, we're perform two searches against the index\n            # The default operator is AND, which means all the words in your search\n            # input must match the choice for it to count as as a hit.\n            # In this example, we index the choices under PHRASE, but set the input\n            # tokenizer to WORDS. Our input 'Japan K' gets tokenized to ['Japan', 'K']\n            # There is no phrase which starts with Both \"Japan\" and \"K\" so we get no\n            # matches returned\n            TestData({'choice_tokenizer': PrefixTokenizers.ENTIRE_PHRASE,\n                      'input_tokenizer': PrefixTokenizers.WORDS}, 'Japan K', []),\n            # Tokenize the choices by WORDS means we can now filter on both words\n            TestData({'choice_tokenizer': PrefixTokenizers.WORDS,\n                      'input_tokenizer': PrefixTokenizers.WORDS}, 'Jap K', [p.kyoto]),\n            # the default AND behavior can be swapped to OR to facilitate matching across\n            # different records in the index.\n            TestData({'choice_tokenizer': PrefixTokenizers.WORDS,\n                      'input_tokenizer': PrefixTokenizers.WORDS,\n                      'operator': 'OR'}, 'Kyo Tok', [p.kyoto, p.tokyo]),\n\n            # Turning on Suffix indexing allow matching anywhere within a word.\n            # Now 'kyo' will match both the beginning 'Kyoto' and substring 'ToKYO'\n            TestData({'choice_tokenizer': PrefixTokenizers.WORDS,\n                      'input_tokenizer': PrefixTokenizers.WORDS,\n                      'index_suffix': True}, 'kyo ', [p.kyoto, p.tokyo]),\n\n            TestData({'choice_tokenizer': PrefixTokenizers.WORDS,\n                      'input_tokenizer': PrefixTokenizers.WORDS,\n                      'index_suffix': True}, 'j kyo ', [p.kyoto, p.tokyo]),\n        ]\n\n        for case in cases:\n            with self.subTest(case):\n                searcher = PrefixSearch(self.testdata, case.options)\n                result = searcher.findMatches(case.input_string)\n                self.assertEqual(result, case.expected_results)\n\n\n"
  },
  {
    "path": "gooey/tests/test_formatters.py",
    "content": "import argparse\r\nimport os\r\nimport shlex\r\nimport unittest\r\n\r\nfrom gooey.gui import formatters\r\n\r\n\r\nclass TestFormatters(unittest.TestCase):\r\n\r\n\r\n    def test_counter_formatter(self):\r\n        \"\"\"\r\n        Should return the first option repeated N times\r\n        None if N is unspecified\r\n\r\n        Issue #316 - using long-form argument caused formatter to produce incorrect output\r\n        \"\"\"\r\n        expected_outputs = [\r\n            (['-v', '--verbose'], '-v', 1),\r\n            (['-v', '--verbose'], '-v -v', 2),\r\n            (['-v', '--verbose'], '-v -v -v', 3),\r\n            (['-v', '--verbose'], '', 0),\r\n            # ensuring that log-forms are handled correctly\r\n            (['--verbose', '-v'], '--verbose', 1),\r\n            (['--verbose', '-v'], '--verbose --verbose', 2),\r\n            (['--verbose', '-v'], '--verbose --verbose --verbose', 3),\r\n            # single args\r\n            (['-v'], '-v', 1),\r\n            (['-v'], '-v -v', 2),\r\n            (['--verbose'], '--verbose', 1),\r\n            # bad inputs\r\n            (['-v'], None, None),\r\n            (['-v'], None, 'some-garbage'),\r\n            (['-v'], None, 'af3gd'),\r\n        ]\r\n\r\n        for commands, expected, vebosity_level in expected_outputs:\r\n            result = formatters.counter({'commands': commands}, vebosity_level)\r\n            self.assertEqual(result, expected)\r\n            # make sure that argparse actually accepts it as valid.\r\n            if result:\r\n                parser = argparse.ArgumentParser()\r\n                parser.add_argument('-v', '--verbose', action='count')\r\n                parser.parse_args(result.split())\r\n\r\n    def test_multifilechooser_formatter(self):\r\n        \"\"\"\r\n        Should return files (quoted), separated by spaces if there is more\r\n        than one, preceded by optional command if the argument is optional.\r\n\r\n        Assumes the argument has been created with some form of nargs, which\r\n        only makes sense for possibly choosing multiple values.\r\n        \"\"\"\r\n\r\n        # Helper function to generalize the variants we need to test\r\n        def multifilechooser_helper(names):\r\n            # Note that the MultiFileChooser widget produces a single string with\r\n            # paths separated by os.pathsep.\r\n            if names:\r\n                prefix = names[0] + ' '\r\n            else:\r\n                prefix = ''\r\n\r\n            expected_outputs = [\r\n                (names, None, ''),\r\n                (names, prefix + '\"abc\"', 'abc'),\r\n                (names, prefix + '\"abc\" \"def\"', os.pathsep.join(['abc', 'def'])),\r\n                # paths with spaces\r\n                (names, prefix + '\"a b c\"', 'a b c'),\r\n                (names, prefix + '\"a b c\" \"d e f\"', os.pathsep.join(['a b c', 'd e f'])),\r\n            ]\r\n\r\n            for commands, expected, widget_result in expected_outputs:\r\n                result = formatters.multiFileChooser({'commands': commands}, widget_result)\r\n                self.assertEqual(result, expected)\r\n                # make sure that argparse actually accepts it as valid.\r\n                if result:\r\n                    parser = argparse.ArgumentParser()\r\n                    if not names:\r\n                        names = [\"file\"]\r\n                    parser.add_argument(names[0], nargs='+')\r\n                    parser.parse_args(shlex.split(result))\r\n\r\n        # Positional argument, with nargs\r\n        multifilechooser_helper([])\r\n\r\n        # Optional argument, with nargs\r\n        multifilechooser_helper([\"-f\", \"--file\"])\r\n"
  },
  {
    "path": "gooey/tests/test_header.py",
    "content": "import unittest\nfrom argparse import ArgumentParser\nfrom itertools import *\n\nfrom tests.harness import instrumentGooey\nfrom gooey.tests import *\n\nclass TestGooeyHeader(unittest.TestCase):\n\n    def make_parser(self):\n        parser = ArgumentParser(description='description')\n        return parser\n\n    def test_header_visibility(self):\n        \"\"\"\n        Test that the title and subtitle components correctly show/hide\n        based on config settings.\n\n        Verifying Issue #497\n        \"\"\"\n        for testdata in self.testcases():\n            with self.subTest(testdata):\n                with instrumentGooey(self.make_parser(), **testdata) as (app, frame, gapp):\n                    frame: wx.Frame = frame\n\n                    self.assertEqual(\n                        frame.FindWindowByName(\"header_title\").IsShown(),\n                        testdata.get('header_show_title', True)\n                    )\n\n                    self.assertEqual(\n                        frame.FindWindowByName(\"header_subtitle\").IsShown(),\n                        testdata.get('header_show_subtitle', True)\n                    )\n\n\n    def test_header_string(self):\n        \"\"\"\n        Verify that string in the buildspec get correctly\n        placed into the UI.\n        \"\"\"\n        parser = ArgumentParser(description='Foobar')\n        with instrumentGooey(parser, program_name='BaZzEr') as (app, frame, gapp):\n            self.assertEqual(frame.FindWindowByName(\"header_title\").GetLabel(), 'BaZzEr')\n            self.assertEqual(frame.FindWindowByName(\"header_subtitle\").GetLabel(), 'Foobar')\n\n\n    def testcases(self):\n        \"\"\"\n        Generate a powerset of all possible combinations of\n        the header parameters (empty, some present, all present, all combos)\n        \"\"\"\n        iterable = product(['header_show_title', 'header_show_subtitle'], [True, False])\n        allCombinations = list(powerset(iterable))\n        return [{k: v for k,v in args}\n                for args in allCombinations]\n\n\ndef powerset(iterable):\n    \"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)\"\n    s = list(iterable)\n    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_listbox.py",
    "content": "import unittest\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\n\n\nclass TestListbox(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument(\n            '--widget',\n            widget=\"Listbox\",\n            nargs=\"*\",\n            **kwargs)\n        return parser\n\n    def testInitialValue(self):\n        cases = [\n            # `initial` should supersede `default`\n            {'inputs': {'default': 'b',\n                        'choices': ['a', 'b', 'c'],\n                        'gooey_options': {'initial_value': 'a'}},\n             'expect': ['a']},\n\n            {'inputs': {'choices': ['a', 'b', 'c'],\n                        'gooey_options': {'initial_value': 'a'}},\n             'expect': ['a']},\n\n            {'inputs': {'choices': ['a', 'b', 'c'],\n                        'gooey_options': {'initial_value': ['a', 'c']}},\n             'expect': ['a', 'c']},\n\n            {'inputs': {'choices': ['a', 'b', 'c'],\n                        'default': 'b',\n                        'gooey_options': {}},\n             'expect': ['b']},\n\n            {'inputs': {'choices': ['a', 'b', 'c'],\n                        'default': 'b'},\n             'expect': ['b']},\n\n            {'inputs': {'choices': ['a', 'b', 'c']},\n             'expect': []}\n        ]\n        for case in cases:\n            with self.subTest(case):\n                parser = self.makeParser(**case['inputs'])\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    widget = gapp.getActiveConfig().reifiedWidgets[0]\n                    self.assertEqual(widget.getValue()['rawValue'], case['expect'])\n\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_numeric_inputs.py",
    "content": "import unittest\nfrom random import randint\nfrom unittest.mock import patch\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\nclass TestNumbericInputs(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument('--input', **kwargs)\n        return parser\n\n\n    def testDefault(self):\n        cases = [\n            [{'widget': 'IntegerField'}, 0],\n            [{'default': 0, 'widget': 'IntegerField'}, 0],\n            [{'default': 10, 'widget': 'IntegerField'}, 10],\n            [{'default': 76, 'widget': 'IntegerField'}, 76],\n            # note that WX caps the value\n            # unless explicitly widened via gooey_options\n            [{'default': 81234, 'widget': 'IntegerField'}, 100],\n            # here we set the max to something higher than\n            # the default and all works as expected.\n            # this is a TODO for validation\n            [{'default': 81234, 'widget': 'IntegerField', 'gooey_options': {'max': 99999}}, 81234],\n            # Initial Value cases\n            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 0}}, 0],\n            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 10}}, 10],\n            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 76}}, 76],\n            # note that WX caps the value\n            # unless explicitly widened via gooey_options\n            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 81234}}, 100],\n            # here we set the max to something higher than\n            # the default and all works as expected.\n            # this is a TODO for validation\n            [{'widget': 'IntegerField', 'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],\n\n            [{'widget': 'DecimalField'}, 0],\n            [{'default': 0, 'widget': 'DecimalField'}, 0],\n            [{'default': 81234, 'widget': 'DecimalField'}, 100],\n            [{'default': 81234, 'widget': 'DecimalField', 'gooey_options': {'max': 99999}}, 81234],\n            # Initial Value cases\n            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 0}}, 0],\n            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 10}}, 10],\n            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 76}}, 76],\n            # note that WX caps the value\n            # unless explicitly widened via gooey_options\n            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 81234}}, 100],\n            # here we set the max to something higher than\n            # the default and all works as expected.\n            # this is a TODO for validation\n            [{'widget': 'DecimalField', 'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],\n        ]\n        for inputs, expected in cases:\n            with self.subTest(inputs):\n                parser = self.makeParser(**inputs)\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    input = gapp.getActiveConfig().reifiedWidgets[0]\n                    self.assertEqual(input.getValue()['rawValue'], expected)\n\n    def testGooeyOptions(self):\n        cases = [\n            {'widget': 'DecimalField', 'gooey_options': {'min': -100, 'max': 1234, 'increment': 1.240}},\n            {'widget': 'DecimalField', 'gooey_options': {'min': 1234, 'max': 3456, 'increment': 2.2}},\n            {'widget': 'IntegerField', 'gooey_options': {'min': -100, 'max': 1234}},\n            {'widget': 'IntegerField', 'gooey_options': {'min': 1234, 'max': 3456}}\n        ];\n        using = {\n            'min': lambda widget: widget.GetMin(),\n            'max': lambda widget: widget.GetMax(),\n            'increment': lambda widget: widget.GetIncrement(),\n\n        }\n        for case in cases:\n            with self.subTest(case):\n                parser = self.makeParser(**case)\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    wxWidget = gapp.getActiveConfig().reifiedWidgets[0].widget\n                    for option, value in case['gooey_options'].items():\n                        self.assertEqual(using[option](wxWidget), value)\n\n\n    def testZerosAreReturned(self):\n        \"\"\"\n        Originally the formatter was dropping '0' due to\n        it being interpreted as falsey\n        \"\"\"\n        parser = self.makeParser(widget='IntegerField')\n        with instrumentGooey(parser) as (app, frame, gapp):\n            field = gapp.getActiveConfig().reifiedWidgets[0]\n            result = field.getValue()\n            self.assertEqual(result['rawValue'], 0)\n            self.assertIsNotNone(result['cmd'])\n\n    def testNoLossOfPrecision(self):\n        parser = self.makeParser(widget='DecimalField', default=12.23534, gooey_options={'precision': 20})\n        with instrumentGooey(parser) as (app, frame, gapp):\n            field = gapp.getActiveConfig().reifiedWidgets[0]\n            result = field.getValue()\n            self.assertEqual(result['rawValue'], 12.23534)\n            self.assertIsNotNone(result['cmd'])\n\n\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_options.py",
    "content": "import unittest\n\nfrom gooey.gui.components.options import options\n\nclass TestPrefixFilter(unittest.TestCase):\n\n    def test_doc_schenanigans(self):\n        \"\"\"Sanity check that my docstring wrappers all behave as expected\"\"\"\n        @options._include_layout_docs\n        def no_self_docstring():\n            pass\n\n        @options._include_layout_docs\n        def yes_self_docstring():\n            \"\"\"sup\"\"\"\n            pass\n\n        # gets attached to functions even if they don't have a docstring\n        self.assertIn(options.LayoutOptions.__doc__, no_self_docstring.__doc__)\n        # gets attached to the *end* of existing doc strings\n        self.assertTrue(yes_self_docstring.__doc__.startswith('sup'))\n        self.assertIn(options.LayoutOptions.__doc__, yes_self_docstring.__doc__)\n\n\n    def test_clean_method(self):\n        \"\"\"\n        _clean should drop any keys with None values\n        and flatten the layout_option kwargs to the root level\n        \"\"\"\n        result = options._clean({'a': None, 'b': 123, 'c': 0})\n        self.assertEqual(result, {'b': 123, 'c': 0})\n\n        result = options._clean({'root_level': 123, 'layout_options': {\n            'nested': 'hello',\n            'another': 1234\n        }})\n        self.assertEqual(result, {'root_level': 123, 'nested': 'hello', 'another': 1234})\n\n    def test_only_provided_arguments_included(self):\n        \"\"\"\n        More sanity checking that the internal use of locals()\n        does the Right Thing\n        \"\"\"\n        option = options.LayoutOptions(label_color='#ffffff')\n        self.assertIn('label_color', option)\n\n        option = options.LayoutOptions()\n        self.assertNotIn('label_color', option)\n\n        option = options.TextField(label_color='#ffffff')\n        self.assertIn('label_color', option)\n\n        option = options.TextField()\n        self.assertNotIn('label_color', option)"
  },
  {
    "path": "gooey/tests/test_parent_inheritance.py",
    "content": "import argparse\nimport unittest\n\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\nclass TestParentInheritance(unittest.TestCase):\n\n    def test_parent_arguments_exist_in_child(self):\n        \"\"\"\n        Verifies that the parents parameter is honoured.\n        \"\"\"\n        base_parser = GooeyParser(add_help=False)\n        base_parser.add_argument(\"a_file\", widget=\"FileChooser\")\n\n        parser = GooeyParser(parents=[base_parser])\n        parser.add_argument(\"b_file\", widget=\"DirChooser\")\n\n        found = 0\n        for action in parser._actions:\n            if action.dest == \"a_file\":\n                found += 1\n            elif action.dest == \"b_file\":\n                found += 1\n\n        self.assertEqual(2, found, \"Did not find 2 expected arguments, found \" + str(found))\n        self.assertEqual(parser.widgets[\"a_file\"], \"FileChooser\")\n        self.assertEqual(parser.widgets[\"b_file\"], \"DirChooser\")\n\n    def test_parent_arguments_are_not_overridden(self):\n        \"\"\"\n        Verifies that the same named argument in a parent and child parser is accepted, and only the child\n        parser survives.\n        \"\"\"\n        # Verify how vanilla argparse works\n        base_parser = argparse.ArgumentParser(add_help=False)\n        action1 = base_parser.add_argument(\"a_file\", default=\"a\")\n\n        parser = argparse.ArgumentParser(parents=[base_parser])\n        action2 = parser.add_argument(\"a_file\", default=\"b\")\n\n        self._verify_duplicate_parameters(action1, action2, parser)\n        # So a child can't override a parent - this isn't textbook inheritance\n\n        # Run the same test on GooeyParser\n        base_parser = GooeyParser(add_help=False)\n        action1 = base_parser.add_argument(\"a_file\", widget=\"FileChooser\", default=\"a\")\n\n        parser = GooeyParser(parents=[base_parser])\n        action2 = parser.add_argument(\"a_file\", widget=\"DirChooser\", default=\"b\")\n\n        self._verify_duplicate_parameters(action1, action2, parser)\n        self.assertEqual(parser.widgets[\"a_file\"], \"FileChooser\")\n\n    def test_duplicates_on_same_parser_are_ignored(self):\n        \"\"\"\n        Verify that adding duplicate named arguments works the same in argparse and Gooey.\n        Assuming the behaviour of the \"default\" parameter is a good match for the \"widget\" parameter.\n        \"\"\"\n\n        # Verify how vanilla argparse works\n        parser = argparse.ArgumentParser()\n        action1 = parser.add_argument(\"a_file\", default=\"a\")\n        action2 = parser.add_argument(\"a_file\", default=\"b\")\n\n        self._verify_duplicate_parameters(action1, action2, parser)\n\n        # Run the same test on GooeyParser\n        parser = GooeyParser()\n        action1 = parser.add_argument(\"a_file\", default=\"a\", widget=\"FileChooser\")\n        action2 = parser.add_argument(\"a_file\", default=\"b\", widget=\"DirChooser\")\n\n        self._verify_duplicate_parameters(action1, action2, parser)\n        self.assertEqual(parser.widgets[\"a_file\"], \"FileChooser\")\n\n    def _verify_duplicate_parameters(self, action1, action2, parser):\n        \"\"\"\n        Verify two parameters named a_file exist and the default value is \"a\".\n        \"\"\"\n        found = 0\n        for action in parser._actions:\n            if action.dest == \"a_file\":\n                found += 1\n        self.assertEqual(2, found, \"Expected a both actions handling a_file but got \" + str(found))\n        self.assertEqual(parser.get_default(\"a_file\"), \"a\")\n        self.assertNotEqual(action1, action2)"
  },
  {
    "path": "gooey/tests/test_password.py",
    "content": "import unittest\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\nclass TestPasswordField(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument('--widget', widget=\"PasswordField\", **kwargs)\n        return parser\n\n\n    def testPlaceholder(self):\n        cases = [\n            [{}, ''],\n            [{'placeholder': 'Hello'}, 'Hello']\n        ]\n        for options, expected in cases:\n            parser = self.makeParser(gooey_options=options)\n            with instrumentGooey(parser) as (app, frame, gapp):\n                # because of how poorly designed the Gooey widgets are\n                # we have to reach down 3 levels in order to find the\n                # actual WX object we need to test.\n                widget = gapp.getActiveConfig().reifiedWidgets[0].widget\n                self.assertEqual(widget.widget.GetHint(), expected)\n\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_radiogroup.py",
    "content": "import unittest\n\nfrom gooey import GooeyParser\nfrom gooey.tests import *\nfrom tests.harness import instrumentGooey\n\n\nclass TestRadioGroupBehavior(unittest.TestCase):\n\n\n    def mutext_group(self, options):\n        \"\"\"\n        Basic radio group consisting of two options.\n        \"\"\"\n        parser = GooeyParser()\n        group = parser.add_mutually_exclusive_group(**options)\n        group.add_argument(\"-b\", type=str)\n        group.add_argument(\"-d\", type=str, widget=\"DateChooser\")\n        return parser\n\n\n    def test_initial_selection_options(self):\n        \"\"\"\n        Ensure that the initial_selection GooeyOption behaves as expected.\n        \"\"\"\n        # each pair in the below datastructure represents input/output\n        # First position: kwargs which will be supplied to the parser\n        # Second position: expected indices which buttons/widgets should be enabled/disabled\n        testCases = [\n            [{'required': True, 'gooey_options': {}},\n             {'selected': None, 'enabled': [], 'disabled': [0, 1]}],\n\n            # Issue #517 - initial section with required=True was not enabling\n            # the inner widget\n            [{'required': True, 'gooey_options': {\"initial_selection\": 0}},\n             {'selected': 0, 'enabled': [0], 'disabled': [1]}],\n\n            [{'required': True, 'gooey_options': {\"initial_selection\": 1}},\n             {'selected': 1, 'enabled': [1], 'disabled': [0]}],\n\n            [{'required': False, 'gooey_options': {}},\n             {'selected': None, 'enabled': [], 'disabled': [0, 1]}],\n\n            [{'required': False, 'gooey_options': {\"initial_selection\": 0}},\n             {'selected': 0, 'enabled': [0], 'disabled': [1]}],\n\n            [{'required': False, 'gooey_options': {\"initial_selection\": 1}},\n             {'selected': 1, 'enabled': [1], 'disabled': [0]}],\n        ]\n        for options, expected in testCases:\n            parser = self.mutext_group(options)\n            with self.subTest(options):\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    radioGroup = gapp.getActiveConfig().reifiedWidgets[0]\n\n                    # verify that the checkboxes themselves are correct\n                    if expected['selected'] is not None:\n                        self.assertEqual(\n                            radioGroup.selected,\n                            radioGroup.radioButtons[expected['selected']])\n                    else:\n                        self.assertEqual(radioGroup.selected, None)\n\n                    # verify the widgets contained in the radio group\n                    # are in the correct state\n                    for enabled in expected['enabled']:\n                        # The widget contained within the group should be enabled\n                        self.assertTrue(radioGroup.widgets[enabled].IsEnabled())\n\n                    # make sure all widgets other than the selected\n                    # are disabled\n                    for enabled in expected['disabled']:\n                        self.assertFalse(radioGroup.widgets[enabled].IsEnabled())\n\n\n    def test_optional_radiogroup_click_behavior(self):\n        \"\"\"\n        Testing that select/deselect behaves as expected\n        \"\"\"\n        testcases = [\n            self.click_scenarios_optional_widget(),\n            self.click_scenarios_required_widget(),\n            self.click_scenarios_initial_selection()\n        ]\n\n        for testcase in testcases:\n            with self.subTest(testcase['name']):\n                # wire up the parse with our test case options\n                parser = self.mutext_group(testcase['input'])\n\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    radioGroup = gapp.getActiveConfig().reifiedWidgets[0]\n\n                    for scenario in testcase['scenario']:\n                        targetButton = scenario['clickButton']\n\n                        event = wx.CommandEvent(wx.wxEVT_LEFT_DOWN, wx.Window.NewControlId())\n                        event.SetEventObject(radioGroup.radioButtons[targetButton])\n\n                        radioGroup.radioButtons[targetButton].ProcessEvent(event)\n\n                        expectedEnabled, expectedDisabled = scenario['postState']\n\n                        for index in expectedEnabled:\n                            self.assertEqual(radioGroup.selected, radioGroup.radioButtons[index])\n                            self.assertTrue(radioGroup.widgets[index].IsEnabled())\n\n                        for index in expectedDisabled:\n                            self.assertNotEqual(radioGroup.selected, radioGroup.radioButtons[index])\n                            self.assertFalse(radioGroup.widgets[index].IsEnabled())\n\n\n    def click_scenarios_optional_widget(self):\n        return {\n            'name': 'click_scenarios_optional_widget',\n            'input': {'required': False},\n            'scenario': [\n                # clicking enabled the button\n                {'clickButton': 0,\n                 'postState': [[0], [1]]},\n\n                # clicking again disables the button (*when not required*)\n                {'clickButton': 0,\n                 'postState': [[], [0, 1]]},\n\n                # clicking group 2 enabled it\n                {'clickButton': 1,\n                 'postState': [[1], [0]]},\n\n                # and similarly clicking group 2 again disables it\n                {'clickButton': 1,\n                 'postState': [[], [0, 1]]},\n\n                # enable second group\n                {'clickButton': 1,\n                 'postState': [[1], [0]]},\n\n                # can switch to group one\n                {'clickButton': 0,\n                 'postState': [[0], [1]]},\n            ]\n        }\n\n    def click_scenarios_required_widget(self):\n        return {\n            'name': 'click_scenarios_required_widget',\n            'input': {'required': True},\n            'scenario': [\n                # clicking enables the button\n                {'clickButton': 0,\n                 'postState': [[0], [1]]},\n\n                # unlike the the optional case, this\n                # has no effect. You cannot _not_ select something\n                # when it is required.\n                {'clickButton': 0,\n                 'postState': [[0], [1]]},\n\n                # we can select a different button\n                {'clickButton': 1,\n                 'postState': [[1], [0]]},\n\n                # again, if we click it again, we cannot deselect it\n                {'clickButton': 1,\n                 'postState': [[1], [0]]},\n\n                # we can click back to the other group\n                {'clickButton': 0,\n                 'postState': [[0], [1]]},\n            ]}\n\n    def click_scenarios_initial_selection(self):\n        return {\n            'name': 'click_scenarios_initial_selection',\n            'input': {'required': False, 'gooey_options': {'initial_selection': 0}},\n            'scenario': [\n                # we start already selected via GooeyOptions. As such,\n                # clicking on the radiobutton should deselect it\n                {'clickButton': 0,\n                 'postState': [[], [0, 1]]},\n                # clicking again reselected it\n                {'clickButton': 0,\n                 'postState': [[0], [1]]},\n            ]}\n\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_slider.py",
    "content": "import unittest\nfrom unittest.mock import patch\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\nclass TestGooeySlider(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument('--slider', widget=\"Slider\", **kwargs)\n        return parser\n\n\n    def testSliderDefault(self):\n        cases = [\n            [{}, 0],\n            [{'default': 0}, 0],\n            [{'default': 10}, 10],\n            [{'default': 76}, 76],\n            # note that WX caps the value\n            # unless explicitly widened via gooey_options\n            [{'default': 81234}, 100],\n            # here we set the max to something higher than\n            # the default and all works as expected.\n            # this is a TODO for validation\n            [{'default': 81234, 'gooey_options': {'max': 99999}}, 81234],\n\n            # Initial Value cases\n            [{}, 0],\n            [{'gooey_options': {'initial_value': 0}}, 0],\n            [{'gooey_options': {'initial_value': 10}}, 10],\n            [{'gooey_options': {'initial_value': 76}}, 76],\n            # note that WX caps the value\n            # unless explicitly widened via gooey_options\n            [{'gooey_options': {'initial_value': 81234}}, 100],\n            # here we set the max to something higher than\n            # the default and all works as expected.\n            # this is a TODO for validation\n            [{'gooey_options': {'initial_value': 81234, 'max': 99999}}, 81234],\n        ]\n        for inputs, expected in cases:\n            with self.subTest(inputs):\n                parser = self.makeParser(**inputs)\n                with instrumentGooey(parser) as (app, frame, gapp):\n                    slider = gapp.getActiveConfig().reifiedWidgets[0]\n                    self.assertEqual(slider.getValue()['rawValue'], expected)\n\n    def testZerosAreReturned(self):\n        \"\"\"\n        Originally the formatter was dropping '0' due to\n        it being interpreted as falsey\n        \"\"\"\n        parser = self.makeParser()\n        with instrumentGooey(parser) as (app, frame, gapp):\n            field = gapp.getActiveConfig().reifiedWidgets[0]\n            result = field.getValue()\n            self.assertEqual(result['rawValue'], 0)\n            self.assertIsNotNone(result['cmd'])\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_textarea.py",
    "content": "import unittest\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\nclass TestTextarea(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument('--widget', widget=\"Textarea\", **kwargs)\n        return parser\n\n\n    def testPlaceholder(self):\n        cases = [\n            [{}, ''],\n            [{'placeholder': 'Hello'}, 'Hello']\n        ]\n        for options, expected in cases:\n            parser = self.makeParser(gooey_options=options)\n            with instrumentGooey(parser) as (app, frame, gapp):\n                # because of how poorly designed the Gooey widgets are\n                # we have to reach down 3 levels in order to find the\n                # actual WX object we need to test.\n                widget = gapp.getActiveConfig().reifiedWidgets[0]\n                self.assertEqual(widget.widget.GetHint(), expected)\n\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_textfield.py",
    "content": "import unittest\nfrom collections import namedtuple\n\nfrom tests.harness import instrumentGooey\nfrom gooey import GooeyParser\nfrom gooey.tests import *\n\nCase = namedtuple('Case', 'inputs initialExpected expectedAfterClearing')\n\nclass TestTextField(unittest.TestCase):\n\n    def makeParser(self, **kwargs):\n        parser = GooeyParser(description='description')\n        parser.add_argument('--widget', widget=\"TextField\", **kwargs)\n        return parser\n\n\n    def testPlaceholder(self):\n        cases = [\n            [{}, ''],\n            [{'placeholder': 'Hello'}, 'Hello']\n        ]\n        for options, expected in cases:\n            parser = self.makeParser(gooey_options=options)\n            with instrumentGooey(parser) as (app, frame, gapp):\n                # because of how poorly designed the Gooey widgets are\n                # we have to reach down 3 levels in order to find the\n                # actual WX object we need to test.\n                widget = gapp.getActiveConfig().reifiedWidgets[0].widget\n                self.assertEqual(widget.widget.GetHint(), expected)\n\n\n\n\n    def testDefaultAndInitialValue(self):\n        cases = [\n            # initial_value takes precedence when both are present\n            Case(\n                {'default': 'default_val', 'gooey_options': {'initial_value': 'some val'}},\n                'some val',\n                None),\n            # when no default is present\n            # Case({'gooey_options': {'initial_value': 'some val'}},\n            #  'some val',\n            #  ''),\n            # [{'default': 'default', 'gooey_options': {}},\n            #  'default'],\n            # [{'default': 'default'},\n            #  'default'],\n        ]\n        for case in cases:\n            parser = self.makeParser(**case.inputs)\n            with instrumentGooey(parser) as (app, frame, gapp):\n                widget = gapp.getActiveConfig().reifiedWidgets[0]\n                self.assertEqual(widget.getValue()['rawValue'], case.initialExpected)\n                widget.setValue('')\n                print(widget.getValue())\n                self.assertEqual(widget.getValue()['cmd'], case.expectedAfterClearing)\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_time_remaining.py",
    "content": "import time\nimport unittest\nfrom argparse import ArgumentParser\nfrom itertools import *\nfrom gooey.gui import state as s\nfrom tests.harness import instrumentGooey\n\n\nfrom gooey.tests import *\nfrom gooey.util.functional import identity\n\n\nclass TestFooterTimeRemaining(unittest.TestCase):\n\n    def make_parser(self):\n        parser = ArgumentParser(description='description')\n        return parser\n\n    def test_time_remaining_visibility(self):\n        for testdata in self.testcases():\n            with self.subTest(testdata):\n                with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, frame, gapp):\n\n                    gapp.set_state(s.consoleScreen(identity, gapp.state))\n                    app: wx.App = app\n                    wx.CallLater(1, app.ExitMainLoop)\n                    app.MainLoop()\n\n                    self.assertEqual(\n                        frame.FindWindowByName('timing').Shown,\n                        testdata.get('show_time_remaining',False)\n                    )\n\n    def test_time_remaining_visibility_on_complete(self):\n        for testdata in self.testcases():\n            with self.subTest(testdata):\n                with instrumentGooey(self.make_parser(), timing_options=testdata) as (app, frame, gapp):\n\n                    gapp.set_state(s.successScreen(identity, gapp.state))\n                    app: wx.App = app\n                    wx.CallLater(1, app.ExitMainLoop)\n                    app.MainLoop()\n\n\n                    if not testdata.get('show_time_remaining') and testdata:\n                        self.assertEqual(\n                            frame.FindWindowByName('timing').Shown,\n                            testdata.get('hide_time_remaining_on_complete',True)\n                        )\n                    else:\n                        return True\n\n    def testcases(self):\n        \"\"\"\n        Generate a powerset of all possible combinations of\n        the header parameters (empty, some present, all present, all combos)\n        \"\"\"\n        iterable = product(['show_time_remaining', 'hide_time_remaining_on_complete'], [True, False])\n        allCombinations = list(powerset(iterable))\n        return [{k: v for k,v in args}\n                for args in allCombinations]\n\n\ndef powerset(iterable):\n    \"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)\"\n    s = list(iterable)\n    return chain.from_iterable(combinations(s, r) for r in range(len(s)+1))\n\n\nif __name__ == '__main__':\n    unittest.main()"
  },
  {
    "path": "gooey/tests/test_util.py",
    "content": "import re\nimport unittest\n\nfrom gooey.tests import *\n\nfrom gooey.gui.util.time import get_current_time,get_elapsed_time,estimate_time_remaining,format_interval\n\n\nclass TestTimeUtil(unittest.TestCase):\n    def test_time_elapsed(self):\n        # Check that time elapsed is greater than zero\n        _start_time = get_current_time()\n        elapsed = get_elapsed_time(_start_time)\n        self.assertGreater(elapsed,0)\n\n    def test_time_remaining(self):\n        # Check that time elapsed is greater than zero\n        _start_time = get_current_time()\n        remaining = estimate_time_remaining(30,_start_time)\n        self.assertGreater(remaining,0)\n\n    def test_current_time(self):\n        # Test that current time is greater than zero\n        _start_time = get_current_time()\n        self.assertGreater(_start_time,0)\n\n    \n    def test_format_interval(self):\n        # Test same as TQDM https://github.com/tqdm/tqdm/blob/0cd9448b2bc08125e74538a2aea6af42ee1a7b6f/tqdm/tests/tests_tqdm.py#L234\n        # but in unittest form\n\n        self.assertEqual(format_interval(60), '01:00')\n        self.assertEqual(format_interval(6160), '1:42:40')\n        self.assertEqual(format_interval(238113), '66:08:33')\n"
  },
  {
    "path": "gooey/tests/tmmmmp.py",
    "content": "from gooey import GooeyParser, Gooey\n\n\n\ndef main():\n    parser = GooeyParser()\n    subs = parser.add_subparsers()\n    foo = subs.add_parser('foo')\n    foo.add_argument('a')\n    foo.add_argument('b')\n    foo.add_argument('p')\n\n    bar = subs.add_parser('bar')\n    bar.add_argument('a')\n    bar.add_argument('b')\n    bar.add_argument('z')\n    parser.parse_args(['foo'])\n\n\nmain()"
  },
  {
    "path": "gooey/tests/tmp.txt",
    "content": "['C:\\\\Users\\\\Chris\\\\Documents\\\\Gooey\\\\gooey\\\\tests\\\\dynamics\\\\files\\\\basic.py', '--ignore-gooey', '--', '10']"
  },
  {
    "path": "gooey/util/__init__.py",
    "content": ""
  },
  {
    "path": "gooey/util/functional.py",
    "content": "\"\"\"\r\nA collection of functional utilities/helpers\r\n\"\"\"\r\nfrom functools import reduce, wraps\r\nfrom copy import deepcopy\r\nfrom itertools import chain, dropwhile\r\nfrom typing import Tuple, Any, List, Union\r\n\r\nfrom gooey.python_bindings.types import Try, Success, Failure\r\n\r\n\r\ndef getin(m, path, default=None):\r\n    \"\"\"returns the value in a nested dict\"\"\"\r\n    keynotfound = ':com.gooey-project/not-found'\r\n    result = reduce(lambda acc, val: acc.get(val, {keynotfound: None}), path, m)\r\n    # falsey values like 0 would incorrectly trigger the default to be returned\r\n    # so the keynotfound val is used to signify a miss vs just a falesy val\r\n    if isinstance(result, dict) and keynotfound in result:\r\n        return default\r\n    return result\r\n\r\n\r\ndef assoc(m, key, val):\r\n    \"\"\"Copy-on-write associates a value in a dict\"\"\"\r\n    cpy = deepcopy(m)\r\n    cpy[key] = val\r\n    return cpy\r\n\r\ndef dissoc(m, key, val):\r\n    cpy = deepcopy(m)\r\n    del cpy[key]\r\n    return cpy\r\n\r\ndef associn(m, path, value):\r\n    \"\"\" Copy-on-write associates a value in a nested dict \"\"\"\r\n    def assoc_recursively(m, path, value):\r\n        if not path:\r\n            return value\r\n        p = path[0]\r\n        return assoc(m, p, assoc_recursively(m.get(p,{}), path[1:], value))\r\n    return assoc_recursively(m, path, value)\r\n\r\n\r\ndef associnMany(m, *args: Tuple[Union[str, List[str]], Any]):\r\n    def apply(_m, change: Tuple[Union[str, List[str]], Any]):\r\n        path, value = change\r\n        if isinstance(path, list):\r\n            return associn(_m, path, value)\r\n        else:\r\n            return associn(_m, path.split('.'), value)\r\n    return reduce(apply, args, m)\r\n\r\n\r\n\r\ndef merge(*maps):\r\n    \"\"\"Merge all maps left to right\"\"\"\r\n    copies = map(deepcopy, maps)\r\n    return reduce(lambda acc, val: acc.update(val) or acc, copies)\r\n\r\n\r\ndef flatmap(f, coll):\r\n    \"\"\"Applies concat to the result of applying f to colls\"\"\"\r\n    return list(chain(*map(f, coll)))\r\n\r\n\r\ndef indexunique(f, coll):\r\n    \"\"\"Build a map from the collection keyed off of f\r\n    e.g.\r\n        [{id:1,..}, {id:2, ...}] => {1: {id:1,...}, 2: {id:2,...}}\r\n\r\n    Note: duplicates, if present, are overwritten\r\n    \"\"\"\r\n    return zipmap(map(f, coll), coll)\r\n\r\n\r\ndef zipmap(keys, vals):\r\n    \"\"\"Return a map from keys to values\"\"\"\r\n    return dict(zip(keys, vals))\r\n\r\n\r\ndef compact(coll):\r\n    \"\"\"Returns a new list with all falsy values removed\"\"\"\r\n    if isinstance(coll, dict):\r\n        return {k:v for k,v in coll.items() if v is not None}\r\n    else:\r\n        return list(filter(None, coll))\r\n\r\n\r\ndef ifPresent(f):\r\n    \"\"\"Execute f only if value is present and not None\"\"\"\r\n    def inner(value):\r\n        if value:\r\n            return f(value)\r\n        else:\r\n            return True\r\n    return inner\r\n\r\n\r\ndef identity(x):\r\n    \"\"\"Identity function always returns the supplied argument\"\"\"\r\n    return x\r\n\r\n\r\ndef unit(val):\r\n    return val\r\n\r\n\r\ndef bind(val, f):\r\n    return f(val) if val else None\r\n\r\n\r\ndef lift(f):\r\n    @wraps(f)\r\n    def inner(x) -> Try:\r\n        try:\r\n            return Success(f(x))\r\n        except Exception as e:\r\n            return Failure(e)\r\n    return inner\r\n\r\n"
  },
  {
    "path": "pip_deploy.py",
    "content": "import subprocess\r\n\r\nsubprocess.call('python setup.py sdist')\r\nsubprocess.call('python setup.py bdist_wheel --universal')\r\nsubprocess.call('twine upload dist/*')\r\n"
  },
  {
    "path": "requirements.txt",
    "content": "wxpython>=4.1.0\r\nPillow>=4.3.0\r\npsutil>=5.4.2\r\ncolored>=1.3.93\r\npygtrie>=2.3.3\r\nre-wx>=0.0.2\r\ntyping-extensions==3.10.0.2\r\nmypy-extensions==0.4.3\r\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"Script for setuptools.\"\"\"\r\nimport sys\r\nfrom setuptools import setup, find_packages\r\n\r\n\r\nwith open('README.md') as readme:\r\n    long_description = readme.read()\r\n\r\nversion = '1.2.0-ALPHA'\r\n\r\ndeps = [\r\n    'Pillow>=4.3.0',\r\n    'psutil>=5.4.2',\r\n    'colored>=1.3.93',\r\n    'pygtrie>=2.3.3',\r\n    're-wx>=0.0.9',\r\n    'typing-extensions==3.10.0.2',\r\n    'wxpython>=4.1.0',\r\n    \"dataclasses>=0.8;python_version<'3.7'\",\r\n]\r\n\r\nsetup(\r\n    name='Gooey',\r\n    version=version,\r\n    url='http://pypi.python.org/pypi/Gooey/',\r\n    author='Chris Kiehl',\r\n    author_email='audionautic@gmail.com',\r\n    description=('Turn (almost) any command line program into a full GUI '\r\n                 'application with one line'),\r\n    license='MIT',\r\n    python_requires='>=3.6',\r\n    packages=find_packages(),\r\n    install_requires=deps,\r\n    include_package_data=True,\r\n    dependency_links = [\"http://www.wxpython.org/download.php\"],\r\n    classifiers = [\r\n        'Development Status :: 4 - Beta',\r\n        'Intended Audience :: Developers',\r\n        'Topic :: Desktop Environment',\r\n        'Topic :: Software Development :: Build Tools',\r\n        'Topic :: Software Development :: Widget Sets',\r\n        'Programming Language :: Python :: 3',\r\n        'License :: OSI Approved :: MIT License'\r\n    ],\r\n    long_description='''\r\n\r\nGooey (Beta)\r\n############\r\n\r\n\r\nTurn (almost) any Python Console Program into a GUI application with one line\r\n-----------------------------------------------------------------------------\r\n\r\n.. image:: https://cloud.githubusercontent.com/assets/1408720/7904381/f54f97f6-07c5-11e5-9bcb-c3c102920769.png\r\n\r\n\r\nQuick Start\r\n-----------\r\n\r\nGooey is attached to your code via a simple decorator on your `main` method.\r\n\r\n.. code-block::\r\n\r\n  from gooey import Gooey\r\n  @Gooey      <--- all it takes! :)\r\n  def main():\r\n      # rest of code\r\n\r\n\r\n\r\nWith the decorator attached, run your program and the GUI will now appear!\r\n\r\nCheckout the full documentation, instructions, and source on `Github <https://github.com/chriskiehl/Gooey>`_'''\r\n)\r\n"
  }
]