-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merge rdbende:numberentry into master to add NumberEntry widget #79
base: master
Are you sure you want to change the base?
Changes from all commits
551a19c
a6db0b3
7611f2a
61a46c5
8941ab3
8488995
8b614d2
9584d3f
af42360
a4af566
439323e
19f361e
acdbec5
fb2dd12
e5d60f3
de2d5c2
c2f68ca
c1887dd
213bd08
3bad128
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ ttkwidgets | |
DebugWindow | ||
ItemsCanvas | ||
LinkLabel | ||
NumberEntry | ||
ScaleEntry | ||
ScrolledListbox | ||
Table | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, allowed_chars={'p': 3.14159, 'x': 5}).pack(pady=30) | ||
|
||
root.mainloop() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# 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, roundto=4, allowed_chars={'p': 3.14}) | ||
entry.pack() | ||
self.window.update() | ||
|
||
def test_numberentry_events(self): | ||
entry = NumberEntry(self.window, roundto=4, allowed_chars={'p': 3.14}) | ||
entry.pack() | ||
self.window.update() | ||
entry.insert(0, "1+2-3*4/5**p") | ||
self.window.update() | ||
entry._check() | ||
self.window.update() | ||
entry._eval() | ||
self.window.update() | ||
|
||
def test_numberentry_config(self): | ||
entry = NumberEntry(self.window, roundto=4, allowed_chars={'p': 3.14}) | ||
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["allowed_chars"] = {'p': 3.14159} | ||
self.window.update() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
""" | ||
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, calculations or variables, | ||
and calculates the result of the calculation | ||
""" | ||
def __init__(self, master=None, allowed_chars={}, **kwargs): | ||
""" | ||
Create a NumberEntry | ||
|
||
:param allowed_chars: Set the accepted variables, the name must be one single character | ||
e.g.: allowed_chars={'p': 3.14} | ||
:type allowed_chars: dict | ||
: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 | ||
:param variables: Allow the use of the user specified variables | ||
specified in allowed_chars (default is True) | ||
:type variables: bool | ||
""" | ||
self._allowed = allowed_chars | ||
self._expr = kwargs.pop("expressions", True) | ||
self._round = kwargs.pop("roundto", 0) | ||
self._vars = kwargs.pop("variables", True) | ||
ttk.Entry.__init__(self, master, **kwargs) | ||
self.bind("<Return>", self._eval) | ||
self.bind("<FocusOut>", self._eval) | ||
self.bind("<KeyRelease>", 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() | ||
for i in current: | ||
if i in self._allowed.keys(): | ||
current = current.replace(i, str(self._allowed[i])) | ||
if current: | ||
try: | ||
if int(self._round) == 0: | ||
result = int(round(eval(current), 0)) | ||
self.delete(0, tk.END) | ||
self.insert(0, result) | ||
else: | ||
result = round(float(eval(current)), self._round) | ||
self.delete(0, tk.END) | ||
self.insert(0, result) | ||
except SyntaxError: | ||
self.delete(0, tk.END) | ||
self.insert(0, "SyntaxError") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a bad idea. Why would the user care? Simply just do nothing with the expression. Or maybe set the |
||
self.select_range(0, tk.END) | ||
except ZeroDivisionError: | ||
self.delete(0, tk.END) | ||
self.insert(0, "ZeroDivisionError") | ||
self.select_range(0, tk.END) | ||
|
||
def _check(self, *args): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ohh, this looks cursed. And why didn't I use an actual validator function? |
||
typed = self.get() | ||
if not typed == "SyntaxError" and not typed == "ZeroDivisionError": | ||
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", "."] | ||
if self._vars: | ||
allowed.extend(self._allowed.keys()) | ||
for current in typed: | ||
if not current in allowed: | ||
typed = typed.replace(current, "") | ||
self.delete(0, tk.END) | ||
self.insert(0, typed) | ||
|
||
def configure(self, allowed_chars={}, **kwargs): | ||
"""Configure resources of the widget""" | ||
self._allowed = allowed_chars | ||
self._expr = kwargs.pop("expressions", self._expr) | ||
self._round = kwargs.pop("roundto", self._round) | ||
self._vars = kwargs.pop("variables", self._vars) | ||
ttk.Entry.configure(self, **kwargs) | ||
|
||
config = configure | ||
|
||
def cget(self, key): | ||
"""Return the resource value for a KEY given as string""" | ||
if key == "allowed_chars": | ||
return self._allowed | ||
elif key == "expressions": | ||
return self._expr | ||
elif key == "roundto": | ||
return self._round | ||
elif key == "variables": | ||
return self._vars | ||
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(["allowed_chars", "expressions", "roundto", "variables"]) | ||
keys.sort() | ||
return keys |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a serious security threat.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should allow numbers and operator characters only.