├── docs ├── source │ ├── genindex.rst │ ├── ttkwidgets │ │ ├── ttkwidgets │ │ │ ├── ttkwidgets.tooltips.rst │ │ │ ├── ttkwidgets.hook.rst │ │ │ ├── ttkwidgets.Table.rst │ │ │ ├── ttkwidgets.Calendar.rst │ │ │ ├── ttkwidgets.TimeLine.rst │ │ │ ├── ttkwidgets.LinkLabel.rst │ │ │ ├── ttkwidgets.TickScale.rst │ │ │ ├── ttkwidgets.ScaleEntry.rst │ │ │ ├── ttkwidgets.DebugWindow.rst │ │ │ ├── ttkwidgets.ItemsCanvas.rst │ │ │ ├── ttkwidgets.ScrolledListbox.rst │ │ │ ├── ttkwidgets.CheckboxTreeview.rst │ │ │ └── ttkwidgets.AutoHideScrollbar.rst │ │ ├── ttkwidgets.color │ │ │ ├── ttkwidgets.color.AlphaBar.rst │ │ │ ├── ttkwidgets.color.ColorPicker.rst │ │ │ ├── ttkwidgets.color.ColorSquare.rst │ │ │ └── ttkwidgets.color.GradientBar.rst │ │ ├── ttkwidgets.frames │ │ │ ├── ttkwidgets.frames.Tooltip.rst │ │ │ ├── ttkwidgets.frames.ToggledFrame.rst │ │ │ └── ttkwidgets.frames.ScrolledFrame.rst │ │ ├── ttkwidgets.font │ │ │ ├── ttkwidgets.font.FontChooser.rst │ │ │ ├── ttkwidgets.font.FontSelectFrame.rst │ │ │ ├── ttkwidgets.font.FontSizeDropdown.rst │ │ │ ├── ttkwidgets.font.FontFamilyDropdown.rst │ │ │ ├── ttkwidgets.font.FontFamilyListbox.rst │ │ │ └── ttkwidgets.font.FontPropertiesFrame.rst │ │ ├── ttkwidgets.autocomplete │ │ │ ├── ttkwidgets.autocomplete.AutocompleteEntry.rst │ │ │ ├── ttkwidgets.autocomplete.AutocompleteCombobox.rst │ │ │ └── ttkwidgets.autocomplete.AutocompleteEntryListbox.rst │ │ ├── ttkwidgets.validated_entries │ │ │ ├── ttkwidgets.validated_entries.validated_entry.rst │ │ │ ├── ttkwidgets.validated_entries.numbers.rst │ │ │ ├── ttkwidgets.validated_entries.strings.rst │ │ │ └── ttkwidgets.validated_entries.validators.rst │ │ ├── ttkwidgets.frames.rst │ │ ├── ttkwidgets.autocomplete.rst │ │ ├── ttkwidgets.color.rst │ │ ├── ttkwidgets.font.rst │ │ ├── ttkwidgets.rst │ │ └── ttkwidgets.validated_entries.rst │ ├── _templates │ │ └── autosummary │ │ │ ├── base.rst │ │ │ ├── class.rst │ │ │ └── module.rst │ ├── installation.rst │ ├── documentation.rst │ ├── generate_examples.py │ ├── index.rst │ ├── authors.rst │ └── conf.py ├── Makefile └── make.bat ├── tests ├── __init__.py ├── base_widget_testcase.py ├── test_calendar.py ├── test_scrolledlistbox.py ├── test_toggledframe.py ├── test_scrolledframe.py ├── test_utilities.py ├── test_debugwindow.py ├── test_linklabel.py ├── test_autohidescrollbar.py ├── test_autocompletewidgets.py ├── test_tooltips.py ├── test_checkboxtreeview.py ├── test_balloon.py ├── test_font_widgets.py ├── test_itemscanvas.py ├── test_tickscale.py ├── test_hooks.py ├── test_scaleentry.py └── test_validatedentries.py ├── ttkwidgets ├── assets │ ├── drag.png │ ├── open.png │ ├── closed.png │ ├── marker.png │ ├── balloon.png │ ├── checked.png │ ├── tristate.png │ ├── unchecked.png │ ├── zoom_in.png │ ├── zoom_out.png │ └── zoom_reset.png ├── autocomplete │ ├── __init__.py │ └── autocomplete_entry.py ├── font │ ├── __init__.py │ ├── sizedropdown.py │ ├── familydropdown.py │ ├── familylistbox.py │ ├── selectframe.py │ ├── propertiesframe.py │ └── chooser.py ├── validated_entries │ ├── __init__.py │ ├── numbers.py │ ├── strings.py │ └── validated_entry.py ├── frames │ ├── __init__.py │ ├── toggledframe.py │ └── scrolledframe.py ├── __init__.py ├── color │ ├── __init__.py │ ├── limitvar.py │ ├── functions.py │ ├── gradientbar.py │ ├── spinbox.py │ └── alphabar.py ├── utilities.py ├── scrolledlistbox.py ├── debugwindow.py ├── tooltips.py ├── linklabel.py └── hook.py ├── .codecov.yml ├── examples ├── example_tooltip.py ├── example_debugwindow.py ├── example_scaleentry.py ├── example_scrolledlistbox.py ├── example_toggledframe.py ├── example_autocompleteentry.py ├── example_autocompleteentrylistbox.py ├── example_itemscanvas.py ├── example_autocompletecombobox.py ├── example_scrolledframe.py ├── example_linklabel.py ├── example_checkboxtreeview.py ├── example_askfont.py ├── example_tooltips.py ├── example_fontselectframe.py ├── example_calendar.py ├── example_autohidescrollbar.py ├── example_hook.py ├── example_validated_entries.py ├── example_askcolor.py ├── example_tickscale.py ├── example_timeline.py ├── example_table.py └── run.py ├── .appveyor.yml ├── .travis.yml ├── .github └── PULL_REQUEST_TEMPLATE.md ├── setup.py ├── .gitignore ├── AUTHORS.md └── README.md /docs/source/genindex.rst: -------------------------------------------------------------------------------- 1 | Index 2 | ===== 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from tests.base_widget_testcase import BaseWidgetTest 2 | -------------------------------------------------------------------------------- /ttkwidgets/assets/drag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/drag.png -------------------------------------------------------------------------------- /ttkwidgets/assets/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/open.png -------------------------------------------------------------------------------- /ttkwidgets/assets/closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/closed.png -------------------------------------------------------------------------------- /ttkwidgets/assets/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/marker.png -------------------------------------------------------------------------------- /ttkwidgets/assets/balloon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/balloon.png -------------------------------------------------------------------------------- /ttkwidgets/assets/checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/checked.png -------------------------------------------------------------------------------- /ttkwidgets/assets/tristate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/tristate.png -------------------------------------------------------------------------------- /ttkwidgets/assets/unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/unchecked.png -------------------------------------------------------------------------------- /ttkwidgets/assets/zoom_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/zoom_in.png -------------------------------------------------------------------------------- /ttkwidgets/assets/zoom_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/zoom_out.png -------------------------------------------------------------------------------- /ttkwidgets/assets/zoom_reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TkinterEP/ttkwidgets/HEAD/ttkwidgets/assets/zoom_reset.png -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.tooltips.rst: -------------------------------------------------------------------------------- 1 | Tooltips 2 | ======== 3 | 4 | .. currentmodule:: ttkwidgets 5 | .. automodule:: tooltips 6 | -------------------------------------------------------------------------------- /docs/source/_templates/autosummary/base.rst: -------------------------------------------------------------------------------- 1 | {{ name | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. auto{{ objtype }}:: {{ objname }} 6 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.hook.rst: -------------------------------------------------------------------------------- 1 | Widget Option Hooks 2 | =================== 3 | 4 | .. currentmodule:: ttkwidgets 5 | .. automodule:: hook 6 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | ci: 3 | - travis 4 | - appveyor 5 | status: 6 | patch: false 7 | changes: false 8 | project: 9 | default: 10 | target: '80' 11 | comment: false 12 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.Table.rst: -------------------------------------------------------------------------------- 1 | Table 2 | ===== 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: Table 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.Calendar.rst: -------------------------------------------------------------------------------- 1 | Calendar 2 | ======== 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: Calendar 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.TimeLine.rst: -------------------------------------------------------------------------------- 1 | TimeLine 2 | ======== 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: TimeLine 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.LinkLabel.rst: -------------------------------------------------------------------------------- 1 | LinkLabel 2 | ========= 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: LinkLabel 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.TickScale.rst: -------------------------------------------------------------------------------- 1 | TickScale 2 | ========= 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: TickScale 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/_templates/autosummary/class.rst: -------------------------------------------------------------------------------- 1 | {{ name | escape | underline}} 2 | 3 | .. currentmodule:: {{ module }} 4 | 5 | .. autoclass:: {{ objname }} 6 | :show-inheritance: 7 | :members: 8 | 9 | .. automethod:: __init__ 10 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.ScaleEntry.rst: -------------------------------------------------------------------------------- 1 | ScaleEntry 2 | ========== 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: ScaleEntry 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.DebugWindow.rst: -------------------------------------------------------------------------------- 1 | DebugWindow 2 | =========== 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: DebugWindow 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.ItemsCanvas.rst: -------------------------------------------------------------------------------- 1 | ItemsCanvas 2 | =========== 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: ItemsCanvas 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.color/ttkwidgets.color.AlphaBar.rst: -------------------------------------------------------------------------------- 1 | AlphaBar 2 | ======== 3 | 4 | .. currentmodule:: ttkwidgets.color 5 | 6 | .. autoclass:: AlphaBar 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.Tooltip.rst: -------------------------------------------------------------------------------- 1 | Tooltip 2 | ======= 3 | 4 | .. currentmodule:: ttkwidgets.frames 5 | 6 | .. autoclass:: Tooltip 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ 11 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.color/ttkwidgets.color.ColorPicker.rst: -------------------------------------------------------------------------------- 1 | ColorPicker 2 | =========== 3 | 4 | .. currentmodule:: ttkwidgets.color 5 | 6 | .. autoclass:: ColorPicker 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.color/ttkwidgets.color.ColorSquare.rst: -------------------------------------------------------------------------------- 1 | ColorSquare 2 | =========== 3 | 4 | .. currentmodule:: ttkwidgets.color 5 | 6 | .. autoclass:: ColorSquare 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.color/ttkwidgets.color.GradientBar.rst: -------------------------------------------------------------------------------- 1 | GradientBar 2 | =========== 3 | 4 | .. currentmodule:: ttkwidgets.color 5 | 6 | .. autoclass:: GradientBar 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.font/ttkwidgets.font.FontChooser.rst: -------------------------------------------------------------------------------- 1 | FontChooser 2 | =========== 3 | 4 | .. currentmodule:: ttkwidgets.font 5 | 6 | .. autoclass:: FontChooser 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.ScrolledListbox.rst: -------------------------------------------------------------------------------- 1 | ScrolledListbox 2 | =============== 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: ScrolledListbox 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.CheckboxTreeview.rst: -------------------------------------------------------------------------------- 1 | CheckboxTreeview 2 | ================ 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: CheckboxTreeview 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.ToggledFrame.rst: -------------------------------------------------------------------------------- 1 | ToggledFrame 2 | ============ 3 | 4 | .. currentmodule:: ttkwidgets.frames 5 | 6 | .. autoclass:: ToggledFrame 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets/ttkwidgets.AutoHideScrollbar.rst: -------------------------------------------------------------------------------- 1 | AutoHideScrollbar 2 | ================= 3 | 4 | .. currentmodule:: ttkwidgets 5 | 6 | .. autoclass:: AutoHideScrollbar 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.font/ttkwidgets.font.FontSelectFrame.rst: -------------------------------------------------------------------------------- 1 | FontSelectFrame 2 | =============== 3 | 4 | .. currentmodule:: ttkwidgets.font 5 | 6 | .. autoclass:: FontSelectFrame 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.frames/ttkwidgets.frames.ScrolledFrame.rst: -------------------------------------------------------------------------------- 1 | ScrolledFrame 2 | ============= 3 | 4 | .. currentmodule:: ttkwidgets.frames 5 | 6 | .. autoclass:: ScrolledFrame 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.font/ttkwidgets.font.FontSizeDropdown.rst: -------------------------------------------------------------------------------- 1 | FontSizeDropdown 2 | ================ 3 | 4 | .. currentmodule:: ttkwidgets.font 5 | 6 | .. autoclass:: FontSizeDropdown 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.font/ttkwidgets.font.FontFamilyDropdown.rst: -------------------------------------------------------------------------------- 1 | FontFamilyDropdown 2 | ================== 3 | 4 | .. currentmodule:: ttkwidgets.font 5 | 6 | .. autoclass:: FontFamilyDropdown 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.font/ttkwidgets.font.FontFamilyListbox.rst: -------------------------------------------------------------------------------- 1 | FontFamilyListbox 2 | ================= 3 | 4 | .. currentmodule:: ttkwidgets.font 5 | 6 | .. autoclass:: FontFamilyListbox 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.font/ttkwidgets.font.FontPropertiesFrame.rst: -------------------------------------------------------------------------------- 1 | FontPropertiesFrame 2 | =================== 3 | 4 | .. currentmodule:: ttkwidgets.font 5 | 6 | .. autoclass:: FontPropertiesFrame 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /ttkwidgets/autocomplete/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) The ttkwidgets authors 2017 2 | # Available under the license found in LICENSE 3 | from .autocomplete_entry import AutocompleteEntry 4 | from .autocompletecombobox import AutocompleteCombobox 5 | from .autocomplete_entrylistbox import AutocompleteEntryListbox -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.autocomplete/ttkwidgets.autocomplete.AutocompleteEntry.rst: -------------------------------------------------------------------------------- 1 | AutocompleteEntry 2 | ================= 3 | 4 | .. currentmodule:: ttkwidgets.autocomplete 5 | 6 | .. autoclass:: AutocompleteEntry 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.validated_entries/ttkwidgets.validated_entries.validated_entry.rst: -------------------------------------------------------------------------------- 1 | ValidatedEntry 2 | ============ 3 | 4 | .. currentmodule:: ttkwidgets.validated_entries 5 | 6 | .. autoclass:: ValidatedEntry 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ 11 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.autocomplete/ttkwidgets.autocomplete.AutocompleteCombobox.rst: -------------------------------------------------------------------------------- 1 | AutocompleteCombobox 2 | ==================== 3 | 4 | .. currentmodule:: ttkwidgets.autocomplete 5 | 6 | .. autoclass:: AutocompleteCombobox 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.autocomplete/ttkwidgets.autocomplete.AutocompleteEntryListbox.rst: -------------------------------------------------------------------------------- 1 | AutocompleteEntryListbox 2 | ======================== 3 | 4 | .. currentmodule:: ttkwidgets.autocomplete 5 | 6 | .. autoclass:: AutocompleteEntryListbox 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ 11 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.frames.rst: -------------------------------------------------------------------------------- 1 | .. _frames: 2 | 3 | ttkwidgets.frames 4 | ================= 5 | 6 | .. currentmodule:: ttkwidgets.frames 7 | 8 | .. rubric:: Classes 9 | 10 | .. autosummary:: 11 | :nosignatures: 12 | :toctree: ttkwidgets.frames 13 | 14 | Tooltip 15 | ScrolledFrame 16 | ToggledFrame 17 | -------------------------------------------------------------------------------- /examples/example_tooltip.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) RedFantom 2017 4 | # For license see LICENSE 5 | from ttkwidgets.frames import Tooltip 6 | import tkinter as tk 7 | 8 | 9 | window = tk.Tk() 10 | button = tk.Button(window, text="Button", command=window.destroy) 11 | button.pack() 12 | balloon = Tooltip(button) 13 | window.mainloop() 14 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.autocomplete.rst: -------------------------------------------------------------------------------- 1 | .. _autocomplete: 2 | 3 | ttkwidgets.autocomplete 4 | ======================= 5 | 6 | .. currentmodule:: ttkwidgets.autocomplete 7 | 8 | .. autosummary:: 9 | :nosignatures: 10 | :toctree: ttkwidgets.autocomplete 11 | 12 | AutocompleteCombobox 13 | AutocompleteEntry 14 | AutocompleteEntryListbox 15 | -------------------------------------------------------------------------------- /tests/base_widget_testcase.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | import unittest 4 | import tkinter as tk 5 | 6 | 7 | class BaseWidgetTest(unittest.TestCase): 8 | def setUp(self): 9 | self.window = tk.Toplevel() 10 | self.window.update() 11 | 12 | def tearDown(self): 13 | self.window.update() 14 | self.window.destroy() 15 | -------------------------------------------------------------------------------- /ttkwidgets/font/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) The ttkwidgets authors 2017 2 | # Available under the license found in LICENSE 3 | from .chooser import askfont, FontChooser 4 | from .familydropdown import FontFamilyDropdown 5 | from .familylistbox import FontFamilyListbox 6 | from .propertiesframe import FontPropertiesFrame 7 | from .selectframe import FontSelectFrame 8 | from .sizedropdown import FontSizeDropdown 9 | -------------------------------------------------------------------------------- /examples/example_debugwindow.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) RedFantom 2017 4 | # Copyright (c) Juliette Monsel 2017 5 | # For license see LICENSE 6 | 7 | from ttkwidgets import DebugWindow 8 | import tkinter as tk 9 | from tkinter import ttk 10 | 11 | root = tk.Tk() 12 | ttk.Button(root, text="Print ok", command=lambda: print('ok')).pack() 13 | DebugWindow(root) 14 | root.mainloop() 15 | -------------------------------------------------------------------------------- /examples/example_scaleentry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets import ScaleEntry 7 | import tkinter as tk 8 | 9 | 10 | window = tk.Tk() 11 | scaleentry = ScaleEntry(window, scalewidth=200, entrywidth=3, from_=0, to=20) 12 | scaleentry.config_entry(justify='center') 13 | scaleentry.pack() 14 | window.mainloop() 15 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.color.rst: -------------------------------------------------------------------------------- 1 | .. _color: 2 | 3 | ttkwidgets.color 4 | ================ 5 | 6 | .. currentmodule:: ttkwidgets.color 7 | 8 | .. rubric:: Functions 9 | 10 | .. autofunction:: askcolor 11 | 12 | .. rubric:: Classes 13 | 14 | .. autosummary:: 15 | :nosignatures: 16 | :toctree: ttkwidgets.color 17 | 18 | AlphaBar 19 | ColorPicker 20 | ColorSquare 21 | GradientBar 22 | -------------------------------------------------------------------------------- /ttkwidgets/validated_entries/__init__.py: -------------------------------------------------------------------------------- 1 | from ttkwidgets.validated_entries.validated_entry import ValidatedEntry 2 | 3 | from ttkwidgets.validated_entries.numbers import FloatEntry, IntEntry, PercentEntry 4 | from ttkwidgets.validated_entries.strings import ( 5 | LowerStringEntry, UpperStringEntry, EmailEntry, 6 | PasswordEntry, CapitalizedStringEntry, 7 | ) 8 | 9 | from ttkwidgets.validated_entries.validators import * 10 | -------------------------------------------------------------------------------- /examples/example_scrolledlistbox.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets import ScrolledListbox 7 | import tkinter as tk 8 | 9 | window = tk.Tk() 10 | listbox = ScrolledListbox(window, height=5) 11 | 12 | for i in range(10): 13 | listbox.listbox.insert('end', 'item {}'.format(i)) 14 | 15 | listbox.pack(fill='both', expand=True) 16 | window.mainloop() 17 | -------------------------------------------------------------------------------- /ttkwidgets/frames/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) The ttkwidgets authors 2017 2 | # Available under the license found in LICENSE 3 | from .scrolledframe import ScrolledFrame 4 | from .toggledframe import ToggledFrame 5 | from .tooltip import Tooltip 6 | 7 | 8 | def Balloon(*args, **kwargs): 9 | from warnings import warn 10 | warn("'Balloon' has been renamed to 'Tooltip'", DeprecationWarning, stacklevel=2) 11 | return Tooltip(*args, **kwargs) 12 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - PYTHON: "C:\\PYTHON36" 4 | - PYTHON: "C:\\PYTHON37" 5 | - PYTHON: "C:\\PYTHON38" 6 | install: 7 | - "%PYTHON%\\python.exe -m pip install -U codecov coverage nose mock pynput setuptools pip" 8 | build: off 9 | test_script: 10 | - "%PYTHON%\\python.exe -m pip install ." 11 | - "%PYTHON%\\python.exe -m nose --with-coverage --cover-xml" 12 | after_test: 13 | - "%PYTHON%\\Scripts\\codecov.exe -f coverage.xml" 14 | -------------------------------------------------------------------------------- /examples/example_toggledframe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) RedFantom 2017 4 | # For license see LICENSE 5 | 6 | from ttkwidgets.frames import ToggledFrame 7 | import tkinter as tk 8 | from tkinter import ttk 9 | 10 | window = tk.Tk() 11 | frame = ToggledFrame(window, text="Value", width=10) 12 | frame.pack() 13 | button = ttk.Button(frame.interior, text="Button", command=window.destroy) 14 | button.grid() 15 | frame.toggle() 16 | window.mainloop() 17 | -------------------------------------------------------------------------------- /examples/example_autocompleteentry.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets.autocomplete import AutocompleteEntry 7 | import tkinter as tk 8 | 9 | window = tk.Tk() 10 | tk.Label(window, text="Entry with autocompletion for the Tk instance's methods:").pack(side='left') 11 | entry = AutocompleteEntry(window, width=20, completevalues=dir(window)) 12 | entry.pack(side='right') 13 | window.mainloop() 14 | -------------------------------------------------------------------------------- /examples/example_autocompleteentrylistbox.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2019 4 | # For license see LICENSE 5 | import tkinter as tk 6 | from ttkwidgets.autocomplete import AutocompleteEntryListbox 7 | 8 | window = tk.Tk() 9 | tk.Label(window, text="Entry + Listbox with autocompletion for the Tk instance's methods:").pack() 10 | entry = AutocompleteEntryListbox(window, width=20, completevalues=dir(window)) 11 | entry.pack() 12 | window.mainloop() 13 | -------------------------------------------------------------------------------- /examples/example_itemscanvas.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) RedFantom 2017 4 | # For license see LICENSE 5 | from ttkwidgets import ItemsCanvas 6 | import tkinter as tk 7 | from tkinter import ttk 8 | 9 | 10 | root = tk.Tk() 11 | 12 | canvas = ItemsCanvas(root) 13 | canvas.pack() 14 | 15 | canvas.add_item("Example", font=("default", 13, "italic"), backgroundcolor="green", textcolor="darkblue", 16 | highlightcolor="blue") 17 | 18 | root.mainloop() 19 | -------------------------------------------------------------------------------- /examples/example_autocompletecombobox.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets.autocomplete import AutocompleteCombobox 7 | import tkinter as tk 8 | 9 | window = tk.Tk() 10 | tk.Label(window, text="Combobox with autocompletion for the Tk instance's methods:").pack(side='left') 11 | entry = AutocompleteCombobox(window, width=20, completevalues=dir(window)) 12 | entry.pack(side='right') 13 | window.mainloop() 14 | -------------------------------------------------------------------------------- /examples/example_scrolledframe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets.frames import ScrolledFrame 7 | import tkinter as tk 8 | from tkinter import ttk 9 | 10 | window = tk.Tk() 11 | frame = ScrolledFrame(window, compound=tk.RIGHT, canvasheight=200) 12 | frame.pack(fill='both', expand=True) 13 | 14 | for i in range(20): 15 | ttk.Label(frame.interior, text='Label %i' % i).pack() 16 | window.mainloop() 17 | -------------------------------------------------------------------------------- /examples/example_linklabel.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) RedFantom 2017 4 | # Copyright (c) Juliette Monsel 2018 5 | # For license see LICENSE 6 | 7 | from ttkwidgets import LinkLabel 8 | import tkinter as tk 9 | 10 | window = tk.Tk() 11 | LinkLabel(window, text="ttkwidgets repository", 12 | link="https://github.com/RedFantom/ttkwidgets", 13 | normal_color='royal blue', 14 | hover_color='blue', 15 | clicked_color='purple').pack() 16 | window.mainloop() 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | required: sudo 3 | python: 4 | - "3.6" 5 | - "3.7" 6 | - "3.8" 7 | before_install: 8 | - "export DISPLAY=:99.0" 9 | - sudo systemctl start xvfb 10 | - sleep 3 11 | install: 12 | - sudo apt-get install python-tk python3-tk 13 | - python -m pip install -U setuptools pip importlib-metadata 14 | - python -m pip install nose coverage codecov mock pynput 15 | script: 16 | - python -m pip install . 17 | - python -m nose --with-coverage --cover-xml 18 | after_success: 19 | - codecov 20 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.validated_entries/ttkwidgets.validated_entries.numbers.rst: -------------------------------------------------------------------------------- 1 | Numbers 2 | ============ 3 | 4 | .. currentmodule:: ttkwidgets.validated_entries 5 | 6 | .. autoclass:: FloatEntry 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ 11 | 12 | 13 | .. autoclass:: IntEntry 14 | :show-inheritance: 15 | :members: 16 | 17 | .. automethod:: __init__ 18 | 19 | 20 | .. autoclass:: PercentEntry 21 | :show-inheritance: 22 | :members: 23 | 24 | .. automethod:: __init__ 25 | -------------------------------------------------------------------------------- /examples/example_checkboxtreeview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2017 4 | # For license see LICENSE 5 | 6 | from ttkwidgets import CheckboxTreeview 7 | import tkinter as tk 8 | 9 | root = tk.Tk() 10 | 11 | tree = CheckboxTreeview(root) 12 | tree.pack() 13 | 14 | tree.insert("", "end", "1", text="1") 15 | tree.insert("1", "end", "11", text="11") 16 | tree.insert("1", "end", "12", text="12") 17 | tree.insert("11", "end", "111", text="111") 18 | tree.insert("", "end", "2", text="2") 19 | 20 | root.mainloop() 21 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.font.rst: -------------------------------------------------------------------------------- 1 | .. _font: 2 | 3 | ttkwidgets.font 4 | =============== 5 | 6 | .. currentmodule:: ttkwidgets.font 7 | 8 | .. rubric:: Functions 9 | 10 | .. autofunction:: askfont 11 | 12 | 13 | .. rubric:: Classes 14 | 15 | .. autosummary:: 16 | :nosignatures: 17 | :toctree: ttkwidgets.font 18 | 19 | FontChooser 20 | FontFamilyDropdown 21 | FontFamilyListbox 22 | FontPropertiesFrame 23 | FontSelectFrame 24 | FontSizeDropdown 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | - With pip: 5 | 6 | :: 7 | 8 | pip install ttkwidgets 9 | 10 | - Ubuntu: ttkwidgets is available in the PPA `ppa:j-4321-i/ttkwidgets `_. 11 | 12 | .. code-block:: text 13 | 14 | sudo add-apt-repository ppa:j-4321-i/ttkwidgets 15 | sudo apt-get update 16 | sudo apt-get install python(3)-ttkwidgets 17 | 18 | - Archlinux: ttkwidgets is available in `AUR `_. 19 | -------------------------------------------------------------------------------- /examples/example_askfont.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets.font import askfont 7 | import tkinter as tk 8 | from tkinter import ttk 9 | 10 | def font(): 11 | res = askfont() 12 | if res[0] is not None: 13 | label.configure(font=res[0]) 14 | print(res) 15 | 16 | window = tk.Tk() 17 | label = ttk.Label(window, text='Sample text rendered in the chosen font.') 18 | label.pack(padx=10, pady=10) 19 | ttk.Button(window, text="Pick a font", command=font).pack() 20 | window.mainloop() 21 | -------------------------------------------------------------------------------- /examples/example_tooltips.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: The ttkwidgets repository 5 | """ 6 | import tkinter as tk 7 | from tkinter import ttk 8 | # Import once, use everywhere 9 | from ttkwidgets import tooltips 10 | 11 | 12 | window = tk.Tk() 13 | button = ttk.Button(window, text="Destroy", command=window.destroy, tooltip="This button destroys the window.") 14 | button.pack() 15 | x = lambda: button.configure(tooltip="This button no longer destroys the window", command=lambda: print("Behaviour changed!")) 16 | window.after(5000, x) 17 | window.mainloop() 18 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.rst: -------------------------------------------------------------------------------- 1 | .. _ttkwidgets: 2 | 3 | ttkwidgets 4 | ========== 5 | 6 | .. currentmodule:: ttkwidgets 7 | 8 | .. rubric:: Classes 9 | 10 | .. autosummary:: 11 | :nosignatures: 12 | :toctree: ttkwidgets 13 | 14 | AutoHideScrollbar 15 | Calendar 16 | CheckboxTreeview 17 | DebugWindow 18 | ItemsCanvas 19 | LinkLabel 20 | ScaleEntry 21 | ScrolledListbox 22 | Table 23 | TickScale 24 | TimeLine 25 | 26 | .. rubric:: Modules 27 | 28 | .. autosummary:: 29 | :nosignatures: 30 | :toctree: ttkwidgets 31 | 32 | hook 33 | tooltips 34 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /examples/example_fontselectframe.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets.font import FontSelectFrame 7 | import tkinter as tk 8 | from tkinter import ttk 9 | 10 | def update_preview(font_tuple): 11 | print(font_tuple) 12 | font = font_selection.font[0] 13 | if font is not None: 14 | label.configure(font=font) 15 | 16 | window = tk.Tk() 17 | label = ttk.Label(window, text='Sample text rendered in the chosen font.') 18 | label.pack(padx=10, pady=10) 19 | font_selection = FontSelectFrame(window, callback=update_preview) 20 | font_selection.pack() 21 | window.mainloop() 22 | -------------------------------------------------------------------------------- /examples/example_calendar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets import Calendar 7 | import tkinter as tk 8 | 9 | def validate(): 10 | sel = calendar.selection 11 | if sel is not None: 12 | label.configure(text='Selected date: %s' % sel.strftime('%x')) 13 | 14 | window = tk.Tk() 15 | calendar = Calendar(window, year=2015, month=3, selectforeground='white', 16 | selectbackground='red') 17 | calendar.pack() 18 | 19 | tk.Button(window, text='Select', command=validate).pack() 20 | label = tk.Label(window, text='Selected date:') 21 | label.pack() 22 | window.mainloop() 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | For new widgets only: 2 | 3 | Title: Merge branch_name into master to add WidgetName widget 4 | ### PR Details: 5 | - Widget name: 6 | - Author: 7 | 8 | ### Description 9 | A short description of this particular widget. 10 | 11 | ### Checklist 12 | - [ ] Widget in a separate file in the appropriate folder 13 | - [ ] Widget functions properly on both Windows and Linux 14 | - [ ] Widget code includes docstrings with parameter descriptions 15 | - [ ] Included an example file in `/examples` 16 | - [ ] Widget is covered by unitttests in `/tests` 17 | - [ ] Widget includes required assets files 18 | - [ ] Reference to widget in `AUTHORS.md` 19 | - [ ] Entry in sphinx documentation 20 | -------------------------------------------------------------------------------- /examples/example_autohidescrollbar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets import AutoHideScrollbar 7 | import tkinter as tk 8 | 9 | window = tk.Tk() 10 | listbox = tk.Listbox(window, height=5) 11 | scrollbar = AutoHideScrollbar(window, command=listbox.yview) 12 | listbox.configure(yscrollcommand=scrollbar.set) 13 | 14 | for i in range(10): 15 | listbox.insert('end', 'item %i' % i) 16 | 17 | tk.Label(window, text="Increase the window's height\nto make the scrollbar vanish.").pack(side='top', padx=4, pady=4) 18 | scrollbar.pack(side='right', fill='y') 19 | listbox.pack(side='left', fill='both', expand=True) 20 | 21 | window.mainloop() 22 | -------------------------------------------------------------------------------- /tests/test_calendar.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets import Calendar 4 | from tests import BaseWidgetTest 5 | import calendar 6 | import tkinter as tk 7 | 8 | 9 | class TestCalendar(BaseWidgetTest): 10 | def test_calendar_init(self): 11 | widget = Calendar(self.window) 12 | widget.pack() 13 | self.window.update() 14 | 15 | def test_calendar_buttons_functions(self): 16 | widget = Calendar(self.window) 17 | widget.pack() 18 | widget._prev_month() 19 | widget._next_month() 20 | 21 | def test_calendar_kw(self): 22 | widget = Calendar(self.window, firstweekday=calendar.SUNDAY, year=2016, month=12) 23 | widget.pack() 24 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.validated_entries.rst: -------------------------------------------------------------------------------- 1 | .. _validated_entries: 2 | 3 | ttkwidgets.validated_entries 4 | ================= 5 | 6 | .. currentmodule:: ttkwidgets.validated_entries 7 | 8 | .. rubric:: Classes 9 | 10 | .. autosummary:: 11 | :nosignatures: 12 | :toctree: ttkwidgets.validated_entries 13 | 14 | ValidatedEntry 15 | Validator 16 | RegexValidator 17 | IntValidator 18 | FloatValidator 19 | PercentValidator 20 | StringValidator 21 | CapitalizedStringValidator 22 | EmailValidator 23 | PasswordValidator 24 | IntEntry 25 | FloatEntry 26 | PercentEntry 27 | LowerStringEntry 28 | UpperStringEntry 29 | CapitalizedStringEntry 30 | EmailEntry 31 | PasswordEntry 32 | -------------------------------------------------------------------------------- /ttkwidgets/validated_entries/numbers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: Dogeek 3 | License: GNU GPLv3 4 | Source: This repository 5 | 6 | ValidatedEntry widgets for number validation. 7 | """ 8 | 9 | 10 | from .validated_entry import ValidatedEntry 11 | from .validators import PercentValidator, IntValidator, FloatValidator 12 | 13 | 14 | class PercentEntry(ValidatedEntry): 15 | """ 16 | Validates floats between 0 and 100 17 | """ 18 | VALIDATOR = PercentValidator 19 | 20 | 21 | class IntEntry(ValidatedEntry): 22 | """ 23 | Validates integers (no floating point) 24 | """ 25 | VALIDATOR = IntValidator 26 | 27 | 28 | class FloatEntry(ValidatedEntry): 29 | """ 30 | Validated floating-point numbers. 31 | """ 32 | VALIDATOR = FloatValidator 33 | -------------------------------------------------------------------------------- /tests/test_scrolledlistbox.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets import ScrolledListbox 4 | from tests import BaseWidgetTest 5 | 6 | import tkinter as tk 7 | 8 | 9 | class TestScrolledListBox(BaseWidgetTest): 10 | def test_scrolledlistbox_init(self): 11 | listbox = ScrolledListbox(self.window, height=10, width=10, compound=tk.LEFT) 12 | listbox.pack() 13 | self.window.update() 14 | self.assertFalse(listbox.scrollbar.winfo_ismapped()) 15 | listbox.destroy() 16 | 17 | listbox = ScrolledListbox(self.window, height=20, width=10, compound=tk.RIGHT, autohidescrollbar=False) 18 | listbox.pack() 19 | self.window.update() 20 | self.assertTrue(listbox.scrollbar.winfo_ismapped()) 21 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.validated_entries/ttkwidgets.validated_entries.strings.rst: -------------------------------------------------------------------------------- 1 | Strings 2 | ============ 3 | 4 | .. currentmodule:: ttkwidgets.validated_entries 5 | 6 | .. autoclass:: LowerStringEntry 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ 11 | 12 | 13 | .. autoclass:: UpperStringEntry 14 | :show-inheritance: 15 | :members: 16 | 17 | .. automethod:: __init__ 18 | 19 | 20 | .. autoclass:: CapitalizedStringEntry 21 | :show-inheritance: 22 | :members: 23 | 24 | .. automethod:: __init__ 25 | 26 | 27 | .. autoclass:: EmailEntry 28 | :show-inheritance: 29 | :members: 30 | 31 | .. automethod:: __init__ 32 | 33 | 34 | .. autoclass:: PasswordEntry 35 | :show-inheritance: 36 | :members: 37 | 38 | .. automethod:: __init__ 39 | -------------------------------------------------------------------------------- /tests/test_toggledframe.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets.frames import ToggledFrame 4 | from tests import BaseWidgetTest 5 | import tkinter as tk 6 | 7 | 8 | class TestToggledFrame(BaseWidgetTest): 9 | def test_toggledframe_init(self): 10 | frame = ToggledFrame(self.window) 11 | frame.pack() 12 | self.window.update() 13 | 14 | def test_toggledframe_open(self): 15 | frame = ToggledFrame(self.window) 16 | frame.pack() 17 | frame.toggle() 18 | self.assertTrue(frame._open) 19 | 20 | def test_toggledframe_open_close(self): 21 | frame = ToggledFrame(self.window) 22 | frame.pack() 23 | frame.toggle() 24 | self.assertTrue(frame._open) 25 | frame.toggle() 26 | self.assertFalse(frame._open) 27 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/_templates/autosummary/module.rst: -------------------------------------------------------------------------------- 1 | {{ name | escape | underline}} 2 | 3 | .. automodule:: {{ fullname }} 4 | :show-inheritance: 5 | 6 | {% block functions %} 7 | {% if functions %} 8 | .. rubric:: Functions 9 | 10 | .. autosummary:: 11 | :toctree: functions 12 | {% for item in functions %} 13 | {{ item }} 14 | {%- endfor %} 15 | {% endif %} 16 | {% endblock %} 17 | 18 | {% block classes %} 19 | {% if classes %} 20 | .. rubric:: Classes 21 | 22 | .. autosummary:: 23 | :toctree: classes 24 | {% for item in classes %} 25 | {{ item }} 26 | {%- endfor %} 27 | {% endif %} 28 | {% endblock %} 29 | 30 | {% block exceptions %} 31 | {% if exceptions %} 32 | .. rubric:: Exceptions 33 | 34 | .. autosummary:: 35 | :toctree: exceptions 36 | {% for item in exceptions %} 37 | {{ item }} 38 | {%- endfor %} 39 | {% endif %} 40 | {% endblock %} 41 | -------------------------------------------------------------------------------- /ttkwidgets/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) The ttkwidgets authors 2017 2 | # Available under the license found in LICENSE 3 | from ttkwidgets.calendarwidget import Calendar 4 | from ttkwidgets.autohidescrollbar import AutoHideScrollbar 5 | from ttkwidgets.linklabel import LinkLabel 6 | from ttkwidgets.scrolledlistbox import ScrolledListbox 7 | from ttkwidgets.debugwindow import DebugWindow 8 | from ttkwidgets.checkboxtreeview import CheckboxTreeview 9 | from ttkwidgets.itemscanvas import ItemsCanvas 10 | from ttkwidgets.scaleentry import ScaleEntry 11 | from ttkwidgets.timeline import TimeLine 12 | from ttkwidgets.tickscale import TickScale 13 | from ttkwidgets.table import Table 14 | 15 | from ttkwidgets.validated_entries.numbers import ( 16 | PercentEntry, IntEntry, FloatEntry, 17 | ) 18 | from ttkwidgets.validated_entries.strings import ( 19 | IPv4Entry, PasswordEntry, EmailEntry, 20 | CapitalizedStringEntry, UpperStringEntry, LowerStringEntry, 21 | ) 22 | -------------------------------------------------------------------------------- /examples/example_hook.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) RedFantom 2021 4 | # For license see LICENSE 5 | from tkinter import ttk 6 | import tkinter as tk 7 | from ttkwidgets.hook import hook_ttk_widgets 8 | 9 | if __name__ == '__main__': 10 | hook_ttk_widgets(lambda s, o, v: print(s, o, v), {"tooltip": "Default Value"}) 11 | hook_ttk_widgets(lambda s, o, v: print(s, o, v), {"hello_world": "second_hook"}) 12 | 13 | original_init = ttk.Button.__init__ 14 | 15 | def __init__(self, *args, **kwargs): 16 | print("User custom hook") 17 | original_init(self, *args, **kwargs) 18 | 19 | ttk.Button.__init__ = __init__ 20 | 21 | window = tk.Tk() 22 | button = ttk.Button(window, text="Destroy", command=window.destroy, tooltip="Destroys Window") 23 | button.pack() 24 | print([name for name in dir(button) if name.startswith("WidgetHook")]) 25 | window.after(1000, lambda: button.configure(tooltip="Does not destroy window", command=lambda: None)) 26 | window.mainloop() 27 | -------------------------------------------------------------------------------- /examples/example_validated_entries.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Dogeek 2020 4 | # For license see LICENSE 5 | 6 | import ttkwidgets.validated_entries as v_entries 7 | import tkinter as tk 8 | from tkinter import ttk 9 | 10 | window = tk.Tk() 11 | window.title('Validated Entries Example') 12 | frame = ttk.Frame(window) 13 | frame.pack() 14 | 15 | include = [] 16 | exclude = [] 17 | 18 | def filter_func(name): 19 | if name == 'ValidatedEntry': 20 | return False 21 | if name in include: 22 | return True 23 | if name in exclude: 24 | return False 25 | if name.endswith('Entry'): 26 | return True 27 | return False 28 | 29 | 30 | for i, entry_data in enumerate([(n, vars(v_entries)[n]) for n in dir(v_entries) if filter_func(n)]): 31 | name, klass = entry_data 32 | label = ttk.Label(frame, text=name) 33 | entry = klass(frame) 34 | entry.configure_validator() 35 | label.grid(row=i, column=0) 36 | entry.grid(row=i, column=1) 37 | window.mainloop() 38 | -------------------------------------------------------------------------------- /examples/example_askcolor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets.color import askcolor 7 | import tkinter as tk 8 | from tkinter import ttk 9 | from PIL import Image, ImageTk 10 | 11 | 12 | def pick(alpha=False): 13 | global im # to avoid garbage collection of image 14 | res = askcolor('sky blue', parent=window, title='Pick a color', alpha=alpha) 15 | canvas.delete('image') 16 | if res[1] is not None: 17 | im = ImageTk.PhotoImage(Image.new('RGBA', (100, 100), res[1]), master=window) 18 | canvas.create_image(60, 60, image=im, tags='image', anchor='center') 19 | print(res) 20 | 21 | 22 | window = tk.Tk() 23 | canvas = tk.Canvas(window, width=120, height=120) 24 | canvas.create_text(60, 60, text='Background', anchor='center') 25 | canvas.pack() 26 | ttk.Button(window, text="Pick a color (No alpha channel)", command=pick).pack(fill='x') 27 | ttk.Button(window, text="Pick a color (With alpha channel)", command=lambda: pick(True)).pack(fill='x') 28 | window.mainloop() 29 | -------------------------------------------------------------------------------- /tests/test_scrolledframe.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets.frames import ScrolledFrame 4 | from tests import BaseWidgetTest 5 | import tkinter as tk 6 | 7 | 8 | class TestScrolledFrame(BaseWidgetTest): 9 | def test_scrollframe_init(self): 10 | frame = ScrolledFrame(self.window) 11 | frame.pack() 12 | self.window.update() 13 | self.assertFalse(frame._scrollbar.winfo_ismapped()) 14 | 15 | frame.destroy() 16 | frame = ScrolledFrame(self.window, autohidescrollbar=False) 17 | frame.pack() 18 | self.window.update() 19 | self.assertTrue(frame._scrollbar.winfo_ismapped()) 20 | 21 | def test_scrollframe_methods(self): 22 | frame = ScrolledFrame(self.window) 23 | frame.pack(fill='both', expand=True) 24 | self.window.update() 25 | frame.resize_canvas(200, 200) 26 | self.window.update() 27 | self.window.geometry('300x100') 28 | self.window.update() 29 | tk.Button(frame.interior, text='Test').pack() 30 | -------------------------------------------------------------------------------- /examples/example_tickscale.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2017 4 | # For license see LICENSE 5 | from ttkwidgets import TickScale 6 | import tkinter as tk 7 | from tkinter import ttk 8 | 9 | 10 | root = tk.Tk() 11 | style = ttk.Style(root) 12 | style.theme_use('clam') 13 | style.configure('my.Vertical.TScale', sliderlength=50, background='white', 14 | foreground='red') 15 | style.configure('my.Horizontal.TScale', sliderlength=10, 16 | font='TkDefaultFont 20 italic') 17 | s1 = TickScale(root, orient='vertical', style='my.Vertical.TScale', 18 | tickinterval=0.2, from_=-1, to=1, showvalue=True, digits=2, 19 | length=400, labelpos='e') 20 | s2 = TickScale(root, orient='horizontal', style='my.Horizontal.TScale', 21 | from_=0, to=10, tickinterval=2, resolution=1, 22 | showvalue=True, length=400) 23 | s3 = TickScale(root, orient='horizontal', from_=0.25, to=1, tickinterval=0.1, 24 | resolution=0.1) 25 | 26 | s1.pack(fill='y') 27 | s2.pack(fill='x') 28 | s3.pack(fill='x') 29 | 30 | root.mainloop() 31 | -------------------------------------------------------------------------------- /docs/source/documentation.rst: -------------------------------------------------------------------------------- 1 | Documentation 2 | ============= 3 | 4 | .. note:: 5 | Only the widgets' specific options and methods are documented here, 6 | to get information about the options and methods inherited from standard tk/ttk 7 | widgets, consult `tkinter's documentation `_. 8 | 9 | .. toctree:: 10 | :hidden: 11 | :glob: 12 | :maxdepth: 1 13 | :Caption: Packages 14 | 15 | ttkwidgets/* 16 | 17 | Package structure 18 | 19 | :: 20 | 21 | ttkwidgets 22 | ├── autocomplete 23 | ├── color 24 | ├── font 25 | ├── validated_entries 26 | └── frames 27 | 28 | ======================== ======================= 29 | Package Content 30 | ======================== ======================= 31 | :ref:`ttkwidgets` Miscellanous widgets. 32 | :ref:`autocomplete` Autocompletion widgets. 33 | :ref:`color` Color choosing widgets. 34 | :ref:`font` Font choosing widgets. 35 | :ref:`frames` Frame based widgets. 36 | :ref:`validated_entries` ttk Entries with character validation. 37 | =================== ======================= 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: See AUTHORS.md 3 | License: GNU GPLv3 4 | Copyright (c) 2018-2020 The ttkwidgets authors 5 | """ 6 | from setuptools import setup 7 | 8 | 9 | def read(file_name: str): 10 | with open(file_name) as fi: 11 | return fi.read() 12 | 13 | setup( 14 | name="ttkwidgets", 15 | packages=["ttkwidgets", "ttkwidgets.frames", "ttkwidgets.font", "ttkwidgets.autocomplete", "ttkwidgets.color", "ttkwidgets.validated_entries"], 16 | py_modules=["ttkwidgets"], 17 | package_data={"ttkwidgets": ["assets/*"]}, 18 | version="0.13.0", 19 | description=" A collection of widgets for Tkinter's ttk extensions by various authors ", 20 | long_description=read("README.md"), 21 | long_description_content_type="text/markdown", 22 | author="The ttkwidgets authors", 23 | url="https://www.github.com/RedFantom/ttkwidgets", 24 | download_url="https://www.github.com/RedFantom/ttkwidgets/releases", 25 | license="AGPL", 26 | classifiers=["Programming Language :: Python :: 3", 27 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)"], 28 | python_requires=">=3.6", 29 | install_requires=["pillow"] 30 | ) 31 | -------------------------------------------------------------------------------- /tests/test_utilities.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Dogeek 2020 2 | # For license see LICENSE 3 | from ttkwidgets.utilities import isint, isfloat 4 | from tests import BaseWidgetTest 5 | import tkinter as tk 6 | 7 | 8 | class TestUtilities(BaseWidgetTest): 9 | def test_isint(self): 10 | intvar = tk.IntVar() 11 | self.assertTrue(isint(intvar)) 12 | stringvar = tk.StringVar() 13 | stringvar.set('123') 14 | self.assertTrue(isint(stringvar)) 15 | self.assertTrue(isint('123')) 16 | self.assertTrue(isint(123)) 17 | self.assertFalse(isint(123.0)) 18 | self.assertFalse(isint('123.4')) 19 | 20 | def test_isfloat(self): 21 | intvar = tk.IntVar() 22 | self.assertFalse(isfloat(intvar)) 23 | stringvar = tk.StringVar() 24 | stringvar.set('123') 25 | self.assertFalse(isfloat(stringvar)) 26 | stringvar.set('123.0') 27 | self.assertTrue(isfloat(stringvar)) 28 | self.assertFalse(isfloat('123')) 29 | self.assertFalse(isfloat(123)) 30 | self.assertTrue(isfloat(123.0)) 31 | self.assertTrue(isfloat('123.4')) 32 | 33 | 34 | if __name__ == '__main__': 35 | import unittest 36 | unittest.main() 37 | -------------------------------------------------------------------------------- /ttkwidgets/color/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Author: Juliette Monsel 4 | License: GNU GPLv3 5 | Source: https://github.com/j4321/tkColorPicker 6 | 7 | Edited by RedFantom for Python 2/3 cross-compatibility and docstring formatting 8 | """ 9 | 10 | """ 11 | tkcolorpicker - Alternative to colorchooser for Tkinter. 12 | Copyright 2017 Juliette Monsel 13 | 14 | tkcolorpicker is free software: you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation, either version 3 of the License, or 17 | (at your option) any later version. 18 | 19 | tkcolorpicker is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | You should have received a copy of the GNU General Public License 25 | along with this program. If not, see . 26 | """ 27 | 28 | 29 | from .colorpicker import ColorPicker, askcolor 30 | from .alphabar import AlphaBar 31 | from .gradientbar import GradientBar 32 | from .colorsquare import ColorSquare 33 | -------------------------------------------------------------------------------- /examples/example_timeline.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) RedFantom 2017 4 | # For license see LICENSE 5 | 6 | import tkinter as tk 7 | from ttkwidgets import TimeLine 8 | 9 | window = tk.Tk() 10 | timeline = TimeLine( 11 | window, 12 | categories={str(key): {"text": "Category {}".format(key)} for key in range(0, 5)}, 13 | height=100, extend=True 14 | ) 15 | menu = tk.Menu(window, tearoff=False) 16 | menu.add_command(label="Some Action", command=lambda: print("Command Executed")) 17 | timeline.tag_configure("1", right_callback=lambda *args: print(args), menu=menu, foreground="green", 18 | active_background="yellow", hover_border=2, move_callback=lambda *args: print(args)) 19 | timeline.create_marker("1", 1.0, 2.0, background="white", text="Change Color", tags=("1",), iid="1") 20 | timeline.create_marker("2", 2.0, 3.0, background="green", text="Change Category", foreground="white", iid="2", 21 | change_category=True) 22 | timeline.create_marker("3", 1.0, 2.0, text="Show Menu", tags=("1",)) 23 | timeline.create_marker("4", 4.0, 5.0, text="Do nothing", move=False) 24 | timeline.draw_timeline() 25 | timeline.grid() 26 | window.after(2500, lambda: timeline.configure(marker_background="cyan")) 27 | window.after(5000, lambda: timeline.update_marker("1", background="red")) 28 | window.after(5000, lambda: print(timeline.time)) 29 | window.mainloop() 30 | -------------------------------------------------------------------------------- /tests/test_debugwindow.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets import DebugWindow 4 | from tests import BaseWidgetTest 5 | import mock 6 | import tkinter as tk 7 | import os 8 | import sys 9 | 10 | 11 | def is_python_3(): 12 | return sys.version_info[0] is 3 13 | 14 | 15 | class TestDebugWindow(BaseWidgetTest): 16 | def test_debugwindow_init(self): 17 | debug = DebugWindow(self.window) 18 | self.window.update() 19 | self.assertFalse(debug.scroll.winfo_ismapped()) 20 | debug.destroy() 21 | debug = DebugWindow(self.window, autohidescrollbar=False) 22 | self.window.update() 23 | self.assertTrue(debug.scroll.winfo_ismapped()) 24 | 25 | def test_debugwindow_print(self): 26 | debug = DebugWindow(self.window) 27 | print("Something!") 28 | self.window.update() 29 | self.assertTrue("Something!" in debug.text.get("1.0", tk.END)) 30 | 31 | def test_debugwindow_save(self): 32 | debug = DebugWindow(self.window) 33 | print("Something!") 34 | self.window.update() 35 | module = "tkinter.filedialog.asksaveasfilename" if is_python_3() else "tkFileDialog.asksaveasfilename" 36 | with mock.patch(module, return_value="somefile.txt"): 37 | debug.save() 38 | self.assertTrue(os.path.exists("somefile.txt")) 39 | with open("somefile.txt", "r") as f: 40 | self.assertTrue("Something!" in f.read()) 41 | os.remove('somefile.txt') 42 | -------------------------------------------------------------------------------- /docs/source/ttkwidgets/ttkwidgets.validated_entries/ttkwidgets.validated_entries.validators.rst: -------------------------------------------------------------------------------- 1 | Validators 2 | ============ 3 | 4 | .. currentmodule:: ttkwidgets.validated_entries 5 | 6 | .. autoclass:: Validator 7 | :show-inheritance: 8 | :members: 9 | 10 | .. automethod:: __init__ 11 | 12 | 13 | .. autoclass:: MultiValidator 14 | :show-inheritance: 15 | :members: 16 | 17 | .. automethod:: __init__ 18 | 19 | 20 | .. autoclass:: AnyValidator 21 | :show-inheritance: 22 | :members: 23 | 24 | .. automethod:: __init__ 25 | 26 | 27 | .. autoclass:: AllValidator 28 | :show-inheritance: 29 | :members: 30 | 31 | .. automethod:: __init__ 32 | 33 | 34 | .. autoclass:: RegexValidator 35 | :show-inheritance: 36 | :members: 37 | 38 | .. automethod:: __init__ 39 | 40 | 41 | .. autoclass:: IntValidator 42 | :show-inheritance: 43 | :members: 44 | 45 | .. automethod:: __init__ 46 | 47 | 48 | .. autoclass:: FloatValidator 49 | :show-inheritance: 50 | :members: 51 | 52 | .. automethod:: __init__ 53 | 54 | 55 | .. autoclass:: PercentValidator 56 | :show-inheritance: 57 | :members: 58 | 59 | .. automethod:: __init__ 60 | 61 | 62 | .. autoclass:: StringValidator 63 | :show-inheritance: 64 | :members: 65 | 66 | .. automethod:: __init__ 67 | 68 | 69 | .. autoclass:: CapitalizedStringValidator 70 | :show-inheritance: 71 | :members: 72 | 73 | .. automethod:: __init__ 74 | 75 | 76 | .. autoclass:: EmailValidator 77 | :show-inheritance: 78 | :members: 79 | 80 | .. automethod:: __init__ 81 | 82 | 83 | .. autoclass:: PasswordValidator 84 | :show-inheritance: 85 | :members: 86 | 87 | .. automethod:: __init__ 88 | -------------------------------------------------------------------------------- /ttkwidgets/validated_entries/strings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: Dogeek 3 | License: GNU GPLv3 4 | Source: This repository 5 | 6 | ValidatedEntry widgets for string validation 7 | """ 8 | 9 | 10 | from string import ascii_lowercase, ascii_uppercase 11 | 12 | from .validated_entry import ValidatedEntry 13 | from .validators import ( 14 | EmailValidator, CapitalizedStringValidator, 15 | PasswordValidator, StringValidator, 16 | IPv4Validator, 17 | ) 18 | 19 | 20 | class LowerStringEntry(ValidatedEntry): 21 | """ 22 | Validates only lowercase ascii characters. 23 | """ 24 | VALIDATOR = StringValidator(ascii_lowercase) 25 | 26 | 27 | class UpperStringEntry(ValidatedEntry): 28 | """ 29 | Validates only uppercase ascii characters. 30 | """ 31 | VALIDATOR = StringValidator(ascii_uppercase) 32 | 33 | 34 | class CapitalizedStringEntry(ValidatedEntry): 35 | """ 36 | Validates only capitalized strings (Starts with a capital letter, then any character) 37 | """ 38 | VALIDATOR = CapitalizedStringValidator 39 | 40 | 41 | class EmailEntry(ValidatedEntry): 42 | """ 43 | Validates email addresses. 44 | """ 45 | VALIDATOR = EmailValidator 46 | 47 | 48 | class PasswordEntry(ValidatedEntry): 49 | """ 50 | Validates passwords. The requirements are as follows: 51 | * At least 1 lowercase character 52 | * At least 1 uppercase character 53 | * At least 1 digit 54 | * At least 1 special character (!@#$%^&*) 55 | * Be at least 8 characters long 56 | """ 57 | VALIDATOR = PasswordValidator 58 | 59 | 60 | class IPv4Entry(ValidatedEntry): 61 | """ 62 | Validates IPv4 addresses 63 | """ 64 | VALIDATOR = IPv4Validator 65 | -------------------------------------------------------------------------------- /ttkwidgets/utilities.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: The ttkwidgets authors 3 | License: GNU GPLv3 4 | Source: The ttkwidgets repository 5 | """ 6 | import os 7 | from PIL import Image, ImageTk 8 | import re 9 | 10 | 11 | def get_assets_directory(): 12 | return os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "assets")) 13 | 14 | 15 | def open_icon(icon_name): 16 | return ImageTk.PhotoImage(Image.open(os.path.join(get_assets_directory(), icon_name))) 17 | 18 | 19 | def isfloat(value): 20 | """ 21 | Checks if a value is a float 22 | :param value: any variable 23 | :returns: True if value is a float, string matching \d+\.\d* or tk.FloatVar 24 | """ 25 | if isinstance(value, float): 26 | return True 27 | if isinstance(value, str) and re.search(r'\d+[\.]\d*', value): 28 | return True 29 | if value.__class__.__name__ == 'StringVar' and isfloat(value.get()): 30 | return True 31 | return False 32 | 33 | 34 | def isint(value): 35 | """ 36 | Checks if a value is an int 37 | :param value: any variable 38 | :returns: True if value is an int, string matching \d+ or tk.IntVar 39 | """ 40 | if isinstance(value, int): 41 | return True 42 | if isinstance(value, str) and value.isdigit(): 43 | return True 44 | if value.__class__.__name__ == 'IntVar': 45 | return True 46 | if value.__class__.__name__ == 'StringVar' and isint(value.get()): 47 | return True 48 | return False 49 | 50 | 51 | def parse_geometry_string(string): 52 | """Parse a Tkinter geometry string ('XxY+W+H') into a box tuple""" 53 | e = string.split("x") 54 | w = int(e[0]) 55 | e = e[1].split("+") 56 | h, x, y = map(int, e) 57 | return x, y, w, h 58 | -------------------------------------------------------------------------------- /tests/test_linklabel.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets import LinkLabel 4 | from tests import BaseWidgetTest 5 | import tkinter as tk 6 | 7 | 8 | class TestLinkLabel(BaseWidgetTest): 9 | def test_linklabel_init(self): 10 | label = LinkLabel(self.window, link="www.google.com", text="Visit Google") 11 | label.pack() 12 | self.window.update() 13 | 14 | def test_linklabel_events(self): 15 | label = LinkLabel(self.window, link="www.google.com", text="Visit Google") 16 | label.pack() 17 | self.window.update() 18 | label._on_enter() 19 | self.window.update() 20 | label._on_leave() 21 | self.window.update() 22 | label.open_link() 23 | self.window.update() 24 | 25 | def test_linklabel_config(self): 26 | label = LinkLabel(self.window, link="www.google.com", text="Visit Google") 27 | label.pack() 28 | self.window.update() 29 | label.keys() 30 | self.window.update() 31 | label.configure(link="www.wikipedia.fr") 32 | self.window.update() 33 | label.cget("hover_color") 34 | self.window.update() 35 | value = label["normal_color"] 36 | self.window.update() 37 | label["clicked_color"] = "purple" 38 | self.window.update() 39 | 40 | def test_linklabel_cget(self): 41 | label = LinkLabel(self.window, link="www.google.com", text="Visit Google") 42 | label.pack() 43 | assert label.cget("hover_color") == label._hover_color 44 | assert label.cget("link") == label._link 45 | assert label.cget("normal_color") == label._normal_color 46 | assert label.cget("clicked_color") == label._clicked_color 47 | assert label.cget("text") == "Visit Google" 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # PyCharm 104 | /.idea 105 | 106 | # Documentation 107 | docs/source/examples 108 | docs/source/examples.rst 109 | 110 | -------------------------------------------------------------------------------- /ttkwidgets/font/sizedropdown.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | # Based on an idea by Nelson Brochado (https://www.github.com/nbro/tkinter-kit) 7 | from ttkwidgets.autocomplete import AutocompleteCombobox 8 | 9 | 10 | class FontSizeDropdown(AutocompleteCombobox): 11 | """ 12 | A dropdown with default font sizes 13 | """ 14 | 15 | def __init__(self, master=None, callback=None, **kwargs): 16 | """ 17 | :param master: master widget 18 | :type master: widget 19 | :param callback: callback on click with single argument: `int` size 20 | :type callback: function 21 | :param kwargs: keyword arguments passed on to the :class:`~ttkwidgets.autocomplete.AutocompleteCombobox` initializer 22 | """ 23 | int_values = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72] 24 | values = [str(value) for value in int_values] 25 | AutocompleteCombobox.__init__(self, master, completevalues=values, **kwargs) 26 | self.bind("<>", self._on_click) 27 | self.bind("", self._on_click) 28 | self.__callback = callback 29 | self.insert(0, "12") 30 | 31 | def _on_click(self, event): 32 | """ 33 | Function bound to event of selection in the Combobox, calls callback if callable 34 | 35 | :param event: Tkinter event 36 | """ 37 | if callable(self.__callback): 38 | self.__callback(self.selection) 39 | 40 | @property 41 | def selection(self): 42 | """ 43 | Selection property. 44 | 45 | :return: None if no value is selected and size if selected. 46 | :rtype: None or int 47 | """ 48 | if self.get() is "": 49 | return None 50 | else: 51 | return int(self.get()) 52 | -------------------------------------------------------------------------------- /tests/test_autohidescrollbar.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets import AutoHideScrollbar 4 | from tests import BaseWidgetTest 5 | 6 | 7 | class TestAutoHideScrollbar(BaseWidgetTest): 8 | def test_autohidescrollbar_init(self): 9 | AutoHideScrollbar(self.window) 10 | self.window.update() 11 | 12 | def test_autohidescrollbar_methods(self): 13 | scroll = AutoHideScrollbar(self.window, orient='vertical') 14 | # pack layout 15 | scroll.pack(side='right', fill='y') 16 | info = scroll._get_info("pack") 17 | self.assertEqual(info["side"], "right") 18 | self.assertEqual(info["fill"], "y") 19 | self.window.update() 20 | scroll.set(-0.1, 1.1) 21 | self.window.update() 22 | self.assertFalse(scroll.winfo_ismapped()) 23 | scroll.set(0.1, 0.8) 24 | self.window.update() 25 | self.window.update() 26 | self.assertTrue(scroll.winfo_ismapped()) 27 | scroll.pack_forget() 28 | self.window.update() 29 | # place layout 30 | scroll.place(anchor='ne', relx=1, rely=0, relheight=1) 31 | self.window.update() 32 | scroll.set(-0.1, 1.1) 33 | self.window.update() 34 | self.assertFalse(scroll.winfo_ismapped()) 35 | scroll.set(0.1, 0.8) 36 | self.window.update() 37 | self.window.update() 38 | self.assertTrue(scroll.winfo_ismapped()) 39 | scroll.place_forget() 40 | self.window.update() 41 | # grid layout 42 | scroll.grid(row=0, column=1, sticky='ns') 43 | self.window.update() 44 | scroll.set(-0.1, 1.1) 45 | self.window.update() 46 | self.assertFalse(scroll.winfo_ismapped()) 47 | scroll.set(0.1, 0.8) 48 | self.window.update() 49 | self.window.update() 50 | self.assertTrue(scroll.winfo_ismapped()) 51 | -------------------------------------------------------------------------------- /tests/test_autocompletewidgets.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets.autocomplete import AutocompleteCombobox, AutocompleteEntry 4 | from tests import BaseWidgetTest 5 | import tkinter as tk 6 | 7 | 8 | class TestEvent(object): 9 | def __init__(self, key="A"): 10 | self.keysym = key 11 | 12 | 13 | class TestAutocompleteWidgets(BaseWidgetTest): 14 | def test_autocompletecombobox(self): 15 | box = AutocompleteCombobox(self.window, completevalues=["Apple", "Pear", "Banana"]) 16 | box.pack() 17 | self.window.update() 18 | 19 | self.assertIn('completevalues', box.keys()) 20 | self.assertEqual(box['completevalues'], sorted(["Apple", "Pear", "Banana"])) 21 | 22 | box.insert(0, "A") 23 | self.window.update() 24 | for item in ["A", "Up", "Down", "Left", "Right", "Return"]: 25 | box.handle_keyrelease(TestEvent(item)) 26 | box.autocomplete(0) 27 | box.set_completion_list(["Apply"]) 28 | self.assertEqual(box['completevalues'], ["Apply"]) 29 | box['completevalues'] = ["Test"] 30 | self.assertEqual(box['completevalues'], ["Test"]) 31 | 32 | def test_autocompleteentry(self): 33 | entry = AutocompleteEntry(self.window, completevalues=["Apple", "Pear", "Banana"]) 34 | entry.pack() 35 | self.window.update() 36 | 37 | self.assertIn('completevalues', entry.keys()) 38 | self.assertEqual(entry['completevalues'], sorted(["Apple", "Pear", "Banana"])) 39 | 40 | entry.insert(0, "A") 41 | self.window.update() 42 | for item in ["A", "Up", "Down", "Left", "Right", "Return"]: 43 | entry.handle_keyrelease(TestEvent(item)) 44 | entry.autocomplete(0) 45 | entry.set_completion_list(["Apply"]) 46 | self.assertEqual(entry['completevalues'], ["Apply"]) 47 | entry['completevalues'] = ["Test"] 48 | self.assertEqual(entry['completevalues'], ["Test"]) 49 | -------------------------------------------------------------------------------- /ttkwidgets/font/familydropdown.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | # Based on an idea by Nelson Brochado (https://www.github.com/nbro/tkinter-kit) 7 | import tkinter as tk 8 | from tkinter import font 9 | from ttkwidgets.autocomplete import AutocompleteCombobox 10 | 11 | 12 | class FontFamilyDropdown(AutocompleteCombobox): 13 | """ 14 | A dropdown menu to select a font family with callback support and selection property. 15 | """ 16 | 17 | def __init__(self, master=None, callback=None, **kwargs): 18 | """ 19 | Create a FontFamilyDropdown. 20 | 21 | :param master: master widget 22 | :type master: widget 23 | :param callback: callable object with single argument: font family name 24 | :type callback: function 25 | :param kwargs: keyword arguments passed on to the :class:`~ttkwidgets.autocomplete.AutocompleteCombobox` initializer 26 | """ 27 | font_families = sorted(set(font.families())) 28 | self._fonts = font_families 29 | self._font = tk.StringVar(master) 30 | self.__callback = callback 31 | AutocompleteCombobox.__init__(self, master, textvariable=self._font, completevalues=font_families, **kwargs) 32 | self.bind("<>", self._on_select) 33 | self.bind("", self._on_select) 34 | 35 | def _on_select(self, *args): 36 | """ 37 | Function bound to event of selection in the Combobox, calls callback if callable 38 | 39 | :param args: Tkinter event 40 | """ 41 | if callable(self.__callback): 42 | self.__callback(self.selection) 43 | 44 | @property 45 | def selection(self): 46 | """ 47 | Selection property. 48 | 49 | :return: None if no font is selected and font family name if one is selected. 50 | :rtype: None or str 51 | """ 52 | if self._font.get() is "" or self._font.get() not in self._fonts: 53 | return None 54 | else: 55 | return self._font.get() 56 | -------------------------------------------------------------------------------- /ttkwidgets/validated_entries/validated_entry.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: Dogeek 3 | License: GNU GPLv3 4 | Source: This repository 5 | 6 | ValidatedEntry widget using validators to check the input against specific patterns. 7 | """ 8 | 9 | import tkinter.ttk as ttk 10 | 11 | from .validators import Validator 12 | 13 | 14 | class ValidatedEntry(ttk.Entry): 15 | """ 16 | ValidatedEntry base class. 17 | An entry that has a VALIDATOR class attribute. 18 | Set it to a validator class from `ttkwidgets.validated_entries.validators` 19 | 20 | The entry won't accept input that has not been validated. 21 | """ 22 | VALIDATOR = None 23 | 24 | def _get_validator(self, validator=None): 25 | """ 26 | Gets a validator instance from either the VALIDATOR class attribute 27 | or the validator keyword argument. 28 | 29 | :keyword validator: defaults None. A Validator class or instance. 30 | :type validator: `ttkwidgets.validated_entries.validators.Validator`, `type` 31 | :raises: TypeError if the validator is None, or is not of type `Validator` 32 | :returns: a Validator instance 33 | :rtype: `ttkwidgets.validated_entries.validators.Validator` 34 | """ 35 | 36 | validator = validator or self.VALIDATOR 37 | if validator is None: 38 | raise TypeError('No validator found.') 39 | 40 | if isinstance(validator, type): 41 | validator = validator() 42 | 43 | if not isinstance(validator, Validator): 44 | raise TypeError("Variable `validator` doesn't have type 'Validator'") 45 | return validator 46 | 47 | def configure_validator(self, validator=None): 48 | """ 49 | Configures the validator on this entry. See `ValidatedEntry._get_validator` 50 | for more info. 51 | """ 52 | existing_config = {k: self.config(k)[-1] for k in ('validate', 'validatecommand')} 53 | 54 | validator = self._get_validator(validator) 55 | new_config = validator.validate(self) 56 | new_config.update(existing_config) 57 | self.config(new_config) 58 | -------------------------------------------------------------------------------- /examples/example_table.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2018 4 | # For license see LICENSE 5 | 6 | from ttkwidgets import Table 7 | import tkinter as tk 8 | from tkinter import ttk 9 | 10 | root = tk.Tk() 11 | 12 | root.columnconfigure(0, weight=1) 13 | root.rowconfigure(0, weight=1) 14 | 15 | style = ttk.Style(root) 16 | style.theme_use('alt') 17 | sortable = tk.BooleanVar(root, False) 18 | drag_row = tk.BooleanVar(root, False) 19 | drag_col = tk.BooleanVar(root, False) 20 | 21 | columns = ["A", "B", "C", "D", "E", "F", "G"] 22 | table = Table(root, columns=columns, sortable=sortable.get(), drag_cols=drag_col.get(), 23 | drag_rows=drag_row.get(), height=6) 24 | for col in columns: 25 | table.heading(col, text=col) 26 | table.column(col, width=100, stretch=False) 27 | 28 | # sort column A content as int instead of strings 29 | table.column('A', type=int) 30 | 31 | for i in range(12): 32 | table.insert('', 'end', iid=i, 33 | values=(i, i) + tuple(i + 10 * j for j in range(2, 7))) 34 | 35 | # add scrollbars 36 | sx = tk.Scrollbar(root, orient='horizontal', command=table.xview) 37 | sy = tk.Scrollbar(root, orient='vertical', command=table.yview) 38 | table.configure(yscrollcommand=sy.set, xscrollcommand=sx.set) 39 | 40 | table.grid(sticky='ewns') 41 | sx.grid(row=1, column=0, sticky='ew') 42 | sy.grid(row=0, column=1, sticky='ns') 43 | root.update_idletasks() 44 | 45 | 46 | # toggle table properties 47 | def toggle_sort(): 48 | table.config(sortable=sortable.get()) 49 | 50 | 51 | def toggle_drag_col(): 52 | table.config(drag_cols=drag_col.get()) 53 | 54 | 55 | def toggle_drag_row(): 56 | table.config(drag_rows=drag_row.get()) 57 | 58 | 59 | frame = tk.Frame(root) 60 | tk.Checkbutton(frame, text='sortable', variable=sortable, command=toggle_sort).pack(side='left') 61 | tk.Checkbutton(frame, text='drag columns', variable=drag_col, command=toggle_drag_col).pack(side='left') 62 | tk.Checkbutton(frame, text='drag rows', variable=drag_row, command=toggle_drag_row).pack(side='left') 63 | frame.grid() 64 | root.geometry('400x200') 65 | 66 | root.mainloop() 67 | -------------------------------------------------------------------------------- /ttkwidgets/scrolledlistbox.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | import tkinter as tk 7 | from tkinter import ttk 8 | from ttkwidgets import AutoHideScrollbar 9 | 10 | 11 | class ScrolledListbox(ttk.Frame): 12 | """Simple :class:`tk.Listbox` with an added scrollbar.""" 13 | def __init__(self, master=None, compound=tk.RIGHT, autohidescrollbar=True, **kwargs): 14 | """ 15 | Create a Listbox with a vertical scrollbar. 16 | 17 | :param master: master widget 18 | :type master: widget 19 | :param compound: side for the Scrollbar to be on (:obj:`tk.LEFT` or :obj:`tk.RIGHT`) 20 | :type compound: str 21 | :param autohidescrollbar: whether to use an :class:`~ttkwidgets.AutoHideScrollbar` or a :class:`ttk.Scrollbar` 22 | :type autohidescrollbar: bool 23 | :param kwargs: keyword arguments passed on to the :class:`tk.Listbox` initializer 24 | """ 25 | ttk.Frame.__init__(self, master) 26 | self.columnconfigure(1, weight=1) 27 | self.rowconfigure(0, weight=1) 28 | self.listbox = tk.Listbox(self, **kwargs) 29 | if autohidescrollbar: 30 | self.scrollbar = AutoHideScrollbar(self, orient=tk.VERTICAL, command=self.listbox.yview) 31 | else: 32 | self.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.listbox.yview) 33 | self.config_listbox(yscrollcommand=self.scrollbar.set) 34 | if compound is not tk.LEFT and compound is not tk.RIGHT: 35 | raise ValueError("Invalid compound value passed: {0}".format(compound)) 36 | self.__compound = compound 37 | self._grid_widgets() 38 | 39 | def _grid_widgets(self): 40 | """Puts the two whole widgets in the correct position depending on compound.""" 41 | scrollbar_column = 0 if self.__compound is tk.LEFT else 2 42 | self.listbox.grid(row=0, column=1, sticky="nswe") 43 | self.scrollbar.grid(row=0, column=scrollbar_column, sticky="ns") 44 | 45 | def config_listbox(self, *args, **kwargs): 46 | """Configure resources of the Listbox widget.""" 47 | self.listbox.configure(*args, **kwargs) 48 | 49 | -------------------------------------------------------------------------------- /tests/test_tooltips.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: The ttkwidgets repository 5 | """ 6 | from unittest import TestCase 7 | try: 8 | import Tkinter as tk 9 | import ttk 10 | except ImportError: 11 | import tkinter as tk 12 | from tkinter import ttk 13 | from ttkwidgets.hook import is_hooked 14 | from ttkwidgets import tooltips 15 | 16 | 17 | class TestTooltipsModule(TestCase): 18 | def test_hook_exists(self): 19 | self.assertTrue(is_hooked(tooltips.OPTIONS)) 20 | 21 | def test_hook_works(self): 22 | tooltip = "This is a great tooltip." 23 | widget = ttk.Button(text="Hello World", tooltip=tooltip) 24 | 25 | # Check the holder existence 26 | holder = getattr(widget, tooltips.NAME.lower(), None) 27 | self.assertIsNotNone(holder) 28 | self.assertTrue(hasattr(holder, "tooltip_widget")) 29 | 30 | # Check that the tooltip widget exists and is created with the given value 31 | tooltip_widget = getattr(holder, "tooltip_widget", None) 32 | self.assertIsNotNone(tooltip_widget) 33 | self.assertEqual(tooltip_widget["text"], tooltip) 34 | 35 | # Check that the text is updated when configuring the widget only 36 | tooltip = "This is another great tooltip." 37 | widget["tooltip"] = tooltip 38 | self.assertEqual(tooltip_widget["text"], tooltip) 39 | self.assertEqual(widget["tooltip"], tooltip) 40 | 41 | # Check that the tooltip is destroyed when configured with None 42 | widget["tooltip"] = None 43 | tooltip_widget = getattr(holder, "tooltip_widget") 44 | self.assertIsNone(tooltip_widget) 45 | 46 | # Check that the tooltip is updated when its options are changed 47 | widget.configure(tooltip_options={"headertext": "header"}, tooltip=tooltip) 48 | tooltip_widget = getattr(holder, "tooltip_widget", None) 49 | self.assertIsNotNone(tooltip_widget) 50 | self.assertEqual(tooltip_widget["headertext"], "header") 51 | self.assertEqual(tooltip_widget["text"], tooltip) 52 | self.assertEqual(widget["tooltip"], tooltip) 53 | self.assertEqual(widget["tooltip_options"], {"headertext": "header"}) 54 | -------------------------------------------------------------------------------- /ttkwidgets/font/familylistbox.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | # Based on an idea by Nelson Brochado (https://www.github.com/nbro/tkinter-kit) 7 | import tkinter as tk 8 | from tkinter import font 9 | from ttkwidgets import ScrolledListbox 10 | 11 | 12 | class FontFamilyListbox(ScrolledListbox): 13 | """ 14 | :class:`~ttkwidgets.ScrolledListbox` listing all font families available on the system with a Scrollbar on the right with the option 15 | of a callback when double clicked and a property to get the font family name. 16 | """ 17 | 18 | def __init__(self, master=None, callback=None, **kwargs): 19 | """ 20 | Create a FontFamilyListbox. 21 | 22 | :param master: master widget 23 | :type master: widget 24 | :param callback: callable object with one argument: the font family name 25 | :type callback: function 26 | :param kwargs: keyword arguments passed to :class:`~ttkwidgets.ScrolledListbox`, in turn passed to :class:`tk.Listbox` 27 | """ 28 | ScrolledListbox.__init__(self, master, compound=tk.RIGHT, **kwargs) 29 | self._callback = callback 30 | font_names = sorted(set(font.families())) 31 | index = 0 32 | self.font_indexes = {} 33 | for name in font_names: 34 | self.listbox.insert(index, name) 35 | self.font_indexes[index] = name 36 | index += 1 37 | self.listbox.bind("<>", self._on_click) 38 | 39 | def _on_click(self, *args): 40 | """ 41 | Function bound to double click on Listbox that calls the callback if a valid callback object is passed 42 | 43 | :param args: Tkinter event 44 | """ 45 | if callable(self._callback): 46 | self._callback(self.selection) 47 | 48 | @property 49 | def selection(self): 50 | """ 51 | Selection property. 52 | 53 | :return: None if no font is selected and font family name if one is selected. 54 | :rtype: None or str 55 | """ 56 | selection = self.listbox.curselection() 57 | if len(selection) is 0: 58 | return None 59 | return self.font_indexes[self.listbox.curselection()[0]] 60 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | This file contains a list of all the authors of widgets in this repository. Please note that this list only mentions the original creators of the widgets, and the widgets may have been edited and/or improved or otherwise modified by other authors. 3 | 4 | - [RedFantom](https://www.github.com/RedFantom) 5 | * `ScrolledFrame`, based on an Unpythonic [idea](http://tkinter.unpythonic.net/wiki/VerticalScrolledFrame) 6 | * `ToggledFrame`, based on an idea by [Onlyjus](http://stackoverflow.com/questions/13141259/expandable-and-contracting-frame-in-tkinter) 7 | * `LinkLabel`, based on an idea by [Nelson Brochado](https://www.github.com/nbro) 8 | * `ScrolledListbox` 9 | * `FontChooser`, based on an idea by [Nelson Brochado](https://www.github.com/nbro) 10 | * `FontSelectFrame` 11 | * `Tooltip` 12 | * `ItemsCanvas` 13 | * `TimeLine` 14 | * `hook.py` and `tooltips.py` modules 15 | - The Python Team 16 | * `Calendar`, found [here](http://svn.python.org/projects/sandbox/trunk/ttk-gsoc/samples/ttkcalendar.py) 17 | - Mitja Martini 18 | * `AutocompleteEntry`, found [here](https://mail.python.org/pipermail/tkinter-discuss/2012-January/003041.html) 19 | - Russell Adams 20 | * `AutocompleteCombobox`, found [here](https://mail.python.org/pipermail/tkinter-discuss/2012-January/003041.html) 21 | - [Juliette Monsel](https://www.github.com/j4321) 22 | * `CheckboxTreeview` 23 | * `Table` 24 | * `TickScale` 25 | * `AutoHideScrollbar` based on an idea by [Fredrik Lundh](effbot.org/zone/tkinter-autoscrollbar.htm) 26 | * All color widgets: `askcolor`, `ColorPicker`, `GradientBar` and `ColorSquare`, `LimitVar`, `Spinbox`, `AlphaBar` and supporting functions in `functions.py`. 27 | * `AutocompleteEntryListbox` 28 | - [Simon Bordeyne](https://github.com/Dogeek) 29 | * `validated_entries` submodule 30 | - Multiple authors: 31 | * `ScaleEntry` (RedFantom and Juliette Monsel) 32 | - [rdbende](https://github.com/rdbende) 33 | - [Bart Broere](https://github.com/bartbroere) 34 | * PR #86 35 | - [Ryan Baker](https://github.com/ryanbaekr) 36 | * PR #96: `check_all` and `uncheck_all` for `CheckboxTreeview` 37 | - [Fredy Ramirez](https://github.com/formateli) 38 | * PR #59: Example runner (`examples/run.py`) 39 | - [jnhyperion](https://github.com/jnhyperion) 40 | * PR #31 41 | -------------------------------------------------------------------------------- /examples/run.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Fredy Ramirez 2 | # For license see LICENSE 3 | """ 4 | run.py 5 | Show all examples located in this example folder. 6 | Main window show a button for each example. 7 | """ 8 | import os 9 | import subprocess 10 | import tkinter as tk 11 | from tkinter import ttk 12 | 13 | _DIR = os.path.dirname(os.path.realpath(__file__)) 14 | _DIR = os.path.normpath(os.path.join(_DIR, '..')) 15 | 16 | EXAMPLE_ENV = os.environ.copy() 17 | if not 'PYTHONPATH' in EXAMPLE_ENV: 18 | EXAMPLE_ENV["PYTHONPATH"] = _DIR 19 | else: 20 | EXAMPLE_ENV["PYTHONPATH"] = _DIR + os.pathsep + EXAMPLE_ENV["PYTHONPATH"] 21 | 22 | 23 | class _SampleButton: 24 | def __init__(self, root, text, col, row): 25 | self.btn = ttk.Button(root, text=text, command=self.run_example) 26 | self.btn.grid(row=row, column=col, sticky="nsew") 27 | 28 | def run_example(self, event=None): 29 | subprocess.Popen(['python', 'example_' + self.btn['text'] + '.py'], env=EXAMPLE_ENV) 30 | 31 | 32 | def _get_samples(): 33 | result = [] 34 | dir_ = os.path.dirname(os.path.realpath(__file__)) 35 | files = os.listdir(dir_) 36 | for f in files: 37 | if f == 'run.py': 38 | continue 39 | fp = os.path.join(dir_, f) 40 | if os.path.isfile(f) and f.endswith('.py'): 41 | f = f[8:] # remove example_ 42 | f = f[0:len(f)-3] # remove .py 43 | result.append([f, fp]) 44 | return result 45 | 46 | 47 | def _add_samples(window): 48 | samples = _get_samples() 49 | max_col_count = 5 50 | row = -1 51 | col = -1 52 | row_checked = False 53 | for s in samples: 54 | if col == -1 or (row + 1) > max_col_count: 55 | col += 1 56 | window.grid_columnconfigure(col, weight=1) 57 | 58 | if row == -1 or (row + 1) > max_col_count: 59 | if (row + 1) > max_col_count: 60 | row_checked = True 61 | row = 0 62 | 63 | if not row_checked: 64 | window.grid_rowconfigure(row, weight=1) 65 | 66 | _SampleButton(window, s[0], col, row) 67 | row += 1 68 | 69 | 70 | if __name__ == '__main__': 71 | root = tk.Tk() 72 | root.title('ttkwidgets Examples') 73 | root.geometry('800x500') 74 | _add_samples(root) 75 | root.mainloop() 76 | -------------------------------------------------------------------------------- /ttkwidgets/color/limitvar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Author: Juliette Monsel 4 | License: GNU GPLv3 5 | Source: https://github.com/j4321/tkColorPicker 6 | 7 | Edited by RedFantom for Python 2/3 cross-compatibility and docstring formatting 8 | 9 | 10 | tkcolorpicker - Alternative to colorchooser for Tkinter. 11 | Copyright 2017 Juliette Monsel 12 | 13 | tkcolorpicker is free software: you can redistribute it and/or modify 14 | it under the terms of the GNU General Public License as published by 15 | the Free Software Foundation, either version 3 of the License, or 16 | (at your option) any later version. 17 | 18 | tkcolorpicker is distributed in the hope that it will be useful, 19 | but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | GNU General Public License for more details. 22 | 23 | You should have received a copy of the GNU General Public License 24 | along with this program. If not, see . 25 | 26 | Limited StringVar 27 | """ 28 | 29 | 30 | from .functions import tk 31 | 32 | 33 | class LimitVar(tk.StringVar): 34 | def __init__(self, from_, to, master=None, value=None, name=None): 35 | tk.StringVar.__init__(self, master, value, name) 36 | try: 37 | self._from = int(from_) 38 | self._to = int(to) 39 | except ValueError: 40 | raise ValueError("from_ and to should be integers.") 41 | if self._from >= self._to: 42 | raise ValueError("from_ should be smaller than to.") 43 | # ensure that the initial value is valid 44 | val = self.get() 45 | self.set(val) 46 | 47 | def get(self): 48 | """ 49 | Convert the content to int between the limits of the variable. 50 | 51 | If the content is not an integer between the limits, the value is 52 | corrected and the corrected result is returned. 53 | """ 54 | val = tk.StringVar.get(self) 55 | try: 56 | val = int(val) 57 | if val < self._from: 58 | val = self._from 59 | self.set(val) 60 | elif val > self._to: 61 | val = self._to 62 | self.set(val) 63 | except ValueError: 64 | val = 0 65 | self.set(0) 66 | return val 67 | -------------------------------------------------------------------------------- /ttkwidgets/frames/toggledframe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | import tkinter as tk 7 | from tkinter import ttk 8 | import os 9 | from PIL import Image, ImageTk 10 | from ttkwidgets.utilities import get_assets_directory 11 | 12 | 13 | class ToggledFrame(ttk.Frame): 14 | """ 15 | A frame that can be toggled to open and close. 16 | 17 | :ivar interior: :class:`ttk.Frame` in which to put the widgets to be toggled with any geometry manager. 18 | """ 19 | 20 | def __init__(self, master=None, text="", width=20, compound=tk.LEFT, **kwargs): 21 | """ 22 | Create a ToggledFrame. 23 | 24 | :param master: master widget 25 | :type master: widget 26 | :param text: text to display next to the toggle arrow 27 | :type text: str 28 | :param width: width of the closed ToggledFrame (in characters) 29 | :type width: int 30 | :param compound: "center", "none", "top", "bottom", "right" or "left": 31 | position of the toggle arrow compared to the text 32 | :type compound: str 33 | :param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer 34 | """ 35 | ttk.Frame.__init__(self, master, **kwargs) 36 | self._open = False 37 | self.__checkbutton_var = tk.BooleanVar() 38 | self._open_image = ImageTk.PhotoImage(Image.open(os.path.join(get_assets_directory(), "open.png"))) 39 | self._closed_image = ImageTk.PhotoImage(Image.open(os.path.join(get_assets_directory(), "closed.png"))) 40 | self._checkbutton = ttk.Checkbutton(self, style="Toolbutton", command=self.toggle, 41 | variable=self.__checkbutton_var, text=text, compound=compound, 42 | image=self._closed_image, width=width) 43 | self.interior = ttk.Frame(self, relief=tk.SUNKEN) 44 | self._grid_widgets() 45 | 46 | def _grid_widgets(self): 47 | self._checkbutton.grid(row=0, column=0, sticky="we") 48 | 49 | def toggle(self): 50 | """Toggle :obj:`ToggledFrame.interior` opened or closed.""" 51 | if self._open: 52 | self._open = False 53 | self.__checkbutton_var.set(False) 54 | self.interior.grid_forget() 55 | self._checkbutton.config(image=self._closed_image) 56 | else: 57 | self._open = True 58 | self.__checkbutton_var.set(True) 59 | self.interior.grid(row=1, column=0, sticky="nswe") 60 | self._checkbutton.config(image=self._open_image) 61 | -------------------------------------------------------------------------------- /docs/source/generate_examples.py: -------------------------------------------------------------------------------- 1 | # Examples documentation builder 2 | import os 3 | 4 | HEADER = "Example: {}" 5 | TEMPLATE = \ 6 | "{}\n" \ 7 | "{}\n" \ 8 | "\n" \ 9 | ".. code-block:: python\n" \ 10 | "\n" \ 11 | " {}" 12 | 13 | FILE_DIR = os.path.dirname(os.path.abspath(__file__)) 14 | FOLDER = os.path.join(FILE_DIR, "..", "..", "examples") 15 | EXAMPLES = os.listdir(FOLDER) 16 | 17 | EXAMPLES_FILE = \ 18 | "Examples\n" \ 19 | "========\n" \ 20 | "\n" \ 21 | "Examples can be run in one of two ways:\n" \ 22 | " - Run each example file as a stand alone script.\n" \ 23 | " - Run the *run.py* script, which open a window\n" \ 24 | " with all reports at once represented each one by a button.\n" \ 25 | "\n" \ 26 | "{}" 27 | 28 | TOCTREE_TEMPLATE = \ 29 | "{}\n"\ 30 | "\n" \ 31 | ".. toctree::\n" \ 32 | " :glob:\n" \ 33 | "\n" \ 34 | " {}" 35 | 36 | if not os.path.exists("examples"): 37 | os.makedirs("examples") 38 | 39 | example_files = list() 40 | 41 | pkgs = {} 42 | 43 | for example in EXAMPLES: 44 | if example == 'run.py': 45 | continue 46 | path = os.path.join(FOLDER, example) 47 | if not os.path.isfile(path): 48 | continue 49 | with open(path) as fi: 50 | lines = fi.readlines() 51 | text = " ".join(lines) 52 | package, widget = None, None 53 | for line in lines: 54 | if "from ttkwidgets" in line: 55 | elems = line.replace("from", "").split("import") 56 | package, widget = map(str.strip, elems) 57 | break 58 | if widget is None: 59 | print("[WARNING] Could not determine imported widget in {}".format(example)) 60 | continue 61 | 62 | pkg = package 63 | if not pkg in pkgs: 64 | pkgs[pkg] = [] 65 | 66 | package = package.split(".") 67 | package = "" if len(package) != 2 else package[1] 68 | 69 | header = HEADER.format(widget) 70 | text = TEMPLATE.format(header, "=" * len(header), text) 71 | 72 | out_path = os.path.join("examples", package) 73 | if not os.path.exists(out_path): 74 | os.makedirs(out_path) 75 | out_file = os.path.join(out_path, widget + ".rst") 76 | 77 | with open(out_file, "w") as fo: 78 | fo.write(text) 79 | 80 | pkgs[pkg].append(out_file.replace(".rst", "") + "\n") 81 | 82 | toctrees = [] 83 | 84 | for pkg in sorted(pkgs.keys()): 85 | header = pkg + '\n' + '-' * len(pkg) 86 | toctrees.append(TOCTREE_TEMPLATE.format(header, " ".join(sorted(pkgs[pkg])))) 87 | 88 | with open("examples.rst", "w") as fo: 89 | fo.write(EXAMPLES_FILE.format("\n\n".join(sorted(toctrees)))) 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ttkwidgets 2 | [![Build Status](https://travis-ci.org/TkinterEP/ttkwidgets.svg?branch=master)](https://travis-ci.org/TkinterEP/ttkwidgets) 3 | [![Build status](https://ci.appveyor.com/api/projects/status/eegux50s3kmb5w9g/branch/master?svg=true)](https://ci.appveyor.com/project/RedFantom/ttkwidgets-pq6y3) 4 | [![codecov](https://codecov.io/gh/TkinterEP/ttkwidgets/branch/master/graph/badge.svg)](https://codecov.io/gh/TkinterEP/ttkwidgets) 5 | [![PyPI version](https://badge.fury.io/py/ttkwidgets.svg)](https://badge.fury.io/py/ttkwidgets) 6 | [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](http://www.gnu.org/licenses/gpl-3.0) 7 | [![Documentation Status](https://readthedocs.org/projects/ttkwidgets/badge/?version=latest)](https://ttkwidgets.readthedocs.io/en/latest/) 8 | 9 | A collection of widgets for Tkinter's ttk extensions by various authors. 10 | 11 | ## License 12 | ttkwidgets: A collection of widgets for Tkinter's ttk extensions by various authors 13 | Copyright (c) 2008-2022 The ttkwidgets authors 14 | See AUTHORS.md for more details 15 | 16 | This program is free software: you can redistribute it and/or modify 17 | it under the terms of the GNU General Public License as published by 18 | the Free Software Foundation, either version 3 of the License, or 19 | (at your option) any later version. 20 | 21 | This program is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | GNU General Public License for more details. 25 | 26 | You should have received a copy of the GNU General Public License 27 | along with this program. If not, see . 28 | 29 | ## Installation 30 | - With pip: 31 | 32 | pip install ttkwidgets 33 | 34 | - Ubuntu: ttkwidgets is available in the PPA [ppa:j-4321-i/ttkwidgets](https://launchpad.net/~j-4321-i/+archive/ubuntu/ttkwidgets). 35 | 36 | sudo add-apt-repository ppa:j-4321-i/ttkwidgets 37 | sudo apt-get update 38 | sudo apt-get install python(3)-ttkwidgets 39 | 40 | - Archlinux: ttkwidgets is available in [AUR](https://aur.archlinux.org/packages/python-ttkwidgets). 41 | 42 | ## Documentation 43 | Online documentation is available here: https://ttkwidgets.readthedocs.io/en/latest/ 44 | 45 | ## Contributing 46 | If you have created a widget that you think is worth adding, then feel free to fork the repository and create a Pull 47 | Request when you've added the widget to your copy of the repository. You will be credited for your work, and you can add 48 | headers to your files. You will also be added to the [AUTHORS.md](AUTHORS.md) file. 49 | 50 | ## Issues 51 | If you find any bugs or have any ideas, feel free to open an issue here in the repository, and it will be looked at. 52 | 53 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. ttkwidgets documentation master file, created by 2 | sphinx-quickstart on Fri Sep 28 15:10:33 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to ttkwidgets's documentation! 7 | ====================================== 8 | 9 | |Travis| |Appveyor| |Codecov| |Pypi| |License| 10 | 11 | A collection of widgets for Tkinter's ttk extensions by various authors 12 | 13 | 14 | .. toctree:: 15 | :maxdepth: 1 16 | :caption: Contents: 17 | 18 | authors 19 | installation 20 | documentation 21 | examples 22 | genindex 23 | 24 | 25 | License 26 | ------- 27 | 28 | ttkwidgets: A collection of widgets for Tkinter's ttk extensions by various authors. 29 | 30 | * Copyright (C) RedFantom 2017 31 | * Copyright (C) The Python Team 32 | * Copyright (C) Mitja Martini 2008 33 | * Copyright (C) Russell Adams 2011 34 | * Copyright (C) Juliette Monsel 2017 35 | 36 | This program is free software: you can redistribute it and/or modify 37 | it under the terms of the GNU General Public License as published by 38 | the Free Software Foundation, either version 3 of the License, or 39 | (at your option) any later version. 40 | 41 | This program is distributed in the hope that it will be useful, 42 | but WITHOUT ANY WARRANTY; without even the implied warranty of 43 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 44 | GNU General Public License for more details. 45 | 46 | You should have received a copy of the GNU General Public License 47 | along with this program. If not, see http://www.gnu.org/licenses/. 48 | 49 | Contributing 50 | ------------ 51 | 52 | If you have created a widget that you think is worth adding, then feel 53 | free to fork the `repository `_ 54 | and create a `Pull Request `_ 55 | when you've added the widget to your copy of the repository. You will be 56 | credited for your work, and you can add headers to your files. 57 | You will also be added to the 58 | `AUTHORS.md `_ file. 59 | 60 | Issues 61 | ------ 62 | If you find any bugs or have any ideas, feel free to open an 63 | `issue `_ in the 64 | repository, and it will be looked at. 65 | 66 | 67 | 68 | .. |Travis| image:: https://travis-ci.org/RedFantom/ttkwidgets.svg?branch=master 69 | :alt: Travis CI Build Status 70 | :target: https://travis-ci.org/RedFantom/ttkwidgets 71 | 72 | .. |Appveyor| image:: https://ci.appveyor.com/api/projects/status/c6j6td273u3y6rw7/branch/master?svg=true 73 | :alt: Appveyor Build Status 74 | :target: https://ci.appveyor.com/project/RedFantom/ttkwidgets/branch/master 75 | 76 | .. |Codecov| image:: https://codecov.io/gh/RedFantom/ttkwidgets/branch/master/graph/badge.svg 77 | :alt: Code Coverage 78 | :target: https://codecov.io/gh/RedFantom/ttkwidgets 79 | 80 | .. |Pypi| image:: https://badge.fury.io/py/ttkwidgets.svg 81 | :alt: PyPI version 82 | :target: https://badge.fury.io/py/ttkwidgets 83 | 84 | .. |License| image:: https://img.shields.io/badge/License-GPL%20v3-blue.svg 85 | :alt: License: GPL v3 86 | :target: http://www.gnu.org/licenses/gpl-3.0 87 | 88 | -------------------------------------------------------------------------------- /ttkwidgets/debugwindow.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | import tkinter as tk 7 | from tkinter import ttk 8 | from tkinter import filedialog as fd 9 | import sys 10 | from ttkwidgets import AutoHideScrollbar 11 | 12 | 13 | class DebugWindow(tk.Toplevel): 14 | """ 15 | A Toplevel that shows sys.stdout and sys.stderr for Tkinter applications 16 | """ 17 | def __init__(self, master=None, title="Debug window", stdout=True, 18 | stderr=False, width=70, autohidescrollbar=True, **kwargs): 19 | """ 20 | Create a Debug window. 21 | 22 | :param master: master widget 23 | :type master: widget 24 | :param stdout: whether to redirect stdout to the widget 25 | :type stdout: bool 26 | :param stderr: whether to redirect stderr to the widget 27 | :type stderr: bool 28 | :param width: window width (in characters) 29 | :type width: int 30 | :param autohidescrollbar: whether to use an :class:`~ttkwidgets.AutoHideScrollbar` or a :class:`ttk.Scrollbar` 31 | :type autohidescrollbar: bool 32 | :param kwargs: options to be passed on to the :class:`tk.Toplevel` initializer 33 | """ 34 | self._width = width 35 | tk.Toplevel.__init__(self, master, **kwargs) 36 | self.columnconfigure(0, weight=1) 37 | self.rowconfigure(0, weight=1) 38 | self.protocol("WM_DELETE_WINDOW", self.quit) 39 | self.wm_title(title) 40 | self._oldstdout = sys.stdout 41 | self._oldstderr = sys.stderr 42 | if stdout: 43 | sys.stdout = self 44 | if stderr: 45 | sys.stderr = self 46 | self.menu = tk.Menu(self) 47 | self.config(menu=self.menu) 48 | self.filemenu = tk.Menu(self.menu, tearoff=0) 49 | self.filemenu.add_command(label="Save file", command=self.save) 50 | self.filemenu.add_command(label="Exit", command=self.quit) 51 | self.menu.add_cascade(label="File", menu=self.filemenu) 52 | self.text = tk.Text(self, width=width, wrap=tk.WORD) 53 | if autohidescrollbar: 54 | self.scroll = AutoHideScrollbar(self, orient=tk.VERTICAL, command=self.text.yview) 55 | else: 56 | self.scroll = ttk.Scrollbar(self, orient=tk.VERTICAL, command=self.text.yview) 57 | self.text.config(yscrollcommand=self.scroll.set) 58 | self.text.bind("", lambda e: "break") 59 | self._grid_widgets() 60 | 61 | def save(self): 62 | """Save widget content.""" 63 | file_name = fd.asksaveasfilename() 64 | if file_name == "" or file_name is None: 65 | return 66 | with open(file_name, "w") as f: 67 | f.write(self.text.get("1.0", tk.END)) 68 | 69 | def _grid_widgets(self): 70 | self.text.grid(row=0, column=0, sticky="nsew") 71 | self.scroll.grid(row=0, column=1, sticky="ns") 72 | 73 | def write(self, line): 74 | """ 75 | Write line at the end of the widget. 76 | 77 | :param line: text to insert in the widget 78 | :type line: str 79 | """ 80 | self.text.insert(tk.END, line) 81 | 82 | def flush(self): 83 | pass 84 | 85 | def quit(self): 86 | """Restore previous stdout/stderr and destroy the window.""" 87 | sys.stdout = self._oldstdout 88 | sys.stderr = self._oldstderr 89 | self.destroy() 90 | -------------------------------------------------------------------------------- /docs/source/authors.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | List of all the authors of widgets in this repository. Please note that this list only mentions the original creators of the widgets, and the widgets may have been edited and/or improved or otherwise modified by other authors. 5 | 6 | - `RedFantom `_ 7 | 8 | * :class:`~ttkwidgets.frames.ScrolledFrame`, based on an Unpythonic `idea `_ 9 | * :class:`~ttkwidgets.frames.ToggledFrame`, based on an idea by `Onlyjus `_ 10 | * :class:`~ttkwidgets.LinkLabel`, based on an idea by `Nelson Brochado `_ 11 | * :class:`~ttkwidgets.ScrolledListbox` 12 | * :class:`~ttkwidgets.font.FontChooser`, based on an idea by `Nelson Brochado `_ 13 | * :class:`~ttkwidgets.font.FontSelectFrame` 14 | * :class:`~ttkwidgets.frames.Tooltip` 15 | * :class:`~ttkwidgets.ItemsCanvas` 16 | * :class:`~ttkwidgets.TimeLine` 17 | 18 | 19 | - The Python Team 20 | 21 | * :class:`~ttkwidgets.Calendar`, found `here `_ 22 | 23 | - Mitja Martini 24 | 25 | * :class:`~ttkwidgets.autocomplete.AutocompleteEntry`, found `here `_ 26 | 27 | - Russell Adams 28 | 29 | * :class:`~ttkwidgets.autocomplete.AutocompleteCombobox`, found `here `_ 30 | 31 | - `Juliette Monsel `_ 32 | 33 | * :class:`~ttkwidgets.CheckboxTreeview` 34 | * :class:`~ttkwidgets.Table` 35 | * :class:`~ttkwidgets.TickScale` 36 | * :class:`~ttkwidgets.AutoHideScrollbar` based on an idea by `Fredrik Lundh `_ 37 | * All color widgets: :func:`~ttkwidgets.color.askcolor`, :class:`~ttkwidgets.color.ColorPicker`, :class:`~ttkwidgets.color.GradientBar` and :class:`~ttkwidgets.color.ColorSquare`, :class:`~ttkwidgets.color.LimitVar`, :class:`~ttkwidgets.color.Spinbox`, :class:`~ttkwidgets.color.AlphaBar` and supporting functions in :file:`functions.py`. 38 | * :class:`~ttkwidgets.autocomplete.AutocompleteEntryListbox` 39 | 40 | - `Dogeek `_ 41 | 42 | * :class:`~ttkwidgets.validated_entries.ValidatedEntry` 43 | * :class:`~ttkwidgets.validated_entries.Validator` 44 | * :class:`~ttkwidgets.validated_entries.RegexValidator` 45 | * :class:`~ttkwidgets.validated_entries.IntValidator` 46 | * :class:`~ttkwidgets.validated_entries.FloatValidator` 47 | * :class:`~ttkwidgets.validated_entries.PercentValidator` 48 | * :class:`~ttkwidgets.validated_entries.StringValidator` 49 | * :class:`~ttkwidgets.validated_entries.CapitalizedStringValidator` 50 | * :class:`~ttkwidgets.validated_entries.EmailValidator` 51 | * :class:`~ttkwidgets.validated_entries.PasswordValidator` 52 | * :class:`~ttkwidgets.validated_entries.IntEntry` 53 | * :class:`~ttkwidgets.validated_entries.FloatEntry` 54 | * :class:`~ttkwidgets.validated_entries.PercentEntry` 55 | * :class:`~ttkwidgets.validated_entries.LowerStringEntry` 56 | * :class:`~ttkwidgets.validated_entries.UpperStringEntry` 57 | * :class:`~ttkwidgets.validated_entries.CapitalizedStringEntry` 58 | * :class:`~ttkwidgets.validated_entries.EmailEntry` 59 | * :class:`~ttkwidgets.validated_entries.PasswordEntry` 60 | 61 | - Multiple authors: 62 | 63 | * :class:`~ttkwidgets.ScaleEntry` (RedFantom and Juliette Monsel) 64 | -------------------------------------------------------------------------------- /tests/test_checkboxtreeview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Juliette Monsel 2017 4 | # For license see LICENSE 5 | 6 | from ttkwidgets import CheckboxTreeview 7 | from tests import BaseWidgetTest 8 | 9 | 10 | class TestCheckboxTreeview(BaseWidgetTest): 11 | def test_checkboxtreeview_init(self): 12 | tree = CheckboxTreeview(self.window) 13 | tree.pack() 14 | self.window.update() 15 | 16 | def test_checkboxtreeview_events(self): 17 | tree = CheckboxTreeview(self.window) 18 | tree.pack() 19 | self.window.update() 20 | tree.event_generate("<1>", x=10, y=10) 21 | self.window.update() 22 | 23 | def test_checkboxtreeview_methods(self): 24 | tree = CheckboxTreeview(self.window) 25 | tree.pack() 26 | self.window.update() 27 | tree.insert("", "end", "1", text="1") 28 | tree.insert("1", "end", "11", text="11") 29 | tree.insert("11", "end", "111", text="111") 30 | tree.insert("11", "end", "112", text="112") 31 | self.window.update() 32 | tree.state() 33 | self.window.update() 34 | tree.state(['disabled']) 35 | self.window.update() 36 | tree.state(['!disabled']) 37 | self.window.update() 38 | tree.collapse_all() 39 | self.window.update() 40 | tree.expand_all() 41 | self.window.update() 42 | tree.tag_add("1", "item") 43 | self.assertTrue(tree.tag_has("item", "1")) 44 | self.window.update() 45 | tree.change_state("1", "checked") 46 | self.assertTrue(tree.tag_has("checked", "1")) 47 | self.assertFalse(tree.tag_has("unchecked", "1")) 48 | self.assertFalse(tree.tag_has("tristate", "1")) 49 | self.assertTrue(tree.tag_has("item", "1")) 50 | self.window.update() 51 | tree.tag_del("1", "item") 52 | self.assertFalse(tree.tag_has("item", "1")) 53 | self.window.update() 54 | tree._check_descendant("1") 55 | self.assertTrue(tree.tag_has("checked", "11")) 56 | self.assertTrue(tree.tag_has("checked", "111")) 57 | self.assertTrue(tree.tag_has("checked", "112")) 58 | self.window.update() 59 | tree._uncheck_descendant("1") 60 | self.assertTrue(tree.tag_has("unchecked", "11")) 61 | self.assertTrue(tree.tag_has("unchecked", "111")) 62 | self.assertTrue(tree.tag_has("unchecked", "112")) 63 | self.window.update() 64 | tree.check_all() 65 | self.assertTrue(tree.tag_has("checked", "11")) 66 | self.assertTrue(tree.tag_has("checked", "111")) 67 | self.assertTrue(tree.tag_has("checked", "112")) 68 | self.window.update() 69 | tree.uncheck_all() 70 | self.assertTrue(tree.tag_has("unchecked", "11")) 71 | self.assertTrue(tree.tag_has("unchecked", "111")) 72 | self.assertTrue(tree.tag_has("unchecked", "112")) 73 | self.window.update() 74 | tree._check_ancestor("111") 75 | self.assertTrue(tree.tag_has("tristate", "11")) 76 | self.window.update() 77 | tree._check_ancestor("112") 78 | self.assertTrue(tree.tag_has("checked", "11")) 79 | self.window.update() 80 | tree._uncheck_ancestor("111") 81 | self.assertTrue(tree.tag_has("tristate", "11")) 82 | self.window.update() 83 | tree._uncheck_ancestor("112") 84 | self.assertTrue(tree.tag_has("unchecked", "11")) 85 | self.window.update() 86 | tree._tristate_parent("111") 87 | self.assertTrue(tree.tag_has("tristate", "11")) 88 | self.assertTrue(tree.tag_has("tristate", "1")) 89 | self.window.update() 90 | tree.change_state("1", "checked") 91 | tree._check_descendant("1") 92 | self.assertEqual(tree.get_checked(), ["111", "112"]) 93 | self.window.update() 94 | -------------------------------------------------------------------------------- /tests/test_balloon.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | from ttkwidgets.frames import Tooltip 7 | from ttkwidgets.utilities import parse_geometry_string 8 | from tests import BaseWidgetTest 9 | import tkinter as tk 10 | from time import sleep 11 | 12 | 13 | class TestTooltip(BaseWidgetTest): 14 | def test_balloon_init(self): 15 | balloon = Tooltip(self.window) 16 | self.window.update() 17 | 18 | def test_balloon_kwargs(self): 19 | balloon = Tooltip(self.window, headertext="Help", text="This is a test for the Tooltip widget.", width=300, 20 | timeout=2, background="white", showheader=True, offset=(20, 20), static=True) 21 | self.assertEqual(balloon.cget("headertext"), "Help") 22 | self.assertEqual(balloon.cget("text"), "This is a test for the Tooltip widget.") 23 | self.assertEqual(balloon.cget("width"), 300) 24 | self.assertEqual(balloon.cget("timeout"), 2) 25 | self.assertEqual(balloon.cget("background"), "white") 26 | 27 | balloon.config(headertext="New Help", text="This is another test for the Tooltip widget.", width=400, 28 | timeout=3, background="black") 29 | self.assertEqual(balloon["headertext"], "New Help") 30 | self.assertEqual(balloon["text"], "This is another test for the Tooltip widget.") 31 | self.assertEqual(balloon["width"], 400) 32 | self.assertEqual(balloon["timeout"], 3) 33 | self.assertEqual(balloon["background"], "black") 34 | self.assertEqual(balloon["showheader"], True) 35 | self.assertEqual(balloon["offset"], (20, 20)) 36 | self.assertEqual(balloon["static"], True) 37 | 38 | # Keys for the Frame widget 39 | balloon.configure(height=40) 40 | self.assertEqual(balloon.cget("height"), 40) 41 | 42 | balloon['height'] = 50 43 | self.assertEqual(balloon["height"], 50) 44 | 45 | for key in ["headertext", "text", "width", "timeout", "background"]: 46 | self.assertIn(key, balloon.keys()) 47 | 48 | balloon.config(showheader=False) 49 | balloon.show() 50 | self.assertFalse(balloon.header_label.winfo_viewable() == 1) 51 | 52 | balloon.config(offset=(0, 0)) 53 | balloon.show() 54 | self.window.update() 55 | x1, y1, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry()) 56 | balloon.config(offset=(20, 20)) 57 | balloon._on_leave(None) 58 | balloon.show() 59 | self.window.update() 60 | x2, y2, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry()) 61 | self.assertTrue(x2 - x1 == 20 and y2 - y1 == 20) 62 | 63 | balloon.config(static=False) 64 | balloon.show() 65 | self.window.update() 66 | x3, y3, _, _ = parse_geometry_string(balloon._toplevel.winfo_geometry()) 67 | self.assertFalse(x2 == x3 or y2 == y3) 68 | 69 | def test_balloon_show(self): 70 | balloon = Tooltip(self.window) 71 | self.window.update() 72 | balloon.show() 73 | self.window.update() 74 | balloon.config(text="Something else") 75 | self.window.update() 76 | self.assertEqual(balloon.cget("text"), "Something else") 77 | balloon._on_leave(None) 78 | self.window.update() 79 | self.assertIs(balloon._toplevel, None) 80 | 81 | def test_balloon_events(self): 82 | balloon = Tooltip(self.window, timeout=0.2) 83 | balloon._on_enter(None) 84 | self.window.update() 85 | sleep(0.3) 86 | self.window.update() 87 | self.assertIsInstance(balloon._toplevel, tk.Toplevel) 88 | balloon._on_leave(None) 89 | self.assertIs(balloon._toplevel, None) 90 | 91 | def test_balloon_events_noshow(self): 92 | balloon = Tooltip(self.window) 93 | balloon._on_enter(None) 94 | balloon._on_leave(None) 95 | -------------------------------------------------------------------------------- /tests/test_font_widgets.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets.font import FontChooser, FontSelectFrame 4 | from tests import BaseWidgetTest 5 | import tkinter as tk 6 | from tkinter import font 7 | import os 8 | 9 | 10 | if "TRAVIS" not in os.environ: 11 | class TestFontChooser(BaseWidgetTest): 12 | def test_fontchooser_init(self): 13 | chooser = FontChooser(self.window) 14 | self.window.update() 15 | chooser._close() 16 | 17 | def test_fontchooser_noselection(self): 18 | chooser = FontChooser(self.window) 19 | self.window.update() 20 | results = chooser.font 21 | self.assertEqual(results, (None, None)) 22 | 23 | def test_fontchooser_selection(self): 24 | chooser = FontChooser(self.window) 25 | self.window.update() 26 | chooser._font_family_list.listbox.selection_set(1) 27 | chooser._font_family_list._on_click() 28 | self.window.update() 29 | results = chooser.font 30 | self.assertIsInstance(results, tuple) 31 | self.assertEqual(len(results), 2) 32 | self.assertIsInstance(results[0], tuple) 33 | self.assertEqual(len(results[0]), 2) 34 | self.assertIsInstance(results[1], font.Font) 35 | chooser._on_family('') 36 | self.window.update() 37 | results = chooser.font 38 | self.assertIsNone(results[0]) 39 | 40 | def test_fontchooser_properties(self): 41 | chooser = FontChooser(self.window) 42 | self.window.update() 43 | chooser._font_family_list.listbox.selection_set(1) 44 | chooser._font_family_list._on_click() 45 | self.window.update() 46 | chooser._font_properties_frame._bold.set(True) 47 | chooser._font_properties_frame._on_click() 48 | self.window.update() 49 | results = chooser.font 50 | self.assertTrue("bold" in results[0]) 51 | 52 | chooser._on_properties((True, True, True, True)) 53 | self.window.update() 54 | results = chooser.font 55 | self.assertEqual(results[0][2:], ('bold', 'italic', 56 | 'underline', 'overstrike')) 57 | 58 | def test_fontchooser_size(self): 59 | chooser = FontChooser(self.window) 60 | self.window.update() 61 | chooser._font_family_list.listbox.selection_set(1) 62 | chooser._font_family_list._on_click() 63 | self.window.update() 64 | chooser._size_dropdown.delete(0, tk.END) 65 | chooser._size_dropdown.insert(0, "14") 66 | self.window.update() 67 | chooser._size_dropdown._on_click(None) 68 | self.window.update() 69 | results = chooser.font 70 | self.assertEqual(results[0][1], 14) 71 | 72 | class TestFontSelectFrame(BaseWidgetTest): 73 | def test_fontselectframe_init(self): 74 | frame = FontSelectFrame(self.window) 75 | frame.pack() 76 | self.window.update() 77 | 78 | def test_fontselectframe_family(self): 79 | frame = FontSelectFrame(self.window) 80 | frame.pack() 81 | self.window.update() 82 | frame._family_dropdown.set(font.families()[1]) 83 | self.window.update() 84 | frame._on_family(frame._family_dropdown.get()) 85 | results = frame.font 86 | self.assertIsInstance(results, tuple) 87 | self.assertEqual(len(results), 2) 88 | self.assertIsInstance(results[0], tuple) 89 | self.assertEqual(len(results[0]), 2) 90 | self.assertIsInstance(results[1], font.Font) 91 | 92 | frame._on_size(20) 93 | frame._on_family('Arial') 94 | frame._on_properties((True, True, True, True)) 95 | self.window.update() 96 | results = frame.font 97 | self.assertEqual(results[0], ('Arial', 20, 'bold', 'italic', 98 | 'underline', 'overstrike')) 99 | -------------------------------------------------------------------------------- /ttkwidgets/tooltips.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: The ttkwidgets repository 5 | 6 | By importing this file at some point in a program, the 7 | ttk.Widget parent classes get dynamically mixed in with a class that 8 | adds the functionality of adding a tooltip (Tooltip) to any widget. 9 | 10 | Tooltips are only added when a string to show in them is explicitly 11 | given. Options for a Tooltip may be given as a dictionary of keyword 12 | arguments upon widget initialization. 13 | 14 | The default options for the Tooltip widget for the program may also be 15 | changed by using the update_defaults function. 16 | """ 17 | try: 18 | import Tkinter as tk 19 | import ttk 20 | except ImportError: 21 | import tkinter as tk 22 | from tkinter import ttk 23 | from ttkwidgets.hook import hook_ttk_widgets, generate_hook_name, is_hooked 24 | from ttkwidgets.frames import Tooltip 25 | 26 | 27 | OPTIONS = {"tooltip": None, "tooltip_options": {}} 28 | NAME = generate_hook_name(OPTIONS) 29 | 30 | 31 | def update_defaults(defaults): 32 | # type: (dict) -> None 33 | """ 34 | Update the default options applied to the tooltip of a widget 35 | 36 | Updating the default options of the hook is no longer possible after 37 | the hook has been setup, but the :meth:`tooltip_updater` applies 38 | the default options first to all tooltips and only overwrites them 39 | if custom options have been given for the tooltip. 40 | """ 41 | global OPTIONS 42 | OPTIONS["tooltip_options"] = defaults 43 | 44 | 45 | def tooltip_options_hook(self, option, value): 46 | # type: (ttk.Widget, str, (str, dict)) -> None 47 | """ 48 | Updater function for the 'tooltip' and 'tooltip_options' kwargs hook 49 | 50 | Given option ``tooltip``, configures the text of the tooltip of a 51 | widget. 52 | Given option ``tooltip_options``, updates the options the tooltip 53 | widget is configured with. 54 | 55 | :param self: ttk.Widget for which the hook is executed 56 | :param option: Option to be updated on the ttk.Widget 57 | :param value: The value of the given option. Will be a ``str`` for 58 | option ``tooltip`` and a dictionary for option 59 | ``tooltip_options``. 60 | """ 61 | holder = getattr(self, NAME.lower()) # Instance of OriginalFunctions 62 | tooltip_widget = getattr(holder, "tooltip_widget", None) 63 | if option == "tooltip": 64 | tooltip_tooltip_updater(self, holder, tooltip_widget, value) 65 | elif option == "tooltip_options": 66 | tooltip_options_updater(self, holder, tooltip_widget, value) 67 | else: 68 | raise RuntimeError("Invalid option passed to tooltip_updater") 69 | 70 | 71 | def tooltip_tooltip_updater(self, holder, tooltip_widget, tooltip): 72 | # type: ((tk.Widget, ttk.Widget), object, (Tooltip, None), (str, None)) -> None 73 | """Update the ``tooltip`` option of a widget by updating tooltip text""" 74 | if tooltip_widget is None and tooltip is not None: 75 | # Create a new tooltip 76 | options = OPTIONS["tooltip_options"].copy() 77 | options.update(getattr(holder, "tooltip_options", {})) 78 | options["text"] = tooltip 79 | tooltip_widget = Tooltip(self, **options) 80 | elif tooltip_widget is not None and tooltip is None: 81 | # Destroy existing tooltip 82 | tooltip_widget.destroy() 83 | tooltip_widget = None 84 | elif tooltip_widget is not None: 85 | # Update existing tooltip 86 | tooltip_widget.configure(text=tooltip) 87 | else: # tooltip_widget is None and tooltip is None 88 | pass 89 | setattr(holder, "tooltip_widget", tooltip_widget) 90 | 91 | 92 | def tooltip_options_updater(self, holder, tooltip_widget, options): 93 | """Update the options of a tooltip widget held on a widget""" 94 | # Update options for when a new tooltip is created 95 | new_options = getattr(holder, "tooltip_options", {}) 96 | new_options.update(options) 97 | setattr(holder, "tooltip_options", new_options) 98 | if tooltip_widget is not None: 99 | # Tooltip already exists, configure it with new options 100 | tooltip_widget.configure(**new_options) 101 | 102 | 103 | if not is_hooked(OPTIONS): 104 | hook_ttk_widgets(tooltip_options_hook, OPTIONS) 105 | -------------------------------------------------------------------------------- /ttkwidgets/font/selectframe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | # Based on an idea by Nelson Brochado (https://www.github.com/nbro/tkinter-kit) 7 | from tkinter import ttk 8 | from tkinter import font 9 | from .familydropdown import FontFamilyDropdown 10 | from .propertiesframe import FontPropertiesFrame 11 | from .sizedropdown import FontSizeDropdown 12 | 13 | 14 | class FontSelectFrame(ttk.Frame): 15 | """ 16 | A frame to use in your own application to let the user choose a font. 17 | 18 | For :class:`~font.Font` object, use :obj:`font` property. 19 | """ 20 | 21 | def __init__(self, master=None, callback=None, **kwargs): 22 | """ 23 | :param master: master widget 24 | :type master: widget 25 | :param callback: callback passed argument 26 | (`str` family, `int` size, `bool` bold, `bool` italic, `bool` underline) 27 | :type callback: function 28 | :param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer 29 | """ 30 | ttk.Frame.__init__(self, master, **kwargs) 31 | self.__callback = callback 32 | self._family = None 33 | self._size = 11 34 | self._bold = False 35 | self._italic = False 36 | self._underline = False 37 | self._overstrike = False 38 | self._family_dropdown = FontFamilyDropdown(self, callback=self._on_family) 39 | self._size_dropdown = FontSizeDropdown(self, callback=self._on_size, width=4) 40 | self._properties_frame = FontPropertiesFrame(self, callback=self._on_properties, label=False) 41 | self._grid_widgets() 42 | 43 | def _grid_widgets(self): 44 | """ 45 | Puts all the widgets in the correct place. 46 | """ 47 | self._family_dropdown.grid(row=0, column=0, sticky="nswe") 48 | self._size_dropdown.grid(row=0, column=1, sticky="nswe") 49 | self._properties_frame.grid(row=0, column=2, sticky="nswe") 50 | 51 | def _on_family(self, name): 52 | """ 53 | Callback if family is changed. 54 | 55 | :param name: font family name 56 | """ 57 | self._family = name 58 | self._on_change() 59 | 60 | def _on_size(self, size): 61 | """ 62 | Callback if size is changed. 63 | 64 | :param size: font size int 65 | """ 66 | self._size = size 67 | self._on_change() 68 | 69 | def _on_properties(self, properties): 70 | """ 71 | Callback if properties are changed 72 | 73 | :param properties: tuple (bold, italic, underline, overstrike) 74 | """ 75 | self._bold, self._italic, self._underline, self._overstrike = properties 76 | self._on_change() 77 | 78 | def _on_change(self): 79 | """Call callback if any property is changed.""" 80 | if callable(self.__callback): 81 | self.__callback((self._family, self._size, self._bold, self._italic, self._underline, self._overstrike)) 82 | 83 | def __generate_font_tuple(self): 84 | """ 85 | Generate a font tuple for tkinter widgets based on the user's entries. 86 | 87 | :return: font tuple (family_name, size, *options) 88 | """ 89 | if not self._family: 90 | return None 91 | font = [self._family, self._size] 92 | if self._bold: 93 | font.append("bold") 94 | if self._italic: 95 | font.append("italic") 96 | if self._underline: 97 | font.append("underline") 98 | if self._overstrike: 99 | font.append("overstrike") 100 | return tuple(font) 101 | 102 | @property 103 | def font(self): 104 | """ 105 | Font property. 106 | 107 | :return: a :class:`~font.Font` object if family is set, else None 108 | :rtype: :class:`~font.Font` or None 109 | """ 110 | if not self._family: 111 | return None, None 112 | font_obj = font.Font(family=self._family, size=self._size, 113 | weight=font.BOLD if self._bold else font.NORMAL, 114 | slant=font.ITALIC if self._italic else font.ROMAN, 115 | underline=1 if self._underline else 0, 116 | overstrike=1 if self._overstrike else 0) 117 | font_tuple = self.__generate_font_tuple() 118 | return font_tuple, font_obj 119 | -------------------------------------------------------------------------------- /ttkwidgets/linklabel.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | # Based on an idea by Nelson Brochado (https://www.github.com/nbrol/tkinter-kit) 7 | import tkinter as tk 8 | from tkinter import ttk 9 | import webbrowser 10 | 11 | 12 | class LinkLabel(ttk.Label): 13 | """ 14 | A :class:`ttk.Label` that can be clicked to open a link with a default blue color, a purple color when clicked and a bright 15 | blue color when hovering over the Label. 16 | """ 17 | def __init__(self, master=None, **kwargs): 18 | """ 19 | Create a LinkLabel. 20 | 21 | :param master: master widget 22 | :param link: link to be opened 23 | :type link: str 24 | :param normal_color: text color when widget is created 25 | :type normal_color: str 26 | :param hover_color: text color when hovering over the widget 27 | :type hover_color: str 28 | :param clicked_color: text color when link is clicked 29 | :type clicked_color: str 30 | :param kwargs: options to be passed on to the :class:`ttk.Label` initializer 31 | """ 32 | self._cursor = kwargs.pop("cursor", "hand1") 33 | self._link = kwargs.pop("link", "") 34 | self._normal_color = kwargs.pop("normal_color", "#0563c1") 35 | self._hover_color = kwargs.pop("hover_color", "#057bc1") 36 | self._clicked_color = kwargs.pop("clicked_color", "#954f72") 37 | ttk.Label.__init__(self, master, **kwargs) 38 | self.config(foreground=self._normal_color) 39 | self.__clicked = False 40 | self.bind("", self.open_link) 41 | self.bind("", self._on_enter) 42 | self.bind("", self._on_leave) 43 | 44 | def __getitem__(self, key): 45 | return self.cget(key) 46 | 47 | def __setitem__(self, key, value): 48 | self.configure(**{key: value}) 49 | 50 | def _on_enter(self, *args): 51 | """Set the text color to the hover color.""" 52 | self.config(foreground=self._hover_color, cursor=self._cursor) 53 | 54 | def _on_leave(self, *args): 55 | """Set the text color to either the normal color when not clicked or the clicked color when clicked.""" 56 | if self.__clicked: 57 | self.config(foreground=self._clicked_color) 58 | else: 59 | self.config(foreground=self._normal_color) 60 | self.config(cursor="") 61 | 62 | def reset(self): 63 | """Reset Label to unclicked status if previously clicked.""" 64 | self.__clicked = False 65 | self._on_leave() 66 | 67 | def open_link(self, *args): 68 | """Open the link in the web browser.""" 69 | if "disabled" not in self.state(): 70 | webbrowser.open(self._link) 71 | self.__clicked = True 72 | self._on_leave() 73 | 74 | def cget(self, key): 75 | """ 76 | Query widget option. 77 | 78 | :param key: option name 79 | :type key: str 80 | :return: value of the option 81 | 82 | To get the list of options for this widget, call the method :meth:`~LinkLabel.keys`. 83 | """ 84 | if key == "link": 85 | return self._link 86 | elif key == "hover_color": 87 | return self._hover_color 88 | elif key == "normal_color": 89 | return self._normal_color 90 | elif key == "clicked_color": 91 | return self._clicked_color 92 | else: 93 | return ttk.Label.cget(self, key) 94 | 95 | def configure(self, **kwargs): 96 | """ 97 | Configure resources of the widget. 98 | 99 | To get the list of options for this widget, call the method :meth:`~LinkLabel.keys`. 100 | See :meth:`~LinkLabel.__init__` for a description of the widget specific option. 101 | """ 102 | self._link = kwargs.pop("link", self._link) 103 | self._hover_color = kwargs.pop("hover_color", self._hover_color) 104 | self._normal_color = kwargs.pop("normal_color", self._normal_color) 105 | self._clicked_color = kwargs.pop("clicked_color", self._clicked_color) 106 | ttk.Label.configure(self, **kwargs) 107 | self._on_leave() 108 | 109 | def keys(self): 110 | """Return a list of all resource names of this widget.""" 111 | keys = ttk.Label.keys(self) 112 | keys.extend(["link", "normal_color", "hover_color", "clicked_color"]) 113 | return keys 114 | 115 | -------------------------------------------------------------------------------- /ttkwidgets/font/propertiesframe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | # Based on an idea by Nelson Brochado (https://www.github.com/nbro/tkinter-kit) 7 | import tkinter as tk 8 | from tkinter import ttk 9 | 10 | 11 | class FontPropertiesFrame(ttk.Frame): 12 | """ 13 | Simple frame with buttons for Bold, Italic and Underline font types. 14 | """ 15 | 16 | def __init__(self, master=None, callback=None, label=True, fontsize=11, **kwargs): 17 | """ 18 | Create a FontPropertiesFrame. 19 | 20 | :param master: master widget 21 | :type master: widget 22 | :param callback: callback with argument 23 | (`bool` bold, `bool` italic, `bool` underline, `bool` overstrike) 24 | :type callback: function 25 | :param label: show a header label 26 | :type label: str 27 | :param fontsize: size of the font on the buttons 28 | :type fontsize: int 29 | :param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer 30 | """ 31 | ttk.Frame.__init__(self, master, **kwargs) 32 | self._style = ttk.Style() 33 | self.__label = label 34 | self.__callback = callback 35 | self._header_label = ttk.Label(self, text="Font properties:") 36 | self._style.configure("Bold.Toolbutton", font=("default", fontsize, "bold"), anchor=tk.CENTER) 37 | self._style.configure("Italic.Toolbutton", font=("default", fontsize, "italic"), anchor=tk.CENTER) 38 | self._style.configure("Underline.Toolbutton", font=("default", fontsize, "underline"), anchor=tk.CENTER) 39 | self._style.configure("Overstrike.Toolbutton", font=("default", fontsize, "overstrike"), anchor=tk.CENTER) 40 | self._bold = tk.BooleanVar() 41 | self._italic = tk.BooleanVar() 42 | self._underline = tk.BooleanVar() 43 | self._overstrike = tk.BooleanVar() 44 | self._bold_button = ttk.Checkbutton(self, style="Bold.Toolbutton", text="B", width=2, command=self._on_click, 45 | variable=self._bold) 46 | self._italic_button = ttk.Checkbutton(self, style="Italic.Toolbutton", text="I", width=2, 47 | command=self._on_click, variable=self._italic) 48 | self._underline_button = ttk.Checkbutton(self, style="Underline.Toolbutton", text="U", width=2, 49 | command=self._on_click, variable=self._underline) 50 | self._overstrike_button = ttk.Checkbutton(self, style="Overstrike.Toolbutton", text="O", width=2, 51 | command=self._on_click, variable=self._overstrike) 52 | self._grid_widgets() 53 | 54 | def _grid_widgets(self): 55 | """ 56 | Place the widgets in the correct positions 57 | :return: None 58 | """ 59 | if self.__label: 60 | self._header_label.grid(row=0, column=1, columnspan=3, sticky="nw", padx=5, pady=(5, 0)) 61 | self._bold_button.grid(row=1, column=1, sticky="nswe", padx=5, pady=2) 62 | self._italic_button.grid(row=1, column=2, sticky="nswe", padx=(0, 5), pady=2) 63 | self._underline_button.grid(row=1, column=3, sticky="nswe", padx=(0, 5), pady=2) 64 | self._overstrike_button.grid(row=1, column=4, sticky="nswe", padx=(0, 5), pady=2) 65 | 66 | def _on_click(self): 67 | """Handles clicks and calls callback.""" 68 | if callable(self.__callback): 69 | self.__callback((self.bold, self.italic, self.underline, self.overstrike)) 70 | 71 | @property 72 | def bold(self): 73 | """ 74 | Bold property. 75 | 76 | :return: True if bold is selected 77 | :rtype: bool 78 | """ 79 | return self._bold.get() 80 | 81 | @property 82 | def italic(self): 83 | """ 84 | Italic property. 85 | 86 | :return: True if italic is selected 87 | :rtype: bool 88 | """ 89 | return self._italic.get() 90 | 91 | @property 92 | def underline(self): 93 | """ 94 | Underline property. 95 | 96 | :return: True if underline is selected 97 | :rtype: bool 98 | """ 99 | return self._underline.get() 100 | 101 | @property 102 | def overstrike(self): 103 | """ 104 | Overstrike property. 105 | 106 | :return: True if overstrike is selected 107 | :rtype: bool 108 | """ 109 | return self._overstrike.get() 110 | -------------------------------------------------------------------------------- /ttkwidgets/color/functions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Author: Juliette Monsel 4 | License: GNU GPLv3 5 | Source: https://github.com/j4321/tkColorPicker 6 | 7 | Edited by RedFantom for Python 2/3 cross-compatibility and docstring formatting 8 | """ 9 | 10 | """ 11 | tkcolorpicker - Alternative to colorchooser for Tkinter. 12 | Copyright 2017 Juliette Monsel 13 | 14 | tkcolorpicker is free software: you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation, either version 3 of the License, or 17 | (at your option) any later version. 18 | 19 | tkcolorpicker is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | You should have received a copy of the GNU General Public License 25 | along with this program. If not, see . 26 | 27 | Functions and constants 28 | """ 29 | 30 | 31 | import tkinter as tk 32 | from tkinter import ttk 33 | from PIL import Image, ImageDraw, ImageTk 34 | from math import atan2, sqrt, pi 35 | import colorsys 36 | 37 | 38 | PALETTE = ("red", "dark red", "orange", "yellow", "green", "lightgreen", "blue", 39 | "royal blue", "sky blue", "purple", "magenta", "pink", "black", 40 | "white", "gray", "saddle brown", "lightgray", "wheat") 41 | 42 | 43 | # in some python versions round returns a float instead of an int 44 | if not isinstance(round(1.0), int): 45 | def round2(nb): 46 | """Round number to 0 digits and return an int.""" 47 | return int(nb + 0.5) # works because nb >= 0 48 | else: 49 | round2 = round 50 | 51 | 52 | # --- conversion functions 53 | def rgb_to_hsv(r, g, b): 54 | """Convert RGB color to HSV.""" 55 | h, s, v = colorsys.rgb_to_hsv(r / 255., g / 255., b / 255.) 56 | return round2(h * 360), round2(s * 100), round2(v * 100) 57 | 58 | 59 | def hsv_to_rgb(h, s, v): 60 | """Convert HSV color to RGB.""" 61 | r, g, b = colorsys.hsv_to_rgb(h / 360., s / 100., v / 100.) 62 | return round2(r * 255), round2(g * 255), round2(b * 255) 63 | 64 | 65 | def rgb_to_hexa(*args): 66 | """Convert RGB(A) color to hexadecimal.""" 67 | if len(args) == 3: 68 | return ("#%2.2x%2.2x%2.2x" % tuple(args)).upper() 69 | elif len(args) == 4: 70 | return ("#%2.2x%2.2x%2.2x%2.2x" % tuple(args)).upper() 71 | else: 72 | raise ValueError("Wrong number of arguments.") 73 | 74 | 75 | def hexa_to_rgb(color): 76 | """Convert hexadecimal color to RGB.""" 77 | r = int(color[1:3], 16) 78 | g = int(color[3:5], 16) 79 | b = int(color[5:7], 16) 80 | if len(color) == 7: 81 | return r, g, b 82 | elif len(color) == 9: 83 | return r, g, b, int(color[7:9], 16) 84 | else: 85 | raise ValueError("Invalid hexadecimal notation.") 86 | 87 | 88 | def col2hue(r, g, b): 89 | """Return hue value corresponding to given RGB color.""" 90 | return round2(180 / pi * atan2(sqrt(3) * (g - b), 2 * r - g - b) + 360) % 360 91 | 92 | 93 | def hue2col(h): 94 | """Return the color in RGB format corresponding to (h, 100, 100) in HSV.""" 95 | if h < 0 or h > 360: 96 | raise ValueError("Hue should be between 0 and 360") 97 | else: 98 | return hsv_to_rgb(h, 100, 100) 99 | 100 | 101 | # --- Fake transparent image creation with PIL 102 | def create_checkered_image(width, height, c1=(154, 154, 154, 255), 103 | c2=(100, 100, 100, 255), s=6): 104 | """ 105 | Return a checkered image of size width x height. 106 | 107 | Arguments: 108 | * width: image width 109 | * height: image height 110 | * c1: first color (RGBA) 111 | * c2: second color (RGBA) 112 | * s: size of the squares 113 | """ 114 | im = Image.new("RGBA", (width, height), c1) 115 | draw = ImageDraw.Draw(im, "RGBA") 116 | for i in range(s, width, 2 * s): 117 | for j in range(0, height, 2 * s): 118 | draw.rectangle(((i, j), ((i + s - 1, j + s - 1))), fill=c2) 119 | for i in range(0, width, 2 * s): 120 | for j in range(s, height, 2 * s): 121 | draw.rectangle(((i, j), ((i + s - 1, j + s - 1))), fill=c2) 122 | return im 123 | 124 | 125 | def overlay(image, color): 126 | """ 127 | Overlay a rectangle of color (RGBA) on the image and return the result. 128 | """ 129 | width, height = image.size 130 | im = Image.new("RGBA", (width, height), color) 131 | preview = Image.alpha_composite(image, im) 132 | return preview 133 | -------------------------------------------------------------------------------- /ttkwidgets/frames/scrolledframe.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | # The following sites were used for reference in the creation of this file: 7 | # http://code.activestate.com/recipes/578894-mousewheel-binding-to-scrolling-area-tkinter-multi/ 8 | # http://tkinter.unpythonic.net/wiki/VerticalScrolledFrame 9 | import tkinter as tk 10 | from tkinter import ttk 11 | from ttkwidgets import AutoHideScrollbar 12 | 13 | 14 | class ScrolledFrame(ttk.Frame): 15 | """ 16 | A frame that sports a vertically oriented scrollbar for scrolling. 17 | 18 | :ivar interior: :class:`ttk.Frame` in which to put the widgets to be scrolled with any geometry manager. 19 | """ 20 | 21 | def __init__(self, master=None, compound=tk.RIGHT, canvasheight=400, 22 | canvaswidth=400, canvasborder=0, autohidescrollbar=True, **kwargs): 23 | """ 24 | Create a ScrolledFrame. 25 | 26 | :param master: master widget 27 | :type master: widget 28 | :param compound: "right" or "left": side the scrollbar should be on 29 | :type compound: str 30 | :param canvasheight: height of the internal canvas 31 | :type canvasheight: int 32 | :param canvaswidth: width of the internal canvas 33 | :type canvaswidth: int 34 | :param canvasborder: border width of the internal canvas 35 | :type canvasborder: int 36 | :param autohidescrollbar: whether to use an :class:`~ttkwidgets.AutoHideScrollbar` or a :class:`ttk.Scrollbar` 37 | :type autohidescrollbar: bool 38 | :param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer 39 | """ 40 | ttk.Frame.__init__(self, master, **kwargs) 41 | self.rowconfigure(0, weight=1) 42 | self.columnconfigure(1, weight=1) 43 | if autohidescrollbar: 44 | self._scrollbar = AutoHideScrollbar(self, orient=tk.VERTICAL) 45 | else: 46 | self._scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL) 47 | self._canvas = tk.Canvas(self, borderwidth=canvasborder, highlightthickness=0, 48 | yscrollcommand=self._scrollbar.set, width=canvaswidth, height=canvasheight) 49 | self.__compound = compound 50 | self._scrollbar.config(command=self._canvas.yview) 51 | self._canvas.yview_moveto(0) 52 | self.interior = ttk.Frame(self._canvas) 53 | self._interior_id = self._canvas.create_window(0, 0, window=self.interior, anchor=tk.NW) 54 | self.interior.bind("", self.__configure_interior) 55 | self._canvas.bind("", self.__configure_canvas) 56 | self.__grid_widgets() 57 | 58 | def __grid_widgets(self): 59 | """Places all the child widgets in the appropriate positions.""" 60 | scrollbar_column = 0 if self.__compound is tk.LEFT else 2 61 | self._canvas.grid(row=0, column=1, sticky="nswe") 62 | self._scrollbar.grid(row=0, column=scrollbar_column, sticky="ns") 63 | 64 | def __configure_interior(self, *args): 65 | """ 66 | Private function to configure the interior Frame. 67 | 68 | :param args: Tkinter event 69 | """ 70 | # Resize the canvas scrollregion to fit the entire frame 71 | (size_x, size_y) = (self.interior.winfo_reqwidth(), self.interior.winfo_reqheight()) 72 | self._canvas.config(scrollregion="0 0 {0} {1}".format(size_x, size_y)) 73 | if self.interior.winfo_reqwidth() is not self._canvas.winfo_width(): 74 | # If the interior Frame is wider than the canvas, automatically resize the canvas to fit the frame 75 | self._canvas.config(width=self.interior.winfo_reqwidth()) 76 | 77 | def __configure_canvas(self, *args): 78 | """ 79 | Private function to configure the internal Canvas. 80 | 81 | Changes the width of the canvas to fit the interior Frame 82 | 83 | :param args: Tkinter event 84 | """ 85 | if self.interior.winfo_reqwidth() is not self._canvas.winfo_width(): 86 | self._canvas.configure(width=self.interior.winfo_reqwidth()) 87 | 88 | def __mouse_wheel(self, event): 89 | """ 90 | Private function to scroll the canvas view. 91 | 92 | :param event: Tkinter event 93 | """ 94 | self._canvas.yview_scroll(-1 * (event.delta // 100), "units") 95 | 96 | def resize_canvas(self, height=400, width=400): 97 | """ 98 | Function for the user to resize the internal Canvas widget if desired. 99 | 100 | :param height: new height in pixels 101 | :type height: int 102 | :param width: new width in pixels 103 | :type width: int 104 | """ 105 | self._canvas.configure(width=width, height=height) 106 | -------------------------------------------------------------------------------- /tests/test_itemscanvas.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets import ItemsCanvas 4 | from tests import BaseWidgetTest 5 | from ttkwidgets.utilities import get_assets_directory 6 | import os 7 | from PIL import Image, ImageTk 8 | 9 | import tkinter as tk 10 | from pynput.mouse import Controller, Button 11 | 12 | 13 | class TestItemsCanvas(BaseWidgetTest): 14 | def test_itemscanvas_init(self): 15 | canvas = ItemsCanvas(self.window) 16 | canvas.pack() 17 | self.window.update() 18 | 19 | def test_items_canvas_get_options(self): 20 | canvas = ItemsCanvas() 21 | keys = ["canvaswidth", "canvasheight", "function_new", "callback_add", "callback_del", "callback_move", "width"] 22 | for key in keys: 23 | canvas.cget(key) 24 | self.assertTrue(key in canvas.keys()) 25 | 26 | def test_itemscanvas_getsetitem(self): 27 | canvas = ItemsCanvas() 28 | value = canvas["canvaswidth"] 29 | self.assertIsInstance(value, int) 30 | canvas["canvaswidth"] = 20 31 | 32 | def test_itemscanvas_config(self): 33 | canvas = ItemsCanvas() 34 | canvas.config(canvaswidth=1024, canvasheight=1024) 35 | 36 | def test_itemscanvas_items(self): 37 | canvas = ItemsCanvas(callback_del=lambda *args: args) 38 | canvas.add_item(text="Item", backgroundcolor="red", textcolor="green", highlightcolor="#ffffff", 39 | font=("default", 15, "italic")) 40 | canvas.current = 1 41 | canvas.del_item() 42 | self.window.update() 43 | 44 | def test_itemscanvas_events(self): 45 | canvas = ItemsCanvas(callback_add=lambda *args: args, 46 | callback_move=lambda *args: args, 47 | function_new=lambda *args: args) 48 | canvas.add_item(text="Item") 49 | canvas.current = 1 50 | canvas.left_motion(self.TkinterEvent()) 51 | canvas.current = 1 52 | canvas.canvas.itemconfigure(1, tags=("item", "current")) 53 | canvas.left_motion(self.TkinterEvent()) 54 | # canvas.right_press(self.TkinterEvent()) 55 | canvas.current = 1 56 | # canvas.right_press(self.TkinterEvent()) 57 | canvas.frame_menu.invoke(1) 58 | canvas.left_press(self.TkinterEvent()) 59 | canvas.current = 1 60 | canvas.left_press(self.TkinterEvent()) 61 | canvas.current = 1 62 | canvas.left_release(self.TkinterEvent()) 63 | 64 | def test_itemscanvas_background(self): 65 | canvas = ItemsCanvas() 66 | path = os.path.join(get_assets_directory(), "open.png") 67 | img = ImageTk.PhotoImage(Image.open(path)) 68 | canvas.set_background(image=img) 69 | self.window.update() 70 | canvas.set_background(path=path) 71 | self.window.update() 72 | self.assertRaises(ValueError, lambda: canvas.set_background(image=img, path=path)) 73 | self.assertRaises(ValueError, canvas.set_background) 74 | self.assertRaises(ValueError, canvas.set_background, path=1) 75 | self.assertRaises(ValueError, canvas.set_background, path="/path/not/existing") 76 | self.assertRaises(ValueError, canvas.set_background, image=1) 77 | 78 | def test_itemscanvas_drag(self): 79 | canvas = ItemsCanvas(self.window) 80 | canvas.pack() 81 | canvas.add_item("item", font=("default", 16)) 82 | self.window.wm_geometry("+0+0") 83 | self.window.update() 84 | mouse_controller = Controller() 85 | mouse_controller.position = (30, 40) 86 | self.window.update() 87 | mouse_controller.press(Button.left) 88 | self.window.update() 89 | mouse_controller.move(100, 100) 90 | self.window.update() 91 | mouse_controller.release(Button.left) 92 | self.window.update() 93 | 94 | def test_itemscanvas_select(self): 95 | canvas = ItemsCanvas() 96 | canvas.pack() 97 | canvas.add_item("item", font=("default", 16)) 98 | self.window.wm_geometry("+0+0") 99 | self.window.update() 100 | mouse_controller = Controller() 101 | mouse_controller.position = (30, 40) 102 | self.window.update() 103 | mouse_controller.press(Button.left) 104 | self.window.update() 105 | mouse_controller.release(Button.left) 106 | self.window.update() 107 | 108 | def test_itemscanvas_menu(self): 109 | canvas = ItemsCanvas() 110 | canvas.pack() 111 | self.window.wm_geometry("+0+0") 112 | self.window.update() 113 | mouse_controller = Controller() 114 | mouse_controller.position = (0, 0) 115 | mouse_controller.move(30, 40) 116 | self.window.update() 117 | mouse_controller.press(Button.right) 118 | self.window.update() 119 | mouse_controller.release(Button.right) 120 | self.window.update() 121 | 122 | class TkinterEvent(object): 123 | x_root = 0 124 | y_root = 0 125 | x = 0 126 | y = 0 127 | widget = tk.Canvas() 128 | -------------------------------------------------------------------------------- /tests/test_tickscale.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets import TickScale 4 | from tests import BaseWidgetTest 5 | import tkinter as tk 6 | from tkinter import ttk 7 | 8 | 9 | class TestTickScale(BaseWidgetTest): 10 | def test_tickscale_init(self): 11 | TickScale(self.window, orient='vertical', style='my.Vertical.TScale', 12 | tickinterval=0.2, from_=-1, to=1, showvalue=True, 13 | digits=2, length=400, cursor='watch').pack() 14 | self.window.update() 15 | TickScale(self.window, orient='horizontal', from_=0, to=10, 16 | tickinterval=0, tickpos='n', labelpos='s', 17 | showvalue=False).pack() 18 | self.window.update() 19 | 20 | with self.assertRaises(ValueError): 21 | TickScale(self.window, orient='vertical', tickpos='n') 22 | 23 | with self.assertRaises(ValueError): 24 | TickScale(self.window, orient='horizontal', tickpos='e') 25 | 26 | with self.assertRaises(ValueError): 27 | TickScale(self.window, labelpos='ne') 28 | 29 | def test_tickscale_methods(self): 30 | scale = TickScale(self.window, from_=0, to=10, orient='horizontal') 31 | scale.pack() 32 | self.window.update() 33 | self.assertEqual(scale.cget('digits'), -1) 34 | self.assertEqual(scale['tickinterval'], 0) 35 | self.assertEqual(scale['resolution'], 0) 36 | self.assertTrue(scale.cget('showvalue')) 37 | self.assertEqual(scale['from'], 0) 38 | self.assertEqual(scale.cget('to'), 10) 39 | keys = ['command', 40 | 'variable', 41 | 'orient', 42 | 'from', 43 | 'to', 44 | 'value', 45 | 'length', 46 | 'takefocus', 47 | 'cursor', 48 | 'style', 49 | 'class', 50 | 'tickinterval', 51 | 'showvalue', 52 | 'digits'] 53 | self.assertTrue(all(key in scale.keys() for key in keys)) 54 | 55 | scale.config(from_=-1) 56 | self.window.update() 57 | self.assertEqual(scale['from'], -1) 58 | 59 | scale.configure({'to': 20, 'tickinterval': 5, 'digits': 1}) 60 | self.window.update() 61 | self.assertEqual(scale['to'], 20) 62 | self.assertEqual(scale['digits'], 1) 63 | self.assertEqual(scale['tickinterval'], 5) 64 | 65 | scale.configure(tickinterval=0.01, resolution=0.05) 66 | self.window.update() 67 | self.assertEqual(scale['digits'], 2) 68 | self.assertEqual(scale['resolution'], 0.05) 69 | self.assertEqual(scale['tickinterval'], 0.05) 70 | 71 | scale.set(1.1036247) 72 | self.assertEqual(scale.get(), 1.10) 73 | 74 | scale['labelpos'] = 's' 75 | self.window.update() 76 | self.assertEqual(scale['labelpos'], 's') 77 | 78 | scale['labelpos'] = 'e' 79 | self.window.update() 80 | self.assertEqual(scale['labelpos'], 'e') 81 | 82 | scale['labelpos'] = 'w' 83 | self.window.update() 84 | self.assertEqual(scale['labelpos'], 'w') 85 | 86 | scale['tickpos'] = 'n' 87 | self.window.update() 88 | self.assertEqual(scale['tickpos'], 'n') 89 | 90 | scale['orient'] = 'vertical' 91 | self.window.update() 92 | 93 | scale['tickpos'] = 'e' 94 | self.window.update() 95 | self.assertEqual(scale['tickpos'], 'e') 96 | 97 | with self.assertRaises(ValueError): 98 | scale.configure(orient='vertical', tickpos='n') 99 | 100 | with self.assertRaises(ValueError): 101 | scale.configure(orient='horizontal', tickpos='e') 102 | 103 | with self.assertRaises(ValueError): 104 | TickScale(self.window, labelpos='ne') 105 | 106 | scale.configure(style='my.Vertical.TScale') 107 | self.window.update() 108 | scale.style.configure('my.Vertical.TScale', font='TkDefaultFont 20 italic', 109 | sliderlength=50) 110 | 111 | scale.x = 0 112 | 113 | def cmd(value): 114 | scale.x = 2 * float(value) 115 | 116 | scale.configure(command=cmd) 117 | self.window.update() 118 | scale.configure(digits=1) 119 | scale.set(10) 120 | self.window.update() 121 | self.assertEqual(scale.x, 20) 122 | self.assertEqual(scale.label.cget('text'), '10.0') 123 | 124 | scale['showvalue'] = False 125 | self.window.update() 126 | self.assertEqual(scale.label.place_info(), {}) 127 | 128 | scale['orient'] = 'horizontal' 129 | self.window.update() 130 | scale['style'] = '' 131 | scale['showvalue'] = True 132 | self.window.update() 133 | scale['length'] = 200 134 | scale['digits'] = 3 135 | scale.style.configure('Horizontal.TScale', font='TkDefaultFont 20 italic', 136 | sliderlength=10) 137 | self.window.update() 138 | scale.set(0) 139 | self.window.update() 140 | scale.set(20) 141 | self.window.update() 142 | -------------------------------------------------------------------------------- /ttkwidgets/color/gradientbar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Author: Juliette Monsel 4 | License: GNU GPLv3 5 | Source: https://github.com/j4321/tkColorPicker 6 | 7 | Edited by RedFantom for Python 2/3 cross-compatibility and docstring formatting 8 | 9 | 10 | 11 | tkcolorpicker - Alternative to colorchooser for Tkinter. 12 | Copyright 2017 Juliette Monsel 13 | 14 | tkcolorpicker is free software: you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation, either version 3 of the License, or 17 | (at your option) any later version. 18 | 19 | tkcolorpicker is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | You should have received a copy of the GNU General Public License 25 | along with this program. If not, see . 26 | 27 | HSV gradient bar 28 | """ 29 | 30 | 31 | from .functions import tk, round2, rgb_to_hexa, hue2col 32 | 33 | 34 | class GradientBar(tk.Canvas): 35 | """HSV gradient colorbar with selection cursor.""" 36 | 37 | def __init__(self, parent, hue=0, height=11, width=256, variable=None, 38 | **kwargs): 39 | """ 40 | Create a GradientBar. 41 | 42 | :param parent: parent widget 43 | :type parent: widget 44 | :param hue: initially selected hue value (between 0 and 360) 45 | :type hue: int 46 | :param variable: variable linked to the hue value 47 | :type variable: IntVar 48 | :param height: height of the widget in pixels 49 | :type height: int 50 | :param width: width of the widget in pixels 51 | :type width: int 52 | :param kwargs: options to be passed on to the :class:`tk.Canvas` initializer 53 | """ 54 | tk.Canvas.__init__(self, parent, width=width, height=height, **kwargs) 55 | 56 | self._variable = variable 57 | if variable is not None: 58 | try: 59 | hue = int(variable.get()) 60 | except Exception: 61 | pass 62 | else: 63 | self._variable = tk.IntVar(self) 64 | if hue > 360: 65 | hue = 360 66 | elif hue < 0: 67 | hue = 0 68 | self._variable.set(hue) 69 | try: 70 | self._variable.trace_add("write", self._update_hue) 71 | except Exception: 72 | self._variable.trace("w", self._update_hue) 73 | 74 | self.gradient = tk.PhotoImage(master=self, width=width, height=height) 75 | 76 | self.bind('', lambda e: self._draw_gradient(hue)) 77 | self.bind('', self._on_click) 78 | self.bind('', self._on_move) 79 | 80 | def _draw_gradient(self, hue): 81 | """Draw the gradient and put the cursor on hue.""" 82 | self.delete("gradient") 83 | self.delete("cursor") 84 | del self.gradient 85 | width = self.winfo_width() 86 | height = self.winfo_height() 87 | 88 | self.gradient = tk.PhotoImage(master=self, width=width, height=height) 89 | 90 | line = [] 91 | for i in range(width): 92 | line.append(rgb_to_hexa(*hue2col(float(i) / width * 360))) 93 | line = "{" + " ".join(line) + "}" 94 | self.gradient.put(" ".join([line for j in range(height)])) 95 | self.create_image(0, 0, anchor="nw", tags="gradient", 96 | image=self.gradient) 97 | self.lower("gradient") 98 | 99 | x = hue / 360. * width 100 | self.create_line(x, 0, x, height, width=2, tags='cursor') 101 | 102 | def _on_click(self, event): 103 | """Move selection cursor on click.""" 104 | x = event.x 105 | self.coords('cursor', x, 0, x, self.winfo_height()) 106 | self._variable.set(round2((360. * x) / self.winfo_width())) 107 | 108 | def _on_move(self, event): 109 | """Make selection cursor follow the cursor.""" 110 | w = self.winfo_width() 111 | x = min(max(event.x, 0), w) 112 | self.coords('cursor', x, 0, x, self.winfo_height()) 113 | self._variable.set(round2((360. * x) / w)) 114 | 115 | def _update_hue(self, *args): 116 | hue = int(self._variable.get()) 117 | if hue > 360: 118 | hue = 360 119 | elif hue < 0: 120 | hue = 0 121 | self.set(hue) 122 | self.event_generate("<>") 123 | 124 | def get(self): 125 | """Return hue of color under cursor.""" 126 | coords = self.coords('cursor') 127 | return round2(360 * coords[0] / self.winfo_width()) 128 | 129 | def set(self, hue): 130 | """ 131 | Set cursor position on the color corresponding to the hue value. 132 | 133 | :param hue: new hue value (between 0 and 360) 134 | :type hue: int 135 | """ 136 | if hue > 360: 137 | hue = 360 138 | elif hue < 0: 139 | hue = 0 140 | x = hue / 360. * self.winfo_width() 141 | self.coords('cursor', x, 0, x, self.winfo_height()) 142 | self._variable.set(hue) 143 | -------------------------------------------------------------------------------- /tests/test_hooks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: The ttkwidgets repository 5 | """ 6 | from unittest import TestCase 7 | try: 8 | import Tkinter as tk 9 | import ttk 10 | except ImportError: 11 | import tkinter as tk 12 | from tkinter import ttk 13 | from ttkwidgets.hook import hook_ttk_widgets, generate_hook_name, is_hooked 14 | 15 | 16 | class TestHooks(TestCase): 17 | 18 | def setUp(self): 19 | self.window = tk.Tk() 20 | self.expected = {None: None} 21 | self.updated = False 22 | self.user_hook_called = False 23 | self.second_updated = False 24 | self.second_expected = {None: None} 25 | 26 | def basic_updater(self, widget, option, value): 27 | if option not in self.expected: 28 | return 29 | self.assertEquals(value, self.expected[option], "Invalid value for {}: {}".format(option, value)) 30 | self.updated = True 31 | del self.expected[option] 32 | 33 | def second_updater(self, widget, option, value): 34 | if option not in self.second_expected: 35 | return # Not updated with desired option 36 | self.assertEquals(value, self.second_expected[option]) 37 | self.second_updated = True 38 | self.second_expected.clear() 39 | 40 | def has_been_updated(self): 41 | updated = self.updated 42 | self.updated = False 43 | return updated and len(self.expected) == 0 44 | 45 | def has_been_second_updated(self): 46 | updated = self.second_updated 47 | self.second_updated = False 48 | return updated and len(self.expected) == 0 49 | 50 | def test_basic_hook(self): 51 | self.expected = {"random_kwarg": "Hello World"} 52 | options = {"random_kwarg": "Default Value"} 53 | hook_ttk_widgets(self.basic_updater, options) 54 | button = ttk.Button(random_kwarg="Hello World") 55 | self.assertTrue(self.has_been_updated()) 56 | 57 | self.assertTrue(is_hooked(options)) 58 | self.assertTrue(hasattr(ttk.Button, generate_hook_name(options))) 59 | self.assertTrue("random_kwarg" in button.keys()) 60 | 61 | def test_user_hook_and_defaults(self): 62 | self.expected = {"not_user": "Hello World"} 63 | options = self.expected.copy() 64 | hook_ttk_widgets(self.basic_updater, self.expected.copy()) 65 | 66 | button_init = ttk.Button.__init__ 67 | 68 | def __init__(self_widget, *args, **kwargs): 69 | self.user_hook_called = True 70 | button_init(self_widget, *args, **kwargs) 71 | 72 | ttk.Button.__init__ = __init__ 73 | 74 | ttk.Button() 75 | self.assertTrue(self.user_hook_called) 76 | self.assertTrue(is_hooked(options)) 77 | self.assertTrue(self.has_been_updated()) 78 | 79 | def test_multi_hooks(self): 80 | options1 = {"hook1": "Default One"} 81 | options2 = {"hook2": "Default Two"} 82 | self.expected = {"hook1": "Custom One"} 83 | self.second_expected = {"hook2": "Default Two"} 84 | 85 | name = hook_ttk_widgets(self.basic_updater, options1) 86 | hook_ttk_widgets(self.second_updater, options2) 87 | self.assertEquals(name, generate_hook_name(options1)) 88 | 89 | self.assertTrue(is_hooked(options1)) 90 | self.assertTrue(is_hooked(options2)) 91 | 92 | button = ttk.Button(hook1="Custom One") 93 | 94 | self.assertTrue(is_hooked(options1)) 95 | self.assertTrue(is_hooked(options2)) 96 | 97 | self.assertTrue(self.has_been_updated()) 98 | self.assertTrue(self.has_been_second_updated()) 99 | 100 | self.assertTrue("hook1" in button.keys() and "hook2" in button.keys()) 101 | 102 | def test_multi_option_hooks_cget_config_keys_overwrite(self): 103 | options = {"hookx": "Default X", "hooky": "Default Y"} 104 | self.expected = {"hookx": "Default X", "hooky": "Option Y"} 105 | 106 | hook_ttk_widgets(self.basic_updater, options) 107 | self.assertTrue(is_hooked(options)) 108 | self.assertTrue(is_hooked({"hookx": None})) 109 | self.assertTrue(is_hooked({"hooky": None})) 110 | 111 | button = ttk.Button(hooky="Option Y") 112 | 113 | self.assertTrue(self.has_been_updated()) 114 | self.assertTrue("hooky" in button.keys()) 115 | self.assertTrue("hookx" in button.keys()) 116 | self.assertEqual("Default X", button.cget("hookx")) 117 | self.assertEqual("Option Y", button.cget("hooky")) 118 | 119 | self.expected = {"hookx": "Option X"} 120 | button.configure(hookx="Option X", command=self.window.destroy) 121 | self.assertTrue(self.has_been_updated()) 122 | 123 | self.assertEqual("Option X", button.cget("hookx")) 124 | self.assertEqual("Option Y", button.cget("hooky")) 125 | self.assertIsNotNone(button.cget("command")) 126 | 127 | self.assertRaises(RuntimeError, lambda: hook_ttk_widgets(self.basic_updater, options)) 128 | self.assertRaises(RuntimeError, lambda: hook_ttk_widgets(None, {"hookx": "New Default X"})) 129 | 130 | options["hookx"] = "New Default X" 131 | hook_ttk_widgets(None, options) 132 | self.expected = options.copy() 133 | ttk.Button() 134 | self.assertTrue(self.has_been_updated()) 135 | 136 | def tearDown(self): 137 | self.window.destroy() 138 | 139 | -------------------------------------------------------------------------------- /tests/test_scaleentry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) RedFantom 2017 2 | # For license see LICENSE 3 | from ttkwidgets import ScaleEntry 4 | from tests import BaseWidgetTest 5 | import tkinter as tk 6 | 7 | 8 | class TestScaleEntry(BaseWidgetTest): 9 | def test_scaleentry_init(self): 10 | scale = ScaleEntry(self.window) 11 | scale.pack() 12 | self.window.update() 13 | 14 | def test_scaleentry_entry(self): 15 | scale = ScaleEntry(self.window, from_=0, to=50) 16 | scale.pack() 17 | self.window.update() 18 | scale.config_entry(width=10) 19 | self.window.update() 20 | self.assertEqual(scale.cget_entry('width'), 10) 21 | scale._entry.delete(0, tk.END) 22 | scale._entry.insert(0, "5") 23 | scale._on_entry(None) 24 | self.assertEqual(scale._variable.get(), 5) 25 | scale._entry.insert(0, "a") 26 | scale._on_entry(None) 27 | self.assertEqual(scale._variable.get(), 5) 28 | scale._entry.insert(0, "") 29 | scale._on_entry(None) 30 | self.assertEqual(scale._variable.get(), 5) 31 | 32 | def test_scaleentry_scale(self): 33 | scale = ScaleEntry(self.window) 34 | scale.pack() 35 | self.window.update() 36 | scale.config_scale(length=100) 37 | self.window.update() 38 | self.assertEqual(scale.cget_scale('length'), 100) 39 | try: 40 | info = scale._scale.grid_info() 41 | except TypeError: 42 | # bug in some tkinter versions 43 | res = str(scale.tk.call('grid', 'info', scale._scale._w)).split("-") 44 | info = {} 45 | for i in res: 46 | if i: 47 | key, val = i.strip().split() 48 | info[key] = val 49 | self.assertEqual(info['sticky'], 'ew') 50 | scale.config_scale(orient='vertical') 51 | self.window.update() 52 | try: 53 | info = scale._scale.grid_info() 54 | except TypeError: 55 | # bug in some tkinter versions 56 | res = str(scale.tk.call('grid', 'info', scale._scale._w)).split("-") 57 | info = {} 58 | for i in res: 59 | if i: 60 | key, val = i.strip().split() 61 | info[key] = val 62 | self.assertEqual(info['sticky'], 'ns') 63 | scale._variable.set(20) 64 | scale._on_scale(None) 65 | self.assertEqual(scale._variable.get(), 20) 66 | 67 | def test_scaleentry_limitedintvar(self): 68 | var = ScaleEntry.LimitedIntVar(5, 55) 69 | var.set(60) 70 | self.assertEqual(var.get(), 55) 71 | var.set(0) 72 | self.assertEqual(var.get(), 5) 73 | var.configure(low=10) 74 | self.assertEqual(var._low, 10) 75 | self.assertEqual(var.get(), 10) 76 | var.set(54) 77 | var.configure(high=20) 78 | self.assertEqual(var._high, 20) 79 | self.assertEqual(var.get(), 20) 80 | self.assertRaises(TypeError, lambda: var.set('a')) 81 | 82 | def test_scaleentry_property(self): 83 | scale = ScaleEntry(from_=50) 84 | self.assertEqual(scale.value, 50) 85 | 86 | def test_scaleentry_methods(self): 87 | scale = ScaleEntry(self.window, scalewidth=100, entrywidth=4, from_=-10, 88 | to=10, orient=tk.VERTICAL, compound=tk.TOP, 89 | entryscalepad=10) 90 | scale.pack() 91 | self.window.update() 92 | keys = ['borderwidth', 'padding', 'relief', 'width', 'height', 93 | 'takefocus', 'cursor', 'style', 'class', 'scalewidth', 'orient', 94 | 'entrywidth', 'from', 'to', 'compound', 'entryscalepad'] 95 | keys.sort() 96 | widget_keys = scale.keys() 97 | self.assertTrue(all(key in widget_keys for key in keys)) 98 | self.assertEqual(scale['orient'], tk.VERTICAL) 99 | self.assertEqual(scale['scalewidth'], 100) 100 | self.assertEqual(scale['entrywidth'], 4) 101 | self.assertEqual(scale['from'], -10) 102 | self.assertEqual(scale['to'], 10) 103 | self.assertEqual(scale['compound'], tk.TOP) 104 | self.assertEqual(scale['entryscalepad'], 10) 105 | 106 | scale.configure({'to': 50, 'compound': tk.RIGHT}, padding=2, from_=20, 107 | orient='horizontal', entryscalepad=0) 108 | scale['entrywidth'] = 5 109 | self.window.update() 110 | self.assertEqual(scale.cget('compound'), tk.RIGHT) 111 | self.assertEqual(str(scale.cget('padding')[0]), '2') 112 | self.assertEqual(scale.cget('entryscalepad'), 0) 113 | self.assertEqual(scale.cget('entrywidth'), 5) 114 | self.assertEqual(str(scale.cget_scale('orient')), 'horizontal') 115 | self.assertEqual(scale.cget_scale('from'), 20) 116 | self.assertEqual(scale.cget_scale('to'), 50) 117 | self.assertEqual(scale._variable._low, 20) 118 | self.assertEqual(scale._variable._high, 50) 119 | scale.config({'scalewidth': 50, 'from': -10}, compound=tk.BOTTOM) 120 | self.assertEqual(scale.cget_scale('from'), -10) 121 | self.assertEqual(scale.cget('scalewidth'), 50) 122 | self.assertEqual(scale.cget_scale('length'), 50) 123 | 124 | with self.assertRaises(ValueError): 125 | scale['compound'] = 'topp' 126 | 127 | with self.assertRaises(ValueError): 128 | scale['entryscalepad'] = 'a' 129 | 130 | def test_scaleentry_kwargs(self): 131 | self.assertRaises(ValueError, lambda: ScaleEntry(compound="something!")) 132 | self.assertRaises(TypeError, lambda: ScaleEntry(self.window, entryscalepad='a')) 133 | 134 | -------------------------------------------------------------------------------- /ttkwidgets/color/spinbox.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Author: Juliette Monsel 4 | License: GNU GPLv3 5 | Source: https://github.com/j4321/tkColorPicker 6 | 7 | Edited by RedFantom for Python 2/3 cross-compatibility and docstring formatting 8 | 9 | tkcolorpicker - Alternative to colorchooser for Tkinter. 10 | Copyright 2017 Juliette Monsel 11 | 12 | tkcolorpicker is free software: you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation, either version 3 of the License, or 15 | (at your option) any later version. 16 | 17 | tkcolorpicker is distributed in the hope that it will be useful, 18 | but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | GNU General Public License for more details. 21 | 22 | You should have received a copy of the GNU General Public License 23 | along with this program. If not, see . 24 | 25 | Nicer Spinbox than the tk.Spinbox 26 | """ 27 | 28 | 29 | from .functions import tk, ttk 30 | 31 | 32 | class Spinbox(tk.Spinbox): 33 | """Spinbox closer to ttk look (designed to be used with clam).""" 34 | 35 | def __init__(self, parent, **kwargs): 36 | """ 37 | Create a Spinbox. 38 | 39 | The keyword arguments are the same as for a tk.Spinbox. 40 | """ 41 | self.style = ttk.Style(parent) 42 | self.frame = ttk.Frame(parent, class_="ttkSpinbox", 43 | relief=kwargs.get("relief", "sunken"), 44 | borderwidth=1) 45 | self.style.configure("%s.spinbox.TFrame" % self.frame, 46 | background=self.style.lookup("TSpinbox", 47 | "fieldbackground", 48 | default='white')) 49 | self.frame.configure(style="%s.spinbox.TFrame" % self.frame) 50 | kwargs["relief"] = "flat" 51 | kwargs["highlightthickness"] = 0 52 | kwargs["selectbackground"] = self.style.lookup("TSpinbox", 53 | "selectbackground", 54 | ("focus",)) 55 | kwargs["selectforeground"] = self.style.lookup("TSpinbox", 56 | "selectforeground", 57 | ("focus",)) 58 | kwargs["background"] = self.style.lookup("TSpinbox", 59 | "fieldbackground", 60 | default='white') 61 | kwargs["foreground"] = self.style.lookup("TSpinbox", 62 | "foreground") 63 | kwargs["buttonbackground"] = self.style.lookup("TSpinbox", 64 | "background") 65 | tk.Spinbox.__init__(self, self.frame, **kwargs) 66 | tk.Spinbox.pack(self, padx=1, pady=1) 67 | self.frame.spinbox = self 68 | 69 | # pack/place/grid methods 70 | self.pack = self.frame.pack 71 | self.pack_slaves = self.frame.pack_slaves 72 | self.pack_propagate = self.frame.pack_propagate 73 | self.pack_configure = self.frame.pack_configure 74 | self.pack_info = self.frame.pack_info 75 | self.pack_forget = self.frame.pack_forget 76 | 77 | self.grid = self.frame.grid 78 | self.grid_slaves = self.frame.grid_slaves 79 | self.grid_size = self.frame.grid_size 80 | self.grid_rowconfigure = self.frame.grid_rowconfigure 81 | self.grid_remove = self.frame.grid_remove 82 | self.grid_propagate = self.frame.grid_propagate 83 | self.grid_info = self.frame.grid_info 84 | self.grid_location = self.frame.grid_location 85 | self.grid_columnconfigure = self.frame.grid_columnconfigure 86 | self.grid_configure = self.frame.grid_configure 87 | self.grid_forget = self.frame.grid_forget 88 | self.grid_bbox = self.frame.grid_bbox 89 | try: 90 | self.grid_anchor = self.frame.grid_anchor 91 | except AttributeError: 92 | pass 93 | 94 | self.place = self.frame.place 95 | self.place_configure = self.frame.place_configure 96 | self.place_forget = self.frame.place_forget 97 | self.place_info = self.frame.place_info 98 | self.place_slaves = self.frame.place_slaves 99 | 100 | self.bind('<1>', lambda e: self.focus_set()) 101 | self.frame.bind("", self.focusin) 102 | self.frame.bind("", self.focusout) 103 | 104 | def focusout(self, event): 105 | """Change style on focus out events.""" 106 | bc = self.style.lookup("TEntry", "bordercolor", ("!focus",)) 107 | dc = self.style.lookup("TEntry", "darkcolor", ("!focus",)) 108 | lc = self.style.lookup("TEntry", "lightcolor", ("!focus",)) 109 | self.style.configure("%s.spinbox.TFrame" % self.frame, bordercolor=bc, 110 | darkcolor=dc, lightcolor=lc) 111 | 112 | def focusin(self, event): 113 | """Change style on focus in events.""" 114 | self.old_value = self.get() 115 | bc = self.style.lookup("TEntry", "bordercolor", ("focus",)) 116 | dc = self.style.lookup("TEntry", "darkcolor", ("focus",)) 117 | lc = self.style.lookup("TEntry", "lightcolor", ("focus",)) 118 | self.style.configure("%s.spinbox.TFrame" % self.frame, bordercolor=bc, 119 | darkcolor=dc, lightcolor=lc) 120 | -------------------------------------------------------------------------------- /ttkwidgets/autocomplete/autocomplete_entry.py: -------------------------------------------------------------------------------- 1 | """ 2 | Authors: Mitja Martini and Russell Adams 3 | License: "Licensed same as original by Mitja Martini or public domain, whichever is less restrictive" 4 | Source: https://mail.python.org/pipermail/tkinter-discuss/2012-January/003041.html 5 | 6 | Edited by RedFantom for ttk and Python 2 and 3 cross-compatibility and binding 7 | """ 8 | import tkinter as tk 9 | from tkinter import ttk 10 | 11 | tk_umlauts = ['odiaeresis', 'adiaeresis', 'udiaeresis', 'Odiaeresis', 'Adiaeresis', 'Udiaeresis', 'ssharp'] 12 | 13 | 14 | class AutocompleteEntry(ttk.Entry): 15 | """ 16 | Subclass of :class:`ttk.Entry` that features autocompletion. 17 | 18 | To enable autocompletion use :meth:`set_completion_list` to define 19 | a list of possible strings to hit. 20 | To cycle through hits use down and up arrow keys. 21 | """ 22 | def __init__(self, master=None, completevalues=None, **kwargs): 23 | """ 24 | Create an AutocompleteEntry. 25 | 26 | :param master: master widget 27 | :type master: widget 28 | :param completevalues: autocompletion values 29 | :type completevalues: list 30 | :param kwargs: keyword arguments passed to the :class:`ttk.Entry` initializer 31 | """ 32 | ttk.Entry.__init__(self, master, **kwargs) 33 | self._completion_list = completevalues 34 | self.set_completion_list(completevalues) 35 | self._hits = [] 36 | self._hit_index = 0 37 | self.position = 0 38 | 39 | def set_completion_list(self, completion_list): 40 | """ 41 | Set a new auto completion list 42 | 43 | :param completion_list: completion values 44 | :type completion_list: list 45 | """ 46 | self._completion_list = sorted(completion_list, key=str.lower) # Work with a sorted list 47 | self._hits = [] 48 | self._hit_index = 0 49 | self.position = 0 50 | self.bind('', self.handle_keyrelease) 51 | 52 | def autocomplete(self, delta=0): 53 | """ 54 | Autocomplete the Entry. 55 | 56 | :param delta: 0, 1 or -1: how to cycle through possible hits 57 | :type delta: int 58 | """ 59 | if delta: # need to delete selection otherwise we would fix the current position 60 | self.delete(self.position, tk.END) 61 | else: # set position to end so selection starts where textentry ended 62 | self.position = len(self.get()) 63 | # collect hits 64 | _hits = [] 65 | for element in self._completion_list: 66 | if element.lower().startswith(self.get().lower()): # Match case-insensitively 67 | _hits.append(element) 68 | # if we have a new hit list, keep this in mind 69 | if _hits != self._hits: 70 | self._hit_index = 0 71 | self._hits = _hits 72 | # only allow cycling if we are in a known hit list 73 | if _hits == self._hits and self._hits: 74 | self._hit_index = (self._hit_index + delta) % len(self._hits) 75 | # now finally perform the auto completion 76 | if self._hits: 77 | self.delete(0, tk.END) 78 | self.insert(0, self._hits[self._hit_index]) 79 | self.select_range(self.position, tk.END) 80 | 81 | def handle_keyrelease(self, event): 82 | """ 83 | Event handler for the keyrelease event on this widget. 84 | 85 | :param event: Tkinter event 86 | """ 87 | if event.keysym == "BackSpace": 88 | self.delete(self.index(tk.INSERT), tk.END) 89 | self.position = self.index(tk.END) 90 | if event.keysym == "Left": 91 | if self.position < self.index(tk.END): # delete the selection 92 | self.delete(self.position, tk.END) 93 | else: 94 | self.position -= 1 # delete one character 95 | self.delete(self.position, tk.END) 96 | if event.keysym == "Right": 97 | self.position = self.index(tk.END) # go to end (no selection) 98 | if event.keysym == "Down": 99 | self.autocomplete(1) # cycle to next hit 100 | if event.keysym == "Up": 101 | self.autocomplete(-1) # cycle to previous hit 102 | if event.keysym == "Return": 103 | self.handle_return(None) 104 | return 105 | if len(event.keysym) == 1 or event.keysym in tk_umlauts: 106 | self.autocomplete() 107 | 108 | def handle_return(self, event): 109 | """ 110 | Function to bind to the Enter/Return key so if Enter is pressed the selection is cleared. 111 | 112 | :param event: Tkinter event 113 | """ 114 | self.icursor(tk.END) 115 | self.selection_clear() 116 | 117 | def config(self, **kwargs): 118 | """Alias for configure""" 119 | self.configure(**kwargs) 120 | 121 | def configure(self, **kwargs): 122 | """Configure widget specific keyword arguments in addition to :class:`ttk.Entry` keyword arguments.""" 123 | if "completevalues" in kwargs: 124 | self.set_completion_list(kwargs.pop("completevalues")) 125 | return ttk.Entry.configure(self, **kwargs) 126 | 127 | def cget(self, key): 128 | """Return value for widget specific keyword arguments""" 129 | if key == "completevalues": 130 | return self._completion_list 131 | return ttk.Entry.cget(self, key) 132 | 133 | def keys(self): 134 | """Return a list of all resource names of this widget.""" 135 | keys = ttk.Entry.keys(self) 136 | keys.append("completevalues") 137 | return keys 138 | 139 | def __setitem__(self, key, value): 140 | self.configure(**{key: value}) 141 | 142 | def __getitem__(self, item): 143 | return self.cget(item) 144 | -------------------------------------------------------------------------------- /ttkwidgets/color/alphabar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Author: Juliette Monsel 4 | License: GNU GPLv3 5 | Source: https://github.com/j4321/tkColorPicker 6 | 7 | Edited by RedFantom for Python 2/3 cross-compatibility and docstring formatting 8 | 9 | 10 | 11 | tkcolorpicker - Alternative to colorchooser for Tkinter. 12 | Copyright 2017 Juliette Monsel 13 | 14 | tkcolorpicker is free software: you can redistribute it and/or modify 15 | it under the terms of the GNU General Public License as published by 16 | the Free Software Foundation, either version 3 of the License, or 17 | (at your option) any later version. 18 | 19 | tkcolorpicker is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | You should have received a copy of the GNU General Public License 25 | along with this program. If not, see . 26 | 27 | Alpha channel gradient bar 28 | """ 29 | 30 | 31 | from PIL import Image, ImageTk 32 | from .functions import tk, round2, rgb_to_hsv 33 | from .functions import create_checkered_image 34 | 35 | 36 | class AlphaBar(tk.Canvas): 37 | """Bar to select alpha value.""" 38 | 39 | def __init__(self, parent, alpha=255, color=(255, 0, 0), height=11, 40 | width=256, variable=None, **kwargs): 41 | """ 42 | Create a bar to select the alpha value. 43 | 44 | :param parent: parent widget 45 | :type parent: widget 46 | :param alpha: initially selected alpha value (between 0 and 255) 47 | :type alpha: int 48 | :param color: gradient color in RGB format 49 | :type color: tuple[int] 50 | :param variable: variable linked to the alpha value 51 | :type variable: IntVar 52 | :param height: height of the widget in pixels 53 | :type height: int 54 | :param width: width of the widget in pixels 55 | :type width: int 56 | :param kwargs: options to be passed on to the :class:`tk.Canvas` initializer 57 | """ 58 | tk.Canvas.__init__(self, parent, width=width, height=height, **kwargs) 59 | self.gradient = tk.PhotoImage(master=self, width=width, height=height) 60 | 61 | self._variable = variable 62 | if variable is not None: 63 | try: 64 | alpha = int(variable.get()) 65 | except Exception: 66 | pass 67 | else: 68 | self._variable = tk.IntVar(self) 69 | if alpha > 255: 70 | alpha = 255 71 | elif alpha < 0: 72 | alpha = 0 73 | self._variable.set(alpha) 74 | try: 75 | self._variable.trace_add("write", self._update_alpha) 76 | except Exception: 77 | self._variable.trace("w", self._update_alpha) 78 | 79 | self.bind('', lambda e: self._draw_gradient(alpha, color)) 80 | self.bind('', self._on_click) 81 | self.bind('', self._on_move) 82 | 83 | def _draw_gradient(self, alpha, color): 84 | """Draw the gradient and put the cursor on alpha.""" 85 | self.delete("gradient") 86 | self.delete("cursor") 87 | del self.gradient 88 | width = self.winfo_width() 89 | height = self.winfo_height() 90 | 91 | bg = create_checkered_image(width, height) 92 | r, g, b = color 93 | w = width - 1. 94 | gradient = Image.new("RGBA", (width, height)) 95 | for i in range(width): 96 | for j in range(height): 97 | gradient.putpixel((i, j), (r, g, b, round2(i / w * 255))) 98 | self.gradient = ImageTk.PhotoImage(Image.alpha_composite(bg, gradient), 99 | master=self) 100 | 101 | self.create_image(0, 0, anchor="nw", tags="gardient", 102 | image=self.gradient) 103 | self.lower("gradient") 104 | 105 | x = alpha / 255. * width 106 | h, s, v = rgb_to_hsv(r, g, b) 107 | if v < 50: 108 | fill = "gray80" 109 | else: 110 | fill = 'black' 111 | self.create_line(x, 0, x, height, width=2, tags='cursor', fill=fill) 112 | 113 | def _on_click(self, event): 114 | """Move selection cursor on click.""" 115 | x = event.x 116 | self.coords('cursor', x, 0, x, self.winfo_height()) 117 | self._variable.set(round2((255. * x) / self.winfo_width())) 118 | 119 | def _on_move(self, event): 120 | """Make selection cursor follow the cursor.""" 121 | w = self.winfo_width() 122 | x = min(max(event.x, 0), w) 123 | self.coords('cursor', x, 0, x, self.winfo_height()) 124 | self._variable.set(round2((255. * x) / w)) 125 | 126 | def _update_alpha(self, *args): 127 | alpha = int(self._variable.get()) 128 | if alpha > 255: 129 | alpha = 255 130 | elif alpha < 0: 131 | alpha = 0 132 | self.set(alpha) 133 | self.event_generate("<>") 134 | 135 | def get(self): 136 | """Return alpha value of color under cursor.""" 137 | coords = self.coords('cursor') 138 | return round2((255. * coords[0]) / self.winfo_width()) 139 | 140 | def set(self, alpha): 141 | """ 142 | Set cursor position on the color corresponding to the alpha value. 143 | 144 | :param alpha: new alpha value (between 0 and 255) 145 | :type alpha: int 146 | """ 147 | if alpha > 255: 148 | alpha = 255 149 | elif alpha < 0: 150 | alpha = 0 151 | x = alpha / 255. * self.winfo_width() 152 | self.coords('cursor', x, 0, x, self.winfo_height()) 153 | self._variable.set(alpha) 154 | 155 | def set_color(self, color): 156 | """ 157 | Change gradient color and change cursor position if an alpha value is supplied. 158 | 159 | :param color: new gradient color in RGB(A) format 160 | :type color: tuple[int] 161 | """ 162 | if len(color) == 3: 163 | alpha = self.get() 164 | else: 165 | alpha = color[3] 166 | self._draw_gradient(alpha, color[:3]) 167 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../..')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'ttkwidgets' 23 | copyright = '2018-2022, ttkwidgets developers' 24 | author = 'ttkwidgets developers' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '0.13.0' 30 | 31 | 32 | # -- Example Generation ------------------------------------------------------ 33 | with open("generate_examples.py") as fi: 34 | exec(fi.read()) 35 | 36 | 37 | # -- General configuration --------------------------------------------------- 38 | 39 | # If your documentation needs a minimal Sphinx version, state it here. 40 | # 41 | # needs_sphinx = '1.0' 42 | 43 | # Add any Sphinx extension module names here, as strings. They can be 44 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 45 | # ones. 46 | extensions = [ 47 | 'sphinx.ext.autodoc', 48 | 'sphinx.ext.viewcode', 49 | 'sphinx.ext.autosummary', 50 | ] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix(es) of source filenames. 56 | # You can specify multiple suffix as a list of string: 57 | # 58 | # source_suffix = ['.rst', '.md'] 59 | source_suffix = '.rst' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # 67 | # This is also used if you do content translation via gettext catalogs. 68 | # Usually you set "language" from the command line for these cases. 69 | language = None 70 | 71 | # List of patterns, relative to source directory, that match files and 72 | # directories to ignore when looking for source files. 73 | # This pattern also affects html_static_path and html_extra_path. 74 | exclude_patterns = [] 75 | 76 | # The name of the Pygments (syntax highlighting) style to use. 77 | pygments_style = 'tango' 78 | 79 | 80 | # -- Options for HTML output ------------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = 'sphinx_rtd_theme' 86 | 87 | # Theme options are theme-specific and customize the look and feel of a theme 88 | # further. For a list of options available for each theme, see the 89 | # documentation. 90 | # 91 | # html_theme_options = {} 92 | 93 | # Add any paths that contain custom static files (such as style sheets) here, 94 | # relative to this directory. They are copied after the builtin static files, 95 | # so a file named "default.css" will overwrite the builtin "default.css". 96 | html_static_path = ['_static'] 97 | 98 | # Custom sidebar templates, must be a dictionary that maps document names 99 | # to template names. 100 | # 101 | # The default sidebars (for documents that don't match any pattern) are 102 | # defined by theme itself. Builtin themes are using these templates by 103 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 104 | # 'searchbox.html']``. 105 | # 106 | # html_sidebars = {} 107 | 108 | 109 | # -- Options for HTMLHelp output --------------------------------------------- 110 | 111 | # Output file base name for HTML help builder. 112 | htmlhelp_basename = 'ttkwidgetsdoc' 113 | 114 | 115 | # -- Options for LaTeX output ------------------------------------------------ 116 | 117 | latex_elements = { 118 | # The paper size ('letterpaper' or 'a4paper'). 119 | # 120 | # 'papersize': 'letterpaper', 121 | 122 | # The font size ('10pt', '11pt' or '12pt'). 123 | # 124 | # 'pointsize': '10pt', 125 | 126 | # Additional stuff for the LaTeX preamble. 127 | # 128 | # 'preamble': '', 129 | 130 | # Latex figure (float) alignment 131 | # 132 | # 'figure_align': 'htbp', 133 | } 134 | 135 | # Grouping the document tree into LaTeX files. List of tuples 136 | # (source start file, target name, title, 137 | # author, documentclass [howto, manual, or own class]). 138 | latex_documents = [ 139 | (master_doc, 'ttkwidgets.tex', 'ttkwidgets Documentation', 140 | 'ttwidgets developpers', 'howto'), 141 | ] 142 | 143 | 144 | # -- Options for manual page output ------------------------------------------ 145 | 146 | # One entry per manual page. List of tuples 147 | # (source start file, name, description, authors, manual section). 148 | man_pages = [ 149 | (master_doc, 'ttkwidgets', 'ttkwidgets Documentation', 150 | [author], 1) 151 | ] 152 | 153 | 154 | # -- Options for Texinfo output ---------------------------------------------- 155 | 156 | # Grouping the document tree into Texinfo files. List of tuples 157 | # (source start file, target name, title, author, 158 | # dir menu entry, description, category) 159 | texinfo_documents = [ 160 | (master_doc, 'ttkwidgets', 'ttkwidgets Documentation', 161 | author, 'ttkwidgets', "A collection of widgets for Tkinter's ttk extensions by various authors.", 162 | 'Miscellaneous'), 163 | ] 164 | 165 | 166 | # -- Options for Epub output ------------------------------------------------- 167 | 168 | # Bibliographic Dublin Core info. 169 | epub_title = project 170 | 171 | # The unique identifier of the text. This can be a ISBN number 172 | # or the project homepage. 173 | # 174 | # epub_identifier = '' 175 | 176 | # A unique identification for the text. 177 | # 178 | # epub_uid = '' 179 | 180 | # A list of files that should not be packed into the epub file. 181 | epub_exclude_files = ['search.html'] 182 | 183 | 184 | # -- Extension configuration ------------------------------------------------- 185 | autosummary_generate = True 186 | -------------------------------------------------------------------------------- /tests/test_validatedentries.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Dogeek 2020 2 | # For license see LICENSE 3 | import string 4 | 5 | import ttkwidgets.validated_entries as v_entries 6 | from tests import BaseWidgetTest 7 | import tkinter as tk 8 | 9 | 10 | class TestValidatedEntry(BaseWidgetTest): 11 | def _test_entry_init(self, entry): 12 | entry = entry(self.window, validate='all') 13 | entry.configure_validator() 14 | entry.pack() 15 | self.window.update() 16 | 17 | def assertValidatedTrue(self, entry_class, inserted): 18 | entry = entry_class(self.window) 19 | entry.pack() 20 | self.window.update() 21 | validator = entry._get_validator() 22 | self.assertTrue(validator._validate(inserted)) 23 | 24 | def assertValidatedFalse(self, entry_class, inserted): 25 | entry = entry_class(self.window) 26 | entry.pack() 27 | self.window.update() 28 | validator = entry._get_validator() 29 | self.assertFalse(validator._validate(inserted)) 30 | 31 | def test_validated_entries_init(self): 32 | entries = [e for e in dir(v_entries) if e.endswith('Entry')] 33 | for e in entries: 34 | entry = vars(v_entries)[e] 35 | if isinstance(entry, v_entries.ValidatedEntry): 36 | self._test_entry_init(entry) 37 | 38 | def test_intentry_validation(self): 39 | self.assertValidatedFalse(v_entries.IntEntry, 'abc123') 40 | self.assertValidatedTrue(v_entries.IntEntry, '123') 41 | 42 | def test_floatentry_validation(self): 43 | # self.assertValidatedFalse(v_entries.FloatEntry, 'abc123.45') 44 | self.assertValidatedTrue(v_entries.FloatEntry, '123.45') 45 | 46 | def test_percententry_validation(self): 47 | self.assertValidatedFalse(v_entries.PercentEntry, '123.56') 48 | self.assertValidatedFalse(v_entries.PercentEntry, 'aaa') 49 | self.assertValidatedTrue(v_entries.PercentEntry, '12.56') 50 | 51 | def test_lowerstringentry_validation(self): 52 | self.assertValidatedFalse(v_entries.LowerStringEntry, 'abc123') 53 | self.assertValidatedTrue(v_entries.LowerStringEntry, 'abc') 54 | 55 | def test_upperstringentry_validation(self): 56 | self.assertValidatedFalse(v_entries.UpperStringEntry, 'ABCdef') 57 | self.assertValidatedTrue(v_entries.UpperStringEntry, 'ABC') 58 | 59 | def test_capitalizedstringentry_validation(self): 60 | self.assertValidatedTrue(v_entries.CapitalizedStringEntry, 'Abc') 61 | self.assertValidatedFalse(v_entries.CapitalizedStringEntry, 'abc') 62 | 63 | def test_emailentry_validation(self): 64 | self.assertValidatedTrue(v_entries.EmailEntry, 'test@example.com') 65 | self.assertValidatedFalse(v_entries.EmailEntry, 'abcdef') 66 | 67 | def test_passwordentry_validation(self): 68 | self.assertValidatedTrue(v_entries.PasswordEntry, 'aBc&12345') 69 | self.assertValidatedFalse(v_entries.PasswordEntry, 'aBc&123') # Length 70 | self.assertValidatedFalse(v_entries.PasswordEntry, 'aBc&aaaaa') # No digits 71 | self.assertValidatedFalse(v_entries.PasswordEntry, 'abc&12345') # No cap 72 | self.assertValidatedFalse(v_entries.PasswordEntry, 'ABC&1234') # No lowercase 73 | self.assertValidatedFalse(v_entries.PasswordEntry, 'aBcd1234') # No special 74 | 75 | def test_validatedentry_no_validator(self): 76 | entry = v_entries.ValidatedEntry() 77 | validator = v_entries.Validator 78 | vinstance = validator() 79 | 80 | with self.assertRaises(TypeError): 81 | entry._get_validator() 82 | 83 | with self.assertRaises(TypeError): 84 | entry._get_validator(123) 85 | entry._get_validator('123') 86 | 87 | self.assertIsInstance(entry._get_validator(validator), v_entries.Validator) 88 | self.assertIs(entry._get_validator(vinstance), vinstance) 89 | 90 | def test_multi_validator(self): 91 | with self.assertRaises(TypeError): 92 | val = v_entries.AllValidator(v_entries.FloatValidator, int) 93 | 94 | self.assertTrue( 95 | v_entries.AllValidator( 96 | v_entries.RegexValidator(r'.'), 97 | v_entries.RegexValidator(r'\d'), 98 | )._validate('1') 99 | ) 100 | self.assertFalse( 101 | v_entries.AllValidator( 102 | v_entries.RegexValidator(r'[a-z]'), 103 | v_entries.RegexValidator(r'\d'), 104 | )._validate('1') 105 | ) 106 | 107 | self.assertTrue( 108 | v_entries.AnyValidator( 109 | v_entries.RegexValidator(r'.'), 110 | v_entries.RegexValidator(r'\d'), 111 | )._validate('1') 112 | ) 113 | self.assertTrue( 114 | v_entries.AnyValidator( 115 | v_entries.RegexValidator(r'[a-z]'), 116 | v_entries.RegexValidator(r'\d'), 117 | )._validate('1') 118 | ) 119 | 120 | def test_regex_validator(self): 121 | self.assertTrue(v_entries.RegexValidator(r'\d')._validate('1')) 122 | self.assertTrue(v_entries.RegexValidator(r'[a-z]')._validate('a')) 123 | self.assertFalse(v_entries.RegexValidator(r'[a-z]')._validate('1')) 124 | self.assertFalse(v_entries.RegexValidator(r'\d')._validate('a')) 125 | 126 | def test_number_validators(self): 127 | self.assertTrue(v_entries.FloatValidator()._validate('1.0')) 128 | self.assertTrue(v_entries.IntValidator()._validate('1')) 129 | self.assertTrue(v_entries.PercentValidator()._validate('0.55')) 130 | self.assertFalse(v_entries.PercentValidator()._validate('123')) 131 | 132 | def test_string_validators(self): 133 | self.assertTrue(v_entries.StringValidator(string.ascii_lowercase)._validate('abc')) 134 | self.assertFalse(v_entries.StringValidator(string.ascii_lowercase)._validate('ABC')) 135 | self.assertTrue(v_entries.StringValidator(string.ascii_uppercase)._validate('ABC')) 136 | self.assertFalse(v_entries.StringValidator(string.ascii_uppercase)._validate('abc')) 137 | self.assertTrue(v_entries.EmailValidator()._validate('firstname@example.com')) 138 | self.assertTrue(v_entries.PasswordValidator()._validate('Abcd&1234')) 139 | self.assertTrue(v_entries.IPv4Validator()._validate('127.0.0.1')) 140 | self.assertTrue(v_entries.IPv4Validator()._validate('127.0.0.1:9999')) 141 | self.assertTrue(v_entries.IPv4Validator()._validate('localhost')) 142 | 143 | 144 | if __name__ == '__main__': 145 | import unittest 146 | unittest.main() 147 | -------------------------------------------------------------------------------- /ttkwidgets/font/chooser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: This repository 5 | """ 6 | # Based on an idea by Nelson Brochado (https://www.github.com/nbro/tkinter-kit) 7 | import tkinter as tk 8 | from tkinter import ttk 9 | from tkinter import font as tkfont 10 | from .familylistbox import FontFamilyListbox 11 | from .sizedropdown import FontSizeDropdown 12 | from .propertiesframe import FontPropertiesFrame 13 | 14 | 15 | class FontChooser(tk.Toplevel): 16 | """ 17 | A Toplevel to choose a :class:`~font.Font` from a list. 18 | Should only be used through :func:`askfont`. 19 | """ 20 | 21 | def __init__(self, master=None, **kwargs): 22 | """ 23 | Create a FontChooser. 24 | 25 | :param master: master window 26 | :type master: widget 27 | :param kwargs: keyword arguments passed to :class:`tk.Toplevel` initializer 28 | """ 29 | tk.Toplevel.__init__(self, master, **kwargs) 30 | self.wm_title("Choose a font") 31 | self.resizable(False, False) 32 | self.style = ttk.Style() 33 | self.style.configure("FontChooser.TLabel", font=("default", 11), relief=tk.SUNKEN, anchor=tk.CENTER) 34 | self._font_family_header = ttk.Label(self, text="Font family", style="FontChooser.TLabel") 35 | self._font_family_list = FontFamilyListbox(self, callback=self._on_family, height=8) 36 | self._font_label_variable = tk.StringVar() 37 | self._font_label = ttk.Label(self, textvariable=self._font_label_variable, background="white") 38 | self._font_properties_header = ttk.Label(self, text="Font properties", style="FontChooser.TLabel") 39 | self._font_properties_frame = FontPropertiesFrame(self, callback=self._on_properties, label=False) 40 | self._font_size_header = ttk.Label(self, text="Font size", style="FontChooser.TLabel") 41 | self._size_dropdown = FontSizeDropdown(self, callback=self._on_size, width=4) 42 | self._example_label = tk.Label(self, text="Example", anchor=tk.CENTER, background="white", height=2, 43 | relief=tk.SUNKEN) 44 | 45 | self._family = None 46 | self._size = 11 47 | self._bold = False 48 | self._italic = False 49 | self._underline = False 50 | self._overstrike = False 51 | self._font = None 52 | self._ok_button = ttk.Button(self, text="OK", command=self._close) 53 | self._cancel_button = ttk.Button(self, text="Cancel", command=self._cancel) 54 | self._grid_widgets() 55 | 56 | def _grid_widgets(self): 57 | """Puts all the child widgets in the correct position.""" 58 | self._font_family_header.grid(row=0, column=1, sticky="nswe", padx=5, pady=5) 59 | self._font_label.grid(row=1, column=1, sticky="nswe", padx=5, pady=(0, 5)) 60 | self._font_family_list.grid(row=2, rowspan=3, column=1, sticky="nswe", padx=5, pady=(0, 5)) 61 | self._font_properties_header.grid(row=0, column=2, sticky="nswe", padx=5, pady=5) 62 | self._font_properties_frame.grid(row=1, rowspan=2, column=2, sticky="we", padx=5, pady=5) 63 | self._font_size_header.grid(row=3, column=2, sticky="we", padx=5, pady=5) 64 | self._size_dropdown.grid(row=4, column=2, sticky="we", padx=5, pady=5) 65 | self._example_label.grid(row=5, column=1, columnspan=2, sticky="nswe", padx=5, pady=5) 66 | self._ok_button.grid(row=6, column=2, sticky="nswe", padx=5, pady=5) 67 | self._cancel_button.grid(row=6, column=1, sticky="nswe", padx=5, pady=5) 68 | 69 | def _on_family(self, family): 70 | """ 71 | Callback if family is changed 72 | 73 | :param family: family name 74 | """ 75 | self._font_label_variable.set(family) 76 | self._family = family 77 | self._on_change() 78 | 79 | def _on_size(self, size): 80 | """ 81 | Callback if size is changed 82 | 83 | :param size: int size 84 | """ 85 | self._size = size 86 | self._on_change() 87 | 88 | def _on_properties(self, properties): 89 | """ 90 | Callback if properties are changed. 91 | 92 | :param properties: (bool bold, bool italic, bool underline, bool overstrike) 93 | """ 94 | self._bold, self._italic, self._underline, self._overstrike = properties 95 | self._on_change() 96 | 97 | def _on_change(self): 98 | """Callback if any of the values are changed.""" 99 | font = self.__generate_font_tuple() 100 | self._example_label.configure(font=font) 101 | 102 | def __generate_font_tuple(self): 103 | """ 104 | Generate a font tuple for tkinter widgets based on the user's entries. 105 | 106 | :return: font tuple (family_name, size, *options) 107 | """ 108 | if not self._family: 109 | return None 110 | font = [self._family, self._size] 111 | if self._bold: 112 | font.append("bold") 113 | if self._italic: 114 | font.append("italic") 115 | if self._underline: 116 | font.append("underline") 117 | if self._overstrike: 118 | font.append("overstrike") 119 | return tuple(font) 120 | 121 | @property 122 | def font(self): 123 | """ 124 | Selected font. 125 | 126 | :return: font tuple (family_name, size, \*options), :class:`~font.Font` object 127 | """ 128 | if self._family is None: 129 | return None, None 130 | else: 131 | font_tuple = self.__generate_font_tuple() 132 | font_obj = tkfont.Font(family=self._family, size=self._size, 133 | weight=tkfont.BOLD if self._bold else tkfont.NORMAL, 134 | slant=tkfont.ITALIC if self._italic else tkfont.ROMAN, 135 | underline=1 if self._underline else 0, 136 | overstrike=1 if self._overstrike else 0) 137 | return font_tuple, font_obj 138 | 139 | def _close(self): 140 | """Destroy the window.""" 141 | self.destroy() 142 | 143 | def _cancel(self): 144 | """Cancel font selection and destroy window.""" 145 | self._family = None 146 | self.destroy() 147 | 148 | 149 | def askfont(): 150 | """ 151 | Opens a :class:`FontChooser` toplevel to allow the user to select a font 152 | 153 | :return: font tuple (family_name, size, \*options), :class:`~font.Font` object 154 | """ 155 | chooser = FontChooser() 156 | chooser.wait_window() 157 | return chooser.font 158 | -------------------------------------------------------------------------------- /ttkwidgets/hook.py: -------------------------------------------------------------------------------- 1 | """ 2 | Author: RedFantom 3 | License: GNU GPLv3 4 | Source: The ttkwidgets repository 5 | 6 | This file provides a set of functions that can be used for adding 7 | options to all classes that inherit from ``ttk.Widget``, so `ttk.Button``, 8 | for example, but also every widget contained in this package. 9 | 10 | When an option is changed, an updater function is called that the 11 | developer creating the hook has to provide. This updater is called 12 | after the widget has initialized if the option is set upon 13 | initialization of the widget. 14 | 15 | Default values may be specified as well. For more details, see 16 | :meth:`hook_ttk_widgets` for more details. See :meth:`tooltip_updater` 17 | for a practical implementation of a hook. 18 | """ 19 | try: 20 | import Tkinter as tk 21 | import ttk 22 | except ImportError: 23 | import tkinter as tk 24 | from tkinter import ttk 25 | 26 | 27 | def is_hooked(options): 28 | # type: (dict) -> bool 29 | """Return whether ``ttk.Widget`` is hooked for any of the given options""" 30 | for hookname in [hook for hook in dir(ttk.Widget) if hook.startswith("WidgetHook_")]: 31 | hookoptions = getattr(ttk.Widget, hookname).defaults 32 | if any(option in hookoptions for option in options): 33 | return True 34 | return hasattr(ttk.Widget, generate_hook_name(options)) 35 | 36 | 37 | def generate_hook_name(options): 38 | # type: (dict) -> str 39 | """Generate a unique name for a hook given a set of options""" 40 | return "WidgetHook_" + "_".join(sorted(options)) 41 | 42 | 43 | def hook_ttk_widgets(updater, options): 44 | # type: (callable, dict) -> str 45 | """ 46 | Create a hook in either tk.Widget or ttk.Widget to support options 47 | 48 | This function works by overriding the ``__init__``, ``configure``, 49 | ``config``, ``cget`` and ``keys`` functions of the ``ttk.Widget`` 50 | class. The original functions are stored safely inside a class 51 | object created upon the ``ttk.Widget`` class, so that they can 52 | still be executed when necessary. 53 | 54 | Multiple hooks are allowed at the same time and a custom hook 55 | overwriting any of the functions (as long as it is done properly) 56 | does not cause any issues. 57 | 58 | The order in which hooks are executed is not guaranteed as a stable 59 | library feature. 60 | 61 | :param updater: Function to call when an option in the given options 62 | is changed. The function should support updating for all the 63 | options given in the hook. 64 | :type updater: (widget: ttk.Widget, option: str, value: Any) -> None 65 | :param options: A dictionary of options where the keys are the 66 | keyword argument names and the values are their respective 67 | default values. A default value must be specified for every 68 | option. All option names must be allowed in valid Python syntax. 69 | :type options: Dict[str, Any] 70 | :return: Name of the attribute created to store values 71 | :rtype: str 72 | """ 73 | NULL = object() 74 | 75 | assert len(options) > 0 76 | 77 | # Create a unique name so that multiple hooks do not interfere 78 | name = generate_hook_name(options) 79 | # Check to see if the hook already exists 80 | if hasattr(ttk.Widget, name): # Hook already exists, will be updated 81 | if updater is not None: 82 | raise RuntimeError("Invalid parameter: Updater may not be changed after hook creation") 83 | getattr(ttk.Widget, name).defaults = options.copy() 84 | return name 85 | elif is_hooked(options): 86 | raise RuntimeError("Invalid options: Cannot replace full hook with partial hook") 87 | 88 | # Create a class with the original functions 89 | class OriginalFunctions(object): 90 | original_init = ttk.Widget.__init__ 91 | original_config = ttk.Widget.config 92 | original_configure = ttk.Widget.configure 93 | original_cget = ttk.Widget.cget 94 | original_keys = ttk.Widget.keys 95 | defaults = options 96 | 97 | # Move the OriginalFunctions class to the target class 98 | setattr(ttk.Widget, name, OriginalFunctions) 99 | 100 | def setter(self, option, value): 101 | """Store an option on the embedded object and then call updater""" 102 | current = getter(self, option) 103 | if current != value: 104 | setattr(getattr(self, name.lower()), option, value) 105 | updater(self, option, value) 106 | 107 | def getter(self, option): 108 | """Retrieve an option value from the embedded object""" 109 | return getattr(getattr(self, name.lower()), option, NULL) 110 | 111 | def __init__(self, *args): 112 | """Catch initialization and pop all the custom options""" 113 | master, widget, widget_options = args 114 | # Pop all the options, taking default values first 115 | values = getattr(self, name).defaults.copy() 116 | for (option, default) in options.items(): 117 | value = widget_options.pop(option, default) 118 | values[option] = value 119 | # Perform initialization of the widget 120 | getattr(self, name).original_init(self, master, widget, widget_options) 121 | # Create an instance object to store options on 122 | setattr(self, name.lower(), OriginalFunctions()) 123 | # Set all the options only after widget init is complete 124 | for option, value in values.items(): # updater only called after init is done 125 | setter(self, option, value) 126 | 127 | def configure(self, *args, **kwargs): 128 | """Catch configure to pop custom options and configure them""" 129 | for widget_options in args + (kwargs,): # Loop over all sets of options available 130 | if widget_options is None: 131 | continue 132 | for option, _ in options.items(): 133 | current = getter(self, option) 134 | value = widget_options.pop(option, current) 135 | setter(self, option, value) 136 | return getattr(self, name).original_configure(self, *args, **kwargs) 137 | 138 | def cget(self, key): 139 | """Return the value of a custom option if key is a custom option""" 140 | if key in options: 141 | return getter(self, key) 142 | return getattr(self, name).original_cget(self, key) 143 | 144 | def keys(self): 145 | """Return an updated list of keys with the custom options""" 146 | keys = getattr(self, name).original_keys(self) 147 | keys.extend(options.keys()) 148 | return keys 149 | 150 | ttk.Widget.__init__ = __init__ 151 | ttk.Widget.configure = configure 152 | ttk.Widget.config = configure 153 | ttk.Widget.cget = cget 154 | ttk.Widget.__getitem__ = cget 155 | ttk.Widget.keys = keys 156 | 157 | return name 158 | --------------------------------------------------------------------------------