diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5a7a0b51..054a3744 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -8,12 +8,12 @@ jobs: runs-on: windows-latest steps: - name: Python Setup - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" architecture: x64 - name: Checkout Source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Dependencies run: | pip install --upgrade pip diff --git a/.github/workflows/flake8.yaml b/.github/workflows/flake8.yaml index 20eb688c..d0bedcfa 100644 --- a/.github/workflows/flake8.yaml +++ b/.github/workflows/flake8.yaml @@ -13,12 +13,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Python Setup - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" architecture: x64 - name: Checkout Source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 056b5b39..00e82821 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -16,12 +16,12 @@ jobs: # - name: Collect Workflow Telemetry # uses: runforesight/workflow-telemetry-action@v1 - name: Python Setup - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.11" architecture: x64 - name: Checkout Source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Dependencies run: | pip install --upgrade pip diff --git a/pywr_editor/assets/assets.qrc b/pywr_editor/assets/assets.qrc index 64062ba3..64470798 100644 --- a/pywr_editor/assets/assets.qrc +++ b/pywr_editor/assets/assets.qrc @@ -49,6 +49,7 @@ toolbar/sc_diagramtype.svg toolbar/lc_morecontrols.svg toolbar/searchdialog.svg + toolbar/sync-model.svg file_browser/x-circle.svg diff --git a/pywr_editor/assets/toolbar/sync-model.svg b/pywr_editor/assets/toolbar/sync-model.svg new file mode 100644 index 00000000..bd03aa8d --- /dev/null +++ b/pywr_editor/assets/toolbar/sync-model.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pywr_editor/main_window.py b/pywr_editor/main_window.py index 70208a76..12d08706 100644 --- a/pywr_editor/main_window.py +++ b/pywr_editor/main_window.py @@ -79,6 +79,7 @@ def __init__(self, model_file: str | None = None): self.empty_model: bool = False self.prompt_unsaved_changes: bool = True """ Prompts the user for unsaved model changes """ + self.toggle_sync_model_file = False if model_file is None: self.logger.debug("No model file was provided") @@ -221,6 +222,19 @@ def register_model_actions(self) -> None: icon=":/toolbar/reload", tooltip="Reload the JSON file if it was externally edited", connection=self.reload_model_file, + button_separator=False, + ) + ) + self.app_actions.add( + Action( + key="sync-model", + name="Sync\n file", + icon=":/toolbar/sync-model", + tooltip="Automatically reload the editor if the JSON file is " + "edited externally. Sync will be paused if there are unsaved " + "changes within the editor", + connection=self.sync_model_file, + is_checked=self.toggle_sync_model_file, button_separator=True, ) ) @@ -588,6 +602,8 @@ def setup_toolbar(self) -> None: file_panel.add_button(self.app_actions.get("save-model")) self.app_actions.get("save-model").setDisabled(True) file_panel.add_button(self.app_actions.get("reload-model")) + file_panel.add_button(self.app_actions.get("sync-model")) + file_panel.add_button(self.app_actions.get("search-in-model")) file_panel.add_button(self.app_actions.get("open-json-reader")) @@ -1106,8 +1122,8 @@ def listen_for_changes(self) -> None: Check whether the file is changed externally and reload the model. :return: None """ - # do not refresh if the window has not focus - if not self.window().isActiveWindow(): + # do not refresh if the window has not focus or sync is off + if not self.window().isActiveWindow() or not self.toggle_sync_model_file: return # noinspection PyBroadException @@ -1126,3 +1142,16 @@ def listen_for_changes(self) -> None: MainWindow(self.model_file) except Exception: pass + + @Slot() + def sync_model_file(self) -> None: + """ + Enable sync model. + :return: None + """ + self.toggle_sync_model_file = not self.toggle_sync_model_file + self.app_actions.get("sync-model").setChecked(self.toggle_sync_model_file) + + status_message = "JSON file synchronisation " + status_message += "enabled" if self.toggle_sync_model_file else "disabled" + self.statusBar().showMessage(status_message) diff --git a/pywr_editor/model/model_config.py b/pywr_editor/model/model_config.py index fc8ea0d6..ab707c3c 100644 --- a/pywr_editor/model/model_config.py +++ b/pywr_editor/model/model_config.py @@ -266,6 +266,7 @@ def check_missing_keys(self) -> None: for key in ["nodes", "edges", "includes", "scenarios"]: if key not in self.json: self.json[key] = [] + self.has_changed() # check for missing keys with dictionary as value for key in [ @@ -278,6 +279,7 @@ def check_missing_keys(self) -> None: ]: if key not in self.json: self.json[key] = {} + self.has_changed() if ( Constants.SHAPES_KEY.value @@ -286,6 +288,7 @@ def check_missing_keys(self) -> None: self.json[Constants.EDITOR_CONFIG_KEY.value][ Constants.SHAPES_KEY.value ] = [] + self.has_changed() # check that the metadata dictionary and its key/value pairs are defined default_metadata = { @@ -298,6 +301,7 @@ def check_missing_keys(self) -> None: self.json["metadata"][key], str ): self.json["metadata"][key] = value + self.has_changed() def is_valid(self) -> bool: """ diff --git a/pywr_editor/style/assets.py b/pywr_editor/style/assets.py index f0ba78ab..6d7cf8f2 100644 --- a/pywr_editor/style/assets.py +++ b/pywr_editor/style/assets.py @@ -1,6 +1,6 @@ # Resource object code (Python 3) # Created by: object code -# Created by: The Resource Compiler for Qt version 6.4.3 +# Created by: The Resource Compiler for Qt version 6.6.2 # WARNING! All changes made in this file will be lost! from PySide6 import QtCore @@ -545,6 +545,208 @@ fill=\x22#f8db8f\x22/>\ \x0d\x0a \x0d\x0a\x0d\x0a\ +\x00\x00\x0c|\ +<\ +svg height=\x2216\x22 \ +viewBox=\x220 0 4.2\ +333332 4.2333335\ +\x22 width=\x2216\x22 xml\ +ns=\x22http://www.w\ +3.org/2000/svg\x22>\ +\x0d\x0a \x0d\x0a \x0d\x0a \x0d\ +\x0a \x0d\x0a \x0d\x0a \ + \x0d\x0a \x0d\x0a \x0d\x0a \ +\x0d\x0a \x0d\x0a <\ +/g>\x0d\x0a\ \x00\x00\x02\xe3\ \x00\ \x00\x09\xd6x\xda\x9dVQs\xda0\x0c~\xef]\xff\ @@ -4370,6 +4572,10 @@ \x00\x07f\xbe\ \x00o\ \x00p\x00e\x00n\ +\x00\x0a\ +\x06O:<\ +\x00s\ +\x00y\x00n\x00c\x00-\x00m\x00o\x00d\x00e\x00l\ \x00\x06\ \x07\x9c\x14\xaf\ \x00r\ @@ -4665,184 +4871,186 @@ qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x08\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\xa4\x00\x02\x00\x00\x00\x0d\x00\x00\x00W\ +\x00\x00\x00\xa4\x00\x02\x00\x00\x00\x0d\x00\x00\x00X\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00U\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00V\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x0e\x00\x02\x00\x00\x00\x02\x00\x00\x00S\ +\x00\x00\x00\x0e\x00\x02\x00\x00\x00\x02\x00\x00\x00T\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00l\x00\x02\x00\x00\x00\x05\x00\x00\x00N\ +\x00\x00\x00l\x00\x02\x00\x00\x00\x05\x00\x00\x00O\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00N\x00\x02\x00\x00\x00\x01\x00\x00\x00M\ +\x00\x00\x00N\x00\x02\x00\x00\x00\x01\x00\x00\x00N\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x006\x00\x02\x00\x00\x00\x04\x00\x00\x00I\ +\x00\x00\x006\x00\x02\x00\x00\x00\x04\x00\x00\x00J\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x00\x90\x00\x02\x00\x00\x000\x00\x00\x00\x19\ +\x00\x00\x00\x90\x00\x02\x00\x00\x001\x00\x00\x00\x19\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x1e\x00\x02\x00\x00\x00\x10\x00\x00\x00\x09\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x09\xa6\x00\x00\x00\x00\x00\x01\x00\x00\xde\x81\ +\x00\x00\x09\xc0\x00\x00\x00\x00\x00\x01\x00\x00\xeb\x01\ \x00\x00\x01\x85$d\x9d\x5c\ -\x00\x00\x09.\x00\x00\x00\x00\x00\x01\x00\x00\xd2\xcd\ +\x00\x00\x09H\x00\x00\x00\x00\x00\x01\x00\x00\xdfM\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x09\x80\x00\x00\x00\x00\x00\x01\x00\x00\xd9\x9b\ +\x00\x00\x09\x9a\x00\x00\x00\x00\x00\x01\x00\x00\xe6\x1b\ \x00\x00\x01\x85$d\x9d\x5c\ -\x00\x00\x09\xd4\x00\x00\x00\x00\x00\x01\x00\x00\xe3\xe4\ +\x00\x00\x09\xee\x00\x00\x00\x00\x00\x01\x00\x00\xf0d\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x09\xb4\x00\x00\x00\x00\x00\x01\x00\x00\xe0\x8a\ +\x00\x00\x09\xce\x00\x00\x00\x00\x00\x01\x00\x00\xed\x0a\ \x00\x00\x01\x85$d\x9d\x5c\ -\x00\x00\x09L\x00\x00\x00\x00\x00\x01\x00\x00\xd5\xf6\ +\x00\x00\x09f\x00\x00\x00\x00\x00\x01\x00\x00\xe2v\ \x00\x00\x01\x85$d\x9d\x5c\ -\x00\x00\x09\xc4\x00\x00\x00\x00\x00\x01\x00\x00\xe1\xc5\ +\x00\x00\x09\xde\x00\x00\x00\x00\x00\x01\x00\x00\xeeE\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x09<\x00\x01\x00\x00\x00\x01\x00\x00\xd4\xd0\ +\x00\x00\x09V\x00\x01\x00\x00\x00\x01\x00\x00\xe1P\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x09\x5c\x00\x00\x00\x00\x00\x01\x00\x00\xd8\x84\ +\x00\x00\x09v\x00\x00\x00\x00\x00\x01\x00\x00\xe5\x04\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x09\x16\x00\x00\x00\x00\x00\x01\x00\x00\xcf\xcb\ +\x00\x00\x090\x00\x00\x00\x00\x00\x01\x00\x00\xdcK\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x01\x00\x00\xe8\xdd\ +\x00\x00\x0a\x1a\x00\x00\x00\x00\x00\x01\x00\x00\xf5]\ \x00\x00\x01\x85$d\x9d\x5c\ -\x00\x00\x09\xe4\x00\x00\x00\x00\x00\x01\x00\x00\xe7_\ +\x00\x00\x09\xfe\x00\x00\x00\x00\x00\x01\x00\x00\xf3\xdf\ \x00\x00\x01\x85$d\x9d\x5c\ -\x00\x00\x09\x04\x00\x00\x00\x00\x00\x01\x00\x00\xcdB\ +\x00\x00\x09\x1e\x00\x00\x00\x00\x00\x01\x00\x00\xd9\xc2\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x0a \x00\x00\x00\x00\x00\x01\x00\x00\xea\xae\ +\x00\x00\x0a:\x00\x00\x00\x00\x00\x01\x00\x00\xf7.\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x08\xea\x00\x00\x00\x00\x00\x01\x00\x00\xc7\xfa\ +\x00\x00\x09\x04\x00\x00\x00\x00\x00\x01\x00\x00\xd4z\ \x00\x00\x01\x85$d\x9d\x5c\ -\x00\x00\x09\x90\x00\x00\x00\x00\x00\x01\x00\x00\xdb\xad\ +\x00\x00\x09\xaa\x00\x00\x00\x00\x00\x01\x00\x00\xe8-\ \x00\x00\x01\x85$d\x9d\x5c\ -\x00\x00\x04(\x00\x00\x00\x00\x00\x01\x00\x00=\xbd\ +\x00\x00\x04B\x00\x00\x00\x00\x00\x01\x00\x00J=\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x04~\x00\x00\x00\x00\x00\x01\x00\x00I\xd7\ -\x00\x00\x01\x87\ +\x00\x00\x05\xe0\x00\x00\x00\x00\x00\x01\x00\x00x\xbe\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x05\xb2\x00\x00\x00\x00\x00\x01\x00\x00jF\ +\x00\x00\x05\xcc\x00\x00\x00\x00\x00\x01\x00\x00v\xc6\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x06\x5c\x00\x00\x00\x00\x00\x01\x00\x00r\xd8\ +\x00\x00\x06v\x00\x00\x00\x00\x00\x01\x00\x00\x7fX\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x05`\x00\x00\x00\x00\x00\x01\x00\x00bx\ +\x00\x00\x05z\x00\x00\x00\x00\x00\x01\x00\x00n\xf8\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x07N\x00\x00\x00\x00\x00\x01\x00\x00\xae\xde\ +\x00\x00\x07h\x00\x00\x00\x00\x00\x01\x00\x00\xbb^\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x04\xc8\x00\x00\x00\x00\x00\x01\x00\x00PI\ +\x00\x00\x04\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x5c\xc9\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x06.\x00\x00\x00\x00\x00\x01\x00\x00oU\ +\x00\x00\x06H\x00\x00\x00\x00\x00\x01\x00\x00{\xd5\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x03\x82\x00\x00\x00\x00\x00\x01\x00\x00+\xe9\ +\x00\x00\x03\x9c\x00\x00\x00\x00\x00\x01\x00\x008i\ \x00\x00\x01\x85$d\x9dl\ \x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00\x17f\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x072\x00\x01\x00\x00\x00\x01\x00\x00\xac\xb9\ +\x00\x00\x07L\x00\x01\x00\x00\x00\x01\x00\x00\xb99\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x06\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x96f\ +\x00\x00\x06\xda\x00\x00\x00\x00\x00\x01\x00\x00\xa2\xe6\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x04\x8a\x00\x00\x00\x00\x00\x01\x00\x00LK\ +\x00\x00\x04\xa4\x00\x00\x00\x00\x00\x01\x00\x00X\xcb\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x07\x82\x00\x00\x00\x00\x00\x01\x00\x00\xb1\x19\ +\x00\x00\x03P\x00\x00\x00\x00\x00\x01\x00\x00\x1f^\ +\x00\x00\x01\x8d\xd9\xd4Z\x11\ +\x00\x00\x07\x9c\x00\x00\x00\x00\x00\x01\x00\x00\xbd\x99\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x04\x12\x00\x00\x00\x00\x00\x01\x00\x00<:\ +\x00\x00\x04,\x00\x00\x00\x00\x00\x01\x00\x00H\xba\ \x00\x00\x01\x85$d\x9dl\ -\x00\x00\x05\x00\x00\x00\x00\x00\x00\x01\x00\x00Sr\ -\x00\x00\x01\x87