From 551a19c83b88e269a852f5d93b2a1e3aca531582 Mon Sep 17 00:00:00 2001 From: rdbende Date: Mon, 1 Mar 2021 22:54:21 +0100 Subject: [PATCH 01/32] Create NumberEntry widget An entry that takes only numbers or calculations and calculates the result of the calculation --- ttkwidgets/numberentry.py | 91 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 ttkwidgets/numberentry.py diff --git a/ttkwidgets/numberentry.py b/ttkwidgets/numberentry.py new file mode 100644 index 00000000..ec69b6f3 --- /dev/null +++ b/ttkwidgets/numberentry.py @@ -0,0 +1,91 @@ +""" +Author: rdbende +License: GNU GPLv3 +Copyright (c) 2021 rdbende +""" + +import tkinter as tk +from tkinter import ttk + + +class NumberEntry(ttk.Entry): + """ + An entry that takes only numbers or calculations and calculates the result of the calculation + + :param expressions: Allow the use of expressions (default is True) + :type expressions: bool + + :param roundto: The number of decimals in the result (default is 0) + :type roundto: int + """ + + + def __init__(self, master, **kwargs): + + + self._expr = kwargs.pop("expressions", True) + self._round = kwargs.pop("roundto", 0) + ttk.Entry.__init__(self, master, **kwargs) + self.bind("", self._eval) + self.bind("", self._eval) + self.bind("", self._check) + + + def _eval(self, *args): + current = self.get() + try: + if len(current) > 0: + if int(self._round) == 0: + result = int(round(eval(current), 0)) + self.delete(0, "end") + self.insert(0, result) + else: + result = round(float(eval(current)), self._round) + self.delete(0, "end") + self.insert(0, result) + except SyntaxError: + self.delete(0, "end") + self.insert(0, "SyntaxError") + self.select_range(0, "end") + except ZeroDivisionError: + self.delete(0, "end") + self.insert(0, "ZeroDivisionError") + self.select_range(0, "end") + + + def _check(self, *args): + typed = self.get() + if not typed == "SyntaxError" and not typed == "ZeroDivisionError": + checked = self._replace(typed) + self.delete(0, "end") + self.insert(0, checked) + + + def _replace(self, typed) -> str: + if self._expr: + allowed = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "-", "*", "/", "%", "."] + else: + allowed = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."] + for current in typed: + if not current in allowed: + typed = typed.replace(current, "") + return typed + + + def cget(self, key): + """Return the resource value for a KEY given as string""" + if key == "expressions": + return self._expr + elif key == "roundto": + return self._round + else: + return ttk.Entry.cget(self, key) + + + def keys(self): + """Return a list of all resource names of this widget""" + keys = ttk.Entry.keys(self) + keys.extend(["expressions", "roundto"]) + keys = sorted(keys) + return keys + From a6db0b37d142b00b5c39b74d86daf05411a3bdce Mon Sep 17 00:00:00 2001 From: rdbende Date: Mon, 1 Mar 2021 22:55:48 +0100 Subject: [PATCH 02/32] Change the default hover-cursor to hand2 Change the default hover-cursor from hand1 to hand2 --- ttkwidgets/linklabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttkwidgets/linklabel.py b/ttkwidgets/linklabel.py index f1f4f730..7af88e78 100644 --- a/ttkwidgets/linklabel.py +++ b/ttkwidgets/linklabel.py @@ -29,7 +29,7 @@ def __init__(self, master=None, **kwargs): :type clicked_color: str :param kwargs: options to be passed on to the :class:`ttk.Label` initializer """ - self._cursor = kwargs.pop("cursor", "hand1") + self._cursor = kwargs.pop("cursor", "hand2") self._link = kwargs.pop("link", "") self._normal_color = kwargs.pop("normal_color", "#0563c1") self._hover_color = kwargs.pop("hover_color", "#057bc1") From 7611f2a1ffbe6639825e7ab81e3ece98b9209d27 Mon Sep 17 00:00:00 2001 From: rdbende Date: Mon, 1 Mar 2021 22:57:11 +0100 Subject: [PATCH 03/32] Change the movement cursor to fleur Change the movement cursor from exchange to fleur --- ttkwidgets/itemscanvas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttkwidgets/itemscanvas.py b/ttkwidgets/itemscanvas.py index ea7bb128..06f210e9 100644 --- a/ttkwidgets/itemscanvas.py +++ b/ttkwidgets/itemscanvas.py @@ -113,7 +113,7 @@ def left_motion(self, event): return item = results[0] rectangle = self.items[item] - self.config(cursor="exchange") + self.config(cursor="fleur") self.canvas.itemconfigure(item, fill="blue") xc, yc = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) dx, dy = xc - self.current_coords[0], yc - self.current_coords[1] From 61a46c5f7d20764776a084e8dacdb3c4bc7e39f1 Mon Sep 17 00:00:00 2001 From: rdbende Date: Mon, 1 Mar 2021 22:58:07 +0100 Subject: [PATCH 04/32] Add NumberEntry to __init__.py --- ttkwidgets/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ttkwidgets/__init__.py b/ttkwidgets/__init__.py index 2360b89c..f8d9f0f7 100644 --- a/ttkwidgets/__init__.py +++ b/ttkwidgets/__init__.py @@ -11,6 +11,7 @@ from ttkwidgets.timeline import TimeLine from ttkwidgets.tickscale import TickScale from ttkwidgets.table import Table +from ttkwidgets.numberentry import NumberEntry from ttkwidgets.validated_entries.numbers import ( PercentEntry, IntEntry, FloatEntry, From 8941ab3e8bbb4e7be37413d464041636bb51008b Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 2 Mar 2021 18:33:27 +0100 Subject: [PATCH 05/32] Fixed portability issues Change the default cursor to use the native link pointer on Mac and Windows --- ttkwidgets/linklabel.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ttkwidgets/linklabel.py b/ttkwidgets/linklabel.py index 7af88e78..10cb31fd 100644 --- a/ttkwidgets/linklabel.py +++ b/ttkwidgets/linklabel.py @@ -29,7 +29,12 @@ def __init__(self, master=None, **kwargs): :type clicked_color: str :param kwargs: options to be passed on to the :class:`ttk.Label` initializer """ - self._cursor = kwargs.pop("cursor", "hand2") + if root.tk.call('tk', 'windowingsystem') == 'win32': + self._cursor = kwargs.pop("cursor", "hand2") # Native Windows hand pointer + elif root.tk.call('tk', 'windowingsystem') == 'aqua': + self._cursor = kwargs.pop("cursor", "pointinghand") # Native Mac hand pointer + else: + self._cursor = kwargs.pop("cursor", "hand1") self._link = kwargs.pop("link", "") self._normal_color = kwargs.pop("normal_color", "#0563c1") self._hover_color = kwargs.pop("hover_color", "#057bc1") From 8488995fd0d8ef26616a16c8a2cafc8462cd3dd3 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 9 Mar 2021 09:31:12 +0100 Subject: [PATCH 06/32] Restore cursor --- ttkwidgets/itemscanvas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttkwidgets/itemscanvas.py b/ttkwidgets/itemscanvas.py index 06f210e9..ea7bb128 100644 --- a/ttkwidgets/itemscanvas.py +++ b/ttkwidgets/itemscanvas.py @@ -113,7 +113,7 @@ def left_motion(self, event): return item = results[0] rectangle = self.items[item] - self.config(cursor="fleur") + self.config(cursor="exchange") self.canvas.itemconfigure(item, fill="blue") xc, yc = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) dx, dy = xc - self.current_coords[0], yc - self.current_coords[1] From 8b614d2ce7da363483c3aacc19ba7e621082c5c9 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 9 Mar 2021 09:32:19 +0100 Subject: [PATCH 07/32] Restore linklabel --- ttkwidgets/linklabel.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ttkwidgets/linklabel.py b/ttkwidgets/linklabel.py index 10cb31fd..f1f4f730 100644 --- a/ttkwidgets/linklabel.py +++ b/ttkwidgets/linklabel.py @@ -29,12 +29,7 @@ def __init__(self, master=None, **kwargs): :type clicked_color: str :param kwargs: options to be passed on to the :class:`ttk.Label` initializer """ - if root.tk.call('tk', 'windowingsystem') == 'win32': - self._cursor = kwargs.pop("cursor", "hand2") # Native Windows hand pointer - elif root.tk.call('tk', 'windowingsystem') == 'aqua': - self._cursor = kwargs.pop("cursor", "pointinghand") # Native Mac hand pointer - else: - self._cursor = kwargs.pop("cursor", "hand1") + self._cursor = kwargs.pop("cursor", "hand1") self._link = kwargs.pop("link", "") self._normal_color = kwargs.pop("normal_color", "#0563c1") self._hover_color = kwargs.pop("hover_color", "#057bc1") From 9584d3f36941a6aa89ac3dbe70d36f6032bdb6ee Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 9 Mar 2021 10:03:30 +0100 Subject: [PATCH 08/32] Add __getitem__, __setitem__ and configure Add __getitem__, __setitem__, config and configure, docstring formatting --- ttkwidgets/numberentry.py | 40 +++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/ttkwidgets/numberentry.py b/ttkwidgets/numberentry.py index ec69b6f3..dfef7310 100644 --- a/ttkwidgets/numberentry.py +++ b/ttkwidgets/numberentry.py @@ -11,27 +11,31 @@ class NumberEntry(ttk.Entry): """ An entry that takes only numbers or calculations and calculates the result of the calculation - - :param expressions: Allow the use of expressions (default is True) - :type expressions: bool - - :param roundto: The number of decimals in the result (default is 0) - :type roundto: int """ - - - def __init__(self, master, **kwargs): - + def __init__(self, master=None, **kwargs): + """ + Create a NumberEntry + :param expressions: Allow the use of expressions (default is True) + :type expressions: bool + :param roundto: The number of decimals in the result (default is 0) + :type roundto: int + """ self._expr = kwargs.pop("expressions", True) self._round = kwargs.pop("roundto", 0) ttk.Entry.__init__(self, master, **kwargs) self.bind("", self._eval) self.bind("", self._eval) self.bind("", self._check) - + + def __getitem__(self, key): + return self.cget(key) + + def __setitem__(self, key, value): + self.configure(**{key: value}) def _eval(self, *args): + """Calculate the result of the entered calculation""" current = self.get() try: if len(current) > 0: @@ -52,7 +56,6 @@ def _eval(self, *args): self.insert(0, "ZeroDivisionError") self.select_range(0, "end") - def _check(self, *args): typed = self.get() if not typed == "SyntaxError" and not typed == "ZeroDivisionError": @@ -60,8 +63,8 @@ def _check(self, *args): self.delete(0, "end") self.insert(0, checked) - def _replace(self, typed) -> str: + """Delete the not allowed characters""" if self._expr: allowed = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "-", "*", "/", "%", "."] else: @@ -70,7 +73,14 @@ def _replace(self, typed) -> str: if not current in allowed: typed = typed.replace(current, "") return typed - + + def configure(self, **kwargs): + """Configure resources of the widget.""" + self._expr = kwargs.pop("expressions", True) + self._round = kwargs.pop("roundto", 0) + ttk.Entry.configure(self, **kwargs) + + config = configure def cget(self, key): """Return the resource value for a KEY given as string""" @@ -81,11 +91,9 @@ def cget(self, key): else: return ttk.Entry.cget(self, key) - def keys(self): """Return a list of all resource names of this widget""" keys = ttk.Entry.keys(self) keys.extend(["expressions", "roundto"]) keys = sorted(keys) return keys - From af423609e1248373b11443eb6cd1c73e66ef71a9 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 9 Mar 2021 10:20:29 +0100 Subject: [PATCH 09/32] Update AUTHORS.md --- AUTHORS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 1b80c6ff..2d96264b 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -27,5 +27,7 @@ This file contains a list of all the authors of widgets in this repository. Plea * `AutocompleteEntryListbox` - [Dogeek](https://github.com/Dogeek) * `validated_entries` submodule +- [rdbende](https://github.com/Dogeek) + * `NumberEntry` - Multiple authors: * `ScaleEntry` (RedFantom and Juliette Monsel) From a4af5668769e0c1e3a491d036e9f429a4b97b4bd Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 9 Mar 2021 10:24:43 +0100 Subject: [PATCH 10/32] Create example for NumberEntry --- examples/example_numberentry.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 examples/example_numberentry.py diff --git a/examples/example_numberentry.py b/examples/example_numberentry.py new file mode 100644 index 00000000..d46d9e8b --- /dev/null +++ b/examples/example_numberentry.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) rdbende 2021 +# For license see LICENSE + +from ttkwidgets import NumberEntry +import tkinter as tk + +root = tk.Tk() +root.title('NumberEntry') + +NumberEntry(root, expressions=True, roundto=4).pack(pady=30) + +root.mainloop() From 439323ee87e87e5d8ea7a5c4dda99ec5236a8474 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 9 Mar 2021 10:50:14 +0100 Subject: [PATCH 11/32] Create unittest for NumberEntry --- tests/test_numberentry.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/test_numberentry.py diff --git a/tests/test_numberentry.py b/tests/test_numberentry.py new file mode 100644 index 00000000..6e421775 --- /dev/null +++ b/tests/test_numberentry.py @@ -0,0 +1,41 @@ +# Copyright (c) rdbende 2021 +# For license see LICENSE + +from ttkwidgets import NumberEntry +from tests import BaseWidgetTest +import tkinter as tk + + +class TestNumberEntry(BaseWidgetTest): + def test_numberentry_init(self): + entry = NumberEntry(self.window, expressions=True, roundto=4) + entry.pack() + self.window.update() + + def test_numberentry_events(self): + entry = NumberEntry(self.window, expressions=True, roundto=4) + entry.pack() + self.window.update() + entry.insert(0, "1+2-3*4/5**6") + self.window.update() + entry._check() + self.window.update() + entry._replace("1+2-3*4/5**6") + self.window.update() + entry._eval() + self.window.update() + + def test_numberentry_config(self): + entry = NumberEntry(self.window, expressions=True, roundto=4) + entry.pack() + self.window.update() + entry.keys() + self.window.update() + entry.configure(expressions=False, roundto=0) + self.window.update() + entry.cget("expressions") + self.window.update() + value = entry["roundto"] + self.window.update() + entry["roundto"] = 4 + self.window.update() From 19f361e044aacdfc5ab0e2f87936d6ccc370502c Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 9 Mar 2021 10:59:21 +0100 Subject: [PATCH 12/32] Add NumberEntry to sphinx documentation --- docs/source/ttkwidgets/ttkwidgets.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/source/ttkwidgets/ttkwidgets.rst b/docs/source/ttkwidgets/ttkwidgets.rst index 21543502..65bef4a9 100644 --- a/docs/source/ttkwidgets/ttkwidgets.rst +++ b/docs/source/ttkwidgets/ttkwidgets.rst @@ -17,6 +17,7 @@ ttkwidgets DebugWindow ItemsCanvas LinkLabel + NumberEntry ScaleEntry ScrolledListbox Table From acdbec576364ceb875536d2eabcf4f5144ac88b6 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 9 Mar 2021 14:59:21 +0100 Subject: [PATCH 13/32] Update AUTHORS.md --- AUTHORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.md b/AUTHORS.md index 2d96264b..5aa5cfb0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -27,7 +27,7 @@ This file contains a list of all the authors of widgets in this repository. Plea * `AutocompleteEntryListbox` - [Dogeek](https://github.com/Dogeek) * `validated_entries` submodule -- [rdbende](https://github.com/Dogeek) +- [rdbende](https://github.com/rdbende) * `NumberEntry` - Multiple authors: * `ScaleEntry` (RedFantom and Juliette Monsel) From fb2dd121122afc866508164b756007ce96c6bdb9 Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 11 Mar 2021 11:41:05 +0100 Subject: [PATCH 14/32] Fixed fatal config bug --- ttkwidgets/numberentry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttkwidgets/numberentry.py b/ttkwidgets/numberentry.py index dfef7310..0d329420 100644 --- a/ttkwidgets/numberentry.py +++ b/ttkwidgets/numberentry.py @@ -76,8 +76,8 @@ def _replace(self, typed) -> str: def configure(self, **kwargs): """Configure resources of the widget.""" - self._expr = kwargs.pop("expressions", True) - self._round = kwargs.pop("roundto", 0) + self._expr = kwargs.pop("expressions", self._expr) + self._round = kwargs.pop("roundto", self._round) ttk.Entry.configure(self, **kwargs) config = configure From e5d60f33b188b36b0cbc7f8b4ac04877dcf44104 Mon Sep 17 00:00:00 2001 From: rdbende Date: Mon, 22 Mar 2021 17:27:48 +0100 Subject: [PATCH 15/32] Update __init__.py --- ttkwidgets/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ttkwidgets/__init__.py b/ttkwidgets/__init__.py index f8d9f0f7..2360b89c 100644 --- a/ttkwidgets/__init__.py +++ b/ttkwidgets/__init__.py @@ -11,7 +11,6 @@ from ttkwidgets.timeline import TimeLine from ttkwidgets.tickscale import TickScale from ttkwidgets.table import Table -from ttkwidgets.numberentry import NumberEntry from ttkwidgets.validated_entries.numbers import ( PercentEntry, IntEntry, FloatEntry, From de2d5c23ff025895ced00f9c9c9b73179edd240e Mon Sep 17 00:00:00 2001 From: rdbende Date: Mon, 22 Mar 2021 17:28:52 +0100 Subject: [PATCH 16/32] Update __init__.py --- ttkwidgets/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ttkwidgets/__init__.py b/ttkwidgets/__init__.py index 2360b89c..f8d9f0f7 100644 --- a/ttkwidgets/__init__.py +++ b/ttkwidgets/__init__.py @@ -11,6 +11,7 @@ from ttkwidgets.timeline import TimeLine from ttkwidgets.tickscale import TickScale from ttkwidgets.table import Table +from ttkwidgets.numberentry import NumberEntry from ttkwidgets.validated_entries.numbers import ( PercentEntry, IntEntry, FloatEntry, From d2fa5e669d9faa414024b17ca3fdc8fe3160fa16 Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 24 Mar 2021 21:50:51 +0100 Subject: [PATCH 17/32] Update ttkwidgets.rst --- docs/source/ttkwidgets/ttkwidgets.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/source/ttkwidgets/ttkwidgets.rst b/docs/source/ttkwidgets/ttkwidgets.rst index 65bef4a9..21543502 100644 --- a/docs/source/ttkwidgets/ttkwidgets.rst +++ b/docs/source/ttkwidgets/ttkwidgets.rst @@ -17,7 +17,6 @@ ttkwidgets DebugWindow ItemsCanvas LinkLabel - NumberEntry ScaleEntry ScrolledListbox Table From 28d339cdad69cbab31ed908429ecea42f047ce7c Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 24 Mar 2021 21:51:18 +0100 Subject: [PATCH 18/32] Update __init__.py --- ttkwidgets/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ttkwidgets/__init__.py b/ttkwidgets/__init__.py index f8d9f0f7..2360b89c 100644 --- a/ttkwidgets/__init__.py +++ b/ttkwidgets/__init__.py @@ -11,7 +11,6 @@ from ttkwidgets.timeline import TimeLine from ttkwidgets.tickscale import TickScale from ttkwidgets.table import Table -from ttkwidgets.numberentry import NumberEntry from ttkwidgets.validated_entries.numbers import ( PercentEntry, IntEntry, FloatEntry, From d7891cb0c8d97deec0e9386db5e6e188cbec0ad6 Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 24 Mar 2021 21:51:47 +0100 Subject: [PATCH 19/32] Update AUTHORS.md --- AUTHORS.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/AUTHORS.md b/AUTHORS.md index 5aa5cfb0..1b80c6ff 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -27,7 +27,5 @@ This file contains a list of all the authors of widgets in this repository. Plea * `AutocompleteEntryListbox` - [Dogeek](https://github.com/Dogeek) * `validated_entries` submodule -- [rdbende](https://github.com/rdbende) - * `NumberEntry` - Multiple authors: * `ScaleEntry` (RedFantom and Juliette Monsel) From e98f4163cd6d68ec6161662fc92429f2d95c7d51 Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 24 Mar 2021 22:02:44 +0100 Subject: [PATCH 20/32] Delete test_numberentry.py --- tests/test_numberentry.py | 41 --------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 tests/test_numberentry.py diff --git a/tests/test_numberentry.py b/tests/test_numberentry.py deleted file mode 100644 index 6e421775..00000000 --- a/tests/test_numberentry.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) rdbende 2021 -# For license see LICENSE - -from ttkwidgets import NumberEntry -from tests import BaseWidgetTest -import tkinter as tk - - -class TestNumberEntry(BaseWidgetTest): - def test_numberentry_init(self): - entry = NumberEntry(self.window, expressions=True, roundto=4) - entry.pack() - self.window.update() - - def test_numberentry_events(self): - entry = NumberEntry(self.window, expressions=True, roundto=4) - entry.pack() - self.window.update() - entry.insert(0, "1+2-3*4/5**6") - self.window.update() - entry._check() - self.window.update() - entry._replace("1+2-3*4/5**6") - self.window.update() - entry._eval() - self.window.update() - - def test_numberentry_config(self): - entry = NumberEntry(self.window, expressions=True, roundto=4) - entry.pack() - self.window.update() - entry.keys() - self.window.update() - entry.configure(expressions=False, roundto=0) - self.window.update() - entry.cget("expressions") - self.window.update() - value = entry["roundto"] - self.window.update() - entry["roundto"] = 4 - self.window.update() From a386adfa52894938c31faf684c78d1da97735d68 Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 24 Mar 2021 22:03:27 +0100 Subject: [PATCH 21/32] Delete example_numberentry.py --- examples/example_numberentry.py | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 examples/example_numberentry.py diff --git a/examples/example_numberentry.py b/examples/example_numberentry.py deleted file mode 100644 index d46d9e8b..00000000 --- a/examples/example_numberentry.py +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) rdbende 2021 -# For license see LICENSE - -from ttkwidgets import NumberEntry -import tkinter as tk - -root = tk.Tk() -root.title('NumberEntry') - -NumberEntry(root, expressions=True, roundto=4).pack(pady=30) - -root.mainloop() From 7806feef9edf24bf6805ac62d5570b785c07796e Mon Sep 17 00:00:00 2001 From: rdbende Date: Wed, 24 Mar 2021 22:04:51 +0100 Subject: [PATCH 22/32] Delete numberentry.py --- ttkwidgets/numberentry.py | 99 --------------------------------------- 1 file changed, 99 deletions(-) delete mode 100644 ttkwidgets/numberentry.py diff --git a/ttkwidgets/numberentry.py b/ttkwidgets/numberentry.py deleted file mode 100644 index 0d329420..00000000 --- a/ttkwidgets/numberentry.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -Author: rdbende -License: GNU GPLv3 -Copyright (c) 2021 rdbende -""" - -import tkinter as tk -from tkinter import ttk - - -class NumberEntry(ttk.Entry): - """ - An entry that takes only numbers or calculations and calculates the result of the calculation - """ - def __init__(self, master=None, **kwargs): - """ - Create a NumberEntry - - :param expressions: Allow the use of expressions (default is True) - :type expressions: bool - :param roundto: The number of decimals in the result (default is 0) - :type roundto: int - """ - self._expr = kwargs.pop("expressions", True) - self._round = kwargs.pop("roundto", 0) - ttk.Entry.__init__(self, master, **kwargs) - self.bind("", self._eval) - self.bind("", self._eval) - self.bind("", self._check) - - def __getitem__(self, key): - return self.cget(key) - - def __setitem__(self, key, value): - self.configure(**{key: value}) - - def _eval(self, *args): - """Calculate the result of the entered calculation""" - current = self.get() - try: - if len(current) > 0: - if int(self._round) == 0: - result = int(round(eval(current), 0)) - self.delete(0, "end") - self.insert(0, result) - else: - result = round(float(eval(current)), self._round) - self.delete(0, "end") - self.insert(0, result) - except SyntaxError: - self.delete(0, "end") - self.insert(0, "SyntaxError") - self.select_range(0, "end") - except ZeroDivisionError: - self.delete(0, "end") - self.insert(0, "ZeroDivisionError") - self.select_range(0, "end") - - def _check(self, *args): - typed = self.get() - if not typed == "SyntaxError" and not typed == "ZeroDivisionError": - checked = self._replace(typed) - self.delete(0, "end") - self.insert(0, checked) - - def _replace(self, typed) -> str: - """Delete the not allowed characters""" - if self._expr: - allowed = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "-", "*", "/", "%", "."] - else: - allowed = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "."] - for current in typed: - if not current in allowed: - typed = typed.replace(current, "") - return typed - - def configure(self, **kwargs): - """Configure resources of the widget.""" - self._expr = kwargs.pop("expressions", self._expr) - self._round = kwargs.pop("roundto", self._round) - ttk.Entry.configure(self, **kwargs) - - config = configure - - def cget(self, key): - """Return the resource value for a KEY given as string""" - if key == "expressions": - return self._expr - elif key == "roundto": - return self._round - else: - return ttk.Entry.cget(self, key) - - def keys(self): - """Return a list of all resource names of this widget""" - keys = ttk.Entry.keys(self) - keys.extend(["expressions", "roundto"]) - keys = sorted(keys) - return keys From c6e7ab83368a9ba3dbfd2fd55f5934a740b1c51e Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 25 Mar 2021 22:29:10 +0100 Subject: [PATCH 23/32] native cursors, virtual event, colors --- ttkwidgets/linklabel.py | 63 +++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/ttkwidgets/linklabel.py b/ttkwidgets/linklabel.py index f1f4f730..8b0a7176 100644 --- a/ttkwidgets/linklabel.py +++ b/ttkwidgets/linklabel.py @@ -2,8 +2,11 @@ Author: RedFantom License: GNU GPLv3 Source: This repository + +Edited by rdbende: default, native cursors, virtual event, colors """ # Based on an idea by Nelson Brochado (https://www.github.com/nbrol/tkinter-kit) +# Unfortunately this link is invalid. I can't find any nbrol, or Nelson Brochado on GitHub :( import tkinter as tk from tkinter import ttk import webbrowser @@ -13,7 +16,7 @@ class LinkLabel(ttk.Label): """ 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 blue color when hovering over the Label. - """ + """ def __init__(self, master=None, **kwargs): """ Create a LinkLabel. @@ -29,13 +32,18 @@ def __init__(self, master=None, **kwargs): :type clicked_color: str :param kwargs: options to be passed on to the :class:`ttk.Label` initializer """ - self._cursor = kwargs.pop("cursor", "hand1") self._link = kwargs.pop("link", "") - self._normal_color = kwargs.pop("normal_color", "#0563c1") - self._hover_color = kwargs.pop("hover_color", "#057bc1") - self._clicked_color = kwargs.pop("clicked_color", "#954f72") + self._normal_color = kwargs.pop("normal_color", "#005fff") + self._hover_color = kwargs.pop("hover_color", "#000fff") + self._clicked_color = kwargs.pop("clicked_color", "#6600a6") + self._master = master or tk._default_root + if self._master.tk.call("tk", "windowingsystem") == "aqua": + self._cursor = kwargs.pop("cursor", "pointinghand") + else: + self._cursor = kwargs.pop("cursor", "hand2") ttk.Label.__init__(self, master, **kwargs) - self.config(foreground=self._normal_color) + if "disabled" not in self.state(): + self.configure(foreground=self._normal_color, cursor=self._cursor) self.__clicked = False self.bind("", self.open_link) self.bind("", self._on_enter) @@ -49,7 +57,10 @@ def __setitem__(self, key, value): def _on_enter(self, *args): """Set the text color to the hover color.""" - self.config(foreground=self._hover_color, cursor=self._cursor) + if self.__clicked: + self.config(foreground=self._clicked_color) + else: + self.config(foreground=self._hover_color) def _on_leave(self, *args): """Set the text color to either the normal color when not clicked or the clicked color when clicked.""" @@ -57,7 +68,6 @@ def _on_leave(self, *args): self.config(foreground=self._clicked_color) else: self.config(foreground=self._normal_color) - self.config(cursor="") def reset(self): """Reset Label to unclicked status if previously clicked.""" @@ -70,7 +80,24 @@ def open_link(self, *args): webbrowser.open(self._link) self.__clicked = True self._on_leave() + self.event_generate("<>") + + def configure(self, **kwargs): + """ + Configure resources of the widget. + + To get the list of options for this widget, call the method :meth:`~LinkLabel.keys`. + See :meth:`~LinkLabel.__init__` for a description of the widget specific option. + """ + self._link = kwargs.pop("link", self._link) + self._hover_color = kwargs.pop("hover_color", self._hover_color) + self._normal_color = kwargs.pop("normal_color", self._normal_color) + self._clicked_color = kwargs.pop("clicked_color", self._clicked_color) + self._cursor = kwargs.pop("cursor", self._cursor) + ttk.Label.configure(self, **kwargs) + config = configure + def cget(self, key): """ Query widget option. @@ -90,25 +117,11 @@ def cget(self, key): elif key is "clicked_color": return self._clicked_color else: - return ttk.Label.cget(self, key) - - def configure(self, **kwargs): - """ - Configure resources of the widget. - - To get the list of options for this widget, call the method :meth:`~LinkLabel.keys`. - See :meth:`~LinkLabel.__init__` for a description of the widget specific option. - """ - self._link = kwargs.pop("link", self._link) - self._hover_color = kwargs.pop("hover_color", self._hover_color) - self._normal_color = kwargs.pop("normal_color", self._normal_color) - self._clicked_color = kwargs.pop("clicked_color", self._clicked_color) - ttk.Label.configure(self, **kwargs) - self._on_leave() - + return ttk.Label.cget(self, key) + def keys(self): """Return a list of all resource names of this widget.""" keys = ttk.Label.keys(self) keys.extend(["link", "normal_color", "hover_color", "clicked_color"]) + keys.sort() return keys - From 32ed8a493183c2c5532b4b0f20f2445052898d3a Mon Sep 17 00:00:00 2001 From: rdbende Date: Thu, 25 Mar 2021 22:30:26 +0100 Subject: [PATCH 24/32] Change exchange cursor to fleur --- ttkwidgets/itemscanvas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttkwidgets/itemscanvas.py b/ttkwidgets/itemscanvas.py index ea7bb128..06f210e9 100644 --- a/ttkwidgets/itemscanvas.py +++ b/ttkwidgets/itemscanvas.py @@ -113,7 +113,7 @@ def left_motion(self, event): return item = results[0] rectangle = self.items[item] - self.config(cursor="exchange") + self.config(cursor="fleur") self.canvas.itemconfigure(item, fill="blue") xc, yc = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y) dx, dy = xc - self.current_coords[0], yc - self.current_coords[1] From e80986d72287793157825bed408559ee7514d590 Mon Sep 17 00:00:00 2001 From: rdbende Date: Fri, 26 Mar 2021 16:41:28 +0100 Subject: [PATCH 25/32] ToggledFrame improved Since Tk 8.6 supports png, I don't think it's actually necessary to use PIL. I hope no one uses Tk 8.5 these days. I know I forgot something very important but I can't remember what it is. --- ttkwidgets/frames/toggledframe.py | 100 +++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 22 deletions(-) diff --git a/ttkwidgets/frames/toggledframe.py b/ttkwidgets/frames/toggledframe.py index cd475b69..a4d131dd 100644 --- a/ttkwidgets/frames/toggledframe.py +++ b/ttkwidgets/frames/toggledframe.py @@ -2,11 +2,13 @@ Author: RedFantom License: GNU GPLv3 Source: This repository + +Improved by rdbende """ + import tkinter as tk from tkinter import ttk import os -from PIL import Image, ImageTk from ttkwidgets.utilities import get_assets_directory @@ -16,8 +18,8 @@ class ToggledFrame(ttk.Frame): :ivar interior: :class:`ttk.Frame` in which to put the widgets to be toggled with any geometry manager. """ - - def __init__(self, master=None, text="", width=20, compound=tk.LEFT, **kwargs): + + def __init__(self, master=None, **kwargs): """ Create a ToggledFrame. @@ -28,33 +30,87 @@ def __init__(self, master=None, text="", width=20, compound=tk.LEFT, **kwargs): :param width: width of the closed ToggledFrame (in characters) :type width: int :param compound: "center", "none", "top", "bottom", "right" or "left": - position of the toggle arrow compared to the text + position of the toggle arrow compared to the text :type compound: str + :param opened: whether the frame should be opened by default + :type opened: bool + :param cursor: cursor that appears on the toggler-checkbutton + :type cursor: str :param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer """ + self._compound = kwargs.pop("compound", tk.LEFT) + self._cursor = kwargs.pop("cursor", "arrow") + self._open = kwargs.pop("opened", False) + self._text = kwargs.pop("text", None) + self._width = kwargs.pop("width", 20) + self._toggled = tk.BooleanVar(value=self._open) ttk.Frame.__init__(self, master, **kwargs) - self._open = False - self.__checkbutton_var = tk.BooleanVar() - self._open_image = ImageTk.PhotoImage(Image.open(os.path.join(get_assets_directory(), "open.png"))) - self._closed_image = ImageTk.PhotoImage(Image.open(os.path.join(get_assets_directory(), "closed.png"))) - self._checkbutton = ttk.Checkbutton(self, style="Toolbutton", command=self.toggle, - variable=self.__checkbutton_var, text=text, compound=compound, - image=self._closed_image, width=width) - self.interior = ttk.Frame(self, relief=tk.SUNKEN) - self._grid_widgets() - - def _grid_widgets(self): - self._checkbutton.grid(row=0, column=0, sticky="we") - - def toggle(self): + + self._open_image = tk.PhotoImage(file=os.path.join(get_assets_directory(), "open.png")) + self._closed_image = tk.PhotoImage(file=os.path.join(get_assets_directory(), "closed.png")) + self._button = ttk.Checkbutton(self, style="Toolbutton", image=self._closed_image, + cursor=self._cursor, variable=self._toggled, + text=self._text, command=self.toggle, + compound=self._compound, width=self._width) + self._button.grid(row=0, column=0, sticky="ew") + self.interior = ttk.Frame(self) + if self._open: + self.toggle() + + def __getitem__(self, key): + return self.cget(key) + + def __setitem__(self, key, value): + self.configure(**{key: value}) + + def toggle(self, *args): """Toggle :obj:`ToggledFrame.interior` opened or closed.""" if self._open: self._open = False - self.__checkbutton_var.set(False) + self._toggled.set(False) self.interior.grid_forget() - self._checkbutton.config(image=self._closed_image) + self._button.config(image=self._closed_image) + self.event_generate("<>") else: self._open = True - self.__checkbutton_var.set(True) + self._toggled.set(True) self.interior.grid(row=1, column=0, sticky="nswe") - self._checkbutton.config(image=self._open_image) + self._button.config(image=self._open_image) + self.event_generate("<>") + self.event_generate("<>") + + def configure(self, **kwargs): + """Configure resources of the widget""" + self._compound = kwargs.pop("compound", self._compound) + self._cursor = kwargs.pop("cursor", self._cursor) + self._open = kwargs.pop("opened", self._open) + self._text = kwargs.pop("text", self._text) + self._width = kwargs.pop("width", self._width) + self._button.configure(text=self._text, cursor=self._cursor, compound=self._compound, width=self._width) + ttk.Frame.configure(self, **kwargs) + self._open = not self._open + self.toggle() + + config = configure + + def cget(self, key): + """Return the resource value for a KEY given as string""" + if key == "compound": + return self._compound + elif key == "cursor": + return self._cursor + elif key == "opened": + return self._opened + elif key == "text": + return self._text + elif key == "width": + return self._width + else: + return ttk.Frame.cget(key) + + def keys(self): + """Return a list of all resource names of this widget""" + keys = ttk.Frame.keys() + keys.extend(["compound", "cursor", "opened", "text", "width"]) + keys.sort() + return keys From 6e8cf44c9d873d8d1f4ee4aa8390326306bf9987 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 13 Dec 2022 20:07:26 +0100 Subject: [PATCH 26/32] Update LinkLabel --- ttkwidgets/linklabel.py | 64 ++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/ttkwidgets/linklabel.py b/ttkwidgets/linklabel.py index e4c1eccd..b02406c3 100644 --- a/ttkwidgets/linklabel.py +++ b/ttkwidgets/linklabel.py @@ -6,29 +6,30 @@ Edited by rdbende: default, native cursors, virtual event, colors """ # Based on an idea by Nelson Brochado (https://www.github.com/nbrol/tkinter-kit) -# Unfortunately this link is invalid. I can't find any nbrol, or Nelson Brochado on GitHub :( +# Available from fork: https://www.github.com/RedFantom/tkinter-kit import tkinter as tk -from tkinter import ttk import webbrowser +from tkinter import ttk class LinkLabel(ttk.Label): """ - 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 - blue color when hovering over the Label. - """ + A :class:`ttk.Label` that can be clicked to open a link with a default blue color, + a purple when clicked and dark blue when hovering over the Label. + """ + def __init__(self, master=None, **kwargs): """ Create a LinkLabel. - + :param master: master widget :param link: link to be opened :type link: str - :param normal_color: text color when widget is created + :param normal_color: text color when the widget is in neutral state :type normal_color: str :param hover_color: text color when hovering over the widget :type hover_color: str - :param clicked_color: text color when link is clicked + :param clicked_color: text color when the widget has been clicked :type clicked_color: str :param kwargs: options to be passed on to the :class:`ttk.Label` initializer """ @@ -36,15 +37,17 @@ def __init__(self, master=None, **kwargs): self._normal_color = kwargs.pop("normal_color", "#005fff") self._hover_color = kwargs.pop("hover_color", "#000fff") self._clicked_color = kwargs.pop("clicked_color", "#6600a6") - self._master = master or tk._default_root - if self._master.tk.call("tk", "windowingsystem") == "aqua": - self._cursor = kwargs.pop("cursor", "pointinghand") - else: - self._cursor = kwargs.pop("cursor", "hand2") - ttk.Label.__init__(self, master, **kwargs) + + parent = master or tk._default_root + is_mac = parent.tk.call("tk", "windowingsystem") == "aqua" + kwargs.setdefault("cursor", "pointinghand" if is_mac else "hand2") + + ttk.Label.__init__(self, parent, **kwargs) + if "disabled" not in self.state(): - self.configure(foreground=self._normal_color, cursor=self._cursor) - self.__clicked = False + self.configure(foreground=self._normal_color) + + self._clicked = False self.bind("", self.open_link) self.bind("", self._on_enter) self.bind("", self._on_leave) @@ -57,31 +60,33 @@ def __setitem__(self, key, value): def _on_enter(self, *args): """Set the text color to the hover color.""" - if self.__clicked: + if self._clicked: self.config(foreground=self._clicked_color) else: self.config(foreground=self._hover_color) def _on_leave(self, *args): """Set the text color to either the normal color when not clicked or the clicked color when clicked.""" - if self.__clicked: + if self._clicked: self.config(foreground=self._clicked_color) else: self.config(foreground=self._normal_color) def reset(self): """Reset Label to unclicked status if previously clicked.""" - self.__clicked = False + self._clicked = False self._on_leave() def open_link(self, *args): """Open the link in the web browser.""" - if "disabled" not in self.state(): - webbrowser.open(self._link) - self.__clicked = True - self._on_leave() - self.event_generate("<>") - + if "disabled" in self.state(): + return + + webbrowser.open(self._link) + self._clicked = True + self._on_leave() + self.event_generate("<>") + def configure(self, **kwargs): """ Configure resources of the widget. @@ -90,14 +95,13 @@ def configure(self, **kwargs): See :meth:`~LinkLabel.__init__` for a description of the widget specific option. """ self._link = kwargs.pop("link", self._link) - self._hover_color = kwargs.pop("hover_color", self._hover_color) self._normal_color = kwargs.pop("normal_color", self._normal_color) + self._hover_color = kwargs.pop("hover_color", self._hover_color) self._clicked_color = kwargs.pop("clicked_color", self._clicked_color) - self._cursor = kwargs.pop("cursor", self._cursor) ttk.Label.configure(self, **kwargs) config = configure - + def cget(self, key): """ Query widget option. @@ -117,8 +121,8 @@ def cget(self, key): elif key == "clicked_color": return self._clicked_color else: - return ttk.Label.cget(self, key) - + return ttk.Label.cget(self, key) + def keys(self): """Return a list of all resource names of this widget.""" keys = ttk.Label.keys(self) From 364021d4a57e506db6199c7c44c868a0ef535d39 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 13 Dec 2022 20:09:47 +0100 Subject: [PATCH 27/32] Update LinkLabel tests --- tests/test_linklabel.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/test_linklabel.py b/tests/test_linklabel.py index 35c79e41..86f00f91 100644 --- a/tests/test_linklabel.py +++ b/tests/test_linklabel.py @@ -1,30 +1,31 @@ # Copyright (c) RedFantom 2017 # For license see LICENSE -from ttkwidgets import LinkLabel -from tests import BaseWidgetTest import tkinter as tk +from tests import BaseWidgetTest +from ttkwidgets import LinkLabel + class TestLinkLabel(BaseWidgetTest): def test_linklabel_init(self): label = LinkLabel(self.window, link="www.google.com", text="Visit Google") label.pack() - self.window.update() def test_linklabel_events(self): label = LinkLabel(self.window, link="www.google.com", text="Visit Google") label.pack() + self.window.update() label._on_enter() self.window.update() label._on_leave() self.window.update() label.open_link() - self.window.update() def test_linklabel_config(self): label = LinkLabel(self.window, link="www.google.com", text="Visit Google") label.pack() + self.window.update() label.keys() self.window.update() @@ -36,12 +37,16 @@ def test_linklabel_config(self): self.window.update() label["clicked_color"] = "purple" self.window.update() + label.config(cursor="hand1") + self.window.update() + assert label.cget("cursor") == "hand1" def test_linklabel_cget(self): label = LinkLabel(self.window, link="www.google.com", text="Visit Google") label.pack() - assert label.cget("hover_color") == label._hover_color + assert label.cget("link") == label._link assert label.cget("normal_color") == label._normal_color + assert label.cget("hover_color") == label._hover_color assert label.cget("clicked_color") == label._clicked_color assert label.cget("text") == "Visit Google" From 040b13e6c5c6e4ea0578dcf71965ecbb5eec89b7 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 13 Dec 2022 20:12:50 +0100 Subject: [PATCH 28/32] State changes as required by this GPL license --- ttkwidgets/linklabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttkwidgets/linklabel.py b/ttkwidgets/linklabel.py index b02406c3..e176ac29 100644 --- a/ttkwidgets/linklabel.py +++ b/ttkwidgets/linklabel.py @@ -3,7 +3,7 @@ License: GNU GPLv3 Source: This repository -Edited by rdbende: default, native cursors, virtual event, colors +Edited by rdbende: change default widget colors, use native cursors by default, add virtual event """ # Based on an idea by Nelson Brochado (https://www.github.com/nbrol/tkinter-kit) # Available from fork: https://www.github.com/RedFantom/tkinter-kit From 347787991a6ccdffb2a36fc7063c3c76a1868607 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 13 Dec 2022 21:03:48 +0100 Subject: [PATCH 29/32] Remove unnecessary variable --- ttkwidgets/linklabel.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ttkwidgets/linklabel.py b/ttkwidgets/linklabel.py index e176ac29..1ff8a921 100644 --- a/ttkwidgets/linklabel.py +++ b/ttkwidgets/linklabel.py @@ -38,11 +38,10 @@ def __init__(self, master=None, **kwargs): self._hover_color = kwargs.pop("hover_color", "#000fff") self._clicked_color = kwargs.pop("clicked_color", "#6600a6") - parent = master or tk._default_root - is_mac = parent.tk.call("tk", "windowingsystem") == "aqua" + is_mac = (master or tk._default_root).tk.call("tk", "windowingsystem") == "aqua" kwargs.setdefault("cursor", "pointinghand" if is_mac else "hand2") - ttk.Label.__init__(self, parent, **kwargs) + ttk.Label.__init__(self, master, **kwargs) if "disabled" not in self.state(): self.configure(foreground=self._normal_color) From d3a67db24f5e9ad576692e628ad541cc6b3e49cd Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 13 Dec 2022 21:23:59 +0100 Subject: [PATCH 30/32] Fix LinkLabel test --- tests/test_linklabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_linklabel.py b/tests/test_linklabel.py index 86f00f91..11295321 100644 --- a/tests/test_linklabel.py +++ b/tests/test_linklabel.py @@ -39,7 +39,7 @@ def test_linklabel_config(self): self.window.update() label.config(cursor="hand1") self.window.update() - assert label.cget("cursor") == "hand1" + assert str(label.cget("cursor")) == "hand1" def test_linklabel_cget(self): label = LinkLabel(self.window, link="www.google.com", text="Visit Google") From 12560cfa5efb5e21e5a024fc2d15b07823749b92 Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 13 Dec 2022 21:24:11 +0100 Subject: [PATCH 31/32] Update ToggledFrame stuff --- tests/test_toggledframe.py | 24 ++++-- ttkwidgets/frames/toggledframe.py | 135 ++++++++++++++++-------------- 2 files changed, 88 insertions(+), 71 deletions(-) diff --git a/tests/test_toggledframe.py b/tests/test_toggledframe.py index da54915c..9cc5e67f 100644 --- a/tests/test_toggledframe.py +++ b/tests/test_toggledframe.py @@ -1,26 +1,36 @@ # Copyright (c) RedFantom 2017 # For license see LICENSE -from ttkwidgets.frames import ToggledFrame -from tests import BaseWidgetTest import tkinter as tk +from tests import BaseWidgetTest +from ttkwidgets.frames import ToggledFrame + class TestToggledFrame(BaseWidgetTest): def test_toggledframe_init(self): frame = ToggledFrame(self.window) frame.pack() - self.window.update() def test_toggledframe_open(self): frame = ToggledFrame(self.window) frame.pack() + self.window.update() frame.toggle() - self.assertTrue(frame._open) + assert frame.opened def test_toggledframe_open_close(self): frame = ToggledFrame(self.window) frame.pack() + self.window.update() frame.toggle() - self.assertTrue(frame._open) - frame.toggle() - self.assertFalse(frame._open) + self.window.update() + assert frame.opened + frame.close() + self.window.update() + assert not frame.opened + frame.open() + self.window.update() + assert frame.opened + frame._button.invoke() + self.window.update() + assert not frame.opened diff --git a/ttkwidgets/frames/toggledframe.py b/ttkwidgets/frames/toggledframe.py index a4d131dd..524442b9 100644 --- a/ttkwidgets/frames/toggledframe.py +++ b/ttkwidgets/frames/toggledframe.py @@ -7,10 +7,13 @@ """ import tkinter as tk +from pathlib import Path from tkinter import ttk -import os + from ttkwidgets.utilities import get_assets_directory +assets_dir = Path(get_assets_directory()) + class ToggledFrame(ttk.Frame): """ @@ -18,99 +21,103 @@ class ToggledFrame(ttk.Frame): :ivar interior: :class:`ttk.Frame` in which to put the widgets to be toggled with any geometry manager. """ - + def __init__(self, master=None, **kwargs): """ Create a ToggledFrame. :param master: master widget :type master: widget - :param text: text to display next to the toggle arrow + :param text: text to in the header of the ToggledFrame :type text: str :param width: width of the closed ToggledFrame (in characters) :type width: int - :param compound: "center", "none", "top", "bottom", "right" or "left": - position of the toggle arrow compared to the text - :type compound: str - :param opened: whether the frame should be opened by default - :type opened: bool - :param cursor: cursor that appears on the toggler-checkbutton + :param cursor: cursor that appears on the ToggledFrame's button :type cursor: str :param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer """ - self._compound = kwargs.pop("compound", tk.LEFT) - self._cursor = kwargs.pop("cursor", "arrow") - self._open = kwargs.pop("opened", False) - self._text = kwargs.pop("text", None) - self._width = kwargs.pop("width", 20) - self._toggled = tk.BooleanVar(value=self._open) + cursor = kwargs.pop("cursor", "arrow") + text = kwargs.pop("text", None) + width = kwargs.pop("width", 20) + + self._open = tk.BooleanVar(value=False) + ttk.Frame.__init__(self, master, **kwargs) - - self._open_image = tk.PhotoImage(file=os.path.join(get_assets_directory(), "open.png")) - self._closed_image = tk.PhotoImage(file=os.path.join(get_assets_directory(), "closed.png")) - self._button = ttk.Checkbutton(self, style="Toolbutton", image=self._closed_image, - cursor=self._cursor, variable=self._toggled, - text=self._text, command=self.toggle, - compound=self._compound, width=self._width) - self._button.grid(row=0, column=0, sticky="ew") + self.interior = ttk.Frame(self) - if self._open: - self.toggle() - + + self._open_image = tk.PhotoImage(file=assets_dir / "open.png") + self._closed_image = tk.PhotoImage(file=assets_dir / "closed.png") + + self._button = ttk.Checkbutton( + self, + style="Toolbutton", + compound="right", + cursor=cursor, + image=self._closed_image, + text=text, + variable=self._open, + command=self._toggle_when_clicked, + width=width, + ) + self._button.grid(row=0, column=0, sticky="ew") + def __getitem__(self, key): return self.cget(key) def __setitem__(self, key, value): self.configure(**{key: value}) - def toggle(self, *args): - """Toggle :obj:`ToggledFrame.interior` opened or closed.""" - if self._open: - self._open = False - self._toggled.set(False) + def _toggle_when_clicked(self): + # when clicking the checkbutton it inverts its variable, so we can't simply use self.toggle + if self._open.get(): + self.interior.grid(row=1, column=0, sticky="nswe") + self._button.config(image=self._open_image) + self.event_generate("<>") + else: self.interior.grid_forget() self._button.config(image=self._closed_image) self.event_generate("<>") + + def open(self): + self.interior.grid(row=1, column=0, sticky="nswe") + self._open.set(True) + self._button.config(image=self._open_image) + self.event_generate("<>") + + def close(self): + self.interior.grid_forget() + self._open.set(False) + self._button.config(image=self._closed_image) + self.event_generate("<>") + + def toggle(self): + if self._open.get(): + self.close() else: - self._open = True - self._toggled.set(True) - self.interior.grid(row=1, column=0, sticky="nswe") - self._button.config(image=self._open_image) - self.event_generate("<>") - self.event_generate("<>") - + self.open() + + @property + def opened(self): + return self._open.get() + def configure(self, **kwargs): """Configure resources of the widget""" - self._compound = kwargs.pop("compound", self._compound) - self._cursor = kwargs.pop("cursor", self._cursor) - self._open = kwargs.pop("opened", self._open) - self._text = kwargs.pop("text", self._text) - self._width = kwargs.pop("width", self._width) - self._button.configure(text=self._text, cursor=self._cursor, compound=self._compound, width=self._width) + button_options = { + key: kwargs.pop(key) for key in ("cursor", "text", "width") if key in kwargs + } + self._button.configure(**button_options) ttk.Frame.configure(self, **kwargs) - self._open = not self._open - self.toggle() - + config = configure - + def cget(self, key): """Return the resource value for a KEY given as string""" - if key == "compound": - return self._compound - elif key == "cursor": - return self._cursor - elif key == "opened": - return self._opened - elif key == "text": - return self._text - elif key == "width": - return self._width + if key in {"cursor", "text", "width"}: + return self._button.cget(key) else: - return ttk.Frame.cget(key) - + return ttk.Frame.cget(self, key) + def keys(self): """Return a list of all resource names of this widget""" - keys = ttk.Frame.keys() - keys.extend(["compound", "cursor", "opened", "text", "width"]) - keys.sort() - return keys + return sorted(ttk.Frame.keys(self) + ["text"]) From 3f8ac80f6d0a19e5881c6488bc57a62761cd8e9d Mon Sep 17 00:00:00 2001 From: rdbende Date: Tue, 13 Dec 2022 21:26:51 +0100 Subject: [PATCH 32/32] Use keyword arguments with default instead of kwargs.pop --- ttkwidgets/frames/toggledframe.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ttkwidgets/frames/toggledframe.py b/ttkwidgets/frames/toggledframe.py index 524442b9..ce56cc05 100644 --- a/ttkwidgets/frames/toggledframe.py +++ b/ttkwidgets/frames/toggledframe.py @@ -22,7 +22,7 @@ class ToggledFrame(ttk.Frame): :ivar interior: :class:`ttk.Frame` in which to put the widgets to be toggled with any geometry manager. """ - def __init__(self, master=None, **kwargs): + def __init__(self, master=None, *, text=None, cursor="arrow", width=20, **kwargs): """ Create a ToggledFrame. @@ -36,10 +36,6 @@ def __init__(self, master=None, **kwargs): :type cursor: str :param kwargs: keyword arguments passed on to the :class:`ttk.Frame` initializer """ - cursor = kwargs.pop("cursor", "arrow") - text = kwargs.pop("text", None) - width = kwargs.pop("width", 20) - self._open = tk.BooleanVar(value=False) ttk.Frame.__init__(self, master, **kwargs)