From e9a56e7df03ac79b33ce1da6a1fe278e5a42c40c Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Sat, 30 Sep 2023 01:17:18 +0200 Subject: [PATCH 01/16] also maximal values are now switched --- .../gui_structure_classes/float_box.py | 37 +++++++++++++++++-- .../gui_structure_classes/matrix_option.py | 16 +++----- examples/example_classes/gui_structure.py | 2 +- tests/gui_structure_for_tests.py | 2 +- .../test_float_box.py | 25 ++++++++++++- 5 files changed, 65 insertions(+), 17 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index 030b2c5..de01987 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -47,10 +47,41 @@ def validate(self, float_str: str, pos: int) -> object: object """ # position is index +1 in input - nb_of_chars = len(float_str) - 1 if "," in float_str else 0 - nb_of_decimals = 0 if "," not in float_str else nb_of_chars - float_str.index(",") + decimal_sign = self.locale().decimalPoint() + sep_sign = self.locale().groupSeparator() + has_sep = sep_sign in float_str + if decimal_sign not in float_str and self.decimals() > 0: + float_str += decimal_sign + float_str += "0" * self.decimals() + # move the curser to the next number if the current one is not + # float_str = float_str.replace(sep_sign, "") + nb_of_chars = len(float_str) - 1 if decimal_sign in float_str else 0 + nb_of_decimals = 0 if decimal_sign not in float_str else nb_of_chars - float_str.index(decimal_sign) # overwrite decimals - float_str = float_str[:-1] if nb_of_decimals > self.decimals() and pos != nb_of_chars + 1 else float_str + if nb_of_decimals > self.decimals() and pos != nb_of_chars + 1: + float_str = float_str[:-1] + self.lineEdit().setCursorPosition(pos + 1) if pos < len(float_str) and decimal_sign in float_str[pos-1:pos+1] else None + # move values if the current one is above the maximum + if self.maximum() > 0: + float_str = float_str.replace(sep_sign, "") + strings = float_str.split(decimal_sign) + if strings[0] == "": + strings[0] = "0" + if float(strings[0]) > self.maximum(): + float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] + self.lineEdit().setCursorPosition(pos + 1) if pos < len(float_str) and decimal_sign in float_str[pos-1:pos+1] else None + if has_sep: + dec_idx = float_str.index(decimal_sign) if self.decimals() > 0 and float_str.index(decimal_sign) > 2 else 0 + # Initialize an empty result string + result_string = "" + # Iterate through the original string in reverse + for i, char in enumerate(reversed(float_str[: dec_idx])): + # Check if it's time to insert the symbol + if i > 0 and i % 3 == 0: + result_string = sep_sign + result_string # Insert the symbol + result_string = char + result_string # Add the character + float_str = result_string + float_str[dec_idx :] + return QtW.QDoubleSpinBox.validate(self, float_str, pos) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/matrix_option.py b/ScenarioGUI/gui_classes/gui_structure_classes/matrix_option.py index 8ab6585..9e47b6d 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/matrix_option.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/matrix_option.py @@ -8,6 +8,7 @@ import numpy as np import ScenarioGUI.global_settings as globs +from .float_box import DoubleSpinBox from .functions import check_and_set_max_min_values, check_conditional_visibility from ...utils import set_default_font @@ -23,7 +24,7 @@ from .hint import Hint -class DoubleSpinBox(QtW.QDoubleSpinBox): # pragma: no cover +class ArrowDoubleSpinBox(DoubleSpinBox): # pragma: no cover def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.up = self @@ -32,7 +33,7 @@ def __init__(self, *args, **kwargs): self.right = self self.setButtonSymbols(QtW.QDoubleSpinBox.NoButtons) - def set_boxes(self, up: DoubleSpinBox, down: DoubleSpinBox, left: DoubleSpinBox, right: DoubleSpinBox): + def set_boxes(self, up: ArrowDoubleSpinBox, down: ArrowDoubleSpinBox, left: ArrowDoubleSpinBox, right: ArrowDoubleSpinBox): self.up = up self.down = down self.left = left @@ -54,13 +55,6 @@ def keyPressEvent(self, event): else: super().keyPressEvent(event) - def wheelEvent(self, event: QtG.QWheelEvent): - if self.hasFocus(): - super().wheelEvent(event) - return - self.parent().wheelEvent(event) - - class MatrixBox(Option): """ This class contains all the functionalities of the MatrixBox option in the GUI. @@ -118,8 +112,8 @@ def __init__( self.maximal_value: list[list[float]] = ([[maximal_value] * column] * row) if isinstance(maximal_value, (float, int)) else maximal_value self.column = column self.row = row - self.widget: list[list[DoubleSpinBox]] = [ - [DoubleSpinBox(self.default_parent, valueChanged=self.valueChanged.emit) for _ in range(column)] for _ in range(row) + self.widget: list[list[ArrowDoubleSpinBox]] = [ + [ArrowDoubleSpinBox(self.default_parent, valueChanged=self.valueChanged.emit) for _ in range(column)] for _ in range(row) ] def get_value(self) -> list[list[float]]: diff --git a/examples/example_classes/gui_structure.py b/examples/example_classes/gui_structure.py index 546587a..448ef61 100644 --- a/examples/example_classes/gui_structure.py +++ b/examples/example_classes/gui_structure.py @@ -109,7 +109,7 @@ def __init__(self, default_parent: QtW.QWidget, translations: Translations): label="b", default_value=100, minimal_value=0, - maximal_value=1000, + maximal_value=100_000, decimal_number=2, category=self.sub_category, ) diff --git a/tests/gui_structure_for_tests.py b/tests/gui_structure_for_tests.py index ce3cab9..e20991e 100644 --- a/tests/gui_structure_for_tests.py +++ b/tests/gui_structure_for_tests.py @@ -64,7 +64,7 @@ def __init__(self, default_parent: QtW.QWidget, translations: Translations): label="b", default_value=100, minimal_value=0, - maximal_value=1000, + maximal_value=100_000, decimal_number=2, category=self.sub_category, ) diff --git a/tests/test_gui_structure_elements/test_float_box.py b/tests/test_gui_structure_elements/test_float_box.py index e1e456a..4231185 100644 --- a/tests/test_gui_structure_elements/test_float_box.py +++ b/tests/test_gui_structure_elements/test_float_box.py @@ -1,5 +1,5 @@ import numpy as np -import PySide6.QtGui as QtG +import PySide6.QtCore as QtC import pytest from ScenarioGUI.gui_classes.gui_structure_classes.functions import ConditionalVisibilityWarning @@ -55,9 +55,32 @@ def test_float_box(qtbot): assert not main_window.gui_structure.int_a.is_hidden() float_b.set_value(220) assert not main_window.gui_structure.int_a.is_hidden() + float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.German, QtC.QLocale.Germany)) # test validation res = float_b.widget.validate("100,020", 5) assert np.isclose(float(res[1].replace(",", ".")), 100.02) + res = float_b.widget.validate("10.000,020", 8) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000.02) + res = float_b.widget.validate("103.000,20", 3) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_300.02) + res = float_b.widget.validate(",20", 3) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 0.2) + #assert float_b.widget.lineEdit().cursorPosition() == 4 + res = float_b.widget.validate("10.0003,20", 7) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000.32) + res = float_b.widget.validate("10002,0", 5) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10002) + # test validation + float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.English, QtC.QLocale.UnitedStates)) + res = float_b.widget.validate("10,0003.20", 7) + assert np.isclose(float(res[1].replace(",", "")), 10_000.32) + res = float_b.widget.validate(".20", 3) + assert np.isclose(float(res[1]), 0.2) + res = float_b.widget.validate("100.020", 5) + assert np.isclose(float(res[1]), 100.02) + res = float_b.widget.validate("10002.0", 5) + assert np.isclose(float(res[1].replace(",", "")), 10002) + float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.German, QtC.QLocale.Germany)) # test set text main_window.gui_structure.float_b.set_text("Hello") assert main_window.gui_structure.float_b.label.text() == "Hello" From 3147aeb2d354c2d22709c7ce29ad9a2bf40553a7 Mon Sep 17 00:00:00 2001 From: Tobias Blanke <86232208+tblanke@users.noreply.github.com> Date: Sun, 1 Oct 2023 11:48:54 +0200 Subject: [PATCH 02/16] fixed cursor postition setting --- .../gui_structure_classes/float_box.py | 61 ++++++++++--------- .../gui_structure_classes/matrix_option.py | 2 +- .../test_float_box.py | 22 ++++++- 3 files changed, 53 insertions(+), 32 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index de01987..bd28abf 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -50,6 +50,9 @@ def validate(self, float_str: str, pos: int) -> object: decimal_sign = self.locale().decimalPoint() sep_sign = self.locale().groupSeparator() has_sep = sep_sign in float_str + if pos > len(float_str) or float_str == "": + return QtW.QDoubleSpinBox.validate(self, float_str, pos) + is_number = not (float_str[pos-1] in [sep_sign, decimal_sign]) if decimal_sign not in float_str and self.decimals() > 0: float_str += decimal_sign float_str += "0" * self.decimals() @@ -60,16 +63,16 @@ def validate(self, float_str: str, pos: int) -> object: # overwrite decimals if nb_of_decimals > self.decimals() and pos != nb_of_chars + 1: float_str = float_str[:-1] - self.lineEdit().setCursorPosition(pos + 1) if pos < len(float_str) and decimal_sign in float_str[pos-1:pos+1] else None # move values if the current one is above the maximum + bigger_as_max: bool = False if self.maximum() > 0: float_str = float_str.replace(sep_sign, "") strings = float_str.split(decimal_sign) if strings[0] == "": strings[0] = "0" if float(strings[0]) > self.maximum(): + bigger_as_max = True float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] - self.lineEdit().setCursorPosition(pos + 1) if pos < len(float_str) and decimal_sign in float_str[pos-1:pos+1] else None if has_sep: dec_idx = float_str.index(decimal_sign) if self.decimals() > 0 and float_str.index(decimal_sign) > 2 else 0 # Initialize an empty result string @@ -80,8 +83,8 @@ def validate(self, float_str: str, pos: int) -> object: if i > 0 and i % 3 == 0: result_string = sep_sign + result_string # Insert the symbol result_string = char + result_string # Add the character - float_str = result_string + float_str[dec_idx :] - + float_str = result_string + float_str[dec_idx:] + pos = (pos + 1) if bigger_as_max and is_number and float_str[pos-1] in [sep_sign, decimal_sign] else pos return QtW.QDoubleSpinBox.validate(self, float_str, pos) @@ -92,15 +95,15 @@ class FloatBox(Option): """ def __init__( # noqa: PLR0913 - self, - label: str | list[str], - default_value: float, - category: Category, - *, - decimal_number: int = 0, - minimal_value: float = 0.0, - maximal_value: float = 100.0, - step: float = 1.0, + self, + label: str | list[str], + default_value: float, + category: Category, + *, + decimal_number: int = 0, + minimal_value: float = 0.0, + maximal_value: float = 100.0, + step: float = 1.0, ): """ @@ -184,10 +187,10 @@ def _check_value(self) -> bool: return self.minimal_value <= self.get_value() <= self.maximal_value def add_link_2_show( - self, - option: Option | Category | FunctionButton | Hint, - below: float = None, - above: float = None, + self, + option: Option | Category | FunctionButton | Hint, + below: float = None, + above: float = None, ) -> None: """ This function couples the visibility of an option to the value of the FloatBox object. @@ -217,11 +220,11 @@ def add_link_2_show( check_conditional_visibility(option) def show_option( - self, - option: Option | Category | FunctionButton | Hint, - below: float | None, - above: float | None, - args=None, + self, + option: Option | Category | FunctionButton | Hint, + below: float | None, + above: float | None, + args=None, ): """ This function shows the option if the value of the FloatBox is between the below and above value. @@ -292,12 +295,12 @@ def create_function_2_check_linked_value(self, value: tuple[float | None, float return ft_partial(self.check_linked_value, value, value_if_hidden) def create_widget( - self, - frame: QtW.QFrame, - layout_parent: QtW.QLayout, - *, - row: int | None = None, - column: int | None = None, + self, + frame: QtW.QFrame, + layout_parent: QtW.QLayout, + *, + row: int | None = None, + column: int | None = None, ) -> None: """ This functions creates the FloatBox widget in the frame. @@ -325,7 +328,7 @@ def create_widget( f'QDoubleSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};' f'border: 1px solid {globs.WHITE};{"}"}' ) self.widget.setAlignment(QtC.Qt.AlignRight | QtC.Qt.AlignTrailing | QtC.Qt.AlignVCenter) - self.widget.setProperty("showGroupSeparator", True) + self.widget.setGroupSeparatorShown(True) self.widget.setMinimum(self.minimal_value) self.widget.setMaximum(self.maximal_value) self.widget.setDecimals(self.decimal_number) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/matrix_option.py b/ScenarioGUI/gui_classes/gui_structure_classes/matrix_option.py index 9e47b6d..b5ae7b2 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/matrix_option.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/matrix_option.py @@ -355,7 +355,7 @@ def create_widget( f'QDoubleSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};' f'border: 1px solid {globs.WHITE};{"}"}' ) widget.setAlignment(QtC.Qt.AlignRight | QtC.Qt.AlignTrailing | QtC.Qt.AlignVCenter) - widget.setProperty("showGroupSeparator", True) + widget.setGroupSeparatorShown(True) widget.setMinimum(self.minimal_value[row][column]) widget.setMaximum(self.maximal_value[row][column]) widget.setDecimals(self.decimal_number[row][column]) diff --git a/tests/test_gui_structure_elements/test_float_box.py b/tests/test_gui_structure_elements/test_float_box.py index 4231185..9ce79e8 100644 --- a/tests/test_gui_structure_elements/test_float_box.py +++ b/tests/test_gui_structure_elements/test_float_box.py @@ -57,29 +57,47 @@ def test_float_box(qtbot): assert not main_window.gui_structure.int_a.is_hidden() float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.German, QtC.QLocale.Germany)) # test validation + res = float_b.widget.validate("100,02", 7) + assert np.isclose(float(res[1].replace(",", ".")), 100.02) + assert res[2] == 7 res = float_b.widget.validate("100,020", 5) assert np.isclose(float(res[1].replace(",", ".")), 100.02) + assert res[2] == 5 res = float_b.widget.validate("10.000,020", 8) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000.02) + assert res[2] == 8 + res = float_b.widget.validate("10.000,00", 4) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000) + assert res[2] == 4 res = float_b.widget.validate("103.000,20", 3) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_300.02) - res = float_b.widget.validate(",20", 3) + assert res[2] == 4 + res = float_b.widget.validate("103.000", 7) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_300) + assert res[2] == 8 + res = float_b.widget.validate(",20", 0) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 0.2) - #assert float_b.widget.lineEdit().cursorPosition() == 4 + assert res[2] == 0 res = float_b.widget.validate("10.0003,20", 7) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000.32) + assert res[2] == 8 res = float_b.widget.validate("10002,0", 5) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10002) + assert res[2] == 5 # test validation float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.English, QtC.QLocale.UnitedStates)) res = float_b.widget.validate("10,0003.20", 7) assert np.isclose(float(res[1].replace(",", "")), 10_000.32) + assert res[2] == 8 res = float_b.widget.validate(".20", 3) assert np.isclose(float(res[1]), 0.2) + assert res[2] == 3 res = float_b.widget.validate("100.020", 5) assert np.isclose(float(res[1]), 100.02) + assert res[2] == 5 res = float_b.widget.validate("10002.0", 5) assert np.isclose(float(res[1].replace(",", "")), 10002) + assert res[2] == 5 float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.German, QtC.QLocale.Germany)) # test set text main_window.gui_structure.float_b.set_text("Hello") From 4ebb5ef591de55a7832693212e3a26cb71b1c89d Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Wed, 4 Oct 2023 08:48:30 +0200 Subject: [PATCH 03/16] Update gui_structure.py --- examples/example_classes/gui_structure.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/examples/example_classes/gui_structure.py b/examples/example_classes/gui_structure.py index 448ef61..6b435f1 100644 --- a/examples/example_classes/gui_structure.py +++ b/examples/example_classes/gui_structure.py @@ -114,6 +114,15 @@ def __init__(self, default_parent: QtW.QWidget, translations: Translations): category=self.sub_category, ) + self.negative_float = els.FloatBox( + label="negative_float", + default_value=-1000, + minimal_value=-100_000, + maximal_value=-100, + decimal_number=2, + category=self.sub_category, + ) + self.list_box = els.ListBox( label="List box", default_index=0, From f92d1becd9aca33ca0e1d2e4eb09e65885989235 Mon Sep 17 00:00:00 2001 From: Tobias Blanke <86232208+tblanke@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:54:44 +0200 Subject: [PATCH 04/16] the negative lower limit is also considered now --- .../gui_structure_classes/float_box.py | 32 +++++++++++++++---- tests/gui_structure_for_tests.py | 9 ++++++ .../test_float_box.py | 16 ++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index bd28abf..3649838 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -9,6 +9,7 @@ import PySide6.QtCore as QtC # type: ignore import PySide6.QtGui as QtG import PySide6.QtWidgets as QtW # type: ignore +import numpy as np import ScenarioGUI.global_settings as globs @@ -64,14 +65,13 @@ def validate(self, float_str: str, pos: int) -> object: if nb_of_decimals > self.decimals() and pos != nb_of_chars + 1: float_str = float_str[:-1] # move values if the current one is above the maximum - bigger_as_max: bool = False - if self.maximum() > 0: + limit_reached: bool = False + if self.maximum() > 1: float_str = float_str.replace(sep_sign, "") strings = float_str.split(decimal_sign) - if strings[0] == "": - strings[0] = "0" - if float(strings[0]) > self.maximum(): - bigger_as_max = True + strings[0] = "0" if strings[0] == "" else strings[0] + limit_reached = float(strings[0]) > self.maximum() + if limit_reached: float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] if has_sep: dec_idx = float_str.index(decimal_sign) if self.decimals() > 0 and float_str.index(decimal_sign) > 2 else 0 @@ -84,7 +84,25 @@ def validate(self, float_str: str, pos: int) -> object: result_string = sep_sign + result_string # Insert the symbol result_string = char + result_string # Add the character float_str = result_string + float_str[dec_idx:] - pos = (pos + 1) if bigger_as_max and is_number and float_str[pos-1] in [sep_sign, decimal_sign] else pos + elif self.minimum() < -1: + float_str = float_str.replace(sep_sign, "") + strings = float_str.split(decimal_sign) + strings[0] = "0" if strings[0] == "" else strings[0] + limit_reached = float(strings[0]) < self.minimum() + if limit_reached: + float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] + if has_sep: + dec_idx = float_str.index(decimal_sign) if self.decimals() > 0 and float_str.index(decimal_sign) > 2 else 0 + # Initialize an empty result string + result_string = "" + # Iterate through the original string in reverse + for i, char in enumerate(reversed(float_str[: dec_idx])): + # Check if it's time to insert the symbol + if i > 0 and i % 3 == 0: + result_string = sep_sign + result_string # Insert the symbol + result_string = char + result_string # Add the character + float_str = result_string + float_str[dec_idx:] + pos = (pos + 1) if limit_reached and is_number and float_str[pos-1] in [sep_sign, decimal_sign] else pos return QtW.QDoubleSpinBox.validate(self, float_str, pos) diff --git a/tests/gui_structure_for_tests.py b/tests/gui_structure_for_tests.py index e20991e..2b5536d 100644 --- a/tests/gui_structure_for_tests.py +++ b/tests/gui_structure_for_tests.py @@ -101,6 +101,15 @@ def __init__(self, default_parent: QtW.QWidget, translations: Translations): category=self.sub_category, ) + self.negative_float = els.FloatBox( + label="negative_float", + default_value=-1000, + minimal_value=-100_000, + maximal_value=-100, + decimal_number=2, + category=self.sub_category, + ) + self.int_units = els.IntBoxWithUnits( label="IntBoxWithUnits", default_value=2, diff --git a/tests/test_gui_structure_elements/test_float_box.py b/tests/test_gui_structure_elements/test_float_box.py index 9ce79e8..5d4319e 100644 --- a/tests/test_gui_structure_elements/test_float_box.py +++ b/tests/test_gui_structure_elements/test_float_box.py @@ -19,6 +19,7 @@ def test_float_box(qtbot): # init gui window main_window = start_tests(qtbot) float_b = main_window.gui_structure.float_b + negative_float = main_window.gui_structure.negative_float assert np.isclose(float_b.get_value(), float_b.default_value) float_b.set_value(float_b.default_value + 50) assert np.isclose(float_b.default_value + 50, float_b.get_value()) @@ -84,6 +85,21 @@ def test_float_box(qtbot): res = float_b.widget.validate("10002,0", 5) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10002) assert res[2] == 5 + res = negative_float.widget.validate("-20000,20", 5) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -20_000.20) + assert res[2] == 5 + res = negative_float.widget.validate("-212.000,20", 4) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -21_200.02) + assert res[2] == 5 + res = negative_float.widget.validate("-21.000,230", 10) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -21_000.23) + assert res[2] == 10 + res = negative_float.widget.validate("-21.000,230", 9) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -21_000.23) + assert res[2] == 9 + res = negative_float.widget.validate("-21.0002,23", 8) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -21_000.22) + assert res[2] == 9 # test validation float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.English, QtC.QLocale.UnitedStates)) res = float_b.widget.validate("10,0003.20", 7) From 44d4740ad5834609d0c2678f104310b4eddc9ade Mon Sep 17 00:00:00 2001 From: Tobias Blanke <86232208+tblanke@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:56:17 +0200 Subject: [PATCH 05/16] Update float_box.py --- .../gui_structure_classes/float_box.py | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index 3649838..df99295 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -66,29 +66,11 @@ def validate(self, float_str: str, pos: int) -> object: float_str = float_str[:-1] # move values if the current one is above the maximum limit_reached: bool = False - if self.maximum() > 1: + if self.maximum() > 1 or self.minimum() < -1: float_str = float_str.replace(sep_sign, "") strings = float_str.split(decimal_sign) strings[0] = "0" if strings[0] == "" else strings[0] - limit_reached = float(strings[0]) > self.maximum() - if limit_reached: - float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] - if has_sep: - dec_idx = float_str.index(decimal_sign) if self.decimals() > 0 and float_str.index(decimal_sign) > 2 else 0 - # Initialize an empty result string - result_string = "" - # Iterate through the original string in reverse - for i, char in enumerate(reversed(float_str[: dec_idx])): - # Check if it's time to insert the symbol - if i > 0 and i % 3 == 0: - result_string = sep_sign + result_string # Insert the symbol - result_string = char + result_string # Add the character - float_str = result_string + float_str[dec_idx:] - elif self.minimum() < -1: - float_str = float_str.replace(sep_sign, "") - strings = float_str.split(decimal_sign) - strings[0] = "0" if strings[0] == "" else strings[0] - limit_reached = float(strings[0]) < self.minimum() + limit_reached = float(strings[0]) > self.maximum() or float(strings[0]) < self.minimum() if limit_reached: float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] if has_sep: From 5294e4cf272985174d26a8684801e138d2da7fef Mon Sep 17 00:00:00 2001 From: Tobias Blanke <86232208+tblanke@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:28:02 +0200 Subject: [PATCH 06/16] Update test_float_box.py --- tests/test_gui_structure_elements/test_float_box.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_gui_structure_elements/test_float_box.py b/tests/test_gui_structure_elements/test_float_box.py index 5d4319e..5476ec9 100644 --- a/tests/test_gui_structure_elements/test_float_box.py +++ b/tests/test_gui_structure_elements/test_float_box.py @@ -85,6 +85,7 @@ def test_float_box(qtbot): res = float_b.widget.validate("10002,0", 5) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10002) assert res[2] == 5 + negative_float.widget.setLocale(QtC.QLocale(QtC.QLocale.German, QtC.QLocale.Germany)) res = negative_float.widget.validate("-20000,20", 5) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -20_000.20) assert res[2] == 5 From b3281d859650905a6824ec9099606b313eaa9f8e Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Fri, 6 Oct 2023 10:37:38 +0200 Subject: [PATCH 07/16] reaches also the max if the decimal places are relevant --- ScenarioGUI/gui_classes/gui_structure_classes/float_box.py | 6 ++++-- tests/test_gui_structure_elements/test_float_box.py | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index df99295..0e16266 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -47,7 +47,7 @@ def validate(self, float_str: str, pos: int) -> object: ------- object """ - # position is index +1 in input + # get sign and decimal symbols decimal_sign = self.locale().decimalPoint() sep_sign = self.locale().groupSeparator() has_sep = sep_sign in float_str @@ -70,7 +70,9 @@ def validate(self, float_str: str, pos: int) -> object: float_str = float_str.replace(sep_sign, "") strings = float_str.split(decimal_sign) strings[0] = "0" if strings[0] == "" else strings[0] - limit_reached = float(strings[0]) > self.maximum() or float(strings[0]) < self.minimum() + strings[0] = "-0" if strings[0] == "-" else strings[0] + new_float_str = f"{strings[0]}.{strings[1]}" if len(strings) > 1 else strings[0] + limit_reached = (float(new_float_str) > self.maximum() and float(new_float_str) > 0) or float(new_float_str) < self.minimum() if limit_reached: float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] if has_sep: diff --git a/tests/test_gui_structure_elements/test_float_box.py b/tests/test_gui_structure_elements/test_float_box.py index 5476ec9..63c1f17 100644 --- a/tests/test_gui_structure_elements/test_float_box.py +++ b/tests/test_gui_structure_elements/test_float_box.py @@ -82,6 +82,9 @@ def test_float_box(qtbot): res = float_b.widget.validate("10.0003,20", 7) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000.32) assert res[2] == 8 + res = float_b.widget.validate("10.0000,20", 7) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000.02) + assert res[2] == 8 res = float_b.widget.validate("10002,0", 5) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10002) assert res[2] == 5 @@ -101,6 +104,9 @@ def test_float_box(qtbot): res = negative_float.widget.validate("-21.0002,23", 8) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -21_000.22) assert res[2] == 9 + res = negative_float.widget.validate("-10.0000,23", 8) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -10_000.02) + assert res[2] == 9 # test validation float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.English, QtC.QLocale.UnitedStates)) res = float_b.widget.validate("10,0003.20", 7) From 73d4543cfbae5e6a6f628a8475b758ffc9ea4eb7 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Fri, 6 Oct 2023 10:39:30 +0200 Subject: [PATCH 08/16] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f25ef2d..b2dff17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Remember last save location (issue 136) - Create a grid layout / matrix box as option (issue 102) - Move within this matrix box with the arrows (issue 101) +- Use insert in FloatBox (issue 103) ## Fixed - Change_scenario is not working properly (issue 131) From 0343405cc92533728b6ddcea17b8712fe1f80659 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Fri, 6 Oct 2023 12:12:08 +0200 Subject: [PATCH 09/16] fiexed wrong "-.100.000,00" casting --- ScenarioGUI/gui_classes/gui_structure_classes/float_box.py | 5 +++++ examples/example_classes/gui_structure.py | 2 +- requirements_docs.txt | 3 ++- tests/test_gui_structure_elements/test_float_box.py | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index 0e16266..302e22f 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -76,6 +76,9 @@ def validate(self, float_str: str, pos: int) -> object: if limit_reached: float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] if has_sep: + minus = float_str[0] == "-" + if minus: + float_str = float_str[1:] dec_idx = float_str.index(decimal_sign) if self.decimals() > 0 and float_str.index(decimal_sign) > 2 else 0 # Initialize an empty result string result_string = "" @@ -86,6 +89,8 @@ def validate(self, float_str: str, pos: int) -> object: result_string = sep_sign + result_string # Insert the symbol result_string = char + result_string # Add the character float_str = result_string + float_str[dec_idx:] + if minus: + float_str = f"-{float_str}" pos = (pos + 1) if limit_reached and is_number and float_str[pos-1] in [sep_sign, decimal_sign] else pos return QtW.QDoubleSpinBox.validate(self, float_str, pos) diff --git a/examples/example_classes/gui_structure.py b/examples/example_classes/gui_structure.py index 906650d..5677154 100644 --- a/examples/example_classes/gui_structure.py +++ b/examples/example_classes/gui_structure.py @@ -225,7 +225,7 @@ def __init__(self, default_parent: QtW.QWidget, translations: Translations): entries=["entry 1", "entry 2", "entry 3"], ) self.hint_flex = els.Hint( - hint="wrong length of flexible option", + hint=["wrong length of flexible option", "Falscher Wert in flexible option"], category=self.category_inputs, warning=True, ) diff --git a/requirements_docs.txt b/requirements_docs.txt index 6efe475..024f0b4 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -8,4 +8,5 @@ pygfunction>=2.2.1 PySide6>=6.3.1 scipy>=1.8.1 numpydoc >= 1.2.0 -sphinx_design >= 0.3.0 \ No newline at end of file +sphinx_design >= 0.3.0 +sphinx-rtd-theme>=1.3.0 \ No newline at end of file diff --git a/tests/test_gui_structure_elements/test_float_box.py b/tests/test_gui_structure_elements/test_float_box.py index 63c1f17..4141687 100644 --- a/tests/test_gui_structure_elements/test_float_box.py +++ b/tests/test_gui_structure_elements/test_float_box.py @@ -107,6 +107,9 @@ def test_float_box(qtbot): res = negative_float.widget.validate("-10.0000,23", 8) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), -10_000.02) assert res[2] == 9 + res = negative_float.widget.validate("-10.0000,00", 4) + assert res[1] == "-100.000,00" + assert res[2] == 4 # test validation float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.English, QtC.QLocale.UnitedStates)) res = float_b.widget.validate("10,0003.20", 7) From 815cdffd4906e694ff6037c12869c06deb4a4673 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:06:32 +0200 Subject: [PATCH 10/16] fixed 0 adding issue --- .../gui_structure_classes/float_box.py | 5 ++++- .../test_float_box.py | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index 302e22f..ba9b802 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -51,6 +51,7 @@ def validate(self, float_str: str, pos: int) -> object: decimal_sign = self.locale().decimalPoint() sep_sign = self.locale().groupSeparator() has_sep = sep_sign in float_str + len_bef = float_str[:pos].count(sep_sign) if pos > len(float_str) or float_str == "": return QtW.QDoubleSpinBox.validate(self, float_str, pos) is_number = not (float_str[pos-1] in [sep_sign, decimal_sign]) @@ -91,7 +92,9 @@ def validate(self, float_str: str, pos: int) -> object: float_str = result_string + float_str[dec_idx:] if minus: float_str = f"-{float_str}" - pos = (pos + 1) if limit_reached and is_number and float_str[pos-1] in [sep_sign, decimal_sign] else pos + pos = (pos + 1) if limit_reached and is_number and float_str[pos-1] == decimal_sign else pos + pos = (pos + 1) if len_bef < float_str[:pos].count(sep_sign) else pos + pos = (pos - 1) if len_bef > float_str[:pos].count(sep_sign) else pos return QtW.QDoubleSpinBox.validate(self, float_str, pos) diff --git a/tests/test_gui_structure_elements/test_float_box.py b/tests/test_gui_structure_elements/test_float_box.py index 4141687..4123270 100644 --- a/tests/test_gui_structure_elements/test_float_box.py +++ b/tests/test_gui_structure_elements/test_float_box.py @@ -85,6 +85,21 @@ def test_float_box(qtbot): res = float_b.widget.validate("10.0000,20", 7) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000.02) assert res[2] == 8 + res = float_b.widget.validate("100.000,00", 0) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 100_000) + assert res[2] == 0 + res = float_b.widget.validate("0100.000,00", 0) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 100_000) + assert res[2] == 0 + res = float_b.widget.validate("0.100.000,00", 0) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 100_000) + assert res[2] == 0 + res = float_b.widget.validate("0000.100.000,00", 4) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 100_000) + assert res[2] == 5 + res = float_b.widget.validate("0.10.000,00", 2) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_000) + assert res[2] == 1 res = float_b.widget.validate("10002,0", 5) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10002) assert res[2] == 5 @@ -109,7 +124,7 @@ def test_float_box(qtbot): assert res[2] == 9 res = negative_float.widget.validate("-10.0000,00", 4) assert res[1] == "-100.000,00" - assert res[2] == 4 + assert res[2] == 3 # test validation float_b.widget.setLocale(QtC.QLocale(QtC.QLocale.English, QtC.QLocale.UnitedStates)) res = float_b.widget.validate("10,0003.20", 7) From 43f9927092d38b4b115b738d3770595248e37f74 Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Fri, 6 Oct 2023 14:12:52 +0200 Subject: [PATCH 11/16] included int Box --- .../gui_structure_classes/float_box.py | 2 +- .../gui_classes/gui_structure_classes/int_box.py | 16 +++++----------- .../gui_structure_classes/multiple_int_box.py | 7 ++++--- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index ba9b802..f9c9a0a 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -335,7 +335,7 @@ def create_widget( layout = self.create_frame(frame, layout_parent) self.widget.setParent(self.frame) self.widget.setStyleSheet( - f'QDoubleSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};' f'border: 1px solid {globs.WHITE};{"}"}' + f'QDoubleSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};border: 1px solid {globs.WHITE};{"}"}' ) self.widget.setAlignment(QtC.Qt.AlignRight | QtC.Qt.AlignTrailing | QtC.Qt.AlignVCenter) self.widget.setGroupSeparatorShown(True) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/int_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/int_box.py index e86e575..da033e4 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/int_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/int_box.py @@ -10,6 +10,7 @@ import PySide6.QtWidgets as QtW # type: ignore import ScenarioGUI.global_settings as globs +from .float_box import DoubleSpinBox from ...utils import set_default_font from .functions import check_and_set_max_min_values, check_conditional_visibility @@ -25,14 +26,6 @@ from .hint import Hint -class SpinBox(QtW.QSpinBox): # pragma: no cover - def wheelEvent(self, event: QtG.QWheelEvent): - if self.hasFocus(): - super().wheelEvent(event) - return - self.parent().wheelEvent(event) - - class IntBox(Option): """ This class contains all the functionalities of the IntBox (integer box) option in the GUI. @@ -85,7 +78,8 @@ def __init__( self.minimal_value: int = minimal_value self.maximal_value: int = maximal_value self.step: int = step - self.widget: SpinBox = SpinBox(self.default_parent, valueChanged=self.valueChanged.emit) + self.widget: DoubleSpinBox = DoubleSpinBox(self.default_parent, valueChanged=self.valueChanged.emit) + self.widget.setDecimals(0) def get_value(self) -> int: """ @@ -96,7 +90,7 @@ def get_value(self) -> int: int Value of the IntBox """ - return self.widget.value() + return int(self.widget.value()) def set_value(self, value: int) -> None: """ @@ -265,7 +259,7 @@ def create_widget( layout = self.create_frame(frame, layout_parent) self.widget.setParent(self.frame) self.widget.setStyleSheet( - f'QSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};border: 1px solid {globs.WHITE};{"}"}' + f'QDoubleSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};border: 1px solid {globs.WHITE};{"}"}' ) self.widget.setAlignment(QtC.Qt.AlignRight | QtC.Qt.AlignTrailing | QtC.Qt.AlignVCenter) self.widget.setMinimum(self.minimal_value) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/multiple_int_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/multiple_int_box.py index 91d39d2..5a638f0 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/multiple_int_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/multiple_int_box.py @@ -15,7 +15,7 @@ from ...utils import set_default_font from .functions import check_conditional_visibility -from .int_box import SpinBox +from .int_box import DoubleSpinBox from .option import Option if TYPE_CHECKING: # pragma: no cover @@ -78,7 +78,8 @@ def __init__( # noqa: PLR0913 self.minimal_value: list[int] = [minimal_value for _ in default_value] if not isinstance(minimal_value, Iterable) else minimal_value self.maximal_value: list[int] = [maximal_value for _ in default_value] if not isinstance(maximal_value, Iterable) else maximal_value self.step: list[int] = [step for _ in default_value] if not isinstance(step, Iterable) else step - self.widget: list[SpinBox] = [SpinBox(self.default_parent, valueChanged=self.valueChanged.emit) for _ in default_value] + self.widget: list[DoubleSpinBox] = [DoubleSpinBox(self.default_parent, valueChanged=self.valueChanged.emit) for _ in default_value] + _ = [wid.setDecimals(0) for wid in self.widget] def get_value(self) -> tuple[int]: """ @@ -260,7 +261,7 @@ def create_widget( for widget, max_val, min_val, step, def_val in zip(self.widget, self.maximal_value, self.minimal_value, self.step, self.default_value): widget.setParent(self.frame) widget.setStyleSheet( - f'QSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};' f'border: 1px solid {globs.WHITE};{"}"}' + f'QDoubleSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};' f'border: 1px solid {globs.WHITE};{"}"}' ) widget.setAlignment(QtC.Qt.AlignRight | QtC.Qt.AlignTrailing | QtC.Qt.AlignVCenter) widget.setMinimum(min_val) From 9065ab264c1d2adf40a853797e47001602da2eee Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:43:52 +0200 Subject: [PATCH 12/16] updated Readme --- README.md | 5 ++--- requirements.txt | 2 +- requirements_docs.txt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 65af944..ce04770 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ This can be found on [ScenarioGUI.readthedocs.io](https://scenariogui.readthedoc ## Requirements -This code is tested with Python 3.10 and 3.11 and requires the following libraries (the versions mentioned are the ones with which the code is tested) +This code is tested with Python 3.8 to 3.11 and requires the following libraries (the versions mentioned are the ones with which the code is tested) -* PySide6>=6.4.1 +* PySide6>=6.5.3 * matplotlib>=3.5.2 * numpy>=1.23.1 * pandas>=1.4.3 @@ -34,7 +34,6 @@ For the tests * pytest-cov>=3.0.0 * pytest-timeout>=2.1.0 * pytest-qt>=4.1.0 -* keyboard>=0.13.5 ## Quick start ### Installation diff --git a/requirements.txt b/requirements.txt index a902a2b..a94ed1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ matplotlib>=3.5.2 numpy>=1.23.1 -PySide6>=6.5.2 +PySide6>=6.5.3 pandas>=1.4.3 black>=23.1.0 \ No newline at end of file diff --git a/requirements_docs.txt b/requirements_docs.txt index 024f0b4..22ed6c3 100644 --- a/requirements_docs.txt +++ b/requirements_docs.txt @@ -5,7 +5,7 @@ matplotlib>=3.5.2 numpy>=1.23.1 pandas>=1.4.3 pygfunction>=2.2.1 -PySide6>=6.3.1 +PySide6>=6.5.3 scipy>=1.8.1 numpydoc >= 1.2.0 sphinx_design >= 0.3.0 From 364d34d9ae114a4eb11fc20f1627427e6369ba2e Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Sat, 7 Oct 2023 00:07:32 +0200 Subject: [PATCH 13/16] Update float_box.py --- ScenarioGUI/gui_classes/gui_structure_classes/float_box.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index f9c9a0a..798cd80 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -55,9 +55,9 @@ def validate(self, float_str: str, pos: int) -> object: if pos > len(float_str) or float_str == "": return QtW.QDoubleSpinBox.validate(self, float_str, pos) is_number = not (float_str[pos-1] in [sep_sign, decimal_sign]) - if decimal_sign not in float_str and self.decimals() > 0: + has_no_decimal_sign = decimal_sign not in float_str and self.decimals() > 0 + if has_no_decimal_sign: float_str += decimal_sign - float_str += "0" * self.decimals() # move the curser to the next number if the current one is not # float_str = float_str.replace(sep_sign, "") nb_of_chars = len(float_str) - 1 if decimal_sign in float_str else 0 @@ -95,6 +95,7 @@ def validate(self, float_str: str, pos: int) -> object: pos = (pos + 1) if limit_reached and is_number and float_str[pos-1] == decimal_sign else pos pos = (pos + 1) if len_bef < float_str[:pos].count(sep_sign) else pos pos = (pos - 1) if len_bef > float_str[:pos].count(sep_sign) else pos + float_str = float_str[:-1] if has_no_decimal_sign else float_str return QtW.QDoubleSpinBox.validate(self, float_str, pos) From 92163b985b52cbecbd097b3fe8b6d28666de35a6 Mon Sep 17 00:00:00 2001 From: Tobias Blanke <86232208+tblanke@users.noreply.github.com> Date: Sat, 7 Oct 2023 00:35:18 +0200 Subject: [PATCH 14/16] Update test_float_box.py --- tests/test_gui_structure_elements/test_float_box.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_gui_structure_elements/test_float_box.py b/tests/test_gui_structure_elements/test_float_box.py index 4123270..13983c2 100644 --- a/tests/test_gui_structure_elements/test_float_box.py +++ b/tests/test_gui_structure_elements/test_float_box.py @@ -76,6 +76,9 @@ def test_float_box(qtbot): res = float_b.widget.validate("103.000", 7) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 10_300) assert res[2] == 8 + res = float_b.widget.validate("10.300", 5) + assert res[1] == "10.300" + assert res[2] == 5 res = float_b.widget.validate(",20", 0) assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 0.2) assert res[2] == 0 From c9dd80936fcd6bd86418a7985775c71a424d06fc Mon Sep 17 00:00:00 2001 From: tblanke <86232208+tblanke@users.noreply.github.com> Date: Mon, 9 Oct 2023 08:30:22 +0200 Subject: [PATCH 15/16] test IntBox --- .../gui_classes/gui_structure_classes/float_box.py | 7 ++++--- .../gui_classes/gui_structure_classes/int_box.py | 1 + examples/example_classes/gui_structure.py | 2 +- tests/gui_structure_for_tests.py | 2 +- tests/test_gui_structure_elements/test_int_box.py | 13 +++++++++++++ 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py index 798cd80..25a7718 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/float_box.py @@ -75,12 +75,12 @@ def validate(self, float_str: str, pos: int) -> object: new_float_str = f"{strings[0]}.{strings[1]}" if len(strings) > 1 else strings[0] limit_reached = (float(new_float_str) > self.maximum() and float(new_float_str) > 0) or float(new_float_str) < self.minimum() if limit_reached: - float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 0 else strings[:-1] + float_str = f"{strings[0][:-1]}{decimal_sign}{strings[0][-1]}{strings[1][:-1]}" if len(strings) > 1 else strings[0][:-1] if has_sep: minus = float_str[0] == "-" if minus: float_str = float_str[1:] - dec_idx = float_str.index(decimal_sign) if self.decimals() > 0 and float_str.index(decimal_sign) > 2 else 0 + dec_idx = float_str.index(decimal_sign) if self.decimals() > 0 and float_str.index(decimal_sign) > 2 else len(float_str) # Initialize an empty result string result_string = "" # Iterate through the original string in reverse @@ -92,7 +92,8 @@ def validate(self, float_str: str, pos: int) -> object: float_str = result_string + float_str[dec_idx:] if minus: float_str = f"-{float_str}" - pos = (pos + 1) if limit_reached and is_number and float_str[pos-1] == decimal_sign else pos + + pos = (pos + 1) if limit_reached and is_number and len(float_str) > pos and float_str[pos-1] == decimal_sign else pos pos = (pos + 1) if len_bef < float_str[:pos].count(sep_sign) else pos pos = (pos - 1) if len_bef > float_str[:pos].count(sep_sign) else pos float_str = float_str[:-1] if has_no_decimal_sign else float_str diff --git a/ScenarioGUI/gui_classes/gui_structure_classes/int_box.py b/ScenarioGUI/gui_classes/gui_structure_classes/int_box.py index da033e4..52288d9 100644 --- a/ScenarioGUI/gui_classes/gui_structure_classes/int_box.py +++ b/ScenarioGUI/gui_classes/gui_structure_classes/int_box.py @@ -262,6 +262,7 @@ def create_widget( f'QDoubleSpinBox{"{"}selection-color: {globs.WHITE};selection-background-color: {globs.LIGHT};border: 1px solid {globs.WHITE};{"}"}' ) self.widget.setAlignment(QtC.Qt.AlignRight | QtC.Qt.AlignTrailing | QtC.Qt.AlignVCenter) + self.widget.setGroupSeparatorShown(True) self.widget.setMinimum(self.minimal_value) self.widget.setMaximum(self.maximal_value) self.widget.setValue(self.default_value) diff --git a/examples/example_classes/gui_structure.py b/examples/example_classes/gui_structure.py index b78ff18..239c922 100644 --- a/examples/example_classes/gui_structure.py +++ b/examples/example_classes/gui_structure.py @@ -34,7 +34,7 @@ def __init__(self, default_parent: QtW.QWidget, translations: Translations): label="a", default_value=2, minimal_value=0, - maximal_value=200, + maximal_value=30000, category=self.category_inputs, ) self.int_a.set_tool_tip("This is an explanation\nfor the value a") diff --git a/tests/gui_structure_for_tests.py b/tests/gui_structure_for_tests.py index c23049e..408bcdc 100644 --- a/tests/gui_structure_for_tests.py +++ b/tests/gui_structure_for_tests.py @@ -46,7 +46,7 @@ def __init__(self, default_parent: QtW.QWidget, translations: Translations): label="a", default_value=2, minimal_value=0, - maximal_value=300, + maximal_value=3000, category=self.category_inputs, ) diff --git a/tests/test_gui_structure_elements/test_int_box.py b/tests/test_gui_structure_elements/test_int_box.py index 230ccb0..7804cef 100644 --- a/tests/test_gui_structure_elements/test_int_box.py +++ b/tests/test_gui_structure_elements/test_int_box.py @@ -1,4 +1,5 @@ import numpy as np +import PySide6.QtCore as QtC import pytest from ScenarioGUI.gui_classes.gui_structure_classes.functions import ConditionalVisibilityWarning @@ -44,6 +45,18 @@ def test_int_box(qtbot): assert not main_window.gui_structure.float_b.is_hidden() main_window.save_scenario() assert "int_a" in main_window.list_ds[0].to_dict() + int_a.widget.setLocale(QtC.QLocale(QtC.QLocale.German, QtC.QLocale.Germany)) + # test validation + res = int_a.widget.validate("100", 2) + assert np.isclose(float(res[1].replace(",", ".")), 100) + assert res[2] == 2 + res = int_a.widget.validate("12345", 2) + assert np.isclose(float(res[1].replace(",", ".")), 1234) + assert res[2] == 2 + res = int_a.widget.validate("1.234", 2) + assert np.isclose(float(res[1].replace(".", "").replace(",", ".")), 1234) + assert res[1] == "1.234" + assert res[2] == 2 int_a.add_link_2_show(main_window.gui_structure.hint_1, below=0) From aec2ef1e6e15151776cebe045e7b631f3ed7c3f5 Mon Sep 17 00:00:00 2001 From: Tobias Blanke <86232208+tblanke@users.noreply.github.com> Date: Tue, 17 Oct 2023 13:22:52 +0200 Subject: [PATCH 16/16] 3.12 compatible --- .github/workflows/test.yml | 2 +- requirements.txt | 2 +- setup.cfg | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 84574c6..6ae789e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: strategy: matrix: os: [windows-latest, macos-latest] - python-version: ['3.8', '3.9', '3.10', '3.11'] + python-version: ['3.8', '3.9', '3.10', '3.11', '3.12'] steps: - uses: actions/checkout@v2 diff --git a/requirements.txt b/requirements.txt index a94ed1f..24565ba 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ matplotlib>=3.5.2 numpy>=1.23.1 -PySide6>=6.5.3 +PySide6>=6.6.0 pandas>=1.4.3 black>=23.1.0 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index b2697b2..eb30367 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,6 +13,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 License :: OSI Approved :: BSD License Operating System :: OS Independent @@ -23,7 +24,7 @@ include_package_data = True install_requires = matplotlib>=3.5.2 numpy>=1.23.1 - PySide6>=6.4.1 + PySide6>=6.6.0 [options.extras_require] GUI = pyside6>=6.4.1