├── .dictionary ├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.md ├── docs ├── src │ └── markdown │ │ ├── _snippets │ │ ├── abbr.md │ │ ├── links.md │ │ └── refs.md │ │ ├── changelog.md │ │ ├── contributing.md │ │ ├── images │ │ ├── CSE_NIX.png │ │ ├── CSE_OSX.png │ │ ├── CSE_WIN.png │ │ ├── edit_global_color.png │ │ ├── edit_scope_color.png │ │ ├── global_settings.png │ │ └── scope_settings.png │ │ ├── index.md │ │ ├── installation.md │ │ ├── license.md │ │ └── usage.md └── theme │ ├── extra-2aceccb07f.js │ ├── extra-9b4c72abee.css │ └── partials │ └── footer.html ├── gui.fbp ├── mkdocs.yml ├── requirements ├── docs.txt ├── lint.txt ├── project.txt └── test.txt ├── setup.cfg ├── setup.py ├── subclrschm ├── __init__.py ├── __main__.py └── lib │ ├── __init__.py │ ├── __meta__.py │ ├── __version__.py │ ├── default_new_theme.py │ ├── gui │ ├── __init__.py │ ├── about_dialog.py │ ├── basic_dialogs.py │ ├── color_setting_dialog.py │ ├── custom_app.py │ ├── custom_statusbar.py │ ├── data │ │ ├── __init__.py │ │ ├── floppy.png │ │ ├── subclrschm.icns │ │ ├── subclrschm.ico │ │ ├── subclrschm.png │ │ ├── subclrschm_dialog.png │ │ ├── subclrschm_hires.png │ │ └── subclrschm_large.png │ ├── global_setting_dialog.py │ ├── global_settings_panel.py │ ├── grid_helper.py │ ├── gui.py │ ├── messages.py │ ├── platform_window_focus.py │ ├── settings_codes.py │ ├── settings_key_bindings.py │ ├── simplelog.py │ ├── style_settings_panel.py │ └── subclrschm_app.py │ ├── parse_args.py │ ├── rgba.py │ ├── util.py │ └── x11colors.py ├── tests └── spellcheck.py ├── tools └── wxpy4_patch.py └── tox.ini /.dictionary: -------------------------------------------------------------------------------- 1 | Changelog 2 | checkbox 3 | checkboxes 4 | CSS 5 | dev 6 | dialogs 7 | distros 8 | emoji 9 | EmojiOne 10 | GitHub 11 | Homebrew 12 | JSON 13 | macOS 14 | MERCHANTABILITY 15 | MkDocs 16 | MkDocs 17 | multi 18 | NONINFRINGEMENT 19 | popup 20 | PyMdown 21 | requesters 22 | subclrschm 23 | SubclrSshm 24 | sublicense 25 | symlink 26 | TextMate 27 | Tox 28 | UUID 29 | UUIDs 30 | wiki 31 | wxPython 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | pyInstaller/* 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | *.DS_Store 59 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: python 3 | matrix: 4 | include: 5 | - python: 2.7 6 | env: TOXENV=lint 7 | - python: 2.7 8 | env: TOXENV=documents 9 | install: 10 | - pip install "flake8>=2,<3" 11 | - pip install flake8_docstrings 12 | - pip install flake8-putty 13 | - pip install pep8-naming 14 | addons: 15 | apt: 16 | packages: 17 | - aspell 18 | - aspell-en 19 | install: 20 | - pip install tox 21 | script: 22 | - tox 23 | deploy: 24 | - provider: pypi 25 | user: facelessuser 26 | server: https://upload.pypi.org/legacy/ 27 | password: 28 | secure: L9Chmt56K0LOLpg3Y3ak0lpLHnJur+NQJcWt8HRNkaCmixzfRrVvFl4OotkdCNu24cEtjjuqZ7dg8XPPRRZJMN8D4VsbQ0CIZRqQBSEc5Sisp5NfQuZTVpuKoJZ3UNiZ4Plj6CQgmZD2YOz8f9Monpfg8qWOQ6D95x0FyOYFRB1xR68vVCwj/+Ezn78u9P3/JSh9cskCCL7+V0v7V1tXzHjzEJ29xPoe+1OBP4PAdo0pqEvhnPMsodODiqKYE9SzUwh7dnNXKARPGvbHYz+rJOwGstXbVkO9vm8nhDuox3RILmonGlYuE/s+kq3lWTDf3NYIGN2FjzX16cVADfiiUSnVehxqbh/ONJilpLLmUrJy+USfjGH+dbOi4xj9HmXMOQe8f/9UewbRO/Ybxx81q0sb5lAufOJxU6MUMN/1cgslf9jgneu25qRBGX/NUJndq0/yNftYj05np8ZYQMTxid0U1e4Q+JmaRBeenWt0Ns7pKeLTK3cFTz2xRgNYtJb0d5NqnJ8GAepSmGxXdghDnHUwf1JOdq1OT/xbSl20/57+qf3lbKiKKeXEr5DICpHmz53aAyXE02hjwnPEcW8noVlLPD01/ghXZbuSPENn3pfOexTClbLc9di9PjBDNUQe8Y53nL+1ZJ4n3wefSOCDPjV5Hyp2NdljiTjoBgLz8r4= 29 | distributions: sdist bdist_wheel 30 | skip_upload_docs: true 31 | on: 32 | tags: true 33 | repo: facelessuser/subclrschm 34 | condition: $TOXENV = documents 35 | - provider: pages 36 | github_token: $GITHUB_TOKEN 37 | name: $GITHUB_USER 38 | email: $GITHUB_EMAIL 39 | skip_cleanup: true 40 | local_dir: site 41 | on: 42 | tags: true 43 | repo: facelessuser/subclrschm 44 | condition: $TOXENV = documents 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 2017 Isaac Muse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include subclrschm *.py 2 | recursive-include docs *.md *.png *.gif *.js *.css *.html 3 | recursive-include subclrschm/lib/gui/data *.png *.icns *.ico *.js *.css 4 | recursive-include tests *.py 5 | recursive-include requirements *.txt 6 | recursive-include tools *.py 7 | include .dictionary 8 | include setup.py 9 | include setup.cfg 10 | include tox.ini 11 | include gui.fbp 12 | include mkdocs.yml 13 | include LICENSE 14 | include README.md 15 | include MANIFEST.in 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Unix Build Status][travis-image]][travis-link] 2 | [![pypi-version][pypi-image]][pypi-link] 3 | ![License][license-image] 4 | # Sublime Color Scheme Editor 5 | 6 | Sublime Color Scheme Editor for Sublime Text. This is the GUI source code. 7 | 8 | ## Supported Platforms 9 | 10 | Windows 11 | 12 | ![Windows](./docs/src/markdown/images/CSE_WIN.png) 13 | 14 | macOS 15 | 16 | ![macOS](./docs/src/markdown/images/CSE_OSX.png) 17 | 18 | Linux 19 | 20 | ![Ubuntu](./docs/src/markdown/images/CSE_NIX.png) 21 | 22 | ## Documentation 23 | 24 | http://facelessuser.github.io/subclrschm/ 25 | 26 | ## License 27 | 28 | ColorSchemeEditor plugin is released under the MIT license. 29 | 30 | Copyright (c) 2013 - 2017 Isaac Muse 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 33 | 34 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 37 | 38 | [travis-image]: https://img.shields.io/travis/facelessuser/subclrschm.svg 39 | [travis-link]: https://travis-ci.org/facelessuser/subclrschm 40 | [pypi-image]: https://img.shields.io/pypi/v/subclrschm.svg 41 | [pypi-link]: https://pypi.python.org/pypi/subclrschm 42 | [license-image]: https://img.shields.io/badge/license-MIT-blue.svg 43 | -------------------------------------------------------------------------------- /docs/src/markdown/_snippets/abbr.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/docs/src/markdown/_snippets/abbr.md -------------------------------------------------------------------------------- /docs/src/markdown/_snippets/links.md: -------------------------------------------------------------------------------- 1 | [mkdocs]: https://github.com/mkdocs/mkdocs 2 | [mkdocs-material]: https://github.com/squidfunk/mkdocs-material 3 | [pymdown-extensions]: https://github.com/facelessuser/pymdown-extensions 4 | [wxpython]: https://pypi.python.org/pypi/wxPython/ 5 | -------------------------------------------------------------------------------- /docs/src/markdown/_snippets/refs.md: -------------------------------------------------------------------------------- 1 | --8<-- 2 | links.md 3 | abbr.md 4 | --8<-- 5 | -------------------------------------------------------------------------------- /docs/src/markdown/changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.1.3 4 | 5 | Aug 18, 2017 6 | 7 | - **FIX**: Invalid `stdout` and/or `stderr` on Windows when using `pythonw`. 8 | 9 | ## 2.1.2 10 | 11 | Jul 27, 2017 12 | 13 | - **FIX**: Properly mix transparent, global settings with the right background after editing the global background. 14 | 15 | ## 2.1.1 16 | 17 | Jul 26, 2017 18 | 19 | - **FIX**: `wxPython` 4.0.0b1 removed label parameter from constructor. 20 | 21 | ## 2.1.0 22 | 23 | Jul 26, 2017 24 | 25 | - **NEW**: Add menu entry to create a new color scheme. 26 | - **NEW**: Show visual indicator when unsaved changes are present. 27 | - **NEW**: Apply name and UUID if enter is pressed in the respective text box. 28 | - **NEW**: `phantomCss` and `popupCss` are no longer treated special but like an ordinary text entry. Text entries have a separate box that can optionally contain multi-line data or single line data. 29 | - **NEW**: Slight redesign of edit dialogs. 30 | - **NEW**: When in single instance mode, pipe arguments to existing instance. 31 | - **NEW**: Minor tweaks to GUI. 32 | - **FIX**: Ensure a new live thread is started when switching files. 33 | - **FIX**: Fix issue where event isn't passed into UUID check. 34 | - **FIX**: When opening new file while another file is open, don't clean up current file until after the new file has been selected and successfully parsed. 35 | 36 | ## 2.0.2 37 | 38 | Jul 23, 2017 39 | 40 | - **FIX**: `UUID` should be optional. 41 | 42 | ## 2.0.1 43 | 44 | Jul 23, 2017 45 | 46 | - **FIX**: Include images as data in `setup.py` when installing. 47 | 48 | ## 2.0.0 49 | 50 | Jul 23, 2017 51 | 52 | - **NEW**: Add support for X11 color names. Convert them to hex on color scheme load. 53 | - **NEW**: Handle `popupCss` and `phantomCss`. Inject them if they are missing. 54 | - **NEW**: Require `wxPython` 4+ and rework code to use it and support Python 2.7 and 3.4+. 55 | - **NEW**: Remove importing and exporting of JSON color schemes. 56 | - **FIX**: Multiple Ubuntu dock icons (possibly similar issue in other Linux distros). 57 | 58 | ## 1.0.0 59 | 60 | - **NEW**: Initial release. 61 | -------------------------------------------------------------------------------- /docs/src/markdown/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing & Support 2 | 3 | ## Overview 4 | 5 | Contribution from the community is encouraged and can be done in a variety of ways: 6 | 7 | - Bug reports. 8 | - Reviewing code. 9 | - Code patches via pull requests. 10 | - Documentation improvements via pull requests. 11 | 12 | ## Bug Reports 13 | 14 | 1. Please **read the documentation** and **search the issue tracker** to try to find the answer to your question **before** posting an issue. 15 | 16 | 2. When creating an issue on the repository, please provide as much info as possible: 17 | 18 | - Version being used. 19 | - Operating system. 20 | - Errors in console. 21 | - Detailed description of the problem. 22 | - Examples for reproducing the error. You can post pictures, but if specific text or code is required to reproduce the issue, please provide the text in a plain text format for easy copy/paste. 23 | 24 | The more info provided the greater the chance someone will take the time to answer, implement, or fix the issue. 25 | 26 | 3. Be prepared to answer questions and provide additional information if required. Issues in which the creator refuses to respond to follow up questions will be marked as stale and closed. 27 | 28 | ## Reviewing Code 29 | 30 | Take part in reviewing pull requests and/or reviewing direct commits. Make suggestions to improve the code and discuss solutions to overcome weaknesses in the algorithm. 31 | 32 | ## Pull Requests 33 | 34 | Pull requests are welcome, and if you plan on contributing directly to the code, there are a couple of things to be mindful of. 35 | 36 | Continuous integration tests are run on all pull requests and commits via Travis CI. When making a pull request, the tests will automatically be run, and the request must pass to be accepted. You can (and should) run these tests before pull requesting. If it is not possible to run these tests locally, they will be run when the pull request is made, but it is strongly suggested that requesters make an effort to verify before requesting to allow for a quick, smooth merge. 37 | 38 | ### Running Validation Tests 39 | 40 | 1. Make sure that Tox is installed: 41 | 42 | ``` 43 | pip install tox 44 | ``` 45 | 46 | 2. Run Tox: 47 | 48 | ``` 49 | tox 50 | ``` 51 | 52 | Tox should install necessary dependencies and run the tests. 53 | 54 | ## Documentation Improvements 55 | 56 | A ton of time has been spent not only creating and supporting this module, but also spent making this documentation. If you feel it is still lacking, show your appreciation for the module by helping to improve the documentation. Help with documentation is always appreciated and can be done via pull requests. There shouldn't be any need to run validation tests if only updating documentation. 57 | 58 | You don't have to render the docs locally before pull requesting, but if you wish to, I currently use a combination of [MkDocs][mkdocs], the [Material theme][mkdocs-material], and [PyMdown Extensions][pymdown-extensions] to render the docs. You can preview the docs if you install these packages. The command for previewing the docs is `mkdocs serve`. It should be run from the root directory. You can then view the documents at `localhost:8000`. 59 | 60 | --8<-- "links.md" 61 | -------------------------------------------------------------------------------- /docs/src/markdown/images/CSE_NIX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/docs/src/markdown/images/CSE_NIX.png -------------------------------------------------------------------------------- /docs/src/markdown/images/CSE_OSX.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/docs/src/markdown/images/CSE_OSX.png -------------------------------------------------------------------------------- /docs/src/markdown/images/CSE_WIN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/docs/src/markdown/images/CSE_WIN.png -------------------------------------------------------------------------------- /docs/src/markdown/images/edit_global_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/docs/src/markdown/images/edit_global_color.png -------------------------------------------------------------------------------- /docs/src/markdown/images/edit_scope_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/docs/src/markdown/images/edit_scope_color.png -------------------------------------------------------------------------------- /docs/src/markdown/images/global_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/docs/src/markdown/images/global_settings.png -------------------------------------------------------------------------------- /docs/src/markdown/images/scope_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/docs/src/markdown/images/scope_settings.png -------------------------------------------------------------------------------- /docs/src/markdown/index.md: -------------------------------------------------------------------------------- 1 | # Sublime Color Scheme Editor 2 | 3 | Sublime Color Scheme Editor (subclrschm) is a graphical Sublime Text color scheme editor. It is cross platform and runs in Windows, macOS, and Linux. 4 | 5 | Windows 6 | 7 | ![Windows](./images/CSE_WIN.png) 8 | 9 | macOS 10 | 11 | ![macOS](./images/CSE_OSX.png) 12 | 13 | Linux 14 | 15 | ![Ubuntu](./images/CSE_NIX.png) 16 | -------------------------------------------------------------------------------- /docs/src/markdown/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ## Requirements 4 | 5 | Subclrschm has a only one requirement when installing. 6 | 7 | Name | Details 8 | -------------------------------- | ------- 9 | [`wxPython`\ 4.0.0a3+][wxpython] | The new wxPython 4.0.0 is required for for Rummage to run in Python 2 and Python 3. Classic wxPython support has unfortunately be dropped. 10 | 11 | !!! warning "Linux Prerequisites" 12 | In traditional Linux fashion, there is a little extra work that needs to be done prior to installing. Linux requires some prerequisites so that it can build wxPython during installation. 13 | 14 | Example is for Ubuntu: 15 | 16 | ```bash 17 | sudo apt-get install dpkg-dev build-essential python2.7-dev libwebkitgtk-dev libjpeg-dev libtiff-dev libgtk2.0-dev libsdl1.2-dev libgstreamer-plugins-base0.10-dev libnotify-dev freeglut3 freeglut3-dev 18 | ``` 19 | 20 | Replace `python2.7-dev` with the Python version you are using. 21 | 22 | If your Linux distribution has `gstreamer` 1.0 available, you can install the dev packages for that instead of the 0.10 version. 23 | 24 | Be patient while installing on Linux as Linux must build wxPython while macOS and Windows do not. 25 | 26 | Check out the wxPython document to see if prerequisites have changed: https://github.com/wxWidgets/Phoenix/blob/master/README.rst#prerequisites. 27 | 28 | ## Installation 29 | 30 | Here are a couple of ways to install and upgrade. Keep in mind if you are a Linux user, you have some prerequisites to install before proceeding: see [Requirements](#requirements). 31 | 32 | 1. Install: `#!bash python pip install subclrschm`. 33 | 34 | 2. To upgrade: `#!bash python install --upgrade subclrschm`. 35 | 36 | 3. If developing on subclrschm, you can clone the project, and install the requirements with the following command: 37 | 38 | ```bash 39 | pip install -r requirements/project.txt` 40 | ``` 41 | 42 | You can then run the command below. This method will allow you to instantly see your changes between iterations without reinstalling which is great for developing. If you want to do this in a virtual machine, you can as well. Like the first method, you should then be able to access subclrschm from the command line via `rummage` or `rummage --path mydirectory`. 43 | 44 | ```bash 45 | pip install --editable . 46 | ``` 47 | 48 | You could also just optionally run the package locally, skipping the actual install of subclrschm. You can run the project by issuing the following command from the root folder: 49 | 50 | ``` 51 | python -m subclrschm 52 | ``` 53 | 54 | In general, you may find it more appropriate to use the `pythonw` command instead of `python`. In some environments, it may be required (see ["Running in Anaconda (macOS)"](#running-in-anaconda-macos)). 55 | 56 | ## Running in Virtual Environments (macOS) 57 | 58 | If installing in a virtual environment via `virtualenv`, you may run into the following error: 59 | 60 | 61 | This used to be a fairly annoying issue to workaround, but in wxPython 4+, it's not too bad. The wxPython wiki is a bit out of date. You don't have to symlink `wx.pth` or anything like that anymore as the design of wxPython is a bit different now. All you have to do is place the script below in `my_virtual_env/bin`. In this example I call it `fwpy` for "framework python" (make sure to adjust paths or Python versions to match your installation). 62 | 63 | ``` 64 | #!/bin/bash 65 | 66 | # what real Python executable to use 67 | PYVER=2.7 68 | PYTHON=/Library/Frameworks/Python.framework/Versions/$PYVER/bin/python$PYVER 69 | 70 | # find the root of the virtualenv, it should be the parent of the dir this script is in 71 | ENV=`$PYTHON -c "import os; print os.path.abspath(os.path.join(os.path.dirname(\"$0\"), '..'))"` 72 | echo $ENV 73 | 74 | # now run Python with the virtualenv set as Python's HOME 75 | export PYTHONHOME=$ENV 76 | exec $PYTHON "$@" 77 | ``` 78 | 79 | ## Running in Homebrew (macOS) 80 | 81 | Homebrew from what I read used to have issues running wxPython in versions less than 4, but this doesn't seem to be an issue with wxPython 4 with Homebrew (at least in my testing). 82 | 83 | ## Running in Anaconda (macOS) 84 | 85 | Anaconda can run Rummage fine from my testing. The important thing to note is you must launch it with `pythonw -m rummage` and **not** `python -m rummage`. 86 | 87 | --8<-- "links.md" 88 | -------------------------------------------------------------------------------- /docs/src/markdown/license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | MIT license. 4 | 5 | Copyright (c) 2013 - 2017 Isaac Muse 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /docs/src/markdown/usage.md: -------------------------------------------------------------------------------- 1 | # User Guide 2 | 3 | Subclrschm is a simple GUI for editing a Sublime color scheme file (`.tmTheme`). Simply point it at a `tmTheme` file and get editing. 4 | 5 | ## Command Line 6 | 7 | ``` 8 | Faceless-MacBook-Pro:subclrschm facelessuser$ python3 -m subclrschm --help 9 | usage: subclrschm [-h] [--version] [--multi-instance] [--log [LOG]] 10 | [--live_save] [--select | --new] 11 | [file] 12 | 13 | Sublime Color Scheme Editor - Edit Sublime Color Scheme 14 | 15 | positional arguments: 16 | file Theme file 17 | 18 | optional arguments: 19 | -h, --help show this help message and exit 20 | --version show program's version number and exit 21 | --multi-instance, -m Allow multiple instances 22 | --log [LOG], -l [LOG] 23 | Absolute path to directory to store log file 24 | --live-save, -L Enable live save. 25 | --select, -s Prompt for theme selection 26 | --new, -n Open prompting for new theme to create 27 | ``` 28 | 29 | Option | Description 30 | ---------------- | ----------- 31 | `file` | Optional positional argument to specify a `tmTheme` file to open. 32 | `multi-instance` | By default, subclrschm will only allow one instance of subclrschm, and if another instance is opened, it will send the arguments to the instance already open. You can allow multiple instances with this setting. Restarting the current instances may be required. 33 | `log` | Subclrschm will store a log with any errors and such in `~/.subclrschm`, `~/.config/subclrschm`, or `c:\Users\\.subclrschm` in macOS, Linux, and Windows respectively. You can redirect the log placement by pointing this setting at a folder. This is mainly used for integration in Sublime Text to save log to User folder. 34 | `live-save` | This saves any changes immediately to the color scheme file. This is mainly used in Sublime Text integration to provide live updates when editing the current, active color scheme. 35 | `select` | Instead of asking the user if they want to create a new color scheme or open an existing one, you can force it to immediately ask you to select an existing color scheme. This is used in Sublime Text integration. 36 | `new` | Instead of asking the user if they want to create a new color scheme or open an existing one, you can force it to create a new color scheme. This is used in Sublime Text integration. 37 | 38 | ## Opening/Creating New Color Schemes 39 | 40 | When running subclrschm, if a color scheme was not provided on the command line, it will prompt the user to either create a new color scheme or browse for an existing color scheme. You can also open a different color scheme at any time via the menu: `File->Open`. 41 | 42 | ## Editing 43 | 44 | When opening a color scheme, you will be prevented with a GUI containing boxes with the name of the color scheme and the current UUID. Here you can change the name or UUID (UUIDs are carried over from TextMate, but don't do anything in Sublime color schemes). 45 | 46 | Underneath the name and UUID boxes is a find box that allows you to search for names, scopes, and values in the color scheme. 47 | 48 | Lastly you see a tabbed interface that displays "Global Settings" and "Scope Settings". Here you can add a new setting, delete a setting, change the order of settings, change a setting's name, and change a setting's value. 49 | 50 | ### Global Settings 51 | 52 | ![Global Settings](./images/global_settings.png) 53 | 54 | The Global Settings tab contains all the general, global settings like background, foreground, gutter colors, popup CSS, etc. 55 | 56 | You can create new entries by selecting the `+` button or delete entries by selecting an entry and pressing the `-` button. You can edit an entry by double clicking it or pressing enter if you have one selected. 57 | 58 | You can navigate the entries with the arrow keys or by using the mouse. 59 | 60 | ### Edit Dialog 61 | 62 | ![Edit Global Color](./images/edit_global_color.png) 63 | 64 | When editing an entry, the edit dialog will allow you to either insert a string value when the `Text` radio button is selected, or a color value when the color radio button is selected. When dealing with a color, you will get a color preview as you change the color's hex value, you can also click the color box to bring up your system's color picker (or a generic color picker in the case of Linux). 65 | 66 | Transparent colors are allowed and are represented by the following form `#RRGGBBAA`. Color previews will simulate the transparent color by overlaying foreground colors over the rule's background color (defaults to global background if entry does not contain a background or it is simulating that entry's background color). 67 | 68 | ### Scope Settings 69 | 70 | ![Scope Settings](./images/scope_settings.png) 71 | 72 | The Scope Settings tab contains all the scope related entries. 73 | 74 | You can create new entries by selecting the `+` button or delete entries by selecting an entry and pressing the `-` button. You can edit an entry by double clicking it or pressing enter if you have one selected. You can also change the order of the settings by using the arrow buttons, or by pressing ++alt+up++ or ++alt+down++. 75 | 76 | You can also change text emphasis by pressing the ++b++, ++i++, or ++u++ key to toggle bold, italic, or underline respectively (underline doesn't do anything in Sublime Text and is more a TextMate option). 77 | You can also navigate the entries with the arrow keys or by using the mouse. 78 | 79 | ### Edit Scope Dialog 80 | 81 | ![Edit Scope Colors](./images/edit_scope_color.png) 82 | 83 | When editing a scope entry, you can change the name and the target scope. 84 | 85 | Underneath the name and scope box, you will find checkboxes allowing you to control whether the target scope will be bold, italic, or underlined (underline doesn't do anything in Sublime Text and is more a TextMate option). 86 | 87 | Scope dialogs also give you two colors you can set: background and foreground. The associated text box takes colors in the form `#RRGGBBAA` and support an alpha channel. You can alternatively click the color preview box and use your system's color picker (or a generic color picker in the case of Linux). 88 | -------------------------------------------------------------------------------- /docs/theme/extra-2aceccb07f.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";var e=function(e,t,n){for(var o=document.querySelectorAll("article"),r=document.querySelectorAll("pre."+t+",div."+t),a=void 0===n?{}:n,i=0;iab",e.style.display="block",n.appendChild(e);var o=e.offsetHeight;return e.open=!0,o=o!==e.offsetHeight,n.removeChild(e),t&&n.parentNode.removeChild(n),o}())for(var e=document.querySelectorAll("details>summary"),t=0;tcode{margin:0 .29412em;padding:.07353em 0;border-radius:.2rem;background-color:hsla(0,0%,93%,.5);-webkit-box-shadow:.29412em 0 0 hsla(0,0%,93%,.5),-.29412em 0 0 hsla(0,0%,93%,.5);box-shadow:.29412em 0 0 hsla(0,0%,93%,.5),-.29412em 0 0 hsla(0,0%,93%,.5);-webkit-box-decoration-break:clone;box-decoration-break:clone}.md-typeset td code{word-break:normal}.md-typeset .codehilite .hll,.md-typeset .highlight .hll{display:inline}.md-typeset .headerlink{font:normal 400 2rem Material Icons;vertical-align:middle}.md-typeset h2 .headerlink{margin-top:-.4rem}.md-typeset h3 .headerlink{margin-top:-.3rem}.md-typeset h4 .headerlink,.md-typeset h5 .headerlink,.md-typeset h6 .headerlink{margin-top:-.2rem}.md-typeset .progress-label{position:absolute;width:100%;margin:0;color:rgba(0,0,0,.5);font-weight:700;line-height:1.2rem;text-align:center;white-space:nowrap;overflow:hidden}.md-typeset .progress-bar{height:1.2rem;float:left;background-color:#2979ff}.md-typeset .progress{display:block;position:relative;width:100%;height:1.2rem;margin:.5rem 0;background-color:#eee}.md-typeset .progress.thin{height:.4rem;margin-top:.9rem}.md-typeset .progress.thin .progress-label{margin-top:-.4rem}.md-typeset .progress.thin .progress-bar{height:.4rem}.md-typeset .progress.candystripe .progress-bar{background-image:linear-gradient(135deg,hsla(0,0%,100%,.8) 27%,transparent 0,transparent 52%,hsla(0,0%,100%,.8) 0,hsla(0,0%,100%,.8) 77%,transparent 0,transparent);background-size:2rem 2rem}.md-typeset .progress-80plus .progress-bar,.md-typeset .progress-100plus .progress-bar{background-color:#00e676}.md-typeset .progress-60plus .progress-bar{background-color:#fbc02d}.md-typeset .progress-40plus .progress-bar{background-color:#ff9100}.md-typeset .progress-20plus .progress-bar{background-color:#ff5252}.md-typeset .progress-0plus .progress-bar{background-color:#ff1744}.md-typeset .progress.note .progress-bar{background-color:#2979ff}.md-typeset .progress.summary .progress-bar{background-color:#00b0ff}.md-typeset .progress.tip .progress-bar{background-color:#00bfa5}.md-typeset .progress.success .progress-bar{background-color:#00e676}.md-typeset .progress.warning .progress-bar{background-color:#ff9100}.md-typeset .progress.failure .progress-bar{background-color:#ff5252}.md-typeset .progress.danger .progress-bar{background-color:#ff1744}.md-typeset .progress.bug .progress-bar{background-color:#f50057}.md-typeset .progress.quote .progress-bar{background-color:#9e9e9e}.md-typeset .candystripe-animate .progress-bar{-webkit-animation:a 3s linear infinite;animation:a 3s linear infinite}@-webkit-keyframes a{0%{background-position:0 0}to{background-position:6rem 0}}@keyframes a{0%{background-position:0 0}to{background-position:6rem 0}}.md-typeset details{display:block;margin:1em 0}.md-typeset details[open]>summary:before{content:"\f0d7"}.md-typeset details>summary{display:block;cursor:pointer}.md-typeset details>summary:before{display:inline-block;width:.75em;font-family:fontawesome;font-size:1em;font-style:normal;font-variant:normal;font-weight:400;line-height:1;text-transform:none;white-space:nowrap;content:"\f0da";speak:none;word-wrap:normal;direction:ltr}.md-typeset details>summary::-webkit-details-marker{display:none}.md-typeset details.no-details:not([open])>*{display:none}.md-typeset details.no-details:not([open])>summary{display:block}.md-typeset details.attention,.md-typeset details.bug,.md-typeset details.caution,.md-typeset details.check,.md-typeset details.cite,.md-typeset details.danger,.md-typeset details.done,.md-typeset details.error,.md-typeset details.fail,.md-typeset details.failure,.md-typeset details.hint,.md-typeset details.important,.md-typeset details.missing,.md-typeset details.note,.md-typeset details.quote,.md-typeset details.seealso,.md-typeset details.settings,.md-typeset details.success,.md-typeset details.summary,.md-typeset details.tip,.md-typeset details.tldr,.md-typeset details.warning{-webkit-box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12),0 3px 1px -2px rgba(0,0,0,.2);position:relative;margin-bottom:1rem;padding:1.2rem;overflow:hidden}.md-typeset details.attention :nth-child(2),.md-typeset details.bug :nth-child(2),.md-typeset details.caution :nth-child(2),.md-typeset details.check :nth-child(2),.md-typeset details.cite :nth-child(2),.md-typeset details.danger :nth-child(2),.md-typeset details.done :nth-child(2),.md-typeset details.error :nth-child(2),.md-typeset details.fail :nth-child(2),.md-typeset details.failure :nth-child(2),.md-typeset details.hint :nth-child(2),.md-typeset details.important :nth-child(2),.md-typeset details.missing :nth-child(2),.md-typeset details.note :nth-child(2),.md-typeset details.quote :nth-child(2),.md-typeset details.seealso :nth-child(2),.md-typeset details.settings :nth-child(2),.md-typeset details.success :nth-child(2),.md-typeset details.summary :nth-child(2),.md-typeset details.tip :nth-child(2),.md-typeset details.tldr :nth-child(2),.md-typeset details.warning :nth-child(2){margin-top:0}.md-typeset details.attention :last-child,.md-typeset details.bug :last-child,.md-typeset details.caution :last-child,.md-typeset details.check :last-child,.md-typeset details.cite :last-child,.md-typeset details.danger :last-child,.md-typeset details.done :last-child,.md-typeset details.error :last-child,.md-typeset details.fail :last-child,.md-typeset details.failure :last-child,.md-typeset details.hint :last-child,.md-typeset details.important :last-child,.md-typeset details.missing :last-child,.md-typeset details.note :last-child,.md-typeset details.quote :last-child,.md-typeset details.seealso :last-child,.md-typeset details.settings :last-child,.md-typeset details.success :last-child,.md-typeset details.summary :last-child,.md-typeset details.tip :last-child,.md-typeset details.tldr :last-child,.md-typeset details.warning :last-child{margin-bottom:0}.md-typeset details.attention>summary,.md-typeset details.bug>summary,.md-typeset details.caution>summary,.md-typeset details.check>summary,.md-typeset details.cite>summary,.md-typeset details.danger>summary,.md-typeset details.done>summary,.md-typeset details.error>summary,.md-typeset details.fail>summary,.md-typeset details.failure>summary,.md-typeset details.hint>summary,.md-typeset details.important>summary,.md-typeset details.missing>summary,.md-typeset details.note>summary,.md-typeset details.quote>summary,.md-typeset details.seealso>summary,.md-typeset details.settings>summary,.md-typeset details.success>summary,.md-typeset details.summary>summary,.md-typeset details.tip>summary,.md-typeset details.tldr>summary,.md-typeset details.warning>summary{position:relative;margin:-1.2rem -1.2rem 0;padding:.5rem 3.2rem;outline:none}.md-typeset details.attention>summary:after,.md-typeset details.attention>summary:before,.md-typeset details.bug>summary:after,.md-typeset details.bug>summary:before,.md-typeset details.caution>summary:after,.md-typeset details.caution>summary:before,.md-typeset details.check>summary:after,.md-typeset details.check>summary:before,.md-typeset details.cite>summary:after,.md-typeset details.cite>summary:before,.md-typeset details.danger>summary:after,.md-typeset details.danger>summary:before,.md-typeset details.done>summary:after,.md-typeset details.done>summary:before,.md-typeset details.error>summary:after,.md-typeset details.error>summary:before,.md-typeset details.fail>summary:after,.md-typeset details.fail>summary:before,.md-typeset details.failure>summary:after,.md-typeset details.failure>summary:before,.md-typeset details.hint>summary:after,.md-typeset details.hint>summary:before,.md-typeset details.important>summary:after,.md-typeset details.important>summary:before,.md-typeset details.missing>summary:after,.md-typeset details.missing>summary:before,.md-typeset details.note>summary:after,.md-typeset details.note>summary:before,.md-typeset details.quote>summary:after,.md-typeset details.quote>summary:before,.md-typeset details.seealso>summary:after,.md-typeset details.seealso>summary:before,.md-typeset details.settings>summary:after,.md-typeset details.settings>summary:before,.md-typeset details.success>summary:after,.md-typeset details.success>summary:before,.md-typeset details.summary>summary:after,.md-typeset details.summary>summary:before,.md-typeset details.tip>summary:after,.md-typeset details.tip>summary:before,.md-typeset details.tldr>summary:after,.md-typeset details.tldr>summary:before,.md-typeset details.warning>summary:after,.md-typeset details.warning>summary:before{position:absolute;top:.7rem;font-family:Material Icons;font-size:2rem;font-style:normal;font-variant:normal;font-weight:400;line-height:1;text-transform:none;white-space:nowrap;speak:none;word-wrap:normal;direction:ltr}.md-typeset details.attention>summary:after,.md-typeset details.bug>summary:after,.md-typeset details.caution>summary:after,.md-typeset details.check>summary:after,.md-typeset details.cite>summary:after,.md-typeset details.danger>summary:after,.md-typeset details.done>summary:after,.md-typeset details.error>summary:after,.md-typeset details.fail>summary:after,.md-typeset details.failure>summary:after,.md-typeset details.hint>summary:after,.md-typeset details.important>summary:after,.md-typeset details.missing>summary:after,.md-typeset details.note>summary:after,.md-typeset details.quote>summary:after,.md-typeset details.seealso>summary:after,.md-typeset details.settings>summary:after,.md-typeset details.success>summary:after,.md-typeset details.summary>summary:after,.md-typeset details.tip>summary:after,.md-typeset details.tldr>summary:after,.md-typeset details.warning>summary:after{right:1rem}.md-typeset details.attention>summary:before,.md-typeset details.bug>summary:before,.md-typeset details.caution>summary:before,.md-typeset details.check>summary:before,.md-typeset details.cite>summary:before,.md-typeset details.danger>summary:before,.md-typeset details.done>summary:before,.md-typeset details.error>summary:before,.md-typeset details.fail>summary:before,.md-typeset details.failure>summary:before,.md-typeset details.hint>summary:before,.md-typeset details.important>summary:before,.md-typeset details.missing>summary:before,.md-typeset details.note>summary:before,.md-typeset details.quote>summary:before,.md-typeset details.seealso>summary:before,.md-typeset details.settings>summary:before,.md-typeset details.success>summary:before,.md-typeset details.summary>summary:before,.md-typeset details.tip>summary:before,.md-typeset details.tldr>summary:before,.md-typeset details.warning>summary:before{left:1rem}.md-typeset details.attention[open]>summary,.md-typeset details.bug[open]>summary,.md-typeset details.caution[open]>summary,.md-typeset details.check[open]>summary,.md-typeset details.cite[open]>summary,.md-typeset details.danger[open]>summary,.md-typeset details.done[open]>summary,.md-typeset details.error[open]>summary,.md-typeset details.fail[open]>summary,.md-typeset details.failure[open]>summary,.md-typeset details.hint[open]>summary,.md-typeset details.important[open]>summary,.md-typeset details.missing[open]>summary,.md-typeset details.note[open]>summary,.md-typeset details.quote[open]>summary,.md-typeset details.seealso[open]>summary,.md-typeset details.settings[open]>summary,.md-typeset details.success[open]>summary,.md-typeset details.summary[open]>summary,.md-typeset details.tip[open]>summary,.md-typeset details.tldr[open]>summary,.md-typeset details.warning[open]>summary{margin-bottom:1.2rem}.md-typeset details.attention[open]>summary:after,.md-typeset details.bug[open]>summary:after,.md-typeset details.caution[open]>summary:after,.md-typeset details.check[open]>summary:after,.md-typeset details.cite[open]>summary:after,.md-typeset details.danger[open]>summary:after,.md-typeset details.done[open]>summary:after,.md-typeset details.error[open]>summary:after,.md-typeset details.fail[open]>summary:after,.md-typeset details.failure[open]>summary:after,.md-typeset details.hint[open]>summary:after,.md-typeset details.important[open]>summary:after,.md-typeset details.missing[open]>summary:after,.md-typeset details.note[open]>summary:after,.md-typeset details.quote[open]>summary:after,.md-typeset details.seealso[open]>summary:after,.md-typeset details.settings[open]>summary:after,.md-typeset details.success[open]>summary:after,.md-typeset details.summary[open]>summary:after,.md-typeset details.tip[open]>summary:after,.md-typeset details.tldr[open]>summary:after,.md-typeset details.warning[open]>summary:after{content:"keyboard_arrow_down"}.md-typeset details.attention:not([open]),.md-typeset details.bug:not([open]),.md-typeset details.caution:not([open]),.md-typeset details.check:not([open]),.md-typeset details.cite:not([open]),.md-typeset details.danger:not([open]),.md-typeset details.done:not([open]),.md-typeset details.error:not([open]),.md-typeset details.fail:not([open]),.md-typeset details.failure:not([open]),.md-typeset details.hint:not([open]),.md-typeset details.important:not([open]),.md-typeset details.missing:not([open]),.md-typeset details.note:not([open]),.md-typeset details.quote:not([open]),.md-typeset details.seealso:not([open]),.md-typeset details.settings:not([open]),.md-typeset details.success:not([open]),.md-typeset details.summary:not([open]),.md-typeset details.tip:not([open]),.md-typeset details.tldr:not([open]),.md-typeset details.warning:not([open]){padding-bottom:0}.md-typeset details.attention:not([open])>summary:after,.md-typeset details.bug:not([open])>summary:after,.md-typeset details.caution:not([open])>summary:after,.md-typeset details.check:not([open])>summary:after,.md-typeset details.cite:not([open])>summary:after,.md-typeset details.danger:not([open])>summary:after,.md-typeset details.done:not([open])>summary:after,.md-typeset details.error:not([open])>summary:after,.md-typeset details.fail:not([open])>summary:after,.md-typeset details.failure:not([open])>summary:after,.md-typeset details.hint:not([open])>summary:after,.md-typeset details.important:not([open])>summary:after,.md-typeset details.missing:not([open])>summary:after,.md-typeset details.note:not([open])>summary:after,.md-typeset details.quote:not([open])>summary:after,.md-typeset details.seealso:not([open])>summary:after,.md-typeset details.settings:not([open])>summary:after,.md-typeset details.success:not([open])>summary:after,.md-typeset details.summary:not([open])>summary:after,.md-typeset details.tip:not([open])>summary:after,.md-typeset details.tldr:not([open])>summary:after,.md-typeset details.warning:not([open])>summary:after{content:"keyboard_arrow_left"}.md-typeset details.note>summary{background-color:#2979ff;color:#fff}.md-typeset details.note>summary:before{content:"edit"}.md-typeset details.summary>summary{background-color:#00b0ff;color:#fff}.md-typeset details.summary>summary:before{content:"subject"}.md-typeset details.tip>summary{background-color:#00bfa5;color:#fff}.md-typeset details.tip>summary:before{content:"whatshot"}.md-typeset details.success>summary{background-color:#00e676;color:#fff}.md-typeset details.success>summary:before{content:"done"}.md-typeset details.warning>summary{background-color:#ff9100;color:#fff}.md-typeset details.warning>summary:before{content:"warning"}.md-typeset details.failure>summary{background-color:#ff5252;color:#fff}.md-typeset details.failure>summary:before{content:"clear"}.md-typeset details.danger>summary{background-color:#ff1744;color:#fff}.md-typeset details.danger>summary:before{content:"flash_on"}.md-typeset details.bug>summary{background-color:#f50057;color:#fff}.md-typeset details.bug>summary:before{content:"bug_report"}.md-typeset details.quote>summary{background-color:#9e9e9e;color:#fff}.md-typeset details.quote>summary:before{content:"format_quote"}.md-typeset details.settings>summary{background-color:#a0f;color:#fff}.md-typeset details.settings>summary:before{content:"settings"}@media only screen and (min-width:76.1876em){.md-typeset .headerlink{margin-left:-2.4rem;float:left}.md-typeset h2 .headerlink{margin-top:.6rem}.md-typeset h3 .headerlink{margin-top:.4rem}.md-typeset h4 .headerlink{margin-top:.2rem}.md-typeset h5 .headerlink,.md-typeset h6 .headerlink{margin-top:0}} -------------------------------------------------------------------------------- /docs/theme/partials/footer.html: -------------------------------------------------------------------------------- 1 | {% import "partials/language.html" as lang %} 2 | 61 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Sublime Color Scheme Editor Documentation 2 | site_url: https://facelessuser.github.io/subclrschm 3 | repo_url: https://github.com/facelessuser/subclrschm 4 | edit_uri: tree/master/docs/src/markdown 5 | site_description: A graphical Sublime Text color scheme editor. 6 | copyright: Copyright © 2013 - 2017 Isaac Muse 7 | 8 | pages: 9 | - Sublime Color Scheme Editor: index.md 10 | - Installation: installation.md 11 | - User Guide: usage.md 12 | - Contributing & Support: contributing.md 13 | - Changelog: changelog.md 14 | - License: license.md 15 | 16 | theme: material 17 | docs_dir: docs/src/markdown 18 | theme_dir: docs/theme 19 | 20 | markdown_extensions: 21 | - markdown.extensions.toc: 22 | slugify: !!python/name:pymdownx.slugs.uslugify 23 | permalink: "\ue157" 24 | - markdown.extensions.admonition: 25 | - markdown.extensions.smarty: 26 | smart_quotes: false 27 | - pymdownx.betterem: 28 | - markdown.extensions.attr_list: 29 | - markdown.extensions.def_list: 30 | - markdown.extensions.tables: 31 | - markdown.extensions.abbr: 32 | - pymdownx.extrarawhtml: 33 | - pymdownx.superfences: 34 | - pymdownx.highlight: 35 | css_class: codehilite 36 | extend_pygments_lang: 37 | - name: php-inline 38 | lang: php 39 | options: 40 | startinline: true 41 | - pymdownx.inlinehilite: 42 | - pymdownx.magiclink: 43 | repo_url_shortener: true 44 | base_repo_url: https://github.com/facelessuser/subclrschm 45 | - pymdownx.tilde: 46 | - pymdownx.caret: 47 | - pymdownx.smartsymbols: 48 | - pymdownx.emoji: 49 | emoji_generator: !!python/name:pymdownx.emoji.to_png 50 | - pymdownx.escapeall: 51 | hardbreak: True 52 | nbsp: True 53 | - pymdownx.tasklist: 54 | custom_checkbox: true 55 | - pymdownx.progressbar: 56 | - pymdownx.arithmatex: 57 | - pymdownx.mark: 58 | - pymdownx.plainhtml: 59 | strip_attributes: '' 60 | - pymdownx.snippets: 61 | base_path: docs/src/markdown/_snippets 62 | - pymdownx.keys: 63 | separator: "\uff0b" 64 | - pymdownx.details: 65 | 66 | extra: 67 | palette: 68 | primary: blue 69 | accent: blue 70 | font: 71 | text: Roboto 72 | code: Roboto Mono 73 | social: 74 | - type: github 75 | link: https://github.com/facelessuser 76 | extra_css: 77 | - extra-9b4c72abee.css 78 | extra_javascript: 79 | - extra-2aceccb07f.js 80 | -------------------------------------------------------------------------------- /requirements/docs.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | pymdown-extensions 4 | -------------------------------------------------------------------------------- /requirements/lint.txt: -------------------------------------------------------------------------------- 1 | flake8>=2,<3 2 | flake8-docstrings 3 | flake8-putty 4 | pep8-naming 5 | -------------------------------------------------------------------------------- /requirements/project.txt: -------------------------------------------------------------------------------- 1 | wxpython>=4.0.0a3 2 | -------------------------------------------------------------------------------- /requirements/test.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pytest-cov 3 | coverage 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Setup package.""" 4 | from setuptools import setup, find_packages 5 | import os 6 | import imp 7 | import traceback 8 | 9 | 10 | def get_version(): 11 | """Get version and version_info without importing the entire module.""" 12 | 13 | devstatus = { 14 | 'alpha': '3 - Alpha', 15 | 'beta': '4 - Beta', 16 | 'candidate': '4 - Beta', 17 | 'final': '5 - Production/Stable' 18 | } 19 | path = os.path.join(os.path.dirname(__file__), 'subclrschm', 'lib') 20 | fp, pathname, desc = imp.find_module('__version__', [path]) 21 | try: 22 | v = imp.load_module('__version__', fp, pathname, desc) 23 | return v.version, devstatus[v.version_info[3]] 24 | except Exception: 25 | print(traceback.format_exc()) 26 | finally: 27 | fp.close() 28 | 29 | 30 | VER, DEVSTATUS = get_version() 31 | 32 | LONG_DESC = ''' 33 | Sublime Color Scheme Editor (subclrschm) is a color scheme editor for Sublime Text 3. 34 | 35 | It is built with wxPython 4.0.0+ and requires Python 2.7 or 3.4+. 36 | You can learn more about using subclrschm by `reading the docs`_. 37 | 38 | .. _`reading the docs`: http://facelessuser.github.io/subclrschm/ 39 | 40 | Support 41 | ======= 42 | 43 | Help and support is available here at the repository's `bug tracker`_. 44 | Please read about `support and contributing`_ before creating issues. 45 | 46 | .. _`bug tracker`: https://github.com/facelessuser/subclrschm/issues 47 | .. _`support and contributing`: http://facelessuser.github.io/subclrschm/contributing/ 48 | ''' 49 | 50 | setup( 51 | name='subclrschm', 52 | version=VER, 53 | keywords='Sublime color scheme', 54 | description='GUI for editing Sublime Text color schemes.', 55 | long_description=LONG_DESC, 56 | author='Isaac Muse', 57 | author_email='Isaac.Muse@gmail.com', 58 | url='https://github.com/facelessuser/subclrschm', 59 | packages=find_packages(exclude=['tests', 'tools']), 60 | install_requires=[ 61 | "wxpython>=4.0.0a3" 62 | ], 63 | zip_safe=False, 64 | entry_points={ 65 | 'gui_scripts': [ 66 | 'subclrschm=subclrschm.__main__:main' 67 | ] 68 | }, 69 | package_data={ 70 | 'subclrschm.lib.gui.data': ['*.png', '*.ico', '*.icns'] 71 | }, 72 | license='MIT License', 73 | classifiers=[ 74 | 'Development Status :: 5 - Production/Stable', 75 | 'Environment :: Console', 76 | 'Intended Audience :: Developers', 77 | 'License :: OSI Approved :: MIT License', 78 | 'Operating System :: OS Independent', 79 | 'Programming Language :: Python :: 2', 80 | 'Programming Language :: Python :: 2.7', 81 | 'Programming Language :: Python :: 3', 82 | 'Programming Language :: Python :: 3.4', 83 | 'Programming Language :: Python :: 3.5', 84 | 'Programming Language :: Python :: 3.6', 85 | 'Topic :: Software Development :: Libraries :: Python Modules' 86 | ] 87 | ) 88 | -------------------------------------------------------------------------------- /subclrschm/__init__.py: -------------------------------------------------------------------------------- 1 | """Subclrschm.""" 2 | from .lib import __version__ 3 | 4 | version = __version__.version 5 | version_info = __version__.version_info 6 | -------------------------------------------------------------------------------- /subclrschm/__main__.py: -------------------------------------------------------------------------------- 1 | """Sublime Text Color Scheme Editor.""" 2 | from __future__ import unicode_literals 3 | import os 4 | import sys 5 | from .lib.gui import custom_app 6 | from .lib.gui import subclrschm_app 7 | from .lib import parse_args 8 | from .lib import util 9 | 10 | # Handle case where pythonw.exe is used and there is not a valid stdout or stderr 11 | if sys.executable.endswith("pythonw.exe"): 12 | sys.stdout = open(os.devnull, "w") 13 | sys.stderr = open(os.devnull, "w") 14 | 15 | 16 | def get_log_location(): 17 | """Get log location.""" 18 | 19 | platform = util.platform() 20 | 21 | if platform == "windows": 22 | folder = os.path.expanduser("~\\.subclrschm") 23 | fifo = os.path.join(folder, '\\\\.\\pipe\\subclrschm') 24 | elif platform == "osx": 25 | folder = os.path.expanduser("~/.subclrschm") 26 | fifo = os.path.join(folder, 'subclrschm.fifo') 27 | elif platform == "linux": 28 | folder = os.path.expanduser("~/.config/subclrschm") 29 | fifo = os.path.join(folder, 'subclrschm.fifo') 30 | 31 | if not os.path.exists(folder): 32 | os.mkdir(folder) 33 | 34 | return folder, fifo 35 | 36 | 37 | def run(): 38 | """Run the app.""" 39 | 40 | args = parse_args.parse_arguments() 41 | def_loacation, fifo = get_log_location() 42 | 43 | if not args.log: 44 | args.log = def_loacation 45 | if os.path.exists(args.log): 46 | args.log = os.path.join(os.path.normpath(args.log), 'subclrschm.log') 47 | 48 | custom_app.init_app_log(args.log) 49 | if args.debug: 50 | custom_app.set_debug_mode(True) 51 | 52 | app = subclrschm_app.SubClrSchmApp( 53 | redirect=not args.no_redirect, 54 | single_instance_name="subclrschm" if not args.multi_instance else None, 55 | pipe_name=fifo if not args.multi_instance else None 56 | ) 57 | 58 | if args.multi_instance or app.is_instance_okay(): 59 | 60 | action = "" 61 | if args.select: 62 | action = "select" 63 | elif args.new: 64 | action = "new" 65 | 66 | main_win = subclrschm_app.Editor( 67 | None, 68 | live_save=args.live_save, 69 | debugging=args.debug 70 | ) 71 | 72 | main_win.Show() 73 | main_win.init_frame(args.file, action) 74 | 75 | if main_win.is_ready(): 76 | app.MainLoop() 77 | return 0 78 | 79 | 80 | def main(): 81 | """Main entry point.""" 82 | 83 | sys.exit(run()) 84 | 85 | 86 | if __name__ == "__main__": 87 | main() 88 | -------------------------------------------------------------------------------- /subclrschm/lib/__init__.py: -------------------------------------------------------------------------------- 1 | """Subclrschm.""" 2 | -------------------------------------------------------------------------------- /subclrschm/lib/__meta__.py: -------------------------------------------------------------------------------- 1 | """Version.""" 2 | from __future__ import unicode_literals 3 | from .__version__ import version, version_info 4 | 5 | __version__ = version 6 | __version_info__ = version_info 7 | __app__ = "SubClrSchm" 8 | __status__ = version_info[3] 9 | __maintainers__ = [("Isaac Muse", "IsaacMuse@gmail.com")] 10 | __help__ = "https://github.com/facelessuser/subclrschm/issues" 11 | __manual__ = "https://github.com/facelessuser/subclrschm/blob/master/README.md" 12 | -------------------------------------------------------------------------------- /subclrschm/lib/__version__.py: -------------------------------------------------------------------------------- 1 | """Version.""" 2 | 3 | # (major, minor, micro, release type, pre-release build, post-release build) 4 | version_info = (2, 1, 3, 'final', 0, 0) 5 | 6 | 7 | def _version(): 8 | """ 9 | Get the version (PEP 440). 10 | 11 | Version structure 12 | (major, minor, micro, release type, pre-release build, post-release build) 13 | Release names are named is such a way they are sortable and comparable with ease. 14 | (alpha | beta | candidate | final) 15 | 16 | - "final" should never have a pre-release build number 17 | - pre-releases should have a pre-release build number greater than 0 18 | - post-release is only applied if post-release build is greater than 0 19 | """ 20 | 21 | releases = {"alpha": 'a', "beta": 'b', "candidate": 'rc', "final": ''} 22 | # Version info should be proper length 23 | assert len(version_info) == 6 24 | # Should be a valid release 25 | assert version_info[3] in releases 26 | # Pre-release releases should have a pre-release value 27 | assert version_info[3] == 'final' or version_info[4] > 0 28 | # Final should not have a pre-release value 29 | assert version_info[3] != 'final' or version_info[4] == 0 30 | 31 | main = '.'.join(str(x)for x in (version_info[0:2] if version_info[2] == 0 else version_info[0:3])) 32 | prerel = releases[version_info[3]] 33 | prerel += str(version_info[4]) if prerel else '' 34 | postrel = '.post%d' % version_info[5] if version_info[5] > 0 else '' 35 | 36 | return ''.join((main, prerel, postrel)) 37 | 38 | version = _version() 39 | -------------------------------------------------------------------------------- /subclrschm/lib/default_new_theme.py: -------------------------------------------------------------------------------- 1 | """Default theme.""" 2 | theme = { 3 | "name": "Grayscale", 4 | "settings": [ 5 | { 6 | "settings": { 7 | "activeGuide": "#888888", 8 | "background": "#FFFFFF", 9 | "bracketContentsForeground": "#000000", 10 | "bracketContentsOptions": "underline", 11 | "bracketsForeground": "#000000", 12 | "bracketsOptions": "underline", 13 | "caret": "#000000", 14 | "findHighlight": "#666666", 15 | "findHighlightForeground": "#000000", 16 | "foreground": "#000000", 17 | "gutter": "#E5E5E5", 18 | "gutterForeground": "#000000", 19 | "inactiveSelection": "#888888", 20 | "invisibles": "#323232", 21 | "lineHighlight": "#2E2E2E22", 22 | "selection": "#666666", 23 | "selectionForeground": "#FFFFFF", 24 | "tagsOptions": "stippled_underline" 25 | } 26 | }, 27 | { 28 | "name": "Comment", 29 | "scope": "comment", 30 | "settings": { 31 | "foreground": "#A5A5A5" 32 | } 33 | }, 34 | { 35 | "name": "Storage type", 36 | "scope": "storage.type", 37 | "settings": { 38 | "fontStyle": "italic", 39 | "foreground": "#000000" 40 | } 41 | }, 42 | { 43 | "name": "Class name", 44 | "scope": "entity.name.class", 45 | "settings": { 46 | "fontStyle": "underline", 47 | "foreground": "#000000" 48 | } 49 | }, 50 | { 51 | "name": "Inherited class", 52 | "scope": "entity.other.inherited-class", 53 | "settings": { 54 | "fontStyle": "italic underline", 55 | "foreground": "#000000" 56 | } 57 | }, 58 | { 59 | "name": "Function argument", 60 | "scope": "variable.parameter", 61 | "settings": { 62 | "fontStyle": "italic", 63 | "foreground": "#000000" 64 | } 65 | }, 66 | { 67 | "name": "Library class/type", 68 | "scope": "support.type, support.class", 69 | "settings": { 70 | "fontStyle": "italic", 71 | "foreground": "#000000" 72 | } 73 | } 74 | ], 75 | "uuid": "07379361-ce49-4e6c-a03f-26378a7a2131" 76 | } 77 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/__init__.py: -------------------------------------------------------------------------------- 1 | """Subclrschm lib.""" 2 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/about_dialog.py: -------------------------------------------------------------------------------- 1 | """ 2 | About Dialog. 3 | 4 | Licensed under MIT 5 | Copyright (c) 2013 - 2015 Isaac Muse 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 8 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 10 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 16 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 18 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | """ 21 | from __future__ import unicode_literals 22 | import wx 23 | from . import gui 24 | from . import data 25 | from .. import __meta__ 26 | 27 | 28 | class AboutDialog(gui.AboutDialog): 29 | """About Dialog.""" 30 | 31 | def __init__(self, parent): 32 | """Initialize the AboutDialog object.""" 33 | 34 | super(AboutDialog, self).__init__(parent) 35 | 36 | self.SetTitle("About") 37 | 38 | self.m_bitmap = wx.StaticBitmap( 39 | self.m_about_panel, 40 | wx.ID_ANY, 41 | data.get_bitmap('subclrschm_dialog.png'), 42 | wx.DefaultPosition, 43 | wx.Size(64, 64), 0 44 | ) 45 | self.m_app_label.SetLabel(__meta__.__app__) 46 | self.m_version_label.SetLabel( 47 | "Version: %s %s" % (__meta__.__version__, __meta__.__status__) 48 | ) 49 | self.m_developers_label.SetLabel( 50 | "Developer(s):\n%s" % ("\n".join([" %s - %s" % (m[0], m[1]) for m in __meta__.__maintainers__])) 51 | ) 52 | 53 | self.m_dev_toggle.SetLabel("Contact" + " >>") 54 | 55 | self.Fit() 56 | 57 | def on_toggle(self, event): 58 | """Show/hide contact info on when contact button is toggled.""" 59 | 60 | if self.m_dev_toggle.GetValue(): 61 | self.m_dev_toggle.SetLabel("Contact" + " <<") 62 | self.m_developers_label.Show() 63 | else: 64 | self.m_dev_toggle.SetLabel("Contact" + " >>") 65 | self.m_developers_label.Hide() 66 | self.Fit() 67 | self.Refresh() 68 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/basic_dialogs.py: -------------------------------------------------------------------------------- 1 | """Basic dialogs.""" 2 | from __future__ import unicode_literals 3 | from . import messages 4 | 5 | 6 | def filepicker(msg, default_path, wildcard, save=False): 7 | """Call file picker.""" 8 | 9 | return messages.filepickermsg(msg, default_path, wildcard, save) 10 | 11 | 12 | def yesno(question, title='Yes or no?', bitmap=None, yes="Okay", no="Cancel"): 13 | """Prompt for yes/no.""" 14 | 15 | return messages.promptmsg(question, title, bitmap, yes, no) 16 | 17 | 18 | def infomsg(msg, title="INFO", bitmap=None): 19 | """Info message.""" 20 | 21 | messages.infomsg(msg, title, bitmap) 22 | 23 | 24 | def errormsg(msg, title="ERROR", bitmap=None): 25 | """Error message.""" 26 | 27 | messages.errormsg(msg, title, bitmap) 28 | 29 | 30 | def warnmsg(msg, title="WARNING", bitmap=None): 31 | """Warning message.""" 32 | 33 | messages.warnmsg(msg, title, bitmap) 34 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/color_setting_dialog.py: -------------------------------------------------------------------------------- 1 | """Color setting dialog.""" 2 | from __future__ import unicode_literals 3 | import wx 4 | from . import gui 5 | from . import settings_key_bindings 6 | from . import settings_codes as sc 7 | from ..rgba import RGBA 8 | 9 | 10 | class ColorEditor(gui.ColorSetting, settings_key_bindings.SettingsKeyBindings): 11 | """Color editor.""" 12 | 13 | def __init__(self, parent, obj, insert=False): 14 | """Initialize.""" 15 | 16 | super(ColorEditor, self).__init__(parent) 17 | self.setup_keybindings() 18 | self.Fit() 19 | size = self.GetSize() 20 | self.SetMinSize(size) 21 | size.Set(-1, size[1]) 22 | self.SetMaxSize(size) 23 | self.foreground_save = "" 24 | self.background_save = "" 25 | self.apply_settings = False 26 | self.color_obj = obj 27 | self.insert = bool(insert) 28 | 29 | self.m_bold_checkbox.SetValue(False) 30 | self.m_italic_checkbox.SetValue(False) 31 | self.m_underline_checkbox.SetValue(False) 32 | 33 | for x in self.color_obj["settings"]["fontStyle"].split(" "): 34 | if x == "bold": 35 | self.m_bold_checkbox.SetValue(True) 36 | elif x == "italic": 37 | self.m_italic_checkbox.SetValue(True) 38 | elif x == "underline": 39 | self.m_underline_checkbox.SetValue(True) 40 | 41 | self.m_name_textbox.SetValue(self.color_obj["name"]) 42 | self.m_scope_textbox.SetValue(self.color_obj["scope"]) 43 | 44 | self.m_foreground_textbox.SetValue(self.color_obj["settings"]["foreground"]) 45 | if self.color_obj["settings"]["foreground"] == "": 46 | cl = RGBA("#FFFFFF") 47 | bg = wx.Colour(cl.r, cl.g, cl.b) 48 | self.m_foreground_picker.SetBackgroundColour(bg) 49 | if cl.get_luminance() > 128: 50 | fg = wx.Colour(0, 0, 0) 51 | else: 52 | fg = wx.Colour(255, 255, 255) 53 | self.m_foreground_button_label.SetForegroundColour(fg) 54 | 55 | self.m_background_textbox.SetValue(self.color_obj["settings"]["background"]) 56 | if self.color_obj["settings"]["background"] == "": 57 | cl = RGBA("#FFFFFF") 58 | bg = wx.Colour(cl.r, cl.g, cl.b) 59 | self.m_background_picker.SetBackgroundColour(bg) 60 | if cl.get_luminance() > 128: 61 | fg = wx.Colour(0, 0, 0) 62 | else: 63 | fg = wx.Colour(255, 255, 255) 64 | self.m_background_button_label.SetForegroundColour(fg) 65 | 66 | def on_foreground_button_click(self, event): 67 | """Handle foreground button click event.""" 68 | 69 | color = None 70 | data = wx.ColourData() 71 | data.SetChooseFull(True) 72 | 73 | alpha = None 74 | 75 | text = self.m_foreground_textbox.GetValue() 76 | if text == "": 77 | rgb = RGBA("#FFFFFF") 78 | else: 79 | rgb = RGBA(text) 80 | if len(text) == 9: 81 | alpha == text[7:9] 82 | 83 | # set the default color in the chooser 84 | data.SetColour(wx.Colour(rgb.r, rgb.g, rgb.b)) 85 | 86 | # construct the chooser 87 | dlg = wx.ColourDialog(self, data) 88 | 89 | if dlg.ShowModal() == wx.ID_OK: 90 | # set the panel background color 91 | color = dlg.GetColourData().GetColour().GetAsString(wx.C2S_HTML_SYNTAX) 92 | self.m_foreground_textbox.SetValue(color if alpha is None else color + alpha) 93 | dlg.Destroy() 94 | event.Skip() 95 | 96 | def on_background_button_click(self, event): 97 | """Handle background button click event.""" 98 | 99 | color = None 100 | data = wx.ColourData() 101 | data.SetChooseFull(True) 102 | 103 | alpha = None 104 | 105 | text = self.m_background_textbox.GetValue() 106 | if text == "": 107 | rgb = RGBA("#FFFFFF") 108 | else: 109 | rgb = RGBA(text) 110 | if len(text) == 9: 111 | alpha == text[7:9] 112 | 113 | # set the default color in the chooser 114 | data.SetColour(wx.Colour(rgb.r, rgb.g, rgb.b)) 115 | 116 | # construct the chooser 117 | dlg = wx.ColourDialog(self, data) 118 | 119 | if dlg.ShowModal() == wx.ID_OK: 120 | # set the panel background color 121 | color = dlg.GetColourData().GetColour().GetAsString(wx.C2S_HTML_SYNTAX) 122 | self.m_background_textbox.SetValue(color if alpha is None else color + alpha) 123 | dlg.Destroy() 124 | event.Skip() 125 | 126 | def on_background_change(self, event): 127 | """On background change event.""" 128 | 129 | text = self.m_background_textbox.GetValue() 130 | try: 131 | if text == "": 132 | cl = RGBA("#FFFFFF") 133 | else: 134 | cl = RGBA(text) 135 | except: 136 | event.Skip() 137 | return 138 | 139 | cl.apply_alpha(self.Parent.m_style_settings.bg_color.get_rgb()) 140 | bg = wx.Colour(cl.r, cl.g, cl.b) 141 | self.m_background_picker.SetBackgroundColour(bg) 142 | if cl.get_luminance() > 128: 143 | fg = wx.Colour(0, 0, 0) 144 | else: 145 | fg = wx.Colour(255, 255, 255) 146 | self.m_background_button_label.SetForegroundColour(fg) 147 | self.m_background_picker.Refresh() 148 | 149 | def on_foreground_change(self, event): 150 | """Handle foreground change event.""" 151 | 152 | text = self.m_foreground_textbox.GetValue() 153 | try: 154 | if text == "": 155 | cl = RGBA("#FFFFFF") 156 | else: 157 | cl = RGBA(text) 158 | except: 159 | event.Skip() 160 | return 161 | 162 | cl.apply_alpha(self.Parent.m_style_settings.bg_color.get_rgb()) 163 | bg = wx.Colour(cl.r, cl.g, cl.b) 164 | self.m_foreground_picker.SetBackgroundColour(bg) 165 | if cl.get_luminance() > 128: 166 | fg = wx.Colour(0, 0, 0) 167 | else: 168 | fg = wx.Colour(255, 255, 255) 169 | self.m_foreground_button_label.SetForegroundColour(fg) 170 | self.m_foreground_picker.Refresh() 171 | 172 | def on_foreground_focus(self, event): 173 | """Handle foreground focus event.""" 174 | self.foreground_save = self.m_foreground_textbox.GetValue() 175 | event.Skip() 176 | 177 | def on_background_focus(self, event): 178 | """Handle background focus event.""" 179 | self.background_save = self.m_background_textbox.GetValue() 180 | event.Skip() 181 | 182 | def on_foreground_blur(self, event): 183 | """Handle foreground blur event.""" 184 | text = self.m_foreground_textbox.GetValue() 185 | if text != "": 186 | try: 187 | RGBA(text) 188 | except: 189 | self.m_foreground_textbox.SetValue(self.foreground_save) 190 | event.Skip() 191 | 192 | def on_background_blur(self, event): 193 | """Handle background blur event.""" 194 | text = self.m_background_textbox.GetValue() 195 | if text != "": 196 | try: 197 | RGBA(text) 198 | except: 199 | self.m_background_textbox.SetValue(self.background_save) 200 | event.Skip() 201 | 202 | def on_apply_button_click(self, event): 203 | """Handle appply button click event.""" 204 | 205 | self.apply_settings = True 206 | self.Close() 207 | 208 | def on_set_color_close(self, event): 209 | """Handle set color close event.""" 210 | 211 | fontstyle = [] 212 | if self.m_bold_checkbox.GetValue(): 213 | fontstyle.append("bold") 214 | if self.m_italic_checkbox.GetValue(): 215 | fontstyle.append("italic") 216 | if self.m_underline_checkbox.GetValue(): 217 | fontstyle.append("underline") 218 | 219 | if self.apply_settings: 220 | self.color_obj = { 221 | "name": self.m_name_textbox.GetValue(), 222 | "scope": self.m_scope_textbox.GetValue(), 223 | "settings": { 224 | "foreground": self.m_foreground_textbox.GetValue(), 225 | "background": self.m_background_textbox.GetValue(), 226 | "fontStyle": " ".join(fontstyle) 227 | } 228 | } 229 | 230 | if self.insert: 231 | grid = self.Parent.m_style_settings.m_plist_grid 232 | num = grid.GetNumberRows() 233 | row = grid.GetGridCursorRow() 234 | if num > 0: 235 | grid.InsertRows(row, 1, True) 236 | else: 237 | grid.AppendRows(1) 238 | row = 0 239 | grid.GetParent().update_row(row, self.color_obj) 240 | grid.GetParent().go_cell(grid, row, 0) 241 | self.Parent.update_plist(sc.JSON_ADD, {"table": "style", "index": row, "data": self.color_obj}) 242 | else: 243 | self.Parent.set_style_object(self.color_obj) 244 | event.Skip() 245 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/custom_app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom App. 3 | 4 | https://gist.github.com/facelessuser/5750404 5 | 6 | Licensed under MIT 7 | Copyright (c) 2013 - 2015 Isaac Muse 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 10 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 12 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 18 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 20 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | """ 23 | from __future__ import unicode_literals 24 | import codecs 25 | import json 26 | import os 27 | import time 28 | import wx 29 | import wx.lib.newevent 30 | from . import simplelog 31 | from .. import util 32 | try: 33 | import thread 34 | except ImportError: 35 | import _thread as thread 36 | 37 | 38 | if util.platform() == "windows": 39 | import ctypes 40 | GENERIC_READ = 0x80000000 41 | GENERIC_WRITE = 0x40000000 42 | OPEN_EXISTING = 0x3 43 | PIPE_ACCESS_DUPLEX = 0x3 44 | PIPE_TYPE_MESSAGE = 0x4 45 | PIPE_WAIT = 0x0 46 | 47 | PipeEvent, EVT_PIPE_ARGS = wx.lib.newevent.NewEvent() 48 | 49 | log = None 50 | last_level = simplelog.ERROR 51 | DEBUG_MODE = False 52 | DEBUG_CONSOLE = False 53 | 54 | 55 | class GuiLog(wx.PyOnDemandOutputWindow): 56 | """GUI logging.""" 57 | 58 | def __init__(self, title="Debug Console"): 59 | """Init the PyOnDemandOutputWindow object.""" 60 | 61 | # wx.PyOnDemandOutputWindow is old class style 62 | # Cannot use super with old class styles 63 | wx.PyOnDemandOutputWindow.__init__(self, title) 64 | 65 | def CreateOutputWindow(self, st): # noqa 66 | """Create the logging console.""" 67 | 68 | self.frame = wx.Frame(self.parent, -1, self.title, self.pos, self.size, style=wx.DEFAULT_FRAME_STYLE) 69 | self.text = wx.TextCtrl(self.frame, -1, "", style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_RICH) 70 | self.text.AppendText(st) 71 | self.frame.Show(True) 72 | self.frame.Bind(wx.EVT_CLOSE, self.OnCloseWindow) 73 | 74 | # Create debug keybinding to open debug console 75 | debugid = wx.NewId() 76 | self.frame.Bind(wx.EVT_MENU, self.debug_close, id=debugid) 77 | mod = wx.ACCEL_CMD if util.platform() == "osx" else wx.ACCEL_CTRL 78 | accel_tbl = wx.AcceleratorTable( 79 | [(mod, ord('`'), debugid)] 80 | ) 81 | self.frame.SetAcceleratorTable(accel_tbl) 82 | 83 | def debug_close(self, event): 84 | """Close debug frame.""" 85 | 86 | self.frame.Close() 87 | 88 | def write(self, text, echo=True): 89 | """Write to log, and if console is open, echo to it as well.""" 90 | 91 | if self.frame is None: 92 | if not wx.IsMainThread(): 93 | if echo: 94 | wx.CallAfter(gui_log, text) 95 | if get_debug_console(): 96 | wx.CallAfter(self.CreateOutputWindow, text) 97 | else: 98 | if echo: 99 | gui_log(text) 100 | if get_debug_console(): 101 | self.CreateOutputWindow(text) 102 | else: 103 | if not wx.IsMainThread(): 104 | if echo: 105 | wx.CallAfter(gui_log, text) 106 | if get_debug_console(): 107 | wx.CallAfter(self.text.AppendText, text) 108 | else: 109 | if echo: 110 | gui_log(text) 111 | if get_debug_console(): 112 | self.text.AppendText(text) 113 | 114 | def OnCloseWindow(self, event): # noqa 115 | """Close logging console.""" 116 | 117 | if self.frame is not None: 118 | self.frame.Destroy() 119 | self.frame = None 120 | self.text = None 121 | self.parent = None 122 | if get_debug_console(): 123 | set_debug_console(False) 124 | log.set_echo(False) 125 | debug("**Debug Console Closed**\n") 126 | 127 | 128 | class CustomApp(wx.App): 129 | """Custom app that adds a number of features.""" 130 | 131 | outputWindowClass = GuiLog 132 | 133 | def __init__(self, *args, **kwargs): 134 | """ 135 | Init the custom app. 136 | 137 | Provide two new inputs: 138 | single_instance_name: this creates an instance id with the name given 139 | this will allow you to check if this is the only 140 | instance currently open with the same name. 141 | callback: A callback you can do if instance checks out 142 | """ 143 | 144 | self.instance = None 145 | self.custom_init(*args, **kwargs) 146 | if "single_instance_name" in kwargs: 147 | del kwargs["single_instance_name"] 148 | if "callback" in kwargs: 149 | del kwargs["callback"] 150 | wx.App.__init__(self, *args, **kwargs) 151 | 152 | def custom_init(self, *args, **kwargs): 153 | """Parse for new inputs and store them because they must be removed.""" 154 | 155 | self.single_instance = None 156 | self.init_callback = None 157 | instance_name = kwargs.get("single_instance_name", None) 158 | callback = kwargs.get("callback", None) 159 | if instance_name is not None and isinstance(instance_name, util.string_type): 160 | self.single_instance = instance_name 161 | if callback is not None and hasattr(callback, '__call__'): 162 | self.init_callback = callback 163 | 164 | def ensure_single_instance(self, name): 165 | """Check to see if this is the only instance.""" 166 | 167 | self.name = "%s-%s" % (self.single_instance, wx.GetUserId()) 168 | self.instance = wx.SingleInstanceChecker(self.name) 169 | if self.instance.IsAnotherRunning(): 170 | # wx.MessageBox("Only one instance allowed!", "ERROR", wx.OK | wx.ICON_ERROR) 171 | return False 172 | return True 173 | 174 | def is_instance_okay(self): 175 | """Return whether this is the only instance.""" 176 | 177 | return self.instance_okay 178 | 179 | def OnInit(self): # noqa 180 | """ 181 | Execute callback if instance is okay. 182 | 183 | Store instance check variable. 184 | """ 185 | 186 | self.locale = wx.Locale(wx.LANGUAGE_DEFAULT) 187 | 188 | self.instance_okay = True 189 | if self.single_instance is not None: 190 | if not self.ensure_single_instance(self.single_instance): 191 | self.instance_okay = False 192 | if self.init_callback is not None and self.instance_okay: 193 | self.init_callback() 194 | return True 195 | 196 | def OnExit(self): # noqa 197 | """Cleanup instance check.""" 198 | 199 | return 0 200 | 201 | 202 | class ArgPipeThread(object): 203 | """Argument pipe thread for receiving arguments from another instance.""" 204 | 205 | def __init__(self, app, pipe_name): 206 | """Init pipe thread variables.""" 207 | 208 | self.app = app 209 | self.pipe_name = pipe_name 210 | 211 | def Start(self): # noqa 212 | """Start listening to the pipe.""" 213 | 214 | self.check_pipe = True 215 | self.running = True 216 | thread.start_new_thread(self.Run, ()) 217 | 218 | def Stop(self): # noqa 219 | """ 220 | Stop listening to the pipe. 221 | 222 | Send a new line to kick the listener out from waiting. 223 | """ 224 | 225 | self.check_pipe = False 226 | if util.platform() == "windows": 227 | file_handle = ctypes.windll.kernel32.CreateFileW( 228 | self.pipe_name, 229 | GENERIC_READ | GENERIC_WRITE, 230 | 0, None, 231 | OPEN_EXISTING, 232 | 0, None 233 | ) 234 | data = '\n' 235 | bytes_written = ctypes.c_ulong(0) 236 | ctypes.windll.kernel32.WriteFile( 237 | file_handle, ctypes.c_wchar_p(data), len(data), ctypes.byref(bytes_written), None 238 | ) 239 | ctypes.windll.kernel32.CloseHandle(file_handle) 240 | else: 241 | # It's okay if the pipe is broken, our goal is just to break the 242 | # wait loop for recieving pipe data. 243 | try: 244 | with codecs.open(self.pipe_name, "w", encoding="utf-8") as pipeout: 245 | try: 246 | pipeout.write('\n') 247 | except IOError: 248 | pass 249 | except util.CommonBrokenPipeError: 250 | pass 251 | 252 | def IsRunning(self): # noqa 253 | """Return if the thread is still busy.""" 254 | 255 | return self.running 256 | 257 | def Run(self): # noqa 258 | """The actual thread listening loop.""" 259 | 260 | if util.platform() == "windows": 261 | data = "" 262 | p = ctypes.windll.kernel32.CreateNamedPipeW( 263 | self.pipe_name, 264 | PIPE_ACCESS_DUPLEX, 265 | PIPE_TYPE_MESSAGE | PIPE_WAIT, 266 | 1, 65536, 65536, 300, None 267 | ) 268 | while self.check_pipe: 269 | ctypes.windll.kernel32.ConnectNamedPipe(p, None) 270 | result = ctypes.create_unicode_buffer(4096) 271 | bytes_read = ctypes.c_ulong(0) 272 | success = ctypes.windll.kernel32.ReadFile(p, result, 4096, ctypes.byref(bytes_read), None) 273 | if success: 274 | data += result.value.replace("\r", "") 275 | if len(data) and data[-1] == "\n": 276 | lines = data.rstrip("\n").split("\n") 277 | try: 278 | args = json.loads(lines[-1]) 279 | except Exception: 280 | args = [] 281 | evt = PipeEvent(data=args) 282 | wx.PostEvent(self.app, evt) 283 | data = "" 284 | ctypes.windll.kernel32.DisconnectNamedPipe(p) 285 | time.sleep(0.2) 286 | ctypes.windll.kernel32.CloseHandle(p) 287 | else: 288 | if os.path.exists(self.pipe_name): 289 | os.unlink(self.pipe_name) 290 | if not os.path.exists(self.pipe_name): 291 | os.mkfifo(self.pipe_name) 292 | 293 | while self.check_pipe: 294 | with codecs.open(self.pipe_name, "r", 'utf-8') as pipein: 295 | while self.check_pipe: 296 | line = pipein.readline()[:-1] 297 | if line != "": 298 | try: 299 | args = json.loads(line) 300 | except Exception: 301 | args = [] 302 | evt = PipeEvent(data=args) 303 | wx.PostEvent(self.app, evt) 304 | break 305 | time.sleep(0.2) 306 | self.running = False 307 | 308 | 309 | class PipeApp(CustomApp): 310 | """Pip app variant that allows the app to be sent data via a pipe.""" 311 | 312 | def __init__(self, *args, **kwargs): 313 | """Parse pipe args.""" 314 | 315 | self.active_pipe = False 316 | self.pipe_thread = None 317 | self.pipe_name = kwargs.get("pipe_name", None) 318 | if "pipe_name" in kwargs: 319 | del kwargs["pipe_name"] 320 | CustomApp.__init__(self, *args, **kwargs) 321 | 322 | def OnInit(self): # noqa 323 | """ 324 | Check if this is the first instance, and if so, start the pipe listener. 325 | 326 | If not, send the current args to the pipe to be read 327 | by the first instance. 328 | """ 329 | 330 | CustomApp.OnInit(self) 331 | self.Bind(EVT_PIPE_ARGS, self.on_pipe_args) 332 | if self.pipe_name is not None: 333 | if self.is_instance_okay(): 334 | self.receive_arg_pipe() 335 | else: 336 | self.send_arg_pipe() 337 | return False 338 | return True 339 | 340 | def get_sys_args(self): 341 | """Get system args as unicode.""" 342 | 343 | return util.to_unicode_argv()[1:] 344 | 345 | def send_arg_pipe(self): 346 | """Send the current arguments down the pipe.""" 347 | argv = self.get_sys_args() 348 | if util.platform() == "windows": 349 | args = self.process_args(argv) 350 | file_handle = ctypes.windll.kernel32.CreateFileW( 351 | self.pipe_name, 352 | GENERIC_READ | GENERIC_WRITE, 353 | 0, None, 354 | OPEN_EXISTING, 355 | 0, None 356 | ) 357 | data = json.dumps(args) + '\n' 358 | bytes_written = ctypes.c_ulong(0) 359 | ctypes.windll.kernel32.WriteFile( 360 | file_handle, ctypes.c_wchar_p(data), len(data) * 2, ctypes.byref(bytes_written), None 361 | ) 362 | ctypes.windll.kernel32.CloseHandle(file_handle) 363 | else: 364 | with codecs.open(self.pipe_name, "w", encoding="utf-8") as pipeout: 365 | args = self.process_args(argv) 366 | pipeout.write(json.dumps(args) + '\n') 367 | 368 | def process_args(self, arguments): 369 | """Noop, but can be overriden to process the args.""" 370 | 371 | return arguments 372 | 373 | def receive_arg_pipe(self): 374 | """Start the pipe listenr thread.""" 375 | 376 | self.active_pipe = True 377 | self.pipe_thread = ArgPipeThread(self, self.pipe_name) 378 | self.pipe_thread.Start() 379 | 380 | def OnExit(self): # noqa 381 | """Stop the thread if needed.""" 382 | 383 | if self.active_pipe: 384 | wx.Yield() 385 | self.pipe_thread.Stop() 386 | running = True 387 | while running: 388 | running = self.pipe_thread.IsRunning() 389 | time.sleep(0.1) 390 | return CustomApp.OnExit(self) 391 | 392 | def on_pipe_args(self, event): 393 | """An overridable event for when pipe arguments are received.""" 394 | 395 | event.Skip() 396 | 397 | 398 | class DebugFrameExtender(object): 399 | """Extend frame with debugger.""" 400 | 401 | def set_keybindings(self, keybindings=[], debug_event=None): 402 | """ 403 | Method to easily set key bindings. 404 | 405 | Also sets up debug keybindings and events. 406 | """ 407 | 408 | # Create keybinding to open debug console, bind debug console to ctrl/cmd + ` depending on platform 409 | # if an event is passed in. 410 | tbl = [] 411 | bindings = keybindings 412 | if debug_event is not None: 413 | mod = wx.ACCEL_CMD if util.platform() == "osx" else wx.ACCEL_CTRL 414 | bindings.append((mod, ord('`'), debug_event)) 415 | 416 | for binding in keybindings: 417 | keyid = wx.NewId() 418 | self.Bind(wx.EVT_MENU, binding[2], id=keyid) 419 | tbl.append((binding[0], binding[1], keyid)) 420 | 421 | if len(bindings): 422 | self.SetAcceleratorTable(wx.AcceleratorTable(tbl)) 423 | 424 | def open_debug_console(self): 425 | """Open the debug console.""" 426 | 427 | set_debug_console(True) 428 | # echo out log to console 429 | log.set_echo(True) 430 | app = wx.GetApp() 431 | if app.stdioWin is not None: 432 | if app.stdioWin.frame is None: 433 | app.stdioWin.write(log.read(), False) 434 | else: 435 | app.stdioWin.write("", False) 436 | debug("**Debug Console Opened**") 437 | 438 | def toggle_debug_console(self): 439 | """Open up the debug console if closed or close if opened.""" 440 | 441 | set_debug_console(not get_debug_console()) 442 | app = wx.GetApp() 443 | if get_debug_console(): 444 | # echo out log to console 445 | log.set_echo(True) 446 | if app.stdioWin is not None: 447 | app.stdioWin.write(log.read(), False) 448 | debug("**Debug Console Opened**") 449 | else: 450 | debug("**Debug Console Closed**") 451 | # disable echoing of log to console 452 | log.set_echo(False) 453 | if app.stdioWin is not None: 454 | app.stdioWin.close() 455 | 456 | def close_debug_console(self): 457 | """ 458 | On close, ensure that console is closes. 459 | 460 | Also, make sure echo is off in case logging 461 | occurs after App closing. 462 | """ 463 | 464 | if get_debug_console(): 465 | debug("**Debug Console Closed**") 466 | set_debug_console(False) 467 | log.set_echo(False) 468 | app = wx.GetApp() 469 | if app.stdioWin is not None: 470 | app.stdioWin.close() 471 | 472 | 473 | def _log_struct(obj, log_func, label="Object"): 474 | """Base logger to log a dict in pretty print format.""" 475 | 476 | log_func(obj, log_fmt="%(loglevel)s: " + label + ": %(message)s\n", msg_fmt=json_fmt) 477 | 478 | 479 | def json_fmt(obj): 480 | """Format the dict as JSON.""" 481 | 482 | return json.dumps(obj, sort_keys=True, indent=4, separators=(',', ': ')) 483 | 484 | 485 | def gui_log(msg): 486 | """Logger used in the GUI frames.""" 487 | 488 | log._log(msg, echo=False) 489 | 490 | 491 | def debug(msg, echo=True, log_fmt="%(loglevel)s: %(message)s\n", msg_fmt=None): 492 | """Debug level log.""" 493 | 494 | if get_debug_mode(): 495 | log.debug(msg, echo=echo, log_fmt=log_fmt, msg_fmt=msg_fmt) 496 | 497 | 498 | def info(msg, echo=True, log_fmt="%(loglevel)s: %(message)s\n", msg_fmt=None): 499 | """Info level log.""" 500 | 501 | log.info(msg, echo=echo, log_fmt=log_fmt, msg_fmt=msg_fmt) 502 | 503 | 504 | def critical(msg, echo=True, log_fmt="%(loglevel)s: %(message)s\n", msg_fmt=None): 505 | """Critical level log.""" 506 | 507 | log.critical(msg, echo=echo, log_fmt=log_fmt, msg_fmt=msg_fmt) 508 | 509 | 510 | def warning(msg, echo=True, log_fmt="%(loglevel)s: %(message)s\n", msg_fmt=None): 511 | """Warning level log.""" 512 | 513 | log.warning(msg, echo=echo, log_fmt=log_fmt, msg_fmt=msg_fmt) 514 | 515 | 516 | def error(msg, echo=True, log_fmt="%(loglevel)s: %(message)s\n", msg_fmt=None): 517 | """Error level log.""" 518 | 519 | log.error(msg, echo=echo, log_fmt=log_fmt, msg_fmt=msg_fmt) 520 | 521 | 522 | def debug_struct(obj, label="Object"): 523 | """Debug level dict log.""" 524 | 525 | _log_struct(obj, debug, label) 526 | 527 | 528 | def info_struct(obj, label="Object"): 529 | """Info level dict log.""" 530 | 531 | _log_struct(obj, info, label) 532 | 533 | 534 | def critical_struct(obj, label="Object"): 535 | """Critical level dict log.""" 536 | 537 | _log_struct(obj, critical, label) 538 | 539 | 540 | def warning_struct(obj, label="Object"): 541 | """Warning level dict log.""" 542 | 543 | _log_struct(obj, warning, label) 544 | 545 | 546 | def error_struct(obj, label="Object"): 547 | """Error level dict log.""" 548 | 549 | _log_struct(obj, error, label) 550 | 551 | 552 | def init_app_log(name, level=simplelog.ERROR): 553 | """Init the app log.""" 554 | 555 | global log 556 | global last_level 557 | if level != simplelog.DEBUG: 558 | last_level = level 559 | simplelog.init_global_log(name, level=last_level) 560 | log = simplelog.get_global_log() 561 | 562 | 563 | def set_debug_mode(value): 564 | """Set whether the app is in debug mode.""" 565 | 566 | global DEBUG_MODE 567 | global last_level 568 | DEBUG_MODE = bool(value) 569 | current_level = log.get_level() 570 | if DEBUG_MODE: 571 | if current_level > simplelog.DEBUG: 572 | last_level = current_level 573 | log.set_level(simplelog.DEBUG) 574 | elif not DEBUG_MODE: 575 | if last_level == simplelog.DEBUG: 576 | last_level = simplelog.ERROR 577 | log.set_level(last_level) 578 | 579 | 580 | def set_debug_console(value): 581 | """Set debug console enable.""" 582 | 583 | global DEBUG_CONSOLE 584 | DEBUG_CONSOLE = bool(value) 585 | 586 | 587 | def get_debug_mode(): 588 | """Get the debug mode.""" 589 | 590 | return DEBUG_MODE 591 | 592 | 593 | def get_debug_console(): 594 | """Get debug console mode.""" 595 | 596 | return DEBUG_CONSOLE 597 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/custom_statusbar.py: -------------------------------------------------------------------------------- 1 | """ 2 | Custom Status Bar. 3 | 4 | https://gist.github.com/facelessuser/5750045 5 | 6 | Licensed under MIT 7 | Copyright (c) 2013 - 2015 Isaac Muse 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 9 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 11 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 14 | of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 19 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | """ 22 | from __future__ import unicode_literals 23 | from collections import OrderedDict 24 | import wx 25 | import wx.lib.agw.supertooltip 26 | from .. import util 27 | 28 | if wx.VERSION > (2, 9, 4): # When will this get fixed? :( 29 | def monkey_patch(): 30 | """ 31 | Monkey patch Supertooltips. 32 | 33 | Remove once WxPython gets its crap together. 34 | """ 35 | 36 | import inspect 37 | import re 38 | 39 | target_line = re.compile(r'([ ]{8})(maxWidth = max\(bmpWidth\+\(textWidth\+self._spacing\*3\), maxWidth\)\n)') 40 | tt_source = inspect.getsourcelines(wx.lib.agw.supertooltip.ToolTipWindowBase.OnPaint)[0] 41 | count = 0 42 | found = False 43 | for line in tt_source: 44 | if not found: 45 | m = target_line.match(line) 46 | if m: 47 | tt_source[count] = m.group(0) 48 | found = True 49 | count += 1 50 | continue 51 | tt_source[count] = line[4:] 52 | count += 1 53 | exec(''.join(tt_source)) 54 | wx.lib.agw.supertooltip.ToolTipWindowBase.OnPaint = locals()['OnPaint'] # noqa 55 | 56 | monkey_patch() 57 | 58 | 59 | class ContextMenu(wx.Menu): 60 | """Context Menu.""" 61 | 62 | def __init__(self, parent, menu, pos): 63 | """Attach the context menu to to the parent with the defined items.""" 64 | 65 | wx.Menu.__init__(self) 66 | self._callbacks = {} 67 | 68 | for i in menu: 69 | menuid = wx.NewId() 70 | item = wx.MenuItem(self, menuid, i[0]) 71 | self._callbacks[menuid] = i[1] 72 | self.Append(item) 73 | self.Bind(wx.EVT_MENU, self.on_callback, item) 74 | 75 | parent.PopupMenu(self, pos) 76 | 77 | def on_callback(self, event): 78 | """Execute the menu item callback.""" 79 | 80 | menuid = event.GetId() 81 | self._callbacks[menuid](event) 82 | event.Skip() 83 | 84 | 85 | class ToolTip(wx.lib.agw.supertooltip.SuperToolTip): 86 | """Tooltip.""" 87 | 88 | def __init__(self, target, message, header="", style="Office 2007 Blue", start_delay=.1): 89 | """Attach the defined tooltip to the target.""" 90 | 91 | super(ToolTip, self).__init__(message, header=header) 92 | self.SetTarget(target) 93 | self.ApplyStyle(style) 94 | self.SetStartDelay(start_delay) 95 | target.tooltip = self 96 | 97 | def hide(self): 98 | """Hide the tooltip.""" 99 | 100 | if self._superToolTip: 101 | self._superToolTip.Destroy() 102 | 103 | 104 | class TimedStatusExtension(object): 105 | """Timed status in status bar.""" 106 | 107 | def set_timed_status(self, text, index=0): 108 | """ 109 | Set the status for a short time. 110 | 111 | Save the previous status for restore 112 | when the timed status completes. 113 | """ 114 | 115 | if self.text_timer[index].IsRunning(): 116 | self.text_timer[index].Stop() 117 | else: 118 | self.saved_text = self.GetStatusText(index) 119 | self.SetStatusText(text, index) 120 | self.text_timer[index].Start(5000, oneShot=True) 121 | 122 | def sb_time_setup(self, field_count): 123 | """Setup timer for timed status.""" 124 | 125 | self.field_count = field_count 126 | self.saved_text = [""] * field_count 127 | self.text_timer = [wx.Timer(self)] * field_count 128 | count = 0 129 | for x in self.text_timer: 130 | self.Bind(wx.EVT_TIMER, lambda event, index=count: self.clear_text(event, index), self.text_timer[count]) 131 | count += 1 132 | 133 | def clear_text(self, event, index): 134 | """Clear the status.""" 135 | 136 | self.SetStatusText(self.saved_text, index) 137 | 138 | def set_status(self, text, index=0): 139 | """Set the status.""" 140 | 141 | if self.text_timer[index].IsRunning(): 142 | self.text_timer[index].Stop() 143 | self.SetStatusText(text, index) 144 | 145 | 146 | class IconTrayExtension(object): 147 | """Add icon tray extension.""" 148 | 149 | fields = [-1] 150 | 151 | def sb_tray_setup(self): 152 | """Setup the status bar with icon tray.""" 153 | 154 | self.SetFieldsCount(len(self.fields) + 1) 155 | self.SetStatusText('', 0) 156 | self.SetStatusWidths(self.fields + [1]) 157 | self.sb_icons = OrderedDict() 158 | self.Bind(wx.EVT_SIZE, self.on_sb_size) 159 | 160 | def remove_icon(self, name): 161 | """Remove an icon from the tray.""" 162 | 163 | if name in self.sb_icons: 164 | self.hide_tooltip(name) 165 | self.sb_icons[name].Destroy() 166 | del self.sb_icons[name] 167 | self.place_icons(resize=True) 168 | 169 | def hide_tooltip(self, name): 170 | """Hide the tooltip.""" 171 | 172 | if self.sb_icons[name].tooltip: 173 | self.sb_icons[name].tooltip.hide() 174 | 175 | def set_icon( 176 | self, name, icon, msg=None, context=None, 177 | click_right=None, click_left=None, 178 | dclick_right=None, dclick_left=None 179 | ): 180 | """ 181 | Set the given icon in the tray. 182 | 183 | Attach a menu and/or tooltip if provided. 184 | """ 185 | 186 | if name in self.sb_icons: 187 | self.hide_tooltip(name) 188 | self.sb_icons[name].Destroy() 189 | bmp = wx.StaticBitmap(self) 190 | bmp.SetBitmap(label=icon) 191 | self.sb_icons[name] = bmp 192 | if msg is not None: 193 | ToolTip(self.sb_icons[name], msg) 194 | if click_left is not None: 195 | self.sb_icons[name].Bind(wx.EVT_LEFT_DOWN, click_left) 196 | if context is not None: 197 | self.sb_icons[name].Bind(wx.EVT_RIGHT_DOWN, lambda e: self.show_menu(name, context)) 198 | elif click_right is not None: 199 | self.sb_icons[name].Bind(wx.EVT_RIGHT_DOWN, click_right) 200 | if dclick_left is not None: 201 | self.sb_icons[name].Bind(wx.EVT_LEFT_DCLICK, dclick_left) 202 | if dclick_right is not None: 203 | self.sb_icons[name].Bind(wx.EVT_RIGHT_DCLICK, dclick_right) 204 | self.place_icons(resize=True) 205 | 206 | def show_menu(self, name, context): 207 | """Show context menu on icon in tray.""" 208 | 209 | self.hide_tooltip(name) 210 | ContextMenu(self, context, self.sb_icons[name].GetPosition()) 211 | 212 | def place_icons(self, resize=False): 213 | """Calculate new icon position and icon tray size.""" 214 | 215 | x_offset = 0 216 | if resize: 217 | platform = util.platform() 218 | if platform in "osx": 219 | # OSX must increment by 10 220 | self.SetStatusWidths([-1, len(self.sb_icons) * 20 + 10]) 221 | elif platform == "windows": 222 | # In at least wxPython 2.9+, the first icon inserted changes the size, additional icons don't. 223 | # I've only tested >= 2.9. 224 | if len(self.sb_icons): 225 | self.SetStatusWidths([-1, (len(self.sb_icons) - 1) * 20 + 1]) 226 | else: 227 | self.SetStatusWidths([-1, len(self.sb_icons) * 20 + 1]) 228 | else: 229 | # Linux? Should be fine with 1, but haven't tested yet. 230 | self.SetStatusWidths([-1, len(self.sb_icons) * 20 + 1]) 231 | rect = self.GetFieldRect(len(self.fields)) 232 | for v in self.sb_icons.values(): 233 | v.SetPosition((rect.x + x_offset, rect.y)) 234 | v.Hide() 235 | v.Show() 236 | x_offset += 20 237 | 238 | def on_sb_size(self, event): 239 | """Ensure icons are properly placed on resize.""" 240 | 241 | event.Skip() 242 | self.place_icons() 243 | 244 | 245 | class CustomStatusExtension(IconTrayExtension, TimedStatusExtension): 246 | """Custom status extension.""" 247 | 248 | def sb_setup(self, fields): 249 | """Setup the extention variant of the CustomStatusBar object.""" 250 | 251 | self.fields = fields 252 | self.sb_tray_setup() 253 | self.sb_time_setup(len(self.fields)) 254 | 255 | 256 | class CustomStatusBar(wx.StatusBar, CustomStatusExtension): 257 | """Custom status bar.""" 258 | 259 | def __init__(self, parent, name, fields=None): 260 | """Init the CustomStatusBar object.""" 261 | 262 | field_array = [-1] if not fields else fields[:] 263 | super(CustomStatusBar, self).__init__( 264 | parent, 265 | id=wx.ID_ANY, 266 | style=wx.STB_DEFAULT_STYLE, 267 | name=name 268 | ) 269 | self.sb_setup(field_array) 270 | 271 | 272 | def extend(instance, extension): 273 | """Extend instance with extension class.""" 274 | 275 | instance.__class__ = type( 276 | ('%s_extended_with_%s' if util.PY3 else b'%s_extended_with_%s') % ( 277 | instance.__class__.__name__, extension.__name__ 278 | ), 279 | (instance.__class__, extension), 280 | {} 281 | ) 282 | 283 | 284 | def extend_sb(sb, fields=None): 285 | """Extend the statusbar.""" 286 | 287 | field_array = [-1] if not fields else fields[:] 288 | extend(sb, CustomStatusExtension) 289 | sb.sb_setup(field_array) 290 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/data/__init__.py: -------------------------------------------------------------------------------- 1 | """Data resource lib.""" 2 | from __future__ import unicode_literals 3 | import os 4 | import codecs 5 | import base64 6 | from wx.lib.embeddedimage import PyEmbeddedImage 7 | from ... import util 8 | 9 | RESOURCE_PATH = os.path.abspath(os.path.dirname(__file__)) 10 | 11 | 12 | def get_file(file_name, raw=False): 13 | """Get the data file.""" 14 | 15 | text = b'' if raw else '' 16 | resource = os.path.join(RESOURCE_PATH, file_name) 17 | if os.path.exists(resource): 18 | try: 19 | if raw: 20 | with open(resource, 'rb') as f: 21 | text = f.read() 22 | else: 23 | with codecs.open(resource, 'r', encoding='utf-8') as f: 24 | text = f.read() 25 | except Exception: 26 | pass 27 | return text 28 | 29 | 30 | def get_image(file_name, b64=False): 31 | """Get the image as a PyEmbeddedImage.""" 32 | icon = b'' 33 | resource = os.path.join(RESOURCE_PATH, file_name) 34 | if os.path.exists(resource): 35 | try: 36 | with open(resource, "rb") as f: 37 | icon = base64.b64encode(f.read()) 38 | except Exception: 39 | pass 40 | return PyEmbeddedImage(icon) if not b64 else util.to_ustr(icon) 41 | 42 | 43 | def get_bitmap(file_name): 44 | """ 45 | Get bitmap. 46 | 47 | For retina, we provide images that are 2X size. 48 | We can't detect retina yet, so we use 2X for non retina as well. 49 | This works fine for OSX as it seems to scale the images to fit the sizer 50 | as long as you set the height and width of the bitmap object (not the actual data) 51 | to half size. 52 | 53 | Windows and Linux do not auto scale, so we have to actually scale them. 54 | In the future, we should load normal sizes for Windows/Linux (and OSX non retina if we can detect retina), 55 | and load a separate 2X size for OSX retina. But this is an okay work around for now. 56 | """ 57 | 58 | image = get_image(file_name).GetImage() 59 | if util.platform() == "osx": 60 | bm = image.ConvertToBitmap() 61 | bm.SetSize( 62 | ( 63 | int(bm.GetWidth() / 2), 64 | int(bm.GetHeight() / 2) 65 | ) 66 | ) 67 | else: 68 | scaled = image.Rescale( 69 | int(image.GetWidth() / 2), 70 | int(image.GetHeight() / 2) 71 | ) 72 | bm = scaled.ConvertToBitmap() 73 | 74 | return bm 75 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/data/floppy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/subclrschm/lib/gui/data/floppy.png -------------------------------------------------------------------------------- /subclrschm/lib/gui/data/subclrschm.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/subclrschm/lib/gui/data/subclrschm.icns -------------------------------------------------------------------------------- /subclrschm/lib/gui/data/subclrschm.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/subclrschm/lib/gui/data/subclrschm.ico -------------------------------------------------------------------------------- /subclrschm/lib/gui/data/subclrschm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/subclrschm/lib/gui/data/subclrschm.png -------------------------------------------------------------------------------- /subclrschm/lib/gui/data/subclrschm_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/subclrschm/lib/gui/data/subclrschm_dialog.png -------------------------------------------------------------------------------- /subclrschm/lib/gui/data/subclrschm_hires.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/subclrschm/lib/gui/data/subclrschm_hires.png -------------------------------------------------------------------------------- /subclrschm/lib/gui/data/subclrschm_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facelessuser/subclrschm/52cf5bc39bac6e3dd6d44061cd0c005cdc9a41d1/subclrschm/lib/gui/data/subclrschm_large.png -------------------------------------------------------------------------------- /subclrschm/lib/gui/global_setting_dialog.py: -------------------------------------------------------------------------------- 1 | """Global editor dialog.""" 2 | from __future__ import unicode_literals 3 | import wx 4 | from . import basic_dialogs 5 | from . import gui 6 | from . import settings_key_bindings 7 | from . import settings_codes as sc 8 | from ..rgba import RGBA 9 | from .. import util 10 | 11 | 12 | class GlobalEditor(gui.GlobalSetting, settings_key_bindings.SettingsKeyBindings): 13 | """GlobalEditor.""" 14 | 15 | def __init__(self, parent, current_entries, name, value, insert=False): 16 | """Initialize.""" 17 | 18 | super(GlobalEditor, self).__init__(parent) 19 | 20 | if util.platform() == "windows": 21 | self.SetDoubleBuffered(True) 22 | 23 | self.setup_keybindings() 24 | self.Fit() 25 | size = self.GetSize() 26 | self.SetMinSize(size) 27 | self.obj_key = name 28 | self.obj_val = value 29 | self.color_save = "" 30 | self.apply_settings = False 31 | self.color_setting = False 32 | self.m_color_picker.Disable() 33 | self.entries = current_entries 34 | self.current_name = name 35 | self.valid = True 36 | self.insert = bool(insert) 37 | 38 | self.m_name_textbox.SetValue(self.obj_key) 39 | try: 40 | RGBA(self.obj_val) 41 | self.color_setting = True 42 | self.color_save = self.obj_val 43 | self.m_color_picker.Enable() 44 | self.m_color_radio.SetValue(True) 45 | self.m_value_textbox.SetValue(self.obj_val) 46 | except: 47 | self.m_text_radio.SetValue(True) 48 | self.m_text_textbox.SetValue(self.obj_val) 49 | 50 | def on_color_button_click(self, event): 51 | """Handle color button click event.""" 52 | 53 | if not self.color_setting: 54 | event.Skip() 55 | return 56 | color = None 57 | data = wx.ColourData() 58 | data.SetChooseFull(True) 59 | 60 | alpha = None 61 | 62 | text = self.m_value_textbox.GetValue() 63 | rgb = RGBA(text) 64 | if len(text) == 9: 65 | alpha == text[7:9] 66 | 67 | # set the default color in the chooser 68 | data.SetColour(wx.Colour(rgb.r, rgb.g, rgb.b)) 69 | 70 | # construct the chooser 71 | dlg = wx.ColourDialog(self, data) 72 | 73 | if dlg.ShowModal() == wx.ID_OK: 74 | # set the panel background color 75 | color = dlg.GetColourData().GetColour().GetAsString(wx.C2S_HTML_SYNTAX) 76 | self.m_value_textbox.SetValue(color if alpha is None else color + alpha) 77 | dlg.Destroy() 78 | event.Skip() 79 | 80 | def on_radio_click(self, event): 81 | """Handle radio event.""" 82 | obj = event.GetEventObject() 83 | if obj is self.m_color_radio: 84 | self.m_text_textbox.Disable() 85 | self.m_value_textbox.Enable() 86 | self.m_color_picker.Enable() 87 | self.color_setting = True 88 | try: 89 | RGBA(self.m_value_textbox.GetValue()) 90 | self.on_color_change(event) 91 | except: 92 | self.m_value_textbox.SetValue("#000000") 93 | return 94 | else: 95 | self.color_setting = False 96 | self.m_color_picker.Disable() 97 | self.m_value_textbox.Disable() 98 | self.m_color_picker.SetBackgroundColour(wx.Colour(255, 255, 255)) 99 | self.m_text_textbox.Enable() 100 | self.m_color_picker.Refresh() 101 | event.Skip() 102 | 103 | def is_name_valid(self): 104 | """Check if name is valid.""" 105 | 106 | valid = True 107 | name = self.m_name_textbox.GetValue() 108 | if name != self.current_name: 109 | for k in self.entries: 110 | if name == k: 111 | valid = False 112 | break 113 | return valid 114 | 115 | def on_global_name_blur(self, event): 116 | """Handle global name blur event.""" 117 | 118 | if not self.is_name_valid(): 119 | basic_dialogs.errormsg( 120 | "Key name \"%s\" already exists in global settings. " 121 | "Please use a different name." % self.m_name_textbox.GetValue() 122 | ) 123 | self.m_name_textbox.SetValue(self.current_name) 124 | else: 125 | self.current_name = self.m_name_textbox.GetValue() 126 | 127 | def on_color_change(self, event): 128 | """Handle color change event.""" 129 | 130 | if not self.color_setting: 131 | event.Skip() 132 | return 133 | text = self.m_value_textbox.GetValue() 134 | try: 135 | cl = RGBA(text) 136 | except: 137 | event.Skip() 138 | return 139 | 140 | cl.apply_alpha(self.Parent.m_style_settings.bg_color.get_rgb()) 141 | bg = wx.Colour(cl.r, cl.g, cl.b) 142 | self.m_color_picker.SetBackgroundColour(bg) 143 | self.m_color_picker.Refresh() 144 | 145 | def on_color_focus(self, event): 146 | """Handle color focus event.""" 147 | 148 | if not self.color_setting: 149 | event.Skip() 150 | return 151 | if self.color_setting: 152 | self.color_save = self.m_value_textbox.GetValue() 153 | event.Skip() 154 | 155 | def on_color_blur(self, event): 156 | """Handle color blur event.""" 157 | 158 | if not self.color_setting: 159 | event.Skip() 160 | return 161 | if self.color_setting: 162 | text = self.m_value_textbox.GetValue() 163 | try: 164 | RGBA(text) 165 | except: 166 | self.m_value_textbox.SetValue(self.color_save) 167 | event.Skip() 168 | 169 | def on_apply_button_click(self, event): 170 | """Handle apply button click event.""" 171 | 172 | self.m_apply_button.SetFocus() 173 | if self.is_name_valid(): 174 | self.apply_settings = True 175 | self.Close() 176 | else: 177 | basic_dialogs.errormsg( 178 | "Key name \"%s\" already exists in global settings. " 179 | "Please use a different name." % self.m_name_textbox.GetValue() 180 | ) 181 | self.m_name_textbox.SetValue(self.current_name) 182 | 183 | def on_set_color_close(self, event): 184 | """Handle set color close event.""" 185 | 186 | self.obj_key = self.m_name_textbox.GetValue() 187 | self.current_name = self.obj_key 188 | 189 | if self.apply_settings: 190 | self.obj_val = ( 191 | self.m_value_textbox.GetValue() if self.m_color_radio.GetValue() else self.m_text_textbox.GetValue() 192 | ) 193 | 194 | if self.insert: 195 | grid = self.Parent.m_global_settings.m_plist_grid 196 | num = grid.GetNumberRows() 197 | row = grid.GetGridCursorRow() 198 | if num > 0: 199 | grid.InsertRows(row, 1, True) 200 | else: 201 | grid.AppendRows(1) 202 | row = 0 203 | grid.GetParent().update_row(row, self.obj_key, self.obj_val) 204 | grid.GetParent().go_cell(grid, row, 0) 205 | self.Parent.update_plist(sc.ADD, {"table": "global", "index": self.obj_key, "data": self.obj_val}) 206 | else: 207 | self.Parent.set_global_object(self.obj_key, self.obj_val) 208 | 209 | event.Skip() 210 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/global_settings_panel.py: -------------------------------------------------------------------------------- 1 | """Global settigns panel.""" 2 | from __future__ import unicode_literals 3 | import wx 4 | from . import gui 5 | from . import grid_helper 6 | from . import global_setting_dialog 7 | from . import settings_codes as sc 8 | from ..rgba import RGBA 9 | from ..x11colors import name2hex 10 | from .. import util 11 | 12 | 13 | class GlobalSettings(gui.GlobalSettingsPanel, grid_helper.GridHelper): 14 | """GlobalSettings.""" 15 | 16 | def __init__(self, parent, scheme, update, reshow): 17 | """Initialize.""" 18 | 19 | super(GlobalSettings, self).__init__(parent) 20 | if util.platform() == "windows": 21 | self.SetDoubleBuffered(False) 22 | self.diag = None 23 | self.setup_keybindings() 24 | self.parent = parent 25 | self.m_plist_grid.GetGridWindow().Bind(wx.EVT_MOTION, self.on_mouse_motion) 26 | self.m_plist_grid.SetDefaultCellBackgroundColour(self.GetBackgroundColour()) 27 | self.read_plist(scheme) 28 | self.reshow = reshow 29 | self.update_plist = update 30 | 31 | def read_plist(self, scheme): 32 | """Read plist to get global settings.""" 33 | 34 | color = scheme["settings"][0]["settings"].get("foreground", "#000000").strip() 35 | if not color.startswith('#'): 36 | color = name2hex(color) 37 | foreground = RGBA(color) 38 | color = scheme["settings"][0]["settings"].get("background", "#FFFFFF").strip() 39 | if not color.startswith('#'): 40 | color = name2hex(color) 41 | background = RGBA(color) 42 | self.bg_color = background 43 | self.fg_color = foreground 44 | count = 0 45 | 46 | for k in sorted(scheme["settings"][0]["settings"].keys()): 47 | v = scheme["settings"][0]["settings"][k].strip() 48 | self.m_plist_grid.AppendRows(1) 49 | if not v.startswith('#'): 50 | color = name2hex(v) 51 | if color is not None: 52 | v = color 53 | self.update_row(count, k, v) 54 | count += 1 55 | 56 | self.resize_table() 57 | 58 | self.go_cell(self.m_plist_grid, 0, 0) 59 | 60 | def resize_table(self): 61 | """Resize teh table.""" 62 | 63 | self.m_plist_grid.BeginBatch() 64 | nb_size = self.parent.GetSize() 65 | total_size = 0 66 | for x in range(0, 2): 67 | self.m_plist_grid.AutoSizeColumn(x) 68 | total_size += self.m_plist_grid.GetColSize(x) 69 | delta = nb_size[0] - 20 - total_size 70 | if delta > 0: 71 | self.m_plist_grid.SetColSize(1, self.m_plist_grid.GetColSize(1) + delta) 72 | self.m_plist_grid.EndBatch() 73 | 74 | def update_row(self, count, k, v): 75 | """Update row.""" 76 | 77 | try: 78 | bg = RGBA(v.strip()) 79 | if k != "background": 80 | bg.apply_alpha(self.bg_color.get_rgb()) 81 | fg = RGBA("#000000") if bg.get_luminance() > 128 else RGBA("#FFFFFF") 82 | except: 83 | bg = RGBA("#FFFFFF") 84 | fg = RGBA("#000000") 85 | 86 | self.m_plist_grid.SetCellValue(count, 0, k) 87 | self.m_plist_grid.SetCellValue(count, 1, v) 88 | 89 | b = self.m_plist_grid.GetCellBackgroundColour(count, 0) 90 | f = self.m_plist_grid.GetCellTextColour(count, 0) 91 | 92 | b.Set(bg.r, bg.g, bg.b) 93 | f.Set(fg.r, fg.g, fg.b) 94 | 95 | self.m_plist_grid.SetCellBackgroundColour(count, 0, b) 96 | self.m_plist_grid.SetCellBackgroundColour(count, 1, b) 97 | 98 | self.m_plist_grid.SetCellTextColour(count, 0, f) 99 | self.m_plist_grid.SetCellTextColour(count, 1, f) 100 | 101 | def set_object(self, key, value): 102 | """Set the object.""" 103 | 104 | row = self.m_plist_grid.GetGridCursorRow() 105 | col = self.m_plist_grid.GetGridCursorCol() 106 | self.update_row(row, key, value) 107 | self.update_plist(sc.MODIFY, {"table": "global", "index": key, "data": value}) 108 | if key == "background" or key == "foreground": 109 | self.reshow(row, col) 110 | self.resize_table() 111 | 112 | def delete_row(self): 113 | """Delete the row.""" 114 | 115 | row = self.m_plist_grid.GetGridCursorRow() 116 | col = self.m_plist_grid.GetGridCursorCol() 117 | name = self.m_plist_grid.GetCellValue(row, 0) 118 | self.m_plist_grid.DeleteRows(row, 1) 119 | self.m_plist_grid.GetParent().update_plist(sc.DELETE, {"table": "global", "index": name}) 120 | if name == "foreground" or name == "background": 121 | self.reshow(row, col) 122 | 123 | def validate_name(self, name): 124 | """Validate the name.""" 125 | 126 | valid = True 127 | editor = self.GetParent().GetParent().GetParent() 128 | for k in editor.scheme["settings"][0]["settings"]: 129 | if name == k: 130 | valid = False 131 | break 132 | return valid 133 | 134 | def insert_row(self): 135 | """Insert a new row.""" 136 | 137 | new_name = "new_item" 138 | count = 0 139 | while not self.validate_name(new_name): 140 | new_name = "new_item_%d" % count 141 | count += 1 142 | 143 | editor = self.GetParent().GetParent().GetParent() 144 | self.diag = global_setting_dialog.GlobalEditor( 145 | editor, 146 | editor.scheme["settings"][0]["settings"], 147 | new_name, 148 | "nothing", 149 | insert=True 150 | ) 151 | self.diag.ShowModal() 152 | self.diag.Destroy() 153 | self.diag = None 154 | 155 | def edit_cell(self): 156 | """Edit the cell.""" 157 | 158 | grid = self.m_plist_grid 159 | row = grid.GetGridCursorRow() 160 | editor = self.GetParent().GetParent().GetParent() 161 | self.diag = global_setting_dialog.GlobalEditor( 162 | editor, 163 | editor.scheme["settings"][0]["settings"], 164 | grid.GetCellValue(row, 0), 165 | grid.GetCellValue(row, 1) 166 | ) 167 | self.diag.ShowModal() 168 | self.diag.Destroy() 169 | self.diag = None 170 | 171 | def on_grid_label_left_click(self, event): 172 | """Handle grid label left click.""" 173 | 174 | return 175 | 176 | def on_mouse_motion(self, event): 177 | """Handle mouse motion event.""" 178 | 179 | self.mouse_motion(event) 180 | 181 | def on_edit_cell(self, event): 182 | """Handle edit cell event.""" 183 | 184 | self.edit_cell() 185 | 186 | def on_grid_key_down(self, event): 187 | """Handle grid key down event.""" 188 | 189 | self.grid_key_down(event) 190 | 191 | def on_grid_select_cell(self, event): 192 | """Handle grid select cell event.""" 193 | 194 | self.grid_select_cell(event) 195 | 196 | def on_row_add_click(self, event): 197 | """Handle add row click event.""" 198 | 199 | self.insert_row() 200 | 201 | def on_row_delete_click(self, event): 202 | """Handle delete row event.""" 203 | 204 | self.delete_row() 205 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/grid_helper.py: -------------------------------------------------------------------------------- 1 | """Helper for gird objects.""" 2 | from __future__ import unicode_literals 3 | import sys 4 | import wx 5 | from ..rgba import RGBA 6 | 7 | 8 | class GridHelper(object): 9 | """Grid helper.""" 10 | 11 | cell_select_semaphore = False 12 | range_semaphore = False 13 | current_row = None 14 | current_col = None 15 | 16 | def setup_keybindings(self): 17 | """Setup grid keybindings.""" 18 | 19 | deleteid = wx.NewId() 20 | insertid = wx.NewId() 21 | 22 | self.Bind(wx.EVT_MENU, self.on_delete_row, id=deleteid) 23 | self.Bind(wx.EVT_MENU, self.on_insert_row, id=insertid) 24 | 25 | accel_tbl = wx.AcceleratorTable( 26 | [ 27 | (wx.ACCEL_NORMAL, wx.WXK_DELETE, deleteid), 28 | (wx.ACCEL_CMD, ord('I'), insertid) if sys.platform == "darwin" else (wx.ACCEL_CTRL, ord('I'), insertid) 29 | ] 30 | ) 31 | self.SetAcceleratorTable(accel_tbl) 32 | 33 | def go_cell(self, grid, row, col, focus=False): 34 | """Go to cell.""" 35 | 36 | if focus: 37 | grid.GoToCell(row, col) 38 | else: 39 | grid.SetGridCursor(row, col) 40 | bg = grid.GetCellBackgroundColour(row, 0) 41 | lum = RGBA(bg.GetAsString(wx.C2S_HTML_SYNTAX)).get_luminance() 42 | if lum > 128: 43 | bg.Set(0, 0, 0) 44 | else: 45 | bg.Set(255, 255, 255) 46 | grid.SetCellHighlightColour(bg) 47 | 48 | def mouse_motion(self, event): 49 | """Capture if mouse is dragging.""" 50 | 51 | if event.Dragging(): # mouse being dragged? 52 | pass # eat the event 53 | else: 54 | event.Skip() # no dragging, pass on to the window 55 | 56 | def grid_key_down(self, event): 57 | """Check for certain key down events in the grid.""" 58 | 59 | no_modifiers = event.GetModifiers() == 0 60 | alt_mod = event.GetModifiers() == wx.MOD_ALT 61 | if no_modifiers and event.GetKeyCode() == ord('B'): 62 | self.toggle_bold() 63 | return 64 | elif no_modifiers and event.GetKeyCode() == ord('I'): 65 | self.toggle_italic() 66 | return 67 | elif no_modifiers and event.GetKeyCode() == ord('U'): 68 | self.toggle_underline() 69 | return 70 | elif no_modifiers and event.GetKeyCode() == wx.WXK_RETURN: 71 | self.edit_cell() 72 | return 73 | elif alt_mod and event.GetKeyCode() == wx.WXK_UP: 74 | self.row_up() 75 | return 76 | elif alt_mod and event.GetKeyCode() == wx.WXK_DOWN: 77 | self.row_down() 78 | return 79 | elif alt_mod and event.GetKeyCode() == wx.WXK_LEFT: 80 | self.on_panel_left(event) 81 | return 82 | elif alt_mod and event.GetKeyCode() == wx.WXK_RIGHT: 83 | self.on_panel_right(event) 84 | return 85 | elif event.AltDown(): 86 | # Eat...NOM NOM 87 | if event.GetKeyCode() == wx.WXK_UP: 88 | return 89 | elif event.GetKeyCode() == wx.WXK_DOWN: 90 | return 91 | elif event.GetKeyCode() == wx.WXK_LEFT: 92 | return 93 | elif event.GetKeyCode() == wx.WXK_RIGHT: 94 | return 95 | elif event.ShiftDown(): 96 | # Eat...NOM NOM 97 | if event.GetKeyCode() == wx.WXK_UP: 98 | return 99 | elif event.GetKeyCode() == wx.WXK_DOWN: 100 | return 101 | elif event.GetKeyCode() == wx.WXK_LEFT: 102 | return 103 | elif event.GetKeyCode() == wx.WXK_RIGHT: 104 | return 105 | event.Skip() 106 | 107 | def grid_select_cell(self, event): 108 | """Grid cell selected.""" 109 | 110 | grid = self.m_plist_grid 111 | if not self.cell_select_semaphore and event.Selecting(): 112 | self.cell_select_semaphore = True 113 | self.current_row = event.GetRow() 114 | self.current_col = event.GetCol() 115 | self.go_cell(grid, self.current_row, self.current_col) 116 | self.cell_select_semaphore = False 117 | event.Skip() 118 | 119 | def on_panel_left(self, event): 120 | """Handle left key press.""" 121 | 122 | grid = self.m_plist_grid 123 | grid.GetParent().GetParent().ChangeSelection(0) 124 | grid.GetParent().GetParent().GetPage(0).m_plist_grid.SetFocus() 125 | 126 | def on_panel_right(self, event): 127 | """Handle right key press.""" 128 | 129 | grid = self.m_plist_grid 130 | grid.GetParent().GetParent().ChangeSelection(1) 131 | grid.GetParent().GetParent().GetPage(1).m_plist_grid.SetFocus() 132 | 133 | def on_row_up(self, event): 134 | """Handle row up.""" 135 | 136 | self.row_up() 137 | 138 | def on_row_down(self, event): 139 | """Handle row down.""" 140 | 141 | self.row_down() 142 | 143 | def on_insert_row(self, event): 144 | """Handle insert row.""" 145 | 146 | self.insert_row() 147 | 148 | def on_delete_row(self, event): 149 | """Handle delete row.""" 150 | 151 | self.delete_row() 152 | 153 | def on_edit_cell_key(self, event): 154 | """Handle edit cell key.""" 155 | 156 | self.edit_cell() 157 | 158 | def on_toggle_bold(self, event): 159 | """Handle bold event.""" 160 | 161 | self.toggle_bold() 162 | 163 | def on_toggle_italic(self, event): 164 | """Handle italic event.""" 165 | 166 | self.toggle_italic() 167 | 168 | def on_toggle_underline(self, event): 169 | """Handle underline event.""" 170 | 171 | self.toggle_underline() 172 | 173 | def toggle_bold(self): 174 | """Override for toggle bold.""" 175 | 176 | def toggle_italic(self): 177 | """Override for toggle italic.""" 178 | 179 | def toggle_underline(self): 180 | """Override for toggle underline.""" 181 | 182 | def row_up(self): 183 | """Override row up.""" 184 | 185 | def row_down(self): 186 | """Override for row down.""" 187 | 188 | def edit_cell(self): 189 | """Override for edit cell.""" 190 | 191 | def delete_row(self): 192 | """Override for delete row.""" 193 | 194 | def insert_row(self): 195 | """Override for insert tow.""" 196 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/platform_window_focus.py: -------------------------------------------------------------------------------- 1 | """Platform window focus.""" 2 | from __future__ import unicode_literals 3 | import ctypes 4 | import ctypes.util 5 | from .. import util 6 | 7 | if util.platform() == "osx": 8 | appkit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('AppKit')) 9 | objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) 10 | 11 | objc.objc_getClass.restype = ctypes.c_void_p 12 | objc.sel_registerName.restype = ctypes.c_void_p 13 | objc.objc_msgSend.restype = ctypes.c_void_p 14 | objc.objc_msgSend.argtypes = [ctypes.c_void_p, ctypes.c_void_p] 15 | 16 | 17 | def platform_window_focus(frame): 18 | """Set focus to the window frame.""" 19 | 20 | # General window raising 21 | if frame.IsIconized(): 22 | frame.Iconize(False) 23 | if not frame.IsShown(): 24 | frame.Show(True) 25 | frame.Raise() 26 | 27 | # OSX specific extra to ensure raise 28 | if util.platform() == "osx": 29 | try: 30 | nsapplication = ctypes.c_void_p(objc.objc_getClass('NSApplication')) 31 | nsapp = ctypes.c_void_p(objc.objc_msgSend(nsapplication, objc.sel_registerName('sharedApplication'))) 32 | objc.objc_msgSend(nsapp, objc.sel_registerName('activateIgnoringOtherApps:'), True) 33 | except Exception: 34 | # Failed to bring window to top in OSX 35 | pass 36 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/settings_codes.py: -------------------------------------------------------------------------------- 1 | """Settings codes.""" 2 | ADD = 1 3 | DELETE = 2 4 | MODIFY = 3 5 | MOVE = 4 6 | UUID = 5 7 | NAME = 6 8 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/settings_key_bindings.py: -------------------------------------------------------------------------------- 1 | """Settings key bindings.""" 2 | from __future__ import unicode_literals 3 | import wx 4 | 5 | 6 | class SettingsKeyBindings(object): 7 | """Key binding for settings.""" 8 | 9 | def setup_keybindings(self): 10 | """Setup the key bindings.""" 11 | self.Bind(wx.EVT_CHAR_HOOK, self.on_char_hook) 12 | 13 | def on_char_hook(self, event): 14 | """Evaluate keycode on char hook.""" 15 | 16 | if event.GetKeyCode() == wx.WXK_ESCAPE: 17 | self.Close() 18 | event.Skip() 19 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/simplelog.py: -------------------------------------------------------------------------------- 1 | """ 2 | Simple Log. 3 | 4 | Licensed under MIT 5 | Copyright (c) 2013 - 2015 Isaac Muse 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 8 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 10 | and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 16 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 18 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | """ 21 | from __future__ import unicode_literals 22 | import sys 23 | import codecs 24 | import threading 25 | from .. import util 26 | 27 | ALL = 0 28 | DEBUG = 10 29 | INFO = 20 30 | WARNING = 30 31 | ERROR = 40 32 | CRITICAL = 50 33 | 34 | global_log = None 35 | 36 | 37 | class Log(object): 38 | """Log.""" 39 | 40 | def __init__(self, filename=None, fmt="%(message)s", level=ERROR, filemode="w"): 41 | """Init Log object.""" 42 | 43 | self._lock = threading.Lock() 44 | try: 45 | self.encoding = sys.stdout.encoding if sys.stdout.encoding is not None else 'ascii' 46 | except AttributeError: 47 | self.encoding = 'ascii' 48 | if filemode == "w": 49 | with codecs.open(filename, "w", "utf-8") as f: 50 | f.write("") 51 | self.filename = filename 52 | self.level = level 53 | self.format = fmt 54 | self.save_to_file = self.filename is not None 55 | self.echo = not self.save_to_file 56 | 57 | def set_echo(self, enable): 58 | """Turn on/off echoing to std out.""" 59 | 60 | with self._lock: 61 | if self.save_to_file: 62 | self.echo = bool(enable) 63 | 64 | def set_level(self, level): 65 | """Set log level.""" 66 | 67 | with self._lock: 68 | self.level = int(level) 69 | 70 | def get_level(self): 71 | """Get log level.""" 72 | 73 | return self.level 74 | 75 | def formatter(self, lvl, log_fmt, msg, msg_fmt=None): 76 | """Special formatters for log message.""" 77 | 78 | return log_fmt % { 79 | "loglevel": lvl, 80 | "message": util.to_ustr(msg if msg_fmt is None else msg_fmt(msg)) 81 | } 82 | 83 | def debug(self, msg, log_fmt="%(loglevel)s: %(message)s\n", echo=True, msg_fmt=None): 84 | """Debug level logging.""" 85 | 86 | if self.level <= DEBUG: 87 | self._log(self.formatter("DEBUG", log_fmt, msg, msg_fmt), echo) 88 | 89 | def info(self, msg, log_fmt="%(loglevel)s: %(message)s\n", echo=True, msg_fmt=None): 90 | """Info level logging.""" 91 | 92 | if self.level <= INFO: 93 | self._log(self.formatter("INFO", log_fmt, msg, msg_fmt), echo) 94 | 95 | def warning(self, msg, log_fmt="%(loglevel)s: %(message)s\n", echo=True, msg_fmt=None): 96 | """Warning level logging.""" 97 | 98 | if self.level <= WARNING: 99 | self._log(self.formatter("WARNING", log_fmt, msg, msg_fmt), echo) 100 | 101 | def error(self, msg, log_fmt="%(loglevel)s: %(message)s\n", echo=True, msg_fmt=None): 102 | """Error level logging.""" 103 | 104 | if self.level <= ERROR: 105 | self._log(self.formatter("ERROR", log_fmt, msg, msg_fmt), echo) 106 | 107 | def critical(self, msg, log_fmt="%(loglevel)s: %(message)s\n", echo=True, msg_fmt=None): 108 | """Critical level logging.""" 109 | 110 | if self.level <= CRITICAL: 111 | self._log(self.formater("CRITICAL", log_fmt, msg, msg_fmt), echo) 112 | 113 | def _log(self, msg, echo=True): 114 | """Base logger.""" 115 | 116 | if not (echo and self.echo) and self.save_to_file: 117 | with self._lock: 118 | with codecs.open(self.filename, "a", "utf-8") as f: 119 | f.write(self.format % {"message": msg}) 120 | if echo and self.echo: 121 | try: 122 | print((self.format % {"message": msg})) 123 | except UnicodeEncodeError: 124 | print((self.format % {"message": msg}).encode(self.encoding, 'replace').decode(self.encoding)) 125 | 126 | def read(self): 127 | """Read the log.""" 128 | 129 | txt = "" 130 | with self._lock: 131 | try: 132 | with codecs.open(self.filename, "r", "utf-8") as f: 133 | txt = f.read() 134 | except Exception: 135 | pass 136 | return txt 137 | 138 | 139 | def init_global_log(file_name, level=ERROR): 140 | """Initialize a global log object.""" 141 | 142 | global global_log 143 | global_log = Log(file_name, level=level) 144 | 145 | 146 | def get_global_log(): 147 | """Get the global log object.""" 148 | 149 | return global_log 150 | -------------------------------------------------------------------------------- /subclrschm/lib/gui/style_settings_panel.py: -------------------------------------------------------------------------------- 1 | """Style settings panel.""" 2 | from __future__ import unicode_literals 3 | import wx 4 | from . import gui 5 | from . import color_setting_dialog 6 | from . import grid_helper 7 | from . import settings_codes as sc 8 | from ..rgba import RGBA 9 | from ..x11colors import name2hex 10 | from .. import util 11 | 12 | 13 | class StyleSettings(gui.StyleSettingsPanel, grid_helper.GridHelper): 14 | """Style settings handler.""" 15 | 16 | def __init__(self, parent, scheme, update): 17 | """Initialize.""" 18 | 19 | super(StyleSettings, self).__init__(parent) 20 | if util.platform() == "windows": 21 | self.SetDoubleBuffered(False) 22 | self.diag = None 23 | self.setup_keybindings() 24 | self.parent = parent 25 | self.m_plist_grid.GetGridWindow().Bind(wx.EVT_MOTION, self.on_mouse_motion) 26 | self.m_plist_grid.SetDefaultCellBackgroundColour(self.GetBackgroundColour()) 27 | self.read_plist(scheme) 28 | self.update_plist = update 29 | 30 | def read_plist(self, scheme): 31 | """Read the plist.""" 32 | 33 | color = scheme["settings"][0]["settings"].get("foreground", "#000000").strip() 34 | if not color.startswith('#'): 35 | color = name2hex(color) 36 | foreground = RGBA(color) 37 | color = scheme["settings"][0]["settings"].get("background", "#FFFFFF").strip() 38 | if not color.startswith('#'): 39 | color = name2hex(color) 40 | background = RGBA(color) 41 | self.bg_color = background 42 | self.fg_color = foreground 43 | count = 0 44 | 45 | for s in scheme["settings"]: 46 | if "name" in s: 47 | self.m_plist_grid.AppendRows(1) 48 | self.update_row(count, s) 49 | count += 1 50 | self.resize_table() 51 | self.go_cell(self.m_plist_grid, 0, 0) 52 | 53 | def update_row(self, count, s): 54 | """Update stye row.""" 55 | 56 | self.m_plist_grid.SetCellValue(count, 0, s["name"]) 57 | self.m_plist_grid.SetCellValue(count, 4, s.get("scope", "")) 58 | settings = s["settings"] 59 | b = self.m_plist_grid.GetCellBackgroundColour(count, 0) 60 | if "background" in settings: 61 | try: 62 | named_color = name2hex(settings["background"].strip()) 63 | color = named_color if named_color is not None else settings["background"].strip() 64 | bg = RGBA(color) 65 | bg.apply_alpha(self.bg_color.get_rgb()) 66 | self.m_plist_grid.SetCellValue(count, 2, color) 67 | except: 68 | bg = self.bg_color 69 | self.m_plist_grid.SetCellValue(count, 2, "") 70 | else: 71 | bg = self.bg_color 72 | b = self.m_plist_grid.GetCellBackgroundColour(count, 0) 73 | b.Set(bg.r, bg.g, bg.b) 74 | self.m_plist_grid.SetCellBackgroundColour(count, 0, b) 75 | self.m_plist_grid.SetCellBackgroundColour(count, 1, b) 76 | self.m_plist_grid.SetCellBackgroundColour(count, 2, b) 77 | self.m_plist_grid.SetCellBackgroundColour(count, 3, b) 78 | self.m_plist_grid.SetCellBackgroundColour(count, 4, b) 79 | if "foreground" in settings: 80 | try: 81 | named_color = name2hex(settings["foreground"].strip()) 82 | color = named_color if named_color is not None else settings["foreground"].strip() 83 | fg = RGBA(color) 84 | fg.apply_alpha(self.bg_color.get_rgb()) 85 | self.m_plist_grid.SetCellValue(count, 1, color) 86 | except: 87 | fg = self.fg_color 88 | self.m_plist_grid.SetCellValue(count, 1, "") 89 | else: 90 | fg = self.fg_color 91 | f = self.m_plist_grid.GetCellTextColour(count, 0) 92 | f.Set(fg.r, fg.g, fg.b) 93 | self.m_plist_grid.SetCellTextColour(count, 0, f) 94 | self.m_plist_grid.SetCellTextColour(count, 1, f) 95 | self.m_plist_grid.SetCellTextColour(count, 2, f) 96 | self.m_plist_grid.SetCellTextColour(count, 3, f) 97 | self.m_plist_grid.SetCellTextColour(count, 4, f) 98 | 99 | fs_setting = settings.get("fontStyle", "") 100 | font_style = [] 101 | for x in fs_setting.split(" "): 102 | if x in ["bold", "italic", "underline"]: 103 | font_style.append(x) 104 | 105 | self.m_plist_grid.SetCellValue(count, 3, " ".join(font_style)) 106 | fs = self.m_plist_grid.GetCellFont(count, 0) 107 | fs.SetWeight(wx.FONTWEIGHT_NORMAL) 108 | fs.SetStyle(wx.FONTSTYLE_NORMAL) 109 | fs.SetUnderlined(False) 110 | 111 | if "bold" in font_style: 112 | fs.SetWeight(wx.FONTWEIGHT_BOLD) 113 | 114 | if "italic" in font_style: 115 | fs.SetStyle(wx.FONTSTYLE_ITALIC) 116 | 117 | if "underline" in font_style: 118 | fs.SetUnderlined(True) 119 | 120 | self.m_plist_grid.SetCellFont(count, 0, fs) 121 | self.m_plist_grid.SetCellFont(count, 1, fs) 122 | self.m_plist_grid.SetCellFont(count, 2, fs) 123 | self.m_plist_grid.SetCellFont(count, 3, fs) 124 | self.m_plist_grid.SetCellFont(count, 4, fs) 125 | 126 | def resize_table(self): 127 | """Resize the table.""" 128 | 129 | self.m_plist_grid.BeginBatch() 130 | nb_size = self.parent.GetSize() 131 | total_size = 0 132 | for x in range(0, 5): 133 | self.m_plist_grid.AutoSizeColumn(x) 134 | total_size += self.m_plist_grid.GetColSize(x) 135 | delta = nb_size[0] - 20 - total_size 136 | if delta > 0: 137 | self.m_plist_grid.SetColSize(4, self.m_plist_grid.GetColSize(4) + delta) 138 | self.m_plist_grid.EndBatch() 139 | 140 | def set_object(self, obj): 141 | """Set the object.""" 142 | 143 | row = self.m_plist_grid.GetGridCursorRow() 144 | self.update_row(row, obj) 145 | self.update_plist(sc.MODIFY, {"table": "style", "index": row, "data": obj}) 146 | self.resize_table() 147 | 148 | def edit_cell(self): 149 | """Handle editting the cell.""" 150 | 151 | grid = self.m_plist_grid 152 | row = grid.GetGridCursorRow() 153 | editor = self.GetParent().GetParent().GetParent() 154 | self.diag = color_setting_dialog.ColorEditor( 155 | editor, 156 | { 157 | "name": grid.GetCellValue(row, 0), 158 | "scope": grid.GetCellValue(row, 4), 159 | "settings": { 160 | "foreground": grid.GetCellValue(row, 1), 161 | "background": grid.GetCellValue(row, 2), 162 | "fontStyle": grid.GetCellValue(row, 3) 163 | } 164 | } 165 | ) 166 | self.diag.ShowModal() 167 | self.diag.Destroy() 168 | self.diag = None 169 | 170 | def delete_row(self): 171 | """Handle row delete.""" 172 | 173 | row = self.m_plist_grid.GetGridCursorRow() 174 | self.m_plist_grid.DeleteRows(row, 1) 175 | self.m_plist_grid.GetParent().update_plist(sc.DELETE, {"table": "style", "index": row}) 176 | 177 | def insert_row(self): 178 | """Handle inserting into row.""" 179 | 180 | obj = { 181 | "name": "New Item", 182 | "scope": "comment", 183 | "settings": { 184 | "foreground": "#FFFFFF", 185 | "background": "#000000", 186 | "fontStyle": "" 187 | } 188 | } 189 | editor = self.GetParent().GetParent().GetParent() 190 | self.diag = color_setting_dialog.ColorEditor( 191 | editor, 192 | obj, 193 | insert=True 194 | ) 195 | self.diag.ShowModal() 196 | self.diag.Destroy() 197 | self.diag = None 198 | 199 | def row_up(self): 200 | """Handle row up.""" 201 | 202 | grid = self.m_plist_grid 203 | row = grid.GetGridCursorRow() 204 | col = grid.GetGridCursorCol() 205 | if row > 0: 206 | text = [grid.GetCellValue(row, x) for x in range(0, 5)] 207 | bg = [grid.GetCellBackgroundColour(row, x) for x in range(0, 5)] 208 | fg = [grid.GetCellTextColour(row, x) for x in range(0, 5)] 209 | font = [grid.GetCellFont(row, x) for x in range(0, 5)] 210 | grid.DeleteRows(row, 1, False) 211 | grid.InsertRows(row - 1, 1, True) 212 | [grid.SetCellValue(row - 1, x, text[x]) for x in range(0, 5)] 213 | [grid.SetCellBackgroundColour(row - 1, x, bg[x]) for x in range(0, 5)] 214 | [grid.SetCellTextColour(row - 1, x, fg[x]) for x in range(0, 5)] 215 | [grid.SetCellFont(row - 1, x, font[x]) for x in range(0, 5)] 216 | self.go_cell(grid, row - 1, col, True) 217 | grid.GetParent().update_plist(sc.MOVE, {"from": row, "to": row - 1}) 218 | grid.SetFocus() 219 | 220 | def row_down(self): 221 | """Handle row down.""" 222 | 223 | grid = self.m_plist_grid 224 | row = grid.GetGridCursorRow() 225 | col = grid.GetGridCursorCol() 226 | if row < grid.GetNumberRows() - 1: 227 | text = [grid.GetCellValue(row, x) for x in range(0, 5)] 228 | bg = [grid.GetCellBackgroundColour(row, x) for x in range(0, 5)] 229 | fg = [grid.GetCellTextColour(row, x) for x in range(0, 5)] 230 | font = [grid.GetCellFont(row, x) for x in range(0, 5)] 231 | grid.DeleteRows(row, 1, False) 232 | grid.InsertRows(row + 1, 1, True) 233 | [grid.SetCellValue(row + 1, x, text[x]) for x in range(0, 5)] 234 | [grid.SetCellBackgroundColour(row + 1, x, bg[x]) for x in range(0, 5)] 235 | [grid.SetCellTextColour(row + 1, x, fg[x]) for x in range(0, 5)] 236 | [grid.SetCellFont(row + 1, x, font[x]) for x in range(0, 5)] 237 | self.go_cell(grid, row + 1, col, True) 238 | grid.GetParent().update_plist(sc.MOVE, {"from": row, "to": row + 1}) 239 | grid.SetFocus() 240 | 241 | def is_fontstyle_cell(self): 242 | """Check if fontstyle cell.""" 243 | 244 | return self.m_plist_grid.GetGridCursorCol() == 3 245 | 246 | def toggle_font_style(self, row, attr): 247 | """Toggle the font style.""" 248 | 249 | # if not self.is_fontstyle_cell(): 250 | # return 251 | grid = self.m_plist_grid 252 | text = [grid.GetCellValue(row, x) for x in range(0, 5)] 253 | style = text[3].split(" ") 254 | try: 255 | idx = style.index(attr) 256 | del style[idx] 257 | except: 258 | style.append(attr) 259 | text[3] = " ".join(style) 260 | 261 | obj = { 262 | "name": text[0], 263 | "scope": text[4], 264 | "settings": { 265 | "foreground": text[1], 266 | "background": text[2], 267 | "fontStyle": text[3] 268 | } 269 | } 270 | grid.GetParent().update_row(row, obj) 271 | self.update_plist(sc.MODIFY, {"table": "style", "index": row, "data": obj}) 272 | self.resize_table() 273 | 274 | def toggle_bold(self): 275 | """Toggle bold.""" 276 | 277 | self.toggle_font_style(self.m_plist_grid.GetGridCursorRow(), "bold") 278 | 279 | def toggle_italic(self): 280 | """Toggle italic.""" 281 | 282 | self.toggle_font_style(self.m_plist_grid.GetGridCursorRow(), "italic") 283 | 284 | def toggle_underline(self): 285 | """Toggle underline.""" 286 | 287 | self.toggle_font_style(self.m_plist_grid.GetGridCursorRow(), "underline") 288 | 289 | def on_mouse_motion(self, event): 290 | """Handle mouse motion event.""" 291 | 292 | self.mouse_motion(event) 293 | 294 | def on_edit_cell(self, event): 295 | """Handle editing cell event.""" 296 | 297 | self.edit_cell() 298 | 299 | def on_grid_key_down(self, event): 300 | """Handle key down event on grid.""" 301 | 302 | self.grid_key_down(event) 303 | 304 | def on_grid_select_cell(self, event): 305 | """Handle grid select event.""" 306 | 307 | self.grid_select_cell(event) 308 | 309 | def on_row_up_click(self, event): 310 | """Handle row up click.""" 311 | 312 | self.row_up() 313 | 314 | def on_row_down_click(self, event): 315 | """Handle row down click.""" 316 | 317 | self.row_down() 318 | 319 | def on_row_add_click(self, event): 320 | """Handle row add click.""" 321 | 322 | self.insert_row() 323 | 324 | def on_row_delete_click(self, event): 325 | """Handle row delete click.""" 326 | 327 | self.delete_row() 328 | 329 | def on_grid_label_left_click(self, event): 330 | """Handle grid label left click.""" 331 | 332 | return 333 | -------------------------------------------------------------------------------- /subclrschm/lib/parse_args.py: -------------------------------------------------------------------------------- 1 | """Parse arguments.""" 2 | from __future__ import unicode_literals 3 | import argparse 4 | from . import util 5 | from . import __meta__ 6 | 7 | 8 | def parse_arguments(args=None): 9 | """Parse the command arguments.""" 10 | 11 | parser = argparse.ArgumentParser( 12 | prog='subclrschm', 13 | description='Sublime Color Scheme Editor - Edit Sublime Color Scheme' 14 | ) 15 | # Flag arguments 16 | parser.add_argument('--version', action='version', version=('%(prog)s ' + __meta__.__version__)) 17 | parser.add_argument('--debug', action='store_true', default=False, help=argparse.SUPPRESS) 18 | parser.add_argument('--no-redirect', action='store_true', default=False, help=argparse.SUPPRESS) 19 | parser.add_argument('--multi-instance', '-m', action='store_true', default=False, help="Allow multiple instances") 20 | parser.add_argument( 21 | '--log', '-l', nargs='?', default='', 22 | help="Absolute path to directory to store log file" 23 | ) 24 | parser.add_argument('--live-save', '-L', action='store_true', default=False, help="Enable live save.") 25 | # Mutually exclusinve flags 26 | group = parser.add_mutually_exclusive_group() 27 | group.add_argument('--select', '-s', action='store_true', default=False, help="Prompt for theme selection") 28 | group.add_argument('--new', '-n', action='store_true', default=False, help="Open prompting for new theme to create") 29 | # Positional 30 | parser.add_argument('file', nargs='?', default=None, help='Theme file') 31 | return parser.parse_args(util.to_unicode_argv()[1:] if args is None else args) 32 | -------------------------------------------------------------------------------- /subclrschm/lib/rgba.py: -------------------------------------------------------------------------------- 1 | """ 2 | RGBA. 3 | 4 | Licensed under MIT 5 | Copyright (c) 2012 - 2016 Isaac Muse 6 | """ 7 | import re 8 | from colorsys import rgb_to_hls, hls_to_rgb, rgb_to_hsv, hsv_to_rgb 9 | import decimal 10 | 11 | RGB_CHANNEL_SCALE = 1.0 / 255.0 12 | HUE_SCALE = 1.0 / 360.0 13 | 14 | 15 | def clamp(value, mn, mx): 16 | """Clamp the value to the the given minimum and maximum.""" 17 | 18 | return max(min(value, mx), mn) 19 | 20 | 21 | def round_int(dec): 22 | """Round float to nearest int using expected rounding.""" 23 | 24 | return int(decimal.Decimal(dec).quantize(decimal.Decimal('0'), decimal.ROUND_HALF_UP)) 25 | 26 | 27 | class RGBA(object): 28 | """RGBA object for converting between color formats or applying filters to the color.""" 29 | 30 | r = None 31 | g = None 32 | b = None 33 | a = None 34 | color_pattern = re.compile(r"^#(?:([A-Fa-f\d]{6})([A-Fa-f\d]{2})?|([A-Fa-f\d]{3}))") 35 | 36 | def __init__(self, s=None): 37 | """Initialize.""" 38 | 39 | if s is None: 40 | s = "#000000FF" 41 | self.r, self.g, self.b, self.a = self._split_channels(s) 42 | 43 | def _split_channels(self, s): 44 | """Split the color into color channels: red, green, blue, alpha.""" 45 | 46 | def alpha_channel(alpha): 47 | """Get alpha channel.""" 48 | return int(alpha, 16) if alpha else 0xFF 49 | 50 | m = self.color_pattern.match(s) 51 | assert(m is not None) 52 | if m.group(1): 53 | return int(s[1:3], 16), int(s[3:5], 16), int(s[5:7], 16), alpha_channel(m.group(2)) 54 | else: 55 | return int(s[1] * 2, 16), int(s[2] * 2, 16), int(s[3] * 2, 16), 0xFF 56 | 57 | def get_rgba(self): 58 | """Get the RGB color with the alpha channel.""" 59 | 60 | return "#%02X%02X%02X%02X" % (self.r, self.g, self.b, self.a) 61 | 62 | def get_rgb(self): 63 | """Get the RGB valuie.""" 64 | 65 | return "#%02X%02X%02X" % (self.r, self.g, self.b) 66 | 67 | def apply_alpha(self, background="#000000FF"): 68 | """ 69 | Apply the given transparency with the given background. 70 | 71 | This gives a color that represents what the eye sees with 72 | the transparent color against the given background. 73 | """ 74 | 75 | def tx_alpha(cf, af, cb, ab): 76 | """Translate the color channel with the alpha channel and background channel color.""" 77 | 78 | return round_int( 79 | abs( 80 | cf * (af * RGB_CHANNEL_SCALE) + cb * (ab * RGB_CHANNEL_SCALE) * (1 - (af * RGB_CHANNEL_SCALE)) 81 | ) 82 | ) & 0xFF 83 | 84 | if self.a < 0xFF: 85 | r, g, b, a = self._split_channels(background) 86 | 87 | self.r = tx_alpha(self.r, self.a, r, a) 88 | self.g = tx_alpha(self.g, self.a, g, a) 89 | self.b = tx_alpha(self.b, self.a, b, a) 90 | 91 | return self.get_rgb() 92 | 93 | def get_luminance(self): 94 | """Get percieved luminance.""" 95 | 96 | return clamp(round_int(0.299 * self.r + 0.587 * self.g + 0.114 * self.b), 0, 255) 97 | 98 | def get_true_luminance(self): 99 | """Get true liminance.""" 100 | 101 | l = self.tohls()[1] 102 | return clamp(round_int(l * 255.0), 0, 255) 103 | 104 | def alpha(self, factor): 105 | """Adjust alpha.""" 106 | 107 | self.a = round_int(clamp(self.a + (255.0 * factor) - 255.0, 0.0, 255.0)) 108 | 109 | def red(self, factor): 110 | """Adjust red.""" 111 | 112 | self.r = round_int(clamp(self.r + (255.0 * factor) - 255.0, 0.0, 255.0)) 113 | 114 | def green(self, factor): 115 | """Adjust green.""" 116 | 117 | self.g = round_int(clamp(self.g + (255.0 * factor) - 255.0, 0.0, 255.0)) 118 | 119 | def blue(self, factor): 120 | """Adjust blue.""" 121 | 122 | self.b = round_int(clamp(self.b + (255.0 * factor) - 255.0, 0.0, 255.0)) 123 | 124 | def luminance(self, factor): 125 | """Get true luminance.""" 126 | 127 | h, l, s = self.tohls() 128 | l = clamp(l + factor - 1.0, 0.0, 1.0) 129 | self.fromhls(h, l, s) 130 | 131 | def tohsv(self): 132 | """Convert to HSV color format.""" 133 | 134 | return rgb_to_hsv(self.r * RGB_CHANNEL_SCALE, self.g * RGB_CHANNEL_SCALE, self.b * RGB_CHANNEL_SCALE) 135 | 136 | def fromhsv(self, h, s, v): 137 | """Convert to RGB from HSV.""" 138 | 139 | r, g, b = hsv_to_rgb(h, s, v) 140 | self.r = round_int(r * 255.0) & 0xFF 141 | self.g = round_int(g * 255.0) & 0xFF 142 | self.b = round_int(b * 255.0) & 0xFF 143 | 144 | def tohls(self): 145 | """Convert to HLS color format.""" 146 | 147 | return rgb_to_hls(self.r * RGB_CHANNEL_SCALE, self.g * RGB_CHANNEL_SCALE, self.b * RGB_CHANNEL_SCALE) 148 | 149 | def fromhls(self, h, l, s): 150 | """Convert to RGB from HSL.""" 151 | 152 | r, g, b = hls_to_rgb(h, l, s) 153 | self.r = round_int(r * 255.0) & 0xFF 154 | self.g = round_int(g * 255.0) & 0xFF 155 | self.b = round_int(b * 255.0) & 0xFF 156 | 157 | def tohwb(self): 158 | """Convert to HWB from RGB.""" 159 | 160 | h, s, v = self.tohsv() 161 | w = (1.0 - s) * v 162 | b = 1.0 - v 163 | return h, w, b 164 | 165 | def fromhwb(self, h, w, b): 166 | """Convert to RGB from HWB.""" 167 | 168 | # Normalize white and black 169 | # w + b <= 1.0 170 | if w + b > 1.0: 171 | norm_factor = 1.0 / (w + b) 172 | w *= norm_factor 173 | b *= norm_factor 174 | 175 | # Convert to HSV and then to RGB 176 | s = 1.0 - (w / (1.0 - b)) 177 | v = 1.0 - b 178 | r, g, b = hsv_to_rgb(h, s, v) 179 | self.r = round_int(r * 255.0) & 0xFF 180 | self.g = round_int(g * 255.0) & 0xFF 181 | self.b = round_int(b * 255.0) & 0xFF 182 | 183 | def colorize(self, deg): 184 | """Colorize the color with the given hue.""" 185 | 186 | h, l, s = self.tohls() 187 | h = clamp(deg * HUE_SCALE, 0.0, 1.0) 188 | self.fromhls(h, l, s) 189 | 190 | def hue(self, deg): 191 | """Shift the hue.""" 192 | 193 | d = deg * HUE_SCALE 194 | h, l, s = self.tohls() 195 | h = h + d 196 | while h > 1.0: 197 | h -= 1.0 198 | while h < 0.0: 199 | h += 1.0 200 | self.fromhls(h, l, s) 201 | 202 | def invert(self): 203 | """Invert the color.""" 204 | 205 | self.r ^= 0xFF 206 | self.g ^= 0xFF 207 | self.b ^= 0xFF 208 | 209 | def saturation(self, factor): 210 | """Saturate or unsaturate the color by the given factor.""" 211 | 212 | h, l, s = self.tohls() 213 | s = clamp(s + factor - 1.0, 0.0, 1.0) 214 | self.fromhls(h, l, s) 215 | 216 | def grayscale(self): 217 | """Convert the color with a grayscale filter.""" 218 | 219 | luminance = self.get_luminance() & 0xFF 220 | self.r = luminance 221 | self.g = luminance 222 | self.b = luminance 223 | 224 | def sepia(self): 225 | """Apply a sepia filter to the color.""" 226 | 227 | r = clamp(round_int((self.r * .393) + (self.g * .769) + (self.b * .189)), 0, 255) & 0xFF 228 | g = clamp(round_int((self.r * .349) + (self.g * .686) + (self.b * .168)), 0, 255) & 0xFF 229 | b = clamp(round_int((self.r * .272) + (self.g * .534) + (self.b * .131)), 0, 255) & 0xFF 230 | self.r, self.g, self.b = r, g, b 231 | 232 | def _get_overage(self, c): 233 | """Get overage.""" 234 | 235 | if c < 0.0: 236 | o = 0.0 + c 237 | c = 0.0 238 | elif c > 255.0: 239 | o = c - 255.0 240 | c = 255.0 241 | else: 242 | o = 0.0 243 | return o, c 244 | 245 | def _distribute_overage(self, c, o, s): 246 | """Distribute overage.""" 247 | 248 | channels = len(s) 249 | if channels == 0: 250 | return c 251 | parts = o / len(s) 252 | if "r" in s and "g" in s: 253 | c = c[0] + parts, c[1] + parts, c[2] 254 | elif "r" in s and "b" in s: 255 | c = c[0] + parts, c[1], c[2] + parts 256 | elif "g" in s and "b" in s: 257 | c = c[0], c[1] + parts, c[2] + parts 258 | elif "r" in s: 259 | c = c[0] + parts, c[1], c[2] 260 | elif "g" in s: 261 | c = c[0], c[1] + parts, c[2] 262 | else: # "b" in s: 263 | c = c[0], c[1], c[2] + parts 264 | return c 265 | 266 | def brightness(self, factor): 267 | """ 268 | Adjust the brightness by the given factor. 269 | 270 | Brightness is determined by percieved luminance. 271 | """ 272 | 273 | channels = ["r", "g", "b"] 274 | total_lumes = clamp(self.get_luminance() + (255.0 * factor) - 255.0, 0.0, 255.0) 275 | 276 | if total_lumes == 255.0: 277 | # white 278 | self.r, self.g, self.b = 0xFF, 0xFF, 0xFF 279 | elif total_lumes == 0.0: 280 | # black 281 | self.r, self.g, self.b = 0x00, 0x00, 0x00 282 | else: 283 | # Adjust Brightness 284 | pts = (total_lumes - 0.299 * self.r - 0.587 * self.g - 0.114 * self.b) 285 | slots = set(channels) 286 | components = [float(self.r) + pts, float(self.g) + pts, float(self.b) + pts] 287 | count = 0 288 | for c in channels: 289 | overage, components[count] = self._get_overage(components[count]) 290 | if overage: 291 | slots.remove(c) 292 | components = list(self._distribute_overage(components, overage, slots)) 293 | count += 1 294 | 295 | self.r = clamp(round_int(components[0]), 0, 255) & 0xFF 296 | self.g = clamp(round_int(components[1]), 0, 255) & 0xFF 297 | self.b = clamp(round_int(components[2]), 0, 255) & 0xFF 298 | -------------------------------------------------------------------------------- /subclrschm/lib/util.py: -------------------------------------------------------------------------------- 1 | """Compatibility module.""" 2 | from __future__ import unicode_literals 3 | import sys 4 | import functools 5 | import os 6 | import plistlib 7 | import copy 8 | import locale 9 | 10 | PY3 = (3, 0) <= sys.version_info 11 | PY2 = (2, 0) <= sys.version_info < (3, 0) 12 | 13 | if sys.platform.startswith('win'): 14 | _PLATFORM = "windows" 15 | elif sys.platform == "darwin": 16 | _PLATFORM = "osx" 17 | else: 18 | _PLATFORM = "linux" 19 | 20 | if PY3: 21 | string_type = str 22 | ustr = str 23 | bstr = bytes 24 | CommonBrokenPipeError = BrokenPipeError # noqa 25 | else: 26 | string_type = basestring 27 | ustr = unicode 28 | bstr = str 29 | 30 | class CommonBrokenPipeError(Exception): 31 | """ 32 | Broken Pipe Error. 33 | 34 | Include this for consistency, but we won't actually 35 | capture this in PY2. 36 | """ 37 | 38 | 39 | def platform(): 40 | """Get Platform.""" 41 | 42 | return _PLATFORM 43 | 44 | 45 | def sorted_callback(l, sorter): 46 | """Use a callback with sort in a PY2/PY3 way.""" 47 | 48 | if PY3: 49 | l.sort(key=functools.cmp_to_key(sorter)) 50 | else: 51 | l.sort(sorter) 52 | 53 | 54 | def to_ascii_bytes(string): 55 | """Convert unicode to ascii byte string.""" 56 | 57 | return bytes(string, 'ascii') if PY3 else bytes(string) 58 | 59 | 60 | def to_ustr(obj): 61 | """Convert to string.""" 62 | 63 | if isinstance(obj, ustr): 64 | return obj 65 | elif isinstance(obj, bstr): 66 | return ustr(obj, 'utf-8') 67 | else: 68 | return ustr(obj) 69 | 70 | 71 | def to_bstr(obj): 72 | """Convert to byte string.""" 73 | 74 | assert isinstance(obj, string_type), TypeError 75 | return obj.encode('utf-8') if isinstance(obj, ustr) else obj 76 | 77 | 78 | def getcwd(): 79 | """Get the current working directory.""" 80 | 81 | if PY3: 82 | return os.getcwd() 83 | else: 84 | return os.getcwdu() 85 | 86 | 87 | def iternext(item): 88 | """Iterate to next.""" 89 | 90 | if PY3: 91 | return item.__next__() 92 | else: 93 | return item.next() 94 | 95 | 96 | def translate(lang, text): 97 | """Translate text.""" 98 | 99 | return lang.gettext(text) if PY3 else lang.ugettext(text) 100 | 101 | 102 | def dump_plist(content): 103 | """Dump plist.""" 104 | 105 | return plistlib.dumps(content).decode('utf-8') if PY3 else plistlib.writePlistToString(content).decode('utf-8') 106 | 107 | 108 | def to_unicode_argv(): 109 | """Convert inputs to Unicode.""" 110 | 111 | args = copy.copy(sys.argv) 112 | 113 | if PY2: 114 | if _PLATFORM == "windows": 115 | # Solution copied from http://stackoverflow.com/a/846931/145400 116 | 117 | from ctypes import POINTER, byref, cdll, c_int, windll 118 | from ctypes.wintypes import LPCWSTR, LPWSTR 119 | 120 | GetCommandLineW = cdll.kernel32.GetCommandLineW 121 | GetCommandLineW.argtypes = [] 122 | GetCommandLineW.restype = LPCWSTR 123 | 124 | CommandLineToArgvW = windll.shell32.CommandLineToArgvW 125 | CommandLineToArgvW.argtypes = [LPCWSTR, POINTER(c_int)] 126 | CommandLineToArgvW.restype = POINTER(LPWSTR) 127 | 128 | cmd = GetCommandLineW() 129 | argc = c_int(0) 130 | argv = CommandLineToArgvW(cmd, byref(argc)) 131 | if argc.value > 0: 132 | # Remove Python executable and commands if present 133 | start = argc.value - len(sys.argv) 134 | args = [argv[i] for i in xrange(start, argc.value)] 135 | else: 136 | cli_encoding = sys.stdin.encoding or locale.getpreferredencoding() 137 | args = [arg.decode(cli_encoding) for arg in sys.argv if isinstance(arg, bstr)] 138 | return args 139 | -------------------------------------------------------------------------------- /subclrschm/lib/x11colors.py: -------------------------------------------------------------------------------- 1 | """ 2 | X11 colors. 3 | 4 | A simple name to hex and hex to name map of X11 colors. 5 | """ 6 | name2hex_map = { 7 | "black": "#000000", 8 | "aliceblue": "#f0f8ff", 9 | "blueviolet": "#8a2be2", 10 | "cadetblue": "#5f9ea0", 11 | "cadetblue1": "#98f5ff", 12 | "cadetblue2": "#8ee5ee", 13 | "cadetblue3": "#7ac5cd", 14 | "cadetblue4": "#53868b", 15 | "cornflowerblue": "#6495ed", 16 | "darkblue": "#00008b", 17 | "darkcyan": "#008b8b", 18 | "darkslateblue": "#483d8b", 19 | "darkturquoise": "#00ced1", 20 | "deepskyblue": "#00bfff", 21 | "deepskyblue1": "#00bfff", 22 | "deepskyblue2": "#00b2ee", 23 | "deepskyblue3": "#009acd", 24 | "deepskyblue4": "#00688b", 25 | "dodgerblue": "#1e90ff", 26 | "dodgerblue1": "#1e90ff", 27 | "dodgerblue2": "#1c86ee", 28 | "dodgerblue3": "#1874cd", 29 | "dodgerblue4": "#104e8b", 30 | "lightblue": "#add8e6", 31 | "lightblue1": "#bfefff", 32 | "lightblue2": "#b2dfee", 33 | "lightblue3": "#9ac0cd", 34 | "lightblue4": "#68838b", 35 | "lightcyan": "#e0ffff", 36 | "lightcyan1": "#e0ffff", 37 | "lightcyan2": "#d1eeee", 38 | "lightcyan3": "#b4cdcd", 39 | "lightcyan4": "#7a8b8b", 40 | "lightskyblue": "#87cefa", 41 | "lightskyblue1": "#b0e2ff", 42 | "lightskyblue2": "#a4d3ee", 43 | "lightskyblue3": "#8db6cd", 44 | "lightskyblue4": "#607b8b", 45 | "lightslateblue": "#8470ff", 46 | "lightsteelblue": "#b0c4de", 47 | "lightsteelblue1": "#cae1ff", 48 | "lightsteelblue2": "#bcd2ee", 49 | "lightsteelblue3": "#a2b5cd", 50 | "lightsteelblue4": "#6e7b8b", 51 | "mediumaquamarine": "#66cdaa", 52 | "mediumblue": "#0000cd", 53 | "mediumslateblue": "#7b68ee", 54 | "mediumturquoise": "#48d1cc", 55 | "midnightblue": "#191970", 56 | "navyblue": "#000080", 57 | "paleturquoise": "#afeeee", 58 | "paleturquoise1": "#bbffff", 59 | "paleturquoise2": "#aeeeee", 60 | "paleturquoise3": "#96cdcd", 61 | "paleturquoise4": "#668b8b", 62 | "powderblue": "#b0e0e6", 63 | "royalblue": "#4169e1", 64 | "royalblue1": "#4876ff", 65 | "royalblue2": "#436eee", 66 | "royalblue3": "#3a5fcd", 67 | "royalblue4": "#27408b", 68 | "skyblue": "#87ceeb", 69 | "skyblue1": "#87ceff", 70 | "skyblue2": "#7ec0ee", 71 | "skyblue3": "#6ca6cd", 72 | "skyblue4": "#4a708b", 73 | "slateblue": "#6a5acd", 74 | "slateblue1": "#836fff", 75 | "slateblue2": "#7a67ee", 76 | "slateblue3": "#6959cd", 77 | "slateblue4": "#473c8b", 78 | "steelblue": "#4682b4", 79 | "steelblue1": "#63b8ff", 80 | "steelblue2": "#5cacee", 81 | "steelblue3": "#4f94cd", 82 | "steelblue4": "#36648b", 83 | "aquamarine": "#7fffd4", 84 | "aquamarine1": "#7fffd4", 85 | "aquamarine2": "#76eec6", 86 | "aquamarine3": "#66cdaa", 87 | "aquamarine4": "#458b74", 88 | "azure": "#f0ffff", 89 | "azure1": "#f0ffff", 90 | "azure2": "#e0eeee", 91 | "azure3": "#c1cdcd", 92 | "azure4": "#838b8b", 93 | "blue": "#0000ff", 94 | "blue1": "#0000ff", 95 | "blue2": "#0000ee", 96 | "blue3": "#0000cd", 97 | "blue4": "#00008b", 98 | "cyan": "#00ffff", 99 | "cyan1": "#00ffff", 100 | "cyan2": "#00eeee", 101 | "cyan3": "#00cdcd", 102 | "cyan4": "#008b8b", 103 | "navy": "#000080", 104 | "turquoise": "#40e0d0", 105 | "turquoise1": "#00f5ff", 106 | "turquoise2": "#00e5ee", 107 | "turquoise3": "#00c5cd", 108 | "turquoise4": "#00868b", 109 | "rosybrown": "#bc8f8f", 110 | "rosybrown1": "#ffc1c1", 111 | "rosybrown2": "#eeb4b4", 112 | "rosybrown3": "#cd9b9b", 113 | "rosybrown4": "#8b6969", 114 | "saddlebrown": "#8b4513", 115 | "sandybrown": "#f4a460", 116 | "beige": "#f5f5dc", 117 | "brown": "#a52a2a", 118 | "brown1": "#ff4040", 119 | "brown2": "#ee3b3b", 120 | "brown3": "#cd3333", 121 | "brown4": "#8b2323", 122 | "burlywood": "#deb887", 123 | "burlywood1": "#ffd39b", 124 | "burlywood2": "#eec591", 125 | "burlywood3": "#cdaa7d", 126 | "burlywood4": "#8b7355", 127 | "chocolate": "#d2691e", 128 | "chocolate1": "#ff7f24", 129 | "chocolate2": "#ee7621", 130 | "chocolate3": "#cd661d", 131 | "chocolate4": "#8b4513", 132 | "peru": "#cd853f", 133 | "tan": "#d2b48c", 134 | "tan1": "#ffa54f", 135 | "tan2": "#ee9a49", 136 | "tan3": "#cd853f", 137 | "tan4": "#8b5a2b", 138 | "darkslategray": "#2f4f4f", 139 | "darkslategray1": "#97ffff", 140 | "darkslategray2": "#8deeee", 141 | "darkslategray3": "#79cdcd", 142 | "darkslategray4": "#528b8b", 143 | "darkslategrey": "#2f4f4f", 144 | "dimgray": "#696969", 145 | "dimgrey": "#696969", 146 | "lightgray": "#d3d3d3", 147 | "lightgrey": "#d3d3d3", 148 | "lightslategray": "#778899", 149 | "lightslategrey": "#778899", 150 | "slategray": "#708090", 151 | "slategray1": "#c6e2ff", 152 | "slategray2": "#b9d3ee", 153 | "slategray3": "#9fb6cd", 154 | "slategray4": "#6c7b8b", 155 | "slategrey": "#708090", 156 | "gray": "#bebebe", 157 | "gray0": "#000000", 158 | "gray1": "#030303", 159 | "gray2": "#050505", 160 | "gray3": "#080808", 161 | "gray4": "#0a0a0a", 162 | "gray5": "#0d0d0d", 163 | "gray6": "#0f0f0f", 164 | "gray7": "#121212", 165 | "gray8": "#141414", 166 | "gray9": "#171717", 167 | "gray10": "#1a1a1a", 168 | "gray11": "#1c1c1c", 169 | "gray12": "#1f1f1f", 170 | "gray13": "#212121", 171 | "gray14": "#242424", 172 | "gray15": "#262626", 173 | "gray16": "#292929", 174 | "gray17": "#2b2b2b", 175 | "gray18": "#2e2e2e", 176 | "gray19": "#303030", 177 | "gray20": "#333333", 178 | "gray21": "#363636", 179 | "gray22": "#383838", 180 | "gray23": "#3b3b3b", 181 | "gray24": "#3d3d3d", 182 | "gray25": "#404040", 183 | "gray26": "#424242", 184 | "gray27": "#454545", 185 | "gray28": "#474747", 186 | "gray29": "#4a4a4a", 187 | "gray30": "#4d4d4d", 188 | "gray31": "#4f4f4f", 189 | "gray32": "#525252", 190 | "gray33": "#545454", 191 | "gray34": "#575757", 192 | "gray35": "#595959", 193 | "gray36": "#5c5c5c", 194 | "gray37": "#5e5e5e", 195 | "gray38": "#616161", 196 | "gray39": "#636363", 197 | "gray40": "#666666", 198 | "gray41": "#696969", 199 | "gray42": "#6b6b6b", 200 | "gray43": "#6e6e6e", 201 | "gray44": "#707070", 202 | "gray45": "#737373", 203 | "gray46": "#757575", 204 | "gray47": "#787878", 205 | "gray48": "#7a7a7a", 206 | "gray49": "#7d7d7d", 207 | "gray50": "#7f7f7f", 208 | "gray51": "#828282", 209 | "gray52": "#858585", 210 | "gray53": "#878787", 211 | "gray54": "#8a8a8a", 212 | "gray55": "#8c8c8c", 213 | "gray56": "#8f8f8f", 214 | "gray57": "#919191", 215 | "gray58": "#949494", 216 | "gray59": "#969696", 217 | "gray60": "#999999", 218 | "gray61": "#9c9c9c", 219 | "gray62": "#9e9e9e", 220 | "gray63": "#a1a1a1", 221 | "gray64": "#a3a3a3", 222 | "gray65": "#a6a6a6", 223 | "gray66": "#a8a8a8", 224 | "gray67": "#ababab", 225 | "gray68": "#adadad", 226 | "gray69": "#b0b0b0", 227 | "gray70": "#b3b3b3", 228 | "gray71": "#b5b5b5", 229 | "gray72": "#b8b8b8", 230 | "gray73": "#bababa", 231 | "gray74": "#bdbdbd", 232 | "gray75": "#bfbfbf", 233 | "gray76": "#c2c2c2", 234 | "gray77": "#c4c4c4", 235 | "gray78": "#c7c7c7", 236 | "gray79": "#c9c9c9", 237 | "gray80": "#cccccc", 238 | "gray81": "#cfcfcf", 239 | "gray82": "#d1d1d1", 240 | "gray83": "#d4d4d4", 241 | "gray84": "#d6d6d6", 242 | "gray85": "#d9d9d9", 243 | "gray86": "#dbdbdb", 244 | "gray87": "#dedede", 245 | "gray88": "#e0e0e0", 246 | "gray89": "#e3e3e3", 247 | "gray90": "#e5e5e5", 248 | "gray91": "#e8e8e8", 249 | "gray92": "#ebebeb", 250 | "gray93": "#ededed", 251 | "gray94": "#f0f0f0", 252 | "gray95": "#f2f2f2", 253 | "gray96": "#f5f5f5", 254 | "gray97": "#f7f7f7", 255 | "gray98": "#fafafa", 256 | "gray99": "#fcfcfc", 257 | "gray100": "#ffffff", 258 | "grey": "#bebebe", 259 | "grey0": "#000000", 260 | "grey1": "#030303", 261 | "grey2": "#050505", 262 | "grey3": "#080808", 263 | "grey4": "#0a0a0a", 264 | "grey5": "#0d0d0d", 265 | "grey6": "#0f0f0f", 266 | "grey7": "#121212", 267 | "grey8": "#141414", 268 | "grey9": "#171717", 269 | "grey10": "#1a1a1a", 270 | "grey11": "#1c1c1c", 271 | "grey12": "#1f1f1f", 272 | "grey13": "#212121", 273 | "grey14": "#242424", 274 | "grey15": "#262626", 275 | "grey16": "#292929", 276 | "grey17": "#2b2b2b", 277 | "grey18": "#2e2e2e", 278 | "grey19": "#303030", 279 | "grey20": "#333333", 280 | "grey21": "#363636", 281 | "grey22": "#383838", 282 | "grey23": "#3b3b3b", 283 | "grey24": "#3d3d3d", 284 | "grey25": "#404040", 285 | "grey26": "#424242", 286 | "grey27": "#454545", 287 | "grey28": "#474747", 288 | "grey29": "#4a4a4a", 289 | "grey30": "#4d4d4d", 290 | "grey31": "#4f4f4f", 291 | "grey32": "#525252", 292 | "grey33": "#545454", 293 | "grey34": "#575757", 294 | "grey35": "#595959", 295 | "grey36": "#5c5c5c", 296 | "grey37": "#5e5e5e", 297 | "grey38": "#616161", 298 | "grey39": "#636363", 299 | "grey40": "#666666", 300 | "grey41": "#696969", 301 | "grey42": "#6b6b6b", 302 | "grey43": "#6e6e6e", 303 | "grey44": "#707070", 304 | "grey45": "#737373", 305 | "grey46": "#757575", 306 | "grey47": "#787878", 307 | "grey48": "#7a7a7a", 308 | "grey49": "#7d7d7d", 309 | "grey50": "#7f7f7f", 310 | "grey51": "#828282", 311 | "grey52": "#858585", 312 | "grey53": "#878787", 313 | "grey54": "#8a8a8a", 314 | "grey55": "#8c8c8c", 315 | "grey56": "#8f8f8f", 316 | "grey57": "#919191", 317 | "grey58": "#949494", 318 | "grey59": "#969696", 319 | "grey60": "#999999", 320 | "grey61": "#9c9c9c", 321 | "grey62": "#9e9e9e", 322 | "grey63": "#a1a1a1", 323 | "grey64": "#a3a3a3", 324 | "grey65": "#a6a6a6", 325 | "grey66": "#a8a8a8", 326 | "grey67": "#ababab", 327 | "grey68": "#adadad", 328 | "grey69": "#b0b0b0", 329 | "grey70": "#b3b3b3", 330 | "grey71": "#b5b5b5", 331 | "grey72": "#b8b8b8", 332 | "grey73": "#bababa", 333 | "grey74": "#bdbdbd", 334 | "grey75": "#bfbfbf", 335 | "grey76": "#c2c2c2", 336 | "grey77": "#c4c4c4", 337 | "grey78": "#c7c7c7", 338 | "grey79": "#c9c9c9", 339 | "grey80": "#cccccc", 340 | "grey81": "#cfcfcf", 341 | "grey82": "#d1d1d1", 342 | "grey83": "#d4d4d4", 343 | "grey84": "#d6d6d6", 344 | "grey85": "#d9d9d9", 345 | "grey86": "#dbdbdb", 346 | "grey87": "#dedede", 347 | "grey88": "#e0e0e0", 348 | "grey89": "#e3e3e3", 349 | "grey90": "#e5e5e5", 350 | "grey91": "#e8e8e8", 351 | "grey92": "#ebebeb", 352 | "grey93": "#ededed", 353 | "grey94": "#f0f0f0", 354 | "grey95": "#f2f2f2", 355 | "grey96": "#f5f5f5", 356 | "grey97": "#f7f7f7", 357 | "grey98": "#fafafa", 358 | "grey99": "#fcfcfc", 359 | "grey100": "#ffffff", 360 | "darkgreen": "#006400", 361 | "darkkhaki": "#bdb76b", 362 | "darkolivegreen": "#556b2f", 363 | "darkolivegreen1": "#caff70", 364 | "darkolivegreen2": "#bcee68", 365 | "darkolivegreen3": "#a2cd5a", 366 | "darkolivegreen4": "#6e8b3d", 367 | "darkseagreen": "#8fbc8f", 368 | "darkseagreen1": "#c1ffc1", 369 | "darkseagreen2": "#b4eeb4", 370 | "darkseagreen3": "#9bcd9b", 371 | "darkseagreen4": "#698b69", 372 | "forestgreen": "#228b22", 373 | "greenyellow": "#adff2f", 374 | "lawngreen": "#7cfc00", 375 | "lightgreen": "#90ee90", 376 | "lightseagreen": "#20b2aa", 377 | "limegreen": "#32cd32", 378 | "mediumseagreen": "#3cb371", 379 | "mediumspringgreen": "#00fa9a", 380 | "mintcream": "#f5fffa", 381 | "olivedrab": "#6b8e23", 382 | "olivedrab1": "#c0ff3e", 383 | "olivedrab2": "#b3ee3a", 384 | "olivedrab3": "#9acd32", 385 | "olivedrab4": "#698b22", 386 | "palegreen": "#98fb98", 387 | "palegreen1": "#9aff9a", 388 | "palegreen2": "#90ee90", 389 | "palegreen3": "#7ccd7c", 390 | "palegreen4": "#548b54", 391 | "seagreen": "#2e8b57", 392 | "seagreen1": "#54ff9f", 393 | "seagreen2": "#4eee94", 394 | "seagreen3": "#43cd80", 395 | "seagreen4": "#2e8b57", 396 | "springgreen": "#00ff7f", 397 | "springgreen1": "#00ff7f", 398 | "springgreen2": "#00ee76", 399 | "springgreen3": "#00cd66", 400 | "springgreen4": "#008b45", 401 | "yellowgreen": "#9acd32", 402 | "chartreuse": "#7fff00", 403 | "chartreuse1": "#7fff00", 404 | "chartreuse2": "#76ee00", 405 | "chartreuse3": "#66cd00", 406 | "chartreuse4": "#458b00", 407 | "green": "#00ff00", 408 | "green1": "#00ff00", 409 | "green2": "#00ee00", 410 | "green3": "#00cd00", 411 | "green4": "#008b00", 412 | "khaki": "#f0e68c", 413 | "khaki1": "#fff68f", 414 | "khaki2": "#eee685", 415 | "khaki3": "#cdc673", 416 | "khaki4": "#8b864e", 417 | "darkorange": "#ff8c00", 418 | "darkorange1": "#ff7f00", 419 | "darkorange2": "#ee7600", 420 | "darkorange3": "#cd6600", 421 | "darkorange4": "#8b4500", 422 | "darksalmon": "#e9967a", 423 | "lightcoral": "#f08080", 424 | "lightsalmon": "#ffa07a", 425 | "lightsalmon1": "#ffa07a", 426 | "lightsalmon2": "#ee9572", 427 | "lightsalmon3": "#cd8162", 428 | "lightsalmon4": "#8b5742", 429 | "peachpuff": "#ffdab9", 430 | "peachpuff1": "#ffdab9", 431 | "peachpuff2": "#eecbad", 432 | "peachpuff3": "#cdaf95", 433 | "peachpuff4": "#8b7765", 434 | "bisque": "#ffe4c4", 435 | "bisque1": "#ffe4c4", 436 | "bisque2": "#eed5b7", 437 | "bisque3": "#cdb79e", 438 | "bisque4": "#8b7d6b", 439 | "coral": "#ff7f50", 440 | "coral1": "#ff7256", 441 | "coral2": "#ee6a50", 442 | "coral3": "#cd5b45", 443 | "coral4": "#8b3e2f", 444 | "honeydew": "#f0fff0", 445 | "honeydew1": "#f0fff0", 446 | "honeydew2": "#e0eee0", 447 | "honeydew3": "#c1cdc1", 448 | "honeydew4": "#838b83", 449 | "orange": "#ffa500", 450 | "orange1": "#ffa500", 451 | "orange2": "#ee9a00", 452 | "orange3": "#cd8500", 453 | "orange4": "#8b5a00", 454 | "salmon": "#fa8072", 455 | "salmon1": "#ff8c69", 456 | "salmon2": "#ee8262", 457 | "salmon3": "#cd7054", 458 | "salmon4": "#8b4c39", 459 | "sienna": "#a0522d", 460 | "sienna1": "#ff8247", 461 | "sienna2": "#ee7942", 462 | "sienna3": "#cd6839", 463 | "sienna4": "#8b4726", 464 | "darkred": "#8b0000", 465 | "deeppink": "#ff1493", 466 | "deeppink1": "#ff1493", 467 | "deeppink2": "#ee1289", 468 | "deeppink3": "#cd1076", 469 | "deeppink4": "#8b0a50", 470 | "hotpink": "#ff69b4", 471 | "hotpink1": "#ff6eb4", 472 | "hotpink2": "#ee6aa7", 473 | "hotpink3": "#cd6090", 474 | "hotpink4": "#8b3a62", 475 | "indianred": "#cd5c5c", 476 | "indianred1": "#ff6a6a", 477 | "indianred2": "#ee6363", 478 | "indianred3": "#cd5555", 479 | "indianred4": "#8b3a3a", 480 | "lightpink": "#ffb6c1", 481 | "lightpink1": "#ffaeb9", 482 | "lightpink2": "#eea2ad", 483 | "lightpink3": "#cd8c95", 484 | "lightpink4": "#8b5f65", 485 | "mediumvioletred": "#c71585", 486 | "mistyrose": "#ffe4e1", 487 | "mistyrose1": "#ffe4e1", 488 | "mistyrose2": "#eed5d2", 489 | "mistyrose3": "#cdb7b5", 490 | "mistyrose4": "#8b7d7b", 491 | "orangered": "#ff4500", 492 | "orangered1": "#ff4500", 493 | "orangered2": "#ee4000", 494 | "orangered3": "#cd3700", 495 | "orangered4": "#8b2500", 496 | "palevioletred": "#db7093", 497 | "palevioletred1": "#ff82ab", 498 | "palevioletred2": "#ee799f", 499 | "palevioletred3": "#cd6889", 500 | "palevioletred4": "#8b475d", 501 | "violetred": "#d02090", 502 | "violetred1": "#ff3e96", 503 | "violetred2": "#ee3a8c", 504 | "violetred3": "#cd3278", 505 | "violetred4": "#8b2252", 506 | "firebrick": "#b22222", 507 | "firebrick1": "#ff3030", 508 | "firebrick2": "#ee2c2c", 509 | "firebrick3": "#cd2626", 510 | "firebrick4": "#8b1a1a", 511 | "pink": "#ffc0cb", 512 | "pink1": "#ffb5c5", 513 | "pink2": "#eea9b8", 514 | "pink3": "#cd919e", 515 | "pink4": "#8b636c", 516 | "red": "#ff0000", 517 | "red1": "#ff0000", 518 | "red2": "#ee0000", 519 | "red3": "#cd0000", 520 | "red4": "#8b0000", 521 | "tomato": "#ff6347", 522 | "tomato1": "#ff6347", 523 | "tomato2": "#ee5c42", 524 | "tomato3": "#cd4f39", 525 | "tomato4": "#8b3626", 526 | "darkmagenta": "#8b008b", 527 | "darkorchid": "#9932cc", 528 | "darkorchid1": "#bf3eff", 529 | "darkorchid2": "#b23aee", 530 | "darkorchid3": "#9a32cd", 531 | "darkorchid4": "#68228b", 532 | "darkviolet": "#9400d3", 533 | "lavenderblush": "#fff0f5", 534 | "lavenderblush1": "#fff0f5", 535 | "lavenderblush2": "#eee0e5", 536 | "lavenderblush3": "#cdc1c5", 537 | "lavenderblush4": "#8b8386", 538 | "mediumorchid": "#ba55d3", 539 | "mediumorchid1": "#e066ff", 540 | "mediumorchid2": "#d15fee", 541 | "mediumorchid3": "#b452cd", 542 | "mediumorchid4": "#7a378b", 543 | "mediumpurple": "#9370db", 544 | "mediumpurple1": "#ab82ff", 545 | "mediumpurple2": "#9f79ee", 546 | "mediumpurple3": "#8968cd", 547 | "mediumpurple4": "#5d478b", 548 | "lavender": "#e6e6fa", 549 | "magenta": "#ff00ff", 550 | "magenta1": "#ff00ff", 551 | "magenta2": "#ee00ee", 552 | "magenta3": "#cd00cd", 553 | "magenta4": "#8b008b", 554 | "maroon": "#b03060", 555 | "maroon1": "#ff34b3", 556 | "maroon2": "#ee30a7", 557 | "maroon3": "#cd2990", 558 | "maroon4": "#8b1c62", 559 | "orchid": "#da70d6", 560 | "orchid1": "#ff83fa", 561 | "orchid2": "#ee7ae9", 562 | "orchid3": "#cd69c9", 563 | "orchid4": "#8b4789", 564 | "plum": "#dda0dd", 565 | "plum1": "#ffbbff", 566 | "plum2": "#eeaeee", 567 | "plum3": "#cd96cd", 568 | "plum4": "#8b668b", 569 | "purple": "#a020f0", 570 | "purple1": "#9b30ff", 571 | "purple2": "#912cee", 572 | "purple3": "#7d26cd", 573 | "purple4": "#551a8b", 574 | "thistle": "#d8bfd8", 575 | "thistle1": "#ffe1ff", 576 | "thistle2": "#eed2ee", 577 | "thistle3": "#cdb5cd", 578 | "thistle4": "#8b7b8b", 579 | "violet": "#ee82ee", 580 | "antiquewhite": "#faebd7", 581 | "antiquewhite1": "#ffefdb", 582 | "antiquewhite2": "#eedfcc", 583 | "antiquewhite3": "#cdc0b0", 584 | "antiquewhite4": "#8b8378", 585 | "floralwhite": "#fffaf0", 586 | "ghostwhite": "#f8f8ff", 587 | "navajowhite": "#ffdead", 588 | "navajowhite1": "#ffdead", 589 | "navajowhite2": "#eecfa1", 590 | "navajowhite3": "#cdb38b", 591 | "navajowhite4": "#8b795e", 592 | "oldlace": "#fdf5e6", 593 | "whitesmoke": "#f5f5f5", 594 | "gainsboro": "#dcdcdc", 595 | "ivory": "#fffff0", 596 | "ivory1": "#fffff0", 597 | "ivory2": "#eeeee0", 598 | "ivory3": "#cdcdc1", 599 | "ivory4": "#8b8b83", 600 | "linen": "#faf0e6", 601 | "seashell": "#fff5ee", 602 | "seashell1": "#fff5ee", 603 | "seashell2": "#eee5de", 604 | "seashell3": "#cdc5bf", 605 | "seashell4": "#8b8682", 606 | "snow": "#fffafa", 607 | "snow1": "#fffafa", 608 | "snow2": "#eee9e9", 609 | "snow3": "#cdc9c9", 610 | "snow4": "#8b8989", 611 | "wheat": "#f5deb3", 612 | "wheat1": "#ffe7ba", 613 | "wheat2": "#eed8ae", 614 | "wheat3": "#cdba96", 615 | "wheat4": "#8b7e66", 616 | "white": "#ffffff", 617 | "blanchedalmond": "#ffebcd", 618 | "darkgoldenrod": "#b8860b", 619 | "darkgoldenrod1": "#ffb90f", 620 | "darkgoldenrod2": "#eead0e", 621 | "darkgoldenrod3": "#cd950c", 622 | "darkgoldenrod4": "#8b6508", 623 | "lemonchiffon": "#fffacd", 624 | "lemonchiffon1": "#fffacd", 625 | "lemonchiffon2": "#eee9bf", 626 | "lemonchiffon3": "#cdc9a5", 627 | "lemonchiffon4": "#8b8970", 628 | "lightgoldenrod": "#eedd82", 629 | "lightgoldenrod1": "#ffec8b", 630 | "lightgoldenrod2": "#eedc82", 631 | "lightgoldenrod3": "#cdbe70", 632 | "lightgoldenrod4": "#8b814c", 633 | "lightgoldenrodyellow": "#fafad2", 634 | "lightyellow": "#ffffe0", 635 | "lightyellow1": "#ffffe0", 636 | "lightyellow2": "#eeeed1", 637 | "lightyellow3": "#cdcdb4", 638 | "lightyellow4": "#8b8b7a", 639 | "palegoldenrod": "#eee8aa", 640 | "papayawhip": "#ffefd5", 641 | "cornsilk": "#fff8dc", 642 | "cornsilk1": "#fff8dc", 643 | "cornsilk2": "#eee8cd", 644 | "cornsilk3": "#cdc8b1", 645 | "cornsilk4": "#8b8878", 646 | "gold": "#ffd700", 647 | "gold1": "#ffd700", 648 | "gold2": "#eec900", 649 | "gold3": "#cdad00", 650 | "gold4": "#8b7500", 651 | "goldenrod": "#daa520", 652 | "goldenrod1": "#ffc125", 653 | "goldenrod2": "#eeb422", 654 | "goldenrod3": "#cd9b1d", 655 | "goldenrod4": "#8b6914", 656 | "moccasin": "#ffe4b5", 657 | "yellow": "#ffff00", 658 | "yellow1": "#ffff00", 659 | "yellow2": "#eeee00", 660 | "yellow3": "#cdcd00", 661 | "yellow4": "#8b8b00" 662 | } 663 | 664 | hex2name_map = dict([(v, k) for k, v in name2hex_map.items()]) 665 | 666 | 667 | def hex2name(hex): 668 | """Convert X11 hex to webcolor name.""" 669 | 670 | return hex2name_map.get(hex.lower(), None) 671 | 672 | 673 | def name2hex(name): 674 | """Convert X11 webcolor name to hex.""" 675 | 676 | return name2hex_map.get(name.lower(), None) 677 | -------------------------------------------------------------------------------- /tests/spellcheck.py: -------------------------------------------------------------------------------- 1 | """Spell check with aspell.""" 2 | from __future__ import unicode_literals 3 | import subprocess 4 | import os 5 | import sys 6 | import yaml 7 | import codecs 8 | 9 | PY3 = sys.version_info >= (3, 0) 10 | 11 | USER_DICT = '.dictionary' 12 | BUILD_DIR = os.path.join('.', 'build', 'docs') 13 | MKDOCS_CFG = 'mkdocs.yml' 14 | COMPILED_DICT = os.path.join(BUILD_DIR, 'dictionary.bin') 15 | MKDOCS_SPELL = os.path.join(BUILD_DIR, MKDOCS_CFG) 16 | MKDOCS_BUILD = os.path.join(BUILD_DIR, 'site') 17 | 18 | 19 | def console(cmd, input_file=None): 20 | """Call with arguments.""" 21 | 22 | returncode = None 23 | output = None 24 | 25 | if sys.platform.startswith('win'): 26 | startupinfo = subprocess.STARTUPINFO() 27 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 28 | process = subprocess.Popen( 29 | cmd, 30 | startupinfo=startupinfo, 31 | stdout=subprocess.PIPE, 32 | stderr=subprocess.STDOUT, 33 | stdin=subprocess.PIPE, 34 | shell=False 35 | ) 36 | else: 37 | process = subprocess.Popen( 38 | cmd, 39 | stdout=subprocess.PIPE, 40 | stderr=subprocess.STDOUT, 41 | stdin=subprocess.PIPE, 42 | shell=False 43 | ) 44 | 45 | if input_file is not None: 46 | with open(input_file, 'rb') as f: 47 | process.stdin.write(f.read()) 48 | output = process.communicate() 49 | returncode = process.returncode 50 | 51 | assert returncode == 0, "Runtime Error: %s" % ( 52 | output[0].rstrip().decode('utf-8') if PY3 else output[0] 53 | ) 54 | 55 | return output[0].decode('utf-8') if PY3 else output[0] 56 | 57 | 58 | def yaml_dump(data, stream=None, dumper=yaml.Dumper, **kwargs): 59 | """Special dumper wrapper to modify the yaml dumper.""" 60 | 61 | class Dumper(dumper): 62 | """Custom dumper.""" 63 | 64 | if not PY3: 65 | # Unicode 66 | Dumper.add_representer( 67 | unicode, # noqa 68 | lambda dumper, value: dumper.represent_scalar(u'tag:yaml.org,2002:str', value) 69 | ) 70 | 71 | return yaml.dump(data, stream, Dumper, **kwargs) 72 | 73 | 74 | def yaml_load(source, loader=yaml.Loader): 75 | """ 76 | Wrap PyYaml's loader so we can extend it to suit our needs. 77 | 78 | Load all strings as unicode: http://stackoverflow.com/a/2967461/3609487. 79 | """ 80 | 81 | def construct_yaml_str(self, node): 82 | """Override the default string handling function to always return Unicode objects.""" 83 | return self.construct_scalar(node) 84 | 85 | class Loader(loader): 86 | """Define a custom loader to leave the global loader unaltered.""" 87 | 88 | # Attach our unicode constructor to our custom loader ensuring all strings 89 | # will be unicode on translation. 90 | Loader.add_constructor('tag:yaml.org,2002:str', construct_yaml_str) 91 | 92 | return yaml.load(source, Loader) 93 | 94 | 95 | def patch_doc_config(config_file): 96 | """Patch the config file to wrap arithmatex with a tag aspell can ignore.""" 97 | 98 | with open(config_file, 'rb') as f: 99 | config = yaml_load(f) 100 | 101 | index = 0 102 | for extension in config.get('markdown_extensions', []): 103 | if isinstance(extension, str if PY3 else unicode) and extension == 'pymdownx.arithmatex': # noqa 104 | config['markdown_extensions'][index] = {'pymdownx.arithmatex': {'insert_as_script': True}} 105 | break 106 | elif isinstance(extension, dict) and 'pymdownx.arithmatex' in extension: 107 | if isinstance(extension['pymdownx.arithmatex'], dict): 108 | extension['pymdownx.arithmatex']['insert_as_script'] = True 109 | elif extension['pymdownx.arithmatex'] is None: 110 | extension['pymdownx.arithmatex'] = {'insert_as_script': True} 111 | break 112 | index += 1 113 | 114 | with codecs.open(MKDOCS_SPELL, "w", encoding="utf-8") as f: 115 | yaml_dump( 116 | config, f, 117 | width=None, 118 | indent=4, 119 | allow_unicode=True, 120 | default_flow_style=False 121 | ) 122 | return MKDOCS_SPELL 123 | 124 | 125 | def build_docs(): 126 | """Build docs with MkDocs.""" 127 | print('Building Docs...') 128 | print( 129 | console( 130 | [ 131 | sys.executable, 132 | '-m', 'mkdocs', 'build', '--clean', 133 | '-d', MKDOCS_BUILD, 134 | '-f', patch_doc_config(MKDOCS_CFG) 135 | ] 136 | ) 137 | ) 138 | 139 | 140 | def compile_dictionary(): 141 | """Compile user dictionary.""" 142 | if os.path.exists(COMPILED_DICT): 143 | os.remove(COMPILED_DICT) 144 | print("Compiling Custom Dictionary...") 145 | print( 146 | console( 147 | [ 148 | 'aspell', 149 | '--lang=en', 150 | '--encoding=utf-8', 151 | 'create', 152 | 'master', 153 | COMPILED_DICT 154 | ], 155 | USER_DICT 156 | ) 157 | ) 158 | 159 | 160 | def check_spelling(): 161 | """Check spelling.""" 162 | print('Spell Checking...') 163 | 164 | fail = False 165 | 166 | for base, dirs, files in os.walk(MKDOCS_BUILD): 167 | # Remove child folders based on exclude rules 168 | for f in files: 169 | if f.endswith('.html'): 170 | file_name = os.path.join(base, f) 171 | wordlist = console( 172 | [ 173 | 'aspell', 174 | 'list', 175 | '--lang=en', 176 | '--mode=html', 177 | '--encoding=utf-8', 178 | '--add-html-skip=code', 179 | '--add-html-skip=pre', 180 | '--add-html-skip=nospell', 181 | '--extra-dicts=%s' % COMPILED_DICT 182 | ], 183 | file_name 184 | ) 185 | 186 | words = [w for w in sorted(set(wordlist.split('\n'))) if w] 187 | 188 | if words: 189 | fail = True 190 | print('Misspelled words in %s' % file_name) 191 | print('-' * 80) 192 | for word in words: 193 | print(word) 194 | print('-' * 80) 195 | print('\n') 196 | return fail 197 | 198 | 199 | def main(): 200 | """Main.""" 201 | if not os.path.exists(BUILD_DIR): 202 | os.makedirs(BUILD_DIR) 203 | build_docs() 204 | compile_dictionary() 205 | return check_spelling() 206 | 207 | 208 | if __name__ == "__main__": 209 | sys.exit(main()) 210 | -------------------------------------------------------------------------------- /tools/wxpy4_patch.py: -------------------------------------------------------------------------------- 1 | """Script to patch wxFormBuilder output of wxPython files for version 4.0.""" 2 | from __future__ import print_function 3 | import re 4 | import os 5 | import codecs 6 | 7 | RE_ADD_WX_ADV = re.compile(r'(import wx)\n') 8 | RE_SIZE_HINTS_SZ = re.compile(r'\bSetSizeHintsSz\b') 9 | RE_ADD_SPACER = re.compile(r'\bAddSpacer\b') 10 | RE_APPEND_ITEM = re.compile(r'\bAppendItem\b') 11 | RE_ADV = re.compile(r'\b(wx\.)(GenericDatePickerCtrl|DP_)') 12 | RE_ST = re.compile(r'\b(wx\.ST)(_)') 13 | 14 | gui = os.path.join('subclrschm', 'lib', 'gui', 'gui.py') 15 | 16 | with codecs.open(gui, 'r', encoding="utf-8") as f: 17 | buf = f.read().replace('\r', '') 18 | 19 | buf = RE_ADD_WX_ADV.sub(r'\1\nimport wx.adv\n', buf) 20 | buf = RE_SIZE_HINTS_SZ.sub('SetSizeHints', buf) 21 | buf = RE_ADD_SPACER.sub('Add', buf) 22 | buf = RE_APPEND_ITEM.sub('Append', buf) 23 | buf = RE_ADV.sub(r'\1adv.\2', buf) 24 | buf = RE_ST.sub(r'\1B\2', buf) 25 | 26 | with codecs.open(gui, 'w', encoding='utf-8') as f: 27 | f.write(buf.strip() + '\n') 28 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist=true 3 | envlist= 4 | lint 5 | 6 | [testenv:lint] 7 | deps= 8 | -rrequirements/lint.txt 9 | commands= 10 | {envbindir}/flake8 . 11 | 12 | [testenv:documents] 13 | basepython = python2.7 14 | deps= 15 | -rrequirements/docs.txt 16 | commands= 17 | {envpython} {toxinidir}/tests/spellcheck.py 18 | {envpython} -m mkdocs build --clean --verbose --strict 19 | 20 | [flake8] 21 | ignore=D202,D203,D204,D401 22 | max-line-length=120 23 | exclude=subclrschm/lib/gui/gui.py,build/* 24 | putty-ignore= 25 | subclrschm/lib/util.py : +N806 26 | --------------------------------------------------------------------------------