├── .gitattributes ├── .github └── workflows │ ├── check-addon.yml │ ├── publish-addon.yml │ └── publish-docs.yml ├── .gitignore ├── Readme.md ├── docs ├── Makefile ├── _autosummary │ └── .notempty ├── _static │ ├── addon_dialog_window.jpg │ ├── addon_full_window.jpg │ ├── example_ui.jpg │ ├── hello_world.jpg │ ├── pop-up.jpg │ ├── pyxbmct_controls_confl.jpg │ └── pyxbmct_controls_est.jpg ├── classes.rst ├── conf.py ├── connection.rst ├── controls.rst ├── estuary.rst ├── examples.rst ├── grid.rst ├── index.rst ├── links.rst ├── make.bat ├── pyxbmct.rst ├── skins.rst └── using.rst ├── script.module.pyxbmct ├── License.txt ├── addon.xml ├── icon.png └── lib │ └── pyxbmct │ ├── __init__.py │ ├── addonskin.py │ ├── addonwindow.py │ └── textures │ ├── confluence │ ├── AddonWindow │ │ ├── ContentPanel.png │ │ ├── DialogCloseButton-focus.png │ │ ├── DialogCloseButton.png │ │ ├── SKINDEFAULT.jpg │ │ └── dialogheader.png │ ├── Button │ │ ├── KeyboardKey.png │ │ └── KeyboardKeyNF.png │ ├── Edit │ │ ├── black-back2.png │ │ └── button-focus.png │ ├── List │ │ ├── MenuItemFO.png │ │ └── MenuItemNF.png │ ├── RadioButton │ │ ├── MenuItemFO.png │ │ ├── MenuItemNF.png │ │ ├── radiobutton-focus.png │ │ └── radiobutton-nofocus.png │ └── Slider │ │ ├── osd_slider_bg.png │ │ ├── osd_slider_nib.png │ │ └── osd_slider_nibNF.png │ └── estuary │ ├── AddonWindow │ ├── ContentPanel.png │ ├── DialogCloseButton-focus.png │ ├── DialogCloseButton.png │ ├── SKINDEFAULT.jpg │ └── dialogheader.png │ ├── Button │ ├── KeyboardKey.png │ └── KeyboardKeyNF.png │ ├── Edit │ ├── black-back2.png │ └── button-focus.png │ ├── List │ ├── MenuItemFO.png │ └── MenuItemNF.png │ ├── RadioButton │ ├── MenuItemFO.png │ ├── MenuItemNF.png │ ├── radiobutton-focus.png │ └── radiobutton-nofocus.png │ └── Slider │ ├── osd_slider_bg.png │ ├── osd_slider_nib.png │ └── osd_slider_nibNF.png └── setup.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/workflows/check-addon.yml: -------------------------------------------------------------------------------- 1 | name: Check addon 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | check: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python 3.11 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: "3.11" 16 | - name: Install addon checker 17 | run: | 18 | pip install -q kodi-addon-checker 19 | - name: Check with addon-checker 20 | run: | 21 | kodi-addon-checker --branch matrix script.module.pyxbmct 22 | -------------------------------------------------------------------------------- /.github/workflows/publish-addon.yml: -------------------------------------------------------------------------------- 1 | name: Publish addon 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | - '!*-beta' 8 | 9 | jobs: 10 | publish: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python 3.11 17 | uses: actions/setup-python@v1 18 | with: 19 | python-version: "3.11" 20 | - name: Install addon checker 21 | run: | 22 | pip install -q kodi-addon-checker 23 | - name: Check with addon-checker 24 | run: | 25 | kodi-addon-checker --branch matrix script.module.pyxbmct 26 | - name: Install addon submitter 27 | run: | 28 | pip install -q git+https://github.com/xbmc/kodi-addon-submitter.git 29 | - name: Submit addon 30 | run: | 31 | submit-addon -r repo-scripts -b matrix -s --pull-request script.module.pyxbmct 32 | env: 33 | GH_USERNAME: romanvm 34 | GH_TOKEN: ${{ secrets.gh_token }} 35 | EMAIL: roman1972@gmail.com 36 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | 8 | jobs: 9 | 10 | publish: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python 3.11 16 | uses: actions/setup-python@v1 17 | with: 18 | python-version: "3.11" 19 | - name: Build HTML docs 20 | run: | 21 | pip install Sphinx Kodistubs 22 | cd docs 23 | make html 24 | - name: Publish docs to GH pages 25 | uses: JamesIves/github-pages-deploy-action@v4 26 | with: 27 | folder: docs/_build/html 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | .venv/ 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | #lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | 64 | ### JetBrains template 65 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion 66 | 67 | *.iml 68 | 69 | ## Directory-based project format: 70 | .idea/ 71 | # if you remove the above rule, at least ignore the following: 72 | 73 | # User-specific stuff: 74 | # .idea/workspace.xml 75 | # .idea/tasks.xml 76 | # .idea/dictionaries 77 | 78 | # Sensitive or high-churn files: 79 | # .idea/dataSources.ids 80 | # .idea/dataSources.xml 81 | # .idea/sqlDataSources.xml 82 | # .idea/dynamic.xml 83 | # .idea/uiDesigner.xml 84 | 85 | # Gradle: 86 | # .idea/gradle.xml 87 | # .idea/libraries 88 | 89 | # Mongo Explorer plugin: 90 | # .idea/mongoSettings.xml 91 | 92 | ## File-based project format: 93 | *.ipr 94 | *.iws 95 | 96 | ## Plugin-specific files: 97 | 98 | # IntelliJ 99 | /out/ 100 | 101 | # mpeltonen/sbt-idea plugin 102 | .idea_modules/ 103 | 104 | # JIRA plugin 105 | atlassian-ide-plugin.xml 106 | 107 | # Crashlytics plugin (for Android Studio and IntelliJ) 108 | com_crashlytics_export_strings.xml 109 | crashlytics.properties 110 | crashlytics-build.properties 111 | 112 | epydoc.* 113 | _docs/ 114 | docs/_autosummary 115 | 116 | venv/ 117 | *.zip 118 | *.cmd 119 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | PyXBMCt 2 | ======= 3 | 4 | PyXBMCt is a mini-framework which simplifies UI creation for [Kodi (XBMC)](www.kodi.tv) addons. 5 | It was inspired by PyQt GUI framework (hence the name) and provides a main window and a number of controls (widgets). 6 | The main windows serves as a parent widget for the controls much like QtGui.QWidget class and provides a grid layout 7 | manager to place the controls. 8 | 9 | PyXBMCt developer's documentation: http://romanvm.github.io/script.module.pyxbmct 10 | 11 | Support thread on Kodi forum: http://forum.kodi.tv/showthread.php?tid=174859 12 | 13 | The framework uses image textures from Kodi Estuary and Confluence skins. 14 | 15 | See also [PyXBMCt demo addon](https://github.com/romanvm/pyxbmct.demo) that demonstrates 16 | the usage of PyXBMCt mini-framework. 17 | 18 | Licence: [GPL v.3](http://www.gnu.org/licenses/gpl.html). 19 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyXBMCt.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyXBMCt.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PyXBMCt" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyXBMCt" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/_autosummary/.notempty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/docs/_autosummary/.notempty -------------------------------------------------------------------------------- /docs/_static/addon_dialog_window.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/docs/_static/addon_dialog_window.jpg -------------------------------------------------------------------------------- /docs/_static/addon_full_window.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/docs/_static/addon_full_window.jpg -------------------------------------------------------------------------------- /docs/_static/example_ui.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/docs/_static/example_ui.jpg -------------------------------------------------------------------------------- /docs/_static/hello_world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/docs/_static/hello_world.jpg -------------------------------------------------------------------------------- /docs/_static/pop-up.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/docs/_static/pop-up.jpg -------------------------------------------------------------------------------- /docs/_static/pyxbmct_controls_confl.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/docs/_static/pyxbmct_controls_confl.jpg -------------------------------------------------------------------------------- /docs/_static/pyxbmct_controls_est.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/docs/_static/pyxbmct_controls_est.jpg -------------------------------------------------------------------------------- /docs/classes.rst: -------------------------------------------------------------------------------- 1 | Base Classes 2 | ============ 3 | 4 | PyXBMCt provides 4 base classes: `AddonDialogWindow`_, `AddonFullWindow`_, `BlankDialogWindow and BlankFullWindow`_. These classes serve as containers for other UI elements (controls). 5 | All base classes are "new-style" Python classes. 6 | 7 | AddonDialogWindow 8 | ----------------- 9 | 10 | :class:`AddonDialogWindow` class is based on :class:`xbmcgui.WindowDialog` 11 | and provides an interface window with background and a title-bar. 12 | The window serves as a parent control to other UI controls. Like all the other base classes, 13 | :class:`AddonDialogWindow` has the Grid layout manager 14 | to simplify arranging your UI controls and the event connection manager 15 | to connect XBMC UI events to functions and methods of your addon. 16 | 17 | .. figure:: _static/addon_dialog_window.jpg 18 | 19 | **AddonDialogWindow parent window** 20 | 21 | The main control window of :class:`AddonDialogWindow` 22 | is always displayed on top of Kodi UI, even video playback and music visualization, so it’s better suited for addons 23 | that are not supposed to play video or music. 24 | 25 | .. note:: Width, height and coordinates (optional) for the control window are specified 26 | in Kodi UI coordinate grid pixels. 27 | 28 | The default resolution of UI coordinate grid is always 1280x720 regardless of your actual display resolution. 29 | This way UI elements have the same visible size no matter what display resolution you use. 30 | 31 | AddonFullWindow 32 | --------------- 33 | 34 | :class:`AddonFullWinow` is based on :class:`xbcmgui.Window` class. 35 | It is similar to :class:`AddonDialogWindow` and also provides 36 | a parent control window for other UI controls. 37 | But, unlike :class:`AddonDialogWindow`, 38 | it has a solid main background (for which the default Estuary or Confluence background is used) 39 | and can hide under video or music visualization. 40 | 41 | .. figure:: _static/addon_full_window.jpg 42 | 43 | **AddonFullWindow parent control window** 44 | 45 | BlankDialogWindow and BlankFullWindow 46 | ------------------------------------- 47 | 48 | :class:`BlankDialogWindow` and 49 | :class:`BlankFullWindow` are based on :class:`xbmcgui.WindowDialog` 50 | and :class:`xbmcgui.Window` respectively. 51 | They have no visual elements whatsoever, but, like the 2 previously described classes, 52 | they provide the Grid layout and event connection managers. 53 | 54 | Those classes are meant for DIY developers who want full control over the visual appearance of their addons. 55 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | basedir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 5 | 6 | sys.path.insert(0, os.path.join(basedir, 'script.module.pyxbmct', 'lib')) 7 | 8 | extensions = [ 9 | 'sphinx.ext.autodoc', 10 | 'sphinx.ext.ifconfig', 11 | 'sphinx.ext.viewcode', 12 | 'sphinx.ext.autosummary', 13 | 'sphinx.ext.intersphinx', 14 | 'sphinx.ext.githubpages', 15 | 'alabaster', 16 | ] 17 | 18 | autodoc_member_order = 'bysource' 19 | autodoc_default_options = { 20 | 'members': True, 21 | 'show-inheritance': True, 22 | } 23 | autosummary_generate = True 24 | intersphinx_mapping = { 25 | 'python': ('https://docs.python.org/3.11', None), 26 | 'Kodistubs': ('http://romanvm.github.io/Kodistubs', None), 27 | } 28 | 29 | # Add any paths that contain templates here, relative to this directory. 30 | templates_path = ['_templates'] 31 | 32 | # The suffix(es) of source filenames. 33 | # You can specify multiple suffix as a list of string: 34 | # source_suffix = ['.rst', '.md'] 35 | source_suffix = '.rst' 36 | 37 | # The encoding of source files. 38 | source_encoding = 'utf-8' 39 | 40 | # The master toctree document. 41 | master_doc = 'index' 42 | 43 | # General information about the project. 44 | project = u'PyXBMCt' 45 | copyright = u'2015, Roman Miroshnychenko' 46 | author = u'Roman Miroshnychenko' 47 | 48 | language = 'en_US' 49 | 50 | exclude_patterns = ['_build'] 51 | 52 | pygments_style = 'sphinx' 53 | 54 | todo_include_todos = False 55 | 56 | html_theme = 'alabaster' 57 | 58 | html_theme_options = { 59 | 'github_button': True, 60 | 'github_type': 'star&v=2', 61 | 'github_user': 'romanvm', 62 | 'github_repo': 'script.module.pyxbmct', 63 | 'github_banner': True, 64 | 'description': 'A GUI micro-framework for Kodi mediacenter addons', 65 | 'font_family': 'Georgia', 66 | } 67 | 68 | html_static_path = ['_static'] 69 | 70 | html_sidebars = { 71 | '**': [ 72 | 'about.html', 73 | 'navigation.html', 74 | 'searchbox.html', 75 | ] 76 | } 77 | 78 | html_show_sourcelink = False 79 | 80 | htmlhelp_basename = 'PyXBMCtdoc' 81 | 82 | # -- Options for LaTeX output --------------------------------------------- 83 | 84 | latex_elements = { 85 | # The paper size ('letterpaper' or 'a4paper'). 86 | #'papersize': 'letterpaper', 87 | 88 | # The font size ('10pt', '11pt' or '12pt'). 89 | #'pointsize': '10pt', 90 | 91 | # Additional stuff for the LaTeX preamble. 92 | #'preamble': '', 93 | 94 | # Latex figure (float) alignment 95 | #'figure_align': 'htbp', 96 | } 97 | 98 | # Grouping the document tree into LaTeX files. List of tuples 99 | # (source start file, target name, title, 100 | # author, documentclass [howto, manual, or own class]). 101 | latex_documents = [ 102 | (master_doc, 'PyXBMCt.tex', u'PyXBMCt Documentation', 103 | u'Roman Miroshnychenko', 'manual'), 104 | ] 105 | 106 | # -- Options for manual page output --------------------------------------- 107 | 108 | # One entry per manual page. List of tuples 109 | # (source start file, name, description, authors, manual section). 110 | man_pages = [ 111 | (master_doc, 'pyxbmct', u'PyXBMCt Documentation', 112 | [author], 1) 113 | ] 114 | 115 | # -- Options for Texinfo output ------------------------------------------- 116 | 117 | # Grouping the document tree into Texinfo files. List of tuples 118 | # (source start file, target name, title, author, 119 | # dir menu entry, description, category) 120 | texinfo_documents = [ 121 | (master_doc, 'PyXBMCt', u'PyXBMCt Documentation', 122 | author, 'PyXBMCt', 'One line description of project.', 123 | 'Miscellaneous'), 124 | ] 125 | -------------------------------------------------------------------------------- /docs/connection.rst: -------------------------------------------------------------------------------- 1 | Connecting Events 2 | ================= 3 | 4 | Connecting events works similarly to the signal-slot connection mechanism of Qt framework. 5 | There are two types of events: Control events (when an on-screen Control, e.g. a button, is actvated) 6 | and a keyboard action (when a key bound to an action is pressed). 7 | You can connect an event to a function or a method that will be run 8 | when the respective event is triggered. 9 | The connection mechanism is implemented through :meth:`connect` 10 | method of a base PyXBMCt class. This methods takes 2 parameters: an object to be connected (a Control instance 11 | or a numeric action code, and a function/method object to be called. For example:: 12 | 13 | self.connect(self.foo_button, self.on_foo_clicked) 14 | 15 | Here ``self.foo_button`` is a :class:`Button ` instance and 16 | ``self.on_foo_clicked`` is some method that needs to be called when a user activates 17 | ``self.foo_button``. 18 | 19 | .. warning:: 20 | For connection you must provide a function object without brackets ``()``, not a function call. 21 | Do not confuse those two! 22 | 23 | Similarly to PyQt signal-slot connection, :keyword:`lambda` can be used to connect a function/method with arguments 24 | known at runtime. For example:: 25 | 26 | self.connect(self.foo_button, lambda: self.on_foo_clicked('bar', 'spam')) 27 | 28 | You can only connect the following controls: :class:`Button `, 29 | :class:`RadioButton ` and :class:`List `. 30 | Other controls do not generate any events, so connecting them won’t have any effect. 31 | 32 | The key code ``ACTION_PREVIOUS_MENU`` or ``10`` (bound to ``ESC`` key by default) is already connected 33 | to the method that closes a current addon window (``close``), so you cannot connect it to any function/method. 34 | Or technically you can, but such connection won’t work. It guarantees that you always have a way 35 | to close an active addon window. 36 | -------------------------------------------------------------------------------- /docs/controls.rst: -------------------------------------------------------------------------------- 1 | Controls 2 | ======== 3 | 4 | PyXBMCt provides 9 ready-to-use UI controls that are based on the respective :mod:`xbmcgui` controls 5 | with the following differences: 6 | 7 | * You don’t need to specify coordinates and size for the controls explicitly. 8 | The Grid layout manager takes care of control placement. 9 | * All controls that require textures are provided with default textures 10 | (borrowed from Confluence and Estuary skin resources). 11 | You can specify your own textures for PyXBMCt controls, but you need to do this through keyword arguments (important!). 12 | * Button caption is center-aligned by default. You can change button caption alignment by providing a necessary alignment 13 | parameter through a keyword argument (PyXBMCt already includes symbolic constants for control text alignment). 14 | Since all PyXBMCt Controls are subclassed from ``xbmcgui.Control*`` classes, you can use all parent :mod:`xbmcgui` 15 | classes' methods to set Control properties. 16 | 17 | .. figure:: _static/pyxbmct_controls_confl.jpg 18 | 19 | **PyXBMCt controls (Confluence-based skin)** 20 | 21 | 22 | .. figure:: _static/pyxbmct_controls_est.jpg 23 | 24 | **PyXBMCt controls (Estuary-based skin)** 25 | 26 | Below is the list of PyXBMCt controls with brief descriptions: 27 | 28 | Label 29 | ----- 30 | 31 | :class:`Label` implements a simple text label much like ``Tkinter.Label`` or ``QLabel``. 32 | 33 | FadeLabel 34 | --------- 35 | 36 | :class:`FaldeLabel` is similar to :class:`Label`, 37 | but a very long text string is auto-scrolled. 38 | 39 | TextBox 40 | ------- 41 | 42 | :class:`TextBox` shows multiline text. It can autoscroll very long text. 43 | 44 | Image 45 | ----- 46 | 47 | :class:`Image` control displays images from files (``.jpg``, ``.png``, ``.gif``). 48 | For ``.gif`` and ``.png`` images transparency is supported, and for ``.gif`` animation is shown as well. 49 | 50 | Button 51 | ------ 52 | 53 | :class:`Button` implements a clickable button. It generates a control event on click. 54 | 55 | RadioButton 56 | ----------- 57 | 58 | :class:`RadioButton` is a 2-state switch. It generates a control event on click. 59 | 60 | Edit 61 | ---- 62 | 63 | :class:`Edit` implements a text entry field, similar to ``Tkinter.Entry`` or ``QLineEdit``. 64 | When activated, it opens an on-screen keyboard to enter text. 65 | 66 | List 67 | ---- 68 | 69 | :class:`List` implements a list of items. 70 | The list scrolls when it cannot display all its items within available space. 71 | It generates a control event when an item is selected. 72 | 73 | Slider 74 | ------ 75 | 76 | :class:`Slider` is a control for stepless adjusting some value (e.g. volume level). 77 | -------------------------------------------------------------------------------- /docs/estuary.rst: -------------------------------------------------------------------------------- 1 | Estuary Skin Support 2 | ==================== 3 | 4 | Starting from v.17 (Krypton) Kodi got a new default skin -- Estuary. PyXBMCt supports Estuary and automatically 5 | selects its appearance -- Confluence-based or Estuary-based -- depending on Kodi version by default. 6 | PyXBMCt appearance can also be set explicitly:: 7 | 8 | import pyxbmct 9 | 10 | pyxbmct.skin.estuary = True # False: use old design 11 | 12 | # Then create your UI elements 13 | 14 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Code Examples 2 | ============= 3 | 4 | Now let's take a look at some examples. As always, we'll start with "Hello, World!". 5 | 6 | "Hello, World!" example 7 | ----------------------- 8 | 9 | The simplest code which will display a window with "Hello, World!" header looks like this:: 10 | 11 | # Import PyXBMCt module. 12 | import pyxbmct 13 | 14 | # Create a window instance. 15 | window = pyxbmct.AddonDialogWindow('Hello, World!') 16 | # Set window width, height and grid resolution. 17 | window.setGeometry(400, 400, 1, 1) 18 | # Show the created window. 19 | window.doModal() 20 | # Delete the window instance when it is no longer used. 21 | del window 22 | 23 | If you've done everything correctly, you should see a window like the one shown below: 24 | 25 | .. figure:: _static/hello_world.jpg 26 | 27 | **"Hello World!" example** 28 | 29 | The window Grid has 1 row and 1 column. We haven't placed any controls on it, but ``setGeometry`` method takes 30 | at least 4 arguments, so we have provided it dummy values. 31 | Also for simplicity's sake we haven't used OOP in this example. 32 | 33 | Now let's analyze a more complex example. 34 | 35 | Example with interactive controls 36 | --------------------------------- 37 | 38 | First, we need to draft the layout or our UI. You can use a pen and paper or imagine the layout in your head, 39 | it does not matter. The following table showsh the draft of the UI layout for our example addon: 40 | 41 | +----------------+----------------+----------------+ 42 | | Rows\\Columns | 0 | 1 | 43 | +----------------+----------------+----------------+ 44 | | 0 | Image | 45 | +----------------+ | 46 | | 1 | | 47 | +----------------+ | 48 | | 2 | | 49 | +----------------+----------------+----------------+ 50 | | 3 | Name Label | Name Edit | 51 | +----------------+----------------+----------------+ 52 | | 4 | "Close" button | "Hello" button | 53 | +----------------+----------------+----------------+ 54 | 55 | As you can see, our example UI will have 4 rows, 2 columns and 5 controls placed in grid cells. 56 | Let’s see how it looks in Python code:: 57 | 58 | # Import necessary modules 59 | import xbmc 60 | import pyxbmct 61 | 62 | # Create a class for our UI 63 | class MyAddon(pyxbmct.AddonDialogWindow): 64 | 65 | def __init__(self, title=''): 66 | """Class constructor""" 67 | # Call the base class' constructor. 68 | super(MyAddon, self).__init__(title) 69 | # Set width, height and the grid parameters 70 | self.setGeometry(300, 280, 5, 2) 71 | # Call set controls method 72 | self.set_controls() 73 | # Call set navigation method. 74 | self.set_navigation() 75 | # Connect Backspace button to close our addon. 76 | self.connect(pyxbmct.ACTION_NAV_BACK, self.close) 77 | 78 | def set_controls(self): 79 | """Set up UI controls""" 80 | # Image control 81 | image = pyxbmct.Image('https://peach.blender.org/wp-content/uploads/poster_rodents_small.jpg?3016dc') 82 | self.placeControl(image, 0, 0, rowspan=3, columnspan=2) 83 | # Text label 84 | label = pyxbmct.Label('Your name:') 85 | self.placeControl(label, 3, 0) 86 | # Text edit control 87 | self.name_field = pyxbmct.Edit('') 88 | self.placeControl(self.name_field, 3, 1) 89 | # Close button 90 | self.close_button = pyxbmct.Button('Close') 91 | self.placeControl(self.close_button, 4, 0) 92 | # Connect close button 93 | self.connect(self.close_button, self.close) 94 | # Hello button. 95 | self.hello_buton = pyxbmct.Button('Hello') 96 | self.placeControl(self.hello_buton, 4, 1) 97 | # Connect Hello button. 98 | self.connect(self.hello_buton, lambda: 99 | xbmc.executebuiltin('Notification(Hello {0}!, Welcome to PyXBMCt.)'.format( 100 | self.name_field.getText()))) 101 | 102 | def set_navigation(self): 103 | """Set up keyboard/remote navigation between controls.""" 104 | self.name_field.controlUp(self.hello_buton) 105 | self.name_field.controlDown(self.hello_buton) 106 | self.close_button.controlLeft(self.hello_buton) 107 | self.close_button.controlRight(self.hello_buton) 108 | self.hello_buton.setNavigation(self.name_field, self.name_field, self.close_button, self.close_button) 109 | # Set initial focus. 110 | self.setFocus(self.name_field) 111 | 112 | 113 | if __name__ == '__main__': 114 | myaddon = MyAddon('PyXBMCt Example') 115 | myaddon.doModal() 116 | del myaddon 117 | 118 | This code should display the following window: 119 | 120 | .. figure:: _static/example_ui.jpg 121 | 122 | **Our example UI** 123 | 124 | If you enter your name (or any words for that matter) and click "Hello" button, 125 | the addon will display a pop-up notification: 126 | 127 | .. figure:: _static/pop-up.jpg 128 | 129 | **The pop-up notification** 130 | 131 | Two remarks about the code: 132 | 133 | - In my example I have used an online URL for the Image control. 134 | Paths to image files stored on your local disks can be used as well. 135 | - Note the usage of :keyword:`lambda` to connect a function 136 | (:func:`xbmc.executebuiltin` in this case) with an argument. 137 | 138 | Despite being rather simple, this example illustrates main steps of initializing PyXBMCt-based addon UI: 139 | 140 | - Set up the geometry and grid of the main window. 141 | - Place UI controls on the grid. 142 | - Connect interactive controls and key actions to functions/methods. 143 | - Set up keyboard/remote navigation between controls. 144 | - Set initial focus on a control (necessary for navigation to work). 145 | 146 | `PyXBMCt demo addon`_ povides more compherensive example on how to use all PyXBMCt Controls. 147 | 148 | .. _PyXBMCt demo addon: https://github.com/romanvm/pyxbmct.demo 149 | -------------------------------------------------------------------------------- /docs/grid.rst: -------------------------------------------------------------------------------- 1 | Grid Layout 2 | =========== 3 | 4 | The Grid Layout helps to place UI controls within the parent window. 5 | It is similar to PyQt’s QGridLayout or Tkniter’s Grid geometry manager. 6 | The Grid Layout is implemented through 7 | :meth:`setGeometry ` and 8 | :meth:`placeControl ` methods of a base PyXBMCt class. 9 | 10 | .. warning:: 11 | Currently PyXBMCt does not support changing window geometry at runtime so you must call 12 | :meth:`setGeometry` method only once. 13 | 14 | To place a control you simply provide it as the 1st positional argument to 15 | :meth:`placeControl ` method, 16 | and then specify a row and a column for the control as the next arguments, 17 | and the control will be placed in a specific grid cell. 18 | This eliminates the need to provide exact coordinates for each control and then fine-tune them. 19 | If a control needs to occupy several grid cells, you can provide ``rowspan`` 20 | and/or ``columspan`` parameters to specify how many cells it should take. 21 | 22 | .. note:: 23 | Row and column numbers start from zero, i.e. the top-left cell will have row# = ``0``, column# = ``0``. 24 | 25 | The :meth:`placeControl ` medhod 26 | does not actually check if a control will actually be placed within the parent window. 27 | By providing a row and/or a column number which exceeds row and/or column count of the parent window, 28 | a control can be placed outside the window, intentionally or unintentionally. 29 | You need to check the visual appearance of your addon window and correct positions of controls, if necessary. 30 | 31 | The Grid Layout also works with xbmcgui Controls, but when instantiating an xbmcgui Control you need 32 | to provide it with fake coordinates and size. Any integer values will do. 33 | 34 | .. tip:: 35 | The size and aspect of an individual control can be adjusted with ``pad_x`` and ``pad_y`` parameters 36 | of :meth:`placeControl` method. 37 | By default, both padding values equal ``5``. 38 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Wellcome to PyXBMCt documentation! 2 | ================================== 3 | 4 | PyXBMCt is a Python micro-framework created to simplify creating GUI for `Kodi (XBMC)`_ mediacenter addons. 5 | It was inspired by PyQt (hence the name) and shares the same basic principles, 6 | so those who are familiar with PyQt/PySide should feel themselves right at home. 7 | 8 | The framework provides 4 base container classes, 9 ready-to-use widgets or, in Kodi terms, controls, 9 | a Grid layout manager and an event connection manager. 10 | 11 | PyXBMCt uses texture images from Kodi's default Confluence and Estuary (Kodi 17 "Krypton" and above) 12 | skins to decorate its visual elements. 13 | Those textures are included in PyXBMCt, so UI based on it will have the same look in different skins. 14 | 15 | PyXBMCt is essentially a thin wrapper around several :mod:`xbmcgui` classes so please consult 16 | xbmcgui module documentation on how to use all features of PyXBMCt windows and controls. 17 | 18 | PyXBMCt does not provide as many features and customization capabilites as skined GUIs based on 19 | :class:`xbmcgui.WindowXML` and :class:`xbmcgui.WindowXMLDialog` classes but it is relatively easy to learn 20 | and does not require the knowledge of Kodi skinning. PyXBMCt-based GUIs can be created entirely in Python. 21 | 22 | .. _Kodi (XBMC): http://www.kodi.tv 23 | 24 | Licenses: 25 | --------- 26 | 27 | * Source code: `GPL v.3`_ 28 | * This documentation: `CC BY-NC-SA 4.0`_ 29 | 30 | .. _GPL v.3: https://www.gnu.org/licenses/gpl-3.0.en.html 31 | .. _CC BY-NC-SA 4.0: https://creativecommons.org/licenses/by-nc-sa/4.0/ 32 | 33 | Contents: 34 | --------- 35 | 36 | .. toctree:: 37 | :maxdepth: 2 38 | 39 | classes 40 | controls 41 | grid 42 | connection 43 | using 44 | examples 45 | estuary 46 | skins 47 | links 48 | pyxbmct 49 | 50 | 51 | Indices and tables 52 | ================== 53 | 54 | * :ref:`genindex` 55 | * :ref:`search` 56 | -------------------------------------------------------------------------------- /docs/links.rst: -------------------------------------------------------------------------------- 1 | Links 2 | ===== 3 | 4 | * `PyXBMCt demo addon`_ 5 | * `Kodi Python API documentation`_ 6 | * `The support thread on Kodi (XBMC) forum`_ 7 | 8 | .. _PyXBMCt demo addon: https://github.com/romanvm/pyxbmct.demo 9 | .. _Kodi Python API documentation: http://romanvm.github.io/xbmcstubs/docs 10 | .. _The support thread on Kodi (XBMC) forum: http://forum.xbmc.org/showthread.php?tid=174859 11 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 1>NUL 2>NUL 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyXBMCt.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyXBMCt.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/pyxbmct.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | All non-abstract classes are available at package level, for example:: 5 | 6 | from pyxbmct import AddonDialogWindow, Label, Button 7 | 8 | .. autosummary:: 9 | :toctree: _autosummary 10 | 11 | pyxbmct.addonwindow 12 | pyxbmct.addonskin 13 | -------------------------------------------------------------------------------- /docs/skins.rst: -------------------------------------------------------------------------------- 1 | Skins 2 | ===== 3 | 4 | Starting from ``v.1.2.0``, PyXBMCt provides :class:`BaseSkin` and 5 | :class:`Skin` classes that allow you to customize 6 | PyXBMCt UI elements appearance fully or partially. 7 | To customize your UI skin you need to subclass one of those classes and 8 | set your custom skin instance as ``pyxbmct.skin`` module-level property (see below). 9 | 10 | :class:`BaseSkin` is an abstract class that allows you 11 | to fully customize the appearance of PyXBMCt ``*Window`` classes. 12 | You need to subclass it and define all your custom skin attributes -- image textures, 13 | coorditate offsets, etc. -- as properties of your derived class. 14 | 15 | The :class:`Skin` can be subclassed to change only some 16 | of the current PyXBMCt skin properties. The following example shows how to change the 17 | fullscreen background of :class:`AddonFullWindow` 18 | class without altering other elements:: 19 | 20 | import pyxbmct 21 | 22 | 23 | class MySkin(pyxbmct.Skin): 24 | @property 25 | def main_bg_img(self): 26 | return '/path/to/my/background_image.png' 27 | 28 | 29 | pyxbmct.addonwindow.skin = MySkin() 30 | 31 | 32 | # Then create your UI window class with the new background 33 | class MyCoolWindow(pyxbmct.AddonWindow): 34 | ... 35 | 36 | -------------------------------------------------------------------------------- /docs/using.rst: -------------------------------------------------------------------------------- 1 | Using PyXBMCt In Your Addon 2 | =========================== 3 | 4 | PyXBMCt addon module is included in the official Kodi (XBMC) repo. So to use it, first you need to add 5 | the following string into ```` section of your ``addon.xml``:: 6 | 7 | 8 | 9 | Then you need to import pyxbmct module into the namespace of your addon:: 10 | 11 | import pyxbmct 12 | 13 | -------------------------------------------------------------------------------- /script.module.pyxbmct/License.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | -------------------------------------------------------------------------------- /script.module.pyxbmct/addon.xml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | all 12 | PyXBMCt UI framework 13 | PyXBMCt is a mini-framework for simple XBMC addon UI buliding. It is similar to PyQt and provides parent windows, a number of UI controls (widgets) and a grid layout manager to place controls. 14 | GPL-3.0-only 15 | https://forum.kodi.tv/showthread.php?tid=174859 16 | http://romanvm.github.io/script.module.pyxbmct/ 17 | roman1972@gmail.com 18 | https://github.com/romanvm/script.module.pyxbmct 19 | 1.3.2: 20 | - Drop Python 2 compatibility 21 | 22 | icon.png 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /script.module.pyxbmct/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/icon.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | PyXBMCt framework package 3 | 4 | PyXBMCt is a mini-framework for creating Kodi (XBMC) Python addons 5 | with arbitrary UI made of Controls - decendants of xbmcgui.Control class. 6 | The framework uses image textures from Kodi Confluence skin. 7 | 8 | Licence: GPL v.3 http://www.gnu.org/licenses/gpl.html 9 | """ 10 | 11 | from __future__ import absolute_import 12 | from .addonwindow import * 13 | from .addonskin import BaseSkin 14 | 15 | __all__ = [ 16 | 'ALIGN_LEFT', 17 | 'ALIGN_RIGHT', 18 | 'ALIGN_CENTER_X', 19 | 'ALIGN_CENTER_Y', 20 | 'ALIGN_CENTER', 21 | 'ALIGN_TRUNCATED', 22 | 'ALIGN_JUSTIFY', 23 | 'ACTION_PREVIOUS_MENU', 24 | 'ACTION_NAV_BACK', 25 | 'ACTION_MOVE_LEFT', 26 | 'ACTION_MOVE_RIGHT', 27 | 'ACTION_MOVE_UP', 28 | 'ACTION_MOVE_DOWN', 29 | 'ACTION_MOUSE_WHEEL_UP', 30 | 'ACTION_MOUSE_WHEEL_DOWN', 31 | 'ACTION_MOUSE_DRAG', 32 | 'ACTION_MOUSE_MOVE', 33 | 'ACTION_MOUSE_LEFT_CLICK', 34 | 'AddonWindowError', 35 | 'Label', 36 | 'FadeLabel', 37 | 'TextBox', 38 | 'Image', 39 | 'Button', 40 | 'RadioButton', 41 | 'Edit', 42 | 'List', 43 | 'Slider', 44 | 'BlankFullWindow', 45 | 'BlankDialogWindow', 46 | 'AddonDialogWindow', 47 | 'AddonFullWindow', 48 | 'Skin', 49 | 'skin', 50 | 'BaseSkin' 51 | ] 52 | -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/addonskin.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # Module: skin 3 | # Created on: 04.03.2016 4 | # Author: Roman Miroshnychenko aka Roman V.M. (romanvm@yandex.ua) 5 | # Licence: GPL v.3 6 | """Classes for defining the appearance of PyXBMCt Windows and Controls""" 7 | 8 | import os 9 | from abc import ABC, abstractmethod 10 | 11 | import xbmcvfs 12 | from xbmcaddon import Addon 13 | 14 | ADDON_DIR = xbmcvfs.translatePath(Addon('script.module.pyxbmct').getAddonInfo('path')) 15 | 16 | 17 | class BaseSkin(ABC): 18 | """ 19 | Abstract class for creating fully customized skins 20 | 21 | .. warning:: This class is meant for subclassing and cannot be instantiated directly! 22 | A sublcass must implement all the following properties. 23 | """ 24 | @abstractmethod 25 | def images(self): 26 | """ 27 | Get the base directory for image files 28 | 29 | :rtype: str 30 | """ 31 | return 32 | 33 | @abstractmethod 34 | def x_margin(self): 35 | """ 36 | Get horizontal adjustment for the header background 37 | if the main background has transparent edges. 38 | 39 | :rtype: int 40 | """ 41 | return 42 | 43 | @abstractmethod 44 | def y_margin(self): 45 | """ 46 | Get vertical adjustment for the header background 47 | if the main background has transparent edges. 48 | 49 | :rtype: int 50 | """ 51 | return 52 | 53 | @abstractmethod 54 | def title_bar_x_shift(self): 55 | """ 56 | Get horizontal adjustment for title bar texture 57 | 58 | :rtype: int 59 | """ 60 | return 61 | 62 | @abstractmethod 63 | def title_bar_y_shift(self): 64 | """ 65 | Get vertical adjustment for title bar texture 66 | 67 | :rtype: int 68 | """ 69 | return 70 | 71 | @abstractmethod 72 | def title_back_y_shift(self): 73 | """ 74 | Get header position adjustment 75 | if the main background has visible borders. 76 | 77 | :rtype: int 78 | """ 79 | return 80 | 81 | @abstractmethod 82 | def header_height(self): 83 | """ 84 | Get the height of a window header 85 | (for the title background and the title label). 86 | 87 | :rtype: int 88 | """ 89 | return 90 | 91 | @abstractmethod 92 | def close_btn_width(self): 93 | """ 94 | Get the width of the top-right close button 95 | 96 | :rtype: int 97 | """ 98 | return 99 | 100 | @abstractmethod 101 | def close_btn_height(self): 102 | """ 103 | Get the height of the top-right close button 104 | 105 | :rtype: int 106 | """ 107 | return 108 | 109 | @abstractmethod 110 | def close_btn_x_offset(self): 111 | """ 112 | Get close button horizontal adjustment 113 | 114 | :rtype: int 115 | """ 116 | return 117 | 118 | @abstractmethod 119 | def close_btn_y_offset(self): 120 | """ 121 | Get close button vertical adjustment 122 | 123 | :rtype: int 124 | """ 125 | return 126 | 127 | @abstractmethod 128 | def header_align(self): 129 | """ 130 | Get a numeric value for header text alignment 131 | 132 | For example: 133 | 134 | - ``0``: left 135 | - ``6``: center 136 | 137 | :rtype: int 138 | """ 139 | return 140 | 141 | @abstractmethod 142 | def header_text_color(self): 143 | """ 144 | Get the color of the header text 145 | 146 | :rtype: str 147 | """ 148 | return 149 | 150 | @abstractmethod 151 | def background_img(self): 152 | """ 153 | Get dialog background texture 154 | 155 | :rtype: str 156 | """ 157 | return 158 | 159 | @abstractmethod 160 | def title_background_img(self): 161 | """ 162 | Get title bar background texture 163 | 164 | :rtype: str 165 | """ 166 | return 167 | 168 | @abstractmethod 169 | def close_button_focus(self): 170 | """ 171 | Get close button focused texture 172 | 173 | :rtype: str 174 | """ 175 | return 176 | 177 | @abstractmethod 178 | def close_button_no_focus(self): 179 | """ 180 | Get close button unfocused texture 181 | 182 | :rtype: str 183 | """ 184 | return 185 | 186 | @abstractmethod 187 | def main_bg_img(self): 188 | """ 189 | Get fullscreen background for 190 | :class:`AddonFullWindow` class 191 | 192 | :rtype: str 193 | """ 194 | return 195 | 196 | 197 | class Skin(BaseSkin): 198 | """ 199 | Skin class 200 | 201 | Defines parameters that control 202 | the appearance of PyXBMCt windows and controls. 203 | """ 204 | def __init__(self): 205 | self._estuary = True 206 | self._texture_dir = os.path.join(ADDON_DIR, 207 | 'lib', 'pyxbmct', 'textures') 208 | 209 | @property 210 | def estuary(self): 211 | """ 212 | Get or set a boolean property that defines the look of PyXBMCt elements: 213 | 214 | - ``True`` -- use Estuary skin appearance 215 | - ``False`` -- use Confluence skin appearance. 216 | 217 | :rtype: bool 218 | """ 219 | return self._estuary 220 | 221 | @estuary.setter 222 | def estuary(self, value): 223 | if not isinstance(value, bool): 224 | raise TypeError('estuary property value must be bool!') 225 | self._estuary = value 226 | 227 | @property 228 | def images(self): 229 | if self.estuary: 230 | return os.path.join(self._texture_dir, 'estuary') 231 | else: 232 | return os.path.join(self._texture_dir, 'confluence') 233 | 234 | @property 235 | def x_margin(self): 236 | if self.estuary: 237 | return 0 238 | else: 239 | return 5 240 | 241 | @property 242 | def y_margin(self): 243 | if self.estuary: 244 | return 0 245 | else: 246 | return 5 247 | 248 | @property 249 | def title_bar_x_shift(self): 250 | if self.estuary: 251 | return 20 252 | else: 253 | return 0 254 | 255 | @property 256 | def title_bar_y_shift(self): 257 | if self.estuary: 258 | return 8 259 | else: 260 | return 4 261 | 262 | @property 263 | def title_back_y_shift(self): 264 | if self.estuary: 265 | return 0 266 | else: 267 | return 4 268 | 269 | @property 270 | def header_height(self): 271 | if self.estuary: 272 | return 45 273 | else: 274 | return 35 275 | 276 | @property 277 | def close_btn_width(self): 278 | if self.estuary: 279 | return 35 280 | else: 281 | return 60 282 | 283 | @property 284 | def close_btn_height(self): 285 | if self.estuary: 286 | return 30 287 | else: 288 | return 30 289 | 290 | @property 291 | def close_btn_x_offset(self): 292 | if self.estuary: 293 | return 50 294 | else: 295 | return 70 296 | 297 | @property 298 | def close_btn_y_offset(self): 299 | if self.estuary: 300 | return 7 301 | else: 302 | return 4 303 | 304 | @property 305 | def header_align(self): 306 | if self.estuary: 307 | return 0 308 | else: 309 | return 6 310 | 311 | @property 312 | def header_text_color(self): 313 | if self.estuary: 314 | return '' 315 | else: 316 | return '0xFFFFA500' 317 | 318 | @property 319 | def background_img(self): 320 | return os.path.join(self.images, 'AddonWindow', 'ContentPanel.png') 321 | 322 | @property 323 | def title_background_img(self): 324 | return os.path.join(self.images, 'AddonWindow', 'dialogheader.png') 325 | 326 | @property 327 | def close_button_focus(self): 328 | return os.path.join(self.images, 'AddonWindow', 'DialogCloseButton-focus.png') 329 | 330 | @property 331 | def close_button_no_focus(self): 332 | return os.path.join(self.images, 'AddonWindow', 'DialogCloseButton.png') 333 | 334 | @property 335 | def main_bg_img(self): 336 | return os.path.join(self.images, 'AddonWindow', 'SKINDEFAULT.jpg') 337 | -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/addonwindow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # PyXBMCt framework module 3 | # 4 | # PyXBMCt is a mini-framework for creating Kodi (XBMC) Python addons 5 | # with arbitrary UI made of Controls - decendants of xbmcgui.Control class. 6 | # The framework uses image textures from Kodi Confluence skin. 7 | # 8 | # Licence: GPL v.3 9 | """ 10 | This module contains all classes and constants of PyXBMCt framework 11 | """ 12 | 13 | import os 14 | 15 | import xbmc 16 | import xbmcgui 17 | 18 | from .addonskin import Skin 19 | 20 | skin = Skin() 21 | 22 | # Text alighnment constants. Mixed variants are obtained by bit OR (|) 23 | ALIGN_LEFT = 0 24 | """Align left""" 25 | ALIGN_RIGHT = 1 26 | """Align right""" 27 | ALIGN_CENTER_X = 2 28 | """Align center horisontally""" 29 | ALIGN_CENTER_Y = 4 30 | """Align center vertically""" 31 | ALIGN_CENTER = 6 32 | """Align center by both axis""" 33 | ALIGN_TRUNCATED = 8 34 | """Align truncated""" 35 | ALIGN_JUSTIFY = 10 36 | """Align justify""" 37 | 38 | # Kodi key action codes. 39 | # More codes available in xbmcgui module 40 | ACTION_PREVIOUS_MENU = 10 41 | """ESC action""" 42 | ACTION_NAV_BACK = 92 43 | """Backspace action""" 44 | ACTION_MOVE_LEFT = 1 45 | """Left arrow key""" 46 | ACTION_MOVE_RIGHT = 2 47 | """Right arrow key""" 48 | ACTION_MOVE_UP = 3 49 | """Up arrow key""" 50 | ACTION_MOVE_DOWN = 4 51 | """Down arrow key""" 52 | ACTION_MOUSE_WHEEL_UP = 104 53 | """Mouse wheel up""" 54 | ACTION_MOUSE_WHEEL_DOWN = 105 55 | """Mouse wheel down""" 56 | ACTION_MOUSE_DRAG = 106 57 | """Mouse drag""" 58 | ACTION_MOUSE_MOVE = 107 59 | """Mouse move""" 60 | ACTION_MOUSE_LEFT_CLICK = 100 61 | """Mouse click""" 62 | 63 | 64 | def _set_textures(textures, kwargs): 65 | """Set texture arguments for controls.""" 66 | for texture in textures: 67 | if kwargs.get(texture) is None: 68 | kwargs[texture] = textures[texture] 69 | 70 | 71 | class AddonWindowError(Exception): 72 | """Custom exception""" 73 | pass 74 | 75 | 76 | class Label(xbmcgui.ControlLabel): 77 | """ 78 | Label(label, font=None, textColor=None, disabledColor=None, alignment=0,hasPath=False, angle=0) 79 | 80 | ControlLabel class. 81 | 82 | Implements a simple text label. 83 | 84 | :param label: text string 85 | :type label: str 86 | :param font: font used for label text. (e.g. ``'font13'``) 87 | :type font: str 88 | :param textColor: hex color code of enabled label's label. (e.g. ``'0xFFFFFFFF'``) 89 | :type textColor: str 90 | :param disabledColor: hex color code of disabled label's label. (e.g. ``'0xFFFF3300'``) 91 | :type disabledColor: str 92 | :param alignment: alignment of label. **Note**: see ``xbfont.h`` 93 | :type alignment: int 94 | :param hasPath: ``True`` = stores a path / ``False`` = no path. 95 | :type hasPath: bool 96 | :param angle: angle of control. (``+`` rotates CCW, ``-`` rotates CW) 97 | :type angle: int 98 | 99 | .. note:: After you create the control, you need to add it to the window with placeControl(). 100 | 101 | Example:: 102 | 103 | self.label = Label('Status', angle=45) 104 | """ 105 | def __new__(cls, *args, **kwargs): 106 | return super(Label, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 107 | 108 | 109 | class FadeLabel(xbmcgui.ControlFadeLabel): 110 | """ 111 | FadeLabel(font=None, textColor=None, _alignment=0) 112 | 113 | Control that scrolls label text. 114 | 115 | Implements a text label that can auto-scroll very long text. 116 | 117 | :param font: font used for label text. (e.g. ``'font13'``) 118 | :type font: str 119 | :param textColor: hex color code of fadelabel's labels. (e.g. ``'0xFFFFFFFF'``) 120 | :type textColor: str 121 | :param _alignment: alignment of label. **Note**: see ``xbfont.h`` 122 | :type _alignment: int 123 | 124 | .. note:: After you create the control, you need to add it to the window with placeControl(). 125 | 126 | Example:: 127 | 128 | self.fadelabel = FadeLabel(textColor='0xFFFFFFFF') 129 | """ 130 | def __new__(cls, *args, **kwargs): 131 | return super(FadeLabel, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 132 | 133 | 134 | class TextBox(xbmcgui.ControlTextBox): 135 | """ 136 | TextBox(font=None, textColor=None) 137 | 138 | ControlTextBox class 139 | 140 | Implements a box for displaying multi-line text. 141 | Long text is truncated from below. Also supports auto-scrolling. 142 | 143 | :param font: font used for text. (e.g. ``'font13'``) 144 | :type font: str 145 | :param textColor: hex color code of textbox's text. (e.g. ``'0xFFFFFFFF'``) 146 | :type textColor: str 147 | 148 | .. note:: After you create the control, you need to add it to the window with placeControl(). 149 | 150 | Example:: 151 | 152 | self.textbox = TextBox(textColor='0xFFFFFFFF') 153 | """ 154 | def __new__(cls, *args, **kwargs): 155 | return super(TextBox, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 156 | 157 | 158 | class Image(xbmcgui.ControlImage): 159 | """ 160 | Image(filename, aspectRatio=0, colorDiffuse=None) 161 | 162 | ControlImage class. 163 | 164 | Implements a box for displaying ``.jpg``, ``.png``, and ``.gif`` images. 165 | 166 | :param filename: path or URL to an image file. 167 | :type filename: str 168 | :param aspectRatio: (values: ``0`` = stretch (default), ``1`` = scale up (crops), ``2`` = scale down (black bars) 169 | :type aspectRatio: int 170 | :param colorDiffuse: for example, ``'0xC0FF0000'`` (red tint) 171 | :type colorDiffuse: str 172 | 173 | .. note:: After you create the control, you need to add it to the window with placeControl(). 174 | 175 | Example:: 176 | 177 | self.image = Image('d:\images\picture.jpg', aspectRatio=2) 178 | """ 179 | def __new__(cls, *args, **kwargs): 180 | return super(Image, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 181 | 182 | 183 | class CompareMixin: 184 | def __eq__(self, other): 185 | if hasattr(other, 'getId'): 186 | return self.getId() == other.getId() 187 | return False 188 | 189 | 190 | class Button(CompareMixin, xbmcgui.ControlButton): 191 | """ 192 | Button(label, focusTexture=None, noFocusTexture=None, textOffsetX=CONTROL_TEXT_OFFSET_X, textOffsetY=CONTROL_TEXT_OFFSET_Y, alignment=4, font=None, textColor=None, disabledColor=None, angle=0, shadowColor=None, focusedColor=None) 193 | 194 | ControlButton class. 195 | 196 | Implements a clickable button. 197 | 198 | :param label: button caption 199 | :type label: str 200 | :param focusTexture: filename for focus texture. 201 | :type focusTexture: str 202 | :param noFocusTexture: filename for no focus texture. 203 | :type noFocusTexture: str 204 | :param textOffsetX: x offset of label. 205 | :type textOffsetX: int 206 | :param textOffsetY: y offset of label. 207 | :type textOffsetY: int 208 | :param alignment: alignment of label. **Note**: see ``xbfont.h`` 209 | :type alignment: int 210 | :param font: font used for label text. (e.g. ``'font13'``) 211 | :type font: str 212 | :param textColor: hex color code of enabled button's label. (e.g. ``'0xFFFFFFFF'``) 213 | :type textColor: str 214 | :param disabledColor: hex color code of disabled button's label. (e.g. ``'0xFFFF3300'``) 215 | :type disabledColor: str 216 | :param angle: angle of control. (``+`` rotates CCW, ``-`` rotates CW) 217 | :type angle: int 218 | :param shadowColor: hex color code of button's label's shadow. (e.g. ``'0xFF000000'``) 219 | :type shadowColor: str 220 | :param focusedColor: hex color code of focused button's label. (e.g. ``'0xFF00FFFF'``) 221 | :type focusedColor: str 222 | 223 | .. note:: After you create the control, you need to add it to the window with placeControl(). 224 | 225 | Example:: 226 | 227 | self.button = Button('Status', font='font14') 228 | """ 229 | def __new__(cls, *args, **kwargs): 230 | textures = {'focusTexture': os.path.join(skin.images, 'Button', 'KeyboardKey.png'), 231 | 'noFocusTexture': os.path.join(skin.images, 'Button', 'KeyboardKeyNF.png')} 232 | _set_textures(textures, kwargs) 233 | if kwargs.get('alignment') is None: 234 | kwargs['alignment'] = ALIGN_CENTER 235 | return super(Button, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 236 | 237 | 238 | class RadioButton(CompareMixin, xbmcgui.ControlRadioButton): 239 | """ 240 | RadioButton(label, focusTexture=None, noFocusTexture=None, textOffsetX=None, textOffsetY=None, _alignment=None, font=None, textColor=None, disabledColor=None, angle=None, shadowColor=None, focusedColor=None, focusOnTexture=None, noFocusOnTexture=None, focusOffTexture=None, noFocusOffTexture=None) 241 | 242 | ControlRadioButton class. 243 | 244 | Implements a 2-state switch. 245 | 246 | :param label: label text. 247 | :type: str or unicode 248 | :param focusTexture: filename for focus texture. 249 | :type focusTexture: str 250 | :param noFocusTexture: filename for no focus texture. 251 | :type noFocusTexture: str 252 | :param textOffsetX: x offset of label. 253 | :type textOffsetX: int 254 | :param textOffsetY: y offset of label. 255 | :type textOffsetY: int 256 | :param _alignment: alignment of label - Note: see xbfont.h 257 | :type _alignment: int 258 | :param font: font used for label text. (e.g. 'font13') 259 | :type font: str 260 | :param textColor: hexstring -- color of enabled radio button's label. (e.g. '0xFFFFFFFF') 261 | :type textColor: str 262 | :param disabledColor: hexstring -- color of disabled radio button's label. (e.g. '0xFFFF3300') 263 | :type disabledColor: str 264 | :param angle: angle of control. (+ rotates CCW, - rotates CW) 265 | :type angle: int 266 | :param shadowColor: hexstring -- color of radio button's label's shadow. (e.g. '0xFF000000') 267 | :type shadowColor: str 268 | :param focusedColor: hexstring -- color of focused radio button's label. (e.g. '0xFF00FFFF') 269 | :type focusedColor: str 270 | :param focusOnTexture: filename for radio focused/checked texture. 271 | :type focusOnTexture: str 272 | :param noFocusOnTexture: filename for radio not focused/checked texture. 273 | :type noFocusOnTexture: str 274 | :param focusOffTexture: filename for radio focused/unchecked texture. 275 | :type focusOffTexture: str 276 | :param noFocusOffTexture: filename for radio not focused/unchecked texture. 277 | :type noFocusOffTexture: str 278 | 279 | .. note:: To customize RadioButton all 4 abovementioned textures need to be provided. 280 | 281 | .. note:: After you create the control, you need to add it to the window with placeControl(). 282 | 283 | Example:: 284 | 285 | self.radiobutton = RadioButton('Status', font='font14') 286 | """ 287 | def __new__(cls, *args, **kwargs): 288 | if xbmc.getInfoLabel('System.BuildVersion')[:2] >= '13': 289 | textures = {'focusTexture': os.path.join(skin.images, 'RadioButton', 'MenuItemFO.png'), 290 | 'noFocusTexture': os.path.join(skin.images, 'RadioButton', 'MenuItemNF.png'), 291 | 'focusOnTexture': os.path.join(skin.images, 'RadioButton', 'radiobutton-focus.png'), 292 | 'noFocusOnTexture': os.path.join(skin.images, 'RadioButton', 'radiobutton-focus.png'), 293 | 'focusOffTexture': os.path.join(skin.images, 'RadioButton', 'radiobutton-nofocus.png'), 294 | 'noFocusOffTexture': os.path.join(skin.images, 'RadioButton', 'radiobutton-nofocus.png')} 295 | else: # This is for compatibility with Frodo and earlier versions. 296 | textures = {'focusTexture': os.path.join(skin.images, 'RadioButton', 'MenuItemFO.png'), 297 | 'noFocusTexture': os.path.join(skin.images, 'RadioButton', 'MenuItemNF.png'), 298 | 'TextureRadioFocus': os.path.join(skin.images, 'RadioButton', 'radiobutton-focus.png'), 299 | 'TextureRadioNoFocus': os.path.join(skin.images, 'RadioButton', 'radiobutton-nofocus.png')} 300 | _set_textures(textures, kwargs) 301 | return super(RadioButton, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 302 | 303 | 304 | class Edit(CompareMixin, xbmcgui.ControlEdit): 305 | """ 306 | Edit(label, font=None, textColor=None, disabledColor=None, _alignment=0, focusTexture=None, noFocusTexture=None, isPassword=False) 307 | 308 | ControlEdit class. 309 | 310 | Implements a clickable text entry field with an on-screen keyboard. 311 | 312 | :param label: text string. 313 | :type label: str or unicode 314 | :param font: [opt] font used for label text. (e.g. 'font13') 315 | :type font: str 316 | :param textColor: [opt] hexstring -- color of enabled label's label. (e.g. '0xFFFFFFFF') 317 | :type textColor: str 318 | :param disabledColor: [opt] hexstring -- color of disabled label's label. (e.g. '0xFFFF3300') 319 | :type disabledColor: str 320 | :param _alignment: [opt] lignment of label - Note: see xbfont.h 321 | :type _alignment: int 322 | :param focusTexture: [opt] filename for focus texture. 323 | :type focusTexture: str 324 | :param noFocusTexture: [opt] filename for no focus texture. 325 | :type noFocusTexture: str 326 | :param isPassword: [opt] if ``True``, mask text value. 327 | :type isPassword: bool 328 | 329 | .. note:: You can use the above as keywords for arguments and skip certain optional arguments. 330 | Once you use a keyword, all following arguments require the keyword. 331 | After you create the control, you need to add it to the window with ``placeControl()``. 332 | 333 | Example:: 334 | 335 | self.edit = Edit('Status') 336 | """ 337 | def __new__(cls, *args, **kwargs): 338 | textures = {'focusTexture': os.path.join(skin.images, 'Edit', 'button-focus.png'), 339 | 'noFocusTexture': os.path.join(skin.images, 'Edit', 'black-back2.png')} 340 | _set_textures(textures, kwargs) 341 | return super(Edit, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 342 | 343 | 344 | class List(CompareMixin, xbmcgui.ControlList): 345 | """ 346 | List(font=None, textColor=None, buttonTexture=None, buttonFocusTexture=None, selectedColor=None, _imageWidth=10, _imageHeight=10, _itemTextXOffset=10, _itemTextYOffset=2, _itemHeight=27, _space=2, _alignmentY=4) 347 | 348 | ControlList class. 349 | 350 | Implements a scrollable list of items. 351 | 352 | :param font: string - font used for items label. (e.g. 'font13') 353 | :param textColor: hexstring - color of items label. (e.g. '0xFFFFFFFF') 354 | :param buttonTexture: string - filename for no focus texture. 355 | :param buttonFocusTexture: string - filename for focus texture. 356 | :param selectedColor: integer - x offset of label. 357 | :param _imageWidth: integer - width of items icon or thumbnail. 358 | :param _imageHeight: integer - height of items icon or thumbnail. 359 | :param _itemTextXOffset: integer - x offset of items label. 360 | :param _itemTextYOffset: integer - y offset of items label. 361 | :param _itemHeight: integer - height of items. 362 | :param _space: integer - space between items. 363 | :param _alignmentY: integer - Y-axis alignment of items label - Note: see xbfont.h 364 | 365 | .. note:: After you create the control, you need to add it to the window with placeControl(). 366 | 367 | Example:: 368 | 369 | self.cList = List('font14', space=5) 370 | """ 371 | def __new__(cls, *args, **kwargs): 372 | textures = {'buttonTexture': os.path.join(skin.images, 'List', 'MenuItemNF.png'), 373 | 'buttonFocusTexture': os.path.join(skin.images, 'List', 'MenuItemFO.png')} 374 | _set_textures(textures, kwargs) 375 | return super(List, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 376 | 377 | 378 | class Slider(CompareMixin, xbmcgui.ControlSlider): 379 | """ 380 | Slider(textureback=None, texture=None, texturefocus=None, orientation=xbmcgui.HORIZONTAL) 381 | 382 | ControlSlider class. 383 | 384 | Implements a movable slider for adjusting some value. 385 | 386 | :param textureback: string -- image filename. 387 | :param texture: string -- image filename. 388 | :param texturefocus: string -- image filename. 389 | :param orientation: int -- slider orientation 390 | 391 | .. note:: After you create the control, you need to add it to the window with placeControl(). 392 | 393 | Example:: 394 | 395 | self.slider = Slider() 396 | """ 397 | def __new__(cls, *args, **kwargs): 398 | textures = {'textureback': os.path.join(skin.images, 'Slider', 'osd_slider_bg.png'), 399 | 'texture': os.path.join(skin.images, 'Slider', 'osd_slider_nibNF.png'), 400 | 'texturefocus': os.path.join(skin.images, 'Slider', 'osd_slider_nib.png')} 401 | _set_textures(textures, kwargs) 402 | if xbmc.getInfoLabel('System.BuildVersion')[:2] >= '17': 403 | kwargs['orientation'] = xbmcgui.HORIZONTAL 404 | return super(Slider, cls).__new__(cls, -10, -10, 1, 1, *args, **kwargs) 405 | 406 | 407 | class AbstractWindow: 408 | 409 | """ 410 | Top-level control window. 411 | 412 | The control windows serves as a parent widget for other XBMC UI controls 413 | much like Tkinter.Tk or PyQt QWidget class. 414 | 415 | This class is a basic "skeleton" for a control window. 416 | 417 | .. warning:: This is an abstract class and is not supposed to be instantiated directly! 418 | """ 419 | 420 | def __init__(self): 421 | self.actions_connected = [] 422 | self.controls_connected = [] 423 | 424 | def setGeometry(self, width_, height_, rows_, columns_, pos_x=-1, pos_y=-1): 425 | """ 426 | Set width, height, Grid layout, and coordinates (optional) for a new control window. 427 | 428 | :param width_: widgh of the created window. 429 | :param height_: height of the created window. 430 | :param rows_: # rows of the Grid layout to place controls on. 431 | :param columns_: # colums of the Grid layout to place controls on. 432 | :param pos_x: (opt) x coordinate of the top left corner of the window. 433 | :param pos_y: (opt) y coordinates of the top left corner of the window. 434 | 435 | If pos_x and pos_y are not privided, the window will be placed 436 | at the center of the screen. 437 | 438 | Example:: 439 | 440 | self.setGeometry(400, 500, 5, 4) 441 | """ 442 | self.width = width_ 443 | self.height = height_ 444 | self.rows = rows_ 445 | self.columns = columns_ 446 | if pos_x > 0 and pos_y > 0: 447 | self.x = pos_x 448 | self.y = pos_y 449 | else: 450 | self.x = 640 - self.width // 2 451 | self.y = 360 - self.height // 2 452 | self._setGrid() 453 | 454 | def _setGrid(self): 455 | """ 456 | Set window grid layout of rows x columns. 457 | 458 | This is a helper method not to be called directly. 459 | """ 460 | self.grid_x = self.x 461 | self.grid_y = self.y 462 | self.tile_width = self.width // self.columns 463 | self.tile_height = self.height // self.rows 464 | 465 | def placeControl(self, control, row, column, rowspan=1, columnspan=1, pad_x=5, pad_y=5): 466 | """ 467 | Place a control within the window grid layout. 468 | 469 | :param control: control instance to be placed in the grid. 470 | :param row: row number where to place the control (starts from 0). 471 | :param column: column number where to place the control (starts from 0). 472 | :param rowspan: set when the control needs to occupy several rows. 473 | :param columnspan: set when the control needs to occupy several columns. 474 | :param pad_x: horisontal padding. 475 | :param pad_y: vertical padding. 476 | :raises: :class:`AddonWindowError` if a grid has not yet been set. 477 | 478 | Use ``pad_x`` and ``pad_y`` to adjust control's aspect. 479 | Negative padding values can be used to make a control overlap with grid cells next to it, if necessary. 480 | 481 | Example:: 482 | 483 | self.placeControl(self.label, 0, 1) 484 | """ 485 | try: 486 | control_x = (self.grid_x + self.tile_width * column) + pad_x 487 | control_y = (self.grid_y + self.tile_height * row) + pad_y 488 | control_width = self.tile_width * columnspan - 2 * pad_x 489 | control_height = self.tile_height * rowspan - 2 * pad_y 490 | except AttributeError: 491 | raise AddonWindowError('Window geometry is not defined! Call setGeometry first.') 492 | control.setPosition(control_x, control_y) 493 | control.setWidth(control_width) 494 | control.setHeight(control_height) 495 | self.addControl(control) 496 | self.setAnimation(control) 497 | 498 | def getX(self): 499 | """Get X coordinate of the top-left corner of the window.""" 500 | try: 501 | return self.x 502 | except AttributeError: 503 | raise AddonWindowError('Window geometry is not defined! Call setGeometry first.') 504 | 505 | def getY(self): 506 | """Get Y coordinate of the top-left corner of the window.""" 507 | try: 508 | return self.y 509 | except AttributeError: 510 | raise AddonWindowError('Window geometry is not defined! Call setGeometry first.') 511 | 512 | def getWindowWidth(self): 513 | """Get window width.""" 514 | try: 515 | return self.width 516 | except AttributeError: 517 | raise AddonWindowError('Window geometry is not defined! Call setGeometry first.') 518 | 519 | def getWindowHeight(self): 520 | """Get window height.""" 521 | try: 522 | return self.height 523 | except AttributeError: 524 | raise AddonWindowError('Window geometry is not defined! Call setGeometry first.') 525 | 526 | def getRows(self): 527 | """ 528 | Get grid rows count. 529 | 530 | :raises: :class:`AddonWindowError` if a grid has not yet been set. 531 | """ 532 | try: 533 | return self.rows 534 | except AttributeError: 535 | raise AddonWindowError('Grid layot is not set! Call setGeometry first.') 536 | 537 | def getColumns(self): 538 | """ 539 | Get grid columns count. 540 | 541 | :raises: :class:`AddonWindowError` if a grid has not yet been set. 542 | """ 543 | try: 544 | return self.columns 545 | except AttributeError: 546 | raise AddonWindowError('Grid layout is not set! Call setGeometry first.') 547 | 548 | def connect(self, event, callable): 549 | """ 550 | Connect an event to a function. 551 | 552 | :param event: event to be connected. 553 | :param callable: callable object the event is connected to. 554 | 555 | An event can be an inctance of a Control object or an integer key action code. 556 | Several basic key action codes are provided by PyXBMCt. ``xbmcgui`` module 557 | provides more action codes. 558 | 559 | You can connect the following Controls: :class:`Button`, :class:`RadioButton` 560 | and :class:`List`. Other Controls do not generate any control events when activated 561 | so their connections won't work. 562 | 563 | To catch :class:`Slider` events you need to connect the following key actions: 564 | ``ACTION_MOVE_LEFT``, ``ACTION_MOVE_RIGHT`` and ``ACTION_MOUSE_DRAG``, and do a check 565 | whether the ``Slider`` instance is focused. 566 | 567 | ``callable`` parameter is a function or a method to be executed on when the event is fired. 568 | 569 | .. warning:: For connection you must provide a function object without brackets ``()``, 570 | not a function call! 571 | 572 | ``lambda`` can be used as to call another function or method with parameters known at runtime. 573 | 574 | Examples:: 575 | 576 | self.connect(self.exit_button, self.close) 577 | 578 | or:: 579 | 580 | self.connect(ACTION_NAV_BACK, self.close) 581 | """ 582 | try: 583 | self.disconnect(event) 584 | except AddonWindowError: 585 | if isinstance(event, int): 586 | self.actions_connected.append([event, callable]) 587 | else: 588 | self.controls_connected.append([event, callable]) 589 | 590 | def connectEventList(self, events, function): 591 | """ 592 | Connect a list of controls/action codes to a function. 593 | 594 | See :meth:`connect` docstring for more info. 595 | """ 596 | [self.connect(event, function) for event in events] 597 | 598 | def disconnect(self, event): 599 | """ 600 | Disconnect an event from a function. 601 | 602 | An event can be an inctance of a Control object or an integer key action code 603 | which has previously been connected to a function or a method. 604 | 605 | :param event: event to be disconnected. 606 | :raises: :class:`AddonWindowError` if an event is not connected to any function. 607 | 608 | Examples:: 609 | 610 | self.disconnect(self.exit_button) 611 | 612 | or:: 613 | 614 | self.disconnect(ACTION_NAV_BACK) 615 | """ 616 | if isinstance(event, int): 617 | event_list = self.actions_connected 618 | else: 619 | event_list = self.controls_connected 620 | for index in range(len(event_list)): 621 | if event == event_list[index][0]: 622 | event_list.pop(index) 623 | break 624 | else: 625 | raise AddonWindowError('The action or control %s is not connected!' % event) 626 | 627 | def disconnectEventList(self, events): 628 | """ 629 | Disconnect a list of controls/action codes from functions. 630 | 631 | See :func:`disconnect` docstring for more info. 632 | 633 | :param events: the list of events to be disconnected. 634 | :raises: :class:`AddonWindowError` if at least one event in the list 635 | is not connected to any function. 636 | """ 637 | [self.disconnect(event) for event in events] 638 | 639 | def _executeConnected(self, event, connected_list): 640 | """ 641 | Execute a connected event (an action or a control). 642 | 643 | This is a helper method not to be called directly. 644 | """ 645 | for item in connected_list: 646 | if item[0] == event: 647 | item[1]() 648 | break 649 | 650 | def setAnimation(self, control): 651 | """ 652 | Set animation for control 653 | 654 | :param control: control for which animation is set. 655 | 656 | This method is called automatically to set animation properties for all controls 657 | added to the current addon window instance -- both for built-in controls 658 | (window background, title bar etc.) and for controls added with :meth:`placeControl`. 659 | 660 | It receives a control instance as the 2nd positional argument (besides ``self``). 661 | By default the method does nothing, i.e. no animation is set for controls. 662 | To add animation you need to re-implement this method in your child class. 663 | 664 | E.g:: 665 | 666 | def setAnimation(self, control): 667 | control.setAnimations([('WindowOpen', 'effect=fade start=0 end=100 time=1000',), 668 | ('WindowClose', 'effect=fade start=100 end=0 time=1000',)]) 669 | """ 670 | pass 671 | 672 | 673 | class AddonWindow(AbstractWindow): 674 | 675 | """ 676 | Top-level control window. 677 | 678 | The control windows serves as a parent widget for other XBMC UI controls 679 | much like ``Tkinter.Tk`` or PyQt ``QWidget`` class. 680 | This is an abstract class which is not supposed to be instantiated directly 681 | and will raise exeptions. It is designed to be implemented in a grand-child class 682 | with the second inheritance from ``xbmcgui.Window`` or ``xbmcgui.WindowDialog`` 683 | in a direct child class. 684 | 685 | This class provides a control window with a background and a header 686 | similar to top-level widgets of desktop UI frameworks. 687 | 688 | .. warning:: This is an abstract class and is not supposed to be instantiated directly! 689 | """ 690 | 691 | def __init__(self, title=''): 692 | """Constructor method.""" 693 | super(AddonWindow, self).__init__() 694 | self._setFrame(title) 695 | 696 | def _setFrame(self, title): 697 | """ 698 | Set window frame 699 | 700 | Define paths to images for window background and title background textures, 701 | and set control position adjustment constants used in setGrid. 702 | 703 | This is a helper method not to be called directly. 704 | """ 705 | # Window background image 706 | self.background_img = skin.background_img 707 | # Background for a window header 708 | self.title_background_img = skin.title_background_img 709 | self.background = xbmcgui.ControlImage(-10, -10, 1, 1, self.background_img) 710 | self.addControl(self.background) 711 | self.setAnimation(self.background) 712 | self.title_background = xbmcgui.ControlImage(-10, -10, 1, 1, self.title_background_img) 713 | self.addControl(self.title_background) 714 | self.setAnimation(self.title_background) 715 | self.title_bar = xbmcgui.ControlLabel(-10, -10, 1, 1, title, alignment=skin.header_align, 716 | textColor=skin.header_text_color, font='font13_title') 717 | self.addControl(self.title_bar) 718 | self.setAnimation(self.title_bar) 719 | self.window_close_button = xbmcgui.ControlButton(-100, -100, skin.close_btn_width, skin.close_btn_height, '', 720 | focusTexture=skin.close_button_focus, 721 | noFocusTexture=skin.close_button_no_focus) 722 | self.addControl(self.window_close_button) 723 | self.setAnimation(self.window_close_button) 724 | 725 | def setGeometry(self, width_, height_, rows_, columns_, pos_x=-1, pos_y=-1, padding=5): 726 | """ 727 | Set width, height, Grid layout, and coordinates (optional) for a new control window. 728 | 729 | :param width_: new window width in pixels. 730 | :param height_: new window height in pixels. 731 | :param rows_: # of rows in the Grid layout to place controls on. 732 | :param columns_: # of colums in the Grid layout to place controls on. 733 | :param pos_x: (optional) x coordinate of the top left corner of the window. 734 | :param pos_y: (optional) y coordinate of the top left corner of the window. 735 | :param padding: (optional) padding between outer edges of the window 736 | and controls placed on it. 737 | 738 | If ``pos_x`` and ``pos_y`` are not privided, the window will be placed 739 | at the center of the screen. 740 | 741 | Example:: 742 | 743 | self.setGeometry(400, 500, 5, 4) 744 | """ 745 | self.win_padding = padding 746 | super(AddonWindow, self).setGeometry(width_, height_, rows_, columns_, pos_x, pos_y) 747 | self.background.setPosition(self.x, self.y) 748 | self.background.setWidth(self.width) 749 | self.background.setHeight(self.height) 750 | self.title_background.setPosition(self.x + skin.x_margin, self.y + skin.y_margin + skin.title_back_y_shift) 751 | self.title_background.setWidth(self.width - 2 * skin.x_margin) 752 | self.title_background.setHeight(skin.header_height) 753 | self.title_bar.setPosition(self.x + skin.x_margin + skin.title_bar_x_shift, 754 | self.y + skin.y_margin + skin.title_bar_y_shift) 755 | self.title_bar.setWidth(self.width - 2 * skin.x_margin) 756 | self.title_bar.setHeight(skin.header_height) 757 | self.window_close_button.setPosition(self.x + self.width - skin.close_btn_x_offset, 758 | self.y + skin.y_margin + skin.close_btn_y_offset) 759 | 760 | def _setGrid(self): 761 | """ 762 | Set window grid layout of rows * columns. 763 | 764 | This is a helper method not to be called directly. 765 | """ 766 | self.grid_x = self.x + skin.x_margin + self.win_padding 767 | self.grid_y = self.y + skin.y_margin + skin.title_back_y_shift + skin.header_height + self.win_padding 768 | self.tile_width = (self.width - 2 * (skin.x_margin + self.win_padding)) // self.columns 769 | self.tile_height = ((self.height - skin.header_height - skin.title_back_y_shift - 770 | 2 * (skin.y_margin + self.win_padding)) // self.rows) 771 | 772 | def setWindowTitle(self, title=''): 773 | """ 774 | Set window title. 775 | 776 | .. warning:: This method must be called **AFTER** (!!!) :meth:`setGeometry`, 777 | otherwise there is some werid bug with all skin text labels set to the ``title`` text. 778 | 779 | Example:: 780 | 781 | self.setWindowTitle('My Cool Addon') 782 | """ 783 | self.title_bar.setLabel(title) 784 | 785 | def getWindowTitle(self): 786 | """Get window title.""" 787 | return self.title_bar.getLabel() 788 | 789 | 790 | class FullWindowMixin(xbmcgui.Window): 791 | 792 | """An abstract class to define window event processing.""" 793 | 794 | def onAction(self, action): 795 | """ 796 | Catch button actions. 797 | 798 | ``action`` is an instance of :class:`xbmcgui.Action` class. 799 | """ 800 | if action == ACTION_PREVIOUS_MENU: 801 | self.close() 802 | else: 803 | self._executeConnected(action, self.actions_connected) 804 | 805 | def onControl(self, control): 806 | """ 807 | Catch activated controls. 808 | 809 | ``control`` is an instance of :class:`xbmcgui.Control` class. 810 | """ 811 | if (hasattr(self, 'window_close_button') and 812 | control.getId() == self.window_close_button.getId()): 813 | self.close() 814 | else: 815 | self._executeConnected(control, self.controls_connected) 816 | 817 | 818 | class DialogWindowMixin(xbmcgui.WindowDialog): 819 | 820 | """An abstract class to define window event processing.""" 821 | 822 | def onAction(self, action): 823 | """ 824 | Catch button actions. 825 | 826 | ``action`` is an instance of class:`xbmcgui.Action` class. 827 | """ 828 | if action == ACTION_PREVIOUS_MENU: 829 | self.close() 830 | else: 831 | self._executeConnected(action, self.actions_connected) 832 | 833 | def onControl(self, control): 834 | """ 835 | Catch activated controls. 836 | 837 | ``control`` is an instance of :class:`xbmcgui.Control` class. 838 | """ 839 | if (hasattr(self, 'window_close_button') and 840 | control.getId() == self.window_close_button.getId()): 841 | self.close() 842 | else: 843 | self._executeConnected(control, self.controls_connected) 844 | 845 | 846 | class BlankFullWindow(AbstractWindow, FullWindowMixin): 847 | """ 848 | BlankFullWindow() 849 | 850 | Addon UI container with a solid background. 851 | 852 | This is a blank window with a black background and without any elements whatsoever. 853 | The decoration and layout are completely up to an addon developer. 854 | The window controls can hide under video or music visualization. 855 | """ 856 | pass 857 | 858 | 859 | class BlankDialogWindow(AbstractWindow, DialogWindowMixin): 860 | """ 861 | BlankDialogWindow() 862 | 863 | Addon UI container with a transparent background. 864 | 865 | This is a blank window with a transparent background and without any elements whatsoever. 866 | The decoration and layout are completely up to an addon developer. 867 | The window controls are always displayed over video or music visualization. 868 | """ 869 | pass 870 | 871 | 872 | class AddonFullWindow(AddonWindow, FullWindowMixin): 873 | 874 | """ 875 | AddonFullWindow(title='') 876 | 877 | Addon UI container with a solid background. 878 | 879 | ``AddonFullWindow`` instance is displayed on top of the main background image -- 880 | ``self.main_bg`` -- and can hide behind a fullscreen video or music viaualisation. 881 | 882 | Minimal example:: 883 | 884 | addon = AddonFullWindow('My Cool Addon') 885 | addon.setGeometry(400, 300, 4, 3) 886 | addon.doModal() 887 | """ 888 | 889 | def __new__(cls, title='', *args, **kwargs): 890 | return super(AddonFullWindow, cls).__new__(cls, *args, **kwargs) 891 | 892 | def _setFrame(self, title): 893 | """ 894 | Set the image for for the fullscreen background. 895 | """ 896 | # Image for the fullscreen background. 897 | self.main_bg_img = skin.main_bg_img 898 | # Fullscreen background image control. 899 | self.main_bg = xbmcgui.ControlImage(1, 1, 1280, 720, self.main_bg_img) 900 | self.addControl(self.main_bg) 901 | super(AddonFullWindow, self)._setFrame(title) 902 | 903 | def setBackground(self, image=''): 904 | """ 905 | Set the main bacground to an image file. 906 | 907 | :param image: path to an image file as str. 908 | 909 | Example:: 910 | 911 | self.setBackground('/images/bacground.png') 912 | """ 913 | self.main_bg.setImage(image) 914 | 915 | 916 | class AddonDialogWindow(AddonWindow, DialogWindowMixin): 917 | """ 918 | AddonDialogWindow(title='') 919 | 920 | Addon UI container with a transparent background. 921 | 922 | .. note:: ``AddonDialogWindow`` instance is displayed on top of XBMC UI, 923 | including fullscreen video and music visualization. 924 | 925 | Minimal example:: 926 | 927 | addon = AddonDialogWindow('My Cool Addon') 928 | addon.setGeometry(400, 300, 4, 3) 929 | addon.doModal() 930 | """ 931 | pass 932 | -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/ContentPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/ContentPanel.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/DialogCloseButton-focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/DialogCloseButton-focus.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/DialogCloseButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/DialogCloseButton.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/SKINDEFAULT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/SKINDEFAULT.jpg -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/dialogheader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/AddonWindow/dialogheader.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/Button/KeyboardKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/Button/KeyboardKey.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/Button/KeyboardKeyNF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/Button/KeyboardKeyNF.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/Edit/black-back2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/Edit/black-back2.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/Edit/button-focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/Edit/button-focus.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/List/MenuItemFO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/List/MenuItemFO.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/List/MenuItemNF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/List/MenuItemNF.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/RadioButton/MenuItemFO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/RadioButton/MenuItemFO.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/RadioButton/MenuItemNF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/RadioButton/MenuItemNF.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/RadioButton/radiobutton-focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/RadioButton/radiobutton-focus.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/RadioButton/radiobutton-nofocus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/RadioButton/radiobutton-nofocus.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/Slider/osd_slider_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/Slider/osd_slider_bg.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/Slider/osd_slider_nib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/Slider/osd_slider_nib.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/confluence/Slider/osd_slider_nibNF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/confluence/Slider/osd_slider_nibNF.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/ContentPanel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/ContentPanel.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/DialogCloseButton-focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/DialogCloseButton-focus.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/DialogCloseButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/DialogCloseButton.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/SKINDEFAULT.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/SKINDEFAULT.jpg -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/dialogheader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/AddonWindow/dialogheader.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/Button/KeyboardKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/Button/KeyboardKey.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/Button/KeyboardKeyNF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/Button/KeyboardKeyNF.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/Edit/black-back2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/Edit/black-back2.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/Edit/button-focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/Edit/button-focus.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/List/MenuItemFO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/List/MenuItemFO.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/List/MenuItemNF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/List/MenuItemNF.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/RadioButton/MenuItemFO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/RadioButton/MenuItemFO.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/RadioButton/MenuItemNF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/RadioButton/MenuItemNF.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/RadioButton/radiobutton-focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/RadioButton/radiobutton-focus.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/RadioButton/radiobutton-nofocus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/RadioButton/radiobutton-nofocus.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/Slider/osd_slider_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/Slider/osd_slider_bg.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/Slider/osd_slider_nib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/Slider/osd_slider_nib.png -------------------------------------------------------------------------------- /script.module.pyxbmct/lib/pyxbmct/textures/estuary/Slider/osd_slider_nibNF.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romanvm/script.module.pyxbmct/2c8d70a12166fffa44c0a809e882032769805efd/script.module.pyxbmct/lib/pyxbmct/textures/estuary/Slider/osd_slider_nibNF.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # (c) 2020, Roman Miroshnychenko 3 | # License: GPL v.3 4 | 5 | import os 6 | from xml.dom.minidom import parse 7 | 8 | from setuptools import setup, find_packages 9 | 10 | this_dir = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | 13 | def get_version(): 14 | doc = parse(os.path.join(this_dir, 'script.module.pyxbmct', 'addon.xml')) 15 | return doc.firstChild.getAttribute('version') 16 | 17 | 18 | setup( 19 | name='PyXBMCt', 20 | author='Roman Miroshnychenko', 21 | version=get_version(), 22 | package_dir={'': 'script.module.pyxbmct/lib'}, 23 | packages=find_packages('./script.module.pyxbmct/lib'), 24 | install_requires=[ 25 | 'Kodistubs', 26 | ], 27 | extras_require={ 28 | 'dev': [ 29 | 'Sphinx' 30 | ] 31 | }, 32 | zip_safe=False 33 | ) 34 | --------------------------------------------------------------------------------