From c4904caf4fb02b2b4d708a00f1d3925463710f97 Mon Sep 17 00:00:00 2001
From: Christoph Schmidt <christoph.,schmidt@tugraz.at>
Date: Wed, 20 Dec 2023 10:01:28 +0100
Subject: [PATCH] Updated to Version 1.1.2. Has now the option to connect a
 property that is automatically set on a field change.

---
 examples/main.py                              | 22 +++++++++++++++
 src/confighandler/controller/CSignal.py       |  1 -
 src/confighandler/controller/ConfigNode.py    |  2 +-
 src/confighandler/controller/Field.py         | 14 +++++++++-
 .../controller/fields/FieldSelectableList.py  |  1 -
 src/confighandler/view/FieldView.py           | 16 +++++++++++
 .../view/fields/FieldViewBool.py              |  2 +-
 .../view/fields/FieldViewFloat.py             |  2 +-
 src/confighandler/view/fields/FieldViewInt.py |  2 +-
 .../view/fields/FieldViewList.py              |  2 +-
 .../view/fields/FieldViewPath.py              |  2 +-
 .../view/fields/FieldViewSelectableList.py    | 28 +++----------------
 .../view/fields/FieldViewString.py            |  2 +-
 .../view/fields/FieldViewTuple.py             |  2 +-
 14 files changed, 63 insertions(+), 35 deletions(-)

diff --git a/examples/main.py b/examples/main.py
index efcca1b..2fd56e6 100644
--- a/examples/main.py
+++ b/examples/main.py
@@ -8,12 +8,28 @@ from rich.logging import RichHandler
 
 from ApplicationConfig import ApplicationConfig
 
+class TestClass:
+
+    def __init__(self):
+        self._wafer = 0
+
+    @property
+    def wafer(self):
+        return self._wafer
+
+    @wafer.setter
+    def wafer(self, value):
+        self._wafer = value
+        print("Wafer changed to", value)
+
+
 if __name__ == "__main__":
     app = QApplication(sys.argv)
 
     # setup the logging module
 
     config = ApplicationConfig()
+    testclass = TestClass()
     time.sleep(1)
     #config.autosave(enable=True, path='./configs_autosave')
     (config.load('./configs/ApplicationConfig.yaml'))
@@ -45,6 +61,12 @@ if __name__ == "__main__":
     btn_save.clicked.connect(lambda: config.save('./configs/ApplicationConfig.yaml'))
     grd.addWidget(btn_save, 4, 0)
 
+    # Add a new combo box
+    combo = QtWidgets.QComboBox()
+    grd.addWidget(combo, 5, 0)
+    config.wafer_list1.view.add_new_view(combo)
+    config.wafer_list1.connect_property(testclass, TestClass.wafer)
+    
     window.setCentralWidget(wdg)
     #print(config.load('config.yaml'))
 
diff --git a/src/confighandler/controller/CSignal.py b/src/confighandler/controller/CSignal.py
index b47c158..a1d4494 100644
--- a/src/confighandler/controller/CSignal.py
+++ b/src/confighandler/controller/CSignal.py
@@ -11,7 +11,6 @@ class CSignal:
         self.connections = []
 
     def emit(self, *args, **kwargs):
-        #print(f"CSignal emit: {args}, {kwargs}")
         for connection in self.connections:
             connection()
 
diff --git a/src/confighandler/controller/ConfigNode.py b/src/confighandler/controller/ConfigNode.py
index 817f8cc..6fb02ba 100644
--- a/src/confighandler/controller/ConfigNode.py
+++ b/src/confighandler/controller/ConfigNode.py
@@ -193,7 +193,7 @@ class ConfigNode(CObject):
     # ==================================================================================================================
     def _on_field_changed(self):
         # Emit that a field has changed, thus the keywords have changed
-        # print(f"Field changed {self.keywords}")
+       # print(f"Field changed {self.keywords}")
         for attr, val in self.fields.items():
             val: Field
             val._on_keyword_changed()
diff --git a/src/confighandler/controller/Field.py b/src/confighandler/controller/Field.py
index b37496a..09a174c 100644
--- a/src/confighandler/controller/Field.py
+++ b/src/confighandler/controller/Field.py
@@ -23,6 +23,7 @@ class FieldData(object):
         self.value = [value, friendly_name, description]
 
 
+
 T = TypeVar('T')
 
 
@@ -47,6 +48,9 @@ class Field(Generic[T], CObject):
         # The view, usd for handling the UI
         self.view = FieldView(self)
 
+        # Connected properties that should bet set if the field changes
+        self.props = []
+
         self._internal_logger.debug(f"Field {self.__class__.__name__} created with value {value} of type {type(value)}")
 
     def __new__(cls, value, friendly_name: str = None, description: str = None):
@@ -80,6 +84,13 @@ class Field(Generic[T], CObject):
         """Used for serializing instances. Returns the current field as a yaml-line."""
         return f"{self.name}: {self._yaml_repr()} # {self.friendly_name}: {self.description}"
 
+    def connect_property(self, instance, prop: property):
+        self.props.append((instance, prop))
+
+    def _set_all_props(self, value):
+        for inst, prop in self.props:
+            prop.fset(inst, value)
+
     # ==================================================================================================================
     # Register the field to a configuration
     # ==================================================================================================================
@@ -161,6 +172,7 @@ class Field(Generic[T], CObject):
         By default, the value will be emitted. Overwrite if you need another behavior
         :return:
         """
+
         return self._value
 
     def get(self) -> T:
@@ -169,6 +181,7 @@ class Field(Generic[T], CObject):
     def set(self, value: T, *args, force_emit: bool = False, **kwargs):
         if not self._value_to_emit == value or force_emit:
             self._internal_logger.info(f"{self.name} = {value} ({type(value)})")
+            self._set_all_props(value)
             self._set(value, *args, **kwargs)
             self.set_keywords()
             # self.view.value_changed.emit(self.value)
@@ -186,7 +199,6 @@ class Field(Generic[T], CObject):
 
     def _on_keyword_changed(self):
         self.set(self._value_to_emit)
-        # print(f"Field {self.__class__.__name__}._on_keyword_changed called: {self.value}: {str(self.value)}")
         self.view.value_changed.emit(self._value_to_emit)
 
     def _yaml_repr(self):
diff --git a/src/confighandler/controller/fields/FieldSelectableList.py b/src/confighandler/controller/fields/FieldSelectableList.py
index d74ba0f..6933e6b 100644
--- a/src/confighandler/controller/fields/FieldSelectableList.py
+++ b/src/confighandler/controller/fields/FieldSelectableList.py
@@ -35,7 +35,6 @@ class FieldSelectableList(ch.Field):
         return self._value.selected_index
 
     def _set(self, value, list = None, description = None):
-        print(f"FieldSelectableList: {value}, {list}, {description}")
         if list is not None:
             self._value = ch.SelectableList(list, selected_index=value, description=description)
             self.csig_field_changed.emit(self._value_to_emit)
diff --git a/src/confighandler/view/FieldView.py b/src/confighandler/view/FieldView.py
index cf8227f..6b4645d 100644
--- a/src/confighandler/view/FieldView.py
+++ b/src/confighandler/view/FieldView.py
@@ -20,6 +20,7 @@ class FieldView(QWidget):
 
     def __init__(self, parent_field):
         super().__init__()
+
         self.parent_field = parent_field
 
         self.label = None
@@ -33,11 +34,26 @@ class FieldView(QWidget):
             print(">>> String")
 
     def _on_value_changed(self, value):
+        """
+        Updates the UI when the value is changed.
+        :param value:
+        :return:
+        """
+        self._on_value_changed_partial(value)
+
+    def _on_value_changed_partial(self, value):
+        """
+        Additional actions when the value is changed.
+        :param value:
+        :return:
+        """
         raise NotImplementedError()
 
     def add_new_view(self, view: QWidget):
         self.ui_field(view)
 
+
+
     def ui_field(self, view: QWidget) -> QWidget:
         """
         Returns a QLineEdit for the UI.
diff --git a/src/confighandler/view/fields/FieldViewBool.py b/src/confighandler/view/fields/FieldViewBool.py
index d0dc2d9..bd99f10 100644
--- a/src/confighandler/view/fields/FieldViewBool.py
+++ b/src/confighandler/view/fields/FieldViewBool.py
@@ -41,7 +41,7 @@ class FieldViewBool(FieldView):
     # def _on_keyword_changed(self, keywords):
     #    pass
 
-    def _on_value_changed(self, value):
+    def _on_value_changed_partial(self, value):
         # print(f">>> {self.parent_field.name}: Value changed {value}")
         for edit in self.ui_edit_fields:
             edit: QCheckBox  # Just for typehinting
diff --git a/src/confighandler/view/fields/FieldViewFloat.py b/src/confighandler/view/fields/FieldViewFloat.py
index 9ff44c4..1f131ba 100644
--- a/src/confighandler/view/fields/FieldViewFloat.py
+++ b/src/confighandler/view/fields/FieldViewFloat.py
@@ -40,7 +40,7 @@ class FieldViewFloat(FieldView):
     def _on_value_edited(self, value):
         self.parent_field.set(float(value))
 
-    def _on_value_changed(self, value):
+    def _on_value_changed_partial(self, value):
         # print(f">>> {self.parent_field.name}: Value changed {value}")
         for edit in self.ui_edit_fields:
             edit.setValue(float(value))
diff --git a/src/confighandler/view/fields/FieldViewInt.py b/src/confighandler/view/fields/FieldViewInt.py
index 345b548..07c1490 100644
--- a/src/confighandler/view/fields/FieldViewInt.py
+++ b/src/confighandler/view/fields/FieldViewInt.py
@@ -42,7 +42,7 @@ class FieldViewInt(FieldView):
     # def _on_keyword_changed(self, keywords):
     #    pass
 
-    def _on_value_changed(self, value):
+    def _on_value_changed_partial(self, value):
         #print(f">>> {self.parent_field.name}: Value changed {value}")
         for edit in self.ui_edit_fields:
             edit.setValue(int(value))
diff --git a/src/confighandler/view/fields/FieldViewList.py b/src/confighandler/view/fields/FieldViewList.py
index 097991c..c5a8e95 100644
--- a/src/confighandler/view/fields/FieldViewList.py
+++ b/src/confighandler/view/fields/FieldViewList.py
@@ -39,7 +39,7 @@ class FieldViewList(FieldView):
     def _on_text_edited(self, value):
         self.parent_field.set(value)
 
-    def _on_value_changed(self, value):
+    def _on_value_changed_partial(self, value):
         for edit in self.ui_edit_fields:
             edit.setText(str(value))
 
diff --git a/src/confighandler/view/fields/FieldViewPath.py b/src/confighandler/view/fields/FieldViewPath.py
index ab8dd10..2449c67 100644
--- a/src/confighandler/view/fields/FieldViewPath.py
+++ b/src/confighandler/view/fields/FieldViewPath.py
@@ -70,7 +70,7 @@ class FieldViewPath(FieldView):
     def _on_text_edited(self, value):
         self.parent_field.set(value)
 
-    def _on_value_changed(self, value: Path):
+    def _on_value_changed_partial(self, value: Path):
         # print(value)
         # Check if path exists
         for edit, lbl in zip(self.ui_edit_fields, self.ui_edit_fields_lbl):
diff --git a/src/confighandler/view/fields/FieldViewSelectableList.py b/src/confighandler/view/fields/FieldViewSelectableList.py
index 9f7e3e2..9b6e942 100644
--- a/src/confighandler/view/fields/FieldViewSelectableList.py
+++ b/src/confighandler/view/fields/FieldViewSelectableList.py
@@ -14,26 +14,6 @@ from PySide6.QtWidgets import QWidget, QLineEdit, QComboBox, QVBoxLayout, QLabel
 import confighandler as ch
 
 
-class MyComboBox(QComboBox):
-    def __init__(self, parent=None):
-        super(MyComboBox, self).__init__(parent)
-        self.setContextMenuPolicy()  # Qt.CustomContextMenu
-
-    def show_context_menu(self, pos):
-        index = self.currentIndex()
-        if index >= 0:
-            context_menu = QMenu(self)
-            delete_action = QAction('Delete', self)
-            delete_action.triggered.connect(self.delete_item)
-            context_menu.addAction(delete_action)
-
-            action = context_menu.exec_(self.mapToGlobal(pos))
-
-    def delete_item(self):
-        index = self.currentIndex()
-        if index >= 0:
-            self.removeItem(index)
-
 
 class FieldViewAddEntry(QWidget):
     value_changed = Signal(tuple)
@@ -129,13 +109,12 @@ class FieldViewSelectableList(ch.FieldView):
             cb.clear()
         sel_list = self.parent_field.get_selectable_list()
         for v in sel_list:
-            cb.addItem(f"{v[1]} - {v[0]} ", v[0])
+            cb.addItem(f"{v[1]}", v[0])
         cb.addItem("<Add new ...>", self.add_entry.show)
         #cb.currentIndexChanged.connect(self._on_index_changed)
 
 
     def show_context_menu(self, pos, field):
-        print(field)
         index = field.currentIndex()
         if index >= 0:
             context_menu = QMenu(field)
@@ -168,8 +147,9 @@ class FieldViewSelectableList(ch.FieldView):
         self.parent_field.set(len(self.parent_field.get_selectable_list()) - 1)
         self.parent_field.csig_field_changed.emit()
 
-    def _on_value_changed(self, value):
+    def _on_value_changed_partial(self, value):
         for edit in self.ui_edit_fields:
             edit: QComboBox
-            self.parent_field.logger.info(f"{edit}: Setting index to {value}")
+            # self.parent_field.logger.info(f"{edit}: Setting index to {value}")
             edit.setCurrentIndex(value)
+            edit.setToolTip(f"<{edit.currentData()}> ({self.parent_field.name}) {self.parent_field._description}")
diff --git a/src/confighandler/view/fields/FieldViewString.py b/src/confighandler/view/fields/FieldViewString.py
index 5d1c89b..9f296a0 100644
--- a/src/confighandler/view/fields/FieldViewString.py
+++ b/src/confighandler/view/fields/FieldViewString.py
@@ -41,7 +41,7 @@ class FieldViewString(FieldView):
         self.parent_field._internal_logger.debug(f"LineEdit {f} changed to {value}.")
         self.parent_field.set(value)
 
-    def _on_value_changed(self, value):
+    def _on_value_changed_partial(self, value):
         for edit in self.ui_edit_fields:
             edit.setText(value)
         # for tree_item in self.tree_items:
diff --git a/src/confighandler/view/fields/FieldViewTuple.py b/src/confighandler/view/fields/FieldViewTuple.py
index 32fb814..a8b62ed 100644
--- a/src/confighandler/view/fields/FieldViewTuple.py
+++ b/src/confighandler/view/fields/FieldViewTuple.py
@@ -39,7 +39,7 @@ class FieldViewTuple(FieldView):
     def _on_text_edited(self, value):
         self.parent_field.set(value)
 
-    def _on_value_changed(self, value):
+    def _on_value_changed_partial(self, value):
         for edit in self.ui_edit_fields:
             edit.setText(str(value))
 
-- 
GitLab