pax_global_header00006660000000000000000000000064147452403210014514gustar00rootroot0000000000000052 comment=7655ba92b529632144e07868829f0e0c9ccb5055 pygubu-0.36.1/000077500000000000000000000000001474524032100131165ustar00rootroot00000000000000pygubu-0.36.1/.github/000077500000000000000000000000001474524032100144565ustar00rootroot00000000000000pygubu-0.36.1/.github/FUNDING.yml000066400000000000000000000004321474524032100162720ustar00rootroot00000000000000# These are supported funding model platforms # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] # ko_fi: # Replace with a single Ko-fi username # liberapay: # Replace with a single Liberapay username custom: ["https://paypal.me/AlejandroA36"] pygubu-0.36.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001474524032100166415ustar00rootroot00000000000000pygubu-0.36.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000014001474524032100213260ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Your Environment (please complete the following information):** - OS: [e.g. iOS, Debian 11, Windows 10] - Python version: [e.g. Python 3.10] - Pygubu version: [e.g. pygubu 0.29] - Designer version: [e.g. pygubu-designer 0.34] **Additional context** Add any other context about the problem here. pygubu-0.36.1/.github/ISSUE_TEMPLATE/config.yml000066400000000000000000000005031474524032100206270ustar00rootroot00000000000000blank_issues_enabled: false contact_links: - name: Questions url: https://github.com/alejandroautalan/pygubu-designer/discussions about: "Ask a question in the discussion forum" - name: Documentation url: https://github.com/alejandroautalan/pygubu-designer/wiki about: "Pygubu Designer's documentation" pygubu-0.36.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000013451474524032100223710ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Your Environment (please complete the following information):** - OS: [e.g. iOS, Debian 11, Windows 10] - Python version: [e.g. Python 3.10] **Additional context** Add any other context or screenshots about the feature request here. pygubu-0.36.1/.gitignore000066400000000000000000000002441474524032100151060ustar00rootroot00000000000000__pycache__/ *~ *.pyc output.tkb exampleui_2.tkb build/ dist/ MANIFEST TODO pygubu.nja pygubu.egg-info/ myvenv*/ venv/ pygubu-core.wpr pygubu-core.wpu *.kdev4 *.mo pygubu-0.36.1/.pre-commit-config.yaml000066400000000000000000000012461474524032100174020ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: check-added-large-files - id: check-merge-conflict # exclude files where underlines are not distinguishable from merge conflicts exclude: HISTORY\.md$|\.pot?$ - id: debug-statements - id: detect-private-key - id: end-of-file-fixer - repo: https://github.com/psf/black rev: 22.3.0 hooks: - id: black args: - --line-length=80 - repo: https://github.com/PyCQA/flake8 rev: 7.1.1 hooks: - id: flake8 args: - --max-line-length=80 - --ignore=E203,E501,F401,W503 pygubu-0.36.1/AUTHORS000066400000000000000000000001031474524032100141600ustar00rootroot00000000000000Copyright 2012-2016 Alejandro Autalan pygubu-0.36.1/HISTORY.md000066400000000000000000000237301474524032100146060ustar00rootroot00000000000000History ======= Changes for version 0.36.1 * Fix TypeError, scroll_rs not subscriptable exception. Changes for version 0.36: * BuilderObject class: Store reference to parent. * BuilderObject class: Add option to override children layout. * Initial support for TKinterModernThemes package. * Clean tkinterweb plugin. Add tkinterweb Notebook widget. refs alejandroautalan/pygubu-designer#278 Changes for version 0.35.6 * Fix issue when setting menu for Menubutton. refs alejandroautalan/pygubu-designer#276 Changes for version 0.35.5 * Fix menu code generation. refs #296 * Fix issue with CTKEntry text property. refs alejandroautalan/pygubu-designer#266 Changes for version 0.35.4 * Fix from, to properties on customtkinter CTKSlide. Changes for version 0.35.3 * Fix container layout on customtkinter CTKFrame. Changes for version 0.35.2 * Drop support for python 3.6 and 3.7 (was already not working) * Update pyinstaller hooks Changes for version 0.35.1 * Fix error in customtkinter Ctk builder Changes for version 0.35 * New theming module. Adds bootstrap like themes, based on ttkbootstrap. Initial draft. TODO: improve graphics, draw with pillow if available. * Modified ApplicationLevelBindManager, methods are now class methods and not static methods. Added parameter master to the init_mousewheel_binding method. * Modified UI definition. Added project options section. Options saved: general options, code generation options, style definition, custom widgets. * New widgets: - New Hideable Frame widget. - New PathChooserButton widget. - New ColorInput widget. - New docking widgets: DockFrame, DockWidget, DockPane. In alpha status, although pygubu designer uses them. - New form widgets in alpha status. - Expose accordion widget hidden in pygubu code (Maybe it will be useful). * New emit_close_event for pygubu Dialog * Grid container options, fixed bug: process "all" index first, so specific row/col props are configured correctly. * New classmethods in Builder object canbe_parent_of, canbe_child_of. * Added copy_custom_property function * Added "public" argument to function register_widget * Added new method Builder.add_resource_paths(path_list: list) Changes for version 0.34 * Add missing container layout options for ScrolledFrame widget. Fixes alejandroautalan/pygubu#293 Changes for version 0.33 * Update to support customtkinter 5.2.2 Changes for version 0.32 * New builder for create a tkinter.Tk widget. (use it with caution) * Rewrite StockImage class. Allow to pass a user specific tkroot to create images. * Set widget tcl name for named widgets in designer. refs alejandroautalan/pygubu#287 * Editabletreeview, allow a column to have multiple editors per row. * New FilterableTreeview widget. * Fix callback argument for code generation in designer. refs alejandroautalan/pygubu-designer#205 * Fix for issue #284 * Fix for missed update of 'activeoutline' color in CalendarFrame widget alejandroautalan/pygubu#285 (BloodyRain2k) * New "linewidth" option and visual fixes for CalendarFrame widget alejandroautalan/pygubu#286 (BloodyRain2k) Changes for version 0.31: * Allow to setup values for option database after first window created. refs alejandroautalan/pygubu#282 * Fix for widget highlighter offsets in preview, refs alejandroautalan/pygubu-designer#203 Changes for version 0.30: * Editabletreeview: Hide editors when user clicks inside treeview area with no rows. This generates \<\\> event. refs alejandroautalan/pygubu#279 * Editabletreeview: Add method get_value(col, item) to quicky access tree data. refs alejandroautalan/pygubu#279 * Fix error on ttkwidgets plugin (autocomplete widgets) * Add support to customtkinter 5. Customtkinter changed a lot from 4.6 to 5. The plugin will support only the latest version. Changes for version 0.29 * Fixes for PathChooserInput. refs alejandroautalan/pygubu#278, alejandroautalan/pygubu-designer#145 * Fixes for ToplevelPreviewBaseBO (for plugins) Changes for version 0.28 * Added ttk.OptionMenu and ttk.LabeledScale (alejandroautalan/pygubu-designer#178) * Fixed issues when working with Notebook tabs. * Restrict customtkinter plugin to customtkinter < 5 (next version will only support customtkinter >= 5) Changes for version 0.27 * Builder object, REMOVED configure_for_preview method. * Added support for: customtkinter, tkintermapview Changes for version 0.26.2 * Fix for issues alejandroautalan/pygubu-designer#154, alejandroautalan/pygubu-designer#155 Changes for version 0.26.1 * Hotfix for alejandroautalan/pygubu-designer#153 Changes for version 0.26 * Allow pygubu to use importlib.resources module. refs alejandroautalan/pygubu#269 * Code generation: Fix callback registration arguments. * Builder object, new static method configure_for_preview. Changes for version 0.25.1 * Fix Menubutton code generation. refs alejandroautalan/pygubu-designer#151 * Fix pyinstaller hook for python 3.8 refs alejandroautalan/pygubu#270 Changes for version 0.25 * Modified ui definition file to allow decluttering of widget ids in designer (alejandroautalan/pygubu-designer#117) * Fix initial value for boolean tkvariables (issue alejandroautalan/pygubu#268) * Improved menu code generation (refs alejandroautalan/pygubu-designer#103) Changes for version 0.24.2 * Fix loading of custom widgets Changes for version 0.24.1 * Hotfix: Fix error loading tkcalendar DateEntry * Added pyinstaller hook (thanks to @gwelch-contegix) Changes for version 0.24 * New plugin engine and API (alpha state) * Added support for: AwesomeTkinter, tkintertable, tksheet, ttkwidgets, tkinterweb, tkcalendar. * Changed project structure to use src folder. Changes for version 0.23.1 * Fix: Generate regular treeview properties in the Code Script alejandroautalan/pygubu#264 (jrezai) Changes for version 0.23 * Translations for pygubu strings in pygubu-designer (larryw3i) Changes for version 0.22 * Code generation: mark translatable text in code. issue alejandroautalan/pygubu-designer#120 * Code generation: generate keyword arguments as integers when posible. issue alejandroautalan/pygubu-designer#114 * Code generation: Fix OptionMenu. issue alejandroautalan/pygubu-designer#125 Changes for version 0.21 * Editabletreeview: Add InplaceEditor abstract class for better management of column data editors. * Improve argument names for entry validate callback. * Fix: Generate escaped strings on code generation. * Other minor fixes. Changes for version 0.20 * Removed Python 2.7 support, Minimum Python version required is now 3.6 * Added support to configure grid with 'all' index * Change in xml specification. Interface version is now 1.2. This includes reorganization of grid row/column properties. Changes for version 0.19 * Fix generating redundant code for grid properties * Fix install error on python 2.7 * This is the last version with python 2.7 support Changes for version 0.9.8 * Use entry_points field for installing a startup script with setuptools * Fixed issues #66, #86 Changes for version 0.9.7.9 * Fixed issues #72, #74, #78, #81 Changes for version 0.9.7.8 * Added wheel support. * Fixed issues #64, #65 Changes for version 0.9.7.7 * Improved ui tester. * Fixed issues #54, #58, #59, #60 Changes for version 0.9.7.5 * Allow to specify variable names when importing tk variables. * Allow to register an already created tk image. * Allow to specify loggin level from console command. * Added new pathchooser input widget. * Improved README (thanks to Nelson Brochado) * Fixed issue #52 Changes for version 0.9.7.3 * Added custom widgets preference option. * Added appdirs dependency. * New sticky property editor. * Fixed issues #40, #45 Changes for version 0.9.7 * Fixed issues #39, #41 Changes for version 0.9.6.7 * Remove old pygubu.py script for old installations. Create pybugu-designer.bat file for windows platform. Fixes #38 Changes for version 0.9.6.6 * Fixed bug: color value setting to None when presing Cancel on color selector. * Add '.png' format to Stockimage if tk version support it. fixes #36 * Minor changes to main UI. Changes for version 0.9.6.5 * Fixed bug on menu creation. * Fixed issues #14 and #22 * Added helper method to avoid call get_variable for every variable. refs #29 Changes for version 0.9.6.4 * Fixed bug #33 "Wrong textvariable format when create ui file" Changes for version 0.9.6.3 * Use old menu preview on platforms other than linux (new preview does not work on windows) Changes for version 0.9.6.2 * Property editors rewritten from scratch * Improved menu preview * Added font property editor * Fixed menu issues Changes for version 0.9.5.1 * Add select hotkey to widget tree. (i - select previous item, k - select next item) * Copied menu example from wiki to examples folder. Changes for version 0.9.5 * Renamed designer startup script to pygubu-designer (see [#20](/../../issues/20)) * Fixed bugs. Changes for version 0.9.4 * Added Toplevel widget * Added generic Dialog widget * Rewrited scrolledframe widget internals, ideas and code taken from tkinter wiki. * Added more widget icons. * Fixed bugs. Changes for version 0.9.3 * Allow to select control variable type * Fixed some bugs. Changes for version 0.9.2 * Added more wiki pages. * Fixed issues #3, #4 Changes for version 0.9.1 * Separate designer module from main package * Added menu to select current ttk theme * Fix color selector issues. Changes for version 0.9 * Add validator for pax and pady properties. * Improved ScrolledFrame widget. * Added more wiget icons. * Fix cursor type on preview panel. Changes for version 0.8 * Added translation support * Translated pygubu designer to Spanish Changes for version 0.7 * Added python 2.7 support * Added initial TkApplication class * Fixed some bugs. First public version 0.6 pygubu-0.36.1/LICENSE000066400000000000000000000020671474524032100141300ustar00rootroot00000000000000MIT License Copyright (c) [2020] [Alejandro Autalán] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pygubu-0.36.1/MANIFEST.in000066400000000000000000000001321474524032100146500ustar00rootroot00000000000000include LICENSE include README.md include HISTORY.md graft development global-exclude *~ pygubu-0.36.1/README.md000066400000000000000000000112041474524032100143730ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/alejandroautalan/pygubu.svg?branch=master)](https://travis-ci.org/alejandroautalan/pygubu) [Leer en Español](LEEME.md) Welcome to Pygubu! ================== `Pygubu` is a [RAD tool](https://en.wikipedia.org/wiki/Rapid_application_development) to enable _quick_ and _easy development of user interfaces_ for the Python's `tkinter` module. The user interfaces designed are saved as [XML](https://en.wikipedia.org/wiki/XML) files, and, by using the _pygubu builder_, these can be loaded by applications dynamically as needed. Pygubu is inspired by [Glade](https://gitlab.gnome.org/GNOME/glade). Installation ============ The latest version of pygubu requires Python >= 3.8 You can install pygubu using pip: ``` pip install pygubu ``` Usage ===== Since version 0.10 the project was splitted in two main modules: - The **pygubu core** (this project), that load and build user interfaces defined in xml. - The **interface editor** [pygubu-designer](https://github.com/alejandroautalan/pygubu-designer), that helps you create the xml definition graphically. Start creating your tkinter application xml UI definition using the pygubu-designer editor. The following is a UI definition example called [helloworld.ui](https://github.com/alejandroautalan/pygubu-designer/blob/master/examples/helloworld/helloworld.ui): ```xml 200 both Hello World App 200 200 20 200 true top center Helvetica 26 #0000b8 Hello World ! top ``` Then, you should create your _application script_ as shown below ([helloworld.py](https://github.com/alejandroautalan/pygubu-designer/blob/master/examples/helloworld/helloworld.py)): ```python # helloworld.py import pathlib import tkinter as tk import tkinter.ttk as ttk import pygubu PROJECT_PATH = pathlib.Path(__file__).parent PROJECT_UI = PROJECT_PATH / "helloworld.ui" class HelloworldApp: def __init__(self, master=None): # 1: Create a builder and setup resources path (if you have images) self.builder = builder = pygubu.Builder() builder.add_resource_path(PROJECT_PATH) # 2: Load a ui file builder.add_from_file(PROJECT_UI) # 3: Create the mainwindow self.mainwindow = builder.get_object('mainwindow', master) # 4: Connect callbacks builder.connect_callbacks(self) def run(self): self.mainwindow.mainloop() if __name__ == '__main__': app = HelloworldApp() app.run() ``` Note: instead of `helloworld.ui`, you should insert the _filename_ (or full path) of your UI definition: ```python PROJECT_UI = PROJECT_PATH / "helloworld.ui" ``` Note: instead of 'mainwindow', you should have the name of your _main_widget_ (the parent of all widgets) in the following line: ```python self.mainwindow = builder.get_object('_your_main_widget_') ``` Documentation ============= Visit the [pygubu wiki](https://github.com/alejandroautalan/pygubu/wiki) for more documentation. The following are some good tkinter (and tk) references: - [TkDocs](http://www.tkdocs.com) - [Graphical User Interfaces with Tk](https://docs.python.org/3/library/tk.html) - [Tkinter 8.5 reference: a GUI for Python](https://tkdocs.com/shipman) - [An Introduction to Tkinter](http://effbot.org/tkinterbook) [(archive)](http://web.archive.org/web/20200504141939/http://www.effbot.org/tkinterbook) - [Tcl/Tk 8.5 Manual](http://www.tcl.tk/man/tcl8.5/) You can also see the [examples](https://github.com/alejandroautalan/pygubu-designer/tree/master/examples) directory or watch [this introductory video tutorial](http://youtu.be/wuzV9P8geDg). History ======= See the list of changes [here](HISTORY.md). pygubu-0.36.1/development/000077500000000000000000000000001474524032100154405ustar00rootroot00000000000000pygubu-0.36.1/development/devtool.sh000077500000000000000000000005401474524032100174520ustar00rootroot00000000000000#!/bin/bash designer_dir="../pygubu-designer" project_dir_path="pygubu" [[ $dde == t ]] && . ${designer_dir}/devtool.sh install_designer(){ pip3 install -e $designer_dir } test_installation(){ pip3 install -e . pip3 install -e $designer_dir pygubu-designer } insd(){ install_designer; } ti(){ test_installation; } $* pygubu-0.36.1/development/makebuild.sh000066400000000000000000000001011474524032100177210ustar00rootroot00000000000000#!/bin/sh python3 setup.py sdist bdist_wheel twine upload dist/* pygubu-0.36.1/development/req-extra.py000066400000000000000000000022261474524032100177240ustar00rootroot00000000000000import io import re from collections import defaultdict from pprint import pp extra_requirements = """ # FORMAT # Put your extra requirements here in the following format # # package[version_required]: tag1, tag2, ... ttkwidgets: ttkwidgets tksheet: tksheet tkinterweb: tkinterweb tkintertable: tkintertable tkcalendar: tkcalendar AwesomeTkinter: awesometkinter """ def get_extra_requires(add_all=True): ifile = io.StringIO(extra_requirements) with ifile as fp: extra_deps = defaultdict(set) for k in fp: if k.strip() and not k.startswith("#"): tags = set() if ":" in k: k, v = k.split(":") tags.update(vv.strip() for vv in v.split(",")) # tags.add(re.split("[<=>]", k)[0]) for t in tags: extra_deps[t].add(k) # add tag `all` at the end if add_all: extra_deps["all"] = set(vv for v in extra_deps.values() for vv in v) return extra_deps if __name__ == "__main__": requires = get_extra_requires() for key, dep in requires.items(): print(f"{key} = {list(dep)}") pygubu-0.36.1/development/requirements.txt000066400000000000000000000000131474524032100207160ustar00rootroot00000000000000pre-commit pygubu-0.36.1/development/runtests.sh000077500000000000000000000006521474524032100176710ustar00rootroot00000000000000#!/bin/sh python3bin=$(which python3) echo " =============" echo " Default Python 3: $python3bin " echo " version: $($python3bin --version)" echo " tk version :$($python3bin -c 'import tkinter; print(tkinter.TkVersion)')" cd tests; $python3bin -W default -m unittest -v; cd ..; #echo "" #echo "=============" #echo " Python 3.6.15 (custom build)" #echo " tk 8.6 (debian package)" # #cd tests; cpython3.6 -m unittest; cd ..; pygubu-0.36.1/pyproject.toml000066400000000000000000000042151474524032100160340ustar00rootroot00000000000000[build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" [project] name = "pygubu" description = "A simple GUI builder for the python tkinter module" readme = "README.md" requires-python = ">=3.8" license = { text = "MIT" } keywords = ["gui", "tkinter", "designer"] authors = [ { name = "Alejandro Autalan", email = "alejandroautalan@gmail.com" }, ] classifiers = [ "Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities", "Topic :: Software Development :: User Interfaces", ] dependencies = [ "importlib_resources;python_version<'3.9'", ] dynamic = ["version"] [project.urls] Documentation = "https://github.com/alejandroautalan/pygubu#readme" Issues = "https://github.com/alejandroautalan/pygubu/issues" Source = "https://github.com/alejandroautalan/pygubu" [project.optional-dependencies] ttkwidgets = ['ttkwidgets'] tksheet = ['tksheet'] tkinterweb = ['tkinterweb'] tkintertable = ['tkintertable'] tkcalendar = ['tkcalendar'] awesometkinter = ['AwesomeTkinter'] customtkinter = ['customtkinter >=5.2.2', 'packaging', 'pillow'] tkintermapview = ['tkintermapview'] all = [ 'AwesomeTkinter', 'tkintertable', 'tksheet', 'ttkwidgets', 'tkinterweb', 'tkcalendar', 'customtkinter >=5.2.2', 'packaging', 'pillow', 'tkintermapview'] [project.entry-points."pyinstaller40"] hook-dirs = "pygubu.__pyinstaller:get_hook_dirs" [tool.setuptools.dynamic] version = {attr = "pygubu.__version__"} [tool.setuptools.packages.find] where = ["src"] [tool.black] line-length = 80 target-version = ["py38", "py39", "py310"] include = '\\.pyi?$' exclude = ''' /( \.git | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist )/ ''' pygubu-0.36.1/run.sh000077500000000000000000000026301474524032100142620ustar00rootroot00000000000000#!/bin/bash # # usage: ./run.sh command [argument ...] # # Commands used during development / CI. # Also, executable documentation for project dev practices. # # See https://death.andgravity.com/run-sh # for an explanation of how it works and why it's useful. # First, set up the environment. # (Check the notes at the end when changing this.) set -o nounset set -o pipefail set -o errexit # Change the current directory to the project root. PROJECT_ROOT=${0%/*} if [[ $0 != $PROJECT_ROOT && $PROJECT_ROOT != "" ]]; then cd "$PROJECT_ROOT" fi readonly PROJECT_ROOT=$( pwd ) # Store the absolute path to this script (useful for recursion). readonly SCRIPT="$PROJECT_ROOT/$( basename "$0" )" # Commands follow. # System requirements: # apt install python3-build twine # python3bin=$(which python3) function tests { cd tests; $python3bin -W default -m unittest -v; cd ..; } function build { $python3bin -m build } function upload_testpypi { build && \ twine upload --skip-existing -r test_pygubu dist/* } function upload_pypi { build && \ twine upload --skip-existing -r pygubu_project dist/* } # Commands end. Dispatch to command. "$@" # Some dev notes for this script. # # The commands *require*: # # * The current working directory is the project root. # * The shell options and globals are set as they are. # # Inspired by http://www.oilshell.org/blog/2020/02/good-parts-sketch.html # pygubu-0.36.1/setup.py000066400000000000000000000000751474524032100146320ustar00rootroot00000000000000#!/usr/bin/env python3 from setuptools import setup setup() pygubu-0.36.1/src/000077500000000000000000000000001474524032100137055ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/000077500000000000000000000000001474524032100152205ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/__init__.py000066400000000000000000000001371474524032100173320ustar00rootroot00000000000000# encoding: utf-8 __all__ = ["Builder"] __version__ = "0.36.1" from .builder import Builder pygubu-0.36.1/src/pygubu/__pyinstaller/000077500000000000000000000000001474524032100200645ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/__pyinstaller/__init__.py000066400000000000000000000001551474524032100221760ustar00rootroot00000000000000from typing import List import os def get_hook_dirs() -> List[str]: return [os.path.dirname(__file__)] pygubu-0.36.1/src/pygubu/__pyinstaller/hook-pygubu.py000066400000000000000000000017541474524032100227160ustar00rootroot00000000000000hiddenimports = [ "pygubu.plugins.pygubu.accordionframe_bo", "pygubu.plugins.pygubu.calendarframe_bo", "pygubu.plugins.pygubu.colorinput_bo", "pygubu.plugins.pygubu.combobox_bo", "pygubu.plugins.pygubu.dialog_bo", "pygubu.plugins.pygubu.dockfw_bo", "pygubu.plugins.pygubu.editabletreeview_bo", "pygubu.plugins.pygubu.filterabletreeview_bo", "pygubu.plugins.pygubu.flodgauge_bo", "pygubu.plugins.pygubu.forms", "pygubu.plugins.pygubu.forms.pygubuwidget_bo", "pygubu.plugins.pygubu.forms.tkwidget_bo", "pygubu.plugins.pygubu.forms.ttkwidget_bo", "pygubu.plugins.pygubu.hideableframe_bo", "pygubu.plugins.pygubu.pathchooserinput_bo", "pygubu.plugins.pygubu.scrollbarhelper_bo", "pygubu.plugins.pygubu.scrolledframe_bo", "pygubu.plugins.pygubu.tkscrollbarhelper_bo", "pygubu.plugins.pygubu.tkscrolledframe_bo", "pygubu.plugins.tk.scrolledtext_bo", "pygubu.plugins.tk.tkstdwidgets", "pygubu.plugins.ttk.ttkstdwidgets", ] pygubu-0.36.1/src/pygubu/api/000077500000000000000000000000001474524032100157715ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/api/__init__.py000066400000000000000000000000001474524032100200700ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/api/v1/000077500000000000000000000000001474524032100163175ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/api/v1/__init__.py000066400000000000000000000005161474524032100204320ustar00rootroot00000000000000from ...component.builderobject import BuilderObject from ...component.plugin_engine import ( IPluginBase, IBuilderLoaderPlugin, IDesignerPlugin, BuilderLoaderPlugin, ) from ._private import ( register_builder, register_widget, register_property, register_custom_property, copy_custom_property, ) pygubu-0.36.1/src/pygubu/api/v1/_private.py000066400000000000000000000041071474524032100205040ustar00rootroot00000000000000import logging from ...component.builderobject import ( CLASS_MAP, WidgetDescription, CUSTOM_PROPERTIES, ) logger = logging.getLogger(__name__) def register_builder( classname, builder, label=None, tags=None, group=None, public=True ): if label is None: label = classname if tags is None: tags = tuple() if group is None: group = 9 CLASS_MAP[classname] = WidgetDescription( classname, builder, label, tags, group, public ) def register_widget( classname, builder, label=None, tags=None, group=None, public=True ): return register_builder(classname, builder, label, tags, group, public) def register_property(name, description): if name in CUSTOM_PROPERTIES: CUSTOM_PROPERTIES[name].update(description) logger.debug("Updating registered property %s", name) else: CUSTOM_PROPERTIES[name] = description logger.debug("Registered property %s", name) def register_custom_property( builder_uid, prop_name, editor, default_value=None, help=None, **editor_params, ): """Helper function to register a custom property. All custom properties are created using internal dynamic editor. """ description = { "editor": "dynamic", builder_uid: { "params": { "mode": editor, } }, } description[builder_uid]["params"].update(editor_params) if default_value is not None: description[builder_uid]["default"] = default_value if help is not None: description[builder_uid]["help"] = help register_property(prop_name, description) def copy_custom_property(from_builder_id, pname, to_builder_id): if pname not in CUSTOM_PROPERTIES: raise RuntimeError(f"Property {pname} not registered.") elif from_builder_id not in CUSTOM_PROPERTIES[pname]: raise RuntimeError(f"Builder ID {from_builder_id} not registered.") else: description = CUSTOM_PROPERTIES[pname][from_builder_id].copy() CUSTOM_PROPERTIES[pname][to_builder_id] = description pygubu-0.36.1/src/pygubu/binding/000077500000000000000000000000001474524032100166325ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/binding/__init__.py000066400000000000000000000002231474524032100207400ustar00rootroot00000000000000__all__ = ["remove_binding", "ApplicationLevelBindManager"] from .base import remove_binding from .bindmanager import ApplicationLevelBindManager pygubu-0.36.1/src/pygubu/binding/base.py000066400000000000000000000020261474524032100201160ustar00rootroot00000000000000# encoding: utf-8 import logging import tkinter as tk logger = logging.getLogger(__name__) def bindings(widget, seq): return [x for x in widget.bind(seq).splitlines() if x.strip()] def _funcid(binding): return binding.split()[1][3:] def remove_binding(widget, seq, index=None, funcid=None): b = bindings(widget, seq) if index is not None: try: binding = b[index] widget.unbind(seq, _funcid(binding)) b.remove(binding) except IndexError: logger.info("Binding #%d not defined.", index) return elif funcid: binding = None for x in b: if _funcid(x) == funcid: binding = x b.remove(binding) widget.unbind(seq, funcid) break if not binding: logger.info('Binding "%s" not defined.', funcid) return else: raise ValueError("No index or function id defined.") for x in b: widget.bind(seq, "+" + x, 1) pygubu-0.36.1/src/pygubu/binding/bindmanager.py000066400000000000000000000171741474524032100214650ustar00rootroot00000000000000import logging import platform import tkinter as tk from pygubu.utils.widget import iter_to_toplevel logger = logging.getLogger(__name__) class MouseWheelCommand: def __init__(self, view_command, factor=1): self.view_command = view_command self.factor = factor class UnixMouseWheelCommandTk9(MouseWheelCommand): def __call__(self, event=None) -> bool: can_keep_scrolling = False self.view_command( "scroll", (-1) * int((event.delta / 120) * self.factor), "units", ) scroll_rs = self.view_command() if scroll_rs is not None: can_keep_scrolling = scroll_rs[0] != 0.0 if event.delta < 0: can_keep_scrolling = scroll_rs[1] != 1.0 return can_keep_scrolling class UnixMouseWheelCommandTk8(MouseWheelCommand): def __call__(self, event=None) -> bool: can_keep_scrolling = False if event.num == 4: self.view_command("scroll", (-1) * self.factor, "units") scroll_rs = self.view_command() if scroll_rs is not None: can_keep_scrolling = scroll_rs[0] != 0.0 elif event.num == 5: self.view_command("scroll", self.factor, "units") scroll_rs = self.view_command() if scroll_rs is not None: can_keep_scrolling = scroll_rs[1] != 1.0 return can_keep_scrolling class WindowsMouseWheelCommand(MouseWheelCommand): def __call__(self, event=None) -> bool: can_keep_scrolling = False self.view_command( "scroll", (-1) * int((event.delta / 120) * self.factor), "units" ) scroll_rs = self.view_command() if scroll_rs is not None: can_keep_scrolling = scroll_rs[0] != 0.0 if event.delta < 0: can_keep_scrolling = scroll_rs[1] != 1.0 return can_keep_scrolling class DarwingMouseWheelCommand(MouseWheelCommand): def __call__(self, event=None) -> bool: can_keep_scrolling = False self.view_command("scroll", event.delta, "units") scroll_rs = self.view_command() if scroll_rs is not None: can_keep_scrolling = scroll_rs[0] != 0.0 if event.delta < 0: can_keep_scrolling = scroll_rs[1] != 1.0 return can_keep_scrolling class UnknownMouseWheelCommand(MouseWheelCommand): def __call__(self, event=None) -> bool: # Unknown platform scroll method return False class AppBindManagerBase(object): CLASS_NAME = "TAppBindManager" FLAG_NAME = "_appbind_visited" # Acces to tk instances masters: list[tk.Widget] = [] # Mouse wheel support mw_listeners = [] # Mousewheel listeners @classmethod def master_for_path(cls, widget_path: str): master_found = None for master in cls.masters: exists = master.tk.eval(f"winfo exists {widget_path}") if exists: master_found = master break return master_found @classmethod def on_mousewheel_enter(cls, event): """Prepare widget for future mousewheel event.""" widget_below = event.widget if not isinstance(widget_below, tk.Widget): return if not hasattr(widget_below, cls.FLAG_NAME): flag_value = cls.has_mousewheel_listener(widget_below) setattr(widget_below, cls.FLAG_NAME, flag_value) if flag_value: # print(f"Preparing widget for mousewheel {widget_below}") # cname = widget_below.winfo_class() tags = list(widget_below.bindtags()) tags.insert(0, cls.CLASS_NAME) widget_below.bindtags(tags) @classmethod def has_mousewheel_listener(cls, widget: tk.Widget) -> bool: for w in iter_to_toplevel(widget): if w in cls.mw_listeners: return True return False @classmethod def on_mousewheel(cls, event): """ Manage Application level mousewheel event Here we expect that the widget in wm_listeners list, have a method "on_mousewheel" created with the make_onmousewheel_cb function of this class. """ # print("on_mousewheel, cls", event) widget_below = event.widget if not isinstance(widget_below, tk.Widget): try: master = cls.master_for_path(widget_below) widget_below = master.winfo_containing( event.x_root, event.y_root ) except (AttributeError, KeyError): widget_below = None # scroll_performed = False if widget_below: for w in iter_to_toplevel(widget_below): if w in cls.mw_listeners: can_keep_scrolling = w.on_mousewheel(event) if can_keep_scrolling: # scroll_performed = True break # if scroll_performed: # print("scroll performed") # For now just stop event propagation. return "break" @classmethod def mousewheel_bind(cls, widget): if widget not in cls.mw_listeners: cls.mw_listeners.append(widget) @classmethod def mousewheel_unbind(cls, widget): if widget in cls.mw_listeners: cls.mw_listeners.remove(widget) @classmethod def init_mousewheel_binding(cls, master): top = master.winfo_toplevel() if top not in cls.masters: cls.masters.append(top) # Bind to Enter event to prepare widgets top.bind_all("", cls.on_mousewheel_enter, add="+") # Bind Wheel events to a specific class name # so we cant "break" the event and stop propagation _os = platform.system() if _os in ("Linux", "OpenBSD", "FreeBSD"): if tk.TkVersion >= 9: top.bind_class( cls.CLASS_NAME, "", cls.on_mousewheel, add="+", ) else: top.bind_class( cls.CLASS_NAME, "<4>", cls.on_mousewheel, add="+" ) top.bind_class( cls.CLASS_NAME, "<5>", cls.on_mousewheel, add="+" ) else: # Windows and MacOS top.bind_class( cls.CLASS_NAME, "", cls.on_mousewheel, add="+", ) @classmethod def make_onmousewheel_cb(cls, widget, orient, factor=1): """Create a callback to manage mousewheel events orient: string (posible values: ('x', 'y')) widget: widget that implement tk xview and yview methods """ _os = platform.system() view_command = getattr(widget, orient + "view") on_mousewheel = None if _os in ("Linux", "OpenBSD", "FreeBSD"): if tk.TkVersion >= 9: on_mousewheel = UnixMouseWheelCommandTk9(view_command, factor) else: on_mousewheel = UnixMouseWheelCommandTk8(view_command, factor) elif _os == "Windows": on_mousewheel = WindowsMouseWheelCommand(view_command, factor) elif _os == "Darwin": on_mousewheel = DarwingMouseWheelCommand(view_command, factor) else: on_mousewheel = UnknownMouseWheelCommand(view_command, factor) return on_mousewheel class ApplicationLevelBindManager(AppBindManagerBase): ... pygubu-0.36.1/src/pygubu/builder.py000066400000000000000000000331041474524032100172210ustar00rootroot00000000000000# encoding: utf-8 import sys import importlib import logging import tkinter from pathlib import Path from .component.builderobject import CLASS_MAP, BuilderObject from .component.widgetmeta import WidgetMeta from .stockimage import StockImage, StockImageException from .component.uidefinition import UIDefinition from .component.plugin_manager import PluginManager from .component.datapool import IDataPool, DictDataPool, InvalidURIError logger = logging.getLogger(__name__) # # Builder class # class Builder(object): """Allows to build a tk interface from xml definition. Parameters ---------- translator: callable function used to process translatable strings. on_first_object: Callable[[tkinter.Widget], None] if specified, the function will be called just after the first object is created. Usefult to set styles and setup the option database when the first Tk object is created. """ TK_VARIABLE_TYPES = ("string", "int", "boolean", "double") def __init__( self, translator=None, *, on_first_object=None, image_cache=None, data_pool=None, ): super().__init__() self.uidefinition = UIDefinition(translator=translator) self.root = None self.objects = {} self.tkvariables = {} self.translator = translator self.on_first_object = on_first_object # On first object callback if image_cache is None: image_cache = StockImage self.image_cache = image_cache self.data_pool: IDataPool = ( data_pool if isinstance(data_pool, IDataPool) else DictDataPool(data_pool) ) def get_resource(self, uri: str): """Gets a generic resource from the data pool. Note, this is NOT related to the add_resource_* methods. """ data = None try: data = self.data_pool.get_resource(uri) except InvalidURIError as e: logger.exception(e) return data def add_resource_path(self, path): """Add additional path to the resources paths.""" self.image_cache.add_resource_path(path) def add_resource_paths(self, path_list: list): """Add multiple paths for aditional resources.""" for path in path_list: self.add_resource_path(path) def add_resource_package(self, pkg: str): self.image_cache.add_resource_package(pkg) def get_image(self, path): """Return tk image corresponding to name which is taken form path.""" image = "" name = Path(path).name if not self.image_cache.is_registered(name): self.image_cache.find_and_register(name) try: image = self.image_cache.get(name) except StockImageException: # TODO: notify something here. pass return image def get_iconbitmap(self, path): """Return path to use as iconbitmap property.""" image = None name = Path(path).name if not self.image_cache.is_registered(name): self.image_cache.find_and_register(name) try: image = self.image_cache.as_iconbitmap(name) except StockImageException: # TODO: notify something here. pass return image def get_variable(self, varname): """Return a tk variable created with 'create_variable' method.""" return self.tkvariables[varname] def import_variables(self, container, varnames=None): """Helper method to avoid call get_variable for every variable.""" if varnames is None: for keyword in self.tkvariables: setattr(container, keyword, self.tkvariables[keyword]) else: for keyword in varnames: if keyword in self.tkvariables: setattr(container, keyword, self.tkvariables[keyword]) def _process_variable_description(self, name_or_desc): vname = name_or_desc vtype = "string" # default type if not defined if ":" in name_or_desc: vtype, vname = name_or_desc.split(":") # Fix incorrect order bug #33 if vtype not in self.TK_VARIABLE_TYPES: # Swap order vtype, vname = vname, vtype if vtype not in self.TK_VARIABLE_TYPES: msg = 'Undefined variable type in "{0}"'.format(vname) raise Exception(msg) return (vname, vtype) def create_variable(self, varname, vtype=None): """Create a tk variable. If the variable was created previously return that instance. """ vname, type_from_name = self._process_variable_description(varname) if vname in self.tkvariables: var = self.tkvariables[vname] else: if vtype is None: # get type from name if type_from_name == "int": var = tkinter.IntVar() elif type_from_name == "boolean": var = tkinter.BooleanVar() elif type_from_name == "double": var = tkinter.DoubleVar() else: var = tkinter.StringVar() else: var = vtype() self.tkvariables[vname] = var return var def add_from_file(self, file_or_filename): """Load ui definition from file.""" self.uidefinition.load_file(file_or_filename) def add_from_string(self, strdata): self.uidefinition.load_from_string(strdata) def add_from_xmlnode(self, element): """Load ui definition from xml.etree.Element node.""" self.uidefinition.add_xmlnode(element) def get_object(self, name, master=None, extra_init_args: dict = None): """Find and create the widget named name. Use master as parent. If widget was already created, return that instance.""" widget = None if name in self.objects: widget = self.objects[name].widget else: wmeta = self.uidefinition.get_widget(name) if wmeta is not None: rmeta = WidgetMeta("root", "root") root = BuilderObject(self, rmeta) root.widget = master bobject = self._realize(root, wmeta, extra_init_args) widget = bobject.widget if widget is None: msg = 'Widget "{0}" not defined.'.format(name) raise Exception(msg) return widget def _import_class(self, builder_id): plugin_managed = False for loader in PluginManager.builder_plugins(): if loader.can_load(builder_id): _module = loader.get_module_for(builder_id) try: importlib.import_module(_module) plugin_managed = True logger.debug("Module %s loaded.", _module) # Module found, Stop searching break except (ModuleNotFoundError, ImportError) as e: msg = "Failed to import module as fullname: %s" logger.debug(msg, _module) raise e # If no plugin, Try loading as new project custom widget new_project_custom_widget = False if not plugin_managed: self._load_custom_widgets() if self.is_mapped(builder_id): new_project_custom_widget = True # If no plugin, or custom widget. Try loading as old custom widget method. if not plugin_managed and not new_project_custom_widget: _module: str = builder_id targets = [] first_module = _module.split(".")[0] spec = importlib.util.find_spec(first_module) if spec is not None: if "." in _module: # Import module as full path fullpath, b = _module.rsplit(".", 1) if fullpath != first_module: targets.append(fullpath) # A single module can contain various widgets # try to import the first part of the path targets.append(first_module) # Load target modules, first fullpath, then first_module module_loaded = False last_exception = None for module in targets: try: importlib.import_module(module) module_loaded = True logger.debug("Module %s loaded.", module) break except Exception as e: last_exception = e self._handle_load_problems(_module, module_loaded, last_exception) def _handle_load_problems(self, _module, module_loaded, last_exception): if not module_loaded: error = RuntimeError( f"Failed to import a module for builder id '{_module}'" ) logger.exception(error) if last_exception: raise error from last_exception raise error def _load_custom_widgets(self): ui_dir = Path().resolve() uifile = self.uidefinition.uifile if uifile is not None: ui_dir = Path(uifile).parent.resolve() custom_widgets = [ Path(ui_dir, cwpath).resolve() for cwpath in self.uidefinition.custom_widgets ] for path in custom_widgets: if not path.match("*.py"): continue dirname = str(path.parent) modulename = path.name[:-3] if dirname not in sys.path: sys.path.append(dirname) try: importlib.import_module(modulename) except (ModuleNotFoundError, ImportError) as e: logger.error( "Failed to load custom widget module: %s", str(path) ) logger.exception(e) raise e def is_mapped(self, builder_uid): return builder_uid in CLASS_MAP def _get_builder_for(self, builder_uid): return CLASS_MAP[builder_uid].builder def _realize(self, master, wmeta, extra_init_args: dict = None): """Builds a widget from widget metadata using master as parent.""" if not self.is_mapped(wmeta.classname): self._import_class(wmeta.classname) if self.is_mapped(wmeta.classname): bclass = self._get_builder_for(wmeta.classname) parent = bclass.factory(self, wmeta) self._pre_realize(parent) parent.realize(master, extra_init_args) parent.configure() if not self.objects and self.on_first_object is not None: self.on_first_object(parent.widget) self.objects[wmeta.identifier] = parent for childmeta in self.uidefinition.widget_children( wmeta.identifier ): child = self._realize(parent, childmeta) parent.add_child(child) parent.configure_children() parent.layout() self._post_realize(parent) return parent else: msg = 'Class "{0}" not mapped'.format(wmeta.classname) raise Exception(msg) def _pre_realize(self, bobject): wmeta = bobject.wmeta cname = wmeta.classname wmeta.layout_required = bobject.layout_required has_layout = len(wmeta.layout_properties) > 1 if wmeta.layout_required and not has_layout: logger.debug( "No layout information for: (%s, %s).", cname, wmeta.identifier, ) def _post_realize(self, bobject): pass def connect_callbacks(self, callbacks_bag): """Connect callbacks specified in callbacks_bag with callbacks defined in the ui definition. Return a list with the name of the callbacks not connected. """ notconnected = [] for wname, builderobj in self.objects.items(): missing = builderobj.connect_commands(callbacks_bag) if missing is not None: notconnected.extend(missing) missing = builderobj.connect_bindings(callbacks_bag) if missing is not None: notconnected.extend(missing) if notconnected: notconnected = list(set(notconnected)) msg = "Missing callbacks for commands: %s" logger.warning(msg, notconnected) return notconnected else: return None def forget_unnamed(self): """Removes unnamed object from the objects attribute. User should calls this method at the end of the build process, after calling "connect_callbacks". """ for key in list(self.objects.keys()): if not self.objects[key].wmeta.is_named: del self.objects[key] def code_create_variable(self, name_or_desc, value, vtype=None): raise NotImplementedError() def code_create_image(self, filename): raise NotImplementedError() def code_create_iconbitmap(self, filename): raise NotImplementedError() def code_classname_for(self, bobject): raise NotImplementedError() def code_create_callback(self, widgetid, cbname, cbtype, args=None): raise NotImplementedError() def code_translate_str(self, value: str) -> str: raise NotImplementedError() def code_get_resource(self, uri: str): raise NotImplementedError() # Load plugins on module init PluginManager.load_plugins() pygubu-0.36.1/src/pygubu/component/000077500000000000000000000000001474524032100172225ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/component/__init__.py000066400000000000000000000000001474524032100213210ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/component/builderobject.py000066400000000000000000001102741474524032100224160ustar00rootroot00000000000000# encoding: utf-8 import json import logging import tkinter as tk from collections import defaultdict, namedtuple __all__ = [ "BuilderObject", "EntryBaseBO", "PanedWindowBO", "PanedWindowPaneBO", "WidgetDescription", "CLASS_MAP", "CB_TYPES", "CUSTOM_PROPERTIES", "TRANSLATABLE_PROPERTIES", ] logger = logging.getLogger(__name__) # # Utility functions # def isfloat(num: str) -> bool: try: float(num) return True except ValueError: return False # # BuilderObject # WidgetDescription = namedtuple( "WidgetDescription", ["classname", "builder", "label", "tags", "group", "public"], ) CLASS_MAP = {} CUSTOM_PROPERTIES = {} class CB_TYPES: """Callback types""" SIMPLE = "simple" WITH_WID = "with_wid" ENTRY_VALIDATE = "entry_validate" SCROLL = "scroll" SCROLLSET = "scrollset" SCALE = "scale" BIND_EVENT = "bind_event" TRANSLATABLE_PROPERTIES = ["label", "text", "title"] # # Base class # class BuilderObject(object): """Base class for Widgets created with Builder. children_layout_override: Determine if children need to cancel layout on themselves. """ OPTIONS_STANDARD = tuple() OPTIONS_SPECIFIC = tuple() OPTIONS_CUSTOM = tuple() class_ = None container = False container_layout = False allowed_parents = None allowed_children = None maxchildren = None children_layout_override = False properties = tuple() ro_properties = tuple() layout_required = True command_properties = tuple() allow_bindings = True tkvar_properties = ("listvariable", "textvariable", "variable") tkimage_properties = ( "image", "selectimage", "iconphoto", "backgroundimage", ) tkfont_properties = ("font",) virtual_events = tuple() @classmethod def factory(cls, builder, wdata): clsobj = cls(builder, wdata) wdata.layout_required = clsobj.layout_required return clsobj @classmethod def add_allowed_parent(cls, builder_uid): if cls.allowed_parents is None: cls.allowed_parents = (builder_uid,) else: cls.allowed_parents = cls.allowed_parents + (builder_uid,) @classmethod def add_allowed_child(cls, builder_uid): if cls.allowed_children is None: cls.allowed_children = (builder_uid,) else: cls.allowed_children = cls.allowed_children + (builder_uid,) @classmethod def canbe_parent_of(cls, child_builder, classname): allowed = bool(cls.container) if ( cls.allowed_children is not None and classname not in cls.allowed_children ): allowed = False return allowed @classmethod def canbe_child_of(cls, parent_builder, classname): allowed = True if ( cls.allowed_parents is not None and classname not in cls.allowed_parents ): allowed = False return allowed def __init__(self, builder, wmeta): super(BuilderObject, self).__init__() self.widget = None self.builder = builder self.wmeta = wmeta self._code_identifier = None self.parent_bo = None def realize(self, parent, extra_init_args: dict = None): self.parent_bo = parent args = self._get_init_args(extra_init_args) master = parent.get_child_master() self.widget = self.class_(master, **args) return self.widget def _get_init_args(self, extra_init_args: dict = None): """Creates dict with properties marked as readonly""" args = {} for rop in self.ro_properties: if rop in self.wmeta.properties: pvalue = self._process_property_value( rop, self.wmeta.properties[rop] ) args[rop] = pvalue if extra_init_args is not None: for key, value in extra_init_args.items(): args[key] = value # Set widget tcl name # Note: tcl does not like capital letters for widget name pname = "name" if self._can_set_tcl_widget_name(): if pname not in args: args[pname] = str(self.wmeta.identifier).lower() logger.debug( "Setting widget tcl name to: %s", self.wmeta.identifier ) return args def _can_set_tcl_widget_name(self) -> bool: """Returns True if widget accepts the tcl "name" init argument.""" if self.class_ is None: return False return self.wmeta.is_named and issubclass(self.class_, tk.Widget) def configure(self, target=None): if target is None: target = self.widget for pname, value in self.wmeta.properties.items(): if ( pname not in self.ro_properties and pname not in self.command_properties ): self._set_property(target, pname, value) def configure_children(self, target=None): "Aditional configurations after adding all children." pass def _process_property_value(self, pname, value): propvalue = value uri = str(value) if uri.startswith("res://"): propvalue = self.builder.get_resource(uri) elif pname in self.tkvar_properties: propvalue = self.builder.create_variable(value) if "text" in self.wmeta.properties and pname == "textvariable": propvalue.set(self.wmeta.properties["text"]) elif "value" in self.wmeta.properties and pname == "variable": propvalue.set(self.wmeta.properties["value"]) elif pname in self.tkimage_properties: if value: # image value should not be empty here. propvalue = self.builder.get_image(value) elif pname == "takefocus": if value: propvalue = tk.getboolean(value) return propvalue def _set_property(self, target_widget, pname, value): if pname not in self.__class__.properties: msg = "Attempt to set an unknown property '%s' on class '%s'" logger.warning(msg, pname, repr(self.class_)) else: try: logger.debug("processing property %s value", pname) propvalue = self._process_property_value(pname, value) logger.debug("setting property %s = %s", pname, propvalue) target_widget.configure(**{pname: propvalue}) except Exception as e: msg = "Failed to set property '%s' on class '%s'. Error: %s" logger.error(msg, pname, repr(self.class_), str(e)) # logger.exception(e) def _get_default_value(self, widget: tk.Widget, pname: str, default=None): """ Query widget instance for property default value. Follow tk configure convention. """ try: description = widget.configure(pname) # print(description) except tk.TclError: logger.error("Failed to get default value for property %s", pname) return default has_default = False value = None if isinstance(description, (tuple, list)): if len(description) == 5: # 0: option name 1: database name # 2: database class 3: default value, # 4: current value value = description[3] has_default = True # Post processing for special values: if pname == "takefocus" and value == "ttk::takefocus": wname = str(widget) value = widget.tk.eval(f"{value} {wname}") return value if has_default else default def unset_property(self, pname, target=None): """Try setting the widget property to its default value.""" widget = target if target is not None else self.widget unset_value = self._get_default_value(widget, pname) self._set_property(widget, pname, unset_value) def _parent_cancels_children_layout(self) -> bool: override = False if self.parent_bo is not None: override = self.parent_bo.children_layout_override return override def is_layout_required(self) -> bool: return ( self.layout_required and not self._parent_cancels_children_layout() ) def layout(self, target=None, *, forget=False): if target is None: target = self.widget if self.is_layout_required(): # Check manager manager = self.wmeta.manager logger.debug( "Applying %s layout to %s", manager, self.wmeta.identifier ) properties = self.wmeta.layout_properties if manager == "grid": if forget: target.grid_forget() target.grid(**properties) elif manager == "pack": if forget: target.pack_forget() target.pack(**properties) elif manager == "place": if forget: target.place_forget() target.place(**self.wmeta.layout_properties) else: msg = f"Invalid layout manager: {manager}" raise Exception(msg) # container layout if self.container_layout: self._container_layout( target, self.wmeta.container_manager, self.wmeta.container_properties, ) def _container_layout(self, target, container_manager, properties): # Do container layout propagate = properties.get("propagate", "true") propagate = tk.getboolean(propagate) propagate = 1 if propagate else 0 anchor = properties.get("anchor", None) if container_manager == "grid": if anchor: target.grid_anchor(anchor) target.grid_propagate(propagate) self._gridrc_config(target) elif container_manager == "pack": target.pack_propagate(propagate) elif container_manager is None: raise Exception("Container Manager is none :(") def _gridrc_config(self, target): """Configure grid row/col properties""" # process all index first rc_props = sorted( self.wmeta.gridrc_properties, key=lambda rc: -1 if rc[1] == "all" else 1, ) for type_, num, pname, value in rc_props: if type_ == "row": target.rowconfigure(num, **{pname: value}) else: target.columnconfigure(num, **{pname: value}) def get_child_master(self): return self.widget def add_child(self, bobject): pass def _create_callback(self, cmd, command): callback = command cmd_type = cmd["cbtype"] if cmd_type == CB_TYPES.WITH_WID: def widget_callback(button_id=self.wmeta.identifier): command(button_id) callback = widget_callback if cmd_type == CB_TYPES.ENTRY_VALIDATE: args = cmd["args"] if args: args = args.split(" ") callback = (self.widget.register(command),) + tuple(args) else: callback = self.widget.register(command) return callback def _connect_command(self, cmd_pname, callback): prop = {cmd_pname: callback} self.widget.configure(**prop) def connect_commands(self, cmd_bag): notconnected = [] commands = {} for cmd_pname in self.command_properties: cmd = self.wmeta.properties.get(cmd_pname, None) if cmd is not None: cmd = json.loads(cmd) cmd_name = (cmd["value"]).strip() if cmd_name: commands[cmd_pname] = cmd else: msg = "%s: invalid value for property '%s'." logger.warning(msg, self.wmeta.identifier, cmd_pname) if isinstance(cmd_bag, dict): for cmd_pname, cmd in commands.items(): cmd_name = cmd["value"] if cmd_name in cmd_bag: callback = self._create_callback(cmd, cmd_bag[cmd_name]) self._connect_command(cmd_pname, callback) else: notconnected.append(cmd_name) else: for cmd_pname, cmd in commands.items(): cmd_name = cmd["value"] if hasattr(cmd_bag, cmd_name): callback = self._create_callback( cmd, getattr(cmd_bag, cmd_name) ) self._connect_command(cmd_pname, callback) else: notconnected.append(cmd_name) if notconnected: return notconnected else: return None def _connect_binding(self, sequence: str, callback, add): self.widget.bind(sequence, callback, add) def connect_bindings(self, cb_bag): notconnected = [] if isinstance(cb_bag, dict): for bind in self.wmeta.bindings: cb_name = bind.handler if cb_name in cb_bag: callback = cb_bag[cb_name] self._connect_binding(bind.sequence, callback, add=bind.add) else: notconnected.append(cb_name) else: for bind in self.wmeta.bindings: cb_name = bind.handler if hasattr(cb_bag, cb_name): callback = getattr(cb_bag, cb_name) self._connect_binding(bind.sequence, callback, add=bind.add) else: notconnected.append(cb_name) if notconnected: return notconnected else: return None # # Code generation methods # @staticmethod def code_escape_str(value: str) -> str: return repr(value) def _code_get_init_args(self, code_identifier): """Creates dict with properties marked as readonly""" args = {} for rop in self.ro_properties: if rop in self.wmeta.properties: pvalue = self._code_process_property_value( code_identifier, rop, self.wmeta.properties[rop] ) args[rop] = pvalue # Set widget tcl name # Note: tcl does not like capital letters for widget name pname = "name" if self._can_set_tcl_widget_name(): if pname not in args: pvalue = self._code_process_property_value( code_identifier, pname, str(self.wmeta.identifier).lower() ) args[pname] = pvalue logger.debug("Setting widget tcl name to: %s", pvalue) return args def code_realize(self, boparent, code_identifier=None): self.parent_bo = boparent if code_identifier is not None: self._code_identifier = code_identifier lines = [] master = boparent.code_child_master() init_args = self._code_get_init_args(self.code_identifier()) bag = [] for pname, value in init_args.items(): bag.append(f"{pname}={value}") kwargs = "" if bag: kwargs = f""", {", ".join(bag)}""" s = f"{self.code_identifier()} = {self._code_class_name()}({master}{kwargs})" lines.append(s) return lines def code_identifier(self): if self._code_identifier is None: return self.wmeta.identifier return self._code_identifier def code_child_master(self): return self.code_identifier() def code_child_add(self, childid): return [] def code_configure(self, targetid=None): if targetid is None: targetid = self.code_identifier() ( code_bag, kwproperties, complex_properties, ) = self._code_process_properties(self.wmeta.properties, targetid) lines = [] args_bag = [] for pname in sorted(kwproperties): arg_stmt = f"{pname}={code_bag[pname]}" args_bag.append(arg_stmt) if args_bag: args = ", ".join(args_bag) line = f"{targetid}.configure({args})" lines.append(line) for pname in complex_properties: lines.extend(code_bag[pname]) return lines def code_configure_children(self, target=None): "Aditional configurations after adding all children." return tuple() def code_layout(self, targetid=None, parentid=None): if targetid is None: targetid = self.code_identifier() if parentid is None: parentid = targetid lines = [] if self.is_layout_required(): manager = self.wmeta.manager layout = self.wmeta.layout_properties args_bag = [] for p, v in sorted(layout.items()): pvalue = self._code_process_layout_property(manager, p, v) arg_stmt = f"{p}={pvalue}" args_bag.append(arg_stmt) args = ", ".join(args_bag) line = f"{targetid}.{manager}({args})" lines.append(line) if self.container_layout: manager = self.wmeta.container_manager container_prop = self.wmeta.container_properties pvalue = str(container_prop.get("propagate", "")).lower() if "propagate" in container_prop and pvalue == "false": line = f"{targetid}.{manager}_propagate(0)" lines.append(line) if manager == "grid" and "anchor" in container_prop: value = container_prop.get("anchor") line = f'{targetid}.{manager}_anchor("{value}")' lines.append(line) rowbag = defaultdict(list) colbag = defaultdict(list) # process all index first rc_props = sorted( self.wmeta.gridrc_properties, key=lambda rc: -1 if rc[1] == "all" else 1, ) for type_, num, pname, value in rc_props: pvalue = self._code_process_layout_property( "grid", pname, value ) arg = f"{pname}={pvalue}" if type_ == "row": rowbag[num].append(arg) else: colbag[num].append(arg) for k, bag in rowbag.items(): args = ", ".join(bag) rowid = f'"{k}"' if k == "all" else k line = f"{targetid}.rowconfigure({rowid}, {args})" lines.append(line) for k, bag in colbag.items(): args = ", ".join(bag) colid = f'"{k}"' if k == "all" else k line = f"{targetid}.columnconfigure({colid}, {args})" lines.append(line) return lines def _code_class_name(self): cname = None # try here ? cname = self.builder.code_classname_for(self) if cname is None: if self.class_ is not None: prefix = self.class_.__module__ name = self.class_.__name__ cname = f"{prefix}.{name}" else: cname = "ClassNameNotDefined" return cname def _code_process_properties( self, properties, targetid, skip_ro=True, skip_commands=True ): code_bag = {} for pname, value in properties.items(): if pname in self.ro_properties and skip_ro: continue if pname in self.command_properties and skip_commands: continue self._code_set_property(targetid, pname, value, code_bag) # properties # determine kw properties or complex properties kwproperties = [] complex_properties = [] for pname, value in code_bag.items(): if ( isinstance(value, str) or isinstance(value, bool) or isinstance(value, int) or isinstance(value, float) ): kwproperties.append(pname) else: complex_properties.append(pname) return (code_bag, kwproperties, complex_properties) def _code_process_property_value(self, targetid, pname, value: str): propvalue = None uri = str(value) if uri.startswith("res://"): propvalue = self.builder.code_get_resource(uri) elif pname in self.tkvar_properties: propvalue = self._code_set_tkvariable_property(pname, value) elif pname in self.command_properties: cmd = json.loads(value) propvalue = self._code_define_callback(pname, cmd) elif pname in self.tkimage_properties: propvalue = self.builder.code_create_image(value) elif pname in TRANSLATABLE_PROPERTIES: propvalue = self.builder.code_translate_str(value) elif str(value).lower() in ("true", "false"): propvalue = str(tk.getboolean(value)) # default processing if propvalue is None: pvalue_str = str(value) propvalue = ( pvalue_str if pvalue_str.isnumeric() or isfloat(pvalue_str) else f'"{value}"' ) return propvalue def _code_set_property(self, targetid, pname, value, code_bag): code_bag[pname] = self._code_process_property_value( targetid, pname, value ) def _code_set_tkvariable_property(self, pname, value): """Create code for tk variable property. Can be used from subclases for custom tk variable properties.""" varvalue = None if "text" in self.wmeta.properties and pname == "textvariable": varvalue = self.wmeta.properties["text"] elif "value" in self.wmeta.properties and pname == "variable": varvalue = self.wmeta.properties["value"] propvalue = self.builder.code_create_variable(value, varvalue) return propvalue def code_connect_commands(self): commands = {} for cmd_pname in self.command_properties: cmd = self.wmeta.properties.get(cmd_pname, None) if cmd is not None: cmd = json.loads(cmd) cmd_name = (cmd["value"]).strip() if cmd_name: commands[cmd_pname] = cmd else: msg = "%s: invalid callback name for property '%s'." logger.warning(msg, self.wmeta.identifier, cmd_pname) lines = [] for cmd_pname, cmd in commands.items(): callback = self._code_define_callback(cmd_pname, cmd) cmd_code = self._code_connect_command(cmd_pname, cmd, callback) if cmd_code: lines.extend(cmd_code) return lines def _code_define_callback_args(self, cmd_pname, cmd): cmdtype = cmd["cbtype"] args = None if cmdtype == CB_TYPES.WITH_WID: args = ("widget_id",) if cmdtype == CB_TYPES.SCALE: args = ("scale_value",) if cmdtype == CB_TYPES.SCROLLSET: args = ("first", "last") if cmdtype == CB_TYPES.SCROLL: args = ("mode", "value", "units") if cmdtype == CB_TYPES.ENTRY_VALIDATE: cb_args = cmd["args"] if cb_args: name_map = { "%d": "d_action", "%i": "i_index", "%P": "p_entry_value", "%s": "s_prev_value", "%S": "s_new_value", "%v": "v_validate", "%V": "v_condition", "%W": "w_entry_name", } args = [] for key in cb_args.split(): args.append(name_map[key]) return args def _code_define_callback(self, cmd_pname, cmd): cmdname = cmd["value"] cmdtype = cmd["cbtype"] args = self._code_define_callback_args(cmd_pname, cmd) wid = self.code_identifier() return self.builder.code_create_callback(wid, cmdname, cmdtype, args) def _code_connect_command(self, cmd_pname, cmd, cbname): target = self.code_identifier() args = cmd.get("args", "") args = args.split() if args else None lines = [] cmdtype = cmd["cbtype"] if cmdtype == CB_TYPES.WITH_WID: wid = self.wmeta.identifier fname = f"{wid}_cmd_" fdef = f"""def {fname}(): {cbname}("{wid}")\n""" cbname = fname lines.append(fdef) if args is not None: if cmdtype == CB_TYPES.ENTRY_VALIDATE: original_cb = cbname tk_args = [f'"{a}"' for a in args] tk_args = ",".join(tk_args) fdef = f"_validatecmd = ({target}.register({original_cb}), {tk_args})" cbname = "_validatecmd" lines.append(fdef) line = f"{target}.configure({cmd_pname}={cbname})" lines.append(line) return lines def _code_connect_binding( self, target: str, sequence: str, callback: str, add_arg: str ): return f'{target}.bind("{sequence}", {callback}, add="{add_arg}")' def code_connect_bindings(self): lines = [] target = self.code_identifier() for bind in self.wmeta.bindings: cb_name = self.builder.code_create_callback( target, bind.handler, CB_TYPES.BIND_EVENT ) add_arg = "+" if bind.add else "" line = self._code_connect_binding( target, bind.sequence, cb_name, add_arg ) lines.append(line) return lines def _code_process_layout_property( self, manager: str, pname: str, pvalue: str ) -> str: pvalue_str = str(pvalue) if pname in ("propagate", "expand"): fvalue = str(tk.getboolean(pvalue)) else: fvalue = ( pvalue_str if pvalue_str.isnumeric() or isfloat(pvalue_str) else f'"{pvalue}"' ) return fvalue def code_imports(self): # will return an iterable of (module, classname/function) to import # or None return None # # Base clases for some widgets # class EntryBaseBO(BuilderObject): """Base class for tk.Entry and ttk.Entry builder objects""" def _set_property(self, target_widget, pname, value): if pname == "text": wstate = str(target_widget["state"]) if wstate != "normal": # change state temporarily target_widget["state"] = "normal" target_widget.delete("0", tk.END) target_widget.insert("0", value) target_widget["state"] = wstate else: super(EntryBaseBO, self)._set_property(target_widget, pname, value) # # Code generation methods # def _code_set_property(self, targetid, pname, value, code_bag): if pname == "text": fval = self.builder.code_translate_str(value) lines = [ f"_text_ = {fval}", f'{targetid}.delete("0", "end")', f'{targetid}.insert("0", _text_)', ] if "state" in self.wmeta.properties: state_value = self.wmeta.properties["state"] if state_value != "normal": lines.insert(1, f'{targetid}["state"] = "normal"') line = f'{targetid}["state"] = "{state_value}"' lines.append(line) code_bag[pname] = lines else: super(EntryBaseBO, self)._code_set_property( targetid, pname, value, code_bag ) class PanedWindowBO(BuilderObject): """Base class for tk.PanedWindow and ttk.Panedwindow builder objects""" class_ = None container = True properties = [] ro_properties = ("orient",) def realize(self, parent, extra_init_args: dict = None): master = parent.get_child_master() args = self._get_init_args(extra_init_args) if "orient" not in args: args["orient"] = "vertical" self.widget = self.class_(master, **args) return self.widget # # Base clases for some widget Helpers # class PanedWindowPaneBO(BuilderObject): class_ = None container = True properties = [] layout_required = False allow_bindings = False def realize(self, parent, extra_init_args: dict = None): self.widget = parent.get_child_master() return self.widget def configure(self): pass def layout(self): pass def add_child(self, bobject): self.widget.add(bobject.widget, **self.wmeta.properties) # # code generation functions # def code_realize(self, boparent, code_identifier=None): self._code_identifier = boparent.code_child_master() return tuple() def code_configure(self, targetid=None): return tuple() def code_layout(self, targetid=None, parentid=None): return tuple() def code_child_add(self, childid): config = [] masterid = self.code_child_master() for pname, val in self.wmeta.properties.items(): line = f'{pname}="{val}"' config.append(line) kw = "" lines = [] if config: kw = f""", {", ".join(config)}""" line = f"{masterid}.add({childid}{kw})" lines.append(line) return lines # # Base mixin to process Wm options for Tk and toplevel # class WmMixin: RESIZABLE = { "both": (True, True), "horizontally": (True, False), "vertically": (False, True), "none": (False, False), } def _process_property_value(self, pname, value): if pname in ("maxsize", "minsize"): if "|" in value: w, h = value.split("|") value = (int(w), int(h)) return value return super()._process_property_value(pname, value) def _set_property(self, target_widget, pname, value): method_props = ("geometry", "overrideredirect", "title") if pname in method_props: method = getattr(target_widget, pname) method(value) elif pname == "resizable" and value: target_widget.resizable(*self.RESIZABLE[value]) elif pname == "maxsize": maxsize = self._process_property_value(pname, value) if isinstance(maxsize, tuple): target_widget.maxsize(maxsize[0], maxsize[1]) elif pname == "minsize": minsize = self._process_property_value(pname, value) if isinstance(minsize, tuple): target_widget.minsize(minsize[0], minsize[1]) elif pname == "iconphoto": icon = self.builder.get_image(value) target_widget.iconphoto(True, icon) elif pname == "iconbitmap": icon = self.builder.get_iconbitmap(value) target_widget.iconbitmap(icon) else: super()._set_property(target_widget, pname, value) # # Code generation methods # def _code_set_property(self, targetid, pname, value, code_bag): if pname in ("geometry", "overrideredirect", "title"): line = f'{targetid}.{pname}("{value}")' code_bag[pname] = (line,) elif pname == "resizable": p1, p2 = self.RESIZABLE[value] line = "{0}.resizable({1}, {2})".format(targetid, p1, p2) code_bag[pname] = (line,) elif pname in ("maxsize", "minsize"): if "|" in value: w, h = value.split("|") line = "{0}.{1}({2}, {3})".format(targetid, pname, w, h) code_bag[pname] = (line,) elif pname == "iconbitmap": bitmap = self.builder.code_create_iconbitmap(value) line = f'{targetid}.iconbitmap("{bitmap}")' code_bag[pname] = (line,) elif pname == "iconphoto": image = self.builder.code_create_image(value) line = "{0}.iconphoto(True, {1})".format(targetid, image) code_bag[pname] = (line,) else: super()._code_set_property(targetid, pname, value, code_bag) # # Base mixin for OptionMenu # class OptionMenuBaseMixin: def realize(self, parent, extra_init_args: dict = None): init_args = self._get_init_args(extra_init_args) master = parent.get_child_master() # prepare init args variable = init_args.pop("variable", None) value = init_args.pop("value", None) if variable is None: varname = "{0}__var".format(self.wmeta.identifier) variable = self.builder.create_variable(varname) if value is not None: variable.set(value) values = init_args.pop("values", "") if values is not None: values = values.split(",") class _cb_proxy(object): def __init__(self): super(_cb_proxy, self).__init__() self.callback = None def __call__(self, arg1): if self.callback is not None: self.callback(arg1) cb_proxy = _cb_proxy() # tk.OptionMenu and ttk.OptionMenu differ on constructor # class so, move code to method than can be overriden self.widget = self._create_option_menu( master, variable, value, values, cb_proxy ) self.widget._cb_proxy = cb_proxy return self.widget def _create_option_menu(self, master, variable, value, values, command): return self.class_(master, variable, *values, command=command) def _connect_command(self, cmd_pname, callback): if cmd_pname == "command": self.widget._cb_proxy.callback = callback else: super()._connect_command(cmd_pname, callback) # # Code generation methods # def code_realize(self, boparent, code_identifier=None): import json if code_identifier is not None: self._code_identifier = code_identifier lines = [] master = boparent.code_child_master() init_args = self._code_get_init_args(self.code_identifier()) command_arg = None variable_arg = None # value_arg = None # command property pname = "command" if pname in self.wmeta.properties: value = json.loads(self.wmeta.properties[pname]) cmdname = value["value"] cmdtype = value["type"] args = ("option",) pvalue = self.builder.code_create_callback( self.code_identifier(), cmdname, cmdtype, args ) command_arg = f"{pvalue}" # Value property # # Already setup in _code_get_init_args if set by user. # But if not: var_value = init_args.get("value", None) # Variable property pname = "variable" varname = "__tkvar" if pname in init_args: varname = init_args[pname] else: str_val = "" if var_value is None else var_value line = f"__tkvar = tk.StringVar(value={str_val})" lines.append(line) variable_arg = varname # values property pname = "values" om_values = [] if pname in self.wmeta.properties: value = self.wmeta.properties["values"] om_values = value.split(",") line = f"__values = {om_values}" lines.append(line) s = self._code_create_optionmenu( self.code_identifier(), self._code_class_name(), master, var_value, variable_arg, command_arg, ) lines.append(s) return lines def _code_create_optionmenu( self, identifier, classname, master, value_arg, variable_arg, command_arg, ): return f"{identifier} = {classname}({master}, {variable_arg}, *__values, command={command_arg})" def code_configure(self, targetid=None): return [] def code_connect_commands(self): return [] pygubu-0.36.1/src/pygubu/component/datapool.py000066400000000000000000000030011474524032100213710ustar00rootroot00000000000000from abc import ABC, abstractmethod from typing import Optional, Any from urllib.parse import urlparse from functools import reduce class InvalidURIError(Exception): ... class IDataPool(ABC): """A generic pool of data.""" @abstractmethod def get_resource(self, uri: str) -> Any: ... def get_uri_path(self, uri: str) -> str: try: result = urlparse(uri) if all([result.scheme == "res", result.netloc]): return f"{result.netloc}{result.path}" except AttributeError: pass raise InvalidURIError(uri) class DictDataPool(IDataPool): def __init__(self, data: dict = None): self.data = {} if data is None else data def get_resource(self, uri: str) -> Any: path = self.get_uri_path(uri) return self.deep_get(self.data, path) def deep_get(self, dictionary, keys, default=None): return reduce( lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("/"), dictionary, ) if __name__ == "__main__": data = {"value_a": 185, "lists": {"dogs": [10, 20, 30]}} pool = DictDataPool(data) rlist = [ "res://value_a", "res://lists/dogs", "something", "res://lists/dogs//failed", ] for uri in rlist: try: res = pool.get_resource(uri) print(res) except InvalidURIError: print("Invalid uri error:", uri) pygubu-0.36.1/src/pygubu/component/plugin_engine.py000066400000000000000000000037511474524032100224250ustar00rootroot00000000000000from abc import ABC, ABCMeta, abstractmethod from typing import Optional class PluginRegistry(ABCMeta): plugins = [] def __init__(cls, name, bases, attrs): if name not in ( "IPluginBase", "BuilderLoaderPlugin", ): PluginRegistry.plugins.append(cls) class IDesignerPlugin(ABC): def get_preview_builder(self, builder_uid: str): """Return a BuilderObject subclass used to build a preview for the target builder_uid""" return None def get_toplevel_preview_for( self, builder_uid: str, widget_id: str, builder, top_master ): """Return a Toplevel with the target widget_id rendered inside.""" return None def configure_for_preview(self, builder_uid: str, widget): """Make widget just display with minimal functionality.""" return None def ensure_visibility_in_preview(self, builder, selected_uid: str): """Ensure visibility of selected_uid in preview. Usage example: Activate a tab of a Notebook if the selected widget is inside the notebook. """ return None class IPluginBase(ABC, metaclass=PluginRegistry): @abstractmethod def do_activate(self) -> bool: "Initialize plugin and return if it is operational or not." ... def get_designer_plugin(self) -> Optional[IDesignerPlugin]: """Return class instance that implements IDesignerPlugin""" return None class IBuilderLoaderPlugin(ABC): @abstractmethod def get_module_for(self, identifier: str) -> str: "Return module name for specified identifier." ... @abstractmethod def get_all_modules(self): "Return an iterable with module names of all builders." ... @abstractmethod def can_load(self, identifier: str) -> bool: "Return if this loader can manage specified identifier." ... class BuilderLoaderPlugin(IBuilderLoaderPlugin, IPluginBase): pass pygubu-0.36.1/src/pygubu/component/plugin_manager.py000066400000000000000000000045651474524032100225760ustar00rootroot00000000000000import importlib import pkgutil import pygubu.plugins from .plugin_engine import PluginRegistry, IBuilderLoaderPlugin, IDesignerPlugin def iter_namespace(ns_pkg): # Specifying the second argument (prefix) to iter_modules makes the # returned name an absolute name instead of a relative one. This allows # import_module to work without having to do additional modification to # the name. # # Source: https://packaging.python.org/guides/creating-and-discovering-plugins/ return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".") class PluginManager: plugins = [] designer_plugins = [] @classmethod def load_plugins(cls): for _, name, _ in iter_namespace(pygubu.plugins): importlib.import_module(name) for class_ in PluginRegistry.plugins: plugin = class_() if plugin.do_activate(): cls.plugins.append(plugin) @classmethod def builder_plugins(cls): return ( plugin for plugin in cls.plugins if isinstance(plugin, IBuilderLoaderPlugin) ) @classmethod def load_designer_plugins(cls): for plugin in cls.plugins: helper = plugin.get_designer_plugin() if helper: cls.designer_plugins.append(helper) @classmethod def get_preview_builder_for(cls, builder_uid: str): builder = None for plugin in cls.designer_plugins: builder = plugin.get_preview_builder(builder_uid) if builder is not None: break return builder @classmethod def get_toplevel_preview_for( cls, builder_uid, widget_id, builder, top_master ): top_preview = None for plugin in cls.designer_plugins: top_preview = plugin.get_toplevel_preview_for( builder_uid, widget_id, builder, top_master ) if top_preview is not None: break return top_preview @classmethod def configure_for_preview(cls, builder_uid: str, target): for plugin in cls.designer_plugins: plugin.configure_for_preview(builder_uid, target) @classmethod def ensure_visibility_in_preview(cls, builder, selected_uid: str): for plugin in cls.designer_plugins: plugin.ensure_visibility_in_preview(builder, selected_uid) pygubu-0.36.1/src/pygubu/component/uidefinition.py000066400000000000000000000500121474524032100222600ustar00rootroot00000000000000# encoding: UTF-8 import json import operator import sys import xml.etree.ElementTree as ET from .builderobject import CLASS_MAP, TRANSLATABLE_PROPERTIES from .widgetmeta import BindingMeta, GridRCLine, WidgetMeta # in-place prettyprint formatter def indent(elem, level=0): i = "\n" + level * " " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: indent(elem, level + 1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i try: JSONDecodeError = json.decoder.JSONDecodeError except AttributeError: # Python 2 JSONDecodeError = ValueError class UIDefinition(object): TK_COMMAND_PROPERTIES = ( "command", "validatecommand", "invalidcommand", "postcommand", "xscrollcommand", "yscrollcommand", "tearoffcommand", ) def __init__(self, wmetaclass=None, translator=None, author=None): super(UIDefinition, self).__init__() self.tree = None self.root = None self._latest_version = "1.4" self.version = self._latest_version self.author = "" if author is None else author self._ignore_properties = ( "command_id_arg", "idtocommand", "validatecommand_args", "invalidcommand_args", ) self.wmetaclass = wmetaclass if wmetaclass is None: self.wmetaclass = WidgetMeta self.translator = translator self.project_node = None self._project_settings = {} self._custom_widgets = [] self.uifile = None self.__create() @property def custom_widgets(self) -> list: return self._custom_widgets @custom_widgets.setter def custom_widgets(self, values: list): self._custom_widgets = values def _load_custom_widgets(self): xpath = "./customwidgets/customwidget" node_list: ET.Element = self.project_node.findall(xpath) if node_list is not None: for node in node_list: self._custom_widgets.append(node.attrib["path"]) def _save_custom_widgets(self): xpath = "./customwidgets" node: ET.Element = self.project_node.find(xpath) if node is None: node = ET.Element("customwidgets") self.project_node.append(node) node.clear() for value in self._custom_widgets: cnode = ET.Element("customwidget") cnode.attrib["path"] = str(value) node.append(cnode) @property def project_settings(self) -> dict: return self._project_settings @project_settings.setter def project_settings(self, bag: dict): self._project_settings = bag def _load_project_settings(self): xpath = "./settings/setting" node_list: ET.Element = self.project_node.findall(xpath) if node_list is not None: for node in node_list: self._project_settings[node.attrib["id"]] = node.text def _save_project_settings(self): xpath = ".//settings" node: ET.Element = self.project_node.find(xpath) if node is None: node = ET.Element("settings") self.project_node.append(node) node.clear() for key, value in self._project_settings.items(): child = ET.Element("setting") child.attrib["id"] = str(key) if value is not None: child.text = str(value) node.append(child) def _prop_from_xml(self, pnode, element): pname = pnode.get("name") pvalue = pnode.text if pname in self._ignore_properties: return (None, None) if self.translator is not None and pnode.get("translatable"): pvalue = self.translator(pvalue) # if node has a type property, send value as a json string jvalue = {} if pnode.get("type"): for attr, attrval in pnode.items(): jvalue[attr] = attrval jvalue["value"] = pnode.text pvalue = json.dumps(jvalue) # Process old ui versions if self.version < "1.1": if pname in self.TK_COMMAND_PROPERTIES: cmd = { "type": "command", "value": pnode.text, "cbtype": "simple", "args": "", } # get old format value xpath = "./property[@name='{0}']" for oldp in ("command_id_arg", "idtocommand"): dpath = xpath.format(oldp) node = element.find(dpath) if node is not None: nvalue = node.text.lower() if nvalue == "true": cmd["cbtype"] = "with_wid" if pname in ("validatecommand", "invalidcommand"): cmd["cbtype"] = "entry_validate" # get old format args pargs = "{0}_args".format(pname) dpath = xpath.format(pargs) node = element.find(dpath) if node is not None: cmd["args"] = node.text pvalue = json.dumps(cmd) return (pname, pvalue) def xmlnode_to_widget(self, element): elemid = element.get("id") meta = self.wmetaclass(element.get("class"), elemid) meta.is_named = True if element.get("named") is not None else False # properties properties = element.findall("./property") pdict = {} for p in properties: pname, pvalue = self._prop_from_xml(p, element) if pname is not None: pdict[pname] = pvalue meta.properties = pdict # Bindings bindings = [] bind_elements = element.findall("./bind") for e in bind_elements: binding = BindingMeta( e.get("sequence"), e.get("handler"), e.get("add") ) bindings.append(binding) meta.bindings = bindings # # Load widget layout configuration # if self.version >= "1.2": self.__load_layout_v1_2(element, meta) elif self.version >= "1.0": self.__load_layout_v1_0(element, meta) else: self.__load_layout_v_empty(element, meta) return meta def __load_layout_v1_2(self, element, meta): # new 1.2 version # layout properties # use grid layout by default manager = "grid" layout_elem = element.find("./layout") if layout_elem is not None: manager = layout_elem.get("manager", "grid") meta.manager = manager props = layout_elem.findall("./property") for p in props: meta.layout_properties[p.get("name")] = p.text # # Load widget as container layout configuration # clayout_node = element.find("./containerlayout") if clayout_node is not None: cmanager = clayout_node.get("manager", None) meta.container_manager = cmanager props = clayout_node.findall("./property") for p in props: ptype = p.get("type", None) pname = p.get("name") if ptype is None: # its a regular container property meta.container_properties[pname] = p.text else: # GRID RC PROPERTY pvalue = ( "" if p.text is None else p.text ) # fix malformed xml saved line = GridRCLine(ptype, p.get("id"), pname, pvalue) meta.gridrc_properties.append(line) def __load_layout_v1_0(self, element, meta): # use grid layout by default manager = "grid" layout_elem = element.find("./layout") if layout_elem is not None: manager = layout_elem.get("manager", "grid") meta.manager = manager props = layout_elem.findall("./property") for p in props: ptype = p.get("type", None) pname = p.get("name") if ptype is not None: # Its a grid rc, ignore it. Allready loaded in parent. continue if pname == "propagate": # don't load if is true. Its the default. # avoid generating an extra containerlayout node # when loading old file versions. value = p.text if value.lower() != "true": meta.container_properties[pname] = p.text else: meta.layout_properties[pname] = p.text # Try to setup: # - container_manager # - gridrc properties. gridrc properties are on the children. child_layouts = element.findall("./child/object/layout") rclines_loaded = set() if child_layouts is not None: clmanager = self._handle_child_layouts(meta, child_layouts, rclines_loaded) meta.container_manager = clmanager def _handle_child_layouts(self, meta, child_layouts, rclines_loaded): clmanager = "grid" for layout_node in child_layouts: manager = layout_node.get("manager", "grid") if manager != "place": clmanager = manager props = layout_node.findall("./property") if props is not None: for p in props: ptype = p.get("type", None) if ptype is not None: rcid = p.get("id") rcname = p.get("name") key = (ptype, rcid, rcname) if key not in rclines_loaded: rcvalue = p.text line = GridRCLine( ptype, rcid, rcname, rcvalue ) meta.gridrc_properties.append(line) rclines_loaded.add(key) return clmanager def __load_layout_v_empty(self, element, meta): """Load layout with ui version empty.""" elemid = element.get("id") parent_has_layout = False parent_layout = None xpath = ".//*[@id='{0}']/../..".format(elemid) parent = self.root.find(xpath) if parent is not None: parent_layout = parent.find("./layout") if parent_layout is not None: parent_has_layout = True # layout properties # use grid layout by default manager = "grid" layout_elem = element.find("./layout") if layout_elem is not None: meta.manager = manager props = layout_elem.findall("./property") for p in props: pname = p.get("name") if pname == "propagate": # don't load if is true. Its the default. # avoid generating an extra containerlayout node # when loading old file versions. value = p.text if value.lower() != "true": meta.container_properties[pname] = p.text else: meta.layout_properties[pname] = p.text # try to load old version grid rc info # Gridrc info was in the parent, or # in the widget itself if has no parent. if not parent_has_layout and layout_elem is not None: self.__load_old_gridrc_layout(layout_elem, meta) def __load_old_gridrc_layout(self, element, meta): """Load old grid rc information.""" rows = element.findall("./rows/row") for row in rows: rid = row.get("id") props = row.findall("./property") for p in props: rpname = p.get("name") rpvalue = p.text line = GridRCLine("row", rid, rpname, rpvalue) meta.gridrc_properties.append(line) columns = element.findall("./columns/column") for col in columns: cid = col.get("id") props = col.findall("./property") for p in props: cpname = p.get("name") cpvalue = p.text line = GridRCLine("col", cid, cpname, cpvalue) meta.gridrc_properties.append(line) def _prop_to_xml(self, pname, pvalue): pnode = ET.Element("property") pnode.set("name", pname) pnode.text = pvalue if pname in TRANSLATABLE_PROPERTIES: pnode.set("translatable", "yes") # if pvalue is a json do special try: dv = json.loads(pvalue) if isinstance(dv, dict): if "value" not in dv: raise Exception("Invalid json value for property") for k, attrval in dv.items(): if k != "value": pnode.set(k, str(attrval)) pnode.text = dv["value"] except (JSONDecodeError, TypeError): pass return pnode def widget_to_xmlnode(self, wmeta): """Returns xml representation of widget""" node = ET.Element("object") node.set("class", wmeta.classname) node.set("id", wmeta.identifier) if wmeta.is_named: node.set("named", str(wmeta.is_named)) pkeys = sorted(wmeta.properties.keys()) for pkey in pkeys: pnode = self._prop_to_xml(pkey, wmeta.properties[pkey]) node.append(pnode) # bindings: bindings = sorted(wmeta.bindings, key=operator.itemgetter(0, 1)) for b in bindings: bind = ET.Element("bind") for key in b._fields: bind.set(key, getattr(b, key)) node.append(bind) # layout: layout_required = CLASS_MAP[wmeta.classname].builder.layout_required if layout_required: # create layout node layout_node = ET.Element("layout") layout_node.set("manager", wmeta.manager) keys = sorted(wmeta.layout_properties) for prop in keys: pnode = ET.Element("property") pnode.set("name", prop) pnode.text = wmeta.layout_properties[prop] layout_node.append(pnode) # Append node layout node.append(layout_node) # Container layout properties container_layout_required = ( wmeta.container_properties or wmeta.gridrc_properties ) if container_layout_required: # create layout node clnode = ET.Element("containerlayout") clnode.set("manager", wmeta.container_manager) keys = sorted(wmeta.container_properties) for prop in keys: pnode = ET.Element("property") pnode.set("name", prop) pnode.text = wmeta.container_properties[prop] clnode.append(pnode) lines = sorted( wmeta.gridrc_properties, key=operator.itemgetter(0, 1, 2) ) for line in lines: p = ET.Element("property") p.set("type", line.rctype) p.set("id", line.rcid) p.set("name", line.pname) p.text = line.pvalue clnode.append(p) # Append container layout node node.append(clnode) return node def __create(self): # Version 1.0: start of schema versioning, implements multiple layout managers # Version 1.1: remove idtocommand and command_id_arg properties # Version 1.4: new project structure. self.root = root = ET.Element("interface") root.set("version", self._latest_version) if self.author: root.set("author", self.author) self.project_node = ET.Element("project") root.append(self.project_node) self.tree = ET.ElementTree(root) def _tree_load(self, tree, default_version=None): if default_version is None: default_version = "" self.tree = tree root: ET.Element = tree.getroot() version = root.get("version", default_version) author = root.get("author", "") self.root = root self.version = version self.author = author self.project_node = root.find("./project") if self.project_node is None: self.project_node = ET.Element("project") root.append(self.project_node) self._load_project_settings() self._load_custom_widgets() def load_file(self, file_or_filename): tree = ET.parse(file_or_filename) self._tree_load(tree) self.uifile = file_or_filename def load_from_string(self, source, version=None): tree = ET.ElementTree(ET.fromstring(source)) self._tree_load(tree, version) def get_xmlnode(self, identifier): xpath = ".//object[@id='{0}']".format(identifier) node = self.tree.find(xpath) return node def add_xmlnode(self, node, parent=None): if parent is None: self.root.append(node) else: parent.append(node) return node def add_xmlchild(self, parent, node): child = ET.Element("child") child.append(node) parent.append(child) def __str__(self): encoding = "unicode" return ET.tostring(self.root, encoding=encoding) def __repr__(self): return ''.format(self.__str__()) def save(self, file_or_filename): self._save_project_settings() self._save_custom_widgets() indent(self.root) self.tree.write( file_or_filename, xml_declaration=True, encoding="utf-8" ) def get_widget(self, identifier): wmeta = None xpath = ".//object[@id='{0}']".format(identifier) node = self.tree.find(xpath) if node is not None: wmeta = self.xmlnode_to_widget(node) return wmeta def widgets(self): xpath = "./object" children = self.root.findall(xpath) for child in children: wmeta = self.xmlnode_to_widget(child) yield wmeta def widget_children(self, identifier): xpath = ".//object[@id='{0}']".format(identifier) node = self.tree.find(xpath) if node is not None: xpath = "./child/object" children = node.findall(xpath) for child in children: wmeta = self.xmlnode_to_widget(child) yield wmeta def replace_widget(self, identifier, rootmeta): xpath = ".//object[@id='{0}']".format(identifier) parent = self.root.find(xpath + "/..") target = parent.find(xpath) if parent is not None: # found something parent.remove(target) replacement = self.widget_to_xmlnode(rootmeta) xpath = "./child/object" children = target.findall(xpath) for child in children: self.add_xmlchild(replacement, child) if parent.tag == "interface": parent.append(replacement) else: self.add_xmlchild(parent, replacement) if __name__ == "__main__": ui = UIDefinition() ui.author = "Module test" print(ui) xml = """ """ ui.load_from_string(xml) print(ui, ui.author, ui.version) print("Iterating file:") fname = "managers.ui" ui.load_file(fname) def print_widgets(w): print(w) for cw in ui.widget_children(w.identifier): print_widgets(cw) for w in ui.widgets(): print_widgets(w) pygubu-0.36.1/src/pygubu/component/widgetmeta.py000066400000000000000000000146571474524032100217430ustar00rootroot00000000000000# encoding: UTF-8 import xml.etree.ElementTree as ET from collections import namedtuple from .builderobject import CLASS_MAP __all__ = ["WidgetMeta", "BindingMeta"] BindingMeta = namedtuple("BindingMeta", ["sequence", "handler", "add"]) GridRCLine = namedtuple("GridRCLine", ["rctype", "rcid", "pname", "pvalue"]) class WidgetMeta: def __init__( self, cname, identifier, manager=None, properties_defaults=None, layout_defaults=None, ): super().__init__() self.classname = cname self._id = identifier self.properties = {} self.bindings = [] self._manager = manager if manager is not None else "grid" widget_description = CLASS_MAP.get(cname) if widget_description: self.layout_required = widget_description.builder.layout_required else: self.layout_required = True self.layout_properties = {} self._container_manager = self._manager self.container_properties = {} self.gridrc_properties = [] self.properties_defaults = ( properties_defaults if properties_defaults is not None else {} ) self.layout_defaults = ( layout_defaults if layout_defaults is not None else {} ) self._named = False # init defaults self.apply_properties_defaults() self.apply_layout_defaults() @property def manager(self): return self._manager @manager.setter def manager(self, value): self._manager = value @property def container_manager(self): return self._container_manager @container_manager.setter def container_manager(self, value): if value == "pack" and self.gridrc_properties: self.gridrc_properties.clear() self._container_manager = value @property def is_named(self) -> bool: return self._named @is_named.setter def is_named(self, value: bool): self._named = value @property def identifier(self) -> str: return self._id @identifier.setter def identifier(self, value: str): if not value: raise ValueError() self._id = value def apply_properties_defaults(self): for name, value in self.properties_defaults.items(): self.properties[name] = value def apply_layout_defaults(self): layout_defaults_by_manager = self.layout_defaults if self.manager in self.layout_defaults: layout_defaults_by_manager = self.layout_defaults[self.manager] for name, value in layout_defaults_by_manager.items(): self.layout_properties[name] = value def has_layout_defined(self): return len(self.layout_properties) > 0 def clear_layout(self): self.layout_properties = {} self.gridrc_properties = [] self.apply_layout_defaults() def get_gridrc_value(self, rctype, rcid, pname): value = None for line in self.gridrc_properties: if ( line.rctype == rctype and line.rcid == rcid and line.pname == pname ): value = line.pvalue break return value def set_gridrc_value(self, rctype, rcid, pname, value): index = None for i, r in enumerate(self.gridrc_properties): if r.rctype == rctype and r.rcid == rcid and r.pname == pname: index = i break if index is None: # We're setting the grid rc property on this widget for the first time. line = GridRCLine(rctype, rcid, pname, value) self.gridrc_properties.append(line) else: # We're updating an existing grid rc property value. # Prevent code such as weight='0', uniform='' from showing up # in the generated code - it would be redundant. if (pname in ("minsize", "pad", "weight") and value == "0") or ( pname == "uniform" and not value ): # We found a redundant value # '0' or a blank string if it's for the property: uniform # Remove the gridrc property self.gridrc_properties.pop(index) else: # Update the gridrc property line = GridRCLine(rctype, rcid, pname, value) self.gridrc_properties[index] = line def __repr__(self): tpl = """""" return tpl.format(self.classname, self.identifier) def copy_gridrc(self, from_, rctype): """Copy gridrc lines of type rctype from from_""" rc = [line for line in self.gridrc_properties if line.rctype != rctype] for line in from_.gridrc_properties: if line.rctype == rctype: rc.append(line) self.gridrc_properties = rc def copy_properties(self, wfrom): # Used on preview methods self.properties = wfrom.properties.copy() self.layout_properties = wfrom.layout_properties.copy() self.gridrc_properties = wfrom.gridrc_properties.copy() self.container_properties = wfrom.container_properties.copy() self.bindings = wfrom.bindings.copy() self._manager = wfrom._manager self._container_manager = wfrom._container_manager if __name__ == "__main__": w = WidgetMeta("mywidget", "w1") w.manager = "pack" w.properties = {"key1": "value1", "key2": "value2"} w.bindings = [ BindingMeta("<>", "callback_1", ""), BindingMeta("<>", "callback_2", "+"), ] w.layout_properties = {"prop1": "value1", "prop2": "value2"} print("To xml") node = w.to_xmlnode() node = ET.tostring(node) print(node, end="\n\n") print("From xml:") strdata = """ Label_1 yellow True 20 10 1 """ node = ET.fromstring(strdata) meta = WidgetMeta.from_xmlnode(node) print(meta) pygubu-0.36.1/src/pygubu/forms/000077500000000000000000000000001474524032100163465ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/forms/__init__.py000066400000000000000000000000001474524032100204450ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/forms/builder.py000066400000000000000000000021321474524032100203440ustar00rootroot00000000000000from .form import Form, FormField class FormBuilder: def __init__(self, *args, field_name: str, **kw): super().__init__(*args, **kw) self.field_name = field_name self.fields_scanned = False self.widgets = {} self.widgets_info = {} self.form_info = {} def iter_widgets(self, force_scan=False): if self.fields_scanned is False or force_scan: self.scan_widgets() for name, widget in self.widgets.items(): yield name, widget self.fields_scanned = True def scan_widgets(self): pass def get_form(self, config: dict): form = Form() for name, widget in self.iter_widgets(): params = {} if name in config: params = config[name] field = FormField(widget, **params) form.add(name, field) for name, info in self.widgets_info.items(): form.add_info_display(name, info) trans_key = "translator" if trans_key in config: form.translator = config[trans_key] return form pygubu-0.36.1/src/pygubu/forms/config.py000066400000000000000000000000521474524032100201620ustar00rootroot00000000000000ENTRY_MARK_INVALID_USING_VALIDATE = False pygubu-0.36.1/src/pygubu/forms/form.py000066400000000000000000000117461474524032100176740ustar00rootroot00000000000000from typing import Optional from collections import OrderedDict from .transformer import NoopTransfomer, TransformationError from .validation.base import ExecutionContext from .validation.constraint.form import Form as FormConstraint from .validation.constraint.form import FormValidator class FormError(Exception): ... class FormField: def __init__(self, widget, **kw): self.widget = widget self.required = kw.get("required", True) self.initial = kw.get("initial", None) self.constraints = kw.get("constraints", []) self.help = kw.get("help") # self.mapped = kw.get("mapped", True) noop_transformer = NoopTransfomer() self.model_transformer = kw.get("model_transformer", noop_transformer) self.view_transformer = kw.get("view_transformer", noop_transformer) @property def data(self): # NOTE: should return initial if field is disabled. if self.widget.wis_disabled(): return self.initial return self.view_transformer.reversetransform(self.widget.wget_value()) @data.setter def data(self, value): self.widget.wset_value( self.view_transformer.transform( self.model_transformer.transform(value) ) ) @property def field_name(self): return self.widget.field_name def clean(self, value): """ Validate the given value and return its "cleaned" value as an appropriate Python object. Raise TransormationError for any errors. """ value = self.model_transformer.reversetransform(value) return value class Form: def __init__(self): self.fields = OrderedDict() self.initialized = False self.fields_initial = {} self.info_display = {} self.cleaned_data = {} self.transformation_error = None self._errors = {} self.translator = None def add(self, name: str, field: FormField): self.fields[name] = field def add_info_display(self, name: str, info_display): self.info_display[name] = info_display def edit(self, data: dict, initial_bag: Optional[dict] = None): """Intializes form to edit data values.""" self.initialized = True self.fields_initial = {} if initial_bag is None: initial_bag = {} for name, field in self.fields.items(): field_initial = initial_bag.get(name, field.initial) field_initial = "" if field_initial is None else field_initial self.fields_initial[name] = field_initial field.data = data.get(name, field_initial) self.edit_field_init(field) def edit_field_init(self, field): if field.field_name in self.info_display: field_info = self.info_display[field.field_name] field_info.clear() if field.help: field_info.show_help(field.help) def is_valid(self): return self.is_bound and not self._errors def get_data(self): return self.cleaned_data def submit(self): if self.initialized: self.is_bound = True self.submit_init() self.do_submit() self.do_validation() else: raise FormError( "Form initialization error. Call form.edit() before submit" ) def submit_init(self): self.cleaned_data = {} self._errors = {} self.transformation_error = None for name, field in self.fields.items(): field.widget.wmark_invalid(False) for name in self.info_display: self.info_display[name].clear() def do_submit(self): for name, field in self.fields.items(): value = field.initial if field.widget.wis_disabled() else field.data try: value = field.clean(value) self.cleaned_data[name] = value except TransformationError as e: self.transformation_error = e def field_submit_pass(self, field): if field.help: field_info = self.info_display.get(field.field_name, None) if field_info is not None: field_info.show_help(field.help) def field_submit_error(self, field, error): field.widget.wmark_invalid(True) field_info = self.info_display.get(field.field_name, None) if field_info is not None: field_info.show_error(error) def do_validation(self): constraint = FormConstraint() validator = FormValidator() context = ExecutionContext(translator=self.translator) validator.initialize(context) validator.validate(self, constraint) for fname, field in self.fields.items(): if fname in self._errors: self.field_submit_error(field, self._errors[fname]) else: self.field_submit_pass(field) def add_error(self, field_name, error): self._errors[field_name] = error pygubu-0.36.1/src/pygubu/forms/nouiforms.py000066400000000000000000000016101474524032100207370ustar00rootroot00000000000000"""No UI forms""" from .forms import FormBase from .widgets import FieldWidget from .fields import ( CharField as CharFieldBase, IntegerField as IntegerFieldBase, ChoiceField as ChoiceFieldBase, ) class Form(FormBase): pass class NoUIWidget(FieldWidget): def __init__(self, *args, **kw): self._value = None super().__init__(*args, **kw) def wset_value(self, value): self._value = value def wget_value(self): return self._value def wmark_invalid(self, state: bool): print(f"Field {self._field.fname} invalid: {state}") def wis_disabled(self) -> bool: return False class CharField(CharFieldBase, NoUIWidget): pass class IntegerField(IntegerFieldBase, NoUIWidget): pass # class NoUIChoiceWidget(NoUIWidget, ChoiceWidget): # pass # class ChoiceField(ChoiceFieldBase, NoUIChoiceWidget): # pass pygubu-0.36.1/src/pygubu/forms/pygubuwidget.py000066400000000000000000000002531474524032100214370ustar00rootroot00000000000000from pygubu.widgets.combobox import Combobox from .ttkwidget import TtkVarBasedWidget class PygubuCombobox(TtkVarBasedWidget, Combobox): tkvar_pname = "keyvariable" pygubu-0.36.1/src/pygubu/forms/tkwidget.py000066400000000000000000000075371474524032100205560ustar00rootroot00000000000000import tkinter as tk from .builder import FormBuilder from .widget import FieldWidget, WidgetInfo EMPTY_VALUES = (None, "", [], (), {}) class TkFormBuilder(FormBuilder): def scan_widgets(self): self.search_widgets(self) def search_widgets(self, master=None): if master is None: master = self for widget in master.winfo_children(): if isinstance(widget, FieldWidget): self.widgets[widget.field_name] = widget elif isinstance(widget, WidgetInfo): self.widgets_info[widget.field_name] = widget else: self.search_widgets(widget) class WidgetViewManager: """A class to provide default behavior and allow user to inject custom code.""" def mark_invalid(self, widget, state: bool): ... def is_disabled(self, widget) -> bool: ... class TkWidgetViewManager(WidgetViewManager): """Default view manager for tk widgets To mark widget as invalid, uses option database to get color information: form_error_bg: background color form_error_fg: foreground color If color information is not found, does nothing. """ def mark_invalid(self, widget: tk.Widget, state: bool): class_ = widget.winfo_class() error_bg = widget.option_get("form_error_bg", class_) error_fg = widget.option_get("form_error_fg", class_) if not all((error_bg, error_fg)): # no configuration found, do nothing return if not hasattr(widget, "_oldbg"): widget._oldbg = None widget._oldfg = None if state: widget._oldbg = widget.cget("background") widget._oldfg = widget.cget("foreground") widget.configure(background=error_bg, foreground=error_fg) elif widget._oldbg is not None: widget.configure(background=widget._oldbg, foreground=widget._oldfg) def is_disabled(self, widget) -> bool: return "disabled" == widget.cget("state") class TkWidgetBase(FieldWidget): view_manager = TkWidgetViewManager() def wis_disabled(self) -> bool: return self.view_manager.is_disabled(self) def wmark_invalid(self, state: bool): self.view_manager.mark_invalid(self, state) class TkVarBasedWidget(TkWidgetBase): tkvar_pname = "textvariable" tkvar_class = tk.StringVar def __init__(self, *args, **kw): user_var = kw.get(self.tkvar_pname, None) if user_var is None: self._data_var = self.tkvar_class() kw[self.tkvar_pname] = self._data_var # # Some widgets can use diferent variable types such as checkbutton elif isinstance(user_var, tk.Variable): self._data_var = user_var self.tkvar_class = type(user_var) else: raise ValueError("Incorrect type for data variable") super().__init__(*args, **kw) def configure(self, cnf=None, **kw): if self.tkvar_pname in kw: self._data_var = kw[self.tkvar_pname] return super().configure(cnf, **kw) def wset_value(self, value): if value in EMPTY_VALUES: value = "" # FIXME avoid TclErrors when using typed variables Int, Double, Boolean tk.Variable.set(self._data_var, value) def wget_value(self): # FIXME avoid TclErrors when using typed variables Int, Double, Boolean return tk.Variable.get(self._data_var) class Text(TkWidgetBase, tk.Text): def wset_value(self, value): self.delete("0.0", tk.END) state = self.cget("state") if state == tk.DISABLED: self.configure(state=tk.NORMAL) self.insert("0.0", value) self.configure(state=tk.DISABLED) else: self.insert("0.0", value) def wget_value(self): return self.get("0.0", "end-1c") pygubu-0.36.1/src/pygubu/forms/transformer/000077500000000000000000000000001474524032100207105ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/forms/transformer/__init__.py000066400000000000000000000015671474524032100230320ustar00rootroot00000000000000from abc import ABC, abstractmethod """ Model transformers: transform(): "model data" => "norm data" reverseTransform(): "norm data" => "model data" View transformers: transform(): "norm data" => "view data" reverseTransform(): "view data" => "norm data Model data <| | | | transform | reverse transform | | |> | Norm data | <| | transform | |> | reverse transform View data | """ class DataTransformer(ABC): @abstractmethod def transform(self, value): ... @abstractmethod def reversetransform(self, value): ... class NoopTransfomer(DataTransformer): def transform(self, value): return value def reversetransform(self, value): return value class TransformationError(Exception): ... pygubu-0.36.1/src/pygubu/forms/transformer/tkboolean.py000066400000000000000000000005431474524032100232420ustar00rootroot00000000000000import tkinter as tk from . import DataTransformer class BoolTransformer(DataTransformer): def transform(self, value): tval = False try: tval = tk.getboolean(value) except (ValueError, tk.TclError): pass return tval def reversetransform(self, value): return self.transform(value) pygubu-0.36.1/src/pygubu/forms/ttkwidget.py000066400000000000000000000054661474524032100207410ustar00rootroot00000000000000import tkinter.ttk as ttk from pygubu.utils.widget import HideableMixin from .builder import FormBuilder from .widget import FieldWidget, WidgetInfo, FormInfo from .tkwidget import TkFormBuilder, TkVarBasedWidget, TkWidgetViewManager class FrameFormBuilder(TkFormBuilder, ttk.Frame): ... class TtkWidgetViewManager(TkWidgetViewManager): """Default view manager for ttk widgets To mark widget as invalid, combines the word "Error." with the widget style class. """ def mark_invalid(self, widget: ttk.Widget, state: bool): style: str = widget.cget("style") if style: # remove prefix if exists. prefix = "Error." if style.startswith(prefix): style = style[len(prefix) :] if state: style = f"{prefix}{style}" widget.configure(style=style) class TtkVarBasedWidget(TkVarBasedWidget): view_manager = TtkWidgetViewManager() class TtkWidgetInfoViewManager: def _set_style(self, widget, mode: str): style: str = widget.cget("style") if style: # remove prefix if exists prefix = "Error." if style.startswith(prefix): style = style[len(prefix) :] # remove prefix if exists prefix = "Help." if style.startswith(prefix): style = style[len(prefix) :] style = f"{mode}.{style}" widget.configure(style=style) def show_error(self, widget: ttk.Widget): self._set_style(widget, "Error") def show_help(self, widget: ttk.Widget): self._set_style(widget, "Help") class LabelWidgetInfo(WidgetInfo, HideableMixin, ttk.Label): """Used to display help and errors messages for the associated form field.""" view_manager = TtkWidgetInfoViewManager() def __init__(self, *args, **kw): super().__init__(*args, **kw) def show_error(self, error): self.hidden = False self.configure(text=error.message) self.view_manager.show_error(self) def show_help(self, message): self.hidden = False self.configure(text=message) self.view_manager.show_help(self) def clear(self): self.configure(text="") self.hidden = True class Entry(TtkVarBasedWidget, ttk.Entry): pass class Label(TtkVarBasedWidget, ttk.Label): """A Display only field using ttk.Label""" def __init__(self, *args, **kw): self.original_data = None super().__init__(*args, **kw) def wget_value(self): return self.original_data def wset_value(self, value): self.original_data = value super().wset_value(str(value)) class Checkbutton(TtkVarBasedWidget, ttk.Checkbutton): tkvar_pname = "variable" class Combobox(TtkVarBasedWidget, ttk.Combobox): pass pygubu-0.36.1/src/pygubu/forms/validation/000077500000000000000000000000001474524032100205005ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/forms/validation/__init__.py000066400000000000000000000000001474524032100225770ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/forms/validation/base.py000066400000000000000000000045101474524032100217640ustar00rootroot00000000000000from abc import ABC, abstractmethod from dataclasses import dataclass from typing import Any class Constraint(ABC): CLASS_CONSTRAINT = 1 PROPERTY_CONSTRAINT = 2 def __init__(self, *args, options=None, groups=None, payload=None): self.groups = groups self.payload = payload self.options = options @abstractmethod def validated_by(self): ... def get_targets(self): return self.PROPERTY_CONSTRAINT class ConstraintViolation: def __init__(self, message, constraint, *, code=None, params=None): self._message = message self.code = code self.params = params self.constraint = constraint def _msg_format(self): if self.params is None: return self._message else: return self._message % self.params @property def message(self): return self._msg_format() def messages(self): yield self.code, self.message class ConstraintViolationList(ConstraintViolation): def __init__(self, violations: list): self.violations = violations @property def message(self): msgs = [violation.message for violation in self.violations] return "\n".join(msgs) def messages(self): for violation in self.violations: yield violation.code, violation.message def __bool__(self): return bool(self.violations) class ExecutionContext: def __init__(self, translator=None): self._violations = [] self._translator = ( self._noop_trans if translator is None else translator ) def _noop_trans(self, s): """Default no-op translator""" return s @property def violations(self): return ConstraintViolationList(self._violations) def add_violation(self, /, **kw): msg_key = "message" if msg_key in kw: kw[msg_key] = str(self._translator(kw[msg_key])) violation = ConstraintViolation(**kw) self._violations.append(violation) def clear(self): self._violations = [] class ConstraintValidator(ABC): def __init__(self): self.context: ExecutionContext = None def initialize(self, context: ExecutionContext): self.context = context @abstractmethod def validate(self, value, constraint): ... pygubu-0.36.1/src/pygubu/forms/validation/constraint/000077500000000000000000000000001474524032100226645ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/forms/validation/constraint/__init__.py000066400000000000000000000000001474524032100247630ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/forms/validation/constraint/form.py000066400000000000000000000020411474524032100241760ustar00rootroot00000000000000from ..base import Constraint, ConstraintValidator from .notblank import NotBlank from pygubu.i18n import _ class Required(NotBlank): code = "required" message = _("This value is required.") class Form(Constraint): def get_targets(self): return self.CLASS_CONSTRAINT def validated_by(self): return FormValidator class FormValidator(ConstraintValidator): def validate(self, form, constraint): for name, field in form.fields.items(): field_constraints = [] if field.required: field_constraints.append(Required()) field_constraints.extend(field.constraints) for fc in field_constraints: validator_class = fc.validated_by() validator = validator_class() validator.initialize(self.context) validator.validate(field.data, fc) violations = self.context.violations if violations: form.add_error(name, violations) self.context.clear() pygubu-0.36.1/src/pygubu/forms/validation/constraint/istrue.py000066400000000000000000000017221474524032100245530ustar00rootroot00000000000000from ..base import Constraint, ConstraintValidator class IsTrue(Constraint): code = "not_true_error" message = "This value should be true." true_values = (True, "1", 1) def __init__( self, *args, message=None, allow_none=False, true_values=None, **kw ): super().__init__(*args, **kw) if message is not None: self.message = message if true_values is not None: self.true_values = true_values self.allow_none = allow_none def validated_by(self): return IsTrueValidator class IsTrueValidator(ConstraintValidator): def validate(self, value, constraint): if constraint.allow_none and value is None: return if value not in constraint.true_values: self.context.add_violation( message=constraint.message, constraint=constraint, code=constraint.code, params=None, ) pygubu-0.36.1/src/pygubu/forms/validation/constraint/notblank.py000066400000000000000000000017471474524032100250570ustar00rootroot00000000000000from ..base import Constraint, ConstraintValidator class NotBlank(Constraint): code = "is_blank_error" message = "This value should not be blank." empty_values = (None, "", [], (), {}) def __init__( self, *args, message=None, allow_none=False, empty_values=None, **kw ): super().__init__(*args, **kw) if message is not None: self.message = message if empty_values is not None: self.empty_values = empty_values self.allow_none = allow_none def validated_by(self): return NotBlankValidator class NotBlankValidator(ConstraintValidator): def validate(self, value, constraint): if constraint.allow_none and value is None: return if value in constraint.empty_values: self.context.add_violation( message=constraint.message, constraint=constraint, code=constraint.code, params=None, ) pygubu-0.36.1/src/pygubu/forms/widget.py000066400000000000000000000027731474524032100202140ustar00rootroot00000000000000class FieldWidget: def __init__(self, *args, field_name: str, **kw): super().__init__(*args, **kw) self.field_name = field_name def wset_value(self, value): # will set the value in the widget format raise NotImplementedError( f"Subclasses must define this method. {self.__class__}" f" for field class {self.__class__}" ) def wget_value(self): # Get value in the widget raise NotImplementedError( f"Subclasses must define this method. {self.__class__}" f" for field class {self.__class__}" ) def wmark_invalid(self, state: bool): # Visually mark the widget as invalid depending on state parameter. raise NotImplementedError( f"Subclasses must define this method. {self.__class__}" f" for field class {self.__class__}" ) def wis_disabled(self) -> bool: raise NotImplementedError( f"Subclasses must define this method. {self.__class__}" f" for field class {self.__class__}" ) class WidgetInfoBase: def __init__(self, *args, field_name: str, **kw): self.field_name = field_name super().__init__(*args, **kw) def show_error(self, error): raise NotImplementedError def show_help(self, message): raise NotImplementedError def clear(self): raise NotImplementedError class WidgetInfo(WidgetInfoBase): pass class FormInfo(WidgetInfoBase): pass pygubu-0.36.1/src/pygubu/i18n.py000066400000000000000000000016221474524032100163520ustar00rootroot00000000000000""" Keep pygubu as a simple library. Translations will be done in pygubu-designer repo. Here _ is used to mark strings in plugins files as translatable. Do not use this file in your application projects. """ import os def _real_translator(msg): return msg _ = _real_translator class LazyTranslator: def __init__(self, message): self._message = message def __str__(self): return _real_translator(self._message) def __repr__(self): return f"LazyTranslator('{self._message}')" def setup_translator(translator): global _real_translator _real_translator = translator def translator(message: str) -> str: return LazyTranslator(message) def noop_translator(message: str) -> str: return message if "PYGUBU_LAZY_TRANSLATOR" in os.environ: # Environment variable is set in pygubu-designer to activate string translations. _ = T = translator pygubu-0.36.1/src/pygubu/plugins/000077500000000000000000000000001474524032100167015ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/__init__.py000066400000000000000000000000001474524032100210000ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/awesometkinter/000077500000000000000000000000001474524032100217425ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/awesometkinter/__init__.py000066400000000000000000000036011474524032100240530ustar00rootroot00000000000000import importlib from pygubu.i18n import _ from pygubu.api.v1 import BuilderLoaderPlugin _designer_tab_label = _("AwesomeTkinter") _plugin_uid = "awesometkinter" _designer_tabs = ("ttk", _designer_tab_label) class AwesometkinterLoader(BuilderLoaderPlugin): module_map = { "pygubu.plugins.awesometkinter.frame": ( f"{_plugin_uid}.Frame3d", f"{_plugin_uid}.ScrollableFrame", ), "pygubu.plugins.awesometkinter.button": ( f"{_plugin_uid}.Button3d", f"{_plugin_uid}.Radiobutton", f"{_plugin_uid}.Checkbutton", ), "pygubu.plugins.awesometkinter.label": ( f"{_plugin_uid}.AutoWrappingLabel", f"{_plugin_uid}.AutofitLabel", ), "pygubu.plugins.awesometkinter.progressbar": ( f"{_plugin_uid}.RadialProgressbar", f"{_plugin_uid}.RadialProgressbar3d", f"{_plugin_uid}.Segmentbar", ), "pygubu.plugins.awesometkinter.scrollbar": ( f"{_plugin_uid}.SimpleScrollbar", ), "pygubu.plugins.awesometkinter.text": (f"{_plugin_uid}.ScrolledText",), } def do_activate(self) -> bool: spec = importlib.util.find_spec("awesometkinter") return spec is not None def get_module_for(self, identifier: str) -> str: for module, identifiers in self.module_map.items(): if identifier in identifiers: return module return None def get_all_modules(self): return [m for m in self.module_map.keys()] def can_load(self, identifier: str) -> bool: return identifier.startswith("awesometkinter.") def get_designer_plugin(self): """Load class that implements IDesignerPlugin""" # Just load the module for properties definitions. from .designer.designerplugin import AwesometkinterPlugin return None pygubu-0.36.1/src/pygubu/plugins/awesometkinter/button.py000066400000000000000000000035221474524032100236310ustar00rootroot00000000000000import awesometkinter as atk from pygubu.api.v1 import register_widget from pygubu.plugins.tk.tkstdwidgets import TKCheckbutton from pygubu.plugins.ttk.ttkstdwidgets import TTKButton, TTKRadiobutton from ..awesometkinter import _plugin_uid, _designer_tabs class Button3dBO(TTKButton): OPTIONS_STANDARD = tuple(set(TTKButton.OPTIONS_STANDARD) - set(("style",))) OPTIONS_CUSTOM = ("bg", "fg") properties = OPTIONS_STANDARD + TTKButton.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ro_properties = TTKButton.ro_properties + ("bg", "fg") class_ = atk.Button3d _builder_uid = f"{_plugin_uid}.Button3d" register_widget(_builder_uid, Button3dBO, "Button3d", _designer_tabs, group=2) class RadiobuttonBO(TTKRadiobutton): OPTIONS_STANDARD = tuple( set(TTKRadiobutton.OPTIONS_STANDARD) - set(("style",)) ) OPTIONS_CUSTOM = ( "bg", "fg", "ind_bg", "ind_mark_color", "ind_outline_color", "font", ) properties = ( OPTIONS_STANDARD + TTKRadiobutton.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = ( TTKRadiobutton.ro_properties + OPTIONS_CUSTOM + ("text", "value") ) class_ = atk.Radiobutton _builder_uid = f"{_plugin_uid}.Radiobutton" register_widget( _builder_uid, RadiobuttonBO, "Radiobutton", _designer_tabs, group=2, ) class CheckbuttonBO(TKCheckbutton): OPTIONS_CUSTOM = ( "box_color", "check_mark_color", "text_color", ) properties = ( TKCheckbutton.OPTIONS_STANDARD + TKCheckbutton.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TKCheckbutton.ro_properties + OPTIONS_CUSTOM class_ = atk.Checkbutton _builder_uid = f"{_plugin_uid}.Checkbutton" register_widget( _builder_uid, CheckbuttonBO, "Checkbutton", _designer_tabs, group=2, ) pygubu-0.36.1/src/pygubu/plugins/awesometkinter/designer/000077500000000000000000000000001474524032100235425ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/awesometkinter/designer/__init__.py000066400000000000000000000000001474524032100256410ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/awesometkinter/designer/designerplugin.py000066400000000000000000000002471474524032100271360ustar00rootroot00000000000000from pygubu.api.v1 import IPluginBase, IDesignerPlugin import pygubu.plugins.awesometkinter.designer.properties class AwesometkinterPlugin(IDesignerPlugin): ... pygubu-0.36.1/src/pygubu/plugins/awesometkinter/designer/properties.py000066400000000000000000000167241474524032100263220ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import register_custom_property from pygubu.plugins.awesometkinter import _plugin_uid _builder_all = f"{_plugin_uid}.*" _button3d = f"{_plugin_uid}.Button3d" _radiobutton = f"{_plugin_uid}.Radiobutton" _checkbutton = f"{_plugin_uid}.Checkbutton" _frame3d = f"{_plugin_uid}.Frame3d" _scrollableframe = f"{_plugin_uid}.ScrollableFrame" _autowraplabel = f"{_plugin_uid}.AutoWrappingLabel" _autofitlabel = f"{_plugin_uid}.AutofitLabel" _radialpbar = f"{_plugin_uid}.RadialProgressbar" _radialpbar3d = f"{_plugin_uid}.RadialProgressbar3d" _segmentbar = f"{_plugin_uid}.Segmentbar" _simplesbar = f"{_plugin_uid}.SimpleScrollbar" _scrolledtext = f"{_plugin_uid}.ScrolledText" plugin_properties = { "autoscroll": [ dict( buid=_scrollableframe, editor="choice", values=("", "false", "true"), state="readonly", help=_("auto scroll to bottom if new items added to frame"), ), dict( buid=_scrolledtext, editor="choice", values=("", "false", "true"), state="readonly", help=_("automatic vertical scrolling"), ), ], "base_img": dict( buid=_radialpbar, editor="imageentry", help=_("base image for progressbar"), ), "check_mark_color": dict( buid=_checkbutton, editor="colorentry", ), "bd": dict( buid=_scrolledtext, editor="naturalnumber", help=_("border width") ), "bg": [ dict(buid=_button3d, editor="colorentry", help=_("button color")), dict( buid=_radiobutton, editor="colorentry", help=_('background color "should match parent bg"'), ), dict(buid=_frame3d, editor="colorentry", help=_("color of frame")), dict( buid=_scrollableframe, editor="colorentry", help=_("background color"), ), dict( buid=_radialpbar, editor="colorentry", help=_("color of base ring") ), dict(buid=[_segmentbar, _simplesbar], editor="colorentry"), dict( buid=_scrolledtext, editor="colorentry", help=_("background color") ), ], "box_color": dict( buid=_checkbutton, editor="colorentry", ), "fg": [ dict( buid=[_button3d, _radiobutton], editor="colorentry", help=_("text color"), ), dict( buid=_radialpbar, editor="colorentry", help=_("color of indicator ring"), ), dict(buid=_radialpbar3d, editor="colorentry"), dict(buid=_segmentbar, editor="colorentry"), dict( buid=_scrolledtext, editor="colorentry", help=_("foreground color") ), ], "font": [ dict(buid=_radiobutton, editor="fontentry"), dict( buid=_radialpbar, editor="fontentry", help=_("tkinter font for percentage text"), ), ], "font_size_ratio": dict( buid=_radialpbar, editor="spinbox", from_=0.1, to=1, increment=0.1, help=_( "font size to progressbar width ratio," + "e.g. for a progressbar size 100 pixels, a 0.1 ratio means font size 10" ), ), "height": [ dict(buid=_segmentbar, editor="dimensionentry"), dict( buid=[_frame3d, _scrollableframe], editor="dimensionentry", default_value=200, ), ], "hbar_width": [ dict( buid=_scrollableframe, editor="dimensionentry", help=_("horizontal scrollbar width"), ), dict( buid=_scrolledtext, editor="naturalnumber", help=_("horizontal scrollbar width"), ), ], "hscroll": [ dict( buid=_scrollableframe, editor="choice", values=("", "false", "true"), state="readonly", help=_("use horizontal scrollbar"), ), dict( buid=_scrolledtext, editor="choice", values=("", "false", "true"), state="readonly", help=_("include horizontal scrollbar"), ), ], "ind_bg": dict( buid=_radiobutton, editor="colorentry", help=_('indicator ring background "fill color"'), ), "ind_mark_color": dict( buid=_radiobutton, editor="colorentry", help=_("check mark color") ), "ind_outline_color": dict( buid=_radiobutton, editor="colorentry", help=_("indicator outline / ring color"), ), "indicator_img": dict( buid=_radialpbar, editor="imageentry", help=_("indicator image for progressbar"), ), "max_chars": dict( buid=_scrolledtext, editor="naturalnumber", help=_( "maximum characters allowed in Text widget, " + "text will be truncated from the beginning to match the max chars" ), ), "parent_bg": dict( buid=_radialpbar, editor="colorentry", help=_("color of parent container"), ), "refresh_time": dict( buid=_autofitlabel, editor="naturalnumber", help=_("milliseconds"), ), "sbar_bg": dict( buid=[_scrollableframe, _scrolledtext], editor="colorentry", help=_("color of scrollbars' trough, default to frame's background"), ), "sbar_fg": dict( buid=[_scrollableframe, _scrolledtext], editor="colorentry", help=_("color of scrollbars' slider"), ), "slider_color": dict(buid=_simplesbar, editor="colorentry"), "text_color": dict( buid=_checkbutton, editor="colorentry", ), "text_bg": dict(buid=_radialpbar3d, editor="colorentry"), "text_fg": [ dict( buid=_radialpbar, editor="colorentry", help=_("percentage text color"), ), dict(buid=_radialpbar3d, editor="colorentry"), ], "vscroll": [ dict( buid=_scrollableframe, editor="choice", values=("", "false", "true"), state="readonly", help=_("use vertical scrollbar"), ), dict( buid=_scrolledtext, editor="choice", values=("", "false", "true"), state="readonly", help=_("include vertical scrollbar"), ), ], "vbar_width": [ dict( buid=_scrollableframe, editor="dimensionentry", help=_("vertical scrollbar width"), ), dict( buid=_scrolledtext, editor="naturalnumber", help=_("vertical scrollbar width"), ), ], "width": [ dict(buid=[_segmentbar, _simplesbar], editor="naturalnumber"), dict( buid=[_frame3d, _scrollableframe], editor="dimensionentry", default_value=200, ), ], } for prop in plugin_properties: definitions = plugin_properties[prop] if isinstance(definitions, dict): definitions = [definitions] for definition in definitions: builders = definition.pop("buid", _builder_all) if isinstance(builders, str): builders = [builders] editor = definition.pop("editor", "entry") for builder_uid in builders: register_custom_property(builder_uid, prop, editor, **definition) pygubu-0.36.1/src/pygubu/plugins/awesometkinter/frame.py000066400000000000000000000033511474524032100234100ustar00rootroot00000000000000""" Documentation, License etc. @package pygubu.plugins.awesometkinter """ import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.tk.tkstdwidgets import TKFrame from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame import awesometkinter as atk from ..awesometkinter import _plugin_uid, _designer_tabs class Frame3dBO(TTKFrame): OPTIONS_STANDARD = tuple(set(TTKFrame.OPTIONS_STANDARD) - set(("style",))) OPTIONS_CUSTOM = ("bg",) properties = OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ro_properties = TTKFrame.ro_properties + ("bg",) class_ = atk.Frame3d _builder_uid = f"{_plugin_uid}.Frame3d" register_widget(_builder_uid, Frame3dBO, "Frame3d", _designer_tabs, group=0) class ScrollableFrameBO(TKFrame): OPTIONS_STANDARD = tuple() OPTIONS_SPECIFIC = tuple() OPTIONS_CUSTOM = ( "vscroll", "hscroll", "autoscroll", "bg", "sbar_fg", "sbar_bg", "vbar_width", "hbar_width", ) ro_properties = OPTIONS_CUSTOM class_ = atk.ScrollableFrame def _process_property_value(self, pname, value): if pname in ("vscroll", "hscroll", "autoscroll"): return tk.getboolean(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value): if pname in ("vscroll", "hscroll", "autoscroll"): return tk.getboolean(value) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.ScrollableFrame" register_widget( _builder_uid, ScrollableFrameBO, "ScrollableFrame", _designer_tabs, group=0, ) pygubu-0.36.1/src/pygubu/plugins/awesometkinter/label.py000066400000000000000000000015621474524032100233770ustar00rootroot00000000000000import awesometkinter as atk from pygubu.i18n import _ from pygubu.api.v1 import register_widget from pygubu.plugins.tk.tkstdwidgets import TKLabel from ..awesometkinter import _plugin_uid, _designer_tabs class AutoWrappingLabelBO(TKLabel): class_ = atk.label.AutoWrappingLabel _builder_uid = f"{_plugin_uid}.AutoWrappingLabel" register_widget( _builder_uid, AutoWrappingLabelBO, "AutoWrappingLabel", _designer_tabs, group=1, ) class AutofitLabelBO(TKLabel): OPTIONS_CUSTOM = ("refresh_time",) properties = ( TKLabel.OPTIONS_STANDARD + TKLabel.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TKLabel.ro_properties + OPTIONS_CUSTOM class_ = atk.label.AutofitLabel _builder_uid = f"{_plugin_uid}.AutofitLabel" register_widget( _builder_uid, AutofitLabelBO, "AutofitLabel", _designer_tabs, group=1, ) pygubu-0.36.1/src/pygubu/plugins/awesometkinter/progressbar.py000066400000000000000000000032551474524032100246520ustar00rootroot00000000000000import awesometkinter as atk from pygubu.api.v1 import ( BuilderObject, register_widget, ) from ..awesometkinter import _plugin_uid, _designer_tabs class RadialProgressbarBO(BuilderObject): OPTIONS_CUSTOM = ( "bg", "fg", "text_fg", "font", "font_size_ratio", "base_img", "indicator_img", "parent_bg", ) ro_properties = OPTIONS_CUSTOM class_ = atk.RadialProgressbar def _process_property_value(self, pname, value): if pname == "font_size_ratio": value_ = 0.1 try: value_ = float(value) except ValueError: pass return value_ return super()._process_property_value(pname, value) _builder_uid = f"{_plugin_uid}.RadialProgressbar" register_widget( _builder_uid, RadialProgressbarBO, "RadialProgressbar", _designer_tabs, ) class RadialProgressbar3dBO(BuilderObject): OPTIONS_CUSTOM = ("fg", "text_fg", "text_bg") ro_properties = OPTIONS_CUSTOM class_ = atk.RadialProgressbar3d _builder_uid = f"{_plugin_uid}.RadialProgressbar3d" register_widget( _builder_uid, RadialProgressbar3dBO, "RadialProgressbar3d", _designer_tabs, ) class SegmentbarBO(BuilderObject): OPTIONS_CUSTOM = ("bg", "fg", "width", "height") ro_properties = OPTIONS_CUSTOM class_ = atk.Segmentbar def _process_property_value(self, pname, value): if pname in ("width",): return int(value) return super()._process_property_value(pname, value) _builder_uid = f"{_plugin_uid}.Segmentbar" register_widget(_builder_uid, SegmentbarBO, "Segmentbar", _designer_tabs) pygubu-0.36.1/src/pygubu/plugins/awesometkinter/scrollbar.py000066400000000000000000000020341474524032100242760ustar00rootroot00000000000000from pygubu.api.v1 import register_widget from pygubu.plugins.ttk.ttkstdwidgets import TTKScrollbar import awesometkinter as atk from ..awesometkinter import _plugin_uid, _designer_tabs class SimpleScrollbarBO(TTKScrollbar): OPTIONS_STANDARD = tuple( set(TTKScrollbar.OPTIONS_STANDARD) - set(("style",)) ) OPTIONS_CUSTOM = ("bg", "slider_color", "width") properties = ( OPTIONS_STANDARD + TTKScrollbar.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKScrollbar.ro_properties + OPTIONS_CUSTOM + ("orient",) class_ = atk.scrollbar.SimpleScrollbar def _process_property_value(self, pname, value): if pname in ("width",): value_ = 5 try: value_ = int(value) except ValueError: pass return value_ return super()._process_property_value(pname, value) _builder_uid = f"{_plugin_uid}.SimpleScrollbar" register_widget( _builder_uid, SimpleScrollbarBO, "SimpleScrollbar", _designer_tabs, ) pygubu-0.36.1/src/pygubu/plugins/awesometkinter/text.py000066400000000000000000000027541474524032100233100ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.tk.tkstdwidgets import TKText import awesometkinter as atk from ..awesometkinter import _plugin_uid, _designer_tabs class ScrolledTextBO(TKText): OPTIONS_CUSTOM = TKText.OPTIONS_CUSTOM + ( "bg", "fg", "bd", "vscroll", "hscroll", "autoscroll", "max_chars", "sbar_fg", "sbar_bg", "vbar_width", "hbar_width", ) properties = ( TKText.OPTIONS_STANDARD + TKText.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = tuple( (set(TKText.ro_properties) | set(OPTIONS_CUSTOM) | set(("wrap",))) - set(("text",)) ) class_ = atk.text.ScrolledText def _process_property_value(self, pname, value): if pname in ("vscroll", "hscroll", "autoscroll"): return tk.getboolean(value) if pname in ("max_chars", "vbar_width", "hbar_width"): return int(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value): if pname in ("vscroll", "hscroll", "autoscroll"): return tk.getboolean(value) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.ScrolledText" register_widget( _builder_uid, ScrolledTextBO, "ScrolledText", _designer_tabs, group=1, ) pygubu-0.36.1/src/pygubu/plugins/customtkinter/000077500000000000000000000000001474524032100216145ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/customtkinter/__init__.py000066400000000000000000000035021474524032100237250ustar00rootroot00000000000000import importlib from pygubu.i18n import _ from pygubu.api.v1 import IPluginBase, IBuilderLoaderPlugin, IDesignerPlugin _designer_tab_label = _("CustomTkinter") _plugin_uid = "customtkinter" class CTkBuilderLoader(IBuilderLoaderPlugin, IPluginBase): module_map = { "pygubu.plugins.customtkinter.windows": ( f"{_plugin_uid}.CTkToplevel", f"{_plugin_uid}.CTk", ), "pygubu.plugins.customtkinter.widgets": ( f"{_plugin_uid}.CTkFrame", f"{_plugin_uid}.CTkLabel", f"{_plugin_uid}.CTkProgressBar", f"{_plugin_uid}.CTkButton", f"{_plugin_uid}.CTkSlider", f"{_plugin_uid}.CTkEntry", f"{_plugin_uid}.CTkOptionMenu", f"{_plugin_uid}.CTkComboBox", f"{_plugin_uid}.CTkCheckBox", f"{_plugin_uid}.CTkRadioButton", f"{_plugin_uid}.CTkSwitch", f"{_plugin_uid}.CTkTextbox", f"{_plugin_uid}.CTkCanvas", f"{_plugin_uid}.CTkScrollbar", f"{_plugin_uid}.CTkTabview", f"{_plugin_uid}.CTkScrollableFrame", ), } def do_activate(self) -> bool: spec = importlib.util.find_spec("customtkinter") return spec is not None def get_module_for(self, identifier: str) -> str: for module, identifiers in self.module_map.items(): if identifier in identifiers: return module return None def get_all_modules(self): return [m for m in self.module_map.keys()] def can_load(self, identifier: str) -> bool: return identifier.startswith("customtkinter.") def get_designer_plugin(self): """Load class that implements IDesignerPlugin""" from .designer.designerplugin import CTkDesignerPlugin return CTkDesignerPlugin() pygubu-0.36.1/src/pygubu/plugins/customtkinter/ctkbase.py000066400000000000000000000225751474524032100236150ustar00rootroot00000000000000import os import tkinter as tk import customtkinter from pathlib import Path from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from pygubu.i18n import _ from pygubu.plugins.tk.tkstdwidgets import TKFrame as TKFrameBO from pygubu.utils.font import tkfontstr_to_dict from pygubu.stockimage import StockImage from ..customtkinter import _designer_tab_label, _plugin_uid from customtkinter.windows.widgets.core_widget_classes import CTkBaseClass from customtkinter import CTkFont, CTkImage from PIL import Image, ImageTk # Groups for ordering buttons in designer palette. GCONTAINER = 0 GDISPLAY = 1 GINPUT = 2 _use_fixed_image_class = False if os.getenv("PYGUBU_DESIGNER_RUNNING"): _use_fixed_image_class = True class CTKImageFix(CTkImage): "Fix loader for pygubu designer toplevel preview" def __init__( self, light_image: "Image.Image" = None, dark_image: "Image.Image" = None, size=(20, 20), tk_master=None, ): super().__init__(light_image, dark_image, size) self._tk_master = tk_master def _get_scaled_light_photo_image( self, scaled_size ) -> "ImageTk.PhotoImage": if scaled_size in self._scaled_light_photo_images: return self._scaled_light_photo_images[scaled_size] else: self._scaled_light_photo_images[ scaled_size ] = ImageTk.PhotoImage( self._light_image.resize(scaled_size), master=self._tk_master, ) return self._scaled_light_photo_images[scaled_size] def _get_scaled_dark_photo_image( self, scaled_size ) -> "ImageTk.PhotoImage": if scaled_size in self._scaled_dark_photo_images: return self._scaled_dark_photo_images[scaled_size] else: self._scaled_dark_photo_images[ scaled_size ] = ImageTk.PhotoImage( self._dark_image.resize(scaled_size), master=self._tk_master ) return self._scaled_dark_photo_images[scaled_size] def ctk_image_loader(source_type, source, tk_master): if _use_fixed_image_class: return CTKImageFix(Image.open(source), tk_master=tk_master) return CTkImage(Image.open(source)) int_properties = { "border_width": {}, "border_spacing": {}, "button_corner_radius": {}, "button_length": {}, "corner_radius": {}, "height": {}, "from_": {}, "to": {}, "number_of_steps": {}, "width": {}, "checkbox_width": {}, "checkbox_height": {}, "radiobutton_width": {}, "radiobutton_height": {}, "border_width_unchecked": {}, "border_width_checked": {}, "switch_width": {}, "switch_height": {}, "minimum_pixel_length": {}, "determinate_speed": {"editor": "realnumber"}, "indeterminate_speed": {"editor": "realnumber"}, # "": {}, } bool_properties = { "dynamic_resizing": {}, "hover": {}, "activate_scrollbars": {}, "round_width_to_even_numbers": {}, "round_height_to_even_numbers": {}, # "": {}, } font_properties = { "font": {}, "dropdown_text_font": {}, "dropdown_font": {}, "label_font": {}, # "": {}, } class CTkBaseMixin: uses_ctk_font = False uses_ctk_image = False def _can_set_tcl_widget_name(self) -> bool: """Returns True if widget accepts the tcl "name" init argument.""" return False def _process_property_value(self, pname, value): if pname in bool_properties: return tk.getboolean(value) if pname in int_properties: return float(value) if pname in font_properties: fdesc = tkfontstr_to_dict(value) _modifiers = ( [] if fdesc["modifiers"] is None else fdesc["modifiers"] ) family = fdesc["family"] size = None if fdesc["size"] is None else int(fdesc["size"]) weight = "bold" if "bold" in _modifiers else None slant = "italic" if "italic" in _modifiers else "roman" underline = True if "underline" in _modifiers else False overstrike = True if "overstrike" in _modifiers else False _font = CTkFont(family, size, weight, slant, underline, overstrike) return _font if pname == "image": name = Path(value).name if not StockImage.is_registered(name): StockImage.find_and_register(name) img = StockImage.get(value, ctk_image_loader) return img return super()._process_property_value(pname, value) # # Code generation methods # def _code_process_property_value(self, targetid, pname, value: str): if ( pname in int_properties or pname in bool_properties or pname in font_properties ): return super()._process_property_value(pname, value) return super()._code_process_property_value(targetid, pname, value) def _code_set_property(self, targetid, pname, value, code_bag): if pname in font_properties: CTkBaseMixin.uses_ctk_font = True fdesc = tkfontstr_to_dict(value) _modifiers = ( [] if fdesc["modifiers"] is None else fdesc["modifiers"] ) family = f'"{fdesc["family"]}"' size = None if fdesc["size"] is None else int(fdesc["size"]) weight = '"bold"' if "bold" in _modifiers else None slant = '"italic"' if "italic" in _modifiers else '"roman"' underline = True if "underline" in _modifiers else False overstrike = True if "overstrike" in _modifiers else False fvalue = f"CTkFont({family}, {size}, {weight}, {slant}, {underline}, {overstrike})" code_bag[pname] = fvalue elif pname == "image": CTkBaseMixin.uses_ctk_image = True lines = [ f'_img = Image.open("{value}")', f"{targetid}.configure(image=CTkImage(_img))", ] code_bag[pname] = lines else: super()._code_set_property(targetid, pname, value, code_bag) def code_imports(self): imports = [ ("customtkinter", self.class_.__name__), ] if CTkBaseMixin.uses_ctk_font: imports.append(("customtkinter", "CTkFont")) if CTkBaseMixin.uses_ctk_image: imports.append(("customtkinter", "CTkImage")) imports.append(("PIL", "Image")) return imports # I will register here all common properties used by customtkinter widgets # so I don't have to repeat for each one (notice the .* at end of _builder_uid): _builder_uid = f"{_plugin_uid}.*" color_properties = { "bg_color": { "help": _("Color behind the widget if it has rounded corners.") }, "border_color": {}, "button_color": {}, "button_hover_color": {}, "checkmark_color": {}, "dropdown_color": {}, "dropdown_hover_color": {}, "dropdown_text_color": {}, "dropdown_fg_color": {}, "fg_color": {"help": _("Main color of the widget.")}, "hover_color": {}, "placeholder_text_color": {}, "progress_color": {}, "segmented_button_fg_color": {}, "segmented_button_selected_color": {}, "segmented_button_selected_hover_color": {}, "segmented_button_unselected_color": {}, "segmented_button_unselected_hover_color": {}, "text_color": {}, "text_color_disabled": {}, "scrollbar_button_color": {}, "scrollbar_button_hover_color": {}, "scrollbar_fg_color": {}, "background_corner_colors": {}, "label_fg_color": {}, "label_text_color": {}, # "": {}, } for color in color_properties: register_custom_property( _builder_uid, color, "colorentry", **color_properties[color] ) for prop in int_properties: editor = int_properties[prop].pop("editor", "naturalnumber") register_custom_property(_builder_uid, prop, editor, **int_properties[prop]) for prop in bool_properties: register_custom_property( _builder_uid, prop, "choice", values=("", "True", "False"), state="readonly", **bool_properties[prop], ) for prop in font_properties: register_custom_property(_builder_uid, prop, "fontentry") register_custom_property(_builder_uid, "placeholder_text", "entry") register_custom_property(_builder_uid, "command", "simplecommandentry") register_custom_property(_builder_uid, "text", "text") register_custom_property(_builder_uid, "values", "entry") register_custom_property(_builder_uid, "variable", "tkvarentry") register_custom_property(_builder_uid, "label_text", "text") register_custom_property( _builder_uid, "orientation", "choice", values=("vertical", "horizontal"), state="readonly", default_value="horizontal", ) register_custom_property( _builder_uid, "state", "choice", values=("", "normal", "active", "disabled"), state="readonly", ) register_custom_property( _builder_uid, "appearance_mode", "choice", values=("", "dark", "light"), state="readonly", ) register_custom_property( _builder_uid, "color_theme", "choice", values=("", "blue", "green", "dark-blue", "sweetkind"), state="readonly", help=_("Default color theme."), ) pygubu-0.36.1/src/pygubu/plugins/customtkinter/designer/000077500000000000000000000000001474524032100234145ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/customtkinter/designer/__init__.py000066400000000000000000000000001474524032100255130ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/customtkinter/designer/designerplugin.py000066400000000000000000000106211474524032100270050ustar00rootroot00000000000000from pygubu.api.v1 import IPluginBase, IDesignerPlugin from pygubu.utils.widget import crop_widget from pygubu.stockimage import StockRegistry, StockImageCache, StockImage from .preview import ( CTkToplevelPreviewBO, CTkPreviewBO, CTkFramePreviewBO, CTkTabviewForPreviewBO, CTkSegmentedButtonForPreviewBO, ) from ..ctkbase import _plugin_uid class CTkDesignerPlugin(IDesignerPlugin): def get_preview_builder(self, builder_uid: str): if builder_uid == f"{_plugin_uid}.CTkToplevel": return CTkToplevelPreviewBO if builder_uid == f"{_plugin_uid}.CTk": return CTkPreviewBO if builder_uid == f"{_plugin_uid}.CTkFrame": return CTkFramePreviewBO if builder_uid == f"{_plugin_uid}.CTkTabview": return CTkTabviewForPreviewBO if builder_uid == f"{_plugin_uid}.CTkSegmentedButton": return CTkSegmentedButtonForPreviewBO return None def get_toplevel_preview_for( self, builder_uid: str, widget_id: str, builder, top_master ): top = None toplevel_uids = ("customtkinter.CTkToplevel", "customtkinter.CTk") if builder_uid in toplevel_uids: # for a new tk root, create a diferent image cache: def on_root_created(root): image_cache = StockImageCache(root, StockImage.registry) builder.image_cache = image_cache builder.on_first_object = on_root_created top = builder.get_object(widget_id) return top def configure_for_preview(self, builder_uid: str, widget): """Make widget just display with minimal functionality.""" if not builder_uid.startswith(f"{_plugin_uid}."): return # # do recursive cropping # crop_widget(widget, recursive=True) # # Remove default bindings # def _no_op(event=None): pass has_canvas = hasattr(widget, "_canvas") widget_canvas = widget._canvas if has_canvas else None if builder_uid.endswith(".CTkScrollableFrame"): return elif builder_uid.endswith(".CTKEntry"): seqlist = ("", "") for seq in seqlist: widget_canvas.bind(seq, _no_op) elif builder_uid.endswith(".CTkSlider"): seqlist = ("", "", "", "") for seq in seqlist: widget_canvas.bind(seq, _no_op) elif builder_uid.endswith(".CTkOptionMenu"): seqlist = ("", "", "") for seq in seqlist: widget_canvas.bind(seq, _no_op) widget._text_label.bind(seq, _no_op) elif builder_uid.endswith(".CTkComboBox"): widget_canvas.tag_bind("right_parts", "", _no_op) widget_canvas.tag_bind("dropdown_arrow", "", _no_op) widget_canvas.tag_bind("right_parts", "", _no_op) widget_canvas.tag_bind("dropdown_arrow", "", _no_op) widget_canvas.tag_bind("right_parts", "", _no_op) widget_canvas.tag_bind("dropdown_arrow", "", _no_op) def ensure_visibility_in_preview(self, builder, selected_uid: str): """Ensure visibility of selected_uid in preview.""" xpath = ".//object[@class='customtkinter.CTkTabview.Tab']" # find all tabs tabs = builder.uidefinition.root.findall(xpath) if tabs is None: return for tab in tabs: tab_id = tab.get("id") activate_tab = False # Check if this tab was clicked if tab_id == selected_uid: activate_tab = True else: # check if selected_uid is inside this tab xpath = f".//object[@id='{selected_uid}']" o = tab.find(xpath) if o is not None: activate_tab = True if activate_tab: tab_builder = builder.objects[tab_id] top = tab_builder.widget.winfo_toplevel() tabview = top.nametowidget(tab_builder.widget.winfo_parent()) tabname = tab_builder.wmeta.properties.get("label") current = tabview.get() if current != tabname: tabview.set(tabname) top.update() break pygubu-0.36.1/src/pygubu/plugins/customtkinter/designer/preview.py000066400000000000000000000073461474524032100254610ustar00rootroot00000000000000import tkinter as tk import customtkinter as ctk from customtkinter.windows.widgets.core_widget_classes import CTkBaseClass from pygubu.api.v1 import BuilderObject from pygubu.plugins.pygubu.designer.basehelpers import ( ToplevelPreviewBaseBO, ToplevelPreviewFactory, ToplevelPreviewMixin, ) from ..widgets import CTkFrameBO, CTkSegmentedButtonBO from ..tabview import CTkTabviewBO # # Preview class for CTkFrame # class CTkFrameForPreview(ctk.CTkFrame): def winfo_children(self): # CTkFrame has a hidden canvas inside. So, to make it clickable on preview # we need a hack. return super(tk.Frame, self).winfo_children() class CTkFramePreviewBO(CTkFrameBO): class_ = CTkFrameForPreview # # Preview class for Tabview # class CTkTabviewForPreview(ctk.CTkTabview): # def winfo_children(self): # return super(tk.Frame, self).winfo_children() def bind(self, sequence=None, func=None, add=None): return super(tk.Frame, self).bind(sequence, func, add) class CTkTabviewForPreviewBO(CTkTabviewBO): class_ = CTkTabviewForPreview # # Preview classes for ctk.CTKSegmentedbutton: # class CTkSegmentedButtonForPreview(ctk.CTkSegmentedButton): def bind(self, sequence=None, func=None, add=None): # FIXME: this is not working... # I can't select a segmented button in preview ㅜㅜ for child_name in self.winfo_children(): child = self.nametowidget(child_name) child.bind(sequence, func, True) class CTkSegmentedButtonForPreviewBO(CTkSegmentedButtonBO): class_ = CTkSegmentedButtonForPreview # # Preview classes for CTKToplevel # class CTKToplevelPreviewMixin: def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) # configure properties not supported by CtkFrame but yes by CtkToplevel props = ("borderwidth", "highlightbackground", "highlightthickness") for pname in props: if pname in kw: super(CTkBaseClass, self).configure(**{pname: kw.pop(pname)}) return super().configure(cnf, **kw) CTKToplevelPreview = ToplevelPreviewFactory( "CTKToplevelPreview", (CTKToplevelPreviewMixin, ToplevelPreviewMixin, CTkFrameForPreview, object), {}, ) class CTkToplevelPreviewBO(ToplevelPreviewBaseBO): class_ = CTKToplevelPreview ro_properties = ToplevelPreviewBaseBO.ro_properties + ( "background", "fg_color", ) def _process_property_value(self, pname, value): if pname in ("width", "height"): return int(value) return super()._process_property_value(pname, value) # # Preview classes for CTK # class CTKPreviewMixin: def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) # configure properties not supported by CtkFrame but yes by Ctk props = ("padx", "pady", "relief", "takefocus") for pname in props: if pname in kw: super(CTkBaseClass, self).configure(**{pname: kw.pop(pname)}) return super().configure(cnf, **kw) CTKPreview = ToplevelPreviewFactory( "CTKPreview", (CTKPreviewMixin, ToplevelPreviewMixin, CTkFrameForPreview, object), {}, ) class CTkPreviewBO(CTkToplevelPreviewBO): class_ = CTKPreview properties = ToplevelPreviewBaseBO.properties + ("appearance_mode",) ro_properties = ToplevelPreviewBaseBO.ro_properties + ("fg_color",) def _set_property(self, target_widget, pname, value): if pname == "appearance_mode": ctk.set_appearance_mode(value) elif pname == "color_theme": ctk.set_default_color_theme(value) else: return super()._set_property(target_widget, pname, value) pygubu-0.36.1/src/pygubu/plugins/customtkinter/scrollableframe.py000066400000000000000000000031271474524032100253260ustar00rootroot00000000000000from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from pygubu.i18n import _ from ..customtkinter import _designer_tab_label, _plugin_uid from .ctkbase import CTkBaseMixin, GCONTAINER from customtkinter import CTkScrollableFrame class CTkScrollableFrameBO(CTkBaseMixin, BuilderObject): class_ = CTkScrollableFrame container = True # CTkScrollableFrame does some weird things # with layout so I disable container layout here on purpose. container_layout = False properties = ( "width", "height", "corner_radius", "border_width", "bg_color", "fg_color", "border_color", "scrollbar_fg_color", "scrollbar_button_color", "scrollbar_button_hover_color:", "label_fg_color", "label_text_color", "label_text", "label_font", "label_anchor", "orientation", ) ro_properties = ("orientation",) _builder_uid = f"{_plugin_uid}.CTkScrollableFrame" register_widget( _builder_uid, CTkScrollableFrameBO, "CTkScrollableFrame", ("ttk", _designer_tab_label), group=GCONTAINER, ) register_custom_property( _builder_uid, "label_anchor", "choice", values=( "", "n", "ne", "nw", "e", "w", "s", "se", "sw", "center", ), state="readonly", ) register_custom_property( _builder_uid, "orientation", "choice", values=("vertical", "horizontal"), default_value="vertical", state="readonly", ) pygubu-0.36.1/src/pygubu/plugins/customtkinter/tabview.py000066400000000000000000000046121474524032100236320ustar00rootroot00000000000000from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from pygubu.i18n import _ from ..customtkinter import _designer_tab_label, _plugin_uid from .ctkbase import CTkBaseMixin, GCONTAINER from customtkinter import CTkTabview class CTkTabviewBO(CTkBaseMixin, BuilderObject): class_ = CTkTabview allow_bindings = False container = True properties = ( "width", "height", "corner_radius", "border_width", "bg_color", "fg_color", "border_color", "segmented_button_fg_color", "segmented_button_selected_color", "segmented_button_selected_hover_color", "segmented_button_unselected_color", "segmented_button_unselected_hover_color", "text_color", "text_color_disabled", "command", "anchor", "state", ) command_properties = ("command",) _builder_uid = f"{_plugin_uid}.CTkTabview" register_widget( _builder_uid, CTkTabviewBO, "CTkTabview", ("ttk", _designer_tab_label), group=GCONTAINER, ) class CTkTabviewTabBO(BuilderObject): class_ = None container = True container_layout = True layout_required = False allow_bindings = False allowed_parents = (f"{_plugin_uid}.CTkTabview",) properties = ("label",) def _get_tab_name(self): return self.wmeta.properties.get("label", self.wmeta.identifier) def realize(self, parent, extra_init_args: dict = None): view = parent.get_child_master() self.widget = view.add(self._get_tab_name()) return self.widget def configure(self, target=None): pass # # Code generation methods # def code_realize(self, boparent, code_identifier=None): view = boparent.code_child_master() tabid = self.code_identifier() tab_name = self._get_tab_name() lines = [f'{tabid} = {view}.add("{tab_name}")'] return lines def code_configure(self, targetid=None): return tuple() _builder_uid = f"{_plugin_uid}.CTkTabview.Tab" CTkTabviewBO.add_allowed_child(_builder_uid) register_widget( _builder_uid, CTkTabviewTabBO, "CTkTabview.Tab", ("ttk", _designer_tab_label), group=GCONTAINER, ) _help = _("The 'name' argument of method: CTkTabview.add(self, name: str)") register_custom_property(_builder_uid, "label", "entry", help=_help) pygubu-0.36.1/src/pygubu/plugins/customtkinter/widgets.py000066400000000000000000000422421474524032100236400ustar00rootroot00000000000000import customtkinter as ctk import pygubu.plugins.customtkinter.tabview import pygubu.plugins.customtkinter.scrollableframe from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from pygubu.i18n import _ from pygubu.utils.datatrans import ListDTO from pygubu.plugins.tk.tkstdwidgets import TKCanvas as TKCanvasBO from ..customtkinter import _designer_tab_label, _plugin_uid from .ctkbase import ( CTkBaseMixin, GCONTAINER, GDISPLAY, GINPUT, ) class CTkFrameBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkFrame container = True container_layout = True properties = ( "width", "height", "corner_radius", "border_width", "bg_color", "fg_color", "border_color", "background_corner_colors", ) _builder_uid = f"{_plugin_uid}.CTkFrame" register_widget( _builder_uid, CTkFrameBO, "CTkFrame", ("ttk", _designer_tab_label), group=GCONTAINER, ) class CTkLabelBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkLabel properties = ( "anchor", "compound", "cursor", "image", "justify", "font", "height", "padx", "pady", "state", "takefocus", "text", "textvariable", "underline", "width", # CTK properties "corner_radius", "bg_color", "fg_color", "text_color", "text_color_disabled", "border_color", "border_width", ) _builder_uid = f"{_plugin_uid}.CTkLabel" register_widget( _builder_uid, CTkLabelBO, "CTkLabel", ("ttk", _designer_tab_label), group=GDISPLAY, ) class CTkProgressBarBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkProgressBar allow_bindings = False properties = ( "width", "height", "variable", "mode", "orientation", # CTK properties "bg_color", "fg_color", "border_color", "border_width", "corner_radius", "progress_color", "determinate_speed", "indeterminate_speed", ) ro_properties = ("orientation",) _builder_uid = f"{_plugin_uid}.CTkProgressBar" register_widget( _builder_uid, CTkProgressBarBO, "CTkProgressBar", ("ttk", _designer_tab_label), group=GDISPLAY, ) register_custom_property( _builder_uid, "variable", "tkvarentry", type_choices=("int", "double"), type_default="int", ) class CTkButtonBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkButton allow_bindings = False properties = ( "text", "width", "height", "textvariable", "image", "compound", "state", "command", # CTK properties "bg_color", "fg_color", "border_color", "border_width", "border_spacing", "corner_radius", "hover_color", "text_color", "text_color_disabled", "hover", "font", "background_corner_colors", "round_width_to_even_numbers", "round_height_to_even_numbers", "anchor", ) command_properties = ("command",) ro_properties = ("hover",) _builder_uid = f"{_plugin_uid}.CTkButton" register_widget( _builder_uid, CTkButtonBO, "CTkButton", ("ttk", _designer_tab_label), group=GINPUT, ) class CTkSliderBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkSlider allow_bindings = False properties = ( "width", "height", "variable", "from_", "to", "command", "state", # CTK properties "bg_color", "fg_color", "border_color", "border_width", "corner_radius", "progress_color", "button_color", "button_hover_color", "button_corner_radius", "button_length", "number_of_steps", "orientation", ) command_properties = ("command",) ro_properties = ( "orientation", "button_length", ) def _code_define_callback_args(self, cmd_pname, cmd): return ("value",) _builder_uid = f"{_plugin_uid}.CTkSlider" register_widget( _builder_uid, CTkSliderBO, "CTkSlider", ("ttk", _designer_tab_label), group=GINPUT, ) register_custom_property( _builder_uid, "variable", "tkvarentry", type_choices=("int", "double"), type_default="int", ) class CTkEntryBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkEntry allow_bindings = False properties = ( "borderwidth", "cursor", "exportselection", "font", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "justify", "selectborderwidth", "takefocus", "textvariable", "xscrollcommand", # specific "invalidcommand", "readonlybackground", "show", "state", "validate", "validatecommand", "width", # custom "text", # CTK options "bg_color", "fg_color", "border_color", "border_width", "corner_radius", "text_color", "placeholder_text_color", "placeholder_text", ) def _set_property(self, target_widget, pname, value): if pname == "text": target_widget.delete(0, "end") target_widget.insert(0, value) else: super()._set_property(target_widget, pname, value) def _code_set_property(self, targetid, pname, value, code_bag): if pname == "text": sval = self.builder.code_translate_str(value) lines = [ f"""{targetid}.delete(0, "end")""", f"{targetid}.insert(0, {sval})", ] code_bag[pname] = lines else: super()._code_set_property(targetid, pname, value, code_bag) _builder_uid = f"{_plugin_uid}.CTkEntry" register_widget( _builder_uid, CTkEntryBO, "CTkEntry", ("ttk", _designer_tab_label), group=GINPUT, ) _list_dto = ListDTO() class CTkOptionMenuBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkOptionMenu allow_bindings = False properties = ( "command", "variable", "values", "bg_color", "fg_color", "button_color", "button_hover_color", "text_color", "text_color_disabled", "dropdown_hover_color", "dropdown_text_color", "dropdown_color", "dropdown_font", "width", "height", "corner_radius", "state", "dynamic_resizing", "font", ) command_properties = ("command",) def _process_property_value(self, pname, value): if pname == "values": return _list_dto.transform(value) return super()._process_property_value(pname, value) def _code_define_callback_args(self, cmd_pname, cmd): return ("current_value",) def _code_process_property_value(self, targetid, pname, value: str): if pname == "values": return super()._process_property_value(pname, value) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.CTkOptionMenu" _ctk_values_help = _( "Specifies the list of values to display. " "In code you can pass any iterable. " 'In Designer, a json like list: ["item1", "item2"]' ) register_widget( _builder_uid, CTkOptionMenuBO, "CTkOptionMenu", ("ttk", _designer_tab_label), group=GINPUT, ) register_custom_property(_builder_uid, "values", "entry", help=_ctk_values_help) register_custom_property( _builder_uid, "state", "choice", values=("", "normal", "disabled", "readonly"), state="readonly", ) class CTkComboBoxBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkComboBox allow_bindings = False properties = ( "corner_radius", "border_width", "bg_color", "fg_color", "border_color", "button_color", "button_hover_color", "dropdown_fg_color", "dropdown_hover_color", "dropdown_text_color", "text_color", "text_color_disabled", "font", "dropdown_font", "command", "variable", "values", "width", "height", "state", "hover", ) command_properties = ("command",) ro_properties = ("hover",) def _process_property_value(self, pname, value): if pname == "values": return _list_dto.transform(value) return super()._process_property_value(pname, value) def _code_define_callback_args(self, cmd_pname, cmd): return ("value",) def _code_process_property_value(self, targetid, pname, value: str): if pname == "values": return super()._process_property_value(pname, value) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.CTkComboBox" register_widget( _builder_uid, CTkComboBoxBO, "CTkComboBox", ("ttk", _designer_tab_label), group=GINPUT, ) register_custom_property(_builder_uid, "values", "entry", help=_ctk_values_help) register_custom_property( _builder_uid, "state", "choice", values=("", "normal", "disabled", "readonly"), state="readonly", ) class CTkCheckBoxBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkCheckBox allow_bindings = False properties = ( "width", "height", "checkbox_width", "checkbox_height", "corner_radius", "border_width", "bg_color", "fg_color", "hover_color", "border_color", "checkmark_color", "text_color", "text_color_disabled", "text", "font", "textvariable", "state", "hover", "command", "onvalue", "offvalue", "variable", ) command_properties = ("command",) ro_properties = ("hover", "onvalue", "offvalue") _builder_uid = f"{_plugin_uid}.CTkCheckBox" register_widget( _builder_uid, CTkCheckBoxBO, "CTkCheckBox", ("ttk", _designer_tab_label), group=GINPUT, ) class CTkRadioButtonBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkRadioButton allow_bindings = False properties = ( "width", "height", "radiobutton_width", "radiobutton_height", "corner_radius", "border_width_unchecked", "border_width_checked", "bg_color", "fg_color", "hover_color", "border_color", "text_color", "text_color_disabled", "text", "font", "textvariable", "variable", "value", "state", "hover", "command", ) command_properties = ("command",) ro_properties = ("hover", "value") _builder_uid = f"{_plugin_uid}.CTkRadioButton" register_widget( _builder_uid, CTkRadioButtonBO, "CTkRadioButton", ("ttk", _designer_tab_label), group=GINPUT, ) class CTkSwitchBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkSwitch allow_bindings = False properties = ( "width", "height", "switch_width", "switch_height", "corner_radius", "border_width", "button_length", "bg_color", "fg_color", "border_color", "progress_color", "button_color", "button_hover_color", "text_color", "text_color_disabled", "text", "font", "textvariable", "onvalue", "offvalue", "variable", "hover", "command", "state", ) command_properties = ("command",) ro_properties = ( "onvalue", "offvalue", "hover", "text_color", "text_color_disabled", ) _builder_uid = f"{_plugin_uid}.CTkSwitch" register_widget( _builder_uid, CTkSwitchBO, "CTkSwitch", ("ttk", _designer_tab_label), group=GINPUT, ) class CTkTextboxBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkTextbox properties = ( "autoseparators", "cursor", "exportselection", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "maxundo", "padx", "pady", "selectborderwidth", "spacing1", "spacing2", "spacing3", "state", "tabs", "takefocus", "undo", "wrap", # ctk "width", "height", "corner_radius", "border_width", "border_spacing", "bg_color", "fg_color", "border_color", "text_color", "font", "scrollbar_button_color", "scrollbar_button_hover_color", "activate_scrollbars", # custom "text", ) def _set_property(self, target_widget, pname, value): if pname == "text": target_widget = target_widget._textbox state = target_widget.cget("state") if state == ctk.DISABLED: target_widget.configure(state=ctk.NORMAL) target_widget.insert("0.0", value) target_widget.configure(state=ctk.DISABLED) else: target_widget.insert("0.0", value) else: super()._set_property(target_widget, pname, value) def _code_set_property(self, targetid, pname, value, code_bag): if pname == "text": state_value = "" if "state" in self.wmeta.properties: state_value = self.wmeta.properties["state"] sval = self.builder.code_translate_str(value) lines = [ f"_text_ = {sval}", ] if state_value == ctk.DISABLED: lines.extend( ( f'{targetid}.configure(state="normal")', f'{targetid}.insert("0.0", _text_)', f'{targetid}.configure(state="disabled")', ) ) else: lines.append(f'{targetid}.insert("0.0", _text_)') code_bag[pname] = lines else: super()._code_set_property(targetid, pname, value, code_bag) _builder_uid = f"{_plugin_uid}.CTkTextbox" register_widget( _builder_uid, CTkTextboxBO, "CTkTextbox", ("ttk", _designer_tab_label), group=GINPUT, ) register_custom_property( _builder_uid, "wrap", "choice", values=("", ctk.CHAR, ctk.WORD, ctk.NONE), state="readonly", ) class CTkScrollbarBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkScrollbar properties = ( "width", "height", "orientation", "command", # CTK "cursor", "corner_radius", "border_spacing", "minimum_pixel_length", "bg_color", "fg_color", "button_color", "button_hover_color", "hover", ) ro_properties = ("orientation", "cursor") command_properties = ("command",) _builder_uid = f"{_plugin_uid}.CTkScrollbar" register_widget( _builder_uid, CTkScrollbarBO, "CTkScrollbar", ("ttk", _designer_tab_label), group=GINPUT, ) register_custom_property( _builder_uid, "orientation", "choice", values=("vertical", "horizontal"), state="readonly", default_value="vertical", ) register_custom_property( _builder_uid, "command", "scrollcommandentry", ) class CTkSegmentedButtonBO(CTkBaseMixin, BuilderObject): class_ = ctk.CTkSegmentedButton allow_bindings = False properties = ( "width", "height", "corner_radius", "border_width", "bg_color", "fg_color", "selected_color", "selected_hover_color", "unselected_color", "unselected_hover_color", "text_color", "text_color_disabled", "background_corner_colors", "font", "values", "variable", "dynamic_resizing", "command", "state", ) command_properties = ("command",) ro_properties = ("hover",) def _process_property_value(self, pname, value): if pname == "values": return _list_dto.transform(value) return super()._process_property_value(pname, value) def _code_define_callback_args(self, cmd_pname, cmd): return ("current_value",) def _code_process_property_value(self, targetid, pname, value: str): if pname == "values": return super()._process_property_value(pname, value) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.CTkSegmentedButton" register_widget( _builder_uid, CTkSegmentedButtonBO, "CTkSegmentedButton", ("ttk", _designer_tab_label), group=GINPUT, ) register_custom_property(_builder_uid, "values", "entry", help=_ctk_values_help) class CTkCanvasBO(TKCanvasBO): class_ = ctk.CTkCanvas _builder_uid = f"{_plugin_uid}.CTkCanvas" register_widget( _builder_uid, CTkCanvasBO, "CTkCanvas", ("ttk", _designer_tab_label), group=GDISPLAY, ) pygubu-0.36.1/src/pygubu/plugins/customtkinter/windows.py000066400000000000000000000111031474524032100236540ustar00rootroot00000000000000from pygubu.api.v1 import register_widget, register_custom_property from pygubu.i18n import _ from pygubu.plugins.tk.tkstdwidgets import TKToplevel as TKToplevelBO from pygubu.plugins.tk.tkstdwidgets import TKRootBO from customtkinter import ( CTk, CTkToplevel, set_appearance_mode, set_default_color_theme, ) from ..customtkinter import _designer_tab_label, _plugin_uid from .ctkbase import CTkBaseMixin class CTkBO(TKRootBO): class_ = CTk properties = ( "cursor", "padx", "pady", "relief", "takefocus", "height", "width", # CUSTOM OPTIONS, "title", "geometry", "overrideredirect", "minsize", "maxsize", "resizable", "iconbitmap", "iconphoto", # CUSTOMTKINTER options, "appearance_mode", "color_theme", "fg_color", ) ro_properties = ("container", "fg_color") def realize(self, parent, extra_init_args: dict = None): args = self._get_init_args(extra_init_args) # master = parent.get_child_master() fg_color = args.pop("fg_color", None) self.widget = self.class_(fg_color, **args) return self.widget def _set_property(self, target_widget, pname, value): if pname == "appearance_mode": set_appearance_mode(value) elif pname == "color_theme": set_default_color_theme(value) else: return super()._set_property(target_widget, pname, value) # # Code generation methods # def code_realize(self, boparent, code_identifier=None): if code_identifier is not None: self._code_identifier = code_identifier lines = [] # master = boparent.code_child_master() init_args = self._code_get_init_args(self.code_identifier()) fg_color = init_args.pop("fg_color", None) bag = [] for pname, value in init_args.items(): bag.append(f"{pname}={value}") kwargs = "" if bag: kwargs = f""", {", ".join(bag)}""" s = f"{self.code_identifier()} = {self._code_class_name()}({fg_color}{kwargs})" lines.append(s) return lines def _code_set_property(self, targetid, pname, value, code_bag): if pname == "appearance_mode": code_bag[pname] = (f'set_appearance_mode("{value}")',) elif pname == "color_theme": code_bag[pname] = (f'set_default_color_theme("{value}")',) else: super()._code_set_property(targetid, pname, value, code_bag) def code_imports(self): imports = ["CTk"] if "appearance_mode" in self.wmeta.properties: imports.append("set_appearance_mode") if "color_theme" in self.wmeta.properties: imports.append("set_default_color_theme") return [("customtkinter", name) for name in imports] _builder_uid = f"{_plugin_uid}.CTk" register_widget( _builder_uid, CTkBO, "CTk", ("ttk", _designer_tab_label), group=-1, ) _maxsize_help = _("Set the maximum window size.") _minsize_help = _("Set the minimum window size.") register_custom_property( _builder_uid, "minsize", "whentry", help=_minsize_help, ) register_custom_property( _builder_uid, "maxsize", "whentry", help=_maxsize_help, ) class CTkToplevelBO(TKToplevelBO): class_ = CTkToplevel properties = ( "borderwidth", "cursor", "takefocus", "height", "width", "highlightbackground", "highlightthickness", # CUSTOM OPTIONS, "title", "geometry", "overrideredirect", "minsize", "maxsize", "resizable", "iconbitmap", "iconphoto", # CUSTOMTKINTER options, "fg_color", ) ro_properties = ("fg_color",) def realize(self, parent, extra_init_args: dict = None): kwargs = self._get_init_args(extra_init_args) fg_color = kwargs.pop("fg_color", None) master = parent.get_child_master() self.widget = self.class_(master, fg_color=fg_color, **kwargs) return self.widget def code_imports(self): return [("customtkinter", self.class_.__name__)] _builder_uid = f"{_plugin_uid}.CTkToplevel" register_widget( _builder_uid, CTkToplevelBO, "CTkToplevel", ("ttk", _designer_tab_label), group=-1, ) register_custom_property( _builder_uid, "minsize", "whentry", help=_minsize_help, ) register_custom_property( _builder_uid, "maxsize", "whentry", help=_maxsize_help, ) pygubu-0.36.1/src/pygubu/plugins/pygubu/000077500000000000000000000000001474524032100202145ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/pygubu/__init__.py000066400000000000000000000071711474524032100223330ustar00rootroot00000000000000import os from pygubu.api.v1 import BuilderLoaderPlugin from pygubu.i18n import _ _tab_widgets_label = _("Pygubu Widgets") _tab_helpers_label = _("Pygubu Helpers") _plugin_uid = "pygubu.widgets" class PygubuWidgetsLoader(BuilderLoaderPlugin): module_map = { "pygubu.plugins.pygubu.accordionframe_bo": ( f"{_plugin_uid}.AccordionFrame", ), "pygubu.plugins.pygubu.calendarframe_bo": ( f"{_plugin_uid}.CalendarFrame", "pygubu.builder.widgets.calendarframe", ), "pygubu.plugins.pygubu.colorinput_bo": (f"{_plugin_uid}.ColorInput",), "pygubu.plugins.pygubu.combobox_bo": ( f"{_plugin_uid}.Combobox", "pygubu.builder.widgets.combobox", ), "pygubu.plugins.pygubu.dialog_bo": ( f"{_plugin_uid}.Dialog", "pygubu.builder.widgets.dialog", ), "pygubu.plugins.pygubu.dockfw_bo": ( f"{_plugin_uid}.dockframe", f"{_plugin_uid}.dockpane", f"{_plugin_uid}.dockwidget", ), "pygubu.plugins.pygubu.editabletreeview_bo": ( f"{_plugin_uid}.EditableTreeview", "pygubu.builder.widgets.editabletreeview", ), "pygubu.plugins.pygubu.filterabletreeview_bo": ( f"{_plugin_uid}.FilterableTreeview", ), "pygubu.plugins.pygubu.floodgauge_bo": (f"{_plugin_uid}.Floodgauge",), # Forms are not finished so expect changes "pygubu.plugins.pygubu.forms.pygubuwidget_bo": ( "pygubu.forms.pygubuwidget.PygubuCombobox" ), "pygubu.plugins.pygubu.forms.tkwidget_bo": ( "pygubu.forms.tkwidget.Text", ), "pygubu.plugins.pygubu.forms.ttkwidget_bo": ( "pygubu.forms.ttkwidget.FrameFormBuilder", "pygubu.forms.ttkwidget.Label", "pygubu.forms.ttkwidget.Entry", "pygubu.forms.ttkwidget.LabelWidgetInfo", ), "pygubu.plugins.pygubu.hideableframe_bo": ( f"{_plugin_uid}.hideableframe", ), "pygubu.plugins.pygubu.pathchooserinput_bo": ( "pygubu.builder.widgets.pathchooserinput", "pygubu.widgets.PathChooserInput", f"{_plugin_uid}.PathChooserButton", ), "pygubu.plugins.pygubu.scrollbarhelper_bo": ( "pygubu.builder.widgets.scrollbarhelper", f"{_plugin_uid}.ScrollbarHelper", ), "pygubu.plugins.pygubu.scrolledframe_bo": ( "pygubu.builder.widgets.scrolledframe", f"{_plugin_uid}.ScrolledFrame", ), "pygubu.plugins.pygubu.tkscrollbarhelper_bo": ( "pygubu.builder.widgets.tkscrollbarhelper", f"{_plugin_uid}.TkScrollbarHelper", ), "pygubu.plugins.pygubu.tkscrolledframe_bo": ( "pygubu.builder.widgets.tkscrolledframe", f"{_plugin_uid}.TkScrolledFrame", ), } def do_activate(self) -> bool: return True def get_all_modules(self): return [m for m in self.module_map.keys()] def get_module_for(self, identifier: str) -> str: for module, identifiers in self.module_map.items(): if identifier in identifiers: return module return None def can_load(self, identifier: str) -> bool: for module, builders in self.module_map.items(): if identifier in builders: return True return False def get_designer_plugin(self): """Load class that implements IDesignerPlugin""" from .designer.designerplugin import PygubuDesignerPlugin return PygubuDesignerPlugin() pygubu-0.36.1/src/pygubu/plugins/pygubu/accordionframe_bo.py000066400000000000000000000040121474524032100242170ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk from pygubu.api.v1 import ( BuilderObject, register_widget, ) from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame from pygubu.widgets.accordionframe import AccordionFrame from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class AccordionFrameBO(TTKFrame): class_ = AccordionFrame allowed_children = (f"{_plugin_uid}.AccordionFrameGroup",) _img_properties = ("img_expand", "img_collapse") properties = TTKFrame.properties + _img_properties tkimage_properties = TTKFrame.tkimage_properties + _img_properties _builder_uid = f"{_plugin_uid}.AccordionFrame" register_widget( _builder_uid, AccordionFrameBO, "AccordionFrame", (_tab_widgets_label, "ttk"), ) class AccordionFrameGroupBO(BuilderObject): allowed_parents = (f"{_plugin_uid}.AccordionFrame",) properties = ("label", "expanded", "compound", "style") layout_required = False container = True container_layout = True allow_bindings = False def realize(self, parent, extra_init_args: dict = None): args = self._get_init_args(extra_init_args) master = parent.get_child_master() self._accordion = parent.widget gid = self.wmeta.identifier self.widget = master.add_group(gid, **args) return self.widget def _process_property_value(self, pname, value): if pname == "expanded": return tk.getboolean(value) return super()._process_property_value(pname, value) def _set_property(self, target_widget, pname, value): if pname in self.properties: propvalue = self._process_property_value(pname, value) self._accordion.group_config( self.wmeta.identifier, **{pname: propvalue} ) else: super()._set_property(target_widget, pname, value) _builder_uid = f"{_plugin_uid}.AccordionFrameGroup" register_widget( _builder_uid, AccordionFrameGroupBO, "AccordionFrame.Group", (_tab_widgets_label, "ttk"), ) pygubu-0.36.1/src/pygubu/plugins/pygubu/calendarframe_bo.py000066400000000000000000000022731474524032100240360ustar00rootroot00000000000000# encoding: utf-8 from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame from pygubu.widgets.calendarframe import CalendarFrame from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class CalendarFrameBuilder(BuilderObject): class_ = CalendarFrame OPTIONS_STANDARD = TTKFrame.OPTIONS_STANDARD OPTIONS_SPECIFIC = TTKFrame.OPTIONS_SPECIFIC OPTIONS_CUSTOM = ( "firstweekday", "year", "month", "calendarfg", "calendarbg", "headerfg", "headerbg", "selectbg", "selectfg", "state", "markbg", "markfg", "linewidth", ) ro_properties = TTKFrame.ro_properties properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM virtual_events = ("<>",) _builder_id = f"{_plugin_uid}.CalendarFrame" register_widget( _builder_id, CalendarFrameBuilder, "CalendarFrame", ("ttk", _tab_widgets_label), ) # Register old name until removal register_widget( "pygubu.builder.widgets.calendarframe", CalendarFrameBuilder, public=False ) pygubu-0.36.1/src/pygubu/plugins/pygubu/colorinput_bo.py000066400000000000000000000011051474524032100234410ustar00rootroot00000000000000#!/usr/bin/python3 import tkinter as tk import tkinter.ttk as ttk from pygubu.api.v1 import ( BuilderObject, register_widget, ) from pygubu.widgets.colorinput import ColorInput from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid # # Builder definition section # class ColorInputBO(BuilderObject): class_ = ColorInput virtual_events = (ColorInput.EVENT_COLOR_CHANGED,) properties = ("value", "textvariable") _builder_id = f"{_plugin_uid}.ColorInput" register_widget( _builder_id, ColorInputBO, "ColorInput", ("ttk", _tab_widgets_label) ) pygubu-0.36.1/src/pygubu/plugins/pygubu/combobox_bo.py000066400000000000000000000016731474524032100230650ustar00rootroot00000000000000# encoding: utf-8 from pygubu.api.v1 import register_widget from pygubu.plugins.ttk.ttkstdwidgets import TTKCombobox from pygubu.widgets.combobox import Combobox from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class ComboboxBO(TTKCombobox): OPTIONS_SPECIFIC = tuple( set(TTKCombobox.OPTIONS_SPECIFIC) - set(("state",)) ) OPTIONS_CUSTOM = TTKCombobox.OPTIONS_CUSTOM + ("keyvariable",) properties = ( TTKCombobox.OPTIONS_STANDARD + TTKCombobox.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) tkvar_properties = TTKCombobox.tkvar_properties + ("keyvariable",) class_ = Combobox ComboboxBuilder = ComboboxBO # Alias for future rename of builder. _builder_id = f"{_plugin_uid}.Combobox" register_widget( _builder_id, ComboboxBO, "Combobox", ("ttk", _tab_widgets_label) ) # Register old name until removal register_widget("pygubu.builder.widgets.combobox", ComboboxBO, public=False) pygubu-0.36.1/src/pygubu/plugins/pygubu/designer/000077500000000000000000000000001474524032100220145ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/pygubu/designer/__init__.py000066400000000000000000000000001474524032100241130ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/pygubu/designer/basehelpers.py000066400000000000000000000204401474524032100246630ustar00rootroot00000000000000import tkinter as tk from pygubu.api.v1 import BuilderObject from pygubu.plugins.tk.tkstdwidgets import TKToplevel class ToplevelPreviewFactory(type): def __new__(cls, clsname, superclasses, attrs): return type.__new__(cls, str(clsname), superclasses, attrs) class ToplevelPreviewMixin(object): def __init__(self, master=None, **kw): super().__init__(master, **kw) self.tl_attrs = {} self._w_set = False # Marks if width was set using "width" property self._h_set = False # Marks if height was set using "height" property self._propagate = True # Save propagate state configured by user self._geometry_set = False self._geom_w = 1 self._geom_h = 1 self._uwidth = 1 # Save width configured by user self._uheight = 1 # Save height configured by user def _get_req_wh(self): """Calculate aproximation of real width and height because this frame is locked for preview purposes.""" my_w = self._uwidth my_h = self._uheight has_minsize = False min_w = my_w min_h = my_h final_w = my_w final_h = my_h if "minsize" in self.tl_attrs: minsize = self.tl_attrs.get("minsize", (1, 1)) min_w = minsize[0] min_h = minsize[1] has_minsize = True content_w = content_h = 0 if self._propagate: content_w = super().winfo_reqwidth() content_h = super().winfo_reqheight() children = self.winfo_children() if children: if has_minsize: # propagate is enabled, resize to # content size or minsize final_w = max(content_w, min_w) final_h = max(content_h, min_h) else: final_w = content_w final_h = content_h else: # No children, show frame with user configured size final_w = max(my_w, min_w) final_h = max(my_h, min_h) else: # Propagate is false, fixed w/h frame. final_w = max(min_w, my_w) final_h = max(min_h, my_h) if self._geometry_set: # Geometry overrides all final_w = self._geom_w final_h = self._geom_h # print(f"{my_w=}, {my_h=}, {has_minsize=}, {min_w=}, {min_h=}, {content_w=}, {content_h=}") # print(f"{final_w=}, {final_h=}") return (final_w, final_h) def winfo_reqwidth(self): return self._get_req_wh()[0] def winfo_reqheight(self): return self._get_req_wh()[1] def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) self._handle_width(kw) self._handle_height(kw) key = "menu" if key in kw: # No menu preview available kw.pop(key) return super().configure(cnf, **kw) def _handle_height(self, kw): key = "height" if key in kw: value = int(kw[key]) minsize = self.tl_attrs.get("minsize", None) maxsize = self.tl_attrs.get("maxsize", None) remove = False if minsize and value < minsize[1]: remove = True if maxsize and value > maxsize[1]: remove = True if self._h_set: resizable = self.tl_attrs.get("resizable", None) if resizable and not TKToplevel.RESIZABLE[resizable][1]: remove = True if remove: kw.pop(key) else: self._h_set = True # save user height setting self._uheight = value def _handle_width(self, kw): key = "width" if key in kw: value = int(kw[key]) minsize = self.tl_attrs.get("minsize", None) maxsize = self.tl_attrs.get("maxsize", None) remove = False if minsize and value < minsize[0]: remove = True if maxsize and value > maxsize[0]: remove = True if self._w_set: resizable = self.tl_attrs.get("resizable", None) if resizable and not TKToplevel.RESIZABLE[resizable][0]: remove = True if remove: kw.pop(key) else: self._w_set = True # save user width setting self._uwidth = value class ToplevelPreviewBaseBO(BuilderObject): container = True container_layout = True # Add fake 'modal' property for Dialog preview properties = TKToplevel.properties + ("modal",) ro_properties = TKToplevel.ro_properties def configure(self, target=None): # setup width and height properties if # geometry is defined. geom = "geometry" if geom in self.wmeta.properties: w, h = self._get_dimwh(self.wmeta.properties[geom]) if w and h: self.wmeta.properties["width"] = w self.wmeta.properties["height"] = h super().configure(target) def configure_children(self, target=None): w = self.widget # Set a fixed size in preview if geometry is set. if "geometry" in self.wmeta.properties: if w.pack_slaves(): w.pack_propagate(0) elif w.grid_slaves(): w.grid_propagate(0) def _container_layout(self, target, container_manager, properties): # save user configured propagate state value = self.wmeta.container_properties.get("propagate", True) value = self._process_property_value("propagate", value) self.widget._propagate = value super()._container_layout(target, container_manager, properties) def layout(self, target=None, *, forget=False): self.widget.pack(expand=True, fill="both") self._container_layout( self.widget, self.wmeta.container_manager, self.wmeta.container_properties, ) def _get_dimwh(self, dimvalue: str): # get width and height from dimension string dim = dimvalue.split("+")[0] dim = dim.split("-")[0] w, h = dim.split("x") return (int(w), int(h)) def _process_property_value(self, pname, value): if pname in ("maxsize", "minsize"): if "|" in value: w, h = value.split("|") value = (int(w), int(h)) return value if pname == "propagate": return tk.getboolean(value) return super()._process_property_value(pname, value) def _set_property(self, target_widget, pname, value): tw = target_widget tw.tl_attrs[pname] = value method_props = ( "iconbitmap", "iconphoto", "overrideredirect", "title", ) if pname in method_props: pass elif pname in ("maxsize", "minsize"): pvalue = self._process_property_value(pname, value) if not pvalue: del tw.tl_attrs[pname] else: if isinstance(pvalue, tuple): tw.tl_attrs[pname] = pvalue if pname == "minsize": tw.configure(width=pvalue[0], height=pvalue[1]) else: del tw.tl_attrs[pname] elif pname == "geometry": self._handle_geometry(value, tw) elif pname == "resizable": # Do nothing, fake 'resizable' property for Toplevel preview pass elif pname == "modal": # Do nothing, fake 'modal' property for dialog preview pass elif pname in ("className", "baseName"): # Do nothing, fake properties for dialog preview pass else: super()._set_property(tw, pname, value) def _handle_geometry(self, value, tw): if value: w, h = self._get_dimwh(value) if w and h: w, h = int(w), int(h) tw.tl_attrs["minsize"] = (w, h) tw._h_set = tw._w_set = False tw.configure(width=w, height=h) tw._geometry_set = True tw._geom_w = w tw._geom_h = h pygubu-0.36.1/src/pygubu/plugins/pygubu/designer/designerplugin.py000066400000000000000000000067501474524032100254150ustar00rootroot00000000000000import pygubu.plugins.pygubu.designer.properties from pygubu.api.v1 import IDesignerPlugin from pygubu.utils.widget import crop_widget from pygubu.stockimage import StockRegistry, StockImageCache, StockImage from .toplevelframe import ToplevelFramePreviewBO class PygubuDesignerPlugin(IDesignerPlugin): def get_preview_builder(self, builder_uid: str): if builder_uid in ( "tk.Tk", "tk.Toplevel", "pygubu.widgets.Dialog", "pygubu.builder.widgets.dialog", # This will be removed ): return ToplevelFramePreviewBO return None def get_toplevel_preview_for( self, builder_uid: str, widget_id: str, builder, top_master ): top = None if builder_uid == "tk.Toplevel": top = builder.get_object(widget_id, top_master) elif builder_uid == "tk.Tk": # for a new tk root, create a diferent image cache: def on_root_created(root): image_cache = StockImageCache(root, StockImage.registry) builder.image_cache = image_cache builder.on_first_object = on_root_created top = builder.get_object(widget_id) elif builder_uid == "pygubu.builder.widgets.dialog": dialog = builder.get_object(widget_id, top_master) dialog.run() top = dialog.toplevel return top def configure_for_preview(self, builder_uid: str, widget): """Make widget just display with minimal functionality.""" if builder_uid.endswith(".pathchooserinput"): crop_widget(widget.entry) crop_widget(widget.folder_button) if builder_uid.endswith(".ColorInput"): crop_widget(widget._entry) crop_widget(widget._button) def ensure_visibility_in_preview(self, builder, selected_uid: str): """Ensure visibility of selected_uid in preview. Usage example: Activate a tab of a Notebook if the selected widget is inside the notebook. """ xpath = ".//object[@class='ttk.Notebook.Tab']" # find all tabs tabs = builder.uidefinition.root.findall(xpath) if tabs is None: return for tab in tabs: tab_id = tab.get("id") # Check if this tab was clicked if tab_id == selected_uid: xpath = "./child/object[1]" child = tab.find(xpath) # A tab can be empty, check that. if child is not None: child_id = child.get("id") notebook = builder.objects[tab_id].widget current_tab = builder.objects[child_id].widget notebook.select(current_tab) notebook.update() # Found, stop searching break # check if selected_uid is inside this tab xpath = f".//object[@id='{selected_uid}']" o = tab.find(xpath) if o is not None: # selected_uid is inside, find the tab child # and select this tab xpath = "./child/object[1]" child = tab.find(xpath) child_id = child.get("id") notebook = builder.objects[tab_id].widget current_tab = builder.objects[child_id].widget notebook.select(current_tab) notebook.update() # Found, stop searching break pygubu-0.36.1/src/pygubu/plugins/pygubu/designer/properties.py000066400000000000000000000226261474524032100245720ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_custom_property from pygubu.plugins.pygubu import _plugin_uid from pygubu.plugins.pygubu.forms.base import _plugin_forms_uid as forms_uid from pygubu.widgets.pathchooserinput import PathChooserInput _builder_all = f"{_plugin_uid}.*" _AccordionFrame = f"{_plugin_uid}.AccordionFrame" _AccordionFrameGroup = f"{_plugin_uid}.AccordionFrameGroup" _CalendarFrame = f"{_plugin_uid}.CalendarFrame" _CalendarFrame_old = "pygubu.builder.widgets.calendarframe" _ColorInput = f"{_plugin_uid}.ColorInput" _Combobox = f"{_plugin_uid}.Combobox" _Combobox_old = "pygubu.builder.widgets.combobox" _Dialog = f"{_plugin_uid}.Dialog" _Dialog_old = "pygubu.builder.widgets.dialog" _DockFrame = f"{_plugin_uid}.docframe" _DockPane = f"{_plugin_uid}.docpane" _DockWidget = f"{_plugin_uid}.dockwidget" _forms_all = f"{forms_uid}.*" _forms_PygubuCombobox = f"{forms_uid}.pygubuwidget.PygubuCombobox" _forms_FrameFormBuilder = f"{forms_uid}.ttkwidget.FrameFormBuilder" _forms_LabelWidgetInfo = f"{forms_uid}.ttkwidget.LabelWidgetInfo" _forms_Combobox = f"{forms_uid}.ttkwidget.Combobox" _HideableFrame = f"{_plugin_uid}.hideableframe" _PathChoser_all = f"{_plugin_uid}.PathChooser.*" _PathChoser_old = "pygubu.builder.widgets.pathchooser.*" _PathChoserButton = f"{_plugin_uid}.PathChooserButton" h_values = _( "In designer: json list of key, value pairs\n" ' Example: [["A", "Option 1 Label"], ["B", "Option 2 Label"]]\n' "In code: an iterable of key, value pairs" ) # combobox h_keyvariable = _("Tk variable associated to the key value.") # combobox h_state = _("Combobox state.") # combobox h_modal = _("Determines if dialog is run in normal or modal mode.") # Dialog h_weight = _("The weight value for the pane.") # DockPane h_mustexist = _( "Dialog option. Determines if path must exist for directory and file dialogs." + " The default value is True." ) # PathChooserInput h_initialdir = _("Dialog option. Sets initial directory.") # PathChooserInput h_title = _("Dialog option. Sets dialog title.") # PathChooserInput h_defaultextension = _help = _("Dialog option. Sets default file extension.") plugin_properties = { "calendarfg": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="colorentry" ), "calendarbg": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="colorentry" ), "compound": dict( buid=_AccordionFrameGroup, editor="choice", values=( "", tk.LEFT, tk.RIGHT, tk.NONE, ), state="readonly", ), "defaultextension": dict( buid=[_PathChoser_all, _PathChoser_old], help=h_defaultextension ), "expanded": dict( buid=_AccordionFrameGroup, editor="choice", values=("", "false", "true"), state="readonly", ), "field_name": [ dict(buid=_forms_all, editor="fieldname_entry"), dict(buid=_forms_LabelWidgetInfo, editor="fieldname_selector"), ], "firstweekday": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="choice", values=("0", "6"), state="readonly", default_value="6", ), "grouped": dict( buid=_DockWidget, editor="choice", values=("", "true", "false"), state="readonly", ), "headerbg": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="colorentry" ), "headerfg": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="colorentry" ), "height": [ dict(buid=_AccordionFrame, editor="dimensionentry", default_value=200), dict(buid=_HideableFrame, editor="dimensionentry", default_value=200), dict( buid=_forms_FrameFormBuilder, editor="dimensionentry", default_value=200, ), dict( buid=[_Dialog, _Dialog_old], editor="dimensionentry", default_value=200, ), ], "image": dict( buid=[_PathChoser_all, _PathChoser_old], editor="imageentry", help=_("Image for the button."), ), "img_expand": dict(editor="imageentry"), "img_collapse": dict(editor="imageentry"), "initialdir": dict( buid=[_PathChoser_all, _PathChoser_old], help=h_initialdir ), "keyvariable": dict( buid=[_Combobox, _Combobox_old, _forms_PygubuCombobox], editor="tkvarentry", help=h_keyvariable, ), "label": dict(buid=_AccordionFrameGroup), "linewidth": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="choice", values=("1", "2", "3", "4"), state="readonly", default_value="1", ), "mask": dict(buid=f"{_plugin_uid}.Floodgauge"), "markbg": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="colorentry" ), "markfg": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="colorentry" ), "modal": [ dict( buid=[_Dialog, _Dialog_old], editor="choice", values=("true", "false"), state="readonly", help=h_modal, ), ], "month": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="choice", values=( "", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", ), state="readonly", ), "mustexist": dict( buid=[_PathChoser_all, _PathChoser_old], editor="choice", values=("", "true", "false"), state="readonly", default_value="true", help=h_mustexist, ), "path": dict( buid=[_PathChoser_all, _PathChoser_old], help=_("Initial path value."), ), "selectbg": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="colorentry" ), "selectfg": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="colorentry" ), "state": [ dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="choice", values=("", "normal", "disabled"), state="readonly", ), dict( buid=[_Combobox, _Combobox_old], editor="choice", values=("", "normal", "disabled"), state="readonly", help=h_state, ), dict( buid=_forms_PygubuCombobox, editor="choice", values=("", "normal", "disabled"), state="readonly", help=h_state, ), dict( buid=[_PathChoser_all, _PathChoser_old], editor="choice", values=("", "normal", "disabled", "readonly"), state="readonly", help=_("Path entry state."), ), dict( buid=_forms_Combobox, editor="choice", values=("", "normal", "disabled", "readonly"), state="readonly", ), ], "style": dict( buid=_AccordionFrameGroup, editor="ttkstylechoice", default_value="Toolbutton", ), "textvariable": [ dict( buid=[_PathChoserButton], editor="tkvarentry", help=_( "Tk variable whose value will be used in place of the text resource." ), ), dict( buid=[_PathChoser_all, _PathChoser_old], editor="tkvarentry", help=_("Tk variable associated to the path property."), ), ], "title": dict(buid=[_PathChoser_all, _PathChoser_old], help=h_title), "type": dict( buid=[_PathChoser_all, _PathChoser_old], editor="choice", values=(PathChooserInput.FILE, PathChooserInput.DIR), state="readonly", default_value=PathChooserInput.FILE, help=_("Dialog type"), # PathChooserInput ), "value": dict(buid=_ColorInput, editor="colorentry"), "values": [ dict( buid=[_Combobox, _Combobox_old, _forms_PygubuCombobox], help=h_values, ), ], "weight": [ dict(buid=_DockPane, editor="integernumber", help=h_weight), dict( buid=_DockWidget, editor="integernumber", help=_("The weight value of the widget in the pane"), ), ], "width": [ dict(buid=_AccordionFrame, editor="dimensionentry", default_value=200), dict(buid=_HideableFrame, editor="dimensionentry", default_value=200), dict( buid=_forms_FrameFormBuilder, editor="dimensionentry", default_value=200, ), dict(buid=_PathChoserButton, editor="integernumber"), dict( buid=[_Dialog, _Dialog_old], editor="dimensionentry", default_value=200, ), ], "year": dict( buid=[_CalendarFrame, _CalendarFrame_old], editor="naturalnumber" ), } for prop in plugin_properties: definitions = plugin_properties[prop] if isinstance(definitions, dict): definitions = [definitions] for definition in definitions: builders = definition.pop("buid", _builder_all) if isinstance(builders, str): builders = [builders] editor = definition.pop("editor", "entry") for builder_uid in builders: register_custom_property(builder_uid, prop, editor, **definition) pygubu-0.36.1/src/pygubu/plugins/pygubu/designer/toplevelframe.py000066400000000000000000000006331474524032100252350ustar00rootroot00000000000000import tkinter as tk from pygubu.api.v1 import BuilderObject, register_widget from .basehelpers import ( ToplevelPreviewFactory, ToplevelPreviewMixin, ToplevelPreviewBaseBO, ) ToplevelFramePreview = ToplevelPreviewFactory( "ToplevelFramePreview", (ToplevelPreviewMixin, tk.Frame, object), {}, ) class ToplevelFramePreviewBO(ToplevelPreviewBaseBO): class_ = ToplevelFramePreview pygubu-0.36.1/src/pygubu/plugins/pygubu/dialog_bo.py000066400000000000000000000033611474524032100225100ustar00rootroot00000000000000# encoding: utf-8 from pygubu.api.v1 import register_widget from pygubu.plugins.tk.tkstdwidgets import TKToplevel from pygubu.widgets.dialog import Dialog from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class DialogBO(TKToplevel): class_ = Dialog OPTIONS_STANDARD = TKToplevel.OPTIONS_STANDARD OPTIONS_SPECIFIC = TKToplevel.OPTIONS_SPECIFIC OPTIONS_CUSTOM = TKToplevel.OPTIONS_CUSTOM + ("modal",) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM virtual_events = ("<>",) def layout(self, target=None, *, forget=False): super(DialogBO, self).layout(self.widget.toplevel, forget=forget) def _set_property(self, target_widget, pname, value): if pname == "modal": modal = False value = value.lower() if value == "true": modal = True self.widget.set_modal(modal) else: super(DialogBO, self)._set_property( self.widget.toplevel, pname, value ) def get_child_master(self): return self.widget.toplevel # # Code generation methods # def code_child_master(self): return "{0}.toplevel".format(self.code_identifier()) def _code_set_property(self, targetid, pname, value, code_bag): if pname == "modal": code_bag[pname] = '"{0}"'.format(value) else: super(DialogBO, self)._code_set_property( targetid, pname, value, code_bag ) _builder_id = f"{_plugin_uid}.Dialog" register_widget( _builder_id, DialogBO, "Dialog", (_tab_widgets_label, "ttk"), group=0 ) # Register old name until removal register_widget("pygubu.builder.widgets.dialog", DialogBO, public=False) pygubu-0.36.1/src/pygubu/plugins/pygubu/dockfw_bo.py000066400000000000000000000070541474524032100225310ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk from pygubu.api.v1 import ( register_widget, BuilderObject, ) import pygubu.widgets.dockfw.widgets as widgets from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class DockWidgetBaseBO(BuilderObject): def _get_init_args(self, extra_init_args: dict = None): args = super()._get_init_args(extra_init_args) if "uid" not in args: args["uid"] = self.wmeta.identifier return args class DockFrameBO(DockWidgetBaseBO): class_ = widgets.DockFrame container = True container_layout = False maxchildren = 1 virtual_events = (widgets.DockFrame.EVENT_LAYOUT_CHANGED,) _builder_id = dockframe_uid = f"{_plugin_uid}.dockframe" register_widget( _builder_id, DockFrameBO, "DockFrame", ("ttk", _tab_widgets_label) ) class DockPaneBO(DockWidgetBaseBO): class_ = widgets.DockPane container = True container_layout = False layout_required = False properties = ("orient", "weight") ro_properties = properties allowed_parents = (dockframe_uid,) @classmethod def canbe_child_of(cls, parent_builder, classname): allowed = False if parent_builder in (DockFrameBO, DockPaneBO): allowed = True return allowed def __init__(self, builder, wmeta): super().__init__(builder, wmeta) self.pane_widget = None def realize(self, parent, extra_init_args: dict = None): self.widget: widgets.DockFrame = parent.widget args = self._get_init_args(extra_init_args) if not self.widget.main_pane: args["main_pane"] = True pweight = args.pop("weight", 1) self.pane_widget = self.widget.new_pane(**args) if isinstance(parent, DockPaneBO): parent.pane_widget.add_pane(self.pane_widget, weight=pweight) return self.widget def configure(self, target=None): pass def layout(self, target=None, *, forget=False): pass _builder_id = dockpane_uid = f"{_plugin_uid}.dockpane" DockPaneBO.add_allowed_child(_builder_id) register_widget( _builder_id, DockPaneBO, "DockPane", ("ttk", _tab_widgets_label) ) class DockWidgetBO(DockWidgetBaseBO): class_ = widgets.DockWidget container = True container_layout = True layout_required = False properties = ("grouped", "weight", "title") ro_properties = ("grouped", "weight") allowed_parents = (dockframe_uid, dockpane_uid) @classmethod def canbe_child_of(cls, parent_builder, classname): allowed = False if parent_builder is DockPaneBO: allowed = True return allowed def _process_property_value(self, pname, value): if pname == "grouped": return tk.getboolean(value) return super()._process_property_value(pname, value) def realize(self, parent, extra_init_args: dict = None): dock = parent.pane_widget.maindock args: dict = self._get_init_args(extra_init_args) grouped = args.pop("grouped", False) weight = args.pop("weight", 1) self.widget = dock.new_widget(**args) super().configure(target=self.widget) # hack used here parent.pane_widget.add_widget( self.widget, grouped=grouped, weight=weight ) def get_child_master(self): return self.widget.fcenter def configure(self, target=None): pass _builder_id = f"{_plugin_uid}.dockwidget" DockPaneBO.add_allowed_child(_builder_id) register_widget( _builder_id, DockWidgetBO, "DockWidget", ("ttk", _tab_widgets_label) ) pygubu-0.36.1/src/pygubu/plugins/pygubu/editabletreeview_bo.py000066400000000000000000000021061474524032100245710ustar00rootroot00000000000000# encoding: utf-8 from pygubu.api.v1 import register_widget from pygubu.plugins.ttk.ttkstdwidgets import ( TTKTreeviewBO, TTKTreeviewColumnBO, ) from .scrollbarhelper_bo import TTKSBHelperBO from pygubu.widgets.editabletreeview import EditableTreeview from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class EditableTreeviewBO(TTKTreeviewBO): class_ = EditableTreeview virtual_events = TTKTreeviewBO.virtual_events + ( "<>", "<>", "<>", ) _builder_uid = f"{_plugin_uid}.EditableTreeview" if _builder_uid not in TTKTreeviewColumnBO.allowed_parents: TTKTreeviewColumnBO.add_allowed_parent(_builder_uid) if _builder_uid not in TTKSBHelperBO.allowed_children: TTKSBHelperBO.add_allowed_child(_builder_uid) register_widget( _builder_uid, EditableTreeviewBO, "EditableTreeview", (_tab_widgets_label, "ttk"), ) # Register old name until removal register_widget( "pygubu.builder.widgets.editabletreeview", EditableTreeviewBO, public=False ) pygubu-0.36.1/src/pygubu/plugins/pygubu/filterabletreeview_bo.py000066400000000000000000000016231474524032100251340ustar00rootroot00000000000000# encoding: utf-8 from pygubu.api.v1 import register_widget from pygubu.plugins.ttk.ttkstdwidgets import ( TTKTreeviewBO, TTKTreeviewColumnBO, ) from .scrollbarhelper_bo import TTKSBHelperBO from pygubu.widgets.filterabletreeview import FilterableTreeview from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class FilterableTreeviewBO(TTKTreeviewBO): class_ = FilterableTreeview # virtual_events = TTKTreeviewBO.virtual_events + ( # "<>", # ) _builder_uid = f"{_plugin_uid}.FilterableTreeview" if _builder_uid not in TTKTreeviewColumnBO.allowed_parents: TTKTreeviewColumnBO.add_allowed_parent(_builder_uid) if _builder_uid not in TTKSBHelperBO.allowed_children: TTKSBHelperBO.add_allowed_child(_builder_uid) register_widget( _builder_uid, FilterableTreeviewBO, "FilterableTreeview", (_tab_widgets_label, "ttk"), ) pygubu-0.36.1/src/pygubu/plugins/pygubu/floodgauge_bo.py000066400000000000000000000011421474524032100233600ustar00rootroot00000000000000#!/usr/bin/python3 import tkinter as tk from pygubu.api.v1 import ( BuilderObject, register_widget, ) from pygubu.widgets.floodgauge import Floodgauge from pygubu.plugins.ttk.ttkstdwidgets import TTKProgressbar from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid # # Builder definition section # class FloodgaugeBO(TTKProgressbar): class_ = Floodgauge properties = TTKProgressbar.properties + ("mask", "text", "textvariable") _builder_uid = f"{_plugin_uid}.Floodgauge" register_widget( _builder_uid, FloodgaugeBO, "Floodgauge", ("ttk", _tab_widgets_label), ) pygubu-0.36.1/src/pygubu/plugins/pygubu/forms/000077500000000000000000000000001474524032100213425ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/pygubu/forms/__init__.py000066400000000000000000000000001474524032100234410ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/pygubu/forms/base.py000066400000000000000000000026341474524032100226330ustar00rootroot00000000000000import tkinter as tk from pygubu.api.v1 import BuilderObject from pygubu.i18n import _ _plugin_forms_uid = "pygubu.forms" _tab_form_widgets_label = _("Pygubu Forms") class WidgetBOMixin: """Manages base widget properties.""" FIELD_NAME_PROP = "field_name" base_properties = (FIELD_NAME_PROP,) # @classmethod # def properties(cls): # if isinstance(super().properties, tuple): # return super().properties + cls.base_properties # return super().properties() + cls.base_properties # @classmethod # def ro_properties(cls): # if isinstance(super().ro_properties, tuple): # return super().ro_properties + cls.base_properties # return super().ro_properties() + cls.base_properties def _get_init_args(self, extra_init_args: dict = None): args = super()._get_init_args(extra_init_args) name = args.get(self.FIELD_NAME_PROP, None) if not name: args[self.FIELD_NAME_PROP] = self.wmeta.identifier return args def _code_get_init_args(self, code_identifier): args = super()._code_get_init_args(code_identifier) field_name_value = args.get("_name", None) if not field_name_value: field_name_value = self.wmeta.identifier args[self.FIELD_NAME_PROP] = self._code_process_property_value( code_identifier, self.FIELD_NAME_PROP, field_name_value ) return args pygubu-0.36.1/src/pygubu/plugins/pygubu/forms/pygubuwidget_bo.py000066400000000000000000000015451474524032100251200ustar00rootroot00000000000000import tkinter as tk import pygubu.plugins.pygubu.combobox_bo as cbb import pygubu.forms.pygubuwidget as pygubuwidget from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, copy_custom_property, ) from .base import ( WidgetBOMixin, _plugin_forms_uid, _tab_form_widgets_label, ) _plugin_uid = f"{_plugin_forms_uid}.pygubuwidget" _designer_tabs = ("tk", _tab_form_widgets_label) class PygubuComboboxBO(WidgetBOMixin, cbb.ComboboxBO): class_ = pygubuwidget.PygubuCombobox properties = cbb.ComboboxBuilder.properties + WidgetBOMixin.base_properties ro_properties = ( cbb.ComboboxBuilder.ro_properties + WidgetBOMixin.base_properties ) _builder_uid = f"{_plugin_uid}.PygubuCombobox" register_widget( _builder_uid, PygubuComboboxBO, "PygubuCombobox", _designer_tabs, ) pygubu-0.36.1/src/pygubu/plugins/pygubu/forms/tkwidget_bo.py000066400000000000000000000017171474524032100242240ustar00rootroot00000000000000import tkinter as tk import pygubu.plugins.tk.tkstdwidgets as tkw import pygubu.plugins.ttk.ttkstdwidgets as ttkw import pygubu.forms.tkwidget as tkwidget from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from pygubu.i18n import _ from pygubu.plugins.pygubu.tkscrollbarhelper_bo import TKSBHelperBO from .base import ( WidgetBOMixin, _plugin_forms_uid, _tab_form_widgets_label, ) _plugin_uid = f"{_plugin_forms_uid}.tkwidget" _designer_tabs = ("tk", _tab_form_widgets_label) class TextBO(WidgetBOMixin, tkw.TKText): class_ = tkwidget.Text properties = tkw.TKText.properties + WidgetBOMixin.base_properties ro_properties = tkw.TKText.ro_properties + WidgetBOMixin.base_properties _builder_uid = f"{_plugin_uid}.Text" _tk_text_builder_uid = _builder_uid register_widget( _builder_uid, TextBO, "Text", _designer_tabs, group=0, ) TKSBHelperBO.add_allowed_child(_builder_uid) pygubu-0.36.1/src/pygubu/plugins/pygubu/forms/ttkwidget_bo.py000066400000000000000000000062111474524032100244020ustar00rootroot00000000000000import tkinter as tk import pygubu.forms.ttkwidget as ttkwidget import pygubu.plugins.tk.tkstdwidgets as tkw import pygubu.plugins.ttk.ttkstdwidgets as ttkw from pygubu.api.v1 import ( BuilderObject, register_widget, ) from pygubu.i18n import _ from .base import ( WidgetBOMixin, _plugin_forms_uid, _tab_form_widgets_label, ) from pygubu.plugins.pygubu.scrollbarhelper_bo import TTKSBHelperBO from .tkwidget_bo import _tk_text_builder_uid # Groups for ordering buttons in designer palette. GROUP0: int = 0 GROUP1: int = 10 GROUP2: int = 20 GROUP3: int = 30 _plugin_uid = f"{_plugin_forms_uid}.ttkwidget" _designer_tabs = ("ttk", _tab_form_widgets_label) # Register text as child of TTKSBHelperBO TTKSBHelperBO.add_allowed_child(_tk_text_builder_uid) class FrameFormBuilderBO(WidgetBOMixin, ttkw.TTKFrame): class_ = ttkwidget.FrameFormBuilder properties = ttkw.TTKFrame.properties + WidgetBOMixin.base_properties ro_properties = ttkw.TTKFrame.ro_properties + WidgetBOMixin.base_properties _builder_uid = f"{_plugin_uid}.FrameFormBuilder" register_widget( _builder_uid, FrameFormBuilderBO, "FrameFormBuilder", _designer_tabs, group=GROUP0, ) class EntryBO(WidgetBOMixin, ttkw.TTKEntry): class_ = ttkwidget.Entry properties = ttkw.TTKEntry.properties + WidgetBOMixin.base_properties ro_properties = ttkw.TTKEntry.ro_properties + WidgetBOMixin.base_properties _builder_uid = f"{_plugin_uid}.Entry" register_widget( _builder_uid, EntryBO, "Entry", _designer_tabs, group=GROUP0, ) class LabelWidgetInfoBO(WidgetBOMixin, ttkw.TTKLabel): class_ = ttkwidget.LabelWidgetInfo properties = ttkw.TTKLabel.properties + WidgetBOMixin.base_properties ro_properties = ttkw.TTKLabel.ro_properties + WidgetBOMixin.base_properties _builder_uid = f"{_plugin_uid}.LabelWidgetInfo" register_widget( _builder_uid, LabelWidgetInfoBO, "LabelWidgetInfo", _designer_tabs, group=GROUP1, ) class LabelBO(WidgetBOMixin, ttkw.TTKLabel): class_ = ttkwidget.Label properties = ttkw.TTKLabel.properties + WidgetBOMixin.base_properties ro_properties = ttkw.TTKLabel.ro_properties + WidgetBOMixin.base_properties _builder_uid = f"{_plugin_uid}.Label" register_widget( _builder_uid, LabelBO, "Label", _designer_tabs, group=GROUP0, ) class CheckbuttonBO(WidgetBOMixin, ttkw.TTKCheckbutton): class_ = ttkwidget.Checkbutton properties = ttkw.TTKCheckbutton.properties + WidgetBOMixin.base_properties ro_properties = ( ttkw.TTKCheckbutton.ro_properties + WidgetBOMixin.base_properties ) _builder_uid = f"{_plugin_uid}.Checkbutton" register_widget( _builder_uid, CheckbuttonBO, "Checkbutton", _designer_tabs, group=GROUP0, ) class ComboboxBO(WidgetBOMixin, ttkw.TTKCombobox): class_ = ttkwidget.Combobox properties = ttkw.TTKCombobox.properties + WidgetBOMixin.base_properties ro_properties = ( ttkw.TTKCombobox.ro_properties + WidgetBOMixin.base_properties ) _builder_uid = f"{_plugin_uid}.Combobox" register_widget( _builder_uid, ComboboxBO, "Combobox", _designer_tabs, group=GROUP0, ) pygubu-0.36.1/src/pygubu/plugins/pygubu/hideableframe_bo.py000066400000000000000000000006671474524032100240270ustar00rootroot00000000000000from pygubu.api.v1 import register_widget from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame from pygubu.widgets.hideableframe import HideableFrame from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class HideableFrameBO(TTKFrame): class_ = HideableFrame _builder_uid = f"{_plugin_uid}.hideableframe" register_widget( _builder_uid, HideableFrameBO, "HideableFrame", (_tab_widgets_label, "ttk"), ) pygubu-0.36.1/src/pygubu/plugins/pygubu/pathchooserinput_bo.py000066400000000000000000000036101474524032100246450ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk from pygubu.api.v1 import BuilderObject, register_widget from pygubu.plugins.ttk.ttkstdwidgets import TTKButton from pygubu.widgets.pathchooserinput import PathChooserInput, PathChooserButton from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class PathChooserBaseMixin: base_properties = ( "type", "path", "initialdir", "mustexist", "title", "defaultextension", ) def _process_property_value(self, pname, value): if pname == "mustexist": return tk.getboolean(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value: str): if pname == "mustexist": return self._process_property_value(pname, value) return super()._code_process_property_value(targetid, pname, value) class PathChooserInputBO(PathChooserBaseMixin, BuilderObject): class_ = PathChooserInput properties = PathChooserBaseMixin.base_properties + ( "image", "textvariable", "state", ) virtual_events = ("<>",) _builder_id = f"{_plugin_uid}.PathChooserInput" register_widget( _builder_id, PathChooserInputBO, "PathChooserInput", ("ttk", _tab_widgets_label), ) _old_bid = "pygubu.builder.widgets.pathchooserinput" register_widget( _old_bid, PathChooserInputBO, public=False, ) class PathChooserButtonBO(PathChooserBaseMixin, BuilderObject): class_ = PathChooserButton properties = PathChooserBaseMixin.base_properties + tuple( set(TTKButton.properties) - set(("command", "default")) ) virtual_events = ("<>",) _builder_id = f"{_plugin_uid}.PathChooserButton" register_widget( _builder_id, PathChooserButtonBO, "PathChooserButton", ("ttk", _tab_widgets_label), ) pygubu-0.36.1/src/pygubu/plugins/pygubu/scrollbarhelper_bo.py000066400000000000000000000036041474524032100244340ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk from pygubu.api.v1 import BuilderObject, register_widget from pygubu.widgets.scrollbarhelper import ScrollbarHelper from pygubu.plugins.pygubu import _tab_helpers_label, _plugin_uid class TTKSBHelperBO(BuilderObject): class_ = ScrollbarHelper container = True maxchildren = 1 allowed_children = ( "tk.Entry", "ttk.Entry", "tk.Text", "tk.Canvas", "tk.Listbox", "ttk.Treeview", ) OPTIONS_STANDARD = ("class_", "cursor", "takefocus", "style") OPTIONS_SPECIFIC = ("borderwidth", "relief", "padding", "height", "width") OPTIONS_CUSTOM = ("scrolltype", "usemousewheel") properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ro_properties = ("class_", "scrolltype") allow_bindings = False def get_child_master(self): return self.widget.container def add_child(self, bobject): cwidget = bobject.widget self.widget.add_child(cwidget) # # Code generation methods # def code_child_master(self): return "{0}.container".format(self.code_identifier()) def code_child_add(self, childid): lines = [] line = "{0}.add_child({1})".format(self.code_identifier(), childid) lines.append(line) return lines def _code_set_property(self, targetid, pname, value, code_bag): if pname == "usemousewheel": code_bag[pname] = "{0}".format(tk.getboolean(value)) else: super(TTKSBHelperBO, self)._code_set_property( targetid, pname, value, code_bag ) _builder_id = f"{_plugin_uid}.ScrollbarHelper" register_widget( _builder_id, TTKSBHelperBO, "ScrollbarHelper", (_tab_helpers_label, "ttk"), ) _builder_old = "pygubu.builder.widgets.scrollbarhelper" register_widget( _builder_old, TTKSBHelperBO, public=False, ) pygubu-0.36.1/src/pygubu/plugins/pygubu/scrolledframe_bo.py000066400000000000000000000047041474524032100240750ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk from pygubu.api.v1 import BuilderObject, register_widget from pygubu.widgets.scrolledframe import ScrolledFrame from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class TTKScrolledFrameBO(BuilderObject): class_ = ScrolledFrame container = True container_layout = True # maxchildren = 1 # allowed_children = ('tk.Frame', 'ttk.Frame' ) OPTIONS_STANDARD = ("class_", "cursor", "takefocus", "style") OPTIONS_SPECIFIC = ("borderwidth", "relief", "padding", "height", "width") OPTIONS_CUSTOM = ("scrolltype", "usemousewheel") properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ro_properties = ( "class_", "scrolltype", ) def get_child_master(self): return self.widget.innerframe def configure(self, target=None): super().configure(self.widget.innerframe) def _container_layout(self, target, container_manager, properties): super()._container_layout( self.widget.innerframe, container_manager, properties ) def _set_property(self, target_widget, pname, value): if pname in ("usemousewheel",): super(TTKScrolledFrameBO, self)._set_property( self.widget, pname, value ) else: super(TTKScrolledFrameBO, self)._set_property( target_widget, pname, value ) # # Code generation methods # def code_child_master(self): return "{0}.innerframe".format(self.code_identifier()) def code_configure(self, targetid=None): realtarget = "{0}.innerframe".format(self.code_identifier()) return super(TTKScrolledFrameBO, self).code_configure(realtarget) def _code_set_property(self, targetid, pname, value, code_bag): if pname == "usemousewheel": nvalue = "{0}.configure({1}={2})".format( self.code_identifier(), pname, tk.getboolean(value) ) code_bag[pname] = [nvalue] else: super(TTKScrolledFrameBO, self)._code_set_property( targetid, pname, value, code_bag ) _builder_id = f"{_plugin_uid}.ScrolledFrame" register_widget( _builder_id, TTKScrolledFrameBO, "ScrolledFrame", (_tab_widgets_label, "ttk"), group=0, ) _builder_old = "pygubu.builder.widgets.scrolledframe" register_widget( _builder_old, TTKScrolledFrameBO, public=False, ) pygubu-0.36.1/src/pygubu/plugins/pygubu/tkscrollbarhelper_bo.py000066400000000000000000000041401474524032100247670ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk from pygubu.api.v1 import BuilderObject, register_widget from pygubu.widgets.tkscrollbarhelper import TkScrollbarHelper from pygubu.plugins.pygubu import _tab_helpers_label, _plugin_uid class TKSBHelperBO(BuilderObject): class_ = TkScrollbarHelper container = True maxchildren = 1 allowed_children = ( "tk.Entry", "ttk.Entry", "tk.Text", "tk.Canvas", "tk.Listbox", "ttk.Treeview", ) OPTIONS_STANDARD = ( "borderwidth", "cursor", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "relief", "takefocus", ) OPTIONS_SPECIFIC = ( "background", "class_", "container", "height", "width", ) OPTIONS_CUSTOM = ("scrolltype", "usemousewheel") properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ro_properties = ("class_", "scrolltype") allow_bindings = False def get_child_master(self): return self.widget.container def add_child(self, bobject): cwidget = bobject.widget self.widget.add_child(cwidget) # # Code generation methods # def code_child_master(self): return "{0}.container".format(self.code_identifier()) def code_child_add(self, childid): lines = [] line = "{0}.add_child({1})".format(self.code_identifier(), childid) lines.append(line) return lines def _code_set_property(self, targetid, pname, value, code_bag): if pname == "usemousewheel": code_bag[pname] = "{0}".format(tk.getboolean(value)) else: super(TKSBHelperBO, self)._code_set_property( targetid, pname, value, code_bag ) _builder_id = f"{_plugin_uid}.TkScrollbarHelper" register_widget( _builder_id, TKSBHelperBO, "tk.ScrollbarHelper", (_tab_helpers_label, "tk"), ) _builder_old = "pygubu.builder.widgets.tkscrollbarhelper" register_widget( _builder_old, TKSBHelperBO, public=False, ) pygubu-0.36.1/src/pygubu/plugins/pygubu/tkscrolledframe_bo.py000066400000000000000000000052051474524032100244310ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk from pygubu.api.v1 import BuilderObject, register_widget from pygubu.widgets.tkscrolledframe import TkScrolledFrame from pygubu.plugins.pygubu import _tab_widgets_label, _plugin_uid class TKScrolledFrameBO(BuilderObject): class_ = TkScrolledFrame container = True container_layout = True # maxchildren = 1 # allowed_children = ('tk.Frame', 'ttk.Frame' ) OPTIONS_STANDARD = ( "borderwidth", "cursor", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "relief", "takefocus", ) OPTIONS_SPECIFIC = ( "background", "class_", "container", "height", "width", ) OPTIONS_CUSTOM = ("scrolltype", "usemousewheel") properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ro_properties = ("class_", "scrolltype") def get_child_master(self): return self.widget.innerframe def configure(self, target=None): super().configure(self.widget.innerframe) def _container_layout(self, target, container_manager, properties): super()._container_layout( self.widget.innerframe, container_manager, properties ) def _set_property(self, target_widget, pname, value): if pname in ("usemousewheel",): super(TKScrolledFrameBO, self)._set_property( self.widget, pname, value ) else: super(TKScrolledFrameBO, self)._set_property( target_widget, pname, value ) # # Code generation methods # def code_child_master(self): return "{0}.innerframe".format(self.code_identifier()) def code_configure(self, targetid=None): realtarget = "{0}.innerframe".format(self.code_identifier()) return super(TKScrolledFrameBO, self).code_configure(realtarget) def _code_set_property(self, targetid, pname, value, code_bag): if pname == "usemousewheel": nvalue = "{0}.configure({1}={2})".format( self.code_identifier(), pname, tk.getboolean(value) ) code_bag[pname] = [nvalue] else: super(TKScrolledFrameBO, self)._code_set_property( targetid, pname, value, code_bag ) _builder_id = f"{_plugin_uid}.TkScrolledFrame" register_widget( _builder_id, TKScrolledFrameBO, "TkScrolledFrame", (_tab_widgets_label, "tk"), group=0, ) _builder_old = "pygubu.builder.widgets.tkscrolledframe" register_widget( _builder_old, TKScrolledFrameBO, public=False, ) pygubu-0.36.1/src/pygubu/plugins/tk/000077500000000000000000000000001474524032100173175ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tk/__init__.py000066400000000000000000000016661474524032100214410ustar00rootroot00000000000000from pygubu.api.v1 import BuilderLoaderPlugin class StandardTKWidgetsLoader(BuilderLoaderPlugin): module_default = "pygubu.plugins.tk.tkstdwidgets" module_map = { module_default: tuple(), "pygubu.plugins.tk.scrolledtext_bo": ( "tk.ScrolledText", "pygubu.builder.widgets.tkinterscrolledtext", # old name ), } def do_activate(self) -> bool: return True def get_all_modules(self): return [m for m in self.module_map.keys()] def get_module_for(self, identifier: str) -> str: for module, identifiers in self.module_map.items(): if identifier in identifiers: return module return self.module_default def can_load(self, identifier: str) -> bool: for module, builders in self.module_map.items(): if identifier in builders: return True return identifier.startswith("tk.") pygubu-0.36.1/src/pygubu/plugins/tk/scrolledtext_bo.py000066400000000000000000000010111474524032100230560ustar00rootroot00000000000000# encoding: utf-8 from tkinter.scrolledtext import ScrolledText from pygubu.api.v1 import register_widget from pygubu.plugins.tk.tkstdwidgets import TKText from pygubu.i18n import _ class TkinterScrolledTextBO(TKText): class_ = ScrolledText register_widget( "tk.ScrolledText", TkinterScrolledTextBO, "tk.ScrolledText", (_("Control & Display"), "tk"), ) _builder_old = "pygubu.builder.widgets.tkinterscrolledtext" register_widget( _builder_old, TkinterScrolledTextBO, public=False, ) pygubu-0.36.1/src/pygubu/plugins/tk/tkstdwidgets.py000066400000000000000000001117201474524032100224130ustar00rootroot00000000000000# encoding: utf-8 import logging import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import BuilderObject, register_widget from pygubu.component.builderobject import ( CB_TYPES, EntryBaseBO, PanedWindowBO, PanedWindowPaneBO, OptionMenuBaseMixin, WmMixin, ) logger = logging.getLogger(__name__) # # tkinter widgets # has_tk_version_9 = tk.TkVersion >= 9 _v9_frame_options = ("backgroundimage", "tile") if has_tk_version_9 else tuple() class TKRootBO(WmMixin, BuilderObject): class_ = tk.Tk container = True layout_required = False container_layout = True allowed_parents = ("root",) properties = ( # OPTIONS_STANDARD "borderwidth", "cursor", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "relief", "takefocus", # OPTIONS_SPECIFIC "baseName", "className", "background", "height", "width", # WM OPTIONS "title", "geometry", "overrideredirect", "minsize", "maxsize", "resizable", "iconbitmap", "iconphoto", ) + _v9_frame_options ro_properties = ( "baseName", "className", ) register_widget("tk.Tk", TKRootBO, "tk.Tk", (_("Containers"), "tk", "ttk")) class TKToplevel(WmMixin, BuilderObject): class_ = tk.Toplevel container = True layout_required = False container_layout = True allowed_parents = ("root",) # maxchildren = 2 # A menu and a frame OPTIONS_STANDARD = ( "borderwidth", "cursor", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "relief", "takefocus", ) OPTIONS_SPECIFIC = ( "background", "class_", "container", "height", "width", ) + _v9_frame_options OPTIONS_CUSTOM = ( "title", "geometry", "overrideredirect", "minsize", "maxsize", "resizable", "iconbitmap", "iconphoto", ) ro_properties = ( "container", "class_", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM def realize(self, parent, extra_init_args: dict = None): args = self._get_init_args(extra_init_args) master = parent.get_child_master() if master is None and tk._default_root is None: self.widget = tk.Tk() else: self.widget = self.class_(master, **args) return self.widget register_widget( "tk.Toplevel", TKToplevel, "tk.Toplevel", (_("Containers"), "tk", "ttk") ) class TKFrame(BuilderObject): OPTIONS_STANDARD = ( "borderwidth", "cursor", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "relief", "takefocus", ) OPTIONS_SPECIFIC = ( "background", "class_", "container", "height", "width", ) + _v9_frame_options class_ = tk.Frame container = True container_layout = True properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC ro_properties = ("class_", "container") register_widget("tk.Frame", TKFrame, "tk.Frame", (_("Containers"), "tk")) class TKLabel(BuilderObject): OPTIONS_STANDARD = ( "activebackground", "activeforeground", "anchor", "background", "bitmap", "borderwidth", "compound", "cursor", "disabledforeground", "font", "foreground", "height", "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "padx", "pady", "relief", "takefocus", "text", "textvariable", "underline", "wraplength", ) OPTIONS_SPECIFIC = ("height", "state", "width") class_ = tk.Label container = False properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget("tk.Label", TKLabel, "tk.Label", (_("Control & Display"), "tk")) class TKLabelFrame(BuilderObject): class_ = tk.LabelFrame container = True container_layout = True OPTIONS_STANDARD = ( "borderwidth", "cursor", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "relief", "takefocus", "text", ) OPTIONS_SPECIFIC = ( "background", "class_", "height", "labelanchor", "width", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC ro_properties = ("class_",) register_widget( "tk.LabelFrame", TKLabelFrame, "tk.LabelFrame", (_("Containers"), "tk") ) class TKEntry(EntryBaseBO): OPTIONS_STANDARD = ( "background", "borderwidth", "cursor", "exportselection", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "justify", "relief", "selectbackground", "selectborderwidth", "selectforeground", "takefocus", "textvariable", "xscrollcommand", ) OPTIONS_SPECIFIC = ( "disabledbackground", "disabledforeground", "invalidcommand", "readonlybackground", "show", "state", "validate", "validatecommand", "width", ) OPTIONS_CUSTOM = ("text",) class_ = tk.Entry container = False properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM command_properties = ( "validatecommand", "invalidcommand", "xscrollcommand", ) register_widget("tk.Entry", TKEntry, "tk.Entry", (_("Control & Display"), "tk")) class TKButton(BuilderObject): class_ = tk.Button container = False OPTIONS_STANDARD = ( "activebackground", "activeforeground", "anchor", "background", "bitmap", "borderwidth", "compound", "cursor", "disabledforeground", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "padx", "pady", "relief", "repeatdelay", "repeatinterval", "takefocus", "text", "textvariable", "underline", "wraplength", ) OPTIONS_SPECIFIC = ( "command", "default", "height", "overrelief", "state", "width", ) properties = ( OPTIONS_STANDARD + OPTIONS_SPECIFIC + BuilderObject.OPTIONS_CUSTOM ) command_properties = ("command",) register_widget( "tk.Button", TKButton, "tk.Button", (_("Control & Display"), "tk") ) class TKCheckbutton(BuilderObject): class_ = tk.Checkbutton container = False OPTIONS_STANDARD = ( "activebackground", "activeforeground", "anchor", "background", "bitmap", "borderwidth", "compound", "cursor", "disabledforeground", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "padx", "pady", "relief", "takefocus", "text", "textvariable", "underline", "wraplength", ) OPTIONS_SPECIFIC = ( "command", "height", "indicatoron", "overrelief", "offrelief", "offvalue", "onvalue", "overrelief", "selectcolor", "selectimage", "state", "tristateimage", "tristatevalue", "variable", "width", ) properties = ( OPTIONS_STANDARD + OPTIONS_SPECIFIC + BuilderObject.OPTIONS_CUSTOM ) command_properties = ("command",) register_widget( "tk.Checkbutton", TKCheckbutton, "tk.Checkbutton", (_("Control & Display"), "tk"), ) class TKRadiobutton(BuilderObject): class_ = tk.Radiobutton container = False OPTIONS_STANDARD = ( "activebackground", "activeforeground", "anchor", "background", "bitmap", "borderwidth", "compound", "cursor", "disabledforeground", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "padx", "pady", "relief", "takefocus", "text", "textvariable", "underline", "wraplength", ) OPTIONS_SPECIFIC = ( "command", "height", "indicatoron", "overrelief", "offrelief", "overrelief", "selectcolor", "selectimage", "state", "tristateimage", "tristatevalue", "value", "variable", "width", ) properties = ( OPTIONS_STANDARD + OPTIONS_SPECIFIC + BuilderObject.OPTIONS_CUSTOM ) command_properties = ("command",) register_widget( "tk.Radiobutton", TKRadiobutton, "tk.Radiobutton", (_("Control & Display"), "tk"), ) class TKListbox(BuilderObject): class_ = tk.Listbox container = False OPTIONS_STANDARD = ( "background", "borderwidth", "cursor", "disabledforeground", "exportselection", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "justify", "relief", "selectbackground", "selectborderwidth", "selectforeground", "setgrid", "takefocus", "xscrollcommand", "yscrollcommand", ) OPTIONS_SPECIFIC = ( "activestyle", "height", "listvariable", "selectmode", "state", "width", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC command_properties = ("xscrollcommand", "yscrollcommand") register_widget( "tk.Listbox", TKListbox, "tk.Listbox", (_("Control & Display"), "tk") ) class TKText(BuilderObject): class_ = tk.Text container = False OPTIONS_STANDARD = ( "background", "borderwidth", "cursor", "exportselection", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "padx", "pady", "relief", "selectbackground", "selectborderwidth", "selectforeground", "setgrid", "takefocus", "xscrollcommand", "yscrollcommand", ) OPTIONS_SPECIFIC = ( "autoseparators", "blockcursor", "endline", "height", "inactiveselectbackground", "insertunfocussed", "maxundo", "spacing1", "spacing2", "spacing3", "startline", "state", "tabs", "tabstyle", "undo", "width", "wrap", ) OPTIONS_CUSTOM = ("text",) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM command_properties = ("xscrollcommand", "yscrollcommand") def _set_property(self, target_widget, pname, value): if pname == "text": state = target_widget.cget("state") if state == tk.DISABLED: target_widget.configure(state=tk.NORMAL) target_widget.delete("0.0", tk.END) target_widget.insert("0.0", value) target_widget.configure(state=tk.DISABLED) else: target_widget.delete("0.0", tk.END) target_widget.insert("0.0", value) else: super(TKText, self)._set_property(target_widget, pname, value) # # Code generation methods # def _code_set_property(self, targetid, pname, value, code_bag): if pname == "text": state_value = "" if "state" in self.wmeta.properties: state_value = self.wmeta.properties["state"] sval = self.builder.code_translate_str(value) lines = [ f"_text_ = {sval}", ] if state_value == tk.DISABLED: lines.extend( ( f'{targetid}.configure(state="normal")', f'{targetid}.insert("0.0", _text_)', f'{targetid}.configure(state="disabled")', ) ) else: lines.append(f'{targetid}.insert("0.0", _text_)') code_bag[pname] = lines else: super(TKText, self)._code_set_property( targetid, pname, value, code_bag ) register_widget( "tk.Text", TKText, "tk.Text", (_("Control & Display"), "tk", "ttk") ) class TKPanedWindow(PanedWindowBO): class_ = tk.PanedWindow allowed_children = ("tk.PanedWindow.Pane",) OPTIONS_STANDARD = ( "background", "borderwidth", "cursor", "orient", "relief", ) OPTIONS_SPECIFIC = ( "handlepad", "handlesize", "height", "opaqueresize", "proxybackground", "proxyborderwidth", "proxyrelief", "sashcursor", "sashpad", "sashrelief", "sashwidth", "showhandle", "width", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget( "tk.PanedWindow", TKPanedWindow, "tk.PanedWindow", (_("Containers"), "tk") ) class TKMenubutton(BuilderObject): class_ = tk.Menubutton container = True OPTIONS_STANDARD = ( "activebackground", "activeforeground", "anchor", "background", "bitmap", "borderwidth", "compound", "cursor", "disabledforeground", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "image", "justify", "padx", "pady", "relief", "takefocus", "text", "textvariable", "underline", "wraplength", ) OPTIONS_SPECIFIC = ( "direction", "height", "indicatoron", "state", "width", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC allowed_children = ("tk.Menu",) maxchildren = 1 def add_child(self, bobject): self.widget.configure(menu=bobject.widget) def code_child_add(self, childid): lines = [f"{self.code_identifier()}.configure(menu={childid})"] return lines register_widget( "tk.Menubutton", TKMenubutton, "tk.Menubutton", ( _("Menu"), _("Control & Display"), "tk", ), ) class TKMessage(BuilderObject): class_ = tk.Message container = False OPTIONS_STANDARD = ( "anchor", "background", "borderwidth", "cursor", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "padx", "pady", "relief", "takefocus", "text", "textvariable", ) OPTIONS_SPECIFIC = ("aspect", "justify", "width") properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget( "tk.Message", TKMessage, "tk.Message", (_("Control & Display"), "tk", "ttk") ) class TKScale(BuilderObject): class_ = tk.Scale container = False OPTIONS_STANDARD = ( "activebackground", "background", "borderwidth", "cursor", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "orient", "relief", "repeatdelay", "repeatinterval", "takefocus", "troughcolor", ) OPTIONS_SPECIFIC = ( "bigincrement", "command", "digits", "from_", "label", "length", "resolution", "showvalue", "sliderlength", "sliderrelief", "state", "tickinterval", "to", "variable", "width", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC command_properties = ("command",) register_widget("tk.Scale", TKScale, "tk.Scale", (_("Control & Display"), "tk")) class TKScrollbar(BuilderObject): class_ = tk.Scrollbar container = False OPTIONS_STANDARD = ( "activebackground", "background", "borderwidth", "cursor", "highlightbackground", "highlightcolor", "highlightthickness", "jump", "orient", "relief", "repeatdelay", "repeatinterval", "takefocus", "troughcolor", ) OPTIONS_SPECIFIC = ( "activerelief", "command", "elementborderwidth", "width", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC command_properties = ("command",) register_widget( "tk.Scrollbar", TKScrollbar, "tk.Scrollbar", (_("Control & Display"), "tk") ) class TKSpinbox(BuilderObject): class_ = tk.Spinbox container = False OPTIONS_STANDARD = ( "activebackground", "background", "borderwidth", "cursor", "exportselection", "font", "foreground", "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "justify", "relief", "repeatdelay", "repeatinterval", "selectbackground", "selectborderwidth", "selectforeground", "takefocus", "textvariable", "xscrollcommand", ) OPTIONS_SPECIFIC = ( "buttonbackground", "buttoncursor", "buttondownrelief", "buttonuprelief", "command", "disabledbackground", "disabledforeground", "format", "from_", "invalidcommand", "increment", "readonlybackground", "state", "to", "validate", "validatecommand", "values", "width", "wrap", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC command_properties = ( "command", "invalidcommand", "validatecommand", "xscrollcommand", ) def _set_property(self, target_widget, pname, value): # hack to configure 'from_' and 'to' and avoid exception if pname == "from_": from_ = float(value) to = float(self.wmeta.properties.get("to", 0)) if from_ > to: to = from_ + 1 target_widget.configure(from_=from_, to=to) else: super(TKSpinbox, self)._set_property(target_widget, pname, value) register_widget( "tk.Spinbox", TKSpinbox, "tk.Spinbox", (_("Control & Display"), "tk") ) class TKCanvas(BuilderObject): class_ = tk.Canvas container = False OPTIONS_STANDARD = ( "background", "borderwidth", "cursor", "highlightbackground", "highlightcolor", "highlightthickness", "insertbackground", "insertborderwidth", "insertofftime", "insertontime", "insertwidth", "relief", "selectbackground", "selectborderwidth", "selectforeground", "takefocus", "xscrollcommand", "yscrollcommand", ) OPTIONS_SPECIFIC = ( "closeenough", "confine", "height", "scrollregion", "state", "width", "xscrollincrement", "yscrollincrement", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC command_properties = ("xscrollcommand", "yscrollcommand") register_widget( "tk.Canvas", TKCanvas, "tk.Canvas", (_("Control & Display"), "tk", "ttk") ) class TKMenu(BuilderObject): layout_required = False allowed_parents = ( "root", "tk.Menubutton", "ttk.Menubutton", "pygubu.builder.widgets.toplevelmenu", ) allowed_children = ( "tk.Menuitem.Submenu", "tk.Menuitem.Checkbutton", "tk.Menuitem.Command", "tk.Menuitem.Radiobutton", "tk.Menuitem.Separator", ) class_ = tk.Menu container = True OPTIONS_STANDARD = ( "activebackground", "activeborderwidth", "activeforeground", "background", "borderwidth", "cursor", "disabledforeground", "font", "foreground", "relief", "takefocus", ) OPTIONS_SPECIFIC = ( "postcommand", "selectcolor", "tearoff", "tearoffcommand", "title", ) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC command_properties = ("postcommand", "tearoffcommand") allow_bindings = False def layout(self, target=None, *, forget=False): pass # # code generation functions # def _code_define_callback_args(self, cmd_pname, cmd): args = None if cmd_pname == "tearoffcommand": args = ("menu", "tearoff") return args register_widget("tk.Menu", TKMenu, "tk.Menu", (_("Menu"), "tk", "ttk")) # # Helpers for Standard tk widgets # class TKMenuitem(BuilderObject): class_ = None container = False itemtype = None layout_required = False OPTIONS_STANDARD = ( "activebackground", "activeforeground", "background", "bitmap", "compound", "foreground", "state", ) OPTIONS_SPECIFIC = ( "accelerator", "columnbreak", "command", "font", "hidemargin", "image", "label", "underline", ) properties = ( OPTIONS_STANDARD + OPTIONS_SPECIFIC + BuilderObject.OPTIONS_CUSTOM ) command_properties = ("command",) allow_bindings = False def realize(self, parent, extra_init_args: dict = None): self.widget = master = parent.get_child_master() itemproperties = dict(self.wmeta.properties) self._setup_item_properties(itemproperties) master.add(self.itemtype, **itemproperties) self._setup_item_index(parent) return self.widget def _setup_item_index(self, parent): master = parent.get_child_master() index = master.index(tk.END) or 0 # TODO: index of items is shifted if tearoff is changed # for now check tearoff config and recalculate index. has_tearoff = master.type(0) == "tearoff" tearoff_conf = parent.wmeta.properties.get("tearoff", "1") offset = 0 if has_tearoff and tearoff_conf in ("0", "false"): offset = 1 self.__index = index - offset def _setup_item_properties(self, itemprops): for pname in itemprops: if pname == "variable": varname = itemprops[pname] itemprops[pname] = self.builder.create_variable(varname) if pname in ("image", "selectimage"): name = itemprops[pname] itemprops[pname] = self.builder.get_image(name) def configure(self): pass def layout(self, target=None, *, forget=False): pass def _connect_command(self, cpname, callback): self.widget.entryconfigure(self.__index, command=callback) # # code generation functions # def code_realize(self, boparent, code_identifier=None): self._code_identifier = boparent.code_child_master() ( code_bag, kwproperties, complex_properties, ) = self._code_process_properties( self.wmeta.properties, self._code_identifier, skip_commands=False ) properties = {} for pname in kwproperties: properties[pname] = code_bag[pname] lines = [] # process commands first commands = super().code_connect_commands() lines.extend(commands) # create item item_lines = self._code_render_menuitem( boparent, self.wmeta.identifier, self.itemtype, properties ) lines.extend(item_lines) for pname in complex_properties: lines.extend(code_bag[pname]) return lines def _code_render_menuitem( self, parent_bo, itemid: str, itemtype: str, properties: dict ): lines = [] targetid = parent_bo.code_identifier() bag = [] for pname, value in properties.items(): s = f"{pname}={value}" bag.append(s) props = "" if bag: props = ", " + ", ".join(bag) line = f'{targetid}.add("{itemtype}"{props})' lines.append(line) return lines def code_configure(self, targetid=None): return tuple() def _code_set_property(self, targetid, pname, value, code_bag): if pname == "command_id_arg": # pass, property for command configuration pass else: super(TKMenuitem, self)._code_set_property( targetid, pname, value, code_bag ) def _pass_widgetid_to_callback(self): include_id = self.wmeta.properties.get( "command_id_arg", "false" ).lower() return include_id == "true" def _code_define_callback_args(self, cmd_pname, cmd): cmdtype = cmd["cbtype"] args = None if cmdtype == CB_TYPES.WITH_WID or self._pass_widgetid_to_callback(): args = ("itemid",) return args def _code_define_callback(self, cmd_pname, cmd): usercb = super()._code_define_callback(cmd_pname, cmd) self._realcb = usercb args = self._code_define_callback_args(cmd_pname, cmd) if args is not None and "itemid" in args: usercb = f"{self.wmeta.identifier}_cmd" return usercb def _code_connect_item_command(self, cmd_pname, cmd, cbname): lines = [] if cbname != self._realcb: cbname = self._realcb newcb = f"{self.wmeta.identifier}_cmd" wid = self.wmeta.identifier line = f'def {newcb}(itemid="{wid}"): {cbname}(itemid)' lines.append(line) return lines def _code_connect_command(self, cmd_pname, cmd, cbname): if self.itemtype != "submenu": return self._code_connect_item_command(cmd_pname, cmd, cbname) else: return super(TKMenuitem, self)._code_connect_command( cmd_pname, cmd, cbname ) def code_connect_commands(self): # Do nothing here because we already called in realize method. return tuple() class TKMenuitemSubmenu(TKMenuitem): container = True itemtype = "submenu" allowed_parents = ("tk.Menu", "tk.Menuitem.Submenu") allowed_children = ( "tk.Menuitem.Submenu", "tk.Menuitem.Checkbutton", "tk.Menuitem.Command", "tk.Menuitem.Radiobutton", "tk.Menuitem.Separator", ) OPTIONS_STANDARD = ( "activebackground", "activeborderwidth", "activeforeground", "background", "borderwidth", "bitmap", "compound", "cursor", "disabledforeground", "font", "foreground", "relief", "takefocus", "state", ) OPTIONS_SPECIFIC = ( "accelerator", "columnbreak", "hidemargin", "image", "label", "selectcolor", "tearoff", "tearoffcommand", "underline", "postcommand", ) OPTIONS_CUSTOM = ("specialmenu",) properties = tuple( set(OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM) ) # ro_properties = ('specialmenu', ) command_properties = ("postcommand", "tearoffcommand") def __init__(self, builder, wdescr): super().__init__(builder, wdescr) self._menuitems = None def realize(self, parent, extra_init_args: dict = None): master = parent.get_child_master() self._setup_item_index(parent) menu_properties = dict( (k, v) for k, v in self.wmeta.properties.items() if k in TKMenu.properties or k == "specialmenu" ) self._setup_item_properties(menu_properties) item_properties = dict( (k, v) for k, v in self.wmeta.properties.items() if k in TKMenuitem.properties ) self._setup_item_properties(item_properties) self.widget = submenu = TKMenu.class_(master, **menu_properties) item_properties["menu"] = submenu master.add(tk.CASCADE, **item_properties) return self.widget def _setup_item_properties(self, itemprops): super(TKMenuitemSubmenu, self)._setup_item_properties(itemprops) pname = "specialmenu" if pname in itemprops: specialmenu = itemprops.pop(pname) itemprops["name"] = specialmenu def configure(self): pass def layout(self, target=None, *, forget=False): pass def _connect_command(self, cpname, callback): # suported commands: tearoffcommand, postcommand kwargs = {cpname: callback} self.widget.configure(**kwargs) # # code generation functions # def code_realize(self, boparent, code_identifier=None): self._code_identifier = code_identifier masterid = boparent.code_child_master() lines = [] # menu properties menuprop = {} for pname, value in self.wmeta.properties.items(): if pname in TKMenu.properties: menuprop[pname] = value if pname == "specialmenu": menuprop["name"] = value ( code_bag, kw_properties, complex_properties, ) = self._code_process_properties(menuprop, self.code_identifier()) for pname in complex_properties: lines.extend(code_bag[pname]) mpbag = [] for pname in kw_properties: line = "{0}={1}".format(pname, code_bag[pname]) mpbag.append(line) mprops = "" if mpbag: mprops = ", " + ", ".join(mpbag) # item properties itemprop = {} for pname, value in self.wmeta.properties.items(): if pname in TKMenuitem.properties: itemprop[pname] = value ( code_bag, kw_properties, complex_properties, ) = self._code_process_properties(itemprop, self.code_identifier()) for pname in complex_properties: lines.extend(code_bag[pname]) pbag = [] prop = "menu={0}".format(self.code_identifier()) pbag.append(prop) for pname in kw_properties: line = "{0}={1}".format(pname, code_bag[pname]) pbag.append(line) props = "" if pbag: props = ", {0}".format(", ".join(pbag)) # creation line = "{0} = tk.Menu({1}{2})".format( self.code_identifier(), masterid, mprops ) lines.append(line) line = "{0}.add(tk.CASCADE{1})".format(masterid, props) lines.append(line) return lines def code_connect_commands(self): # Call code_connect_commands from BuilderObject because # I'm a subclass of TKMenuitem which has # code_connect_commands muted. return super(TKMenuitem, self).code_connect_commands() def code_configure(self, targetid=None): return tuple() def _code_define_callback_args(self, cmd_pname, cmd): args = None if cmd_pname == "tearoffcommand": args = ("menu", "tearoff") return args register_widget( "tk.Menuitem.Submenu", TKMenuitemSubmenu, "Menuitem.Submenu", (_("Menu"), "tk", "ttk"), ) class TKMenuitemCommand(TKMenuitem): allowed_parents = ("tk.Menu", "tk.Menuitem.Submenu") itemtype = tk.COMMAND register_widget( "tk.Menuitem.Command", TKMenuitemCommand, "Menuitem.Command", (_("Menu"), "tk", "ttk"), ) class TKMenuitemCheckbutton(TKMenuitem): allowed_parents = ("tk.Menu", "tk.Menuitem.Submenu") itemtype = tk.CHECKBUTTON OPTIONS_STANDARD = TKMenuitem.OPTIONS_STANDARD OPTIONS_SPECIFIC = TKMenuitem.OPTIONS_SPECIFIC + ( "indicatoron", "onvalue", "offvalue", "selectcolor", "selectimage", "variable", ) OPTIONS_CUSTOM = TKMenuitem.OPTIONS_CUSTOM properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM register_widget( "tk.Menuitem.Checkbutton", TKMenuitemCheckbutton, "Menuitem.Checkbutton", (_("Menu"), "tk", "ttk"), ) class TKMenuitemRadiobutton(TKMenuitem): allowed_parents = ("tk.Menu", "tk.Menuitem.Submenu") itemtype = tk.RADIOBUTTON OPTIONS_STANDARD = TKMenuitem.OPTIONS_STANDARD OPTIONS_SPECIFIC = TKMenuitem.OPTIONS_SPECIFIC + ( "indicatoron", "selectcolor", "selectimage", "value", "variable", ) OPTIONS_CUSTOM = TKMenuitem.OPTIONS_CUSTOM properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM register_widget( "tk.Menuitem.Radiobutton", TKMenuitemRadiobutton, "Menuitem.Radiobutton", (_("Menu"), "tk", "ttk"), ) class TKMenuitemSeparator(TKMenuitem): allowed_parents = ("tk.Menu", "tk.Menuitem.Submenu") itemtype = tk.SEPARATOR OPTIONS_STANDARD = ("background",) OPTIONS_SPECIFIC = tuple() OPTIONS_CUSTOM = tuple() properties = tuple() command_properties = tuple() register_widget( "tk.Menuitem.Separator", TKMenuitemSeparator, "Menuitem.Separator", (_("Menu"), "tk", "ttk"), ) class TKPanedWindowPane(PanedWindowPaneBO): class_ = None container = True allowed_parents = ("tk.PanedWindow",) maxchildren = 1 OPTIONS_SPECIFIC = ( "height", "hide", "minsize", "padx", "pady", "sticky", "stretch", "width", ) properties = OPTIONS_SPECIFIC register_widget( "tk.PanedWindow.Pane", TKPanedWindowPane, "tk.PanedWindow.Pane", (_("Pygubu Helpers"), "tk"), ) class TKLabelwidgetBO(BuilderObject): class_ = None container = True allowed_parents = ("tk.LabelFrame", "ttk.Labelframe") maxchildren = 1 layout_required = False allow_bindings = False def realize(self, parent, extra_init_args: dict = None): self.widget = parent.get_child_master() return self.widget def add_child(self, bobject): self.widget.configure(labelwidget=bobject.widget) def layout(self, target=None, *, forget=False): pass # # code generation functions # def code_realize(self, boparent, code_identifier=None): self._code_identifier = boparent.code_child_master() return tuple() def code_configure(self, targetid=None): return tuple() def code_layout(self, targetid=None, parentid=None): return tuple() def code_child_add(self, childid): line = "{0}.configure(labelwidget={1})" line = line.format(self.code_child_master(), childid) return (line,) register_widget( "pygubu.builder.widgets.Labelwidget", TKLabelwidgetBO, "Labelwidget", (_("Pygubu Helpers"), "tk", "ttk"), ) class ToplevelMenuHelperBO(BuilderObject): class_ = None container = True allowed_parents = ("tk.Tk", "tk.Toplevel") maxchildren = 1 layout_required = False allow_bindings = False def realize(self, parent, extra_init_args: dict = None): self.widget = parent.get_child_master() return self.widget def add_child(self, bobject): self.widget.configure(menu=bobject.widget) def layout(self, target=None, *, forget=False): pass # # code generation functions # def code_realize(self, boparent, code_identifier=None): self._code_identifier = boparent.code_child_master() return tuple() def code_configure(self, targetid=None): return tuple() def code_layout(self, targetid=None, parentid=None): return tuple() def code_child_add(self, childid): line = "{0}.configure(menu={1})" line = line.format(self.code_child_master(), childid) return (line,) register_widget( "pygubu.builder.widgets.toplevelmenu", ToplevelMenuHelperBO, "ToplevelMenu", (_("Menu"), _("Pygubu Helpers"), "tk", "ttk"), ) class TKOptionMenu(OptionMenuBaseMixin, BuilderObject): class_ = tk.OptionMenu properties = ("command", "variable", "value", "values") command_properties = ("command",) ro_properties = ("variable", "value", "values") register_widget( "tk.OptionMenu", TKOptionMenu, "tk.OptionMenu", (_("Control & Display"), "tk"), ) pygubu-0.36.1/src/pygubu/plugins/tkcalendar/000077500000000000000000000000001474524032100210115ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkcalendar/__init__.py000066400000000000000000000020761474524032100231270ustar00rootroot00000000000000import importlib from pygubu.i18n import _ from pygubu.api.v1 import BuilderLoaderPlugin _designer_tab_label = _("tkcalendar") _plugin_uid = "tkcalendar" class TkcalendarLoader(BuilderLoaderPlugin): module_map = { "pygubu.plugins.tkcalendar.calendar": (f"{_plugin_uid}.Calendar",), "pygubu.plugins.tkcalendar.dateentry": (f"{_plugin_uid}.DateEntry",), } def do_activate(self) -> bool: spec = importlib.util.find_spec("tkcalendar") return spec is not None def get_module_for(self, identifier: str) -> str: for module, identifiers in self.module_map.items(): if identifier in identifiers: return module return None def get_all_modules(self): return [m for m in self.module_map.keys()] def can_load(self, identifier: str) -> bool: return identifier.startswith("tkcalendar.") def get_designer_plugin(self): """Load class that implements IDesignerPlugin""" from .designer.designerplugin import TkcalendarPlugin return TkcalendarPlugin() pygubu-0.36.1/src/pygubu/plugins/tkcalendar/basecalendar.py000066400000000000000000000037751474524032100240030ustar00rootroot00000000000000import tkinter as tk from datetime import datetime from pygubu.api.v1 import BuilderObject class CalendarBaseBO(BuilderObject): OPTIONS_CUSTOM = ( "year", "month", "day", "firstweekday", "mindate", "maxdate", "showweeknumbers", "showothermonthdays", "date_pattern", "selectmode", "textvariable", "background", "foreground", "disabledbackground", "disabledforeground", "bordercolor", "headersbackground", "headersforeground", "selectbackground", "selectforeground", "disabledselectbackground", "disabledselectforeground", "normalbackground", "normalforeground", "weekendbackground", "weekendforeground", "othermonthforeground", "othermonthbackground", "othermonthweforeground", "othermonthwebackground", "disableddaybackground", "disableddayforeground", "tooltipforeground", "tooltipbackground", "tooltipalpha", "tooltipdelay", ) ro_properties = ("year", "month", "day") def _process_property_value(self, pname, value): if pname in ("year", "month", "day", "tooltipdelay"): return int(value) if pname in ("showweeknumbers", "showothermonthdays"): return tk.getboolean(value) if pname == "tooltipalpha": return float(value) if pname in ("mindate", "maxdate"): return datetime.strptime(value, "%Y-%m-%d") return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value: str): if pname in ("showweeknumbers", "showothermonthdays"): return self._process_property_value(pname, value) if pname in ("mindate", "maxdate"): return f"""Calendar.datetime.strptime("{value}", "%Y-%m-%d")""" return super()._code_process_property_value(targetid, pname, value) pygubu-0.36.1/src/pygubu/plugins/tkcalendar/calendar.py000066400000000000000000000013031474524032100231310ustar00rootroot00000000000000from pygubu.api.v1 import register_widget from tkcalendar import Calendar from ..tkcalendar import _designer_tab_label, _plugin_uid from .basecalendar import CalendarBaseBO class CalendarBO(CalendarBaseBO): class_ = Calendar OPTIONS_STANDARD = ("class_", "cursor") OPTIONS_SPECIFIC = ("borderwidth", "state") properties = ( OPTIONS_STANDARD + OPTIONS_SPECIFIC + CalendarBaseBO.OPTIONS_CUSTOM ) ro_properties = CalendarBaseBO.ro_properties + ("class_",) virtual_events = ("<>", "<>") _builder_id = f"{_plugin_uid}.Calendar" register_widget( _builder_id, CalendarBO, "Calendar", ("ttk", _designer_tab_label), ) pygubu-0.36.1/src/pygubu/plugins/tkcalendar/dateentry.py000066400000000000000000000013511474524032100233620ustar00rootroot00000000000000from pygubu.api.v1 import register_widget from pygubu.plugins.ttk.ttkstdwidgets import TTKEntry from tkcalendar import DateEntry from ..tkcalendar import _designer_tab_label, _plugin_uid from .basecalendar import CalendarBaseBO class DateEntryBO(CalendarBaseBO, TTKEntry): class_ = DateEntry OPTIONS_CUSTOM = CalendarBaseBO.OPTIONS_CUSTOM + ("calendar_cursor",) properties = ( TTKEntry.OPTIONS_STANDARD + TTKEntry.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = CalendarBaseBO.ro_properties + ("calendar_cursor",) virtual_events = ("<>",) _builder_id = f"{_plugin_uid}.DateEntry" register_widget( _builder_id, DateEntryBO, "DateEntry", ("ttk", _designer_tab_label), ) pygubu-0.36.1/src/pygubu/plugins/tkcalendar/designer/000077500000000000000000000000001474524032100226115ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkcalendar/designer/__init__.py000066400000000000000000000000001474524032100247100ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkcalendar/designer/designerplugin.py000066400000000000000000000006451474524032100262070ustar00rootroot00000000000000from pygubu.api.v1 import IDesignerPlugin from .properties import _calendar, _dateentry from .previewhelper import CalendarPreviewBO, DateEntryPreviewBO class TkcalendarPlugin(IDesignerPlugin): def get_preview_builder(self, builder_uid: str): if builder_uid == _calendar: return CalendarPreviewBO elif builder_uid == _dateentry: return DateEntryPreviewBO return None pygubu-0.36.1/src/pygubu/plugins/tkcalendar/designer/previewhelper.py000066400000000000000000000044141474524032100260470ustar00rootroot00000000000000from tkcalendar import Calendar, DateEntry from pygubu.plugins.tkcalendar.calendar import CalendarBO from pygubu.plugins.tkcalendar.dateentry import DateEntryBO class CalendarPreview(Calendar): """ Temporary fix for recreating theme on every <> event. """ ... def __init__(self, master=None, **kw): super().__init__(master, **kw) self.theme_change_cbid = None self._theme_name = None self._setup_style_fixed() self.bind("<>", self.schedule_style_update) def _setup_style(self, event=None): # Setup is ignored here on purpose. return None def _setup_style_fixed(self, event=None): super()._setup_style(event) self._theme_name = self.style.theme_use() def schedule_style_update(self, event=None): if self.theme_change_cbid is None: self.theme_change_cbid = self.after(10, self._on_theme_change) def _on_theme_change(self): theme = self.style.theme_use() if self._theme_name != theme: # the theme has changed, update the DateEntry style to look like a combobox self._theme_name = theme self._setup_style_fixed() self.theme_change_cbid = None class DateEntryPreview(DateEntry): """ Temporary fix for recreating theme on every <> event. """ def __init__(self, master=None, **kw): super().__init__(master, **kw) self.theme_change_cbid = None self.bind("<>", self.schedule_style_update) def _setup_style(self, event=None): super()._setup_style(event) self._theme_name = self.style.theme_use() def schedule_style_update(self, event=None): if self.theme_change_cbid is None: self.theme_change_cbid = self.after(10, self._on_theme_change) def _on_theme_change(self): theme = self.style.theme_use() if self._theme_name != theme: # the theme has changed, update the DateEntry style to look like a combobox self._theme_name = theme self._setup_style() self.theme_change_cbid = None class CalendarPreviewBO(CalendarBO): class_ = CalendarPreview class DateEntryPreviewBO(DateEntryBO): class_ = DateEntryPreview pygubu-0.36.1/src/pygubu/plugins/tkcalendar/designer/properties.py000066400000000000000000000075551474524032100253730ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import register_custom_property from pygubu.plugins.tkcalendar import _plugin_uid _builder_all = f"{_plugin_uid}.*" _calendar = f"{_plugin_uid}.Calendar" _dateentry = f"{_plugin_uid}.DateEntry" _none = {} _base_prop_desc = ( ( "year", "naturalnumber", {"help": _("intinitially displayed year, default is current year.")}, ), ( "month", "naturalnumber", {"help": _("initially displayed month, default is current month.")}, ), ("day", "naturalnumber", {"help": "initially selected day"}), ( "firstweekday", "choice", { "values": ("", "monday", "sunday"), "state": "readonly", "help": _("first day of the week"), }, ), ( "mindate", "entry", {"help": _("minimum allowed date (string with format yyyy-mm-dd)")}, ), ( "maxdate", "entry", {"help": _("maximum allowed date (string with format yyyy-mm-dd)")}, ), ( "showweeknumbers", "choice", { "values": ("", "true", "false"), "state": "readonly", "help": _("whether to display week numbers"), }, ), ( "showothermonthdays", "choice", { "values": ("", "true", "false"), "state": "readonly", "help": _( "whether to display the last days of the previous month and the first of the next month." ), }, ), ( "date_pattern", "choice", { "values": ( "", "dd/mm/yy", "dd/mm/yyyy", "mm/dd/yy", "mm/dd/yyyy", "yyyy/mm/dd", ), "state": "normal", "help": _("date pattern used to format the date as a string."), }, ), ( "selectmode", "choice", {"values": ("", "none", "day"), "state": "readonly"}, ), ( "textvariable", "tkvarentry", {"help": _("connect the currently selected date to the variable.")}, ), ("background", "colorentry", _none), ("foreground", "colorentry", _none), ("disabledbackground", "colorentry", _none), ("disabledforeground", "colorentry", _none), ("bordercolor", "colorentry", _none), ("headersbackground", "colorentry", _none), ("headersforeground", "colorentry", _none), ("selectbackground", "colorentry", _none), ("selectforeground", "colorentry", _none), ("disabledselectbackground", "colorentry", _none), ("disabledselectforeground", "colorentry", _none), ("normalbackground", "colorentry", _none), ("normalforeground", "colorentry", _none), ("weekendbackground", "colorentry", _none), ("weekendforeground", "colorentry", _none), ("othermonthforeground", "colorentry", _none), ("othermonthbackground", "colorentry", _none), ("othermonthweforeground", "colorentry", _none), ("othermonthwebackground", "colorentry", _none), ("disableddaybackground", "colorentry", _none), ("disableddayforeground", "colorentry", _none), ("tooltipforeground", "colorentry", _none), ("tooltipbackground", "colorentry", _none), ( "tooltipalpha", "spinbox", { "from_": 0.1, "to": 1, "increment": 0.1, "help": _("tooltip opacity between 0 and 1"), }, ), ( "tooltipdelay", "naturalnumber", {"help": _("delay in ms before displaying the tooltip")}, ), ) for pname, editor, options in _base_prop_desc: register_custom_property(_calendar, pname, editor, **options) register_custom_property(_dateentry, pname, editor, **options) register_custom_property( _dateentry, "calendar_cursor", "cursorentry", ) pygubu-0.36.1/src/pygubu/plugins/tkintermapview/000077500000000000000000000000001474524032100217525ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkintermapview/__init__.py000066400000000000000000000032161474524032100240650ustar00rootroot00000000000000import importlib from typing import Optional from pygubu.i18n import _ from pygubu.api.v1 import BuilderLoaderPlugin, IDesignerPlugin _designer_tab_label = _("TkinterMapView") _plugin_uid = "tkintermapview" class TkinterMapViewLoader(BuilderLoaderPlugin, IDesignerPlugin): _module = "pygubu.plugins.tkintermapview.mapview" # # IPluginBase interface methods # def do_activate(self) -> bool: spec = importlib.util.find_spec("tkintermapview") return spec is not None def get_designer_plugin(self) -> Optional[IDesignerPlugin]: return self # # IBuilderLoaderPlugin interface methods # def get_module_for(self, identifier: str) -> str: return self._module def get_all_modules(self): return (self._module,) def can_load(self, identifier: str) -> bool: return identifier.startswith("tkintermapview.") # # IDesignerPlugin interface methods # def configure_for_preview(self, builder_uid: str, widget): """Make widget just display with minimal functionality.""" if builder_uid.endswith(f"{_plugin_uid}.TkinterMapView"): def _no_op(event=None): pass seqlist = ( "", "", "", "", "", "", "", "", ) for seq in seqlist: widget.canvas.bind(seq, _no_op) widget.button_zoom_in.command = _no_op widget.button_zoom_out.command = _no_op pygubu-0.36.1/src/pygubu/plugins/tkintermapview/mapview.py000066400000000000000000000030651474524032100240000ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from tkintermapview import TkinterMapView from ..tkintermapview import _designer_tab_label, _plugin_uid class TkinterMapViewBuilder(BuilderObject): class_ = TkinterMapView _int_props = ("width", "height", "corner_radius", "max_zoom") OPTIONS_CUSTOM = _int_props + ("bg_color",) ro_properties = OPTIONS_CUSTOM def _process_property_value(self, pname, value): if pname in self._int_props: return int(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value: str): if pname in self._int_props: return self._process_property_value(pname, value) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.TkinterMapView" register_widget( _builder_uid, TkinterMapViewBuilder, "TkinterMapView", ("ttk", _designer_tab_label), ) _none = {} # pname, editor, options _properties = ( ("widht", "naturalnumber", _none), ("height", "naturalnumber", _none), ( "corner_radius", "choice", {"values": [""] + [x for x in range(0, 31)], "state": "readonly"}, ), ("bg_color", "colorentry", _none), ( "max_zoom", "choice", {"values": [x for x in range(1, 20)], "state": "readonly"}, ), ) for pname, editor, options in _properties: register_custom_property(_builder_uid, pname, editor, **options) pygubu-0.36.1/src/pygubu/plugins/tkintertable/000077500000000000000000000000001474524032100213715ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkintertable/__init__.py000066400000000000000000000031151474524032100235020ustar00rootroot00000000000000import importlib from typing import Optional from pygubu.i18n import _ from pygubu.api.v1 import BuilderLoaderPlugin, IDesignerPlugin _designer_tab_label = _("tkintertable") _plugin_uid = "tkintertable" class StandardTKWidgetsLoader(BuilderLoaderPlugin, IDesignerPlugin): _module = "pygubu.plugins.tkintertable.table" # # IPluginBase interface methods # def do_activate(self) -> bool: spec = importlib.util.find_spec("tkintertable") return spec is not None def get_designer_plugin(self) -> Optional[IDesignerPlugin]: return self # # IBuilderLoaderPlugin interface methods # def get_module_for(self, identifier: str) -> str: return self._module def get_all_modules(self): return (self._module,) def can_load(self, identifier: str) -> bool: return identifier.startswith("tkintertable.") # # IDesignerPlugin interface methods # def configure_for_preview(self, builder_uid: str, widget): """Make widget just display with minimal functionality.""" if builder_uid.endswith(f"{_plugin_uid}.TableCanvas"): def _no_op(event=None): pass seqlist = ( "", "", "", "", "", "" "", "", "", "", ) for seq in seqlist: widget.bind(seq, _no_op) pygubu-0.36.1/src/pygubu/plugins/tkintertable/table.py000066400000000000000000000110701474524032100230310ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from pygubu.utils.font import tkfontstr_to_tuple from tkintertable import TableCanvas from ..tkintertable import _designer_tab_label, _plugin_uid class TableCanvasBuilder(BuilderObject): class_ = TableCanvas OPTIONS_CUSTOM = ( "read_only", "width", "height", "bgcolor", "fgcolor", "rows", "cols", "cellwidth", "maxcellwidth", "rowheight", "horizlines", "vertlines", "alternaterows", "autoresizecols", "linewidth", "rowheaderwidth", "showkeynamesinheader", "thefont", "entrybackgr", "grid_color", "selectedcolor", "rowselectedcolor", "multipleselectioncolor", ) ro_properties = OPTIONS_CUSTOM layout_required = False def _process_property_value(self, pname, value): if pname in ( "rows", "cols", "cellwidth", "maxcellwidth", "rowheight", "rowheaderwidth", ): value = int(value) elif pname == "linewidth": value = float(value) elif pname in ("read_only", "showkeynamesinheader"): value = tk.getboolean(value) elif pname == "thefont": value = tkfontstr_to_tuple(value) elif pname in ( "horizlines", "vertlines", "alternaterows", "autoresizecols", ): value = int(tk.getboolean(value)) return value def layout(self, target=None, *, forget=False): self.widget.show() def code_layout(self, targetid=None, parentid=None): if targetid is None: targetid = self.code_identifier() return [f"{targetid}.show()"] def _code_process_property_value(self, targetid, pname, value: str): pvalue = self._process_property_value(pname, value) if pname in ("thefont", "showkeynamesinheader"): return pvalue pvalue = f"{pvalue}" return super()._code_process_property_value(targetid, pname, pvalue) _builder_uid = f"{_plugin_uid}.TableCanvas" register_widget( _builder_uid, TableCanvasBuilder, "TableCanvas", ("ttk", _designer_tab_label), ) register_custom_property( _builder_uid, "read_only", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "width", "dimensionentry", ) register_custom_property( _builder_uid, "height", "dimensionentry", ) register_custom_property( _builder_uid, "bgcolor", "colorentry", ) register_custom_property( _builder_uid, "fgcolor", "colorentry", ) register_custom_property( _builder_uid, "rows", "naturalnumber", ) register_custom_property( _builder_uid, "cols", "naturalnumber", ) register_custom_property( _builder_uid, "cellwidth", "naturalnumber", ) register_custom_property( _builder_uid, "maxcellwidth", "naturalnumber", ) register_custom_property( _builder_uid, "rowheight", "naturalnumber", ) register_custom_property( _builder_uid, "horizlines", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "vertlines", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "alternaterows", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "autoresizecols", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "linewidth", "realnumber", ) register_custom_property( _builder_uid, "rowheaderwidth", "naturalnumber", ) register_custom_property( _builder_uid, "showkeynamesinheader", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "thefont", "fontentry", ) register_custom_property( _builder_uid, "entrybackgr", "colorentry", ) register_custom_property( _builder_uid, "grid_color", "colorentry", ) register_custom_property( _builder_uid, "selectedcolor", "colorentry", ) register_custom_property( _builder_uid, "rowselectedcolor", "colorentry", ) register_custom_property( _builder_uid, "multipleselectioncolor", "colorentry", ) pygubu-0.36.1/src/pygubu/plugins/tkinterweb/000077500000000000000000000000001474524032100210575ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkinterweb/__init__.py000066400000000000000000000023631474524032100231740ustar00rootroot00000000000000import importlib from pygubu.i18n import _ from pygubu.api.v1 import BuilderLoaderPlugin _designer_tab_label = _("TkinterWeb") _plugin_uid = "tkinterweb" class TkinterwebLoader(BuilderLoaderPlugin): module_map = { "pygubu.plugins.tkinterweb.htmlwidgets": ( f"{_plugin_uid}.HtmlFrame", f"{_plugin_uid}.HtmlLabel", ), "pygubu.plugins.tkinterweb.extrawidgets": ( f"{_plugin_uid}.Notebook", f"{_plugin_uid}.ColourSelector", ), } def do_activate(self) -> bool: spec = importlib.util.find_spec("tkinterweb") return spec is not None def get_module_for(self, identifier: str) -> str: for module, identifiers in self.module_map.items(): if identifier in identifiers: return module return None def get_all_modules(self): return [m for m in self.module_map.keys()] def can_load(self, identifier: str) -> bool: return identifier.startswith("tkinterweb.") def get_designer_plugin(self): """Load class that implements IDesignerPlugin""" # Just load the module for properties definitions. from .designer.designerplugin import TkinterwebPlugin return None pygubu-0.36.1/src/pygubu/plugins/tkinterweb/designer/000077500000000000000000000000001474524032100226575ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkinterweb/designer/__init__.py000066400000000000000000000000001474524032100247560ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkinterweb/designer/designerplugin.py000066400000000000000000000002371474524032100262520ustar00rootroot00000000000000from pygubu.api.v1 import IPluginBase, IDesignerPlugin import pygubu.plugins.tkinterweb.designer.properties class TkinterwebPlugin(IDesignerPlugin): ... pygubu-0.36.1/src/pygubu/plugins/tkinterweb/designer/properties.py000066400000000000000000000033471474524032100254340ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import register_custom_property from pygubu.plugins.tkinterweb import _plugin_uid _builder_all = f"{_plugin_uid}.*" _htmlFrame = f"{_plugin_uid}.HtmlFrame" _htmlLabel = f"{_plugin_uid}.HtmlLabel" _notebook = f"{_plugin_uid}.Notebook" _colourSelector = f"{_plugin_uid}.ColourSelector" plugin_properties = { "colour": dict( buid=_colourSelector, editor="colorentry", default_value="white" ), "height": dict( buid=_notebook, editor="dimensionentry", default_value=200, ), "horizontal_scrollbar": dict( buid=_htmlFrame, editor="choice", values=("", "true", "false", "auto"), state="readonly", ), "messages_enabled": dict( buid=_builder_all, editor="choice", values=("", "false", "true"), state="readonly", help=_("Show debugging messages on console output."), ), "text": dict( buid=_htmlLabel, editor="text", ), "vertical_scrollbar": dict( buid=_htmlFrame, editor="choice", values=("", "true", "false", "auto"), state="readonly", ), "width": dict( buid=_notebook, editor="dimensionentry", default_value=200, ), } for prop in plugin_properties: definitions = plugin_properties[prop] if isinstance(definitions, dict): definitions = [definitions] for definition in definitions: builders = definition.pop("buid", _builder_all) if isinstance(builders, str): builders = [builders] editor = definition.pop("editor", "entry") for builder_uid in builders: register_custom_property(builder_uid, prop, editor, **definition) pygubu-0.36.1/src/pygubu/plugins/tkinterweb/extrawidgets.py000066400000000000000000000031301474524032100241400ustar00rootroot00000000000000import tkinter as tk from pygubu.api.v1 import BuilderObject, register_widget from pygubu.plugins.ttk.ttkstdwidgets import TTKNotebookTab from tkinterweb.utilities import Notebook, ColourSelector from ..tkinterweb import _designer_tab_label, _plugin_uid class NotebookBO(BuilderObject): class_ = Notebook container = True container_layout = True ro_properties = ("class_", "takefocus") properties = ( # ttk.Frame properties: "class_", "cursor", "style", "borderwidth", "relief", "padding", "height", "width", ) virtual_events = ("<>",) _builder_uid = f"{_plugin_uid}.Notebook" _notebook_uid = _builder_uid register_widget( _builder_uid, NotebookBO, "Notebook", ("ttk", _designer_tab_label), group=1 ) class NotebookPageBO(TTKNotebookTab): allowed_parents = (_notebook_uid,) children_layout_override = True properties = ( "state", "text", "image", "compound", "underline", ) _builder_uid = f"{_plugin_uid}.Notebook.Page" NotebookBO.add_allowed_child(_builder_uid) register_widget( _builder_uid, NotebookPageBO, "Notebook.Page", ("ttk", _designer_tab_label), group=1, ) class ColourSelectorBO(BuilderObject): class_ = ColourSelector properties = ("colour",) ro_properties = ("colour",) virtual_events = ("<>",) _builder_uid = f"{_plugin_uid}.ColourSelector" register_widget( _builder_uid, ColourSelectorBO, "ColourSelector", ("ttk", _designer_tab_label), group=2, ) pygubu-0.36.1/src/pygubu/plugins/tkinterweb/htmlwidgets.py000066400000000000000000000036741474524032100237760ustar00rootroot00000000000000import tkinter as tk # from pygubu.i18n import _ from pygubu.api.v1 import register_widget from tkinterweb import HtmlLabel, HtmlFrame from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame from ..tkinterweb import _designer_tab_label, _plugin_uid class HtmlFrameBO(TTKFrame): class_ = HtmlFrame container = False OPTIONS_CUSTOM = ( "messages_enabled", "vertical_scrollbar", "horizontal_scrollbar", ) properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = OPTIONS_CUSTOM def _process_property_value(self, pname, value): if pname == "messages_enabled": return tk.getboolean(value) if pname in ("vertical_scrollbar", "horizontal_scrollbar"): return value if value == "auto" else tk.getboolean(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value): if pname in self.OPTIONS_CUSTOM: newval = self._process_property_value(pname, value) return newval if isinstance(newval, bool) else f"'{newval}'" return super()._code_process_property_value(targetid, pname, value) def code_imports(self): return (("tkinterweb", "HtmlFrame"),) _builder_uid = f"{_plugin_uid}.HtmlFrame" register_widget( _builder_uid, HtmlFrameBO, "HtmlFrame", ("ttk", _designer_tab_label), group=0, ) class HtmlLabelBO(HtmlFrameBO): class_ = HtmlLabel OPTIONS_CUSTOM = ("messages_enabled", "text") properties = ( HtmlFrameBO.OPTIONS_STANDARD + HtmlFrameBO.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = OPTIONS_CUSTOM def code_imports(self): return (("tkinterweb", "HtmlLabel"),) _builder_uid = f"{_plugin_uid}.HtmlLabel" register_widget( _builder_uid, HtmlLabelBO, "HtmlLabel", ("ttk", _designer_tab_label), group=0, ) pygubu-0.36.1/src/pygubu/plugins/tkmt/000077500000000000000000000000001474524032100176605ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkmt/__init__.py000066400000000000000000000021431474524032100217710ustar00rootroot00000000000000import importlib from pygubu.i18n import _ from pygubu.api.v1 import BuilderLoaderPlugin _designer_tab_label = _("TkinterModernThemes") _plugin_uid = "tkmt" class TkmthemesLoader(BuilderLoaderPlugin): module_map = { "pygubu.plugins.tkmt.widgets": (f"{_plugin_uid}.ThemedTKinterFrame",), } def do_activate(self) -> bool: spec = importlib.util.find_spec("TKinterModernThemes") return spec is not None def get_module_for(self, identifier: str) -> str: for module, identifiers in self.module_map.items(): if identifier in identifiers: return module return None def get_all_modules(self): return [m for m in self.module_map.keys()] def can_load(self, identifier: str) -> bool: for module, builders in self.module_map.items(): if identifier in builders: return True return False def get_designer_plugin(self): """Load class that implements IDesignerPlugin""" from .designer.designerplugin import PygubuDesignerPlugin return PygubuDesignerPlugin() pygubu-0.36.1/src/pygubu/plugins/tkmt/base.py000066400000000000000000000131501474524032100211440ustar00rootroot00000000000000import tkinter as tk from typing import Mapping from TKinterModernThemes.WidgetFrame import WidgetFrame, Notebook, PanedWindow from pygubu.api.v1 import BuilderObject from pygubu.utils.datatrans import ListDTO from .codegen import TkmtWidgetCodeMixin def tkmt_to_tkwidget(widget): """Get underline tk widget.""" if isinstance(widget, tk.Widget): return widget if isinstance(widget, WidgetFrame): return widget.master if isinstance(widget, Notebook): return widget.notebook if isinstance(widget, PanedWindow): return widget.panedwindow return None # Groups for ordering buttons in designer palette. GROUP_ROOT = 0 GROUP_CONTAINER = 10 GROUP_DISPLAY = 30 GROUP_INPUT = 60 class CommandProxy: def __init__(self): self.command = None @property def __name__(self): return f"CommandProxy({self.command})" def __call__(self, *args): if self.command is not None: self.command(*args) class TkmtWidgetBO(TkmtWidgetCodeMixin, BuilderObject): allow_bindings = False layout_required = False pos_args = () kw_args = ("row", "col", "padx", "pady", "rowspan", "colspan", "sticky") args_to_list = ListDTO([], []) # To process args property def __init__(self, builder, wmeta): super().__init__(builder, wmeta) self.command_proxies: Mapping[str, CommandProxy] = {} self.make_resizable = None def realize(self, parent, extra_init_args: dict = None): master = parent.get_child_master() pbag = self._process_properties(tkmt_to_tkwidget(master)) kargs = self._get_keyword_args(pbag) args = self._get_positional_args(pbag) self.widget = self.class_(*args, **kargs) return self.widget def _process_properties(self, tkmaster: tk.Widget) -> dict: defaults = self._get_property_defaults(tkmaster) pbag = {} for pname in self.properties: if pname in self.wmeta.properties: pvalue = self.wmeta.properties[pname] pbag[pname] = self._process_property_value(pname, pvalue) elif pname in defaults: pbag[pname] = defaults[pname] self._post_process_properties(tkmaster, pbag) return pbag def _post_process_properties(self, tkmaster: tk.Widget, pbag: dict) -> None: pass def _get_keyword_args(self, bag: dict) -> dict: kargs = {} for pname in self.properties: if pname in self.kw_args and pname in bag: kargs[pname] = bag[pname] return kargs def _get_positional_args(self, bag: dict) -> list: args = [] for pname in self.pos_args: if pname in bag: value = bag[pname] args.append(value) return args def _get_property_defaults(self, master: tk.Widget = None) -> dict: return {} def configure(self, target=None): if target is None: target = self.widget for pname, value in self.wmeta.properties.items(): if pname not in self.pos_args and pname not in self.kw_args: self._set_property(target, pname, value) def _set_property(self, target_widget, pname, value): if pname == "makeResizable": self.make_resizable = value return super()._set_property(target_widget, pname, value) def configure_children(self, target=None): if target is None: target = self.widget if self.make_resizable is not None: if self.make_resizable == "all": target.makeResizable() elif self.make_resizable == "recursive": target.makeResizable(recursive=True, onlyFrames=False) else: target.makeResizable(recursive=False, onlyFrames=True) def _process_property_value(self, pname, value): if pname in self.command_properties: cmd_proxy = self.command_proxies.get(pname, None) if cmd_proxy is None: cmd_proxy = CommandProxy() self.command_proxies[pname] = cmd_proxy return cmd_proxy if pname in ("row", "col", "rowspan", "colspan"): return int(value) if pname in ("args", "validatecommandargs", "invalidcommandargs"): return self.args_to_list.transform(value) if pname in ("disabled", "usecommandlineargs", "useconfigfile"): return tk.getboolean(value) return super()._process_property_value(pname, value) def _connect_command(self, cmd_pname, callback): self.command_proxies[cmd_pname].command = callback def _code_process_property_value(self, targetid, pname, value: str): if pname in ("args", "validatecommandargs", "invalidcommandargs"): return str(self.args_to_list.transform(value)) if pname == "variable": # variables can be none in some constructors # avoid error when builder trys to create a tk variable. if value is None: return str(value) return super()._code_process_property_value(targetid, pname, value) class WidgetAsMethodBO(TkmtWidgetBO): master_add_method = None def realize(self, parent, extra_init_args: dict = None): master = parent.get_child_master() assert self.master_add_method is not None add_method = getattr(master, self.master_add_method) pbag = self._process_properties(tkmt_to_tkwidget(master)) kargs = self._get_keyword_args(pbag) args = self._get_positional_args(pbag) self.widget = add_method(*args, **kargs) return self.widget pygubu-0.36.1/src/pygubu/plugins/tkmt/codegen.py000066400000000000000000000036021474524032100216370ustar00rootroot00000000000000class TkmtWidgetCodeMixin: def code_configure(self, targetid=None): return [] def code_connect_commands(self): return [] def _code_process_pos_kw_properties(self, code_identifier): """Creates dict with properties values for code""" defaults = self._get_property_defaults() args = {} for rop in self.properties: value_is_set = False pvalue = None if rop in self.wmeta.properties: pvalue = self.wmeta.properties[rop] value_is_set = True elif rop in defaults: pvalue = defaults[rop] value_is_set = True if value_is_set: pvalue = self._code_process_property_value( code_identifier, rop, pvalue ) args[rop] = pvalue return args def code_realize(self, boparent, code_identifier=None): if code_identifier is not None: self._code_identifier = code_identifier lines = [] master = boparent.code_child_master() pbag = self._code_process_pos_kw_properties(code_identifier) kargs = self._get_keyword_args(pbag) args = self._get_positional_args(pbag) pos_args = "" if args: pos_args = ",".join(args) bag = [] for pname, value in kargs.items(): bag.append(f"{pname}={value}") kwargs = "" if bag: if args: kwargs = f""", {", ".join(bag)}""" else: kwargs = ", ".join(bag) s = f"{self.code_identifier()} = {self._code_class_name()}({pos_args}{kwargs})" if hasattr(self, "master_add_method"): method = self.master_add_method s = f"{self.code_identifier()} = {master}.{method}({pos_args}{kwargs})" lines.append(s) return lines pygubu-0.36.1/src/pygubu/plugins/tkmt/designer/000077500000000000000000000000001474524032100214605ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkmt/designer/__init__.py000066400000000000000000000000001474524032100235570ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tkmt/designer/designerplugin.py000066400000000000000000000054141474524032100250550ustar00rootroot00000000000000import tkinter as tk from pygubu.api.v1 import IDesignerPlugin from pygubu.utils.widget import crop_widget from pygubu.stockimage import StockRegistry, StockImageCache, StockImage import pygubu.plugins.tkmt.designer.preview as preview import pygubu.plugins.tkmt.designer.properties from .toplevelpreview import ThemedTKinterFrameTLPreview class PygubuDesignerPlugin(IDesignerPlugin): def get_preview_builder(self, builder_uid: str): if builder_uid.startswith("tkmt."): notused, class_name = builder_uid.split(".") preview_name = f"{class_name}PreviewBO" if hasattr(preview, preview_name): return getattr(preview, preview_name) # else: # print(f"Warning: {preview_name} NOT defined") return None def get_toplevel_preview_for( self, builder_uid: str, widget_id: str, builder, top_master ): top = None toplevel_uids = ("tkmt.ThemedTKinterFrame",) if builder_uid in toplevel_uids: ui = builder.uidefinition xpath = f".//object[@class='{builder_uid}']" node = ui.root.find(xpath) if node is not None: node.set("class", "tkmt.ThemedTKinterFrameTLPreview") # for a new tk root, create a diferent image cache: def on_root_created(root): image_cache = StockImageCache(root, StockImage.registry) builder.image_cache = image_cache builder.on_first_object = on_root_created top = builder.get_object(widget_id) return top # def configure_for_preview(self, builder_uid: str, widget): # """Make widget just display with minimal functionality.""" def ensure_visibility_in_preview(self, builder, selected_uid: str): """Ensure visibility of selected_uid in preview.""" xpath = ".//object[@class='tkmt.NotebookTab']" # find all tabs tabs = builder.uidefinition.root.findall(xpath) if tabs is None: return for tab in tabs: tab_id = tab.get("id") activate_tab = False # Check if this tab was clicked if tab_id == selected_uid: activate_tab = True else: # check if selected_uid is inside this tab xpath = f".//object[@id='{selected_uid}']" o = tab.find(xpath) if o is not None: activate_tab = True if activate_tab: tab_builder = builder.objects[tab_id] tab_widget = tab_builder.widget notebook = tab_widget.nametowidget(tab_widget.winfo_parent()) notebook.select(tab_widget) notebook.update() break pygubu-0.36.1/src/pygubu/plugins/tkmt/designer/preview.py000066400000000000000000000043341474524032100235170ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk import pygubu.plugins.tkmt.widgets as tkmt_builders from TKinterModernThemes.WidgetFrame import WidgetFrame from ..base import tkmt_to_tkwidget class ThemedTkFramePreview(ttk.Frame): def __init__( self, master, title: str, theme: str = "", mode: str = "", usecommandlineargs=True, useconfigfile=True, ): super().__init__(master, width=200, height=200) self.tkmt_widget = WidgetFrame(self, "Master Frame") class PreviewBaseMixin: def realize(self, parent, extra_init_args: dict = None): if self.class_ == ThemedTkFramePreview: self.widget = super().realize(parent, extra_init_args) self.tkmt_widget = self.widget.tkmt_widget else: self.tkmt_widget = super().realize(parent, extra_init_args) self.widget = tkmt_to_tkwidget(self.tkmt_widget) return self.widget def get_child_master(self): return self.tkmt_widget class ThemedTKinterFramePreviewBO( PreviewBaseMixin, tkmt_builders.ThemedTKinterFrameBO ): class_ = ThemedTkFramePreview layout_required = True pos_args = ( "master", "title", ) properties = pos_args + tkmt_builders.ThemedTKinterFrameBO.kw_args def _get_property_defaults(self, master: tk.Widget = None) -> dict: return {"master": master, "title": self.wmeta.identifier} class FramePreviewBO(PreviewBaseMixin, tkmt_builders.FrameBO): ... class LabelFramePreviewBO(PreviewBaseMixin, tkmt_builders.LabelFrameBO): ... class NotebookPreviewBO(PreviewBaseMixin, tkmt_builders.NotebookBO): ... class NotebookTabPreviewBO(PreviewBaseMixin, tkmt_builders.NotebookTabBO): def _set_property(self, target_widget, pname, value): return super()._set_property(self.tkmt_widget, pname, value) def configure_children(self, target=None): super().configure_children(self.tkmt_widget) class PanedWindowPreviewBO(PreviewBaseMixin, tkmt_builders.PanedWindowBO): ... class PanedWindowPanePreviewBO( PreviewBaseMixin, tkmt_builders.PanedWindowPaneBO ): ... class FrameNextColPreviewBO(PreviewBaseMixin, tkmt_builders.FrameNextColBO): ... pygubu-0.36.1/src/pygubu/plugins/tkmt/designer/properties.py000066400000000000000000000125331474524032100242320ustar00rootroot00000000000000from pygubu.api.v1 import register_custom_property from pygubu.plugins.tkmt import _designer_tab_label, _plugin_uid _builder_uid = f"{_plugin_uid}.*" tkmt_properties = { "args": { "editor": "json_entry", "help": "Args passsed to command property. In designer, a json list.", }, "col": {"editor": "naturalnumber"}, "colspan": {"editor": "naturalnumber", "min_value": 1}, "columnnames": { # Treeview "editor": "json_entry", "help": "Column header names. In designer, a json list of strings.", "json_type": list, "json_item_type": str, }, "columnwidths": { # Treeview "editor": "json_entry", "help": ("Width of each column, should be same size as columnnames." + " In designer, a json list of ints"), "json_type": list, "json_item_type": int, }, "command": {"editor": "simplecommandentry"}, "datacolumnnames": { # Treeview "editor": "json_entry", "help": """Defaults to columnnames, a mapping of columnnames to the data file. Should be same size as columnnames. In designer, a json list of strings.""", "json_type": list, "json_item_type": str, }, "data": { # Treeview "editor": "entry", "help": """The tree data. In designer, use a resource URI here, example: res://my_treeview_data""", }, "defaulttext": {"editor": "entry"}, # MenuButton "disabled": { "editor": "choice", "values": ("", "True"), "state": "readonly", }, "increment": {"editor": "realnumber"}, "invalidcommand": { "editor": "simplecommandentry", }, "invalidcommandargs": { "editor": "json_entry", "help": "Args passsed to invalidcommand property. In designer, a json list.", }, "lower": {"editor": "realnumber"}, "makeResizable": { "editor": "choice", "values": ("", "all", "recursive", "onlyframes"), "state": "readonly", }, "menu": { # MenuButton "buid": f"{_plugin_uid}.MenuButton", "editor": "entry", "help": "Use a resource URI here, example: res://my_menu", }, "mode": { "editor": "choice", "values": ("", "light", "dark"), "state": "readonly", }, "newframe": { # Treeview "editor": "choice", "values": ("", "True", "False"), "state": "readonly", }, "openkey": { # Treeview "editor": "alphanumentry", }, "orient": { "editor": "choice", "values": ("", "horizontal", "vertical"), "state": "readonly", }, "padx": { "editor": "twodimensionentry", }, "pady": { "editor": "twodimensionentry", }, "row": {"editor": "naturalnumber"}, "rowspan": {"editor": "naturalnumber", "min_value": 1}, "subentryname": {"editor": "entry"}, # Treeview "theme": { "editor": "choice", "values": ("", "azure", "sun-valley", "park"), "state": "readonly", }, "upper": {"editor": "realnumber"}, "usecommandlineargs": { "editor": "choice", "values": ("", "True", "False"), "state": "readonly", }, "useconfigfile": { "editor": "choice", "values": ("", "True", "False"), "state": "readonly", }, "validate": { "editor": "choice", "values": ( "", "all", "focus", "focusin", "focusout", "key", "none", ), "state": "readonly", }, "validatecommand": {"editor": "simplecommandentry"}, "validatecommandargs": { "editor": "json_entry", "help": "Args passsed to validatecommand property. In designer, a json list.", }, "validatecommandmode": { "editor": "choice", "values": ("", "%P", "%d %i %P %s %S %v %V %W"), # "state": "readonly", }, "values": [ { # OptionMenu "editor": "json_entry", "buid": f"{_plugin_uid}.OptionMenu", "help": "A json list of strings.", }, { # Combobox "editor": "json_entry", "buid": f"{_plugin_uid}.Combobox", "help": "A json list of strings.", }, { # NonnumericalSpinbox "editor": "json_entry", "buid": f"{_plugin_uid}.NonnumericalSpinbox", "help": "A json list of strings.", }, ], "variable": [ { "buid": f"{_plugin_uid}.Scale", "editor": "tkvarentry", "type_choices": ("int", "double"), "type_default": "int", }, { "buid": f"{_plugin_uid}.NumericalSpinbox", "editor": "tkvarentry", "type_choices": ("int", "double"), "type_default": "int", }, { "buid": f"{_plugin_uid}.NonnumericalSpinbox", "editor": "tkvarentry", "type_choices": ("string",), "type_default": "string", }, ], } for prop in tkmt_properties: definitions = tkmt_properties[prop] if isinstance(definitions, dict): definitions = [definitions] for definition in definitions: builder_uid = definition.pop("buid", _builder_uid) editor = definition.pop("editor", "entry") register_custom_property(builder_uid, prop, editor, **definition) pygubu-0.36.1/src/pygubu/plugins/tkmt/designer/toplevelpreview.py000066400000000000000000000044511474524032100252720ustar00rootroot00000000000000import sys import os import json import tkinter as tk import TKinterModernThemes as tkmt from pygubu.plugins.tkmt.widgets import ThemedTKinterFrameBO from pygubu.api.v1 import register_builder class ThemedTKinterFrameTLPreview(tkmt.ThemedTKinterFrame): def __init__( self, title: str, theme: str = "", mode: str = "", usecommandlineargs=True, useconfigfile=True, ): self.root: tk.Tk = tk.Tk() self.root.protocol("WM_DELETE_WINDOW", self.handleExit) self.root.title(title) # region Set Theme if usecommandlineargs: args = sys.argv if len(args) == 3: theme = args[1] mode = args[2] if useconfigfile: try: with open("themeconfig.json") as f: themeconfig = json.load(f) if "theme" in themeconfig: theme = themeconfig["theme"] if "mode" in themeconfig: mode = themeconfig["mode"] except (FileNotFoundError, json.JSONDecodeError): pass # no config file was specified if theme == "": theme = "park" if mode == "": mode = "dark" try: path = os.path.abspath( tkmt.__file__ + "/../themes/" + theme.lower() + "/" + theme.lower() + ".tcl" ) self.root.tk.call("source", path) except tk.TclError: pass # theme already loaded... self.root.tk.call("set_theme", mode.lower()) self.theme = theme.lower() self.mode = mode.lower() # endregion super(tkmt.ThemedTKinterFrame, self).__init__(self.root, "Master Frame") def handleExit(self): self.root.withdraw() def destroy(self): self.root.destroy() class ThemedTKinterFrameTLPreviewBO(ThemedTKinterFrameBO): class_ = ThemedTKinterFrameTLPreview register_builder( "tkmt.ThemedTKinterFrameTLPreview", ThemedTKinterFrameTLPreviewBO, "ThemedTKinterFrameTLPreview", ("ttk",), public=False, ) if __name__ == "__main__": test = ThemedTKinterFrameTLPreview("Preview Test") test.run() pygubu-0.36.1/src/pygubu/plugins/tkmt/widgets.py000066400000000000000000000501501474524032100217010ustar00rootroot00000000000000import os import json import tkinter as tk import TKinterModernThemes as tkmt import TKinterModernThemes.WidgetFrame as tkmt_widgets from pygubu.i18n import _ from pygubu.api.v1 import ( BuilderObject, register_widget, register_custom_property, ) from pygubu.utils.datatrans import ListDTO from ..tkmt import _designer_tab_label, _plugin_uid from .base import ( TkmtWidgetBO, WidgetAsMethodBO, CommandProxy, GROUP_ROOT, GROUP_CONTAINER, GROUP_DISPLAY, GROUP_INPUT, ) running_in_designer = os.getenv("PYGUBU_DESIGNER_RUNNING") class ThemedTKinterFrameBO(TkmtWidgetBO): allow_bindings = False layout_required = False allowed_parents = ("root",) class_ = tkmt.ThemedTKinterFrame container = True pos_args = ("title",) kw_args = ("theme", "mode", "usecommandlineargs", "useconfigfile") properties = pos_args + kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return {"title": self.wmeta.identifier} _builder_uid = f"{_plugin_uid}.ThemedTKinterFrame" _themedtkinterframe = _builder_uid register_widget( _builder_uid, ThemedTKinterFrameBO, "ThemedTKinterFrame", ("ttk", _designer_tab_label), GROUP_ROOT, ) class FrameBO(WidgetAsMethodBO): container = True master_add_method = "addFrame" pos_args = ("name",) properties = pos_args + WidgetAsMethodBO.kw_args def _get_property_defaults(self, master: tk.Widget = None) -> dict: return {"name": self.wmeta.identifier} _builder_uid = f"{_plugin_uid}.Frame" _frame = _builder_uid register_widget( _builder_uid, FrameBO, "Frame", ("ttk", _designer_tab_label), GROUP_CONTAINER, ) FrameBO.add_allowed_parent(_themedtkinterframe) FrameBO.add_allowed_parent(_frame) class LabelFrameBO(WidgetAsMethodBO): container = True master_add_method = "addLabelFrame" pos_args = ("text",) properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return {"text": self.wmeta.identifier} _builder_uid = f"{_plugin_uid}.LabelFrame" _labelframe = _builder_uid register_widget( _builder_uid, LabelFrameBO, "LabelFrame", ("ttk", _designer_tab_label), GROUP_CONTAINER, ) FrameBO.add_allowed_parent(_themedtkinterframe) FrameBO.add_allowed_parent(_frame) FrameBO.add_allowed_parent(_labelframe) class FrameNextColBO(BuilderObject): allow_bindings = False layout_required = False def realize(self, parent, extra_init_args: dict = None): self.widget = parent.widget master = parent.get_child_master() master.nextCol() return self.widget def code_realize(self, boparent, code_identifier=None): lines = [] master = boparent.code_child_master() s = f"{master}.nextCol()" lines.append(s) return lines _builder_uid = f"{_plugin_uid}.FrameNextCol" register_widget( _builder_uid, FrameNextColBO, "Frame.NextCol", ("ttk", _designer_tab_label), GROUP_CONTAINER, ) FrameNextColBO.add_allowed_parent(_themedtkinterframe) FrameNextColBO.add_allowed_parent(_frame) FrameNextColBO.add_allowed_parent(_labelframe) class SeparatorBO(WidgetAsMethodBO): master_add_method = "Seperator" _builder_uid = f"{_plugin_uid}.Separator" register_widget( _builder_uid, SeparatorBO, "Separator", ("ttk", _designer_tab_label), GROUP_DISPLAY, ) class ButtonBO(WidgetAsMethodBO): master_add_method = "Button" pos_args = ("text", "command") kw_args = ("args",) + WidgetAsMethodBO.kw_args properties = pos_args + kw_args ro_properties = properties command_properties = ("command",) def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "text": self.wmeta.identifier, "command": None, } _builder_uid = f"{_plugin_uid}.Button" register_widget( _builder_uid, ButtonBO, "Button", ("ttk", _designer_tab_label), GROUP_INPUT ) class AccentButtonBO(ButtonBO): master_add_method = "AccentButton" _builder_uid = f"{_plugin_uid}.AccentButton" register_widget( _builder_uid, AccentButtonBO, "AccentButton", ("ttk", _designer_tab_label), GROUP_INPUT, ) class CheckbuttonBO(WidgetAsMethodBO): master_add_method = "Checkbutton" pos_args = ("text", "variable") kw_args = ("command", "args", "disabled") + WidgetAsMethodBO.kw_args properties = pos_args + kw_args ro_properties = properties command_properties = ("command",) def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "text": self.wmeta.identifier, "variable": None, } _builder_uid = f"{_plugin_uid}.Checkbutton" register_widget( _builder_uid, CheckbuttonBO, "Checkbutton", ("ttk", _designer_tab_label), GROUP_INPUT, ) class ToggleButtonBO(CheckbuttonBO): master_add_method = "ToggleButton" _builder_uid = f"{_plugin_uid}.ToggleButton" register_widget( _builder_uid, ToggleButtonBO, "ToggleButton", ("ttk", _designer_tab_label), GROUP_INPUT, ) class SlideSwitchBO(CheckbuttonBO): master_add_method = "SlideSwitch" _builder_uid = f"{_plugin_uid}.SlideSwitch" register_widget( _builder_uid, SlideSwitchBO, "SlideSwitch", ("ttk", _designer_tab_label), GROUP_INPUT, ) class RadiobuttonBO(CheckbuttonBO): master_add_method = "Radiobutton" pos_args = ("text", "variable", "value") properties = pos_args + CheckbuttonBO.kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "text": self.wmeta.identifier, "variable": None, "value": 0, } _builder_uid = f"{_plugin_uid}.Radiobutton" register_widget( _builder_uid, RadiobuttonBO, "Radiobutton", ("ttk", _designer_tab_label), GROUP_INPUT, ) class EntryBO(WidgetAsMethodBO): master_add_method = "Entry" pos_args = ("textvariable",) kw_args = ( "validatecommand", "validatecommandargs", "validatecommandmode", "invalidcommand", "invalidcommandargs", "validate", ) + WidgetAsMethodBO.kw_args properties = pos_args + kw_args ro_properties = properties command_properties = ("validatecommand", "invalidcommand") def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "textvariable": None, } _builder_uid = f"{_plugin_uid}.Entry" register_widget( _builder_uid, EntryBO, "Entry", ("ttk", _designer_tab_label), GROUP_INPUT ) class NumericalSpinboxBO(WidgetAsMethodBO): master_add_method = "NumericalSpinbox" pos_args = ("lower", "upper", "increment", "variable") properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "lower": 0, "upper": 10, "increment": 1, "variable": None, } def _process_property_value(self, pname, value): if pname in ("lower", "upper", "increment"): return float(value) return super()._process_property_value(pname, value) _builder_uid = f"{_plugin_uid}.NumericalSpinbox" register_widget( _builder_uid, NumericalSpinboxBO, "NumericalSpinbox", ("ttk", _designer_tab_label), GROUP_INPUT, ) class NonnumericalSpinboxBO(WidgetAsMethodBO): master_add_method = "NonnumericalSpinbox" pos_args = ("values", "variable") properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties json_to_list = ListDTO([], ["values should be a json list"]) def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "values": [], "variable": None, } def _process_property_value(self, pname, value): if pname == "values": return self.json_to_list.transform(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value: str): if pname == "values": return str(self.json_to_list.transform(value)) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.NonnumericalSpinbox" register_widget( _builder_uid, NonnumericalSpinboxBO, "NonnumericalSpinbox", ("ttk", _designer_tab_label), GROUP_INPUT, ) class TreeviewBO(WidgetAsMethodBO): master_add_method = "Treeview" pos_args = ("columnnames", "columnwidths", "height", "data", "subentryname") kw_args = ( "datacolumnnames", "openkey", "anchor", "newframe", ) + WidgetAsMethodBO.kw_args properties = pos_args + kw_args ro_properties = properties json_to_colname = ListDTO([], ["values should be a json list"]) json_to_colwidth = ListDTO([], []) def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "columnnames": [], "columnwidths": [], "height": 5, "data": {}, "subentryname": "", } def _process_property_value(self, pname, value): if pname in ("columnnames", "datacolumnnames"): return self.json_to_colname.transform(value) if pname == "columnwidths": return self.json_to_colwidth.transform(value) if pname == "newframe": return tk.getboolean(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value: str): if pname in ("columnnames", "datacolumnnames"): return str(self.json_to_colname.transform(value)) if pname == "columnwidths": return str(self.json_to_colwidth.transform(value)) return super()._code_process_property_value(targetid, pname, value) def _post_process_properties(self, tkmaster: tk.Widget, pbag: dict) -> None: if running_in_designer: self._fix_initargs_in_designer(pbag) def _fix_initargs_in_designer(self, kargs: dict) -> None: key_names = "columnnames" key_widths = "columnwidths" key_datacn = "datacolumnnames" # Fix names and widths if key_names in kargs and key_widths in kargs: name_count = len(kargs[key_names]) width_count = len(kargs[key_widths]) if width_count > name_count: kargs[key_widths] = kargs[key_widths][:name_count] if width_count < name_count: diff = name_count - width_count for i in range(0, diff): kargs[key_widths].append(100) elif key_names in kargs and key_widths not in kargs: name_count = len(kargs[key_names]) kargs[key_widths] = [] for i in range(0, name_count): kargs[key_widths].append(100) elif key_names not in kargs and key_widths in kargs: width_count = len(kargs[key_widths]) kargs[key_names] = [] for i in range(0, width_count): kargs[key_names].append(f"column {i+1}") # fix datacolumnnames if we are in designer editing. if key_datacn in kargs: name_count = len(kargs[key_names]) dcn_count = len(kargs[key_datacn]) if name_count != dcn_count: # just clear data names until user enter correct ones kargs[key_datacn] = None # Fix data if we are in designer and no resource is set. key_data = "data" if key_data in kargs: kargs[key_data] = {} _builder_uid = f"{_plugin_uid}.Treeview" register_widget( _builder_uid, TreeviewBO, "Treeview", ("ttk", _designer_tab_label), GROUP_DISPLAY, ) class OptionMenuBO(WidgetAsMethodBO): master_add_method = "OptionMenu" pos_args = ("values", "variable") kw_args = ("command", "args") + WidgetAsMethodBO.kw_args properties = pos_args + kw_args ro_properties = properties command_properties = ("command",) jlist_values = ListDTO([], ["values should be a json list"]) def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "values": ["Test Item"], "variable": tk.StringVar(master), } def _process_property_value(self, pname, value): if pname == "values": return self.jlist_values.transform(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value: str): if pname == "values": return str(self.jlist_values.transform(value)) return super()._code_process_property_value(targetid, pname, value) def _code_define_callback_args(self, cmd_pname, cmd): # arg for command callback fargs = ["value"] user_args = self.wmeta.properties.get("args", None) if user_args: user_args = self._process_property_value("args", user_args) if user_args: for idx, arg in enumerate(user_args): fargs.insert(0, f"arg{idx}") return fargs _builder_uid = f"{_plugin_uid}.OptionMenu" register_widget( _builder_uid, OptionMenuBO, "OptionMenu", ("ttk", _designer_tab_label), GROUP_INPUT, ) class ComboboxBO(WidgetAsMethodBO): master_add_method = "Combobox" pos_args = ("values", "variable") properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties jlist_values = ListDTO([], ["values should be a json list"]) def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "values": ["Test Item"], "variable": tk.StringVar(master), } def _process_property_value(self, pname, value): if pname == "values": return self.jlist_values.transform(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value: str): if pname == "values": return str(self.jlist_values.transform(value)) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.Combobox" register_widget( _builder_uid, ComboboxBO, "Combobox", ("ttk", _designer_tab_label), GROUP_INPUT, ) class MenuButtonBO(WidgetAsMethodBO): master_add_method = "MenuButton" pos_args = ("menu", "defaulttext") properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "menu": None, "defaulttext": "MenuButton", } _builder_uid = f"{_plugin_uid}.MenuButton" register_widget( _builder_uid, MenuButtonBO, "MenuButton", ("ttk", _designer_tab_label), GROUP_INPUT, ) class NotebookBO(WidgetAsMethodBO): master_add_method = "Notebook" pos_args = ("name",) properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties container = True def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "name": self.wmeta.identifier, } _builder_uid = f"{_plugin_uid}.Notebook" _notebook = _builder_uid register_widget( _builder_uid, NotebookBO, "Notebook", ("ttk", _designer_tab_label), GROUP_CONTAINER, ) class NotebookTabBO(WidgetAsMethodBO): master_add_method = "addTab" pos_args = ("text",) kw_args = () properties = pos_args + ("makeResizable",) ro_properties = pos_args container = True def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "text": self.wmeta.identifier, } _builder_uid = f"{_plugin_uid}.NotebookTab" _notebok_tab = _builder_uid register_widget( _builder_uid, NotebookTabBO, "Notebook.Tab", ("ttk", _designer_tab_label), GROUP_CONTAINER, ) NotebookBO.add_allowed_child(_notebok_tab) NotebookTabBO.add_allowed_parent(_notebook) class PanedWindowBO(WidgetAsMethodBO): master_add_method = "PanedWindow" pos_args = ("name", "orient") properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties container = True def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "name": self.wmeta.identifier, } _builder_uid = f"{_plugin_uid}.PanedWindow" _panedwindow = _builder_uid register_widget( _builder_uid, PanedWindowBO, "PanedWindow", ("ttk", _designer_tab_label), GROUP_CONTAINER, ) class PanedWindowPaneBO(WidgetAsMethodBO): master_add_method = "addWindow" pos_args = ("weight",) properties = pos_args ro_properties = properties container = True def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "weight": 1, } _builder_uid = f"{_plugin_uid}.PanedWindowPane" _panedwindow_pane = _builder_uid register_widget( _builder_uid, PanedWindowPaneBO, "PanedWindow.Pane", ("ttk", _designer_tab_label), GROUP_CONTAINER, ) PanedWindowBO.add_allowed_child(_panedwindow_pane) PanedWindowPaneBO.add_allowed_parent(_panedwindow) class BlankBO(WidgetAsMethodBO): master_add_method = "Blank" pos_args = ("name",) kw_args = ("row", "col", "rowspan", "colspan") properties = pos_args + kw_args ro_properties = properties def realize(self, parent, extra_init_args: dict = None): super().realize(parent, extra_init_args) self.widget = parent.widget return self.widget def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "name": "Blank", } _builder_uid = f"{_plugin_uid}.Blank" register_widget( _builder_uid, BlankBO, "Blank", ("ttk", _designer_tab_label), GROUP_DISPLAY ) class LabelBO(WidgetAsMethodBO): master_add_method = "Label" pos_args = ("text",) properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return {"text": self.wmeta.identifier} _builder_uid = f"{_plugin_uid}.Label" _labelframe = _builder_uid register_widget( _builder_uid, LabelBO, "Label", ("ttk", _designer_tab_label), GROUP_DISPLAY ) class TextBO(WidgetAsMethodBO): master_add_method = "Text" pos_args = ("text",) properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return {"text": self.wmeta.identifier} _builder_uid = f"{_plugin_uid}.Text" _labelframe = _builder_uid register_widget( _builder_uid, TextBO, "Text", ("ttk", _designer_tab_label), GROUP_DISPLAY ) class ScaleBO(WidgetAsMethodBO): master_add_method = "Scale" pos_args = ("lower", "upper", "variable") properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "lower": 0, "upper": 10, "variable": None, } def _process_property_value(self, pname, value): if pname in ("lower", "upper"): return float(value) return super()._process_property_value(pname, value) _builder_uid = f"{_plugin_uid}.Scale" register_widget( _builder_uid, ScaleBO, "Scale", ("ttk", _designer_tab_label), GROUP_INPUT ) class ProgressbarBO(WidgetAsMethodBO): master_add_method = "Progressbar" pos_args = ("variable",) properties = pos_args + WidgetAsMethodBO.kw_args ro_properties = properties def _get_property_defaults(self, master: tk.Widget = None) -> dict: return { "variable": None, } def _process_property_value(self, pname, value): if pname in ("lower", "upper"): return float(value) return super()._process_property_value(pname, value) _builder_uid = f"{_plugin_uid}.Progressbar" register_widget( _builder_uid, ProgressbarBO, "Progressbar", ("ttk", _designer_tab_label), GROUP_DISPLAY, ) # TODO: matplotlibFrame pygubu-0.36.1/src/pygubu/plugins/tksheet/000077500000000000000000000000001474524032100203505ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/tksheet/__init__.py000066400000000000000000000011331474524032100224570ustar00rootroot00000000000000import importlib from pygubu.i18n import _ from pygubu.api.v1 import BuilderLoaderPlugin _designer_tab_label = _("tksheet") _plugin_uid = "tksheet" class TksheetLoader(BuilderLoaderPlugin): _module = "pygubu.plugins.tksheet.sheet" def do_activate(self) -> bool: spec = importlib.util.find_spec("tksheet") return spec is not None def get_module_for(self, identifier: str) -> str: return self._module def get_all_modules(self): return (self._module,) def can_load(self, identifier: str) -> bool: return identifier.startswith("tksheet.") pygubu-0.36.1/src/pygubu/plugins/tksheet/sheet.py000066400000000000000000000005521474524032100220340ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import ( BuilderObject, register_widget, ) from tksheet import Sheet from ..tksheet import _designer_tab_label, _plugin_uid class SheetBuilder(BuilderObject): class_ = Sheet _builder_uid = f"{_plugin_uid}.Sheet" register_widget( _builder_uid, SheetBuilder, "Sheet", ("ttk", _designer_tab_label) ) pygubu-0.36.1/src/pygubu/plugins/ttk/000077500000000000000000000000001474524032100175035ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/ttk/__init__.py000066400000000000000000000006771474524032100216260ustar00rootroot00000000000000from pygubu.api.v1 import BuilderLoaderPlugin class StandardTTKWidgetsLoader(BuilderLoaderPlugin): _module = "pygubu.plugins.ttk.ttkstdwidgets" def do_activate(self) -> bool: return True def get_module_for(self, identifier: str) -> str: return self._module def get_all_modules(self): return (self._module,) def can_load(self, identifier: str) -> bool: return identifier.startswith("ttk.") pygubu-0.36.1/src/pygubu/plugins/ttk/ttkstdwidgets.py000066400000000000000000000504141474524032100227650ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk import tkinter.ttk as ttk from collections import OrderedDict from pygubu.i18n import _ from pygubu.api.v1 import BuilderObject, register_widget from pygubu.component.builderobject import ( EntryBaseBO, PanedWindowBO, PanedWindowPaneBO, OptionMenuBaseMixin, ) has_tk_version_9 = tk.TkVersion >= 9 # # ttk widgets # class TTKWidgetBO(BuilderObject): OPTIONS_LABEL = ( "compound", "font", "image", "padding", "text", "textvariable", "underline", "width", ) OPTIONS_COMPATIBILITY = ("state",) OPTIONS_STANDARD = ("class_", "cursor", "takefocus", "style") OPTIONS_SPECIFIC = tuple() OPTIONS_CUSTOM = tuple() ro_properties = ("class_",) class TTKFrame(TTKWidgetBO): OPTIONS_SPECIFIC = ("borderwidth", "relief", "padding", "height", "width") class_ = ttk.Frame container = True container_layout = True properties = TTKWidgetBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget("ttk.Frame", TTKFrame, "ttk.Frame", (_("Containers"), "ttk")) class TTKLabel(TTKWidgetBO): OPTIONS_STANDARD = ( TTKWidgetBO.OPTIONS_STANDARD + TTKWidgetBO.OPTIONS_LABEL + ("borderwidth",) ) OPTIONS_SPECIFIC = ( "anchor", "background", "font", "foreground", "justify", "padding", "relief", "state", "wraplength", ) class_ = ttk.Label container = False properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget( "ttk.Label", TTKLabel, "ttk.Label", (_("Control & Display"), "ttk") ) class TTKButton(TTKWidgetBO): OPTIONS_STANDARD = ( TTKWidgetBO.OPTIONS_STANDARD + TTKWidgetBO.OPTIONS_LABEL + TTKWidgetBO.OPTIONS_COMPATIBILITY ) OPTIONS_SPECIFIC = ("command", "default") class_ = ttk.Button container = False properties = ( OPTIONS_STANDARD + OPTIONS_SPECIFIC + TTKWidgetBO.OPTIONS_CUSTOM ) command_properties = ("command",) register_widget( "ttk.Button", TTKButton, "ttk.Button", (_("Control & Display"), "ttk") ) class TTKCheckbutton(TTKWidgetBO): OPTIONS_STANDARD = ( TTKWidgetBO.OPTIONS_STANDARD + TTKWidgetBO.OPTIONS_LABEL + TTKWidgetBO.OPTIONS_COMPATIBILITY ) OPTIONS_SPECIFIC = ("command", "offvalue", "onvalue", "variable") class_ = ttk.Checkbutton container = False properties = ( OPTIONS_STANDARD + OPTIONS_SPECIFIC + TTKWidgetBO.OPTIONS_CUSTOM ) command_properties = ("command",) register_widget( "ttk.Checkbutton", TTKCheckbutton, "ttk.Checkbutton", (_("Control & Display"), "ttk"), ) class TTKRadiobutton(TTKWidgetBO): OPTIONS_STANDARD = ( TTKWidgetBO.OPTIONS_STANDARD + TTKWidgetBO.OPTIONS_LABEL + TTKWidgetBO.OPTIONS_COMPATIBILITY ) OPTIONS_SPECIFIC = ("command", "value", "variable") class_ = ttk.Radiobutton container = False properties = ( OPTIONS_STANDARD + OPTIONS_SPECIFIC + TTKWidgetBO.OPTIONS_CUSTOM ) ro_properties = ("class_",) command_properties = ("command",) register_widget( "ttk.Radiobutton", TTKRadiobutton, "ttk.Radiobutton", (_("Control & Display"), "ttk"), ) _v9_entry_opts = ( ("placeholder", "placeholderforeground") if has_tk_version_9 else tuple() ) class TTKEntry(TTKWidgetBO, EntryBaseBO): OPTIONS_STANDARD = TTKWidgetBO.OPTIONS_STANDARD + ("xscrollcommand",) OPTIONS_SPECIFIC = ( "exportselection", "font", "invalidcommand", "justify", "show", "state", "textvariable", "validate", "validatecommand", "width", ) + _v9_entry_opts OPTIONS_CUSTOM = ("text",) class_ = ttk.Entry container = False properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM command_properties = ("validatecommand", "invalidcommand", "xscrollcommand") register_widget( "ttk.Entry", TTKEntry, "ttk.Entry", (_("Control & Display"), "ttk") ) class TTKCombobox(TTKWidgetBO): OPTIONS_SPECIFIC = ( "exportselection", "justify", "height", "postcommand", "state", "textvariable", "values", "width", "validate", "validatecommand", "invalidcommand", "xscrollcommand", ) + _v9_entry_opts class_ = ttk.Combobox container = False properties = ( TTKWidgetBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC + TTKWidgetBO.OPTIONS_CUSTOM ) command_properties = ( "postcommand", "validatecommand", "invalidcommand", "xscrollcommand", ) virtual_events = ("<>",) def _code_process_property_value(self, targetid, pname, value: str): if pname == "values": return self.code_escape_str(value) return super()._code_process_property_value(targetid, pname, value) register_widget( "ttk.Combobox", TTKCombobox, "ttk.Combobox", (_("Control & Display"), "ttk") ) class TTKScrollbar(TTKWidgetBO): OPTIONS_SPECIFIC = ("command", "orient") class_ = ttk.Scrollbar container = False properties = TTKWidgetBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC command_properties = ("command",) register_widget( "ttk.Scrollbar", TTKScrollbar, "ttk.Scrollbar", (_("Control & Display"), "ttk"), ) class TTKSizegrip(TTKWidgetBO): class_ = ttk.Sizegrip container = False properties = TTKWidgetBO.OPTIONS_STANDARD + TTKWidgetBO.OPTIONS_SPECIFIC register_widget( "ttk.Sizegrip", TTKSizegrip, "ttk.Sizegrip", (_("Control & Display"), "ttk") ) _v9_pbar_options = ( ("anchor", "font", "foreground", "justify", "text", "wraplength") if has_tk_version_9 else tuple() ) class TTKProgressbar(TTKWidgetBO): OPTIONS_SPECIFIC = ( "orient", "length", "mode", "maximum", "value", "variable", ) + _v9_pbar_options class_ = ttk.Progressbar container = False properties = TTKWidgetBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget( "ttk.Progressbar", TTKProgressbar, "ttk.Progressbar", (_("Control & Display"), "ttk"), ) class TTKScale(TTKWidgetBO): OPTIONS_SPECIFIC = ( "command", "from_", "length", "orient", "state", "to", "value", "variable", ) class_ = ttk.Scale container = False properties = TTKWidgetBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC command_properties = ("command",) register_widget( "ttk.Scale", TTKScale, "ttk.Scale", (_("Control & Display"), "ttk") ) class TTKSeparator(TTKWidgetBO): OPTIONS_SPECIFIC = ("orient",) class_ = ttk.Separator container = False properties = TTKWidgetBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget( "ttk.Separator", TTKSeparator, "ttk.Separator", (_("Control & Display"), "ttk"), ) class TTKLabelframe(TTKWidgetBO): OPTIONS_STANDARD = TTKFrame.OPTIONS_STANDARD OPTIONS_SPECIFIC = TTKFrame.OPTIONS_SPECIFIC + ( "labelanchor", "text", "underline", ) class_ = ttk.Labelframe container = True container_layout = True properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget( "ttk.Labelframe", TTKLabelframe, "ttk.Labelframe", (_("Containers"), "ttk") ) class TTKPanedwindow(TTKWidgetBO, PanedWindowBO): OPTIONS_SPECIFIC = ("orient", "height", "width") class_ = ttk.Panedwindow allowed_children = ("ttk.Panedwindow.Pane",) properties = TTKWidgetBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC ro_properties = ("class_", "orient") virtual_events = ("<>",) register_widget( "ttk.Panedwindow", TTKPanedwindow, "ttk.Panedwindow", (_("Containers"), "ttk"), ) class TTKNotebook(TTKWidgetBO): OPTIONS_SPECIFIC = ("height", "padding", "width") class_ = ttk.Notebook container = True allowed_children = ("ttk.Notebook.Tab",) properties = TTKWidgetBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC virtual_events = ("<>",) register_widget( "ttk.Notebook", TTKNotebook, "ttk.Notebook", (_("Containers"), "ttk") ) class TTKMenubuttonBO(TTKWidgetBO): OPTIONS_STANDARD = ( TTKWidgetBO.OPTIONS_STANDARD + TTKWidgetBO.OPTIONS_LABEL + TTKWidgetBO.OPTIONS_COMPATIBILITY ) OPTIONS_SPECIFIC = ("direction",) # 'menu' class_ = ttk.Menubutton container = True properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC allowed_children = ("tk.Menu",) maxchildren = 1 def add_child(self, bobject): self.widget.configure(menu=bobject.widget) def code_child_add(self, childid): lines = [f"{self.code_identifier()}.configure(menu={childid})"] return lines register_widget( "ttk.Menubutton", TTKMenubuttonBO, "ttk.Menubutton", ( _("Menu"), _("Control & Display"), "ttk", ), ) class TTKTreeviewBO(TTKWidgetBO): OPTIONS_STANDARD = TTKWidgetBO.OPTIONS_STANDARD + ( "xscrollcommand", "yscrollcommand", ) OPTIONS_SPECIFIC = ("height", "padding", "selectmode", "show") class_ = ttk.Treeview container = True allowed_children = ("ttk.Treeview.Column",) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC virtual_events = ( "<>", "<>", "<>", ) def __init__(self, builder, wdescr): super(TTKTreeviewBO, self).__init__(builder, wdescr) self._columns = None self._headings = None self._dcolumns = None def configure_children(self): self.__configure_columns() def __configure_columns(self): if self._columns: columns = list(self._columns.keys()) if "#0" in columns: columns.remove("#0") displaycolumns = self._dcolumns self.widget.configure( columns=columns, displaycolumns=displaycolumns ) for col in self._columns: self.widget.column(col, **self._columns[col]) if self._headings: for col in self._headings: self.widget.heading(col, **self._headings[col]) def set_column(self, col_id, attrs, visible=True): if self._columns is None: self._columns = OrderedDict() self._dcolumns = list() self._columns[col_id] = attrs if visible and col_id != "#0": self._dcolumns.append(col_id) def set_heading(self, col_id, attrs): if self._headings is None: self._headings = OrderedDict() self._headings[col_id] = attrs # # Code generation methods # def code_configure_children(self, targetid=None): if targetid is None: targetid = self.code_identifier() lines = [] if self._columns: columns = list(self._columns.keys()) if "#0" in columns: columns.remove("#0") displaycolumns = self._dcolumns line = f"{targetid}_cols = {repr(columns)}" lines.append(line) line = f"{targetid}_dcols = {repr(displaycolumns)}" lines.append(line) line = "{0}.configure(columns={0}_cols, displaycolumns={0}_dcols)" line = line.format(targetid) lines.append(line) for col in self._columns: code_bag, kwp, _ = self._code_process_properties( self._columns[col], targetid ) bag = [] for pname in kwp: s = f"{pname}={code_bag[pname]}" bag.append(s) kwargs = ",".join(bag) line = f'{targetid}.column("{col}", {kwargs})' lines.append(line) if self._headings: for col in self._headings: code_bag, kwp, _ = self._code_process_properties( self._headings[col], targetid ) bag = [] for pname in kwp: s = f"{pname}={code_bag[pname]}" bag.append(s) kwargs = ",".join(bag) line = f'{targetid}.heading("{col}", {kwargs})' lines.append(line) return lines register_widget( "ttk.Treeview", TTKTreeviewBO, "ttk.Treeview", (_("Control & Display"), "ttk"), ) # # Helpers for Standard ttk widgets # class TTKPanedwindowPane(TTKWidgetBO, PanedWindowPaneBO): OPTIONS_STANDARD = tuple() OPTIONS_SPECIFIC = ("weight",) class_ = None container = True allowed_parents = ("ttk.Panedwindow",) maxchildren = 1 properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC register_widget( "ttk.Panedwindow.Pane", TTKPanedwindowPane, "ttk.Panedwindow.Pane", (_("Pygubu Helpers"), "ttk"), ) class TTKNotebookTab(TTKWidgetBO): OPTIONS_STANDARD = tuple() OPTIONS_SPECIFIC = ( "state", "sticky", "padding", "text", "image", "compound", "underline", ) class_ = None container = True layout_required = False allow_bindings = False allowed_parents = ("ttk.Notebook",) maxchildren = 1 properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC def realize(self, parent, extra_init_args: dict = None): self.widget = parent.get_child_master() return self.widget def configure(self, target=None): pass def layout(self, target=None, *, forget=False): pass def add_child(self, bobject): self.widget.add(bobject.widget, **self.wmeta.properties) # # Code generation methods # def code_realize(self, boparent, code_identifier=None): self._code_identifier = boparent.code_child_master() return tuple() def code_configure(self, targetid=None): return tuple() def code_child_add(self, childid): targetid = self.code_identifier() code_bag, kw, _ = self._code_process_properties( self.wmeta.properties, targetid ) kwbag = [] for pname in kw: arg = f"{pname}={code_bag[pname]}" kwbag.append(arg) kwargs = "" if kwbag: kwargs = f", {', '.join(kwbag)}" line = f"{targetid}.add({childid}{kwargs})" return [line] register_widget( "ttk.Notebook.Tab", TTKNotebookTab, "Notebook.Tab", (_("Pygubu Helpers"), "ttk"), ) class TTKTreeviewColumnBO(TTKWidgetBO): OPTIONS_STANDARD = tuple() OPTIONS_SPECIFIC = ( "text", "image", "command", "heading_anchor", "column_anchor", "minwidth", "stretch", "width", ) OPTIONS_CUSTOM = ( "tree_column", "visible", ) class_ = None container = False layout_required = False allow_bindings = False allowed_parents = ("ttk.Treeview",) properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM command_properties = ("command",) def realize(self, parent, extra_init_args: dict = None): self.widget = parent.get_child_master() col_props = dict(self.wmeta.properties) # copy properties self._setup_column(parent, col_props) return self.widget def _get_heading_properties(self, props): text = props.pop("text", None) if text is None: text = self.wmeta.identifier hprops = {"anchor": props.pop("heading_anchor", tk.W), "text": text} # Only add image if has value. Fix code generation imgvalue = props.pop("image", None) if imgvalue: hprops["image"] = self._process_property_value("image", imgvalue) return hprops def _get_column_properties(self, props): cprops = { "anchor": props.pop("column_anchor", ""), "stretch": props.pop("stretch", "1"), "width": props.pop("width", "200"), "minwidth": props.pop("minwidth", "20"), } return cprops def _setup_column(self, parent, col_props): tree_column = col_props.pop("tree_column", "false") tree_column = tree_column.lower() tree_column = True if tree_column == "true" else False column_id = "#0" if tree_column else self.wmeta.identifier visible = col_props.pop("visible", "true") visible = visible.lower() is_visible = True if visible == "true" else False # configure heading properties col_props.pop("command", "") hprops = self._get_heading_properties(col_props) parent.set_heading(column_id, hprops) # configure column properties cprops = self._get_column_properties(col_props) parent.set_column(column_id, cprops, is_visible) def configure(self, target=None): pass def layout(self, target=None, *, forget=False): pass def _connect_command(self, cpname, callback): tree_column = self.wmeta.properties.get("tree_column", "false") tree_column = tree_column.lower() tree_column = True if tree_column == "true" else False column_id = "#0" if tree_column else self.wmeta.identifier self.widget.heading(column_id, command=callback) # # Code generation methods # def code_realize(self, boparent, code_identifier=None): self._code_identifier = boparent.code_child_master() col_props = dict(self.wmeta.properties) # copy properties self._setup_column(boparent, col_props) return tuple() def code_configure(self, targetid=None): return tuple() register_widget( "ttk.Treeview.Column", TTKTreeviewColumnBO, "Treeview.Column", (_("Pygubu Helpers"), "ttk"), ) class TTKSpinboxBO(TTKWidgetBO, EntryBaseBO): OPTIONS_STANDARD = TTKEntry.OPTIONS_STANDARD OPTIONS_SPECIFIC = ( TTKEntry.OPTIONS_SPECIFIC + ( "from_", "to", "increment", "values", "wrap", "format", "command", ) + _v9_entry_opts ) OPTIONS_CUSTOM = TTKEntry.OPTIONS_CUSTOM class_ = None container = False properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM command_properties = ( "validatecommand", "invalidcommand", "xscrollcommand", "command", ) virtual_events = ("<>", "<>") if tk.TkVersion >= 8.6: if not hasattr(ttk, "Spinbox"): from pygubu.widgets.ttkspinbox import Spinbox ttk.Spinbox = Spinbox TTKSpinboxBO.class_ = ttk.Spinbox register_widget( "ttk.Spinbox", TTKSpinboxBO, "ttk.Spinbox", (_("Control & Display"), "ttk"), ) class OptionMenuBO(OptionMenuBaseMixin, BuilderObject): class_ = ttk.OptionMenu properties = ( "style", "direction", "command", "variable", "value", "values", ) command_properties = ("command",) ro_properties = ("variable", "value", "values") def _create_option_menu(self, master, variable, value, values, command): return self.class_(master, variable, value, *values, command=command) def _code_create_optionmenu( self, identifier, classname, master, value_arg, variable_arg, command_arg, ): return ( f"{identifier} = {classname}({master}, {variable_arg}," + f" {value_arg}, *__values, command={command_arg})" ) register_widget( "ttk.OptionMenu", OptionMenuBO, "ttk.OptionMenu", (_("Control & Display"), "ttk"), ) class LabeledScaleBO(BuilderObject): class_ = ttk.LabeledScale properties = ( "compound", "variable", "from_", "to", ) ro_properties = ("compound", "from_", "to", "variable") virtual_events = ("<>",) def _connect_binding(self, sequence: str, callback, add): self.widget.scale.bind(sequence, callback, add) def _code_connect_binding( self, target: str, sequence: str, callback: str, add_arg: str ): scale = f"{target}.scale" return super()._code_connect_binding(scale, sequence, callback, "+") register_widget( "ttk.LabeledScale", LabeledScaleBO, "ttk.LabeledScale", (_("Control & Display"), "ttk"), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/000077500000000000000000000000001474524032100210725ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/__init__.py000066400000000000000000000043601474524032100232060ustar00rootroot00000000000000import importlib from pygubu.i18n import _ from pygubu.api.v1 import BuilderLoaderPlugin _designer_tab_label = _("ttkwidgets") _plugin_uid = "ttkwidgets" class TtkWidgetsLoader(BuilderLoaderPlugin): module_map = { "pygubu.plugins.ttkwidgets.calendar": (f"{_plugin_uid}.Calendar",), "pygubu.plugins.ttkwidgets.checkboxtreeview": ( f"{_plugin_uid}.CheckboxTreeview", ), "pygubu.plugins.ttkwidgets.itemscanvas": ( f"{_plugin_uid}.ItemsCanvas", ), "pygubu.plugins.ttkwidgets.linklabel": (f"{_plugin_uid}.LinkLabel",), "pygubu.plugins.ttkwidgets.scaleentry": (f"{_plugin_uid}.ScaleEntry",), "pygubu.plugins.ttkwidgets.scrolledlistbox": ( f"{_plugin_uid}.ScrolledListbox", ), "pygubu.plugins.ttkwidgets.table": (f"{_plugin_uid}.Table",), "pygubu.plugins.ttkwidgets.tickscale": (f"{_plugin_uid}.TickScale",), "pygubu.plugins.ttkwidgets.frames": ( f"{_plugin_uid}.ScrolledFrame", f"{_plugin_uid}.ToggledFrame", ), "pygubu.plugins.ttkwidgets.color": ( f"{_plugin_uid}.AlphaBar", f"{_plugin_uid}.ColorSquare", f"{_plugin_uid}.GradientBar", ), "pygubu.plugins.ttkwidgets.autocomplete": ( f"{_plugin_uid}.AutocompleteEntry", f"{_plugin_uid}.AutocompleteEntryListbox", f"{_plugin_uid}.AutocompleteCombobox", ), "pygubu.plugins.ttkwidgets.font": ( f"{_plugin_uid}.FontFamilyDropdown", f"{_plugin_uid}.FontFamilyListbox", f"{_plugin_uid}.FontSelectFrame", f"{_plugin_uid}.FontPropertiesFrame", f"{_plugin_uid}.FontSizeDropdown", ), } def do_activate(self) -> bool: spec = importlib.util.find_spec("ttkwidgets") return spec is not None def get_module_for(self, identifier: str) -> str: for module, identifiers in self.module_map.items(): if identifier in identifiers: return module return None def get_all_modules(self): return [m for m in self.module_map.keys()] def can_load(self, identifier: str) -> bool: return identifier.startswith("ttkwidgets.") pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/autocomplete.py000066400000000000000000000106721474524032100241530ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.ttk.ttkstdwidgets import TTKEntry, TTKFrame, TTKCombobox from ttkwidgets.autocomplete import ( AutocompleteEntry, AutocompleteEntryListbox, AutocompleteCombobox, ) from ..ttkwidgets import _designer_tab_label, _plugin_uid from .utils import AutocompleteBaseBO class AutocompleteEntryBO(AutocompleteBaseBO, TTKEntry): class_ = AutocompleteEntry # # Note: completevalues property is managed by AutocompleteBaseBO # OPTIONS_CUSTOM = ("completevalues",) properties = ( TTKEntry.OPTIONS_STANDARD + TTKEntry.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKEntry.ro_properties + OPTIONS_CUSTOM def code_imports(self): return (("ttkwidgets.autocomplete", "AutocompleteEntry"),) _builder_uid = f"{_plugin_uid}.AutocompleteEntry" register_widget( _builder_uid, AutocompleteEntryBO, "AutocompleteEntry", ("ttk", _designer_tab_label), group=4, ) register_custom_property( _builder_uid, "completevalues", "entry", help=_("Values separated by space. In code you can pass a full list"), ) class AutocompleteEntryListboxBO(AutocompleteBaseBO, TTKFrame): class_ = AutocompleteEntryListbox container = False # # Note: completevalues property is managed by AutocompleteBaseBO # OPTIONS_CUSTOM = ( "allow_other_values", "autohidescrollbar", "exportselection", "font", "justify", "completevalues", ) properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKFrame.ro_properties + ("completevalues",) def _process_property_value(self, pname, value): final_value = value if pname in ( "autohidescrollbar", "allow_other_values", "exportselection", ): final_value = tk.getboolean(value) else: final_value = super( AutocompleteEntryListboxBO, self )._process_property_value(pname, value) return final_value def _code_process_property_value(self, targetid, pname, value): if pname in ( "autohidescrollbar", "allow_other_values", "exportselection", ): return tk.getboolean(value) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.AutocompleteEntryListbox" register_widget( _builder_uid, AutocompleteEntryListboxBO, "AutocompleteEntryListbox", ("ttk", _designer_tab_label), group=4, ) register_custom_property( _builder_uid, "autohidescrollbar", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "font", "fontentry", help=_("font in entry and listbox") ) register_custom_property( _builder_uid, "allow_other_values", "choice", values=("", "true", "false"), state="readonly", help=_("whether the user is allowed to enter values not in the list"), ) register_custom_property( _builder_uid, "allow_other_values", "choice", values=("", "true", "false"), state="readonly", help=_("whether to automatically export selected text to the clipboard"), ) register_custom_property( _builder_uid, "justify", "choice", values=("", "left", "center", "right"), state="readonly", help=_("text alignment in entry and listbox"), ) register_custom_property( _builder_uid, "completevalues", "entry", help=_( "Values separated by space. In code you can pass a full python list." ), ) class AutocompleteComboboxBO(AutocompleteBaseBO, TTKCombobox): class_ = AutocompleteCombobox OPTIONS_SPECIFIC = tuple( set(TTKCombobox.OPTIONS_SPECIFIC) - set(("values",)) ) OPTIONS_CUSTOM = ("completevalues",) properties = ( TTKCombobox.OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKCombobox.ro_properties + OPTIONS_CUSTOM _builder_uid = f"{_plugin_uid}.AutocompleteCombobox" register_widget( _builder_uid, AutocompleteComboboxBO, "AutocompleteCombobox", ("ttk", _designer_tab_label), group=4, ) register_custom_property( _builder_uid, "completevalues", "entry", help=_("Values separated by space. In code you can pass a full list"), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/calendar.py000066400000000000000000000037061474524032100232230ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame from ttkwidgets import Calendar from ..ttkwidgets import _designer_tab_label, _plugin_uid class CalendarBO(TTKFrame): class_ = Calendar container = False OPTIONS_CUSTOM = ( "locale", "firstweekday", "year", "month", "selectbackground", "selectforeground", ) properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) # FIXME: set all custom properties as readonly, there is a bug in the # ttkwidgets widget definition. ro_properties = TTKFrame.ro_properties + OPTIONS_CUSTOM def _process_property_value(self, pname, value): if pname in ("firstweekday", "year", "month"): return int(value) return super()._process_property_value(pname, value) _builder_uid = f"{_plugin_uid}.Calendar" register_widget( _builder_uid, CalendarBO, "Calendar", ("ttk", _designer_tab_label), group=2 ) register_custom_property( _builder_uid, "locale", "entry", help=_("calendar locale (defines the language, date formatting)"), ) register_custom_property( _builder_uid, "firstweekday", "choice", values=("", 0, 1, 2, 3, 4, 5, 6), state="readonly", help=_("first day of the week, 0 is monday"), ) register_custom_property( _builder_uid, "year", "integernumber", help=_("year to display") ) register_custom_property( _builder_uid, "month", "choice", values=("", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), state="readonly", help=_("month to display"), ) register_custom_property( _builder_uid, "selectbackground", "colorentry", help=_("background color of the selected day"), ) register_custom_property( _builder_uid, "selectforeground", "colorentry", help=_("selectforeground color of the selected day"), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/checkboxtreeview.py000066400000000000000000000012701474524032100250050ustar00rootroot00000000000000from pygubu.api.v1 import register_widget from pygubu.plugins.ttk.ttkstdwidgets import TTKTreeviewBO, TTKTreeviewColumnBO from pygubu.plugins.pygubu.scrollbarhelper_bo import TTKSBHelperBO from ttkwidgets.checkboxtreeview import CheckboxTreeview from ..ttkwidgets import _designer_tab_label, _plugin_uid class CheckboxTreeviewBO(TTKTreeviewBO): class_ = CheckboxTreeview allowed_children = ("ttk.Treeview.Column",) _builder_uid = f"{_plugin_uid}.CheckboxTreeview" register_widget( _builder_uid, CheckboxTreeviewBO, "CheckboxTreeview", ("ttk", _designer_tab_label), ) TTKSBHelperBO.add_allowed_child(_builder_uid) TTKTreeviewColumnBO.add_allowed_parent(_builder_uid) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/color.py000066400000000000000000000116401474524032100225640ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.tk.tkstdwidgets import TKCanvas from ttkwidgets.color import AlphaBar, ColorSquare, GradientBar from ttkwidgets.color.functions import rgb_to_hsv from PIL.ImageColor import getrgb from ..ttkwidgets import _designer_tab_label, _plugin_uid class AlphaBarBO(TKCanvas): class_ = AlphaBar container = False OPTIONS_CUSTOM = ("alpha", "color", "variable") properties = ( TKCanvas.OPTIONS_STANDARD + TKCanvas.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = OPTIONS_CUSTOM + ("height", "width") virtual_events = ("<>",) def _process_property_value(self, pname, value): final_value = None if pname in ("alpha", "height", "width"): final_value = int(value) elif pname == "color": final_value = getrgb(value) else: final_value = super(AlphaBarBO, self)._process_property_value( pname, value ) return final_value def _code_process_property_value(self, targetid, pname, value): if pname == "color": return f'getrgb("{value}")' return super()._code_process_property_value(targetid, pname, value) def code_imports(self): return (("PIL.ImageColor", "getrgb"), ("ttkwidgets.color", "AlphaBar")) _builder_uid = f"{_plugin_uid}.AlphaBar" register_widget( _builder_uid, AlphaBarBO, "AlphaBar", ("ttk", _designer_tab_label), group=5 ) register_custom_property( _builder_uid, "alpha", "integernumber", help=_("initially selected alpha value (between 0 and 255)"), ) register_custom_property( _builder_uid, "color", "colorentry", help=_("gradient color") ) register_custom_property( _builder_uid, "variable", "tkvarentry", help=_("variable linked to the alpha value"), ) class ColorSquareBO(TKCanvas): class_ = ColorSquare container = False OPTIONS_CUSTOM = ("hue", "color") properties = ( TKCanvas.OPTIONS_STANDARD + TKCanvas.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = OPTIONS_CUSTOM + ("height", "width") virtual_events = ("<>",) def realize(self, parent, extra_init_args: dict = None): args = self._get_init_args(extra_init_args) master = parent.get_child_master() hue_value = args.pop("hue", 0) self.widget = self.class_(master, hue_value, **args) return self.widget def _process_property_value(self, pname, value): final_value = None if pname in ("hue", "height", "width"): final_value = int(value) elif pname == "color": rgb = getrgb(value) final_value = rgb_to_hsv(*rgb) else: final_value = super(ColorSquareBO, self)._process_property_value( pname, value ) return final_value def _code_process_property_value(self, targetid, pname, value): if pname == "color": return f'rgb_to_hsv(*getrgb("{value}"))' return super()._code_process_property_value(targetid, pname, value) def code_imports(self): return ( ("PIL.ImageColor", "getrgb"), ("ttkwidgets.color", "ColorSquare"), ("ttkwidgets.color.functions", "rgb_to_hsv"), ) _builder_uid = f"{_plugin_uid}.ColorSquare" register_widget( _builder_uid, ColorSquareBO, "ColorSquare", ("ttk", _designer_tab_label), group=5, ) # Custom properties register_custom_property( _builder_uid, "hue", "integernumber", default_value=0, help=_("hue (between 0 and 360) of the color square gradient"), ) register_custom_property( _builder_uid, "color", "colorentry", help=_("initially selected color") ) class GradientBarBO(TKCanvas): class_ = GradientBar container = False OPTIONS_CUSTOM = ("hue", "variable") properties = ( TKCanvas.OPTIONS_STANDARD + TKCanvas.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = OPTIONS_CUSTOM + ("height", "width") virtual_events = ("<>",) def _process_property_value(self, pname, value): final_value = None if pname in ("hue", "height", "width"): final_value = int(value) else: final_value = super(GradientBarBO, self)._process_property_value( pname, value ) return final_value def code_imports(self): return (("ttkwidgets.color", "GradientBar"),) _builder_uid = f"{_plugin_uid}.GradientBar" register_widget( _builder_uid, GradientBarBO, "GradientBar", ("ttk", _designer_tab_label), group=5, ) register_custom_property( _builder_uid, "hue", "integernumber", help=_("initially selected hue value (between 0 and 360)"), ) register_custom_property( _builder_uid, "variable", "tkvarentry", help=_("variable linked to the hue value"), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/font.py000066400000000000000000000162531474524032100224210ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.tk.tkstdwidgets import TKListbox from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame, TTKCombobox from ttkwidgets.font import ( FontFamilyDropdown, FontFamilyListbox, FontSelectFrame, FontPropertiesFrame, FontSizeDropdown, ) from ..ttkwidgets import _designer_tab_label, _plugin_uid from .utils import CallbakInitArgMixin from .autocomplete import AutocompleteComboboxBO from .scrolledlistbox import ScrolledListboxBO class FontFamilyDropdownBO(CallbakInitArgMixin, AutocompleteComboboxBO): class_ = FontFamilyDropdown init_completevalues = False container = False OPTIONS_SPECIFIC = tuple( set(AutocompleteComboboxBO.OPTIONS_SPECIFIC) - set(("textvariable",)) ) OPTIONS_CUSTOM = ("callback",) properties = ( AutocompleteComboboxBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKCombobox.ro_properties command_properties = ("callback",) def _code_define_callback_args(self, cmd_pname, cmd): args = None if cmd_pname == "callback": args = ("family",) else: args = super()._code_define_callback_args(cmd_pname, cmd) return args _builder_uid = f"{_plugin_uid}.FontFamilyDropdown" register_widget( _builder_uid, FontFamilyDropdownBO, "FontFamilyDropdown", ("ttk", _designer_tab_label), ) register_custom_property( _builder_uid, "callback", "simplecommandentry", help=_( "name of the callback function with one argument: the font family name" ), ) class FontFamilyListboxBO(CallbakInitArgMixin, ScrolledListboxBO): class_ = FontFamilyListbox container = False OPTIONS_STANDARD = tuple( set(TKListbox.OPTIONS_STANDARD) - set(("xscrollcommand", "yscrollcommand")) ) OPTIONS_SPECIFIC = tuple( set(TKListbox.OPTIONS_SPECIFIC) - set(("listvariable",)) ) OPTIONS_CUSTOM = ("autohidescrollbar", "callback") properties = OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ro_properties = TKListbox.ro_properties + ("autohidescrollbar",) command_properties = ("callback",) def _process_property_value(self, pname, value): if pname == "autohidescrollbar": return tk.getboolean(value) return super()._process_property_value(pname, value) def _code_process_property_value(self, targetid, pname, value): if pname == "autohidescrollbar": return tk.getboolean(value) return super()._code_process_property_value(targetid, pname, value) def _code_define_callback_args(self, cmd_pname, cmd): args = None if cmd_pname == "callback": args = ("family",) else: args = super()._code_define_callback_args(cmd_pname, cmd) return args _builder_uid = f"{_plugin_uid}.FontFamilyListbox" register_widget( _builder_uid, FontFamilyListboxBO, "FontFamilyListbox", ("ttk", _designer_tab_label), ) register_custom_property( _builder_uid, "autohidescrollbar", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "callback", "simplecommandentry", help=_( "name of the callback function with one argument: the font family name" ), ) class FontSelectFrameBO(CallbakInitArgMixin, TTKFrame): class_ = FontSelectFrame container = False OPTIONS_CUSTOM = ("callback",) properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKFrame.ro_properties + OPTIONS_CUSTOM command_properties = ("callback",) def _code_define_callback_args(self, cmd_pname, cmd): args = None if cmd_pname == "callback": args = ("family", "size", "bold", "italic", "underline") else: args = super(FontSelectFrameBO, self)._code_define_callback_args( cmd_pname, cmd ) return args _builder_uid = f"{_plugin_uid}.FontSelectFrame" register_widget( _builder_uid, FontSelectFrameBO, "FontSelectFrame", ("ttk", _designer_tab_label), ) register_custom_property( _builder_uid, "callback", "simplecommandentry", help=_( "name of the callback function with arguments: " + "(family: str, size: int, bold: bool, italic: bool, underline: bool)" ), ) class FontPropertiesFrameBO(CallbakInitArgMixin, TTKFrame): class_ = FontPropertiesFrame container = False OPTIONS_CUSTOM = ("callback", "label", "fontsize") properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKFrame.ro_properties + OPTIONS_CUSTOM command_properties = ("callback",) def _process_property_value(self, pname, value): if pname == "label": return tk.getboolean(value) if pname == "fontsize": return int(value) return super()._process_property_value(pname, value) def _code_define_callback_args(self, cmd_pname, cmd): if cmd_pname == "callback": return ("family", "size", "bold", "italic", "underline") return super()._code_define_callback_args(cmd_pname, cmd) _builder_uid = f"{_plugin_uid}.FontPropertiesFrame" register_widget( _builder_uid, FontPropertiesFrameBO, "FontPropertiesFrame", ("ttk", _designer_tab_label), ) register_custom_property( _builder_uid, "callback", "simplecommandentry", help=_( "name of the callback function with arguments: " + "(bold: bool, italic: bool, underline: bool, overstrike: bool)" ), ) register_custom_property( _builder_uid, "label", "choice", values=("", "true", "false"), default_value="true", state="readonly", help=_("Show/hide Header label"), ) register_custom_property( _builder_uid, "fontsize", "naturalnumber", help=_("size of the font on the buttons"), ) class FontSizeDropdownBO(CallbakInitArgMixin, AutocompleteComboboxBO): class_ = FontSizeDropdown init_completevalues = False container = False OPTIONS_SPECIFIC = tuple( set(AutocompleteComboboxBO.OPTIONS_SPECIFIC) - set(("textvariable",)) ) OPTIONS_CUSTOM = ("callback",) properties = ( AutocompleteComboboxBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKCombobox.ro_properties command_properties = ("callback",) def _code_define_callback_args(self, cmd_pname, cmd): args = None if cmd_pname == "callback": args = ("size",) else: args = super(FontSizeDropdownBO, self)._code_define_callback_args( cmd_pname, cmd ) return args _builder_uid = f"{_plugin_uid}.FontSizeDropdown" register_widget( _builder_uid, FontSizeDropdownBO, "FontSizeDropdown", ("ttk", _designer_tab_label), ) register_custom_property( _builder_uid, "callback", "simplecommandentry", help=_( "name of the callback function on click with single argument: size: int" ), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/frames.py000066400000000000000000000072351474524032100227300ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame from ttkwidgets.frames import ScrolledFrame, ToggledFrame from ..ttkwidgets import _designer_tab_label, _plugin_uid class ScrolledFrameBO(TTKFrame): class_ = ScrolledFrame container = True container_layout = True OPTIONS_CUSTOM = ( "compound", "canvaswidth", "canvasheight", "canvasborder", "autohidescrollbar", ) properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKFrame.ro_properties + OPTIONS_CUSTOM def get_child_master(self): return self.widget.interior def _process_property_value(self, pname, value): if pname == "autohidescrollbar": return tk.getboolean(value) if pname == "compound": return tk.LEFT if value == "left" else tk.RIGHT return super(ScrolledFrameBO, self)._process_property_value( pname, value ) def code_child_master(self): return f"{self.code_identifier()}.interior" def _code_process_property_value(self, targetid, pname, value): if pname == "autohidescrollbar": return tk.getboolean(value) return super()._code_process_property_value(targetid, pname, value) _builder_uid = f"{_plugin_uid}.ScrolledFrame" register_widget( _builder_uid, ScrolledFrameBO, "ScrolledFrame", ("ttk", _designer_tab_label), group=0, ) register_custom_property( _builder_uid, "compound", "choice", default_value=tk.RIGHT, values=("", tk.LEFT, tk.RIGHT), state="readonly", help=_("side the scrollbar should be on"), ) register_custom_property( _builder_uid, "canvaswidth", "dimensionentry", default_value=400 ) register_custom_property( _builder_uid, "canvasheight", "dimensionentry", default_value=400 ) register_custom_property(_builder_uid, "canvasborder", "dimensionentry") register_custom_property( _builder_uid, "autohidescrollbar", "choice", values=("", "true", "false"), state="readonly", ) class ToggledFrameBO(TTKFrame): class_ = ToggledFrame container = True OPTIONS_CUSTOM = ( "compound", "width", "text", ) properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKFrame.ro_properties + OPTIONS_CUSTOM def get_child_master(self): return self.widget.interior def _process_property_value(self, pname, value): final_value = value if pname in ("autohidescrollbar",): final_value = tk.getboolean(value) else: final_value = super(ToggledFrameBO, self)._process_property_value( pname, value ) return final_value def code_child_master(self): return f"{self.code_identifier()}.interior" _builder_uid = f"{_plugin_uid}.ToggledFrame" register_widget( _builder_uid, ToggledFrameBO, "ToggledFrame", ("ttk", _designer_tab_label), group=0, ) register_custom_property( _builder_uid, "compound", "choice", default_value=tk.RIGHT, help=_("position of the toggle arrow compared to the text"), values=("", tk.TOP, tk.BOTTOM, tk.LEFT, tk.RIGHT, tk.CENTER, tk.NONE), state="readonly", ) register_custom_property( _builder_uid, "width", "naturalnumber", help=_("width of the closed ToggledFrame (in characters)"), ) register_custom_property( _builder_uid, "text", "entry", help=_("text to display next to the toggle arrow"), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/itemscanvas.py000066400000000000000000000052611474524032100237650ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame from ttkwidgets import ItemsCanvas from ..ttkwidgets import _designer_tab_label, _plugin_uid class ItemsCanvasBO(TTKFrame): class_ = ItemsCanvas container = False OPTIONS_CUSTOM = ( "canvaswidth", "canvasheight", "callback_add", "callback_del", "callback_move", "function_new", ) properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) command_properties = ( "callback_add", "callback_del", "callback_move", "function_new", ) def _process_property_value(self, pname, value): if pname in ("canvaswidth", "canvasheight"): return int(value) return super(ItemsCanvasBO, self)._process_property_value(pname, value) def _code_define_callback_args(self, cmd_pname, cmd): args = None if cmd_pname in ("callback_add", "callback_del"): args = ("item", "rectangle") elif cmd_pname == "callback_move": args = ("item", "rectangle", "x", "y") elif cmd_pname == "function_new": args = ("add_item",) else: args = super(ItemsCanvasBO, self)._code_define_callback_args( cmd_pname, cmd ) return args _builder_uid = f"{_plugin_uid}.ItemsCanvas" register_widget( _builder_uid, ItemsCanvasBO, "ItemsCanvas", ("ttk", _designer_tab_label), group=6, ) register_custom_property( _builder_uid, "canvaswidth", "integernumber", help=_("width of the canvas in pixels"), ) register_custom_property( _builder_uid, "canvasheight", "integernumber", help=_("height of the canvas in pixels"), ) register_custom_property( _builder_uid, "callback_add", "simplecommandentry", help=_( "callback for when an item is created, with args: (int item, int rectangle)" ), ) register_custom_property( _builder_uid, "callback_del", "simplecommandentry", help=_( "callback for when an item is deleted, with args: (int item, int rectangle)" ), ) register_custom_property( _builder_uid, "callback_move", "simplecommandentry", help=_( "callback for when an item is moved, with args: (int item, int rectangle, int x, int y)" ), ) register_custom_property( _builder_uid, "function_new", "simplecommandentry", help=_( "user defined function for when an item is created, with arg (add_item)." + "\nWhere add_item is a function of this widget." ), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/linklabel.py000066400000000000000000000021561474524032100234050ustar00rootroot00000000000000from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.ttk.ttkstdwidgets import TTKLabel from ttkwidgets import LinkLabel from ..ttkwidgets import _designer_tab_label, _plugin_uid class LinkLabelBO(TTKLabel): class_ = LinkLabel OPTIONS_CUSTOM = ("link", "normal_color", "hover_color", "clicked_color") properties = ( TTKLabel.OPTIONS_STANDARD + TTKLabel.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) _builder_uid = f"{_plugin_uid}.LinkLabel" register_widget( _builder_uid, LinkLabelBO, "LinkLabel", ("ttk", _designer_tab_label), group=1, ) register_custom_property( _builder_uid, "link", "entry", help=_("link to be opened") ) register_custom_property( _builder_uid, "normal_color", "colorentry", help=_("text color when widget is created"), ) register_custom_property( _builder_uid, "hover_color", "colorentry", help=_("text color when hovering over the widget"), ) register_custom_property( _builder_uid, "clicked_color", "colorentry", help=_("text color when link is clicked"), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/scaleentry.py000066400000000000000000000050251474524032100236170ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.ttk.ttkstdwidgets import TTKFrame from ttkwidgets.scaleentry import ScaleEntry from ..ttkwidgets import _designer_tab_label, _plugin_uid class ScaleEntryBO(TTKFrame): class_ = ScaleEntry container = False OPTIONS_CUSTOM = ( "scalewidth", "entrywidth", "from_", "to", "orient", "compound", "entryscalepad", ) properties = ( TTKFrame.OPTIONS_STANDARD + TTKFrame.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) def _process_property_value(self, pname, value): final_value = value if pname in ( "scalewidth", "entrywidth", "from_", "to", "entryscalepad", ): final_value = int(value) elif pname == "compound": final_value = tk.RIGHT values = { "top": tk.TOP, "bottom": tk.BOTTOM, "left": tk.LEFT, "right": tk.RIGHT, } if value in values: final_value = values[value] else: final_value = super(ScaleEntryBO, self)._process_property_value( pname, value ) return final_value _builder_uid = f"{_plugin_uid}.ScaleEntry" register_widget( _builder_uid, ScaleEntryBO, "ScaleEntry", ("ttk", _designer_tab_label), group=3, ) register_custom_property( _builder_uid, "compound", "choice", default_value=tk.RIGHT, help=_("side the Entry must be on."), values=("", tk.TOP, tk.BOTTOM, tk.LEFT, tk.RIGHT), state="readonly", ) register_custom_property( _builder_uid, "orient", "choice", default_value=tk.HORIZONTAL, help=_("scale orientation"), values=("", tk.HORIZONTAL, tk.VERTICAL), state="readonly", ) register_custom_property( _builder_uid, "scalewidth", "naturalnumber", help=_("width of the Scale in pixels"), ) register_custom_property( _builder_uid, "entrywidth", "naturalnumber", help=_("width of the Entry in characters"), ) register_custom_property( _builder_uid, "from_", "naturalnumber", help=_("start value of the scale") ) register_custom_property( _builder_uid, "to", "naturalnumber", help=_("end value of the scale") ) register_custom_property( _builder_uid, "entryscalepad", "naturalnumber", help=_("space between the entry and the scale"), ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/scrolledlistbox.py000066400000000000000000000037271474524032100246710ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.tk.tkstdwidgets import TKListbox from ttkwidgets.scrolledlistbox import ScrolledListbox from ..ttkwidgets import _designer_tab_label, _plugin_uid class ScrolledListboxBO(TKListbox): class_ = ScrolledListbox container = False OPTIONS_CUSTOM = ("compound", "autohidescrollbar") properties = ( TKListbox.OPTIONS_STANDARD + TKListbox.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TKListbox.ro_properties + OPTIONS_CUSTOM def configure(self, target=None): super(ScrolledListboxBO, self).configure(self.widget.listbox) def _process_property_value(self, pname, value): final_value = value if pname in ("autohidescrollbar",): final_value = tk.getboolean(value) elif pname == "compound": final_value = tk.RIGHT values = {"left": tk.LEFT, "right": tk.RIGHT} if value in values: final_value = values[value] else: final_value = super( ScrolledListboxBO, self )._process_property_value(pname, value) return final_value def code_configure(self, targetid=None): if targetid is None: targetid = self.code_identifier() newtarget = f"{targetid}.listbox" return super(ScrolledListboxBO, self).code_configure(newtarget) _builder_uid = f"{_plugin_uid}.ScrolledListbox" register_widget( _builder_uid, ScrolledListboxBO, "ScrolledListbox", ("ttk", _designer_tab_label), group=3, ) register_custom_property( _builder_uid, "compound", "choice", default_value=tk.RIGHT, help="side for the Scrollbar to be on", values=("", tk.LEFT, tk.RIGHT), state="readonly", ) register_custom_property( _builder_uid, "autohidescrollbar", "choice", values=("", "true", "false"), state="readonly", ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/table.py000066400000000000000000000056361474524032100225450ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.ttk.ttkstdwidgets import TTKTreeviewBO, TTKTreeviewColumnBO from pygubu.plugins.pygubu.scrollbarhelper_bo import TTKSBHelperBO from ttkwidgets.table import Table from ..ttkwidgets import _designer_tab_label, _plugin_uid class TableBO(TTKTreeviewBO): class_ = Table OPTIONS_SPECIFIC = ("height", "padding", "selectmode") OPTIONS_CUSTOM = ("drag_cols", "drag_rows", "sortable") properties = ( TTKTreeviewBO.OPTIONS_STANDARD + OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) def _process_property_value(self, pname, value): final_value = value if pname in ("drag_cols", "drag_rows", "sortable"): final_value = tk.getboolean(value) else: final_value = super(TableBO, self)._process_property_value( pname, value ) return final_value _builder_uid = _table_uid = f"{_plugin_uid}.Table" register_widget( _builder_uid, TableBO, "Table", ("ttk", _designer_tab_label), group=6 ) register_custom_property( _builder_uid, "drag_cols", "choice", values=("", "true", "false"), default_value="true", help=_("whether columns are draggable"), ) register_custom_property( _builder_uid, "drag_rows", "choice", values=("", "true", "false"), default_value="true", help=_("whether rows are draggable"), ) register_custom_property( _builder_uid, "sortable", "choice", values=("", "true", "false"), default_value="true", help=_( "whether columns are sortable by clicking on their headings. " + "The sorting order depends on the type of data (str, float, ...) which can be set with the column method." ), ) TTKSBHelperBO.add_allowed_child(_table_uid) class TableColumnBO(TTKTreeviewColumnBO): OPTIONS_CUSTOM = ("type",) properties = ( TTKTreeviewBO.OPTIONS_STANDARD + TTKTreeviewBO.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) allowed_parents = (_table_uid,) def _get_column_properties(self, props): colprop = super(TableColumnBO, self)._get_column_properties(props) type_value = props.get("type", None) if type_value is not None: final_value = str if type_value in ("str", "int", "float", "bool"): final_value = eval(type_value) colprop["type"] = final_value # Setup required anchor if colprop["anchor"] == "": colprop["anchor"] = "nw" return colprop _builder_uid = f"{_plugin_uid}.Table.Column" TableBO.add_allowed_child(_builder_uid) register_widget( _builder_uid, TableColumnBO, "Table.Column", ("ttk", _designer_tab_label), group=6, ) register_custom_property( _builder_uid, "type", "choice", values=("", "str", "int", "float", "bool"), state="readonly", ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/tickscale.py000066400000000000000000000041501474524032100234060ustar00rootroot00000000000000import tkinter as tk from pygubu.i18n import _ from pygubu.api.v1 import register_widget, register_custom_property from pygubu.plugins.ttk.ttkstdwidgets import TTKScale from ttkwidgets.tickscale import TickScale from ..ttkwidgets import _designer_tab_label, _plugin_uid class TickScaleBO(TTKScale): class_ = TickScale OPTIONS_CUSTOM = ( "digits", "labelpos", "resolution", "showvalue", "tickinterval", "tickpos", ) properties = ( TTKScale.OPTIONS_STANDARD + TTKScale.OPTIONS_SPECIFIC + OPTIONS_CUSTOM ) ro_properties = TTKScale.ro_properties + ("from_", "to") def _process_property_value(self, pname, value): final_value = value if pname in ("digits",): final_value = int(value) elif pname in ("from", "to_", "resolution", "tickinterval"): final_value = float(value) else: final_value = super(TickScaleBO, self)._process_property_value( pname, value ) return final_value _builder_uid = f"{_plugin_uid}.TickScale" register_widget( _builder_uid, TickScaleBO, "TickScale", ("ttk", _designer_tab_label), group=3, ) register_custom_property(_builder_uid, "digits", "naturalnumber") register_custom_property( _builder_uid, "resolution", "realnumber", help=_( "increment by which the slider can be moved. 0 means continuous sliding." ), ) register_custom_property( _builder_uid, "tickinterval", "realnumber", help=_("if not 0, display ticks with the given interval"), ) register_custom_property( _builder_uid, "showvalue", "choice", values=("", "true", "false"), state="readonly", ) register_custom_property( _builder_uid, "labelpos", "choice", help="if showvalue is True, position of the label", values=("", tk.N, tk.S, tk.E, tk.W), state="readonly", ) register_custom_property( _builder_uid, "tickpos", "choice", help=_("if tickinterval is not 0, position of the ticks"), values=("", tk.N, tk.S, tk.E, tk.W), state="readonly", ) pygubu-0.36.1/src/pygubu/plugins/ttkwidgets/utils.py000066400000000000000000000053601474524032100226100ustar00rootroot00000000000000from pygubu.api.v1 import BuilderObject class AutocompleteBaseBO(BuilderObject): init_completevalues = True def _get_init_args(self, extra_init_args: dict = None): args = super()._get_init_args(extra_init_args) if self.init_completevalues: prop = "completevalues" if prop not in self.wmeta.properties: args[prop] = "" return args def _process_property_value(self, pname, value): final_value = value if pname == "completevalues": final_value = value.split() else: final_value = super( AutocompleteBaseBO, self )._process_property_value(pname, value) return final_value def _code_process_property_value(self, targetid, pname, value): if pname == "completevalues": cvalues = None try: cvalues = f"{value.split()}" except AttributeError: pass return cvalues return super()._code_process_property_value(targetid, pname, value) class CallbakInitArgMixin: """Some widgtes of the ttkwidget set, have a callback argument in the constructor and the widget does not allow to use configure method to modify it. So create a mixin class to configure the callback with a trick. """ def _get_init_args(self, extra_init_args: dict = None): args = super()._get_init_args(extra_init_args) class CBProxy: def __init__(self): self.real_cb = None def __call__(self, family): if self.real_cb is not None: self.real_cb(family) args["callback"] = cb = CBProxy() self._cb_proxy = cb return args def _connect_command(self, cmd_pname, callback): # continue the trick for setting the callback init argument if cmd_pname == "callback": self._cb_proxy.real_cb = callback else: super(CallbakInitArgMixin, self)._connect_command( cmd_pname, callback ) def _code_get_init_args(self, code_identifier): args = super(CallbakInitArgMixin, self)._code_get_init_args( code_identifier ) pname = "callback" if pname in self.wmeta.properties: pvalue = self.wmeta.properties[pname] args["callback"] = self._code_process_property_value( code_identifier, pname, pvalue ) return args def _code_connect_command(self, cmd_pname, cmd, cbname): if cmd_pname == "callback": return [] else: return super(CallbakInitArgMixin, self)._code_connect_command( cmd_pname, cmd, cbname ) pygubu-0.36.1/src/pygubu/stockimage/000077500000000000000000000000001474524032100173465ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/stockimage/__init__.py000066400000000000000000000004261474524032100214610ustar00rootroot00000000000000from .registry import StockRegistry from .loader import StockImageCache from .exceptions import StockImageException from .config import TK_BITMAP_FORMATS, TK_IMAGE_FORMATS, TK_PHOTO_FORMATS DefaultRegistry = StockRegistry() StockImage = StockImageCache(None, DefaultRegistry) pygubu-0.36.1/src/pygubu/stockimage/config.py000066400000000000000000000034371474524032100211740ustar00rootroot00000000000000import os import tkinter as tk BITMAP_TEMPLATE = "@{0}" TK_BITMAP_FORMATS = [".xbm"] TK_PHOTO_FORMATS = [".gif", ".pgm", ".ppm"] if os.name == "nt": TK_BITMAP_FORMATS.append(".ico") BITMAP_TEMPLATE = "{0}" if tk.TkVersion >= 8.6: TK_PHOTO_FORMATS.append(".png") if tk.TkVersion >= 9.0: TK_PHOTO_FORMATS.append(".svg") TK_IMAGE_FORMATS = TK_PHOTO_FORMATS + TK_BITMAP_FORMATS _img_notsupported = """\ R0lGODlhZAAyAIQAAAAAAAsLCxMTExkZGSYmJicnJ11dXYGBgZubm5ycnJ2dnbGxsbOzs8TExMXF xdXV1dbW1uTk5PLy8v39/f7+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEK AB8ALAAAAABkADIAAAX+4CeOZGmeaKqubOu+cCzPMmDfeK7vfO//QJ5rQCwaj8ikcslsOpUswGBC qVqv2Kx2y+16v9nJAKAaFCiVtHrNbrvf8Lh83qYUBigplc7v+/9yFGJkJVJogIiJinAUYyaGi5GS iI2EJJBuDQ0UChFqmmkNCAqHnAijFKKnnmoRpwgNoagVrqexFaBpEaS0qxWms26VjwOHbaeNDJ4Q BwgVEAsICQ8SEg8Jp6QIBr5qEAG2z9HTEt+nCxAVCAfpEQzFEQ6nDhGcBga8wo6FxW/IAwISTCAA rtEDQQMgQChmRR2CKmwWUqmS8FdCiRQenEEQgMCEBAKKIaNwKk1JRvv+LvVz8+/BA4//Qo5RWEyB GZIBiKTzJmUAqYqNFPZMgObUQJcicw4AZ9IZSksjMB17mLCcw2QQHgigScGdSHYQJKyBIOABhHpA L5Zt1vRZua8K2TqMM4yfMTb/dl5Ne3YaBAfawIr1tpKTg6wJIixMhW5umsUnIzt9U1fl3TUKSBXQ m9lOOs+/7ihIY1Pn2DOYbz5DDeFMZm+uR1d4PVs25ZRRV9ZBcxeisd8QfzVkc3n4LzW8ewtPEzz4 bagipE6aTl0f9A/Sq2unXjm3cuTImxtfc2X58vLMxztHL9x3Q/bIcUfX3brU5tA+SRfRy/zOTdqd +cdaEbYtBJSAaBj+6JMdRLi2H3HyYUcfOJ4EFYFfgHXFQFmD6eKXQo79wwBiipX1FykNoAPNJgOM eE0E1gigDFbprKPQArcwF6FU5tAT1GLP9APkGheaxYpkWL1IllkZItkiiRZ99mSNTp2k43U81iTQ RUJ2eRl+RIVIVUis9SSbkwKEVEpaZJJU5WQWYUkfQw/MBOSdupGX0UZvGhQcRoc4idSaUh5U1Jvk 7TgnGjGGdQ0CjQV5WS2QtiMPAj5WRNhd8cyDlqOJRWlRM9pwgykrVxJjzC6ldPKLArC0kk8rr+RY S4WuyjqpL5zg6uur2aTyCqqp2rXdsdwp+iWyzP7R3Xx7NCutHwiGXSeCatNmS9cdKugBxre7fSvu uFcMwsIT6KKGnH/otuuuES4EIW+elchr77050ECDdM/q6++/M/AbIcAEF5yCwNYarLDC2P5i7sIQ K+ztunhEbHHBUlV78cb/YsIgxyDr663GIZcMgw3wmqxyvPmu7HIeCb8sc1Qxz2zzzTjnrPPOPPcs QwgAOw== """ pygubu-0.36.1/src/pygubu/stockimage/exceptions.py000066400000000000000000000002551474524032100221030ustar00rootroot00000000000000class StockImageException(Exception): pass class ImageNotFoundError(StockImageException): pass class ImageFormatNotSupportedError(StockImageException): pass pygubu-0.36.1/src/pygubu/stockimage/loader.py000066400000000000000000000071711474524032100211740ustar00rootroot00000000000000import logging from .exceptions import ImageFormatNotSupportedError, StockImageException from .registry import StockRegistry, StockItem from .config import _img_notsupported logger = logging.getLogger(__name__) class StockImageCache: IMAGE_NOT_SUPPORTED = "img_not_supported" def __init__(self, tkroot, registry: StockRegistry): self.tkroot = tkroot self.registry: StockRegistry = registry self._cached = {} if not registry.is_registered(self.IMAGE_NOT_SUPPORTED): self.register_from_data( self.IMAGE_NOT_SUPPORTED, "gif", _img_notsupported ) def clear_cache(self): """Call this before closing tk root""" # Prevent tkinter errors on python 2 ?? for key in self._cached: self._cached[key] = None self._cached = {} def register(self, image_id, filename): """Register a image file using image_id""" self.registry.register(image_id, filename) def register_from_package(self, image_id, fpath): self.registry.register_from_package(image_id, fpath) def register_from_data(self, image_id, format, data): """Register a image data using image_id""" self.registry.register_from_data(image_id, format, data) def register_created(self, image_id, image): """Register an already created image using image_id""" if image_id in self._cached: logger.warning("Warning, replacing resource {0}", image_id) self._cached[image_id] = image logger.info("data registered as %s", image_id) def is_registered(self, image_id): return image_id in self._cached or self.registry.is_registered(image_id) def register_all_from_dir( self, dir_path, prefix=None, ext=None, recurse=False ): self.registry.register_all_from_dir(dir_path, prefix, ext, recurse) def register_all_from_pkg(self, pkg, prefix=None, ext=None, recurse=False): self.registry.register_all_from_pkg(pkg, prefix, ext, recurse) def get(self, image_id, custom_loader=None): """Get image previously registered with key image_id. If key not exist, raise StockImageException """ if image_id in self._cached: logger.info("Resource %s is in cache.", image_id) return self._cached[image_id] if self.registry.is_registered(image_id): img = self._load_image(image_id, custom_loader) return img else: raise StockImageException(f"StockImage: {image_id} not registered.") def _load_image(self, image_id, custom_loader=None): """Load image from file or return the cached instance.""" stock_item: StockItem = self.registry.get_item(image_id) img = None try: img = stock_item.create_image( tk_master=self.tkroot, custom_loader=custom_loader ) except ImageFormatNotSupportedError: msg = "Error loading image %s, try installing Pillow module." logger.error(msg, image_id) img = self.get(self.IMAGE_NOT_SUPPORTED) self._cached[image_id] = img logger.info("Loaded resource data for %s.", image_id) return img def as_iconbitmap(self, image_id): """Get image path for use in iconbitmap property""" return self.registry.as_iconbitmap(image_id) def add_resource_path(self, path): self.registry.add_resource_path(path) def add_resource_package(self, package): self.registry.add_resource_package(package) def find_and_register(self, image_id): self.registry.find_and_register(image_id) pygubu-0.36.1/src/pygubu/stockimage/registry.py000066400000000000000000000212351474524032100215730ustar00rootroot00000000000000import logging import sys import tkinter as tk from pathlib import Path from collections import namedtuple from dataclasses import dataclass from enum import Enum from typing import Any from .exceptions import ImageFormatNotSupportedError, ImageNotFoundError from .config import ( TK_IMAGE_FORMATS, TK_BITMAP_FORMATS, BITMAP_TEMPLATE, TK_PHOTO_FORMATS, ) if sys.version_info < (3, 9): import importlib_resources as resources else: import importlib.resources as resources logger = logging.getLogger(__name__) def _iter_package_files(pkg: str): subpkg = [] try: for r in resources.files(pkg).iterdir(): if r.is_file(): yield r if r.is_dir(): subpkg.append(r.name) except NotADirectoryError: pass for s in subpkg: yield from _iter_package_files(f"{pkg}/{s}") class PitType(Enum): PATH = 1 PACKAGE = 2 @dataclass class StockItem: def create_image(self, *, tk_master=None, custom_loader=None): ... @dataclass class ImgFromPath(StockItem): fpath: Any = None def create_image(self, *, tk_master=None, custom_loader=None): file_ext = self.fpath.suffix.lower() if custom_loader is not None: img = custom_loader(PitType.PATH, self.fpath, tk_master) elif file_ext in TK_PHOTO_FORMATS: img = tk.PhotoImage(file=self.fpath, master=tk_master) elif file_ext in TK_BITMAP_FORMATS: img = tk.BitmapImage(file=self.fpath, master=tk_master) else: img = self._create_with_pillow(self.fpath, tk_master) return img def _create_with_pillow(self, fpath, tk_master): try: from PIL import Image, ImageTk aux = Image.open(fpath) img = ImageTk.PhotoImage(aux, master=tk_master) return img except ModuleNotFoundError: msg = f"Error loading {self.fpath}, image format not supported." raise ImageFormatNotSupportedError(msg) @dataclass class ImgFromPackage(ImgFromPath): def create_image(self, *, tk_master=None, custom_loader=None): file_ext = Path(str(self.fpath)).suffix.lower() with resources.as_file(self.fpath) as file: if custom_loader is not None: img = custom_loader(PitType.PACKAGE, file, tk_master) elif file_ext in TK_PHOTO_FORMATS: img = tk.PhotoImage(file=file, master=tk_master) elif file_ext in TK_BITMAP_FORMATS: img = tk.BitmapImage(file=file, master=tk_master) else: img = self._create_with_pillow(file, tk_master) return img @dataclass class ImgFromData(StockItem): data: Any = None format: str = None def create_image(self, *, tk_master=None, custom_loader=None): return tk.PhotoImage( format=self.format, data=self.data, master=tk_master ) class StockRegistry: """Maintain image source definitions to load.""" def __init__(self): self._stock = {} self._formats = TK_IMAGE_FORMATS self._resource_pit = {} def _register_stock_item(self, image_id, cache_item): if image_id in self._stock: logger.warning("Warning, replacing resource %s", image_id) self._stock[image_id] = cache_item def register(self, image_id, filename): """Register a image file using image_id""" fpath = Path(filename) if isinstance(filename, str) else filename self._register_stock_item(image_id, ImgFromPath(fpath)) logger.info("%s registered as %s", filename, image_id) def register_from_package(self, image_id, fpath): self._register_stock_item(image_id, ImgFromPackage(fpath)) logger.info("%s registered as %s", fpath, image_id) def register_from_data(self, image_id, format, data): """Register a image data using image_id""" if image_id in self._stock: logger.warning("Warning, replacing resource %s", image_id) self._stock[image_id] = ImgFromData(data, format) logger.info("%s registered as %s", "data", image_id) def is_registered(self, image_id): return image_id in self._stock def get_item(self, image_id): return self._stock[image_id] def register_all_from_dir( self, dir_path, prefix=None, ext=None, recurse=False ): """List files from dir_path and register images with filename as key (without extension) :param str dir_path: path to search for images. :param str prefix: Additionaly a prefix for the key can be provided, so the resulting key will be prefix + filename :param iterable ext: list of file extensions to load. Defaults to tk supported image extensions. Example ('.jpg', '.png') :param boolean recurse: search recursivelly. """ prefix = "" if prefix is None else prefix if ext is None: ext = TK_IMAGE_FORMATS path_gen = Path(dir_path).iterdir() if recurse: path_gen = Path(dir_path).glob("**/*") for filename in path_gen: if filename.is_file(): name = filename.stem file_ext = filename.suffix if file_ext in ext: fkey = f"{prefix}{name}" self.register(fkey, filename) def register_all_from_pkg(self, pkg, prefix=None, ext=None, recurse=False): """List files from package and register images with filename as key (without extension) :param str pkg: package to search for images. :param str prefix: Additionaly a prefix for the key can be provided, so the resulting key will be prefix + filename :param iterable ext: list of file extensions to load. Defaults to tk supported image extensions. Example ('.jpg', '.png') :param boolean recurse: search recursivelly. """ prefix = "" if prefix is None else prefix if ext is None: ext = TK_IMAGE_FORMATS path_gen = resources.files(pkg).iterdir() if recurse: path_gen = _iter_package_files(pkg) for pkg_path in path_gen: if pkg_path.is_file(): fpath = Path(str(pkg_path)) name = fpath.stem file_ext = fpath.suffix if file_ext in ext: image_id = f"{prefix}{name}" self.register_from_package(image_id, pkg_path) def add_resource_path(self, path): if path not in self._resource_pit: self._resource_pit[path] = PitType.PATH def add_resource_package(self, package): if package not in self._resource_pit: self._resource_pit[package] = PitType.PACKAGE def find_and_register(self, image_id): """Find and register image from the resource pits.""" image_path = None pattern = f"*{image_id}" for pit, pit_type in self._resource_pit.items(): if pit_type == PitType.PATH: try: image_path = self._find_in_path(pit, pattern) except TypeError: pass if image_path: self.register(image_id, image_path) break else: try: image_path = self._find_in_package(pit, pattern) except ModuleNotFoundError: pass if image_path: self.register_from_package(image_id, image_path) break if image_path is None: msg = f"Error: image {image_id} not found in resource pits." raise ImageNotFoundError(msg) @staticmethod def _find_in_path(path_src, pattern): found = None for p in Path(path_src).glob("**/*"): if p.is_file() and p.match(pattern): found = p break return found @staticmethod def _find_in_package(pkg_src, pattern): found = None for r in _iter_package_files(pkg_src): p2 = Path(str(r)) if r.is_file() and p2.match(pattern): found = r break return found def as_iconbitmap(self, image_id): """Get image path for use in iconbitmap property""" img = None if image_id in self._stock: cache_item = self._stock[image_id] if isinstance(cache_item, ImgFromPath): fpath = cache_item.fpath file_ext = fpath.suffix.lower() if file_ext in TK_BITMAP_FORMATS: img = BITMAP_TEMPLATE.format(fpath) return img pygubu-0.36.1/src/pygubu/theming/000077500000000000000000000000001474524032100166535ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/theming/__init__.py000066400000000000000000000000001474524032100207520ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/theming/base.py000066400000000000000000000024371474524032100201450ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk from abc import ABC, ABCMeta, abstractmethod class IThemeBuilder(ABC): @abstractmethod def theme_name(self) -> str: ... @abstractmethod def theme_parent(self) -> str: ... @abstractmethod def theme_settings(self): ... @abstractmethod def db_settings(self): ... @abstractmethod def tk_palette(self): ... def create(self, master): style = ttk.Style(master) name = self.theme_name() parent = self.theme_parent() if name not in style.theme_names(): theme_settings = self.theme_settings() style.theme_create(name, parent, theme_settings) else: raise ValueError("Theme name already exists.") def apply(self, master: tk.Widget): # master.option_clear() palette = self.tk_palette() if palette: master.tk_setPalette(**palette) db_settings = self.db_settings() for pattern, options in db_settings.items(): for option, value in options.items(): fpattern = f"{pattern}{option}" master.option_add(fpattern, value, "widgetDefault") style = ttk.Style(master) style.theme_use(self.theme_name()) pygubu-0.36.1/src/pygubu/theming/bootstrap/000077500000000000000000000000001474524032100206705ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/theming/bootstrap/__init__.py000066400000000000000000000000001474524032100227670ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/theming/bootstrap/assets.py000066400000000000000000000614601474524032100225530ustar00rootroot00000000000000import tkinter as tk import math from ..photodraw import Draw from .config import ( PRIMARY, SECONDARY, SUCCESS, INFO, WARNING, DANGER, LIGHT, DARK, TTK_CLAM, TTK_ALT, TTK_DEFAULT, ) class AssetCreator: def __init__(self, theme_builder): self.builder = theme_builder self.theme_images = {} @property def colors(self): return self.builder.colors @property def colorutil(self): return self.builder.colorutil @property def tk_master(self): return self.builder.tk_master @property def is_light_theme(self): return self.builder.is_light_theme def create_separator_assets(self, size, color): ssize = self.builder.scale_size(size) w = ssize[0] h = ssize[1] draw = Draw(self.tk_master) h_img = draw.canvas_new(width=w, height=h) draw.rectangle(0, 0, w, h, fill=color) v_img = draw.canvas_new(width=h, height=w) draw.rectangle(0, 0, h, w, fill=color) self.theme_images[h_img.name] = h_img self.theme_images[v_img.name] = v_img return (h_img.name, v_img.name) def create_sizegrip_assets(self, color): """Create image assets used to build the sizegrip style. Parameters: color (str): The color _value_ used to draw the image. Returns: str: The PhotoImage name. """ box = self.builder.scale_size(1) pad = box * 2 chunk = box + pad # 4 w = chunk * 3 + pad h = chunk * 3 + pad # size = [w, h] draw = Draw(self.tk_master) canvas = draw.canvas_new(width=w, height=h) draw.rectangle(chunk * 2 + pad, pad, chunk * 3, chunk, fill=color) draw.rectangle( chunk * 2 + pad, chunk + pad, chunk * 3, chunk * 2, fill=color ) draw.rectangle( chunk * 2 + pad, chunk * 2 + pad, chunk * 3, chunk * 3, fill=color, ) draw.rectangle( chunk + pad, chunk + pad, chunk * 2, chunk * 2, fill=color ) draw.rectangle( chunk + pad, chunk * 2 + pad, chunk * 2, chunk * 3, fill=color ) draw.rectangle(pad, chunk * 2 + pad, chunk, chunk * 3, fill=color) self.theme_images[canvas.name] = canvas return canvas.name def create_radiobutton_assets(self, colorname=None): """Create the image assets used to build the radiobutton style. Parameters: colorname (str): Returns: Tuple[str]: A tuple of PhotoImage names """ prime_color = self.colors.get_color(colorname) on_fill = prime_color off_fill = self.colors.bg on_indicator = self.colors.selectfg size = self.builder.scale_size([14, 14]) draw = Draw(self.tk_master) off_border = self.colorutil.make_transparent( 0.4, self.colors.fg, self.colors.bg ) disabled = self.colorutil.make_transparent( 0.3, self.colors.fg, self.colors.bg ) if self.is_light_theme: if colorname == LIGHT: on_indicator = self.colors.dark canvas_size = 134 canvas = draw.canvas_new(width=canvas_size, height=canvas_size) # radio off x1 = y1 = 1 x2 = y2 = 133 stroke = 16 draw.circle( x1, y1, x2, y2, fill=off_fill, outline=off_border, stroke=stroke ) scale_factor = size[0] / canvas_size off_img = draw.canvas_scale(scale_factor) off_name = off_img.name self.theme_images[off_name] = off_img # radio on draw.canvas_blank() if colorname == LIGHT and self.is_light_theme: draw.circle(x1, y1, x2, y2, outline=off_border, stroke=stroke) else: draw.circle(x1, y1, x2, y2, fill=on_fill) ix1 = iy1 = 40 ix2 = iy2 = 94 draw.circle(ix1, iy1, ix2, iy2, fill=on_indicator) on_img = draw.canvas_scale(scale_factor) on_name = on_img.name self.theme_images[on_name] = on_img # radio on/disabled draw.canvas_blank() if colorname == LIGHT and self.is_light_theme: draw.circle(x1, y1, x2, y2, outline=off_border, stroke=stroke) else: draw.circle(x1, y1, x2, y2, fill=disabled) draw.circle(ix1, iy1, ix2, iy2, fill=off_fill) on_dis_img = draw.canvas_scale(scale_factor) on_disabled_name = on_dis_img.name self.theme_images[on_disabled_name] = on_dis_img # radio disabled draw.canvas_blank() stroke_3 = 10 draw.circle( x1, y1, x2, y2, outline=disabled, stroke=stroke_3, fill=off_fill ) disabled_img = draw.canvas_scale(scale_factor) disabled_name = disabled_img.name self.theme_images[disabled_name] = disabled_img del canvas return off_name, on_name, disabled_name, on_disabled_name def create_scrollbar_assets(self, thumbcolor, pressed, active): """Create the image assets used to build the standard scrollbar style. Parameters: thumbcolor (str): The primary color value used to color the thumb. pressed (str): The color value to use when the thumb is pressed. active (str): The color value to use when the thumb is active or hovered. """ vsize = self.builder.scale_size([9, 28]) hsize = self.builder.scale_size([28, 9]) draw = Draw(self.tk_master) def create_image(size, color): # x = size[0] * 10 # y = size[1] * 10 img = draw.canvas_new() draw.rectangle(0, 0, size[0], size[1], fill=color) self.theme_images[img.name] = img return img # create images h_normal_img = create_image(hsize, thumbcolor) h_pressed_img = create_image(hsize, pressed) h_active_img = create_image(hsize, active) v_normal_img = create_image(vsize, thumbcolor) v_pressed_img = create_image(vsize, pressed) v_active_img = create_image(vsize, active) return ( h_normal_img, h_pressed_img, h_active_img, v_normal_img, v_pressed_img, v_active_img, ) def create_scale_assets(self, colorname=None, size=14): """Create the assets used for the ttk.Scale widget. The slider handle is automatically adjusted to fit the screen resolution. Parameters: colorname (str): The color label. size (int): The size diameter of the slider circle; default=16. Returns: Tuple[str]: A tuple of PhotoImage names to be used in the image layout when building the style. """ size = self.builder.scale_size(size) draw = Draw(self.tk_master) if self.is_light_theme: disabled_color = self.colors.border if colorname == LIGHT: track_color = self.colors.bg else: track_color = self.colors.light else: disabled_color = self.colors.selectbg track_color = self.colorutil.update_hsv( self.colors.selectbg, vd=-0.2 ) if any([colorname is None, not colorname]): normal_color = self.colors.primary else: normal_color = self.colors.get_color(colorname) pressed_color = self.colorutil.update_hsv(normal_color, vd=-0.1) hover_color = self.colorutil.update_hsv(normal_color, vd=0.1) canvas_size = 100 scale_factor = size / canvas_size # normal state canvas = draw.canvas_new(width=canvas_size, height=canvas_size) x1 = y1 = 0 x2 = y2 = 95 draw.circle(x1, y1, x2, y2, fill=normal_color) normal_img = draw.canvas_scale(scale_factor) normal_name = normal_img.name self.theme_images[normal_name] = normal_img # pressed state draw.canvas_blank() draw.circle(x1, y1, x2, y2, fill=pressed_color) pressed_img = draw.canvas_scale(scale_factor) pressed_name = pressed_img.name self.theme_images[pressed_name] = pressed_img # hover state draw.canvas_blank() draw.circle(x1, y1, x2, y2, fill=hover_color) hover_img = draw.canvas_scale(scale_factor) hover_name = hover_img.name self.theme_images[hover_name] = hover_img # disabled state draw.canvas_blank() draw.circle(x1, y1, x2, y2, fill=disabled_color) disabled_img = draw.canvas_scale(scale_factor) disabled_name = disabled_img.name self.theme_images[disabled_name] = disabled_img del canvas def create_image(size, color): img = draw.canvas_new() draw.rectangle(0, 0, size[0], size[1], fill=color) self.theme_images[img.name] = img return img # vertical track ?? h_track_img = create_image( self.builder.scale_size((40, 5)), track_color ) h_track_name = h_track_img.name # horizontal track ?? v_track_img = create_image( self.builder.scale_size((5, 40)), track_color ) v_track_name = v_track_img.name return ( normal_name, pressed_name, hover_name, disabled_name, h_track_name, v_track_name, ) def create_checkbutton_assets(self, colorname=None): """Create the image assets used to build the standard checkbutton style. Parameters: colorname (str): The color label used to style the widget. Returns: Tuple[str]: A tuple of PhotoImage names. """ draw = Draw(self.tk_master) prime_color = self.colors.get_color(colorname) on_border = prime_color on_fill = prime_color off_fill = self.colors.bg off_border = self.colors.selectbg off_border = self.colorutil.make_transparent( 0.4, self.colors.fg, self.colors.bg ) disabled_fg = self.colorutil.make_transparent( 0.3, self.colors.fg, self.colors.bg ) if colorname == LIGHT: check_color = self.colors.dark on_border = check_color elif colorname == DARK: check_color = self.colors.light on_border = check_color else: check_color = self.colors.selectfg size = self.builder.scale_size([14, 14]) canvas_size = 134 scale_factor = size[0] / canvas_size # checkbutton off canvas = draw.canvas_new(width=canvas_size, height=canvas_size) x1 = y1 = 2 x2 = y2 = 132 rec_radio = 32 stroke = 18 draw.rounded_rectangle( x1, y1, x2, y2, radio=rec_radio, fill=off_fill, outline=off_border, stroke=stroke, ) off_img = draw.canvas_scale(scale_factor) off_name = off_img.name self.theme_images[off_name] = off_img # checkbutton on draw.canvas_blank() rec_stroke_3 = 10 draw.rounded_rectangle( x1, y1, x2, y2, radio=rec_radio, stroke=rec_stroke_3, outline=on_border, fill=on_fill, ) shape = [ [(45, 85), (35, 85), (35, 100), (45, 100)], [(48, 87), (66, 60), (82, 60), (63, 87)], [(106, 27), (96, 27), (89, 30), (106, 30)], [(42, 75), (34, 75), (28, 80), (44, 80)], [(44, 80), (28, 80), (30, 90), (49, 90)], [(48, 90), (30, 90), (37, 107), (48, 107)], [(63, 87), (46, 87), (46, 107), (50, 107)], [(66, 60), (89, 30), (106, 30), (82, 60)], ] for poly in shape: draw.convex_poly(poly, check_color) # for tri in shape: # draw.triangle_filled(*tri, check_color) on_img = draw.canvas_scale(scale_factor) on_name = on_img.name self.theme_images[on_name] = on_img # checkbutton on/disabled draw.canvas_blank() draw.rounded_rectangle( x1, y1, x2, y2, radio=rec_radio, stroke=rec_stroke_3, outline=disabled_fg, fill=disabled_fg, ) for poly in shape: draw.convex_poly(poly, check_color) # for tri in shape: # draw.triangle_filled(*tri, off_fill) on_dis_img = draw.canvas_scale(scale_factor) on_dis_name = on_dis_img.name self.theme_images[on_dis_name] = on_dis_img # checkbutton alt draw.canvas_blank() draw.rounded_rectangle( x1, y1, x2, y2, radio=rec_radio, stroke=rec_stroke_3, outline=on_border, fill=on_fill, ) line_coords = [36, 58, 100, 70] draw.rectangle(*line_coords, fill=check_color) alt_img = draw.canvas_scale(scale_factor) alt_name = alt_img.name self.theme_images[alt_name] = alt_img # checkbutton alt/disabled draw.canvas_blank() draw.rounded_rectangle( x1, y1, x2, y2, radio=rec_radio, stroke=rec_stroke_3, outline=disabled_fg, fill=disabled_fg, ) draw.rectangle(*line_coords, fill=off_fill) alt_dis_img = draw.canvas_scale(scale_factor) alt_dis_name = alt_dis_img.name self.theme_images[alt_dis_name] = alt_dis_img # checkbutton disabled draw.canvas_blank() draw.rounded_rectangle( x1, y1, x2, y2, radio=rec_radio, stroke=rec_stroke_3, outline=disabled_fg, ) disabled_img = draw.canvas_scale(scale_factor) disabled_name = disabled_img.name self.theme_images[disabled_name] = disabled_img del canvas return ( off_name, on_name, disabled_name, alt_name, on_dis_name, alt_dis_name, ) def create_striped_progressbar_assets(self, thickness, colorname=None): """Create the striped progressbar image and return as a `PhotoImage` Parameters: colorname (str): The color label used to style the widget. Returns: Tuple[str]: A list of photoimage names. """ if any([colorname is None, colorname == ""]): barcolor = self.colors.primary else: barcolor = self.colors.get_color(colorname) draw = Draw(self.tk_master) # calculate value of the light color brightness = self.colorutil.rgb_to_hsv( *self.colorutil.hex_to_rgb(barcolor) )[2] if brightness < 0.4: value_delta = 0.3 elif brightness > 0.8: value_delta = 0 else: value_delta = 0.1 barcolor_light = self.colorutil.update_hsv( barcolor, sd=-0.2, vd=value_delta ) # horizontal progressbar canvas_size = 100 canvas = draw.canvas_new(width=canvas_size, height=canvas_size) draw.rectangle(0, 0, 100, 100, fill=barcolor_light) shape = [(0, 0), (48, 0), (100, 52), (100, 100)] draw.convex_poly(shape, barcolor) shape = [(0, 52), (48, 100), (0, 100)] draw.convex_poly(shape, barcolor) scale_factor = thickness / canvas_size h_img = draw.canvas_scale(scale_factor) h_name = h_img.name v_img = h_img.copy() v_name = v_img.name del canvas draw.canvas = v_img draw.canvas_rotate(90) self.theme_images[h_name] = h_img self.theme_images[v_name] = v_img return h_name, v_name def create_square_toggle_assets(self, colorname=None): """Create the image assets used to build a square toggle style. Parameters: colorname (str): The color label used to style the widget. Returns: Tuple[str]: A tuple of PhotoImage names. """ size = self.builder.scale_size([24, 15]) if any([colorname is None, colorname == ""]): colorname = PRIMARY # set default style color values prime_color = self.colors.get_color(colorname) on_border = prime_color on_indicator = self.colors.selectfg on_fill = prime_color off_fill = self.colors.bg draw = Draw(self.tk_master) disabled_fg = self.colorutil.make_transparent( 0.3, self.colors.fg, self.colors.bg ) off_border = self.colorutil.make_transparent( 0.4, self.colors.fg, self.colors.bg ) off_indicator = self.colorutil.make_transparent( 0.4, self.colors.fg, self.colors.bg ) # override defaults for light and dark colors if colorname == LIGHT: on_border = self.colors.dark on_indicator = on_border elif colorname == DARK: on_border = self.colors.light on_indicator = on_border canvasw = 226 canvash = 130 sfw = size[0] / canvasw sfh = size[1] / canvash stroke6 = 9 # toggle off canvas = draw.canvas_new(width=canvasw, height=canvash) draw.rectangle( 1, 1, 225, 129, outline=off_border, stroke=stroke6, fill=off_fill ) draw.rectangle(20, 20, 110, 110, fill=off_indicator) off_img = draw.canvas_scale(sfw, sfh) off_name = off_img.name self.theme_images[off_name] = off_img # toggle on draw.canvas_blank() draw.rectangle( 1, 1, 225, 129, outline=on_border, stroke=stroke6, fill=on_fill ) draw.rectangle(20, 20, 110, 110, fill=on_indicator) draw.canvas_rotate(180) on_img = draw.canvas_scale(sfw, sfh) on_name = on_img.name self.theme_images[on_name] = on_img # toggle disabled draw.canvas_blank() draw.rectangle(1, 1, 225, 129, outline=disabled_fg, stroke=stroke6) draw.rectangle(20, 20, 110, 110, fill=disabled_fg) disabled_img = draw.canvas_scale(sfw, sfh) disabled_name = disabled_img.name self.theme_images[disabled_name] = disabled_img # toggle on / disabled draw.canvas_blank() draw.rectangle( 1, 1, 225, 129, outline=disabled_fg, stroke=stroke6, fill=off_fill ) draw.rectangle(20, 20, 110, 110, fill=disabled_fg) draw.canvas_rotate(180) on_dis_img = draw.canvas_scale(sfw, sfh) on_disabled_name = on_dis_img.name self.theme_images[on_disabled_name] = on_dis_img del canvas return off_name, on_name, disabled_name, on_disabled_name def create_round_toggle_assets(self, colorname=None): """Create image assets for the round toggle style. Parameters: colorname (str): The color label assigned to the colors property. Returns: Tuple[str]: A tuple of PhotoImage names. """ size = self.builder.scale_size([24, 15]) if any([colorname is None, colorname == ""]): colorname = PRIMARY # set default style color values prime_color = self.colors.get_color(colorname) on_border = prime_color on_indicator = self.colors.selectfg on_fill = prime_color off_fill = self.colors.bg draw = Draw(self.tk_master) disabled_fg = self.colorutil.make_transparent( 0.3, self.colors.fg, self.colors.bg ) off_border = self.colorutil.make_transparent( 0.4, self.colors.fg, self.colors.bg ) off_indicator = self.colorutil.make_transparent( 0.4, self.colors.fg, self.colors.bg ) # override defaults for light and dark colors if colorname == LIGHT: on_border = self.colors.dark on_indicator = on_border elif colorname == DARK: on_border = self.colors.light on_indicator = on_border # specific canvasw = 226 canvash = 130 sfw = size[0] / canvasw sfh = size[1] / canvash stroke6 = 9 radius = int(128 / 2) # toggle off rec_coords = (1, 1, 225, 129) ellip_coords = (20, 18, 112, 110) canvas = draw.canvas_new(width=canvasw, height=canvash) draw.rounded_rectangle( *rec_coords, radio=radius, outline=off_border, stroke=stroke6, fill=off_fill, ) draw.circle(*ellip_coords, fill=off_indicator) off_img = draw.canvas_scale(sfw, sfh) off_name = off_img.name self.theme_images[off_name] = off_img # toggle on draw.canvas_blank() draw.rounded_rectangle( *rec_coords, radio=radius, outline=on_border, stroke=stroke6, fill=on_fill, ) draw.circle(*ellip_coords, fill=on_indicator) draw.canvas_rotate(180) on_img = draw.canvas_scale(sfw, sfh) on_name = on_img.name self.theme_images[on_name] = on_img # toggle on / disabled draw.canvas_blank() draw.rounded_rectangle( *rec_coords, radio=radius, outline=disabled_fg, stroke=stroke6, fill=off_fill, ) draw.circle(*ellip_coords, fill=disabled_fg) draw.canvas_rotate(180) on_dis_img = draw.canvas_scale(sfw, sfh) on_disabled_name = on_dis_img.name self.theme_images[on_disabled_name] = on_dis_img # toggle disabled draw.canvas_blank() draw.rounded_rectangle( *rec_coords, radio=radius, outline=disabled_fg, stroke=stroke6 ) draw.circle(*ellip_coords, fill=disabled_fg) disabled_img = draw.canvas_scale(sfw, sfh) disabled_name = disabled_img.name self.theme_images[disabled_name] = disabled_img del canvas return off_name, on_name, disabled_name, on_disabled_name def create_round_scrollbar_assets(self, thumbcolor, pressed, active): """Create image assets to be used when building the round scrollbar style. Parameters: thumbcolor (str): The color value of the thumb in normal state. pressed (str): The color value to use when the thumb is pressed. active (str): The color value to use when the thumb is active or hovered. """ vsize = self.builder.scale_size([9, 28]) hsize = self.builder.scale_size([28, 9]) draw_v = Draw(self.tk_master) vw = vsize[0] * 10 vh = vsize[1] * 10 vsfw = vsize[0] / vw vsfh = vsize[1] / vh vradius = min([vw, vh]) // 2 draw_v.canvas_new(width=vw, height=vh) hw = hsize[0] hh = hsize[1] hsfw = hsize[0] / hw hsfh = hsize[1] / hh hradius = min([hw, hh]) // 2 draw_h = Draw(self.tk_master) draw_h.canvas_new(width=hsize[0], height=hsize[1]) def vrounded_rect(color): draw_v.canvas_blank() draw_v.rounded_rectangle( 0, 0, vw - 1, vh - 1, radio=vradius, fill=color ) img = draw_v.canvas_scale(vsfw, vsfh) self.theme_images[img.name] = img return img.name def hrounded_rect(color): draw_h.canvas_blank() draw_h.rounded_rectangle( 0, 0, hw - 1, hh - 1, radio=hradius, fill=color ) img = draw_h.canvas_scale(hsfw, hsfh) self.theme_images[img.name] = img return img.name # create images h_normal_img = hrounded_rect(thumbcolor) h_pressed_img = hrounded_rect(pressed) h_active_img = hrounded_rect(active) v_normal_img = vrounded_rect(thumbcolor) v_pressed_img = vrounded_rect(pressed) v_active_img = vrounded_rect(active) del draw_h.canvas del draw_v.canvas return ( h_normal_img, h_pressed_img, h_active_img, v_normal_img, v_pressed_img, v_active_img, ) pygubu-0.36.1/src/pygubu/theming/bootstrap/builder.py000066400000000000000000003025641474524032100227020ustar00rootroot00000000000000import sys import os import tkinter as tk import tkinter.ttk as ttk import math import timeit import logging from tkinter import font from dataclasses import dataclass from ..base import IThemeBuilder from ..color import ColorUtil from .assets import AssetCreator from .config import ( FIX_LINUX_FILE_DIALOG, PRIMARY, SECONDARY, SUCCESS, INFO, WARNING, DANGER, LIGHT, DARK, TTK_CLAM, TTK_ALT, TTK_DEFAULT, ) logger = logging.getLogger(__name__) @dataclass class Colors: primary: str secondary: str success: str info: str warning: str danger: str light: str dark: str # -- bg: str fg: str selectbg: str selectfg: str border: str inputfg: str inputbg: str active: str def names(self): return iter( [ PRIMARY, SECONDARY, SUCCESS, INFO, WARNING, DANGER, LIGHT, DARK, ] ) def get_foreground(self, color_label): if color_label == LIGHT: return self.dark elif color_label == DARK: return self.light else: return self.selectfg def get_color(self, name): return getattr(self, name) class ThemeDefinition: THEME_LIGHT = LIGHT THEME_DARK = DARK def __init__(self, name: str, colors: dict, theme_type=THEME_LIGHT): self.name = name self.colors = Colors(**colors) self.type = theme_type _colect_styles = bool(os.getenv("PYGUBU_DESIGNER_RUNNING")) has_tk_version_9 = tk.TkVersion >= 9 class BootstrapThemeBuilder(IThemeBuilder): generated_styles = set() def __init__(self, theme_def): self.theme = theme_def self.tk_master = None self.colorutil = None self.existing_elements = {} self.assets = AssetCreator(self) self.tk_widgets_options = {} def theme_name(self): return self.theme.name def theme_parent(self): return "clam" def theme_settings(self): return self.build_theme_settings() def tk_palette(self): return self.build_tk_palette() def db_settings(self): self.build_tk_widget_settings() db = {} for widget, options in self.tk_widgets_options.items(): pattern = f"*{widget}." db[pattern] = options.copy() return db def create(self, master): self.tk_master = master self.colorutil = ColorUtil(master) super().create(master) if sys.platform == "linux" and FIX_LINUX_FILE_DIALOG: self._install_linux_filedialog_hack(master) def apply(self, master: tk.Widget): super().apply(master) if sys.platform == "linux" and FIX_LINUX_FILE_DIALOG: self._update_linux_filedialog_bg(master) def _get_filedialog_bg_color(self): color = self.colors.bg if self.is_light_theme else self.colors.selectbg color = self.colorutil.update_hsv(color, vd=0.18) return color def _install_linux_filedialog_hack(self, master): """Install script to modify dialog background color. For now, this only works for the first time the dialog window is created. """ script = """ # Enable filedialog namespace catch {tk_getOpenFile -badoption} # Install hack function if { [info exists ::tk::dialog::file::pbs_hack ] == 0 } { set ::tk::dialog::file::pbs_hack 1 # Set color for background set ::tk::dialog::file::pbs_filedialog_bg {color} rename ::tk::dialog::file::Create ::tk::dialog::file::_pbs_filedialog_create proc ::tk::dialog::file::Create {w class} { eval ::tk::dialog::file::_pbs_filedialog_create $w $class $w.contents.icons.cHull.canvas configure -background $::tk::dialog::file::pbs_filedialog_bg } } """ script = script.replace("{color}", self._get_filedialog_bg_color()) master.tk.eval(script) def _update_linux_filedialog_bg(self, master): script = """ if { [info exists ::tk::dialog::file::pbs_hack ] == 1 } { set ::tk::dialog::file::pbs_filedialog_bg {color} } """ script = script.replace("{color}", self._get_filedialog_bg_color()) master.tk.eval(script) def update_current_widgets(self, master): if not isinstance(master, tk.Tk): master = master.winfo_toplevel() style = ttk.Style(self.tk_master) self._walk_and_update_widget(master, style) def _walk_and_update_widget(self, widget, style, level=0): if isinstance(widget, tk.Misc): wclass = widget.winfo_class() # wstyle = None if isinstance(widget, ttk.Widget) and wclass == "TCombobox": # wstyle = widget.cget("style") # curr_style = wstyle if wstyle else wclass self.update_combobox_popdown_style(widget) else: if wclass in self.tk_widgets_options: # some widget may block the property (like customtkinter) # so add try here. try: widget.configure(**self.tk_widgets_options[wclass]) except Exception as e: logger.debug(e) # print(" " * level, f"{wclass} {wstyle}") for k, v in widget.children.items(): self._walk_and_update_widget(v, style, level + 1) @property def colors(self): return self.theme.colors @property def is_light_theme(self): return self.theme.type == ThemeDefinition.THEME_LIGHT def scale_size(self, size): """Scale the size of images and other assets based on the scaling factor of ttk to ensure that the image matches the screen resolution. Parameters: size (Union[int, List, Tuple]): A single integer or an iterable of integers """ winsys = self.tk_master.tk.call("tk", "windowingsystem") if winsys == "aqua": BASELINE = 1.000492368291482 else: BASELINE = 1.33398982438864281 scaling = self.tk_master.tk.call("tk", "scaling") factor = scaling / BASELINE if isinstance(size, int) or isinstance(size, float): return math.ceil(size * factor) elif isinstance(size, tuple) or isinstance(size, list): return [math.ceil(x * factor) for x in size] def build_tk_widget_settings(self): # widget.option_add('*Text*Font', 'TkDefaultFont') wo = { "Tk": {"background": self.colors.bg}, "Toplevel": {"background": self.colors.bg}, "Canvas": { "background": self.colors.bg, "highlightthickness": 0, }, "Label": { "foreground": self.colors.fg, "background": self.colors.bg, }, "Frame": {"background": self.colors.bg}, "Checkbutton": { "activebackground": self.colors.bg, "activeforeground": self.colors.primary, "background": self.colors.bg, "foreground": self.colors.fg, "selectcolor": self.colors.bg, }, "Radiobutton": { "activebackground": self.colors.bg, "activeforeground": self.colors.primary, "background": self.colors.bg, "foreground": self.colors.fg, "selectcolor": self.colors.bg, }, "Menu": { "tearoff": False, "activebackground": self.colors.selectbg, "activeforeground": self.colors.selectfg, "foreground": self.colors.fg, "selectcolor": self.colors.primary, "background": self.colors.bg, "relief": tk.FLAT, "borderwidth": 0, }, } # buttons background = self.colors.primary foreground = self.colors.selectfg activebackground = self.colorutil.update_hsv( self.colors.primary, vd=-0.1 ) wo["Button"] = { "background": background, "foreground": foreground, "relief": tk.FLAT, "borderwidth": 0, "activebackground": activebackground, "highlightbackground": self.colors.selectfg, } # Entry bordercolor = ( self.colors.border if self.is_light_theme else self.colors.selectbg ) wo["Entry"] = { "relief": tk.FLAT, "highlightthickness": 1, "foreground": self.colors.inputfg, "highlightbackground": bordercolor, "highlightcolor": self.colors.primary, "background": self.colors.inputbg, "insertbackground": self.colors.inputfg, "insertwidth": 1, } # Scale bordercolor = ( self.colors.border if self.is_light_theme else self.colors.selectbg ) activecolor = self.colorutil.update_hsv(self.colors.primary, vd=-0.2) wo["Scale"] = { "background": self.colors.primary, "showvalue": False, "sliderrelief": tk.FLAT, "borderwidth": 0, "activebackground": activecolor, "highlightthickness": 1, "highlightcolor": bordercolor, "highlightbackground": bordercolor, "troughcolor": self.colors.inputbg, } # Spinbox bordercolor = ( self.colors.border if self.is_light_theme else self.colors.selectbg ) wo["Spinbox"] = { "relief": tk.FLAT, "highlightthickness": 1, "foreground": self.colors.inputfg, "highlightbackground": bordercolor, "highlightcolor": self.colors.primary, "background": self.colors.inputbg, "buttonbackground": self.colors.inputbg, "insertbackground": self.colors.inputfg, "insertwidth": 1, # these options should work, but do not have any affect "buttonuprelief": tk.FLAT, "buttondownrelief": tk.SUNKEN, } # Listbox bordercolor = ( self.colors.border if self.is_light_theme else self.colors.selectbg ) wo["Spinbox"] = { "foreground": self.colors.inputfg, "background": self.colors.inputbg, "selectbackground": self.colors.selectbg, "selectforeground": self.colors.selectfg, "highlightcolor": self.colors.primary, "highlightbackground": bordercolor, "highlightthickness": 1, "activestyle": "none", "relief": tk.FLAT, } # Menubutton activebackground = self.colorutil.update_hsv( self.colors.primary, vd=-0.2 ) wo["Menubutton"] = { "background": self.colors.primary, "foreground": self.colors.selectfg, "activebackground": activebackground, "activeforeground": self.colors.selectfg, "borderwidth": 0, } # LabelFrame bordercolor = ( self.colors.border if self.is_light_theme else self.colors.selectbg ) wo["LabelFrame"] = { "highlightcolor": bordercolor, "foreground": self.colors.fg, "borderwidth": 1, "highlightthickness": 0, "background": self.colors.bg, } # Text bordercolor = ( self.colors.border if self.is_light_theme else self.colors.selectbg ) focuscolor = bordercolor wo["Text"] = { "background": self.colors.inputbg, "foreground": self.colors.inputfg, "highlightcolor": focuscolor, "highlightbackground": bordercolor, "insertbackground": self.colors.inputfg, "selectbackground": self.colors.selectbg, "selectforeground": self.colors.selectfg, "insertwidth": 1, "highlightthickness": 1, "relief": tk.FLAT, "padx": 5, "pady": 5, } self.tk_widgets_options = wo def build_theme_settings(self): tstart = timeit.default_timer() settings = {} self.create_default_style(settings) colornames = [c for c in self.colors.names()] colornames.insert(0, None) for name in colornames: # Frames self.create_frame_style(settings, name) self.create_labelframe_style(settings, name) # Labels self.create_label_style(settings, name) self.create_inverse_label_style(settings, name) # Buttons self.create_button_style(settings, name) self.create_toolbutton_style(settings, name) self.create_outline_button_style(settings, name) self.create_outline_menubutton_style(settings, name) self.create_outline_toolbutton_style(settings, name) self.create_link_button_style(settings, name) self.create_radiobutton_style(settings, name) self.create_menubutton_style(settings, name) self.create_checkbutton_style(settings, name) self.create_square_toggle_style(settings, name) self.create_round_toggle_style(settings, name) # Entry self.create_entry_style(settings, name) # Combo self.create_combobox_style(settings, name) # Panedwindow self.create_panedwindow_style(settings, name) # Notebook self.create_notebook_style(settings, name) # separator self.create_separator_style(settings, name) # Progress bars self.create_progressbar_style(settings, name) self.create_striped_progressbar_style(settings, name) # Scale self.create_scale_style(settings, name) # scrollbar self.create_scrollbar_style(settings, name) self.create_round_scrollbar_style(settings, name) # spinbox self.create_spinbox_style(settings, name) # treeview self.create_treeview_style(settings, name) self.create_table_treeview_style(settings, name) # sizegrip self.create_sizegrip_style(settings, name) # Floodgauge self.create_floodgauge_style(settings, name) tfinish = timeit.default_timer() - tstart logger.debug("Bootstrap ttk theme build time: %s", tfinish) return settings def build_tk_palette(self): return { "background": self.colors.bg, "foreground": self.colors.fg, "highlightColor": self.colors.primary, "selectBackground": self.colors.selectbg, "selectForeground": self.colors.selectfg, "activeBackground": self.colors.selectbg, "activeForeground": self.colors.selectfg, } def _register_style(self, style_name): if _colect_styles: self.generated_styles.add(style_name) def update_combobox_popdown_style(self, widget): """Update the legacy ttk.Combobox elements. This method is called every time the theme is changed in order to ensure that the legacy tkinter components embedded in this ttk widget are styled appropriate to the current theme. The ttk.Combobox contains several elements that are not styled using the ttk theme engine. This includes the **popdownwindow** and the **scrollbar**. Both of these widgets are configured manually using calls to tcl/tk. Parameters: widget (ttk.Combobox): The combobox element to be updated. """ if self.is_light_theme: bordercolor = self.colors.border else: bordercolor = self.colors.selectbg tk_settings = [] tk_settings.extend(["-borderwidth", 2]) tk_settings.extend(["-highlightthickness", 1]) tk_settings.extend(["-highlightcolor", bordercolor]) tk_settings.extend(["-background", self.colors.inputbg]) tk_settings.extend(["-foreground", self.colors.inputfg]) tk_settings.extend(["-selectbackground", self.colors.selectbg]) tk_settings.extend(["-selectforeground", self.colors.selectfg]) # set popdown style popdown = widget.tk.eval(f"ttk::combobox::PopdownWindow {widget}") widget.tk.call(f"{popdown}.f.l", "configure", *tk_settings) # set scrollbar style sb_style = "TCombobox.Vertical.TScrollbar" widget.tk.call(f"{popdown}.f.sb", "configure", "-style", sb_style) def create_default_style(self, settings: dict): settings["."] = { "configure": { "background": self.colors.bg, "darkcolor": self.colors.border, "foreground": self.colors.fg, "troughcolor": self.colors.bg, "selectbg": self.colors.selectbg, "selectfg": self.colors.selectfg, "selectbackground": self.colors.selectbg, "selectforeground": self.colors.selectfg, "fieldbackground": self.colors.bg, # "font": "TkDefaultFont", "borderwidth": 1, "focuscolor": "", }, } def create_frame_style(self, settings, colorname=None): """Create a style for the ttk.Frame widget.""" STYLE = "TFrame" if any([colorname is None, not colorname]): ttkstyle = STYLE background = self.colors.bg else: ttkstyle = f"{colorname}.{STYLE}" background = self.colors.get_color(colorname) settings[ttkstyle] = { "configure": { "background": background, } } if colorname: self._register_style(ttkstyle) def create_labelframe_style(self, settings, colorname=None): """Create a style for the ttk.Labelframe widget.""" STYLE = "TLabelframe" background = self.colors.bg if any([colorname is None, not colorname]): foreground = self.colors.fg ttkstyle = STYLE if self.is_light_theme: bordercolor = self.colors.border else: bordercolor = self.colors.selectbg else: foreground = self.colors.get_color(colorname) bordercolor = foreground ttkstyle = f"{colorname}.{STYLE}" settings[f"{ttkstyle}.Label"] = { "configure": { "foreground": foreground, "background": background, } } settings[ttkstyle] = { "configure": { "relief": tk.RAISED, "borderwidth": 1, "bordercolor": bordercolor, "lightcolor": background, "darkcolor": background, "background": background, } } if colorname: self._register_style(ttkstyle) def create_label_style(self, settings, colorname=None): """Create a standard style for the ttk.Label widget. Parameters: colorname (str): The color label used to style the widget. """ STYLE = "TLabel" if any([colorname is None, colorname == ""]): ttkstyle = STYLE foreground = self.colors.fg background = self.colors.bg else: ttkstyle = f"{colorname}.{STYLE}" foreground = self.colors.get_color(colorname) background = self.colors.bg # standard label settings[ttkstyle] = { "configure": {"foreground": foreground, "background": background} } if colorname: self._register_style(ttkstyle) def create_inverse_label_style(self, settings, colorname=None): """Create an inverted style for the ttk.Label.""" STYLE_INVERSE = "Inverse.TLabel" if any([colorname is None, not colorname]): ttkstyle = STYLE_INVERSE background = self.colors.fg foreground = self.colors.bg else: ttkstyle = f"{colorname}.{STYLE_INVERSE}" background = self.colors.get_color(colorname) foreground = self.colors.get_foreground(colorname) settings[ttkstyle] = { "configure": {"foreground": foreground, "background": background} } self._register_style(ttkstyle) def create_button_style(self, settings, colorname=None): STYLE = "TButton" if any([colorname is None, colorname == ""]): ttkstyle = STYLE foreground = self.colors.get_foreground(PRIMARY) background = self.colors.primary else: ttkstyle = f"{colorname}.{STYLE}" foreground = self.colors.get_foreground(colorname) background = self.colors.get_color(colorname) bordercolor = background disabled_bg = self.colorutil.make_transparent( 0.10, self.colors.fg, self.colors.bg ) disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) pressed = self.colorutil.make_transparent( 0.80, background, self.colors.bg ) hover = self.colorutil.make_transparent( 0.90, background, self.colors.bg ) settings[ttkstyle] = { "configure": { "foreground": foreground, "background": background, "bordercolor": bordercolor, "darkcolor": background, "lightcolor": background, "relief": tk.RAISED, "focusthickness": 0, "focuscolor": foreground, "padding": (10, 5), "anchor": tk.CENTER, }, "map": { "foreground": [("disabled", disabled_fg)], "background": [ ("disabled", disabled_bg), ("pressed !disabled", pressed), ("hover !disabled", hover), ], "bordercolor": [("disabled", disabled_bg)], "darkcolor": [ ("disabled", disabled_bg), ("pressed !disabled", pressed), ("hover !disabled", hover), ], "lightcolor": [ ("disabled", disabled_bg), ("pressed !disabled", pressed), ("hover !disabled", hover), ], }, } if colorname: self._register_style(ttkstyle) def create_toolbutton_style(self, settings, colorname=None): """Create a solid toolbutton style for the ttk.Checkbutton""" STYLE = "Toolbutton" if any([colorname is None, colorname == ""]): ttkstyle = STYLE toggle_on = self.colors.primary else: ttkstyle = f"{colorname}.{STYLE}" toggle_on = self.colors.get_color(colorname) foreground = self.colors.get_foreground(colorname) if self.is_light_theme: toggle_off = self.colors.border else: toggle_off = self.colors.selectbg disabled_bg = self.colorutil.make_transparent( 0.10, self.colors.fg, self.colors.bg ) disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) settings[ttkstyle] = { "configure": { "foreground": self.colors.selectfg, "background": toggle_off, "bordercolor": toggle_off, "darkcolor": toggle_off, "lightcolor": toggle_off, "relief": tk.RAISED, "focusthickness": 0, "focuscolor": "", "padding": (10, 5), "anchor": tk.CENTER, }, "map": { "foreground": [ ("disabled", disabled_fg), ("hover", foreground), ("selected", foreground), ], "background": [ ("disabled", disabled_bg), ("pressed !disabled", toggle_on), ("selected !disabled", toggle_on), ("hover !disabled", toggle_on), ], "bordercolor": [ ("disabled", disabled_bg), ("pressed !disabled", toggle_on), ("selected !disabled", toggle_on), ("hover !disabled", toggle_on), ], "darkcolor": [ ("disabled", disabled_bg), ("pressed !disabled", toggle_on), ("selected !disabled", toggle_on), ("hover !disabled", toggle_on), ], "lightcolor": [ ("disabled", disabled_bg), ("pressed !disabled", toggle_on), ("selected !disabled", toggle_on), ("hover !disabled", toggle_on), ], }, } if colorname: self._register_style(ttkstyle) def create_outline_button_style(self, settings, colorname=None): """Create an outline style for the ttk.Button widget.""" STYLE = "Outline.TButton" disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) if any([colorname is None, not colorname]): ttkstyle = STYLE colorname = PRIMARY else: ttkstyle = f"{colorname}.{STYLE}" foreground = self.colors.get_color(colorname) background = self.colors.get_foreground(colorname) foreground_pressed = background bordercolor = foreground pressed = foreground hover = foreground settings[ttkstyle] = { "configure": { "foreground": foreground, "background": self.colors.bg, "bordercolor": bordercolor, "darkcolor": self.colors.bg, "lightcolor": self.colors.bg, "relief": tk.RAISED, "focusthickness": 0, "focuscolor": foreground, "padding": (10, 5), "anchor": tk.CENTER, }, "map": { "foreground": [ ("disabled", disabled_fg), ("pressed !disabled", foreground_pressed), ("hover !disabled", foreground_pressed), ], "background": [ ("pressed !disabled", pressed), ("hover !disabled", hover), ], "bordercolor": [ ("disabled", disabled_fg), ("pressed !disabled", pressed), ("hover !disabled", hover), ], "darkcolor": [ ("pressed !disabled", pressed), ("hover !disabled", hover), ], "lightcolor": [ ("pressed !disabled", pressed), ("hover !disabled", hover), ], "focuscolor": [ ("pressed !disabled", foreground_pressed), ("hover !disabled", foreground_pressed), ], }, } self._register_style(ttkstyle) def create_outline_menubutton_style(self, settings, colorname=None): """Create an outline button style for the ttk.Menubutton widget""" STYLE = "Outline.TMenubutton" disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) if any([colorname is None, not colorname]): ttkstyle = STYLE colorname = PRIMARY else: ttkstyle = f"{colorname}.{STYLE}" foreground = self.colors.get_color(colorname) background = self.colors.get_foreground(colorname) foreground_pressed = background bordercolor = foreground pressed = foreground hover = foreground settings[ttkstyle] = { "configure": { "foreground": foreground, "background": self.colors.bg, "bordercolor": bordercolor, "darkcolor": self.colors.bg, "lightcolor": self.colors.bg, "relief": tk.RAISED, "focusthickness": 0, "focuscolor": foreground, "padding": (10, 5), "arrowcolor": foreground, "arrowpadding": (0, 0, 15, 0), "arrowsize": self.scale_size(4), }, "map": { "foreground": [ ("disabled", disabled_fg), ("pressed !disabled", foreground_pressed), ("hover !disabled", foreground_pressed), ], "background": [ ("pressed !disabled", pressed), ("hover !disabled", hover), ], "bordercolor": [ ("disabled", disabled_fg), ("pressed", pressed), ("hover", hover), ], "darkcolor": [ ("pressed !disabled", pressed), ("hover !disabled", hover), ], "lightcolor": [ ("pressed !disabled", pressed), ("hover !disabled", hover), ], "arrowcolor": [ ("disabled", disabled_fg), ("pressed", foreground_pressed), ("hover", foreground_pressed), ], }, } self._register_style(ttkstyle) def create_outline_toolbutton_style(self, settings, colorname=None): """Create an outline toolbutton style for the ttk.Checkbutton and ttk.Radiobutton widgets. """ STYLE = "Outline.Toolbutton" disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) if any([colorname is None, colorname == ""]): ttkstyle = STYLE colorname = PRIMARY else: ttkstyle = f"{colorname}.{STYLE}" foreground = self.colors.get_color(colorname) background = self.colors.get_foreground(colorname) foreground_pressed = background bordercolor = foreground pressed = foreground hover = foreground settings[ttkstyle] = { "configure": { "foreground": foreground, "background": self.colors.bg, "bordercolor": bordercolor, "darkcolor": self.colors.bg, "lightcolor": self.colors.bg, "relief": tk.RAISED, "focusthickness": 0, "focuscolor": foreground, "padding": (10, 5), "anchor": tk.CENTER, "arrowcolor": foreground, "arrowpadding": (0, 0, 15, 0), "arrowsize": 3, }, "map": { "foreground": [ ("disabled", disabled_fg), ("pressed !disabled", foreground_pressed), ("selected !disabled", foreground_pressed), ("hover !disabled", foreground_pressed), ], "background": [ ("pressed !disabled", pressed), ("selected !disabled", pressed), ("hover !disabled", hover), ], "bordercolor": [ ("disabled", disabled_fg), ("pressed !disabled", pressed), ("selected !disabled", pressed), ("hover !disabled", hover), ], "darkcolor": [ ("disabled", self.colors.bg), ("pressed !disabled", pressed), ("selected !disabled", pressed), ("hover !disabled", hover), ], "lightcolor": [ ("disabled", self.colors.bg), ("pressed !disabled", pressed), ("selected !disabled", pressed), ("hover !disabled", hover), ], }, } self._register_style(ttkstyle) def create_link_button_style(self, settings, colorname=None): """Create a link button style for the ttk.Button widget.""" STYLE = "Link.TButton" pressed = self.colors.info hover = self.colors.info if any([colorname is None, not colorname]): foreground = self.colors.fg ttkstyle = STYLE elif colorname == LIGHT: foreground = self.colors.fg ttkstyle = f"{colorname}.{STYLE}" else: foreground = self.colors.get_color(colorname) ttkstyle = f"{colorname}.{STYLE}" disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) settings[ttkstyle] = { "configure": { "foreground": foreground, "background": self.colors.bg, "bordercolor": self.colors.bg, "darkcolor": self.colors.bg, "lightcolor": self.colors.bg, "relief": tk.RAISED, "focusthickness": 0, "focuscolor": foreground, "anchor": tk.CENTER, "padding": (10, 5), }, "map": { "shiftrelief=": [("pressed !disabled", -1)], "foreground": [ ("disabled", disabled_fg), ("pressed", "!disabled", pressed), ("hover", "!disabled", hover), ], "focuscolor": [ ("pressed !disabled", pressed), ("hover", "!disabled", pressed), ], "background": [ ("disabled", self.colors.bg), ("pressed !disabled", self.colors.bg), ("hover", "!disabled", self.colors.bg), ], "bordercolor": [ ("disabled", self.colors.bg), ("pressed !disabled", self.colors.bg), ("hover !disabled", self.colors.bg), ], "darkcolor": [ ("disabled", self.colors.bg), ("pressed !disabled", self.colors.bg), ("hover !disabled", self.colors.bg), ], "lightcolor": [ ("disabled", self.colors.bg), ("pressed !disabled", self.colors.bg), ("hover !disabled", self.colors.bg), ], }, } self._register_style(ttkstyle) def create_entry_style(self, settings, colorname=None): """Create a style for the ttk.Entry widget.""" STYLE = "TEntry" # general default colors if self.is_light_theme: disabled_fg = self.colors.border bordercolor = self.colors.border readonly = self.colors.light else: disabled_fg = self.colors.selectbg bordercolor = self.colors.selectbg readonly = bordercolor if any([colorname is None, not colorname]): # default style ttkstyle = STYLE focuscolor = self.colors.primary else: # colored style ttkstyle = f"{colorname}.{STYLE}" focuscolor = self.colors.get_color(colorname) bordercolor = focuscolor settings[ttkstyle] = { "configure": { "bordercolor": bordercolor, "darkcolor": self.colors.inputbg, "lightcolor": self.colors.inputbg, "fieldbackground": self.colors.inputbg, "foreground": self.colors.inputfg, "insertcolor": self.colors.inputfg, "padding": 5, }, "map": { "foreground": [("disabled", disabled_fg)], "fieldbackground": [("readonly", readonly)], "bordercolor": [ ("invalid", self.colors.danger), ("focus !disabled", focuscolor), ("hover !disabled", focuscolor), ], "lightcolor": [ ("focus invalid", self.colors.danger), ("focus !disabled", focuscolor), ("readonly", readonly), ], "darkcolor": [ ("focus invalid", self.colors.danger), ("focus !disabled", focuscolor), ("readonly", readonly), ], }, } if colorname: self._register_style(ttkstyle) def create_menubutton_style(self, settings, colorname=None): """Create a solid style for the ttk.Menubutton widget.""" STYLE = "TMenubutton" foreground = self.colors.get_foreground(colorname) if any([colorname is None, not colorname]): ttkstyle = STYLE background = self.colors.primary else: ttkstyle = f"{colorname}.{STYLE}" background = self.colors.get_color(colorname) disabled_bg = self.colorutil.make_transparent( 0.10, self.colors.fg, self.colors.bg ) disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) pressed = self.colorutil.make_transparent( 0.80, background, self.colors.bg ) hover = self.colorutil.make_transparent( 0.90, background, self.colors.bg ) settings[ttkstyle] = { "configure": { "foreground": foreground, "background": background, "bordercolor": background, "darkcolor": background, "lightcolor": background, "arrowsize": self.scale_size(4), "arrowcolor": foreground, "arrowpadding": (0, 0, 15, 0), "relief": tk.RAISED, "focusthickness": 0, "focuscolor": self.colors.selectfg, "padding": (10, 5), }, "map": { "arrowcolor": [("disabled", disabled_fg)], "foreground": [("disabled", disabled_fg)], "background": [ ("disabled", disabled_bg), ("pressed !disabled", pressed), ("hover !disabled", hover), ], "bordercolor": [ ("disabled", disabled_bg), ("pressed !disabled", pressed), ("hover !disabled", hover), ], "darkcolor": [ ("disabled", disabled_bg), ("pressed !disabled", pressed), ("hover !disabled", hover), ], "lightcolor": [ ("disabled", disabled_bg), ("pressed !disabled", pressed), ("hover !disabled", hover), ], }, } if colorname: self._register_style(ttkstyle) def create_combobox_style(self, settings, colorname=None): """Create a style for the ttk.Combobox widget.""" STYLE = "TCombobox" if self.is_light_theme: disabled_fg = self.colors.border bordercolor = self.colors.border readonly = self.colors.light else: disabled_fg = self.colors.selectbg bordercolor = self.colors.selectbg readonly = bordercolor if any([colorname is None, not colorname]): ttkstyle = STYLE element = f"{ttkstyle.replace('TC', 'C')}" focuscolor = self.colors.primary else: ttkstyle = f"{colorname}.{STYLE}" element = f"{ttkstyle.replace('TC', 'C')}" focuscolor = self.colors.get_color(colorname) if all([colorname, colorname is not None]): bordercolor = focuscolor ttk_elements = ( (f"{element}.downarrow", TTK_DEFAULT), (f"{element}.padding", TTK_CLAM), (f"{element}.textarea", TTK_CLAM), ) for element_name, from_ in ttk_elements: settings[element_name] = { "element create": ("from", from_), } settings[ttkstyle] = { "configure": { "bordercolor": bordercolor, "darkcolor": self.colors.inputbg, "lightcolor": self.colors.inputbg, "arrowcolor": self.colors.inputfg, "foreground": self.colors.inputfg, "fieldbackground": self.colors.inputbg, "background": self.colors.inputbg, "insertcolor": self.colors.inputfg, "relief": tk.FLAT, "padding": 5, "arrowsize": self.scale_size(12), }, "map": { "background": [("readonly", readonly)], "fieldbackground": [("readonly", readonly)], "foreground": [("disabled", disabled_fg)], "bordercolor": [ ("invalid", self.colors.danger), ("focus !disabled", focuscolor), ("hover !disabled", focuscolor), ], "lightcolor": [ ("focus invalid", self.colors.danger), ("focus !disabled", focuscolor), ("pressed !disabled", focuscolor), ("readonly", readonly), ], "darkcolor": [ ("focus invalid", self.colors.danger), ("focus !disabled", focuscolor), ("pressed !disabled", focuscolor), ("readonly", readonly), ], "arrowcolor": [ ("disabled", disabled_fg), ("pressed !disabled", focuscolor), ("focus !disabled", focuscolor), ("hover !disabled", focuscolor), ], }, "layout": [ ( "combo.Spinbox.field", { "side": tk.TOP, "sticky": tk.EW, "children": [ ( "Combobox.downarrow", {"side": tk.RIGHT, "sticky": tk.NS}, ), ( "Combobox.padding", { "expand": "1", "sticky": tk.NSEW, "children": [ ( "Combobox.textarea", {"sticky": tk.NSEW}, ) ], }, ), ], }, ) ], } if colorname: self._register_style(ttkstyle) def create_panedwindow_style(self, settings, colorname=None): """Create a standard style for the ttk.Panedwindow widget.""" H_STYLE = "Horizontal.TPanedwindow" V_STYLE = "Vertical.TPanedwindow" if self.is_light_theme: default_color = self.colors.border else: default_color = self.colors.selectbg if any([colorname is None, not colorname]): sashcolor = default_color h_ttkstyle = H_STYLE v_ttkstyle = V_STYLE else: sashcolor = self.colors.get_color(colorname) h_ttkstyle = f"{colorname}.{H_STYLE}" v_ttkstyle = f"{colorname}.{V_STYLE}" settings["Sash"] = { "configure": {"gripcount": 0, "sashthickness": self.scale_size(2)} } settings[h_ttkstyle] = { "configure": { "background": sashcolor, } } settings[v_ttkstyle] = { "configure": { "background": sashcolor, } } if colorname: self._register_style(h_ttkstyle) self._register_style(v_ttkstyle) def create_notebook_style(self, settings, colorname=None): """Create a standard style for the ttk.Notebook widget.""" STYLE = "TNotebook" if self.is_light_theme: bordercolor = self.colors.border foreground = self.colors.inputfg else: bordercolor = self.colors.selectbg foreground = self.colors.selectfg if any([colorname is None, not colorname]): background = self.colors.inputbg selectfg = self.colors.fg ttkstyle = STYLE else: selectfg = self.colors.get_foreground(colorname) background = self.colors.get_color(colorname) ttkstyle = f"{colorname}.{STYLE}" ttkstyle_tab = f"{ttkstyle}.Tab" # create widget style settings[ttkstyle] = { "configure": { "background": self.colors.bg, "bordercolor": bordercolor, "lightcolor": self.colors.bg, "darkcolor": self.colors.bg, "tabmargins": (0, 1, 1, 0), } } settings[ttkstyle_tab] = { "configure": { "focuscolor": "", "foreground": foreground, "padding": (6, 5), }, "map": { "background": [ ("selected", self.colors.bg), ("!selected", background), ], "lightcolor": [ ("selected", self.colors.bg), ("!selected", background), ], "bordercolor": [ ("selected", bordercolor), ("!selected", bordercolor), ], "padding": [("selected", (6, 5)), ("!selected", (6, 5))], "foreground": [ ("selected", foreground), ("!selected", selectfg), ], }, } if colorname: self._register_style(ttkstyle) def create_separator_style(self, settings, colorname=None): """Create a style for the ttk.Separator widget.""" HSTYLE = "Horizontal.TSeparator" VSTYLE = "Vertical.TSeparator" hsize = [20, 1] # vsize = [1, 40] # style colors if self.is_light_theme: default_color = self.colors.border else: default_color = self.colors.selectbg if any([colorname is None, not colorname]): background = default_color h_ttkstyle = HSTYLE v_ttkstyle = VSTYLE else: background = self.colors.get_color(colorname) h_ttkstyle = f"{colorname}.{HSTYLE}" v_ttkstyle = f"{colorname}.{VSTYLE}" # assets h_name, v_name = self.assets.create_separator_assets(hsize, background) # horizontal separator h_element = h_ttkstyle.replace(".TS", ".S") h_element_uid = f"{h_element}.separator" settings[h_element_uid] = { "element create": ("image", h_name), } settings[h_ttkstyle] = {"layout": [(h_element_uid, {"sticky": tk.EW})]} # vertical separator v_element = v_ttkstyle.replace(".TS", ".S") v_element_uid = f"{v_element}.separator" settings[v_element_uid] = { "element create": ("image", v_name), } settings[v_ttkstyle] = {"layout": [(v_element_uid, {"sticky": tk.NS})]} if colorname: self._register_style(h_ttkstyle) self._register_style(v_ttkstyle) def create_progressbar_style(self, settings, colorname=None): """Create a solid ttk style for the ttk.Progressbar widget.""" H_STYLE = "Horizontal.TProgressbar" V_STYLE = "Vertical.TProgressbar" thickness = self.scale_size(10) if self.is_light_theme: if colorname == LIGHT: troughcolor = self.colors.bg bordercolor = self.colors.light else: troughcolor = self.colors.light bordercolor = troughcolor else: troughcolor = self.colorutil.update_hsv( self.colors.selectbg, vd=-0.2 ) bordercolor = troughcolor if any([colorname is None, not colorname]): background = self.colors.primary foreground = self.colors.fg h_ttkstyle = H_STYLE v_ttkstyle = V_STYLE else: background = self.colors.get_color(colorname) foreground = self.colors.get_color(colorname) h_ttkstyle = f"{colorname}.{H_STYLE}" v_ttkstyle = f"{colorname}.{V_STYLE}" # horizontal progressbar h_element = h_ttkstyle.replace(".TP", ".P") htrough_element = f"{h_element}.trough" hpbar_element = f"{h_element}.pbar" hpbar_text_element = f"{h_element}.ctext" if htrough_element not in self.existing_elements: settings[htrough_element] = { "element create": ("from", TTK_CLAM), } settings[hpbar_element] = { "element create": ("from", TTK_DEFAULT), } self.existing_elements[htrough_element] = True settings[h_ttkstyle] = { "configure": { "thickness": thickness, "borderwidth": 1, "bordercolor": bordercolor, "lightcolor": self.colors.border, "pbarrelief": tk.FLAT, "troughcolor": troughcolor, "background": background, "foreground": foreground, }, "layout": [ ( htrough_element, { "sticky": "nswe", "children": [ (hpbar_element, {"side": "left", "sticky": "ns"}) ], }, ) ], } if has_tk_version_9: children = settings[h_ttkstyle]["layout"][0][1]["children"] pbar_text = (hpbar_text_element, {"side": "left", "sticky": ""}) children.append(pbar_text) # vertical progressbar v_element = v_ttkstyle.replace(".TP", ".P") vtrough_element = f"{v_element}.trough" vpbar_element = f"{v_element}.pbar" # vpbar_text_element = f"{v_element}.ctext" if vtrough_element not in self.existing_elements: settings[vtrough_element] = { "element create": ("from", TTK_CLAM), } settings[vpbar_element] = { "element create": ("from", TTK_DEFAULT), } self.existing_elements[vtrough_element] = True settings[v_ttkstyle] = { "configure": { "thickness": thickness, "borderwidth": 1, "bordercolor": bordercolor, "lightcolor": self.colors.border, "pbarrelief": tk.FLAT, "troughcolor": troughcolor, "background": background, }, "layout": [ ( vtrough_element, { "sticky": "nswe", "children": [ (vpbar_element, {"side": "bottom", "sticky": "we"}) ], }, ) ], } # if has_tk_version_9: # children = settings[h_ttkstyle]["layout"][0][1]["children"] # pbar_text = (vpbar_text_element, {"side": "top", "sticky": ""}) # children.append(pbar_text) if colorname: self._register_style(h_ttkstyle) self._register_style(v_ttkstyle) def create_scale_style(self, settings, colorname=None): """Create a style for the ttk.Scale widget.""" STYLE = "TScale" if any([colorname is None, not colorname]): h_ttkstyle = f"Horizontal.{STYLE}" v_ttkstyle = f"Vertical.{STYLE}" else: h_ttkstyle = f"{colorname}.Horizontal.{STYLE}" v_ttkstyle = f"{colorname}.Vertical.{STYLE}" # ( normal, pressed, hover, disabled, htrack, vtrack ) images = self.assets.create_scale_assets(colorname) # horizontal scale h_element = h_ttkstyle.replace(".TS", ".S") settings[f"{h_element}.slider"] = { "element create": ( "image", images[0], ("disabled", images[3]), ("pressed", images[1]), ("hover", images[2]), ), } settings[f"{h_element}.track"] = { "element create": ( "image", images[4], ) } settings[h_ttkstyle] = { "layout": [ ( f"{h_element}.focus", { "expand": "1", "sticky": tk.NSEW, "children": [ (f"{h_element}.track", {"sticky": tk.EW}), ( f"{h_element}.slider", {"side": tk.LEFT, "sticky": ""}, ), ], }, ) ], } # vertical scale v_element = v_ttkstyle.replace(".TS", ".S") settings[f"{v_element}.slider"] = { "element create": ( "image", images[0], ("disabled", images[3]), ("pressed", images[1]), ("hover", images[2]), ), } settings[f"{v_element}.track"] = { "element create": ( "image", images[5], ) } settings[v_ttkstyle] = { "layout": [ ( f"{v_element}.focus", { "expand": "1", "sticky": tk.NSEW, "children": [ (f"{v_element}.track", {"sticky": tk.NS}), ( f"{v_element}.slider", {"side": tk.TOP, "sticky": ""}, ), ], }, ) ], } if colorname: self._register_style(h_ttkstyle) self._register_style(v_ttkstyle) def create_scrollbar_style(self, settings, colorname=None): """Create a standard style for the ttk.Scrollbar widget.""" STYLE = "TScrollbar" if any([colorname is None, not colorname]): h_ttkstyle = f"Horizontal.{STYLE}" v_ttkstyle = f"Vertical.{STYLE}" if self.is_light_theme: background = self.colors.border else: background = self.colors.selectbg else: h_ttkstyle = f"{colorname}.Horizontal.{STYLE}" v_ttkstyle = f"{colorname}.Vertical.{STYLE}" background = self.colors.get_color(colorname) if self.is_light_theme: if colorname == LIGHT: troughcolor = self.colors.bg else: troughcolor = self.colors.light else: troughcolor = self.colorutil.update_hsv( self.colors.selectbg, vd=-0.2 ) pressed = self.colorutil.update_hsv(background, vd=-0.05) active = self.colorutil.update_hsv(background, vd=0.05) scroll_images = self.assets.create_scrollbar_assets( background, pressed, active ) # horizontal scrollbar settings[f"{h_ttkstyle}.thumb"] = { "element create": ( "image", scroll_images[0], ("pressed", scroll_images[1]), ("active", scroll_images[2]), {"border": (3, 0), "sticky": tk.NSEW}, ) } settings[h_ttkstyle] = { "configure": { "troughcolor": troughcolor, "darkcolor": troughcolor, "bordercolor": troughcolor, "lightcolor": troughcolor, "arrowcolor": background, "arrowsize": self.scale_size(11), "background": troughcolor, "relief": tk.FLAT, "borderwidth": 0, "arrowcolor": background, }, "map": { "arrowcolor": [("pressed", pressed), ("active", active)], }, "layout": [ ( "Horizontal.Scrollbar.trough", { "sticky": "we", "children": [ ( "Horizontal.Scrollbar.leftarrow", {"side": "left", "sticky": ""}, ), ( "Horizontal.Scrollbar.rightarrow", {"side": "right", "sticky": ""}, ), ( f"{h_ttkstyle}.thumb", {"expand": "1", "sticky": "nswe"}, ), ], }, ) ], } # vertical scrollbar settings[f"{v_ttkstyle}.thumb"] = { "element create": ( "image", scroll_images[3], ("pressed", scroll_images[4]), ("active", scroll_images[5]), {"border": (0, 3), "sticky": tk.NSEW}, ) } settings[v_ttkstyle] = { "configure": { "troughcolor": troughcolor, "darkcolor": troughcolor, "bordercolor": troughcolor, "lightcolor": troughcolor, "arrowcolor": background, "arrowsize": self.scale_size(11), "background": troughcolor, "relief": tk.FLAT, "borderwidth": 0, "arrowcolor": background, }, "map": { "arrowcolor": [("pressed", pressed), ("active", active)], }, "layout": [ ( "Vertical.Scrollbar.trough", { "sticky": "ns", "children": [ ( "Vertical.Scrollbar.uparrow", {"side": "top", "sticky": ""}, ), ( "Vertical.Scrollbar.downarrow", {"side": "bottom", "sticky": ""}, ), ( f"{v_ttkstyle}.thumb", {"expand": "1", "sticky": "nswe"}, ), ], }, ) ], } if colorname: self._register_style(h_ttkstyle) self._register_style(v_ttkstyle) def create_spinbox_style(self, settings, colorname=None): """Create a style for the ttk.Spinbox widget.""" STYLE = "TSpinbox" if self.is_light_theme: disabled_fg = self.colors.border bordercolor = self.colors.border readonly = self.colors.light else: disabled_fg = self.colors.selectbg bordercolor = self.colors.selectbg readonly = bordercolor if any([colorname is None, not colorname]): ttkstyle = STYLE focuscolor = self.colors.primary else: ttkstyle = f"{colorname}.{STYLE}" focuscolor = self.colors.get_color(colorname) if all([colorname, colorname is not None]): bordercolor = focuscolor if colorname == "light": arrowfocus = self.colors.fg else: arrowfocus = focuscolor element = ttkstyle.replace(".TS", ".S") settings[f"{element}.uparrow"] = { "element create": ("from", TTK_DEFAULT), } settings[f"{element}.downarrow"] = { "element create": ("from", TTK_DEFAULT), } settings[ttkstyle] = { "configure": { "bordercolor": bordercolor, "darkcolor": self.colors.inputbg, "lightcolor": self.colors.inputbg, "fieldbackground": self.colors.inputbg, "foreground": self.colors.inputfg, "borderwidth": 0, "background": self.colors.inputbg, "relief": tk.FLAT, "arrowcolor": self.colors.inputfg, "insertcolor": self.colors.inputfg, "arrowsize": self.scale_size(12), "padding": (10, 5), }, "map": { "foreground": [("disabled", disabled_fg)], "fieldbackground": [("readonly", readonly)], "background": [("readonly", readonly)], "lightcolor": [ ("focus invalid", self.colors.danger), ("focus !disabled", focuscolor), ("readonly", readonly), ], "darkcolor": [ ("focus invalid", self.colors.danger), ("focus !disabled", focuscolor), ("readonly", readonly), ], "bordercolor": [ ("invalid", self.colors.danger), ("focus !disabled", focuscolor), ("hover !disabled", focuscolor), ], "arrowcolor": [ ("disabled !disabled", disabled_fg), ("pressed !disabled", arrowfocus), ("hover !disabled", arrowfocus), ], }, "layout": [ ( f"{element}.field", { "side": tk.TOP, "sticky": tk.EW, "children": [ ( "null", { "side": tk.RIGHT, "sticky": "", "children": [ ( f"{element}.uparrow", {"side": tk.TOP, "sticky": tk.E}, ), ( f"{element}.downarrow", { "side": tk.BOTTOM, "sticky": tk.E, }, ), ], }, ), ( f"{element}.padding", { "sticky": tk.NSEW, "children": [ ( f"{element}.textarea", {"sticky": tk.NSEW}, ) ], }, ), ], }, ) ], } if colorname: self._register_style(ttkstyle) def create_treeview_style(self, settings, colorname=None): """Create a style for the ttk.Treeview widget.""" STYLE = "Treeview" f = font.nametofont("TkDefaultFont") rowheight = f.metrics()["linespace"] if self.is_light_theme: disabled_fg = self.colorutil.update_hsv( self.colors.inputbg, vd=-0.2 ) bordercolor = self.colors.border else: disabled_fg = self.colorutil.update_hsv( self.colors.inputbg, vd=-0.3 ) bordercolor = self.colors.selectbg if any([colorname is None, not colorname]): background = self.colors.inputbg foreground = self.colors.inputfg body_style = STYLE header_style = f"{STYLE}.Heading" focuscolor = self.colors.primary elif colorname == LIGHT and self.is_light_theme: background = self.colors.get_color(colorname) foreground = self.colors.fg body_style = f"{colorname}.{STYLE}" header_style = f"{colorname}.{STYLE}.Heading" focuscolor = background bordercolor = focuscolor else: background = self.colors.get_color(colorname) foreground = self.colors.selectfg body_style = f"{colorname}.{STYLE}" header_style = f"{colorname}.{STYLE}.Heading" focuscolor = background bordercolor = focuscolor # treeview header settings[header_style] = { "configure": { "background": background, "foreground": foreground, "relief": tk.FLAT, "padding": 5, }, "map": { "foreground": [("disabled", disabled_fg)], "bordercolor": [("focus !disabled", background)], }, } # treeview body settings[body_style] = { "configure": { "background": self.colors.inputbg, "fieldbackground": self.colors.inputbg, "foreground": self.colors.inputfg, "bordercolor": bordercolor, "lightcolor": self.colors.inputbg, "darkcolor": self.colors.inputbg, "borderwidth": 2, "padding": 0, "rowheight": rowheight, "relief": tk.RAISED, }, "map": { "background": [("selected", self.colors.selectbg)], "foreground": [ ("disabled", disabled_fg), ("selected", self.colors.selectfg), ], "bordercolor": [ ("disabled", bordercolor), ("focus", focuscolor), ("pressed", focuscolor), ("hover", focuscolor), ], "lightcolor": [("focus", focuscolor)], "darkcolor": [("focus", focuscolor)], }, "layout": [ ( "Button.border", { "sticky": tk.NSEW, "border": "1", "children": [ ( "Treeview.padding", { "sticky": tk.NSEW, "children": [ ( "Treeview.treearea", {"sticky": tk.NSEW}, ) ], }, ) ], }, ) ], } # try: # self.style.element_create("Treeitem.indicator", "from", TTK_ALT) # except: # pass settings["Treeitem.indicator"] = { "element create": ("from", TTK_ALT), } if colorname: self._register_style(body_style) def create_radiobutton_style(self, settings, colorname=None): """Create a style for the ttk.Radiobutton widget.""" STYLE = "TRadiobutton" disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) if any([colorname is None, not colorname]): ttkstyle = STYLE colorname = PRIMARY else: ttkstyle = f"{colorname}.{STYLE}" # ( off, on, disabled ) images = self.assets.create_radiobutton_assets(colorname) width = self.scale_size(20) borderpad = self.scale_size(4) settings[f"{ttkstyle}.indicator"] = { "element create": ( "image", images[1], ("disabled selected", images[3]), ("disabled", images[2]), ("!selected", images[0]), { "width": width, "border": borderpad, "sticky": tk.W, }, ) } settings[ttkstyle] = { "map": {"foreground": [("disabled", disabled_fg)]}, "layout": [ ( "Radiobutton.padding", { "children": [ ( f"{ttkstyle}.indicator", {"side": tk.LEFT, "sticky": ""}, ), ( "Radiobutton.focus", { "children": [ ( "Radiobutton.label", {"sticky": tk.NSEW}, ) ], "side": tk.LEFT, "sticky": "", }, ), ], "sticky": tk.NSEW, }, ) ], } if colorname: self._register_style(ttkstyle) def create_checkbutton_style(self, settings, colorname=None): """Create a standard style for the ttk.Checkbutton widget.""" STYLE = "TCheckbutton" disabled_fg = self.colorutil.make_transparent( 0.3, self.colors.fg, self.colors.bg ) if any([colorname is None, not colorname]): colorname = PRIMARY ttkstyle = STYLE else: ttkstyle = f"{colorname}.TCheckbutton" # ( off, on, disabled ) images = self.assets.create_checkbutton_assets(colorname) element = ttkstyle.replace(".TC", ".C") width = self.scale_size(20) borderpad = self.scale_size(4) settings[f"{element}.indicator"] = { "element create": ( "image", images[1], ("disabled selected", images[4]), ("disabled alternate", images[5]), ("disabled", images[2]), ("alternate", images[3]), ("!selected", images[0]), { "width": width, "border": borderpad, "sticky": tk.W, }, ) } settings[ttkstyle] = { "configure": {"foreground": self.colors.fg}, "map": {"foreground": [("disabled", disabled_fg)]}, "layout": [ ( "Checkbutton.padding", { "children": [ ( f"{element}.indicator", {"side": tk.LEFT, "sticky": ""}, ), ( "Checkbutton.focus", { "children": [ ( "Checkbutton.label", {"sticky": tk.NSEW}, ) ], "side": tk.LEFT, "sticky": "", }, ), ], "sticky": tk.NSEW, }, ) ], } if colorname: self._register_style(ttkstyle) def create_sizegrip_style(self, settings, colorname=None): """Create a style for the ttk.Sizegrip widget.""" STYLE = "TSizegrip" if any([colorname is None, not colorname]): ttkstyle = STYLE if self.is_light_theme: grip_color = self.colors.border else: grip_color = self.colors.inputbg else: ttkstyle = f"{colorname}.{STYLE}" grip_color = self.colors.get_color(colorname) image = self.assets.create_sizegrip_assets(grip_color) settings[f"{ttkstyle}.Sizegrip.sizegrip"] = { "element create": ("image", image) } settings[ttkstyle] = { "layout": [ ( f"{ttkstyle}.Sizegrip.sizegrip", {"side": tk.BOTTOM, "sticky": tk.SE}, ) ], } if colorname: self._register_style(ttkstyle) def create_striped_progressbar_style(self, settings, colorname=None): """Create a striped style for the ttk.Progressbar widget. Parameters: colorname (str): The primary widget color label. """ HSTYLE = "Striped.Horizontal.TProgressbar" VSTYLE = "Striped.Vertical.TProgressbar" thickness = self.scale_size(12) if any([colorname is None, colorname == ""]): h_ttkstyle = HSTYLE v_ttkstyle = VSTYLE else: h_ttkstyle = f"{colorname}.{HSTYLE}" v_ttkstyle = f"{colorname}.{VSTYLE}" if self.is_light_theme: if colorname == LIGHT: troughcolor = self.colors.bg bordercolor = self.colors.light else: troughcolor = self.colors.light bordercolor = troughcolor else: troughcolor = self.colorutil.update_hsv( self.colors.selectbg, vd=-0.2 ) bordercolor = troughcolor # ( horizontal, vertical ) images = self.assets.create_striped_progressbar_assets( thickness, colorname ) # horizontal progressbar h_element = h_ttkstyle.replace(".TP", ".P") settings[f"{h_element}.pbar"] = { "element create": ( "image", images[0], { "width": thickness, "sticky": tk.EW, }, ) } settings[h_ttkstyle] = { "configure": { "troughcolor": troughcolor, "thickness": thickness, "bordercolor": bordercolor, "borderwidth": 1, }, "layout": [ ( f"{h_element}.trough", { "sticky": tk.NSEW, "children": [ ( f"{h_element}.pbar", {"side": tk.LEFT, "sticky": tk.NS}, ) ], }, ) ], } # vertical progressbar v_element = v_ttkstyle.replace(".TP", ".P") settings[f"{v_element}.pbar"] = { "element create": ( "image", images[1], { "width": thickness, "sticky": tk.NS, }, ) } settings[v_ttkstyle] = { "configure": { "troughcolor": troughcolor, "thickness": thickness, "bordercolor": bordercolor, "borderwidth": 1, }, "layout": [ ( f"{v_element}.trough", { "sticky": tk.NSEW, "children": [ ( f"{v_element}.pbar", {"side": tk.BOTTOM, "sticky": tk.EW}, ) ], }, ) ], } self._register_style(h_ttkstyle) self._register_style(v_ttkstyle) def create_square_toggle_style(self, settings, colorname=None): """Create a square toggle style for the ttk.Checkbutton widget. Parameters: colorname (str): The color label used to style the widget. """ STYLE = "Square.Toggle" disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) if any([colorname is None, colorname == ""]): ttkstyle = STYLE else: ttkstyle = f"{colorname}.{STYLE}" # ( off, on, disabled ) images = self.assets.create_square_toggle_assets(colorname) width = self.scale_size(28) borderpad = self.scale_size(4) settings[f"{ttkstyle}.indicator"] = { "element create": ( "image", images[1], ("disabled selected", images[3]), ("disabled", images[2]), ("!selected", images[0]), { "width": width, "sticky": tk.W, "border": borderpad, }, ) } settings[ttkstyle] = { "configure": { "relief": tk.FLAT, "borderwidth": 0, "foreground": self.colors.fg, }, "layout": [ ( "Toolbutton.border", { "sticky": tk.NSEW, "children": [ ( "Toolbutton.padding", { "sticky": tk.NSEW, "children": [ ( f"{ttkstyle}.indicator", {"side": tk.LEFT}, ), ( "Toolbutton.label", {"side": tk.LEFT}, ), ], }, ) ], }, ) ], "map": { "foreground": [("disabled", disabled_fg)], "background": [ ("selected", self.colors.bg), ("!selected", self.colors.bg), ], }, } self._register_style(ttkstyle) def create_round_toggle_style(self, settings, colorname=None): """Create a round toggle style for the ttk.Checkbutton widget. Parameters: colorname (str): The color label used to style the widget. """ STYLE = "Round.Toggle" disabled_fg = self.colorutil.make_transparent( 0.30, self.colors.fg, self.colors.bg ) if any([colorname is None, colorname == ""]): ttkstyle = STYLE colorname = PRIMARY else: ttkstyle = f"{colorname}.{STYLE}" # ( off, on, disabled ) images = self.assets.create_round_toggle_assets(colorname) indicator_element = f"{ttkstyle}.indicator" if indicator_element not in self.existing_elements: width = self.scale_size(28) borderpad = self.scale_size(4) settings[indicator_element] = { "element create": ( "image", images[1], ("disabled selected", images[3]), ("disabled", images[2]), ("!selected", images[0]), { "width": width, "sticky": tk.W, "border": borderpad, }, ) } self.existing_elements[indicator_element] = True settings[ttkstyle] = { "configure": { "relief": tk.FLAT, "borderwidth": 0, "padding": 0, "foreground": self.colors.fg, "background": self.colors.bg, }, "map": { "foreground": [("disabled", disabled_fg)], "background": [("selected", self.colors.bg)], }, "layout": [ ( "Toolbutton.border", { "sticky": tk.NSEW, "children": [ ( "Toolbutton.padding", { "sticky": tk.NSEW, "children": [ ( f"{ttkstyle}.indicator", {"side": tk.LEFT}, ), ( "Toolbutton.label", {"side": tk.LEFT}, ), ], }, ) ], }, ) ], } self._register_style(ttkstyle) def create_round_scrollbar_style(self, settings, colorname=None): """Create a round style for the ttk.Scrollbar widget. Parameters: colorname (str): The color label used to style the widget. """ STYLE = "TScrollbar" if any([colorname is None, colorname == ""]): h_ttkstyle = f"Round.Horizontal.{STYLE}" v_ttkstyle = f"Round.Vertical.{STYLE}" if self.is_light_theme: background = self.colors.border else: background = self.colors.selectbg else: h_ttkstyle = f"{colorname}.Round.Horizontal.{STYLE}" v_ttkstyle = f"{colorname}.Round.Vertical.{STYLE}" background = self.colors.get_color(colorname) if self.is_light_theme: if colorname == LIGHT: troughcolor = self.colors.bg else: troughcolor = self.colors.light else: troughcolor = self.colorutil.update_hsv( self.colors.selectbg, vd=-0.2 ) pressed = self.colorutil.update_hsv(background, vd=-0.05) active = self.colorutil.update_hsv(background, vd=0.05) scroll_images = self.assets.create_round_scrollbar_assets( background, pressed, active ) # horizontal scrollbar settings[f"{h_ttkstyle}.thumb"] = { "element create": ( "image", scroll_images[0], ("pressed", scroll_images[1]), ("active", scroll_images[2]), { "padding": 0, "sticky": tk.EW, "border": self.scale_size(9), }, ) } settings[h_ttkstyle] = { "configure": { "troughcolor": troughcolor, "darkcolor": troughcolor, "bordercolor": troughcolor, "lightcolor": troughcolor, "arrowcolor": background, "arrowsize": self.scale_size(11), "background": troughcolor, "relief": tk.FLAT, "borderwidth": 0, }, "layout": [ ( "Horizontal.Scrollbar.trough", { "sticky": "we", "children": [ ( "Horizontal.Scrollbar.leftarrow", {"side": "left", "sticky": ""}, ), ( "Horizontal.Scrollbar.rightarrow", {"side": "right", "sticky": ""}, ), ( f"{h_ttkstyle}.thumb", {"expand": "1", "sticky": "nswe"}, ), ], }, ) ], "map": {"arrowcolor": [("pressed", pressed), ("active", active)]}, } # vertical scrollbar settings[f"{v_ttkstyle}.thumb"] = { "element create": ( "image", scroll_images[3], ("pressed", scroll_images[4]), ("active", scroll_images[5]), { "padding": 0, "sticky": tk.NS, "border": self.scale_size(9), }, ) } settings[v_ttkstyle] = { "configure": { "troughcolor": troughcolor, "darkcolor": troughcolor, "bordercolor": troughcolor, "lightcolor": troughcolor, "arrowcolor": background, "arrowsize": self.scale_size(11), "background": troughcolor, "relief": tk.FLAT, # "borderwidth": 0, }, "layout": [ ( "Vertical.Scrollbar.trough", { "sticky": "ns", "children": [ ( "Vertical.Scrollbar.uparrow", {"side": "top", "sticky": ""}, ), ( "Vertical.Scrollbar.downarrow", {"side": "bottom", "sticky": ""}, ), ( f"{v_ttkstyle}.thumb", {"expand": "1", "sticky": "nswe"}, ), ], }, ) ], "map": {"arrowcolor": [("pressed", pressed), ("active", active)]}, } if colorname: self._register_style(h_ttkstyle) self._register_style(v_ttkstyle) def create_table_treeview_style(self, settings, colorname=None): """Create a style for the Tableview widget. Parameters: colorname (str): The color label used to style the widget. """ STYLE = "Table.Treeview" f = font.nametofont("TkDefaultFont") rowheight = f.metrics()["linespace"] if self.is_light_theme: disabled_fg = self.colorutil.update_hsv( self.colors.inputbg, vd=-0.2 ) bordercolor = self.colors.border hover = self.colorutil.update_hsv(self.colors.light, vd=-0.1) else: disabled_fg = self.colorutil.update_hsv( self.colors.inputbg, vd=-0.3 ) bordercolor = self.colors.selectbg hover = self.colorutil.update_hsv(self.colors.dark, vd=0.1) if any([colorname is None, colorname == ""]): background = self.colors.inputbg foreground = self.colors.inputfg body_style = STYLE header_style = f"{STYLE}.Heading" elif colorname == LIGHT and self.is_light_theme: background = self.colors.get_color(colorname) foreground = self.colors.fg body_style = f"{colorname}.{STYLE}" header_style = f"{colorname}.{STYLE}.Heading" hover = self.colorutil.update_hsv(background, vd=-0.1) else: background = self.colors.get_color(colorname) foreground = self.colors.selectfg body_style = f"{colorname}.{STYLE}" header_style = f"{colorname}.{STYLE}.Heading" hover = self.colorutil.update_hsv(background, vd=0.1) # treeview header settings[header_style] = { "configure": { "background": background, "foreground": foreground, "relief": tk.RAISED, "borderwidth": 1, "darkcolor": background, "bordercolor": bordercolor, "lightcolor": background, "padding": 5, }, "map": { "foreground": [("disabled", disabled_fg)], "background": [ ("active !disabled", hover), ], "darkcolor": [ ("active !disabled", hover), ], "lightcolor": [ ("active !disabled", hover), ], }, } settings[body_style] = { "configure": { "background": self.colors.inputbg, "fieldbackground": self.colors.inputbg, "foreground": self.colors.inputfg, "bordercolor": bordercolor, "lightcolor": self.colors.inputbg, "darkcolor": self.colors.inputbg, "borderwidth": 2, "padding": 0, "rowheight": rowheight, "relief": tk.RAISED, }, "map": { "background": [("selected", self.colors.selectbg)], "foreground": [ ("disabled", disabled_fg), ("selected", self.colors.selectfg), ], }, "layout": [ ( "Button.border", { "sticky": tk.NSEW, "border": "1", "children": [ ( "Treeview.padding", { "sticky": tk.NSEW, "children": [ ( "Treeview.treearea", {"sticky": tk.NSEW}, ) ], }, ) ], }, ) ], } self._register_style(body_style) def create_floodgauge_style(self, settings, colorname=None): """Create a ttk style for the pygubu Floodgauge widget. This is a custom widget style that uses components of the progressbar and label. """ HSTYLE = "Horizontal.TFloodgauge" VSTYLE = "Vertical.TFloodgauge" FLOOD_FONT = "-size 14" if any([colorname is None, colorname == ""]): h_ttkstyle = HSTYLE v_ttkstyle = VSTYLE background = self.colors.primary else: h_ttkstyle = f"{colorname}.{HSTYLE}" v_ttkstyle = f"{colorname}.{VSTYLE}" background = self.colors.get_color(colorname) if colorname == LIGHT: foreground = self.colors.fg troughcolor = self.colors.bg else: troughcolor = self.colorutil.update_hsv(background, sd=-0.3, vd=0.8) foreground = self.colors.selectfg # horizontal floodgauge h_element = h_ttkstyle.replace(".TF", ".F") # vertical floodgauge v_element = v_ttkstyle.replace(".TF", ".F") ttk_elements = ( (f"{h_element}.trough", TTK_CLAM), (f"{h_element}.pbar", TTK_DEFAULT), (f"{v_element}.trough", TTK_CLAM), (f"{v_element}.pbar", TTK_DEFAULT), ) for element_name, from_ in ttk_elements: settings[element_name] = { "element create": ("from", from_), } h_layout = None v_layout = None if tk.TkVersion >= 9: h_layout, v_layout = self.floodgauge_layout_tk9( h_element, v_element ) else: h_layout, v_layout = self.floodgauge_layout_tk8( h_element, v_element ) settings[h_ttkstyle] = { "configure": dict( thickness=50, borderwidth=1, bordercolor=background, lightcolor=background, pbarrelief=tk.FLAT, troughcolor=troughcolor, background=background, foreground=foreground, justify=tk.CENTER, anchor=tk.CENTER, font=FLOOD_FONT, ), "layout": h_layout, } settings[v_ttkstyle] = { "configure": dict( thickness=50, borderwidth=1, bordercolor=background, lightcolor=background, pbarrelief=tk.FLAT, troughcolor=troughcolor, background=background, foreground=foreground, justify=tk.CENTER, anchor=tk.CENTER, font=FLOOD_FONT, ), "layout": v_layout, } if colorname: self._register_style(h_ttkstyle) self._register_style(v_ttkstyle) def floodgauge_layout_tk9(self, h_element: str, v_element: str) -> tuple: h_layout = [ ( f"{h_element}.trough", { "children": [ (f"{h_element}.pbar", {"side": "left", "sticky": "ns"}), ( f"{h_element}.ctext", # {"side": "left", "sticky": ""} {"side": "left", "sticky": "", "expand": True}, ), ], "sticky": "nswe", }, ) ] v_layout = [ ( f"{v_element}.trough", { "children": [ ( f"{v_element}.pbar", {"side": "bottom", "sticky": "we"}, ), (f"{v_element}.ctext", {"sticky": "", "expand": True}), ], "sticky": "nswe", }, ) ] return h_layout, v_layout def floodgauge_layout_tk8(self, h_element: str, v_element: str) -> tuple: TKCLASS_NAME = "Floodgauge" h_layout = [ ( f"{h_element}.trough", { "children": [ ( f"{h_element}.pbar", {"sticky": "ns"}, ), (f"{TKCLASS_NAME}.label", {"sticky": ""}), ], "sticky": "nsew", }, ) ] v_layout = [ ( f"{v_element}.trough", { "children": [ ( f"{v_element}.pbar", {"sticky": "ew"}, ), (f"{TKCLASS_NAME}.label", {"sticky": ""}), ], "sticky": "nsew", }, ) ] return h_layout, v_layout pygubu-0.36.1/src/pygubu/theming/bootstrap/config.py000066400000000000000000000004541474524032100225120ustar00rootroot00000000000000# Fix background of file selector dialog on linux FIX_LINUX_FILE_DIALOG = True # Constants TTK_CLAM = "clam" TTK_ALT = "alt" TTK_DEFAULT = "default" PRIMARY = "primary" SECONDARY = "secondary" SUCCESS = "success" INFO = "info" WARNING = "warning" DANGER = "danger" LIGHT = "light" DARK = "dark" pygubu-0.36.1/src/pygubu/theming/bootstrap/style.py000066400000000000000000000037331474524032100224100ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk from .themes import STANDARD_THEMES from .builder import ThemeDefinition, BootstrapThemeBuilder class Style(ttk.Style): """Style class to manage bootstrap themes.""" bs_names = [] bs_definitions = {} bs_builders = {} bs_themes_loaded = False def __init__(self, master=None): super().__init__(master) self._load_standard_themes() def _load_standard_themes(self): if not self.bs_themes_loaded: for name, definition in STANDARD_THEMES.items(): self.register_theme( ThemeDefinition( name=name, theme_type=definition["type"], colors=definition["colors"], ) ) type(self).bs_themes_loaded = True @classmethod def register_theme(cls, definition: ThemeDefinition): cls.bs_names.append(definition.name) cls.bs_definitions[definition.name] = definition def theme_names(self): return self.bs_names def theme_use(self, themename=None): if themename is None: return super().theme_use() if themename not in self.bs_names: raise ValueError(f"Bootstrap theme '{themename}' not found.") current_theme = super().theme_use() if current_theme == themename: # Already using theme: themename # Do nothing return if themename not in self.bs_builders: builder = BootstrapThemeBuilder(self.bs_definitions[themename]) builder.create(self.master) builder.apply(self.master) type(self).bs_builders[themename] = builder else: self.bs_builders[themename].apply(self.master) self.bs_builders[themename].update_current_widgets(self.master) @classmethod def get_generated_styles(cls): return list(BootstrapThemeBuilder.generated_styles) pygubu-0.36.1/src/pygubu/theming/bootstrap/themes.py000066400000000000000000000265101474524032100225330ustar00rootroot00000000000000STANDARD_THEMES = { "pbs_cosmo": { "type": "light", "colors": { "primary": "#2780e3", "secondary": "#7E8081", "success": "#3fb618", "info": "#9954bb", "warning": "#ff7518", "danger": "#ff0039", "light": "#F8F9FA", "dark": "#373A3C", "bg": "#ffffff", "fg": "#373a3c", "selectbg": "#7e8081", "selectfg": "#ffffff", "border": "#ced4da", "inputfg": "#373a3c", "inputbg": "#fdfdfe", "active": "#efefef", }, }, "pbs_flatly": { "type": "light", "colors": { "primary": "#2c3e50", "secondary": "#95a5a6", "success": "#18bc9c", "info": "#3498db", "warning": "#f39c12", "danger": "#e74c3c", "light": "#ECF0F1", "dark": "#7B8A8B", "bg": "#ffffff", "fg": "#212529", "selectbg": "#95a5a6", "selectfg": "#ffffff", "border": "#ced4da", "inputfg": "#212529", "inputbg": "#ffffff", "active": "#e2e2e2", }, }, "pbs_litera": { "type": "light", "colors": { "primary": "#4582ec", "secondary": "#adb5bd", "success": "#02b875", "info": "#17a2b8", "warning": "#f0ad4e", "danger": "#d9534f", "light": "#F8F9FA", "dark": "#343A40", "bg": "#ffffff", "fg": "#343a40", "selectbg": "#adb5bd", "selectfg": "#ffffff", "border": "#bfbfbf", "inputfg": "#343a40", "inputbg": "#fff", "active": "#e5e5e5", }, }, "pbs_minty": { "type": "light", "colors": { "primary": "#78c2ad", "secondary": "#f3969a", "success": "#56cc9d", "info": "#6cc3d5", "warning": "#ffce67", "danger": "#ff7851", "light": "#F8F9FA", "dark": "#343A40", "bg": "#ffffff", "fg": "#5a5a5a", "selectbg": "#f3969a", "selectfg": "#ffffff", "border": "#ced4da", "inputfg": "#696969", "inputbg": "#fff", "active": "#e5e5e5", }, }, "pbs_lumen": { "type": "light", "colors": { "primary": "#158cba", "secondary": "#919191", "success": "#28b62c", "info": "#75caeb", "warning": "#ff851b", "danger": "#ff4136", "light": "#F6F6F6", "dark": "#555555", "bg": "#ffffff", "fg": "#555555", "selectbg": "#919191", "selectfg": "#ffffff", "border": "#ced4da", "inputfg": "#555555", "inputbg": "#fff", "active": "#e5e5e5", }, }, "pbs_sandstone": { "type": "light", "colors": { "primary": "#325D88", "secondary": "#8e8c84", "success": "#93c54b", "info": "#29abe0", "warning": "#f47c3c", "danger": "#d9534f", "light": "#F8F5F0", "dark": "#3E3F3A", "bg": "#ffffff", "fg": "#3e3f3a", "selectbg": "#8e8c84", "selectfg": "#ffffff", "border": "#ced4da", "inputfg": "#6E6D69", "inputbg": "#fff", "active": "#e5e5e5", }, }, "pbs_yeti": { "type": "light", "colors": { "primary": "#008cba", "secondary": "#707070", "success": "#43ac6a", "info": "#5bc0de", "warning": "#e99002", "danger": "#f04124", "light": "#EEEEEE", "dark": "#222222", "bg": "#ffffff", "fg": "#222222", "selectbg": "#707070", "selectfg": "#ffffff", "border": "#cccccc", "inputfg": "#222222", "inputbg": "#fff", "active": "#e5e5e5", }, }, "pbs_pulse": { "type": "light", "colors": { "primary": "#593196", "secondary": "#69676E", "success": "#13b955", "info": "#009cdc", "warning": "#efa31d", "danger": "#fc3939", "light": "#F9F8FC", "dark": "#17141F", "bg": "#ffffff", "fg": "#444444", "selectbg": "#69676e", "selectfg": "#ffffff", "border": "#cbc8d0", "inputfg": "#444444", "inputbg": "#fdfdfe", "active": "#e5e5e5", }, }, "pbs_united": { "type": "light", "colors": { "primary": "#e95420", "secondary": "#aea79f", "success": "#38b44a", "info": "#17a2b8", "warning": "#efb73e", "danger": "#df382c", "light": "#E9ECEF", "dark": "#772953", "bg": "#ffffff", "fg": "#333333", "selectbg": "#aea79f", "selectfg": "#ffffff", "border": "#ced4da", "inputfg": "#333333", "inputbg": "#fff", "active": "#e5e5e5", }, }, "pbs_morph": { "type": "light", "colors": { "primary": "#378DFC", "secondary": "#aaaaaa", "success": "#43cc29", "info": "#5B62F4", "warning": "#FFC107", "danger": "#E52527", "light": "#F0F5FA", "dark": "#212529", "bg": "#D9E3F1", "fg": "#7B8AB8", "selectbg": "#aaaaaa", "selectfg": "#FBFDFF", "border": "#B9C7DA", "inputfg": "#7F8EBA", "inputbg": "#F0F5FA", "active": "#C3CCD8", }, }, "pbs_journal": { "type": "light", "colors": { "primary": "#eb6864", "secondary": "#aaaaaa", "success": "#22b24c", "info": "#336699", "warning": "#f5e625", "danger": "#f57a00", "light": "#F8F9FA", "dark": "#222222", "bg": "#ffffff", "fg": "#222222", "selectbg": "#aaaaaa", "selectfg": "#ffffff", "border": "#ced4da", "inputfg": "#565656", "inputbg": "#fff", "active": "#e5e5e5", }, }, "pbs_darkly": { "type": "dark", "colors": { "primary": "#375a7f", "secondary": "#444444", "success": "#00bc8c", "info": "#3498db", "warning": "#f39c12", "danger": "#e74c3c", "light": "#ADB5BD", "dark": "#303030", "bg": "#222222", "fg": "#ffffff", "selectbg": "#555555", "selectfg": "#ffffff", "border": "#222222", "inputfg": "#ffffff", "inputbg": "#2f2f2f", "active": "#1F1F1F", }, }, "pbs_superhero": { "type": "dark", "colors": { "primary": "#4c9be8", "secondary": "#4e5d6c", "success": "#5cb85c", "info": "#5bc0de", "warning": "#f0ad4e", "danger": "#d9534f", "light": "#ABB6C2", "dark": "#20374C", "bg": "#2b3e50", "fg": "#ffffff", "selectbg": "#526170", "selectfg": "#ffffff", "border": "#222222", "inputfg": "#ebebeb", "inputbg": "#32465a", "active": "#2B4155", }, }, "pbs_solar": { "type": "dark", "colors": { "primary": "#bc951a", "secondary": "#94a2a4", "success": "#44aca4", "info": "#3f98d7", "warning": "#d05e2f", "danger": "#d95092", "light": "#A9BDBD", "dark": "#073642", "bg": "#002B36", "fg": "#ffffff", "selectbg": "#0b5162", "selectfg": "#ffffff", "border": "#00252e", "inputfg": "#A9BDBD", "inputbg": "#073642", "active": "#002730", }, }, "pbs_cyborg": { "type": "dark", "colors": { "primary": "#2a9fd6", "secondary": "#555555", "success": "#77b300", "info": "#9933cc", "warning": "#ff8800", "danger": "#cc0000", "light": "#ADAFAE", "dark": "#222222", "bg": "#060606", "fg": "#ffffff", "selectbg": "#454545", "selectfg": "#ffffff", "border": "#060606", "inputfg": "#ffffff", "inputbg": "#191919", "active": "#282828", }, }, "pbs_vapor": { "type": "dark", "colors": { "primary": "#6e40c0", "secondary": "#ea38b8", "success": "#3af180", "info": "#1da2f2", "warning": "#ffbd05", "danger": "#e34b54", "light": "#44d7e8", "dark": "#170229", "bg": "#190831", "fg": "#32fbe2", "selectbg": "#461a8a", "selectfg": "#ffffff", "border": "#060606", "inputfg": "#bfb6cd", "inputbg": "#30115e", "active": "#17082E", }, }, "pbs_simplex": { "type": "light", "colors": { "primary": "#d8220e", "secondary": "#858e96", "success": "#469307", "info": "#0099ce", "warning": "#d88220", "danger": "#9a479e", "light": "#f2f2f2", "dark": "#3b3d3f", "bg": "#fcfcfc", "fg": "#3b3d3f", "selectbg": "#a9afb6", "selectfg": "#ffffff", "border": "#858e96", "inputfg": "#3b3d3f", "inputbg": "#fcfcfc", "active": "#e2e2e2", }, }, "pbs_cerculean": { "type": "light", "colors": { "primary": "#4bb1ea", "secondary": "#a9b4be", "success": "#84b251", "info": "#225384", "warning": "#e16e25", "danger": "#cf3c40", "light": "#eceef1", "dark": "#33383e", "bg": "#ffffff", "fg": "#2ea4e7", "selectbg": "#adb5bd", "selectfg": "#ffffff", "border": "#a9b4be", "inputfg": "#495057", "inputbg": "#ffffff", "active": "#e5e5e5", }, }, "pbs_tabler_dark": { "type": "dark", "colors": { "primary": "#066fd1", "secondary": "#6c7a91", "success": "#2fb344", "info": "#4299e1", "warning": "#f76707", "danger": "#d63939", "light": "#f6f8fb", "dark": "#151f2c", "bg": "#182433", "fg": "#f6f8fb", "selectbg": "#15304c", "selectfg": "#dce1e7", "border": "#066fd1", "inputfg": "#dce1e7", "inputbg": "#151f2c", "active": "#066fd1", }, }, } pygubu-0.36.1/src/pygubu/theming/color.py000066400000000000000000000050061474524032100203440ustar00rootroot00000000000000import colorsys import tkinter as tk class ColorUtil: """Color utilities using tkinter functions.""" def __init__(self, master): self.tk_master: tk.Widget = master # Color utilities begin def make_transparent(self, alpha, foreground, background="#ffffff"): """Simulate color transparency.""" fg = tuple((c // 256 for c in self.tk_master.winfo_rgb(foreground))) bg = tuple((c // 256 for c in self.tk_master.winfo_rgb(background))) rgb_float = [alpha * c1 + (1 - alpha) * c2 for (c1, c2) in zip(fg, bg)] rgb_int = [int(x) for x in rgb_float] return "#{:02x}{:02x}{:02x}".format(*rgb_int) def hex_to_rgb(self, color: str): return tuple((c // 256 for c in self.tk_master.winfo_rgb(color))) def update_hsv(self, color, hd=0, sd=0, vd=0): """Modify the hue, saturation, and/or value of a given hex color value by specifying the _delta_. Parameters: color (str): A hexadecimal color value to adjust. hd (float): % change in hue, _hue delta_. sd (float): % change in saturation, _saturation delta_. vd (float): % change in value, _value delta_. Returns: str: The resulting hexadecimal color value """ rgb_256 = tuple((c // 256 for c in self.tk_master.winfo_rgb(color))) r, g, b = tuple((c / 256 for c in rgb_256)) h, s, v = colorsys.rgb_to_hsv(r, g, b) # hue if h * (1 + hd) > 1: h = 1 elif h * (1 + hd) < 0: h = 0 else: h *= 1 + hd # saturation if s * (1 + sd) > 1: s = 1 elif s * (1 + sd) < 0: s = 0 else: s *= 1 + sd # value if v * (1 + vd) > 1: v = 0.95 elif v * (1 + vd) < 0.05: v = 0.05 else: v *= 1 + vd rgb_float = colorsys.hsv_to_rgb(h, s, v) rgb_int = [int(x * 256) for x in rgb_float] return "#{:02x}{:02x}{:02x}".format(*rgb_int) def rgb_to_hsv(self, r, g, b): """Convert an rgb to hsv color value. Parameters: r (float): red g (float): green b (float): blue Returns: Tuple[float, float, float]: The hsv color value. """ return colorsys.rgb_to_hsv(r, g, b) # Color utilities end pygubu-0.36.1/src/pygubu/theming/photodraw.py000066400000000000000000000522411474524032100212400ustar00rootroot00000000000000import math import time import tkinter as tk class TkPhotoDraw: """A fallback class for drawing in a tk.PhotoImage.""" def __init__(self, master): self.tk_master: tk.Widget = master self.canvas: tk.PhotoImage = None def _tk_call(self, *args, **kw): return self.tk_master.tk.call(*args, **kw) # Canvas related functions begin def canvas_new(self, **photo_args): if "master" not in photo_args: photo_args["master"] = self.tk_master self.canvas = tk.PhotoImage(**photo_args) return self.canvas def canvas_blank(self): self.canvas.blank() def canvas_scale(self, xf, yf=0) -> tk.PhotoImage: """Scale canvas to new image. Returns: tk.PhotoImage """ mode = "-subsample" if abs(xf) < 1: xf = 1.0 / xf if abs(yf) < 1 and yf != 0: yf = 1.0 / yf elif xf >= 0 and yf >= 0: mode = "-zoom" if yf == 0: yf = xf xf, yf = int(xf), int(yf) tmp = tk.PhotoImage(master=self.tk_master) self.tk_master.tk.call( tmp.name, "copy", self.canvas.name, "-shrink", mode, xf, yf ) return tmp def canvas_rotate(self, angle: int, bg_color=None): if bg_color is None: bg_color = "#000000" w = self.canvas.width() h = self.canvas.height() tmp = self.canvas.copy() self.canvas.blank() if angle == 180 or angle == -180: self._tk_call( self.canvas.name, "copy", tmp.name, "-subsample", -1, -1 ) elif angle == 90: self._rotate_90(bg_color, w, h, tmp) elif angle == -90 or angle == 270: self._rotate_270(bg_color, w, h, tmp) else: self._handle_other_rotations(angle, bg_color, w, h, tmp) del tmp def _handle_other_rotations(self, angle, bg_color, w, h, tmp): buf = [] alpha = [] bg_rgb = self.tk_master.winfo_rgb(bg_color) a = math.atan(1) * 8 * angle / 360 xm = w / 2 ym = h / 2 w2 = round(abs(w * math.cos(a)) + abs(h * math.sin(a))) xm2 = w2 / 2 h2 = round(abs(h * math.cos(a)) + abs(w * math.sin(a))) ym2 = h2 / 2 self.canvas.config(width=w2, height=h2) for i in range(0, h2): to_x = -1 for j in range(0, w2): rad = math.hypot(ym2 - i, xm2 - j) th = math.atan2(ym2 - i, xm2 - j) + a x = xm - rad * math.cos(th) if x < 0 or x >= w: alpha.append((i, j)) continue y = ym - rad * math.sin(th) if y < 0 or y >= h: alpha.append((i, j)) continue x0 = int(x) x1 = x0 + 1 if (x0 + 1) < w else x0 dx = x1 - x y0 = int(y) y1 = y0 + 1 if (y0 + 1) < h else y0 dy = y1 - y R = G = B = 0 rgb1 = tmp.get(x0, y0) rgb2 = tmp.get(x0, y1) rgb3 = tmp.get(x1, y0) rgb4 = tmp.get(x1, y1) if ( rgb1 == bg_rgb or rgb2 == bg_rgb or rgb3 == bg_rgb or rgb4 == bg_rgb ): alpha.append((i, j)) else: r, g, b = rgb1 R = R + r * dx * dy G = G + g * dx * dy B = B + b * dx * dy r, g, b = rgb2 R = R + r * dx * (1 - dy) G = G + g * dx * (1 - dy) B = B + b * dx * (1 - dy) r, g, b = rgb3 R = R + r * (1 - dx) * dy G = G + g * (1 - dx) * dy B = B + b * (1 - dx) * dy r, g, b = rgb4 R = R + r * (1 - dx) * (1 - dy) G = G + g * (1 - dx) * (1 - dy) B = B + b * (1 - dx) * (1 - dy) rgb = (round(R), round(G), round(B)) buf.append("#{:02x}{:02x}{:02x}".format(*rgb)) if to_x == -1: to_x = j if to_x >= 0: self.canvas.put(buf, to=(i, to_x)) for x, y in alpha: self.canvas.transparency_set(x, y, True) buf.clear() alpha.clear() del buf def _rotate_270(self, bg_color, w, h, tmp): bg_rgb = self.tk_master.winfo_rgb(bg_color) buf = [] alpha = [] for i in range(0, w): row = [] for j in range(h - 1, -1, -1): rgb = tmp.get(i, j) if rgb == bg_rgb: alpha.append((i, j)) row.append("#{:02x}{:02x}{:02x}".format(*rgb)) buf.append(row) self.canvas.config(width=h, height=w) self.canvas.put(buf) for x, y in alpha: self.canvas.transparency_set(y, x, True) del alpha del buf def _rotate_90(self, bg_color, w, h, tmp): bg_rgb = self.tk_master.winfo_rgb(bg_color) buf = [] alpha = [] for i in range(w - 1, -1, -1): row = [] for j in range(0, h): rgb = tmp.get(i, j) if rgb == bg_rgb: alpha.append((i, j)) row.append("#{:02x}{:02x}{:02x}".format(*rgb)) buf.append(row) self.canvas.config(width=h, height=w) self.canvas.put(buf) for x, y in alpha: self.canvas.transparency_set(y, x, True) del alpha del buf # Canvas related functions end @staticmethod def bresenham_line(x1, y1, x2, y2): dx = abs(x2 - x1) dy = abs(y2 - y1) slope = dy > dx if slope: x1, y1 = y1, x1 x2, y2 = y2, x2 if x1 > x2: x1, x2 = x2, x1 y1, y2 = y2, y1 dx = abs(x2 - x1) dy = abs(y2 - y1) error = dx // 2 y = y1 ystep = 1 if y1 < y2 else -1 for x in range(x1, x2 + 1): coord = (y, x) if slope else (x, y) yield coord error -= dy if error < 0: y += ystep error += dx def _line(self, x1, y1, x2, y2, color): """Draw line.""" for x, y in Draw.bresenham_line(x1, y1, x2, y2): self.canvas.put(color, to=(x, y)) def _draw_rect_filled(self, x1, y1, x2, y2, color): # self.canvas.put(color, to=(x1, y1, x2 + 1, y2 + 1)) self.canvas.put(color, to=(x1, y1, x2, y2)) def _get_ink(self, fill, outline): fill_ink = fill border_ink = outline if fill is None and outline is None: fill_ink = "black" border_ink = "black" elif fill is None: fill_ink = outline elif outline is None: border_ink = fill_ink return fill_ink, border_ink def rectangle(self, x1, y1, x2, y2, *, fill=None, outline=None, stroke=1): fill_ink, border_ink = self._get_ink(fill, outline) if fill_ink == border_ink and fill: self._draw_rect_filled(x1, y1, x2, y2, fill_ink) else: # top line self._draw_rect_filled(x1, y1, x2, y1 + stroke, border_ink) # bottom self._draw_rect_filled(x1, y2 - stroke, x2, y2, border_ink) # left self._draw_rect_filled(x1, y1, x1 + stroke, y2, border_ink) # right self._draw_rect_filled(x2 - stroke, y1, x2, y2, border_ink) # interior if fill: self._draw_rect_filled( x1 + stroke, y1 + stroke, x2 - stroke, y2 - stroke, fill_ink ) def rounded_rectangle( self, x1, y1, x2, y2, *, fill=None, outline=None, stroke=1, radio=0 ): fill_ink, border_ink = self._get_ink(fill, outline) # left border self._draw_rect_filled( x1, y1 + radio, x1 + stroke, y2 - radio, border_ink ) # right border self._draw_rect_filled( x2 - stroke, y1 + radio, x2, y2 - radio, border_ink ) # top border self._draw_rect_filled( x1 + radio, y1, x2 - radio, y1 + stroke, border_ink ) # bottom border self._draw_rect_filled( x1 + radio, y2 - stroke, x2 - radio, y2, border_ink ) if fill: # center rectangle self._draw_rect_filled( x1 + stroke, y1 + radio, x2 - stroke, y2 - radio, fill_ink ) # top self._draw_rect_filled( x1 + radio, y1 + stroke, x2 - radio, y1 + radio, fill_ink ) # bottom self._draw_rect_filled( x1 + radio, y2 - radio, x2 - radio, y2 - stroke, fill_ink ) ix1, iy1, ix2, iy2 = ( # noqa: F841 x1 + stroke, y1 + stroke, x2 - stroke, y2 - stroke, ) diam = radio * 2 cx1, cy1, cx2, cy2 = (x1, y1, x2, y2) cboxes = ( (cx1, cy1, cx1 + diam, cy1 + diam, 0), (cx2 - diam, cy1, cx2, cy1 + diam, 1), (cx1, cy2 - diam, cx1 + diam, cy2, 2), (cx2 - diam, cy2 - diam, x2, cy2, 3), ) for cx1, cy1, cx2, cy2, cuadrant in cboxes: self.circle_cuadrant( cx1, cy1, cx2, cy2, cuadrant, outline=outline, fill=fill, stroke=stroke, ) def circle(self, x1, y1, x2, y2, *, fill=None, outline=None, stroke=1): d = x2 - x1 r = int(d / 2) # center x = x1 + r y = y1 + r fill_ink, border_ink = self._get_ink(fill, outline) # will add +1 to y2 because I'm drawing using # filled rectangles from tk photo image if fill_ink == border_ink and fill: self._draw_rect_filled(x - r, y, x + r, y + 1, fill_ink) for i in range(1, r): a = int(math.sqrt(r * r - i * i)) # Pythagoras self._draw_rect_filled(x - a, y - i, x + a, y - i + 1, fill_ink) self._draw_rect_filled(x - a, y + i, x + a, y + i + 1, fill_ink) else: ix1 = x1 + stroke ix2 = x2 - stroke dd = ix2 - ix1 ir = int(dd / 2) ix = ix1 + ir iy = y1 + stroke + ir for i in range(0, r): a = r if i == 0 else int(math.sqrt(r * r - i * i)) # Pythagoras if i > ir: self._draw_rect_filled( x - a, y - i, x + a, y - i + 1, border_ink ) self._draw_rect_filled( x - a, y + i, x + a, y + i + 1, border_ink ) else: ia = ( ir if i == 0 else int(math.sqrt(ir * ir - i * i)) ) # Pythagoras x1, y1, x2, y2 = (x - a, y - i, x + a, y - i) ix1, iy1, ix2, iy2 = (ix - ia, iy - i, ix + ia, iy - i) self._draw_rect_filled(x1, y1, ix1, iy1 + 1, border_ink) self._draw_rect_filled(ix2, iy2, x2, y2 + 1, border_ink) if fill: self._draw_rect_filled(ix1, iy1, ix2, iy2 + 1, fill_ink) x1, y1, x2, y2 = (x - a, y + i, x + a, y + i) ix1, iy1, ix2, iy2 = (ix - ia, iy + i, ix + ia, iy + i) self._draw_rect_filled(x1, y1, ix1, iy1 + 1, border_ink) self._draw_rect_filled(ix2, iy2, x2, y2 + 1, border_ink) if fill: self._draw_rect_filled(ix1, iy1, ix2, iy2 + 1, fill_ink) def circle_cuadrant( self, x1, y1, x2, y2, cuadrant, *, fill=None, outline=None, stroke=1 ): """Draw a circle cuadrant Circle cuadrants for this function: 0 - 1 2 - 3 """ d = x2 - x1 r = int(d / 2) # center x = x1 + r y = y1 + r fill_ink, border_ink = self._get_ink(fill, outline) # will add +1 to x2 and y2 because I'm drawing using # filled rectangles from tk photo image ix1 = x1 + stroke ix2 = x2 - stroke dd = ix2 - ix1 ir = int(dd / 2) ix = ix1 + ir iy = y1 + stroke + ir for i in range(0, r): a = r if i == 0 else int(math.sqrt(r * r - i * i)) # Pythagoras if i > ir: # UPPER BORDERS if cuadrant == 0: self._draw_rect_filled( x - a, y - i, x, y - i + 1, border_ink ) pass if cuadrant == 1: self._draw_rect_filled( x, y - i, x + a, y - i + 1, border_ink ) # BOTTOM BORDERS if cuadrant == 2: self._draw_rect_filled( x - a, y + i, x, y + i + 1, border_ink ) if cuadrant == 3: self._draw_rect_filled( x, y + i, x + a, y + i + 1, border_ink ) else: self._handle_lower_i(cuadrant, fill, x, y, fill_ink, border_ink, ir, ix, iy, i, a) def _handle_lower_i(self, cuadrant, fill, x, y, fill_ink, border_ink, ir, ix, iy, i, a): ia = ( ir if i == 0 else int(math.sqrt(ir * ir - i * i)) ) # Pythagoras x1, y1, x2, y2 = (x - a, y - i, x + a, y - i) ix1, iy1, ix2, iy2 = (ix - ia, iy - i, ix + ia, iy - i) # upper left if 0 == cuadrant: self._draw_rect_filled(x1, y1, ix1, iy1 + 1, border_ink) if fill: self._draw_rect_filled(ix1, iy1, x, iy1 + i, fill_ink) # upper right if 1 == cuadrant: self._draw_rect_filled(ix2, iy2, x2, y2 + 1, border_ink) if fill: self._draw_rect_filled(x, iy1, ix2, iy2 + 1, fill_ink) x1, y1, x2, y2 = (x - a, y + i, x + a, y + i) ix1, iy1, ix2, iy2 = (ix - ia, iy + i, ix + ia, iy + i) # bottom left if 2 == cuadrant: self._draw_rect_filled(x1, y1, ix1, iy1 + 1, border_ink) if fill: self._draw_rect_filled(ix1, iy1, x, iy2 + 1, fill_ink) # bottom right if 3 == cuadrant: self._draw_rect_filled(ix2, iy2, x2, y2 + 1, border_ink) if fill: self._draw_rect_filled(x, iy1, ix2, iy2 + 1, fill_ink) def triangle(self, x1, y1, x2, y2, x3, y3, color): self._line(x1, y1, x2, y2, color) self._line(x2, y2, x3, y3, color) self._line(x3, y3, x1, y1, color) def triangle_filled(self, x1, y1, x2, y2, x3, y3, color): x1, y1, x2, y2, x3, y3 = self._sort_verticesby_y(x1, y1, x2, y2, x3, y3) if y2 == y3: self._fill_bottom_flat_tri(x1, y1, x2, y2, x3, y3, color) elif y1 == y2: self._fill_top_flat_tri(x1, y1, x2, y2, x3, y3, color) else: newx = int(x1 + ((y2 - y1) / (y3 - y1)) * (x3 - x1)) newy = y2 self._fill_bottom_flat_tri(x1, y1, x2, y2, newx, newy, color) self._fill_top_flat_tri(x2, y2, newx, newy, x3, y3, color) def _fill_bottom_flat_tri(self, x1, y1, x2, y2, x3, y3, color): slope1 = (x2 - x1) / (y2 - y1) slope2 = (x3 - x1) / (y3 - y1) hx1 = x1 hx2 = x1 + 0.5 for scanline_y in range(y1, y2): self._draw_rect_filled( int(hx1), scanline_y, int(hx2), scanline_y + 1, color ) hx1 += slope1 hx2 += slope2 def _fill_top_flat_tri(self, x1, y1, x2, y2, x3, y3, color): slope1 = (x3 - x1) / (y3 - y1) slope2 = (x3 - x2) / (y3 - y2) hx1 = x3 hx2 = x3 + 0.5 for scanline_y in range(y3, y1 - 1, -1): self._draw_rect_filled( int(hx1), scanline_y, int(hx2), scanline_y + 1, color ) hx1 -= slope1 hx2 -= slope2 def _sort_verticesby_y(self, x1, y1, x2, y2, x3, y3): verts = sorted([(x1, y1), (x2, y2), (x3, y3)], key=lambda x: x[1]) return (p for v in verts for p in v) def convex_poly(self, points, color): if self.is_convex(points): # points = sorted(points, key=lambda x: x[1]) for t in self._tripoly(points): self.triangle_filled(*t[0], *t[1], *t[2], color) else: raise Exception("Not a convex polygon.") def _cross_prod(self, points): # A[0][0] px1 [0][1] py1 # A[1][0] px2 A[1][1] py2 # A[2][0] px3 A[2][1] py3 x1 = points[1][0] - points[0][0] y1 = points[1][1] - points[0][1] x2 = points[2][0] - points[0][0] y2 = points[2][1] - points[0][1] # Return cross product return x1 * y2 - y1 * x2 def _tripoly(self, poly): return [(poly[0], b, c) for b, c in zip(poly[1:], poly[2:])] def is_convex(self, points): # Stores count of # edges in polygon N = len(points) # Stores direction of cross product # of previous traversed edges prev = 0 # Stores direction of cross product # of current traversed edges curr = 0 # Traverse the array for i in range(N): # Stores three adjacent edges # of the polygon temp = (points[i], points[(i + 1) % N], points[(i + 2) % N]) # Update curr curr = self._cross_prod(temp) # If curr is not equal to 0 if curr != 0: # If direction of cross product of # all adjacent edges are not same if curr * prev < 0: return False else: # Update curr prev = curr return True Draw = TkPhotoDraw if __name__ == "__main__": root = tk.Tk() root.imgs = [] draw = Draw(root) # rectangle rr = draw.canvas_new(width=300, height=100) root.imgs.append(rr) draw.rectangle(10, 10, 90, 90, outline="green", stroke=1) draw.rectangle(110, 10, 190, 90, fill="lightgreen") draw.rectangle( 210, 10, 290, 90, fill="lightgreen", outline="green", stroke=10 ) label = tk.Label(root, image=rr) label.pack() # rounded rectangle rr = draw.canvas_new(width=300, height=100) root.imgs.append(rr) draw.rounded_rectangle(10, 10, 90, 90, radio=20, outline="green", stroke=1) draw.rounded_rectangle(110, 10, 190, 90, radio=20, fill="lightgreen") draw.rounded_rectangle( 210, 10, 290, 90, radio=20, fill="lightgreen", outline="green", stroke=10, ) label = tk.Label(root, image=rr) label.pack() # circle cuadrant rr = draw.canvas_new(width=300, height=100) root.imgs.append(rr) draw.circle_cuadrant(10, 10, 90, 90, 3, outline="green") draw.circle_cuadrant(110, 10, 190, 90, 3, fill="lightgreen") draw.circle_cuadrant( 210, 10, 290, 90, 3, fill="lightgreen", outline="green", stroke=10 ) label = tk.Label(root, image=rr) label.pack() # circle rr = draw.canvas_new(width=300, height=100) root.imgs.append(rr) draw.circle(10, 10, 90, 90, outline="green") draw.circle(110, 10, 190, 90, fill="lightgreen") draw.circle(210, 10, 290, 90, fill="lightgreen", outline="green", stroke=10) label = tk.Label(root, image=rr) label.pack() # triangle rr = draw.canvas_new(width=300, height=100) root.imgs.append(rr) draw.triangle(10, 90, 50, 0, 90, 90, "green") # triangle filled draw.triangle_filled(110, 90, 150, 0, 190, 90, "green") draw.triangle_filled(210, 0, 290, 0, 250, 90, "green") label = tk.Label(root, image=rr) label.pack() # shape rr = draw.canvas_new(width=134, height=134) root.imgs.append(rr) shape = [ (34, 76, 38, 72, 43, 70), (35, 77, 38, 88, 43, 98), (34, 76, 52, 82, 44, 100), (34, 76, 51, 82, 44, 70), (43, 100, 51, 82, 53, 98), (51, 82, 53, 98, 64, 82), (52, 81, 64, 82, 85, 53), (52, 81, 85, 53, 71, 53), (85, 53, 71, 53, 100, 34), (71, 53, 100, 34, 85, 35), (100, 34, 85, 35, 96, 30), (85, 35, 90, 31, 97, 30), (96, 30, 100, 34, 101, 30), ] for x1, y1, x2, y2, x3, y3 in shape: draw.triangle_filled(x1, y1, x2, y2, x3, y3, "brown") label = tk.Label(root, image=rr) label.pack() points = [[0, 0], [0, 10], [10, 10], [10, 0]] points2 = [(0, 0), (0, 10), (5, 5), (10, 10), (10, 0)] for p in (points, points2): if draw.is_convex(p): print("Yes") draw.convex_poly(p, "red") else: print("No") root.mainloop() pygubu-0.36.1/src/pygubu/utils/000077500000000000000000000000001474524032100163605ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/utils/__init__.py000066400000000000000000000000001474524032100204570ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/utils/datatrans.py000066400000000000000000000012341474524032100207130ustar00rootroot00000000000000import json class ListDTO: def __init__(self, default_value=None, on_error_default=None): self.default_value = default_value self.on_error_default = ( ["Invalid data"] if on_error_default is None else on_error_default ) def transform(self, value: str, default=None): val = self.default_value if default is None else default try: json_list = json.loads(value) if isinstance(json_list, list): val = json_list else: val = self.on_error_default except json.JSONDecodeError: val = self.on_error_default return val pygubu-0.36.1/src/pygubu/utils/font.py000066400000000000000000000025451474524032100177060ustar00rootroot00000000000000# encoding: utf-8 import re RE_FONT = re.compile( "(?P\\{\\w+(\\w|\\s)*\\}|\\w+)\\s?(?P-?\\d+)?\\s?(?P\\{\\w+(\\w|\\s)*\\}|\\w+)?" ) def tkfontstr_to_dict(fontstr): """Parse a string with a tk font format like '{Helvetica} 12 {bold}' and return a dict with keys family, size and modifiers.""" font_dict = { "family": None, "size": None, "modifiers": None, } s = RE_FONT.search(fontstr) if s: g = s.groupdict() font_dict["family"] = g["family"].replace("{", "").replace("}", "") font_dict["size"] = g["size"] modifiers = g["modifiers"] if modifiers is not None: modifiers = modifiers.replace("{", "").replace("}", "") font_dict["modifiers"] = modifiers else: font_dict["family"] = fontstr return font_dict def tkfontstr_to_tuple(fontstr, default_size=12): """Convert string with a tk font format like '{Helvetica} 12 {bold}' to a python tuple ('Helvetica', 12, 'bold').""" fdict = tkfontstr_to_dict(fontstr) font_desc = [ fdict["family"], ] # Add default size size = fdict["size"] size = size if size is not None else default_size font_desc.append(size) key = "modifiers" if fdict[key] is not None: font_desc.append(fdict[key]) return tuple(font_desc) pygubu-0.36.1/src/pygubu/utils/widget.py000066400000000000000000000101761474524032100202220ustar00rootroot00000000000000""" Widget utility functions.""" import tkinter as tk def crop_widget(widget, *, recursive=False): """Remove standard widget functionality.""" # Remove standard bindings wclass = widget.winfo_class() bindtags = widget.bindtags() if wclass in bindtags: bindtags = list(bindtags) bindtags.remove(wclass) widget.bindtags(bindtags) # Don't take focus try: widget.configure(takefocus=False) except Exception: pass if recursive: for childw in widget.winfo_children(): crop_widget(childw, recursive=recursive) def iter_parents(widget: tk.Widget): top = str(widget.winfo_toplevel()) parent = widget.winfo_parent() if not parent or parent == ".": return None while top != parent: pw = widget.nametowidget(parent) yield pw parent = pw.winfo_parent() def iter_to_toplevel(widget: tk.Widget): """Iter parents of widget including widget itself""" yield widget yield from iter_parents(widget) class HideableMixin: """Allows to hide a tk.Widget.""" def __init__(self, *args, **kw): super().__init__(*args, **kw) self._is_hidden = None self._layout = None self._layout_info = None @property def hidden(self): return self._is_hidden @hidden.setter def hidden(self, state: bool): if state: self._hide() else: self._show() def _hide(self): if self._is_hidden is None: self._layout = m = self.winfo_manager() if m == "pack": self._layout_info = self.pack_info() self._layout_info.pop("in", None) parent = self.nametowidget(self.winfo_parent()) wlist = parent.pack_slaves() total = len(wlist) self_index = wlist.index(self) if total > 1: if self_index == 0: self._layout_info["before"] = wlist[1] else: self._layout_info["after"] = wlist[self_index - 1] elif m == "place": self._layout_info = info = self.place_info() info.pop("in", None) # FIXME: ttkbootstrap issue with localization ?? for key in ("relx", "rely", "relwidth", "relheight"): info[key] = str(info[key]).replace(",", ".") self._is_hidden = False if self._is_hidden is False: if self._layout == "pack": self.pack_forget() elif self._layout == "grid": self.grid_remove() elif self._layout == "place": self.place_forget() self._is_hidden = True def _show(self): if self._is_hidden: layout = self._layout if layout == "pack": self.pack(**self._layout_info) elif layout == "grid": self.grid() elif layout == "place": self.place(**self._layout_info) self._is_hidden = False class WidgetConfigureMixin: """Class Mixin to make easy adding properties a la tkinter way. Tkinter widget option management as used in class tk.Misc: cget(option) to get a widget option. configure(option=value) to set widget option configure(option) to get info about option """ def _configure_get(self, option): """Return option information""" return super().configure(option) def _configure_set(self, **kw): """Sets widgets options""" return super().configure(**kw) def _widget_cget(self, option): """Get widget option""" return super().cget(option) def cget(self, key): return self._widget_cget(key) def configure(self, cnf=None, **kwargs): if cnf is not None: return self._configure_get(cnf) else: return self._configure_set(**kwargs) def __getitem__(self, key: str): return self._widget_cget(key) def __setitem__(self, key: str, value): self._configure_set(**{key: value}) pygubu-0.36.1/src/pygubu/widgets/000077500000000000000000000000001474524032100166665ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/widgets/__init__.py000066400000000000000000000000001474524032100207650ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/widgets/accordionframe.py000066400000000000000000000112321474524032100222130ustar00rootroot00000000000000# encoding: UTF-8 import tkinter as tk import tkinter.ttk as ttk img_expand = """\ R0lGODlhEAAQAIAAAAAAAAAAACH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEKAAEALAAAAAAQABAA AAIXjI+py+0P4wK0WprunRo0/VgRJpXmyRQAOw== """ img_collapse = """\ R0lGODlhEAAQAIAAAAAAAAAAACH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEKAAEALAAAAAAQABAA AAIdjI+pywGtwINHTmpvy3rxnnwQh1mUI52o6rCuWgAAOw== """ class AccordionFrame(ttk.Frame): """An accordion like widget. Usage: acframe = AccordionFrame(master) acframe.grid() g = acframe.add_group('g1', 'Group1') label = ttk.Label(g, text='Label on group1') label.grid() """ EXPAND = 1 COLLAPSE = 2 def __init__(self, master=None, **kw): ttk.Frame.__init__(self, master, **kw) self.__images = None self.__groups = {} self.columnconfigure(0, weight=1) self.img_expand = None self.img_collapse = None def _get_image(self, imgid): if imgid == self.EXPAND: if self.img_expand is None: self.img_expand = tk.PhotoImage(data=img_expand, master=self) return self.img_expand else: if self.img_collapse is None: self.img_collapse = tk.PhotoImage( data=img_collapse, master=self ) return self.img_collapse def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) key = "img_expand" if key in kw: value = kw.pop(key) self.img_expand = value key = "img_collapse" if key in kw: value = kw.pop(key) self.img_collapse = value return super().configure(cnf, **kw) config = configure def cget(self, key): option = "img_expand" if key == option: return self.img_expand option = "img_collapse" if key == option: return self.img_collapse return super().cget(key) def add_group( self, gid, label=None, expanded=True, style="Toolbutton", compound="left", ): glabel = label if label is None: glabel = str(gid) # button creation btn = ttk.Button( self, text=glabel, style=style, image=self._get_image(self.EXPAND), compound=compound, ) btn.grid(sticky=tk.EW) btn.configure(command=lambda: self._toggle_group(gid)) # frame creation frame = ttk.Frame(self, width=100, height=100) frame.grid(sticky=tk.NSEW) frame.rowconfigure(0, weight=1) frame.columnconfigure(0, weight=1) if not expanded: self.after_idle(lambda: self._toggle_group(gid)) # store button, and frame self.__groups[gid] = (btn, frame) return frame def get_group(self, gid): return self.__groups[gid][1] def group_toogle(self, gid): self.__button_clicked(gid) def group_config(self, gid, **kw): btn, frame = self.__groups[gid] label = "label" if label in kw: btn.configure(text=kw[label]) expanded = "expanded" if expanded in kw: self.after_idle( lambda: self._toggle_group(gid, toggle_to=kw[expanded]) ) compound = "compound" if compound in kw: btn.configure(compound=kw[compound]) style = "style" if style in kw: btn.configure(style=kw[style]) def _toggle_group(self, gid, toggle_to=None): btn, frame = self.__groups[gid] if frame.winfo_viewable() or toggle_to is False: btn.configure(image=self._get_image(self.COLLAPSE)) frame.grid_remove() else: btn.configure(image=self._get_image(self.EXPAND)) frame.grid() self.event_generate("<>") if __name__ == "__main__": root = tk.Tk() app = AccordionFrame(root) app.grid(sticky=tk.NSEW) top = app.winfo_toplevel() top.rowconfigure(0, weight=1) top.columnconfigure(0, weight=1) g = app.add_group("g1", "Tk widgets") w = tk.Label(g, text="Label1") w.grid() w = tk.Label(g, text="Label2") w.grid() g = app.add_group("g2", "Ttk widgets") ## app = AccordionFrame(g) app.grid(sticky="nsew", padx="5 0") g = app.add_group("g1", "Containers") w = tk.Label(g, text="Label1") w.grid() w = tk.Label(g, text="Label2") w.grid() g = app.add_group("g2", "Control") w = tk.Label(g, text="Label3") w.grid() w = tk.Label(g, text="Label4") w.grid() tk.mainloop() pygubu-0.36.1/src/pygubu/widgets/autoarrangeframe.py000066400000000000000000000042551474524032100225710ustar00rootroot00000000000000# encoding: UTF-8 import tkinter as tk from tkinter import ttk """A frame widget that autoarrange children when is resized. Usefull for frames with several children of same size. """ class AutoArrangeFrame(ttk.Frame): def __init__(self, master=None, **kw): self.__cb = None self.__recurse_check = 0 ttk.Frame.__init__(self, master, **kw) self.bind("", self.__on_configure) def __arrange(self): self.__cb = None self.__recurse_check += 1 self.update_idletasks() self.__recurse_check += -1 if self.__recurse_check != 0: return order = [] sum_width = 0 count = 0 maxc, maxr = self.grid_size() for r in range(0, maxr): for c in range(0, maxc): w = self.grid_slaves(row=r, column=c) if w: order.append(w[0]) width = w[0].winfo_reqwidth() sum_width += width count += 1 avg_width = sum_width / count max_w = self.winfo_width() calc_w = 0 r = c = 0 first_item = True for child in order: calc_w += avg_width if first_item: first_item = False continue if calc_w >= max_w: calc_w = avg_width c = 0 r = r + 1 else: c = c + 1 info = child.grid_info() oldr, oldc = int(info["row"]), int(info["column"]) if oldr != r or oldc != c: child.grid_remove() child.grid(row=r, column=c) def __on_configure(self, event): if self.__cb is None: self.__cb = self.after_idle(self.__arrange) if __name__ == "__main__": import random root = tk.Tk() a = AutoArrangeFrame(root) for idx in range(1, 20): rand = random.randrange(0, 20) txt = str(idx) + "_" * rand b = ttk.Button(a, text=txt, style="Toolbutton") b.grid() a.grid(sticky="nsew") root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) tk.mainloop() pygubu-0.36.1/src/pygubu/widgets/calendarframe.py000066400000000000000000000453271474524032100220370ustar00rootroot00000000000000# encoding: utf-8 __all__ = ["CalendarFrame"] import calendar import locale import tkinter as tk import tkinter.ttk as ttk import math imgp_data = ( "R0lGODlhDAAMAIABAAAAAP///yH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEK" + "AAEALAAAAAAMAAwAAAIVjI+JoMsdgIRyqmoTfrfCmDWh+DUFADs=" ) imgn_data = ( "R0lGODlhDAAMAIABAAAAAP///yH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEK" + "AAEALAAAAAAMAAwAAAIUjI8ZoAnczINtUmdrVpu/uFwcSBYAOw==" ) def get_calendar(locale, fwday): # instantiate proper calendar class if locale is None: return calendar.TextCalendar(fwday) else: return calendar.LocaleTextCalendar(fwday, locale) def i2rc(i, coldim): c = i % coldim f = (i - c) // coldim return (f, c) def rowmajor(rows, cols): size = rows * cols for i in range(0, size): c = i % cols f = (i - c) // cols yield (i, f, c) def matrix_coords(rows, cols, rowh, colw, ox=0, oy=0): "Generate coords for a matrix of rects" for i, f, c in rowmajor(rows, cols): x = ox + c * colw y = oy + f * rowh x1 = x + colw y1 = y + rowh yield (i, x, y, x1, y1) class CalendarFrame(ttk.Frame): """Allows to choose a date in a calendar. WIDGET-SPECIFIC OPTIONS locale, firstweekday, year, month, calendarfg, calendarbg, linewidth, headerfg, headerbg, selectbg, selectfg, markbg, markfg Generates: <> """ datetime = calendar.datetime.datetime timedelta = calendar.datetime.timedelta def __init__(self, master=None, **kw): self.__redraw_cb = None # For redraw callback check. self.__markdays_cb = None # For markdays callback check. sysloc = locale.getlocale(locale.LC_TIME) if None in sysloc: sysloc = None else: sysloc = "{0}.{1}".format(*sysloc) self.__options = options = { "firstweekday": calendar.SUNDAY, "year": self.datetime.now().year, "month": self.datetime.now().month, "locale": sysloc, "calendarfg": "black", "calendarbg": "white", "headerfg": "black", "headerbg": "grey90", "selectbg": "#8000FF", "selectfg": "white", "state": "normal", "markbg": "#DDBBFF", "markfg": "#8000FF", "linewidth": 1, } # remove custom options from kw before initialization ttk.Frame for k, v in options.items(): options[k] = kw.pop(k, v) # Marked days self._marked_days = set() # Calendar variables self._date = self.datetime(options["year"], options["month"], 1) self._cal = get_calendar(options["locale"], options["firstweekday"]) self._weeks = self._cal.monthdayscalendar( options["year"], options["month"] ) self._selection = None # Canvas variables self._rheader = None self._theader = [0 for x in range(0, 7)] self._recmat = [0 for x in rowmajor(6, 7)] self._txtmat = [0 for x in rowmajor(6, 7)] # button bar variables self.__img_prev = None self.__img_next = None self._lmonth = None self._lyear = None ttk.Frame.__init__(self, master, **kw) # build ui self.__build_ui() def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) color_change = False for key in ( "calendarfg", "calendarbg", "headerfg", "headerbg", "selectbg", "selectfg", "markbg", "markfg", "linewidth", ): if key in kw: self.__options[key] = kw.pop(key) color_change = True self._handle_state(kw) calendar_change =self._handle_locale(kw) calendar_change = self._handle_firstweekday(kw) date_change = False for key in ("year", "month"): if key in kw: self.__options[key] = int(kw.pop(key)) date_change = True if date_change: self._reconfigure_date() if color_change or calendar_change or date_change: linewidth = int(self.__options["linewidth"]) for rec_i in self._recmat: self._canvas.itemconfigure( rec_i, width=linewidth, activewidth=linewidth ) self._redraw_calendar() return super().configure(cnf, **kw) def _handle_firstweekday(self, kw): calendar_change = False key = "firstweekday" if key in kw: value = kw.pop(key) self.__options[key] = int(value) calendar_change = True if calendar_change: self._reconfigure_calendar() return calendar_change def _handle_locale(self, kw): calendar_change = False key = "locale" if key in kw: value = locale.normalize(kw.pop(key)) self.__options[key] = value calendar_change = True return calendar_change def _handle_state(self, kw): key = "state" if key in kw: value = kw.pop(key) self.__options[key] = value self._canvas.config(state=value) for w in self._topframe.winfo_children(): if w.winfo_class() == "TButton": w.config(state=value) config = configure def cget(self, key): if key in ( "locale", "firstweekday", "calendarfg", "calendarbg", "headerfg", "headerbg", "selectbg", "selectfg", "markbg", "markfg", "state", "linewidth", ): return self.__options[key] option = "year" if key == option: return self._date.year option = "month" if key == option: return self._date.month return ttk.Frame.cget(self, key) __getitem__ = cget def __build_ui(self): # BUILD UI self.configure(height="200", width="200") self._topframe = ttk.Frame(self) self._topframe.configure(height="200", width="200") self.bpmonth = ttk.Button(self._topframe) self.bpmonth.configure(style="Toolbutton", text="L") self.bpmonth.pack(side="left") self.bnmonth = ttk.Button(self._topframe) self.bnmonth.configure(style="Toolbutton", text="R") self.bnmonth.pack(side="left") self._lmonth = ttk.Label(self._topframe) self._lmonth.configure(anchor="center", text="January") self._lmonth.pack(side="left") self.btoday = ttk.Button(self._topframe) self.btoday.configure(style="Toolbutton", text="Today") self.btoday.pack(expand="true", fill="x", side="left") self._lyear = ttk.Label(self._topframe) self._lyear.configure(text="2020") self._lyear.pack(side="left") self.bpyear = ttk.Button(self._topframe) self.bpyear.configure(style="Toolbutton", text="L") self.bpyear.pack(side="left") self.bnyear = ttk.Button(self._topframe) self.bnyear.configure(style="Toolbutton", text="R") self.bnyear.pack(side="left") self._topframe.pack(anchor="n", fill="x", side="top") self._canvas = tk.Canvas(self) self._canvas.configure( background="#ffffff", borderwidth="0", height=24 * 7, highlightthickness="0", ) self._canvas.configure(width="240") self._canvas.pack( anchor="center", expand="true", fill="both", side="top" ) self.__img_prev = imgp = tk.PhotoImage(data=imgp_data, master=self) self.__img_next = imgn = tk.PhotoImage(data=imgn_data, master=self) # self._lmonth = lmonth # self._lyear = lyear def callback(event=None): return self._change_date("month", -1) self.bpmonth.configure(image=imgp, command=callback) def callback(event=None): return self._change_date("month", 1) self.bnmonth.configure(image=imgn, command=callback) def callback(event=None): return self._change_date("year", -1) self.bpyear.configure(image=imgp, command=callback) def callback(event=None): return self._change_date("year", 1) self.bnyear.configure(image=imgn, command=callback) self.btoday.configure(command=self._go_today) self._canvas.bind("", self._on_canvas_configure) # self._topframe = frame2 # self._canvas = canvas self._draw_calendar(self._canvas) self._canvas.tag_bind("cell", "", self._on_cell_clicked) def _reconfigure_calendar(self): options = self.__options self._date = self.datetime(options["year"], options["month"], 1) self._cal = get_calendar(options["locale"], options["firstweekday"]) self._weeks = self._cal.monthdayscalendar( options["year"], options["month"] ) def _reconfigure_date(self): options = self.__options self._date = self.datetime(options["year"], options["month"], 1) self._weeks = self._cal.monthdayscalendar( options["year"], options["month"] ) self._selection = None # Forget current selected day self._redraw_calendar() def _go_today(self, event=None): options = self.__options today = self.datetime.now() options["year"] = today.year options["month"] = today.month self._reconfigure_date() def _change_date(self, element, direction): options = self.__options newdate = None if element == "month": if direction == -1: newdate = self._date - self.timedelta(days=1) else: year, month = self._date.year, self._date.month days = calendar.monthrange(year, month)[1] + 1 newdate = self._date + self.timedelta(days=days) elif element == "year": year = self._date.year + direction newdate = self.datetime(year, self._date.month, 1) options["year"] = newdate.year options["month"] = newdate.month self._reconfigure_date() def _on_cell_clicked(self, event=None): item = self._canvas.find_withtag("current") idx = self._recmat.index(item[0]) weeks = self._weeks day = 0 f, c = i2rc(idx, 7) if f < len(weeks): day = weeks[f][c] if day != 0: self.select_day(day, self._date.month, self._date.year) def _mark_days(self): options = self.__options year = self._date.year month = self._date.month weeks = self._weeks now = self.datetime.now() today = (now.year, now.month, now.day) for i, f, c in rowmajor(6, 7): day = 0 clear = True if f < len(weeks): day = weeks[f][c] key = (year, month, day) if ( (None, None, day) in self._marked_days or (None, month, day) in self._marked_days or key in self._marked_days ): marked = True self._canvas.itemconfigure( self._recmat[i], fill=options["markbg"], outline=options["markbg"], ) self._canvas.itemconfigure( self._txtmat[i], fill=options["markfg"] ) clear = False else: marked = False if key == today: self._canvas.itemconfigure( self._recmat[i], fill=options["selectfg"] if not marked else None, outline=options["selectbg"], ) self._canvas.itemconfigure( self._txtmat[i], fill=options["selectbg"] ) clear = False if key == self._selection: self._canvas.itemconfigure( self._recmat[i], fill=options["selectbg"], outline=options["selectbg"] if not marked else None, ) self._canvas.itemconfigure( self._txtmat[i], fill=options["selectfg"] ) clear = False if clear: # clear day self._canvas.itemconfigure( self._recmat[i], fill=options["calendarbg"], outline=options["calendarbg"], ) self._canvas.itemconfigure( self._txtmat[i], fill=options["calendarfg"] ) self.__markdays_cb = None def _call_mark_days(self): if self.__markdays_cb is None: self.__markdays_cb = self.after_idle(self._mark_days) def _remark_date(self, day, month=None, year=None, highlight=True): key = (year, month, day) if highlight: self._marked_days.add(key) else: if key in self._marked_days: self._marked_days.remove(key) self._call_mark_days() def mark_day(self, day, month=None, year=None): """Marks the specified month day with a visual marker (typically by making the number bold). If only day is specified and the calendar month and year are changed, the marked day remain marked. You can be more specific setting month and year parameters. """ self._remark_date(day, month, year, highlight=True) def unmark_day(self, day, month=None, year=None): self._remark_date(day, month, year, highlight=False) def clear_marks(self): """Clears all marked days""" self._marked_days.clear() self._call_mark_days() def _draw_calendar(self, canvas, redraw=False): """Draws calendar.""" options = self.__options linewidth = int(options["linewidth"]) # Update labels: name = self._cal.formatmonthname( self._date.year, self._date.month, 0, withyear=False ) self._lmonth.configure(text=name.title()) self._lyear.configure(text=str(self._date.year)) # Update calendar ch = canvas.winfo_height() cw = canvas.winfo_width() rowh = ch / 7.0 colw = cw / 7.0 # Header background if self._rheader is None: self._rheader = canvas.create_rectangle( 0, 0, cw, rowh, width=0, fill=options["headerbg"] ) else: canvas.itemconfigure(self._rheader, fill=options["headerbg"]) canvas.coords(self._rheader, 0, 0, cw, rowh) # Header text ox = 0 oy = rowh / 2.0 - 2 coffset = colw / 2.0 - ((linewidth / 2) - 1) cols = self._cal.formatweekheader(3).split() for i in range(0, 7): x = ox + i * colw + coffset if redraw: item = self._theader[i] canvas.coords(item, x, oy) canvas.itemconfigure( item, text=cols[i], fill=options["headerfg"] ) else: self._theader[i] = canvas.create_text( x, oy, text=cols[i], fill=options["headerbg"] ) # background matrix oy = rowh ox = linewidth // 2 for i, x, y, x1, y1 in matrix_coords(6, 7, rowh, colw, ox, oy): x1 -= linewidth y1 -= linewidth if redraw: rec = self._recmat[i] canvas.coords(rec, x, y, x1, y1) canvas.itemconfigure( rec, fill=options["calendarbg"], activeoutline=options["selectbg"], ) else: rec = canvas.create_rectangle( x, y, x1, y1, width=linewidth, fill=options["calendarbg"], outline=options["calendarbg"], activeoutline=options["selectbg"], activewidth=linewidth, tags="cell", ) self._recmat[i] = rec # text matrix weeks = self._weeks # xoffset = colw / 2.0 yoffset = rowh / 2.0 - (math.floor(linewidth / 2)) oy = rowh ox = linewidth // 2 for i, x, y, x1, y1 in matrix_coords(6, 7, rowh, colw, ox, oy): x += coffset y += yoffset # day text txt = "" f, c = i2rc(i, 7) if f < len(weeks): day = weeks[f][c] txt = "{0}".format(day) if day != 0 else "" if redraw: item = self._txtmat[i] canvas.coords(item, x, y) canvas.itemconfigure(item, text=txt) else: self._txtmat[i] = canvas.create_text( x, y, text=txt, state=tk.DISABLED ) # Mark days self._mark_days() def _redraw_calendar(self): self._draw_calendar(self._canvas, redraw=True) # after idle callback trick self.__redraw_cb = None def _on_canvas_configure(self, event=None): if self.__redraw_cb is None: self.__redraw_cb = self.after_idle(self._redraw_calendar) @property def selection(self): """Return a datetime representing the current selected date.""" if not self._selection: return None year, month = self._date.year, self._date.month return self.datetime(year, month, self._selection[2]) def select_day(self, day, month=None, year=None): options = self.__options options["month"] = month = self._date.month if month is None else month options["year"] = year = self._date.year if year is None else year self._reconfigure_date() self._selection = (year, month, day) self._call_mark_days() self.event_generate("<>") if __name__ == "__main__": import random locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) root = tk.Tk() c = CalendarFrame(root) c.grid() # select day c.select_day(1) root.rowconfigure(0, weight=1) root.columnconfigure(0, weight=1) root.mainloop() pygubu-0.36.1/src/pygubu/widgets/colorinput.py000066400000000000000000000071641474524032100214460ustar00rootroot00000000000000#!/usr/bin/python3 import tkinter as tk import tkinter.colorchooser import tkinter.ttk as ttk from pygubu.utils.widget import WidgetConfigureMixin from pygubu.widgets.colorinputui import ColorInputUI # # Manual user code # class ColorInput(WidgetConfigureMixin, ColorInputUI): BGCOLOR = "" EVENT_COLOR_CHANGED = "<>" KEY_PRESS_MS = 850 def __init__(self, master=None, **kw): entry_var = kw.pop("textvariable", None) super().__init__(master, **kw) entry_var = tk.StringVar(value="") if entry_var is None else entry_var self._entry_var = entry_var self._entry.configure(textvariable=self._entry_var) self._color = "" self._kp_cb = None style = ttk.Style(master) cls = type(self) cls.BGCOLOR = style.lookup("TFrame", "background") def _widget_cget(self, option): if option == "value": return self._color if option == "textvariable": return self._entry_var return super()._widget_cget(option) def _configure_get(self, option): if option in ("value", "textvariable"): return self._widget_cget(option) return super()._configure_get(option) def _configure_set(self, **kw): key = "textvariable" if key in kw: value = kw[key] self._entry_var = value self._entry.configure(textvariable=value) key = "value" if key in kw: value = kw.pop(key) self._set_value(value) key = "state" if key in kw: state = kw.pop(key) if state == "readonly": self._entry.configure(state=state) self._button.configure(state="disabled") else: self._entry.configure(state=state) self._button.configure(state=state) return super()._configure_set(**kw) def _set_value(self, txtcolor): self._color = txtcolor self._entry_var.set(txtcolor) self._show_color(txtcolor) def on_focusout(self, event=None): self.validate_change(self._entry_var.get()) def on_keypress_after(self): self.validate_change(self._entry_var.get()) def on_keypress(self, event=None): if self._kp_cb is not None: self.after_cancel(self._kp_cb) self._kp_cb = self.after(self.KEY_PRESS_MS, self.on_keypress_after) def is_color(self, txtcolor): try: self.winfo_rgb(txtcolor) return True except tk.TclError: pass return False def on_picker_clicked(self): current = self._entry_var.get() current = current if self.is_color(current) else None txtcolor = None _, txtcolor = tk.colorchooser.askcolor(current, parent=self) if txtcolor is not None: self._set_value(txtcolor) self.event_generate(self.EVENT_COLOR_CHANGED) def validate_change(self, newcolor): if self.is_color(newcolor) or newcolor == "": self._set_value(newcolor) self.event_generate(self.EVENT_COLOR_CHANGED) def _show_color(self, newcolor): newcolor = newcolor if newcolor else self.BGCOLOR self._frame.configure(background=newcolor) if __name__ == "__main__": root = tk.Tk() widget = ColorInput(root) widget.pack(expand=True, fill="both") def on_color_changed(event): value = event.widget.cget("value") print(f"Color changed to: {value}") widget.bind(ColorInput.EVENT_COLOR_CHANGED, on_color_changed) widget.configure(value="brown") root.mainloop() pygubu-0.36.1/src/pygubu/widgets/colorinputui.py000066400000000000000000000026051474524032100217770ustar00rootroot00000000000000#!/usr/bin/python3 import tkinter as tk import tkinter.ttk as ttk # # Base class definition # class ColorInputUI(ttk.Frame): def __init__(self, master=None, **kw): super().__init__(master, **kw) self._frame = tk.Frame(self, name="_frame") self._frame.configure(borderwidth=0, height=10, relief="flat", width=24) self._frame.pack(fill="y", side="left") self._entry = ttk.Entry(self, name="_entry") self._entry.configure(validate="all") self._entry.pack(expand=True, fill="both", padx=2, side="left") self._entry.bind("", self.on_focusout, add="") self._entry.bind("", self.on_keypress, add="") self._button = ttk.Button(self, name="_button") self._button.configure( compound="center", style="Toolbutton", takefocus=True, text="…", width=-2, ) self._button.pack(fill="both", side="left") self._button.configure(command=self.on_picker_clicked) self.configure(height=25, width=100) # self.pack(side="top") def on_focusout(self, event=None): pass def on_keypress(self, event=None): pass def on_picker_clicked(self): pass if __name__ == "__main__": root = tk.Tk() widget = ColorInputUI(root) widget.pack(expand=True, fill="both") root.mainloop() pygubu-0.36.1/src/pygubu/widgets/combobox.py000066400000000000000000000162441474524032100210570ustar00rootroot00000000000000# encoding: utf-8 import json import tkinter as tk import tkinter.ttk as ttk from collections import OrderedDict class Combobox(ttk.Combobox): """ A ttk.Combobox that accepts a list of (key, label) pair of options. """ def __init__(self, *args, keyvariable=None, textvariable=None, **kw): self._keyvarcb = None self._txtvarcb = None self._choices = OrderedDict() self._keyvar = tk.StringVar() if keyvariable is None else keyvariable self._txtvar = tk.StringVar() if textvariable is None else textvariable key = "values" if key in kw: values = kw[key] self.choices = values values = self.__choices2tkvalues(self.choices) kw[key] = values # only normal/disabled supported # normal is converted to readonly key = "state" state = kw.get(key, "readonly") if state not in ("readonly", "disabled"): state = "readonly" kw[key] = state kw["textvariable"] = self._txtvar super().__init__(*args, **kw) self.__config_keyvar(self._keyvar) self.__config_txtvar(self._txtvar) def __config_keyvar(self, var): if var is None: return def on_keyvar_changed(varname, elementname, mode): nvalue = self._keyvar.get() if nvalue in self._choices: self.current(nvalue) keyvar = self._keyvar if self._keyvarcb is not None: keyvar.trace_remove("write", self._keyvarcb) self._keyvar = var self._keyvarcb = var.trace_add("write", on_keyvar_changed) def __config_txtvar(self, var): keyvar = self._keyvar if var is None or keyvar is None: return def on_txtvar_changed(varname, elementname, mode): ckey = self.current() if ckey is not None: self._keyvar.set(ckey) txtvar = self._txtvar if txtvar and self._txtvarcb is not None: txtvar.trace_remove("write", self._txtvarcb) self._txtvar = var self._txtvarcb = var.trace_add("write", on_txtvar_changed) def _delayed_clear_vars(self): # clear variables: self._clear_tkvar(self._keyvar) self._clear_tkvar(self._txtvar) def _clear_tkvar(self, variable): if isinstance(variable, tk.StringVar): variable.set("") def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) key = "values" if key in kw: self.choices = kw[key] kw[key] = self.__choices2tkvalues(self.choices) # clear vars after setting values self.after_idle(self._delayed_clear_vars) key = "keyvariable" if key in kw: value = kw.pop(key) self.__config_keyvar(value) key = "textvariable" if key in kw: value = kw[key] self.__config_txtvar(value) # only readonly and disabled supported key = "state" if key in kw: state = kw.get(key, "readonly") if state not in ("readonly", "disabled"): state = "readonly" kw[key] = state return super().configure(cnf, **kw) config = configure def cget(self, key): option = "values" if key == option: return json.dumps(self.choices) option = "keyvariable" if key == option: return self._keyvar option = "textvariable" if key == option: return self._txtvar return super().cget(key) def __obj2choices(self, values): """ - json list of key, value pairs: Example: [["A", "Option 1 Label"], ["B", "Option 2 Label"]] - space separated string with a list of options: Example: "Option1 Opt2 Opt3" will be converted to a key, value pair of the following form: (("Option1", "Option1), ("Opt2", "Opt2"), ("Opt3", "Opt3")) - an iterable of key, value pairs """ choices = values # choices from string if isinstance(values, type("")): obj = None # try json string try: obj = json.loads(values) except ValueError: obj = values if isinstance(obj, type([])): choices = obj else: choices = obj.split() return choices def __choices2tkvalues(self, choices): """choices: iterable of key, value pairs""" values = [] for k, v in choices: values.append(v) return values @property def choices(self): return tuple(self._choices.items()) @choices.setter def choices(self, values): values = self.__obj2choices(values) self._choices.clear() for idx, v in enumerate(values): key = value = v if not isinstance(v, type("")) and len(v) == 2: key, value = v self._choices[key] = value def current(self, key=None): """Get the index of the current selection. If key is given, return the index of the key. """ idx = super().current(None) index = -1 ckey = None # find the key of the current selection, # or if a key was given, its index for i, k in enumerate(self._choices.keys()): if key is None and idx == i: ckey = k break elif key == k: index = i break if key is None: # return current key return ckey return super().current(index) def set(self, key): value = self._choices[key] return super().set(value) def get(self): return self.current() if __name__ == "__main__": def show_selection(event): cbox = event.widget print("values:", cbox.cget("values")) print("current:", cbox.current()) var = cbox.cget("keyvariable") print("keyvar: ", var.get()) var = cbox.cget("textvariable") print("textvar: ", var.get()) root = tk.Tk() txtvalues = '[["DNI", "Doc.Nac.Id."], ["LE", "Libreta Enrolamientox"]]' c = Combobox(root, values=txtvalues) c.bind("<>", show_selection) c.configure(values=txtvalues) c.set("LE") c.grid() values = (("tres", 3), (4, "cuatro"), (6, "seis")) c = Combobox(root, values=values) c.bind("<>", show_selection) c.set(6) c.grid() var = tk.StringVar() var.set("C") e = ttk.Entry(root, textvariable=var) e.grid() var2 = tk.StringVar() e2 = ttk.Entry(root, textvariable=var2) e2.grid() values = ( ("", "Seleccione Opción"), ("A", "A label"), ("B", "B label"), ("C", "C label"), ) c = Combobox(root, textvariable=var) c.bind("<>", show_selection) c.configure(values=values) c.set("") c.grid() c.configure(keyvariable=var2) var2.set("A") root.rowconfigure(0, weight=1) root.columnconfigure(0, weight=1) root.mainloop() pygubu-0.36.1/src/pygubu/widgets/dialog.py000066400000000000000000000110741474524032100205020ustar00rootroot00000000000000# encoding: UTF-8 import tkinter as tk import tkinter.ttk as ttk class Dialog(object): """ Virtual events: <> """ SEQ_DIALOG_CLOSE = "<>" def __init__(self, parent, modal=False): self.parent = parent self.is_modal = modal self.show_centered = True self.running_modal = False self.toplevel = tk.Toplevel(parent) self.toplevel.withdraw() self.toplevel.protocol("WM_DELETE_WINDOW", self._on_wm_delete_window) self.bind(self.SEQ_DIALOG_CLOSE, self._default_close_action) self._init_before() self._create_ui() self._init_after() # self.frame.pack(fill='both', expand=True) def _init_before(self): pass def _create_ui(self): pass def _init_after(self): pass def emit_close_event(self): self.toplevel.event_generate(self.SEQ_DIALOG_CLOSE) def set_modal(self, modal): self.is_modal = modal def center_window(self): """Center a window on its parent or screen.""" window = self.toplevel height = window.winfo_height() width = window.winfo_width() parent = self.parent if parent: x_coord = int( parent.winfo_x() + (parent.winfo_width() / 2 - width / 2) ) y_coord = int( parent.winfo_y() + (parent.winfo_height() / 2 - height / 2) ) else: x_coord = int(window.winfo_screenwidth() / 2 - width / 2) y_coord = int(window.winfo_screenheight() / 2 - height / 2) geom = "{0}x{1}+{2}+{3}".format(width, height, x_coord, y_coord) window.geometry(geom) def run(self): self.toplevel.transient(self.parent) self.toplevel.deiconify() self.toplevel.wait_visibility() if self.show_centered: self.center_window() initial_focus = self.toplevel.focus_lastfor() if initial_focus: initial_focus.focus_set() if self.is_modal: self.running_modal = True self.toplevel.grab_set() def destroy(self): if self.toplevel: self.toplevel.destroy() self.toplevel = None def _on_wm_delete_window(self): self.emit_close_event() def _default_close_action(self, dialog): self.close() def close(self): if self.running_modal: self.toplevel.grab_release() self.running_modal = False self.toplevel.withdraw() self.parent.focus_set() def show(self): if self.toplevel: self.toplevel.deiconify() def set_title(self, title): """Sets the dialog title""" if self.toplevel: self.toplevel.title(title) # # interface to toplevel methods used by gui builder # def configure(self, cnf=None, **kw): if "modal" in kw: value = kw.pop("modal") self.set_modal(tk.getboolean(value)) self.toplevel.configure(cnf, **kw) config = configure def cget(self, key): if key == "modal": return self.is_modal return self.toplevel.cget(key) __getitem__ = cget def __setitem__(self, key, value): self.configure({key: value}) def bind(self, sequence=None, func=None, add=None): def dialog_cb(event, dialog=self): func(dialog) return self.toplevel.bind(sequence, dialog_cb, add) # def unbind(self, sequence, funcid=None): # pass if __name__ == "__main__": class TestDialog(Dialog): def _create_ui(self): f = ttk.Frame(self.toplevel) f.pack(expand=True, fill=tk.BOTH) label = ttk.Label(f, text="TestDialog Class") label.pack() entry = ttk.Entry(f) entry.pack() app = tk.Tk() dialog = None def show_dialog(): global dialog if dialog is None: dialog = TestDialog(app) dialog.run() else: dialog.show() def custom_callback(dialog): print("Custom callback") dialog.close() def show_modal_dialog(): dialog = TestDialog(app) dialog.set_title("Modal dialog") dialog.set_modal(True) dialog.bind("<>", custom_callback) print("before run") dialog.run() print("after run") btn = tk.Button(app, text="show dialog", command=show_dialog) btn.pack() btn = tk.Button(app, text="show modal dialog", command=show_modal_dialog) btn.pack() app.mainloop() pygubu-0.36.1/src/pygubu/widgets/dockfw/000077500000000000000000000000001474524032100201435ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/widgets/dockfw/__init__.py000066400000000000000000000000001474524032100222420ustar00rootroot00000000000000pygubu-0.36.1/src/pygubu/widgets/dockfw/dockframe.py000066400000000000000000000356541474524032100224650ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk import logging from pygubu.widgets.dockfw.dockwidget import ( DockWidgetBase, DockPane, DockWidget, ) from pygubu.widgets.dockfw.framework import ( DockingFramework, IDockFrame, ) logger = logging.getLogger(__name__) class DockFrame(DockWidgetBase, IDockFrame): """A Dock Frame, where user can configure the layout of the frames within a window. Based on https://wiki.tcl-lang.org/page/Docking+framework Generates <> event when the user changes the layout. """ EVENT_LAYOUT_CHANGED = "<>" def __init__(self, *args, **kw): super().__init__(*args, **kw) self.dock_widgets: dict[str, DockWidget] = {} self._main_pane = None self._emit_cbid = None def indicators_visible(self, visible: bool): if self._main_pane: self._main_pane.indicators_visible(visible) @property def main_pane(self): return self._main_pane def _add_pane_to_pane( self, topane: DockPane, newpane: DockPane, position=tk.END, **pane_kw ): # remove newpane as child of current pane if any newpane.detach_from_parent() topane.panedw.insert(position, newpane, **pane_kw) newpane.parent_pane = topane topane.add_dwchild(newpane) def _add_widget_to_pane( self, topane, widget, grouped=False, position=tk.END, weight=1 ): nb = None if grouped: panes = topane.panedw.panes() if panes: nb = self.nametowidget(panes[-1]) if nb is None: nb = ttk.Notebook(self, padding=0) DockingFramework.init_binding(nb) # topane.panedw.add(nb, weight=1) topane.panedw.insert(position, nb, weight=weight) nb.add(widget, text=widget.title) widget.tkraise() widget.detach_from_parent() topane.add_dwchild(widget) # widget.parent_pane = topane widget.noteb = nb self.dock_widgets[widget.uid] = widget def new_pane(self, *, main_pane=False, **pane_kw) -> DockPane: """Creates new DockPane locked to this DockFrame.""" pane = DockPane(self, maindock=self, **pane_kw) if main_pane: self.set_main_pane(pane) return pane def set_main_pane(self, pane): pane.pack(in_=self.fcenter, expand=True, fill=tk.BOTH) self._main_pane = pane def new_widget(self, **widget_kw) -> DockWidget: """Creates new DockWidget locked to this DockFrame.""" widget = DockWidget(self, maindock=self, **widget_kw) return widget def _add_widget_to_group( self, tgroup, swidget: DockWidget, position=tk.END ): nb: ttk.Notebook = tgroup.noteb nb.insert(position, swidget, text=swidget.title) nb.select(position) swidget.noteb = nb if tgroup != swidget: swidget.detach_from_parent() tgroup.parent_pane.add_dwchild(swidget) # swidget.parent_pane = tgroup.parent_pane swidget.tkraise() self.dock_widgets[swidget.uid] = swidget def _move_into_widget_group(self, twidget, swidget, position): noteb = swidget.noteb pane = swidget.parent_pane self._add_widget_to_group(twidget, swidget, position) self._clear_notebook(noteb) self._clear_pane(pane) self._emit_layout_changed() def _move_into_pane_side(self, swidget, tpane, side, position=None): logger.debug("Move to into pane side. position %s", position) move_in_same_pane = swidget.parent_pane == tpane simple_sides = "ns" if tpane.orient == tk.VERTICAL else "we" noteb = swidget.noteb pane = swidget.parent_pane if position is None: position = 0 if side in "nw" else tk.END if move_in_same_pane: if side in simple_sides: # if widget is in a group, must detach and re-insert widget if swidget.is_grouped: self._add_widget_to_pane(tpane, swidget, position=position) elif tpane.count > 1: # pane has more than the moving widget # reposition widget in same pane logger.debug( "Reposition widget in same pane. position %s", position ) tpane.panedw.insert(position, noteb) else: logger.debug("No need to move") else: self._move_into_pane_side_complex( swidget, tpane, side, position ) else: if side in simple_sides: self._add_widget_to_pane(tpane, swidget, position=position) else: self._move_into_pane_side_complex( swidget, tpane, side, position ) self._clear_notebook(noteb) self._clear_pane(pane) self._emit_layout_changed() def _move_into_pane_side_complex(self, swidget, tpane, side, position=None): move_to_new_pane = True # check if parent pane is in sync parent_pane = tpane.parent_pane if parent_pane: in_sync = side in ( "ns" if parent_pane.orient == tk.VERTICAL else "we" ) if in_sync: logger.debug("Parent pane in sync, adding into parent.") index = self._get_position_in_pane(tpane) logger.debug("Pane position: %s side: %s", index, side) if side not in "nw": index += 1 index = tk.END if index >= parent_pane.count else index logger.debug("final index: %s", index) self._add_widget_to_pane(parent_pane, swidget, position=index) move_to_new_pane = False if move_to_new_pane: logger.debug("Moving to new pane.") self._move_to_new_pane(swidget, tpane, side) def _move_to_new_pane(self, swidget: DockWidget, tpane: DockPane, side): """Move DockWidget 'swidget' to a new pane inside target pane 'tpane'.""" logger.debug("Move to new pane.") new_orient = ( tk.HORIZONTAL if tpane.orient == tk.VERTICAL else tk.VERTICAL ) main_pane = tpane == self._main_pane new_pane = self.new_pane(main_pane=main_pane, orient=new_orient) parent_pane = tpane.parent_pane self._add_pane_to_pane(new_pane, tpane) if parent_pane: self._add_pane_to_pane(parent_pane, new_pane) new_pos = 0 if side in "nw" else tk.END self._add_widget_to_pane(new_pane, swidget, position=new_pos) self._raise_panes(new_pane) def _move_to_widget_side( self, swidget: DockWidget, twidget: DockWidget, side ): """Move 'swidget' to one side of 'twidget'.""" logger.debug("Move to widget side: %s", side) if swidget == twidget: logger.debug("No move needed.") return tpane = twidget.parent_pane # move_in_same_pane = swidget.parent_pane == tpane simple_sides = "ns" if tpane.orient == tk.VERTICAL else "we" if side in simple_sides: index = self._get_position_in_pane(twidget) if side not in "nw": index += 1 index = tk.END if index >= tpane.count else index self._move_into_pane_side(swidget, tpane, side, position=index) else: new_orient = ( tk.HORIZONTAL if tpane.orient == tk.VERTICAL else tk.VERTICAL ) self._replace_widget_with_pane(twidget, new_orient) self._move_into_pane_side(swidget, twidget.parent_pane, side) def _get_position_in_pane(self, widget): index = tk.END if isinstance(widget, DockWidget): pane = widget.parent_pane if pane: panes: tuple = pane.panedw.panes() index = panes.index(str(widget.noteb)) elif isinstance(widget, DockPane): pane = widget.parent_pane if pane: panes: tuple = pane.panedw.panes() index = panes.index(str(widget)) return index def _replace_widget_with_pane(self, widget: DockWidget, orient): """Creates new pane, move widget inside it and puts the pane in the current position of widget. """ parent_pane = widget.parent_pane widget_pos = self._get_position_in_pane(widget) new_pane = self.new_pane(orient=orient) self._add_pane_to_pane(parent_pane, new_pane, position=widget_pos) side = "n" if orient == tk.VERTICAL else "w" self._move_into_pane_side(widget, new_pane, side, position=tk.END) def _raise_panes(self, widget): if isinstance(widget, DockPane): widget.tkraise() for w in widget.panedw.panes(): self._raise_panes(widget.nametowidget(w)) elif isinstance(widget, ttk.Notebook): widget.tkraise() for tab in widget.tabs(): self._raise_panes(widget.nametowidget(tab)) elif not isinstance(widget, tk.Canvas): widget.tkraise() def _clear_notebook(self, nb: ttk.Notebook): """Destroy notebook if does not have children.""" if not nb.tabs(): nb.destroy() def _clear_pane(self, pane: DockPane): """If pane does not have children, destroys it. Otherwise tries to simplificate it. """ if pane.count == 0: pane.detach_from_parent() pane.destroy() return logger.debug("Simplify pane for: %s parent: %s", pane, pane.parent_pane) if pane.parent_pane: parent = pane.parent_pane if parent.orient == pane.orient: # FIXME: Pane can be simplified, try moving content to parent pane.? logger.debug( "Pane can be simplified, try moving content to parent pane." ) else: logger.debug( "Pane orients differ: %s %s", parent.orient, pane.orient ) if pane.count == 1: logger.debug( "Pane can be simplified, pane has only one child." ) widget = pane.nametowidget(pane.panedw.panes()[0]) if isinstance(widget, ttk.Notebook): tabs = widget.tabs() if len(tabs) > 1: # FIXME: simplify a pane with grouped widgets. logger.warn("Grouped widgets not managed yet.") logger.warn("Aborting simplification.") return widget = pane.nametowidget(widget.tabs()[0]) side = "n" if parent.orient == tk.VERTICAL else "w" pos = self._get_position_in_pane(pane) logger.debug("Trying to move: %s", widget) if isinstance(widget, DockPane): self._add_pane_to_pane(parent, widget, position=pos) else: self._move_into_pane_side( widget, parent, side, position=pos ) def save_layout(self): """Return a dictionary with information of current layout.""" config = {} if self.main_pane: layout = {} self._create_layout(self.main_pane, layout) config = { "ver": 1, "main": self.main_pane.uid, "layout": layout, "dwl": [dw_uid for dw_uid in self.dock_widgets], } return config def load_layout(self, config: dict): """Restore layout from previusly saved config dictionary.""" if not self._layout_config_valid(config): raise ValueError("Invalid layout configuration.") self._remove_layout() main_uid = config["main"] pane = self._build_layout(main_uid, config["layout"]) self.set_main_pane(pane) def _emit_layout_changed(self): """Schedule a notify of layout changed.""" if self._emit_cbid is not None: self.after_cancel(self._emit_cbid) self._emit_cbid = self.after(800, self._notify_layout_changed) def _notify_layout_changed(self): self.event_generate(self.EVENT_LAYOUT_CHANGED) self._emit_cbid = None def _layout_config_valid(self, config): valid = True # validate "version" if config.get("ver", 0) != 1: valid = False # validate widgets exists if valid and "dwl" in config: for uid in config["dwl"]: if uid not in self.dock_widgets: valid = False break else: valid = False return valid def _create_layout(self, pane: DockPane, layout: dict): conf = {"orient": pane.orient} children = [] layout[pane.uid] = { "cnf": conf, "ch": children, } # if len(pane.panedw.panes()) > 1: # print(">>>", pane.uid), # print(">>>", pane.panedw.sashpos(0)) for child in pane.sorted_dw_children: if isinstance(child, DockPane): children.append(child.uid) self._create_layout(child, layout) else: dw = { "uid": child.uid, "grouped": child.is_grouped, "weight": int(child.dw_options.get("weight", 1)), } children.append(dw) def _build_layout(self, pane_uid, layout): pane = self.new_pane(uid=pane_uid, **layout[pane_uid]["cnf"]) for widget_or_pane in layout[pane_uid]["ch"]: if isinstance(widget_or_pane, dict): child_uid = widget_or_pane["uid"] grouped = widget_or_pane["grouped"] weight = widget_or_pane["weight"] if child_uid in self.dock_widgets: pane.add_widget( self.dock_widgets[child_uid], grouped=grouped, weight=weight, ) else: child_pane = self._build_layout(widget_or_pane, layout) pane.add_pane(child_pane) return pane def _remove_layout(self): """Remove all panes and set main pane to None""" def _recursive_remove(pane): for child in pane.dw_children: if isinstance(child, DockPane): _recursive_remove(child) else: child.detach_from_parent() pane.detach_from_parent() pane.destroy() if self.main_pane: _recursive_remove(self.main_pane) self._main_pane = None pygubu-0.36.1/src/pygubu/widgets/dockfw/dockwidget.py000066400000000000000000000067451474524032100226550ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk from collections import defaultdict from pygubu.widgets.dockfw.slotframe import SlotFrame from pygubu.widgets.dockfw.framework import ( DockingFramework, IDockWidget, IDockFrame, IDockPane, ) class DockWidgetBase(SlotFrame): def __init__(self, *args, maindock=None, uid=None, **kw): super().__init__(*args, **kw) self.maindock = maindock self.uid = uid self.parent_pane: DockPane = None self.dw_options = {} def child_master(self): return self.fcenter def detach_from_parent(self): """Remove this widget as child of parent_pane.""" if self.parent_pane: if self in self.parent_pane.dw_children: self.parent_pane.dw_children.remove(self) # the panedwindow of parent could be deleted exists = self.tk.call( "winfo", "exists", str(self.parent_pane.panedw) ) if exists: if self in self.parent_pane.panedw.panes(): self.parent_pane.panedw.remove(self) self.parent_pane = None class DockPane(DockWidgetBase, IDockPane): pcount = 0 def __init__(self, *args, orient=tk.HORIZONTAL, **kw): uid = kw.get("uid", None) if uid is None: type(self).pcount += 1 kw["uid"] = f"pane{self.pcount}" super().__init__(*args, **kw) self.panedw = ttk.Panedwindow(self.fcenter, orient=orient) self.panedw.pack(expand=True, fill=tk.BOTH) self.dw_children = [] @property def orient(self): return str(self.panedw.cget("orient")) @property def count(self): return len(self.panedw.panes()) @property def sorted_dw_children(self): wsorted = [] for tkpane in self.panedw.panes(): tkpane = self.nametowidget(tkpane) if isinstance(tkpane, ttk.Notebook): for tab in tkpane.tabs(): tab = self.nametowidget(tab) if tab in self.dw_children: wsorted.append(tab) else: if tkpane in self.dw_children: wsorted.append(tkpane) return wsorted def add_pane(self, pane: "DockPane", **pane_kw): pane.dw_options.update(pane_kw) self.maindock._add_pane_to_pane(self, pane, **pane_kw) def add_widget(self, widget: "DockWidget", grouped=False, weight=1): widget.dw_options["weight"] = weight self.maindock._add_widget_to_pane( self, widget, grouped=grouped, weight=weight ) def add_dwchild(self, dw: DockWidgetBase): """Add DockPane or DockWidget as child.""" dw.parent_pane = self self.dw_children.append(dw) class DockWidget(DockWidgetBase, IDockWidget): def __init__(self, *args, **kw): super().__init__(*args, **kw) self.noteb: ttk.Notebook = None self._title = None @property def is_grouped(self): grouped = False if self.noteb is not None: if len(self.noteb.tabs()) > 1: grouped = True return grouped @property def title(self): return self._title if self._title is not None else self.uid def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) key = "title" if key in kw: self._title = kw.pop(key) return super().configure(cnf, **kw) pygubu-0.36.1/src/pygubu/widgets/dockfw/framework.py000066400000000000000000000214311474524032100225130ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk import logging from pygubu.utils.widget import iter_to_toplevel from pygubu.widgets.dockfw.slotframe import SlotIndicator logger = logging.getLogger(__name__) class IDockFrame: ... class IDockPane: ... class IDockWidget: ... def tab_below_mouse(noteb: ttk.Notebook, rootx: int, rooty: int): # Is calculated this way because mouse motion events # return negative values with respect to where the drag started. x = rootx - noteb.winfo_rootx() y = rooty - noteb.winfo_rooty() tab_index = noteb.tk.call(noteb._w, "identify", "tab", x, y) if not isinstance(tab_index, int): tab_index = None return tab_index class DockingFramework: curr_dock = None curr_dpane = None curr_dwidget = None bmouse_dock = None bmouse_dpane = None bmouse_dwidget = None source_dwidget = None source_tab_clicked = None moving = False indicator_active = None cursor_default = "arrow" cursor_moving = "hand1" cursor_tab_target = "sb_down_arrow" cursor_showing = None indicator_bg_color = "#989cec" @classmethod def init_binding(cls, widget: tk.Widget): widget.bind("", cls.drag_start, add=True) widget.bind("", cls.drag_motion, add=True) widget.bind("", cls.drag_end, add=True) @classmethod def get_targets_below_mouse(cls, event: tk.Event): dock = None pane = None widget = None widget_below = event.widget.winfo_containing(event.x_root, event.y_root) for w in iter_to_toplevel(widget_below): if isinstance(w, IDockFrame) and dock is None: dock = w break if isinstance(w, IDockPane) and pane is None: pane = w if isinstance(w, IDockWidget) and widget is None: widget = w if isinstance(w, ttk.Notebook) and widget is None: selected = None if event.type == tk.EventType.ButtonPress: tab_clicked = w.tk.call( w._w, "identify", "tab", event.x, event.y ) if isinstance(tab_clicked, int): tab_id = w.tabs()[tab_clicked] selected = w.nametowidget(tab_id) nb_tabs = w.tabs() if selected is None and nb_tabs: tab_id = nb_tabs[0] selected = w.nametowidget(tab_id) if isinstance(selected, IDockWidget): widget = selected if dock and widget and pane is None: pane = widget.parent_pane cls.bmouse_dock = dock cls.bmouse_dpane = pane cls.bmouse_dwidget = widget @classmethod def drag_start(cls, event: tk.Event): # Only start movement if a tab is clicked cls.source_tab_clicked = tab_below_mouse( event.widget, event.x_root, event.y_root ) if cls.source_tab_clicked is not None: cls.get_targets_below_mouse(event) cls.curr_dock = cls.bmouse_dock cls.curr_dpane = cls.bmouse_dpane cls.curr_dwidget = cls.bmouse_dwidget @classmethod def drag_motion(cls, event: tk.Event): if cls.curr_dock is None: # avoid errors on pygubu-designer preview return show_cursor = cls.cursor_moving cls._handle_cursor_moving() # If drag ends in a menu, a key error is produced. widget_below = None try: widget_below = event.widget.winfo_containing( event.x_root, event.y_root ) except KeyError: pass if not widget_below: return cls.get_targets_below_mouse(event) if cls.bmouse_dpane is not None and cls.curr_dpane != cls.bmouse_dpane: if ( cls.curr_dpane is not None and cls.curr_dpane != cls.curr_dock.main_pane ): cls.curr_dpane.indicators_visible(False) cls.curr_dpane = cls.bmouse_dpane cls.curr_dpane.indicators_visible(True) if ( cls.bmouse_dwidget is not None and cls.curr_dwidget is not None and cls.curr_dwidget != cls.bmouse_dwidget ): cls.curr_dwidget.indicators_visible(False) cls.curr_dwidget = cls.bmouse_dwidget if cls.curr_dwidget != cls.source_dwidget: cls.curr_dwidget.indicators_visible(True) last_indicator = cls.indicator_active cls.indicator_active = None if isinstance(widget_below, SlotIndicator): cls.indicator_active = widget_below indicator = cls.indicator_active if indicator: if not hasattr(indicator, "_ocolor"): indicator._ocolor = indicator.cget("background") indicator.configure(background=cls.indicator_bg_color) show_cursor = indicator.cget("cursor") if ( last_indicator and last_indicator != indicator and last_indicator.winfo_exists() ): last_indicator.configure(background=last_indicator._ocolor) show_cursor = cls._handle_nootbook(event, widget_below, show_cursor) if cls.cursor_showing != show_cursor: cls.curr_dock.configure(cursor=show_cursor) @classmethod def _handle_cursor_moving(cls): if cls.moving is False: cls.source_dwidget = cls.curr_dwidget cls.curr_dock.indicators_visible(True) cls.curr_dpane.indicators_visible(True) cls.moving = True @classmethod def _handle_nootbook(cls, event, widget_below, show_cursor): if cls.bmouse_dwidget and isinstance(widget_below, ttk.Notebook): tab_clicked = tab_below_mouse( widget_below, event.x_root, event.y_root ) if cls.bmouse_dwidget == cls.source_dwidget: if ( tab_clicked is not None and tab_clicked != cls.source_tab_clicked ): show_cursor = show_cursor = cls.cursor_tab_target elif tab_clicked is not None: show_cursor = cls.cursor_tab_target return show_cursor @classmethod def drag_end(cls, event): if cls.curr_dock: if cls.moving: cls.curr_dock.indicators_visible(False) if cls.curr_dpane: cls.curr_dpane.indicators_visible(False) if cls.curr_dwidget: cls.curr_dwidget.indicators_visible(False) # Execute move if any if cls.source_dwidget is not None: cls.execute_move(event) cls.curr_dock.configure(cursor=cls.cursor_default) cls.moving = False cls.curr_dwidget = None @classmethod def execute_move(cls, event: tk.Event): widget_below = None # If drag ends in a menu, a key error is produced. try: widget_below = event.widget.winfo_containing( event.x_root, event.y_root ) except KeyError: pass dock = cls.source_dwidget.maindock relative_to = None side = None if cls.indicator_active: side = cls.indicator_active.side target = cls.indicator_active.nametowidget( cls.indicator_active.winfo_parent() ) relative_to = "pane" if isinstance(target, IDockPane) else "widget" if relative_to == "pane": dock._move_into_pane_side( cls.source_dwidget, cls.curr_dpane, side ) else: dock._move_to_widget_side( cls.source_dwidget, cls.curr_dwidget, side ) return if cls.curr_dwidget and isinstance(widget_below, ttk.Notebook): move_inside = True tab_below = tab_below_mouse( widget_below, event.x_root, event.y_root ) if tab_below is None: # No move logger.debug("No move posible.") return if cls.curr_dwidget == cls.source_dwidget: # Move in same pane if tab_below == cls.source_tab_clicked: move_inside = False if move_inside: logger.debug( "Move %s into group of %s, position %s", cls.source_dwidget, cls.curr_dwidget, tab_below, ) dock._move_into_widget_group( cls.curr_dwidget, cls.source_dwidget, tab_below ) pygubu-0.36.1/src/pygubu/widgets/dockfw/slotframe.py000066400000000000000000000051231474524032100225120ustar00rootroot00000000000000#!/usr/bin/python3 # Note: Template in development. import tkinter as tk import tkinter.ttk as ttk class SlotIndicator(tk.Frame): def __init__(self, *args, side="n", **kw): super().__init__(*args, **kw) self.side = side # # Base class definition # class SlotFrameBase(ttk.Frame): def __init__(self, master=None, **kw): super().__init__(master, **kw) self.fn = SlotIndicator( self, cursor="top_side", background="#003ed9", height=15, width=15, side="n", name="fn", ) self.fn.grid(column=0, columnspan=3, row=0, sticky="ew") self.fs = SlotIndicator( self, cursor="bottom_side", background="#003ed9", height=15, width=10, side="s", name="fs", ) self.fs.grid(column=0, columnspan=3, row=2, sticky="ew") self.fe = SlotIndicator( self, cursor="right_side", background="#003ed9", height=15, width=15, side="e", name="fe", ) self.fe.grid(column=2, row=1, sticky="ns") self.fw = SlotIndicator( self, cursor="left_side", background="#003ed9", height=15, width=15, side="w", name="fw", ) self.fw.grid(column=0, row=1, sticky="ns") self.fcenter = ttk.Frame(self, name="fcenter") self.fcenter.configure(height=20, width=20) self.fcenter.grid(column=1, row=1, sticky="nsew") self.configure(height=200, width=200) # self.pack(side="top") # self.grid_propagate(0) self.rowconfigure(1, weight=1) self.columnconfigure(1, weight=1) # # Manual user code # class SlotFrame(SlotFrameBase): def __init__(self, *args, **kw): super().__init__(*args, **kw) for i in self.iter_indicators(): i.grid_remove() self._indicators_visible = False def iter_indicators(self) -> tk.Widget: for fi in (self.fn, self.fs, self.fw, self.fe): yield fi def indicators_visible(self, visible: bool): if self._indicators_visible != visible: self._indicators_visible = visible if visible: self.configure(padding=4) else: self.configure(padding=0) for f in self.iter_indicators(): if visible: f.grid() else: f.grid_remove() pygubu-0.36.1/src/pygubu/widgets/dockfw/widgets.py000066400000000000000000000001161474524032100221610ustar00rootroot00000000000000from .dockwidget import DockPane, DockWidget from .dockframe import DockFrame pygubu-0.36.1/src/pygubu/widgets/editabletreeview.py000066400000000000000000000327611474524032100225750ustar00rootroot00000000000000import functools import tkinter as tk import tkinter.ttk as ttk from abc import ABC, abstractmethod from collections import defaultdict from enum import Enum class InplaceEditor(ABC): event_value_changed = "<>" def _notify_change(self) -> None: self.widget.event_generate(self.event_value_changed) def focus_set(self) -> None: self.widget.focus_set() @property @abstractmethod def widget(self) -> tk.Widget: ... @property @abstractmethod def value(self): ... @abstractmethod def edit(self, value) -> None: ... class _VariableBasedEditor(InplaceEditor): @abstractmethod def _create_widget(self, master, **kw) -> tk.Widget: ... def __init__(self, master, **kw): self._var_blocked = False self._variable = None if kw is None: kw = {} if "textvariable" in kw: self._variable = kw["textvariable"] else: self._variable = tk.StringVar() kw["textvariable"] = self._variable self._widget = self._create_widget(master, **kw) def on_var_write(var, index, mode): if not self._var_blocked: self._notify_change() self._variable.trace_add("write", on_var_write) @property def widget(self): return self._widget @property def value(self): return self._variable.get() def edit(self, value): self._var_blocked = True self._variable.set(value) self._var_blocked = False class _EntryEditor(_VariableBasedEditor): def _create_widget(self, master, **kw) -> tk.Widget: return ttk.Entry(master, **kw) class _CheckbuttonEditor(_VariableBasedEditor): def _create_widget(self, master, **kw) -> tk.Widget: return ttk.Checkbutton(master, **kw) class _ComboboxEditor(_VariableBasedEditor): def _create_widget(self, master, **kw) -> tk.Widget: return ttk.Combobox(master, **kw) class _SpinboxEditor(_VariableBasedEditor): def _create_widget(self, master, **kw) -> tk.Widget: return ttk.Spinbox(master, **kw) class _CustomEditor(_VariableBasedEditor): def __init__(self, widget, **kw): self._widget = widget super().__init__(widget, **kw) def _create_widget(self, master, **kw) -> tk.Widget: return self._widget class _EditorType(Enum): ENTRY = 1 CHECKBUTTON = 2 COMBOBOX = 3 SPINBOX = 4 CUSTOM_WIDGET = 5 CUSTOM_EDITOR = 6 class EditableTreeview(ttk.Treeview): """A simple editable treeview It uses the following events from Treeview: <> <4> <5> If you need them use add=True when calling bind method. It Generates three virtual events: <> <> <> <> is emitted and used to configure cell editors. <> is emitted after a cell was changed. <> is emitted when user clicks in treeview white area where no rows are rendered. You can know wich cell is being configured or edited, using: get_event_info() To quickly get data from tree columns you can use: get_value(col, item) """ def __init__(self, master=None, **kw): super().__init__(master, **kw) self._curfocus = None self._editors = {} self._editors_show = {} self._editors_bag = defaultdict(dict) # Multiple editors per row self._header_clicked = False self._header_dragged = False self._last_column_clicked = "#0" self._update_callback_id = None self.bind("<>", self.__check_focus) # Wheel events? self.bind("<4>", self._schedule_update) self.bind("<5>", self._schedule_update) self.bind("", self.__check_focus) self.bind("", functools.partial(self.__on_key_press, "Home")) self.bind("", functools.partial(self.__on_key_press, "End")) self.bind("", self.__on_button1) self.bind("", self.__on_button1_release) self.bind("", self.__on_mouse_motion) self.bind("", self._schedule_update) def _schedule_update(self, event=None): if self._update_callback_id is None: self._update_callback_id = self.after_idle(self.__updateWnds) def __on_button1(self, event): r = self.identify_region(event.x, event.y) if r in ("separator", "header"): self._header_clicked = True elif r in ("tree", "cell"): if not self._editors_show: self._schedule_update() elif r == "nothing": if self._editors_show: self.__clear_inplace_widgets() self._curfocus = None self.event_generate("<>") self._last_column_clicked = self.identify_column(event.x) def __on_mouse_motion(self, event): if self._header_clicked: self._header_dragged = True def __on_button1_release(self, event): if self._header_dragged: self._schedule_update(event) self._header_clicked = False self._header_dragged = False def __on_key_press(self, key, event): if key == "Home": self.selection_set("") self.focus(self.get_children()[0]) if key == "End": self.selection_set("") self.focus(self.get_children()[-1]) def delete(self, *items): self._schedule_update() ttk.Treeview.delete(self, *items) def yview(self, *args): """Update inplace widgets position when doing vertical scroll""" self._schedule_update() ttk.Treeview.yview(self, *args) def yview_scroll(self, number, what): self._schedule_update() ttk.Treeview.yview_scroll(self, number, what) def yview_moveto(self, fraction): self._schedule_update() ttk.Treeview.yview_moveto(self, fraction) def xview(self, *args): """Update inplace widgets position when doing horizontal scroll""" self._schedule_update() ttk.Treeview.xview(self, *args) def xview_scroll(self, number, what): self._schedule_update() ttk.Treeview.xview_scroll(self, number, what) def xview_moveto(self, fraction): self._schedule_update() ttk.Treeview.xview_moveto(self, fraction) def __check_focus(self, event): """Checks if the focus has changed""" changed = False if not self._curfocus: changed = True elif self._curfocus != self.focus(): self.__clear_inplace_widgets() changed = True newfocus = self.focus() if changed: if newfocus: # print('Focus changed to:', newfocus) self._curfocus = newfocus self.__focus(newfocus) self.__updateWnds() def __focus(self, item): """Called when focus item has changed""" cols = self.__get_display_columns() for col in cols: self.__event_info = (col, item) self.event_generate("<>") def __updateWnds(self, event=None): if not self._curfocus: for col, editor in self._editors.items(): editor.widget.place_forget() self._update_callback_id = None return item = self._curfocus item_exists = self.exists(item) cols = self.__get_display_columns() col_diff = 0 if "#0" in cols else -1 last_column_index = int(self._last_column_clicked[1:]) + col_diff for index, col in enumerate(cols): if col in self._editors: editor = self._editors[col] bbox = "" if not item_exists else self.bbox(item, column=col) if bbox == "": editor.widget.place_forget() elif col in self._editors_show: editor.widget.place( x=bbox[0], y=bbox[1], width=bbox[2], height=bbox[3] ) # try to focus the widget in the column clicked if last_column_index == index: editor.focus_set() self._update_callback_id = None def __clear_inplace_widgets(self): """Remove all inplace edit widgets.""" for col, editor in self._editors.items(): editor.widget.place_forget() self._editors_show.clear() def __get_display_columns(self): cols = self.cget("displaycolumns") show = (str(s) for s in self.cget("show")) if "#all" in cols or "tree" in show: cols = ("#0",) + self.cget("columns") return cols def get_event_info(self): return self.__event_info def get_value(self, col, item): """Return data value of tree item at the specified column index.""" return self.__get_value(col, item) # FIXME: # def hide_editors(self): # self.__clear_inplace_widgets() # def __get_value(self, col, item): if col == "#0": return self.item(item, "text") else: return self.set(item, col) def __set_value(self, col, item, value): if col == "#0": self.item(item, text=value) else: self.set(item, col, value) self.__event_info = (col, item) self.event_generate("<>") def __update_value(self, col, item): if not self.exists(item): return value = self.__get_value(col, item) newvalue = self._editors[col].value if value != newvalue: self.__set_value(col, item, newvalue) def _setup_editor(self, col, item, editor): editor.edit(self.__get_value(col, item)) def on_value_change(event): self.__update_value(col, item) editor.widget.bind(InplaceEditor.event_value_changed, on_value_change) self._editors_show[col] = True def inplace_entry(self, col, item): current_editor = None if _EditorType.ENTRY not in self._editors_bag[col]: current_editor = _EntryEditor(self) self._editors_bag[col][_EditorType.ENTRY] = current_editor else: current_editor = self._editors_bag[col][_EditorType.ENTRY] self._editors[col] = current_editor self._setup_editor(col, item, current_editor) def inplace_checkbutton(self, col, item, onvalue="True", offvalue="False"): current_editor = None if _EditorType.CHECKBUTTON not in self._editors_bag[col]: svar = tk.StringVar() current_editor = _CheckbuttonEditor( self, textvariable=svar, variable=svar, onvalue=onvalue, offvalue=offvalue, ) self._editors_bag[col][_EditorType.CHECKBUTTON] = current_editor else: current_editor = self._editors_bag[col][_EditorType.CHECKBUTTON] self._editors[col] = current_editor self._setup_editor(col, item, current_editor) def inplace_combobox( self, col, item, values, readonly=True, update_values=False ): current_editor = None if _EditorType.COMBOBOX not in self._editors_bag[col]: state = "readonly" if readonly else "normal" current_editor = _ComboboxEditor(self, values=values, state=state) self._editors_bag[col][_EditorType.COMBOBOX] = current_editor else: current_editor = self._editors_bag[col][_EditorType.COMBOBOX] self._editors[col] = current_editor self._setup_editor(col, item, current_editor) if update_values: current_editor.widget.configure(values=values) def inplace_spinbox(self, col, item, min, max, step): current_editor = None if _EditorType.SPINBOX not in self._editors_bag[col]: current_editor = _SpinboxEditor( self, from_=min, to=max, increment=step ) self._editors_bag[col][_EditorType.SPINBOX] = current_editor else: current_editor = self._editors_bag[col][_EditorType.SPINBOX] self._editors[col] = current_editor self._setup_editor(col, item, current_editor) def inplace_custom(self, col, item, widget, stringvar=None): current_editor = None if _EditorType.CUSTOM_WIDGET not in self._editors_bag[col]: if stringvar is None: stringvar = tk.StringVar() current_editor = _CustomEditor(widget, textvariable=stringvar) self._editors_bag[col][_EditorType.CUSTOM_WIDGET] = current_editor else: current_editor = self._editors_bag[col][_EditorType.CUSTOM_WIDGET] self._editors[col] = current_editor self._setup_editor(col, item, current_editor) def inplace_editor(self, col, item, editor: InplaceEditor): current_editor = None if _EditorType.CUSTOM_EDITOR not in self._editors_bag[col]: current_editor = editor self._editors_bag[col][_EditorType.CUSTOM_EDITOR] = current_editor else: current_editor = self._editors_bag[col][_EditorType.CUSTOM_EDITOR] self._editors[col] = current_editor self._setup_editor(col, item, current_editor) pygubu-0.36.1/src/pygubu/widgets/entrywph.py000066400000000000000000000051041474524032100211200ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk class EntryWPlaceholder(ttk.Entry): _PHKEY = "placeholder" def __init__(self, master=None, **kw): self.placeholder = kw.pop(self._PHKEY, "") super().__init__(master, **kw) self.bind("", self._on_focusin) self.bind("", self._on_focusout) if "textvariable" in kw: var = kw["textvariable"] var.trace_add("write", self._trace_var) self._put_placeholder() def _trace_var(self, var, *args): print(f"tracing {var}", self.__get()) if self.focus_displayof() is not self: print("putting placeholder after var trace") self._put_placeholder(self.__get()) def _put_placeholder(self): print("on put_placeholder", self.__get(), not self.__get()) if not self.__get(): print("inserting placeholder") self.insert(0, self.placeholder) def _on_focusin(self, event): print("on_focusin") if self.__get() == self.placeholder: print("deleting placeholder") self.delete("0", "end") def _on_focusout(self, event): self._put_placeholder() def __get(self): return super().get() def get(self): value = super().get() if value and (value == self.placeholder): value = "" return value def configure(self, cnf=None, **kw): key = self._PHKEY if cnf: if cnf == key: return (key, self.cget(key)) return super().configure(cnf, **kw) if key in kw: self.placeholder = kw.pop(key) self._put_placeholder() return super().configure(cnf, **kw) config = configure def cget(self, key): if key == self._PHKEY: return self.placeholder return super().cget(key) __getitem__ = cget if __name__ == "__main__": root = tk.Tk() var = tk.StringVar() username = EntryWPlaceholder(root, textvariable=var) username["placeholder"] = "--USERNAME--" var2 = tk.StringVar() password = EntryWPlaceholder(root, textvariable=var2) password.configure(style="MyPasswordEntry.TEntry") password.configure(placeholder="--PASSWORD--") username.pack() password.pack() def on_click(): print(f"user: {username.get()}") print(f"pass: {password.get()}") def on_start(): var.set("username") btn = ttk.Button(root, text="Test", takefocus=True, command=on_click) btn.pack() root.after(800, on_start) root.mainloop() pygubu-0.36.1/src/pygubu/widgets/filterabletreeview.py000066400000000000000000000133301474524032100231240ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk from typing import Callable, Optional _filter_func = Callable[[ttk.Treeview, str, str], bool] class BasicFilter: def __call__(self, tree, itemid, filter_value: str) -> bool: # Default filter match function. txt = tree.item(itemid, "text").lower() match_found = filter_value in txt return match_found class FilterableTreeview(ttk.Treeview): def __init__(self, *args, filter_func: Optional[_filter_func] = None, **kw): super().__init__(*args, **kw) self.filter_active = False self.filter_value = "" self.filter_prev_value = None self.filter_prev_selitem = None self._detached = [] self._expanded = set() self.filter_func = BasicFilter() if filter_func is None else filter_func # # Filter functions # def _see(self, item): # The item may have been deleted. if self.exists(item): self.see(item) def _see_later(self, item): self.after_idle(lambda: self._see(item)) def filter_by(self, fvalue): """Filters treeview""" self.filter_remove() if not fvalue: return self.filter_value = fvalue # self._expand_all() self.selection_set("") children = self.get_children("") for item in children: match_, detached = self._filter_and_detach(item) if detached: self._detached.extend(detached) if match_: self.item(item, open=True) for i, p, idx in self._detached: self.detach(i) self.filter_active = True def filter_remove(self, remember=False): """Removes filter and reattaches hidden items. When filter is applied you can not traverse through detached items. So to be able to navigate all tree items with filter active you use the following approach: tree.filter_remove(remember=True) # Do operation, navigate tree items. tree.filter_restore() """ if self.filter_active: sitem = None selection = self.selection() if selection: sitem = selection[0] self._see_later(sitem) if remember: self.filter_prev_value = self.filter_value self.filter_prev_selitem = sitem self._reattach() self._restore_open_state() self.filter_value = "" self.filter_active = False def filter_restore(self): if self.filter_prev_value: self.filter_value = self.filter_prev_value item = self.filter_prev_selitem if item and self.exists(item): self.selection_set(item) self._see_later(item) # clear self.filter_prev_value = "" self.filter_prev_selitem = None # Re apply filter? self.filter_by(self.filter_value) def expand_to(self, target_item, start_item=""): """Search and expand tree to see itemid.""" self._expand_to(start_item, target_item) def _expand_to(self, rootitem, target_item): found = rootitem == target_item if not found: children = self.get_children(rootitem) found = target_item in children if not found: for child_item in children: found = self._expand_to(child_item, target_item) if found: break if found and rootitem != "": self.item(rootitem, open=True) return found def expand_all(self, rootitem=""): children = self.get_children(rootitem) for item in children: self._expand_all(item) if rootitem != "" and children: self.item(rootitem, open=True) def _restore_open_state(self, rootitem=""): if rootitem != "": was_open = rootitem in self._expanded self.item(rootitem, open=was_open) children = self.get_children(rootitem) for item in children: self._restore_open_state(item) if rootitem == "": # End of recursion clear expanded set self._expanded.clear() def _reattach(self): """Reinsert the hidden items.""" for item, p, idx in self._detached: # The item may have been deleted. if self.exists(item) and self.exists(p): self.move(item, p, idx) self._detached = [] def _filter_and_detach(self, item) -> (bool, list): """Hide items from treeview that do not match the search string.""" to_detach = [] children_det = [] children_match = False if self.item(item, "open"): self._expanded.add(item) match_found = self.filter_func(self, item, self.filter_value) parent = self.parent(item) idx = self.index(item) children = self.get_children(item) if children: for child in children: match, detach = self._filter_and_detach(child) children_match = children_match | match if detach: children_det.extend(detach) if match_found: self.item(item, open=True) if children_det: to_detach.extend(children_det) else: if children_match: self.item(item, open=True) if children_det: to_detach.extend(children_det) else: to_detach.append((item, parent, idx)) match_found = match_found | children_match return match_found, to_detach # # End Filter functions # pygubu-0.36.1/src/pygubu/widgets/floodgauge.py000066400000000000000000000320001474524032100213470ustar00rootroot00000000000000import tkinter as tk import tkinter.ttk as ttk from pygubu.utils.widget import WidgetConfigureMixin """Floodgauge Widget Based on ttkbootstrap Floodgauge. """ class FloodgaugeStyleManager: """Manage PygubuFloodgauge style Tasks: - register style if not defined - update style if theme changed - do nothing if style is previously defined (if using pygubu theming). """ TKCLASS_NAME: str = "Floodgauge" STYLE_NAME_H: str = None STYLE_NAME_V: str = None STYLE_LAYOUT_H: list = None STYLE_LAYOUT_V: list = None STYLE_CONF: dict = None STYLE_INITIALIZED = False STYLE_MANAGED_EXTERNALLY = False CONFIGURED_THEMES = None @classmethod def define_layout(cls, master): if cls.STYLE_INITIALIZED or cls.STYLE_MANAGED_EXTERNALLY: return H_STYLE = f"Horizontal.T{cls.TKCLASS_NAME}" V_STYLE = f"Vertical.T{cls.TKCLASS_NAME}" cls.STYLE_NAME_H = H_STYLE cls.STYLE_NAME_V = V_STYLE style = ttk.Style(master) try: h_layout = style.layout(H_STYLE) v_layout = style.layout(V_STYLE) if h_layout and v_layout: cls.STYLE_MANAGED_EXTERNALLY = True cls.STYLE_LAYOUT_H = h_layout cls.STYLE_LAYOUT_V = v_layout return except tk.TclError: pass h_element = H_STYLE.replace(".TF", ".F") v_element = V_STYLE.replace(".TF", ".F") style.element_create(f"{h_element}.trough", "from", "clam") style.element_create(f"{h_element}.pbar", "from", "default") style.element_create(f"{v_element}.trough", "from", "clam") style.element_create(f"{v_element}.pbar", "from", "default") h_layout = None v_layout = None if tk.TkVersion >= 9: h_layout, v_layout = cls.floodgauge_layout_tk9(h_element, v_element) else: h_layout, v_layout = cls.floodgauge_layout_tk8(h_element, v_element) bg_color = style.lookup("TProgressbar", "background") default_conf = dict( background=bg_color, borderwidth=1, font="-size 14", thickness=50, pbarrelief=tk.FLAT, justify=tk.CENTER, anchor=tk.CENTER, ) style.layout(H_STYLE, h_layout) style.configure(H_STYLE, **default_conf) style.layout(V_STYLE, v_layout) style.configure(V_STYLE, **default_conf) cls.CONFIGURED_THEMES = [style.theme_use()] cls.STYLE_LAYOUT_H = h_layout cls.STYLE_LAYOUT_V = v_layout cls.STYLE_CONF = default_conf cls.STYLE_INITIALIZED = True @classmethod def floodgauge_layout_tk9(cls, h_element: str, v_element: str) -> tuple: h_layout = [ ( f"{h_element}.trough", { "children": [ (f"{h_element}.pbar", {"side": "left", "sticky": "ns"}), ( f"{h_element}.ctext", # {"side": "left", "sticky": ""} {"side": "left", "sticky": "", "expand": True}, ), ], "sticky": "nswe", }, ) ] v_layout = [ ( f"{v_element}.trough", { "children": [ ( f"{v_element}.pbar", {"side": "bottom", "sticky": "we"}, ), (f"{v_element}.ctext", {"sticky": "", "expand": True}), ], "sticky": "nswe", }, ) ] return h_layout, v_layout @classmethod def floodgauge_layout_tk8(cls, h_element: str, v_element: str) -> tuple: h_layout = [ ( f"{h_element}.trough", { "children": [ ( f"{h_element}.pbar", {"sticky": "ns"}, ), (f"{cls.TKCLASS_NAME}.label", {"sticky": ""}), ], "sticky": "nsew", }, ) ] v_layout = [ ( f"{v_element}.trough", { "children": [ ( f"{v_element}.pbar", {"sticky": "ew"}, ), (f"{cls.TKCLASS_NAME}.label", {"sticky": ""}), ], "sticky": "nsew", }, ) ] return h_layout, v_layout @classmethod def reconfigure_layout(cls, master): if cls.STYLE_MANAGED_EXTERNALLY: return theme = master.tk.eval("return $ttk::currentTheme") if theme not in cls.CONFIGURED_THEMES: s = ttk.Style(master) bg_color = s.lookup("TProgressbar", "background") conf = cls.STYLE_CONF conf.update(background=bg_color) s.configure(cls.STYLE_NAME_H, **conf) s.configure(cls.STYLE_NAME_V, **conf) cls.CONFIGURED_THEMES.append(theme) @classmethod def style_for_orient(cls, orient): return cls.STYLE_NAME_H if orient == "horizontal" else cls.STYLE_NAME_V @classmethod def layout_for_orient(cls, orient): return ( cls.STYLE_LAYOUT_H if orient == "horizontal" else cls.STYLE_LAYOUT_V ) class FloodgaugeBase(WidgetConfigureMixin, ttk.Progressbar): """A widget that shows the status of a long-running operation with an optional text indicator. Similar to the `ttk.Progressbar`, this widget can operate in two modes. *determinate* mode shows the amount completed relative to the total amount of work to be done, and *indeterminate* mode provides an animated display to let the user know that something is happening. """ FGSM = FloodgaugeStyleManager() def __init__( self, master=None, *, mask=None, variable=None, textvariable=None, value=0, text="", style=None, class_=None, **kw, ): self.FGSM.define_layout(master) self._traceid = None self._pmask = mask self._ptextvar = textvariable self._pvariable = variable if variable is None: self._pvariable = tk.IntVar(master, value=value) if textvariable is None: self._ptextvar = tk.StringVar(master, value=text) if style is None: orient = kw.get("orient", "horizontal") style = self.FGSM.style_for_orient(orient) if class_ is None: class_ = self.FGSM.TKCLASS_NAME kw["value"] = value kw["variable"] = self._pvariable kw["style"] = style kw["class_"] = class_ super().__init__(master, **kw) if self._pmask is not None: self._set_mask() self.bind("<>", self._on_theme_change) self.bind("<>", self._on_theme_change) def _widget_cget(self, option): if option == "value": return self._pvariable.get() if option == "text": return self._ptextvar.get() if option == "textvariable": return self._ptextvar if option == "mask": return self._pmask return super()._widget_cget(option) def _configure_get(self, option): if option in ("value", "text", "mask", "textvariable"): return self._widget_cget(option) return super()._configure_get(option) def _configure_set(self, **kw): update_text = False if "variable" in kw: new_var = kw["variable"] self._unset_mask() self._pvariable = new_var if new_var and self._pmask: self._set_mask() update_text = True if "value" in kw: self._pvariable.set(kw.pop("value")) update_text = True if "text" in kw: self._ptextvar.set(kw.pop("text")) update_text = True if "mask" in kw: self._pmask = kw.pop("mask") if self._pmask: self._set_mask() update_text = True if "orient" in kw: new_orient = kw.get("orient") current = super()._configure_get("orient") if new_orient != current: style = kw.get("style", super()._configure_get("style")) if new_orient[1:] not in style: kw["style"] = self.FGSM.style_for_orient(new_orient) if update_text: self._set_widget_text() return super()._configure_set(**kw) def _set_mask(self): if self._traceid is None: self._traceid = self._pvariable.trace_add( "write", self._set_widget_text ) def _unset_mask(self): if self._traceid is not None: self._pvariable.trace_remove("write", self._traceid) self._traceid = None def _set_widget_text(self, *_): if self._pmask is None: text = self._ptextvar.get() else: value = self._pvariable.get() text = self._pmask.format(value) super()._configure_set(text=text) def _on_theme_change(self, event: tk.Event = None): self.FGSM.reconfigure_layout(self) class FloodgaugeTk8(FloodgaugeBase): INSTANCE_COUNT = 0 def __init__(self, master=None, **kw): self.FGSM.define_layout(master) self._uid = self._new_uid() self._pfont = kw.pop("font", "helvetica 10") orient = kw.get("orient", "horizontal") default_style = self.FGSM.style_for_orient(orient) user_defined_style = kw.get( "style", default_style ) # user defined style kw["style"] = self._new_instance_style( master, orient, user_defined_style ) super().__init__(master, **kw) self._set_widget_text() @classmethod def _new_uid(cls): cls.INSTANCE_COUNT += 1 return f"fg{cls.INSTANCE_COUNT}" def _set_widget_text(self, *_): ttkstyle = self.cget("style") if self._pmask is None: text = self._ptextvar.get() else: value = self._pvariable.get() text = self._pmask.format(value) self.tk.call("ttk::style", "configure", ttkstyle, "-text", text) self.tk.call("ttk::style", "configure", ttkstyle, "-font", self._pfont) def _new_instance_style(self, master, orient: str, style: str) -> str: instance_style = style uid = f"{self._uid}." if not style.startswith(uid): instance_style = f"{uid}{style}" layout_exists = False s = ttk.Style(master) try: s.layout(instance_style) except tk.TclError: pass if not layout_exists: s.layout(instance_style, self.FGSM.layout_for_orient(orient)) # print(f"creating layout: {instance_style}") return instance_style def _on_theme_change(self, event: tk.Event = None): super()._on_theme_change(event) self._set_widget_text() def _widget_cget(self, option): if option == "font": return self._pfont return super()._widget_cget(option) def _configure_get(self, option): if option == "font": return self._widget_cget(option) return super()._configure_get(option) def _configure_set(self, **kw): update_text = False update_style = False if "orient" in kw or "style" in kw: update_style = True if update_style: orient = kw.get("orient", self.cget("orient")) new_style = kw.pop("style", self.FGSM.style_for_orient(orient)) style = self._new_instance_style(self, orient, new_style) # super()._configure_set(style=style) kw["style"] = style update_text = True if "variable" in kw or "value" in kw or "text" in kw or "mask" in kw: update_text = True if "font" in kw: self._pfont = kw.pop("font") update_text = True if update_text: self._set_widget_text() return super()._configure_set(**kw) if tk.TkVersion >= 9: class Floodgauge(FloodgaugeBase): ... else: Floodgauge = FloodgaugeTk8 if __name__ == "__main__": root = tk.Tk() gauge = Floodgauge(root, mask="{}%") gauge.pack(expand=True, fill="both") options = ("value", "text", "mask", "font", "textvariable", "variable") for option in options: value = gauge.cget(option) print(f"The gauge {option} is:", value, type(value)) scale = ttk.Scale( root, variable=gauge.cget("variable"), value=0, from_=0, to=100 ) scale.pack() root.mainloop() pygubu-0.36.1/src/pygubu/widgets/hideableframe.py000066400000000000000000000005061474524032100220110ustar00rootroot00000000000000import tkinter.ttk as ttk from pygubu.utils.widget import HideableMixin class HideableFrame(HideableMixin, ttk.Frame): """A frame that can be easily hidden. Use hidden property to show or hide the frame. myframe.hidden = True """ def __init__(self, *args, **kw): super().__init__(*args, **kw) pygubu-0.36.1/src/pygubu/widgets/pathchooserinput.py000066400000000000000000000153161474524032100226450ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk import tkinter.ttk as ttk from tkinter import filedialog class PathChooserBase: """Allows to choose a file or directory. Encapsulates the usage of the functions: filedialog.askopenfilename() filedialog.asksaveasfilename(**fdoptions) filedialog.askdirectory() Generates <> event when the path is changed. Dialog options: initialdir: str filetypes: iterable title: str mustexist: bool defaultextension: str """ FILE = "file" DIR = "directory" def __init__(self, *args, **kw): self._choose = self.FILE self._oldvalue = None self._path = None self._fdoptions = { "filetypes": tuple(), "initialdir": None, "mustexist": True, "title": None, "defaultextension": None, } super().__init__(*args, **kw) def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) key = "type" if key in kw: self._choose = kw.pop(key) key = "path" if key in kw: self._path = kw.pop(key) # dialog options for key in tuple(kw.keys()): if key in self._fdoptions: self._fdoptions[key] = kw.pop(key) return super().configure(cnf, **kw) config = configure def cget(self, key): option = "type" if key == option: return self._choose option = "path" if key == option: return self._path # dialog options if key in self._fdoptions: return self._fdoptions[key] return super().cget(key) __getitem__ = cget def _is_changed(self): if self._oldvalue != self._get_user_selected_path(): return True return False def _get_user_selected_path(self): return self._path def _generate_changed_event(self): if self._is_changed(): self._oldvalue = self._get_user_selected_path() self.event_generate("<>") def _do_path_selection(self): fname = None fdoptions = self._fdoptions.copy() fdoptions["parent"] = self.winfo_toplevel() if fdoptions["initialdir"] is None: fdoptions["initialdir"] = self.cget("path") if self._choose == self.FILE: must_exists = fdoptions.pop("mustexist") if must_exists: fname = filedialog.askopenfilename(**fdoptions) else: fname = filedialog.asksaveasfilename(**fdoptions) else: fdoptions.pop("filetypes") fdoptions.pop("defaultextension") fname = filedialog.askdirectory(**fdoptions) if fname: self.configure(path=fname) self._generate_changed_event() class PathChooserInput(PathChooserBase, ttk.Frame): """Allows to choose a file or directory. Encapsulates the usage of the functions: filedialog.askopenfilename() filedialog.asksaveasfilename(**fdoptions) filedialog.askdirectory() Generates <> event when the path is changed. Dialog options: initialdir: str filetypes: iterable title: str mustexist: bool defaultextension: str Usage Example: # Choose filename for open: pcifile = PathChooserInput(framex) pcifile.config( initialdir='/home', title='Open file:', type='file', filetypes=[('text files-', '.txt'), ('uifiles', '.ui')] ) pcifile.pack(fill='x', side='top') # Choose filename for save: pcofile = PathChooserInput(framex) pcofile.config( initialdir='/home', title='Save to:', type='file', mustexist=False, defaultextension=".txt", ) pcofile.pack(fill='x', side='top') # Choose directory: pcidir = PathChooserInput(framex) pcidir.config(initialdir='/usr/local', mustexist=True, title='Choose a directory:', type='directory') pcidir.pack(fill='x', side='top') """ def __init__(self, *args, **kw): super().__init__(*args, **kw) self._state = "normal" # subwidgets self.entry = o = ttk.Entry(self, state=self._state) o.grid(row=0, column=0, sticky="ew") o.bind("", self.__on_enter_key_pressed) o.bind("", self.__on_focus_out) self.folder_button = o = ttk.Button( self, text="▶", command=self._do_path_selection, width=4, state=self._state, ) o.grid(row=0, column=1, padx=2) # self.rowconfigure(0, weight = 0) self.columnconfigure(0, weight=1) def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) key = "image" if key in kw: self.folder_button.configure(image=kw.pop(key)) key = "path" if key in kw: self.entry.delete(0, "end") self.entry.insert(0, kw.pop(key)) self._generate_changed_event() key = "textvariable" if key in kw: self.entry.configure(textvariable=kw.pop(key)) self._generate_changed_event() key = "state" if key in kw: value = kw.pop(key) self.entry.config(state=value) if value in ("disabled", "readonly"): self.folder_button.config(state="disabled") else: self.folder_button.config(state=value) return super().configure(cnf, **kw) config = configure def cget(self, key): option = "image" if key == option: return self.folder_button.cget(key) option = "path" if key == option: return self.entry.get() option = "textvariable" if key == option: return self.entry.cget(option) option = "state" if key == option: return self.entry.cget(option) return super().cget(key) __getitem__ = cget def _get_user_selected_path(self): return self.entry.get() def __on_enter_key_pressed(self, event): key = event.keysym if key in ("Return", "KP_Enter"): self._generate_changed_event() def __on_focus_out(self, event): self._generate_changed_event() class PathChooserButton(PathChooserBase, ttk.Button): def __init__(self, *args, **kw): super().__init__(*args, **kw) self.configure(command=self._do_path_selection) pygubu-0.36.1/src/pygubu/widgets/scrollbarhelper.py000066400000000000000000000007231474524032100224250ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk import tkinter.ttk as ttk from pygubu.widgets.tkscrollbarhelper import ScrollbarHelperBase class TTKScrollbarHelperFactory(type): def __new__(cls, clsname, superclasses, attrs): return type.__new__(cls, str(clsname), superclasses, attrs) ScrollbarHelper = TTKScrollbarHelperFactory( "ScrollbarHelper", (ScrollbarHelperBase, ttk.Frame, object), {"_framecls": ttk.Frame, "_sbarcls": ttk.Scrollbar}, ) pygubu-0.36.1/src/pygubu/widgets/scrolledframe.py000066400000000000000000000005001474524032100220550ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk import tkinter.ttk as ttk from pygubu.binding import ( ApplicationLevelBindManager as BindManager, remove_binding, ) from .tkscrolledframe import ScrolledFrameBase class ScrolledFrame(ScrolledFrameBase, ttk.Frame): _framecls = ttk.Frame _sbarcls = ttk.Scrollbar pygubu-0.36.1/src/pygubu/widgets/simpletooltip.py000066400000000000000000000105461474524032100221520ustar00rootroot00000000000000# encoding: utf-8 __all__ = ["ToolTip"] import tkinter as tk class ToolTip(object): def __init__( self, widget, text=None, font=None, background=None, foreground=None, justify=None, wraplength=None, ): self.widget = widget self.tipwindow = None self.id = None self.x = self.y = 0 self.text = text if text is not None else "¿?" self.font = font if font is not None else ("tahoma", "9", "normal") self.background = background if background is not None else "#ffffe0" self.foreground = foreground if foreground is not None else "black" self.justify = justify if justify is not None else tk.LEFT self.wraplength = wraplength if wraplength is not None else 300 def inside_wbbox(self, rx, ry): bbox = self._calc_bbox(self.widget, True) inside = False if (bbox[0] <= rx <= bbox[2]) and (bbox[1] <= ry <= bbox[3]): inside = True return inside def _calc_bbox(self, widget, screen=False): rx = widget.winfo_x() ry = widget.winfo_y() if screen: rx = widget.winfo_rootx() ry = widget.winfo_rooty() x2 = rx + widget.winfo_width() y2 = ry + widget.winfo_height() return (rx, ry, x2, y2) def _calc_final_pos(self, ttwidth, ttheight): rx, ry, rcx, rcy = self._calc_bbox(self.widget, True) w = rcx - rx h = rcy - ry sh = self.widget.winfo_screenheight() sw = self.widget.winfo_screenwidth() x = y = 0 # final_region = None regions = ( "se-al", "se-ar", "sw-al", "sw-ar", "nw-al", "nw-ar", "ne-al", "ne-ar", ) for region in regions: if region == "ne-al": x = rx y = ry - ttheight elif region == "ne-ar": x = rx - ttwidth y = ry - ttheight elif region == "nw-al": x = rx + w y = ry - ttheight elif region == "nw-ar": x = rx + w - ttwidth y = ry - ttheight elif region == "se-al": x = rx y = ry + h elif region == "se-ar": x = rx - ttwidth y = ry + h elif region == "sw-al": x = rx + w y = ry + h elif region == "sw-ar": x = rx + w - ttwidth y = ry + h x2 = x + ttwidth y2 = y + ttheight if (x > 0 and x2 < sw) and (y > 0 and y2 < sh): # final_region = region break # print(final_region, x, x2, y, y2) return (x, y) def showtip(self): "Display text in tooltip window" if self.tipwindow or not self.text: return self.tipwindow = tw = tk.Toplevel(self.widget) tw.wm_overrideredirect(1) try: # For Mac OS tw.tk.call( "::tk::unsupported::MacWindowStyle", "style", tw._w, "help", "noActivates", ) except tk.TclError: pass label = tk.Label( tw, text=self.text, justify=self.justify, background=self.background, foreground=self.foreground, relief=tk.SOLID, borderwidth=1, font=self.font, wraplength=self.wraplength, ) label.pack(ipadx=2) x, y = self._calc_final_pos( label.winfo_reqwidth(), label.winfo_reqheight() ) tw.wm_geometry("+{0}+{1}".format(x, y)) def hidetip(self): tw = self.tipwindow self.tipwindow = None if tw: tw.destroy() def create(widget, text): toolTip = ToolTip(widget, text) def enter(event): toolTip.showtip() def leave(event): toolTip.hidetip() widget.bind("", enter) widget.bind("", leave) return toolTip if __name__ == "__main__": root = tk.Tk() for idx in range(0, 2): b = tk.Button(root, text="A button") b.grid() create(b, "A tooltip !!") root.mainloop() pygubu-0.36.1/src/pygubu/widgets/tkscrollbarhelper.py000066400000000000000000000105761474524032100227730ustar00rootroot00000000000000# encoding: utf-8 import logging import tkinter as tk from pygubu.binding import ( ApplicationLevelBindManager as BindManager, remove_binding, ) logger = logging.getLogger(__name__) def _autoscroll(sbar, first, last): """Hide and show scrollbar as needed. Code from Joe English (JE) at http://wiki.tcl.tk/950""" first, last = float(first), float(last) if first <= 0 and last >= 1: sbar.grid_remove() else: sbar.grid() sbar.set(first, last) class ScrollbarHelperBase(object): VERTICAL = "vertical" HORIZONTAL = "horizontal" BOTH = "both" _framecls = None _sbarcls = None def __init__(self, master=None, **kw): self.scrolltype = kw.pop("scrolltype", self.VERTICAL) self.usemousewheel = tk.getboolean(kw.pop("usemousewheel", False)) super(ScrollbarHelperBase, self).__init__(master, **kw) self.vsb = None self.hsb = None self.cwidget = None self.container = c = self._framecls(self) c.grid(row=0, column=0, sticky="nsew") self._bindingids = [] self._create_scrollbars() def _create_scrollbars(self): if self.scrolltype in (self.BOTH, self.VERTICAL): self.vsb = self._sbarcls(self, orient="vertical") # layout self.vsb.grid(column=1, row=0, sticky=tk.NS) if self.scrolltype in (self.BOTH, self.HORIZONTAL): self.hsb = self._sbarcls(self, orient="horizontal") self.hsb.grid(column=0, row=1, sticky=tk.EW) self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(0, weight=1) def add_child(self, cwidget): self.cwidget = cwidget cwidget.pack(expand=True, fill="both", in_=self.container) if self.scrolltype in (self.BOTH, self.VERTICAL): if hasattr(cwidget, "yview"): self.vsb.configure(command=cwidget.yview) cwidget.configure( yscrollcommand=lambda f, l: _autoscroll(self.vsb, f, l) ) else: msg = "widget %s has no attribute 'yview'" logger.info(msg, str(cwidget)) if self.scrolltype in (self.BOTH, self.HORIZONTAL): if hasattr(cwidget, "xview"): self.hsb.configure(command=cwidget.xview) cwidget.configure( xscrollcommand=lambda f, l: _autoscroll(self.hsb, f, l) ) else: msg = "widget % has no attribute 'xview'" logger.info(msg, str(cwidget)) self._configure_mousewheel() def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) key = "usemousewheel" if key in kw: self.usemousewheel = tk.getboolean(kw.pop(key)) self._configure_mousewheel() return super().configure(cnf, **kw) config = configure def cget(self, key): option = "usemousewheel" if key == option: return self.usemousewheel return super(ScrollbarHelperBase, self).cget(key) __getitem__ = cget def _configure_mousewheel(self): cwidget = self.cwidget if self.usemousewheel and cwidget is not None: BindManager.init_mousewheel_binding(self) if self.hsb and not hasattr(self.hsb, "on_mousewheel"): self.hsb.on_mousewheel = BindManager.make_onmousewheel_cb( cwidget, "x", 2 ) if self.vsb and not hasattr(self.vsb, "on_mousewheel"): self.vsb.on_mousewheel = BindManager.make_onmousewheel_cb( cwidget, "y", 2 ) main_sb = self.vsb or self.hsb if main_sb: cwidget.on_mousewheel = main_sb.on_mousewheel BindManager.mousewheel_bind(cwidget) for s in (self.vsb, self.hsb): if s: BindManager.mousewheel_bind(s) else: for w in (cwidget, self.vsb, self.hsb): BindManager.mousewheel_unbind(w) class ScrollbarHelperFactory(type): def __new__(cls, clsname, superclasses, attrs): return type.__new__(cls, str(clsname), superclasses, attrs) TkScrollbarHelper = ScrollbarHelperFactory( "TkScrollbarHelper", (ScrollbarHelperBase, tk.Frame, object), {"_framecls": tk.Frame, "_sbarcls": tk.Scrollbar}, ) pygubu-0.36.1/src/pygubu/widgets/tkscrolledframe.py000066400000000000000000000237231474524032100224300ustar00rootroot00000000000000# encoding: utf-8 import tkinter as tk from pygubu.binding import ( ApplicationLevelBindManager as BindManager, remove_binding, ) class ScrolledFrameBase: VERTICAL = "vertical" HORIZONTAL = "horizontal" BOTH = "both" _framecls = tk.Frame _sbarcls = tk.Scrollbar def __init__(self, master=None, **kw): self.scrolltype = kw.pop("scrolltype", self.VERTICAL) self.usemousewheel = tk.getboolean(kw.pop("usemousewheel", False)) self._bindingids = [] super().__init__(master, **kw) # self._framecls.__init__(self, master, **kw) self._container = self._framecls(self, width=200, height=200) self._clipper = self._framecls(self._container, width=200, height=200) self.innerframe = self._framecls(self._clipper) self.vsb = self._sbarcls(self._container) self.hsb = self._sbarcls(self._container, orient="horizontal") # variables self.hsbOn = 0 self.vsbOn = 0 self.hsbNeeded = 0 self.vsbNeeded = 0 self._jfraction = 0.05 self._scrollTimer = None self._scrollRecurse = 0 self._startX = 0 self._startY = 0 # configure scroll self.hsb.set(0.0, 1.0) self.vsb.set(0.0, 1.0) self.vsb.config(command=self.yview) self.hsb.config(command=self.xview) # grid self._container.pack(expand=True, fill="both") self._clipper.grid(row=0, column=0, sticky=tk.NSEW) # self.vsb.grid(row=0, column=1, sticky=tk.NS) # self.hsb.grid(row=1, column=0, sticky=tk.EW) self._container.rowconfigure(0, weight=1) self._container.columnconfigure(0, weight=1) # Whenever the clipping window or scrolled frame change size, # update the scrollbars. self.innerframe.bind("", self._reposition) self._clipper.bind("", self._reposition) self.bind("", self._reposition) self._configure_mousewheel() # Set timer to call real reposition method, so that it is not # called multiple times when many things are reconfigured at the # same time. def reposition(self): if self._scrollTimer is None: self._scrollTimer = self.after_idle(self._scrollBothNow) # Called when the user clicks in the horizontal scrollbar. # Calculates new position of frame then calls reposition() to # update the frame and the scrollbar. def xview(self, mode=None, value=None, units=None): if isinstance(value, str): value = float(value) if mode is None: return self.hsb.get() elif mode == "moveto": frameWidth = self.innerframe.winfo_reqwidth() self._startX = value * float(frameWidth) else: # mode == 'scroll' clipperWidth = self._clipper.winfo_width() if units == "units": jump = int(clipperWidth * self._jfraction) else: jump = clipperWidth self._startX = self._startX + value * jump self.reposition() # Called when the user clicks in the vertical scrollbar. # Calculates new position of frame then calls reposition() to # update the frame and the scrollbar. def yview(self, mode=None, value=None, units=None): if isinstance(value, str): value = float(value) if mode is None: return self.vsb.get() elif mode == "moveto": frameHeight = self.innerframe.winfo_reqheight() self._startY = value * float(frameHeight) else: # mode == 'scroll' clipperHeight = self._clipper.winfo_height() if units == "units": jump = int(clipperHeight * self._jfraction) else: jump = clipperHeight self._startY = self._startY + value * jump self.reposition() def _reposition(self, event): self.reposition() def _getxview(self): # Horizontal dimension. clipperWidth = self._clipper.winfo_width() frameWidth = self.innerframe.winfo_reqwidth() if frameWidth <= clipperWidth: # The scrolled frame is smaller than the clipping window. self._startX = 0 endScrollX = 1.0 # use expand by default relwidth = 1 else: # The scrolled frame is larger than the clipping window. # use expand by default if self._startX + clipperWidth > frameWidth: self._startX = frameWidth - clipperWidth endScrollX = 1.0 else: if self._startX < 0: self._startX = 0 endScrollX = (self._startX + clipperWidth) / float(frameWidth) relwidth = "" # Position frame relative to clipper. self.innerframe.place(x=-self._startX, relwidth=relwidth) return (self._startX / float(frameWidth), endScrollX) def _getyview(self): # Vertical dimension. clipperHeight = self._clipper.winfo_height() frameHeight = self.innerframe.winfo_reqheight() if frameHeight <= clipperHeight: # The scrolled frame is smaller than the clipping window. self._startY = 0 endScrollY = 1.0 # use expand by default relheight = 1 else: # The scrolled frame is larger than the clipping window. # use expand by default if self._startY + clipperHeight > frameHeight: self._startY = frameHeight - clipperHeight endScrollY = 1.0 else: if self._startY < 0: self._startY = 0 endScrollY = (self._startY + clipperHeight) / float(frameHeight) relheight = "" # Position frame relative to clipper. self.innerframe.place(y=-self._startY, relheight=relheight) return (self._startY / float(frameHeight), endScrollY) # According to the relative geometries of the frame and the # clipper, reposition the frame within the clipper and reset the # scrollbars. def _scrollBothNow(self): self._scrollTimer = None # Call update_idletasks to make sure that the containing frame # has been resized before we attempt to set the scrollbars. # Otherwise the scrollbars may be mapped/unmapped continuously. self._scrollRecurse = self._scrollRecurse + 1 self.update_idletasks() self._scrollRecurse = self._scrollRecurse - 1 if self._scrollRecurse != 0: return xview = self._getxview() yview = self._getyview() self.hsb.set(xview[0], xview[1]) self.vsb.set(yview[0], yview[1]) require_hsb = self.scrolltype in (self.BOTH, self.HORIZONTAL) self.hsbNeeded = (xview != (0.0, 1.0)) and require_hsb require_vsb = self.scrolltype in (self.BOTH, self.VERTICAL) self.vsbNeeded = (yview != (0.0, 1.0)) and require_vsb # If both horizontal and vertical scrollmodes are dynamic and # currently only one scrollbar is mapped and both should be # toggled, then unmap the mapped scrollbar. This prevents a # continuous mapping and unmapping of the scrollbars. if ( self.hsbNeeded != self.hsbOn and self.vsbNeeded != self.vsbOn and self.vsbOn != self.hsbOn ): if self.hsbOn: self._toggleHorizScrollbar() else: self._toggleVertScrollbar() return if self.hsbNeeded != self.hsbOn: self._toggleHorizScrollbar() if self.vsbNeeded != self.vsbOn: self._toggleVertScrollbar() def _toggleHorizScrollbar(self): self.hsbOn = not self.hsbOn # interior = self #.origInterior if self.hsbOn: self.hsb.grid(row=1, column=0, sticky=tk.EW) # interior.grid_rowconfigure(3, minsize = self['scrollmargin']) else: self.hsb.grid_forget() # interior.grid_rowconfigure(3, minsize = 0) def _toggleVertScrollbar(self): self.vsbOn = not self.vsbOn # interior = self#.origInterior if self.vsbOn: self.vsb.grid(row=0, column=1, sticky=tk.NS) # interior.grid_columnconfigure(3, minsize = self['scrollmargin']) else: self.vsb.grid_forget() # interior.grid_columnconfigure(3, minsize = 0) def configure(self, cnf=None, **kw): if cnf: return super().configure(cnf, **kw) key = "usemousewheel" if key in kw: self.usemousewheel = tk.getboolean(kw.pop(key)) self._configure_mousewheel() return self._framecls.configure(self, cnf, **kw) config = configure def cget(self, key): option = "usemousewheel" if key == option: return self.usemousewheel # return super(ScrolledFrameBase, self).cget(key) return self._framecls.cget(self, key) __getitem__ = cget def _configure_mousewheel(self): if self.usemousewheel: BindManager.init_mousewheel_binding(self) if self.hsb and not hasattr(self.hsb, "on_mousewheel"): self.hsb.on_mousewheel = BindManager.make_onmousewheel_cb( self, "x", 2 ) if self.vsb and not hasattr(self.vsb, "on_mousewheel"): self.vsb.on_mousewheel = BindManager.make_onmousewheel_cb( self, "y", 2 ) main_sb = self.vsb or self.hsb if main_sb: self.on_mousewheel = main_sb.on_mousewheel BindManager.mousewheel_bind(self) for s in (self.vsb, self.hsb): if s: BindManager.mousewheel_bind(s) else: for w in (self, self.vsb, self.hsb): BindManager.mousewheel_unbind(w) class TkScrolledFrame(ScrolledFrameBase, tk.Frame): ... pygubu-0.36.1/src/pygubu/widgets/ttkspinbox.py000066400000000000000000000005411474524032100214450ustar00rootroot00000000000000# encoding: utf-8 import tkinter.ttk as ttk class Spinbox(ttk.Entry): def __init__(self, master=None, **kw): ttk.Entry.__init__(self, master, "ttk::spinbox", **kw) def current(self, newindex=None): return self.tk.call(self._w, "current", newindex) def set(self, value): return self.tk.call(self._w, "set", value) pygubu-0.36.1/tests/000077500000000000000000000000001474524032100142605ustar00rootroot00000000000000pygubu-0.36.1/tests/fixpath.py000066400000000000000000000003311474524032100162720ustar00rootroot00000000000000import os import sys import pathlib import importlib test_dir = pathlib.Path(sys.argv[0]).parent.resolve() pygubu_src = str(test_dir.parent / "src") if pygubu_src not in sys.path: sys.path.insert(0, pygubu_src) pygubu-0.36.1/tests/images/000077500000000000000000000000001474524032100155255ustar00rootroot00000000000000pygubu-0.36.1/tests/images/example.gif000066400000000000000000000000751474524032100176510ustar00rootroot00000000000000GIF89a ! , k˔{p{DQ;pygubu-0.36.1/tests/support.py000066400000000000000000000057551474524032100163620ustar00rootroot00000000000000# encoding: utf-8 # Copyright © 2001-2013 Python Software Foundation; All Rights Reserved import sys import tkinter import unittest _tk_unavailable = None def check_tk_availability(): """Check that Tk is installed and available.""" global _tk_unavailable if _tk_unavailable is None: _tk_unavailable = False if sys.platform == "darwin": # The Aqua Tk implementations on OS X can abort the process if # being called in an environment where a window server connection # cannot be made, for instance when invoked by a buildbot or ssh # process not running under the same user id as the current console # user. To avoid that, raise an exception if the window manager # connection is not available. from ctypes import cdll, c_int, pointer, Structure from ctypes.util import find_library app_services = cdll.LoadLibrary(find_library("ApplicationServices")) if app_services.CGMainDisplayID() == 0: _tk_unavailable = "cannot run without OS X window manager" else: class ProcessSerialNumber(Structure): _fields_ = [ ("highLongOfPSN", c_int), ("lowLongOfPSN", c_int), ] psn = ProcessSerialNumber() psn_p = pointer(psn) if (app_services.GetCurrentProcess(psn_p) < 0) or ( app_services.SetFrontProcess(psn_p) < 0 ): _tk_unavailable = "cannot run without OS X gui process" else: # not OS X try: import tkinter except ModuleNotFoundError: import Tkinter as tkinter try: tkinter.Button() except tkinter.TclError as msg: # assuming tk is not available _tk_unavailable = "tk not available: %s" % msg if _tk_unavailable: raise unittest.SkipTest(_tk_unavailable) return def get_tk_root(): check_tk_availability() # raise exception if tk unavailable try: root = tkinter._default_root except AttributeError: # it is possible to disable default root in Tkinter, although # I haven't seen people doing it (but apparently someone did it # here). root = None if root is None: # create a new master only if there isn't one already root = tkinter.Tk() return root def root_deiconify(): root = get_tk_root() root.deiconify() def root_withdraw(): root = get_tk_root() root.withdraw() def simulate_mouse_click(widget, x, y): """Generate proper events to click at the x, y position (tries to act like an X server).""" widget.event_generate("", x=0, y=0) widget.event_generate("", x=x, y=y) widget.event_generate("", x=x, y=y) widget.event_generate("", x=x, y=y) pygubu-0.36.1/tests/test_builder_forget_unnamed.py000066400000000000000000000014161474524032100223760ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import support import pygubu class TestBuilder01(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_builder_forget_unnamed.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("fmain") builder.connect_callbacks({}) builder.forget_unnamed() def tearDown(self): support.root_withdraw() def test_builder_forget_unnamed(self): expected = ["fmain", "mylabel1", "mylabel2"] result = list(self.builder.objects.keys()) self.assertEqual(expected, result) self.widget.destroy() pygubu-0.36.1/tests/test_builder_forget_unnamed.ui000066400000000000000000000026071474524032100223660ustar00rootroot00000000000000 200 200 0 0 Test builder forget unnamed top mylabel1 top mylabel2 top unnamed label top pygubu-0.36.1/tests/test_builderfont.py000066400000000000000000000021551474524032100202110ustar00rootroot00000000000000# encoding: utf8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import tkinter.font as tkfont import fixpath import pygubu import support class TestBuilderFont(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_builderfont.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.font = font = tkfont.Font( family="Helvetica", size=32, weight="bold" ) builder.set_font("custom_font", font) self.widget = builder.get_object("mainwindow") def tearDown(self): support.root_withdraw() @unittest.skip("Not implemented yet") def test_get_font(self): font = self.builder.get_font("custom_font") self.assertEqual(self.font, font) self.widget.destroy() @unittest.skip("Not implemented yet") def test_get_font_notset(self): fname = "other_font" font = self.builder.get_font(fname) self.assertEqual(fname, font) self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_builderfont.ui000066400000000000000000000017611474524032100202000ustar00rootroot00000000000000 200 200 200 200 0 True 0 {custom_font} 12 {} Font Test 0 True 0 pygubu-0.36.1/tests/test_button.py000066400000000000000000000066211474524032100172110ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestButton(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = """ 250 250 0 nsew True 0 CustomButton on_button_click right CustomButton.TButton Button Label button_var 0 True 0 """ self.builder = builder = pygubu.Builder() builder.add_from_string(xmldata) self.widget = builder.get_object("testbutton") self.is_style_setup = False if self.is_style_setup: self.is_style_setup = True s = ttk.Style() s.configure("CustomButton.TButton", color="Blue") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Button) self.widget.destroy() def test_class_(self): tclobj = self.widget.cget("class") class_ = str(tclobj) self.assertEqual("CustomButton", class_) self.widget.destroy() def test_style(self): tclobj = self.widget.cget("style") style = str(tclobj) self.assertEqual("CustomButton.TButton", style) self.widget.destroy() def test_command_dict(self): success = [] def on_button_click(): success.append(1) cbdic = {"on_button_click": on_button_click} self.builder.connect_callbacks(cbdic) self.widget.invoke() self.assertTrue(success) self.widget.destroy() def test_command_self(self): success = [] class AnObject: def on_button_click(self): success.append(1) cbobj = AnObject() self.builder.connect_callbacks(cbobj) self.widget.invoke() self.assertTrue(success) self.widget.destroy() def test_compound(self): compound = str(self.widget.cget("compound")) self.assertEqual("right", compound) self.widget.destroy() def test_btn_text(self): txt = self.widget.cget("text") self.assertEqual("Button Label", txt) self.widget.destroy() def test_btn_variable(self): var = self.builder.get_variable("button_var") self.assertIsInstance(var, tk.StringVar) self.assertEqual("Button Label", var.get()) newlabel = "Label Changed" var.set(newlabel) self.assertEqual(newlabel, self.widget.cget("text")) self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_button_command.py000066400000000000000000000040231474524032100207010ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestButtonCommand(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_button_command.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") self.button1 = builder.get_object("button1") self.button2 = builder.get_object("button2") def tearDown(self): support.root_withdraw() def test_command_simple(self): success = [] class AnObject: def button1_clicked(self): success.append(1) def on_button_clicked(self): pass cbobj = AnObject() self.builder.connect_callbacks(cbobj) self.button1.invoke() self.widget.update() self.assertTrue(success) self.widget.destroy() def test_command_with_widgetid(self): success = [] received_id = [] class AnObject: def button1_clicked(self): pass def on_button_clicked(self, widgetid): success.append(1) received_id.append(widgetid) cbobj = AnObject() self.builder.connect_callbacks(cbobj) self.button2.invoke() self.widget.update() self.assertTrue(success) self.assertEqual(received_id[0], "button2") self.widget.destroy() # def test_command_generate_event(self): # success = [] # class AnObject: # def button1_clicked(self): # pass # def on_button_clicked(self): # pass # def catch_button_event(self, event): # success.append(1) # cbobj = AnObject() # self.button3.bind('<>', cbobj.catch_button_event) # self.builder.connect_callbacks(cbobj) # self.button3.invoke() # self.widget.update() # self.assertTrue(success) # self.widget.destroy() pygubu-0.36.1/tests/test_button_command.ui000066400000000000000000000022501474524032100206660ustar00rootroot00000000000000 200 200 0 True 0 button1_clicked button1 True top on_button_clicked button2 True top pygubu-0.36.1/tests/test_calendarframe.py000066400000000000000000000043241474524032100204600ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import datetime import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support from pygubu.widgets.calendarframe import CalendarFrame class TestCalendarFrame(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_calendarframe.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.mainwindow = builder.get_object("mainwindow") self.widget = builder.get_object("calendar") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, CalendarFrame) self.widget.destroy() def test_selection(self): self.assertEqual(None, self.widget.selection) testdate = datetime.datetime(1980, 9, 15) self.widget.select_day(15, 9, 1980) self.assertEqual(testdate, self.widget.selection) self.widget.destroy() def test_select_day(self): testdate = datetime.datetime(1980, 9, 15) self.widget.select_day(15, 9, 1980) self.assertEqual(testdate, self.widget.selection) self.widget.destroy() def test_event_CalendarDateSelected(self): success = [] class AnObject: def on_date_selected(self, event=None): success.append(1) cbobj = AnObject() self.builder.connect_callbacks(cbobj) self.widget.select_day(15, 9, 1980) self.widget.update() self.assertTrue(success) self.widget.destroy() def test_uiset_options(self): widget = self.widget self.assertEqual("#f0e3f0", widget["calendarbg"]) self.assertEqual("#0000a1", widget["calendarfg"]) self.assertEqual("#abb8f0", widget["headerbg"]) self.assertEqual("#ffffff", widget["headerfg"]) self.assertEqual("#f06ba4", widget["markbg"]) self.assertEqual("#ffffff", widget["markfg"]) self.assertEqual("#d926ff", widget["selectbg"]) self.assertEqual("#ffff00", widget["selectfg"]) self.assertEqual("9", str(widget["month"])) self.assertEqual("2000", str(widget["year"])) self.widget.destroy() pygubu-0.36.1/tests/test_calendarframe.ui000066400000000000000000000024601474524032100204440ustar00rootroot00000000000000 200 200 0 True 0 #f0e3f0 #0000a1 6 #abb8f0 #ffffff #f06ba4 #ffffff 9 #d926ff #ffff00 2000 0 True 0 pygubu-0.36.1/tests/test_canvas.py000066400000000000000000000011771474524032100171520ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestCanvas(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_canvas.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainframe") self.canvas = builder.get_object("Canvas_1") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.canvas, tk.Canvas) self.widget.destroy() pygubu-0.36.1/tests/test_canvas.ui000066400000000000000000000020401474524032100171250ustar00rootroot00000000000000 200 2 200 0 True 0 nesw 1 1 #ffffff 0 0 True 0 pygubu-0.36.1/tests/test_command_id_arg.py000066400000000000000000000017731474524032100206240ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestCommandIdArg(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_command_id_arg.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") self.menu = builder.get_object("menu1") def tearDown(self): support.root_withdraw() def test_idtocommand(self): success = [] received_id = [] class AnObject: def menu_item_clicked(self, itemid): success.append(1) received_id.append(itemid) cbobj = AnObject() self.builder.connect_callbacks(cbobj) self.menu.invoke(0) self.widget.update() self.assertTrue(success) self.assertEqual(received_id[0], "menu_item_1") self.widget.destroy() pygubu-0.36.1/tests/test_command_id_arg.ui000066400000000000000000000021511474524032100206000ustar00rootroot00000000000000 200 200 True menubutton_1 True top false menu_item_clicked true command1 pygubu-0.36.1/tests/test_custom_widget.py000066400000000000000000000013111474524032100205420ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestCustomWidget(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_custom_widget.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") self.custom_widget = builder.get_object("custom_widget") def tearDown(self): support.root_withdraw() def test_loading(self): message = self.custom_widget.get_message() self.assertEqual(message, "CustomLabel") self.widget.destroy() pygubu-0.36.1/tests/test_custom_widget.ui000066400000000000000000000010641474524032100205340ustar00rootroot00000000000000 200 200 0 0 top pygubu-0.36.1/tests/test_custom_widget_module.py000066400000000000000000000006131474524032100221130ustar00rootroot00000000000000import tkinter.ttk as ttk from pygubu.api.v1 import BuilderObject, register_widget class CustomLabel(ttk.Label): def get_message(self): return "CustomLabel" class TestCustomWidgetBuilder(BuilderObject): class_ = CustomLabel register_widget( "test_custom_widget_module.custom_label", TestCustomWidgetBuilder, "CustomWidget", ("ttk", "Test Custom Widget"), ) pygubu-0.36.1/tests/test_dialog.py000066400000000000000000000011761474524032100171350ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestDialog(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_dialog.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mydialog") self.dialog = self.widget def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.dialog, pygubu.widgets.dialog.Dialog) self.widget.destroy() pygubu-0.36.1/tests/test_dialog.ui000066400000000000000000000123421474524032100171170ustar00rootroot00000000000000 320x240 100 640|480 false 200 2 200 200 0 True 0 nsew 1 1 200 200 0 True 0 ew Enter some text: 0 True 0 ew 2 200 200 0 True 1 ew 10 0 1 Input: 0 True 0 ew 1 True 0 ew 2 200 200 0 True 2 e 20 0 Cancel 0 True 0 Ok 1 True 0 pygubu-0.36.1/tests/test_entry.py000066400000000000000000000117301474524032100170340ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestEntry(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = """ 250 250 0 nsew True 0 center MyEntryStyle.TEntry entry_var key entry_validate Hello %d %P entry_invalid %P 0 True 0 """ self.builder = builder = pygubu.Builder() builder.add_from_string(xmldata) self.widget = builder.get_object("entry") self.is_style_setup = False if self.is_style_setup: self.is_style_setup = True s = ttk.Style() s.configure("MyEntryStyle.TEntry", color="Blue") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Entry) self.widget.destroy() # def test_class_(self): # tclobj = self.widget.cget('class') # class_ = str(tclobj) # self.assertEqual('MyEntry', class_) # self.widget.destroy() def test_style(self): stylename = "MyEntryStyle.TEntry" tclobj = self.widget.cget("style") style = str(tclobj) self.assertEqual(stylename, style) self.widget.destroy() def test_text(self): txt = self.widget.get() self.assertEqual("Hello", txt) self.widget.destroy() def test_variable(self): varname = "entry_var" var = self.builder.get_variable(varname) self.assertIsInstance(var, tk.StringVar) self.assertEqual("Hello", var.get()) newlabel = "Changed" var.set(newlabel) self.assertEqual(newlabel, self.widget.get()) self.widget.destroy() def test_validate_command(self): valid_values = ("Valid value1", "Valid value2") def entry_validate(action, newvalue): valid = False if action == "1": # 1: insert 0: delete if newvalue in valid_values: valid = True else: valid = True return valid def entry_invalid(newvalue): pass callback = { "entry_validate": entry_validate, "entry_invalid": entry_invalid, } self.builder.connect_callbacks(callback) self.widget.delete("0", tk.END) self.assertEqual("", self.widget.get()) self.widget.insert("0", valid_values[0]) self.assertEqual(valid_values[0], self.widget.get()) self.widget.delete("0", tk.END) self.widget.insert("0", "Invalid value") self.assertEqual("", self.widget.get()) self.widget.destroy() def test_invalid_command(self): invalid_text = [] def entry_validate(action, newvalue): valid = False if action == "1": # 1: insert 0: delete if newvalue == "Allowed": valid = True else: valid = True return valid def entry_invalid(newvalue): invalid_text.append(newvalue) callback = { "entry_validate": entry_validate, "entry_invalid": entry_invalid, } self.widget.delete("0", tk.END) self.builder.connect_callbacks(callback) self.widget.insert("0", "Not Allowed") self.assertEqual(invalid_text[0], "Not Allowed") self.widget.destroy() def test_justify(self): prop = "justify" expected_value = "center" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_validate(self): prop = "validate" expected_value = "key" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_entry_commands.py000066400000000000000000000043441474524032100207200ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestEntryCommands(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = xmldata = "test_entry_commands.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("entry") def tearDown(self): support.root_withdraw() def test_validate_command(self): valid_values = ("Valid value1", "Valid value2") def entry_validate(action, newvalue): valid = False if action == "1": # 1: insert 0: delete if newvalue in valid_values: valid = True else: valid = True return valid def entry_invalid(newvalue): pass callback = { "entry_validate": entry_validate, "entry_invalid": entry_invalid, } self.builder.connect_callbacks(callback) self.widget.delete("0", tk.END) self.assertEqual("", self.widget.get()) self.widget.insert("0", valid_values[0]) self.assertEqual(valid_values[0], self.widget.get()) self.widget.delete("0", tk.END) self.widget.insert("0", "Invalid value") self.assertEqual("", self.widget.get()) self.widget.destroy() def test_invalid_command(self): invalid_text = [] def entry_validate(action, newvalue): valid = False if action == "1": # 1: insert 0: delete if newvalue == "Allowed": valid = True else: valid = True return valid def entry_invalid(newvalue): invalid_text.append(newvalue) callback = { "entry_validate": entry_validate, "entry_invalid": entry_invalid, } self.widget.delete("0", tk.END) self.builder.connect_callbacks(callback) self.widget.insert("0", "Not Allowed") self.assertEqual(invalid_text[0], "Not Allowed") self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_entry_commands.ui000066400000000000000000000020311474524032100206740ustar00rootroot00000000000000 250 250 entry_invalid center MyEntryStyle.TEntry Hello entry_var key entry_validate 0 True 0 pygubu-0.36.1/tests/test_frame.py000066400000000000000000000101571474524032100167670ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestFrame(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_frame.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Frame) self.widget.destroy() def test_padding(self): tclobj = self.widget.cget("padding")[0] padding = str(tclobj) self.assertEqual("10", padding) self.widget.destroy() def test_width(self): tclobj = self.widget.cget("width") width = str(tclobj) self.assertEqual("250", width) self.widget.destroy() def test_class_(self): tclobj = self.widget.cget("class") class_ = str(tclobj) self.assertEqual("MyCustomFrame", class_) self.widget.destroy() def test_relief(self): tclobj = self.widget.cget("relief") relief = str(tclobj) self.assertEqual(tk.SUNKEN, relief) self.widget.destroy() def test_style(self): tclobj = self.widget.cget("style") style = str(tclobj) self.assertEqual("MyFrameStyle.TFrame", style) self.widget.destroy() def test_takefocus(self): tclobj = self.widget.cget("takefocus") takefocus = str(tclobj) self.assertEqual("1", takefocus) self.widget.destroy() def test_cursor(self): tclobj = self.widget.cget("cursor") cursor = str(tclobj) self.assertEqual("cross", cursor) self.widget.destroy() def test_layout(self): ginfo = self.widget.grid_info() expected = [ ("row", "0"), ("column", "0"), ("sticky", "nesw"), ("pady", "10"), ("padx", "5"), ("ipadx", "2"), ("ipady", "4"), ("rowspan", "1"), ("columnspan", "2"), ] for k, ev in expected: value = str(ginfo[k]) self.assertEqual(value, ev) # FIX TEST: since interface v1.1 propagate is applied to parent? NO! propagate = self.widget.grid_propagate() self.assertEqual(None, propagate) self.widget.destroy() def test_child_count(self): count = len(self.widget.children) self.assertEqual(1, count) self.widget.destroy() def test_binding_dict(self): success = [] def on_button_click(event): success.append(1) def on_button_click2(event): success.append(1) cbdic = { "on_button_click": on_button_click, "on_button_click2": on_button_click2, } self.builder.connect_callbacks(cbdic) support.simulate_mouse_click(self.widget, 5, 5) self.widget.update_idletasks() self.assertTrue(success) self.widget.destroy() def test_binding_object(self): success = [] class AnObject: def on_button_click(self, event): success.append(1) def on_button_click2(self, event): success.append(1) cbobj = AnObject() self.builder.connect_callbacks(cbobj) support.simulate_mouse_click(self.widget, 5, 5) self.widget.update_idletasks() self.assertTrue(success) self.widget.destroy() def test_binding_add(self): success = [] def on_button_click(event): success.append(1) def on_button_click2(event): success.append(1) cbdic = { "on_button_click": on_button_click, "on_button_click2": on_button_click2, } self.builder.connect_callbacks(cbdic) support.simulate_mouse_click(self.widget, 5, 5) self.widget.update_idletasks() self.assertTrue(len(success) == 2) self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_frame.ui000066400000000000000000000030631474524032100167520ustar00rootroot00000000000000 250 10 250 MyCustomFrame sunken MyFrameStyle.TFrame 1 cross 0 0 nesw 10 5 False 4 2 1 2 label 0 True 1 pygubu-0.36.1/tests/test_idtocommand.py000066400000000000000000000016021474524032100201660ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestIdtocommand(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_idtocommand.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") self.button = builder.get_object("button1") def tearDown(self): support.root_withdraw() def test_idtocommand(self): success = [] class AnObject: def on_button_clicked(self, widgetid): success.append(1) cbobj = AnObject() self.builder.connect_callbacks(cbobj) self.button.invoke() self.widget.update() self.assertTrue(success) self.widget.destroy() pygubu-0.36.1/tests/test_idtocommand.ui000066400000000000000000000013341474524032100201550ustar00rootroot00000000000000 200 200 True on_button_clicked true button1 True top pygubu-0.36.1/tests/test_import_variables.py000066400000000000000000000016171474524032100212400ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class MyContainer(object): pass class TestText(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_import_variables.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") def tearDown(self): support.root_withdraw() def test_import_variables(self): container = MyContainer() self.builder.import_variables(container) self.assertIsInstance(container.myvar_string, tk.StringVar) self.assertIsInstance(container.myvar_int, tk.IntVar) self.assertIsInstance(container.myvar_double, tk.DoubleVar) self.assertIsInstance(container.myvar_boolean, tk.BooleanVar) pygubu-0.36.1/tests/test_import_variables.ui000066400000000000000000000032451474524032100212240ustar00rootroot00000000000000 200 200 0 True 0 string:myvar_string 0 True 0 int:myvar_int 0 True 1 double:myvar_double 0 True 2 boolean:myvar_boolean 0 True 3 pygubu-0.36.1/tests/test_label.py000066400000000000000000000107451474524032100167570ustar00rootroot00000000000000# encoding: utf-8 import support import pygubu import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath class TestEntry(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = """ 250 250 0 nsew True 0 e #94f900 2 right #690400 2 ridge -- A Label -- label_var 20 right 0 True 0 """ self.builder = builder = pygubu.Builder() builder.add_from_string(xmldata) self.widget = builder.get_object("label") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Label) self.widget.destroy() def test_text(self): prop = "text" expected_value = "-- A Label --" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_textvariable(self): varname = "label_var" expected_value = "-- A Label --" var = self.builder.get_variable(varname) self.assertIsInstance(var, tk.StringVar) self.assertEqual(expected_value, var.get()) newlabel = "Changed" var.set(newlabel) self.assertEqual(newlabel, self.widget.cget("text")) self.widget.destroy() def test_justify(self): prop = "justify" expected_value = "right" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_anchor(self): prop = "anchor" expected_value = "e" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_background(self): prop = "background" expected_value = "#94f900" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_borderwidth(self): prop = "borderwidth" expected_value = "2" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_compound(self): prop = "compound" expected_value = "right" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_foreground(self): prop = "foreground" expected_value = "#690400" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_padding(self): prop = "padding" expected_value = "2" tclobj = self.widget.cget(prop)[0] value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_relief(self): prop = "relief" expected_value = "ridge" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_width(self): prop = "width" expected_value = "20" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_labelframe.py000066400000000000000000000011301474524032100177560ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestLabelframe(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_labelframe.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("Labelframe") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Labelframe) self.widget.destroy() pygubu-0.36.1/tests/test_labelframe.ui000066400000000000000000000017531474524032100177560ustar00rootroot00000000000000 200 200 0 True 0 nesw 4 200 groove ttk.Labelframe 200 0 True 0 nesw pygubu-0.36.1/tests/test_menu.py000066400000000000000000000012561474524032100166410ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestMenu(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_menu.ui" self.builder = builder = pygubu.Builder() filepath = os.path.dirname(os.path.realpath(__file__)) builder.add_resource_path(filepath) builder.add_from_file(xmldata) self.widget = builder.get_object("mainmenu") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, tk.Menu) self.widget.destroy() pygubu-0.36.1/tests/test_menu.ui000066400000000000000000000034261474524032100166270ustar00rootroot00000000000000 #d9cc93 2 Helvetica 12 #080060 flat info left Submenu_1 False Command_1 False Checkbutton_1 False Radiobutton_1 False example.gif Command_2 pygubu-0.36.1/tests/test_menu_commands.py000066400000000000000000000063541474524032100205260ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestMenu(unittest.TestCase): def setUp(self): self.root = support.get_tk_root() support.root_deiconify() xmldata = "test_menu_commands.ui" self.builder = builder = pygubu.Builder() filepath = os.path.dirname(os.path.realpath(__file__)) builder.add_resource_path(filepath) builder.add_from_file(xmldata) self.widget = builder.get_object("mainmenu") self.menu1 = builder.get_object("menu1") self.root["menu"] = self.widget def tearDown(self): support.root_withdraw() self.root["menu"] = None def test_tearoff_command(self): success = [] class AnObject: def on_menu_tearoff(self, menu, tearoff): success.append(1) def on_menu_post(self): pass def button1_cb(self): pass def chkbutton_cb(self, widget_id): pass cbobj = AnObject() self.builder.connect_callbacks(cbobj) # Simulate user clicking on tearoff menu self.menu1.invoke(0) self.widget.update() # validate test self.assertTrue(success) self.widget.destroy() def test_post_command(self): success = [] class AnObject: def on_menu_tearoff(self, menu, tearoff): pass def on_menu_post(self): success.append(1) def button1_cb(self): pass def chkbutton_cb(self, widget_id): pass cbobj = AnObject() self.builder.connect_callbacks(cbobj) # Simulate user clicking on menu self.menu1.post(0, 0) self.widget.update() # validate test self.assertTrue(success) self.widget.destroy() def test_button_command(self): success = [] class AnObject: def on_menu_tearoff(self, menu, tearoff): pass def on_menu_post(self): pass def button1_cb(self): success.append(1) def chkbutton_cb(self, widget_id): pass cbobj = AnObject() self.builder.connect_callbacks(cbobj) # Simulate user clicking on menu item self.menu1.invoke(1) self.widget.update() # validate test self.assertTrue(success) self.widget.destroy() def test_button_command_with_widget_id(self): success = [] class AnObject: def on_menu_tearoff(self, menu, tearoff): pass def on_menu_post(self): pass def button1_cb(self): pass def chkbutton_cb(self, widget_id): success.append(widget_id) cbobj = AnObject() self.builder.connect_callbacks(cbobj) # Simulate user clicking on menu item self.menu1.invoke(2) self.widget.update() # validate test self.assertTrue(success) wid = success[0] self.assertEqual(wid, "mchb1") self.widget.destroy() pygubu-0.36.1/tests/test_menu_commands.ui000066400000000000000000000037421474524032100205110ustar00rootroot00000000000000 #d9cc93 2 Helvetica 12 #080060 flat false info left Submenu_1 on_menu_post true on_menu_tearoff button1_cb Command_1 chkbutton_cb Checkbutton_1 Radiobutton_1 example.gif Command_2 pygubu-0.36.1/tests/test_notebook.py000066400000000000000000000011201474524032100175030ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestNotebook(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_notebook.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("Notebook") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Notebook) self.widget.destroy() pygubu-0.36.1/tests/test_notebook.ui000066400000000000000000000036241474524032100175030ustar00rootroot00000000000000 200 200 0 True 0 nesw Tab_1 #d9c6d9 center Notebook test Tab 1 200 0 True 0 ew Tab_2 #80d4d9 center Notebook test Tab 2 200 0 True 0 ew pygubu-0.36.1/tests/test_optionmenu.py000066400000000000000000000015211474524032100200650ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestOptionMenu(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_optionmenu.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") def tearDown(self): support.root_withdraw() def test_class(self): optionmenu = self.builder.get_object("optionmenu1") self.assertIsInstance(optionmenu, tk.OptionMenu) self.widget.destroy() def test_no_variable_defined(self): optionmenu2 = self.builder.get_object("optionmenu2") self.assertIsInstance(optionmenu2, tk.OptionMenu) self.widget.destroy() pygubu-0.36.1/tests/test_optionmenu.ui000066400000000000000000000027131474524032100200560ustar00rootroot00000000000000 200 200 0 True 0 nesw om_command default option1,option2,option3 string:om_var 0 True 0 om_command option1 option1,option2,option3 0 True 1 pygubu-0.36.1/tests/test_panedwindow.py000066400000000000000000000013761474524032100202170ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestPanedwindow(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_panedwindow.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainframe") self.pw1 = builder.get_object("Panedwindow_1") self.pw2 = builder.get_object("Panedwindow_2") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.pw1, ttk.Panedwindow) self.assertIsInstance(self.pw2, ttk.Panedwindow) self.widget.destroy() pygubu-0.36.1/tests/test_panedwindow.ui000066400000000000000000000103651474524032100202020ustar00rootroot00000000000000 200 200 0 True 0 nesw 1 1 1 200 horizontal 200 0 True 0 nesw 1 #d9c8d9 Pane 1 0 True 0 nesw 1 #d99bd9 Pane 2 0 True 0 nesw 200 vertical 200 0 True 1 nesw 2 #d9d9b3 Pane 3 0 True 0 1 #d9d984 Pane 4 0 True 0 pygubu-0.36.1/tests/test_pathchooserinput.py000066400000000000000000000040071474524032100212710ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support from pygubu.widgets.pathchooserinput import PathChooserInput class TestPathChooserInput(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_pathchooserinput.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.mainwindow = builder.get_object("mainwindow") self.widget = builder.get_object("pathchooserinput1") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, PathChooserInput) self.widget.destroy() def test_path_changed_event(self): success = [] def on_path_changed(event=None): success.append(1) bag = {"on_path_changed": on_path_changed} self.builder.connect_callbacks(bag) self.widget.configure(path="/new/path") self.assertTrue(success) self.widget.destroy() def test_type(self): itype = str(self.widget.cget("type")) self.assertEqual("file", itype) self.widget.destroy() def test_path(self): path = str(self.widget.cget("path")) self.assertEqual("/home/user", path) self.widget.destroy() def test_path_dictionary_like(self): path = str(self.widget["path"]) self.assertEqual("/home/user", path) self.widget.destroy() def test_state(self): # allowed states normal/disabled/readonly # normal state = str(self.widget.cget("state")) self.assertEqual("normal", state) # disabled self.widget.config(state="disabled") state = str(self.widget.cget("state")) self.assertEqual("disabled", state) # readonly self.widget.config(state="readonly") state = str(self.widget.cget("state")) self.assertEqual("readonly", state) self.widget.destroy() pygubu-0.36.1/tests/test_pathchooserinput.ui000066400000000000000000000015451474524032100212620ustar00rootroot00000000000000 200 200 0 True 0 /home/user file 0 True 0 pygubu-0.36.1/tests/test_plugin_tkcalendar.py000066400000000000000000000027621474524032100213660ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support from unittest.case import SkipTest has_tkcalendar = True try: from tkcalendar import Calendar, DateEntry except ImportError: has_tkcalendar = False class TestTkcalendarCalendar(unittest.TestCase): def setUp(self): if not has_tkcalendar: raise SkipTest("tkcalendar not installed") support.root_deiconify() xmldata = "test_plugin_tkcalendar.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("frame1") self.calendar = builder.get_object("calendar") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.calendar, Calendar) self.widget.destroy() class TestTkcalendarDateEntry(unittest.TestCase): def setUp(self): if not has_tkcalendar: raise SkipTest("tkcalendar not installed") support.root_deiconify() xmldata = "test_plugin_tkcalendar.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("frame2") self.entry = builder.get_object("dateentry") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.entry, DateEntry) self.widget.destroy() pygubu-0.36.1/tests/test_plugin_tkcalendar.ui000066400000000000000000000020741474524032100213470ustar00rootroot00000000000000 200 200 0 0 0 0 200 200 0 0 0 0 pygubu-0.36.1/tests/test_scrollbarhelper.py000066400000000000000000000072301474524032100210560ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support import pygubu.widgets.scrollbarhelper import pygubu.widgets.tkscrollbarhelper class TestScrollbarHelper(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = """ both 5 5 5 5 raised 0 True 0 #d9d900 0 0 10i 10i 0 True 0 """ builder = pygubu.Builder() builder.add_from_string(xmldata) self.widget = builder.get_object("scrollbarhelper") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance( self.widget, pygubu.widgets.scrollbarhelper.ScrollbarHelper ) self.widget.destroy() def test_padding(self): expected_value = ("5", "5", "5", "5") tclobj = self.widget.cget("padding") padding = ( str(tclobj[0]), str(tclobj[1]), str(tclobj[2]), str(tclobj[3]), ) self.assertEqual(expected_value, padding) self.widget.destroy() class TestTkScrollbarHelper(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = """ both 5 10 raised 0 True 0 #d9d900 0 0 10i 10i 0 True 0 """ builder = pygubu.Builder() builder.add_from_string(xmldata) self.widget = builder.get_object("scrollbarhelper") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance( self.widget, pygubu.widgets.tkscrollbarhelper.TkScrollbarHelper ) self.widget.destroy() def test_padx(self): prop = "padx" expected_value = "5" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_pady(self): prop = "pady" expected_value = "10" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_scrolledframe.py000066400000000000000000000012331474524032100205120ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support from pygubu.widgets.scrolledframe import ScrolledFrame class TestTtkScrolledframe(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_scrolledframe.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("scrolledframe") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ScrolledFrame) self.widget.destroy() pygubu-0.36.1/tests/test_scrolledframe.ui000066400000000000000000000020551474524032100205020ustar00rootroot00000000000000 both true True True top True top True top pygubu-0.36.1/tests/test_text.py000066400000000000000000000011641474524032100166570ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestText(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_text.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainframe") self.text = builder.get_object("Text_1") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.text, tk.Text) self.widget.destroy() pygubu-0.36.1/tests/test_text.ui000066400000000000000000000037511474524032100166500ustar00rootroot00000000000000 200 2 200 0 True 0 nesw 1 1 #00004e Verdana 8 #ffffff 10 #ffffe3 3 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus imperdiet metus ante, eget vehicula arcu malesuada ut. In aliquam pulvinar metus. Maecenas semper erat orci, id porta metus ullamcorper vel. Suspendisse potenti. Ut et aliquam est. Morbi tempor elementum metus vel hendrerit. Phasellus tincidunt vitae lacus non pulvinar. Aliquam faucibus nunc dui, non sagittis dolor elementum eget. Donec nec imperdiet neque, a congue leo. Proin non quam est. Aliquam vel dolor non arcu porttitor posuere eu a lorem. Nulla ut enim sed elit ultrices sagittis. 50 char 0 True 0 nesw pygubu-0.36.1/tests/test_text_issue58.py000066400000000000000000000015661474524032100202520ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support expected_text = """In the designer: - Add a text widget - Set state to disabled - Enter content in the text property - Text content should be set by pygubu-designer. """ class TestText(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_text_issue58.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainframe") self.text = builder.get_object("Text_1") def tearDown(self): support.root_withdraw() def test_set_text_in_disabled_status(self): text_content = self.text.get("0.0", tk.END) self.assertEqual(text_content, expected_text) self.widget.destroy() pygubu-0.36.1/tests/test_text_issue58.ui000066400000000000000000000017201474524032100202270ustar00rootroot00000000000000 200 200 0 True 0 10 disabled In the designer: - Add a text widget - Set state to disabled - Enter content in the text property - Text content should be set by pygubu-designer. 50 0 True 0 pygubu-0.36.1/tests/test_tkinterscrolledtext.py000066400000000000000000000012711474524032100220070ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk from tkinter.scrolledtext import ScrolledText import fixpath import pygubu import support class TestText(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_tkinterscrolledtext.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainframe") self.text = builder.get_object("scrolledtext_1") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.text, tk.Text) self.widget.destroy() pygubu-0.36.1/tests/test_tkinterscrolledtext.ui000066400000000000000000000034201474524032100217720ustar00rootroot00000000000000 200 200 True 200 200 True top 10 scrolledtext_1 True top 200 200 True top 10 tkinterscrolledtext_1 True pygubu-0.36.1/tests/test_tkscrolledframe.py000066400000000000000000000012431474524032100210520ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support from pygubu.widgets.tkscrolledframe import TkScrolledFrame class TestTtkScrolledframe(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_tkscrolledframe.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("scrolledframe") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, TkScrolledFrame) self.widget.destroy() pygubu-0.36.1/tests/test_tkscrolledframe.ui000066400000000000000000000020571474524032100210430ustar00rootroot00000000000000 both true True True top True top True top pygubu-0.36.1/tests/test_tkspinbox.py000066400000000000000000000026771474524032100177260ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestTkSpinbox(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_tkspinbox.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) def tearDown(self): support.root_withdraw() def test_class(self): self.widget = self.builder.get_object("test_from") self.spinbox = self.builder.get_object("spinbox1") self.assertIsInstance(self.spinbox, tk.Spinbox) self.widget.destroy() def test_from_(self): self.widget = self.builder.get_object("test_from") spinbox = self.builder.get_object("spinbox1") value = spinbox.cget("from") self.assertEqual(5, value) self.widget.destroy() def test_to(self): self.widget = self.builder.get_object("test_to") spinbox = self.builder.get_object("spinbox2") value = spinbox.cget("to") self.assertEqual(10, value) self.widget.destroy() def test_from_to(self): self.widget = self.builder.get_object("test_from_to") spinbox = self.builder.get_object("spinbox3") value_from = spinbox.cget("from") value_to = spinbox.cget("to") self.assertEqual(2, value_from) self.assertEqual(10, value_to) self.widget.destroy() pygubu-0.36.1/tests/test_tkspinbox.ui000066400000000000000000000035711474524032100177050ustar00rootroot00000000000000 200 200 0 True 0 5 True top 200 200 0 True 0 10 True top 200 200 0 True 0 2 10 True top pygubu-0.36.1/tests/test_toplevelmenuhelper.py000066400000000000000000000015751474524032100216200ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestToplevelMenuHelper(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_toplevelmenuhelper.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("toplevel") self.menuhelper = builder.get_object("topmenuhelper") self.menu = builder.get_object("topmenu") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.menu, tk.Menu) self.widget.destroy() def test_class_topmenu(self): menu1 = self.widget.nametowidget(self.widget.cget("menu")) self.assertEqual(menu1, self.menu) self.widget.destroy() pygubu-0.36.1/tests/test_toplevelmenuhelper.ui000066400000000000000000000041451474524032100216010ustar00rootroot00000000000000 320x240 200 200 200 200 true True top Toplevel Menu Helper Test True top false Topmenu false false command 1 false command 2 pygubu-0.36.1/tests/test_treeview.py000066400000000000000000000122141474524032100175230ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestTreeview(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = """ 250 250 nesw 0 0 True browse 0 0 True True True Tree on_treecolumn_click w w 200 True 200 False True Column 1 center w 200 False 200 False False hidden w w 20 True 200 """ self.builder = builder = pygubu.Builder() builder.add_from_string(xmldata) self.widget = builder.get_object("treeview") self.widget.wait_visibility() def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Treeview) self.widget.destroy() def test_selectmode(self): expected = "browse" value = str(self.widget.cget("selectmode")) self.assertEqual(expected, value) self.widget.destroy() def test_columns(self): columns = ("column1", "hidden_column") wcolumns = self.widget.cget("columns") self.assertEqual(columns, wcolumns) dcolumns = ("column1",) wdcolumns = self.widget.cget("displaycolumns") self.assertEqual(dcolumns, wdcolumns) self.widget.destroy() def test_tree_heading(self): wh = self.widget.heading("#0") heading = { "text": "Tree", "anchor": "w", } for k, v in heading.items(): self.assertEqual(v, wh[k]) self.widget.destroy() def test_tree_column(self): wc = self.widget.column("#0") column = { "anchor": "w", "stretch": 1, "width": 200, "minwidth": 200, } for k, v in column.items(): self.assertEqual(v, wc[k]) self.widget.destroy() def test_command_dict(self): success = [] def on_treecolumn_click(): success.append(1) cbdic = {"on_treecolumn_click": on_treecolumn_click} self.builder.connect_callbacks(cbdic) x, y = self.widget.winfo_x(), self.widget.winfo_y() self.widget.event_generate("", x=x + 5, y=y + 5) self.widget.event_generate("", x=x + 5, y=y + 5) self.widget.update() self.assertTrue(success) self.widget.destroy() def test_command_self(self): success = [] class AnObject: def on_treecolumn_click(self): success.append(1) cbobj = AnObject() self.builder.connect_callbacks(cbobj) x, y = self.widget.winfo_x(), self.widget.winfo_y() self.widget.event_generate("", x=x + 5, y=y + 5) self.widget.event_generate("", x=x + 5, y=y + 5) self.widget.update() self.assertTrue(success) self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_ttkcombobox.py000066400000000000000000000050631474524032100202300ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestTtkComobox(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_ttkcombobox.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.mainwindow = builder.get_object("mainwindow") self.widget = builder.get_object("combobox1") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Combobox) self.widget.destroy() def test_validate(self): prop = "validate" expected_value = "key" tclobj = self.widget.cget(prop) value = str(tclobj) self.assertEqual(expected_value, value) self.widget.destroy() def test_validate_command(self): valid_values = ("option1", "option2", "option3") def cbox_validate(action, newvalue): valid = False if action == "1": # 1: insert 0: delete if newvalue in valid_values: valid = True else: valid = True return valid def cbox_invalid(newvalue): pass callback = { "cbox_validate": cbox_validate, "cbox_invalid": cbox_invalid, } self.builder.connect_callbacks(callback) self.widget.delete("0", tk.END) self.assertEqual("", self.widget.get()) self.widget.insert("0", valid_values[0]) self.assertEqual(valid_values[0], self.widget.get()) self.widget.delete("0", tk.END) self.widget.insert("0", "Invalid value") self.assertEqual("", self.widget.get()) self.widget.destroy() def test_invalid_command(self): invalid_text = [] def cbox_validate(action, newvalue): valid = False if action == "1": # 1: insert 0: delete if newvalue == "Allowed": valid = True else: valid = True return valid def cbox_invalid(newvalue): invalid_text.append(newvalue) callback = { "cbox_validate": cbox_validate, "cbox_invalid": cbox_invalid, } self.widget.delete("0", tk.END) self.builder.connect_callbacks(callback) self.widget.insert("0", "Not Allowed") self.assertEqual(invalid_text[0], "Not Allowed") self.widget.destroy() pygubu-0.36.1/tests/test_ttkcombobox.ui000066400000000000000000000017541474524032100202200ustar00rootroot00000000000000 200 200 0 True 0 cbox_invalid %P key cbox_validate %d %P option1 option2 option3 0 True 0 pygubu-0.36.1/tests/test_ttkspinbox.py000066400000000000000000000012121474524032100200720ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestSpinbox(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_ttkspinbox.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") self.spinbox = builder.get_object("spinbox1") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.spinbox, ttk.Spinbox) self.widget.destroy() pygubu-0.36.1/tests/test_ttkspinbox.ui000066400000000000000000000014601474524032100200640ustar00rootroot00000000000000 200 200 0 True 0 nesw 0 50 none 0 True 0 pygubu-0.36.1/tests/test_uidefinition_1_1.py000066400000000000000000000024171474524032100210230ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import support import pygubu class TestUIDefinition_1_1(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_uidefinition_1_1.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("fmain") self.fgrid = builder.get_object("fgrid") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Frame) self.widget.destroy() def test_gridrc_options(self): config = self.fgrid.grid_rowconfigure(0) # {'minsize': 10, 'pad': 10, 'weight': 1, 'uniform': 'x'} self.assertEqual(config["minsize"], 10) self.assertEqual(config["pad"], 10) self.assertEqual(config["weight"], 1) self.assertEqual(config["uniform"], "x") config = self.fgrid.grid_columnconfigure(0) # {'minsize': 30, 'pad': 15, 'weight': 1, 'uniform': 'y'} self.assertEqual(config["minsize"], 30) self.assertEqual(config["pad"], 15) self.assertEqual(config["weight"], 1) self.assertEqual(config["uniform"], "y") pygubu-0.36.1/tests/test_uidefinition_1_1.ui000066400000000000000000000133561474524032100210140ustar00rootroot00000000000000 200 10 200 0 True 0 200 200 True top button4 0 True 0 30 15 y 1 10 10 x 1 button5 1 True 0 10 10 x 1 button6 2 True 0 30 15 y 1 10 10 x 1 button7 0 True 1 30 15 y 1 10 10 x 1 button8 1 True 1 10 10 x 1 button9 2 True 1 30 15 y 1 10 10 x 1 pygubu-0.36.1/tests/test_uidefinition_1_2_grid.py000066400000000000000000000025021474524032100220240ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import support import pygubu class TestUIDefinition_1_2(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_uidefinition_1_2_grid.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("fmain") self.fgrid = builder.get_object("fgrid") def tearDown(self): support.root_withdraw() def test_class(self): self.assertIsInstance(self.widget, ttk.Frame) self.widget.destroy() def test_gridrc_row_options(self): config = self.fgrid.grid_rowconfigure(0) # {'minsize': 10, 'pad': 10, 'weight': 1, 'uniform': 'x'} self.assertEqual(config["minsize"], 10) self.assertEqual(config["pad"], 10) self.assertEqual(config["weight"], 1) self.assertEqual(config["uniform"], "x") def test_gridrc_column_options(self): config = self.fgrid.grid_columnconfigure(0) # {'minsize': 30, 'pad': 15, 'weight': 1, 'uniform': 'y'} self.assertEqual(config["minsize"], 30) self.assertEqual(config["pad"], 15) self.assertEqual(config["weight"], 1) self.assertEqual(config["uniform"], "y") pygubu-0.36.1/tests/test_uidefinition_1_2_grid.ui000066400000000000000000000072701474524032100220200ustar00rootroot00000000000000 200 10 200 0 0 200 200 top 30 15 y 1 30 15 y 1 10 10 x 1 10 10 x 1 button4 0 0 button5 1 0 button6 2 0 button7 0 1 button8 1 1 button9 2 1 pygubu-0.36.1/tests/test_uidefinition_1_3.py000066400000000000000000000013061474524032100210210ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import support import pygubu class TestUIDefinition_1_3(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_uidefinition_1_3.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("fmain") def tearDown(self): support.root_withdraw() def test_is_object_named(self): self.assertIsInstance(self.widget, ttk.Frame) is_named = self.builder.objects["fmain"].wmeta.is_named self.assertTrue(is_named) self.widget.destroy() pygubu-0.36.1/tests/test_uidefinition_1_3.ui000066400000000000000000000011601474524032100210040ustar00rootroot00000000000000 200 200 0 0 Test loading a named object top pygubu-0.36.1/tests/test_uidefinition_1_4.py000066400000000000000000000012061474524032100210210ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import support import pygubu class TestUIDefinition_1_4(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_uidefinition_1_4.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("fmain") def tearDown(self): support.root_withdraw() def test_is_version_1_4(self): value = self.builder.uidefinition.version self.assertEqual("1.4", value) self.widget.destroy() pygubu-0.36.1/tests/test_uidefinition_1_4.ui000066400000000000000000000016531474524032100210140ustar00rootroot00000000000000 200 200 0 0 Test UI definition 1.4 top pygubu-0.36.1/tests/test_variables.py000066400000000000000000000031611474524032100176420ustar00rootroot00000000000000# encoding: utf-8 import os import sys import unittest import tkinter as tk import tkinter.ttk as ttk import fixpath import pygubu import support class TestVariables(unittest.TestCase): def setUp(self): support.root_deiconify() xmldata = "test_variables.ui" self.builder = builder = pygubu.Builder() builder.add_from_file(xmldata) self.widget = builder.get_object("mainwindow") def tearDown(self): support.root_withdraw() def test_string_var(self): var = self.builder.get_variable("strvar") self.assertIsInstance(var, tk.StringVar) self.widget.destroy() def test_int_var(self): var = self.builder.get_variable("intvar") self.assertIsInstance(var, tk.IntVar) self.widget.destroy() def test_double_var(self): var = self.builder.get_variable("doublevar") self.assertIsInstance(var, tk.DoubleVar) self.widget.destroy() def test_boolean_var(self): var = self.builder.get_variable("booleanvar") self.assertIsInstance(var, tk.BooleanVar) self.assertEqual(False, var.get()) self.widget.destroy() def test_bugged_oldformat_var(self): var = self.builder.get_variable("testoldformat") self.assertIsInstance(var, tk.StringVar) self.widget.destroy() def test_invalid_variable_type(self): # self.builder.create_variable('complex:mycomplexvar') self.assertRaises( Exception, self.builder.create_variable, "complex:mycomplexvar" ) self.widget.destroy() if __name__ == "__main__": unittest.main() pygubu-0.36.1/tests/test_variables.ui000066400000000000000000000060311474524032100176260ustar00rootroot00000000000000 250 250 0 True 0 nesw string string:strvar none 0 True 0 22 int:intvar none 0 True 1 22.22 double:doublevar none 0 True 2 False boolean:booleanvar none 0 True 3 Bugged format: variablename:variabletype testoldformat:string 0 True 4 Correct format: variabletype:variablename string:testnewformat 0 True 5 pygubu-0.36.1/tests/wheeltest.ui000066400000000000000000000107061474524032100166270ustar00rootroot00000000000000 200 both 200 200 200 0 True 0 nsew 1 1 both 0 True 0 nsew 10 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 Text_2 50 none 0 True 0 horizontal true 0 True 1 nsew Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 0 True 0 both true 0 True 2 nsew #d9bcd9 0 0 800 800 0 True 0 #8cd963 0 True 1 pygubu-0.36.1/tests/wheeltest_tk.ui000066400000000000000000000076241474524032100173320ustar00rootroot00000000000000 200 200 200 200 0 True 0 both true 0 True 0 nsew 10 Text_1 1 2 3 4 5 6 7 8 9 0 END 50 0 True 0 horizontal true 0 True 1 ew START Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 Entry_1 END 0 True 0 both true 0 True 3 nsew #a4dbb6 0 True 0 #d9fe54 0 True 1 pygubu-0.36.1/wdesings/000077500000000000000000000000001474524032100147415ustar00rootroot00000000000000pygubu-0.36.1/wdesings/colorinput.ui000066400000000000000000000054441474524032100175050ustar00rootroot00000000000000 25 100 top 0 10 flat 24 y left all true both 2 left on_picker_clicked center Toolbutton true -2 both left ColorInput A color input for pygubu. colorinput widget frame1 ColorInput ../src/pygubu/widgets ../src/pygubu/plugins/pygubu False False False False False False