diff --git a/stdlib/@tests/stubtest_allowlists/py310.txt b/stdlib/@tests/stubtest_allowlists/py310.txt index bb75541d1c98..7e7396740dd4 100644 --- a/stdlib/@tests/stubtest_allowlists/py310.txt +++ b/stdlib/@tests/stubtest_allowlists/py310.txt @@ -25,6 +25,9 @@ posixpath.join ntpath.join os.path.join +# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that) +tkinter.ttk.Style.element_create + # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/@tests/stubtest_allowlists/py311.txt b/stdlib/@tests/stubtest_allowlists/py311.txt index e9ccc238b37c..98c5ccea1331 100644 --- a/stdlib/@tests/stubtest_allowlists/py311.txt +++ b/stdlib/@tests/stubtest_allowlists/py311.txt @@ -45,6 +45,9 @@ posixpath.join ntpath.join os.path.join +# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that) +tkinter.ttk.Style.element_create + # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/@tests/stubtest_allowlists/py312.txt b/stdlib/@tests/stubtest_allowlists/py312.txt index 079b286a0016..5bdc3878b66c 100644 --- a/stdlib/@tests/stubtest_allowlists/py312.txt +++ b/stdlib/@tests/stubtest_allowlists/py312.txt @@ -62,6 +62,9 @@ posixpath.join ntpath.join os.path.join +# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that) +tkinter.ttk.Style.element_create + # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/@tests/stubtest_allowlists/py313.txt b/stdlib/@tests/stubtest_allowlists/py313.txt index 4e5b4733e620..4eda246de84e 100644 --- a/stdlib/@tests/stubtest_allowlists/py313.txt +++ b/stdlib/@tests/stubtest_allowlists/py313.txt @@ -62,6 +62,9 @@ posixpath.join ntpath.join os.path.join +# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that) +tkinter.ttk.Style.element_create + # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/@tests/stubtest_allowlists/py314.txt b/stdlib/@tests/stubtest_allowlists/py314.txt index 4c8421a6959a..0fcdcbb84d97 100644 --- a/stdlib/@tests/stubtest_allowlists/py314.txt +++ b/stdlib/@tests/stubtest_allowlists/py314.txt @@ -84,6 +84,9 @@ posixpath.join ntpath.join os.path.join +# this is implemented with *args having a minimum size so arguments before it must be positional (but stubtest doesn't see that) +tkinter.ttk.Style.element_create + # typing.IO uses positional-or-keyword arguments, but in the stubs we prefer # to mark these as positional-only for compatibility with existing sub-classes. typing(_extensions)?\.BinaryIO\.write diff --git a/stdlib/tkinter/ttk.pyi b/stdlib/tkinter/ttk.pyi index c46239df81eb..86c55eba7006 100644 --- a/stdlib/tkinter/ttk.pyi +++ b/stdlib/tkinter/ttk.pyi @@ -1,10 +1,11 @@ import _tkinter +import sys import tkinter -from _typeshed import Incomplete, MaybeNone -from collections.abc import Callable +from _typeshed import MaybeNone +from collections.abc import Callable, Iterable from tkinter.font import _FontDescription from typing import Any, Literal, TypedDict, overload, type_check_only -from typing_extensions import TypeAlias +from typing_extensions import Never, TypeAlias, Unpack __all__ = [ "Button", @@ -35,7 +36,7 @@ __all__ = [ ] def tclobjs_to_py(adict: dict[Any, Any]) -> dict[Any, Any]: ... -def setup_master(master=None): ... +def setup_master(master: tkinter.Misc | None = None): ... _Padding: TypeAlias = ( tkinter._ScreenUnits @@ -48,19 +49,153 @@ _Padding: TypeAlias = ( # from ttk_widget (aka ttk::widget) manual page, differs from tkinter._Compound _TtkCompound: TypeAlias = Literal["", "text", "image", tkinter._Compound] +# Last item (option value to apply) varies between different options so use Any. +# It could also be any iterable with items matching the tuple, but that case +# hasn't been added here for consistency with _Padding above. +_Statespec: TypeAlias = tuple[Unpack[tuple[str, ...]], Any] +_ImageStatespec: TypeAlias = tuple[Unpack[tuple[str, ...]], tkinter._ImageSpec] +_VsapiStatespec: TypeAlias = tuple[Unpack[tuple[str, ...]], int] + +class _Layout(TypedDict, total=False): + side: Literal["left", "right", "top", "bottom"] + sticky: str # consists of letters 'n', 's', 'w', 'e', may contain repeats, may be empty + unit: Literal[0, 1] | bool + children: _LayoutSpec + # Note: there seem to be some other undocumented keys sometimes + +# This could be any sequence when passed as a parameter but will always be a list when returned. +_LayoutSpec: TypeAlias = list[tuple[str, _Layout | None]] + +# Keep these in sync with the appropriate methods in Style +class _ElementCreateImageKwargs(TypedDict, total=False): + border: _Padding + height: tkinter._ScreenUnits + padding: _Padding + sticky: str + width: tkinter._ScreenUnits + +_ElementCreateArgsCrossPlatform: TypeAlias = ( + # Could be any sequence here but types are not homogenous so just type it as tuple + tuple[Literal["image"], tkinter._ImageSpec, Unpack[tuple[_ImageStatespec, ...]], _ElementCreateImageKwargs] + | tuple[Literal["from"], str, str] + | tuple[Literal["from"], str] # (fromelement is optional) +) +if sys.platform == "win32" and sys.version_info >= (3, 13): + class _ElementCreateVsapiKwargsPadding(TypedDict, total=False): + padding: _Padding + + class _ElementCreateVsapiKwargsMargin(TypedDict, total=False): + padding: _Padding + + class _ElementCreateVsapiKwargsSize(TypedDict): + width: tkinter._ScreenUnits + height: tkinter._ScreenUnits + + _ElementCreateVsapiKwargsDict: TypeAlias = ( + _ElementCreateVsapiKwargsPadding | _ElementCreateVsapiKwargsMargin | _ElementCreateVsapiKwargsSize + ) + _ElementCreateArgs: TypeAlias = ( # noqa: Y047 # It doesn't recognise the usage below for whatever reason + _ElementCreateArgsCrossPlatform + | tuple[Literal["vsapi"], str, int, _ElementCreateVsapiKwargsDict] + | tuple[Literal["vsapi"], str, int, _VsapiStatespec, _ElementCreateVsapiKwargsDict] + ) +else: + _ElementCreateArgs: TypeAlias = _ElementCreateArgsCrossPlatform +_ThemeSettingsValue = TypedDict( + "_ThemeSettingsValue", + { + "configure": dict[str, Any], + "map": dict[str, Iterable[_Statespec]], + "layout": _LayoutSpec, + "element create": _ElementCreateArgs, + }, + total=False, +) +_ThemeSettings: TypeAlias = dict[str, _ThemeSettingsValue] + class Style: - master: Incomplete + master: tkinter.Misc tk: _tkinter.TkappType def __init__(self, master: tkinter.Misc | None = None) -> None: ... - def configure(self, style, query_opt=None, **kw): ... - def map(self, style, query_opt=None, **kw): ... - def lookup(self, style, option, state=None, default=None): ... - def layout(self, style, layoutspec=None): ... - def element_create(self, elementname, etype, *args, **kw) -> None: ... - def element_names(self): ... - def element_options(self, elementname): ... - def theme_create(self, themename, parent=None, settings=None) -> None: ... - def theme_settings(self, themename, settings) -> None: ... + # For these methods, values given vary between options. Returned values + # seem to be str, but this might not always be the case. + @overload + def configure(self, style: str) -> dict[str, Any] | None: ... # Returns None if no configuration. + @overload + def configure(self, style: str, query_opt: str, **kw: Any) -> Any: ... + @overload + def configure(self, style: str, query_opt: None = None, **kw: Any) -> None: ... + @overload + def map(self, style: str, query_opt: str) -> _Statespec: ... + @overload + def map(self, style: str, query_opt: None = None, **kw: Iterable[_Statespec]) -> dict[str, _Statespec]: ... + def lookup(self, style: str, option: str, state: Iterable[str] | None = None, default: Any | None = None) -> Any: ... + @overload + def layout(self, style: str, layoutspec: _LayoutSpec) -> list[Never]: ... # Always seems to return an empty list + @overload + def layout(self, style: str, layoutspec: None = None) -> _LayoutSpec: ... + @overload + def element_create( + self, + elementname: str, + etype: Literal["image"], + default_image: tkinter._ImageSpec, + /, + *imagespec: _ImageStatespec, + border: _Padding = ..., + height: tkinter._ScreenUnits = ..., + padding: _Padding = ..., + sticky: str = ..., + width: tkinter._ScreenUnits = ..., + ) -> None: ... + @overload + def element_create(self, elementname: str, etype: Literal["from"], themename: str, fromelement: str = ..., /) -> None: ... + if sys.platform == "win32" and sys.version_info >= (3, 13): # and tk version >= 8.6 + # margin, padding, and (width + height) are mutually exclusive. width + # and height must either both be present or not present at all. Note: + # There are other undocumented options if you look at ttk's source code. + @overload + def element_create( + self, + elementname: str, + etype: Literal["vsapi"], + class_: str, + part: int, + vs_statespec: _VsapiStatespec = ..., + /, + *, + padding: _Padding = ..., + ) -> None: ... + @overload + def element_create( + self, + elementname: str, + etype: Literal["vsapi"], + class_: str, + part: int, + vs_statespec: _VsapiStatespec = ..., + /, + *, + margin: _Padding = ..., + ) -> None: ... + @overload + def element_create( + self, + elementname: str, + etype: Literal["vsapi"], + class_: str, + part: int, + vs_statespec: _VsapiStatespec = ..., + /, + *, + width: tkinter._ScreenUnits, + height: tkinter._ScreenUnits, + ) -> None: ... + + def element_names(self) -> tuple[str, ...]: ... + def element_options(self, elementname: str) -> tuple[str, ...]: ... + def theme_create(self, themename: str, parent: str | None = None, settings: _ThemeSettings | None = None) -> None: ... + def theme_settings(self, themename: str, settings: _ThemeSettings) -> None: ... def theme_names(self) -> tuple[str, ...]: ... @overload def theme_use(self, themename: str) -> None: ... @@ -615,7 +750,7 @@ class Panedwindow(Widget, tkinter.PanedWindow): ) -> dict[str, tuple[str, str, str, Any, Any]] | None: ... @overload def config(self, cnf: str) -> tuple[str, str, str, Any, Any]: ... - forget: Incomplete + forget = tkinter.PanedWindow.forget def insert(self, pos, child, **kw) -> None: ... def pane(self, pane, option=None, **kw): ... def sashpos(self, index, newpos=None): ...