From 50ebeb4bc8b5a35c2cd4e726432ec46f7b73330e Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 18 Feb 2026 18:16:29 +0100 Subject: [PATCH 01/32] Added a sample model addiotion group --- src/easyshapes_app.qmlproject | 9 +- src/easyshapes_app.qrc | 166 ++++++++++++++++++ src/easyshapes_app/Gui/ApplicationWindow.qml | 4 +- src/easyshapes_app/Gui/Globals/References.qml | 10 ++ .../Sidebar/Basic/Groups/SampleModel.qml | 65 +++++++ .../SampleModel/Sidebar/Basic/Layout.qml | 8 + .../Sidebar/Basic/Popups/CreateNewModel.qml | 34 ++++ .../Basic/Popups/LoadExistingModel.qml | 34 ++++ 8 files changed, 325 insertions(+), 5 deletions(-) create mode 100644 src/easyshapes_app.qrc create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml diff --git a/src/easyshapes_app.qmlproject b/src/easyshapes_app.qmlproject index ff45ed3..a57d5e8 100644 --- a/src/easyshapes_app.qmlproject +++ b/src/easyshapes_app.qmlproject @@ -1,5 +1,6 @@ import QmlProject 1.1 + Project { mainFile: "easyshapes_app/main.qml" @@ -14,7 +15,7 @@ Project { directory: "easyshapes_app" } QmlFiles { - directory: "../../src/EasyApp" + directory: "../../EasyApp/src/EasyApp" } // Include .js files from specified directory and its subdirectories @@ -22,7 +23,7 @@ Project { directory: "easyshapes_app" } JavaScriptFiles { - directory: "../../src/EasyApp" + directory: "../../EasyApp/src/EasyApp" } // Include Module Definition Files (qmldir), as well as .ts and .qrc @@ -33,9 +34,11 @@ Project { recursive: true } Files { - directory: "../../src/EasyApp" + directory: "../../EasyApp/src/EasyApp" filter: "qmldir;*.ts;*.qrc;*.html" recursive: true } + + mainUiFile: "easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml" } diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc new file mode 100644 index 0000000..7de6409 --- /dev/null +++ b/src/easyshapes_app.qrc @@ -0,0 +1,166 @@ + + + ../../EasyApp/src/EasyApp/Gui/Animations/ColorReset.qml + ../../EasyApp/src/EasyApp/Gui/Animations/qmldir + ../../EasyApp/src/EasyApp/Gui/Animations/ThemeChange.qml + ../../EasyApp/src/EasyApp/Gui/Animations/TranslationChange.qml + ../../EasyApp/src/EasyApp/Gui/Charts/ChartViewHeatmap2dPlotly.qml + ../../EasyApp/src/EasyApp/Gui/Charts/ChartViewSimple1dPlotly.qml + ../../EasyApp/src/EasyApp/Gui/Charts/Plotly1dLine.qml + ../../EasyApp/src/EasyApp/Gui/Charts/Plotly1dMeasVsCalc.qml + ../../EasyApp/src/EasyApp/Gui/Charts/Plotly2dHeatmap.qml + ../../EasyApp/src/EasyApp/Gui/Charts/Plotly3dScatter.qml + ../../EasyApp/src/EasyApp/Gui/Charts/Plotly3dSurface.qml + ../../EasyApp/src/EasyApp/Gui/Charts/qmldir + ../../EasyApp/src/EasyApp/Gui/Charts/QtCharts1dBase 2.qml + ../../EasyApp/src/EasyApp/Gui/Charts/QtCharts1dBase.qml + ../../EasyApp/src/EasyApp/Gui/Charts/QtCharts1dMeasVsCalc.qml + ../../EasyApp/src/EasyApp/Gui/Charts/QtCharts1dValueAxis.qml + ../../EasyApp/src/EasyApp/Gui/Components/AboutDialog.qml + ../../EasyApp/src/EasyApp/Gui/Components/AppBarCentralTabs.qml + ../../EasyApp/src/EasyApp/Gui/Components/AppBarLeftButtons.qml + ../../EasyApp/src/EasyApp/Gui/Components/AppBarRightButtons.qml + ../../EasyApp/src/EasyApp/Gui/Components/ApplicationWindow.qml + ../../EasyApp/src/EasyApp/Gui/Components/BasicReport.qml + ../../EasyApp/src/EasyApp/Gui/Components/ContentArea.qml + ../../EasyApp/src/EasyApp/Gui/Components/ContentPage.qml + ../../EasyApp/src/EasyApp/Gui/Components/GuideWindow.qml + ../../EasyApp/src/EasyApp/Gui/Components/GuideWindowContainer.qml + ../../EasyApp/src/EasyApp/Gui/Components/JsonListModel.qml + ../../EasyApp/src/EasyApp/Gui/Components/MainContent.qml + ../../EasyApp/src/EasyApp/Gui/Components/PreferencesDialog.qml + ../../EasyApp/src/EasyApp/Gui/Components/ProjectDescriptionDialog.qml + ../../EasyApp/src/EasyApp/Gui/Components/qmldir + ../../EasyApp/src/EasyApp/Gui/Components/SideBar.qml + ../../EasyApp/src/EasyApp/Gui/Components/SideBarColumn.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableView.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewAdvancedLabel.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewButton.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewCheckBox.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewComboBox.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewDelegate.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewHeader.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewLabel.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewLabelControl.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewParameter.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewTextInput.qml + ../../EasyApp/src/EasyApp/Gui/Components/TableViewTwoRowsAdvancedLabel.qml + ../../EasyApp/src/EasyApp/Gui/Elements/AppBarTabButton.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ApplicationWindow.qml + ../../EasyApp/src/EasyApp/Gui/Elements/Button.qml + ../../EasyApp/src/EasyApp/Gui/Elements/CheckBox.qml + ../../EasyApp/src/EasyApp/Gui/Elements/CheckIndicator.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ComboBox.qml + ../../EasyApp/src/EasyApp/Gui/Elements/CursorDelegate.qml + ../../EasyApp/src/EasyApp/Gui/Elements/Dialog.qml + ../../EasyApp/src/EasyApp/Gui/Elements/DialogButtonBox.qml + ../../EasyApp/src/EasyApp/Gui/Elements/GroupBox.qml + ../../EasyApp/src/EasyApp/Gui/Elements/GroupButton.qml + ../../EasyApp/src/EasyApp/Gui/Elements/GroupColumn.qml + ../../EasyApp/src/EasyApp/Gui/Elements/GroupRow.qml + ../../EasyApp/src/EasyApp/Gui/Elements/Label.qml + ../../EasyApp/src/EasyApp/Gui/Elements/LinkedImage.qml + ../../EasyApp/src/EasyApp/Gui/Elements/Menu.qml + ../../EasyApp/src/EasyApp/Gui/Elements/MenuItem.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ParamComboBox.qml + ../../EasyApp/src/EasyApp/Gui/Elements/Parameter.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ParamTextField.qml + ../../EasyApp/src/EasyApp/Gui/Elements/qmldir + ../../EasyApp/src/EasyApp/Gui/Elements/RadioButton.qml + ../../EasyApp/src/EasyApp/Gui/Elements/RadioIndicator.qml + ../../EasyApp/src/EasyApp/Gui/Elements/RemoteController.qml + ../../EasyApp/src/EasyApp/Gui/Elements/RemotePointer.qml + ../../EasyApp/src/EasyApp/Gui/Elements/RunningLabel.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ScrollBar.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ScrollIndicator.qml + ../../EasyApp/src/EasyApp/Gui/Elements/SideBarButton.qml + ../../EasyApp/src/EasyApp/Gui/Elements/Slider.qml + ../../EasyApp/src/EasyApp/Gui/Elements/SliderHandle.qml + ../../EasyApp/src/EasyApp/Gui/Elements/SpinBox.qml + ../../EasyApp/src/EasyApp/Gui/Elements/SplashScreen.qml + ../../EasyApp/src/EasyApp/Gui/Elements/StatusBar.qml + ../../EasyApp/src/EasyApp/Gui/Elements/StatusBarItem.qml + ../../EasyApp/src/EasyApp/Gui/Elements/TabBar.qml + ../../EasyApp/src/EasyApp/Gui/Elements/TabButton.qml + ../../EasyApp/src/EasyApp/Gui/Elements/TextArea.qml + ../../EasyApp/src/EasyApp/Gui/Elements/TextField.qml + ../../EasyApp/src/EasyApp/Gui/Elements/TextInput.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ToolButton.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ToolTip.qml + ../../EasyApp/src/EasyApp/Gui/Elements/ToolTipShadow.qml + ../../EasyApp/src/EasyApp/Gui/Globals/qmldir + ../../EasyApp/src/EasyApp/Gui/Globals/Vars.qml + ../../EasyApp/src/EasyApp/Gui/Html/plotly-2.18.0.min.js + ../../EasyApp/src/EasyApp/Gui/Logic/Plotting.js + ../../EasyApp/src/EasyApp/Gui/Logic/ProjectConfig.js + ../../EasyApp/src/EasyApp/Gui/Logic/qmldir + ../../EasyApp/src/EasyApp/Gui/Logic/Translate.js + ../../EasyApp/src/EasyApp/Gui/Logic/Utils.js + ../../EasyApp/src/EasyApp/Gui/Style/Colors.qml + ../../EasyApp/src/EasyApp/Gui/Style/Fonts.qml + ../../EasyApp/src/EasyApp/Gui/Style/qmldir + ../../EasyApp/src/EasyApp/Gui/Style/Sizes.qml + ../../EasyApp/src/EasyApp/Gui/Style/Times.qml + ../../EasyApp/src/EasyApp/Logic/Maintenance/qmldir + ../../EasyApp/src/EasyApp/Logic/Maintenance/Updater.qml + easyshapes_app.qmlproject + easyshapes_app/Backends/MockBackend.qml + easyshapes_app/Backends/MockQml/Analysis.qml + easyshapes_app/Backends/MockQml/Project.qml + easyshapes_app/Backends/MockQml/qmldir + easyshapes_app/Backends/MockQml/Report.qml + easyshapes_app/Backends/MockQml/Status.qml + easyshapes_app/Backends/qmldir + easyshapes_app/Gui/ApplicationWindow.qml + easyshapes_app/Gui/Globals/ApplicationInfo.qml + easyshapes_app/Gui/Globals/BackendWrapper.qml + easyshapes_app/Gui/Globals/qmldir + easyshapes_app/Gui/Globals/References.qml + easyshapes_app/Gui/Pages/Analysis/Layout.qml + easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml + easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml + easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml + easyshapes_app/Gui/Pages/Home/Content.qml + easyshapes_app/Gui/Pages/Home/Popups/About.qml + easyshapes_app/Gui/Pages/Project/Layout.qml + easyshapes_app/Gui/Pages/Project/MainArea/Description.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Examples.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/GetStarted.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Recent.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Layout.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/OpenCifFile.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/ProjectDescription.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Groups/Scrolling.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Layout.qml + easyshapes_app/Gui/Pages/Project/Sidebar/Text/Layout.qml + easyshapes_app/Gui/Pages/Report/Layout.qml + easyshapes_app/Gui/Pages/Report/MainArea/Summary.qml + easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Groups/Export.qml + easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Layout.qml + easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml + easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml + easyshapes_app/Gui/Pages/SampleModel/Layout.qml + easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group1.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group4.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml + easyshapes_app/Gui/Pages/Toolbox/Layout.qml + easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml + easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml + easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml + easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml + easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml + easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml + easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml + easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml + easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml + easyshapes_app/Gui/qmldir + easyshapes_app/Gui/StatusBar.qml + easyshapes_app/main.qml + + \ No newline at end of file diff --git a/src/easyshapes_app/Gui/ApplicationWindow.qml b/src/easyshapes_app/Gui/ApplicationWindow.qml index 3996326..c91a5f5 100644 --- a/src/easyshapes_app/Gui/ApplicationWindow.qml +++ b/src/easyshapes_app/Gui/ApplicationWindow.qml @@ -76,8 +76,8 @@ EaComponents.ApplicationWindow { id: sampleModelButton enabled: false fontIcon: 'puzzle-piece' - text: qsTr('Sample model') - ToolTip.text: qsTr('Sample model definition page') + text: qsTr('Sample Model') + ToolTip.text: qsTr('Sample Model definition page') Component.onCompleted: { Globals.References.applicationWindow.appBarCentralTabs.sampleModelButton = sampleModelButton } diff --git a/src/easyshapes_app/Gui/Globals/References.qml b/src/easyshapes_app/Gui/Globals/References.qml index 53be81c..4c0fb98 100644 --- a/src/easyshapes_app/Gui/Globals/References.qml +++ b/src/easyshapes_app/Gui/Globals/References.qml @@ -34,6 +34,16 @@ QtObject { } } }, + 'samplemodel': { + 'sidebar': { + 'basic': { + 'popups': { + 'LoadExistingModel': null, + 'CreateNewModel': null + } + } + } + }, 'analysis': { 'sidebar': { 'basic': { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml new file mode 100644 index 0000000..5cf49c2 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +//import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Logic as EaLogic + +import Gui.Globals as Globals + +EaElements.GroupColumn { + + EaElements.TextArea { + placeholderText: 'Sample Model was not loaded.' + enabled: false + } + + Grid { + + columns: 2 + spacing: EaStyle.Sizes.fontPixelSize + + // button 1 + EaElements.SideBarButton { + fontIcon: 'upload' + text: qsTr('Load an existing model') + + onClicked: { + console.debug(`Clicking '${text}' button ::: ${this}`) + Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel.open() + } + + Loader { + source: '../Popups/LoadExistingModel.qml' + } + } + // button 1 + + + // button 2 + EaElements.SideBarButton { + fontIcon: 'plus-circle' + text: qsTr('Create a new model') + + onClicked: { + console.debug(`Clicking '${text}' button ::: ${this}`) + Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel.open() + } + + Loader { + source: '../Popups/CreateNewModel.qml' + } + } + // button 2 + + + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index d13c364..7d94fd8 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -13,6 +13,14 @@ import Gui.Globals as Globals EaComponents.SideBarColumn { + EaElements.GroupBox { + title: qsTr('Add a Model') + icon: 'vial' + collapsed: false + + Loader { source: 'Groups/SampleModel.qml' } + } + EaElements.GroupBox { title: qsTr('Group 1') icon: 'rocket' diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml new file mode 100644 index 0000000..68ee4e4 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Elements as EaElements + +import Gui.Globals as Globals + + + +EaElements.Dialog{ + +    Column { +        EaElements.Label { +            enabled: false +            text: qsTr("Name3") +        } + +        EaElements.Label { +            text: qsTr("Name4") +        } +    } + +    Component.onCompleted: { +        Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel = this +    } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml new file mode 100644 index 0000000..b5947eb --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Elements as EaElements + +import Gui.Globals as Globals + + + +EaElements.Dialog{ + +    Column { +        EaElements.Label { +            enabled: false +            text: qsTr("Name") +        } + +        EaElements.Label { +            text: qsTr("Name 2") +        } +    } + +    Component.onCompleted: { +        Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel = this +    } + +} From 99888620e66a3c5dffa9fd874c86f2bb24c0e4b8 Mon Sep 17 00:00:00 2001 From: Andrew Sazonov Date: Wed, 18 Feb 2026 19:08:46 +0100 Subject: [PATCH 02/32] Replace text area with table view in SampleModel --- .../Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index 5cf49c2..3dfaee1 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -16,9 +16,9 @@ import Gui.Globals as Globals EaElements.GroupColumn { - EaElements.TextArea { - placeholderText: 'Sample Model was not loaded.' - enabled: false + EaComponents.TableView { + defaultInfoText: qsTr("No models defined") + header: EaComponents.TableViewHeader {} } Grid { From 63532025a377fefddda11f57601ebb5d05424a13 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Thu, 19 Feb 2026 17:06:21 +0100 Subject: [PATCH 03/32] Added a sample model name field in popup --- .../Sidebar/Basic/Popups/CreateNewModel.qml | 52 +++++++++++++++---- 1 file changed, 43 insertions(+), 9 deletions(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml index 68ee4e4..45b1844 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -7,6 +7,7 @@ import QtQuick.Controls import QtQuick.Dialogs import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle import EasyApp.Gui.Components as EaComponents import EasyApp.Gui.Elements as EaElements @@ -16,19 +17,52 @@ import Gui.Globals as Globals EaElements.Dialog{ -    Column { -        EaElements.Label { -            enabled: false -            text: qsTr("Name3") -        } + property alias sampleModelName: sampleModelNameField.text -        EaElements.Label { -            text: qsTr("Name4") -        } -    } + title: qsTr("Create a new Sample Model") + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + + standardButtons: Dialog.Ok + + Column { + spacing: EaStyle.Sizes.fontPixelSize + Column { + EaElements.Label { + enabled: false + text: qsTr("Name") + } + + EaElements.TextField { + id: sampleModelNameField + + implicitWidth: inputFieldWidth + horizontalAlignment: TextInput.AlignLeft + validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } + placeholderText: qsTr("Enter Sample Model name here") + + Keys.onReturnPressed: dialog.accept() + Keys.onEnterPressed: dialog.accept() + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Name3") + } + + EaElements.Label { + text: qsTr("Name4") + } + } + + }     Component.onCompleted: {         Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel = this     } + + } From 2a5c8cd915f7dc24cb73d7c098c67b1a4b5f9a23 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Fri, 20 Feb 2026 16:37:00 +0100 Subject: [PATCH 04/32] Redefined a samplemodel creation popup --- src/easyshapes_app.qrc | 1 + .../Sidebar/Basic/Popups/CreateNewModel.qml | 24 ++++++++-------- src/easyshapes_app/test.qml | 28 +++++++++++++++++++ 3 files changed, 42 insertions(+), 11 deletions(-) create mode 100644 src/easyshapes_app/test.qml diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index 7de6409..b6901a2 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -162,5 +162,6 @@ easyshapes_app/Gui/qmldir easyshapes_app/Gui/StatusBar.qml easyshapes_app/main.qml + easyshapes_app/test.qml \ No newline at end of file diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml index 45b1844..c3dccff 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -14,8 +14,8 @@ import EasyApp.Gui.Elements as EaElements import Gui.Globals as Globals - EaElements.Dialog{ + id: sampleModelCreationDialog property alias sampleModelName: sampleModelNameField.text @@ -26,7 +26,9 @@ EaElements.Dialog{ standardButtons: Dialog.Ok Column { + spacing: EaStyle.Sizes.fontPixelSize + Column { EaElements.Label { enabled: false @@ -36,10 +38,10 @@ EaElements.Dialog{ EaElements.TextField { id: sampleModelNameField - implicitWidth: inputFieldWidth + implicitWidth: sampleModelCreationDialog.inputFieldWidth horizontalAlignment: TextInput.AlignLeft validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } - placeholderText: qsTr("Enter Sample Model name here") + placeholderText: qsTr("(optional) Enter Sample Model name here") Keys.onReturnPressed: dialog.accept() Keys.onEnterPressed: dialog.accept() @@ -49,20 +51,20 @@ EaElements.Dialog{ Column { EaElements.Label { enabled: false - text: qsTr("Name3") + text: qsTr("Choose the underlying structure type") } - EaElements.Label { - text: qsTr("Name4") + EaElements.ComboBox { + implicitWidth: sampleModelCreationDialog.inputFieldWidth + model: ["Ring", "Ball", "Vesicle", "Rod", "Bilayer", "Monolayer", "Lattice"] } } } -    Component.onCompleted: { -        Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel = this -    } - - + Component.onCompleted: { + Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel = this + } } + diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml new file mode 100644 index 0000000..4abf65b --- /dev/null +++ b/src/easyshapes_app/test.qml @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Elements as EaElements + +import Gui.Globals as Globals + +ApplicationWindow{ + + width: 800 + height: 600 + visible: true + + Loader { + source: 'Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml' + } + + Component.onCompleted: Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel.open() + +} From 7d85dd34489ec0234458bdb1ee8fb4b76fc694b3 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 25 Mar 2026 23:08:50 +0100 Subject: [PATCH 05/32] added a new button for saving samples --- src/easyshapes_app/Gui/ApplicationWindow.qml | 2 +- .../Sidebar/Basic/Groups/SampleModel.qml | 103 ++++++++++- .../Sidebar/Basic/Popups/CreateNewModel.qml | 39 +++- .../Basic/Popups/LoadExistingModel.qml | 170 +++++++++++++++++- src/easyshapes_app/test.qml | 162 ++++++++++++++++- 5 files changed, 454 insertions(+), 22 deletions(-) diff --git a/src/easyshapes_app/Gui/ApplicationWindow.qml b/src/easyshapes_app/Gui/ApplicationWindow.qml index c91a5f5..029c1d7 100644 --- a/src/easyshapes_app/Gui/ApplicationWindow.qml +++ b/src/easyshapes_app/Gui/ApplicationWindow.qml @@ -75,7 +75,7 @@ EaComponents.ApplicationWindow { EaElements.AppBarTabButton { id: sampleModelButton enabled: false - fontIcon: 'puzzle-piece' + fontIcon: 'layer-group' text: qsTr('Sample Model') ToolTip.text: qsTr('Sample Model definition page') Component.onCompleted: { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index 3dfaee1..c273688 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -15,21 +15,96 @@ import EasyApp.Gui.Logic as EaLogic import Gui.Globals as Globals EaElements.GroupColumn { + property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 EaComponents.TableView { + id: loadedSampleModelTableView + clip: true + // anchors.fill: parent + defaultInfoText: qsTr("No models defined") - header: EaComponents.TableViewHeader {} + header: EaComponents.TableViewHeader { + EaComponents.TableViewLabel { + id: modelNameColumnName + width: EaStyle.Sizes.fontPixelSize * 8 + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewLabel { + id: modelTypeColumnName + width: EaStyle.Sizes.fontPixelSize * 6 + text: qsTr("Type") + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewLabel { + id: modelDescrColumnName + width: EaStyle.Sizes.fontPixelSize * 24 + text: qsTr("Description") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: ListModel { + id: loadedSambleModelsModel + } + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.TableViewDelegate { + required property int index + required property string name + required property string structure_type + required property string description + + EaComponents.TableViewTextInput { + id: modelNameColumn + width: EaStyle.Sizes.fontPixelSize * 12 + text: name + onEditingFinished: name = text + leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 + } + + EaComponents.TableViewComboBox{ + id: structureTypeColumn + width: EaStyle.Sizes.fontPixelSize * 6 + model: ["Ring", "Ball", "Vesicle", "Rod", "Bilayer", "Monolayer", "Lattice"] + + Component.onCompleted: { + currentIndex = model.indexOf(structure_type) + } + + onActivated: (index) => { + structure_type = model[index] + } + } + + EaComponents.TableViewTextInput { + id: descrColumn + width: EaStyle.Sizes.fontPixelSize * 20 + text: description + onEditingFinished: { + description = text + console.log("descrColumn onEditingFinished") + } + onAccepted: { + console.log("descrColumn onAccepted") + } + } + } } Grid { - - columns: 2 + columns: 3 spacing: EaStyle.Sizes.fontPixelSize + // button 1 EaElements.SideBarButton { fontIcon: 'upload' text: qsTr('Load an existing model') + width: buttonWidth onClicked: { console.debug(`Clicking '${text}' button ::: ${this}`) @@ -38,6 +113,10 @@ EaElements.GroupColumn { Loader { source: '../Popups/LoadExistingModel.qml' + onLoaded: { + item.targetModel = loadedSambleModelsModel + Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel = item + } } } // button 1 @@ -47,6 +126,7 @@ EaElements.GroupColumn { EaElements.SideBarButton { fontIcon: 'plus-circle' text: qsTr('Create a new model') + width: buttonWidth onClicked: { console.debug(`Clicking '${text}' button ::: ${this}`) @@ -55,11 +135,28 @@ EaElements.GroupColumn { Loader { source: '../Popups/CreateNewModel.qml' + onLoaded: { + item.targetModel = loadedSambleModelsModel + Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel = item + } } } // button 2 + // button 3 + EaElements.SideBarButton { + id: saveModelButton + fontIcon: 'download' + text: qsTr('Save the model') + width: buttonWidth + enabled: loadedSampleModelTableView.model ? loadedSampleModelTableView.model.count > 0 : false + onClicked: { + console.debug(`Clicking '${text}' button ::: ${this}`) + Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel.availableModelsModel.append(loadedSambleModelsModel.get(0)) + } + } + // button 3 } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml index c3dccff..9502ee3 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -17,13 +17,28 @@ import Gui.Globals as Globals EaElements.Dialog{ id: sampleModelCreationDialog - property alias sampleModelName: sampleModelNameField.text + property var targetModel title: qsTr("Create a new Sample Model") property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - standardButtons: Dialog.Ok + standardButtons: Dialog.Ok // | Dialog.Cancel + + onAccepted: { + console.log("Ok Clicked") + + targetModel.clear() + targetModel.append( + { + name: sampleModelNameField.text, + structure_type: sampleModelTypeField.currentText, + description: sampleModelDescrField.text + } + ) + } + onRejected: console.log("Cancel Clicked") + onOpened: console.log("Opened CreateNewModel Clicked") Column { @@ -42,9 +57,6 @@ EaElements.Dialog{ horizontalAlignment: TextInput.AlignLeft validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } placeholderText: qsTr("(optional) Enter Sample Model name here") - - Keys.onReturnPressed: dialog.accept() - Keys.onEnterPressed: dialog.accept() } } @@ -55,11 +67,28 @@ EaElements.Dialog{ } EaElements.ComboBox { + id: sampleModelTypeField implicitWidth: sampleModelCreationDialog.inputFieldWidth model: ["Ring", "Ball", "Vesicle", "Rod", "Bilayer", "Monolayer", "Lattice"] } } + Column { + EaElements.Label { + enabled: false + text: qsTr("Description") + } + + EaElements.TextField { + id: sampleModelDescrField + + implicitWidth: sampleModelCreationDialog.inputFieldWidth + horizontalAlignment: TextInput.AlignLeft + validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } + placeholderText: qsTr("(optional) Enter Sample Model description here") + } + } + } Component.onCompleted: { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml index b5947eb..dd6570e 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml @@ -9,26 +9,180 @@ import QtQuick.Dialogs import EasyApp.Gui.Globals as EaGlobals import EasyApp.Gui.Components as EaComponents import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Style as EaStyle import Gui.Globals as Globals EaElements.Dialog{ + id: sampleModelLoadDialog + + property var targetModel + + title: qsTr("Load a Sample Model from the Asset Library") + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + + property alias availableModelsModel: availableSambleModelsModel + + standardButtons: Dialog.Ok // | Dialog.Cancel + //leftPadding: 40 + + onAccepted: { + console.log("Ok Clicked") + + var indexes = selectionModel.selectedIndexes + + if (indexes.length > 0) { + var row = indexes[0].row + var item = availableSambleModelsModel.get(row) + + console.log("Selected SampleModel: ", item.name) + + targetModel.clear() + targetModel.append(item) + selectionModel.clear() + } + } + onRejected: { + console.log("Cancel Clicked") + selectionModel.clear() + }     Column { + +         EaElements.Label {             enabled: false -            text: qsTr("Name") +            text: qsTr("Available in the Asset Library")         } -        EaElements.Label { -            text: qsTr("Name 2") -        } -    } + EaComponents.TableView { + clip: true + id: loadModelTableView + defaultInfoText: qsTr("No models found") -    Component.onCompleted: { -        Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel = this -    } + header: EaComponents.TableViewHeader { + EaComponents.TableViewLabel { + id: modelNameColumnName + width: EaStyle.Sizes.fontPixelSize * 10 + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 + } + + EaComponents.TableViewLabel { + id: modelTypeColumnName + width: EaStyle.Sizes.fontPixelSize * 6 + text: qsTr("Type") + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewLabel { + id: modelDescrColumnName + width: EaStyle.Sizes.fontPixelSize * 22 + text: qsTr("Description") + color: EaStyle.Colors.themeForegroundMinor + } + } + model: ListModel { + id: availableSambleModelsModel + ListElement { name: "Samle1_aluv"; structure_type: "Vesicle"; description: "In order to avoid a prolonged pro-inflammatory neutrophil response, signaling downstream of an agonist-activated G protein-coupled receptor (GPCR) has to be rapidly terminated. Among the family of GPCR kinases (GRKs) that regulate receptor phosphorylation and signaling termination, GRK2, which is highly expressed by immune cells, plays an important role." } + ListElement { name: "Sample2_nanodisc"; structure_type: "Ring"; description: "The medium chain fatty acid receptor GPR84 as well as formyl peptide receptor 2 (FPR2)" } + ListElement { name: "Sample3_cubosome"; structure_type: "Lattice"; description: "receptors expressed in neutrophils, play a key role in regulating inflammation. In this study, we investigated the effects of GRK2 inhibitors on neutrophil functions induced by GPR84 and FPR2 agonists." } + ListElement { name: "Sample4"; structure_type: "Ring"; description: "GRK2 was shown to be expressed in human neutrophils and analysis of subcellular fractions" } + ListElement { name: "Sample5"; structure_type: "Ball"; description: "revealed a cytosolic localization. The GRK2 inhibitors enhanced and prolonged neutrophil production " } + ListElement { name: "Sample6"; structure_type: "Vesicle"; description: "production of reactive oxygen species (ROS) induced by GPR84- but not FPR2-agonists" } + ListElement { name: "Sample7"; structure_type: "Rod"; description: "suggesting a receptor selective function of GRK2. This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84" } + ListElement { name: "Sample8"; structure_type: "Bilayer"; description: "This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84 agonist was not affected by the GRK2 inhibitor." } + ListElement { name: "Sample9"; structure_type: "Monolayer"; description: "Termination of this β-arrestin independent response relied, similar to the response induced by FPR2 agonists, primarily on the actin cytoskeleton." } + ListElement { name: "Samplewithareallylongname"; structure_type: "Lattice"; description: "In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils." } + } + + property var itemSelectionModel: ItemSelectionModel { + id: selectionModel + model: availableSambleModelsModel // ListView.view.model + // model: ListView.view.model + } + + property bool activeSelection: true + // property var itemSelectionModel: activeSelection ? _selectionModel : null + + // function isItemSelected(index) { + // return itemSelectionModel && itemSelectionModel.isSelected( + // itemSelectionModel.model.index(index, 0) + // ) + // } + + delegate: EaComponents.TableViewDelegate { + + required property int index + required property string name + required property string structure_type + required property string description + + // color: { + // if (!ListView.view.highlightSelection) { + // return "whatever I want" + // } + + // const selected = ListView.view.isItemSelected(index) + + // return selected + // ? EaStyle.Colors.themeAccentMinor + // : EaStyle.Colors.themeBackgroundHovered1 + // } + + color: { + if (!ListView.view.activeSelection) { + return index % 2 ? + EaStyle.Colors.themeBackgroundHovered2 : + EaStyle.Colors.themeBackgroundHovered1 + } + + ListView.view.itemSelectionModel.selection // create dependency + + return ListView.view.itemSelectionModel.isSelected( + ListView.view.itemSelectionModel.model.index(index, 0) + ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 + } + + // color: { + // ListView.view.itemSelectionModel.selection // create dependency + // return ListView.view.itemSelectionModel.isSelected( + // ListView.view.itemSelectionModel.model.index(index, 0) + // ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 + // } + + EaComponents.TableViewLabel { + id: modelNameColumn + width: EaStyle.Sizes.fontPixelSize * 10 + text: name + leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 + } + + EaComponents.TableViewLabel { + id: typeColumn + width: EaStyle.Sizes.fontPixelSize * 6 + text: structure_type + } + + EaComponents.TableViewLabel { + id: descrColumn + width: EaStyle.Sizes.fontPixelSize * 22 + text: description + } + + mouseArea.onPressed: (mouse) => { + let idx = ListView.view.itemSelectionModel.model.index(index, 0) + ListView.view.itemSelectionModel.select( + idx, + ItemSelectionModel.ClearAndSelect + ) + } + } + } +    } } diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index 4abf65b..d452593 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -10,19 +10,171 @@ import EasyApp.Gui.Globals as EaGlobals import EasyApp.Gui.Style as EaStyle import EasyApp.Gui.Components as EaComponents import EasyApp.Gui.Elements as EaElements +import Qt.labs.qmlmodels import Gui.Globals as Globals ApplicationWindow{ - width: 800 - height: 600 + width: 640 + height: 580 + color: EaStyle.Colors.contentBackground visible: true - Loader { - source: 'Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml' + Grid { + spacing: EaStyle.Sizes.fontPixelSize * 4 + leftPadding: 12 + + + Column { + width: 600 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.ComboBox { + model: [qsTr("Light"), qsTr("Dark"), qsTr("System")] + onActivated: { + if (currentIndex === 0) + EaStyle.Colors.theme = EaStyle.Colors.LightTheme + else if (currentIndex === 1) + EaStyle.Colors.theme = EaStyle.Colors.DarkTheme + else if (currentIndex === 2) + EaStyle.Colors.theme = EaStyle.Colors.SystemTheme + } + Component.onCompleted: { + if (EaStyle.Colors.theme === EaStyle.Colors.LightTheme) + currentIndex = 0 + else if (EaStyle.Colors.theme === EaStyle.Colors.DarkTheme) + currentIndex = 1 + else if (EaStyle.Colors.theme === EaStyle.Colors.SystemTheme) + currentIndex = 2 + } + } + + // groubox to test the element + EaElements.GroupBox { + title: qsTr('Test a group widget') + icon: 'wrench' + collapsed: false + + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml'} + } + + // Loader { + // source: 'Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml' + // } + + EaComponents.TableView { + id: whatverTable + defaultInfoText: qsTr("No models found") + + + header: EaComponents.TableViewHeader { + EaComponents.TableViewLabel { + id: modelNameColumnName + width: whatverTable.width * 0.25 + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 + } + + EaComponents.TableViewLabel { + id: modelTypeColumnName + width: whatverTable.width * 0.15 + text: qsTr("Type") + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewLabel { + id: modelDescrColumnName + width: whatverTable.width * 0.6 + text: qsTr("Description") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: ListModel { + id: availableSambleModelsModel + ListElement { name: "Samle1_aluv"; structure_type: "Vesicle"; description: "In order to avoid a prolonged pro-inflammatory neutrophil response, signaling downstream of an agonist-activated G protein-coupled receptor (GPCR) has to be rapidly terminated. Among the family of GPCR kinases (GRKs) that regulate receptor phosphorylation and signaling termination, GRK2, which is highly expressed by immune cells, plays an important role." } + ListElement { name: "Sample2_nanodisc"; structure_type: "Ring"; description: "The medium chain fatty acid receptor GPR84 as well as formyl peptide receptor 2 (FPR2)" } + ListElement { name: "Sample3_cubosome"; structure_type: "Lattice"; description: "receptors expressed in neutrophils, play a key role in regulating inflammation. In this study, we investigated the effects of GRK2 inhibitors on neutrophil functions induced by GPR84 and FPR2 agonists." } + ListElement { name: "Sample4"; structure_type: "Ring"; description: "GRK2 was shown to be expressed in human neutrophils and analysis of subcellular fractions" } + ListElement { name: "Sample5"; structure_type: "Ball"; description: "revealed a cytosolic localization. The GRK2 inhibitors enhanced and prolonged neutrophil production " } + ListElement { name: "Sample6"; structure_type: "Vesicle"; description: "production of reactive oxygen species (ROS) induced by GPR84- but not FPR2-agonists" } + ListElement { name: "Sample7"; structure_type: "Rod"; description: "suggesting a receptor selective function of GRK2. This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84" } + ListElement { name: "Sample8"; structure_type: "Bilayer"; description: "This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84 agonist was not affected by the GRK2 inhibitor." } + ListElement { name: "Sample9"; structure_type: "Monolayer"; description: "Termination of this β-arrestin independent response relied, similar to the response induced by FPR2 agonists, primarily on the actin cytoskeleton." } + ListElement { name: "Samplewithareallylongname"; structure_type: "Lattice"; description: "In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils." } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "end1"; structure_type: "end2"; description: "end3" } + } + + delegate: EaComponents.TableViewDelegate { + required property int index + required property string name + required property string structure_type + required property string description + + EaComponents.TableViewLabel { + id: modelNameColumn + width: whatverTable.width * 0.25 + text: name + } + + EaComponents.TableViewLabel { + id: typeColumn + width: whatverTable.width * 0.15 + text: structure_type + } + + EaComponents.TableViewLabel { + id: descrColumn + width: whatverTable.width * 0.58 + text: description + } + } + + // ScrollBar.vertical: EaElements.ScrollBar { + // policy: ScrollBar.AlwaysOn // ScrollBar.AsNeeded // AlwaysOn + // snapToHeader: true + // } + + // ScrollIndicator.vertical: EaElements.ScrollIndicator {} + } + } } - Component.onCompleted: Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel.open() + + + //Component.onCompleted: Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel.open() } From d4ef5a6c8944059e7c387eafe7bed0c285ef43c1 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 22 Apr 2026 11:51:20 +0200 Subject: [PATCH 06/32] updates to mock group components --- src/easyshapes_app.qrc | 12 +- src/easyshapes_app/Gui/Globals/References.qml | 5 +- .../Sidebar/Basic/Groups/Components.qml | 172 +++++++++++++++ .../Sidebar/Basic/Groups/Group4.qml | 18 -- .../Sidebar/Basic/Groups/SampleModel.qml | 67 ++---- .../SampleModel/Sidebar/Basic/Layout.qml | 16 +- .../Basic/Popups/CreateNewComponent.qml | 165 ++++++++++++++ .../Sidebar/Basic/Popups/CreateNewModel.qml | 53 +++-- .../Basic/Popups/LoadExistingComponent.qml | 206 ++++++++++++++++++ .../Sidebar/Basic/Popups/OpenAssetFile.qml | 30 +++ src/easyshapes_app/test.qml | 45 ++-- src/easyshapes_app/test2.qml | 166 ++++++++++++++ 12 files changed, 835 insertions(+), 120 deletions(-) create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group4.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml create mode 100644 src/easyshapes_app/test2.qml diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index b6901a2..f16d733 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -6,9 +6,11 @@ ../../EasyApp/src/EasyApp/Gui/Animations/TranslationChange.qml ../../EasyApp/src/EasyApp/Gui/Charts/ChartViewHeatmap2dPlotly.qml ../../EasyApp/src/EasyApp/Gui/Charts/ChartViewSimple1dPlotly.qml + ../../EasyApp/src/EasyApp/Gui/Charts/Plotly1dBarPlot.qml ../../EasyApp/src/EasyApp/Gui/Charts/Plotly1dLine.qml ../../EasyApp/src/EasyApp/Gui/Charts/Plotly1dMeasVsCalc.qml ../../EasyApp/src/EasyApp/Gui/Charts/Plotly2dHeatmap.qml + ../../EasyApp/src/EasyApp/Gui/Charts/Plotly2dPolarHeatmap.qml ../../EasyApp/src/EasyApp/Gui/Charts/Plotly3dScatter.qml ../../EasyApp/src/EasyApp/Gui/Charts/Plotly3dSurface.qml ../../EasyApp/src/EasyApp/Gui/Charts/qmldir @@ -27,6 +29,10 @@ ../../EasyApp/src/EasyApp/Gui/Components/GuideWindow.qml ../../EasyApp/src/EasyApp/Gui/Components/GuideWindowContainer.qml ../../EasyApp/src/EasyApp/Gui/Components/JsonListModel.qml + ../../EasyApp/src/EasyApp/Gui/Components/ListView.qml + ../../EasyApp/src/EasyApp/Gui/Components/ListViewDelegate.qml + ../../EasyApp/src/EasyApp/Gui/Components/ListViewHeader.qml + ../../EasyApp/src/EasyApp/Gui/Components/ListViewTextInput.qml ../../EasyApp/src/EasyApp/Gui/Components/MainContent.qml ../../EasyApp/src/EasyApp/Gui/Components/PreferencesDialog.qml ../../EasyApp/src/EasyApp/Gui/Components/ProjectDescriptionDialog.qml @@ -141,14 +147,17 @@ easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Layout.qml easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group1.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group4.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml easyshapes_app/Gui/Pages/Toolbox/Layout.qml easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml @@ -163,5 +172,6 @@ easyshapes_app/Gui/StatusBar.qml easyshapes_app/main.qml easyshapes_app/test.qml + easyshapes_app/test2.qml \ No newline at end of file diff --git a/src/easyshapes_app/Gui/Globals/References.qml b/src/easyshapes_app/Gui/Globals/References.qml index 4c0fb98..3219729 100644 --- a/src/easyshapes_app/Gui/Globals/References.qml +++ b/src/easyshapes_app/Gui/Globals/References.qml @@ -39,7 +39,10 @@ QtObject { 'basic': { 'popups': { 'LoadExistingModel': null, - 'CreateNewModel': null + 'CreateNewModel': null, + 'LoadExistingComponent': null, + 'CreateNewComponent': null, + 'OpenAssetFile': null } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml new file mode 100644 index 0000000..875413f --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml @@ -0,0 +1,172 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +//import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Logic as EaLogic + +import Gui.Globals as Globals + +EaElements.GroupColumn { + property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + + EaComponents.ListView { + id: loadedComponents + + defaultInfoText: qsTr("Load or create components") + + multiSelection: true + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.fontPixelSize * 4, + EaStyle.Sizes.fontPixelSize * 4, + EaStyle.Sizes.fontPixelSize * 4, + EaStyle.Sizes.tableRowHeight + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Type") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Atoms") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + text: qsTr("Mint") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + text: qsTr("Mext") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: ListModel { + id: loadedComponentsModel + } + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string name + required property string component_type + required property int mint + required property int mext + required property int atoms + + EaComponents.TableViewLabel { + text: index + 1 + horizontalAlignment: Text.AlignHCenter + enabled: false + } + + EaComponents.ListViewTextInput { + id: modelNameColumn + text: name + onEditingFinished: name = text + } + + EaComponents.TableViewLabel { + text: component_type + enabled: false + } + + EaComponents.ListViewTextInput { + text: atoms + horizontalAlignment: Text.AlignHCenter + onEditingFinished: atoms = parseInt(text) + validator: IntValidator { bottom: 0 } + } + + EaComponents.ListViewTextInput { + text: mint + horizontalAlignment: Text.AlignHCenter + onEditingFinished: mint = parseInt(text) + validator: IntValidator { bottom: 0 } + } + + EaComponents.ListViewTextInput { + text: mext + horizontalAlignment: Text.AlignHCenter + onEditingFinished: mext = parseInt(text) + validator: IntValidator { bottom: 0 } + } + + EaComponents.TableViewButton { + id: deleteRowColumn + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this component") + onClicked: loadedComponentsModel.remove(index) + } + } + } + + Grid { + columns: 3 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + fontIcon: 'upload' + text: qsTr('Load component(s)') + width: buttonWidth + onClicked: loadExistingComponentLoader.item.open() + } + + EaElements.SideBarButton { + fontIcon: 'plus-circle' + text: qsTr('Create component') + width: buttonWidth + onClicked: createNewComponentLoader.item.open() + } + + EaElements.SideBarButton { + id: saveModelButton + fontIcon: 'download' + text: qsTr('Save component(s)') + width: buttonWidth + enabled: loadedComponents.model ? loadedComponents.model.count > 0 : false + onClicked: loadExistingComponentLoader.item.availableComponentsModel.append(loadedComponentsModel.get(0)) + } + } + + Loader { + id: loadExistingComponentLoader + source: '../Popups/LoadExistingComponent.qml' + onLoaded: item.targetModel = loadedComponentsModel + } + + Loader { + id: createNewComponentLoader + source: '../Popups/CreateNewComponent.qml' + onLoaded: item.targetModel = loadedComponentsModel + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group4.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group4.qml deleted file mode 100644 index f2e5c14..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group4.qml +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn { - -} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index c273688..91d07fb 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -20,9 +20,8 @@ EaElements.GroupColumn { EaComponents.TableView { id: loadedSampleModelTableView clip: true - // anchors.fill: parent - defaultInfoText: qsTr("No models defined") + defaultInfoText: qsTr("Get started by loading or creating a model") header: EaComponents.TableViewHeader { EaComponents.TableViewLabel { id: modelNameColumnName @@ -58,7 +57,7 @@ EaElements.GroupColumn { required property string structure_type required property string description - EaComponents.TableViewTextInput { + EaComponents.ListViewTextInput { id: modelNameColumn width: EaStyle.Sizes.fontPixelSize * 12 text: name @@ -80,7 +79,7 @@ EaElements.GroupColumn { } } - EaComponents.TableViewTextInput { + EaComponents.ListViewTextInput { id: descrColumn width: EaStyle.Sizes.fontPixelSize * 20 text: description @@ -99,64 +98,40 @@ EaElements.GroupColumn { columns: 3 spacing: EaStyle.Sizes.fontPixelSize - - // button 1 EaElements.SideBarButton { fontIcon: 'upload' - text: qsTr('Load an existing model') + text: qsTr('Load model') width: buttonWidth - - onClicked: { - console.debug(`Clicking '${text}' button ::: ${this}`) - Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel.open() - } - - Loader { - source: '../Popups/LoadExistingModel.qml' - onLoaded: { - item.targetModel = loadedSambleModelsModel - Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel = item - } - } + onClicked: loadExistingModelLoader.item.open() } - // button 1 - - // button 2 EaElements.SideBarButton { fontIcon: 'plus-circle' - text: qsTr('Create a new model') + text: qsTr('Create model') width: buttonWidth - - onClicked: { - console.debug(`Clicking '${text}' button ::: ${this}`) - Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel.open() - } - - Loader { - source: '../Popups/CreateNewModel.qml' - onLoaded: { - item.targetModel = loadedSambleModelsModel - Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel = item - } - } + onClicked: createNewModelLoader.item.open() } - // button 2 - // button 3 EaElements.SideBarButton { id: saveModelButton fontIcon: 'download' - text: qsTr('Save the model') + text: qsTr('Save model') width: buttonWidth enabled: loadedSampleModelTableView.model ? loadedSampleModelTableView.model.count > 0 : false - - onClicked: { - console.debug(`Clicking '${text}' button ::: ${this}`) - Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel.availableModelsModel.append(loadedSambleModelsModel.get(0)) - } + onClicked: loadExistingModelLoader.item.availableModelsModel.append(loadedSambleModelsModel.get(0)) } - // button 3 + } + + Loader { + id: loadExistingModelLoader + source: '../Popups/LoadExistingModel.qml' + onLoaded: item.targetModel = loadedSambleModelsModel + } + + Loader { + id: createNewModelLoader + source: '../Popups/CreateNewModel.qml' + onLoaded: item.targetModel = loadedSambleModelsModel } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index 7d94fd8..420bc12 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -22,31 +22,31 @@ EaComponents.SideBarColumn { } EaElements.GroupBox { - title: qsTr('Group 1') - icon: 'rocket' + title: qsTr('Components') + icon: 'puzzle-piece' - Loader { source: 'Groups/Group1.qml' } + Loader { source: 'Groups/Components.qml' } } EaElements.GroupBox { - title: qsTr('Group 2') - icon: 'rocket' + title: qsTr('Solution') + icon: 'tint' Loader { source: 'Groups/Group2.qml' } } EaElements.GroupBox { title: qsTr('Group 3') - icon: 'rocket' + icon: 'grip-horizontal' Loader { source: 'Groups/Group3.qml' } } EaElements.GroupBox { - title: qsTr('Group 4') + title: qsTr('Group 1') icon: 'rocket' - Loader { source: 'Groups/Group4.qml' } + Loader { source: 'Groups/Group1.qml' } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml new file mode 100644 index 0000000..9d03539 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml @@ -0,0 +1,165 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.Dialog{ + id: componentCreationDialog + + property var targetModel + + title: qsTr("Create a new Component") + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + + standardButtons: Dialog.Ok // | Dialog.Cancel + + onAccepted: { + console.log("Ok Clicked") + + targetModel.append({ + name: componentNameField.text, + component_type: sampleModelTypeField.currentText, + atoms: 42, + mint: 0, + mext: 0 + }) + } + onRejected: console.log("Cancel Clicked") + onOpened: console.log("Opened CreateNewComponent Clicked") + + Component.onCompleted: { + Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewComponent = this + } + + Column { + + spacing: EaStyle.Sizes.fontPixelSize + + Row { + spacing: EaStyle.Sizes.fontPixelSize + + property int halfFieldWidth: (componentCreationDialog.inputFieldWidth - spacing) / 2 + + Column { + EaElements.Label { + enabled: false + text: qsTr("Name") + } + + EaElements.TextField { + id: componentNameField + + implicitWidth: parent.parent.halfFieldWidth + horizontalAlignment: TextInput.AlignLeft + validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } + placeholderText: qsTr("(optional) Enter Component name here") + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Component type") + } + + EaElements.ComboBox { + id: sampleModelTypeField + implicitWidth: parent.parent.halfFieldWidth + model: ["Other", "Lipid", "Surfactant"] + } + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("SMILES string") + } + + EaElements.TextField { + implicitWidth: componentCreationDialog.inputFieldWidth + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("(optional) Define component using SMILES") + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Paths") + } + + EaComponents.ListView { + id: filePaths + defaultInfoText: qsTr("No files added") + enabled: true + maxRowCountShow: 2 + width: componentCreationDialog.inputFieldWidth + + columnWidths: [ + -1, + EaStyle.Sizes.tableRowHeight + ] + + scrollBarInteractive: false + + model: ListModel { + id: selectedFilePathsModel + } + + delegate: EaComponents.ListViewDelegate { + required property int index + required property url path + + EaComponents.TableViewLabel { + id: pathColumn + text: path + elide: Text.ElideLeft + horizontalAlignment: Text.Alignleft + + leftPadding: EaStyle.Sizes.fontPixelSize * 0.5 + } + + EaComponents.TableViewButton { + id: deleteRowColumn + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this file") + onClicked: selectedFilePathsModel.remove(index) + } + } + } + } + Column { + EaElements.SideBarButton { + fontIcon: 'upload' + text: qsTr('Add files') + width: componentCreationDialog.width * 0.3164 + + onClicked: { + console.debug(`Clicking '${text}' button ::: ${this}`) + Globals.References.pages.samplemodel.sidebar.basic.popups.openAssetFile.open() + } + + Loader { + source: '../Popups/OpenAssetFile.qml' + onLoaded: { + item.targetModel = selectedFilePathsModel + } + } + } + } + } +} + diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml index 9502ee3..9b4ad62 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -44,32 +44,37 @@ EaElements.Dialog{ spacing: EaStyle.Sizes.fontPixelSize - Column { - EaElements.Label { - enabled: false - text: qsTr("Name") + Row { + id: nameTypeRow + spacing: EaStyle.Sizes.fontPixelSize + + Column { + EaElements.Label { + enabled: false + text: qsTr("Name") + } + + EaElements.TextField { + id: sampleModelNameField + + implicitWidth: (sampleModelCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 + horizontalAlignment: TextInput.AlignLeft + validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } + placeholderText: qsTr("(optional) Enter Sample Model name here") + } } - EaElements.TextField { - id: sampleModelNameField - - implicitWidth: sampleModelCreationDialog.inputFieldWidth - horizontalAlignment: TextInput.AlignLeft - validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } - placeholderText: qsTr("(optional) Enter Sample Model name here") - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("Choose the underlying structure type") - } - - EaElements.ComboBox { - id: sampleModelTypeField - implicitWidth: sampleModelCreationDialog.inputFieldWidth - model: ["Ring", "Ball", "Vesicle", "Rod", "Bilayer", "Monolayer", "Lattice"] + Column { + EaElements.Label { + enabled: false + text: qsTr("Structure type") + } + + EaElements.ComboBox { + id: sampleModelTypeField + implicitWidth: (sampleModelCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 + model: ["Ring", "Ball", "Vesicle", "Rod", "Bilayer", "Monolayer", "Lattice"] + } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml new file mode 100644 index 0000000..7010305 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Style as EaStyle + +import Gui.Globals as Globals + + + +EaElements.Dialog{ + id: sampleModelLoadDialog + + property var targetModel + + title: qsTr("Load Components from the Asset Library") + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + + property alias availableComponentsModel: availableComponentsModel + + standardButtons: Dialog.Ok // | Dialog.Cancel + + // Fields expected by targetModel (loadedComponentsModel in Components.qml): + // name, component_type, mint, mext, atoms + onAccepted: { + console.log("Ok Clicked") + + var indexes = selectionModel.selectedIndexes + + if (indexes.length > 0) { + var row = indexes[0].row + var item = availableComponentsModel.get(row) + + console.log("Selected Component: ", item.name) + + targetModel.append({ + name: item.name, + component_type: item.component_type, + mint: item.mint, + mext: item.mext, + atoms: item.atoms + }) + selectionModel.clear() + } + } + onRejected: { + console.log("Cancel Clicked") + selectionModel.clear() + } + + Column { + + + EaElements.Label { + enabled: false + text: qsTr("Available in the Asset Library") + } + + EaComponents.TableView { + clip: true + id: loadModelTableView + defaultInfoText: qsTr("No models found") + + header: EaComponents.TableViewHeader { + EaComponents.TableViewLabel { + id: modelNameColumnName + width: EaStyle.Sizes.fontPixelSize * 12 + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 + } + + EaComponents.TableViewLabel { + id: modelTypeColumnName + width: EaStyle.Sizes.fontPixelSize * 8 + text: qsTr("Type") + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewLabel { + id: modelAtomsColumnName + width: EaStyle.Sizes.fontPixelSize * 6 + text: qsTr("Atoms") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + + EaComponents.TableViewLabel { + id: modelMintColumnName + width: EaStyle.Sizes.fontPixelSize * 6 + text: qsTr("Mint") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + + EaComponents.TableViewLabel { + id: modelMextColumnName + width: EaStyle.Sizes.fontPixelSize * 6 + text: qsTr("Mext") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + } + + model: ListModel { + id: availableComponentsModel + ListElement { name: "DPPC"; component_type: "Lipid"; atoms: 130; mint: 0; mext: 130 } + ListElement { name: "DOPC"; component_type: "Lipid"; atoms: 138; mint: 0; mext: 138 } + ListElement { name: "POPC"; component_type: "Lipid"; atoms: 134; mint: 0; mext: 134 } + ListElement { name: "DMPC"; component_type: "Lipid"; atoms: 118; mint: 0; mext: 118 } + ListElement { name: "Cholesterol"; component_type: "Lipid"; atoms: 74; mint: 0; mext: 74 } + ListElement { name: "SDS"; component_type: "Surfactant"; atoms: 42; mint: 0; mext: 42 } + ListElement { name: "CTAB"; component_type: "Surfactant"; atoms: 62; mint: 0; mext: 62 } + ListElement { name: "Triton-X100"; component_type: "Surfactant"; atoms: 85; mint: 0; mext: 85 } + ListElement { name: "Tween-20"; component_type: "Surfactant"; atoms: 98; mint: 0; mext: 98 } + ListElement { name: "D2O-buffer"; component_type: "Other"; atoms: 3; mint: 0; mext: 3 } + } + + property var itemSelectionModel: ItemSelectionModel { + id: selectionModel + model: availableComponentsModel + } + + property bool activeSelection: true + + delegate: EaComponents.TableViewDelegate { + + required property int index + required property string name + required property string component_type + required property int atoms + required property int mint + required property int mext + + color: { + if (!ListView.view.activeSelection) { + return index % 2 ? + EaStyle.Colors.themeBackgroundHovered2 : + EaStyle.Colors.themeBackgroundHovered1 + } + + ListView.view.itemSelectionModel.selection // create dependency + + return ListView.view.itemSelectionModel.isSelected( + ListView.view.itemSelectionModel.model.index(index, 0) + ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 + } + + // color: { + // ListView.view.itemSelectionModel.selection // create dependency + // return ListView.view.itemSelectionModel.isSelected( + // ListView.view.itemSelectionModel.model.index(index, 0) + // ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 + // } + + EaComponents.TableViewLabel { + id: modelNameColumn + width: EaStyle.Sizes.fontPixelSize * 12 + text: name + leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 + } + + EaComponents.TableViewLabel { + id: typeColumn + width: EaStyle.Sizes.fontPixelSize * 8 + text: component_type + } + + EaComponents.TableViewLabel { + id: atomsColumn + width: EaStyle.Sizes.fontPixelSize * 6 + text: atoms + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + id: mintColumn + width: EaStyle.Sizes.fontPixelSize * 6 + text: mint + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + id: mextColumn + width: EaStyle.Sizes.fontPixelSize * 6 + text: mext + horizontalAlignment: Text.AlignHCenter + } + + mouseArea.onPressed: (mouse) => { + let idx = ListView.view.itemSelectionModel.model.index(index, 0) + ListView.view.itemSelectionModel.select( + idx, + ItemSelectionModel.ClearAndSelect + ) + } + } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml new file mode 100644 index 0000000..7af31d7 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Components as EaComponents + +import Gui.Globals as Globals + + +FileDialog{ + fileMode: FileDialog.OpenFiles + nameFilters: ['Any (*)', 'Structure files (*.gro *.pdb .*xyz)', 'Topology files (*.itp)', 'Smiles files (*.sml)'] + + property var targetModel + + onAccepted: { + for (let i = 0; i < selectedFiles.length; ++i) + targetModel.append({ path: selectedFiles[i] }) + } + + Component.onCompleted: { + Globals.References.pages.samplemodel.sidebar.basic.popups.openAssetFile = this + } + +} diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index d452593..52876a5 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -16,7 +16,7 @@ import Gui.Globals as Globals ApplicationWindow{ - width: 640 + width: 650 height: 580 color: EaStyle.Colors.contentBackground visible: true @@ -56,22 +56,27 @@ ApplicationWindow{ icon: 'wrench' collapsed: false - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml'} + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml'} } - // Loader { - // source: 'Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml' - // } - - EaComponents.TableView { + EaComponents.ListView { id: whatverTable defaultInfoText: qsTr("No models found") + enabled: true + + columnWidths: [ + whatverTable.width * 0.25, + whatverTable.width * 0.15, + -1, + ] + scrollBarPolicy: ScrollBar.AlwaysOn // ScrollBar.AsNeeded // ScrollBar.AlwaysOn + scrollBarInteractive: true - header: EaComponents.TableViewHeader { + header: EaComponents.ListViewHeader { EaComponents.TableViewLabel { id: modelNameColumnName - width: whatverTable.width * 0.25 + //width: whatverTable.width * 0.25 text: qsTr("Name") color: EaStyle.Colors.themeForegroundMinor leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 @@ -79,14 +84,14 @@ ApplicationWindow{ EaComponents.TableViewLabel { id: modelTypeColumnName - width: whatverTable.width * 0.15 + //width: whatverTable.width * 0.15 text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { id: modelDescrColumnName - width: whatverTable.width * 0.6 + //width: whatverTable.width * 0.6 text: qsTr("Description") color: EaStyle.Colors.themeForegroundMinor } @@ -138,37 +143,33 @@ ApplicationWindow{ ListElement { name: "end1"; structure_type: "end2"; description: "end3" } } - delegate: EaComponents.TableViewDelegate { + delegate: EaComponents.ListViewDelegate { required property int index required property string name required property string structure_type required property string description - EaComponents.TableViewLabel { + EaComponents.ListViewTextInput { id: modelNameColumn - width: whatverTable.width * 0.25 + //width: whatverTable.width * 0.25 text: name } EaComponents.TableViewLabel { id: typeColumn - width: whatverTable.width * 0.15 + //width: whatverTable.width * 0.15 text: structure_type + color: "#666" // new disabled } EaComponents.TableViewLabel { id: descrColumn - width: whatverTable.width * 0.58 + //width: whatverTable.width * 0.58 text: description + color: "#555" // old disabled } } - // ScrollBar.vertical: EaElements.ScrollBar { - // policy: ScrollBar.AlwaysOn // ScrollBar.AsNeeded // AlwaysOn - // snapToHeader: true - // } - - // ScrollIndicator.vertical: EaElements.ScrollIndicator {} } } } diff --git a/src/easyshapes_app/test2.qml b/src/easyshapes_app/test2.qml new file mode 100644 index 0000000..82de25c --- /dev/null +++ b/src/easyshapes_app/test2.qml @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Elements as EaElements +import Qt.labs.qmlmodels + +import Gui.Globals as Globals + +ApplicationWindow{ + + width: 650 + height: 580 + color: EaStyle.Colors.contentBackground + visible: true + + Grid { + spacing: EaStyle.Sizes.fontPixelSize * 4 + leftPadding: 12 + + + Column { + width: 600 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.ComboBox { + model: [qsTr("Light"), qsTr("Dark"), qsTr("System")] + onActivated: { + if (currentIndex === 0) + EaStyle.Colors.theme = EaStyle.Colors.LightTheme + else if (currentIndex === 1) + EaStyle.Colors.theme = EaStyle.Colors.DarkTheme + else if (currentIndex === 2) + EaStyle.Colors.theme = EaStyle.Colors.SystemTheme + } + Component.onCompleted: { + if (EaStyle.Colors.theme === EaStyle.Colors.LightTheme) + currentIndex = 0 + else if (EaStyle.Colors.theme === EaStyle.Colors.DarkTheme) + currentIndex = 1 + else if (EaStyle.Colors.theme === EaStyle.Colors.SystemTheme) + currentIndex = 2 + } + } + + EaComponents.TableView { + id: whatverTable + defaultInfoText: qsTr("No models found") + enabled: true + + header: EaComponents.TableViewHeader { + EaComponents.TableViewLabel { + id: modelNameColumnName + width: whatverTable.width * 0.25 + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 + } + + EaComponents.TableViewLabel { + id: modelTypeColumnName + width: whatverTable.width * 0.15 + text: qsTr("Type") + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewLabel { + id: modelDescrColumnName + width: whatverTable.width * 0.6 + text: qsTr("Description") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: ListModel { + id: availableSambleModelsModel + ListElement { name: "Samle1_aluv"; structure_type: "Vesicle"; description: "In order to avoid a prolonged pro-inflammatory neutrophil response, signaling downstream of an agonist-activated G protein-coupled receptor (GPCR) has to be rapidly terminated. Among the family of GPCR kinases (GRKs) that regulate receptor phosphorylation and signaling termination, GRK2, which is highly expressed by immune cells, plays an important role." } + ListElement { name: "Sample2_nanodisc"; structure_type: "Ring"; description: "The medium chain fatty acid receptor GPR84 as well as formyl peptide receptor 2 (FPR2)" } + ListElement { name: "Sample3_cubosome"; structure_type: "Lattice"; description: "receptors expressed in neutrophils, play a key role in regulating inflammation. In this study, we investigated the effects of GRK2 inhibitors on neutrophil functions induced by GPR84 and FPR2 agonists." } + ListElement { name: "Sample4"; structure_type: "Ring"; description: "GRK2 was shown to be expressed in human neutrophils and analysis of subcellular fractions" } + ListElement { name: "Sample5"; structure_type: "Ball"; description: "revealed a cytosolic localization. The GRK2 inhibitors enhanced and prolonged neutrophil production " } + ListElement { name: "Sample6"; structure_type: "Vesicle"; description: "production of reactive oxygen species (ROS) induced by GPR84- but not FPR2-agonists" } + ListElement { name: "Sample7"; structure_type: "Rod"; description: "suggesting a receptor selective function of GRK2. This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84" } + ListElement { name: "Sample8"; structure_type: "Bilayer"; description: "This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84 agonist was not affected by the GRK2 inhibitor." } + ListElement { name: "Sample9"; structure_type: "Monolayer"; description: "Termination of this β-arrestin independent response relied, similar to the response induced by FPR2 agonists, primarily on the actin cytoskeleton." } + ListElement { name: "Samplewithareallylongname"; structure_type: "Lattice"; description: "In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils." } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "1"; structure_type: "2"; description: "3" } + ListElement { name: "end1"; structure_type: "end2"; description: "end3" } + } + + delegate: EaComponents.TableViewDelegate { + required property int index + required property string name + required property string structure_type + required property string description + + EaComponents.TableViewLabel { + id: modelNameColumn + //width: whatverTable.width * 0.25 + text: name + } + + EaComponents.TableViewLabel { + id: typeColumn + //width: whatverTable.width * 0.15 + text: structure_type + } + + EaComponents.TableViewTextInput { + id: descrColumn + //width: whatverTable.width * 0.58 + text: description + } + } + + // ScrollBar.vertical: EaElements.ScrollBar { + // policy: ScrollBar.AlwaysOn // ScrollBar.AsNeeded // AlwaysOn + // } + + + } + } + } + + + + //Component.onCompleted: Globals.References.pages.samplemodel.sidebar.basic.popups.LoadExistingModel.open() + +} From 156173320b49c71daf5a75fd10250bd30d3a6cf4 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 22 Apr 2026 13:45:05 +0200 Subject: [PATCH 07/32] code cleanup --- .../Sidebar/Basic/Groups/Components.qml | 6 ++- .../Basic/Popups/CreateNewComponent.qml | 2 +- .../Sidebar/Basic/Popups/CreateNewModel.qml | 2 +- .../Basic/Popups/LoadExistingComponent.qml | 7 --- .../Basic/Popups/LoadExistingModel.qml | 46 ++++--------------- src/easyshapes_app/test.qml | 34 -------------- 6 files changed, 15 insertions(+), 82 deletions(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml index 875413f..0e1dd95 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml @@ -153,7 +153,11 @@ EaElements.GroupColumn { text: qsTr('Save component(s)') width: buttonWidth enabled: loadedComponents.model ? loadedComponents.model.count > 0 : false - onClicked: loadExistingComponentLoader.item.availableComponentsModel.append(loadedComponentsModel.get(0)) + onClicked: { + //loadExistingComponentLoader.item.availableComponentsModel.append(loadedComponentsModel.get(0)) + for (let i = 0; i < loadedComponentsModel.length; ++i) + loadExistingComponentLoader.item.availableComponentsModel.append(loadedComponentsModel.get(i)) + } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml index 9d03539..fc0e265 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml @@ -23,7 +23,7 @@ EaElements.Dialog{ property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - standardButtons: Dialog.Ok // | Dialog.Cancel + standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { console.log("Ok Clicked") diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml index 9b4ad62..67dab16 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -23,7 +23,7 @@ EaElements.Dialog{ property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - standardButtons: Dialog.Ok // | Dialog.Cancel + standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { console.log("Ok Clicked") diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml index 7010305..2f226da 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml @@ -154,13 +154,6 @@ EaElements.Dialog{ ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 } - // color: { - // ListView.view.itemSelectionModel.selection // create dependency - // return ListView.view.itemSelectionModel.isSelected( - // ListView.view.itemSelectionModel.model.index(index, 0) - // ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 - // } - EaComponents.TableViewLabel { id: modelNameColumn width: EaStyle.Sizes.fontPixelSize * 12 diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml index dd6570e..f1a77ec 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml @@ -26,8 +26,7 @@ EaElements.Dialog{ property alias availableModelsModel: availableSambleModelsModel - standardButtons: Dialog.Ok // | Dialog.Cancel - //leftPadding: 40 + standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { console.log("Ok Clicked") @@ -50,13 +49,11 @@ EaElements.Dialog{ selectionModel.clear() } -    Column { - - -        EaElements.Label { -            enabled: false -            text: qsTr("Available in the Asset Library") -        } + Column { + EaElements.Label { + enabled: false + text: qsTr("Available in the Asset Library") + } EaComponents.TableView { clip: true @@ -103,18 +100,10 @@ EaElements.Dialog{ property var itemSelectionModel: ItemSelectionModel { id: selectionModel - model: availableSambleModelsModel // ListView.view.model - // model: ListView.view.model + model: availableSambleModelsModel } property bool activeSelection: true - // property var itemSelectionModel: activeSelection ? _selectionModel : null - - // function isItemSelected(index) { - // return itemSelectionModel && itemSelectionModel.isSelected( - // itemSelectionModel.model.index(index, 0) - // ) - // } delegate: EaComponents.TableViewDelegate { @@ -123,18 +112,6 @@ EaElements.Dialog{ required property string structure_type required property string description - // color: { - // if (!ListView.view.highlightSelection) { - // return "whatever I want" - // } - - // const selected = ListView.view.isItemSelected(index) - - // return selected - // ? EaStyle.Colors.themeAccentMinor - // : EaStyle.Colors.themeBackgroundHovered1 - // } - color: { if (!ListView.view.activeSelection) { return index % 2 ? @@ -149,13 +126,6 @@ EaElements.Dialog{ ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 } - // color: { - // ListView.view.itemSelectionModel.selection // create dependency - // return ListView.view.itemSelectionModel.isSelected( - // ListView.view.itemSelectionModel.model.index(index, 0) - // ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 - // } - EaComponents.TableViewLabel { id: modelNameColumn width: EaStyle.Sizes.fontPixelSize * 10 @@ -184,5 +154,5 @@ EaElements.Dialog{ } } } -    } + } } diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index 52876a5..b7bc114 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -76,7 +76,6 @@ ApplicationWindow{ header: EaComponents.ListViewHeader { EaComponents.TableViewLabel { id: modelNameColumnName - //width: whatverTable.width * 0.25 text: qsTr("Name") color: EaStyle.Colors.themeForegroundMinor leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 @@ -84,14 +83,12 @@ ApplicationWindow{ EaComponents.TableViewLabel { id: modelTypeColumnName - //width: whatverTable.width * 0.15 text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { id: modelDescrColumnName - //width: whatverTable.width * 0.6 text: qsTr("Description") color: EaStyle.Colors.themeForegroundMinor } @@ -112,34 +109,6 @@ ApplicationWindow{ ListElement { name: "1"; structure_type: "2"; description: "3" } ListElement { name: "1"; structure_type: "2"; description: "3" } ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } ListElement { name: "end1"; structure_type: "end2"; description: "end3" } } @@ -151,20 +120,17 @@ ApplicationWindow{ EaComponents.ListViewTextInput { id: modelNameColumn - //width: whatverTable.width * 0.25 text: name } EaComponents.TableViewLabel { id: typeColumn - //width: whatverTable.width * 0.15 text: structure_type color: "#666" // new disabled } EaComponents.TableViewLabel { id: descrColumn - //width: whatverTable.width * 0.58 text: description color: "#555" // old disabled } From 86b901808866919878fc44184c149d22b136e962 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 22 Apr 2026 15:00:44 +0200 Subject: [PATCH 08/32] Implemented save button for components groupbox --- .../Pages/SampleModel/Sidebar/Basic/Groups/Components.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml index 0e1dd95..8b506ab 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml @@ -154,9 +154,11 @@ EaElements.GroupColumn { width: buttonWidth enabled: loadedComponents.model ? loadedComponents.model.count > 0 : false onClicked: { - //loadExistingComponentLoader.item.availableComponentsModel.append(loadedComponentsModel.get(0)) - for (let i = 0; i < loadedComponentsModel.length; ++i) - loadExistingComponentLoader.item.availableComponentsModel.append(loadedComponentsModel.get(i)) + const indexes = loadedComponents.selectedIndexes + for (let i = 0; i < indexes.length; ++i) + loadExistingComponentLoader.item.availableComponentsModel.append( + loadedComponentsModel.get(indexes[i].row) + ) } } } From 8d1ccf3bf1fe43c8c246a7b33107254108cd1ac6 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 22 Apr 2026 16:00:32 +0200 Subject: [PATCH 09/32] Fixes to the load components popup --- .../Sidebar/Basic/Groups/Components.qml | 3 +- .../Basic/Popups/LoadExistingComponent.qml | 105 ++++++------------ src/easyshapes_app/test.qml | 1 + 3 files changed, 39 insertions(+), 70 deletions(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml index 8b506ab..8c4c6de 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml @@ -14,14 +14,13 @@ import EasyApp.Gui.Logic as EaLogic import Gui.Globals as Globals + EaElements.GroupColumn { property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 EaComponents.ListView { id: loadedComponents - defaultInfoText: qsTr("Load or create components") - multiSelection: true columnWidths: [ diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml index 2f226da..c352c56 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml @@ -14,7 +14,6 @@ import EasyApp.Gui.Style as EaStyle import Gui.Globals as Globals - EaElements.Dialog{ id: sampleModelLoadDialog @@ -26,21 +25,14 @@ EaElements.Dialog{ property alias availableComponentsModel: availableComponentsModel - standardButtons: Dialog.Ok // | Dialog.Cancel + standardButtons: Dialog.Ok | Dialog.Cancel - // Fields expected by targetModel (loadedComponentsModel in Components.qml): - // name, component_type, mint, mext, atoms onAccepted: { - console.log("Ok Clicked") - - var indexes = selectionModel.selectedIndexes - - if (indexes.length > 0) { - var row = indexes[0].row + let selected = loadModelListView.selectedIndexes + for (let i = 0; i < selected.length; ++i) { + var row = selected[i].row var item = availableComponentsModel.get(row) - console.log("Selected Component: ", item.name) - targetModel.append({ name: item.name, component_type: item.component_type, @@ -48,62 +40,64 @@ EaElements.Dialog{ mext: item.mext, atoms: item.atoms }) - selectionModel.clear() } + loadModelListView.clearSelection() } onRejected: { - console.log("Cancel Clicked") - selectionModel.clear() + loadModelListView.clearSelection() } Column { - - EaElements.Label { enabled: false text: qsTr("Available in the Asset Library") } - EaComponents.TableView { - clip: true - id: loadModelTableView + EaComponents.ListView { + id: loadModelListView defaultInfoText: qsTr("No models found") - - header: EaComponents.TableViewHeader { + multiSelection: true + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + EaStyle.Sizes.fontPixelSize * 8, + EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.tableRowHeight, + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } EaComponents.TableViewLabel { id: modelNameColumnName - width: EaStyle.Sizes.fontPixelSize * 12 text: qsTr("Name") color: EaStyle.Colors.themeForegroundMinor - leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 } - EaComponents.TableViewLabel { id: modelTypeColumnName - width: EaStyle.Sizes.fontPixelSize * 8 text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel { id: modelAtomsColumnName - width: EaStyle.Sizes.fontPixelSize * 6 text: qsTr("Atoms") color: EaStyle.Colors.themeForegroundMinor horizontalAlignment: Text.AlignHCenter } - EaComponents.TableViewLabel { id: modelMintColumnName - width: EaStyle.Sizes.fontPixelSize * 6 text: qsTr("Mint") color: EaStyle.Colors.themeForegroundMinor horizontalAlignment: Text.AlignHCenter } - EaComponents.TableViewLabel { id: modelMextColumnName - width: EaStyle.Sizes.fontPixelSize * 6 text: qsTr("Mext") color: EaStyle.Colors.themeForegroundMinor horizontalAlignment: Text.AlignHCenter @@ -124,15 +118,9 @@ EaElements.Dialog{ ListElement { name: "D2O-buffer"; component_type: "Other"; atoms: 3; mint: 0; mext: 3 } } - property var itemSelectionModel: ItemSelectionModel { - id: selectionModel - model: availableComponentsModel - } - - property bool activeSelection: true - - delegate: EaComponents.TableViewDelegate { + delegateModelAccess: DelegateModel.ReadOnly + delegate: EaComponents.ListViewDelegate { required property int index required property string name required property string component_type @@ -140,58 +128,39 @@ EaElements.Dialog{ required property int mint required property int mext - color: { - if (!ListView.view.activeSelection) { - return index % 2 ? - EaStyle.Colors.themeBackgroundHovered2 : - EaStyle.Colors.themeBackgroundHovered1 - } - - ListView.view.itemSelectionModel.selection // create dependency - - return ListView.view.itemSelectionModel.isSelected( - ListView.view.itemSelectionModel.model.index(index, 0) - ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 + EaComponents.TableViewLabel { + text: index + 1 + horizontalAlignment: Text.AlignHCenter + enabled: false } - EaComponents.TableViewLabel { id: modelNameColumn - width: EaStyle.Sizes.fontPixelSize * 12 text: name - leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 } - EaComponents.TableViewLabel { id: typeColumn - width: EaStyle.Sizes.fontPixelSize * 8 text: component_type } - EaComponents.TableViewLabel { id: atomsColumn - width: EaStyle.Sizes.fontPixelSize * 6 text: atoms horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { id: mintColumn - width: EaStyle.Sizes.fontPixelSize * 6 text: mint horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { id: mextColumn - width: EaStyle.Sizes.fontPixelSize * 6 text: mext horizontalAlignment: Text.AlignHCenter } - - mouseArea.onPressed: (mouse) => { - let idx = ListView.view.itemSelectionModel.model.index(index, 0) - ListView.view.itemSelectionModel.select( - idx, - ItemSelectionModel.ClearAndSelect - ) + EaComponents.TableViewButton { + id: deleteRowColumn + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this component") + onClicked: availableComponentsModel.remove(index) } } } diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index b7bc114..13ba57d 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -74,6 +74,7 @@ ApplicationWindow{ scrollBarInteractive: true header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { id: modelNameColumnName text: qsTr("Name") From 87a265188fe8312e95b9052214ced1c89371dec2 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Mon, 27 Apr 2026 13:44:40 +0200 Subject: [PATCH 10/32] Basic components and samplemodel code cleanup --- src/easyshapes_app/Gui/ApplicationWindow.qml | 4 +- .../Sidebar/Basic/Groups/SampleModel.qml | 62 ++++-------- .../SampleModel/Sidebar/Basic/Layout.qml | 4 +- .../Basic/Popups/CreateNewComponent.qml | 24 +---- .../Sidebar/Basic/Popups/CreateNewModel.qml | 22 +---- .../Basic/Popups/LoadExistingComponent.qml | 24 +---- .../Basic/Popups/LoadExistingModel.qml | 95 ++++++------------- src/easyshapes_app/test.qml | 1 - 8 files changed, 67 insertions(+), 169 deletions(-) diff --git a/src/easyshapes_app/Gui/ApplicationWindow.qml b/src/easyshapes_app/Gui/ApplicationWindow.qml index 029c1d7..9e21918 100644 --- a/src/easyshapes_app/Gui/ApplicationWindow.qml +++ b/src/easyshapes_app/Gui/ApplicationWindow.qml @@ -75,8 +75,8 @@ EaComponents.ApplicationWindow { EaElements.AppBarTabButton { id: sampleModelButton enabled: false - fontIcon: 'layer-group' - text: qsTr('Sample Model') + fontIcon: 'layer-group' // 'layer-group' 'vials' 'vial' + text: qsTr('Sample Model') // qsTr('Model') ToolTip.text: qsTr('Sample Model definition page') Component.onCompleted: { Globals.References.applicationWindow.appBarCentralTabs.sampleModelButton = sampleModelButton diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index 91d07fb..7196bd4 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -17,29 +17,27 @@ import Gui.Globals as Globals EaElements.GroupColumn { property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 - EaComponents.TableView { - id: loadedSampleModelTableView - clip: true - + EaComponents.ListView { + id: loadedSampleModel defaultInfoText: qsTr("Get started by loading or creating a model") - header: EaComponents.TableViewHeader { + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 12, + EaStyle.Sizes.fontPixelSize * 6, + -1 + ] + + header: EaComponents.ListViewHeader { EaComponents.TableViewLabel { - id: modelNameColumnName - width: EaStyle.Sizes.fontPixelSize * 8 text: qsTr("Name") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel { - id: modelTypeColumnName - width: EaStyle.Sizes.fontPixelSize * 6 text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel { - id: modelDescrColumnName - width: EaStyle.Sizes.fontPixelSize * 24 text: qsTr("Description") color: EaStyle.Colors.themeForegroundMinor } @@ -51,45 +49,28 @@ EaElements.GroupColumn { delegateModelAccess: DelegateModel.ReadWrite - delegate: EaComponents.TableViewDelegate { + delegate: EaComponents.ListViewDelegate { required property int index required property string name required property string structure_type required property string description EaComponents.ListViewTextInput { - id: modelNameColumn - width: EaStyle.Sizes.fontPixelSize * 12 text: name onEditingFinished: name = text - leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 } - EaComponents.TableViewComboBox{ - id: structureTypeColumn - width: EaStyle.Sizes.fontPixelSize * 6 - model: ["Ring", "Ball", "Vesicle", "Rod", "Bilayer", "Monolayer", "Lattice"] - + model: [qsTr("Ring"), qsTr("Ball"), qsTr("Vesicle"), qsTr("Rod"), qsTr("Bilayer"), qsTr("Monolayer"), qsTr("Lattice")] Component.onCompleted: { currentIndex = model.indexOf(structure_type) } - onActivated: (index) => { structure_type = model[index] } } - EaComponents.ListViewTextInput { - id: descrColumn - width: EaStyle.Sizes.fontPixelSize * 20 text: description - onEditingFinished: { - description = text - console.log("descrColumn onEditingFinished") - } - onAccepted: { - console.log("descrColumn onAccepted") - } + onEditingFinished: description = text } } } @@ -99,25 +80,24 @@ EaElements.GroupColumn { spacing: EaStyle.Sizes.fontPixelSize EaElements.SideBarButton { - fontIcon: 'upload' - text: qsTr('Load model') + fontIcon: "upload" + text: qsTr("Load model") width: buttonWidth onClicked: loadExistingModelLoader.item.open() } EaElements.SideBarButton { - fontIcon: 'plus-circle' - text: qsTr('Create model') + fontIcon: "plus-circle" + text: qsTr("Create model") width: buttonWidth onClicked: createNewModelLoader.item.open() } EaElements.SideBarButton { - id: saveModelButton - fontIcon: 'download' - text: qsTr('Save model') + fontIcon: "download" + text: qsTr("Save model") width: buttonWidth - enabled: loadedSampleModelTableView.model ? loadedSampleModelTableView.model.count > 0 : false + enabled: loadedSampleModel.model && loadedSampleModel.model.count > 0 && loadedSambleModelsModel.get(0).name !== "" onClicked: loadExistingModelLoader.item.availableModelsModel.append(loadedSambleModelsModel.get(0)) } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index 420bc12..d8a5a7c 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -14,8 +14,8 @@ import Gui.Globals as Globals EaComponents.SideBarColumn { EaElements.GroupBox { - title: qsTr('Add a Model') - icon: 'vial' + title: qsTr('Model Definition') + icon: 'tag' collapsed: false Loader { source: 'Groups/SampleModel.qml' } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml index fc0e265..eee6a2c 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml @@ -18,16 +18,12 @@ EaElements.Dialog{ id: componentCreationDialog property var targetModel - - title: qsTr("Create a new Component") - property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + title: qsTr("Create a new Component") standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { - console.log("Ok Clicked") - targetModel.append({ name: componentNameField.text, component_type: sampleModelTypeField.currentText, @@ -36,31 +32,21 @@ EaElements.Dialog{ mext: 0 }) } - onRejected: console.log("Cancel Clicked") - onOpened: console.log("Opened CreateNewComponent Clicked") - - Component.onCompleted: { - Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewComponent = this - } Column { - spacing: EaStyle.Sizes.fontPixelSize Row { - spacing: EaStyle.Sizes.fontPixelSize - property int halfFieldWidth: (componentCreationDialog.inputFieldWidth - spacing) / 2 + spacing: EaStyle.Sizes.fontPixelSize Column { EaElements.Label { enabled: false text: qsTr("Name") } - EaElements.TextField { id: componentNameField - implicitWidth: parent.parent.halfFieldWidth horizontalAlignment: TextInput.AlignLeft validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } @@ -73,11 +59,10 @@ EaElements.Dialog{ enabled: false text: qsTr("Component type") } - EaElements.ComboBox { id: sampleModelTypeField implicitWidth: parent.parent.halfFieldWidth - model: ["Other", "Lipid", "Surfactant"] + model: [qsTr("Other"), qsTr("Lipid"), qsTr("Surfactant")] } } } @@ -107,14 +92,13 @@ EaElements.Dialog{ enabled: true maxRowCountShow: 2 width: componentCreationDialog.inputFieldWidth + scrollBarInteractive: false columnWidths: [ -1, EaStyle.Sizes.tableRowHeight ] - scrollBarInteractive: false - model: ListModel { id: selectedFilePathsModel } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml index 67dab16..8e91107 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -18,16 +18,12 @@ EaElements.Dialog{ id: sampleModelCreationDialog property var targetModel - - title: qsTr("Create a new Sample Model") - property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + title: qsTr("Create a new Sample Model") standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { - console.log("Ok Clicked") - targetModel.clear() targetModel.append( { @@ -37,11 +33,8 @@ EaElements.Dialog{ } ) } - onRejected: console.log("Cancel Clicked") - onOpened: console.log("Opened CreateNewModel Clicked") Column { - spacing: EaStyle.Sizes.fontPixelSize Row { @@ -53,10 +46,8 @@ EaElements.Dialog{ enabled: false text: qsTr("Name") } - EaElements.TextField { id: sampleModelNameField - implicitWidth: (sampleModelCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 horizontalAlignment: TextInput.AlignLeft validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } @@ -69,11 +60,10 @@ EaElements.Dialog{ enabled: false text: qsTr("Structure type") } - EaElements.ComboBox { id: sampleModelTypeField implicitWidth: (sampleModelCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 - model: ["Ring", "Ball", "Vesicle", "Rod", "Bilayer", "Monolayer", "Lattice"] + model: [qsTr("Ring"), qsTr("Ball"), qsTr("Vesicle"), qsTr("Rod"), qsTr("Bilayer"), qsTr("Monolayer"), qsTr("Lattice")] } } } @@ -83,22 +73,14 @@ EaElements.Dialog{ enabled: false text: qsTr("Description") } - EaElements.TextField { id: sampleModelDescrField - implicitWidth: sampleModelCreationDialog.inputFieldWidth horizontalAlignment: TextInput.AlignLeft validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } placeholderText: qsTr("(optional) Enter Sample Model description here") } } - } - - Component.onCompleted: { - Globals.References.pages.samplemodel.sidebar.basic.popups.CreateNewModel = this - } - } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml index c352c56..b95b813 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml @@ -18,17 +18,14 @@ EaElements.Dialog{ id: sampleModelLoadDialog property var targetModel - - title: qsTr("Load Components from the Asset Library") - property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - property alias availableComponentsModel: availableComponentsModel + title: qsTr("Load Components from the Asset Library") standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { - let selected = loadModelListView.selectedIndexes + let selected = loadComponentListView.selectedIndexes for (let i = 0; i < selected.length; ++i) { var row = selected[i].row var item = availableComponentsModel.get(row) @@ -41,10 +38,10 @@ EaElements.Dialog{ atoms: item.atoms }) } - loadModelListView.clearSelection() + loadComponentListView.clearSelection() } onRejected: { - loadModelListView.clearSelection() + loadComponentListView.clearSelection() } Column { @@ -54,7 +51,7 @@ EaElements.Dialog{ } EaComponents.ListView { - id: loadModelListView + id: loadComponentListView defaultInfoText: qsTr("No models found") multiSelection: true @@ -75,29 +72,24 @@ EaElements.Dialog{ horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { - id: modelNameColumnName text: qsTr("Name") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - id: modelTypeColumnName text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - id: modelAtomsColumnName text: qsTr("Atoms") color: EaStyle.Colors.themeForegroundMinor horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { - id: modelMintColumnName text: qsTr("Mint") color: EaStyle.Colors.themeForegroundMinor horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { - id: modelMextColumnName text: qsTr("Mext") color: EaStyle.Colors.themeForegroundMinor horizontalAlignment: Text.AlignHCenter @@ -134,30 +126,24 @@ EaElements.Dialog{ enabled: false } EaComponents.TableViewLabel { - id: modelNameColumn text: name } EaComponents.TableViewLabel { - id: typeColumn text: component_type } EaComponents.TableViewLabel { - id: atomsColumn text: atoms horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { - id: mintColumn text: mint horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { - id: mextColumn text: mext horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewButton { - id: deleteRowColumn fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this component") onClicked: availableComponentsModel.remove(index) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml index f1a77ec..19fdc0b 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml @@ -19,34 +19,26 @@ EaElements.Dialog{ id: sampleModelLoadDialog property var targetModel - - title: qsTr("Load a Sample Model from the Asset Library") - property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - property alias availableModelsModel: availableSambleModelsModel + title: qsTr("Load a Sample Model from the Asset Library") standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { - console.log("Ok Clicked") - - var indexes = selectionModel.selectedIndexes + var indexes = loadModelListView.selectedIndexes if (indexes.length > 0) { var row = indexes[0].row var item = availableSambleModelsModel.get(row) - console.log("Selected SampleModel: ", item.name) - targetModel.clear() targetModel.append(item) - selectionModel.clear() + loadModelListView.clearSelection() } } onRejected: { - console.log("Cancel Clicked") - selectionModel.clear() + loadModelListView.clearSelection() } Column { @@ -54,31 +46,33 @@ EaElements.Dialog{ enabled: false text: qsTr("Available in the Asset Library") } - - EaComponents.TableView { - clip: true - id: loadModelTableView + EaComponents.ListView { + id: loadModelListView defaultInfoText: qsTr("No models found") - - header: EaComponents.TableViewHeader { + multiSelection: false + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + EaStyle.Sizes.fontPixelSize * 10, + EaStyle.Sizes.fontPixelSize * 6, + -1, + EaStyle.Sizes.tableRowHeight, + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } EaComponents.TableViewLabel { - id: modelNameColumnName - width: EaStyle.Sizes.fontPixelSize * 10 text: qsTr("Name") color: EaStyle.Colors.themeForegroundMinor - leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 } - EaComponents.TableViewLabel { - id: modelTypeColumnName - width: EaStyle.Sizes.fontPixelSize * 6 text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel { - id: modelDescrColumnName - width: EaStyle.Sizes.fontPixelSize * 22 text: qsTr("Description") color: EaStyle.Colors.themeForegroundMinor } @@ -98,59 +92,32 @@ EaElements.Dialog{ ListElement { name: "Samplewithareallylongname"; structure_type: "Lattice"; description: "In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils." } } - property var itemSelectionModel: ItemSelectionModel { - id: selectionModel - model: availableSambleModelsModel - } - - property bool activeSelection: true - - delegate: EaComponents.TableViewDelegate { + delegateModelAccess: DelegateModel.ReadOnly + delegate: EaComponents.ListViewDelegate { required property int index required property string name required property string structure_type required property string description - color: { - if (!ListView.view.activeSelection) { - return index % 2 ? - EaStyle.Colors.themeBackgroundHovered2 : - EaStyle.Colors.themeBackgroundHovered1 - } - - ListView.view.itemSelectionModel.selection // create dependency - - return ListView.view.itemSelectionModel.isSelected( - ListView.view.itemSelectionModel.model.index(index, 0) - ) ? EaStyle.Colors.themeAccentMinor : EaStyle.Colors.themeBackgroundHovered1 + EaComponents.TableViewLabel { + text: index + 1 + horizontalAlignment: Text.AlignHCenter + enabled: false } - EaComponents.TableViewLabel { - id: modelNameColumn - width: EaStyle.Sizes.fontPixelSize * 10 text: name - leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 } - EaComponents.TableViewLabel { - id: typeColumn - width: EaStyle.Sizes.fontPixelSize * 6 text: structure_type } - EaComponents.TableViewLabel { - id: descrColumn - width: EaStyle.Sizes.fontPixelSize * 22 text: description } - - mouseArea.onPressed: (mouse) => { - let idx = ListView.view.itemSelectionModel.model.index(index, 0) - ListView.view.itemSelectionModel.select( - idx, - ItemSelectionModel.ClearAndSelect - ) + EaComponents.TableViewButton { + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this component") + onClicked: availableSambleModelsModel.remove(index) } } } diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index 13ba57d..ca63c21 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -25,7 +25,6 @@ ApplicationWindow{ spacing: EaStyle.Sizes.fontPixelSize * 4 leftPadding: 12 - Column { width: 600 spacing: EaStyle.Sizes.fontPixelSize From f0dcdd8d4d2d22f91aa45f9870d5a93194cd6e2c Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 29 Apr 2026 15:25:16 +0200 Subject: [PATCH 11/32] Made Layers component group --- src/easyshapes_app.qrc | 2 +- src/easyshapes_app/Gui/ApplicationWindow.qml | 2 +- src/easyshapes_app/Gui/Globals/References.qml | 3 + .../Sidebar/Basic/Groups/Components.qml | 10 -- .../Sidebar/Basic/Groups/Group1.qml | 18 -- .../Sidebar/Basic/Groups/Layers.qml | 156 ++++++++++++++++++ .../Sidebar/Basic/Groups/SampleModel.qml | 6 + .../SampleModel/Sidebar/Basic/Layout.qml | 13 +- src/easyshapes_app/test.qml | 2 +- 9 files changed, 175 insertions(+), 37 deletions(-) delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group1.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index f16d733..0cdd901 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -148,9 +148,9 @@ easyshapes_app/Gui/Pages/SampleModel/Layout.qml easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group1.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml diff --git a/src/easyshapes_app/Gui/ApplicationWindow.qml b/src/easyshapes_app/Gui/ApplicationWindow.qml index 9e21918..57b54db 100644 --- a/src/easyshapes_app/Gui/ApplicationWindow.qml +++ b/src/easyshapes_app/Gui/ApplicationWindow.qml @@ -75,7 +75,7 @@ EaComponents.ApplicationWindow { EaElements.AppBarTabButton { id: sampleModelButton enabled: false - fontIcon: 'layer-group' // 'layer-group' 'vials' 'vial' + fontIcon: 'vial' // 'layer-group' 'vials' 'vial' text: qsTr('Sample Model') // qsTr('Model') ToolTip.text: qsTr('Sample Model definition page') Component.onCompleted: { diff --git a/src/easyshapes_app/Gui/Globals/References.qml b/src/easyshapes_app/Gui/Globals/References.qml index 3219729..9189a76 100644 --- a/src/easyshapes_app/Gui/Globals/References.qml +++ b/src/easyshapes_app/Gui/Globals/References.qml @@ -43,6 +43,9 @@ QtObject { 'LoadExistingComponent': null, 'CreateNewComponent': null, 'OpenAssetFile': null + }, + 'groups': { + 'sampleModel': null } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml index 8c4c6de..5c6b0c6 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml @@ -37,7 +37,6 @@ EaElements.GroupColumn { EaComponents.TableViewLabel { text: qsTr("№") color: EaStyle.Colors.themeForegroundMinor - horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { text: qsTr("Name") @@ -50,17 +49,14 @@ EaElements.GroupColumn { EaComponents.TableViewLabel { text: qsTr("Atoms") color: EaStyle.Colors.themeForegroundMinor - horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { text: qsTr("Mint") color: EaStyle.Colors.themeForegroundMinor - horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { text: qsTr("Mext") color: EaStyle.Colors.themeForegroundMinor - horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { color: EaStyle.Colors.themeForegroundMinor @@ -83,12 +79,10 @@ EaElements.GroupColumn { EaComponents.TableViewLabel { text: index + 1 - horizontalAlignment: Text.AlignHCenter enabled: false } EaComponents.ListViewTextInput { - id: modelNameColumn text: name onEditingFinished: name = text } @@ -100,27 +94,23 @@ EaElements.GroupColumn { EaComponents.ListViewTextInput { text: atoms - horizontalAlignment: Text.AlignHCenter onEditingFinished: atoms = parseInt(text) validator: IntValidator { bottom: 0 } } EaComponents.ListViewTextInput { text: mint - horizontalAlignment: Text.AlignHCenter onEditingFinished: mint = parseInt(text) validator: IntValidator { bottom: 0 } } EaComponents.ListViewTextInput { text: mext - horizontalAlignment: Text.AlignHCenter onEditingFinished: mext = parseInt(text) validator: IntValidator { bottom: 0 } } EaComponents.TableViewButton { - id: deleteRowColumn fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this component") onClicked: loadedComponentsModel.remove(index) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group1.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group1.qml deleted file mode 100644 index f2e5c14..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group1.qml +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn { - -} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml new file mode 100644 index 0000000..2f151e7 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml @@ -0,0 +1,156 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Logic as EaLogic + +import Gui.Globals as Globals + +EaElements.GroupColumn { + property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + EaComponents.ListView { + id: layers + defaultInfoText: qsTr("Add at least one layer") + selectable: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.tableRowHeight + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel { + text: qsTr("Dmin") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Rmin") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: ListModel { + id: layersModel + ListElement { dmin: 0.25; rmin: 0.5; } + } + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property double dmin + required property double rmin + + EaComponents.TableViewLabel { + text: index + 1 + enabled: false + } + EaComponents.TableViewLabel {} // filler + EaComponents.ListViewTextInput { + text: dmin + onEditingFinished: dmin = parseFloat(text) + validator: DoubleValidator { bottom: 0.25 } + } + EaComponents.ListViewTextInput { + text: rmin + onEditingFinished: rmin = parseFloat(text) + validator: DoubleValidator { bottom: 0.25 } + } + EaComponents.TableViewButton { + id: deleteRowColumn + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this layer") + onClicked: layersModel.remove(index) + } + } + } + Item { + width: layers.width + height: addLayerButton.height + + EaElements.SideBarButton { + id: addLayerButton + anchors.right: parent.right + anchors.rightMargin: EaStyle.Sizes.tableColumnSpacing + fontIcon: 'plus-circle' + text: qsTr('Add layer') + width: buttonWidth + onClicked: layersModel.append({ dmin: 0.25, rmin: 0.5 }) + } + } + EaComponents.ListView { + id: fractions + defaultInfoText: qsTr("Missing components") + selectionActive: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 6, + -1, + EaStyle.Sizes.fontPixelSize * 8, + EaStyle.Sizes.fontPixelSize * 8, + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("Present") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel { + text: qsTr("Component name") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Molar ratio") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: ListModel { + id: fractionsModel + ListElement { name: "POPC"; fracs: 100; present: true } + ListElement { name: "DOPC"; fracs: 100; present: true } + ListElement { name: "DPPC"; fracs: 100; present: true } + } + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string name + required property double fracs + required property bool present + + EaComponents.TableViewCheckBox { + checked: present + onToggled: present = checked + } + EaComponents.TableViewLabel{} // filler + EaComponents.TableViewLabel{ + text: name + enabled: false + } + EaComponents.ListViewTextInput { + text: present ? fracs : 0 + enabled: present + onEditingFinished: fracs = parseFloat(text) + validator: DoubleValidator { bottom: 0 } + } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index 7196bd4..157c34c 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -15,7 +15,11 @@ import EasyApp.Gui.Logic as EaLogic import Gui.Globals as Globals EaElements.GroupColumn { + id: root property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + property string currentStructureType: "" + + Component.onCompleted: Globals.References.pages.samplemodel.sidebar.basic.groups.sampleModel = root EaComponents.ListView { id: loadedSampleModel @@ -45,6 +49,7 @@ EaElements.GroupColumn { model: ListModel { id: loadedSambleModelsModel + onCountChanged: root.currentStructureType = count > 0 ? get(0).structure_type : "" } delegateModelAccess: DelegateModel.ReadWrite @@ -66,6 +71,7 @@ EaElements.GroupColumn { } onActivated: (index) => { structure_type = model[index] + root.currentStructureType = model[index] } } EaComponents.ListViewTextInput { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index d8a5a7c..dc0afa0 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -18,7 +18,7 @@ EaComponents.SideBarColumn { icon: 'tag' collapsed: false - Loader { source: 'Groups/SampleModel.qml' } + Loader { id: sampleModelLoader; source: 'Groups/SampleModel.qml' } } EaElements.GroupBox { @@ -29,14 +29,15 @@ EaComponents.SideBarColumn { } EaElements.GroupBox { - title: qsTr('Solution') - icon: 'tint' + title: qsTr('Layers') + icon: 'layer-group' // 'grip-lines' + visible: sampleModelLoader.item ? sampleModelLoader.item.currentStructureType === 'Ball' : false - Loader { source: 'Groups/Group2.qml' } + Loader { source: 'Groups/Layers.qml' } } EaElements.GroupBox { - title: qsTr('Group 3') + title: qsTr('Solvent') icon: 'grip-horizontal' Loader { source: 'Groups/Group3.qml' } @@ -46,7 +47,7 @@ EaComponents.SideBarColumn { title: qsTr('Group 1') icon: 'rocket' - Loader { source: 'Groups/Group1.qml' } + Loader { source: 'Groups/Group2.qml' } } } diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index ca63c21..df3ac5d 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -55,7 +55,7 @@ ApplicationWindow{ icon: 'wrench' collapsed: false - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml'} + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml'} } EaComponents.ListView { From e0f0388068d307cbc60f4e9b1972afac9149b0e0 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 29 Apr 2026 16:22:38 +0200 Subject: [PATCH 12/32] Added a label to the fractions listview and made it toggleable based on selected layer --- .../Sidebar/Basic/Groups/Layers.qml | 124 ++++++++++-------- 1 file changed, 67 insertions(+), 57 deletions(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml index 2f151e7..9b5449b 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml @@ -18,7 +18,7 @@ EaElements.GroupColumn { EaComponents.ListView { id: layers defaultInfoText: qsTr("Add at least one layer") - selectable: false + multiSelection: false columnWidths: [ EaStyle.Sizes.fontPixelSize * 2.5, @@ -87,69 +87,79 @@ EaElements.GroupColumn { id: addLayerButton anchors.right: parent.right anchors.rightMargin: EaStyle.Sizes.tableColumnSpacing - fontIcon: 'plus-circle' - text: qsTr('Add layer') + fontIcon: "plus-circle" + text: qsTr("Add layer") width: buttonWidth onClicked: layersModel.append({ dmin: 0.25, rmin: 0.5 }) } } - EaComponents.ListView { - id: fractions - defaultInfoText: qsTr("Missing components") - selectionActive: false - - columnWidths: [ - EaStyle.Sizes.fontPixelSize * 6, - -1, - EaStyle.Sizes.fontPixelSize * 8, - EaStyle.Sizes.fontPixelSize * 8, - ] - - header: EaComponents.ListViewHeader { - EaComponents.TableViewLabel { - text: qsTr("Present") - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel {} // filler - EaComponents.TableViewLabel { - text: qsTr("Component name") - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr("Molar ratio") - color: EaStyle.Colors.themeForegroundMinor - } - } - - model: ListModel { - id: fractionsModel - ListElement { name: "POPC"; fracs: 100; present: true } - ListElement { name: "DOPC"; fracs: 100; present: true } - ListElement { name: "DPPC"; fracs: 100; present: true } + Column { + visible: layers.selectedIndexes.length > 0 + width: parent.width + + EaElements.Label { + id: layerFractionsLabel + enabled: false + text: qsTr("Layer %1 Fractions").arg(layers.selectedIndexes.length > 0 ? layers.selectedIndexes[0].row + 1 : 1) } - - delegateModelAccess: DelegateModel.ReadWrite - - delegate: EaComponents.ListViewDelegate { - required property int index - required property string name - required property double fracs - required property bool present - - EaComponents.TableViewCheckBox { - checked: present - onToggled: present = checked + EaComponents.ListView { + id: fractions + defaultInfoText: qsTr("Missing components") + selectionActive: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 6, + -1, + EaStyle.Sizes.fontPixelSize * 8, + EaStyle.Sizes.fontPixelSize * 8, + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("Present") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel { + text: qsTr("Component name") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Molar ratio") + color: EaStyle.Colors.themeForegroundMinor + } } - EaComponents.TableViewLabel{} // filler - EaComponents.TableViewLabel{ - text: name - enabled: false + + model: ListModel { + id: fractionsModel + ListElement { name: "POPC"; fracs: 100; present: true } + ListElement { name: "DOPC"; fracs: 100; present: true } + ListElement { name: "DPPC"; fracs: 100; present: true } } - EaComponents.ListViewTextInput { - text: present ? fracs : 0 - enabled: present - onEditingFinished: fracs = parseFloat(text) - validator: DoubleValidator { bottom: 0 } + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string name + required property double fracs + required property bool present + + EaComponents.TableViewCheckBox { + checked: present + onToggled: present = checked + } + EaComponents.TableViewLabel{} // filler + EaComponents.TableViewLabel{ + text: name + enabled: false + } + EaComponents.ListViewTextInput { + text: present ? fracs : 0 + enabled: present + onEditingFinished: fracs = parseFloat(text) + validator: DoubleValidator { bottom: 0 } + } } } } From df34aa3c53282125d3df36a1ff713b585782e7ae Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 29 Apr 2026 16:50:27 +0200 Subject: [PATCH 13/32] Extracted fractions into a reusable component --- src/easyshapes_app.qrc | 1 + .../Sidebar/Basic/Components/Fractions.qml | 68 +++++++++++++++++++ .../Sidebar/Basic/Groups/Layers.qml | 61 +---------------- 3 files changed, 72 insertions(+), 58 deletions(-) create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index 0cdd901..4889665 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -147,6 +147,7 @@ easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Layout.qml easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml new file mode 100644 index 0000000..e39f2df --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Components as EaComponents + +EaComponents.ListView { + defaultInfoText: qsTr("Missing components") + selectionActive: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 6, + -1, + EaStyle.Sizes.fontPixelSize * 8, + EaStyle.Sizes.fontPixelSize * 8, + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("Present") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel { + text: qsTr("Component name") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Molar ratio") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: ListModel { + ListElement { name: "POPC"; fracs: 100; present: true } + ListElement { name: "DOPC"; fracs: 100; present: true } + ListElement { name: "DPPC"; fracs: 100; present: true } + } + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string name + required property double fracs + required property bool present + + EaComponents.TableViewCheckBox { + checked: present + onToggled: present = checked + } + EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel { + text: name + enabled: false + } + EaComponents.ListViewTextInput { + text: present ? fracs : 0 + enabled: present + onEditingFinished: fracs = parseFloat(text) + validator: DoubleValidator { bottom: 0 } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml index 9b5449b..1168ec9 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml @@ -13,6 +13,8 @@ import EasyApp.Gui.Logic as EaLogic import Gui.Globals as Globals +import "../Components" as Local + EaElements.GroupColumn { property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 EaComponents.ListView { @@ -102,65 +104,8 @@ EaElements.GroupColumn { enabled: false text: qsTr("Layer %1 Fractions").arg(layers.selectedIndexes.length > 0 ? layers.selectedIndexes[0].row + 1 : 1) } - EaComponents.ListView { + Local.Fractions { id: fractions - defaultInfoText: qsTr("Missing components") - selectionActive: false - - columnWidths: [ - EaStyle.Sizes.fontPixelSize * 6, - -1, - EaStyle.Sizes.fontPixelSize * 8, - EaStyle.Sizes.fontPixelSize * 8, - ] - - header: EaComponents.ListViewHeader { - EaComponents.TableViewLabel { - text: qsTr("Present") - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel {} // filler - EaComponents.TableViewLabel { - text: qsTr("Component name") - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr("Molar ratio") - color: EaStyle.Colors.themeForegroundMinor - } - } - - model: ListModel { - id: fractionsModel - ListElement { name: "POPC"; fracs: 100; present: true } - ListElement { name: "DOPC"; fracs: 100; present: true } - ListElement { name: "DPPC"; fracs: 100; present: true } - } - - delegateModelAccess: DelegateModel.ReadWrite - - delegate: EaComponents.ListViewDelegate { - required property int index - required property string name - required property double fracs - required property bool present - - EaComponents.TableViewCheckBox { - checked: present - onToggled: present = checked - } - EaComponents.TableViewLabel{} // filler - EaComponents.TableViewLabel{ - text: name - enabled: false - } - EaComponents.ListViewTextInput { - text: present ? fracs : 0 - enabled: present - onEditingFinished: fracs = parseFloat(text) - validator: DoubleValidator { bottom: 0 } - } - } } } } From 966c0214539a6deae23dbcaabc14f5b6ed3ac52c Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Thu, 30 Apr 2026 11:11:10 +0200 Subject: [PATCH 14/32] Added lamellae group --- .../Sidebar/Basic/Groups/Lamellae.qml | 162 ++++++++++++++++++ .../SampleModel/Sidebar/Basic/Layout.qml | 8 + src/easyshapes_app/test.qml | 2 +- 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml new file mode 100644 index 0000000..e81f1c9 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml @@ -0,0 +1,162 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Logic as EaLogic + +import Gui.Globals as Globals + +import "../Components" as Local + +EaElements.GroupColumn { + property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + + EaComponents.ListView { + id: lamellae + defaultInfoText: qsTr("Add at least one lamella") + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + EaStyle.Sizes.fontPixelSize * 5, + EaStyle.Sizes.fontPixelSize * 5, + EaStyle.Sizes.fontPixelSize * 5, + EaStyle.Sizes.fontPixelSize * 5, + EaStyle.Sizes.tableRowHeight, + EaStyle.Sizes.tableRowHeight + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel { + text: qsTr("Rmin") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Inner Dmin") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Outer Dmin") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Shell") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Sym") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: ListModel { + id: lamellaeModel + ListElement { rmin: 0.5; innerDmin: 0.25; outerDmin: 0.25; shell: 0.5; symmetric: true } + } + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property double rmin + required property double innerDmin + required property double outerDmin + required property double shell + required property bool symmetric + + EaComponents.TableViewLabel { + text: index + 1 + enabled: false + } + EaComponents.TableViewLabel {} // filler + EaComponents.ListViewTextInput { + text: rmin + onEditingFinished: rmin = parseFloat(text) + validator: DoubleValidator { bottom: 0.25 } + } + EaComponents.ListViewTextInput { + text: innerDmin + onEditingFinished: innerDmin = parseFloat(text) + validator: DoubleValidator { bottom: 0.25 } + } + EaComponents.ListViewTextInput { + text: symmetric ? innerDmin : outerDmin + enabled: !symmetric + onEditingFinished: outerDmin = parseFloat(text) + validator: DoubleValidator { bottom: 0.25 } + } + EaComponents.ListViewTextInput { + text: shell + onEditingFinished: shell = parseFloat(text) + validator: DoubleValidator { bottom: 0 } + } + EaComponents.TableViewCheckBox { + checked: symmetric + onToggled: symmetric = checked + } + EaComponents.TableViewButton { + id: deleteRowColumn + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this lamella") + onClicked: lamellaeModel.remove(index) + } + } + } + + Item { + width: lamellae.width + height: addLamellaButton.height + + EaElements.SideBarButton { + id: addLamellaButton + anchors.right: parent.right + anchors.rightMargin: EaStyle.Sizes.tableColumnSpacing + fontIcon: "plus-circle" + text: qsTr("Add lamella") + width: buttonWidth + onClicked: lamellaeModel.append({ rmin: 0.5, innerDmin: 0.25, outerDmin: 0.25, shell: 0.5, symmetric: true }) + } + } + + Column { + id: fractionsSection + visible: lamellae.selectedIndexes.length > 0 + width: parent.width + + readonly property int selectedRow: lamellae.selectedIndexes.length > 0 ? lamellae.selectedIndexes[0].row : -1 + readonly property bool selectedSymmetric: selectedRow >= 0 && lamellaeModel.get(selectedRow).symmetric + + EaElements.Label { + enabled: false + text: fractionsSection.selectedSymmetric + ? qsTr("Lamella %1 Leaflet Fractions").arg(fractionsSection.selectedRow + 1) + : qsTr("Lamella %1 Inner Leaflet Fractions").arg(fractionsSection.selectedRow + 1) + } + Local.Fractions { + id: innerFractions + } + + EaElements.Label { + visible: !fractionsSection.selectedSymmetric + enabled: false + text: qsTr("Lamella %1 Outer Leaflet Fractions").arg(fractionsSection.selectedRow + 1) + } + Local.Fractions { + id: outerFractions + visible: !fractionsSection.selectedSymmetric + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index dc0afa0..1aa6780 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -36,6 +36,14 @@ EaComponents.SideBarColumn { Loader { source: 'Groups/Layers.qml' } } + EaElements.GroupBox { + title: qsTr('Lamellae') + icon: 'layer-group' + visible: sampleModelLoader.item ? sampleModelLoader.item.currentStructureType === 'Vesicle' : false + + Loader { source: 'Groups/Lamellae.qml' } + } + EaElements.GroupBox { title: qsTr('Solvent') icon: 'grip-horizontal' diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index df3ac5d..1905bb0 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -55,7 +55,7 @@ ApplicationWindow{ icon: 'wrench' collapsed: false - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml'} + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml'} } EaComponents.ListView { From 970e9d5c06673a744c90738b0a278528eb6a369c Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Fri, 1 May 2026 11:15:11 +0200 Subject: [PATCH 15/32] convert to new EasyApp(lication) --- src/easyshapes_app.qrc | 110 +----------------- src/easyshapes_app/Gui/ApplicationWindow.qml | 6 +- .../Gui/Pages/Analysis/Layout.qml | 8 +- .../Gui/Pages/Analysis/MainArea/Chart.qml | 4 +- .../Sidebar/Basic/Groups/GetStarted.qml | 10 +- .../Pages/Analysis/Sidebar/Basic/Layout.qml | 4 +- src/easyshapes_app/Gui/Pages/Home/Content.qml | 6 +- .../Gui/Pages/Home/Popups/About.qml | 4 +- .../Gui/Pages/Project/Layout.qml | 8 +- .../Pages/Project/MainArea/Description.qml | 4 +- .../Project/Sidebar/Basic/Groups/Examples.qml | 10 +- .../Sidebar/Basic/Groups/GetStarted.qml | 10 +- .../Project/Sidebar/Basic/Groups/Recent.qml | 10 +- .../Pages/Project/Sidebar/Basic/Layout.qml | 4 +- .../Sidebar/Basic/Popups/OpenCifFile.qml | 4 +- .../Basic/Popups/ProjectDescription.qml | 4 +- .../Sidebar/Extra/Groups/Scrolling.qml | 4 +- .../Pages/Project/Sidebar/Extra/Layout.qml | 4 +- .../Gui/Pages/Project/Sidebar/Text/Layout.qml | 4 +- .../Gui/Pages/Report/Layout.qml | 8 +- .../Gui/Pages/Report/MainArea/Summary.qml | 6 +- .../Report/Sidebar/Basic/Groups/Export.qml | 8 +- .../Gui/Pages/Report/Sidebar/Basic/Layout.qml | 4 +- .../Report/Sidebar/Extra/Groups/Empty.qml | 4 +- .../Gui/Pages/Report/Sidebar/Extra/Layout.qml | 4 +- .../Gui/Pages/SampleModel/Layout.qml | 8 +- .../Pages/SampleModel/MainArea/GraphsView.qml | 4 +- .../Sidebar/Basic/Components/Fractions.qml | 4 +- .../Sidebar/Basic/Groups/Components.qml | 10 +- .../Sidebar/Basic/Groups/Group2.qml | 10 +- .../Sidebar/Basic/Groups/Group3.qml | 10 +- .../Sidebar/Basic/Groups/Lamellae.qml | 38 +++--- .../Sidebar/Basic/Groups/Layers.qml | 25 ++-- .../Sidebar/Basic/Groups/SampleModel.qml | 10 +- .../SampleModel/Sidebar/Basic/Layout.qml | 4 +- .../Basic/Popups/CreateNewComponent.qml | 8 +- .../Sidebar/Basic/Popups/CreateNewModel.qml | 8 +- .../Basic/Popups/LoadExistingComponent.qml | 8 +- .../Basic/Popups/LoadExistingModel.qml | 8 +- .../Sidebar/Basic/Popups/OpenAssetFile.qml | 4 +- .../Gui/Pages/Toolbox/Layout.qml | 8 +- .../Gui/Pages/Toolbox/MainArea/GraphsView.qml | 4 +- .../Gui/Pages/Toolbox/MainArea/Image.qml | 4 +- .../Pages/Toolbox/MainArea/TextAreaPlain.qml | 4 +- .../Pages/Toolbox/MainArea/TextAreaRich.qml | 4 +- .../Toolbox/Sidebar/Basic/Groups/Group1.qml | 10 +- .../Toolbox/Sidebar/Basic/Groups/Group2.qml | 10 +- .../Toolbox/Sidebar/Basic/Groups/Group3.qml | 10 +- .../Toolbox/Sidebar/Basic/Groups/Group4.qml | 10 +- .../Pages/Toolbox/Sidebar/Basic/Layout.qml | 4 +- src/easyshapes_app/Gui/StatusBar.qml | 6 +- src/easyshapes_app/test.qml | 10 +- src/easyshapes_app/test2.qml | 8 +- 53 files changed, 197 insertions(+), 306 deletions(-) diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index 4889665..525180b 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -1,114 +1,5 @@ - ../../EasyApp/src/EasyApp/Gui/Animations/ColorReset.qml - ../../EasyApp/src/EasyApp/Gui/Animations/qmldir - ../../EasyApp/src/EasyApp/Gui/Animations/ThemeChange.qml - ../../EasyApp/src/EasyApp/Gui/Animations/TranslationChange.qml - ../../EasyApp/src/EasyApp/Gui/Charts/ChartViewHeatmap2dPlotly.qml - ../../EasyApp/src/EasyApp/Gui/Charts/ChartViewSimple1dPlotly.qml - ../../EasyApp/src/EasyApp/Gui/Charts/Plotly1dBarPlot.qml - ../../EasyApp/src/EasyApp/Gui/Charts/Plotly1dLine.qml - ../../EasyApp/src/EasyApp/Gui/Charts/Plotly1dMeasVsCalc.qml - ../../EasyApp/src/EasyApp/Gui/Charts/Plotly2dHeatmap.qml - ../../EasyApp/src/EasyApp/Gui/Charts/Plotly2dPolarHeatmap.qml - ../../EasyApp/src/EasyApp/Gui/Charts/Plotly3dScatter.qml - ../../EasyApp/src/EasyApp/Gui/Charts/Plotly3dSurface.qml - ../../EasyApp/src/EasyApp/Gui/Charts/qmldir - ../../EasyApp/src/EasyApp/Gui/Charts/QtCharts1dBase 2.qml - ../../EasyApp/src/EasyApp/Gui/Charts/QtCharts1dBase.qml - ../../EasyApp/src/EasyApp/Gui/Charts/QtCharts1dMeasVsCalc.qml - ../../EasyApp/src/EasyApp/Gui/Charts/QtCharts1dValueAxis.qml - ../../EasyApp/src/EasyApp/Gui/Components/AboutDialog.qml - ../../EasyApp/src/EasyApp/Gui/Components/AppBarCentralTabs.qml - ../../EasyApp/src/EasyApp/Gui/Components/AppBarLeftButtons.qml - ../../EasyApp/src/EasyApp/Gui/Components/AppBarRightButtons.qml - ../../EasyApp/src/EasyApp/Gui/Components/ApplicationWindow.qml - ../../EasyApp/src/EasyApp/Gui/Components/BasicReport.qml - ../../EasyApp/src/EasyApp/Gui/Components/ContentArea.qml - ../../EasyApp/src/EasyApp/Gui/Components/ContentPage.qml - ../../EasyApp/src/EasyApp/Gui/Components/GuideWindow.qml - ../../EasyApp/src/EasyApp/Gui/Components/GuideWindowContainer.qml - ../../EasyApp/src/EasyApp/Gui/Components/JsonListModel.qml - ../../EasyApp/src/EasyApp/Gui/Components/ListView.qml - ../../EasyApp/src/EasyApp/Gui/Components/ListViewDelegate.qml - ../../EasyApp/src/EasyApp/Gui/Components/ListViewHeader.qml - ../../EasyApp/src/EasyApp/Gui/Components/ListViewTextInput.qml - ../../EasyApp/src/EasyApp/Gui/Components/MainContent.qml - ../../EasyApp/src/EasyApp/Gui/Components/PreferencesDialog.qml - ../../EasyApp/src/EasyApp/Gui/Components/ProjectDescriptionDialog.qml - ../../EasyApp/src/EasyApp/Gui/Components/qmldir - ../../EasyApp/src/EasyApp/Gui/Components/SideBar.qml - ../../EasyApp/src/EasyApp/Gui/Components/SideBarColumn.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableView.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewAdvancedLabel.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewButton.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewCheckBox.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewComboBox.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewDelegate.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewHeader.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewLabel.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewLabelControl.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewParameter.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewTextInput.qml - ../../EasyApp/src/EasyApp/Gui/Components/TableViewTwoRowsAdvancedLabel.qml - ../../EasyApp/src/EasyApp/Gui/Elements/AppBarTabButton.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ApplicationWindow.qml - ../../EasyApp/src/EasyApp/Gui/Elements/Button.qml - ../../EasyApp/src/EasyApp/Gui/Elements/CheckBox.qml - ../../EasyApp/src/EasyApp/Gui/Elements/CheckIndicator.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ComboBox.qml - ../../EasyApp/src/EasyApp/Gui/Elements/CursorDelegate.qml - ../../EasyApp/src/EasyApp/Gui/Elements/Dialog.qml - ../../EasyApp/src/EasyApp/Gui/Elements/DialogButtonBox.qml - ../../EasyApp/src/EasyApp/Gui/Elements/GroupBox.qml - ../../EasyApp/src/EasyApp/Gui/Elements/GroupButton.qml - ../../EasyApp/src/EasyApp/Gui/Elements/GroupColumn.qml - ../../EasyApp/src/EasyApp/Gui/Elements/GroupRow.qml - ../../EasyApp/src/EasyApp/Gui/Elements/Label.qml - ../../EasyApp/src/EasyApp/Gui/Elements/LinkedImage.qml - ../../EasyApp/src/EasyApp/Gui/Elements/Menu.qml - ../../EasyApp/src/EasyApp/Gui/Elements/MenuItem.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ParamComboBox.qml - ../../EasyApp/src/EasyApp/Gui/Elements/Parameter.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ParamTextField.qml - ../../EasyApp/src/EasyApp/Gui/Elements/qmldir - ../../EasyApp/src/EasyApp/Gui/Elements/RadioButton.qml - ../../EasyApp/src/EasyApp/Gui/Elements/RadioIndicator.qml - ../../EasyApp/src/EasyApp/Gui/Elements/RemoteController.qml - ../../EasyApp/src/EasyApp/Gui/Elements/RemotePointer.qml - ../../EasyApp/src/EasyApp/Gui/Elements/RunningLabel.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ScrollBar.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ScrollIndicator.qml - ../../EasyApp/src/EasyApp/Gui/Elements/SideBarButton.qml - ../../EasyApp/src/EasyApp/Gui/Elements/Slider.qml - ../../EasyApp/src/EasyApp/Gui/Elements/SliderHandle.qml - ../../EasyApp/src/EasyApp/Gui/Elements/SpinBox.qml - ../../EasyApp/src/EasyApp/Gui/Elements/SplashScreen.qml - ../../EasyApp/src/EasyApp/Gui/Elements/StatusBar.qml - ../../EasyApp/src/EasyApp/Gui/Elements/StatusBarItem.qml - ../../EasyApp/src/EasyApp/Gui/Elements/TabBar.qml - ../../EasyApp/src/EasyApp/Gui/Elements/TabButton.qml - ../../EasyApp/src/EasyApp/Gui/Elements/TextArea.qml - ../../EasyApp/src/EasyApp/Gui/Elements/TextField.qml - ../../EasyApp/src/EasyApp/Gui/Elements/TextInput.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ToolButton.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ToolTip.qml - ../../EasyApp/src/EasyApp/Gui/Elements/ToolTipShadow.qml - ../../EasyApp/src/EasyApp/Gui/Globals/qmldir - ../../EasyApp/src/EasyApp/Gui/Globals/Vars.qml - ../../EasyApp/src/EasyApp/Gui/Html/plotly-2.18.0.min.js - ../../EasyApp/src/EasyApp/Gui/Logic/Plotting.js - ../../EasyApp/src/EasyApp/Gui/Logic/ProjectConfig.js - ../../EasyApp/src/EasyApp/Gui/Logic/qmldir - ../../EasyApp/src/EasyApp/Gui/Logic/Translate.js - ../../EasyApp/src/EasyApp/Gui/Logic/Utils.js - ../../EasyApp/src/EasyApp/Gui/Style/Colors.qml - ../../EasyApp/src/EasyApp/Gui/Style/Fonts.qml - ../../EasyApp/src/EasyApp/Gui/Style/qmldir - ../../EasyApp/src/EasyApp/Gui/Style/Sizes.qml - ../../EasyApp/src/EasyApp/Gui/Style/Times.qml - ../../EasyApp/src/EasyApp/Logic/Maintenance/qmldir - ../../EasyApp/src/EasyApp/Logic/Maintenance/Updater.qml easyshapes_app.qmlproject easyshapes_app/Backends/MockBackend.qml easyshapes_app/Backends/MockQml/Analysis.qml @@ -151,6 +42,7 @@ easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml diff --git a/src/easyshapes_app/Gui/ApplicationWindow.qml b/src/easyshapes_app/Gui/ApplicationWindow.qml index 57b54db..eb05838 100644 --- a/src/easyshapes_app/Gui/ApplicationWindow.qml +++ b/src/easyshapes_app/Gui/ApplicationWindow.qml @@ -5,9 +5,9 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui as Gui import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml b/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml index 4c9d546..3315766 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml @@ -5,10 +5,10 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml b/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml index 4f8ce10..1d07269 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml @@ -5,8 +5,8 @@ import QtQuick import QtGraphs -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml index 7b73f23..a9d1be1 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml index e59ca3c..ea6ddaf 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml @@ -5,8 +5,8 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Home/Content.qml b/src/easyshapes_app/Gui/Pages/Home/Content.qml index e0b7a19..420851d 100644 --- a/src/easyshapes_app/Gui/Pages/Home/Content.qml +++ b/src/easyshapes_app/Gui/Pages/Home/Content.qml @@ -4,9 +4,9 @@ import QtQuick -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Home/Popups/About.qml b/src/easyshapes_app/Gui/Pages/Home/Popups/About.qml index 11815fd..c1af059 100644 --- a/src/easyshapes_app/Gui/Pages/Home/Popups/About.qml +++ b/src/easyshapes_app/Gui/Pages/Home/Popups/About.qml @@ -4,8 +4,8 @@ import QtQuick -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Layout.qml b/src/easyshapes_app/Gui/Pages/Project/Layout.qml index 6ebbb95..f7c6116 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Layout.qml @@ -5,10 +5,10 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/MainArea/Description.qml b/src/easyshapes_app/Gui/Pages/Project/MainArea/Description.qml index 8ce72b2..eab134f 100644 --- a/src/easyshapes_app/Gui/Pages/Project/MainArea/Description.qml +++ b/src/easyshapes_app/Gui/Pages/Project/MainArea/Description.qml @@ -4,8 +4,8 @@ import QtQuick -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Examples.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Examples.qml index 065031c..bfd3695 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Examples.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Examples.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/GetStarted.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/GetStarted.qml index cb1ab18..83e643d 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/GetStarted.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/GetStarted.qml @@ -6,11 +6,11 @@ import QtQuick import QtQuick.Controls //import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Recent.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Recent.qml index 8357f95..e9158c1 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Recent.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Groups/Recent.qml @@ -6,11 +6,11 @@ import QtQuick import QtQuick.Controls import QtCore -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Layout.qml index 23952b4..0cdbc68 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Layout.qml @@ -5,8 +5,8 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/OpenCifFile.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/OpenCifFile.qml index 649bb8a..29580d6 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/OpenCifFile.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/OpenCifFile.qml @@ -6,8 +6,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/ProjectDescription.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/ProjectDescription.qml index dcc2277..68d21c2 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/ProjectDescription.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Basic/Popups/ProjectDescription.qml @@ -5,8 +5,8 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Groups/Scrolling.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Groups/Scrolling.qml index 4bc8920..5521228 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Groups/Scrolling.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Groups/Scrolling.qml @@ -4,8 +4,8 @@ import QtQuick -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements Column { diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Layout.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Layout.qml index 273d95a..fd00806 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Extra/Layout.qml @@ -5,8 +5,8 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Text/Layout.qml b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Text/Layout.qml index 3c9c9d6..9c4629b 100644 --- a/src/easyshapes_app/Gui/Pages/Project/Sidebar/Text/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Project/Sidebar/Text/Layout.qml @@ -4,8 +4,8 @@ import QtQuick -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents EaComponents.SideBarColumn {} diff --git a/src/easyshapes_app/Gui/Pages/Report/Layout.qml b/src/easyshapes_app/Gui/Pages/Report/Layout.qml index 3beddbd..6604f52 100644 --- a/src/easyshapes_app/Gui/Pages/Report/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Report/Layout.qml @@ -5,10 +5,10 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Report/MainArea/Summary.qml b/src/easyshapes_app/Gui/Pages/Report/MainArea/Summary.qml index 0168ba3..8c1bcd4 100644 --- a/src/easyshapes_app/Gui/Pages/Report/MainArea/Summary.qml +++ b/src/easyshapes_app/Gui/Pages/Report/MainArea/Summary.qml @@ -5,9 +5,9 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Animations as EaAnimations -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Animations as EaAnimations +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Groups/Export.qml b/src/easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Groups/Export.qml index e80a6b4..7cc971b 100644 --- a/src/easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Groups/Export.qml +++ b/src/easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Groups/Export.qml @@ -7,10 +7,10 @@ import QtQuick.Controls import QtQuick.Dialogs import QtCore -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Layout.qml index 472d957..059d6de 100644 --- a/src/easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Report/Sidebar/Basic/Layout.qml @@ -4,8 +4,8 @@ import QtQuick -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml b/src/easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml index 9f47804..4401fe7 100644 --- a/src/easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml +++ b/src/easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml @@ -4,8 +4,8 @@ import QtQuick -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements Column {} diff --git a/src/easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml b/src/easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml index ef2a2ed..7219c46 100644 --- a/src/easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml @@ -5,8 +5,8 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml index ac5e6e4..193d1f9 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml @@ -5,10 +5,10 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml index 9f2fd2d..276dfff 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml @@ -5,8 +5,8 @@ import QtQuick import QtGraphs -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml index e39f2df..eb17efb 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml @@ -5,8 +5,8 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents EaComponents.ListView { defaultInfoText: qsTr("Missing components") diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml index 5c6b0c6..bc23d6f 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml @@ -6,11 +6,11 @@ import QtQuick import QtQuick.Controls //import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml index f2e5c14..af615cc 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml index f2e5c14..af615cc 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml index e81f1c9..65a20de 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals @@ -24,14 +24,13 @@ EaElements.GroupColumn { multiSelection: false columnWidths: [ - EaStyle.Sizes.fontPixelSize * 2.5, - -1, - EaStyle.Sizes.fontPixelSize * 5, - EaStyle.Sizes.fontPixelSize * 5, - EaStyle.Sizes.fontPixelSize * 5, - EaStyle.Sizes.fontPixelSize * 5, - EaStyle.Sizes.tableRowHeight, - EaStyle.Sizes.tableRowHeight + EaStyle.Sizes.tableColumnAuto, // № + EaStyle.Sizes.tableColumnAuto, // Rmin, nm + EaStyle.Sizes.tableColumnAuto, // Inner Dmin, nm + EaStyle.Sizes.tableColumnAuto, // Outer Dmin, nm + EaStyle.Sizes.tableColumnAuto, // Shell thickness, nm + EaStyle.Sizes.tableColumnAuto, // Symmetric (checkbox) + EaStyle.Sizes.tableRowHeight // delete button ] header: EaComponents.ListViewHeader { @@ -39,27 +38,27 @@ EaElements.GroupColumn { text: qsTr("№") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel {} // filler EaComponents.TableViewLabel { - text: qsTr("Rmin") + text: qsTr("Rmin, nm") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Inner Dmin") + text: qsTr("Inner Dmin, nm") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Outer Dmin") + text: qsTr("Outer Dmin, nm") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Shell") + text: qsTr("Shell thickness, nm") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Sym") + text: qsTr("Symmetric") color: EaStyle.Colors.themeForegroundMinor } + EaComponents.TableViewLabel {} } model: ListModel { @@ -81,7 +80,6 @@ EaElements.GroupColumn { text: index + 1 enabled: false } - EaComponents.TableViewLabel {} // filler EaComponents.ListViewTextInput { text: rmin onEditingFinished: rmin = parseFloat(text) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml index 1168ec9..6b8fc1a 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals @@ -23,11 +23,11 @@ EaElements.GroupColumn { multiSelection: false columnWidths: [ - EaStyle.Sizes.fontPixelSize * 2.5, - -1, - EaStyle.Sizes.fontPixelSize * 6, - EaStyle.Sizes.fontPixelSize * 6, - EaStyle.Sizes.tableRowHeight + EaStyle.Sizes.tableColumnAuto, // № + EaStyle.Sizes.tableColumnFlex, // filler + EaStyle.Sizes.tableColumnAuto, // Dmin, nm + EaStyle.Sizes.tableColumnAuto, // Rmin, nm + EaStyle.Sizes.tableRowHeight // delete button ] header: EaComponents.ListViewHeader { @@ -37,13 +37,14 @@ EaElements.GroupColumn { } EaComponents.TableViewLabel {} // filler EaComponents.TableViewLabel { - text: qsTr("Dmin") + text: qsTr("Dmin, nm") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Rmin") + text: qsTr("Rmin, nm") color: EaStyle.Colors.themeForegroundMinor } + EaComponents.TableViewLabel {} } model: ListModel { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index 157c34c..8816d91 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -6,11 +6,11 @@ import QtQuick import QtQuick.Controls //import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index 1aa6780..75f4967 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -5,8 +5,8 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml index eee6a2c..6c0ac06 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml @@ -6,10 +6,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml index 8e91107..09d397a 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -6,10 +6,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml index b95b813..a666182 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml @@ -6,10 +6,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml index 19fdc0b..da29ecf 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml @@ -6,10 +6,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml index 7af31d7..4664933 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml @@ -6,8 +6,8 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Layout.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Layout.qml index 360aa6f..cec9465 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/Layout.qml @@ -5,10 +5,10 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml index 9f2fd2d..276dfff 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml @@ -5,8 +5,8 @@ import QtQuick import QtGraphs -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml index fa89bc8..045b3c7 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml @@ -5,8 +5,8 @@ import QtQuick import QtGraphs -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml index 9d1b2e8..8adbf68 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml @@ -5,8 +5,8 @@ import QtQuick import QtGraphs -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml index 7a04f57..377af9f 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml @@ -5,8 +5,8 @@ import QtQuick import QtGraphs -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml index f177a50..6fd6687 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml index 585ee24..04299dd 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml index f57f027..b32087f 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml index a3d5b92..42e891f 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml @@ -5,11 +5,11 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Logic as EaLogic +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml index 5e16781..ad4c6ca 100644 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml @@ -5,8 +5,8 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/Gui/StatusBar.qml b/src/easyshapes_app/Gui/StatusBar.qml index 2bd91f9..8f1b73a 100644 --- a/src/easyshapes_app/Gui/StatusBar.qml +++ b/src/easyshapes_app/Gui/StatusBar.qml @@ -5,9 +5,9 @@ import QtQuick import QtQuick.Controls -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Elements as EaElements -import EasyApp.Gui.Components as EaComponents +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index 1905bb0..6f03656 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -6,10 +6,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements import Qt.labs.qmlmodels import Gui.Globals as Globals @@ -55,7 +55,7 @@ ApplicationWindow{ icon: 'wrench' collapsed: false - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml'} + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml'} } EaComponents.ListView { diff --git a/src/easyshapes_app/test2.qml b/src/easyshapes_app/test2.qml index 82de25c..4468ba5 100644 --- a/src/easyshapes_app/test2.qml +++ b/src/easyshapes_app/test2.qml @@ -6,10 +6,10 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApp.Gui.Globals as EaGlobals -import EasyApp.Gui.Style as EaStyle -import EasyApp.Gui.Components as EaComponents -import EasyApp.Gui.Elements as EaElements +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements import Qt.labs.qmlmodels import Gui.Globals as Globals From d2f8c81162224a34ee06724b5eae12efb3156bbb Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Fri, 1 May 2026 12:04:49 +0200 Subject: [PATCH 16/32] separate columns for fractions groups in lamella --- .../Sidebar/Basic/Components/Fractions.qml | 2 +- .../Sidebar/Basic/Groups/Lamellae.qml | 49 +++++++++++-------- src/easyshapes_app/test.qml | 2 +- 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml index eb17efb..3bec9d2 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml @@ -30,7 +30,7 @@ EaComponents.ListView { color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Molar ratio") + text: qsTr("Mole ratio") color: EaStyle.Colors.themeForegroundMinor } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml index 65a20de..966bdc8 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml @@ -43,11 +43,11 @@ EaElements.GroupColumn { color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Inner Dmin, nm") + text: qsTr("Inner dmin, nm") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Outer Dmin, nm") + text: qsTr("Outer dmin, nm") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { @@ -63,7 +63,7 @@ EaElements.GroupColumn { model: ListModel { id: lamellaeModel - ListElement { rmin: 0.5; innerDmin: 0.25; outerDmin: 0.25; shell: 0.5; symmetric: true } + ListElement { rmin: 0.5; innerDmin: 0.25; outerDmin: 0.3; shell: 0.5; symmetric: true } } delegateModelAccess: DelegateModel.ReadWrite @@ -91,8 +91,7 @@ EaElements.GroupColumn { validator: DoubleValidator { bottom: 0.25 } } EaComponents.ListViewTextInput { - text: symmetric ? innerDmin : outerDmin - enabled: !symmetric + text: outerDmin onEditingFinished: outerDmin = parseFloat(text) validator: DoubleValidator { bottom: 0.25 } } @@ -125,7 +124,7 @@ EaElements.GroupColumn { fontIcon: "plus-circle" text: qsTr("Add lamella") width: buttonWidth - onClicked: lamellaeModel.append({ rmin: 0.5, innerDmin: 0.25, outerDmin: 0.25, shell: 0.5, symmetric: true }) + onClicked: lamellaeModel.append({ rmin: 0.5, innerDmin: 0.25, outerDmin: 0.3, shell: 0.5, symmetric: true }) } } @@ -133,28 +132,36 @@ EaElements.GroupColumn { id: fractionsSection visible: lamellae.selectedIndexes.length > 0 width: parent.width + spacing: EaStyle.Sizes.groupBoxSpacing readonly property int selectedRow: lamellae.selectedIndexes.length > 0 ? lamellae.selectedIndexes[0].row : -1 readonly property bool selectedSymmetric: selectedRow >= 0 && lamellaeModel.get(selectedRow).symmetric - EaElements.Label { - enabled: false - text: fractionsSection.selectedSymmetric - ? qsTr("Lamella %1 Leaflet Fractions").arg(fractionsSection.selectedRow + 1) - : qsTr("Lamella %1 Inner Leaflet Fractions").arg(fractionsSection.selectedRow + 1) - } - Local.Fractions { - id: innerFractions - } + Column { + width: parent.width - EaElements.Label { - visible: !fractionsSection.selectedSymmetric - enabled: false - text: qsTr("Lamella %1 Outer Leaflet Fractions").arg(fractionsSection.selectedRow + 1) + EaElements.Label { + enabled: false + text: fractionsSection.selectedSymmetric + ? qsTr("Lamella %1 Fractions").arg(fractionsSection.selectedRow + 1) + : qsTr("Lamella %1 Inner Leaflet Fractions").arg(fractionsSection.selectedRow + 1) + } + Local.Fractions { + id: innerFractions + } } - Local.Fractions { - id: outerFractions + + Column { + width: parent.width visible: !fractionsSection.selectedSymmetric + + EaElements.Label { + enabled: false + text: qsTr("Lamella %1 Outer Leaflet Fractions").arg(fractionsSection.selectedRow + 1) + } + Local.Fractions { + id: outerFractions + } } } } diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index 6f03656..8f55d2d 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -55,7 +55,7 @@ ApplicationWindow{ icon: 'wrench' collapsed: false - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml'} + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml'} } EaComponents.ListView { From 23c4765d68b020c37fb210ea43e66aaaebe53e34 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Mon, 4 May 2026 16:17:49 +0200 Subject: [PATCH 17/32] Transfering the sample model's model to mock qml backend --- src/easyshapes_app.qrc | 1 + src/easyshapes_app/Backends/MockBackend.qml | 1 + .../Backends/MockQml/SampleModel.qml | 78 +++++++++++++++++++ src/easyshapes_app/Backends/MockQml/qmldir | 1 + .../Gui/Globals/BackendWrapper.qml | 18 +++++ .../Sidebar/Basic/Groups/SampleModel.qml | 39 ++++------ .../SampleModel/Sidebar/Basic/Layout.qml | 4 +- .../Sidebar/Basic/Popups/CreateNewModel.qml | 16 ++-- .../Basic/Popups/LoadExistingModel.qml | 27 ++----- 9 files changed, 126 insertions(+), 59 deletions(-) create mode 100644 src/easyshapes_app/Backends/MockQml/SampleModel.qml diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index 525180b..dc88980 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -6,6 +6,7 @@ easyshapes_app/Backends/MockQml/Project.qml easyshapes_app/Backends/MockQml/qmldir easyshapes_app/Backends/MockQml/Report.qml + easyshapes_app/Backends/MockQml/SampleModel.qml easyshapes_app/Backends/MockQml/Status.qml easyshapes_app/Backends/qmldir easyshapes_app/Gui/ApplicationWindow.qml diff --git a/src/easyshapes_app/Backends/MockBackend.qml b/src/easyshapes_app/Backends/MockBackend.qml index f911028..a83c9a1 100644 --- a/src/easyshapes_app/Backends/MockBackend.qml +++ b/src/easyshapes_app/Backends/MockBackend.qml @@ -12,6 +12,7 @@ import Backends.MockQml as MockLogic QtObject { property var project: MockLogic.Project + property var sampleModel: MockLogic.SampleModel property var analysis: MockLogic.Analysis property var status: MockLogic.Status property var report: MockLogic.Report diff --git a/src/easyshapes_app/Backends/MockQml/SampleModel.qml b/src/easyshapes_app/Backends/MockQml/SampleModel.qml new file mode 100644 index 0000000..9ba09d1 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/SampleModel.qml @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + +QtObject { + + property bool created: false + + // Loaded sample model — JS array of length 0 or 1. + // Capacity is one by convention (single record), but the value is + // exposed as an array so QML ListView can consume it directly + // (Qt 6 ListView only supports int / ListModel / QAbstractItemModel / JS array). + // Element shape: { name, structure_type, description } + property var loaded: [] + + readonly property var structureTypes: [ + 'Ring', 'Ball', 'Vesicle', 'Rod', 'Bilayer', 'Monolayer', 'Lattice' + ] + + readonly property string currentStructureType: loaded.length > 0 ? loaded[0].structure_type : '' + + // Catalog of saveable/loadable models (asset library). + // ListModel mirrors the Python QAbstractListModel with roles + // (name, structure_type, description) so delegate code is identical + // for both real and mock backends, and ItemSelectionModel works. + readonly property var availableModels: ListModel { + ListElement { name: 'Samle1_aluv'; structure_type: 'Vesicle'; description: 'In order to avoid a prolonged pro-inflammatory neutrophil response, signaling downstream of an agonist-activated G protein-coupled receptor (GPCR) has to be rapidly terminated. Among the family of GPCR kinases (GRKs) that regulate receptor phosphorylation and signaling termination, GRK2, which is highly expressed by immune cells, plays an important role.' } + ListElement { name: 'Sample2_nanodisc'; structure_type: 'Ring'; description: 'The medium chain fatty acid receptor GPR84 as well as formyl peptide receptor 2 (FPR2)' } + ListElement { name: 'Sample3_cubosome'; structure_type: 'Lattice'; description: 'receptors expressed in neutrophils, play a key role in regulating inflammation. In this study, we investigated the effects of GRK2 inhibitors on neutrophil functions induced by GPR84 and FPR2 agonists.' } + ListElement { name: 'Sample4'; structure_type: 'Ring'; description: 'GRK2 was shown to be expressed in human neutrophils and analysis of subcellular fractions' } + ListElement { name: 'Sample5'; structure_type: 'Ball'; description: 'revealed a cytosolic localization. The GRK2 inhibitors enhanced and prolonged neutrophil production ' } + ListElement { name: 'Sample6'; structure_type: 'Vesicle'; description: 'production of reactive oxygen species (ROS) induced by GPR84- but not FPR2-agonists' } + ListElement { name: 'Sample7'; structure_type: 'Rod'; description: 'suggesting a receptor selective function of GRK2. This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84' } + ListElement { name: 'Sample8'; structure_type: 'Bilayer'; description: 'This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84 agonist was not affected by the GRK2 inhibitor.' } + ListElement { name: 'Sample9'; structure_type: 'Monolayer'; description: 'Termination of this β-arrestin independent response relied, similar to the response induced by FPR2 agonists, primarily on the actin cytoskeleton.' } + ListElement { name: 'Samplewithareallylongname'; structure_type: 'Lattice'; description: 'In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils.' } + } + + function setLoaded(model) { + console.debug(`Loading sample model '${model.name}'`) + loaded = [{ + name: model.name, + structure_type: model.structure_type, + description: model.description + }] + created = true + } + + function updateField(field, value) { + if (loaded.length === 0) return + loaded[0][field] = value + } + + function clear() { + loaded = [] + created = false + } + + function saveToCatalog() { + if (loaded.length === 0 || !loaded[0].name) return + availableModels.append({ + name: loaded[0].name, + structure_type: loaded[0].structure_type, + description: loaded[0].description + }) + console.debug(`Saved sample model '${loaded[0].name}' to catalog`) + } + + function removeFromCatalog(index) { + if (index < 0 || index >= availableModels.count) return + availableModels.remove(index) + } + +} diff --git a/src/easyshapes_app/Backends/MockQml/qmldir b/src/easyshapes_app/Backends/MockQml/qmldir index 7ee148e..5f3167d 100644 --- a/src/easyshapes_app/Backends/MockQml/qmldir +++ b/src/easyshapes_app/Backends/MockQml/qmldir @@ -1,6 +1,7 @@ module MockQml singleton Project Project.qml +singleton SampleModel SampleModel.qml singleton Analysis Analysis.qml singleton Report Report.qml singleton Status Status.qml diff --git a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml index e8529b3..4fd1950 100644 --- a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml +++ b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml @@ -56,6 +56,24 @@ QtObject { function projectSave() { activeBackend.project.save() } function projectEditInfo(path, new_value) { activeBackend.project.editInfo(path, new_value) } + //////////////////// + // Sample Model page + //////////////////// + + readonly property var sampleModelLoaded: activeBackend.sampleModel.loaded + readonly property var sampleModelAvailable: activeBackend.sampleModel.availableModels + readonly property var sampleModelStructureTypes: activeBackend.sampleModel.structureTypes + readonly property string sampleModelCurrentStructureType: activeBackend.sampleModel.currentStructureType + + property bool sampleModelCreated: activeBackend.sampleModel.created + onSampleModelCreatedChanged: activeBackend.sampleModel.created = sampleModelCreated + + function sampleModelSetLoaded(model) { activeBackend.sampleModel.setLoaded(model) } + function sampleModelUpdateField(field, value) { activeBackend.sampleModel.updateField(field, value) } + function sampleModelClear() { activeBackend.sampleModel.clear() } + function sampleModelSaveToCatalog() { activeBackend.sampleModel.saveToCatalog() } + function sampleModelRemoveFromCatalog(index) { activeBackend.sampleModel.removeFromCatalog(index) } + //////////////// // Analysis page //////////////// diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index 8816d91..3aa2999 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -4,7 +4,6 @@ import QtQuick import QtQuick.Controls -//import QtQuick.Dialogs import EasyApplication.Gui.Globals as EaGlobals import EasyApplication.Gui.Style as EaStyle @@ -17,7 +16,6 @@ import Gui.Globals as Globals EaElements.GroupColumn { id: root property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 - property string currentStructureType: "" Component.onCompleted: Globals.References.pages.samplemodel.sidebar.basic.groups.sampleModel = root @@ -47,36 +45,28 @@ EaElements.GroupColumn { } } - model: ListModel { - id: loadedSambleModelsModel - onCountChanged: root.currentStructureType = count > 0 ? get(0).structure_type : "" - } - - delegateModelAccess: DelegateModel.ReadWrite + model: Globals.BackendWrapper.sampleModelLoaded delegate: EaComponents.ListViewDelegate { + required property var modelData required property int index - required property string name - required property string structure_type - required property string description EaComponents.ListViewTextInput { - text: name - onEditingFinished: name = text + text: modelData ? modelData.name : '' + onEditingFinished: Globals.BackendWrapper.sampleModelUpdateField('name', text) } - EaComponents.TableViewComboBox{ - model: [qsTr("Ring"), qsTr("Ball"), qsTr("Vesicle"), qsTr("Rod"), qsTr("Bilayer"), qsTr("Monolayer"), qsTr("Lattice")] + EaComponents.TableViewComboBox { + model: Globals.BackendWrapper.sampleModelStructureTypes Component.onCompleted: { - currentIndex = model.indexOf(structure_type) + currentIndex = model.indexOf(modelData.structure_type) } - onActivated: (index) => { - structure_type = model[index] - root.currentStructureType = model[index] + onActivated: (i) => { + Globals.BackendWrapper.sampleModelUpdateField('structure_type', model[i]) } } EaComponents.ListViewTextInput { - text: description - onEditingFinished: description = text + text: modelData ? modelData.description : '' + onEditingFinished: Globals.BackendWrapper.sampleModelUpdateField('description', text) } } } @@ -103,21 +93,20 @@ EaElements.GroupColumn { fontIcon: "download" text: qsTr("Save model") width: buttonWidth - enabled: loadedSampleModel.model && loadedSampleModel.model.count > 0 && loadedSambleModelsModel.get(0).name !== "" - onClicked: loadExistingModelLoader.item.availableModelsModel.append(loadedSambleModelsModel.get(0)) + enabled: Globals.BackendWrapper.sampleModelLoaded.length > 0 + && Globals.BackendWrapper.sampleModelLoaded[0].name !== "" + onClicked: Globals.BackendWrapper.sampleModelSaveToCatalog() } } Loader { id: loadExistingModelLoader source: '../Popups/LoadExistingModel.qml' - onLoaded: item.targetModel = loadedSambleModelsModel } Loader { id: createNewModelLoader source: '../Popups/CreateNewModel.qml' - onLoaded: item.targetModel = loadedSambleModelsModel } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index 75f4967..3f61d10 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -31,7 +31,7 @@ EaComponents.SideBarColumn { EaElements.GroupBox { title: qsTr('Layers') icon: 'layer-group' // 'grip-lines' - visible: sampleModelLoader.item ? sampleModelLoader.item.currentStructureType === 'Ball' : false + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Ball' Loader { source: 'Groups/Layers.qml' } } @@ -39,7 +39,7 @@ EaComponents.SideBarColumn { EaElements.GroupBox { title: qsTr('Lamellae') icon: 'layer-group' - visible: sampleModelLoader.item ? sampleModelLoader.item.currentStructureType === 'Vesicle' : false + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Vesicle' Loader { source: 'Groups/Lamellae.qml' } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml index 09d397a..39a6fe4 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml @@ -17,21 +17,17 @@ import Gui.Globals as Globals EaElements.Dialog{ id: sampleModelCreationDialog - property var targetModel property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 title: qsTr("Create a new Sample Model") standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { - targetModel.clear() - targetModel.append( - { - name: sampleModelNameField.text, - structure_type: sampleModelTypeField.currentText, - description: sampleModelDescrField.text - } - ) + Globals.BackendWrapper.sampleModelSetLoaded({ + name: sampleModelNameField.text, + structure_type: sampleModelTypeField.currentText, + description: sampleModelDescrField.text + }) } Column { @@ -63,7 +59,7 @@ EaElements.Dialog{ EaElements.ComboBox { id: sampleModelTypeField implicitWidth: (sampleModelCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 - model: [qsTr("Ring"), qsTr("Ball"), qsTr("Vesicle"), qsTr("Rod"), qsTr("Bilayer"), qsTr("Monolayer"), qsTr("Lattice")] + model: Globals.BackendWrapper.sampleModelStructureTypes } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml index da29ecf..73e8542 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml @@ -18,9 +18,7 @@ import Gui.Globals as Globals EaElements.Dialog{ id: sampleModelLoadDialog - property var targetModel property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - property alias availableModelsModel: availableSambleModelsModel title: qsTr("Load a Sample Model from the Asset Library") standardButtons: Dialog.Ok | Dialog.Cancel @@ -30,10 +28,8 @@ EaElements.Dialog{ if (indexes.length > 0) { var row = indexes[0].row - var item = availableSambleModelsModel.get(row) - - targetModel.clear() - targetModel.append(item) + var item = Globals.BackendWrapper.sampleModelAvailable.get(row) + Globals.BackendWrapper.sampleModelSetLoaded(item) loadModelListView.clearSelection() } } @@ -76,23 +72,10 @@ EaElements.Dialog{ text: qsTr("Description") color: EaStyle.Colors.themeForegroundMinor } + EaComponents.TableViewLabel {} // filler } - model: ListModel { - id: availableSambleModelsModel - ListElement { name: "Samle1_aluv"; structure_type: "Vesicle"; description: "In order to avoid a prolonged pro-inflammatory neutrophil response, signaling downstream of an agonist-activated G protein-coupled receptor (GPCR) has to be rapidly terminated. Among the family of GPCR kinases (GRKs) that regulate receptor phosphorylation and signaling termination, GRK2, which is highly expressed by immune cells, plays an important role." } - ListElement { name: "Sample2_nanodisc"; structure_type: "Ring"; description: "The medium chain fatty acid receptor GPR84 as well as formyl peptide receptor 2 (FPR2)" } - ListElement { name: "Sample3_cubosome"; structure_type: "Lattice"; description: "receptors expressed in neutrophils, play a key role in regulating inflammation. In this study, we investigated the effects of GRK2 inhibitors on neutrophil functions induced by GPR84 and FPR2 agonists." } - ListElement { name: "Sample4"; structure_type: "Ring"; description: "GRK2 was shown to be expressed in human neutrophils and analysis of subcellular fractions" } - ListElement { name: "Sample5"; structure_type: "Ball"; description: "revealed a cytosolic localization. The GRK2 inhibitors enhanced and prolonged neutrophil production " } - ListElement { name: "Sample6"; structure_type: "Vesicle"; description: "production of reactive oxygen species (ROS) induced by GPR84- but not FPR2-agonists" } - ListElement { name: "Sample7"; structure_type: "Rod"; description: "suggesting a receptor selective function of GRK2. This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84" } - ListElement { name: "Sample8"; structure_type: "Bilayer"; description: "This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84 agonist was not affected by the GRK2 inhibitor." } - ListElement { name: "Sample9"; structure_type: "Monolayer"; description: "Termination of this β-arrestin independent response relied, similar to the response induced by FPR2 agonists, primarily on the actin cytoskeleton." } - ListElement { name: "Samplewithareallylongname"; structure_type: "Lattice"; description: "In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils." } - } - - delegateModelAccess: DelegateModel.ReadOnly + model: Globals.BackendWrapper.sampleModelAvailable delegate: EaComponents.ListViewDelegate { required property int index @@ -117,7 +100,7 @@ EaElements.Dialog{ EaComponents.TableViewButton { fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this component") - onClicked: availableSambleModelsModel.remove(index) + onClicked: Globals.BackendWrapper.sampleModelRemoveFromCatalog(index) } } } From 2e0b32ee28bb6dd8dddf8879ce7704146c0658a0 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 6 May 2026 16:28:45 +0200 Subject: [PATCH 18/32] Added structure definition groups --- src/easyshapes_app.qrc | 20 ++ src/easyshapes_app/Backends/MockBackend.qml | 31 +++ .../Backends/MockQml/BallStructure.qml | 14 ++ .../Backends/MockQml/BilayerStructure.qml | 14 ++ .../Backends/MockQml/Components.qml | 58 ++++++ .../Backends/MockQml/Fractions.qml | 69 +++++++ .../Backends/MockQml/Lamellae.qml | 136 ++++++++++++++ .../Backends/MockQml/LatticeStructure.qml | 69 +++++++ .../Backends/MockQml/Layers.qml | 91 +++++++++ .../Backends/MockQml/MonolayerStructure.qml | 14 ++ .../Backends/MockQml/RingStructure.qml | 17 ++ .../Backends/MockQml/RodStructure.qml | 16 ++ .../Backends/MockQml/SampleModel.qml | 9 +- .../Backends/MockQml/VesicleStructure.qml | 14 ++ src/easyshapes_app/Backends/MockQml/qmldir | 11 ++ .../Gui/Globals/BackendWrapper.qml | 100 +++++++++- .../Sidebar/Basic/Components/Fractions.qml | 13 +- .../Sidebar/Basic/Groups/BallStructure.qml | 51 +++++ .../Sidebar/Basic/Groups/BilayerStructure.qml | 60 ++++++ .../Sidebar/Basic/Groups/Components.qml | 16 +- .../Sidebar/Basic/Groups/Lamellae.qml | 43 +++-- .../Sidebar/Basic/Groups/LatticeStructure.qml | 177 ++++++++++++++++++ .../Sidebar/Basic/Groups/Layers.qml | 30 +-- .../Basic/Groups/MonolayerStructure.qml | 60 ++++++ .../Sidebar/Basic/Groups/RingStructure.qml | 89 +++++++++ .../Sidebar/Basic/Groups/RodStructure.qml | 79 ++++++++ .../Sidebar/Basic/Groups/VesicleStructure.qml | 51 +++++ .../SampleModel/Sidebar/Basic/Layout.qml | 56 ++++++ .../Basic/Popups/CreateNewComponent.qml | 12 +- .../Basic/Popups/CreateNewSubstructure.qml | 119 ++++++++++++ .../Basic/Popups/LoadExistingComponent.qml | 3 +- .../Basic/Popups/LoadExistingSubstructure.qml | 107 +++++++++++ .../Sidebar/Basic/Popups/OpenAssetFile.qml | 4 +- src/easyshapes_app/test.qml | 86 +-------- 34 files changed, 1606 insertions(+), 133 deletions(-) create mode 100644 src/easyshapes_app/Backends/MockQml/BallStructure.qml create mode 100644 src/easyshapes_app/Backends/MockQml/BilayerStructure.qml create mode 100644 src/easyshapes_app/Backends/MockQml/Components.qml create mode 100644 src/easyshapes_app/Backends/MockQml/Fractions.qml create mode 100644 src/easyshapes_app/Backends/MockQml/Lamellae.qml create mode 100644 src/easyshapes_app/Backends/MockQml/LatticeStructure.qml create mode 100644 src/easyshapes_app/Backends/MockQml/Layers.qml create mode 100644 src/easyshapes_app/Backends/MockQml/MonolayerStructure.qml create mode 100644 src/easyshapes_app/Backends/MockQml/RingStructure.qml create mode 100644 src/easyshapes_app/Backends/MockQml/RodStructure.qml create mode 100644 src/easyshapes_app/Backends/MockQml/VesicleStructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index dc88980..41fbf8a 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -3,11 +3,22 @@ easyshapes_app.qmlproject easyshapes_app/Backends/MockBackend.qml easyshapes_app/Backends/MockQml/Analysis.qml + easyshapes_app/Backends/MockQml/BallStructure.qml + easyshapes_app/Backends/MockQml/BilayerStructure.qml + easyshapes_app/Backends/MockQml/Components.qml + easyshapes_app/Backends/MockQml/Fractions.qml + easyshapes_app/Backends/MockQml/Lamellae.qml + easyshapes_app/Backends/MockQml/LatticeStructure.qml + easyshapes_app/Backends/MockQml/Layers.qml + easyshapes_app/Backends/MockQml/MonolayerStructure.qml easyshapes_app/Backends/MockQml/Project.qml easyshapes_app/Backends/MockQml/qmldir easyshapes_app/Backends/MockQml/Report.qml + easyshapes_app/Backends/MockQml/RingStructure.qml + easyshapes_app/Backends/MockQml/RodStructure.qml easyshapes_app/Backends/MockQml/SampleModel.qml easyshapes_app/Backends/MockQml/Status.qml + easyshapes_app/Backends/MockQml/VesicleStructure.qml easyshapes_app/Backends/qmldir easyshapes_app/Gui/ApplicationWindow.qml easyshapes_app/Gui/Globals/ApplicationInfo.qml @@ -40,17 +51,26 @@ easyshapes_app/Gui/Pages/SampleModel/Layout.qml easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml easyshapes_app/Gui/Pages/Toolbox/Layout.qml easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml diff --git a/src/easyshapes_app/Backends/MockBackend.qml b/src/easyshapes_app/Backends/MockBackend.qml index a83c9a1..c994427 100644 --- a/src/easyshapes_app/Backends/MockBackend.qml +++ b/src/easyshapes_app/Backends/MockBackend.qml @@ -13,8 +13,39 @@ QtObject { property var project: MockLogic.Project property var sampleModel: MockLogic.SampleModel + property var components: MockLogic.Components + // Default fractions set tied to the shared component list. + // Used as the fallback for the Fractions sidebar component when no + // per-row override is set. + property var fractions: MockLogic.Fractions { source: MockLogic.Components.loaded } + // Layers owns one Fractions instance per layer (per-row state) and + // shares the same component source so names stay in sync with the + // global components list. + property var layers: MockLogic.Layers + // Lamellae owns distinct inner/outer Fractions instances per lamella. + property var lamellae: MockLogic.Lamellae + // Ring structure parameters (single record, edited inline). + property var ringStructure: MockLogic.RingStructure + // Ball structure parameters (single record, edited inline). + property var ballStructure: MockLogic.BallStructure + // Vesicle structure parameters (single record, edited inline). + property var vesicleStructure: MockLogic.VesicleStructure + // Rod structure parameters (single record, edited inline). + property var rodStructure: MockLogic.RodStructure + // Bilayer structure parameters (single record, edited inline). + property var bilayerStructure: MockLogic.BilayerStructure + // Monolayer structure parameters (single record, edited inline). + property var monolayerStructure: MockLogic.MonolayerStructure + // Lattice structure parameters (single record, edited inline) plus a + // single-record substructure with its own asset library. + property var latticeStructure: MockLogic.LatticeStructure property var analysis: MockLogic.Analysis property var status: MockLogic.Status property var report: MockLogic.Report + Component.onCompleted: { + layers.fractionsSource = MockLogic.Components.loaded + lamellae.fractionsSource = MockLogic.Components.loaded + } + } diff --git a/src/easyshapes_app/Backends/MockQml/BallStructure.qml b/src/easyshapes_app/Backends/MockQml/BallStructure.qml new file mode 100644 index 0000000..8b89830 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/BallStructure.qml @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + property bool fxz: false + property bool rev: false + property string fill: 'FIBO' +} diff --git a/src/easyshapes_app/Backends/MockQml/BilayerStructure.qml b/src/easyshapes_app/Backends/MockQml/BilayerStructure.qml new file mode 100644 index 0000000..c4dd28c --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/BilayerStructure.qml @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + property double zsep: 0.0 + property int nside: 1 + property double dmin: 0.5 +} diff --git a/src/easyshapes_app/Backends/MockQml/Components.qml b/src/easyshapes_app/Backends/MockQml/Components.qml new file mode 100644 index 0000000..d75cfdb --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/Components.qml @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + +QtObject { + + // Working set of components currently loaded into the sample model. + // ListModel (not JS array) so EaComponents.ListView's ItemSelectionModel + // can drive Ctrl/Shift multi-selection, and so role-based delegate + // properties (`required property string name` etc.) bind correctly. + // Roles: name, component_type, atoms, mint, mext. + readonly property var loaded: ListModel { + id: loadedComponentsModel + } + + // File paths staged while creating a new component. + // Roles: path. + readonly property var pendingFilePaths: ListModel { + id: pendingFilePathsModel + } + + function appendItem(item) { + loadedComponentsModel.append({ + name: item.name, + component_type: item.component_type, + atoms: item.atoms, + mint: item.mint, + mext: item.mext + }) + } + + function removeItem(index) { + if (index < 0 || index >= loadedComponentsModel.count) return + loadedComponentsModel.remove(index) + } + + function clear() { + loadedComponentsModel.clear() + } + + function appendPendingFilePath(path) { + pendingFilePathsModel.append({ path: path }) + } + + function removePendingFilePath(index) { + if (index < 0 || index >= pendingFilePathsModel.count) return + pendingFilePathsModel.remove(index) + } + + function clearPendingFilePaths() { + pendingFilePathsModel.clear() + } + +} diff --git a/src/easyshapes_app/Backends/MockQml/Fractions.qml b/src/easyshapes_app/Backends/MockQml/Fractions.qml new file mode 100644 index 0000000..2daaef2 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/Fractions.qml @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick + + +// Fractions backend object — instantiable, NOT a singleton. +// One instance per layer/lamella. Tracks the shared component list via +// `source` and adds per-instance `fracs` / `present` state. Sync between +// `source` and the exposed `model` is owned here so GUI consumers only +// bind `model` and read/write roles via DelegateModel.ReadWrite. +QtObject { + id: root + + // Source list of components this fractions set tracks. + // Typically `MockLogic.Components.loaded`. May be reassigned. + property var source: null + + // ListModel exposed to the GUI ListView. + // Roles: name (mirrored from source), fracs (per-instance), + // present (per-instance). + readonly property var model: ListModel { id: backing } + + Component.onCompleted: rebuild() + onSourceChanged: rebuild() + + // Internal sync. Surgical inserts/removes preserve the per-instance + // fracs/present state of unaffected rows; full rebuild only on reset + // or source reassignment. + property var __sync: Connections { + target: root.source + + function onRowsInserted(parent, first, last) { + for (let i = first; i <= last; ++i) { + const c = root.source.get(i) + backing.insert(i, { name: c.name, fracs: 100, present: true }) + } + } + function onRowsRemoved(parent, first, last) { + for (let i = last; i >= first; --i) backing.remove(i) + } + function onDataChanged(topLeft, bottomRight, roles) { + for (let i = topLeft.row; i <= bottomRight.row; ++i) { + backing.setProperty(i, 'name', root.source.get(i).name) + } + } + function onModelReset() { root.rebuild() } + } + + function rebuild() { + backing.clear() + if (!source) return + for (let i = 0; i < source.count; ++i) { + const c = source.get(i) + backing.append({ name: c.name, fracs: 100, present: true }) + } + } + + function setFracs(index, value) { + if (index < 0 || index >= backing.count) return + backing.setProperty(index, 'fracs', value) + } + + function setPresent(index, value) { + if (index < 0 || index >= backing.count) return + backing.setProperty(index, 'present', value) + } +} diff --git a/src/easyshapes_app/Backends/MockQml/Lamellae.qml b/src/easyshapes_app/Backends/MockQml/Lamellae.qml new file mode 100644 index 0000000..df7012e --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/Lamellae.qml @@ -0,0 +1,136 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +// Pattern D backend (multi-row list with selection) for lamellae, plus +// parallel arrays of per-lamella Fractions instances. Each lamella owns +// distinct inner and outer leaflet fractions so asymmetric lamellae can +// diverge without leaking state between leaflets. +QtObject { + id: root + + // Source of components shared with every leaflet's Fractions instance. + // Each leaflet keeps its own per-instance fracs/present state but + // mirrors the same component name list. + property var fractionsSource: null + + // Table of lamellae. Roles: rmin, innerDmin, outerDmin, shell, symmetric. + readonly property var items: ListModel { + id: lamellaeModel + ListElement { rmin: 0.5; innerDmin: 0.25; outerDmin: 0.3; shell: 0.5; symmetric: true } + } + + // Parallel arrays of Fractions QtObjects, one inner and one outer + // leaflet instance per lamella. + property var innerFractionsInstances: [] + property var outerFractionsInstances: [] + property int itemsRevision: 0 + property int fractionsRevision: 0 + + property var __fractionsComponent: null + + Component.onCompleted: { + __fractionsComponent = Qt.createComponent(Qt.resolvedUrl("Fractions.qml")) + if (__fractionsComponent.status !== Component.Ready) { + console.warn("Lamellae: failed to load Fractions.qml:", __fractionsComponent.errorString()) + return + } + const inner = [] + const outer = [] + for (let i = 0; i < lamellaeModel.count; ++i) { + inner.push(__createFractions()) + outer.push(__createFractions()) + } + innerFractionsInstances = inner + outerFractionsInstances = outer + itemsRevision++ + fractionsRevision++ + } + + function __createFractions() { + // Bind (don't snapshot) so per-instance Fractions follow later + // assignments to root.fractionsSource — singleton init order is + // not deterministic, so the source may be set after this runs. + const f = __fractionsComponent.createObject(root) + f.source = Qt.binding(function() { return root.fractionsSource }) + return f + } + + function appendItem(item) { + lamellaeModel.append({ + rmin: item.rmin, + innerDmin: item.innerDmin, + outerDmin: item.outerDmin, + shell: item.shell, + symmetric: item.symmetric + }) + + const inner = innerFractionsInstances.slice() + const outer = outerFractionsInstances.slice() + inner.push(__createFractions()) + outer.push(__createFractions()) + innerFractionsInstances = inner + outerFractionsInstances = outer + itemsRevision++ + fractionsRevision++ + } + + function removeItem(index) { + if (index < 0 || index >= lamellaeModel.count) return + lamellaeModel.remove(index) + + const inner = innerFractionsInstances.slice() + const outer = outerFractionsInstances.slice() + const removedInner = inner.splice(index, 1)[0] + const removedOuter = outer.splice(index, 1)[0] + if (removedInner) removedInner.destroy() + if (removedOuter) removedOuter.destroy() + innerFractionsInstances = inner + outerFractionsInstances = outer + itemsRevision++ + fractionsRevision++ + } + + function setRmin(index, value) { + if (index < 0 || index >= lamellaeModel.count) return + lamellaeModel.setProperty(index, 'rmin', value) + } + + function setInnerDmin(index, value) { + if (index < 0 || index >= lamellaeModel.count) return + lamellaeModel.setProperty(index, 'innerDmin', value) + } + + function setOuterDmin(index, value) { + if (index < 0 || index >= lamellaeModel.count) return + lamellaeModel.setProperty(index, 'outerDmin', value) + } + + function setShell(index, value) { + if (index < 0 || index >= lamellaeModel.count) return + lamellaeModel.setProperty(index, 'shell', value) + } + + function setSymmetric(index, value) { + if (index < 0 || index >= lamellaeModel.count) return + lamellaeModel.setProperty(index, 'symmetric', value) + itemsRevision++ + } + + function innerFractionsModelAt(index) { + if (index < 0 || index >= innerFractionsInstances.length) return null + const f = innerFractionsInstances[index] + return f ? f.model : null + } + + function outerFractionsModelAt(index) { + if (index < 0 || index >= outerFractionsInstances.length) return null + const f = outerFractionsInstances[index] + return f ? f.model : null + } +} diff --git a/src/easyshapes_app/Backends/MockQml/LatticeStructure.qml b/src/easyshapes_app/Backends/MockQml/LatticeStructure.qml new file mode 100644 index 0000000..c274952 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/LatticeStructure.qml @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + property double alpha: 0.0 + property double theta: 0.0 + property double sbuff: 1.0 + property string latticeType: 'LAT1' + property int nlatx: 1 + property int nlaty: 1 + property int nlatz: 1 + + readonly property var latticeTypes: ['LAT1', 'LAT2', 'LAT3'] + + readonly property var substructureTypes: [ + 'Ring', 'Ball', 'Vesicle', 'Rod', 'Bilayer', 'Monolayer' + ] + + // Loaded substructure — JS array of length 0 or 1, mirroring the + // SampleModel `loaded` shape so EaComponents.ListView can consume it. + // Element shape: { name, structure_type, description, file_path }. + property var substructureLoaded: [] + + readonly property var availableSubstructures: ListModel { + ListElement { name: 'CubicSeed'; structure_type: 'Ball'; description: 'Spherical seed for cubic packing' } + ListElement { name: 'HexRing'; structure_type: 'Ring'; description: 'Ring substructure for hexagonal lattice' } + ListElement { name: 'BilayerTile'; structure_type: 'Bilayer'; description: 'Planar tile for stacked lattices' } + ListElement { name: 'RodSeed'; structure_type: 'Rod'; description: 'Rod-shaped seed' } + ListElement { name: 'VesicleSeed'; structure_type: 'Vesicle'; description: 'Vesicle seed for hollow lattices' } + } + + function setSubstructureLoaded(item) { + substructureLoaded = [{ + name: item.name, + structure_type: item.structure_type, + description: item.description, + file_path: item.file_path !== undefined ? item.file_path : '' + }] + } + + function updateSubstructureField(field, value) { + if (substructureLoaded.length === 0) return + substructureLoaded[0][field] = value + } + + function clearSubstructure() { + substructureLoaded = [] + } + + function saveSubstructureToCatalog() { + if (substructureLoaded.length === 0 || !substructureLoaded[0].name) return + availableSubstructures.append({ + name: substructureLoaded[0].name, + structure_type: substructureLoaded[0].structure_type, + description: substructureLoaded[0].description + }) + } + + function removeSubstructureFromCatalog(index) { + if (index < 0 || index >= availableSubstructures.count) return + availableSubstructures.remove(index) + } +} diff --git a/src/easyshapes_app/Backends/MockQml/Layers.qml b/src/easyshapes_app/Backends/MockQml/Layers.qml new file mode 100644 index 0000000..19cbbb2 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/Layers.qml @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +// Pattern D backend (multi-row list with selection) for layers, plus a +// parallel JS array of per-layer Fractions instances. ListElement only +// accepts literal scalars, so the Fractions QtObjects can't live inside +// the ListModel rows — they're maintained in lockstep here. +QtObject { + id: root + + // Source of components shared with every layer's Fractions instance. + // Each layer keeps its own per-instance fracs/present state but + // mirrors the same name list. + property var fractionsSource: null + + // Table of layers. Roles: dmin, rmin. + readonly property var items: ListModel { + id: layersModel + ListElement { dmin: 0.25; rmin: 0.5 } + } + + // Parallel array of Fractions QtObjects, one per layer. + // Bumping the revision token triggers rebinding for `var` consumers, + // since rebuilding the array reference (not its contents) is what + // QML actually signals on. + property var fractionsInstances: [] + property int fractionsRevision: 0 + + property var __fractionsComponent: null + + Component.onCompleted: { + __fractionsComponent = Qt.createComponent(Qt.resolvedUrl("Fractions.qml")) + if (__fractionsComponent.status !== Component.Ready) { + console.warn("Layers: failed to load Fractions.qml:", __fractionsComponent.errorString()) + return + } + const arr = [] + for (let i = 0; i < layersModel.count; ++i) arr.push(__createFractions()) + fractionsInstances = arr + fractionsRevision++ + } + + function __createFractions() { + // Bind (don't snapshot) so per-instance Fractions follow later + // assignments to root.fractionsSource — singleton init order is + // not deterministic, so the source may be set after this runs. + const f = __fractionsComponent.createObject(root) + f.source = Qt.binding(function() { return root.fractionsSource }) + return f + } + + function appendItem(item) { + layersModel.append({ dmin: item.dmin, rmin: item.rmin }) + const arr = fractionsInstances.slice() + arr.push(__createFractions()) + fractionsInstances = arr + fractionsRevision++ + } + + function removeItem(index) { + if (index < 0 || index >= layersModel.count) return + layersModel.remove(index) + const arr = fractionsInstances.slice() + const removed = arr.splice(index, 1)[0] + if (removed) removed.destroy() + fractionsInstances = arr + fractionsRevision++ + } + + function setDmin(index, value) { + if (index < 0 || index >= layersModel.count) return + layersModel.setProperty(index, 'dmin', value) + } + + function setRmin(index, value) { + if (index < 0 || index >= layersModel.count) return + layersModel.setProperty(index, 'rmin', value) + } + + function fractionsModelAt(index) { + if (index < 0 || index >= fractionsInstances.length) return null + const f = fractionsInstances[index] + return f ? f.model : null + } +} diff --git a/src/easyshapes_app/Backends/MockQml/MonolayerStructure.qml b/src/easyshapes_app/Backends/MockQml/MonolayerStructure.qml new file mode 100644 index 0000000..c4dd28c --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/MonolayerStructure.qml @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + property double zsep: 0.0 + property int nside: 1 + property double dmin: 0.5 +} diff --git a/src/easyshapes_app/Backends/MockQml/RingStructure.qml b/src/easyshapes_app/Backends/MockQml/RingStructure.qml new file mode 100644 index 0000000..1ec3975 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/RingStructure.qml @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + property double dmin: 0.5 + property double rmin: 0.25 + property double alpha: 0.0 + property double theta: 0.0 + property double fxz: 0.0 + property double rev: 0.0 +} diff --git a/src/easyshapes_app/Backends/MockQml/RodStructure.qml b/src/easyshapes_app/Backends/MockQml/RodStructure.qml new file mode 100644 index 0000000..5c0770f --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/RodStructure.qml @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + property double dmin: 0.5 + property double rmin: 0.25 + property int turns: 1 + property bool fxz: false + property bool rev: false +} diff --git a/src/easyshapes_app/Backends/MockQml/SampleModel.qml b/src/easyshapes_app/Backends/MockQml/SampleModel.qml index 9ba09d1..7fef0d2 100644 --- a/src/easyshapes_app/Backends/MockQml/SampleModel.qml +++ b/src/easyshapes_app/Backends/MockQml/SampleModel.qml @@ -21,7 +21,12 @@ QtObject { 'Ring', 'Ball', 'Vesicle', 'Rod', 'Bilayer', 'Monolayer', 'Lattice' ] - readonly property string currentStructureType: loaded.length > 0 ? loaded[0].structure_type : '' + // Externally-observed structure type. Decoupled from `loaded` because + // updateField mutates `loaded[0]` in place (preserving the row delegate + // and any focused TextInput) and that mutation does NOT fire loadedChanged. + // The wrapper writes this property whenever structure_type is updated; + // setLoaded/clear keep it in sync with the active record. + property string currentStructureType: '' // Catalog of saveable/loadable models (asset library). // ListModel mirrors the Python QAbstractListModel with roles @@ -47,6 +52,7 @@ QtObject { structure_type: model.structure_type, description: model.description }] + currentStructureType = model.structure_type created = true } @@ -57,6 +63,7 @@ QtObject { function clear() { loaded = [] + currentStructureType = '' created = false } diff --git a/src/easyshapes_app/Backends/MockQml/VesicleStructure.qml b/src/easyshapes_app/Backends/MockQml/VesicleStructure.qml new file mode 100644 index 0000000..8b89830 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/VesicleStructure.qml @@ -0,0 +1,14 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + property bool fxz: false + property bool rev: false + property string fill: 'FIBO' +} diff --git a/src/easyshapes_app/Backends/MockQml/qmldir b/src/easyshapes_app/Backends/MockQml/qmldir index 5f3167d..04d6591 100644 --- a/src/easyshapes_app/Backends/MockQml/qmldir +++ b/src/easyshapes_app/Backends/MockQml/qmldir @@ -2,6 +2,17 @@ module MockQml singleton Project Project.qml singleton SampleModel SampleModel.qml +singleton Components Components.qml +Fractions Fractions.qml +singleton Layers Layers.qml +singleton Lamellae Lamellae.qml +singleton RingStructure RingStructure.qml +singleton BallStructure BallStructure.qml +singleton VesicleStructure VesicleStructure.qml +singleton RodStructure RodStructure.qml +singleton BilayerStructure BilayerStructure.qml +singleton MonolayerStructure MonolayerStructure.qml +singleton LatticeStructure LatticeStructure.qml singleton Analysis Analysis.qml singleton Report Report.qml singleton Status Status.qml diff --git a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml index 4fd1950..d83d2e4 100644 --- a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml +++ b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml @@ -69,11 +69,109 @@ QtObject { onSampleModelCreatedChanged: activeBackend.sampleModel.created = sampleModelCreated function sampleModelSetLoaded(model) { activeBackend.sampleModel.setLoaded(model) } - function sampleModelUpdateField(field, value) { activeBackend.sampleModel.updateField(field, value) } + function sampleModelUpdateField(field, value) { + activeBackend.sampleModel.updateField(field, value) + // updateField mutates `loaded[0]` in place to avoid recreating the + // ListView delegate (which would steal focus from any field being + // edited). The trade-off is that `loadedChanged` doesn't fire, so + // bindings derived from `loaded` won't re-evaluate. Mirror the + // structure_type into its own property here so visibility bindings + // (Layers/Lamellae GroupBoxes) update. + if (field === 'structure_type') activeBackend.sampleModel.currentStructureType = value + } function sampleModelClear() { activeBackend.sampleModel.clear() } function sampleModelSaveToCatalog() { activeBackend.sampleModel.saveToCatalog() } function sampleModelRemoveFromCatalog(index) { activeBackend.sampleModel.removeFromCatalog(index) } + // Components group (Sample Model sidebar) + readonly property var componentsLoaded: activeBackend.components.loaded + readonly property var componentsPendingFilePaths: activeBackend.components.pendingFilePaths + + function componentsAppend(item) { activeBackend.components.appendItem(item) } + function componentsRemove(index) { activeBackend.components.removeItem(index) } + function componentsClear() { activeBackend.components.clear() } + function componentsAppendPendingFilePath(path) { activeBackend.components.appendPendingFilePath(path) } + function componentsRemovePendingFilePath(index) { activeBackend.components.removePendingFilePath(index) } + function componentsClearPendingFilePaths() { activeBackend.components.clearPendingFilePaths() } + + // Global default Fractions set (fallback for the Fractions sidebar + // component). Layers/Lamellae do NOT use this — see the per-row + // fraction accessors below. + readonly property var fractionsModel: activeBackend.fractions.model + + function fractionsSetFracs(index, value) { activeBackend.fractions.setFracs(index, value) } + function fractionsSetPresent(index, value) { activeBackend.fractions.setPresent(index, value) } + + // Layers (Sample Model sidebar). One Fractions instance per layer is + // owned by the backend; the GUI binds the fractions list of the + // currently-selected layer via layersFractionsModelAt(row). + readonly property var layersItems: activeBackend.layers.items + readonly property int layersFractionsRevision: activeBackend.layers.fractionsRevision + + function layersAppend(item) { activeBackend.layers.appendItem(item) } + function layersRemove(index) { activeBackend.layers.removeItem(index) } + function layersSetDmin(index, value) { activeBackend.layers.setDmin(index, value) } + function layersSetRmin(index, value) { activeBackend.layers.setRmin(index, value) } + // Callers binding through this must also reference layersFractionsRevision + // in their binding body to re-evaluate when rows are inserted/removed. + function layersFractionsModelAt(index) { return activeBackend.layers.fractionsModelAt(index) } + + // Lamellae (Sample Model sidebar). One inner and one outer Fractions + // instance are owned per lamella; asymmetric lamellae bind both models. + readonly property var lamellaeItems: activeBackend.lamellae.items + readonly property int lamellaeItemsRevision: activeBackend.lamellae.itemsRevision + readonly property int lamellaeFractionsRevision: activeBackend.lamellae.fractionsRevision + + function lamellaeAppend(item) { activeBackend.lamellae.appendItem(item) } + function lamellaeRemove(index) { activeBackend.lamellae.removeItem(index) } + function lamellaeSetRmin(index, value) { activeBackend.lamellae.setRmin(index, value) } + function lamellaeSetInnerDmin(index, value) { activeBackend.lamellae.setInnerDmin(index, value) } + function lamellaeSetOuterDmin(index, value) { activeBackend.lamellae.setOuterDmin(index, value) } + function lamellaeSetShell(index, value) { activeBackend.lamellae.setShell(index, value) } + function lamellaeSetSymmetric(index, value) { activeBackend.lamellae.setSymmetric(index, value) } + // Callers binding through these must also reference lamellaeFractionsRevision + // in their binding body to re-evaluate when rows are inserted/removed. + function lamellaeInnerFractionsModelAt(index) { return activeBackend.lamellae.innerFractionsModelAt(index) } + function lamellaeOuterFractionsModelAt(index) { return activeBackend.lamellae.outerFractionsModelAt(index) } + + // Ring structure (Sample Model sidebar). Single-record fielded form + // shown only when sampleModelCurrentStructureType === 'Ring'. + readonly property var ringStructure: activeBackend.ringStructure + + // Ball structure (Sample Model sidebar). Single-record fielded form + // shown only when sampleModelCurrentStructureType === 'Ball'. + readonly property var ballStructure: activeBackend.ballStructure + + // Vesicle structure (Sample Model sidebar). Single-record fielded form + // shown only when sampleModelCurrentStructureType === 'Vesicle'. + readonly property var vesicleStructure: activeBackend.vesicleStructure + + // Rod structure (Sample Model sidebar). Single-record fielded form + // shown only when sampleModelCurrentStructureType === 'Rod'. + readonly property var rodStructure: activeBackend.rodStructure + + // Bilayer structure (Sample Model sidebar). Single-record fielded form + // shown only when sampleModelCurrentStructureType === 'Bilayer'. + readonly property var bilayerStructure: activeBackend.bilayerStructure + + // Monolayer structure (Sample Model sidebar). Single-record fielded form + // shown only when sampleModelCurrentStructureType === 'Monolayer'. + readonly property var monolayerStructure: activeBackend.monolayerStructure + + // Lattice structure (Sample Model sidebar). Single-record fielded form + // shown only when sampleModelCurrentStructureType === 'Lattice'. The + // substructure slot mirrors the SampleModel loaded/available shape, so + // the embedded list/popups follow the same recipe. + readonly property var latticeStructure: activeBackend.latticeStructure + readonly property var latticeSubstructureLoaded: activeBackend.latticeStructure.substructureLoaded + readonly property var latticeSubstructureAvailable: activeBackend.latticeStructure.availableSubstructures + readonly property var latticeSubstructureTypes: activeBackend.latticeStructure.substructureTypes + + function latticeSubstructureSetLoaded(item) { activeBackend.latticeStructure.setSubstructureLoaded(item) } + function latticeSubstructureClear() { activeBackend.latticeStructure.clearSubstructure() } + function latticeSubstructureSaveToCatalog() { activeBackend.latticeStructure.saveSubstructureToCatalog() } + function latticeSubstructureRemoveFromCatalog(index) { activeBackend.latticeStructure.removeSubstructureFromCatalog(index) } + //////////////// // Analysis page //////////////// diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml index 3bec9d2..f4924ab 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml @@ -8,7 +8,14 @@ import QtQuick.Controls import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Components as EaComponents +import Gui.Globals as Globals + + EaComponents.ListView { + // Override to bind a per-row Fractions backend (e.g. layer/lamella). + // Defaults to the global Fractions set on the wrapper. + property var fractionsModel: Globals.BackendWrapper.fractionsModel + defaultInfoText: qsTr("Missing components") selectionActive: false @@ -35,11 +42,7 @@ EaComponents.ListView { } } - model: ListModel { - ListElement { name: "POPC"; fracs: 100; present: true } - ListElement { name: "DOPC"; fracs: 100; present: true } - ListElement { name: "DPPC"; fracs: 100; present: true } - } + model: fractionsModel delegateModelAccess: DelegateModel.ReadWrite diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml new file mode 100644 index 0000000..829e7df --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + + Row { + property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.2 + spacing: EaStyle.Sizes.fontPixelSize * 2 + + Column { + width: parent.itemWidth + + EaElements.Label { + enabled: false + text: qsTr('Fill') + } + EaElements.ComboBox { + width: parent.width + model: ['FIBO', 'RINGS', 'RINGS0'] + Component.onCompleted: { + currentIndex = model.indexOf(Globals.BackendWrapper.ballStructure.fill) + } + onActivated: (i) => { + Globals.BackendWrapper.ballStructure.fill = model[i] + } + } + } + EaElements.CheckBox { + height: EaStyle.Sizes.fontPixelSize * 5 + text: qsTr('Fxz') + checked: Globals.BackendWrapper.ballStructure.fxz + onToggled: Globals.BackendWrapper.ballStructure.fxz = checked + } + EaElements.CheckBox { + height: EaStyle.Sizes.fontPixelSize * 5 + text: qsTr('Rev') + checked: Globals.BackendWrapper.ballStructure.rev + onToggled: Globals.BackendWrapper.ballStructure.rev = checked + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml new file mode 100644 index 0000000..fd53d7a --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + +import "../Components" as Local + + +EaElements.GroupColumn { + + Row { + property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.27 + spacing: (EaStyle.Sizes.sideBarContentWidth - (itemWidth * 3)) / 2 + + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Zsep') + units: 'nm' + validator: DoubleValidator { bottom: 0 } + text: Globals.BackendWrapper.bilayerStructure.zsep + onEditingFinished: Globals.BackendWrapper.bilayerStructure.zsep = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Nside') + text: Globals.BackendWrapper.bilayerStructure.nside + validator: IntValidator { bottom: 1 } + onEditingFinished: Globals.BackendWrapper.bilayerStructure.nside = parseInt(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Dmin') + units: 'nm' + validator: DoubleValidator { bottom: 0.5 } + text: Globals.BackendWrapper.bilayerStructure.dmin + onEditingFinished: Globals.BackendWrapper.bilayerStructure.dmin = parseFloat(text) + } + } + + Column { + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr('Bilayer Fractions') + } + + Local.Fractions { + id: bilayerFractions + } + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml index bc23d6f..9bd2a0e 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml @@ -63,9 +63,7 @@ EaElements.GroupColumn { } } - model: ListModel { - id: loadedComponentsModel - } + model: Globals.BackendWrapper.componentsLoaded delegateModelAccess: DelegateModel.ReadWrite @@ -113,7 +111,7 @@ EaElements.GroupColumn { EaComponents.TableViewButton { fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this component") - onClicked: loadedComponentsModel.remove(index) + onClicked: Globals.BackendWrapper.componentsRemove(index) } } } @@ -131,7 +129,7 @@ EaElements.GroupColumn { EaElements.SideBarButton { fontIcon: 'plus-circle' - text: qsTr('Create component') + text: qsTr('Import component') width: buttonWidth onClicked: createNewComponentLoader.item.open() } @@ -141,12 +139,14 @@ EaElements.GroupColumn { fontIcon: 'download' text: qsTr('Save component(s)') width: buttonWidth - enabled: loadedComponents.model ? loadedComponents.model.count > 0 : false + enabled: Globals.BackendWrapper.componentsLoaded + ? Globals.BackendWrapper.componentsLoaded.count > 0 + : false onClicked: { const indexes = loadedComponents.selectedIndexes for (let i = 0; i < indexes.length; ++i) loadExistingComponentLoader.item.availableComponentsModel.append( - loadedComponentsModel.get(indexes[i].row) + Globals.BackendWrapper.componentsLoaded.get(indexes[i].row) ) } } @@ -155,13 +155,11 @@ EaElements.GroupColumn { Loader { id: loadExistingComponentLoader source: '../Popups/LoadExistingComponent.qml' - onLoaded: item.targetModel = loadedComponentsModel } Loader { id: createNewComponentLoader source: '../Popups/CreateNewComponent.qml' - onLoaded: item.targetModel = loadedComponentsModel } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml index 966bdc8..97064e0 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml @@ -61,10 +61,7 @@ EaElements.GroupColumn { EaComponents.TableViewLabel {} } - model: ListModel { - id: lamellaeModel - ListElement { rmin: 0.5; innerDmin: 0.25; outerDmin: 0.3; shell: 0.5; symmetric: true } - } + model: Globals.BackendWrapper.lamellaeItems delegateModelAccess: DelegateModel.ReadWrite @@ -82,33 +79,34 @@ EaElements.GroupColumn { } EaComponents.ListViewTextInput { text: rmin - onEditingFinished: rmin = parseFloat(text) + onEditingFinished: Globals.BackendWrapper.lamellaeSetRmin(index, parseFloat(text)) validator: DoubleValidator { bottom: 0.25 } } EaComponents.ListViewTextInput { text: innerDmin - onEditingFinished: innerDmin = parseFloat(text) + onEditingFinished: Globals.BackendWrapper.lamellaeSetInnerDmin(index, parseFloat(text)) validator: DoubleValidator { bottom: 0.25 } } EaComponents.ListViewTextInput { - text: outerDmin - onEditingFinished: outerDmin = parseFloat(text) + text: symmetric ? innerDmin : outerDmin + enabled: !symmetric + onEditingFinished: Globals.BackendWrapper.lamellaeSetOuterDmin(index, parseFloat(text)) validator: DoubleValidator { bottom: 0.25 } } EaComponents.ListViewTextInput { text: shell - onEditingFinished: shell = parseFloat(text) + onEditingFinished: Globals.BackendWrapper.lamellaeSetShell(index, parseFloat(text)) validator: DoubleValidator { bottom: 0 } } EaComponents.TableViewCheckBox { checked: symmetric - onToggled: symmetric = checked + onToggled: Globals.BackendWrapper.lamellaeSetSymmetric(index, checked) } EaComponents.TableViewButton { id: deleteRowColumn fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this lamella") - onClicked: lamellaeModel.remove(index) + onClicked: Globals.BackendWrapper.lamellaeRemove(index) } } } @@ -124,7 +122,7 @@ EaElements.GroupColumn { fontIcon: "plus-circle" text: qsTr("Add lamella") width: buttonWidth - onClicked: lamellaeModel.append({ rmin: 0.5, innerDmin: 0.25, outerDmin: 0.3, shell: 0.5, symmetric: true }) + onClicked: Globals.BackendWrapper.lamellaeAppend({ rmin: 0.5, innerDmin: 0.25, outerDmin: 0.3, shell: 0.5, symmetric: true }) } } @@ -135,7 +133,24 @@ EaElements.GroupColumn { spacing: EaStyle.Sizes.groupBoxSpacing readonly property int selectedRow: lamellae.selectedIndexes.length > 0 ? lamellae.selectedIndexes[0].row : -1 - readonly property bool selectedSymmetric: selectedRow >= 0 && lamellaeModel.get(selectedRow).symmetric + readonly property bool selectedSymmetric: { + // Touch the revision token so this binding re-evaluates when + // the selected row's symmetric flag changes. + void Globals.BackendWrapper.lamellaeItemsRevision + return selectedRow >= 0 && selectedRow < Globals.BackendWrapper.lamellaeItems.count + ? Globals.BackendWrapper.lamellaeItems.get(selectedRow).symmetric + : true + } + readonly property var selectedInnerFractionsModel: { + // Touch the revision token so this binding re-evaluates when + // the per-lamella Fractions arrays are rebuilt. + void Globals.BackendWrapper.lamellaeFractionsRevision + return selectedRow >= 0 ? Globals.BackendWrapper.lamellaeInnerFractionsModelAt(selectedRow) : null + } + readonly property var selectedOuterFractionsModel: { + void Globals.BackendWrapper.lamellaeFractionsRevision + return selectedRow >= 0 ? Globals.BackendWrapper.lamellaeOuterFractionsModelAt(selectedRow) : null + } Column { width: parent.width @@ -148,6 +163,7 @@ EaElements.GroupColumn { } Local.Fractions { id: innerFractions + fractionsModel: fractionsSection.selectedInnerFractionsModel } } @@ -161,6 +177,7 @@ EaElements.GroupColumn { } Local.Fractions { id: outerFractions + fractionsModel: fractionsSection.selectedOuterFractionsModel } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml new file mode 100644 index 0000000..b617e62 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml @@ -0,0 +1,177 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + Row { + property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.3 + spacing: (EaStyle.Sizes.sideBarContentWidth - (itemWidth * 3)) / 2 + + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Alpha') + units: '⚬' + validator: DoubleValidator { bottom: 0; top: 360 } + text: Globals.BackendWrapper.latticeStructure.alpha + onEditingFinished: Globals.BackendWrapper.latticeStructure.alpha = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Theta') + units: '⚬' + validator: DoubleValidator { bottom: 0; top: 180 } + text: Globals.BackendWrapper.latticeStructure.theta + onEditingFinished: Globals.BackendWrapper.latticeStructure.theta = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Sbuff') + units: 'nm' + validator: DoubleValidator { bottom: 0 } + text: Globals.BackendWrapper.latticeStructure.sbuff + onEditingFinished: Globals.BackendWrapper.latticeStructure.sbuff = parseFloat(text) + } + } + + Row { + property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.2 + spacing: (EaStyle.Sizes.sideBarContentWidth - (itemWidth * 4)) / 3 + + + Column { + width: parent.itemWidth + + EaElements.Label { + enabled: false + text: qsTr('Type') + } + EaElements.ComboBox { + width: parent.width + model: Globals.BackendWrapper.latticeStructure.latticeTypes + Component.onCompleted: { + currentIndex = model.indexOf(Globals.BackendWrapper.latticeStructure.latticeType) + } + onActivated: (i) => { + Globals.BackendWrapper.latticeStructure.latticeType = model[i] + } + } + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Nlatx') + validator: IntValidator { bottom: 1 } + text: Globals.BackendWrapper.latticeStructure.nlatx + onEditingFinished: Globals.BackendWrapper.latticeStructure.nlatx = parseInt(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Nlaty') + validator: IntValidator { bottom: 1 } + text: Globals.BackendWrapper.latticeStructure.nlaty + onEditingFinished: Globals.BackendWrapper.latticeStructure.nlaty = parseInt(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Nlatz') + validator: IntValidator { bottom: 1 } + text: Globals.BackendWrapper.latticeStructure.nlatz + onEditingFinished: Globals.BackendWrapper.latticeStructure.nlatz = parseInt(text) + } + + } + + Column { + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr('Substructure') + } + + EaComponents.ListView { + id: loadedSubstructure + defaultInfoText: qsTr('Load or create a substructure') + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 12, + EaStyle.Sizes.fontPixelSize * 6, + -1 + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr('Name') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Type') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Description') + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.latticeSubstructureLoaded + + delegate: EaComponents.ListViewDelegate { + required property var modelData + required property int index + + EaComponents.TableViewLabel { + text: modelData ? modelData.name : '' + enabled: false + } + EaComponents.TableViewLabel { + text: modelData ? modelData.structure_type : '' + enabled: false + } + EaComponents.TableViewLabel { + text: modelData ? modelData.description : '' + enabled: false + } + } + } + } + + Row { + property double buttonWidth: (sideBarContentWidth - fontPixelSize) / 2 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + fontIcon: 'upload' + text: qsTr('Load structure') + width: buttonWidth + onClicked: loadExistingSubstructureLoader.item.open() + } + + EaElements.SideBarButton { + fontIcon: 'plus-circle' + text: qsTr('Import structure') + width: buttonWidth + onClicked: createNewSubstructureLoader.item.open() + } + } + + Loader { + id: loadExistingSubstructureLoader + source: '../Popups/LoadExistingSubstructure.qml' + } + + Loader { + id: createNewSubstructureLoader + source: '../Popups/CreateNewSubstructure.qml' + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml index 6b8fc1a..59e3643 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml @@ -17,6 +17,7 @@ import "../Components" as Local EaElements.GroupColumn { property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + EaComponents.ListView { id: layers defaultInfoText: qsTr("Add at least one layer") @@ -47,10 +48,7 @@ EaElements.GroupColumn { EaComponents.TableViewLabel {} } - model: ListModel { - id: layersModel - ListElement { dmin: 0.25; rmin: 0.5; } - } + model: Globals.BackendWrapper.layersItems delegateModelAccess: DelegateModel.ReadWrite @@ -66,19 +64,19 @@ EaElements.GroupColumn { EaComponents.TableViewLabel {} // filler EaComponents.ListViewTextInput { text: dmin - onEditingFinished: dmin = parseFloat(text) - validator: DoubleValidator { bottom: 0.25 } + onEditingFinished: Globals.BackendWrapper.layersSetDmin(index, parseFloat(text)) + validator: DoubleValidator { bottom: 0.25 } } EaComponents.ListViewTextInput { text: rmin - onEditingFinished: rmin = parseFloat(text) - validator: DoubleValidator { bottom: 0.25 } + onEditingFinished: Globals.BackendWrapper.layersSetRmin(index, parseFloat(text)) + validator: DoubleValidator { bottom: 0.25 } } EaComponents.TableViewButton { id: deleteRowColumn fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this layer") - onClicked: layersModel.remove(index) + onClicked: Globals.BackendWrapper.layersRemove(index) } } } @@ -93,20 +91,30 @@ EaElements.GroupColumn { fontIcon: "plus-circle" text: qsTr("Add layer") width: buttonWidth - onClicked: layersModel.append({ dmin: 0.25, rmin: 0.5 }) + onClicked: Globals.BackendWrapper.layersAppend({ dmin: 0.25, rmin: 0.5 }) } } Column { + id: fractionsSection visible: layers.selectedIndexes.length > 0 width: parent.width + readonly property int selectedRow: layers.selectedIndexes.length > 0 ? layers.selectedIndexes[0].row : -1 + readonly property var selectedFractionsModel: { + // Touch the revision token so this binding re-evaluates when + // the per-layer Fractions array is rebuilt. + void Globals.BackendWrapper.layersFractionsRevision + return selectedRow >= 0 ? Globals.BackendWrapper.layersFractionsModelAt(selectedRow) : null + } + EaElements.Label { id: layerFractionsLabel enabled: false - text: qsTr("Layer %1 Fractions").arg(layers.selectedIndexes.length > 0 ? layers.selectedIndexes[0].row + 1 : 1) + text: qsTr("Layer %1 Fractions").arg(fractionsSection.selectedRow >= 0 ? fractionsSection.selectedRow + 1 : 1) } Local.Fractions { id: fractions + fractionsModel: fractionsSection.selectedFractionsModel } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml new file mode 100644 index 0000000..45c5b40 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + +import "../Components" as Local + + +EaElements.GroupColumn { + + Row { + property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.27 + spacing: (EaStyle.Sizes.sideBarContentWidth - (itemWidth * 3)) / 2 + + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Zsep') + units: 'nm' + validator: DoubleValidator { bottom: 0 } + text: Globals.BackendWrapper.monolayerStructure.zsep + onEditingFinished: Globals.BackendWrapper.monolayerStructure.zsep = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Nside') + text: Globals.BackendWrapper.monolayerStructure.nside + validator: IntValidator { bottom: 1 } + onEditingFinished: Globals.BackendWrapper.monolayerStructure.nside = parseInt(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Dmin') + units: 'nm' + validator: DoubleValidator { bottom: 0.5 } + text: Globals.BackendWrapper.monolayerStructure.dmin + onEditingFinished: Globals.BackendWrapper.monolayerStructure.dmin = parseFloat(text) + } + } + + Column { + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr('Monolayer Fractions') + } + + Local.Fractions { + id: monolayerFractions + } + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml new file mode 100644 index 0000000..8c44e0a --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + +import "../Components" as Local + + +EaElements.GroupColumn { + + Row { + property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.2 + spacing: (EaStyle.Sizes.sideBarContentWidth - (itemWidth * 4)) / 3 + + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Dmin') + units: 'nm' + validator: DoubleValidator { bottom: 0.5 } + text: Globals.BackendWrapper.ringStructure.dmin + onEditingFinished: Globals.BackendWrapper.ringStructure.dmin = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Rmin') + units: 'nm' + validator: DoubleValidator { bottom: 0.25 } + text: Globals.BackendWrapper.ringStructure.rmin + onEditingFinished: Globals.BackendWrapper.ringStructure.rmin = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Alpha') + text: Globals.BackendWrapper.ringStructure.alpha + validator: DoubleValidator { bottom: 0; top: 360 } + units: '⚬' + onEditingFinished: Globals.BackendWrapper.ringStructure.alpha = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Theta') + text: Globals.BackendWrapper.ringStructure.theta + validator: DoubleValidator { bottom: 0; top: 180 } + units: '⚬' + onEditingFinished: Globals.BackendWrapper.ringStructure.theta = parseFloat(text) + } + } + Row { + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.CheckBox { + width: EaStyle.Sizes.sideBarContentWidth * 0.15 + height: EaStyle.Sizes.fontPixelSize * 3 + text: qsTr('Fxz') + checked: Globals.BackendWrapper.ringStructure.fxz + onToggled: Globals.BackendWrapper.ringStructure.fxz = checked + } + EaElements.CheckBox { + width: EaStyle.Sizes.sideBarContentWidth * 0.15 + height: EaStyle.Sizes.fontPixelSize * 3 + text: qsTr('Rev') + checked: Globals.BackendWrapper.ringStructure.rev + onToggled: Globals.BackendWrapper.ringStructure.rev = checked + } + } + + Column { + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr('Ring Fractions') + } + + Local.Fractions { + id: ringFractions + } + } + + + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml new file mode 100644 index 0000000..9974c71 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + +import "../Components" as Local + + +EaElements.GroupColumn { + + Row { + property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.27 + spacing: (EaStyle.Sizes.sideBarContentWidth - (itemWidth * 3)) / 2 + + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Dmin') + units: 'nm' + validator: DoubleValidator { bottom: 0.5 } + text: Globals.BackendWrapper.rodStructure.dmin + onEditingFinished: Globals.BackendWrapper.rodStructure.dmin = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Rmin') + units: 'nm' + validator: DoubleValidator { bottom: 0.25 } + text: Globals.BackendWrapper.rodStructure.rmin + onEditingFinished: Globals.BackendWrapper.rodStructure.rmin = parseFloat(text) + } + EaElements.Parameter { + width: parent.itemWidth + title: qsTr('Turns') + text: Globals.BackendWrapper.rodStructure.turns + validator: IntValidator { bottom: 1 } + onEditingFinished: Globals.BackendWrapper.rodStructure.turns = parseInt(text) + } + } + + Row { + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.CheckBox { + width: EaStyle.Sizes.sideBarContentWidth * 0.15 + height: EaStyle.Sizes.fontPixelSize * 3 + text: qsTr('Fxz') + checked: Globals.BackendWrapper.rodStructure.fxz + onToggled: Globals.BackendWrapper.rodStructure.fxz = checked + } + EaElements.CheckBox { + width: EaStyle.Sizes.sideBarContentWidth * 0.15 + height: EaStyle.Sizes.fontPixelSize * 3 + text: qsTr('Rev') + checked: Globals.BackendWrapper.rodStructure.rev + onToggled: Globals.BackendWrapper.rodStructure.rev = checked + } + } + + Column { + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr('Rod Fractions') + } + + Local.Fractions { + id: rodFractions + } + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml new file mode 100644 index 0000000..829e7df --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + + Row { + property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.2 + spacing: EaStyle.Sizes.fontPixelSize * 2 + + Column { + width: parent.itemWidth + + EaElements.Label { + enabled: false + text: qsTr('Fill') + } + EaElements.ComboBox { + width: parent.width + model: ['FIBO', 'RINGS', 'RINGS0'] + Component.onCompleted: { + currentIndex = model.indexOf(Globals.BackendWrapper.ballStructure.fill) + } + onActivated: (i) => { + Globals.BackendWrapper.ballStructure.fill = model[i] + } + } + } + EaElements.CheckBox { + height: EaStyle.Sizes.fontPixelSize * 5 + text: qsTr('Fxz') + checked: Globals.BackendWrapper.ballStructure.fxz + onToggled: Globals.BackendWrapper.ballStructure.fxz = checked + } + EaElements.CheckBox { + height: EaStyle.Sizes.fontPixelSize * 5 + text: qsTr('Rev') + checked: Globals.BackendWrapper.ballStructure.rev + onToggled: Globals.BackendWrapper.ballStructure.rev = checked + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index 3f61d10..d294390 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -28,6 +28,62 @@ EaComponents.SideBarColumn { Loader { source: 'Groups/Components.qml' } } + EaElements.GroupBox { + title: qsTr('Ring Structure Definition') + icon: 'vector-square' + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Ring' + + Loader { source: 'Groups/RingStructure.qml' } + } + + EaElements.GroupBox { + title: qsTr('Ball Structure Definition') + icon: 'vector-square' + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Ball' + + Loader { source: 'Groups/BallStructure.qml' } + } + + EaElements.GroupBox { + title: qsTr('Vesicle Structure Definition') + icon: 'vector-square' + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Vesicle' + + Loader { source: 'Groups/VesicleStructure.qml' } + } + + EaElements.GroupBox { + title: qsTr('Rod Structure Definition') + icon: 'vector-square' + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Rod' + + Loader { source: 'Groups/RodStructure.qml' } + } + + EaElements.GroupBox { + title: qsTr('Bilayer Structure Definition') + icon: 'vector-square' + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Bilayer' + + Loader { source: 'Groups/BilayerStructure.qml' } + } + + EaElements.GroupBox { + title: qsTr('Monolayer Structure Definition') + icon: 'vector-square' + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Monolayer' + + Loader { source: 'Groups/MonolayerStructure.qml' } + } + + EaElements.GroupBox { + title: qsTr('Lattice Structure Definition') + icon: 'vector-square' + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Lattice' + + Loader { source: 'Groups/LatticeStructure.qml' } + } + EaElements.GroupBox { title: qsTr('Layers') icon: 'layer-group' // 'grip-lines' diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml index 6c0ac06..cf82267 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml @@ -17,14 +17,13 @@ import Gui.Globals as Globals EaElements.Dialog{ id: componentCreationDialog - property var targetModel property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 title: qsTr("Create a new Component") standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { - targetModel.append({ + Globals.BackendWrapper.componentsAppend({ name: componentNameField.text, component_type: sampleModelTypeField.currentText, atoms: 42, @@ -99,9 +98,7 @@ EaElements.Dialog{ EaStyle.Sizes.tableRowHeight ] - model: ListModel { - id: selectedFilePathsModel - } + model: Globals.BackendWrapper.componentsPendingFilePaths delegate: EaComponents.ListViewDelegate { required property int index @@ -120,7 +117,7 @@ EaElements.Dialog{ id: deleteRowColumn fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this file") - onClicked: selectedFilePathsModel.remove(index) + onClicked: Globals.BackendWrapper.componentsRemovePendingFilePath(index) } } } @@ -138,9 +135,6 @@ EaElements.Dialog{ Loader { source: '../Popups/OpenAssetFile.qml' - onLoaded: { - item.targetModel = selectedFilePathsModel - } } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml new file mode 100644 index 0000000..1d7c7e4 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.Dialog{ + id: substructureCreationDialog + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + + title: qsTr("Create a new Substructure") + standardButtons: Dialog.Ok | Dialog.Cancel + + onAccepted: { + Globals.BackendWrapper.latticeSubstructureSetLoaded({ + name: substructureNameField.text, + structure_type: substructureTypeField.currentText, + description: substructureDescrField.text, + file_path: substructureFileField.text + }) + } + + Column { + spacing: EaStyle.Sizes.fontPixelSize + + Row { + id: nameTypeRow + spacing: EaStyle.Sizes.fontPixelSize + + Column { + EaElements.Label { + enabled: false + text: qsTr("Name") + } + EaElements.TextField { + id: substructureNameField + implicitWidth: (substructureCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 + horizontalAlignment: TextInput.AlignLeft + validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } + placeholderText: qsTr("(optional) Enter Substructure name here") + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Structure type") + } + EaElements.ComboBox { + id: substructureTypeField + implicitWidth: (substructureCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 + model: Globals.BackendWrapper.latticeSubstructureTypes + } + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Description") + } + EaElements.TextField { + id: substructureDescrField + implicitWidth: substructureCreationDialog.inputFieldWidth + horizontalAlignment: TextInput.AlignLeft + validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } + placeholderText: qsTr("(optional) Enter Substructure description here") + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("File") + } + + Row { + id: fileRow + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.TextField { + id: substructureFileField + implicitWidth: substructureCreationDialog.inputFieldWidth + - browseFileButton.width + - fileRow.spacing + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("(optional) Choose a single structure file") + readOnly: true + } + + EaElements.SideBarButton { + id: browseFileButton + fontIcon: 'upload' + text: qsTr('Browse…') + width: substructureCreationDialog.inputFieldWidth * 0.2 + onClicked: substructureFilePicker.open() + } + } + + FileDialog { + id: substructureFilePicker + fileMode: FileDialog.OpenFile + nameFilters: ['Any (*)', 'Structure files (*.gro *.pdb *.xyz)', 'Topology files (*.itp)'] + onAccepted: substructureFileField.text = selectedFile + } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml index a666182..0209f82 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml @@ -17,7 +17,6 @@ import Gui.Globals as Globals EaElements.Dialog{ id: sampleModelLoadDialog - property var targetModel property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 property alias availableComponentsModel: availableComponentsModel @@ -30,7 +29,7 @@ EaElements.Dialog{ var row = selected[i].row var item = availableComponentsModel.get(row) - targetModel.append({ + Globals.BackendWrapper.componentsAppend({ name: item.name, component_type: item.component_type, mint: item.mint, diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml new file mode 100644 index 0000000..61b896d --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Style as EaStyle + +import Gui.Globals as Globals + + +EaElements.Dialog{ + id: substructureLoadDialog + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + + title: qsTr("Load a Substructure from the Asset Library") + standardButtons: Dialog.Ok | Dialog.Cancel + + onAccepted: { + var indexes = loadSubstructureListView.selectedIndexes + + if (indexes.length > 0) { + var row = indexes[0].row + var item = Globals.BackendWrapper.latticeSubstructureAvailable.get(row) + Globals.BackendWrapper.latticeSubstructureSetLoaded(item) + loadSubstructureListView.clearSelection() + } + } + onRejected: { + loadSubstructureListView.clearSelection() + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Available in the Asset Library") + } + EaComponents.ListView { + id: loadSubstructureListView + defaultInfoText: qsTr("No substructures found") + multiSelection: false + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + EaStyle.Sizes.fontPixelSize * 10, + EaStyle.Sizes.fontPixelSize * 6, + -1, + EaStyle.Sizes.tableRowHeight, + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Type") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Description") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel {} // filler + } + + model: Globals.BackendWrapper.latticeSubstructureAvailable + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string name + required property string structure_type + required property string description + + EaComponents.TableViewLabel { + text: index + 1 + horizontalAlignment: Text.AlignHCenter + enabled: false + } + EaComponents.TableViewLabel { + text: name + } + EaComponents.TableViewLabel { + text: structure_type + } + EaComponents.TableViewLabel { + text: description + } + EaComponents.TableViewButton { + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this substructure") + onClicked: Globals.BackendWrapper.latticeSubstructureRemoveFromCatalog(index) + } + } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml index 4664933..d22023d 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml @@ -16,11 +16,9 @@ FileDialog{ fileMode: FileDialog.OpenFiles nameFilters: ['Any (*)', 'Structure files (*.gro *.pdb .*xyz)', 'Topology files (*.itp)', 'Smiles files (*.sml)'] - property var targetModel - onAccepted: { for (let i = 0; i < selectedFiles.length; ++i) - targetModel.append({ path: selectedFiles[i] }) + Globals.BackendWrapper.componentsAppendPendingFilePath(selectedFiles[i]) } Component.onCompleted: { diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index 8f55d2d..60ceb50 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -17,7 +17,7 @@ import Gui.Globals as Globals ApplicationWindow{ width: 650 - height: 580 + height: 680 color: EaStyle.Colors.contentBackground visible: true @@ -55,87 +55,15 @@ ApplicationWindow{ icon: 'wrench' collapsed: false - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml'} + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml'} } - EaComponents.ListView { - id: whatverTable - defaultInfoText: qsTr("No models found") - enabled: true - - columnWidths: [ - whatverTable.width * 0.25, - whatverTable.width * 0.15, - -1, - ] - - scrollBarPolicy: ScrollBar.AlwaysOn // ScrollBar.AsNeeded // ScrollBar.AlwaysOn - scrollBarInteractive: true - - header: EaComponents.ListViewHeader { - - EaComponents.TableViewLabel { - id: modelNameColumnName - text: qsTr("Name") - color: EaStyle.Colors.themeForegroundMinor - leftPadding: EaStyle.Sizes.fontPixelSize * 0.7 - } - - EaComponents.TableViewLabel { - id: modelTypeColumnName - text: qsTr("Type") - color: EaStyle.Colors.themeForegroundMinor - } - - EaComponents.TableViewLabel { - id: modelDescrColumnName - text: qsTr("Description") - color: EaStyle.Colors.themeForegroundMinor - } - } - - model: ListModel { - id: availableSambleModelsModel - ListElement { name: "Samle1_aluv"; structure_type: "Vesicle"; description: "In order to avoid a prolonged pro-inflammatory neutrophil response, signaling downstream of an agonist-activated G protein-coupled receptor (GPCR) has to be rapidly terminated. Among the family of GPCR kinases (GRKs) that regulate receptor phosphorylation and signaling termination, GRK2, which is highly expressed by immune cells, plays an important role." } - ListElement { name: "Sample2_nanodisc"; structure_type: "Ring"; description: "The medium chain fatty acid receptor GPR84 as well as formyl peptide receptor 2 (FPR2)" } - ListElement { name: "Sample3_cubosome"; structure_type: "Lattice"; description: "receptors expressed in neutrophils, play a key role in regulating inflammation. In this study, we investigated the effects of GRK2 inhibitors on neutrophil functions induced by GPR84 and FPR2 agonists." } - ListElement { name: "Sample4"; structure_type: "Ring"; description: "GRK2 was shown to be expressed in human neutrophils and analysis of subcellular fractions" } - ListElement { name: "Sample5"; structure_type: "Ball"; description: "revealed a cytosolic localization. The GRK2 inhibitors enhanced and prolonged neutrophil production " } - ListElement { name: "Sample6"; structure_type: "Vesicle"; description: "production of reactive oxygen species (ROS) induced by GPR84- but not FPR2-agonists" } - ListElement { name: "Sample7"; structure_type: "Rod"; description: "suggesting a receptor selective function of GRK2. This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84" } - ListElement { name: "Sample8"; structure_type: "Bilayer"; description: "This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84 agonist was not affected by the GRK2 inhibitor." } - ListElement { name: "Sample9"; structure_type: "Monolayer"; description: "Termination of this β-arrestin independent response relied, similar to the response induced by FPR2 agonists, primarily on the actin cytoskeleton." } - ListElement { name: "Samplewithareallylongname"; structure_type: "Lattice"; description: "In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils." } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "1"; structure_type: "2"; description: "3" } - ListElement { name: "end1"; structure_type: "end2"; description: "end3" } - } - - delegate: EaComponents.ListViewDelegate { - required property int index - required property string name - required property string structure_type - required property string description - - EaComponents.ListViewTextInput { - id: modelNameColumn - text: name - } - - EaComponents.TableViewLabel { - id: typeColumn - text: structure_type - color: "#666" // new disabled - } - - EaComponents.TableViewLabel { - id: descrColumn - text: description - color: "#555" // old disabled - } - } + EaElements.GroupBox { + title: qsTr('Test a group widget') + icon: 'wrench' + collapsed: false + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml'} } } } From a000a7be0d7c8ef6f1db01d5ddc3b8cfacf07c76 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 6 May 2026 16:33:23 +0200 Subject: [PATCH 19/32] fix button widths in lattice structure definition --- .../SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml index b617e62..7fe2454 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml @@ -13,6 +13,7 @@ import Gui.Globals as Globals EaElements.GroupColumn { + Row { property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.3 spacing: (EaStyle.Sizes.sideBarContentWidth - (itemWidth * 3)) / 2 @@ -147,20 +148,21 @@ EaElements.GroupColumn { } Row { - property double buttonWidth: (sideBarContentWidth - fontPixelSize) / 2 + id: buttonRow + property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 2 spacing: EaStyle.Sizes.fontPixelSize EaElements.SideBarButton { fontIcon: 'upload' text: qsTr('Load structure') - width: buttonWidth + width: buttonRow.buttonWidth onClicked: loadExistingSubstructureLoader.item.open() } EaElements.SideBarButton { fontIcon: 'plus-circle' text: qsTr('Import structure') - width: buttonWidth + width: buttonRow.buttonWidth onClicked: createNewSubstructureLoader.item.open() } } From 7613f438da171ad6f9104e31b8bef54c60c33795 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Fri, 8 May 2026 15:38:28 +0200 Subject: [PATCH 20/32] solution mock --- src/easyshapes_app.qrc | 7 +- src/easyshapes_app/Backends/MockBackend.qml | 3 + .../Backends/MockQml/Fractions.qml | 4 +- src/easyshapes_app/Backends/MockQml/Ions.qml | 57 +++++ .../Backends/MockQml/Solution.qml | 116 +++++++++ src/easyshapes_app/Backends/MockQml/qmldir | 2 + .../Gui/Globals/BackendWrapper.qml | 34 +++ .../Sidebar/Basic/Groups/Group3.qml | 18 -- .../Sidebar/Basic/Groups/Lamellae.qml | 3 +- .../Sidebar/Basic/Groups/LatticeStructure.qml | 2 +- .../Sidebar/Basic/Groups/SampleModel.qml | 2 +- .../Sidebar/Basic/Groups/Solution.qml | 241 ++++++++++++++++++ .../SampleModel/Sidebar/Basic/Layout.qml | 6 +- .../Sidebar/Basic/Popups/CreateNewSolvent.qml | 118 +++++++++ .../Sidebar/Basic/Popups/LoadExistingIon.qml | 94 +++++++ .../Basic/Popups/LoadExistingSolvent.qml | 101 ++++++++ src/easyshapes_app/test.qml | 10 +- 17 files changed, 781 insertions(+), 37 deletions(-) create mode 100644 src/easyshapes_app/Backends/MockQml/Ions.qml create mode 100644 src/easyshapes_app/Backends/MockQml/Solution.qml delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingIon.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index 41fbf8a..5a4d8c3 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -7,6 +7,7 @@ easyshapes_app/Backends/MockQml/BilayerStructure.qml easyshapes_app/Backends/MockQml/Components.qml easyshapes_app/Backends/MockQml/Fractions.qml + easyshapes_app/Backends/MockQml/Ions.qml easyshapes_app/Backends/MockQml/Lamellae.qml easyshapes_app/Backends/MockQml/LatticeStructure.qml easyshapes_app/Backends/MockQml/Layers.qml @@ -17,6 +18,7 @@ easyshapes_app/Backends/MockQml/RingStructure.qml easyshapes_app/Backends/MockQml/RodStructure.qml easyshapes_app/Backends/MockQml/SampleModel.qml + easyshapes_app/Backends/MockQml/Solution.qml easyshapes_app/Backends/MockQml/Status.qml easyshapes_app/Backends/MockQml/VesicleStructure.qml easyshapes_app/Backends/qmldir @@ -55,7 +57,6 @@ easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml @@ -63,13 +64,17 @@ easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingIon.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml easyshapes_app/Gui/Pages/Toolbox/Layout.qml diff --git a/src/easyshapes_app/Backends/MockBackend.qml b/src/easyshapes_app/Backends/MockBackend.qml index c994427..a250da0 100644 --- a/src/easyshapes_app/Backends/MockBackend.qml +++ b/src/easyshapes_app/Backends/MockBackend.qml @@ -39,6 +39,9 @@ QtObject { // Lattice structure parameters (single record, edited inline) plus a // single-record substructure with its own asset library. property var latticeStructure: MockLogic.LatticeStructure + // Solution group: solution (single-record, TIP3 default) + ions (max 2 rows). + property var solution: MockLogic.Solution + property var ions: MockLogic.Ions property var analysis: MockLogic.Analysis property var status: MockLogic.Status property var report: MockLogic.Report diff --git a/src/easyshapes_app/Backends/MockQml/Fractions.qml b/src/easyshapes_app/Backends/MockQml/Fractions.qml index 2daaef2..92599eb 100644 --- a/src/easyshapes_app/Backends/MockQml/Fractions.qml +++ b/src/easyshapes_app/Backends/MockQml/Fractions.qml @@ -34,7 +34,7 @@ QtObject { function onRowsInserted(parent, first, last) { for (let i = first; i <= last; ++i) { const c = root.source.get(i) - backing.insert(i, { name: c.name, fracs: 100, present: true }) + backing.insert(i, { name: c.name, fracs: 1, present: true }) } } function onRowsRemoved(parent, first, last) { @@ -53,7 +53,7 @@ QtObject { if (!source) return for (let i = 0; i < source.count; ++i) { const c = source.get(i) - backing.append({ name: c.name, fracs: 100, present: true }) + backing.append({ name: c.name, fracs: 1, present: true }) } } diff --git a/src/easyshapes_app/Backends/MockQml/Ions.qml b/src/easyshapes_app/Backends/MockQml/Ions.qml new file mode 100644 index 0000000..73ed1c3 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/Ions.qml @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + + // Loaded ions — at most 2 rows. ListModel so the chip Repeater rebinds + // on append/remove without manual reassignment. Roles: name. + readonly property var loaded: ListModel { + id: loadedIonsModel + } + + readonly property var available: ListModel { + ListElement { name: 'NA+' } + ListElement { name: 'CL-' } + ListElement { name: 'K+' } + ListElement { name: 'CA2+' } + ListElement { name: 'MG2+' } + ListElement { name: 'ZN2+' } + } + + // Hard cap at the backend layer. The button row also disables itself, + // but mocks must not be the only place enforcing the rule. + readonly property int maxIons: 2 + + function appendItem(item) { + if (loadedIonsModel.count >= maxIons) return + loadedIonsModel.append({ name: item.name }) + } + + function removeItem(index) { + if (index < 0 || index >= loadedIonsModel.count) return + loadedIonsModel.remove(index) + } + + function clear() { + loadedIonsModel.clear() + } + + function saveToCatalog() { + for (let i = 0; i < loadedIonsModel.count; ++i) { + const row = loadedIonsModel.get(i) + if (!row.name) continue + available.append({ name: row.name }) + } + } + + function removeFromCatalog(index) { + if (index < 0 || index >= available.count) return + available.remove(index) + } +} diff --git a/src/easyshapes_app/Backends/MockQml/Solution.qml b/src/easyshapes_app/Backends/MockQml/Solution.qml new file mode 100644 index 0000000..940f661 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/Solution.qml @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + + readonly property var solutionTypes: ['other', 'water_3site', 'water_4site', 'water_5site', 'protic_polar', 'halogenated', 'denaturant'] + + property var loaded: [{ + name: 'TIP3P', + solvent_type: 'water_3site', + description: '3-site model standard with AMBER and CHARMM. Uses spc216.gro coordinates.', + file_path: '' + }] + + readonly property var available: ListModel { + id: solventModel + + ListElement { + name: "SPC" + solvent_type: "water_3site" + description: "Simple Point Charge water. Use spc216.gro with -ci. Standard for GROMOS force fields." + } + ListElement { + name: "SPC/E" + solvent_type: "water_3site" + description: "Extended SPC with self-polarization correction. Uses spc216.gro coordinates." + } + ListElement { + name: "TIP3P" + solvent_type: "water_3site" + description: "3-site model standard with AMBER and CHARMM. Uses spc216.gro coordinates." + } + ListElement { + name: "TIP4P" + solvent_type: "water_4site" + description: "4-site water with virtual site. Use tip4p.gro." + } + ListElement { + name: "TIP4P-Ew" + solvent_type: "water_4site" + description: "TIP4P optimized for Ewald summation. Available in AMBER ports." + } + ListElement { + name: "TIP4P/2005" + solvent_type: "water_4site" + description: "Reparameterized TIP4P with excellent bulk properties. Shipped with recent GROMACS." + } + ListElement { + name: "TIP5P" + solvent_type: "water_5site" + description: "5-site model with lone-pair virtual sites. Use tip5p.gro." + } + ListElement { + name: "TIP5PE" + solvent_type: "water_5site" + description: "TIP5P variant for Ewald electrostatics." + } + + ListElement { + name: "Methanol" + solvent_type: "protic_polar" + description: "CH3OH. Equilibrated box methanol.gro shipped with GROMACS (OPLS-AA)." + } + ListElement { + name: "1,2-Dichloroethane" + solvent_type: "halogenated" + description: "ClCH2CH2Ch. Equilibrated box ClCH2CH2Ch.gro included for OPLS-AA." + } + + ListElement { + name: "Urea" + solvent_type: "denaturant" + description: "Equilibrated urea box urea.gro shipped with GROMACS for cosolvent simulations." + } + } + + function setLoaded(item) { + loaded = [{ + name: item.name, + solvent_type: item.solvent_type, + description: item.description, + file_path: item.file_path !== undefined ? item.file_path : '' + }] + } + + // Mutate loaded[0] in place — same trick as SampleModel.updateField — so + // the ListView delegate and any focused TextInput survive the edit. + function updateField(field, value) { + if (loaded.length === 0) return + loaded[0][field] = value + } + + function clear() { + loaded = [] + } + + function saveToCatalog() { + if (loaded.length === 0 || !loaded[0].name) return + available.append({ + name: loaded[0].name, + solvent_type: loaded[0].solvent_type, + description: loaded[0].description + }) + } + + function removeFromCatalog(index) { + if (index < 0 || index >= available.count) return + available.remove(index) + } +} diff --git a/src/easyshapes_app/Backends/MockQml/qmldir b/src/easyshapes_app/Backends/MockQml/qmldir index 04d6591..3894fee 100644 --- a/src/easyshapes_app/Backends/MockQml/qmldir +++ b/src/easyshapes_app/Backends/MockQml/qmldir @@ -13,6 +13,8 @@ singleton RodStructure RodStructure.qml singleton BilayerStructure BilayerStructure.qml singleton MonolayerStructure MonolayerStructure.qml singleton LatticeStructure LatticeStructure.qml +singleton Solution Solution.qml +singleton Ions Ions.qml singleton Analysis Analysis.qml singleton Report Report.qml singleton Status Status.qml diff --git a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml index d83d2e4..80c5f73 100644 --- a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml +++ b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml @@ -172,6 +172,40 @@ QtObject { function latticeSubstructureSaveToCatalog() { activeBackend.latticeStructure.saveSubstructureToCatalog() } function latticeSubstructureRemoveFromCatalog(index) { activeBackend.latticeStructure.removeSubstructureFromCatalog(index) } + // Solution group (Sample Model sidebar). Two backend-owned slots: + // * solution — single-record JS array (capacity 0 or 1), TIP3 by default. + // * ions — ListModel with at most 2 rows, name only. + + readonly property var solutionLoaded: activeBackend.solution.loaded + readonly property var solutionAvailable: activeBackend.solution.available + readonly property var solutionTypes: activeBackend.solution.solutionTypes + + function solutionSetLoaded(item) { activeBackend.solution.setLoaded(item) } + function solutionUpdateField(field, value) { activeBackend.solution.updateField(field, value) } + function solutionClear() { activeBackend.solution.clear() } + function solutionSaveToCatalog() { activeBackend.solution.saveToCatalog() } + function solutionRemoveFromCatalog(index) { activeBackend.solution.removeFromCatalog(index) } + + // Solvent aliases — same backend object, used by the solvent table popup files. + readonly property var solventLoaded: activeBackend.solution.loaded + readonly property var solventAvailable: activeBackend.solution.available + readonly property var solventTypes: activeBackend.solution.solutionTypes + + function solventSetLoaded(item) { activeBackend.solution.setLoaded(item) } + function solventUpdateField(field, value) { activeBackend.solution.updateField(field, value) } + function solventClear() { activeBackend.solution.clear() } + function solventSaveToCatalog() { activeBackend.solution.saveToCatalog() } + function solventRemoveFromCatalog(index) { activeBackend.solution.removeFromCatalog(index) } + + readonly property var ionsLoaded: activeBackend.ions.loaded + readonly property var ionsAvailable: activeBackend.ions.available + + function ionsAppend(item) { activeBackend.ions.appendItem(item) } + function ionsRemove(index) { activeBackend.ions.removeItem(index) } + function ionsClear() { activeBackend.ions.clear() } + function ionsSaveToCatalog() { activeBackend.ions.saveToCatalog() } + function ionsRemoveFromCatalog(index) { activeBackend.ions.removeFromCatalog(index) } + //////////////// // Analysis page //////////////// diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml deleted file mode 100644 index af615cc..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group3.qml +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn { - -} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml index 97064e0..4f4432c 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml @@ -88,8 +88,7 @@ EaElements.GroupColumn { validator: DoubleValidator { bottom: 0.25 } } EaComponents.ListViewTextInput { - text: symmetric ? innerDmin : outerDmin - enabled: !symmetric + text: outerDmin onEditingFinished: Globals.BackendWrapper.lamellaeSetOuterDmin(index, parseFloat(text)) validator: DoubleValidator { bottom: 0.25 } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml index 7fe2454..a546a26 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml @@ -106,7 +106,7 @@ EaElements.GroupColumn { columnWidths: [ EaStyle.Sizes.fontPixelSize * 12, - EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.fontPixelSize * 7, -1 ] diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index 3aa2999..f8cf33e 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -26,7 +26,7 @@ EaElements.GroupColumn { columnWidths: [ EaStyle.Sizes.fontPixelSize * 12, - EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.fontPixelSize * 7, -1 ] diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml new file mode 100644 index 0000000..0e68005 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml @@ -0,0 +1,241 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + id: root + property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - (EaStyle.Sizes.fontPixelSize * 2) ) / 3 + + Column { + width: EaStyle.Sizes.sideBarContentWidth * 0.5 + + EaElements.Label { + enabled: false + text: qsTr('Solvent') + } + + EaComponents.ListView { + id: loadedSolvent + defaultInfoText: qsTr('Load or import a solvent') + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 7, + EaStyle.Sizes.fontPixelSize * 8, + -1 + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr('Name') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Type') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Description') + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.solutionLoaded + + delegate: EaComponents.ListViewDelegate { + required property var modelData + required property int index + + EaComponents.ListViewTextInput { + text: modelData ? modelData.name : '' + onEditingFinished: Globals.BackendWrapper.solventUpdateField('name', text) + } + EaComponents.TableViewComboBox { + model: Globals.BackendWrapper.solventTypes + Component.onCompleted: { + currentIndex = model.indexOf(modelData.solvent_type) + } + onActivated: (i) => { + Globals.BackendWrapper.solventUpdateField('solvent_type', model[i]) + } + } + EaComponents.ListViewTextInput { + text: modelData ? modelData.description : '' + onEditingFinished: Globals.BackendWrapper.solventUpdateField('description', text) + } + } + } + } + + Grid { + columns: 3 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + fontIcon: 'upload' + text: qsTr('Load solvent') + width: buttonWidth + onClicked: loadExistingSolvent.item.open() + } + + EaElements.SideBarButton { + fontIcon: 'plus-circle' + text: qsTr('Import solvent') + width: buttonWidth + onClicked: createNewSolvent.item.open() + } + + EaElements.SideBarButton { + fontIcon: 'download' + text: qsTr('Save solvent') + width: buttonWidth + enabled: Globals.BackendWrapper.solventLoaded.length > 0 + && Globals.BackendWrapper.solventLoaded[0].name !== "" + onClicked: Globals.BackendWrapper.solventSaveToCatalog() + } + } + + Row { + spacing: EaStyle.Sizes.fontPixelSize + + Column { + width: EaStyle.Sizes.sideBarContentWidth / 3 + spacing: EaStyle.Sizes.fontPixelSize * 0.5 + + EaElements.Label { + enabled: false + text: qsTr('Ions') + } + + Row { + id: ionChipRow + spacing: EaStyle.Sizes.fontPixelSize + height: EaStyle.Sizes.fontPixelSize * 2 + width: buttonWidth + + EaElements.Label { + visible: ionChipRow.ionCount === 0 + enabled: false + text: qsTr('No ions loaded') + anchors.verticalCenter: parent.verticalCenter + } + + readonly property int ionCount: + Globals.BackendWrapper.ionsLoaded + ? Globals.BackendWrapper.ionsLoaded.count + : 0 + + Repeater { + model: Globals.BackendWrapper.ionsLoaded + + delegate: Rectangle { + required property int index + required property string name + + height: ionChipRow.height + width: (ionChipRow.width - ionChipRow.spacing) / 2 + radius: height / 2 + color: EaStyle.Colors.appBarBackground + border.color: EaStyle.Colors.appBarComboBoxBorder + border.width: 1 + + Row { + anchors.fill: parent + anchors.leftMargin: EaStyle.Sizes.fontPixelSize + anchors.rightMargin: chipRemove.width + EaStyle.Sizes.fontPixelSize * 0.25 + spacing: EaStyle.Sizes.fontPixelSize * 0.25 + + EaElements.Label { + id: chipLabel + text: name + anchors.verticalCenter: parent.verticalCenter + } + } + + EaElements.ToolButton { + id: chipRemove + width: EaStyle.Sizes.toolButtonHeight * 0.4 + height: EaStyle.Sizes.toolButtonHeight * 0.4 + anchors.right: parent.right + anchors.rightMargin: EaStyle.Sizes.fontPixelSize * 0.6 + anchors.verticalCenter: parent.verticalCenter + ToolTip.text: qsTr('Remove this ion') + onClicked: Globals.BackendWrapper.ionsRemove(index) + + contentItem: Text { + text: 'X' + font.family: EaStyle.Fonts.fontFamily + font.pixelSize: EaStyle.Sizes.fontPixelSize + color: chipRemove.hovered + ? EaStyle.Colors.themeForegroundHovered + : EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + } + } + } + + EaElements.SideBarButton { + fontIcon: 'upload' + text: qsTr('Load ion') + width: buttonWidth + enabled: ionChipRow.ionCount < 2 + onClicked: loadExistingIonLoader.item.open() + } + } + + Column { + width: EaStyle.Sizes.sideBarContentWidth / 3 + spacing: EaStyle.Sizes.fontPixelSize * 0.5 + + EaElements.Label { + enabled: false + text: qsTr('Amount') + } + EaElements.TextField { + id: ionAmountField + width: parent.width + enabled: !matchStructureCheck.checked + placeholderText: qsTr('Set the amount of selected ions') + validator: IntValidator { bottom: 0 } + } + EaElements.CheckBox { + id: matchStructureCheck + text: qsTr('Match components') + checked: false + onCheckedChanged: { + if (checked) { + ionAmountField.text = '' + ionAmountField.placeholderText = 'Will match the structure components' + } + else ionAmountField.placeholderText = 'Set the amount of ions' + } + } + } + } + + Loader { + id: loadExistingSolvent + source: '../Popups/LoadExistingSolvent.qml' + } + Loader { + id: createNewSolvent + source: '../Popups/CreateNewSolvent.qml' + } + Loader { + id: loadExistingIonLoader + source: '../Popups/LoadExistingIon.qml' + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index d294390..2c4ddce 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -101,10 +101,10 @@ EaComponents.SideBarColumn { } EaElements.GroupBox { - title: qsTr('Solvent') - icon: 'grip-horizontal' + title: qsTr('Solution') + icon: 'flask' - Loader { source: 'Groups/Group3.qml' } + Loader { source: 'Groups/Solution.qml' } } EaElements.GroupBox { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml new file mode 100644 index 0000000..0855279 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.Dialog { + id: solventCreationDialog + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + + title: qsTr("Import a new Solvent") + standardButtons: Dialog.Ok | Dialog.Cancel + + onAccepted: { + Globals.BackendWrapper.solventSetLoaded({ + name: solventNameField.text, + solvent_type: solventTypeField.currentText, + description: solventDescrField.text, + file_path: solventFileField.text + }) + } + + Column { + spacing: EaStyle.Sizes.fontPixelSize + + Row { + id: nameTypeRow + spacing: EaStyle.Sizes.fontPixelSize + + Column { + EaElements.Label { + enabled: false + text: qsTr("Name") + } + EaElements.TextField { + id: solventNameField + implicitWidth: (solventCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 + horizontalAlignment: TextInput.AlignLeft + validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.\/]{1,30}$/ } + placeholderText: qsTr("(optional) Enter Solvent name here") + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Solvent type") + } + EaElements.ComboBox { + id: solventTypeField + implicitWidth: (solventCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 + model: Globals.BackendWrapper.solventTypes + } + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Description") + } + EaElements.TextField { + id: solventDescrField + implicitWidth: solventCreationDialog.inputFieldWidth + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("(optional) Enter Solvent description here") + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("File") + } + + Row { + id: fileRow + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.TextField { + id: solventFileField + implicitWidth: solventCreationDialog.inputFieldWidth + - browseFileButton.width + - fileRow.spacing + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("(optional) Choose a single solvent file") + readOnly: true + } + + EaElements.SideBarButton { + id: browseFileButton + fontIcon: 'upload' + text: qsTr('Browse…') + width: solventCreationDialog.inputFieldWidth * 0.2 + onClicked: solventFilePicker.open() + } + } + + FileDialog { + id: solventFilePicker + fileMode: FileDialog.OpenFile + nameFilters: ['Any (*)', 'Structure files (*.gro *.pdb *.xyz)', 'Topology files (*.itp)'] + onAccepted: solventFileField.text = selectedFile + } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingIon.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingIon.qml new file mode 100644 index 0000000..de267fb --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingIon.qml @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.Dialog { + id: ionLoadDialog + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 25 + + title: qsTr("Load an Ion from the Asset Library") + standardButtons: Dialog.Ok | Dialog.Cancel + + // Block confirmation when the slot is full or no row is selected. + Component.onCompleted: { + var ok = standardButton(Dialog.Ok) + if (ok) { + ok.enabled = Qt.binding(function () { + if (!Globals.BackendWrapper.ionsLoaded + || Globals.BackendWrapper.ionsLoaded.count >= 2) return false + return loadIonListView.selectedIndexes.length > 0 + }) + } + } + + onAccepted: { + var indexes = loadIonListView.selectedIndexes + + if (indexes.length > 0) { + var row = indexes[0].row + var item = Globals.BackendWrapper.ionsAvailable.get(row) + Globals.BackendWrapper.ionsAppend({ name: item.name }) + loadIonListView.clearSelection() + } + } + onRejected: { + loadIonListView.clearSelection() + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Available in the Asset Library") + } + EaComponents.ListView { + id: loadIonListView + defaultInfoText: qsTr("No ions found") + multiSelection: false + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.ionsAvailable + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string name + + EaComponents.TableViewLabel { + text: index + 1 + horizontalAlignment: Text.AlignHCenter + enabled: false + } + EaComponents.TableViewLabel { + text: name + } + } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml new file mode 100644 index 0000000..0e0c3ff --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApplication.Gui.Globals as EaGlobals +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Components as EaComponents +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.Dialog { + id: solventLoadDialog + + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + + title: qsTr("Load a Solvent from the Asset Library") + standardButtons: Dialog.Ok | Dialog.Cancel + + onAccepted: { + var indexes = loadSolventListView.selectedIndexes + + if (indexes.length > 0) { + var row = indexes[0].row + var item = Globals.BackendWrapper.solventAvailable.get(row) + Globals.BackendWrapper.solventSetLoaded(item) + loadSolventListView.clearSelection() + } + } + onRejected: { + loadSolventListView.clearSelection() + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Available in the Asset Library") + } + EaComponents.ListView { + id: loadSolventListView + defaultInfoText: qsTr("No solvents found") + multiSelection: false + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + EaStyle.Sizes.fontPixelSize * 10, + EaStyle.Sizes.fontPixelSize * 6, + -1, + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Type") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Description") + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.solventAvailable + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string name + required property string solvent_type + required property string description + + EaComponents.TableViewLabel { + text: index + 1 + horizontalAlignment: Text.AlignHCenter + enabled: false + } + EaComponents.TableViewLabel { + text: name + } + EaComponents.TableViewLabel { + text: solvent_type + } + EaComponents.TableViewLabel { + text: description + ToolTip.text: description + } + } + } + } +} diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index 60ceb50..09cdc99 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -55,15 +55,7 @@ ApplicationWindow{ icon: 'wrench' collapsed: false - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml'} - } - - EaElements.GroupBox { - title: qsTr('Test a group widget') - icon: 'wrench' - collapsed: false - - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml'} + Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml'} } } } From 4727be32f89532bbc113fcf73c55d02263cd0487 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 20 May 2026 15:59:38 +0200 Subject: [PATCH 21/32] adjusted solvent ions columns --- src/easyshapes_app.qmlproject | 8 +- src/easyshapes_app.qrc | 109 ++++++++++++++++++ .../Sidebar/Basic/Groups/Solution.qml | 72 +++++++----- src/easyshapes_app/test.qml | 23 +++- 4 files changed, 174 insertions(+), 38 deletions(-) diff --git a/src/easyshapes_app.qmlproject b/src/easyshapes_app.qmlproject index a57d5e8..1c9f27c 100644 --- a/src/easyshapes_app.qmlproject +++ b/src/easyshapes_app.qmlproject @@ -7,7 +7,7 @@ Project { // List of module and plugin directories passed to QML runtime importPaths: [ "easyshapes_app", - "../../EasyApp/src", // EasyApp + "../../EasyApp/src", // EasyApplication ] // Include .qml files from specified directory and its subdirectories @@ -15,7 +15,7 @@ Project { directory: "easyshapes_app" } QmlFiles { - directory: "../../EasyApp/src/EasyApp" + directory: "../../EasyApp/src/EasyApplication" } // Include .js files from specified directory and its subdirectories @@ -23,7 +23,7 @@ Project { directory: "easyshapes_app" } JavaScriptFiles { - directory: "../../EasyApp/src/EasyApp" + directory: "../../EasyApp/src/EasyApplication" } // Include Module Definition Files (qmldir), as well as .ts and .qrc @@ -34,7 +34,7 @@ Project { recursive: true } Files { - directory: "../../EasyApp/src/EasyApp" + directory: "../../EasyApp/src/EasyApplication" filter: "qmldir;*.ts;*.qrc;*.html" recursive: true } diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index 5a4d8c3..a0e7631 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -1,5 +1,114 @@ + ../../EasyApp/src/EasyApplication/Gui/Animations/ColorReset.qml + ../../EasyApp/src/EasyApplication/Gui/Animations/qmldir + ../../EasyApp/src/EasyApplication/Gui/Animations/ThemeChange.qml + ../../EasyApp/src/EasyApplication/Gui/Animations/TranslationChange.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/ChartViewHeatmap2dPlotly.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/ChartViewSimple1dPlotly.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly1dBarPlot.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly1dLine.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly1dMeasVsCalc.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly2dHeatmap.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly2dPolarHeatmap.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly3dScatter.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly3dSurface.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/qmldir + ../../EasyApp/src/EasyApplication/Gui/Charts/QtCharts1dBase 2.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/QtCharts1dBase.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/QtCharts1dMeasVsCalc.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/QtCharts1dValueAxis.qml + ../../EasyApp/src/EasyApplication/Gui/Components/AboutDialog.qml + ../../EasyApp/src/EasyApplication/Gui/Components/AppBarCentralTabs.qml + ../../EasyApp/src/EasyApplication/Gui/Components/AppBarLeftButtons.qml + ../../EasyApp/src/EasyApplication/Gui/Components/AppBarRightButtons.qml + ../../EasyApp/src/EasyApplication/Gui/Components/ApplicationWindow.qml + ../../EasyApp/src/EasyApplication/Gui/Components/BasicReport.qml + ../../EasyApp/src/EasyApplication/Gui/Components/ContentArea.qml + ../../EasyApp/src/EasyApplication/Gui/Components/ContentPage.qml + ../../EasyApp/src/EasyApplication/Gui/Components/GuideWindow.qml + ../../EasyApp/src/EasyApplication/Gui/Components/GuideWindowContainer.qml + ../../EasyApp/src/EasyApplication/Gui/Components/JsonListModel.qml + ../../EasyApp/src/EasyApplication/Gui/Components/ListView.qml + ../../EasyApp/src/EasyApplication/Gui/Components/ListViewDelegate.qml + ../../EasyApp/src/EasyApplication/Gui/Components/ListViewHeader.qml + ../../EasyApp/src/EasyApplication/Gui/Components/ListViewTextInput.qml + ../../EasyApp/src/EasyApplication/Gui/Components/MainContent.qml + ../../EasyApp/src/EasyApplication/Gui/Components/PreferencesDialog.qml + ../../EasyApp/src/EasyApplication/Gui/Components/ProjectDescriptionDialog.qml + ../../EasyApp/src/EasyApplication/Gui/Components/qmldir + ../../EasyApp/src/EasyApplication/Gui/Components/SideBar.qml + ../../EasyApp/src/EasyApplication/Gui/Components/SideBarColumn.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableView.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewAdvancedLabel.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewButton.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewCheckBox.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewComboBox.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewDelegate.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewHeader.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewLabel.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewLabelControl.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewParameter.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewTextInput.qml + ../../EasyApp/src/EasyApplication/Gui/Components/TableViewTwoRowsAdvancedLabel.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/AppBarTabButton.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ApplicationWindow.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/Button.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/CheckBox.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/CheckIndicator.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ComboBox.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/CursorDelegate.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/Dialog.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/DialogButtonBox.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/GroupBox.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/GroupButton.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/GroupColumn.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/GroupRow.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/Label.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/LinkedImage.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/Menu.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/MenuItem.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ParamComboBox.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/Parameter.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ParamTextField.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/qmldir + ../../EasyApp/src/EasyApplication/Gui/Elements/RadioButton.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/RadioIndicator.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/RemoteController.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/RemotePointer.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/RunningLabel.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ScrollBar.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ScrollIndicator.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/SideBarButton.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/Slider.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/SliderHandle.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/SpinBox.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/SplashScreen.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/StatusBar.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/StatusBarItem.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/TabBar.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/TabButton.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/TextArea.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/TextField.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/TextInput.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ToolButton.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ToolTip.qml + ../../EasyApp/src/EasyApplication/Gui/Elements/ToolTipShadow.qml + ../../EasyApp/src/EasyApplication/Gui/Globals/qmldir + ../../EasyApp/src/EasyApplication/Gui/Globals/Vars.qml + ../../EasyApp/src/EasyApplication/Gui/Html/plotly-2.18.0.min.js + ../../EasyApp/src/EasyApplication/Gui/Logic/Plotting.js + ../../EasyApp/src/EasyApplication/Gui/Logic/ProjectConfig.js + ../../EasyApp/src/EasyApplication/Gui/Logic/qmldir + ../../EasyApp/src/EasyApplication/Gui/Logic/Translate.js + ../../EasyApp/src/EasyApplication/Gui/Logic/Utils.js + ../../EasyApp/src/EasyApplication/Gui/Style/Colors.qml + ../../EasyApp/src/EasyApplication/Gui/Style/Fonts.qml + ../../EasyApp/src/EasyApplication/Gui/Style/qmldir + ../../EasyApp/src/EasyApplication/Gui/Style/Sizes.qml + ../../EasyApp/src/EasyApplication/Gui/Style/Times.qml + ../../EasyApp/src/EasyApplication/Logic/Maintenance/qmldir + ../../EasyApp/src/EasyApplication/Logic/Maintenance/Updater.qml easyshapes_app.qmlproject easyshapes_app/Backends/MockBackend.qml easyshapes_app/Backends/MockQml/Analysis.qml diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml index 0e68005..9ed62bf 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls +import EasyApplication.Gui.Globals as EaGlobals import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements import EasyApplication.Gui.Components as EaComponents @@ -110,7 +111,7 @@ EaElements.GroupColumn { Column { width: EaStyle.Sizes.sideBarContentWidth / 3 - spacing: EaStyle.Sizes.fontPixelSize * 0.5 + spacing: EaStyle.Sizes.fontPixelSize EaElements.Label { enabled: false @@ -119,7 +120,7 @@ EaElements.GroupColumn { Row { id: ionChipRow - spacing: EaStyle.Sizes.fontPixelSize + spacing: EaStyle.Sizes.fontPixelSize * 0.5 height: EaStyle.Sizes.fontPixelSize * 2 width: buttonWidth @@ -142,8 +143,12 @@ EaElements.GroupColumn { required property int index required property string name - height: ionChipRow.height - width: (ionChipRow.width - ionChipRow.spacing) / 2 + height: chipLabel.implicitHeight * 2 + width: EaStyle.Sizes.fontPixelSize + + chipLabel.implicitWidth + + EaStyle.Sizes.fontPixelSize + + chipRemove.width + + EaStyle.Sizes.fontPixelSize * 0.6 radius: height / 2 color: EaStyle.Colors.appBarBackground border.color: EaStyle.Colors.appBarComboBoxBorder @@ -162,25 +167,30 @@ EaElements.GroupColumn { } } - EaElements.ToolButton { + EaElements.Label { id: chipRemove - width: EaStyle.Sizes.toolButtonHeight * 0.4 - height: EaStyle.Sizes.toolButtonHeight * 0.4 + text: '×' + font.pixelSize: EaStyle.Sizes.fontPixelSize * 1.4 + color: chipRemoveArea.containsMouse + ? EaStyle.Colors.themeForegroundHovered + : EaStyle.Colors.themeForegroundMinor anchors.right: parent.right anchors.rightMargin: EaStyle.Sizes.fontPixelSize * 0.6 anchors.verticalCenter: parent.verticalCenter - ToolTip.text: qsTr('Remove this ion') - onClicked: Globals.BackendWrapper.ionsRemove(index) - - contentItem: Text { - text: 'X' - font.family: EaStyle.Fonts.fontFamily - font.pixelSize: EaStyle.Sizes.fontPixelSize - color: chipRemove.hovered - ? EaStyle.Colors.themeForegroundHovered - : EaStyle.Colors.themeForegroundMinor - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + + MouseArea { + id: chipRemoveArea + anchors.fill: parent + anchors.margins: -EaStyle.Sizes.fontPixelSize * 0.3 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: Globals.BackendWrapper.ionsRemove(index) + } + + EaElements.ToolTip { + text: qsTr('Remove this ion') + visible: chipRemoveArea.containsMouse + && EaGlobals.Vars.showToolTips } } } @@ -198,18 +208,22 @@ EaElements.GroupColumn { Column { width: EaStyle.Sizes.sideBarContentWidth / 3 - spacing: EaStyle.Sizes.fontPixelSize * 0.5 + spacing: EaStyle.Sizes.fontPixelSize - EaElements.Label { - enabled: false - text: qsTr('Amount') - } - EaElements.TextField { - id: ionAmountField + Column { width: parent.width - enabled: !matchStructureCheck.checked - placeholderText: qsTr('Set the amount of selected ions') - validator: IntValidator { bottom: 0 } + + EaElements.Label { + enabled: false + text: qsTr('Amount') + } + EaElements.TextField { + id: ionAmountField + width: parent.width + enabled: !matchStructureCheck.checked + placeholderText: qsTr('Set the amount of selected ions') + validator: IntValidator { bottom: 0 } + } } EaElements.CheckBox { id: matchStructureCheck diff --git a/src/easyshapes_app/test.qml b/src/easyshapes_app/test.qml index 09cdc99..cb4d953 100644 --- a/src/easyshapes_app/test.qml +++ b/src/easyshapes_app/test.qml @@ -50,12 +50,25 @@ ApplicationWindow{ } // groubox to test the element - EaElements.GroupBox { - title: qsTr('Test a group widget') - icon: 'wrench' - collapsed: false + // EaElements.GroupBox { + // title: qsTr('Test a group widget') + // icon: 'wrench' + // collapsed: false - Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml'} + // Loader { source: 'Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml'} + // } + + EaElements.Pill { + text: "text" + } + + EaElements.Pill { + text: "fontIcon" + fontIcon: "atom" + } + + EaElements.Pill { + text: "superlongtext why can't I hold all this text in my hands" } } } From 20c7ba6af7b768cffa4dce51325b79b48e7d5983 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Wed, 20 May 2026 16:16:25 +0200 Subject: [PATCH 22/32] adjusted solvent ions columns further --- .../Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml index 9ed62bf..5f3bb0f 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml @@ -143,7 +143,7 @@ EaElements.GroupColumn { required property int index required property string name - height: chipLabel.implicitHeight * 2 + height: ionChipRow.height width: EaStyle.Sizes.fontPixelSize + chipLabel.implicitWidth + EaStyle.Sizes.fontPixelSize From 4fffb3d2299903581a8613ba802c9c3e5f1276a9 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Fri, 22 May 2026 17:04:43 +0200 Subject: [PATCH 23/32] added advanced controls to sample model and basic analysis tab --- src/easyshapes_app.qrc | 24 +-- src/easyshapes_app/Backends/MockBackend.qml | 8 + .../Backends/MockQml/AnalysisConfig.qml | 88 +++++++++ .../Backends/MockQml/ComponentsFiles.qml | 101 +++++++++++ .../Backends/MockQml/PositionRestraints.qml | 93 ++++++++++ .../Backends/MockQml/StructureFiles.qml | 43 +++++ src/easyshapes_app/Backends/MockQml/qmldir | 4 + src/easyshapes_app/Gui/ApplicationWindow.qml | 13 +- .../Gui/Globals/BackendWrapper.qml | 48 +++++ .../Sidebar/Basic/Groups/AnalysisConfig.qml | 151 ++++++++++++++++ .../Sidebar/Basic/Groups/GetStarted.qml | 52 ------ .../Pages/Analysis/Sidebar/Basic/Layout.qml | 25 ++- .../Basic/Popups/AddAnalysisConfigFiles.qml | 23 +++ .../Gui/Pages/SampleModel/Layout.qml | 6 +- .../Advanced/Groups/ComponentsFiles.qml | 169 ++++++++++++++++++ .../Advanced/Groups/PositionRestraints.qml | 100 +++++++++++ .../Advanced/Groups/StructureFiles.qml | 95 ++++++++++ .../SampleModel/Sidebar/Advanced/Layout.qml | 36 ++++ .../Advanced/Popups/AddComponentFiles.qml | 25 +++ .../Advanced/Popups/ReplaceStructure.qml | 11 ++ .../Sidebar/Basic/Groups/Group2.qml | 18 -- .../SampleModel/Sidebar/Basic/Layout.qml | 20 ++- .../Gui/Pages/Toolbox/Layout.qml | 50 ------ .../Gui/Pages/Toolbox/MainArea/GraphsView.qml | 87 --------- .../Gui/Pages/Toolbox/MainArea/Image.qml | 18 -- .../Pages/Toolbox/MainArea/TextAreaPlain.qml | 22 --- .../Pages/Toolbox/MainArea/TextAreaRich.qml | 31 ---- .../Toolbox/Sidebar/Basic/Groups/Group1.qml | 16 -- .../Toolbox/Sidebar/Basic/Groups/Group2.qml | 30 ---- .../Toolbox/Sidebar/Basic/Groups/Group3.qml | 43 ----- .../Toolbox/Sidebar/Basic/Groups/Group4.qml | 30 ---- .../Pages/Toolbox/Sidebar/Basic/Layout.qml | 45 ----- 32 files changed, 1050 insertions(+), 475 deletions(-) create mode 100644 src/easyshapes_app/Backends/MockQml/AnalysisConfig.qml create mode 100644 src/easyshapes_app/Backends/MockQml/ComponentsFiles.qml create mode 100644 src/easyshapes_app/Backends/MockQml/PositionRestraints.qml create mode 100644 src/easyshapes_app/Backends/MockQml/StructureFiles.qml create mode 100644 src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml create mode 100644 src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/Layout.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml delete mode 100644 src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index a0e7631..a69b8b4 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -112,15 +112,18 @@ easyshapes_app.qmlproject easyshapes_app/Backends/MockBackend.qml easyshapes_app/Backends/MockQml/Analysis.qml + easyshapes_app/Backends/MockQml/AnalysisConfig.qml easyshapes_app/Backends/MockQml/BallStructure.qml easyshapes_app/Backends/MockQml/BilayerStructure.qml easyshapes_app/Backends/MockQml/Components.qml + easyshapes_app/Backends/MockQml/ComponentsFiles.qml easyshapes_app/Backends/MockQml/Fractions.qml easyshapes_app/Backends/MockQml/Ions.qml easyshapes_app/Backends/MockQml/Lamellae.qml easyshapes_app/Backends/MockQml/LatticeStructure.qml easyshapes_app/Backends/MockQml/Layers.qml easyshapes_app/Backends/MockQml/MonolayerStructure.qml + easyshapes_app/Backends/MockQml/PositionRestraints.qml easyshapes_app/Backends/MockQml/Project.qml easyshapes_app/Backends/MockQml/qmldir easyshapes_app/Backends/MockQml/Report.qml @@ -129,6 +132,7 @@ easyshapes_app/Backends/MockQml/SampleModel.qml easyshapes_app/Backends/MockQml/Solution.qml easyshapes_app/Backends/MockQml/Status.qml + easyshapes_app/Backends/MockQml/StructureFiles.qml easyshapes_app/Backends/MockQml/VesicleStructure.qml easyshapes_app/Backends/qmldir easyshapes_app/Gui/ApplicationWindow.qml @@ -138,8 +142,9 @@ easyshapes_app/Gui/Globals/References.qml easyshapes_app/Gui/Pages/Analysis/Layout.qml easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml - easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml + easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml + easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml easyshapes_app/Gui/Pages/Home/Content.qml easyshapes_app/Gui/Pages/Home/Popups/About.qml easyshapes_app/Gui/Pages/Project/Layout.qml @@ -161,11 +166,16 @@ easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Layout.qml easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml @@ -186,16 +196,6 @@ easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml - easyshapes_app/Gui/Pages/Toolbox/Layout.qml - easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml - easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml - easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml - easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml - easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml - easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml - easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml - easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml - easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml easyshapes_app/Gui/qmldir easyshapes_app/Gui/StatusBar.qml easyshapes_app/main.qml diff --git a/src/easyshapes_app/Backends/MockBackend.qml b/src/easyshapes_app/Backends/MockBackend.qml index a250da0..81ea03a 100644 --- a/src/easyshapes_app/Backends/MockBackend.qml +++ b/src/easyshapes_app/Backends/MockBackend.qml @@ -14,6 +14,10 @@ QtObject { property var project: MockLogic.Project property var sampleModel: MockLogic.SampleModel property var components: MockLogic.Components + // Advanced sidebar (Sample Model page) domain singletons. + property var componentsFiles: MockLogic.ComponentsFiles + property var positionRestraints: MockLogic.PositionRestraints + property var structureFiles: MockLogic.StructureFiles // Default fractions set tied to the shared component list. // Used as the fallback for the Fractions sidebar component when no // per-row override is set. @@ -43,12 +47,16 @@ QtObject { property var solution: MockLogic.Solution property var ions: MockLogic.Ions property var analysis: MockLogic.Analysis + // Analysis page — equilibration configuration (config files + force + // field + step range), driving the Equilibrate action. + property var analysisConfig: MockLogic.AnalysisConfig property var status: MockLogic.Status property var report: MockLogic.Report Component.onCompleted: { layers.fractionsSource = MockLogic.Components.loaded lamellae.fractionsSource = MockLogic.Components.loaded + positionRestraints.source = MockLogic.Components.loaded } } diff --git a/src/easyshapes_app/Backends/MockQml/AnalysisConfig.qml b/src/easyshapes_app/Backends/MockQml/AnalysisConfig.qml new file mode 100644 index 0000000..eabbd5b --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/AnalysisConfig.qml @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +// Analysis page — equilibration configuration. +// +// Holds: +// * configFiles — Pattern D ListModel of .mdp paths (delete + edit per row). +// * forceFields — JS array of selectable force-field labels (no edits). +// * forceField — currently chosen entry from forceFields. +// * startStep / stopStep — inclusive integer bounds driving which mdp +// files participate in equilibration (defaults 0..6, matching the +// seeded equil0.mdp..equil6.mdp set). +QtObject { + + readonly property var configFiles: ListModel { + id: configFilesModel + ListElement { path: 'analysis/equil0.mdp' } + ListElement { path: 'analysis/equil1.mdp' } + ListElement { path: 'analysis/equil2.mdp' } + ListElement { path: 'analysis/equil3.mdp' } + ListElement { path: 'analysis/equil4.mdp' } + ListElement { path: 'analysis/equil5.mdp' } + ListElement { path: 'analysis/equil6.mdp' } + } + + readonly property var forceFields: [ + 'CHARMM36', + 'CHARMM27', + 'AMBER99SB-ILDN', + 'AMBER14SB', + 'OPLS-AA/L', + 'GROMOS54a7', + 'MARTINI 3', + 'MARTINI 2.2', + 'GAFF' + ] + + property string forceField: 'CHARMM36' + + property int startStep: 0 + property int stopStep: 6 + + function appendFile(item) { + configFilesModel.append({ path: item.path || '' }) + } + + function appendPath(path) { + configFilesModel.append({ path: path }) + } + + function removeFile(index) { + if (index < 0 || index >= configFilesModel.count) return + configFilesModel.remove(index) + } + + function clearFiles() { configFilesModel.clear() } + + // Mock placeholder for opening the file in an editor. + function editFile(index) { + if (index < 0 || index >= configFilesModel.count) return + console.debug('AnalysisConfig.editFile:', configFilesModel.get(index).path) + } + + function setForceField(value) { + if (forceField === value) return + forceField = value + } + + function setStartStep(value) { + const v = Math.max(0, parseInt(value)) + if (isNaN(v) || startStep === v) return + startStep = v + if (stopStep < startStep) stopStep = startStep + } + + function setStopStep(value) { + const v = Math.max(0, parseInt(value)) + if (isNaN(v) || stopStep === v) return + stopStep = v + if (startStep > stopStep) startStep = stopStep + } +} diff --git a/src/easyshapes_app/Backends/MockQml/ComponentsFiles.qml b/src/easyshapes_app/Backends/MockQml/ComponentsFiles.qml new file mode 100644 index 0000000..65f3b06 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/ComponentsFiles.qml @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +// Pattern D backend (multi-row list with selection) for the per-component +// file list shown in the Advanced sidebar of the Sample Model page. +// +// The full state is a map of {componentName -> [paths]}. The UI only ever +// sees one component's file list at a time, so we keep that as a single +// ListModel (`files`) and repopulate it whenever the user picks a different +// component through `selectComponent(name)`. Storing the active list as a +// ListModel (rather than a JS array) keeps role-based delegate properties +// and ItemSelectionModel-driven selection working — see QML_MOCKUP_BACKEND +// Pattern D for the rationale. +QtObject { + id: root + + // Name of the component whose files are currently being shown. + // Empty string means no selection — `files` is then empty. + property string selectedComponent: '' + + // Internal map: componentName -> JS array of file paths. Mutations go + // through __syncBack() so the map stays consistent with the ListModel. + property var filesByComponent: ({ + 'DPPC': [ + 'assets/components/DPPC.itp', + 'assets/components/DPPC.gro', + 'assets/components/DPPC.pdb' + ], + 'DOPC': [ + 'assets/components/DOPC.itp', + 'assets/components/DOPC.gro' + ], + 'POPC': [ + 'assets/components/POPC.itp' + ] + }) + + // Active file list for `selectedComponent`. Roles: path. + readonly property var files: ListModel { + id: filesModel + } + + function selectComponent(name) { + if (selectedComponent === name) return + selectedComponent = name + filesModel.clear() + const list = filesByComponent[name] || [] + for (let i = 0; i < list.length; ++i) { + filesModel.append({ path: list[i] }) + } + } + + function appendFile(path) { + if (selectedComponent === '') return + filesModel.append({ path: path }) + __syncBack() + } + + function removeFile(index) { + if (index < 0 || index >= filesModel.count) return + filesModel.remove(index) + __syncBack() + } + + // Export the whole selected component (all its files). Mock backend + // just logs — the real backend will write to disk. + function exportComponent() { + if (selectedComponent === '') return + console.debug('ComponentsFiles.exportComponent:', selectedComponent, + JSON.stringify(filesByComponent[selectedComponent] || [])) + } + + // Persist the current file list for the selected component. Mock just + // mirrors filesModel back into filesByComponent — already kept in sync, + // so this is a no-op for the mock other than the debug print. + function save() { + if (selectedComponent === '') return + __syncBack() + console.debug('ComponentsFiles.save:', selectedComponent, + JSON.stringify(filesByComponent[selectedComponent])) + } + + function __syncBack() { + if (selectedComponent === '') return + const arr = [] + for (let i = 0; i < filesModel.count; ++i) { + arr.push(filesModel.get(i).path) + } + // Reassign the map so the `filesByComponent` property emits its + // Changed signal — keeps any future bindings on the map honest. + const copy = Object.assign({}, filesByComponent) + copy[selectedComponent] = arr + filesByComponent = copy + } +} diff --git a/src/easyshapes_app/Backends/MockQml/PositionRestraints.qml b/src/easyshapes_app/Backends/MockQml/PositionRestraints.qml new file mode 100644 index 0000000..0ea67aa --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/PositionRestraints.qml @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +// Pattern D backend (multi-row list with selection) for position restraints +// shown in the Advanced sidebar of the Sample Model page. +// +// Rows are derived from the shared Components list: one restraint row per +// loaded component, with the component name mirrored read-only and the +// atom / p_form / geom / radius / f_constant fields owned here. Sync is +// surgical so editing one component doesn't reset the others' state. +// +// Roles: component_name, atom, p_form, geom, radius, f_constant. +QtObject { + id: root + + // Shared component list this mirrors. Wired by MockBackend at startup; + // kept as a property (rather than a hard import) to side-step singleton + // init-order quirks — same approach as Layers/Lamellae fractionsSource. + property var source: null + + readonly property var items: ListModel { id: itemsModel } + + onSourceChanged: rebuild() + + property var __sync: Connections { + target: root.source + + function onRowsInserted(parent, first, last) { + for (let i = first; i <= last; ++i) { + const c = root.source.get(i) + itemsModel.insert(i, { + component_name: c.name, + atom: 1, + p_form: 1, + geom: 1, + radius: 0.5, + f_constant: 'POSRES_FC_LIP' + }) + } + } + function onRowsRemoved(parent, first, last) { + for (let i = last; i >= first; --i) itemsModel.remove(i) + } + function onDataChanged(topLeft, bottomRight, roles) { + for (let i = topLeft.row; i <= bottomRight.row; ++i) { + itemsModel.setProperty(i, 'component_name', root.source.get(i).name) + } + } + function onModelReset() { root.rebuild() } + } + + function rebuild() { + itemsModel.clear() + if (!source) return + for (let i = 0; i < source.count; ++i) { + const c = source.get(i) + itemsModel.append({ + component_name: c.name, + atom: 1, + p_form: 1, + geom: 1, + radius: 0.5, + f_constant: 'POSRES_FC_LIP' + }) + } + } + + function setAtom(index, value) { + if (index < 0 || index >= itemsModel.count) return + itemsModel.setProperty(index, 'atom', value) + } + + function setPForm(index, value) { + if (index < 0 || index >= itemsModel.count) return + itemsModel.setProperty(index, 'p_form', value) + } + + function setGeom(index, value) { + if (index < 0 || index >= itemsModel.count) return + itemsModel.setProperty(index, 'geom', value) + } + + function setRadius(index, value) { + if (index < 0 || index >= itemsModel.count) return + itemsModel.setProperty(index, 'radius', value) + } +} diff --git a/src/easyshapes_app/Backends/MockQml/StructureFiles.qml b/src/easyshapes_app/Backends/MockQml/StructureFiles.qml new file mode 100644 index 0000000..61838eb --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/StructureFiles.qml @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +// Pattern D backend (multi-row list with selection) for structure files +// shown in the Advanced sidebar of the Sample Model page. +// Roles: path. +QtObject { + + readonly property var files: ListModel { + id: filesModel + ListElement { path: 'structure/topology.top' } + ListElement { path: 'structure/coordinates.gro' } + ListElement { path: 'structure/serialized.dat' } + } + + function appendItem(item) { + filesModel.append({ path: item.path || '' }) + } + + function appendPath(path) { + filesModel.append({ path: path }) + } + + function removeItem(index) { + if (index < 0 || index >= filesModel.count) return + filesModel.remove(index) + } + + function clear() { + filesModel.clear() + } + + // Mock placeholder — real backend will persist to the asset library. + function saveToLib() { + console.debug('StructureFiles.saveToLib: paths=' + filesModel.count) + } +} diff --git a/src/easyshapes_app/Backends/MockQml/qmldir b/src/easyshapes_app/Backends/MockQml/qmldir index 3894fee..580dcd6 100644 --- a/src/easyshapes_app/Backends/MockQml/qmldir +++ b/src/easyshapes_app/Backends/MockQml/qmldir @@ -3,6 +3,9 @@ module MockQml singleton Project Project.qml singleton SampleModel SampleModel.qml singleton Components Components.qml +singleton ComponentsFiles ComponentsFiles.qml +singleton PositionRestraints PositionRestraints.qml +singleton StructureFiles StructureFiles.qml Fractions Fractions.qml singleton Layers Layers.qml singleton Lamellae Lamellae.qml @@ -16,5 +19,6 @@ singleton LatticeStructure LatticeStructure.qml singleton Solution Solution.qml singleton Ions Ions.qml singleton Analysis Analysis.qml +singleton AnalysisConfig AnalysisConfig.qml singleton Report Report.qml singleton Status Status.qml diff --git a/src/easyshapes_app/Gui/ApplicationWindow.qml b/src/easyshapes_app/Gui/ApplicationWindow.qml index eb05838..149e8f9 100644 --- a/src/easyshapes_app/Gui/ApplicationWindow.qml +++ b/src/easyshapes_app/Gui/ApplicationWindow.qml @@ -108,16 +108,8 @@ EaComponents.ApplicationWindow { Component.onCompleted: { Globals.References.applicationWindow.appBarCentralTabs.summaryButton = summaryButton } - }, - // Summary page - - // Toolbox page - EaElements.AppBarTabButton { - fontIcon: 'toolbox' - text: qsTr('Toolbox') - ToolTip.text: qsTr('Toolbox with common widgets') } - // Toolbox page + // Summary page ] @@ -131,8 +123,7 @@ EaComponents.ApplicationWindow { Loader { source: 'Pages/Project/Layout.qml' }, Loader { source: 'Pages/SampleModel/Layout.qml' }, Loader { source: 'Pages/Analysis/Layout.qml' }, - Loader { source: 'Pages/Report/Layout.qml' }, - Loader { source: 'Pages/Toolbox/Layout.qml' } + Loader { source: 'Pages/Report/Layout.qml' } ] ///////////// diff --git a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml index 80c5f73..bdcfba1 100644 --- a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml +++ b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml @@ -206,6 +206,39 @@ QtObject { function ionsSaveToCatalog() { activeBackend.ions.saveToCatalog() } function ionsRemoveFromCatalog(index) { activeBackend.ions.removeFromCatalog(index) } + // Components Files (Sample Model Advanced sidebar). Per-component file + // list driven by `componentsFilesSelectedComponent`; switching the + // selection repopulates `componentsFilesFiles`. + readonly property var componentsFilesFiles: activeBackend.componentsFiles.files + readonly property string componentsFilesSelectedComponent: activeBackend.componentsFiles.selectedComponent + + function componentsFilesSelect(name) { activeBackend.componentsFiles.selectComponent(name) } + function componentsFilesAppend(path) { activeBackend.componentsFiles.appendFile(path) } + function componentsFilesRemove(index) { activeBackend.componentsFiles.removeFile(index) } + function componentsFilesExportComponent() { activeBackend.componentsFiles.exportComponent() } + function componentsFilesSave() { activeBackend.componentsFiles.save() } + + // Position Restraints (Sample Model Advanced sidebar). One row per + // loaded component (mirrored from componentsLoaded); component_name is + // read-only here. f_constant is fixed to POSRES_FC_LIP for the mock. + readonly property var positionRestraintsItems: activeBackend.positionRestraints.items + + function positionRestraintsSetAtom(index, value) { activeBackend.positionRestraints.setAtom(index, value) } + function positionRestraintsSetPForm(index, value) { activeBackend.positionRestraints.setPForm(index, value) } + function positionRestraintsSetGeom(index, value) { activeBackend.positionRestraints.setGeom(index, value) } + function positionRestraintsSetRadius(index, value) { activeBackend.positionRestraints.setRadius(index, value) } + + // Structure Files (Sample Model Advanced sidebar). List of structure- + // related files plus actions for saving the set to the asset library + // and re-seeding structure parameters from a serialized data file. + readonly property var structureFilesFiles: activeBackend.structureFiles.files + + function structureFilesAppend(item) { activeBackend.structureFiles.appendItem(item) } + function structureFilesAppendPath(path) { activeBackend.structureFiles.appendPath(path) } + function structureFilesRemove(index) { activeBackend.structureFiles.removeItem(index) } + function structureFilesClear() { activeBackend.structureFiles.clear() } + function structureFilesSaveToLib() { activeBackend.structureFiles.saveToLib() } + //////////////// // Analysis page //////////////// @@ -213,6 +246,21 @@ QtObject { // All the properties and methods related to the analysis page // are defined directly in the Backends/MockQml/Analysis.qml !!! + // Analysis configuration group — equilibration .mdp files, the chosen + // force field, and the inclusive [start, stop] step range. + readonly property var analysisConfigFiles: activeBackend.analysisConfig.configFiles + readonly property var analysisConfigForceFields: activeBackend.analysisConfig.forceFields + readonly property string analysisConfigForceField: activeBackend.analysisConfig.forceField + readonly property int analysisConfigStartStep: activeBackend.analysisConfig.startStep + readonly property int analysisConfigStopStep: activeBackend.analysisConfig.stopStep + + function analysisConfigAppendPath(path) { activeBackend.analysisConfig.appendPath(path) } + function analysisConfigRemoveFile(index) { activeBackend.analysisConfig.removeFile(index) } + function analysisConfigEditFile(index) { activeBackend.analysisConfig.editFile(index) } + function analysisConfigSetForceField(value) { activeBackend.analysisConfig.setForceField(value) } + function analysisConfigSetStartStep(value) { activeBackend.analysisConfig.setStartStep(value) } + function analysisConfigSetStopStep(value) { activeBackend.analysisConfig.setStopStep(value) } + /////////////// // Summary page /////////////// diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml new file mode 100644 index 0000000..03eb8f4 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml @@ -0,0 +1,151 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + + EaComponents.ListView { + id: configFilesList + defaultInfoText: qsTr('No configuration files added') + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + EaStyle.Sizes.tableRowHeight, + EaStyle.Sizes.tableRowHeight + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr('№') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Path') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.analysisConfigFiles + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string path + + EaComponents.TableViewLabel { + text: index + 1 + enabled: false + } + EaComponents.TableViewLabel { + text: path + elide: Text.ElideLeft + } + EaComponents.TableViewButton { + fontIcon: 'edit' + ToolTip.text: qsTr('Edit this file') + onClicked: Globals.BackendWrapper.analysisConfigEditFile(index) + } + EaComponents.TableViewButton { + fontIcon: 'minus-circle' + ToolTip.text: qsTr('Remove this file') + onClicked: Globals.BackendWrapper.analysisConfigRemoveFile(index) + } + } + } + + EaElements.SideBarButton { + fontIcon: 'plus-circle' + text: qsTr('Add file(s)') + width: EaStyle.Sizes.sideBarContentWidth * 0.48 + ToolTip.text: qsTr('Pick one or more .mdp files from disk') + onClicked: addFilesLoader.item.open() + } + + // ForceField selector on the left, step-range pair on the right. + Row { + spacing: EaStyle.Sizes.fontPixelSize + property real halfWidth: (EaStyle.Sizes.sideBarContentWidth - spacing) / 2 + property real fieldWidth: (halfWidth - EaStyle.Sizes.fontPixelSize * 0.5) / 2 + + EaElements.ComboBox { + id: forceFieldSelector + width: parent.halfWidth + topInset: forceFieldLabel.height + topPadding: topInset + padding + model: Globals.BackendWrapper.analysisConfigForceFields + currentIndex: Math.max( + 0, + Globals.BackendWrapper.analysisConfigForceFields.indexOf( + Globals.BackendWrapper.analysisConfigForceField)) + onActivated: (i) => Globals.BackendWrapper.analysisConfigSetForceField( + Globals.BackendWrapper.analysisConfigForceFields[i]) + + EaElements.Label { + id: forceFieldLabel + text: qsTr('ForceField') + } + } + + Row { + spacing: EaStyle.Sizes.fontPixelSize * 0.5 + + EaElements.TextField { + id: startStepField + width: parent.parent.fieldWidth + topInset: startStepLabel.height + topPadding: topInset + padding + horizontalAlignment: TextInput.AlignLeft + inputMethodHints: Qt.ImhDigitsOnly + validator: IntValidator { bottom: 0 } + text: Globals.BackendWrapper.analysisConfigStartStep + onEditingFinished: Globals.BackendWrapper.analysisConfigSetStartStep(text) + + EaElements.Label { + id: startStepLabel + text: qsTr('Start step') + } + } + + EaElements.TextField { + id: stopStepField + width: parent.parent.fieldWidth + topInset: stopStepLabel.height + topPadding: topInset + padding + horizontalAlignment: TextInput.AlignLeft + inputMethodHints: Qt.ImhDigitsOnly + validator: IntValidator { bottom: 0 } + text: Globals.BackendWrapper.analysisConfigStopStep + onEditingFinished: Globals.BackendWrapper.analysisConfigSetStopStep(text) + + EaElements.Label { + id: stopStepLabel + text: qsTr('Stop step') + } + } + } + } + + Loader { + id: addFilesLoader + source: '../Popups/AddAnalysisConfigFiles.qml' + } +} diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml deleted file mode 100644 index a9d1be1..0000000 --- a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/GetStarted.qml +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn { - - // 1st row - EaElements.GroupRow { - spacing: EaStyle.Sizes.fontPixelSize - - // button - EaElements.SideBarButton { - id: generateDataButton - - fontIcon: 'plus-circle' - text: qsTr('Generate new data') - - onClicked: { - console.debug(`Clicking '${text}' button ::: ${this}`) - Globals.BackendWrapper.activeBackend.analysis.generateData() - } - } - // button - - // text input - EaElements.ParamTextField { - height: generateDataButton.height - - inputMethodHints: Qt.ImhDigitsOnly - - value: Globals.BackendWrapper.activeBackend.analysis.dataSize - units: 'points' - - onTextChanged: { - Globals.BackendWrapper.activeBackend.analysis.dataSize = text - } - } - // text input - } - // 1st row -} diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml index ea6ddaf..d647b5b 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls +import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements import EasyApplication.Gui.Components as EaComponents @@ -14,11 +15,29 @@ import Gui.Globals as Globals EaComponents.SideBarColumn { EaElements.GroupBox { - title: qsTr('Get started') - icon: 'rocket' + title: qsTr('Equilibration setup') + icon: 'sliders-h' collapsed: false - Loader { source: 'Groups/GetStarted.qml' } + Loader { source: 'Groups/AnalysisConfig.qml' } + } + + // Centered "Equilibrate" call-to-action below the groups. + Item { + width: parent.width + height: equilibrateButton.height + EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + id: equilibrateButton + anchors.centerIn: parent + text: qsTr('Equilibrate') + fontIcon: 'magic' + width: EaStyle.Sizes.sideBarContentWidth + enabled: Globals.BackendWrapper.analysisConfigFiles + ? Globals.BackendWrapper.analysisConfigFiles.count > 0 + : false + onClicked: console.debug('Equilibrate clicked') + } } } diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml new file mode 100644 index 0000000..34cc2ce --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Dialogs + +import Gui.Globals as Globals + + +FileDialog { + fileMode: FileDialog.OpenFiles + nameFilters: [ + 'GROMACS run parameter files (*.mdp)', + 'Any (*)' + ] + + onAccepted: { + for (let i = 0; i < selectedFiles.length; ++i) { + Globals.BackendWrapper.analysisConfigAppendPath(selectedFiles[i].toString()) + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml index 193d1f9..0450c67 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml @@ -27,11 +27,13 @@ EaComponents.ContentPage { sideBar: EaComponents.SideBar { tabs: [ - EaElements.TabButton { text: qsTr('Basic controls') } + EaElements.TabButton { text: qsTr('Basic controls') }, + EaElements.TabButton { text: qsTr('Advanced controls') } ] items: [ - Loader { source: 'Sidebar/Basic/Layout.qml' } + Loader { source: 'Sidebar/Basic/Layout.qml' }, + Loader { source: 'Sidebar/Advanced/Layout.qml' } ] continueButton.text: qsTr('Continue') diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml new file mode 100644 index 0000000..53681f7 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + + // ComboBox + empty-state placeholder share a single slot so the layout + // doesn't reflow between the two states. The ComboBox is hidden (not + // just disabled) when there are no components because its popup keeps + // an ElevationEffect that leaks a soft shadow when the model is empty. + readonly property int componentsCount: + Globals.BackendWrapper.componentsLoaded + ? Globals.BackendWrapper.componentsLoaded.count + : 0 + + EaElements.ComboBox { + id: componentSelector + visible: componentsCount > 0 + width: EaStyle.Sizes.sideBarContentWidth + topInset: componentLabel.height + topPadding: topInset + padding + textRole: 'name' + model: Globals.BackendWrapper.componentsLoaded + + EaElements.Label { + id: componentLabel + text: qsTr('Component') + } + + onActivated: (i) => { + const row = Globals.BackendWrapper.componentsLoaded.get(i) + if (row) Globals.BackendWrapper.componentsFilesSelect(row.name) + } + + // Seed the selection from the currently-selected component, or the + // first component once the model populates. + function syncFromBackend() { + const total = componentsCount + if (total === 0) { + currentIndex = -1 + return + } + const sel = Globals.BackendWrapper.componentsFilesSelectedComponent + if (sel !== '') { + for (let i = 0; i < total; ++i) { + if (Globals.BackendWrapper.componentsLoaded.get(i).name === sel) { + currentIndex = i + return + } + } + } + currentIndex = 0 + const row = Globals.BackendWrapper.componentsLoaded.get(0) + if (row) Globals.BackendWrapper.componentsFilesSelect(row.name) + } + + Component.onCompleted: syncFromBackend() + // Re-seed when the user adds the first component on the Basic tab. + Connections { + target: Globals.BackendWrapper.componentsLoaded + function onCountChanged() { componentSelector.syncFromBackend() } + } + } + + EaElements.Label { + visible: componentsCount === 0 + enabled: false + width: EaStyle.Sizes.sideBarContentWidth + text: qsTr('Load a component on the Basic controls tab to manage its files here.') + wrapMode: Text.WordWrap + } + + EaComponents.ListView { + id: componentFilesList + defaultInfoText: qsTr('No files associated with this component') + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + EaStyle.Sizes.tableRowHeight + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr('№') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Path') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.componentsFilesFiles + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string path + + EaComponents.TableViewLabel { + text: index + 1 + enabled: false + } + EaComponents.TableViewLabel { + text: path + elide: Text.ElideLeft + } + EaComponents.TableViewButton { + fontIcon: 'minus-circle' + ToolTip.text: qsTr('Remove this file') + onClicked: Globals.BackendWrapper.componentsFilesRemove(index) + } + } + } + + Grid { + columns: 3 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + fontIcon: 'plus-circle' + text: qsTr('Add new file(s)') + width: buttonWidth + ToolTip.text: qsTr('Pick one or more files from disk and add them to this component') + enabled: Globals.BackendWrapper.componentsFilesSelectedComponent !== '' + onClicked: addFilesLoader.item.open() + } + + EaElements.SideBarButton { + fontIcon: 'file-export' + text: qsTr('Export component') + width: buttonWidth + ToolTip.text: qsTr('Export the selected component to disk') + enabled: Globals.BackendWrapper.componentsFilesSelectedComponent !== '' + onClicked: Globals.BackendWrapper.componentsFilesExportComponent() + } + + EaElements.SideBarButton { + fontIcon: 'save' + text: qsTr('Save') + width: buttonWidth + ToolTip.text: qsTr('Save the file list for this component') + enabled: Globals.BackendWrapper.componentsFilesSelectedComponent !== '' + onClicked: Globals.BackendWrapper.componentsFilesSave() + } + } + + Loader { + id: addFilesLoader + source: '../Popups/AddComponentFiles.qml' + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml new file mode 100644 index 0000000..c30d7be --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + + EaComponents.ListView { + id: positionRestraintsList + defaultInfoText: qsTr('No position restraints defined') + multiSelection: false + + columnWidths: [ + -1, + EaStyle.Sizes.fontPixelSize * 4, + EaStyle.Sizes.fontPixelSize * 4, + EaStyle.Sizes.fontPixelSize * 4, + EaStyle.Sizes.fontPixelSize * 5, + EaStyle.Sizes.fontPixelSize * 8 + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr('Component') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Atom') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('P. form') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Geom.') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('Radius') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('F. constant') + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.positionRestraintsItems + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string component_name + required property int atom + required property int p_form + required property int geom + required property double radius + required property string f_constant + + EaComponents.TableViewLabel { + text: component_name + } + EaComponents.ListViewTextInput { + text: atom + validator: IntValidator { bottom: 0 } + onEditingFinished: Globals.BackendWrapper.positionRestraintsSetAtom(index, parseInt(text)) + } + EaComponents.ListViewTextInput { + text: p_form + validator: IntValidator { bottom: 0 } + onEditingFinished: Globals.BackendWrapper.positionRestraintsSetPForm(index, parseInt(text)) + } + EaComponents.ListViewTextInput { + text: geom + validator: IntValidator { bottom: 0 } + onEditingFinished: Globals.BackendWrapper.positionRestraintsSetGeom(index, parseInt(text)) + } + EaComponents.ListViewTextInput { + text: radius + validator: DoubleValidator { bottom: 0 } + onEditingFinished: Globals.BackendWrapper.positionRestraintsSetRadius(index, parseFloat(text)) + } + EaComponents.TableViewLabel { + text: f_constant + enabled: false + } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml new file mode 100644 index 0000000..41820e5 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.48 + + EaComponents.ListView { + id: structureFilesList + defaultInfoText: qsTr('No structure files available') + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + EaStyle.Sizes.tableRowHeight + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr('№') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr('File') + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.structureFilesFiles + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string path + + EaComponents.TableViewLabel { + text: index + 1 + enabled: false + } + EaComponents.TableViewLabel { + text: path + elide: Text.ElideLeft + } + EaComponents.TableViewButton { + fontIcon: 'minus-circle' + ToolTip.text: qsTr('Remove this file') + onClicked: Globals.BackendWrapper.structureFilesRemove(index) + } + } + } + + Grid { + columns: 2 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + fontIcon: 'save' + text: qsTr('Save to lib') + width: buttonWidth + ToolTip.text: qsTr('Save the current structure files to the asset library') + enabled: Globals.BackendWrapper.structureFilesFiles + ? Globals.BackendWrapper.structureFilesFiles.count > 0 + : false + onClicked: Globals.BackendWrapper.structureFilesSaveToLib() + } + + EaElements.SideBarButton { + fontIcon: 'folder-open' + text: qsTr('Replace structure') + width: buttonWidth + ToolTip.text: qsTr('Pick a directory to replace the current structure with') + onClicked: replaceStructureLoader.item.open() + } + } + + Loader { + id: replaceStructureLoader + source: '../Popups/ReplaceStructure.qml' + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml new file mode 100644 index 0000000..a3341e0 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml @@ -0,0 +1,36 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + + +EaComponents.SideBarColumn { + + EaElements.GroupBox { + title: qsTr('Components Files') + icon: 'file-alt' + collapsed: false + + Loader { source: 'Groups/ComponentsFiles.qml' } + } + + EaElements.GroupBox { + title: qsTr('Position Restraints') + icon: 'thumbtack' + + Loader { source: 'Groups/PositionRestraints.qml' } + } + + EaElements.GroupBox { + title: qsTr('Structure Files') + icon: 'folder-open' + + Loader { source: 'Groups/StructureFiles.qml' } + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml new file mode 100644 index 0000000..9c641da --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Dialogs + +import Gui.Globals as Globals + + +FileDialog { + fileMode: FileDialog.OpenFiles + nameFilters: [ + 'Any (*)', + 'Structure files (*.gro *.pdb *.xyz)', + 'Topology files (*.itp)', + 'Smiles files (*.sml)' + ] + + onAccepted: { + for (let i = 0; i < selectedFiles.length; ++i) { + Globals.BackendWrapper.componentsFilesAppend(selectedFiles[i].toString()) + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml new file mode 100644 index 0000000..66fe84e --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Dialogs + + +FolderDialog { + title: qsTr('Select a directory to replace the structure with') +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml deleted file mode 100644 index af615cc..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Group2.qml +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn { - -} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index 2c4ddce..400bc4c 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -5,6 +5,7 @@ import QtQuick import QtQuick.Controls +import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements import EasyApplication.Gui.Components as EaComponents @@ -107,11 +108,20 @@ EaComponents.SideBarColumn { Loader { source: 'Groups/Solution.qml' } } - EaElements.GroupBox { - title: qsTr('Group 1') - icon: 'rocket' - - Loader { source: 'Groups/Group2.qml' } + // Centered "Assemble" call-to-action below the groups. + Item { + width: parent.width + height: assembleButton.height + EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + id: assembleButton + anchors.centerIn: parent + text: qsTr('Assemble') + fontIcon: 'cubes' + width: EaStyle.Sizes.sideBarContentWidth + enabled: Globals.BackendWrapper.sampleModelLoaded.length > 0 + onClicked: console.debug('Assemble clicked') + } } } diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Layout.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Layout.qml deleted file mode 100644 index cec9465..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Layout.qml +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents - -import Gui.Globals as Globals - - -EaComponents.ContentPage { - - mainView: EaComponents.MainContent { - tabs: [ - EaElements.TabButton { text: qsTr('Image') }, - EaElements.TabButton { text: qsTr('EaElements.GraphsView') }, - EaElements.TabButton { text: qsTr('EaElements.TextArea (Plain)') }, - EaElements.TabButton { text: qsTr('EaElements.TextArea (Rich)') } - ] - - items: [ - Loader { source: 'MainArea/Image.qml' }, - Loader { source: 'MainArea/GraphsView.qml' }, - Loader { source: 'MainArea/TextAreaPlain.qml' }, - Loader { source: 'MainArea/TextAreaRich.qml' } - ] - } - - sideBar: EaComponents.SideBar { - tabs: [ - EaElements.TabButton { text: qsTr('Basic controls') } - ] - - items: [ - Loader { source: 'Sidebar/Basic/Layout.qml' } - ] - - continueButton.visible: false - - } - - Component.onCompleted: console.debug(`Toolbox page loaded ::: ${this}`) - Component.onDestruction: console.debug(`Toolbox page destroyed ::: ${this}`) - -} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml deleted file mode 100644 index 276dfff..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/GraphsView.qml +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtGraphs - -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements - -import Gui.Globals as Globals - - -GraphsView { - anchors.fill: parent - - marginTop: EaStyle.Sizes.fontPixelSize * 2 - marginBottom: EaStyle.Sizes.fontPixelSize * 2 - marginLeft: EaStyle.Sizes.fontPixelSize - marginRight: EaStyle.Sizes.fontPixelSize * 2 - - zoomAreaEnabled: true - - // theme - theme: GraphsTheme { - backgroundColor: EaStyle.Colors.chartBackground - plotAreaBackgroundColor: EaStyle.Colors.chartBackground - - axisX.mainColor: EaStyle.Colors.chartGridLine - axisX.mainWidth: 0 - - axisY.mainColor: EaStyle.Colors.chartGridLine - axisY.mainWidth: 0 - - gridVisible: true - grid.mainWidth: 1 - grid.subWidth: 0 - grid.mainColor: EaStyle.Colors.chartGridLine - grid.subColor: EaStyle.Colors.chartMinorGridLine - - labelFont.family: EaStyle.Fonts.fontFamily - labelFont.pixelSize: EaStyle.Sizes.fontPixelSize - labelTextColor: EaStyle.Colors.chartLabels - } - // theme - - // axisX - axisX: ValueAxis { - labelDelegate: TextEdit { - horizontalAlignment: TextInput.AlignHCenter - verticalAlignment: Text.AlignVCenter - bottomPadding: EaStyle.Sizes.fontPixelSize - color: EaStyle.Colors.chartLabels - } - - titleText: 'x' - min: 0 - max: 100 - } - // axisX - - // axisY - axisY: ValueAxis { - labelDelegate: TextEdit { - horizontalAlignment: TextInput.AlignRight - verticalAlignment: Text.AlignVCenter - rightPadding: -EaStyle.Sizes.fontPixelSize - color: EaStyle.Colors.chartLabels - } - - titleText: 'y' - min: -2 - max: 2 - } - // axisY - - // lineSeries - LineSeries { - color: 'red' - - XYPoint { x: 0; y: -1 } - XYPoint { x: 50; y: 1.5 } - XYPoint { x: 100; y: -0.5 } - } - // lineSeries - -} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml deleted file mode 100644 index 045b3c7..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/Image.qml +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtGraphs - -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements - -import Gui.Globals as Globals - -Image { - - fillMode: Image.PreserveAspectFit - source: "../../../Resources/Images/structure.png" - -} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml deleted file mode 100644 index 8adbf68..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaPlain.qml +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtGraphs - -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements - -import Gui.Globals as Globals - - -EaElements.TextArea { - text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, -sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. -Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris -nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in -reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla -pariatur. Excepteur sint occaecat cupidatat non proident, sunt in -culpa qui officia deserunt mollit anim id est laborum.' -} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml b/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml deleted file mode 100644 index 377af9f..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/MainArea/TextAreaRich.qml +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtGraphs - -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements - -import Gui.Globals as Globals - - -EaElements.TextArea { - textFormat: TextEdit.RichText - - text: '

- Lorem ipsum dolor sit amet, consectetur adipiscing elit, - sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. - Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris - nisi ut aliquip ex ea
commodo consequat. -

- -

- Duis aute irure dolor in - reprehenderit in voluptate velit esse cillum dolore eu fugiat - nulla pariatur.
- Excepteur sint occaecat cupidatat non proident, sunt in - culpa qui officia deserunt mollit anim id est laborum. -

' -} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml deleted file mode 100644 index 6fd6687..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group1.qml +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn {} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml deleted file mode 100644 index 04299dd..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group2.qml +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn { - - EaElements.Label { - text: "EaElements.Label" - } - - EaElements.TextInput { - text: 'EaElements.TextInput' - } - - EaElements.TextField { - text: 'EaElements.TextField' - } - -} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml deleted file mode 100644 index b32087f..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group3.qml +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn { - - EaElements.ComboBox { - model: ["EaElements.ComboBox 1", "EaElements.ComboBox 2", "EaElements.ComboBox 3"] - } - - Column { - EaElements.RadioButton { - checked: true - text: qsTr("EaElements.RadioButton 1") - } - EaElements.RadioButton { - text: qsTr("EaElements.RadioButton 2") - } - } - - Column { - spacing: 10 - EaElements.CheckBox { - checked: true - text: qsTr("EaElements.CheckBox 1") - } - EaElements.CheckBox { - text: qsTr("EaElements.CheckBox 2") - } - } - -} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml deleted file mode 100644 index 42e891f..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Groups/Group4.qml +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic - -import Gui.Globals as Globals - -EaElements.GroupColumn { - - - EaElements.SideBarButton { - fontIcon: 'plus-circle' - text: 'EaElements.SideBarButton' - } - - EaElements.Slider { - from: 1 - value: 25 - to: 100 - } - -} diff --git a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml deleted file mode 100644 index ad4c6ca..0000000 --- a/src/easyshapes_app/Gui/Pages/Toolbox/Sidebar/Basic/Layout.qml +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents - -import Gui.Globals as Globals - - -EaComponents.SideBarColumn { - - EaElements.GroupBox { - title: qsTr('Group 1: Empty') - icon: 'rocket' - - Loader { source: 'Groups/Group1.qml' } - } - - EaElements.GroupBox { - title: qsTr('Group 2: Label, TextInput, TextField') - icon: 'rocket' - collapsed: false - - Loader { source: 'Groups/Group2.qml' } - } - - EaElements.GroupBox { - title: qsTr('Group 3: ComboBox, RadioButton, CheckBox') - icon: 'rocket' - - Loader { source: 'Groups/Group3.qml' } - } - - EaElements.GroupBox { - title: qsTr('Group 4: SidebarButton, Slider') - icon: 'rocket' - - Loader { source: 'Groups/Group4.qml' } - } - -} From 675cb8b3b5591e4fde591415dc1a379a82b7056b Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Thu, 25 Jun 2026 15:37:39 +0200 Subject: [PATCH 24/32] gui makeover following the feedback from chalmers --- src/easyshapes_app.qrc | 17 +- src/easyshapes_app/Backends/MockBackend.qml | 14 +- .../Backends/MockQml/Buffer.qml | 52 +++ .../Backends/MockQml/Components.qml | 7 +- .../Backends/MockQml/ComponentsFiles.qml | 52 ++- src/easyshapes_app/Backends/MockQml/Ions.qml | 16 +- .../Backends/MockQml/LatticeStructure.qml | 53 +-- .../Backends/MockQml/LibraryAssetsEditor.qml | 193 ++++++++++ .../Backends/MockQml/PositionRestraints.qml | 93 ----- .../Backends/MockQml/SampleModel.qml | 54 ++- .../Backends/MockQml/SmilesGenerator.qml | 71 ++++ .../Backends/MockQml/Solution.qml | 116 ------ .../Backends/MockQml/Status.qml | 6 +- .../Backends/MockQml/StructureFiles.qml | 5 + src/easyshapes_app/Backends/MockQml/qmldir | 5 +- src/easyshapes_app/Backends/real_py/status.py | 72 +--- .../Gui/Globals/BackendWrapper.qml | 162 +++++--- src/easyshapes_app/Gui/Globals/References.qml | 1 - .../Gui/Pages/Analysis/Layout.qml | 10 +- .../Gui/Pages/Analysis/MainArea/Chart.qml | 4 +- .../Sidebar/Basic/Groups/AnalysisConfig.qml | 50 ++- .../Pages/Analysis/Sidebar/Basic/Layout.qml | 14 +- .../Basic/Popups/AddAnalysisConfigFiles.qml | 4 +- .../Gui/Pages/SampleModel/Layout.qml | 14 +- .../Pages/SampleModel/MainArea/GraphsView.qml | 6 +- .../Advanced/Groups/ComponentsFiles.qml | 174 +++++---- .../Advanced/Groups/LibraryAssetsFiles.qml | 361 ++++++++++++++++++ .../Advanced/Groups/PositionRestraints.qml | 100 ----- .../Advanced/Groups/SmilesGenerator.qml | 210 ++++++++++ .../Advanced/Groups/StructureFiles.qml | 57 ++- .../SampleModel/Sidebar/Advanced/Layout.qml | 25 +- .../Advanced/Popups/AddComponentFiles.qml | 8 +- .../Popups/LoadLibraryAsset.qml} | 51 +-- .../Advanced/Popups/ReplaceStructure.qml | 2 +- .../Sidebar/Basic/Components/Fractions.qml | 7 +- .../Sidebar/Basic/Groups/BallStructure.qml | 49 ++- .../Sidebar/Basic/Groups/BilayerStructure.qml | 21 +- .../Sidebar/Basic/Groups/Buffer.qml | 137 +++++++ .../Sidebar/Basic/Groups/Components.qml | 74 ++-- .../Sidebar/Basic/Groups/Lamellae.qml | 21 +- .../Sidebar/Basic/Groups/LatticeStructure.qml | 118 +----- .../Sidebar/Basic/Groups/Layers.qml | 24 +- .../Basic/Groups/MonolayerStructure.qml | 21 +- .../Sidebar/Basic/Groups/RingStructure.qml | 36 +- .../Sidebar/Basic/Groups/RodStructure.qml | 25 +- .../Sidebar/Basic/Groups/SampleModel.qml | 70 ++-- .../Sidebar/Basic/Groups/Solution.qml | 255 ------------- .../Sidebar/Basic/Groups/VesicleStructure.qml | 57 ++- .../SampleModel/Sidebar/Basic/Layout.qml | 98 +++-- .../Basic/Popups/CreateNewComponent.qml | 8 +- .../Sidebar/Basic/Popups/CreateNewModel.qml | 82 ---- .../Sidebar/Basic/Popups/CreateNewSolvent.qml | 118 ------ .../Basic/Popups/CreateNewSubstructure.qml | 119 ------ ...nt.qml => LoadExistingBufferComponent.qml} | 34 +- .../Basic/Popups/LoadExistingComponent.qml | 30 +- .../Basic/Popups/LoadExistingModel.qml | 9 + .../Sidebar/Basic/Popups/OpenAssetFile.qml | 2 +- src/easyshapes_app/Gui/StatusBar.qml | 58 +-- 58 files changed, 1794 insertions(+), 1758 deletions(-) create mode 100644 src/easyshapes_app/Backends/MockQml/Buffer.qml create mode 100644 src/easyshapes_app/Backends/MockQml/LibraryAssetsEditor.qml delete mode 100644 src/easyshapes_app/Backends/MockQml/PositionRestraints.qml create mode 100644 src/easyshapes_app/Backends/MockQml/SmilesGenerator.qml delete mode 100644 src/easyshapes_app/Backends/MockQml/Solution.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/LibraryAssetsFiles.qml delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/SmilesGenerator.qml rename src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/{Basic/Popups/LoadExistingSubstructure.qml => Advanced/Popups/LoadLibraryAsset.qml} (56%) create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Buffer.qml delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml delete mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml rename src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/{LoadExistingSolvent.qml => LoadExistingBufferComponent.qml} (69%) diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index a69b8b4..55c7cbc 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -115,6 +115,7 @@ easyshapes_app/Backends/MockQml/AnalysisConfig.qml easyshapes_app/Backends/MockQml/BallStructure.qml easyshapes_app/Backends/MockQml/BilayerStructure.qml + easyshapes_app/Backends/MockQml/Buffer.qml easyshapes_app/Backends/MockQml/Components.qml easyshapes_app/Backends/MockQml/ComponentsFiles.qml easyshapes_app/Backends/MockQml/Fractions.qml @@ -122,15 +123,15 @@ easyshapes_app/Backends/MockQml/Lamellae.qml easyshapes_app/Backends/MockQml/LatticeStructure.qml easyshapes_app/Backends/MockQml/Layers.qml + easyshapes_app/Backends/MockQml/LibraryAssetsEditor.qml easyshapes_app/Backends/MockQml/MonolayerStructure.qml - easyshapes_app/Backends/MockQml/PositionRestraints.qml easyshapes_app/Backends/MockQml/Project.qml easyshapes_app/Backends/MockQml/qmldir easyshapes_app/Backends/MockQml/Report.qml easyshapes_app/Backends/MockQml/RingStructure.qml easyshapes_app/Backends/MockQml/RodStructure.qml easyshapes_app/Backends/MockQml/SampleModel.qml - easyshapes_app/Backends/MockQml/Solution.qml + easyshapes_app/Backends/MockQml/SmilesGenerator.qml easyshapes_app/Backends/MockQml/Status.qml easyshapes_app/Backends/MockQml/StructureFiles.qml easyshapes_app/Backends/MockQml/VesicleStructure.qml @@ -167,14 +168,17 @@ easyshapes_app/Gui/Pages/SampleModel/Layout.qml easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/LibraryAssetsFiles.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/SmilesGenerator.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/LoadLibraryAsset.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Buffer.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml @@ -183,18 +187,13 @@ easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml + easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingBufferComponent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingIon.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml - easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml easyshapes_app/Gui/qmldir easyshapes_app/Gui/StatusBar.qml diff --git a/src/easyshapes_app/Backends/MockBackend.qml b/src/easyshapes_app/Backends/MockBackend.qml index 81ea03a..2d9c94a 100644 --- a/src/easyshapes_app/Backends/MockBackend.qml +++ b/src/easyshapes_app/Backends/MockBackend.qml @@ -16,8 +16,11 @@ QtObject { property var components: MockLogic.Components // Advanced sidebar (Sample Model page) domain singletons. property var componentsFiles: MockLogic.ComponentsFiles - property var positionRestraints: MockLogic.PositionRestraints property var structureFiles: MockLogic.StructureFiles + // Library Assets editor (Advanced sidebar) — draft asset create/load/save. + property var libraryAssets: MockLogic.LibraryAssetsEditor + // SMILES generator (Advanced sidebar) — molecule from a SMILES string. + property var smilesGenerator: MockLogic.SmilesGenerator // Default fractions set tied to the shared component list. // Used as the fallback for the Fractions sidebar component when no // per-row override is set. @@ -40,11 +43,11 @@ QtObject { property var bilayerStructure: MockLogic.BilayerStructure // Monolayer structure parameters (single record, edited inline). property var monolayerStructure: MockLogic.MonolayerStructure - // Lattice structure parameters (single record, edited inline) plus a - // single-record substructure with its own asset library. + // Lattice structure parameters (single record, edited inline). property var latticeStructure: MockLogic.LatticeStructure - // Solution group: solution (single-record, TIP3 default) + ions (max 2 rows). - property var solution: MockLogic.Solution + // Buffer group: solvent selection + buffer components (salts, buffering + // agents). Ions singleton stays (used by the Components C-ion picker). + property var buffer: MockLogic.Buffer property var ions: MockLogic.Ions property var analysis: MockLogic.Analysis // Analysis page — equilibration configuration (config files + force @@ -56,7 +59,6 @@ QtObject { Component.onCompleted: { layers.fractionsSource = MockLogic.Components.loaded lamellae.fractionsSource = MockLogic.Components.loaded - positionRestraints.source = MockLogic.Components.loaded } } diff --git a/src/easyshapes_app/Backends/MockQml/Buffer.qml b/src/easyshapes_app/Backends/MockQml/Buffer.qml new file mode 100644 index 0000000..cbe07c6 --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/Buffer.qml @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +QtObject { + + // Solvent — a single selected value. '(None)' means no solvent. + readonly property var solventOptions: ['(None)', 'TIP3', 'Ethanol'] + property string solvent: '(None)' + + // Loaded buffer components. ListModel so the ListView's ItemSelectionModel + // can drive multi-selection and role-based delegate bindings. + // Roles: name, concentration (in mM). dynamicRoles so an edited + // concentration keeps fractional values — a statically-typed role would + // inherit int from the first (whole-number) catalog value and truncate + // floats. + readonly property var components: ListModel { + id: bufferComponentsModel + dynamicRoles: true + } + + // Catalog of loadable buffer components (salts, buffering agents). Identity + // only — concentration is the user's per-experiment choice, set after load. + // Roles: name, description. + readonly property var available: ListModel { + ListElement { name: 'NaCl'; description: 'Sodium chloride — physiological background salt' } + ListElement { name: 'KCl'; description: 'Potassium chloride' } + ListElement { name: 'Tris'; description: 'Tris(hydroxymethyl)aminomethane buffer' } + ListElement { name: 'HEPES'; description: 'Zwitterionic biological buffer (pH 6.8–8.2)' } + ListElement { name: 'MgCl₂'; description: 'Magnesium chloride' } + ListElement { name: 'CaCl₂'; description: 'Calcium chloride' } + ListElement { name: 'Phosphate'; description: 'Sodium phosphate buffer (PBS)' } + ListElement { name: 'EDTA'; description: 'Chelating agent' } + } + + function appendComponent(item) { + bufferComponentsModel.append({ + name: item.name, + concentration: 0 + }) + } + + function removeComponent(index) { + if (index < 0 || index >= bufferComponentsModel.count) return + bufferComponentsModel.remove(index) + } +} diff --git a/src/easyshapes_app/Backends/MockQml/Components.qml b/src/easyshapes_app/Backends/MockQml/Components.qml index d75cfdb..e28ef64 100644 --- a/src/easyshapes_app/Backends/MockQml/Components.qml +++ b/src/easyshapes_app/Backends/MockQml/Components.qml @@ -12,7 +12,10 @@ QtObject { // ListModel (not JS array) so EaComponents.ListView's ItemSelectionModel // can drive Ctrl/Shift multi-selection, and so role-based delegate // properties (`required property string name` etc.) bind correctly. - // Roles: name, component_type, atoms, mint, mext. + // Roles: name, component_type, c_ion, mint, mext. + // c_ion is a counter-ion chosen per component from the shared ion + // library (Ions.available) — not owned here; this only stores the + // selected name (or '' when unset). readonly property var loaded: ListModel { id: loadedComponentsModel } @@ -27,7 +30,7 @@ QtObject { loadedComponentsModel.append({ name: item.name, component_type: item.component_type, - atoms: item.atoms, + c_ion: item.c_ion !== undefined ? item.c_ion : '', mint: item.mint, mext: item.mext }) diff --git a/src/easyshapes_app/Backends/MockQml/ComponentsFiles.qml b/src/easyshapes_app/Backends/MockQml/ComponentsFiles.qml index 65f3b06..dd8bc42 100644 --- a/src/easyshapes_app/Backends/MockQml/ComponentsFiles.qml +++ b/src/easyshapes_app/Backends/MockQml/ComponentsFiles.qml @@ -24,6 +24,11 @@ QtObject { // Empty string means no selection — `files` is then empty. property string selectedComponent: '' + // Editable name shown in the name field. Tracks `selectedComponent` when a + // component is picked from the dropdown, is emptied by `createNew()`, and is + // the name the file list is persisted under by `save()`. + property string editName: '' + // Internal map: componentName -> JS array of file paths. Mutations go // through __syncBack() so the map stays consistent with the ListModel. property var filesByComponent: ({ @@ -47,8 +52,8 @@ QtObject { } function selectComponent(name) { - if (selectedComponent === name) return selectedComponent = name + editName = name filesModel.clear() const list = filesByComponent[name] || [] for (let i = 0; i < list.length; ++i) { @@ -56,16 +61,29 @@ QtObject { } } + // Start a fresh, unsaved component: clear the dropdown selection, the name + // field and the staged file list. + function createNew() { + selectedComponent = '' + editName = '' + filesModel.clear() + } + function appendFile(path) { - if (selectedComponent === '') return filesModel.append({ path: path }) - __syncBack() + if (selectedComponent !== '') __syncBack() } function removeFile(index) { if (index < 0 || index >= filesModel.count) return filesModel.remove(index) - __syncBack() + if (selectedComponent !== '') __syncBack() + } + + // Mock placeholder — real backend will open the file in an editor. + function editFile(index) { + if (index < 0 || index >= filesModel.count) return + console.debug('ComponentsFiles.editFile:', filesModel.get(index).path) } // Export the whole selected component (all its files). Mock backend @@ -76,14 +94,26 @@ QtObject { JSON.stringify(filesByComponent[selectedComponent] || [])) } - // Persist the current file list for the selected component. Mock just - // mirrors filesModel back into filesByComponent — already kept in sync, - // so this is a no-op for the mock other than the debug print. + // Persist the current file list to the asset library under `editName`. + // For a freshly created component (`selectedComponent === ''`) this stores + // the staged files under the new name and adopts it as the selection. + // Renaming an existing component leaves the old map entry untouched (mock + // only). Returns the saved name so the caller can mirror it into the + // basic-tab components list. function save() { - if (selectedComponent === '') return - __syncBack() - console.debug('ComponentsFiles.save:', selectedComponent, - JSON.stringify(filesByComponent[selectedComponent])) + const name = (editName || '').trim() + if (name === '') return '' + const arr = [] + for (let i = 0; i < filesModel.count; ++i) { + arr.push(filesModel.get(i).path) + } + const copy = Object.assign({}, filesByComponent) + copy[name] = arr + filesByComponent = copy + selectedComponent = name + editName = name + console.debug('ComponentsFiles.save:', name, JSON.stringify(arr)) + return name } function __syncBack() { diff --git a/src/easyshapes_app/Backends/MockQml/Ions.qml b/src/easyshapes_app/Backends/MockQml/Ions.qml index 73ed1c3..19383d1 100644 --- a/src/easyshapes_app/Backends/MockQml/Ions.qml +++ b/src/easyshapes_app/Backends/MockQml/Ions.qml @@ -15,13 +15,17 @@ QtObject { id: loadedIonsModel } + // Charge rendered as superscript via markup — EaElements labels + // (AutoText) and the ComboBox (RichText) render it; the markup is part of + // the stored name, so selections round-trip unchanged. readonly property var available: ListModel { - ListElement { name: 'NA+' } - ListElement { name: 'CL-' } - ListElement { name: 'K+' } - ListElement { name: 'CA2+' } - ListElement { name: 'MG2+' } - ListElement { name: 'ZN2+' } + ListElement { name: 'Na+' } + ListElement { name: 'Cl-' } + ListElement { name: 'Br-' } + ListElement { name: 'K+' } + ListElement { name: 'Ca2+' } + ListElement { name: 'Mg2+' } + ListElement { name: 'Zn2+' } } // Hard cap at the backend layer. The button row also disables itself, diff --git a/src/easyshapes_app/Backends/MockQml/LatticeStructure.qml b/src/easyshapes_app/Backends/MockQml/LatticeStructure.qml index c274952..c7e2f3d 100644 --- a/src/easyshapes_app/Backends/MockQml/LatticeStructure.qml +++ b/src/easyshapes_app/Backends/MockQml/LatticeStructure.qml @@ -11,59 +11,10 @@ QtObject { property double alpha: 0.0 property double theta: 0.0 property double sbuff: 1.0 - property string latticeType: 'LAT1' + property string latticeType: 'LCUB' property int nlatx: 1 property int nlaty: 1 property int nlatz: 1 - readonly property var latticeTypes: ['LAT1', 'LAT2', 'LAT3'] - - readonly property var substructureTypes: [ - 'Ring', 'Ball', 'Vesicle', 'Rod', 'Bilayer', 'Monolayer' - ] - - // Loaded substructure — JS array of length 0 or 1, mirroring the - // SampleModel `loaded` shape so EaComponents.ListView can consume it. - // Element shape: { name, structure_type, description, file_path }. - property var substructureLoaded: [] - - readonly property var availableSubstructures: ListModel { - ListElement { name: 'CubicSeed'; structure_type: 'Ball'; description: 'Spherical seed for cubic packing' } - ListElement { name: 'HexRing'; structure_type: 'Ring'; description: 'Ring substructure for hexagonal lattice' } - ListElement { name: 'BilayerTile'; structure_type: 'Bilayer'; description: 'Planar tile for stacked lattices' } - ListElement { name: 'RodSeed'; structure_type: 'Rod'; description: 'Rod-shaped seed' } - ListElement { name: 'VesicleSeed'; structure_type: 'Vesicle'; description: 'Vesicle seed for hollow lattices' } - } - - function setSubstructureLoaded(item) { - substructureLoaded = [{ - name: item.name, - structure_type: item.structure_type, - description: item.description, - file_path: item.file_path !== undefined ? item.file_path : '' - }] - } - - function updateSubstructureField(field, value) { - if (substructureLoaded.length === 0) return - substructureLoaded[0][field] = value - } - - function clearSubstructure() { - substructureLoaded = [] - } - - function saveSubstructureToCatalog() { - if (substructureLoaded.length === 0 || !substructureLoaded[0].name) return - availableSubstructures.append({ - name: substructureLoaded[0].name, - structure_type: substructureLoaded[0].structure_type, - description: substructureLoaded[0].description - }) - } - - function removeSubstructureFromCatalog(index) { - if (index < 0 || index >= availableSubstructures.count) return - availableSubstructures.remove(index) - } + readonly property var latticeTypes: ['LCUB', 'LBCC', 'LFCC', 'LHCP'] } diff --git a/src/easyshapes_app/Backends/MockQml/LibraryAssetsEditor.qml b/src/easyshapes_app/Backends/MockQml/LibraryAssetsEditor.qml new file mode 100644 index 0000000..526033b --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/LibraryAssetsEditor.qml @@ -0,0 +1,193 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +// Backend for the "Library assets" editor (Advanced sidebar). Holds a single +// draft the user either creates from scratch or loads from the library, edits, +// and saves back. Most types are file-bearing assets (Lipid/Surfactant/ +// Component/Ion/Solvent). 'Salt' is the odd one out: it is NOT an asset but a +// named collection of ion assets with stoichiometric counts, so for that type +// the file list is replaced by a two-ion composition. +// +// Instantiation of the typed asset is deferred to save() — while editing we +// keep plain draft fields, so switching the type dropdown never leaves a +// half-built typed object. +QtObject { + id: root + + // Editor mode: + // 'create' — new draft; type dropdown editable. This is the default, so + // the editor opens ready to compose a new asset. + // 'edit' — loaded/saved; type locked (storage is keyed on type). + // 'empty' — reserved; not entered by default. + property string mode: 'create' + + // Type options shown in the dropdown. 'Component (Other)' maps to the + // Component class; 'Salt' is a collection, not a class. + readonly property var typeOptions: ['Lipid', 'Surfactant', 'Component (Other)', 'Ion', 'Solvent', 'Salt'] + + // Draft fields. assetType is a display label from typeOptions. The + // component-only fields (cIon/mint/mext) stay at their defaults for the + // Ion/Solvent/Salt types, where the UI hides them. Defaults to a molecule + // so the editor opens on a typical new-asset form. + property string assetType: 'Lipid' + property string assetName: '' + property string cIon: '' + property int mint: 0 + property int mext: 0 + + // Draft file list (composition of a file-bearing asset). Roles: path. + readonly property var paths: ListModel { id: pathsModel } + + // Draft salt composition (used only when assetType === 'Salt'). Two fixed + // ion slots. Roles: ion (library ion name, or '' when unset), count (>=1). + readonly property var saltComposition: ListModel { + id: saltCompositionModel + ListElement { ion: ''; count: 1 } + ListElement { ion: ''; count: 1 } + } + + // True when a Salt draft can be saved: a name plus both ion slots filled. + property bool saltReady: false + + // Mock asset library browsed by the Load-from-lib popup. A mix of kinds so + // the browser shows more than components. Roles uniform across rows so the + // statically-typed ListModel keeps every column: file-bearing assets leave + // the ion0/ion1 slots blank; salts leave c_ion/mint/mext at defaults. + // Saving upserts here by (type, name). + readonly property var library: ListModel { + id: libraryModel + ListElement { name: 'DPPC'; type: 'Lipid'; c_ion: ''; mint: 0; mext: 130; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'DOPC'; type: 'Lipid'; c_ion: ''; mint: 0; mext: 138; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'POPC'; type: 'Lipid'; c_ion: ''; mint: 0; mext: 134; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'Cholesterol'; type: 'Lipid'; c_ion: ''; mint: 0; mext: 74; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'SDS'; type: 'Surfactant'; c_ion: 'Na+'; mint: 0; mext: 42; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'CTAB'; type: 'Surfactant'; c_ion: 'Br-'; mint: 0; mext: 62; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'Triton-X100'; type: 'Surfactant'; c_ion: ''; mint: 0; mext: 85; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'D2O-buffer'; type: 'Component (Other)'; c_ion: ''; mint: 0; mext: 3; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'Na'; type: 'Ion'; c_ion: ''; mint: 0; mext: 0; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'Cl'; type: 'Ion'; c_ion: ''; mint: 0; mext: 0; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'TIP3'; type: 'Solvent'; c_ion: ''; mint: 0; mext: 0; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'Ethanol'; type: 'Solvent'; c_ion: ''; mint: 0; mext: 0; ion0: ''; count0: 0; ion1: ''; count1: 0 } + ListElement { name: 'NaCl'; type: 'Salt'; c_ion: ''; mint: 0; mext: 0; ion0: 'Na+'; count0: 1; ion1: 'Cl-'; count1: 1 } + ListElement { name: 'CaCl2'; type: 'Salt'; c_ion: ''; mint: 0; mext: 0; ion0: 'Ca2+'; count0: 1; ion1: 'Cl-'; count1: 2 } + } + + onAssetNameChanged: _recomputeSalt() + + // Start a fresh, unsaved draft. Type is editable; default to a molecule so + // the component fields are visible to begin with. + function createNew() { + mode = 'create' + assetType = 'Lipid' + assetName = '' + cIon = '' + mint = 0 + mext = 0 + pathsModel.clear() + _clearSalt() + } + + // Adopt a library record into the draft for editing. Type is locked. + function loadAsset(item) { + mode = 'edit' + assetType = item.type + assetName = item.name + cIon = item.c_ion !== undefined ? item.c_ion : '' + mint = item.mint !== undefined ? item.mint : 0 + mext = item.mext !== undefined ? item.mext : 0 + pathsModel.clear() + if (item.type === 'Salt') { + saltCompositionModel.setProperty(0, 'ion', item.ion0) + saltCompositionModel.setProperty(0, 'count', item.count0) + saltCompositionModel.setProperty(1, 'ion', item.ion1) + saltCompositionModel.setProperty(1, 'count', item.count1) + } else { + _clearSalt() + // Mock: stand-in files for the loaded asset. + pathsModel.append({ path: 'assets/' + item.name + '.itp' }) + pathsModel.append({ path: 'assets/' + item.name + '.gro' }) + } + _recomputeSalt() + } + + function appendPath(path) { + pathsModel.append({ path: path }) + } + + function removePath(index) { + if (index < 0 || index >= pathsModel.count) return + pathsModel.remove(index) + } + + function editPath(index) { + if (index < 0 || index >= pathsModel.count) return + console.debug('LibraryAssetsEditor.editPath:', pathsModel.get(index).path) + } + + function setSaltIon(index, value) { + if (index < 0 || index >= saltCompositionModel.count) return + saltCompositionModel.setProperty(index, 'ion', value) + _recomputeSalt() + } + + function setSaltCount(index, value) { + if (index < 0 || index >= saltCompositionModel.count) return + saltCompositionModel.setProperty(index, 'count', value) + } + + // Instantiate (conceptually) the typed asset / salt from the draft and + // persist it to the library. Mock upserts by (type, name) and flips to edit + // mode so the type locks. Returns false if the draft is incomplete. + function save() { + const name = (assetName || '').trim() + if (name === '') return false + let record + if (assetType === 'Salt') { + if (!saltReady) return false + const r0 = saltCompositionModel.get(0) + const r1 = saltCompositionModel.get(1) + record = { name: name, type: assetType, c_ion: '', mint: 0, mext: 0, + ion0: r0.ion, count0: r0.count, ion1: r1.ion, count1: r1.count } + } else { + if (pathsModel.count === 0) return false + record = { name: name, type: assetType, c_ion: cIon, mint: mint, mext: mext, + ion0: '', count0: 0, ion1: '', count1: 0 } + } + for (let i = 0; i < libraryModel.count; ++i) { + const r = libraryModel.get(i) + if (r.type === assetType && r.name === name) { + libraryModel.set(i, record) + mode = 'edit' + console.debug('LibraryAssetsEditor.save (overwrite):', assetType, name) + return true + } + } + libraryModel.append(record) + mode = 'edit' + console.debug('LibraryAssetsEditor.save (new):', assetType, name) + return true + } + + function exportAsset() { + console.debug('LibraryAssetsEditor.exportAsset:', assetType, assetName) + } + + function _clearSalt() { + saltCompositionModel.setProperty(0, 'ion', '') + saltCompositionModel.setProperty(0, 'count', 1) + saltCompositionModel.setProperty(1, 'ion', '') + saltCompositionModel.setProperty(1, 'count', 1) + } + + function _recomputeSalt() { + const r0 = saltCompositionModel.get(0) + const r1 = saltCompositionModel.get(1) + saltReady = (assetName || '').trim() !== '' && !!r0.ion && !!r1.ion + } +} diff --git a/src/easyshapes_app/Backends/MockQml/PositionRestraints.qml b/src/easyshapes_app/Backends/MockQml/PositionRestraints.qml deleted file mode 100644 index 0ea67aa..0000000 --- a/src/easyshapes_app/Backends/MockQml/PositionRestraints.qml +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -pragma Singleton - -import QtQuick - - -// Pattern D backend (multi-row list with selection) for position restraints -// shown in the Advanced sidebar of the Sample Model page. -// -// Rows are derived from the shared Components list: one restraint row per -// loaded component, with the component name mirrored read-only and the -// atom / p_form / geom / radius / f_constant fields owned here. Sync is -// surgical so editing one component doesn't reset the others' state. -// -// Roles: component_name, atom, p_form, geom, radius, f_constant. -QtObject { - id: root - - // Shared component list this mirrors. Wired by MockBackend at startup; - // kept as a property (rather than a hard import) to side-step singleton - // init-order quirks — same approach as Layers/Lamellae fractionsSource. - property var source: null - - readonly property var items: ListModel { id: itemsModel } - - onSourceChanged: rebuild() - - property var __sync: Connections { - target: root.source - - function onRowsInserted(parent, first, last) { - for (let i = first; i <= last; ++i) { - const c = root.source.get(i) - itemsModel.insert(i, { - component_name: c.name, - atom: 1, - p_form: 1, - geom: 1, - radius: 0.5, - f_constant: 'POSRES_FC_LIP' - }) - } - } - function onRowsRemoved(parent, first, last) { - for (let i = last; i >= first; --i) itemsModel.remove(i) - } - function onDataChanged(topLeft, bottomRight, roles) { - for (let i = topLeft.row; i <= bottomRight.row; ++i) { - itemsModel.setProperty(i, 'component_name', root.source.get(i).name) - } - } - function onModelReset() { root.rebuild() } - } - - function rebuild() { - itemsModel.clear() - if (!source) return - for (let i = 0; i < source.count; ++i) { - const c = source.get(i) - itemsModel.append({ - component_name: c.name, - atom: 1, - p_form: 1, - geom: 1, - radius: 0.5, - f_constant: 'POSRES_FC_LIP' - }) - } - } - - function setAtom(index, value) { - if (index < 0 || index >= itemsModel.count) return - itemsModel.setProperty(index, 'atom', value) - } - - function setPForm(index, value) { - if (index < 0 || index >= itemsModel.count) return - itemsModel.setProperty(index, 'p_form', value) - } - - function setGeom(index, value) { - if (index < 0 || index >= itemsModel.count) return - itemsModel.setProperty(index, 'geom', value) - } - - function setRadius(index, value) { - if (index < 0 || index >= itemsModel.count) return - itemsModel.setProperty(index, 'radius', value) - } -} diff --git a/src/easyshapes_app/Backends/MockQml/SampleModel.qml b/src/easyshapes_app/Backends/MockQml/SampleModel.qml index 7fef0d2..95669e4 100644 --- a/src/easyshapes_app/Backends/MockQml/SampleModel.qml +++ b/src/easyshapes_app/Backends/MockQml/SampleModel.qml @@ -8,41 +8,59 @@ import QtQuick QtObject { - property bool created: false + property bool created: true // Loaded sample model — JS array of length 0 or 1. // Capacity is one by convention (single record), but the value is // exposed as an array so QML ListView can consume it directly // (Qt 6 ListView only supports int / ListModel / QAbstractItemModel / JS array). - // Element shape: { name, structure_type, description } - property var loaded: [] + // Element shape: { name, structure_type, type, description }. + // Preloaded with an empty default model (no name, Ring shape, Discrete + // type) so the user always starts from an editable scaffold. + property var loaded: [{ + name: '', + structure_type: 'Ring', + type: 'Discrete', + description: '' + }] + // Geometric shape family (the "Shape" column). Lattice is no longer a + // shape — it moved to `type` below. readonly property var structureTypes: [ - 'Ring', 'Ball', 'Vesicle', 'Rod', 'Bilayer', 'Monolayer', 'Lattice' + 'Ring', 'Ball', 'Vesicle', 'Rod', 'Bilayer', 'Monolayer' ] + // Arrangement of the shape (the "Type" column). 'Lattice' drives the + // Lattice Parameters group's visibility (decoupled from the shape). + readonly property var modelTypes: ['Discrete', 'Lattice'] + // Externally-observed structure type. Decoupled from `loaded` because // updateField mutates `loaded[0]` in place (preserving the row delegate // and any focused TextInput) and that mutation does NOT fire loadedChanged. // The wrapper writes this property whenever structure_type is updated; // setLoaded/clear keep it in sync with the active record. - property string currentStructureType: '' + property string currentStructureType: 'Ring' + + // Externally-observed model type (Discrete/Lattice). Mirrored for the + // same reason as currentStructureType — drives Lattice Parameters + // visibility in Layout.qml. + property string currentType: 'Discrete' // Catalog of saveable/loadable models (asset library). // ListModel mirrors the Python QAbstractListModel with roles // (name, structure_type, description) so delegate code is identical // for both real and mock backends, and ItemSelectionModel works. readonly property var availableModels: ListModel { - ListElement { name: 'Samle1_aluv'; structure_type: 'Vesicle'; description: 'In order to avoid a prolonged pro-inflammatory neutrophil response, signaling downstream of an agonist-activated G protein-coupled receptor (GPCR) has to be rapidly terminated. Among the family of GPCR kinases (GRKs) that regulate receptor phosphorylation and signaling termination, GRK2, which is highly expressed by immune cells, plays an important role.' } - ListElement { name: 'Sample2_nanodisc'; structure_type: 'Ring'; description: 'The medium chain fatty acid receptor GPR84 as well as formyl peptide receptor 2 (FPR2)' } - ListElement { name: 'Sample3_cubosome'; structure_type: 'Lattice'; description: 'receptors expressed in neutrophils, play a key role in regulating inflammation. In this study, we investigated the effects of GRK2 inhibitors on neutrophil functions induced by GPR84 and FPR2 agonists.' } - ListElement { name: 'Sample4'; structure_type: 'Ring'; description: 'GRK2 was shown to be expressed in human neutrophils and analysis of subcellular fractions' } - ListElement { name: 'Sample5'; structure_type: 'Ball'; description: 'revealed a cytosolic localization. The GRK2 inhibitors enhanced and prolonged neutrophil production ' } - ListElement { name: 'Sample6'; structure_type: 'Vesicle'; description: 'production of reactive oxygen species (ROS) induced by GPR84- but not FPR2-agonists' } - ListElement { name: 'Sample7'; structure_type: 'Rod'; description: 'suggesting a receptor selective function of GRK2. This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84' } - ListElement { name: 'Sample8'; structure_type: 'Bilayer'; description: 'This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84 agonist was not affected by the GRK2 inhibitor.' } - ListElement { name: 'Sample9'; structure_type: 'Monolayer'; description: 'Termination of this β-arrestin independent response relied, similar to the response induced by FPR2 agonists, primarily on the actin cytoskeleton.' } - ListElement { name: 'Samplewithareallylongname'; structure_type: 'Lattice'; description: 'In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils.' } + ListElement { name: 'Samle1_aluv'; structure_type: 'Vesicle'; type: 'Discrete'; description: 'In order to avoid a prolonged pro-inflammatory neutrophil response, signaling downstream of an agonist-activated G protein-coupled receptor (GPCR) has to be rapidly terminated. Among the family of GPCR kinases (GRKs) that regulate receptor phosphorylation and signaling termination, GRK2, which is highly expressed by immune cells, plays an important role.' } + ListElement { name: 'Sample2_nanodisc'; structure_type: 'Ring'; type: 'Discrete'; description: 'The medium chain fatty acid receptor GPR84 as well as formyl peptide receptor 2 (FPR2)' } + ListElement { name: 'Sample3_cubosome'; structure_type: 'Ball'; type: 'Lattice'; description: 'receptors expressed in neutrophils, play a key role in regulating inflammation. In this study, we investigated the effects of GRK2 inhibitors on neutrophil functions induced by GPR84 and FPR2 agonists.' } + ListElement { name: 'Sample4'; structure_type: 'Ring'; type: 'Discrete'; description: 'GRK2 was shown to be expressed in human neutrophils and analysis of subcellular fractions' } + ListElement { name: 'Sample5'; structure_type: 'Ball'; type: 'Discrete'; description: 'revealed a cytosolic localization. The GRK2 inhibitors enhanced and prolonged neutrophil production ' } + ListElement { name: 'Sample6'; structure_type: 'Vesicle'; type: 'Discrete'; description: 'production of reactive oxygen species (ROS) induced by GPR84- but not FPR2-agonists' } + ListElement { name: 'Sample7'; structure_type: 'Rod'; type: 'Discrete'; description: 'suggesting a receptor selective function of GRK2. This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84' } + ListElement { name: 'Sample8'; structure_type: 'Bilayer'; type: 'Discrete'; description: 'This suggestion was supported by β-arrestin recruitment data. The ROS production induced by a non β-arrestin recruiting GPR84 agonist was not affected by the GRK2 inhibitor.' } + ListElement { name: 'Sample9'; structure_type: 'Monolayer'; type: 'Discrete'; description: 'Termination of this β-arrestin independent response relied, similar to the response induced by FPR2 agonists, primarily on the actin cytoskeleton.' } + ListElement { name: 'Samplewithareallylongname'; structure_type: 'Rod'; type: 'Lattice'; description: 'In summary, we show that GPR84 utilizes GRK2 in concert with β-arrestin and actin cytoskeleton dependent processes to fine-tune the activity of the ROS generating NADPH-oxidase in neutrophils.' } } function setLoaded(model) { @@ -50,9 +68,11 @@ QtObject { loaded = [{ name: model.name, structure_type: model.structure_type, + type: model.type !== undefined ? model.type : 'Discrete', description: model.description }] - currentStructureType = model.structure_type + currentStructureType = loaded[0].structure_type + currentType = loaded[0].type created = true } @@ -64,6 +84,7 @@ QtObject { function clear() { loaded = [] currentStructureType = '' + currentType = '' created = false } @@ -72,6 +93,7 @@ QtObject { availableModels.append({ name: loaded[0].name, structure_type: loaded[0].structure_type, + type: loaded[0].type, description: loaded[0].description }) console.debug(`Saved sample model '${loaded[0].name}' to catalog`) diff --git a/src/easyshapes_app/Backends/MockQml/SmilesGenerator.qml b/src/easyshapes_app/Backends/MockQml/SmilesGenerator.qml new file mode 100644 index 0000000..43b91fb --- /dev/null +++ b/src/easyshapes_app/Backends/MockQml/SmilesGenerator.qml @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +pragma Singleton + +import QtQuick + + +// Backend for the "SMILES generator" (Advanced sidebar). Mirrors the shapespyer +// `smiles` tool: a molecule is described by a chemical formula, a short name and +// a SMILES string (hydrogens are added automatically), and is generated into a +// 3D configuration inside a simulation box. The output is a structure file +// (.gro / .pdb) that can feed a component's files. +// +// A .sml record looks like: C12SO4 SDS CCCCCCCCCCCCOS(=O)(=O)[O-] +// followed by a trailing Box Lx Ly Lz. +QtObject { + id: root + + // Molecule identity. `formula` is the chemical formula used as the record + // key in a .sml file (e.g. C12SO4); `moleculeName` is the short name (SDS). + property string moleculeName: '' + property string formula: '' + + // The SMILES string itself. + property string smiles: '' + + // Simulation box edge lengths in nm. + property real boxX: 1.0 + property real boxY: 1.0 + property real boxZ: 1.0 + + // Output configuration format. + readonly property var formatOptions: ['GROMACS (.gro)', 'PDB (.pdb)'] + property string format: 'GROMACS (.gro)' + + // Component type assigned to the component created on Generate. Display + // labels; 'Component (Other)' maps to the stored 'Other'. Defaults to the + // generic 'Component (Other)'. + readonly property var componentTypeOptions: ['Lipid', 'Surfactant', 'Component (Other)'] + property string componentType: 'Component (Other)' + + // Generation flags (mirror the shapespyer getMolecule arguments): + // cisDoubleBonds → dbcis = ['C'] (kink C=C double bonds to cis). + // alignZ → align the backbone to the Z axis. + // flatXZ → flatten into the XZ plane; only acts when alignZ is on. + property bool cisDoubleBonds: false + property bool alignZ: false + property bool flatXZ: false + + // True once a name and a SMILES string are provided. + property bool ready: false + + onMoleculeNameChanged: _recompute() + onSmilesChanged: _recompute() + + function generate() { + if (!ready) return false + console.debug('SmilesGenerator.generate:', + 'formula=' + formula, 'name=' + moleculeName, + 'smiles=' + smiles, + 'box=[' + boxX + ',' + boxY + ',' + boxZ + ']', + 'format=' + format) + return true + } + + function _recompute() { + ready = (moleculeName || '').trim() !== '' && (smiles || '').trim() !== '' + } +} diff --git a/src/easyshapes_app/Backends/MockQml/Solution.qml b/src/easyshapes_app/Backends/MockQml/Solution.qml deleted file mode 100644 index 940f661..0000000 --- a/src/easyshapes_app/Backends/MockQml/Solution.qml +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -pragma Singleton - -import QtQuick - - -QtObject { - - readonly property var solutionTypes: ['other', 'water_3site', 'water_4site', 'water_5site', 'protic_polar', 'halogenated', 'denaturant'] - - property var loaded: [{ - name: 'TIP3P', - solvent_type: 'water_3site', - description: '3-site model standard with AMBER and CHARMM. Uses spc216.gro coordinates.', - file_path: '' - }] - - readonly property var available: ListModel { - id: solventModel - - ListElement { - name: "SPC" - solvent_type: "water_3site" - description: "Simple Point Charge water. Use spc216.gro with -ci. Standard for GROMOS force fields." - } - ListElement { - name: "SPC/E" - solvent_type: "water_3site" - description: "Extended SPC with self-polarization correction. Uses spc216.gro coordinates." - } - ListElement { - name: "TIP3P" - solvent_type: "water_3site" - description: "3-site model standard with AMBER and CHARMM. Uses spc216.gro coordinates." - } - ListElement { - name: "TIP4P" - solvent_type: "water_4site" - description: "4-site water with virtual site. Use tip4p.gro." - } - ListElement { - name: "TIP4P-Ew" - solvent_type: "water_4site" - description: "TIP4P optimized for Ewald summation. Available in AMBER ports." - } - ListElement { - name: "TIP4P/2005" - solvent_type: "water_4site" - description: "Reparameterized TIP4P with excellent bulk properties. Shipped with recent GROMACS." - } - ListElement { - name: "TIP5P" - solvent_type: "water_5site" - description: "5-site model with lone-pair virtual sites. Use tip5p.gro." - } - ListElement { - name: "TIP5PE" - solvent_type: "water_5site" - description: "TIP5P variant for Ewald electrostatics." - } - - ListElement { - name: "Methanol" - solvent_type: "protic_polar" - description: "CH3OH. Equilibrated box methanol.gro shipped with GROMACS (OPLS-AA)." - } - ListElement { - name: "1,2-Dichloroethane" - solvent_type: "halogenated" - description: "ClCH2CH2Ch. Equilibrated box ClCH2CH2Ch.gro included for OPLS-AA." - } - - ListElement { - name: "Urea" - solvent_type: "denaturant" - description: "Equilibrated urea box urea.gro shipped with GROMACS for cosolvent simulations." - } - } - - function setLoaded(item) { - loaded = [{ - name: item.name, - solvent_type: item.solvent_type, - description: item.description, - file_path: item.file_path !== undefined ? item.file_path : '' - }] - } - - // Mutate loaded[0] in place — same trick as SampleModel.updateField — so - // the ListView delegate and any focused TextInput survive the edit. - function updateField(field, value) { - if (loaded.length === 0) return - loaded[0][field] = value - } - - function clear() { - loaded = [] - } - - function saveToCatalog() { - if (loaded.length === 0 || !loaded[0].name) return - available.append({ - name: loaded[0].name, - solvent_type: loaded[0].solvent_type, - description: loaded[0].description - }) - } - - function removeFromCatalog(index) { - if (index < 0 || index >= available.count) return - available.remove(index) - } -} diff --git a/src/easyshapes_app/Backends/MockQml/Status.qml b/src/easyshapes_app/Backends/MockQml/Status.qml index 452ca9e..06ecfc2 100644 --- a/src/easyshapes_app/Backends/MockQml/Status.qml +++ b/src/easyshapes_app/Backends/MockQml/Status.qml @@ -9,10 +9,6 @@ import QtQuick QtObject { readonly property string project: 'Undefined' - readonly property string phasesCount: '1' - readonly property string experimentsCount: '1' - readonly property string calculator: 'CrysPy' - readonly property string minimizer: 'Lmfit (leastsq)' - readonly property string variables: '31 (3 free, 28 fixed)' + readonly property string engine: 'GROMACS' } diff --git a/src/easyshapes_app/Backends/MockQml/StructureFiles.qml b/src/easyshapes_app/Backends/MockQml/StructureFiles.qml index 61838eb..9335585 100644 --- a/src/easyshapes_app/Backends/MockQml/StructureFiles.qml +++ b/src/easyshapes_app/Backends/MockQml/StructureFiles.qml @@ -40,4 +40,9 @@ QtObject { function saveToLib() { console.debug('StructureFiles.saveToLib: paths=' + filesModel.count) } + + // Mock placeholder — real backend will write the files to disk. + function exportFiles() { + console.debug('StructureFiles.exportFiles: paths=' + filesModel.count) + } } diff --git a/src/easyshapes_app/Backends/MockQml/qmldir b/src/easyshapes_app/Backends/MockQml/qmldir index 580dcd6..6802108 100644 --- a/src/easyshapes_app/Backends/MockQml/qmldir +++ b/src/easyshapes_app/Backends/MockQml/qmldir @@ -4,8 +4,9 @@ singleton Project Project.qml singleton SampleModel SampleModel.qml singleton Components Components.qml singleton ComponentsFiles ComponentsFiles.qml -singleton PositionRestraints PositionRestraints.qml singleton StructureFiles StructureFiles.qml +singleton LibraryAssetsEditor LibraryAssetsEditor.qml +singleton SmilesGenerator SmilesGenerator.qml Fractions Fractions.qml singleton Layers Layers.qml singleton Lamellae Lamellae.qml @@ -16,7 +17,7 @@ singleton RodStructure RodStructure.qml singleton BilayerStructure BilayerStructure.qml singleton MonolayerStructure MonolayerStructure.qml singleton LatticeStructure LatticeStructure.qml -singleton Solution Solution.qml +singleton Buffer Buffer.qml singleton Ions Ions.qml singleton Analysis Analysis.qml singleton AnalysisConfig AnalysisConfig.qml diff --git a/src/easyshapes_app/Backends/real_py/status.py b/src/easyshapes_app/Backends/real_py/status.py index f202f51..4aadfbd 100644 --- a/src/easyshapes_app/Backends/real_py/status.py +++ b/src/easyshapes_app/Backends/real_py/status.py @@ -7,20 +7,12 @@ class Status(QObject): projectChanged = Signal() - phasesCountChanged = Signal() - experimentsCountChanged = Signal() - calculatorChanged = Signal() - minimizerChanged = Signal() - variablesChanged = Signal() + engineChanged = Signal() def __init__(self): super().__init__() self._project = 'Undefined' - self._phasesCount = '1' - self._experimentsCount = '1' - self._calculator = 'CrysPy' - self._minimizer = 'Lmfit (leastsq)' - self._variables = '31 (3 free, 28 fixed)' + self._engine = 'GROMACS' ########################## # GUI accessible variables @@ -37,57 +29,13 @@ def project(self, new_value): self._project = new_value self.projectChanged.emit() - @Property(str, notify=phasesCountChanged) - def phasesCount(self): - return self._phasesCount + @Property(str, notify=engineChanged) + def engine(self): + return self._engine - @phasesCount.setter - def phasesCount(self, new_value): - if self._phasesCount == new_value: + @engine.setter + def engine(self, new_value): + if self._engine == new_value: return - self._phasesCount = new_value - self.phasesCountChanged.emit() - - @Property(str, notify=experimentsCountChanged) - def experimentsCount(self): - return self._experimentsCount - - @experimentsCount.setter - def experimentsCount(self, new_value): - if self._experimentsCount == new_value: - return - self._experimentsCount = new_value - self.experimentsCountChanged.emit() - - @Property(str, notify=calculatorChanged) - def calculator(self): - return self._calculator - - @calculator.setter - def calculator(self, new_value): - if self._calculator == new_value: - return - self._calculator = new_value - self.calculatorChanged.emit() - - @Property(str, notify=minimizerChanged) - def minimizer(self): - return self._minimizer - - @minimizer.setter - def minimizer(self, new_value): - if self._minimizer == new_value: - return - self._minimizer = new_value - self.minimizerChanged.emit() - - @Property(str, notify=variablesChanged) - def variables(self): - return self._variables - - @variables.setter - def variables(self, new_value): - if self._variables == new_value: - return - self._variables = new_value - self.variablesChanged.emit() + self._engine = new_value + self.engineChanged.emit() diff --git a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml index bdcfba1..400377e 100644 --- a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml +++ b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml @@ -34,11 +34,7 @@ QtObject { ///////////// readonly property string statusProject: activeBackend.status.project - readonly property string statusPhasesCount: activeBackend.status.phasesCount - readonly property string statusExperimentsCount: activeBackend.status.experimentsCount - readonly property string statusCalculator: activeBackend.status.calculator - readonly property string statusMinimizer: activeBackend.status.minimizer - readonly property string statusVariables: activeBackend.status.variables + readonly property string statusEngine: activeBackend.status.engine /////////////// // Project page @@ -63,7 +59,9 @@ QtObject { readonly property var sampleModelLoaded: activeBackend.sampleModel.loaded readonly property var sampleModelAvailable: activeBackend.sampleModel.availableModels readonly property var sampleModelStructureTypes: activeBackend.sampleModel.structureTypes + readonly property var sampleModelTypes: activeBackend.sampleModel.modelTypes readonly property string sampleModelCurrentStructureType: activeBackend.sampleModel.currentStructureType + readonly property string sampleModelCurrentType: activeBackend.sampleModel.currentType property bool sampleModelCreated: activeBackend.sampleModel.created onSampleModelCreatedChanged: activeBackend.sampleModel.created = sampleModelCreated @@ -78,6 +76,9 @@ QtObject { // structure_type into its own property here so visibility bindings // (Layers/Lamellae GroupBoxes) update. if (field === 'structure_type') activeBackend.sampleModel.currentStructureType = value + // Same in-place-mutation caveat for the Discrete/Lattice type: mirror + // it so the Lattice Parameters GroupBox visibility binding updates. + if (field === 'type') activeBackend.sampleModel.currentType = value } function sampleModelClear() { activeBackend.sampleModel.clear() } function sampleModelSaveToCatalog() { activeBackend.sampleModel.saveToCatalog() } @@ -158,44 +159,23 @@ QtObject { // shown only when sampleModelCurrentStructureType === 'Monolayer'. readonly property var monolayerStructure: activeBackend.monolayerStructure - // Lattice structure (Sample Model sidebar). Single-record fielded form - // shown only when sampleModelCurrentStructureType === 'Lattice'. The - // substructure slot mirrors the SampleModel loaded/available shape, so - // the embedded list/popups follow the same recipe. + // Lattice parameters (Sample Model sidebar). Single-record fielded form + // shown only when sampleModelCurrentType === 'Lattice' (the Discrete/Lattice + // arrangement, decoupled from the shape). readonly property var latticeStructure: activeBackend.latticeStructure - readonly property var latticeSubstructureLoaded: activeBackend.latticeStructure.substructureLoaded - readonly property var latticeSubstructureAvailable: activeBackend.latticeStructure.availableSubstructures - readonly property var latticeSubstructureTypes: activeBackend.latticeStructure.substructureTypes - - function latticeSubstructureSetLoaded(item) { activeBackend.latticeStructure.setSubstructureLoaded(item) } - function latticeSubstructureClear() { activeBackend.latticeStructure.clearSubstructure() } - function latticeSubstructureSaveToCatalog() { activeBackend.latticeStructure.saveSubstructureToCatalog() } - function latticeSubstructureRemoveFromCatalog(index) { activeBackend.latticeStructure.removeSubstructureFromCatalog(index) } - - // Solution group (Sample Model sidebar). Two backend-owned slots: - // * solution — single-record JS array (capacity 0 or 1), TIP3 by default. - // * ions — ListModel with at most 2 rows, name only. - - readonly property var solutionLoaded: activeBackend.solution.loaded - readonly property var solutionAvailable: activeBackend.solution.available - readonly property var solutionTypes: activeBackend.solution.solutionTypes - - function solutionSetLoaded(item) { activeBackend.solution.setLoaded(item) } - function solutionUpdateField(field, value) { activeBackend.solution.updateField(field, value) } - function solutionClear() { activeBackend.solution.clear() } - function solutionSaveToCatalog() { activeBackend.solution.saveToCatalog() } - function solutionRemoveFromCatalog(index) { activeBackend.solution.removeFromCatalog(index) } - - // Solvent aliases — same backend object, used by the solvent table popup files. - readonly property var solventLoaded: activeBackend.solution.loaded - readonly property var solventAvailable: activeBackend.solution.available - readonly property var solventTypes: activeBackend.solution.solutionTypes - - function solventSetLoaded(item) { activeBackend.solution.setLoaded(item) } - function solventUpdateField(field, value) { activeBackend.solution.updateField(field, value) } - function solventClear() { activeBackend.solution.clear() } - function solventSaveToCatalog() { activeBackend.solution.saveToCatalog() } - function solventRemoveFromCatalog(index) { activeBackend.solution.removeFromCatalog(index) } + + // Buffer group (Sample Model sidebar). Solvent is a single selected value + // ('(None)' / TIP3 / Ethanol); buffer components are a row list (salts, + // buffering agents) backed by a shared catalog. + readonly property var bufferSolventOptions: activeBackend.buffer.solventOptions + property string bufferSolvent: activeBackend.buffer.solvent + onBufferSolventChanged: activeBackend.buffer.solvent = bufferSolvent + + readonly property var bufferComponents: activeBackend.buffer.components + readonly property var bufferComponentsAvailable: activeBackend.buffer.available + + function bufferComponentsAppend(item) { activeBackend.buffer.appendComponent(item) } + function bufferComponentsRemove(index) { activeBackend.buffer.removeComponent(index) } readonly property var ionsLoaded: activeBackend.ions.loaded readonly property var ionsAvailable: activeBackend.ions.available @@ -211,22 +191,36 @@ QtObject { // selection repopulates `componentsFilesFiles`. readonly property var componentsFilesFiles: activeBackend.componentsFiles.files readonly property string componentsFilesSelectedComponent: activeBackend.componentsFiles.selectedComponent + readonly property string componentsFilesEditName: activeBackend.componentsFiles.editName function componentsFilesSelect(name) { activeBackend.componentsFiles.selectComponent(name) } + function componentsFilesCreateNew() { activeBackend.componentsFiles.createNew() } + function componentsFilesSetEditName(name) { activeBackend.componentsFiles.editName = name } function componentsFilesAppend(path) { activeBackend.componentsFiles.appendFile(path) } function componentsFilesRemove(index) { activeBackend.componentsFiles.removeFile(index) } + function componentsFilesEditFile(index) { activeBackend.componentsFiles.editFile(index) } function componentsFilesExportComponent() { activeBackend.componentsFiles.exportComponent() } - function componentsFilesSave() { activeBackend.componentsFiles.save() } - // Position Restraints (Sample Model Advanced sidebar). One row per - // loaded component (mirrored from componentsLoaded); component_name is - // read-only here. f_constant is fixed to POSRES_FC_LIP for the mock. - readonly property var positionRestraintsItems: activeBackend.positionRestraints.items - - function positionRestraintsSetAtom(index, value) { activeBackend.positionRestraints.setAtom(index, value) } - function positionRestraintsSetPForm(index, value) { activeBackend.positionRestraints.setPForm(index, value) } - function positionRestraintsSetGeom(index, value) { activeBackend.positionRestraints.setGeom(index, value) } - function positionRestraintsSetRadius(index, value) { activeBackend.positionRestraints.setRadius(index, value) } + // Save the current file list to the asset library and load the component + // into the basic-tab components list (under whatever name is in the name + // field) if it isn't already there. + function componentsFilesSave() { + const name = activeBackend.componentsFiles.save() + if (!name) + return + const loaded = activeBackend.components.loaded + for (let i = 0; i < loaded.count; ++i) { + if (loaded.get(i).name === name) + return + } + activeBackend.components.appendItem({ + name: name, + component_type: 'Other', + c_ion: '', + mint: 0, + mext: 0 + }) + } // Structure Files (Sample Model Advanced sidebar). List of structure- // related files plus actions for saving the set to the asset library @@ -238,6 +232,70 @@ QtObject { function structureFilesRemove(index) { activeBackend.structureFiles.removeItem(index) } function structureFilesClear() { activeBackend.structureFiles.clear() } function structureFilesSaveToLib() { activeBackend.structureFiles.saveToLib() } + function structureFilesExport() { activeBackend.structureFiles.exportFiles() } + + // Library Assets editor (Advanced sidebar). A single draft asset the user + // creates or loads from the library, edits, and saves back. `mode` is + // 'empty' | 'create' | 'edit'; the type is editable only while creating. + readonly property string libraryAssetsMode: activeBackend.libraryAssets.mode + readonly property var libraryAssetsTypeOptions: activeBackend.libraryAssets.typeOptions + readonly property string libraryAssetsType: activeBackend.libraryAssets.assetType + readonly property string libraryAssetsName: activeBackend.libraryAssets.assetName + readonly property string libraryAssetsCIon: activeBackend.libraryAssets.cIon + readonly property int libraryAssetsMint: activeBackend.libraryAssets.mint + readonly property int libraryAssetsMext: activeBackend.libraryAssets.mext + readonly property var libraryAssetsPaths: activeBackend.libraryAssets.paths + readonly property var libraryAssetsLibrary: activeBackend.libraryAssets.library + // Salt composition (used when the type is 'Salt'). `saltReady` is true once + // a name and both ions are set. + readonly property var libraryAssetsSaltComposition: activeBackend.libraryAssets.saltComposition + readonly property bool libraryAssetsSaltReady: activeBackend.libraryAssets.saltReady + + function libraryAssetsCreateNew() { activeBackend.libraryAssets.createNew() } + function libraryAssetsSetSaltIon(index, value) { activeBackend.libraryAssets.setSaltIon(index, value) } + function libraryAssetsSetSaltCount(index, value) { activeBackend.libraryAssets.setSaltCount(index, value) } + function libraryAssetsLoad(item) { activeBackend.libraryAssets.loadAsset(item) } + function libraryAssetsSetType(value) { activeBackend.libraryAssets.assetType = value } + function libraryAssetsSetName(value) { activeBackend.libraryAssets.assetName = value } + function libraryAssetsSetCIon(value) { activeBackend.libraryAssets.cIon = value } + function libraryAssetsSetMint(value) { activeBackend.libraryAssets.mint = value } + function libraryAssetsSetMext(value) { activeBackend.libraryAssets.mext = value } + function libraryAssetsAppendPath(path) { activeBackend.libraryAssets.appendPath(path) } + function libraryAssetsRemovePath(index) { activeBackend.libraryAssets.removePath(index) } + function libraryAssetsEditPath(index) { activeBackend.libraryAssets.editPath(index) } + function libraryAssetsSave() { activeBackend.libraryAssets.save() } + function libraryAssetsExport() { activeBackend.libraryAssets.exportAsset() } + + // SMILES generator (Advanced sidebar). Builds a 3D molecular configuration + // from a SMILES string. `smilesReady` is true once a name and SMILES string + // are provided. + readonly property string smilesMoleculeName: activeBackend.smilesGenerator.moleculeName + readonly property string smilesFormula: activeBackend.smilesGenerator.formula + readonly property string smilesString: activeBackend.smilesGenerator.smiles + readonly property real smilesBoxX: activeBackend.smilesGenerator.boxX + readonly property real smilesBoxY: activeBackend.smilesGenerator.boxY + readonly property real smilesBoxZ: activeBackend.smilesGenerator.boxZ + readonly property var smilesFormatOptions: activeBackend.smilesGenerator.formatOptions + readonly property string smilesFormat: activeBackend.smilesGenerator.format + readonly property var smilesComponentTypeOptions: activeBackend.smilesGenerator.componentTypeOptions + readonly property string smilesComponentType: activeBackend.smilesGenerator.componentType + readonly property bool smilesCisDoubleBonds: activeBackend.smilesGenerator.cisDoubleBonds + readonly property bool smilesAlignZ: activeBackend.smilesGenerator.alignZ + readonly property bool smilesFlatXZ: activeBackend.smilesGenerator.flatXZ + readonly property bool smilesReady: activeBackend.smilesGenerator.ready + + function smilesSetMoleculeName(value) { activeBackend.smilesGenerator.moleculeName = value } + function smilesSetFormula(value) { activeBackend.smilesGenerator.formula = value } + function smilesSetString(value) { activeBackend.smilesGenerator.smiles = value } + function smilesSetBoxX(value) { activeBackend.smilesGenerator.boxX = value } + function smilesSetBoxY(value) { activeBackend.smilesGenerator.boxY = value } + function smilesSetBoxZ(value) { activeBackend.smilesGenerator.boxZ = value } + function smilesSetFormat(value) { activeBackend.smilesGenerator.format = value } + function smilesSetComponentType(value) { activeBackend.smilesGenerator.componentType = value } + function smilesSetCisDoubleBonds(value) { activeBackend.smilesGenerator.cisDoubleBonds = value } + function smilesSetAlignZ(value) { activeBackend.smilesGenerator.alignZ = value } + function smilesSetFlatXZ(value) { activeBackend.smilesGenerator.flatXZ = value } + function smilesGenerate() { activeBackend.smilesGenerator.generate() } //////////////// // Analysis page diff --git a/src/easyshapes_app/Gui/Globals/References.qml b/src/easyshapes_app/Gui/Globals/References.qml index 9189a76..c59d207 100644 --- a/src/easyshapes_app/Gui/Globals/References.qml +++ b/src/easyshapes_app/Gui/Globals/References.qml @@ -39,7 +39,6 @@ QtObject { 'basic': { 'popups': { 'LoadExistingModel': null, - 'CreateNewModel': null, 'LoadExistingComponent': null, 'CreateNewComponent': null, 'OpenAssetFile': null diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml b/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml index 3315766..c2f2689 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml @@ -17,24 +17,24 @@ EaComponents.ContentPage { mainView: EaComponents.MainContent { tabs: [ - EaElements.TabButton { text: qsTr('Chart') } + EaElements.TabButton { text: qsTr("Chart") } ] items: [ - Loader { source: 'MainArea/Chart.qml' } + Loader { source: "MainArea/Chart.qml" } ] } sideBar: EaComponents.SideBar { tabs: [ - EaElements.TabButton { text: qsTr('Basic controls') } + EaElements.TabButton { text: qsTr("Basic controls") } ] items: [ - Loader { source: 'Sidebar/Basic/Layout.qml' } + Loader { source: "Sidebar/Basic/Layout.qml" } ] - continueButton.text: qsTr('Continue') + continueButton.text: qsTr("Continue") continueButton.onClicked: { console.debug(`Clicking '${continueButton.text}' button ::: ${this}`) diff --git a/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml b/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml index 1d07269..2afc6bf 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml @@ -67,7 +67,7 @@ Rectangle { } - titleText: 'x' + titleText: "x" min: Globals.BackendWrapper.activeBackend.analysis.axesRanges["xmin"] max: Globals.BackendWrapper.activeBackend.analysis.axesRanges["xmax"] } @@ -89,7 +89,7 @@ Rectangle { } - titleText: 'y' + titleText: "y" min: Globals.BackendWrapper.activeBackend.analysis.axesRanges["ymin"] max: Globals.BackendWrapper.activeBackend.analysis.axesRanges["ymax"] } diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml index 03eb8f4..39980be 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml @@ -11,12 +11,11 @@ import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals - EaElements.GroupColumn { EaComponents.ListView { id: configFilesList - defaultInfoText: qsTr('No configuration files added') + defaultInfoText: qsTr("No configuration files added") multiSelection: false columnWidths: [ @@ -28,19 +27,15 @@ EaElements.GroupColumn { header: EaComponents.ListViewHeader { EaComponents.TableViewLabel { - text: qsTr('№') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('Path') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { + text: qsTr("№") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { + text: qsTr("Path") color: EaStyle.Colors.themeForegroundMinor } + EaComponents.TableViewLabel {} + EaComponents.TableViewLabel {} } model: Globals.BackendWrapper.analysisConfigFiles @@ -60,24 +55,28 @@ EaElements.GroupColumn { elide: Text.ElideLeft } EaComponents.TableViewButton { - fontIcon: 'edit' - ToolTip.text: qsTr('Edit this file') + fontIcon: "edit" + ToolTip.text: qsTr("Edit this file") onClicked: Globals.BackendWrapper.analysisConfigEditFile(index) } EaComponents.TableViewButton { - fontIcon: 'minus-circle' - ToolTip.text: qsTr('Remove this file') + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this file") onClicked: Globals.BackendWrapper.analysisConfigRemoveFile(index) } } } - EaElements.SideBarButton { - fontIcon: 'plus-circle' - text: qsTr('Add file(s)') - width: EaStyle.Sizes.sideBarContentWidth * 0.48 - ToolTip.text: qsTr('Pick one or more .mdp files from disk') - onClicked: addFilesLoader.item.open() + Grid { + columns: 1 + + EaElements.SideBarButton { + fontIcon: "plus-circle" + text: qsTr("Add file(s)") + width: EaStyle.Sizes.sideBarContentWidth * 0.48 + ToolTip.text: qsTr("Pick one or more .mdp files from disk") + onClicked: addFilesLoader.item.open() + } } // ForceField selector on the left, step-range pair on the right. @@ -87,7 +86,6 @@ EaElements.GroupColumn { property real fieldWidth: (halfWidth - EaStyle.Sizes.fontPixelSize * 0.5) / 2 EaElements.ComboBox { - id: forceFieldSelector width: parent.halfWidth topInset: forceFieldLabel.height topPadding: topInset + padding @@ -101,7 +99,7 @@ EaElements.GroupColumn { EaElements.Label { id: forceFieldLabel - text: qsTr('ForceField') + text: qsTr("ForceField") } } @@ -109,7 +107,6 @@ EaElements.GroupColumn { spacing: EaStyle.Sizes.fontPixelSize * 0.5 EaElements.TextField { - id: startStepField width: parent.parent.fieldWidth topInset: startStepLabel.height topPadding: topInset + padding @@ -121,12 +118,11 @@ EaElements.GroupColumn { EaElements.Label { id: startStepLabel - text: qsTr('Start step') + text: qsTr("Start step") } } EaElements.TextField { - id: stopStepField width: parent.parent.fieldWidth topInset: stopStepLabel.height topPadding: topInset + padding @@ -138,7 +134,7 @@ EaElements.GroupColumn { EaElements.Label { id: stopStepLabel - text: qsTr('Stop step') + text: qsTr("Stop step") } } } @@ -146,6 +142,6 @@ EaElements.GroupColumn { Loader { id: addFilesLoader - source: '../Popups/AddAnalysisConfigFiles.qml' + source: "../Popups/AddAnalysisConfigFiles.qml" } } diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml index d647b5b..5b0ba44 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml @@ -11,15 +11,14 @@ import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals - EaComponents.SideBarColumn { EaElements.GroupBox { - title: qsTr('Equilibration setup') - icon: 'sliders-h' + title: qsTr("Equilibration setup") + icon: "sliders-h" collapsed: false - Loader { source: 'Groups/AnalysisConfig.qml' } + Loader { source: "Groups/AnalysisConfig.qml" } } // Centered "Equilibrate" call-to-action below the groups. @@ -30,14 +29,13 @@ EaComponents.SideBarColumn { EaElements.SideBarButton { id: equilibrateButton anchors.centerIn: parent - text: qsTr('Equilibrate') - fontIcon: 'magic' + text: qsTr("Equilibrate") + fontIcon: "magic" width: EaStyle.Sizes.sideBarContentWidth enabled: Globals.BackendWrapper.analysisConfigFiles ? Globals.BackendWrapper.analysisConfigFiles.count > 0 : false - onClicked: console.debug('Equilibrate clicked') + onClicked: console.debug("Equilibrate clicked") } } - } diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml index 34cc2ce..313c43e 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml @@ -11,8 +11,8 @@ import Gui.Globals as Globals FileDialog { fileMode: FileDialog.OpenFiles nameFilters: [ - 'GROMACS run parameter files (*.mdp)', - 'Any (*)' + "GROMACS run parameter files (*.mdp)", + "Any (*)" ] onAccepted: { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml index 0450c67..4d5bc85 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml @@ -17,26 +17,26 @@ EaComponents.ContentPage { mainView: EaComponents.MainContent { tabs: [ - EaElements.TabButton { text: qsTr('GraphsView') } + EaElements.TabButton { text: qsTr("GraphsView") } ] items: [ - Loader { source: 'MainArea/GraphsView.qml' } + Loader { source: "MainArea/GraphsView.qml" } ] } sideBar: EaComponents.SideBar { tabs: [ - EaElements.TabButton { text: qsTr('Basic controls') }, - EaElements.TabButton { text: qsTr('Advanced controls') } + EaElements.TabButton { text: qsTr("Basic controls") }, + EaElements.TabButton { text: qsTr("Advanced controls") } ] items: [ - Loader { source: 'Sidebar/Basic/Layout.qml' }, - Loader { source: 'Sidebar/Advanced/Layout.qml' } + Loader { source: "Sidebar/Basic/Layout.qml" }, + Loader { source: "Sidebar/Advanced/Layout.qml" } ] - continueButton.text: qsTr('Continue') + continueButton.text: qsTr("Continue") continueButton.onClicked: { console.debug(`Clicking '${continueButton.text}' button ::: ${this}`) diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml index 276dfff..1aa8756 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml @@ -53,7 +53,7 @@ GraphsView { color: EaStyle.Colors.chartLabels } - titleText: 'x' + titleText: "x" min: 0 max: 100 } @@ -68,7 +68,7 @@ GraphsView { color: EaStyle.Colors.chartLabels } - titleText: 'y' + titleText: "y" min: -2 max: 2 } @@ -76,7 +76,7 @@ GraphsView { // lineSeries LineSeries { - color: 'red' + color: "red" XYPoint { x: 0; y: -1 } XYPoint { x: 50; y: 1.5 } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml index 53681f7..681a04b 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml @@ -13,92 +13,112 @@ import Gui.Globals as Globals EaElements.GroupColumn { + id: root property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + property double thirdWidth: (EaStyle.Sizes.sideBarContentWidth - 2 * EaStyle.Sizes.fontPixelSize) / 3 - // ComboBox + empty-state placeholder share a single slot so the layout - // doesn't reflow between the two states. The ComboBox is hidden (not - // just disabled) when there are no components because its popup keeps - // an ElevationEffect that leaks a soft shadow when the model is empty. - readonly property int componentsCount: - Globals.BackendWrapper.componentsLoaded - ? Globals.BackendWrapper.componentsLoaded.count - : 0 - - EaElements.ComboBox { - id: componentSelector - visible: componentsCount > 0 - width: EaStyle.Sizes.sideBarContentWidth - topInset: componentLabel.height - topPadding: topInset + padding - textRole: 'name' - model: Globals.BackendWrapper.componentsLoaded - - EaElements.Label { - id: componentLabel - text: qsTr('Component') - } - - onActivated: (i) => { - const row = Globals.BackendWrapper.componentsLoaded.get(i) - if (row) Globals.BackendWrapper.componentsFilesSelect(row.name) - } + // Component selector + name editor + Create new, in one row of equal + // thirds. The dropdown lists only components already loaded on the Basic + // tab; picking one loads its files and name. + Row { + spacing: EaStyle.Sizes.fontPixelSize - // Seed the selection from the currently-selected component, or the - // first component once the model populates. - function syncFromBackend() { - const total = componentsCount - if (total === 0) { - currentIndex = -1 - return + Column { + EaElements.Label { + enabled: false + text: qsTr("Component") } - const sel = Globals.BackendWrapper.componentsFilesSelectedComponent - if (sel !== '') { - for (let i = 0; i < total; ++i) { - if (Globals.BackendWrapper.componentsLoaded.get(i).name === sel) { - currentIndex = i - return + EaElements.ComboBox { + id: componentSelector + width: root.thirdWidth + textRole: "name" + model: Globals.BackendWrapper.componentsLoaded + + onActivated: (i) => { + const row = Globals.BackendWrapper.componentsLoaded.get(i) + if (row) Globals.BackendWrapper.componentsFilesSelect(row.name) + } + + // Mirror the dropdown index from the backend's selected + // component (set by picking a row, Create new, or Save to lib). + function syncIndex() { + const sel = Globals.BackendWrapper.componentsFilesSelectedComponent + const m = Globals.BackendWrapper.componentsLoaded + if (sel !== "" && m) { + for (let i = 0; i < m.count; ++i) { + if (m.get(i).name === sel) { + currentIndex = i + return + } + } } + currentIndex = -1 + } + + Component.onCompleted: syncIndex() + Connections { + target: Globals.BackendWrapper.componentsLoaded + function onCountChanged() { componentSelector.syncIndex() } + } + Connections { + target: Globals.BackendWrapper + function onComponentsFilesSelectedComponentChanged() { componentSelector.syncIndex() } } } - currentIndex = 0 - const row = Globals.BackendWrapper.componentsLoaded.get(0) - if (row) Globals.BackendWrapper.componentsFilesSelect(row.name) } - Component.onCompleted: syncFromBackend() - // Re-seed when the user adds the first component on the Basic tab. - Connections { - target: Globals.BackendWrapper.componentsLoaded - function onCountChanged() { componentSelector.syncFromBackend() } + Column { + EaElements.Label { + enabled: false + text: qsTr("Name") + } + EaElements.TextField { + width: root.thirdWidth + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("Component name") + text: Globals.BackendWrapper.componentsFilesEditName + onEditingFinished: Globals.BackendWrapper.componentsFilesSetEditName(text) + } } - } - EaElements.Label { - visible: componentsCount === 0 - enabled: false - width: EaStyle.Sizes.sideBarContentWidth - text: qsTr('Load a component on the Basic controls tab to manage its files here.') - wrapMode: Text.WordWrap + Column { + // Empty spacer label so the button bottom-aligns with the fields. + EaElements.Label { + enabled: false + text: " " + } + EaElements.SideBarButton { + fontIcon: "plus-square" + text: qsTr("Create new") + width: root.thirdWidth + ToolTip.text: qsTr("Start a new component — clears the selection, name and file list") + onClicked: Globals.BackendWrapper.componentsFilesCreateNew() + } + } } EaComponents.ListView { id: componentFilesList - defaultInfoText: qsTr('No files associated with this component') + defaultInfoText: qsTr("No files associated with this component") multiSelection: false columnWidths: [ EaStyle.Sizes.fontPixelSize * 2.5, -1, + EaStyle.Sizes.tableRowHeight, EaStyle.Sizes.tableRowHeight ] header: EaComponents.ListViewHeader { EaComponents.TableViewLabel { - text: qsTr('№') + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Path") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr('Path') color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { @@ -123,8 +143,13 @@ EaElements.GroupColumn { elide: Text.ElideLeft } EaComponents.TableViewButton { - fontIcon: 'minus-circle' - ToolTip.text: qsTr('Remove this file') + fontIcon: "edit" + ToolTip.text: qsTr("Edit this file") + onClicked: Globals.BackendWrapper.componentsFilesEditFile(index) + } + EaComponents.TableViewButton { + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this file") onClicked: Globals.BackendWrapper.componentsFilesRemove(index) } } @@ -135,35 +160,34 @@ EaElements.GroupColumn { spacing: EaStyle.Sizes.fontPixelSize EaElements.SideBarButton { - fontIcon: 'plus-circle' - text: qsTr('Add new file(s)') + fontIcon: "plus-circle" + text: qsTr("Add new file(s)") width: buttonWidth - ToolTip.text: qsTr('Pick one or more files from disk and add them to this component') - enabled: Globals.BackendWrapper.componentsFilesSelectedComponent !== '' + ToolTip.text: qsTr("Pick one or more files from disk and add them to this component") onClicked: addFilesLoader.item.open() } EaElements.SideBarButton { - fontIcon: 'file-export' - text: qsTr('Export component') + fontIcon: "save" + text: qsTr("Save to lib") width: buttonWidth - ToolTip.text: qsTr('Export the selected component to disk') - enabled: Globals.BackendWrapper.componentsFilesSelectedComponent !== '' - onClicked: Globals.BackendWrapper.componentsFilesExportComponent() + ToolTip.text: qsTr("Save this component to the asset library and load it on the Basic tab") + enabled: Globals.BackendWrapper.componentsFilesEditName.trim() !== "" + onClicked: Globals.BackendWrapper.componentsFilesSave() } EaElements.SideBarButton { - fontIcon: 'save' - text: qsTr('Save') + fontIcon: "file-export" + text: qsTr("Export") width: buttonWidth - ToolTip.text: qsTr('Save the file list for this component') - enabled: Globals.BackendWrapper.componentsFilesSelectedComponent !== '' - onClicked: Globals.BackendWrapper.componentsFilesSave() + ToolTip.text: qsTr("Export the selected component to disk") + enabled: Globals.BackendWrapper.componentsFilesSelectedComponent !== "" + onClicked: Globals.BackendWrapper.componentsFilesExportComponent() } } Loader { id: addFilesLoader - source: '../Popups/AddComponentFiles.qml' + source: "../Popups/AddComponentFiles.qml" } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/LibraryAssetsFiles.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/LibraryAssetsFiles.qml new file mode 100644 index 0000000..e3f6331 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/LibraryAssetsFiles.qml @@ -0,0 +1,361 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + id: root + property double halfWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 2 + property double thirdWidth: (EaStyle.Sizes.sideBarContentWidth - 2 * EaStyle.Sizes.fontPixelSize) / 3 + + readonly property string assetType: Globals.BackendWrapper.libraryAssetsType + readonly property bool isSalt: assetType === "Salt" + + // Component-only fields (C-ion/Mint/Mext) show for the molecule types and + // hide for Ion/Solvent/Salt, mirroring the asset class hierarchy. + readonly property bool isMolecule: assetType === "Lipid" + || assetType === "Surfactant" + || assetType === "Component (Other)" + + // Ion options: a leading "(None)" sentinel (empty value) plus the shared + // ion library. Used by both the C-ion picker and the salt composition. + readonly property var ionOptions: { + const lib = Globals.BackendWrapper.ionsAvailable + let opts = [qsTr("(None)")] + for (let i = 0; i < lib.count; ++i) + opts.push(lib.get(i).name) + return opts + } + + // Row 1 — Create new / Load from lib. + Grid { + columns: 2 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + fontIcon: "plus-square" + text: qsTr("Create new") + width: root.halfWidth + ToolTip.text: qsTr("Start a new library asset — clears the fields and unlocks the type") + onClicked: Globals.BackendWrapper.libraryAssetsCreateNew() + } + + EaElements.SideBarButton { + fontIcon: "file-import" + text: qsTr("Load from lib") + width: root.halfWidth + ToolTip.text: qsTr("Browse and load an existing asset from the library") + onClicked: loadLibraryAssetLoader.item.open() + } + } + + // Row 2 — Type + Name. Type is editable only while creating. + Row { + spacing: EaStyle.Sizes.fontPixelSize + + Column { + EaElements.Label { + enabled: false + text: qsTr("Type") + } + EaElements.ComboBox { + id: typeSelector + width: root.halfWidth + model: Globals.BackendWrapper.libraryAssetsTypeOptions + enabled: Globals.BackendWrapper.libraryAssetsMode === "create" + displayText: currentIndex < 0 ? "" : currentText + + function syncIndex() { + currentIndex = model + ? model.indexOf(Globals.BackendWrapper.libraryAssetsType) + : -1 + } + Component.onCompleted: syncIndex() + onActivated: (i) => Globals.BackendWrapper.libraryAssetsSetType(model[i]) + Connections { + target: Globals.BackendWrapper + function onLibraryAssetsTypeChanged() { typeSelector.syncIndex() } + } + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Name") + } + EaElements.TextField { + width: root.halfWidth + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("Asset name") + text: Globals.BackendWrapper.libraryAssetsName + onEditingFinished: Globals.BackendWrapper.libraryAssetsSetName(text) + } + } + } + + // Row 3 — component-only fields (C-ion / Mint / Mext). + Row { + visible: root.isMolecule + spacing: EaStyle.Sizes.fontPixelSize + + Column { + EaElements.Label { + enabled: false + text: qsTr("C-ion") + } + EaElements.ComboBox { + id: cIonSelector + width: root.thirdWidth + model: root.ionOptions + // Blank collapsed label for the "(None)" sentinel (index 0) or + // no match (-1); the dropdown still lists "(None)". + displayText: currentIndex <= 0 ? "" : currentText + + function syncIndex() { + const v = Globals.BackendWrapper.libraryAssetsCIon + currentIndex = v === "" ? 0 : find(v) + } + Component.onCompleted: syncIndex() + onActivated: (i) => Globals.BackendWrapper.libraryAssetsSetCIon(i === 0 ? "" : model[i]) + Connections { + target: Globals.BackendWrapper + function onLibraryAssetsCIonChanged() { cIonSelector.syncIndex() } + } + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Mint") + } + EaElements.TextField { + width: root.thirdWidth + horizontalAlignment: TextInput.AlignLeft + validator: IntValidator { bottom: 0 } + text: Globals.BackendWrapper.libraryAssetsMint + onEditingFinished: Globals.BackendWrapper.libraryAssetsSetMint(parseInt(text) || 0) + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Mext") + } + EaElements.TextField { + width: root.thirdWidth + horizontalAlignment: TextInput.AlignLeft + validator: IntValidator { bottom: 0 } + text: Globals.BackendWrapper.libraryAssetsMext + onEditingFinished: Globals.BackendWrapper.libraryAssetsSetMext(parseInt(text) || 0) + } + } + } + + // Salt composition — shown only for the Salt type (replaces the file list). + // Two ion slots with stoichiometric counts. + Column { + visible: root.isSalt + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr("Salt composition") + } + + EaComponents.ListView { + id: saltComposition + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + EaStyle.Sizes.fontPixelSize * 7, + EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.tableColumnFlex + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Count") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel {} // filler, takes the remaining space + } + + model: Globals.BackendWrapper.libraryAssetsSaltComposition + + delegate: EaComponents.ListViewDelegate { + id: saltRow + required property int index + required property string ion + required property int count + + EaComponents.TableViewLabel { + text: index + 1 + enabled: false + } + + EaComponents.TableViewComboBox { + id: ionCombo + model: root.ionOptions + displayText: currentIndex <= 0 ? "" : currentText + + function sync() { currentIndex = saltRow.ion === "" ? 0 : find(saltRow.ion) } + Component.onCompleted: sync() + onActivated: (i) => Globals.BackendWrapper.libraryAssetsSetSaltIon(saltRow.index, i === 0 ? "" : model[i]) + Connections { + target: saltRow + function onIonChanged() { ionCombo.sync() } + } + } + + EaComponents.ListViewTextInput { + text: count + validator: IntValidator { bottom: 1 } + onEditingFinished: Globals.BackendWrapper.libraryAssetsSetSaltCount(saltRow.index, parseInt(text) || 1) + } + + EaComponents.TableViewLabel {} // filler, matches the header + } + } + } + + // Asset composition — the files making up this asset. Hidden for salts. + EaComponents.ListView { + id: assetPathsList + visible: !root.isSalt + defaultInfoText: qsTr("No files in this asset") + multiSelection: false + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + EaStyle.Sizes.tableRowHeight, + EaStyle.Sizes.tableRowHeight + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Path") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + color: EaStyle.Colors.themeForegroundMinor + } + } + + model: Globals.BackendWrapper.libraryAssetsPaths + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string path + + EaComponents.TableViewLabel { + text: index + 1 + enabled: false + } + EaComponents.TableViewLabel { + text: path + elide: Text.ElideLeft + } + EaComponents.TableViewButton { + fontIcon: "edit" + ToolTip.text: qsTr("Edit this file") + onClicked: Globals.BackendWrapper.libraryAssetsEditPath(index) + } + EaComponents.TableViewButton { + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this file") + onClicked: Globals.BackendWrapper.libraryAssetsRemovePath(index) + } + } + } + + // Row — Add new file / Save to lib / Export. + Grid { + columns: 3 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + fontIcon: "plus-circle" + text: qsTr("Add new file") + width: root.thirdWidth + ToolTip.text: qsTr("Pick one or more files from disk and add them to this asset") + enabled: Globals.BackendWrapper.libraryAssetsMode !== "empty" && !root.isSalt + onClicked: addAssetFilesDialog.open() + } + + EaElements.SideBarButton { + fontIcon: "save" + text: qsTr("Save to lib") + width: root.thirdWidth + ToolTip.text: qsTr("Save this asset to the library") + enabled: root.isSalt + ? Globals.BackendWrapper.libraryAssetsSaltReady + : (Globals.BackendWrapper.libraryAssetsName.trim() !== "" + && Globals.BackendWrapper.libraryAssetsPaths + && Globals.BackendWrapper.libraryAssetsPaths.count > 0) + onClicked: Globals.BackendWrapper.libraryAssetsSave() + } + + EaElements.SideBarButton { + fontIcon: "file-export" + text: qsTr("Export") + width: root.thirdWidth + ToolTip.text: qsTr("Export this asset to disk — enabled once it is saved to or loaded from the library") + enabled: Globals.BackendWrapper.libraryAssetsMode === "edit" + onClicked: Globals.BackendWrapper.libraryAssetsExport() + } + } + + FileDialog { + id: addAssetFilesDialog + fileMode: FileDialog.OpenFiles + nameFilters: [ + "Any (*)", + "Structure files (*.gro *.pdb *.xyz)", + "Topology files (*.itp *.top)", + "Smiles files (*.sml)" + ] + onAccepted: { + for (let i = 0; i < selectedFiles.length; ++i) { + Globals.BackendWrapper.libraryAssetsAppendPath(selectedFiles[i].toString()) + } + } + } + + Loader { + id: loadLibraryAssetLoader + source: "../Popups/LoadLibraryAsset.qml" + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml deleted file mode 100644 index c30d7be..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/PositionRestraints.qml +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents - -import Gui.Globals as Globals - - -EaElements.GroupColumn { - - EaComponents.ListView { - id: positionRestraintsList - defaultInfoText: qsTr('No position restraints defined') - multiSelection: false - - columnWidths: [ - -1, - EaStyle.Sizes.fontPixelSize * 4, - EaStyle.Sizes.fontPixelSize * 4, - EaStyle.Sizes.fontPixelSize * 4, - EaStyle.Sizes.fontPixelSize * 5, - EaStyle.Sizes.fontPixelSize * 8 - ] - - header: EaComponents.ListViewHeader { - EaComponents.TableViewLabel { - text: qsTr('Component') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('Atom') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('P. form') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('Geom.') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('Radius') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('F. constant') - color: EaStyle.Colors.themeForegroundMinor - } - } - - model: Globals.BackendWrapper.positionRestraintsItems - - delegateModelAccess: DelegateModel.ReadWrite - - delegate: EaComponents.ListViewDelegate { - required property int index - required property string component_name - required property int atom - required property int p_form - required property int geom - required property double radius - required property string f_constant - - EaComponents.TableViewLabel { - text: component_name - } - EaComponents.ListViewTextInput { - text: atom - validator: IntValidator { bottom: 0 } - onEditingFinished: Globals.BackendWrapper.positionRestraintsSetAtom(index, parseInt(text)) - } - EaComponents.ListViewTextInput { - text: p_form - validator: IntValidator { bottom: 0 } - onEditingFinished: Globals.BackendWrapper.positionRestraintsSetPForm(index, parseInt(text)) - } - EaComponents.ListViewTextInput { - text: geom - validator: IntValidator { bottom: 0 } - onEditingFinished: Globals.BackendWrapper.positionRestraintsSetGeom(index, parseInt(text)) - } - EaComponents.ListViewTextInput { - text: radius - validator: DoubleValidator { bottom: 0 } - onEditingFinished: Globals.BackendWrapper.positionRestraintsSetRadius(index, parseFloat(text)) - } - EaComponents.TableViewLabel { - text: f_constant - enabled: false - } - } - } -} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/SmilesGenerator.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/SmilesGenerator.qml new file mode 100644 index 0000000..915ce87 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/SmilesGenerator.qml @@ -0,0 +1,210 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.GroupColumn { + id: root + property double halfWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 2 + property double thirdWidth: (EaStyle.Sizes.sideBarContentWidth - 2 * EaStyle.Sizes.fontPixelSize) / 3 + + // Map the display type to the value stored on a component ('Other' rather + // than 'Component (Other)', matching the components list). + function storedComponentType(label) { + return label === "Component (Other)" ? "Other" : label + } + + // Molecule identity — Name and Formula. + Row { + spacing: EaStyle.Sizes.fontPixelSize + + Column { + EaElements.Label { + enabled: false + text: qsTr("Name") + } + EaElements.TextField { + width: root.halfWidth + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("e.g. SDS") + text: Globals.BackendWrapper.smilesMoleculeName + onEditingFinished: Globals.BackendWrapper.smilesSetMoleculeName(text) + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Formula") + } + EaElements.TextField { + width: root.halfWidth + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("(optional)") + text: Globals.BackendWrapper.smilesFormula + onEditingFinished: Globals.BackendWrapper.smilesSetFormula(text) + } + } + } + + // The SMILES string. Hydrogens are added automatically by the generator. + Column { + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr("SMILES") + } + EaElements.TextField { + width: EaStyle.Sizes.sideBarContentWidth + horizontalAlignment: TextInput.AlignLeft + placeholderText: qsTr("e.g. CCCCCCCCCCCCOS(=O)(=O)[O-]") + text: Globals.BackendWrapper.smilesString + onEditingFinished: Globals.BackendWrapper.smilesSetString(text) + } + EaElements.Label { + enabled: false + text: qsTr("Hydrogens are added automatically.") + } + } + + // Simulation box. + Column { + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr("Box") + } + Row { + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.Parameter { + width: root.thirdWidth + title: qsTr("Lx") + units: "nm" + validator: DoubleValidator { bottom: 0 } + text: Globals.BackendWrapper.smilesBoxX + onEditingFinished: Globals.BackendWrapper.smilesSetBoxX(parseFloat(text) || 0) + } + EaElements.Parameter { + width: root.thirdWidth + title: qsTr("Ly") + units: "nm" + validator: DoubleValidator { bottom: 0 } + text: Globals.BackendWrapper.smilesBoxY + onEditingFinished: Globals.BackendWrapper.smilesSetBoxY(parseFloat(text) || 0) + } + EaElements.Parameter { + width: root.thirdWidth + title: qsTr("Lz") + units: "nm" + validator: DoubleValidator { bottom: 0 } + text: Globals.BackendWrapper.smilesBoxZ + onEditingFinished: Globals.BackendWrapper.smilesSetBoxZ(parseFloat(text) || 0) + } + } + } + + // Generation flags. Flatten-XZ only acts together with Align-to-Z, so it is + // disabled unless Align-to-Z is on. + Row { + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.CheckBox { + width: root.thirdWidth + height: EaStyle.Sizes.fontPixelSize * 2.5 + text: qsTr("Cis C=C") + ToolTip.text: qsTr("Kink C=C double bonds to cis (dbcis)") + checked: Globals.BackendWrapper.smilesCisDoubleBonds + onToggled: Globals.BackendWrapper.smilesSetCisDoubleBonds(checked) + } + EaElements.CheckBox { + width: root.thirdWidth + height: EaStyle.Sizes.fontPixelSize * 2.5 + text: qsTr("Align Z") + ToolTip.text: qsTr("Align the backbone to the Z axis") + checked: Globals.BackendWrapper.smilesAlignZ + onToggled: Globals.BackendWrapper.smilesSetAlignZ(checked) + } + EaElements.CheckBox { + width: root.thirdWidth + height: EaStyle.Sizes.fontPixelSize * 2.5 + text: qsTr("Flatten XZ") + ToolTip.text: qsTr("Flatten into the XZ plane (only with Align Z)") + enabled: Globals.BackendWrapper.smilesAlignZ + checked: Globals.BackendWrapper.smilesFlatXZ + onToggled: Globals.BackendWrapper.smilesSetFlatXZ(checked) + } + } + + // Component type + output format + Generate, in one row of thirds. + Row { + spacing: EaStyle.Sizes.fontPixelSize + + Column { + EaElements.Label { + enabled: false + text: qsTr("Type") + } + EaElements.ComboBox { + id: componentTypeSelector + width: root.thirdWidth + model: Globals.BackendWrapper.smilesComponentTypeOptions + Component.onCompleted: currentIndex = model.indexOf(Globals.BackendWrapper.smilesComponentType) + onActivated: (i) => Globals.BackendWrapper.smilesSetComponentType(model[i]) + } + } + + Column { + EaElements.Label { + enabled: false + text: qsTr("Format") + } + EaElements.ComboBox { + id: formatSelector + width: root.thirdWidth + model: Globals.BackendWrapper.smilesFormatOptions + Component.onCompleted: currentIndex = model.indexOf(Globals.BackendWrapper.smilesFormat) + onActivated: (i) => Globals.BackendWrapper.smilesSetFormat(model[i]) + } + } + + Column { + // Empty spacer label so the button bottom-aligns with the dropdowns. + EaElements.Label { + enabled: false + text: " " + } + EaElements.SideBarButton { + fontIcon: "cogs" + text: qsTr("Generate") + width: root.thirdWidth + ToolTip.text: qsTr("Generate the molecule and add it to the components") + enabled: Globals.BackendWrapper.smilesReady + onClicked: { + Globals.BackendWrapper.componentsAppend({ + name: Globals.BackendWrapper.smilesMoleculeName, + component_type: root.storedComponentType(Globals.BackendWrapper.smilesComponentType), + c_ion: "", + mint: 0, + mext: 0 + }) + console.debug("SMILES generate → component", + Globals.BackendWrapper.smilesMoleculeName, + "dbcis=" + (Globals.BackendWrapper.smilesCisDoubleBonds ? "['C']" : "[]"), + "alignZ=" + Globals.BackendWrapper.smilesAlignZ, + "fxz=" + Globals.BackendWrapper.smilesFlatXZ) + } + } + } + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml index 41820e5..de07e72 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/StructureFiles.qml @@ -4,6 +4,7 @@ import QtQuick import QtQuick.Controls +import QtQuick.Dialogs import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements @@ -13,11 +14,11 @@ import Gui.Globals as Globals EaElements.GroupColumn { - property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.48 + property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 EaComponents.ListView { id: structureFilesList - defaultInfoText: qsTr('No structure files available') + defaultInfoText: qsTr("No sample model files available") multiSelection: false columnWidths: [ @@ -28,11 +29,11 @@ EaElements.GroupColumn { header: EaComponents.ListViewHeader { EaComponents.TableViewLabel { - text: qsTr('№') + text: qsTr("№") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr('File') + text: qsTr("File") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { @@ -57,22 +58,30 @@ EaElements.GroupColumn { elide: Text.ElideLeft } EaComponents.TableViewButton { - fontIcon: 'minus-circle' - ToolTip.text: qsTr('Remove this file') + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this file") onClicked: Globals.BackendWrapper.structureFilesRemove(index) } } } Grid { - columns: 2 + columns: 3 spacing: EaStyle.Sizes.fontPixelSize EaElements.SideBarButton { - fontIcon: 'save' - text: qsTr('Save to lib') + fontIcon: "plus-circle" + text: qsTr("Add new file(s)") width: buttonWidth - ToolTip.text: qsTr('Save the current structure files to the asset library') + ToolTip.text: qsTr("Pick one or more files from disk and add them to the sample model") + onClicked: addSampleModelFilesDialog.open() + } + + EaElements.SideBarButton { + fontIcon: "save" + text: qsTr("Save to lib") + width: buttonWidth + ToolTip.text: qsTr("Save the current sample model files to the asset library") enabled: Globals.BackendWrapper.structureFilesFiles ? Globals.BackendWrapper.structureFilesFiles.count > 0 : false @@ -80,16 +89,30 @@ EaElements.GroupColumn { } EaElements.SideBarButton { - fontIcon: 'folder-open' - text: qsTr('Replace structure') + fontIcon: "file-export" + text: qsTr("Export") width: buttonWidth - ToolTip.text: qsTr('Pick a directory to replace the current structure with') - onClicked: replaceStructureLoader.item.open() + ToolTip.text: qsTr("Export the current sample model files to disk") + enabled: Globals.BackendWrapper.structureFilesFiles + ? Globals.BackendWrapper.structureFilesFiles.count > 0 + : false + onClicked: Globals.BackendWrapper.structureFilesExport() } } - Loader { - id: replaceStructureLoader - source: '../Popups/ReplaceStructure.qml' + FileDialog { + id: addSampleModelFilesDialog + fileMode: FileDialog.OpenFiles + nameFilters: [ + "Any (*)", + "Structure files (*.gro *.pdb *.xyz)", + "Topology files (*.itp *.top)", + "Data files (*.dat)" + ] + onAccepted: { + for (let i = 0; i < selectedFiles.length; ++i) { + Globals.BackendWrapper.structureFilesAppendPath(selectedFiles[i].toString()) + } + } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml index a3341e0..7da8188 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Layout.qml @@ -12,25 +12,32 @@ import EasyApplication.Gui.Components as EaComponents EaComponents.SideBarColumn { EaElements.GroupBox { - title: qsTr('Components Files') - icon: 'file-alt' + title: qsTr("Library assets") + icon: "book" collapsed: false - Loader { source: 'Groups/ComponentsFiles.qml' } + Loader { source: "Groups/LibraryAssetsFiles.qml" } } EaElements.GroupBox { - title: qsTr('Position Restraints') - icon: 'thumbtack' + title: qsTr("Components Files") + icon: "file-alt" - Loader { source: 'Groups/PositionRestraints.qml' } + Loader { source: "Groups/ComponentsFiles.qml" } } EaElements.GroupBox { - title: qsTr('Structure Files') - icon: 'folder-open' + title: qsTr("Sample Model Files") + icon: "folder-open" - Loader { source: 'Groups/StructureFiles.qml' } + Loader { source: "Groups/StructureFiles.qml" } + } + + EaElements.GroupBox { + title: qsTr("SMILES generator") + icon: "atom" + + Loader { source: "Groups/SmilesGenerator.qml" } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml index 9c641da..55759c1 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/AddComponentFiles.qml @@ -11,10 +11,10 @@ import Gui.Globals as Globals FileDialog { fileMode: FileDialog.OpenFiles nameFilters: [ - 'Any (*)', - 'Structure files (*.gro *.pdb *.xyz)', - 'Topology files (*.itp)', - 'Smiles files (*.sml)' + "Any (*)", + "Structure files (*.gro *.pdb *.xyz)", + "Topology files (*.itp)", + "Smiles files (*.sml)" ] onAccepted: { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/LoadLibraryAsset.qml similarity index 56% rename from src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml rename to src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/LoadLibraryAsset.qml index 61b896d..89d6c36 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSubstructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/LoadLibraryAsset.qml @@ -6,7 +6,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Dialogs -import EasyApplication.Gui.Globals as EaGlobals import EasyApplication.Gui.Components as EaComponents import EasyApplication.Gui.Elements as EaElements import EasyApplication.Gui.Style as EaStyle @@ -14,26 +13,24 @@ import EasyApplication.Gui.Style as EaStyle import Gui.Globals as Globals -EaElements.Dialog{ - id: substructureLoadDialog +EaElements.Dialog { + id: libraryAssetLoadDialog - property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 + property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 30 - title: qsTr("Load a Substructure from the Asset Library") + title: qsTr("Load an Asset from the Library") standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { - var indexes = loadSubstructureListView.selectedIndexes - + var indexes = loadLibraryAssetListView.selectedIndexes if (indexes.length > 0) { - var row = indexes[0].row - var item = Globals.BackendWrapper.latticeSubstructureAvailable.get(row) - Globals.BackendWrapper.latticeSubstructureSetLoaded(item) - loadSubstructureListView.clearSelection() + var item = Globals.BackendWrapper.libraryAssetsLibrary.get(indexes[0].row) + Globals.BackendWrapper.libraryAssetsLoad(item) } + loadLibraryAssetListView.clearSelection() } onRejected: { - loadSubstructureListView.clearSelection() + loadLibraryAssetListView.clearSelection() } Column { @@ -41,16 +38,16 @@ EaElements.Dialog{ enabled: false text: qsTr("Available in the Asset Library") } + EaComponents.ListView { - id: loadSubstructureListView - defaultInfoText: qsTr("No substructures found") + id: loadLibraryAssetListView + defaultInfoText: qsTr("No assets found") multiSelection: false + columnWidths: [ EaStyle.Sizes.fontPixelSize * 2.5, - EaStyle.Sizes.fontPixelSize * 10, - EaStyle.Sizes.fontPixelSize * 6, -1, - EaStyle.Sizes.tableRowHeight, + EaStyle.Sizes.fontPixelSize * 10 ] header: EaComponents.ListViewHeader { @@ -67,20 +64,14 @@ EaElements.Dialog{ text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel { - text: qsTr("Description") - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel {} // filler } - model: Globals.BackendWrapper.latticeSubstructureAvailable + model: Globals.BackendWrapper.libraryAssetsLibrary delegate: EaComponents.ListViewDelegate { required property int index required property string name - required property string structure_type - required property string description + required property string type EaComponents.TableViewLabel { text: index + 1 @@ -91,15 +82,7 @@ EaElements.Dialog{ text: name } EaComponents.TableViewLabel { - text: structure_type - } - EaComponents.TableViewLabel { - text: description - } - EaComponents.TableViewButton { - fontIcon: "minus-circle" - ToolTip.text: qsTr("Remove this substructure") - onClicked: Globals.BackendWrapper.latticeSubstructureRemoveFromCatalog(index) + text: type } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml index 66fe84e..2b891ca 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Popups/ReplaceStructure.qml @@ -7,5 +7,5 @@ import QtQuick.Dialogs FolderDialog { - title: qsTr('Select a directory to replace the structure with') + title: qsTr("Select a directory to replace the structure with") } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml index f4924ab..e539904 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Components/Fractions.qml @@ -10,7 +10,6 @@ import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals - EaComponents.ListView { // Override to bind a per-row Fractions backend (e.g. layer/lamella). // Defaults to the global Fractions set on the wrapper. @@ -23,7 +22,7 @@ EaComponents.ListView { EaStyle.Sizes.fontPixelSize * 6, -1, EaStyle.Sizes.fontPixelSize * 8, - EaStyle.Sizes.fontPixelSize * 8, + EaStyle.Sizes.fontPixelSize * 8 ] header: EaComponents.ListViewHeader { @@ -31,7 +30,7 @@ EaComponents.ListView { text: qsTr("Present") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel {} EaComponents.TableViewLabel { text: qsTr("Component name") color: EaStyle.Colors.themeForegroundMinor @@ -56,7 +55,7 @@ EaComponents.ListView { checked: present onToggled: present = checked } - EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel {} EaComponents.TableViewLabel { text: name enabled: false diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml index 829e7df..3b327c8 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BallStructure.qml @@ -10,40 +10,37 @@ import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals - EaElements.GroupColumn { - Row { - property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.2 - spacing: EaStyle.Sizes.fontPixelSize * 2 - - Column { - width: parent.itemWidth - - EaElements.Label { - enabled: false - text: qsTr('Fill') - } - EaElements.ComboBox { - width: parent.width - model: ['FIBO', 'RINGS', 'RINGS0'] - Component.onCompleted: { - currentIndex = model.indexOf(Globals.BackendWrapper.ballStructure.fill) - } - onActivated: (i) => { - Globals.BackendWrapper.ballStructure.fill = model[i] - } - } + Column { + width: EaStyle.Sizes.sideBarContentWidth * 0.2 + + EaElements.Label { + enabled: false + text: qsTr("Fill") + } + EaElements.ComboBox { + width: parent.width + model: ["FIBO", "RINGS", "RINGS0"] + Component.onCompleted: currentIndex = model.indexOf(Globals.BackendWrapper.ballStructure.fill) + onActivated: (i) => Globals.BackendWrapper.ballStructure.fill = model[i] } + } + + Row { + spacing: EaStyle.Sizes.fontPixelSize + EaElements.CheckBox { - height: EaStyle.Sizes.fontPixelSize * 5 - text: qsTr('Fxz') + width: EaStyle.Sizes.sideBarContentWidth * 0.15 + height: EaStyle.Sizes.fontPixelSize * 3 + text: qsTr("Fxz") checked: Globals.BackendWrapper.ballStructure.fxz onToggled: Globals.BackendWrapper.ballStructure.fxz = checked } EaElements.CheckBox { - height: EaStyle.Sizes.fontPixelSize * 5 - text: qsTr('Rev') + width: EaStyle.Sizes.sideBarContentWidth * 0.15 + height: EaStyle.Sizes.fontPixelSize * 3 + text: qsTr("Rev") checked: Globals.BackendWrapper.ballStructure.rev onToggled: Globals.BackendWrapper.ballStructure.rev = checked } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml index fd53d7a..060a42b 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/BilayerStructure.qml @@ -12,7 +12,6 @@ import Gui.Globals as Globals import "../Components" as Local - EaElements.GroupColumn { Row { @@ -21,23 +20,23 @@ EaElements.GroupColumn { EaElements.Parameter { width: parent.itemWidth - title: qsTr('Zsep') - units: 'nm' + title: qsTr("Zsep") + units: "nm" validator: DoubleValidator { bottom: 0 } text: Globals.BackendWrapper.bilayerStructure.zsep onEditingFinished: Globals.BackendWrapper.bilayerStructure.zsep = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Nside') - text: Globals.BackendWrapper.bilayerStructure.nside + title: qsTr("Nside") validator: IntValidator { bottom: 1 } + text: Globals.BackendWrapper.bilayerStructure.nside onEditingFinished: Globals.BackendWrapper.bilayerStructure.nside = parseInt(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Dmin') - units: 'nm' + title: qsTr("Dmin") + units: "nm" validator: DoubleValidator { bottom: 0.5 } text: Globals.BackendWrapper.bilayerStructure.dmin onEditingFinished: Globals.BackendWrapper.bilayerStructure.dmin = parseFloat(text) @@ -49,12 +48,8 @@ EaElements.GroupColumn { EaElements.Label { enabled: false - text: qsTr('Bilayer Fractions') - } - - Local.Fractions { - id: bilayerFractions + text: qsTr("Bilayer Fractions") } + Local.Fractions {} } - } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Buffer.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Buffer.qml new file mode 100644 index 0000000..4a90df1 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Buffer.qml @@ -0,0 +1,137 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements +import EasyApplication.Gui.Components as EaComponents + +import Gui.Globals as Globals + +EaElements.GroupColumn { + property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 2 + + // Solvent — single selection (None / TIP3 / Ethanol). + Row { + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.Label { + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Solvent") + } + + EaElements.ComboBox { + width: EaStyle.Sizes.sideBarContentWidth * 0.25 + model: Globals.BackendWrapper.bufferSolventOptions + // Blank collapsed label for the "(None)" sentinel (index 0). + displayText: currentIndex <= 0 ? "" : currentText + Component.onCompleted: currentIndex = model.indexOf(Globals.BackendWrapper.bufferSolvent) + onActivated: (i) => Globals.BackendWrapper.bufferSolvent = model[i] + } + } + + // Buffer components — row list of salts / buffering agents. + Column { + width: parent.width + + EaElements.Label { + enabled: false + text: qsTr("Buffer Components") + } + + EaComponents.ListView { + id: bufferComponentsList + defaultInfoText: qsTr("Load or import buffer components") + multiSelection: true + + columnWidths: [ + EaStyle.Sizes.fontPixelSize * 2.5, + -1, + -1, + EaStyle.Sizes.tableRowHeight + ] + + header: EaComponents.ListViewHeader { + EaComponents.TableViewLabel { + text: qsTr("№") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel { + text: qsTr("Name") + color: EaStyle.Colors.themeForegroundMinor + } + EaComponents.TableViewLabel { + text: qsTr("Concentration, mM") + color: EaStyle.Colors.themeForegroundMinor + horizontalAlignment: Text.AlignHCenter + } + EaComponents.TableViewLabel {} + } + + model: Globals.BackendWrapper.bufferComponents + + delegateModelAccess: DelegateModel.ReadWrite + + delegate: EaComponents.ListViewDelegate { + required property int index + required property string name + required property real concentration + + EaComponents.TableViewLabel { + text: index + 1 + horizontalAlignment: Text.AlignHCenter + enabled: false + } + EaComponents.TableViewLabel { + text: name + } + EaComponents.ListViewTextInput { + text: concentration + onEditingFinished: concentration = parseFloat(text) + validator: DoubleValidator { bottom: 0; decimals: 3; notation: DoubleValidator.StandardNotation } + } + EaComponents.TableViewButton { + fontIcon: "minus-circle" + ToolTip.text: qsTr("Remove this component") + onClicked: Globals.BackendWrapper.bufferComponentsRemove(index) + } + } + } + } + + Grid { + columns: 2 + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + fontIcon: "file-import" + text: qsTr("Load components") + width: buttonWidth + onClicked: loadBufferComponentLoader.item.open() + } + + EaElements.SideBarButton { + fontIcon: "upload" + text: qsTr("Import components") + width: buttonWidth + onClicked: importBufferFolderDialog.open() + } + } + + Loader { + id: loadBufferComponentLoader + source: "../Popups/LoadExistingBufferComponent.qml" + } + + // Folder-based import, matching the other Import buttons. + FolderDialog { + id: importBufferFolderDialog + title: qsTr("Import buffer components from a directory") + onAccepted: console.debug(`Import buffer components from folder '${selectedFolder}'`) + } +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml index 9bd2a0e..3adfa5c 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Components.qml @@ -4,19 +4,26 @@ import QtQuick import QtQuick.Controls -//import QtQuick.Dialogs +import QtQuick.Dialogs -import EasyApplication.Gui.Globals as EaGlobals import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals - EaElements.GroupColumn { - property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + id: root + property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 2 + + // Counter-ion options: a "(None)" sentinel (empty c_ion) plus the shared ion library. + readonly property var cIonOptions: { + const lib = Globals.BackendWrapper.ionsAvailable + let opts = [qsTr("(None)")] + for (let i = 0; i < lib.count; ++i) + opts.push(lib.get(i).name) + return opts + } EaComponents.ListView { id: loadedComponents @@ -27,7 +34,7 @@ EaElements.GroupColumn { EaStyle.Sizes.fontPixelSize * 2.5, -1, EaStyle.Sizes.fontPixelSize * 6, - EaStyle.Sizes.fontPixelSize * 4, + EaStyle.Sizes.fontPixelSize * 5, EaStyle.Sizes.fontPixelSize * 4, EaStyle.Sizes.fontPixelSize * 4, EaStyle.Sizes.tableRowHeight @@ -47,7 +54,7 @@ EaElements.GroupColumn { color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Atoms") + text: qsTr("C-ion") color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { @@ -73,7 +80,7 @@ EaElements.GroupColumn { required property string component_type required property int mint required property int mext - required property int atoms + required property string c_ion EaComponents.TableViewLabel { text: index + 1 @@ -90,10 +97,13 @@ EaElements.GroupColumn { enabled: false } - EaComponents.ListViewTextInput { - text: atoms - onEditingFinished: atoms = parseInt(text) - validator: IntValidator { bottom: 0 } + EaComponents.TableViewComboBox { + horizontalAlignment: Text.AlignHCenter + model: root.cIonOptions + // Blank collapsed label for the "(None)" sentinel (0) or no match (-1). + displayText: currentIndex <= 0 ? "" : currentText + Component.onCompleted: currentIndex = c_ion === "" ? 0 : find(c_ion) + onActivated: (i) => c_ion = (i === 0 ? "" : model[i]) } EaComponents.ListViewTextInput { @@ -117,49 +127,33 @@ EaElements.GroupColumn { } Grid { - columns: 3 + columns: 2 spacing: EaStyle.Sizes.fontPixelSize EaElements.SideBarButton { - fontIcon: 'upload' - text: qsTr('Load component(s)') + fontIcon: "file-import" + text: qsTr("Load component(s)") width: buttonWidth onClicked: loadExistingComponentLoader.item.open() } EaElements.SideBarButton { - fontIcon: 'plus-circle' - text: qsTr('Import component') - width: buttonWidth - onClicked: createNewComponentLoader.item.open() - } - - EaElements.SideBarButton { - id: saveModelButton - fontIcon: 'download' - text: qsTr('Save component(s)') + fontIcon: "upload" + text: qsTr("Import component") width: buttonWidth - enabled: Globals.BackendWrapper.componentsLoaded - ? Globals.BackendWrapper.componentsLoaded.count > 0 - : false - onClicked: { - const indexes = loadedComponents.selectedIndexes - for (let i = 0; i < indexes.length; ++i) - loadExistingComponentLoader.item.availableComponentsModel.append( - Globals.BackendWrapper.componentsLoaded.get(indexes[i].row) - ) - } + onClicked: importComponentFolderDialog.open() } } Loader { id: loadExistingComponentLoader - source: '../Popups/LoadExistingComponent.qml' + source: "../Popups/LoadExistingComponent.qml" } - Loader { - id: createNewComponentLoader - source: '../Popups/CreateNewComponent.qml' + // Folder-based import, matching Model Definition's Import. + FolderDialog { + id: importComponentFolderDialog + title: qsTr("Import a component from a directory") + onAccepted: console.debug(`Import component from folder '${selectedFolder}'`) } - } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml index 4f4432c..a35e4af 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Lamellae.qml @@ -5,18 +5,16 @@ import QtQuick import QtQuick.Controls -import EasyApplication.Gui.Globals as EaGlobals import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals import "../Components" as Local EaElements.GroupColumn { - property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize * 2) / 3 EaComponents.ListView { id: lamellae @@ -102,7 +100,6 @@ EaElements.GroupColumn { onToggled: Globals.BackendWrapper.lamellaeSetSymmetric(index, checked) } EaComponents.TableViewButton { - id: deleteRowColumn fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this lamella") onClicked: Globals.BackendWrapper.lamellaeRemove(index) @@ -110,14 +107,10 @@ EaElements.GroupColumn { } } - Item { - width: lamellae.width - height: addLamellaButton.height + Grid { + columns: 1 EaElements.SideBarButton { - id: addLamellaButton - anchors.right: parent.right - anchors.rightMargin: EaStyle.Sizes.tableColumnSpacing fontIcon: "plus-circle" text: qsTr("Add lamella") width: buttonWidth @@ -133,16 +126,14 @@ EaElements.GroupColumn { readonly property int selectedRow: lamellae.selectedIndexes.length > 0 ? lamellae.selectedIndexes[0].row : -1 readonly property bool selectedSymmetric: { - // Touch the revision token so this binding re-evaluates when - // the selected row's symmetric flag changes. + // Re-evaluate when the selected row's symmetric flag changes. void Globals.BackendWrapper.lamellaeItemsRevision return selectedRow >= 0 && selectedRow < Globals.BackendWrapper.lamellaeItems.count ? Globals.BackendWrapper.lamellaeItems.get(selectedRow).symmetric : true } readonly property var selectedInnerFractionsModel: { - // Touch the revision token so this binding re-evaluates when - // the per-lamella Fractions arrays are rebuilt. + // Re-evaluate when the per-lamella Fractions arrays are rebuilt. void Globals.BackendWrapper.lamellaeFractionsRevision return selectedRow >= 0 ? Globals.BackendWrapper.lamellaeInnerFractionsModelAt(selectedRow) : null } @@ -161,7 +152,6 @@ EaElements.GroupColumn { : qsTr("Lamella %1 Inner Leaflet Fractions").arg(fractionsSection.selectedRow + 1) } Local.Fractions { - id: innerFractions fractionsModel: fractionsSection.selectedInnerFractionsModel } } @@ -175,7 +165,6 @@ EaElements.GroupColumn { text: qsTr("Lamella %1 Outer Leaflet Fractions").arg(fractionsSection.selectedRow + 1) } Local.Fractions { - id: outerFractions fractionsModel: fractionsSection.selectedOuterFractionsModel } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml index a546a26..0becae0 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/LatticeStructure.qml @@ -7,11 +7,9 @@ import QtQuick.Controls import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals - EaElements.GroupColumn { Row { @@ -20,24 +18,24 @@ EaElements.GroupColumn { EaElements.Parameter { width: parent.itemWidth - title: qsTr('Alpha') - units: '⚬' + title: qsTr("Alpha") + units: "⚬" validator: DoubleValidator { bottom: 0; top: 360 } text: Globals.BackendWrapper.latticeStructure.alpha onEditingFinished: Globals.BackendWrapper.latticeStructure.alpha = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Theta') - units: '⚬' + title: qsTr("Theta") + units: "⚬" validator: DoubleValidator { bottom: 0; top: 180 } text: Globals.BackendWrapper.latticeStructure.theta onEditingFinished: Globals.BackendWrapper.latticeStructure.theta = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Sbuff') - units: 'nm' + title: qsTr("Sbuff") + units: "nm" validator: DoubleValidator { bottom: 0 } text: Globals.BackendWrapper.latticeStructure.sbuff onEditingFinished: Globals.BackendWrapper.latticeStructure.sbuff = parseFloat(text) @@ -48,132 +46,40 @@ EaElements.GroupColumn { property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.2 spacing: (EaStyle.Sizes.sideBarContentWidth - (itemWidth * 4)) / 3 - Column { width: parent.itemWidth EaElements.Label { enabled: false - text: qsTr('Type') + text: qsTr("Type") } EaElements.ComboBox { width: parent.width model: Globals.BackendWrapper.latticeStructure.latticeTypes - Component.onCompleted: { - currentIndex = model.indexOf(Globals.BackendWrapper.latticeStructure.latticeType) - } - onActivated: (i) => { - Globals.BackendWrapper.latticeStructure.latticeType = model[i] - } + Component.onCompleted: currentIndex = model.indexOf(Globals.BackendWrapper.latticeStructure.latticeType) + onActivated: (i) => Globals.BackendWrapper.latticeStructure.latticeType = model[i] } } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Nlatx') + title: qsTr("Nlatx") validator: IntValidator { bottom: 1 } text: Globals.BackendWrapper.latticeStructure.nlatx onEditingFinished: Globals.BackendWrapper.latticeStructure.nlatx = parseInt(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Nlaty') + title: qsTr("Nlaty") validator: IntValidator { bottom: 1 } text: Globals.BackendWrapper.latticeStructure.nlaty onEditingFinished: Globals.BackendWrapper.latticeStructure.nlaty = parseInt(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Nlatz') + title: qsTr("Nlatz") validator: IntValidator { bottom: 1 } text: Globals.BackendWrapper.latticeStructure.nlatz onEditingFinished: Globals.BackendWrapper.latticeStructure.nlatz = parseInt(text) } - - } - - Column { - width: parent.width - - EaElements.Label { - enabled: false - text: qsTr('Substructure') - } - - EaComponents.ListView { - id: loadedSubstructure - defaultInfoText: qsTr('Load or create a substructure') - multiSelection: false - - columnWidths: [ - EaStyle.Sizes.fontPixelSize * 12, - EaStyle.Sizes.fontPixelSize * 7, - -1 - ] - - header: EaComponents.ListViewHeader { - EaComponents.TableViewLabel { - text: qsTr('Name') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('Type') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('Description') - color: EaStyle.Colors.themeForegroundMinor - } - } - - model: Globals.BackendWrapper.latticeSubstructureLoaded - - delegate: EaComponents.ListViewDelegate { - required property var modelData - required property int index - - EaComponents.TableViewLabel { - text: modelData ? modelData.name : '' - enabled: false - } - EaComponents.TableViewLabel { - text: modelData ? modelData.structure_type : '' - enabled: false - } - EaComponents.TableViewLabel { - text: modelData ? modelData.description : '' - enabled: false - } - } - } - } - - Row { - id: buttonRow - property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 2 - spacing: EaStyle.Sizes.fontPixelSize - - EaElements.SideBarButton { - fontIcon: 'upload' - text: qsTr('Load structure') - width: buttonRow.buttonWidth - onClicked: loadExistingSubstructureLoader.item.open() - } - - EaElements.SideBarButton { - fontIcon: 'plus-circle' - text: qsTr('Import structure') - width: buttonRow.buttonWidth - onClicked: createNewSubstructureLoader.item.open() - } - } - - Loader { - id: loadExistingSubstructureLoader - source: '../Popups/LoadExistingSubstructure.qml' - } - - Loader { - id: createNewSubstructureLoader - source: '../Popups/CreateNewSubstructure.qml' } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml index 59e3643..7b8a422 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Layers.qml @@ -5,18 +5,16 @@ import QtQuick import QtQuick.Controls -import EasyApplication.Gui.Globals as EaGlobals import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals import "../Components" as Local EaElements.GroupColumn { - property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 + property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize * 2) / 3 EaComponents.ListView { id: layers @@ -36,7 +34,7 @@ EaElements.GroupColumn { text: qsTr("№") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel {} EaComponents.TableViewLabel { text: qsTr("Dmin, nm") color: EaStyle.Colors.themeForegroundMinor @@ -61,7 +59,7 @@ EaElements.GroupColumn { text: index + 1 enabled: false } - EaComponents.TableViewLabel {} // filler + EaComponents.TableViewLabel {} EaComponents.ListViewTextInput { text: dmin onEditingFinished: Globals.BackendWrapper.layersSetDmin(index, parseFloat(text)) @@ -73,27 +71,24 @@ EaElements.GroupColumn { validator: DoubleValidator { bottom: 0.25 } } EaComponents.TableViewButton { - id: deleteRowColumn fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this layer") onClicked: Globals.BackendWrapper.layersRemove(index) } } } - Item { - width: layers.width - height: addLayerButton.height + + Grid { + columns: 1 EaElements.SideBarButton { - id: addLayerButton - anchors.right: parent.right - anchors.rightMargin: EaStyle.Sizes.tableColumnSpacing fontIcon: "plus-circle" text: qsTr("Add layer") width: buttonWidth onClicked: Globals.BackendWrapper.layersAppend({ dmin: 0.25, rmin: 0.5 }) } } + Column { id: fractionsSection visible: layers.selectedIndexes.length > 0 @@ -101,19 +96,16 @@ EaElements.GroupColumn { readonly property int selectedRow: layers.selectedIndexes.length > 0 ? layers.selectedIndexes[0].row : -1 readonly property var selectedFractionsModel: { - // Touch the revision token so this binding re-evaluates when - // the per-layer Fractions array is rebuilt. + // Re-evaluate when the per-layer Fractions array is rebuilt. void Globals.BackendWrapper.layersFractionsRevision return selectedRow >= 0 ? Globals.BackendWrapper.layersFractionsModelAt(selectedRow) : null } EaElements.Label { - id: layerFractionsLabel enabled: false text: qsTr("Layer %1 Fractions").arg(fractionsSection.selectedRow >= 0 ? fractionsSection.selectedRow + 1 : 1) } Local.Fractions { - id: fractions fractionsModel: fractionsSection.selectedFractionsModel } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml index 45c5b40..5f0d93c 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/MonolayerStructure.qml @@ -12,7 +12,6 @@ import Gui.Globals as Globals import "../Components" as Local - EaElements.GroupColumn { Row { @@ -21,23 +20,23 @@ EaElements.GroupColumn { EaElements.Parameter { width: parent.itemWidth - title: qsTr('Zsep') - units: 'nm' + title: qsTr("Zsep") + units: "nm" validator: DoubleValidator { bottom: 0 } text: Globals.BackendWrapper.monolayerStructure.zsep onEditingFinished: Globals.BackendWrapper.monolayerStructure.zsep = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Nside') - text: Globals.BackendWrapper.monolayerStructure.nside + title: qsTr("Nside") validator: IntValidator { bottom: 1 } + text: Globals.BackendWrapper.monolayerStructure.nside onEditingFinished: Globals.BackendWrapper.monolayerStructure.nside = parseInt(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Dmin') - units: 'nm' + title: qsTr("Dmin") + units: "nm" validator: DoubleValidator { bottom: 0.5 } text: Globals.BackendWrapper.monolayerStructure.dmin onEditingFinished: Globals.BackendWrapper.monolayerStructure.dmin = parseFloat(text) @@ -49,12 +48,8 @@ EaElements.GroupColumn { EaElements.Label { enabled: false - text: qsTr('Monolayer Fractions') - } - - Local.Fractions { - id: monolayerFractions + text: qsTr("Monolayer Fractions") } + Local.Fractions {} } - } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml index 8c44e0a..8b5fe6c 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RingStructure.qml @@ -12,7 +12,6 @@ import Gui.Globals as Globals import "../Components" as Local - EaElements.GroupColumn { Row { @@ -21,51 +20,52 @@ EaElements.GroupColumn { EaElements.Parameter { width: parent.itemWidth - title: qsTr('Dmin') - units: 'nm' + title: qsTr("Dmin") + units: "nm" validator: DoubleValidator { bottom: 0.5 } text: Globals.BackendWrapper.ringStructure.dmin onEditingFinished: Globals.BackendWrapper.ringStructure.dmin = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Rmin') - units: 'nm' + title: qsTr("Rmin") + units: "nm" validator: DoubleValidator { bottom: 0.25 } text: Globals.BackendWrapper.ringStructure.rmin onEditingFinished: Globals.BackendWrapper.ringStructure.rmin = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Alpha') - text: Globals.BackendWrapper.ringStructure.alpha + title: qsTr("Alpha") + units: "⚬" validator: DoubleValidator { bottom: 0; top: 360 } - units: '⚬' + text: Globals.BackendWrapper.ringStructure.alpha onEditingFinished: Globals.BackendWrapper.ringStructure.alpha = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Theta') - text: Globals.BackendWrapper.ringStructure.theta + title: qsTr("Theta") + units: "⚬" validator: DoubleValidator { bottom: 0; top: 180 } - units: '⚬' + text: Globals.BackendWrapper.ringStructure.theta onEditingFinished: Globals.BackendWrapper.ringStructure.theta = parseFloat(text) } } + Row { spacing: EaStyle.Sizes.fontPixelSize EaElements.CheckBox { width: EaStyle.Sizes.sideBarContentWidth * 0.15 height: EaStyle.Sizes.fontPixelSize * 3 - text: qsTr('Fxz') + text: qsTr("Fxz") checked: Globals.BackendWrapper.ringStructure.fxz onToggled: Globals.BackendWrapper.ringStructure.fxz = checked } EaElements.CheckBox { width: EaStyle.Sizes.sideBarContentWidth * 0.15 height: EaStyle.Sizes.fontPixelSize * 3 - text: qsTr('Rev') + text: qsTr("Rev") checked: Globals.BackendWrapper.ringStructure.rev onToggled: Globals.BackendWrapper.ringStructure.rev = checked } @@ -76,14 +76,8 @@ EaElements.GroupColumn { EaElements.Label { enabled: false - text: qsTr('Ring Fractions') - } - - Local.Fractions { - id: ringFractions + text: qsTr("Ring Fractions") } + Local.Fractions {} } - - - } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml index 9974c71..6f7cb11 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/RodStructure.qml @@ -12,7 +12,6 @@ import Gui.Globals as Globals import "../Components" as Local - EaElements.GroupColumn { Row { @@ -21,25 +20,25 @@ EaElements.GroupColumn { EaElements.Parameter { width: parent.itemWidth - title: qsTr('Dmin') - units: 'nm' + title: qsTr("Dmin") + units: "nm" validator: DoubleValidator { bottom: 0.5 } text: Globals.BackendWrapper.rodStructure.dmin onEditingFinished: Globals.BackendWrapper.rodStructure.dmin = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Rmin') - units: 'nm' + title: qsTr("Rmin") + units: "nm" validator: DoubleValidator { bottom: 0.25 } text: Globals.BackendWrapper.rodStructure.rmin onEditingFinished: Globals.BackendWrapper.rodStructure.rmin = parseFloat(text) } EaElements.Parameter { width: parent.itemWidth - title: qsTr('Turns') - text: Globals.BackendWrapper.rodStructure.turns + title: qsTr("Turns") validator: IntValidator { bottom: 1 } + text: Globals.BackendWrapper.rodStructure.turns onEditingFinished: Globals.BackendWrapper.rodStructure.turns = parseInt(text) } } @@ -50,14 +49,14 @@ EaElements.GroupColumn { EaElements.CheckBox { width: EaStyle.Sizes.sideBarContentWidth * 0.15 height: EaStyle.Sizes.fontPixelSize * 3 - text: qsTr('Fxz') + text: qsTr("Fxz") checked: Globals.BackendWrapper.rodStructure.fxz onToggled: Globals.BackendWrapper.rodStructure.fxz = checked } EaElements.CheckBox { width: EaStyle.Sizes.sideBarContentWidth * 0.15 height: EaStyle.Sizes.fontPixelSize * 3 - text: qsTr('Rev') + text: qsTr("Rev") checked: Globals.BackendWrapper.rodStructure.rev onToggled: Globals.BackendWrapper.rodStructure.rev = checked } @@ -68,12 +67,8 @@ EaElements.GroupColumn { EaElements.Label { enabled: false - text: qsTr('Rod Fractions') - } - - Local.Fractions { - id: rodFractions + text: qsTr("Rod Fractions") } + Local.Fractions {} } - } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml index f8cf33e..0c801aa 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/SampleModel.qml @@ -4,28 +4,25 @@ import QtQuick import QtQuick.Controls +import QtQuick.Dialogs -import EasyApplication.Gui.Globals as EaGlobals import EasyApplication.Gui.Style as EaStyle import EasyApplication.Gui.Elements as EaElements import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Logic as EaLogic import Gui.Globals as Globals EaElements.GroupColumn { - id: root - property double buttonWidth: EaStyle.Sizes.sideBarContentWidth * 0.3164 - - Component.onCompleted: Globals.References.pages.samplemodel.sidebar.basic.groups.sampleModel = root + property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 2 EaComponents.ListView { id: loadedSampleModel - defaultInfoText: qsTr("Get started by loading or creating a model") + defaultInfoText: qsTr("Get started by loading or importing a model") multiSelection: false columnWidths: [ - EaStyle.Sizes.fontPixelSize * 12, + EaStyle.Sizes.fontPixelSize * 10, + EaStyle.Sizes.fontPixelSize * 6, EaStyle.Sizes.fontPixelSize * 7, -1 ] @@ -39,6 +36,10 @@ EaElements.GroupColumn { text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor } + EaComponents.TableViewLabel { + text: qsTr("Shape") + color: EaStyle.Colors.themeForegroundMinor + } EaComponents.TableViewLabel { text: qsTr("Description") color: EaStyle.Colors.themeForegroundMinor @@ -52,61 +53,56 @@ EaElements.GroupColumn { required property int index EaComponents.ListViewTextInput { - text: modelData ? modelData.name : '' - onEditingFinished: Globals.BackendWrapper.sampleModelUpdateField('name', text) + text: modelData ? modelData.name : "" + onEditingFinished: Globals.BackendWrapper.sampleModelUpdateField("name", text) } EaComponents.TableViewComboBox { + horizontalAlignment: Text.AlignHCenter + model: Globals.BackendWrapper.sampleModelTypes + Component.onCompleted: currentIndex = model.indexOf(modelData.type) + onActivated: (i) => Globals.BackendWrapper.sampleModelUpdateField("type", model[i]) + } + EaComponents.TableViewComboBox { + horizontalAlignment: Text.AlignHCenter model: Globals.BackendWrapper.sampleModelStructureTypes - Component.onCompleted: { - currentIndex = model.indexOf(modelData.structure_type) - } - onActivated: (i) => { - Globals.BackendWrapper.sampleModelUpdateField('structure_type', model[i]) - } + Component.onCompleted: currentIndex = model.indexOf(modelData.structure_type) + onActivated: (i) => Globals.BackendWrapper.sampleModelUpdateField("structure_type", model[i]) } EaComponents.ListViewTextInput { - text: modelData ? modelData.description : '' - onEditingFinished: Globals.BackendWrapper.sampleModelUpdateField('description', text) + text: modelData ? modelData.description : "" + onEditingFinished: Globals.BackendWrapper.sampleModelUpdateField("description", text) } } } Grid { - columns: 3 + columns: 2 spacing: EaStyle.Sizes.fontPixelSize EaElements.SideBarButton { - fontIcon: "upload" + fontIcon: "file-import" text: qsTr("Load model") width: buttonWidth onClicked: loadExistingModelLoader.item.open() } EaElements.SideBarButton { - fontIcon: "plus-circle" - text: qsTr("Create model") - width: buttonWidth - onClicked: createNewModelLoader.item.open() - } - - EaElements.SideBarButton { - fontIcon: "download" - text: qsTr("Save model") + fontIcon: "upload" + text: qsTr("Import model") width: buttonWidth - enabled: Globals.BackendWrapper.sampleModelLoaded.length > 0 - && Globals.BackendWrapper.sampleModelLoaded[0].name !== "" - onClicked: Globals.BackendWrapper.sampleModelSaveToCatalog() + onClicked: importModelFolderDialog.open() } } Loader { id: loadExistingModelLoader - source: '../Popups/LoadExistingModel.qml' + source: "../Popups/LoadExistingModel.qml" } - Loader { - id: createNewModelLoader - source: '../Popups/CreateNewModel.qml' + // Folder-based import: Qt's file dialogs can't accept a folder and a file at once. + FolderDialog { + id: importModelFolderDialog + title: qsTr("Import a model from a directory") + onAccepted: console.debug(`Import model from folder '${selectedFolder}'`) } - } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml deleted file mode 100644 index 5f3bb0f..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/Solution.qml +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents - -import Gui.Globals as Globals - - -EaElements.GroupColumn { - id: root - property double buttonWidth: (EaStyle.Sizes.sideBarContentWidth - (EaStyle.Sizes.fontPixelSize * 2) ) / 3 - - Column { - width: EaStyle.Sizes.sideBarContentWidth * 0.5 - - EaElements.Label { - enabled: false - text: qsTr('Solvent') - } - - EaComponents.ListView { - id: loadedSolvent - defaultInfoText: qsTr('Load or import a solvent') - multiSelection: false - - columnWidths: [ - EaStyle.Sizes.fontPixelSize * 7, - EaStyle.Sizes.fontPixelSize * 8, - -1 - ] - - header: EaComponents.ListViewHeader { - EaComponents.TableViewLabel { - text: qsTr('Name') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('Type') - color: EaStyle.Colors.themeForegroundMinor - } - EaComponents.TableViewLabel { - text: qsTr('Description') - color: EaStyle.Colors.themeForegroundMinor - } - } - - model: Globals.BackendWrapper.solutionLoaded - - delegate: EaComponents.ListViewDelegate { - required property var modelData - required property int index - - EaComponents.ListViewTextInput { - text: modelData ? modelData.name : '' - onEditingFinished: Globals.BackendWrapper.solventUpdateField('name', text) - } - EaComponents.TableViewComboBox { - model: Globals.BackendWrapper.solventTypes - Component.onCompleted: { - currentIndex = model.indexOf(modelData.solvent_type) - } - onActivated: (i) => { - Globals.BackendWrapper.solventUpdateField('solvent_type', model[i]) - } - } - EaComponents.ListViewTextInput { - text: modelData ? modelData.description : '' - onEditingFinished: Globals.BackendWrapper.solventUpdateField('description', text) - } - } - } - } - - Grid { - columns: 3 - spacing: EaStyle.Sizes.fontPixelSize - - EaElements.SideBarButton { - fontIcon: 'upload' - text: qsTr('Load solvent') - width: buttonWidth - onClicked: loadExistingSolvent.item.open() - } - - EaElements.SideBarButton { - fontIcon: 'plus-circle' - text: qsTr('Import solvent') - width: buttonWidth - onClicked: createNewSolvent.item.open() - } - - EaElements.SideBarButton { - fontIcon: 'download' - text: qsTr('Save solvent') - width: buttonWidth - enabled: Globals.BackendWrapper.solventLoaded.length > 0 - && Globals.BackendWrapper.solventLoaded[0].name !== "" - onClicked: Globals.BackendWrapper.solventSaveToCatalog() - } - } - - Row { - spacing: EaStyle.Sizes.fontPixelSize - - Column { - width: EaStyle.Sizes.sideBarContentWidth / 3 - spacing: EaStyle.Sizes.fontPixelSize - - EaElements.Label { - enabled: false - text: qsTr('Ions') - } - - Row { - id: ionChipRow - spacing: EaStyle.Sizes.fontPixelSize * 0.5 - height: EaStyle.Sizes.fontPixelSize * 2 - width: buttonWidth - - EaElements.Label { - visible: ionChipRow.ionCount === 0 - enabled: false - text: qsTr('No ions loaded') - anchors.verticalCenter: parent.verticalCenter - } - - readonly property int ionCount: - Globals.BackendWrapper.ionsLoaded - ? Globals.BackendWrapper.ionsLoaded.count - : 0 - - Repeater { - model: Globals.BackendWrapper.ionsLoaded - - delegate: Rectangle { - required property int index - required property string name - - height: ionChipRow.height - width: EaStyle.Sizes.fontPixelSize - + chipLabel.implicitWidth - + EaStyle.Sizes.fontPixelSize - + chipRemove.width - + EaStyle.Sizes.fontPixelSize * 0.6 - radius: height / 2 - color: EaStyle.Colors.appBarBackground - border.color: EaStyle.Colors.appBarComboBoxBorder - border.width: 1 - - Row { - anchors.fill: parent - anchors.leftMargin: EaStyle.Sizes.fontPixelSize - anchors.rightMargin: chipRemove.width + EaStyle.Sizes.fontPixelSize * 0.25 - spacing: EaStyle.Sizes.fontPixelSize * 0.25 - - EaElements.Label { - id: chipLabel - text: name - anchors.verticalCenter: parent.verticalCenter - } - } - - EaElements.Label { - id: chipRemove - text: '×' - font.pixelSize: EaStyle.Sizes.fontPixelSize * 1.4 - color: chipRemoveArea.containsMouse - ? EaStyle.Colors.themeForegroundHovered - : EaStyle.Colors.themeForegroundMinor - anchors.right: parent.right - anchors.rightMargin: EaStyle.Sizes.fontPixelSize * 0.6 - anchors.verticalCenter: parent.verticalCenter - - MouseArea { - id: chipRemoveArea - anchors.fill: parent - anchors.margins: -EaStyle.Sizes.fontPixelSize * 0.3 - hoverEnabled: true - cursorShape: Qt.PointingHandCursor - onClicked: Globals.BackendWrapper.ionsRemove(index) - } - - EaElements.ToolTip { - text: qsTr('Remove this ion') - visible: chipRemoveArea.containsMouse - && EaGlobals.Vars.showToolTips - } - } - } - } - } - - EaElements.SideBarButton { - fontIcon: 'upload' - text: qsTr('Load ion') - width: buttonWidth - enabled: ionChipRow.ionCount < 2 - onClicked: loadExistingIonLoader.item.open() - } - } - - Column { - width: EaStyle.Sizes.sideBarContentWidth / 3 - spacing: EaStyle.Sizes.fontPixelSize - - Column { - width: parent.width - - EaElements.Label { - enabled: false - text: qsTr('Amount') - } - EaElements.TextField { - id: ionAmountField - width: parent.width - enabled: !matchStructureCheck.checked - placeholderText: qsTr('Set the amount of selected ions') - validator: IntValidator { bottom: 0 } - } - } - EaElements.CheckBox { - id: matchStructureCheck - text: qsTr('Match components') - checked: false - onCheckedChanged: { - if (checked) { - ionAmountField.text = '' - ionAmountField.placeholderText = 'Will match the structure components' - } - else ionAmountField.placeholderText = 'Set the amount of ions' - } - } - } - } - - Loader { - id: loadExistingSolvent - source: '../Popups/LoadExistingSolvent.qml' - } - Loader { - id: createNewSolvent - source: '../Popups/CreateNewSolvent.qml' - } - Loader { - id: loadExistingIonLoader - source: '../Popups/LoadExistingIon.qml' - } -} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml index 829e7df..d63d240 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Groups/VesicleStructure.qml @@ -10,42 +10,39 @@ import EasyApplication.Gui.Elements as EaElements import Gui.Globals as Globals - EaElements.GroupColumn { - Row { - property real itemWidth: EaStyle.Sizes.sideBarContentWidth * 0.2 - spacing: EaStyle.Sizes.fontPixelSize * 2 - - Column { - width: parent.itemWidth - - EaElements.Label { - enabled: false - text: qsTr('Fill') - } - EaElements.ComboBox { - width: parent.width - model: ['FIBO', 'RINGS', 'RINGS0'] - Component.onCompleted: { - currentIndex = model.indexOf(Globals.BackendWrapper.ballStructure.fill) - } - onActivated: (i) => { - Globals.BackendWrapper.ballStructure.fill = model[i] - } - } + Column { + width: EaStyle.Sizes.sideBarContentWidth * 0.2 + + EaElements.Label { + enabled: false + text: qsTr("Fill") + } + EaElements.ComboBox { + width: parent.width + model: ["FIBO", "RINGS", "RINGS0"] + Component.onCompleted: currentIndex = model.indexOf(Globals.BackendWrapper.vesicleStructure.fill) + onActivated: (i) => Globals.BackendWrapper.vesicleStructure.fill = model[i] } + } + + Row { + spacing: EaStyle.Sizes.fontPixelSize + EaElements.CheckBox { - height: EaStyle.Sizes.fontPixelSize * 5 - text: qsTr('Fxz') - checked: Globals.BackendWrapper.ballStructure.fxz - onToggled: Globals.BackendWrapper.ballStructure.fxz = checked + width: EaStyle.Sizes.sideBarContentWidth * 0.15 + height: EaStyle.Sizes.fontPixelSize * 3 + text: qsTr("Fxz") + checked: Globals.BackendWrapper.vesicleStructure.fxz + onToggled: Globals.BackendWrapper.vesicleStructure.fxz = checked } EaElements.CheckBox { - height: EaStyle.Sizes.fontPixelSize * 5 - text: qsTr('Rev') - checked: Globals.BackendWrapper.ballStructure.rev - onToggled: Globals.BackendWrapper.ballStructure.rev = checked + width: EaStyle.Sizes.sideBarContentWidth * 0.15 + height: EaStyle.Sizes.fontPixelSize * 3 + text: qsTr("Rev") + checked: Globals.BackendWrapper.vesicleStructure.rev + onToggled: Globals.BackendWrapper.vesicleStructure.rev = checked } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml index 400bc4c..9431243 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Layout.qml @@ -11,101 +11,100 @@ import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals - EaComponents.SideBarColumn { EaElements.GroupBox { - title: qsTr('Model Definition') - icon: 'tag' + title: qsTr("Model Definition") + icon: "tag" collapsed: false - Loader { id: sampleModelLoader; source: 'Groups/SampleModel.qml' } + Loader { source: "Groups/SampleModel.qml" } } EaElements.GroupBox { - title: qsTr('Components') - icon: 'puzzle-piece' + title: qsTr("Components") + icon: "puzzle-piece" - Loader { source: 'Groups/Components.qml' } + Loader { source: "Groups/Components.qml" } } EaElements.GroupBox { - title: qsTr('Ring Structure Definition') - icon: 'vector-square' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Ring' + title: qsTr("Ring Structure Definition") + icon: "project-diagram" + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === "Ring" - Loader { source: 'Groups/RingStructure.qml' } + Loader { source: "Groups/RingStructure.qml" } } EaElements.GroupBox { - title: qsTr('Ball Structure Definition') - icon: 'vector-square' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Ball' + title: qsTr("Ball Structure Definition") + icon: "project-diagram" + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === "Ball" - Loader { source: 'Groups/BallStructure.qml' } + Loader { source: "Groups/BallStructure.qml" } } EaElements.GroupBox { - title: qsTr('Vesicle Structure Definition') - icon: 'vector-square' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Vesicle' + title: qsTr("Vesicle Structure Definition") + icon: "project-diagram" + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === "Vesicle" - Loader { source: 'Groups/VesicleStructure.qml' } + Loader { source: "Groups/VesicleStructure.qml" } } EaElements.GroupBox { - title: qsTr('Rod Structure Definition') - icon: 'vector-square' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Rod' + title: qsTr("Rod Structure Definition") + icon: "project-diagram" + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === "Rod" - Loader { source: 'Groups/RodStructure.qml' } + Loader { source: "Groups/RodStructure.qml" } } EaElements.GroupBox { - title: qsTr('Bilayer Structure Definition') - icon: 'vector-square' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Bilayer' + title: qsTr("Bilayer Structure Definition") + icon: "project-diagram" + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === "Bilayer" - Loader { source: 'Groups/BilayerStructure.qml' } + Loader { source: "Groups/BilayerStructure.qml" } } EaElements.GroupBox { - title: qsTr('Monolayer Structure Definition') - icon: 'vector-square' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Monolayer' + title: qsTr("Monolayer Structure Definition") + icon: "project-diagram" + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === "Monolayer" - Loader { source: 'Groups/MonolayerStructure.qml' } + Loader { source: "Groups/MonolayerStructure.qml" } } EaElements.GroupBox { - title: qsTr('Lattice Structure Definition') - icon: 'vector-square' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Lattice' + title: qsTr("Layers") + icon: "layer-group" + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === "Ball" - Loader { source: 'Groups/LatticeStructure.qml' } + Loader { source: "Groups/Layers.qml" } } EaElements.GroupBox { - title: qsTr('Layers') - icon: 'layer-group' // 'grip-lines' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Ball' + title: qsTr("Lamellae") + icon: "layer-group" + visible: Globals.BackendWrapper.sampleModelCurrentStructureType === "Vesicle" - Loader { source: 'Groups/Layers.qml' } + Loader { source: "Groups/Lamellae.qml" } } EaElements.GroupBox { - title: qsTr('Lamellae') - icon: 'layer-group' - visible: Globals.BackendWrapper.sampleModelCurrentStructureType === 'Vesicle' + title: qsTr("Lattice Parameters") + icon: "vector-square" + visible: Globals.BackendWrapper.sampleModelCurrentType === "Lattice" - Loader { source: 'Groups/Lamellae.qml' } + Loader { source: "Groups/LatticeStructure.qml" } } EaElements.GroupBox { - title: qsTr('Solution') - icon: 'flask' + title: qsTr("Buffer") + icon: "flask" - Loader { source: 'Groups/Solution.qml' } + Loader { source: "Groups/Buffer.qml" } } // Centered "Assemble" call-to-action below the groups. @@ -116,12 +115,11 @@ EaComponents.SideBarColumn { EaElements.SideBarButton { id: assembleButton anchors.centerIn: parent - text: qsTr('Assemble') - fontIcon: 'cubes' + text: qsTr("Assemble") + fontIcon: "cubes" width: EaStyle.Sizes.sideBarContentWidth enabled: Globals.BackendWrapper.sampleModelLoaded.length > 0 - onClicked: console.debug('Assemble clicked') + onClicked: console.debug("Assemble clicked") } } - } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml index cf82267..cfc4383 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewComponent.qml @@ -26,7 +26,7 @@ EaElements.Dialog{ Globals.BackendWrapper.componentsAppend({ name: componentNameField.text, component_type: sampleModelTypeField.currentText, - atoms: 42, + c_ion: "", mint: 0, mext: 0 }) @@ -124,8 +124,8 @@ EaElements.Dialog{ } Column { EaElements.SideBarButton { - fontIcon: 'upload' - text: qsTr('Add files') + fontIcon: "upload" + text: qsTr("Add files") width: componentCreationDialog.width * 0.3164 onClicked: { @@ -134,7 +134,7 @@ EaElements.Dialog{ } Loader { - source: '../Popups/OpenAssetFile.qml' + source: "../Popups/OpenAssetFile.qml" } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml deleted file mode 100644 index 39a6fe4..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewModel.qml +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls -import QtQuick.Dialogs - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Elements as EaElements - -import Gui.Globals as Globals - - -EaElements.Dialog{ - id: sampleModelCreationDialog - - property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - - title: qsTr("Create a new Sample Model") - standardButtons: Dialog.Ok | Dialog.Cancel - - onAccepted: { - Globals.BackendWrapper.sampleModelSetLoaded({ - name: sampleModelNameField.text, - structure_type: sampleModelTypeField.currentText, - description: sampleModelDescrField.text - }) - } - - Column { - spacing: EaStyle.Sizes.fontPixelSize - - Row { - id: nameTypeRow - spacing: EaStyle.Sizes.fontPixelSize - - Column { - EaElements.Label { - enabled: false - text: qsTr("Name") - } - EaElements.TextField { - id: sampleModelNameField - implicitWidth: (sampleModelCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 - horizontalAlignment: TextInput.AlignLeft - validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } - placeholderText: qsTr("(optional) Enter Sample Model name here") - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("Structure type") - } - EaElements.ComboBox { - id: sampleModelTypeField - implicitWidth: (sampleModelCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 - model: Globals.BackendWrapper.sampleModelStructureTypes - } - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("Description") - } - EaElements.TextField { - id: sampleModelDescrField - implicitWidth: sampleModelCreationDialog.inputFieldWidth - horizontalAlignment: TextInput.AlignLeft - validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } - placeholderText: qsTr("(optional) Enter Sample Model description here") - } - } - } -} - diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml deleted file mode 100644 index 0855279..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSolvent.qml +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls -import QtQuick.Dialogs - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Elements as EaElements - -import Gui.Globals as Globals - - -EaElements.Dialog { - id: solventCreationDialog - - property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - - title: qsTr("Import a new Solvent") - standardButtons: Dialog.Ok | Dialog.Cancel - - onAccepted: { - Globals.BackendWrapper.solventSetLoaded({ - name: solventNameField.text, - solvent_type: solventTypeField.currentText, - description: solventDescrField.text, - file_path: solventFileField.text - }) - } - - Column { - spacing: EaStyle.Sizes.fontPixelSize - - Row { - id: nameTypeRow - spacing: EaStyle.Sizes.fontPixelSize - - Column { - EaElements.Label { - enabled: false - text: qsTr("Name") - } - EaElements.TextField { - id: solventNameField - implicitWidth: (solventCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 - horizontalAlignment: TextInput.AlignLeft - validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.\/]{1,30}$/ } - placeholderText: qsTr("(optional) Enter Solvent name here") - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("Solvent type") - } - EaElements.ComboBox { - id: solventTypeField - implicitWidth: (solventCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 - model: Globals.BackendWrapper.solventTypes - } - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("Description") - } - EaElements.TextField { - id: solventDescrField - implicitWidth: solventCreationDialog.inputFieldWidth - horizontalAlignment: TextInput.AlignLeft - placeholderText: qsTr("(optional) Enter Solvent description here") - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("File") - } - - Row { - id: fileRow - spacing: EaStyle.Sizes.fontPixelSize - - EaElements.TextField { - id: solventFileField - implicitWidth: solventCreationDialog.inputFieldWidth - - browseFileButton.width - - fileRow.spacing - horizontalAlignment: TextInput.AlignLeft - placeholderText: qsTr("(optional) Choose a single solvent file") - readOnly: true - } - - EaElements.SideBarButton { - id: browseFileButton - fontIcon: 'upload' - text: qsTr('Browse…') - width: solventCreationDialog.inputFieldWidth * 0.2 - onClicked: solventFilePicker.open() - } - } - - FileDialog { - id: solventFilePicker - fileMode: FileDialog.OpenFile - nameFilters: ['Any (*)', 'Structure files (*.gro *.pdb *.xyz)', 'Topology files (*.itp)'] - onAccepted: solventFileField.text = selectedFile - } - } - } -} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml deleted file mode 100644 index 1d7c7e4..0000000 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/CreateNewSubstructure.qml +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-FileCopyrightText: 2024 EasyApp contributors -// SPDX-License-Identifier: BSD-3-Clause -// © 2024 Contributors to the EasyApp project - -import QtQuick -import QtQuick.Controls -import QtQuick.Dialogs - -import EasyApplication.Gui.Globals as EaGlobals -import EasyApplication.Gui.Style as EaStyle -import EasyApplication.Gui.Components as EaComponents -import EasyApplication.Gui.Elements as EaElements - -import Gui.Globals as Globals - - -EaElements.Dialog{ - id: substructureCreationDialog - - property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - - title: qsTr("Create a new Substructure") - standardButtons: Dialog.Ok | Dialog.Cancel - - onAccepted: { - Globals.BackendWrapper.latticeSubstructureSetLoaded({ - name: substructureNameField.text, - structure_type: substructureTypeField.currentText, - description: substructureDescrField.text, - file_path: substructureFileField.text - }) - } - - Column { - spacing: EaStyle.Sizes.fontPixelSize - - Row { - id: nameTypeRow - spacing: EaStyle.Sizes.fontPixelSize - - Column { - EaElements.Label { - enabled: false - text: qsTr("Name") - } - EaElements.TextField { - id: substructureNameField - implicitWidth: (substructureCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 - horizontalAlignment: TextInput.AlignLeft - validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } - placeholderText: qsTr("(optional) Enter Substructure name here") - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("Structure type") - } - EaElements.ComboBox { - id: substructureTypeField - implicitWidth: (substructureCreationDialog.inputFieldWidth - nameTypeRow.spacing) / 2 - model: Globals.BackendWrapper.latticeSubstructureTypes - } - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("Description") - } - EaElements.TextField { - id: substructureDescrField - implicitWidth: substructureCreationDialog.inputFieldWidth - horizontalAlignment: TextInput.AlignLeft - validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z][a-zA-Z0-9_\-\.]{1,30}$/ } - placeholderText: qsTr("(optional) Enter Substructure description here") - } - } - - Column { - EaElements.Label { - enabled: false - text: qsTr("File") - } - - Row { - id: fileRow - spacing: EaStyle.Sizes.fontPixelSize - - EaElements.TextField { - id: substructureFileField - implicitWidth: substructureCreationDialog.inputFieldWidth - - browseFileButton.width - - fileRow.spacing - horizontalAlignment: TextInput.AlignLeft - placeholderText: qsTr("(optional) Choose a single structure file") - readOnly: true - } - - EaElements.SideBarButton { - id: browseFileButton - fontIcon: 'upload' - text: qsTr('Browse…') - width: substructureCreationDialog.inputFieldWidth * 0.2 - onClicked: substructureFilePicker.open() - } - } - - FileDialog { - id: substructureFilePicker - fileMode: FileDialog.OpenFile - nameFilters: ['Any (*)', 'Structure files (*.gro *.pdb *.xyz)', 'Topology files (*.itp)'] - onAccepted: substructureFileField.text = selectedFile - } - } - } -} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingBufferComponent.qml similarity index 69% rename from src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml rename to src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingBufferComponent.qml index 0e0c3ff..0ed9afd 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingSolvent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingBufferComponent.qml @@ -15,25 +15,24 @@ import Gui.Globals as Globals EaElements.Dialog { - id: solventLoadDialog + id: bufferComponentLoadDialog property int inputFieldWidth: EaStyle.Sizes.fontPixelSize * 35 - title: qsTr("Load a Solvent from the Asset Library") + title: qsTr("Load Buffer Components from the Asset Library") standardButtons: Dialog.Ok | Dialog.Cancel onAccepted: { - var indexes = loadSolventListView.selectedIndexes + var indexes = loadBufferComponentListView.selectedIndexes - if (indexes.length > 0) { - var row = indexes[0].row - var item = Globals.BackendWrapper.solventAvailable.get(row) - Globals.BackendWrapper.solventSetLoaded(item) - loadSolventListView.clearSelection() + for (var i = 0; i < indexes.length; ++i) { + var item = Globals.BackendWrapper.bufferComponentsAvailable.get(indexes[i].row) + Globals.BackendWrapper.bufferComponentsAppend(item) } + loadBufferComponentListView.clearSelection() } onRejected: { - loadSolventListView.clearSelection() + loadBufferComponentListView.clearSelection() } Column { @@ -42,13 +41,12 @@ EaElements.Dialog { text: qsTr("Available in the Asset Library") } EaComponents.ListView { - id: loadSolventListView - defaultInfoText: qsTr("No solvents found") - multiSelection: false + id: loadBufferComponentListView + defaultInfoText: qsTr("No buffer components found") + multiSelection: true columnWidths: [ EaStyle.Sizes.fontPixelSize * 2.5, EaStyle.Sizes.fontPixelSize * 10, - EaStyle.Sizes.fontPixelSize * 6, -1, ] @@ -62,22 +60,17 @@ EaElements.Dialog { text: qsTr("Name") color: EaStyle.Colors.themeForegroundMinor } - EaComponents.TableViewLabel { - text: qsTr("Type") - color: EaStyle.Colors.themeForegroundMinor - } EaComponents.TableViewLabel { text: qsTr("Description") color: EaStyle.Colors.themeForegroundMinor } } - model: Globals.BackendWrapper.solventAvailable + model: Globals.BackendWrapper.bufferComponentsAvailable delegate: EaComponents.ListViewDelegate { required property int index required property string name - required property string solvent_type required property string description EaComponents.TableViewLabel { @@ -88,9 +81,6 @@ EaElements.Dialog { EaComponents.TableViewLabel { text: name } - EaComponents.TableViewLabel { - text: solvent_type - } EaComponents.TableViewLabel { text: description ToolTip.text: description diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml index 0209f82..a01c201 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingComponent.qml @@ -34,7 +34,7 @@ EaElements.Dialog{ component_type: item.component_type, mint: item.mint, mext: item.mext, - atoms: item.atoms + c_ion: item.c_ion }) } loadComponentListView.clearSelection() @@ -58,7 +58,7 @@ EaElements.Dialog{ EaStyle.Sizes.fontPixelSize * 2.5, -1, EaStyle.Sizes.fontPixelSize * 8, - EaStyle.Sizes.fontPixelSize * 6, + EaStyle.Sizes.fontPixelSize * 4.5, EaStyle.Sizes.fontPixelSize * 6, EaStyle.Sizes.fontPixelSize * 6, EaStyle.Sizes.tableRowHeight, @@ -79,7 +79,7 @@ EaElements.Dialog{ color: EaStyle.Colors.themeForegroundMinor } EaComponents.TableViewLabel { - text: qsTr("Atoms") + text: qsTr("C-ion") color: EaStyle.Colors.themeForegroundMinor horizontalAlignment: Text.AlignHCenter } @@ -97,16 +97,16 @@ EaElements.Dialog{ model: ListModel { id: availableComponentsModel - ListElement { name: "DPPC"; component_type: "Lipid"; atoms: 130; mint: 0; mext: 130 } - ListElement { name: "DOPC"; component_type: "Lipid"; atoms: 138; mint: 0; mext: 138 } - ListElement { name: "POPC"; component_type: "Lipid"; atoms: 134; mint: 0; mext: 134 } - ListElement { name: "DMPC"; component_type: "Lipid"; atoms: 118; mint: 0; mext: 118 } - ListElement { name: "Cholesterol"; component_type: "Lipid"; atoms: 74; mint: 0; mext: 74 } - ListElement { name: "SDS"; component_type: "Surfactant"; atoms: 42; mint: 0; mext: 42 } - ListElement { name: "CTAB"; component_type: "Surfactant"; atoms: 62; mint: 0; mext: 62 } - ListElement { name: "Triton-X100"; component_type: "Surfactant"; atoms: 85; mint: 0; mext: 85 } - ListElement { name: "Tween-20"; component_type: "Surfactant"; atoms: 98; mint: 0; mext: 98 } - ListElement { name: "D2O-buffer"; component_type: "Other"; atoms: 3; mint: 0; mext: 3 } + ListElement { name: "DPPC"; component_type: "Lipid"; c_ion: ""; mint: 0; mext: 130 } + ListElement { name: "DOPC"; component_type: "Lipid"; c_ion: ""; mint: 0; mext: 138 } + ListElement { name: "POPC"; component_type: "Lipid"; c_ion: ""; mint: 0; mext: 134 } + ListElement { name: "DMPC"; component_type: "Lipid"; c_ion: ""; mint: 0; mext: 118 } + ListElement { name: "Cholesterol"; component_type: "Lipid"; c_ion: ""; mint: 0; mext: 74 } + ListElement { name: "SDS"; component_type: "Surfactant"; c_ion: "Na+"; mint: 0; mext: 42 } + ListElement { name: "CTAB"; component_type: "Surfactant"; c_ion: "Br-"; mint: 0; mext: 62 } + ListElement { name: "Triton-X100"; component_type: "Surfactant"; c_ion: ""; mint: 0; mext: 85 } + ListElement { name: "Tween-20"; component_type: "Surfactant"; c_ion: ""; mint: 0; mext: 98 } + ListElement { name: "D2O-buffer"; component_type: "Other"; c_ion: ""; mint: 0; mext: 3 } } delegateModelAccess: DelegateModel.ReadOnly @@ -115,7 +115,7 @@ EaElements.Dialog{ required property int index required property string name required property string component_type - required property int atoms + required property string c_ion required property int mint required property int mext @@ -131,7 +131,7 @@ EaElements.Dialog{ text: component_type } EaComponents.TableViewLabel { - text: atoms + text: c_ion horizontalAlignment: Text.AlignHCenter } EaComponents.TableViewLabel { diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml index 73e8542..2b049b8 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml @@ -49,6 +49,7 @@ EaElements.Dialog{ columnWidths: [ EaStyle.Sizes.fontPixelSize * 2.5, EaStyle.Sizes.fontPixelSize * 10, + EaStyle.Sizes.fontPixelSize * 8, EaStyle.Sizes.fontPixelSize * 6, -1, EaStyle.Sizes.tableRowHeight, @@ -64,6 +65,10 @@ EaElements.Dialog{ text: qsTr("Name") color: EaStyle.Colors.themeForegroundMinor } + EaComponents.TableViewLabel { + text: qsTr("Shape") + color: EaStyle.Colors.themeForegroundMinor + } EaComponents.TableViewLabel { text: qsTr("Type") color: EaStyle.Colors.themeForegroundMinor @@ -81,6 +86,7 @@ EaElements.Dialog{ required property int index required property string name required property string structure_type + required property string type required property string description EaComponents.TableViewLabel { @@ -94,6 +100,9 @@ EaElements.Dialog{ EaComponents.TableViewLabel { text: structure_type } + EaComponents.TableViewLabel { + text: type + } EaComponents.TableViewLabel { text: description } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml index d22023d..f0180ea 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml @@ -14,7 +14,7 @@ import Gui.Globals as Globals FileDialog{ fileMode: FileDialog.OpenFiles - nameFilters: ['Any (*)', 'Structure files (*.gro *.pdb .*xyz)', 'Topology files (*.itp)', 'Smiles files (*.sml)'] + nameFilters: ["Any (*)", "Structure files (*.gro *.pdb .*xyz)", "Topology files (*.itp)", "Smiles files (*.sml)"] onAccepted: { for (let i = 0; i < selectedFiles.length; ++i) diff --git a/src/easyshapes_app/Gui/StatusBar.qml b/src/easyshapes_app/Gui/StatusBar.qml index 8f1b73a..0f39f5c 100644 --- a/src/easyshapes_app/Gui/StatusBar.qml +++ b/src/easyshapes_app/Gui/StatusBar.qml @@ -7,55 +7,55 @@ import QtQuick.Controls import EasyApplication.Gui.Globals as EaGlobals import EasyApplication.Gui.Elements as EaElements -import EasyApplication.Gui.Components as EaComponents import Gui.Globals as Globals - EaElements.StatusBar { visible: EaGlobals.Vars.appBarCurrentIndex !== 0 EaElements.StatusBarItem { - keyIcon: 'archive' - keyText: qsTr('Project') + keyIcon: "archive" + keyText: qsTr("Project") valueText: Globals.BackendWrapper.statusProject - ToolTip.text: qsTr('Current project') + ToolTip.text: qsTr("Current project") } EaElements.StatusBarItem { - keyIcon: 'layer-group' - keyText: qsTr('Models') - valueText: Globals.BackendWrapper.statusPhasesCount - ToolTip.text: qsTr('Number of models added') + keyIcon: "vial" + keyText: qsTr("Shape") + // Bare shape for a discrete model, or "Lattice ()" on a lattice. + valueText: { + const shape = Globals.BackendWrapper.sampleModelCurrentStructureType + if (!shape) + return "" + return Globals.BackendWrapper.sampleModelCurrentType === "Lattice" + ? qsTr("Lattice (%1)").arg(shape.toLowerCase()) + : shape + } + ToolTip.text: qsTr("Current sample model shape and lattice arrangement") } EaElements.StatusBarItem { - keyIcon: 'microscope' - keyText: qsTr('Experiments') - valueText: Globals.BackendWrapper.statusExperimentsCount - ToolTip.text: qsTr('Number of experiments added') + keyIcon: "puzzle-piece" + keyText: qsTr("Components") + valueText: "" + (Globals.BackendWrapper.componentsLoaded + ? Globals.BackendWrapper.componentsLoaded.count + : 0) + ToolTip.text: qsTr("Number of components") } EaElements.StatusBarItem { - keyIcon: 'calculator' - keyText: qsTr('Calculator') - valueText: Globals.BackendWrapper.statusCalculator - ToolTip.text: qsTr('Current calculation engine') + keyIcon: "cogs" + keyText: qsTr("Engine") + valueText: Globals.BackendWrapper.statusEngine + ToolTip.text: qsTr("Simulation engine") } EaElements.StatusBarItem { - keyIcon: 'level-down-alt' - keyText: qsTr('Minimizer') - valueText: Globals.BackendWrapper.statusMinimizer - ToolTip.text: qsTr('Current minimization engine and method') + keyIcon: "bezier-curve" + keyText: qsTr("Force field") + valueText: Globals.BackendWrapper.analysisConfigForceField + ToolTip.text: qsTr("Force field selected on the Analysis page") } - - EaElements.StatusBarItem { - keyIcon: 'th-list' - keyText: qsTr('Parameters') - valueText: Globals.BackendWrapper.statusVariables - ToolTip.text: qsTr('Number of parameters: total, free and fixed') - } - } From db1cab0404b4013639b961156f55b756afa75058 Mon Sep 17 00:00:00 2001 From: Ales Kutsepau Date: Mon, 29 Jun 2026 16:28:36 +0200 Subject: [PATCH 25/32] primitive preview for mainarea --- pixi.lock | 133 ++++----- pixi.toml | 1 + src/easyshapes_app.qrc | 32 ++- .../Backends/MockQml/Analysis.qml | 9 + .../Backends/MockQml/Components.qml | 176 ++++++++++++ .../Backends/real_py/analysis.py | 16 ++ .../Gui/Globals/BackendWrapper.qml | 5 + .../Gui/Pages/Analysis/Layout.qml | 9 +- .../Pages/Analysis/MainArea/EngineOutput.qml | 43 +++ .../Pages/Analysis/MainArea/Scattering.qml | 53 ++++ .../Pages/Analysis/Sidebar/Basic/Layout.qml | 5 +- .../Gui/Pages/SampleModel/Layout.qml | 40 ++- .../SampleModel/MainArea/BaseMol3dQuick.qml | 271 ++++++++++++++++++ .../SampleModel/MainArea/ComponentView.qml | 41 +++ .../Pages/SampleModel/MainArea/Components.qml | 94 ++++++ .../Pages/SampleModel/MainArea/Distances.qml | 42 +++ .../Pages/SampleModel/MainArea/Lattice.qml | 42 +++ .../Gui/Pages/SampleModel/MainArea/Layers.qml | 43 +++ .../Gui/Resources/Images/scattering.jpg | Bin 0 -> 578022 bytes src/easyshapes_app/Gui/Resources/molecule.pdb | 135 +++++++++ 20 files changed, 1108 insertions(+), 82 deletions(-) create mode 100644 src/easyshapes_app/Gui/Pages/Analysis/MainArea/EngineOutput.qml create mode 100644 src/easyshapes_app/Gui/Pages/Analysis/MainArea/Scattering.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/MainArea/BaseMol3dQuick.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/MainArea/ComponentView.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Components.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Distances.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Lattice.qml create mode 100644 src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Layers.qml create mode 100644 src/easyshapes_app/Gui/Resources/Images/scattering.jpg create mode 100644 src/easyshapes_app/Gui/Resources/molecule.pdb diff --git a/pixi.lock b/pixi.lock index c68fef6..4bb7870 100644 --- a/pixi.lock +++ b/pixi.lock @@ -5,6 +5,8 @@ environments: - url: https://conda.anaconda.org/conda-forge/ indexes: - https://pypi.org/simple + options: + pypi-prerelease-mode: if-necessary-or-explicit packages: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 @@ -78,7 +80,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/41/04e2a649058b0713b00d6c9bd22da35618bb157289e05d068e51fddf8d7e/dunamai-1.25.0-py3-none-any.whl - - pypi: git+https://github.com/easyscience/EasyApp.git?rev=develop#dfef95a881c7f517a8f18de160500750f7a9b8fc + - pypi: git+https://github.com/easyscience/EasyApp.git#dfef95a881c7f517a8f18de160500750f7a9b8fc - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl @@ -304,7 +306,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/41/04e2a649058b0713b00d6c9bd22da35618bb157289e05d068e51fddf8d7e/dunamai-1.25.0-py3-none-any.whl - - pypi: git+https://github.com/easyscience/EasyApp.git?rev=develop#dfef95a881c7f517a8f18de160500750f7a9b8fc + - pypi: git+https://github.com/easyscience/EasyApp.git#dfef95a881c7f517a8f18de160500750f7a9b8fc - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl @@ -410,9 +412,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/40/6d/b6ee155462a0156b94312bdd82d2b92ea56e909740045a87ccb98bf52405/pymdown_extensions-10.20.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/35/0f/5736889fc850794623692cb369e295a994175e51295fa52134626f486296/pyside6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/61/06/c283567628ffa2cefc3c72374ad607f1dfc9842a03db65f1347b9ae52bee/pyside6_addons-6.10.2-cp39-abi3-macosx_13_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/1d/2e/5f18a77f5e0bd730bacec93a690d0ef3c96a9711d213653eacecbf241b8d/pyside6_essentials-6.10.2-cp39-abi3-macosx_13_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/da/a6/27ba5947ed48918f7b74b7c43a1e280aac069e36f25adeb4c9adfac835c4/pyside6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3f/6b/8bc94aff48b63f788f2d84e5467c12362d68906ba742c0942f46cb04c879/pyside6_addons-6.11.1-cp310-abi3-macosx_13_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/b3/da/10d9197e7370eb4fed8df5fc547b7548dec88e5c5949e2d450db4ae96feb/pyside6_essentials-6.11.1-cp310-abi3-macosx_13_0_universal2.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl @@ -433,7 +435,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/38/3912eb08a3b865b5fcdb4bdce8076cacc211986cee587f5cb62e637791af/shiboken6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/17/f3/f2b63df0251e7cd3172ea28e32ede52739de9566bcefcd0178681538ac81/shiboken6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -531,7 +533,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/41/04e2a649058b0713b00d6c9bd22da35618bb157289e05d068e51fddf8d7e/dunamai-1.25.0-py3-none-any.whl - - pypi: git+https://github.com/easyscience/EasyApp.git?rev=develop#dfef95a881c7f517a8f18de160500750f7a9b8fc + - pypi: git+https://github.com/easyscience/EasyApp.git#dfef95a881c7f517a8f18de160500750f7a9b8fc - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl @@ -637,9 +639,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/40/6d/b6ee155462a0156b94312bdd82d2b92ea56e909740045a87ccb98bf52405/pymdown_extensions-10.20.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/35/0f/5736889fc850794623692cb369e295a994175e51295fa52134626f486296/pyside6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/61/06/c283567628ffa2cefc3c72374ad607f1dfc9842a03db65f1347b9ae52bee/pyside6_addons-6.10.2-cp39-abi3-macosx_13_0_universal2.whl - - pypi: https://files.pythonhosted.org/packages/1d/2e/5f18a77f5e0bd730bacec93a690d0ef3c96a9711d213653eacecbf241b8d/pyside6_essentials-6.10.2-cp39-abi3-macosx_13_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/da/a6/27ba5947ed48918f7b74b7c43a1e280aac069e36f25adeb4c9adfac835c4/pyside6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/3f/6b/8bc94aff48b63f788f2d84e5467c12362d68906ba742c0942f46cb04c879/pyside6_addons-6.11.1-cp310-abi3-macosx_13_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/b3/da/10d9197e7370eb4fed8df5fc547b7548dec88e5c5949e2d450db4ae96feb/pyside6_essentials-6.11.1-cp310-abi3-macosx_13_0_universal2.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl @@ -660,7 +662,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fb/38/3912eb08a3b865b5fcdb4bdce8076cacc211986cee587f5cb62e637791af/shiboken6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl + - pypi: https://files.pythonhosted.org/packages/17/f3/f2b63df0251e7cd3172ea28e32ede52739de9566bcefcd0178681538ac81/shiboken6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -751,7 +753,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/b4/a7ec1eaee86761a9dbfd339732b4706db3c6b65e970c12f0f56cfcce3dcf/docformatter-1.7.7-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/41/04e2a649058b0713b00d6c9bd22da35618bb157289e05d068e51fddf8d7e/dunamai-1.25.0-py3-none-any.whl - - pypi: git+https://github.com/easyscience/EasyApp.git?rev=develop#dfef95a881c7f517a8f18de160500750f7a9b8fc + - pypi: git+https://github.com/easyscience/EasyApp.git#dfef95a881c7f517a8f18de160500750f7a9b8fc - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl @@ -855,9 +857,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/40/6d/b6ee155462a0156b94312bdd82d2b92ea56e909740045a87ccb98bf52405/pymdown_extensions-10.20.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4f/d4/673b8112b4a260377f760be835c4e357163fdaf68a56a1aec59aeb8e584b/pyside6-6.10.2-cp39-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/99/13/503bec9201881968c372cb634069535e80aec2489f3907d676e151a1023f/pyside6_addons-6.10.2-cp39-abi3-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/22/a8/616bbbd009efd3e17bf9a2db09d90c6764c010565cd2bdea2a240bfd18f7/pyside6_essentials-6.10.2-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/57/f2/d9d8ce1373dabb37e5919f63cd18446556079631d3f2eea3ada03c29f6b8/pyside6-6.11.1-cp310-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/9a/bd/8adc4d350b3b363f3dfc8fccdcf5bfed25f7e36c2fff30c64e106f4f1572/pyside6_addons-6.11.1-cp310-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/64/0e/b663ecc96ca57b5c91b83b6615d6b174380b0faf30338125c26e053d6aa7/pyside6_essentials-6.11.1-cp310-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl @@ -881,7 +883,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/5d/5ca52c0ef86b3d01572131b6709bd531a080995f7e680720e9424328ce1d/shiboken6-6.10.2-cp39-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/52/b5/3f6fb2ee65b534193fb4ef713dd619dc31dadff5d12c16979a7699ad58be/shiboken6-6.11.1-cp310-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl @@ -1390,7 +1392,7 @@ packages: - importlib-metadata>=1.6.0 ; python_full_version < '3.8' - packaging>=20.9 requires_python: '>=3.5' -- pypi: git+https://github.com/easyscience/EasyApp.git?rev=develop#dfef95a881c7f517a8f18de160500750f7a9b8fc +- pypi: git+https://github.com/easyscience/EasyApp.git#dfef95a881c7f517a8f18de160500750f7a9b8fc name: easyapp version: 0.8.0 requires_dist: @@ -1398,7 +1400,7 @@ packages: requires_python: '>=3.11' - pypi: ./ name: easyshapes-app - version: 999.0.0+dev9 + version: 999.0.0+devdirty35 sha256: ee8e6e8d672def32f03460f6991f1d5120d46ae93c85c2ead776e4e1daedf1d1 requires_dist: - darkdetect @@ -1438,7 +1440,6 @@ packages: - validate-pyproject[all] ; extra == 'dev' - versioningit ; extra == 'dev' requires_python: '>=3.11' - editable: true - pypi: https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl name: execnet version: 2.1.2 @@ -4886,24 +4887,26 @@ packages: - pyside6-essentials==6.9.3 - pyside6-addons==6.9.3 requires_python: '>=3.9,<3.14' -- pypi: https://files.pythonhosted.org/packages/35/0f/5736889fc850794623692cb369e295a994175e51295fa52134626f486296/pyside6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl +- pypi: https://files.pythonhosted.org/packages/57/f2/d9d8ce1373dabb37e5919f63cd18446556079631d3f2eea3ada03c29f6b8/pyside6-6.11.1-cp310-abi3-win_amd64.whl name: pyside6 - version: 6.10.2 - sha256: 4b084293caa7845d0064aaf6af258e0f7caae03a14a33537d0a552131afddaf0 - requires_dist: - - shiboken6==6.10.2 - - pyside6-essentials==6.10.2 - - pyside6-addons==6.10.2 - requires_python: '>=3.9,<3.15' -- pypi: https://files.pythonhosted.org/packages/4f/d4/673b8112b4a260377f760be835c4e357163fdaf68a56a1aec59aeb8e584b/pyside6-6.10.2-cp39-abi3-win_amd64.whl + version: 6.11.1 + sha256: 0968877ab1fb4ef3587a284da6fe05e8647ada56a6a3750b6395188e01f4aba6 + requires_dist: + - shiboken6==6.11.1 + - pyside6-essentials==6.11.1 + - pyside6-addons==6.11.1 + - tomli>=2.0.1 ; python_full_version < '3.11' + requires_python: '>=3.10,<3.15' +- pypi: https://files.pythonhosted.org/packages/da/a6/27ba5947ed48918f7b74b7c43a1e280aac069e36f25adeb4c9adfac835c4/pyside6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl name: pyside6 - version: 6.10.2 - sha256: 032bad6b18a17fcbf4dddd0397f49b07f8aae7f1a45b7e4de7037bf7fd6e0edf - requires_dist: - - shiboken6==6.10.2 - - pyside6-essentials==6.10.2 - - pyside6-addons==6.10.2 - requires_python: '>=3.9,<3.15' + version: 6.11.1 + sha256: 537682c3b7530817203e667c1f5a2f00486b37bf52c52eeab438544c7a0917f6 + requires_dist: + - shiboken6==6.11.1 + - pyside6-essentials==6.11.1 + - pyside6-addons==6.11.1 + - tomli>=2.0.1 ; python_full_version < '3.11' + requires_python: '>=3.10,<3.15' - pypi: https://files.pythonhosted.org/packages/17/fe/d5c67665f866b8859d02aa1a859f101a1b2fd348cb61746a3e16fd98fb20/pyside6_addons-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl name: pyside6-addons version: 6.9.3 @@ -4912,22 +4915,22 @@ packages: - shiboken6==6.9.3 - pyside6-essentials==6.9.3 requires_python: '>=3.9,<3.14' -- pypi: https://files.pythonhosted.org/packages/61/06/c283567628ffa2cefc3c72374ad607f1dfc9842a03db65f1347b9ae52bee/pyside6_addons-6.10.2-cp39-abi3-macosx_13_0_universal2.whl +- pypi: https://files.pythonhosted.org/packages/3f/6b/8bc94aff48b63f788f2d84e5467c12362d68906ba742c0942f46cb04c879/pyside6_addons-6.11.1-cp310-abi3-macosx_13_0_universal2.whl name: pyside6-addons - version: 6.10.2 - sha256: 0de7d0c9535e17d5e3b634b61314a1867f3b0f6d35c3d7cdc99efc353192faff + version: 6.11.1 + sha256: 54733c77f789bef5f03c6aff4ad3bec8b2eff021f0cfcbc53d5e6c250ded24f9 requires_dist: - - shiboken6==6.10.2 - - pyside6-essentials==6.10.2 - requires_python: '>=3.9,<3.15' -- pypi: https://files.pythonhosted.org/packages/99/13/503bec9201881968c372cb634069535e80aec2489f3907d676e151a1023f/pyside6_addons-6.10.2-cp39-abi3-win_amd64.whl + - shiboken6==6.11.1 + - pyside6-essentials==6.11.1 + requires_python: '>=3.10,<3.15' +- pypi: https://files.pythonhosted.org/packages/9a/bd/8adc4d350b3b363f3dfc8fccdcf5bfed25f7e36c2fff30c64e106f4f1572/pyside6_addons-6.11.1-cp310-abi3-win_amd64.whl name: pyside6-addons - version: 6.10.2 - sha256: c20150068525a17494f3b6576c5d61c417cf9a5870659e29f5ebd83cd20a78ea + version: 6.11.1 + sha256: 0d13c4dfd671b050a48e4f8d8ddc724b7248f9c0437e7fc47fdf316278572923 requires_dist: - - shiboken6==6.10.2 - - pyside6-essentials==6.10.2 - requires_python: '>=3.9,<3.15' + - shiboken6==6.11.1 + - pyside6-essentials==6.11.1 + requires_python: '>=3.10,<3.15' - pypi: https://files.pythonhosted.org/packages/85/e8/9396cf11a60f80175bb3c5c1d498d84e87b7af653ab4ea001acf821a3981/pyside6_essentials-6.9.3-cp39-abi3-manylinux_2_28_x86_64.whl name: pyside6-essentials version: 6.9.3 @@ -4935,20 +4938,20 @@ packages: requires_dist: - shiboken6==6.9.3 requires_python: '>=3.9,<3.14' -- pypi: https://files.pythonhosted.org/packages/1d/2e/5f18a77f5e0bd730bacec93a690d0ef3c96a9711d213653eacecbf241b8d/pyside6_essentials-6.10.2-cp39-abi3-macosx_13_0_universal2.whl +- pypi: https://files.pythonhosted.org/packages/64/0e/b663ecc96ca57b5c91b83b6615d6b174380b0faf30338125c26e053d6aa7/pyside6_essentials-6.11.1-cp310-abi3-win_amd64.whl name: pyside6-essentials - version: 6.10.2 - sha256: 1dee2cb9803ff135f881dadeb5c0edcef793d1ec4f8a9140a1348cecb71074e1 + version: 6.11.1 + sha256: 63311bd48e32c584599ab04b9ef7c324082374cd2c9fa533f978fb893bb47e40 requires_dist: - - shiboken6==6.10.2 - requires_python: '>=3.9,<3.15' -- pypi: https://files.pythonhosted.org/packages/22/a8/616bbbd009efd3e17bf9a2db09d90c6764c010565cd2bdea2a240bfd18f7/pyside6_essentials-6.10.2-cp39-abi3-win_amd64.whl + - shiboken6==6.11.1 + requires_python: '>=3.10,<3.15' +- pypi: https://files.pythonhosted.org/packages/b3/da/10d9197e7370eb4fed8df5fc547b7548dec88e5c5949e2d450db4ae96feb/pyside6_essentials-6.11.1-cp310-abi3-macosx_13_0_universal2.whl name: pyside6-essentials - version: 6.10.2 - sha256: 0741018c2b6395038cad4c41775cfae3f13a409e87995ac9f7d89e5b1fb6b22a + version: 6.11.1 + sha256: 228de53c2bc26b07e5021fbe3614fc44ca08e4dab9999af08c2b389d2c239957 requires_dist: - - shiboken6==6.10.2 - requires_python: '>=3.9,<3.15' + - shiboken6==6.11.1 + requires_python: '>=3.10,<3.15' - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl name: pytest version: 9.0.2 @@ -5409,16 +5412,16 @@ packages: version: 6.9.3 sha256: f3f5337a3a8fc660ba1462265bd9a2bdda9588f8d90fbc3d5ac4ce3134c11e59 requires_python: '>=3.9,<3.14' -- pypi: https://files.pythonhosted.org/packages/71/5d/5ca52c0ef86b3d01572131b6709bd531a080995f7e680720e9424328ce1d/shiboken6-6.10.2-cp39-abi3-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/17/f3/f2b63df0251e7cd3172ea28e32ede52739de9566bcefcd0178681538ac81/shiboken6-6.11.1-cp310-abi3-macosx_13_0_universal2.whl name: shiboken6 - version: 6.10.2 - sha256: 10f3c8c5e1b8bee779346f21c10dbc14cff068f0b0b4e62420c82a6bf36ac2e7 - requires_python: '>=3.9,<3.15' -- pypi: https://files.pythonhosted.org/packages/fb/38/3912eb08a3b865b5fcdb4bdce8076cacc211986cee587f5cb62e637791af/shiboken6-6.10.2-cp39-abi3-macosx_13_0_universal2.whl + version: 6.11.1 + sha256: 1a16867f103ef1c662a5f09dfed03273a9f81688b174555162c58e83650a3f02 + requires_python: '>=3.10,<3.15' +- pypi: https://files.pythonhosted.org/packages/52/b5/3f6fb2ee65b534193fb4ef713dd619dc31dadff5d12c16979a7699ad58be/shiboken6-6.11.1-cp310-abi3-win_amd64.whl name: shiboken6 - version: 6.10.2 - sha256: 3bd4e94e9a3c8c1fa8362fd752d399ef39265d5264e4e37bae61cdaa2a00c8c7 - requires_python: '>=3.9,<3.15' + version: 6.11.1 + sha256: c2c6863aa80ec18c0f82cea3417837b279cdc60024ac17123461dc9042577df7 + requires_python: '>=3.10,<3.15' - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl name: six version: 1.17.0 diff --git a/pixi.toml b/pixi.toml index af00e86..75ecef5 100644 --- a/pixi.toml +++ b/pixi.toml @@ -48,6 +48,7 @@ gsl = '*' # GNU Scientific Library; required for pdffit2. [pypi-dependencies] # == [feature.default.pypi-dependencies] pip = '*' # Native package installer easyshapes_app = { path = ".", editable = true, extras = ['dev'] } +pyside6-addons = ">=6.9.3, <7" # Specific features: Set specific Python versions diff --git a/src/easyshapes_app.qrc b/src/easyshapes_app.qrc index 55c7cbc..7812235 100644 --- a/src/easyshapes_app.qrc +++ b/src/easyshapes_app.qrc @@ -1,9 +1,9 @@ ../../EasyApp/src/EasyApplication/Gui/Animations/ColorReset.qml - ../../EasyApp/src/EasyApplication/Gui/Animations/qmldir ../../EasyApp/src/EasyApplication/Gui/Animations/ThemeChange.qml ../../EasyApp/src/EasyApplication/Gui/Animations/TranslationChange.qml + ../../EasyApp/src/EasyApplication/Gui/Animations/qmldir ../../EasyApp/src/EasyApplication/Gui/Charts/ChartViewHeatmap2dPlotly.qml ../../EasyApp/src/EasyApplication/Gui/Charts/ChartViewSimple1dPlotly.qml ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly1dBarPlot.qml @@ -13,11 +13,11 @@ ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly2dPolarHeatmap.qml ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly3dScatter.qml ../../EasyApp/src/EasyApplication/Gui/Charts/Plotly3dSurface.qml - ../../EasyApp/src/EasyApplication/Gui/Charts/qmldir ../../EasyApp/src/EasyApplication/Gui/Charts/QtCharts1dBase 2.qml ../../EasyApp/src/EasyApplication/Gui/Charts/QtCharts1dBase.qml ../../EasyApp/src/EasyApplication/Gui/Charts/QtCharts1dMeasVsCalc.qml ../../EasyApp/src/EasyApplication/Gui/Charts/QtCharts1dValueAxis.qml + ../../EasyApp/src/EasyApplication/Gui/Charts/qmldir ../../EasyApp/src/EasyApplication/Gui/Components/AboutDialog.qml ../../EasyApp/src/EasyApplication/Gui/Components/AppBarCentralTabs.qml ../../EasyApp/src/EasyApplication/Gui/Components/AppBarLeftButtons.qml @@ -36,7 +36,6 @@ ../../EasyApp/src/EasyApplication/Gui/Components/MainContent.qml ../../EasyApp/src/EasyApplication/Gui/Components/PreferencesDialog.qml ../../EasyApp/src/EasyApplication/Gui/Components/ProjectDescriptionDialog.qml - ../../EasyApp/src/EasyApplication/Gui/Components/qmldir ../../EasyApp/src/EasyApplication/Gui/Components/SideBar.qml ../../EasyApp/src/EasyApplication/Gui/Components/SideBarColumn.qml ../../EasyApp/src/EasyApplication/Gui/Components/TableView.qml @@ -51,6 +50,7 @@ ../../EasyApp/src/EasyApplication/Gui/Components/TableViewParameter.qml ../../EasyApp/src/EasyApplication/Gui/Components/TableViewTextInput.qml ../../EasyApp/src/EasyApplication/Gui/Components/TableViewTwoRowsAdvancedLabel.qml + ../../EasyApp/src/EasyApplication/Gui/Components/qmldir ../../EasyApp/src/EasyApplication/Gui/Elements/AppBarTabButton.qml ../../EasyApp/src/EasyApplication/Gui/Elements/ApplicationWindow.qml ../../EasyApp/src/EasyApplication/Gui/Elements/Button.qml @@ -69,9 +69,8 @@ ../../EasyApp/src/EasyApplication/Gui/Elements/Menu.qml ../../EasyApp/src/EasyApplication/Gui/Elements/MenuItem.qml ../../EasyApp/src/EasyApplication/Gui/Elements/ParamComboBox.qml - ../../EasyApp/src/EasyApplication/Gui/Elements/Parameter.qml ../../EasyApp/src/EasyApplication/Gui/Elements/ParamTextField.qml - ../../EasyApp/src/EasyApplication/Gui/Elements/qmldir + ../../EasyApp/src/EasyApplication/Gui/Elements/Parameter.qml ../../EasyApp/src/EasyApplication/Gui/Elements/RadioButton.qml ../../EasyApp/src/EasyApplication/Gui/Elements/RadioIndicator.qml ../../EasyApp/src/EasyApplication/Gui/Elements/RemoteController.qml @@ -94,21 +93,22 @@ ../../EasyApp/src/EasyApplication/Gui/Elements/ToolButton.qml ../../EasyApp/src/EasyApplication/Gui/Elements/ToolTip.qml ../../EasyApp/src/EasyApplication/Gui/Elements/ToolTipShadow.qml - ../../EasyApp/src/EasyApplication/Gui/Globals/qmldir + ../../EasyApp/src/EasyApplication/Gui/Elements/qmldir ../../EasyApp/src/EasyApplication/Gui/Globals/Vars.qml + ../../EasyApp/src/EasyApplication/Gui/Globals/qmldir ../../EasyApp/src/EasyApplication/Gui/Html/plotly-2.18.0.min.js ../../EasyApp/src/EasyApplication/Gui/Logic/Plotting.js ../../EasyApp/src/EasyApplication/Gui/Logic/ProjectConfig.js - ../../EasyApp/src/EasyApplication/Gui/Logic/qmldir ../../EasyApp/src/EasyApplication/Gui/Logic/Translate.js ../../EasyApp/src/EasyApplication/Gui/Logic/Utils.js + ../../EasyApp/src/EasyApplication/Gui/Logic/qmldir ../../EasyApp/src/EasyApplication/Gui/Style/Colors.qml ../../EasyApp/src/EasyApplication/Gui/Style/Fonts.qml - ../../EasyApp/src/EasyApplication/Gui/Style/qmldir ../../EasyApp/src/EasyApplication/Gui/Style/Sizes.qml ../../EasyApp/src/EasyApplication/Gui/Style/Times.qml - ../../EasyApp/src/EasyApplication/Logic/Maintenance/qmldir + ../../EasyApp/src/EasyApplication/Gui/Style/qmldir ../../EasyApp/src/EasyApplication/Logic/Maintenance/Updater.qml + ../../EasyApp/src/EasyApplication/Logic/Maintenance/qmldir easyshapes_app.qmlproject easyshapes_app/Backends/MockBackend.qml easyshapes_app/Backends/MockQml/Analysis.qml @@ -126,7 +126,6 @@ easyshapes_app/Backends/MockQml/LibraryAssetsEditor.qml easyshapes_app/Backends/MockQml/MonolayerStructure.qml easyshapes_app/Backends/MockQml/Project.qml - easyshapes_app/Backends/MockQml/qmldir easyshapes_app/Backends/MockQml/Report.qml easyshapes_app/Backends/MockQml/RingStructure.qml easyshapes_app/Backends/MockQml/RodStructure.qml @@ -135,14 +134,17 @@ easyshapes_app/Backends/MockQml/Status.qml easyshapes_app/Backends/MockQml/StructureFiles.qml easyshapes_app/Backends/MockQml/VesicleStructure.qml + easyshapes_app/Backends/MockQml/qmldir easyshapes_app/Backends/qmldir easyshapes_app/Gui/ApplicationWindow.qml easyshapes_app/Gui/Globals/ApplicationInfo.qml easyshapes_app/Gui/Globals/BackendWrapper.qml - easyshapes_app/Gui/Globals/qmldir easyshapes_app/Gui/Globals/References.qml + easyshapes_app/Gui/Globals/qmldir easyshapes_app/Gui/Pages/Analysis/Layout.qml easyshapes_app/Gui/Pages/Analysis/MainArea/Chart.qml + easyshapes_app/Gui/Pages/Analysis/MainArea/EngineOutput.qml + easyshapes_app/Gui/Pages/Analysis/MainArea/Scattering.qml easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Groups/AnalysisConfig.qml easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Popups/AddAnalysisConfigFiles.qml @@ -166,7 +168,13 @@ easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml easyshapes_app/Gui/Pages/Report/Sidebar/Extra/Layout.qml easyshapes_app/Gui/Pages/SampleModel/Layout.qml + easyshapes_app/Gui/Pages/SampleModel/MainArea/BaseMol3dQuick.qml + easyshapes_app/Gui/Pages/SampleModel/MainArea/ComponentView.qml + easyshapes_app/Gui/Pages/SampleModel/MainArea/Components.qml + easyshapes_app/Gui/Pages/SampleModel/MainArea/Distances.qml easyshapes_app/Gui/Pages/SampleModel/MainArea/GraphsView.qml + easyshapes_app/Gui/Pages/SampleModel/MainArea/Lattice.qml + easyshapes_app/Gui/Pages/SampleModel/MainArea/Layers.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/ComponentsFiles.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/LibraryAssetsFiles.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Advanced/Groups/SmilesGenerator.qml @@ -195,8 +203,8 @@ easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingIon.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/LoadExistingModel.qml easyshapes_app/Gui/Pages/SampleModel/Sidebar/Basic/Popups/OpenAssetFile.qml - easyshapes_app/Gui/qmldir easyshapes_app/Gui/StatusBar.qml + easyshapes_app/Gui/qmldir easyshapes_app/main.qml easyshapes_app/test.qml easyshapes_app/test2.qml diff --git a/src/easyshapes_app/Backends/MockQml/Analysis.qml b/src/easyshapes_app/Backends/MockQml/Analysis.qml index ebb2579..c45be82 100644 --- a/src/easyshapes_app/Backends/MockQml/Analysis.qml +++ b/src/easyshapes_app/Backends/MockQml/Analysis.qml @@ -20,8 +20,17 @@ QtObject { "ymax": 100.0, } + // True once equilibration has finished, gating the engine-output and + // scattering windows on the Analysis page. + property bool equilibrated: false + signal dataPointsChanged(var points) + function equilibrate() { + console.debug("* Equilibration finished (mock)") + equilibrated = true + } + function generateData() { console.debug(`* Generating ${dataSize} data points...`) const xmin = axesRanges.xmin diff --git a/src/easyshapes_app/Backends/MockQml/Components.qml b/src/easyshapes_app/Backends/MockQml/Components.qml index e28ef64..8cda933 100644 --- a/src/easyshapes_app/Backends/MockQml/Components.qml +++ b/src/easyshapes_app/Backends/MockQml/Components.qml @@ -58,4 +58,180 @@ QtObject { pendingFilePathsModel.clear() } + // Atomistic structure used to depict a component in the Components viewer. + // The mock returns one shared template molecule for every component (index + // ignored); the real backend will return each component's own structure. + // + // The PDB text is embedded here (not read from the file) so the mock needs + // no local-file access in Qt Creator's qml runtime. The real backend will + // read the actual file / use the shapespyer readers. + readonly property string _moleculePdb: `REMARK +ATOM 1 N POPCA 1 -1.628 -1.762 11.597 1.00 0.00 A N +ATOM 2 C12 POPCA 1 -2.376 -3.129 11.317 1.00 0.00 A C +ATOM 3 H12A POPCA 1 -2.294 -3.725 12.214 1.00 0.00 A H +ATOM 4 H12B POPCA 1 -1.904 -3.650 10.498 1.00 0.00 A H +ATOM 5 C13 POPCA 1 -0.222 -2.018 11.969 1.00 0.00 A C +ATOM 6 H13A POPCA 1 -0.225 -2.752 12.761 1.00 0.00 A H +ATOM 7 H13B POPCA 1 0.347 -2.478 11.174 1.00 0.00 A H +ATOM 8 H13C POPCA 1 0.321 -1.190 12.400 1.00 0.00 A H +ATOM 9 C14 POPCA 1 -1.612 -0.790 10.423 1.00 0.00 A C +ATOM 10 H14A POPCA 1 -2.639 -0.829 10.093 1.00 0.00 A H +ATOM 11 H14B POPCA 1 -1.360 0.231 10.669 1.00 0.00 A H +ATOM 12 H14C POPCA 1 -0.912 -1.102 9.662 1.00 0.00 A H +ATOM 13 C15 POPCA 1 -2.242 -1.134 12.850 1.00 0.00 A C +ATOM 14 H15A POPCA 1 -1.837 -0.154 13.056 1.00 0.00 A H +ATOM 15 H15B POPCA 1 -2.091 -1.742 13.730 1.00 0.00 A H +ATOM 16 H15C POPCA 1 -3.302 -0.980 12.715 1.00 0.00 A H +ATOM 17 C11 POPCA 1 -3.887 -2.848 11.003 1.00 0.00 A C +ATOM 18 H11A POPCA 1 -4.232 -2.484 11.995 1.00 0.00 A H +ATOM 19 H11B POPCA 1 -4.385 -3.812 10.768 1.00 0.00 A H +ATOM 20 P POPCA 1 -5.348 -1.753 9.168 1.00 0.00 A P +ATOM 21 O13 POPCA 1 -5.595 -0.383 9.566 1.00 0.00 A O +ATOM 22 O14 POPCA 1 -6.302 -2.795 9.531 1.00 0.00 A O +ATOM 23 O12 POPCA 1 -3.970 -1.995 9.851 1.00 0.00 A O +ATOM 24 O11 POPCA 1 -4.981 -1.795 7.640 1.00 0.00 A O +ATOM 25 C1 POPCA 1 -3.932 -2.520 7.052 1.00 0.00 A C +ATOM 26 HA POPCA 1 -4.285 -2.725 6.018 1.00 0.00 A H +ATOM 27 HB POPCA 1 -3.682 -3.511 7.486 1.00 0.00 A H +ATOM 28 C2 POPCA 1 -2.623 -1.763 6.939 1.00 0.00 A C +ATOM 29 HS POPCA 1 -2.582 -1.208 7.900 1.00 0.00 A H +ATOM 30 O21 POPCA 1 -2.777 -0.772 5.947 1.00 0.00 A O +ATOM 31 C21 POPCA 1 -1.865 0.197 5.973 1.00 0.00 A C +ATOM 32 O22 POPCA 1 -0.973 0.362 6.813 1.00 0.00 A O +ATOM 33 C22 POPCA 1 -2.267 1.169 4.884 1.00 0.00 A C +ATOM 34 H2R POPCA 1 -3.173 1.730 5.200 1.00 0.00 A H +ATOM 35 H2S POPCA 1 -2.425 0.490 4.019 1.00 0.00 A H +ATOM 36 C3 POPCA 1 -1.452 -2.658 6.753 1.00 0.00 A C +ATOM 37 HX POPCA 1 -1.422 -3.386 7.592 1.00 0.00 A H +ATOM 38 HY POPCA 1 -0.496 -2.096 6.829 1.00 0.00 A H +ATOM 39 O31 POPCA 1 -1.540 -3.417 5.530 1.00 0.00 A O +ATOM 40 C31 POPCA 1 -0.769 -3.071 4.532 1.00 0.00 A C +ATOM 41 O32 POPCA 1 0.140 -2.241 4.567 1.00 0.00 A O +ATOM 42 C32 POPCA 1 -1.120 -3.860 3.307 1.00 0.00 A C +ATOM 43 H2X POPCA 1 -2.149 -4.280 3.317 1.00 0.00 A H +ATOM 44 H2Y POPCA 1 -0.534 -4.803 3.256 1.00 0.00 A H +ATOM 45 C23 POPCA 1 -1.275 2.333 4.574 1.00 0.00 A C +ATOM 46 H3R POPCA 1 -0.955 2.762 5.548 1.00 0.00 A H +ATOM 47 H3S POPCA 1 -1.895 3.064 4.013 1.00 0.00 A H +ATOM 48 C24 POPCA 1 -0.124 1.785 3.712 1.00 0.00 A C +ATOM 49 H4R POPCA 1 -0.559 1.116 2.939 1.00 0.00 A H +ATOM 50 H4S POPCA 1 0.599 1.205 4.325 1.00 0.00 A H +ATOM 51 C25 POPCA 1 0.703 2.846 3.019 1.00 0.00 A C +ATOM 52 H5R POPCA 1 0.961 3.668 3.721 1.00 0.00 A H +ATOM 53 H5S POPCA 1 0.098 3.364 2.245 1.00 0.00 A H +ATOM 54 C26 POPCA 1 2.011 2.311 2.397 1.00 0.00 A C +ATOM 55 H6R POPCA 1 1.861 1.508 1.645 1.00 0.00 A H +ATOM 56 H6S POPCA 1 2.727 1.959 3.171 1.00 0.00 A H +ATOM 57 C27 POPCA 1 2.556 3.461 1.488 1.00 0.00 A C +ATOM 58 H7R POPCA 1 2.957 4.238 2.174 1.00 0.00 A H +ATOM 59 H7S POPCA 1 1.730 3.855 0.859 1.00 0.00 A H +ATOM 60 C28 POPCA 1 3.645 2.891 0.549 1.00 0.00 A C +ATOM 61 H8R POPCA 1 3.113 2.216 -0.156 1.00 0.00 A H +ATOM 62 H8S POPCA 1 4.359 2.309 1.169 1.00 0.00 A H +ATOM 63 C29 POPCA 1 4.225 4.177 -0.063 1.00 0.00 A C +ATOM 64 H91 POPCA 1 4.684 4.845 0.680 1.00 0.00 A H +ATOM 65 C210 POPCA 1 4.285 4.458 -1.342 1.00 0.00 A C +ATOM 66 H101 POPCA 1 4.674 5.455 -1.597 1.00 0.00 A H +ATOM 67 C211 POPCA 1 3.750 3.728 -2.495 1.00 0.00 A C +ATOM 68 H11R POPCA 1 2.722 4.144 -2.563 1.00 0.00 A H +ATOM 69 H11S POPCA 1 3.712 2.629 -2.337 1.00 0.00 A H +ATOM 70 C212 POPCA 1 4.281 4.069 -3.940 1.00 0.00 A C +ATOM 71 H12R POPCA 1 4.220 5.160 -4.141 1.00 0.00 A H +ATOM 72 H12S POPCA 1 3.663 3.642 -4.758 1.00 0.00 A H +ATOM 73 C213 POPCA 1 5.721 3.534 -4.003 1.00 0.00 A C +ATOM 74 H13R POPCA 1 5.766 2.477 -3.665 1.00 0.00 A H +ATOM 75 H13S POPCA 1 6.405 4.132 -3.363 1.00 0.00 A H +ATOM 76 C214 POPCA 1 6.377 3.519 -5.402 1.00 0.00 A C +ATOM 77 H14R POPCA 1 6.042 2.618 -5.957 1.00 0.00 A H +ATOM 78 H14S POPCA 1 7.451 3.467 -5.120 1.00 0.00 A H +ATOM 79 C215 POPCA 1 6.143 4.755 -6.334 1.00 0.00 A C +ATOM 80 H15R POPCA 1 6.438 5.637 -5.726 1.00 0.00 A H +ATOM 81 H15S POPCA 1 5.058 4.771 -6.571 1.00 0.00 A H +ATOM 82 C216 POPCA 1 6.921 4.780 -7.565 1.00 0.00 A C +ATOM 83 H16R POPCA 1 7.960 4.556 -7.241 1.00 0.00 A H +ATOM 84 H16S POPCA 1 6.924 5.793 -8.021 1.00 0.00 A H +ATOM 85 C217 POPCA 1 6.396 3.829 -8.671 1.00 0.00 A C +ATOM 86 H17R POPCA 1 5.405 4.090 -9.100 1.00 0.00 A H +ATOM 87 H17S POPCA 1 6.183 2.845 -8.201 1.00 0.00 A H +ATOM 88 C218 POPCA 1 7.419 3.731 -9.870 1.00 0.00 A C +ATOM 89 H18R POPCA 1 7.080 2.929 -10.560 1.00 0.00 A H +ATOM 90 H18S POPCA 1 8.445 3.522 -9.496 1.00 0.00 A H +ATOM 91 H18T POPCA 1 7.406 4.715 -10.385 1.00 0.00 A H +ATOM 92 C33 POPCA 1 -0.830 -3.000 2.040 1.00 0.00 A C +ATOM 93 H3X POPCA 1 0.278 -2.920 2.035 1.00 0.00 A H +ATOM 94 H3Y POPCA 1 -1.222 -1.972 2.193 1.00 0.00 A H +ATOM 95 C34 POPCA 1 -1.345 -3.608 0.766 1.00 0.00 A C +ATOM 96 H4X POPCA 1 -2.431 -3.840 0.797 1.00 0.00 A H +ATOM 97 H4Y POPCA 1 -0.831 -4.573 0.570 1.00 0.00 A H +ATOM 98 C35 POPCA 1 -1.032 -2.798 -0.575 1.00 0.00 A C +ATOM 99 H5X POPCA 1 0.045 -2.530 -0.533 1.00 0.00 A H +ATOM 100 H5Y POPCA 1 -1.525 -1.802 -0.574 1.00 0.00 A H +ATOM 101 C36 POPCA 1 -1.505 -3.426 -1.911 1.00 0.00 A C +ATOM 102 H6X POPCA 1 -2.602 -3.530 -1.765 1.00 0.00 A H +ATOM 103 H6Y POPCA 1 -1.091 -4.447 -2.047 1.00 0.00 A H +ATOM 104 C37 POPCA 1 -1.115 -2.553 -3.161 1.00 0.00 A C +ATOM 105 H7X POPCA 1 -0.009 -2.544 -3.264 1.00 0.00 A H +ATOM 106 H7Y POPCA 1 -1.622 -1.566 -3.122 1.00 0.00 A H +ATOM 107 C38 POPCA 1 -1.761 -3.094 -4.443 1.00 0.00 A C +ATOM 108 H8X POPCA 1 -2.855 -3.216 -4.296 1.00 0.00 A H +ATOM 109 H8Y POPCA 1 -1.491 -4.163 -4.579 1.00 0.00 A H +ATOM 110 C39 POPCA 1 -1.349 -2.188 -5.648 1.00 0.00 A C +ATOM 111 H9X POPCA 1 -0.264 -2.213 -5.886 1.00 0.00 A H +ATOM 112 H9Y POPCA 1 -1.654 -1.168 -5.331 1.00 0.00 A H +ATOM 113 C310 POPCA 1 -2.148 -2.523 -6.913 1.00 0.00 A C +ATOM 114 H10X POPCA 1 -3.214 -2.565 -6.604 1.00 0.00 A H +ATOM 115 H10Y POPCA 1 -1.739 -3.469 -7.329 1.00 0.00 A H +ATOM 116 C311 POPCA 1 -1.814 -1.420 -7.906 1.00 0.00 A C +ATOM 117 H11X POPCA 1 -0.721 -1.244 -8.005 1.00 0.00 A H +ATOM 118 H11Y POPCA 1 -2.343 -0.478 -7.646 1.00 0.00 A H +ATOM 119 C312 POPCA 1 -2.433 -1.707 -9.284 1.00 0.00 A C +ATOM 120 H12X POPCA 1 -3.373 -2.283 -9.151 1.00 0.00 A H +ATOM 121 H12Y POPCA 1 -1.656 -2.272 -9.842 1.00 0.00 A H +ATOM 122 C313 POPCA 1 -2.615 -0.439 -10.124 1.00 0.00 A C +ATOM 123 H13X POPCA 1 -1.617 0.048 -10.151 1.00 0.00 A H +ATOM 124 H13Y POPCA 1 -3.499 0.070 -9.684 1.00 0.00 A H +ATOM 125 C314 POPCA 1 -2.874 -0.832 -11.560 1.00 0.00 A C +ATOM 126 H14X POPCA 1 -3.760 -1.477 -11.742 1.00 0.00 A H +ATOM 127 H14Y POPCA 1 -2.004 -1.385 -11.976 1.00 0.00 A H +ATOM 128 C315 POPCA 1 -3.135 0.378 -12.459 1.00 0.00 A C +ATOM 129 H15X POPCA 1 -2.252 1.042 -12.333 1.00 0.00 A H +ATOM 130 H15Y POPCA 1 -4.060 0.876 -12.098 1.00 0.00 A H +ATOM 131 C316 POPCA 1 -3.480 -0.027 -13.887 1.00 0.00 A C +ATOM 132 H16X POPCA 1 -4.277 -0.802 -13.882 1.00 0.00 A H +ATOM 133 H16Y POPCA 1 -2.605 -0.568 -14.306 1.00 0.00 A H +ATOM 134 H16Z POPCA 1 -3.860 0.816 -14.503 1.00 0.00 A H +` + + // Parsed atom list for Qt Quick 3D: [{element, x, y, z}] in Ångström, + // centred on the centroid so the molecule sits around the scene origin. + // Mock-only parser: this template PDB has one clean atom per ATOM line with + // the element as the last whitespace token and x/y/z as tokens 5/6/7. + // The real backend will use the shapespyer readers instead. + function structureAtoms(index) { + const lines = _moleculePdb.split('\n') + const atoms = [] + let cx = 0, cy = 0, cz = 0 + for (let i = 0; i < lines.length; ++i) { + const ln = lines[i] + if (ln.substring(0, 4) !== 'ATOM' && ln.substring(0, 6) !== 'HETATM') + continue + const t = ln.split(/\s+/) + const x = parseFloat(t[5]) + const y = parseFloat(t[6]) + const z = parseFloat(t[7]) + if (isNaN(x) || isNaN(y) || isNaN(z)) + continue + let el = t[t.length - 1] + el = el.charAt(0).toUpperCase() + el.slice(1).toLowerCase() + atoms.push({ element: el, x: x, y: y, z: z }) + cx += x; cy += y; cz += z + } + const n = atoms.length || 1 + cx /= n; cy /= n; cz /= n + for (let j = 0; j < atoms.length; ++j) { + atoms[j].x -= cx; atoms[j].y -= cy; atoms[j].z -= cz + } + return atoms + } + } diff --git a/src/easyshapes_app/Backends/real_py/analysis.py b/src/easyshapes_app/Backends/real_py/analysis.py index f66d7c1..ee66b80 100644 --- a/src/easyshapes_app/Backends/real_py/analysis.py +++ b/src/easyshapes_app/Backends/real_py/analysis.py @@ -17,6 +17,7 @@ class Analysis(QObject): dataSizeChanged = Signal() dataPointsChanged = Signal('QVariantList') # Emitted with list axesRangesChanged = Signal() # Emitted when range dict updates + equilibratedChanged = Signal() # Emitted when the equilibration state flips def __init__(self): super().__init__() @@ -28,6 +29,7 @@ def __init__(self): 'ymin': 0.0, 'ymax': 100.0, } + self._equilibrated = False # ------------------------------------------------------------------ # QML-accessible Properties @@ -55,10 +57,24 @@ def axesRanges(self): """ return self._axesRanges + @Property(bool, notify=equilibratedChanged) + def equilibrated(self): + """True once equilibration has finished, so the engine output and + scattering data are available. Placeholder flag for the real MD run.""" + return self._equilibrated + # ------------------------------------------------------------------ # Public Slot Called from QML # ------------------------------------------------------------------ + @Slot() + def equilibrate(self): + """Mark equilibration as finished (placeholder for the real engine run).""" + if self._equilibrated: + return + self._equilibrated = True + self.equilibratedChanged.emit() + @Slot() def generateData(self): """Generate new synthetic data and notify QML.""" diff --git a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml index 400377e..bdd2e1d 100644 --- a/src/easyshapes_app/Gui/Globals/BackendWrapper.qml +++ b/src/easyshapes_app/Gui/Globals/BackendWrapper.qml @@ -95,6 +95,11 @@ QtObject { function componentsRemovePendingFilePath(index) { activeBackend.components.removePendingFilePath(index) } function componentsClearPendingFilePaths() { activeBackend.components.clearPendingFilePaths() } + // Atomistic structure of a loaded component, for the Components viewer: + // parsed [{element, x, y, z}] (Å) fed to the Qt Quick 3D viewer. The mock + // returns one shared template molecule for every component. + function componentStructureAtoms(index) { return activeBackend.components.structureAtoms(index) } + // Global default Fractions set (fallback for the Fractions sidebar // component). Layers/Lamellae do NOT use this — see the per-row // fraction accessors below. diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml b/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml index c2f2689..ae31364 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Layout.qml @@ -15,13 +15,18 @@ import Gui.Globals as Globals EaComponents.ContentPage { + // Two static main-area windows. Both are always shown; their content stays + // empty until equilibration is finished (gated on the backend's + // `analysis.equilibrated` flag, set by the sidebar's Equilibrate button). mainView: EaComponents.MainContent { tabs: [ - EaElements.TabButton { text: qsTr("Chart") } + EaElements.TabButton { text: qsTr("Engine output") }, + EaElements.TabButton { text: qsTr("Scattering") } ] items: [ - Loader { source: "MainArea/Chart.qml" } + Loader { source: "MainArea/EngineOutput.qml" }, + Loader { source: "MainArea/Scattering.qml" } ] } diff --git a/src/easyshapes_app/Gui/Pages/Analysis/MainArea/EngineOutput.qml b/src/easyshapes_app/Gui/Pages/Analysis/MainArea/EngineOutput.qml new file mode 100644 index 0000000..3864d3e --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/Analysis/MainArea/EngineOutput.qml @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +// Main-area window: combined engine output (GROMACS stdout + stderr; other +// engines later). The tab is always present, but stays empty until equilibration +// is finished. +// DRAFT CONTAINER — real engine streaming not implemented yet. +Rectangle { + id: root + + readonly property bool equilibrated: Globals.BackendWrapper.activeBackend.analysis.equilibrated === true + + color: EaStyle.Colors.mainContentBackground + + // Before equilibration — no content, just a hint. + EaElements.Label { + anchors.centerIn: parent + visible: !root.equilibrated + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Run Equilibrate to see the engine output.") + } + + // After equilibration — the combined stdout/stderr log (placeholder). + EaElements.TextArea { + anchors.fill: parent + anchors.margins: EaStyle.Sizes.fontPixelSize + visible: root.equilibrated + readOnly: true + font.family: EaStyle.Fonts.monoFontFamily + text: qsTr("Engine stdout / stderr will stream here.\n\n" + + "(Placeholder — GROMACS wiring not implemented yet.)") + } + +} diff --git a/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Scattering.qml b/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Scattering.qml new file mode 100644 index 0000000..cb1a010 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/Analysis/MainArea/Scattering.qml @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +// Main-area window: scattering data. The tab is always present, but stays empty +// until equilibration is finished, then shows the scattering image. +// DRAFT CONTAINER — the image is a static mockup asset for now. +// Drop the picture at Gui/Resources/Images/scattering.png to display it here. +Rectangle { + id: root + + readonly property bool equilibrated: Globals.BackendWrapper.activeBackend.analysis.equilibrated === true + + color: EaStyle.Colors.mainContentBackground + clip: true // crop the image overflow from PreserveAspectCrop + + // Before equilibration — no content, just a hint. + EaElements.Label { + anchors.centerIn: parent + visible: !root.equilibrated + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Run Equilibrate to see the scattering data.") + } + + // After equilibration — the scattering image (mockup asset). PreserveAspectCrop + // scales the image to cover the whole window (matching width or height, + // whichever needs the larger scale, based on the window vs image aspect ratio) + // and crops the overflow — so the window is always fully filled, no bars. + Image { + id: scatteringImage + anchors.fill: parent + visible: root.equilibrated && status === Image.Ready + fillMode: Image.PreserveAspectCrop + source: "../../../Resources/Images/scattering.jpg" + } + + // Fallback while the image asset isn't present yet. + EaElements.Label { + anchors.centerIn: parent + visible: root.equilibrated && scatteringImage.status !== Image.Ready + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Drop scattering.png into Gui/Resources/Images/ to show it here.") + } + +} diff --git a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml index 5b0ba44..5652be4 100644 --- a/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml @@ -35,7 +35,10 @@ EaComponents.SideBarColumn { enabled: Globals.BackendWrapper.analysisConfigFiles ? Globals.BackendWrapper.analysisConfigFiles.count > 0 : false - onClicked: console.debug("Equilibrate clicked") + onClicked: { + console.debug("Equilibrate clicked") + Globals.BackendWrapper.activeBackend.analysis.equilibrate() + } } } } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml b/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml index 4d5bc85..2cfcd6b 100644 --- a/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml +++ b/src/easyshapes_app/Gui/Pages/SampleModel/Layout.qml @@ -14,14 +14,50 @@ import Gui.Globals as Globals EaComponents.ContentPage { + id: root + + // Dynamic main-area tabs. The window set is built from backend state and fed + // to the TabBar/SwipeView through Repeaters, so conditional windows (Layers, + // Lattice) are genuinely INSERTED and REMOVED rather than hidden in place — + // no reserved empty slot/gap. The two Repeaters share `mainAreaModel`, so the + // tabs and views stay index-aligned. Rebuilds when the structure type or the + // model type changes. + // + // LIMITATION: EaComponents.MainContent doesn't expose its TabBar.currentIndex, + // so when a conditional window is inserted/removed the selected index can shift + // (e.g. inserting Layers before Components bumps Components' index, and the view + // jumps). Preserving the selection across edits needs a key-aware container — + // see the proposed framework component (a model-driven MainContent exposing + // `currentKey`). The `key` field below is carried for that future container. + readonly property var mainAreaModel: { + let list = [] + list.push({ key: "distances", label: qsTr("Distances"), source: "MainArea/Distances.qml" }) + list.push({ key: "components", label: qsTr("Components"), source: "MainArea/Components.qml" }) + if (["Ball", "Vesicle", "Monolayer", "Bilayer"] + .includes(Globals.BackendWrapper.sampleModelCurrentStructureType)) + list.push({ key: "layers", label: qsTr("Layers"), source: "MainArea/Layers.qml" }) + if (Globals.BackendWrapper.sampleModelCurrentType === "Lattice") + list.push({ key: "lattice", label: qsTr("Lattice"), source: "MainArea/Lattice.qml" }) + return list + } mainView: EaComponents.MainContent { tabs: [ - EaElements.TabButton { text: qsTr("GraphsView") } + Repeater { + model: root.mainAreaModel + EaElements.TabButton { + text: root.mainAreaModel[index].label + } + } ] items: [ - Loader { source: "MainArea/GraphsView.qml" } + Repeater { + model: root.mainAreaModel + Loader { + source: root.mainAreaModel[index].source + } + } ] } diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/BaseMol3dQuick.qml b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/BaseMol3dQuick.qml new file mode 100644 index 0000000..7a827f0 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/BaseMol3dQuick.qml @@ -0,0 +1,271 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick +import QtQuick3D +import QtQuick3D.Helpers + +import EasyApplication.Gui.Style as EaStyle + + +// 3D molecular structure viewer (engine: native Qt Quick 3D, no WebEngine/JS). +// +// App-local for now (kept in shapes-app rather than promoted to EasyApp until +// the design settles). Data-driven: the caller supplies a parsed atom list in +// `atoms` — [{element, x, y, z}] in Ångström, ideally centred on the origin. +// Each atom is drawn as a CPK-coloured sphere sized by its van der Waals radius. +// An optional orientation arrow runs from atom[vectorStart] to atom[vectorEnd]. +// Tap an atom to label it (index + element); tap empty space to clear. +// Left-drag to orbit, right-drag to pan, wheel to zoom. +// +// Bonds (sticks) are not drawn yet — atoms only for now. +Rectangle { + id: root + + // Parsed atoms: [{element: "C", x, y, z}] in Å, centred near the origin. + property var atoms: [] + // Sphere radius = atomScale * vdW-radius(element), in scene units (= Å). + property real atomScale: 0.4 + // Orientation vector: arrow atom[vectorStart] -> atom[vectorEnd] (0-based, + // clamped). -1 means "no vector". + property int vectorStart: -1 + property int vectorEnd: -1 + property color vectorColor: "magenta" + property real vectorRadius: 0.25 + // Right-button pan sensitivity multiplier (1.0 = baseline). + property real panSpeed: 2.0 + + color: EaStyle.Colors.mainContentBackground + + // CPK-ish colours and van der Waals radii (Å) for the common elements; + // anything else falls back to magenta / 1.6 Å so it stays visible. + readonly property var _cpk: ({ + "H": { c: "#ffffff", r: 1.20 }, + "C": { c: "#909090", r: 1.70 }, + "N": { c: "#3050f8", r: 1.55 }, + "O": { c: "#ff0d0d", r: 1.52 }, + "P": { c: "#ff8000", r: 1.80 }, + "S": { c: "#ffff30", r: 1.80 }, + "F": { c: "#90e050", r: 1.47 }, + "Cl": { c: "#1ff01f", r: 1.75 }, + "Br": { c: "#a62929", r: 1.85 }, + "Na": { c: "#ab5cf2", r: 2.27 }, + "K": { c: "#8f40d4", r: 2.75 }, + "Ca": { c: "#3dff00", r: 2.31 }, + "Mg": { c: "#8aff00", r: 1.73 } + }) + + function _info(element) { + return _cpk[element] !== undefined ? _cpk[element] : { c: "#ff40ff", r: 1.6 } + } + + // --- View framing + derived geometry, recomputed when atoms change ------- + + property real _camDist: 600 // camera distance (fits the molecule) + + // Orientation arrow, in scene space (local +Y of _arrowNode = direction). + property bool _hasVector: false + property vector3d _vecPos: Qt.vector3d(0, 0, 0) + property quaternion _vecRot: Qt.quaternion(1, 0, 0, 0) + property real _vecLen: 0 + + onAtomsChanged: _recompute() + onVectorStartChanged: _updateVector() + onVectorEndChanged: _updateVector() + + function _recompute() { + let maxR = 1 + for (let i = 0; i < atoms.length; ++i) { + const a = atoms[i] + const d = Math.sqrt(a.x * a.x + a.y * a.y + a.z * a.z) + if (d > maxR) + maxR = d + } + _camDist = maxR * 2.4 + 8 + _updateVector() + } + + // Quaternion rotating local +Y onto direction `d`. + function _quatFromY(d) { + const up = Qt.vector3d(0, 1, 0) + const n = d.normalized() + const c = Math.max(-1, Math.min(1, up.dotProduct(n))) + if (c > 0.99999) + return Qt.quaternion(1, 0, 0, 0) + if (c < -0.99999) + return Qt.quaternion(0, 0, 0, 1) // 180° about Z + const axis = up.crossProduct(n).normalized() + const ang = Math.acos(c) + const s = Math.sin(ang / 2) + return Qt.quaternion(Math.cos(ang / 2), axis.x * s, axis.y * s, axis.z * s) + } + + function _updateVector() { + _hasVector = false + const n = atoms.length + if (n === 0 || vectorStart < 0 || vectorEnd < 0) + return + const si = Math.max(0, Math.min(vectorStart, n - 1)) + const ei = Math.max(0, Math.min(vectorEnd, n - 1)) + if (si === ei) + return + const s = Qt.vector3d(atoms[si].x, atoms[si].y, atoms[si].z) + const e = Qt.vector3d(atoms[ei].x, atoms[ei].y, atoms[ei].z) + const dir = e.minus(s) + _vecLen = dir.length() + _vecPos = s + _vecRot = _quatFromY(dir) + _hasVector = _vecLen > 0 + } + + // --- Tap-to-label state -------------------------------------------------- + + property int _pickedIndex: -1 + property string _pickedElement: "" + property real _labelX: 0 + property real _labelY: 0 + + View3D { + id: view + anchors.fill: parent + + environment: SceneEnvironment { + clearColor: root.color + backgroundMode: SceneEnvironment.Color + antialiasingMode: SceneEnvironment.MSAA + antialiasingQuality: SceneEnvironment.High + } + + // Camera orbits this node (kept at the origin = molecule centroid). + Node { + id: originNode + PerspectiveCamera { + id: camera + z: root._camDist + clipFar: 100000 + } + } + + DirectionalLight { eulerRotation.x: -30; eulerRotation.y: -70 } + DirectionalLight { eulerRotation.x: -110; eulerRotation.y: 60; brightness: 0.6 } + + // Atoms. + Node { + id: moleculeNode + Repeater3D { + model: root.atoms + delegate: Model { + required property int index + required property var modelData + readonly property var info: root._info(modelData.element) + property int atomIndex: index + property string element: modelData.element + source: "#Sphere" + pickable: true + position: Qt.vector3d(modelData.x, modelData.y, modelData.z) + // #Sphere built-in mesh has radius 50 units → scale = R/50. + scale: { + const s = root.atomScale * info.r / 50 + return Qt.vector3d(s, s, s) + } + materials: PrincipledMaterial { + baseColor: info.c + roughness: 0.45 + } + } + } + } + + // Orientation arrow atom[vectorStart] -> atom[vectorEnd]: shaft + tip, + // built along local +Y then placed/rotated onto the vector. + Node { + id: arrowNode + visible: root._hasVector + position: root._vecPos + rotation: root._vecRot + + Model { // shaft + source: "#Cylinder" + readonly property real len: root._vecLen * 0.85 + position: Qt.vector3d(0, len / 2, 0) + scale: Qt.vector3d(root.vectorRadius / 50, len / 100, root.vectorRadius / 50) + materials: PrincipledMaterial { baseColor: root.vectorColor } + } + Model { // tip cone + source: "#Cone" + readonly property real len: root._vecLen * 0.15 + position: Qt.vector3d(0, root._vecLen * 0.85 + len / 2, 0) + scale: Qt.vector3d(root.vectorRadius * 2.4 / 50, len / 100, root.vectorRadius * 2.4 / 50) + materials: PrincipledMaterial { baseColor: root.vectorColor } + } + } + + // Tap an atom to label it; tap empty space to clear. TapHandler only + // fires on a tap (not a drag), so it coexists with the orbit controller. + TapHandler { + onTapped: function (eventPoint) { + const p = eventPoint.position + const res = view.pick(p.x, p.y) + if (res.objectHit && res.objectHit.atomIndex !== undefined) { + root._pickedIndex = res.objectHit.atomIndex + root._pickedElement = res.objectHit.element + root._labelX = p.x + root._labelY = p.y + } else { + root._pickedIndex = -1 + } + } + } + } + + // Left-drag to orbit, wheel to zoom. Pan is handled separately below so its + // sensitivity can be tuned, so the controller's own pan is disabled. + OrbitCameraController { + id: orbit + anchors.fill: view + origin: originNode + camera: camera + panEnabled: false + + // Right-button pan, `panSpeed`x more responsive than the baseline. + // Moves the orbit origin in the camera's view plane (Node.right/up are + // world-space basis vectors), scaled by distance so it feels constant. + DragHandler { + target: null + acceptedButtons: Qt.RightButton + property real _lx: 0 + property real _ly: 0 + onActiveChanged: { _lx = 0; _ly = 0 } + onTranslationChanged: { + const dx = translation.x - _lx + const dy = translation.y - _ly + _lx = translation.x + _ly = translation.y + const k = root._camDist * 0.0022 * root.panSpeed + originNode.position = originNode.position + .minus(originNode.right.times(dx * k)) + .plus(originNode.up.times(dy * k)) + } + } + } + + // 2D label for the tapped atom. + Rectangle { + visible: root._pickedIndex >= 0 + x: Math.round(root._labelX) + 8 + y: Math.round(root._labelY) + 8 + width: pickedLabel.implicitWidth + 10 + height: pickedLabel.implicitHeight + 6 + radius: 3 + color: "#cc000000" + Text { + id: pickedLabel + anchors.centerIn: parent + color: "white" + font.pixelSize: 12 + text: root._pickedIndex + " (" + root._pickedElement + ")" + } + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/ComponentView.qml b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/ComponentView.qml new file mode 100644 index 0000000..677a3f0 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/ComponentView.qml @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick + +import EasyApplication.Gui.Style as EaStyle + +import Gui.Globals as Globals + + +// Main-area window: per-component atomistic depiction. Shows the selected +// component on its own in the Qt Quick 3D molecular viewer (parsed atoms drawn +// as CPK spheres). `componentIndex` / `componentName` are set by the page +// Loader. The mock returns one shared template molecule. +// +// mint/mext are atom indices; the viewer draws an orientation arrow +// atom[mint] -> atom[mext] over the molecule. +Rectangle { + id: root + + // Row in Globals.BackendWrapper.componentsLoaded this window depicts. + property int componentIndex: -1 + property string componentName: "" + // Atom indices defining the orientation vector drawn over the molecule + // (arrow atom[mint] -> atom[mext]); -1 means "no vector". + property int componentMint: -1 + property int componentMext: -1 + + color: EaStyle.Colors.mainContentBackground + + BaseMol3dQuick { + anchors.fill: parent + atoms: root.componentIndex >= 0 + ? Globals.BackendWrapper.componentStructureAtoms(root.componentIndex) + : [] + vectorStart: root.componentMint + vectorEnd: root.componentMext + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Components.qml b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Components.qml new file mode 100644 index 0000000..2f3e695 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Components.qml @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +// Main-area window: Components. A dropdown selects which loaded component to +// depict (mirroring the component selector in the Advanced tab's Components Files +// group), and the selected component is shown below in a ComponentView. Keeping +// the per-component choice in a ComboBox keeps the page's outer tabs simple. +// DRAFT CONTAINER — per-component rendering not implemented yet. +Rectangle { + id: root + + readonly property var components: Globals.BackendWrapper.componentsLoaded + + color: EaStyle.Colors.mainContentBackground + + // Empty state — no components loaded yet. + EaElements.Label { + anchors.centerIn: parent + visible: root.components.count === 0 + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("No components yet — load or create one in the sidebar.") + } + + // Component selector. Mirrors Advanced › Components Files: dropdown over the + // loaded components by name. The current index is the selection driving the + // ComponentView below. + Column { + id: selectorBlock + + anchors.top: parent.top + anchors.left: parent.left + anchors.margins: EaStyle.Sizes.fontPixelSize * 1.5 + + visible: root.components.count > 0 + + EaElements.Label { + enabled: false + text: qsTr("Component") + } + + EaElements.ComboBox { + id: componentSelector + width: EaStyle.Sizes.fontPixelSize * 20 + textRole: "name" + model: root.components + + // Keep the selection valid as components are added/removed. + Connections { + target: root.components + function onCountChanged() { + if (root.components.count === 0) + componentSelector.currentIndex = -1 + else if (componentSelector.currentIndex < 0) + componentSelector.currentIndex = 0 + else if (componentSelector.currentIndex >= root.components.count) + componentSelector.currentIndex = root.components.count - 1 + } + } + } + } + + // The selected component's depiction. + ComponentView { + anchors.top: selectorBlock.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.topMargin: EaStyle.Sizes.fontPixelSize + + visible: root.components.count > 0 + + readonly property bool hasSelection: componentSelector.currentIndex >= 0 + && componentSelector.currentIndex < root.components.count + readonly property var selectedRow: hasSelection + ? root.components.get(componentSelector.currentIndex) + : null + + componentIndex: componentSelector.currentIndex + componentName: hasSelection ? selectedRow.name : "" + // mint/mext are atom indices; the viewer draws an arrow atom[mint] -> atom[mext]. + componentMint: hasSelection ? selectedRow.mint : -1 + componentMext: hasSelection ? selectedRow.mext : -1 + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Distances.qml b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Distances.qml new file mode 100644 index 0000000..c3ba6b0 --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Distances.qml @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + + +// Main-area window: distances between components / molecules and their +// mint-mext vectors (spacing). Always available. +// DRAFT CONTAINER — rendering not implemented yet. +Rectangle { + + color: EaStyle.Colors.mainContentBackground + + Column { + anchors.centerIn: parent + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: EaStyle.Sizes.fontPixelSize * 1.5 + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Distances") + } + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Placeholder — to be implemented") + } + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Will show distances between components and their mint–mext vectors.") + } + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Lattice.qml b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Lattice.qml new file mode 100644 index 0000000..9c598fd --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Lattice.qml @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + + +// Main-area window: lattice depiction — size and placement of the lattice +// units. Shown only for the Lattice type. +// DRAFT CONTAINER — rendering not implemented yet. +Rectangle { + + color: EaStyle.Colors.mainContentBackground + + Column { + anchors.centerIn: parent + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: EaStyle.Sizes.fontPixelSize * 1.5 + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Lattice") + } + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Placeholder — to be implemented") + } + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Will show lattice unit size and placement.") + } + } + +} diff --git a/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Layers.qml b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Layers.qml new file mode 100644 index 0000000..36b65dc --- /dev/null +++ b/src/easyshapes_app/Gui/Pages/SampleModel/MainArea/Layers.qml @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2024 EasyApp contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2024 Contributors to the EasyApp project + +import QtQuick + +import EasyApplication.Gui.Style as EaStyle +import EasyApplication.Gui.Elements as EaElements + + +// Main-area window: layer / lamellae depiction for layered structures +// (Ball, Vesicle, Monolayer, Bilayer) — number of layers, thickness and +// the distance between them. +// DRAFT CONTAINER — rendering not implemented yet. +Rectangle { + + color: EaStyle.Colors.mainContentBackground + + Column { + anchors.centerIn: parent + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: EaStyle.Sizes.fontPixelSize * 1.5 + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Layers") + } + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Placeholder — to be implemented") + } + + EaElements.Label { + anchors.horizontalCenter: parent.horizontalCenter + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("Will show layer / lamellae count, thickness and spacing.") + } + } + +} diff --git a/src/easyshapes_app/Gui/Resources/Images/scattering.jpg b/src/easyshapes_app/Gui/Resources/Images/scattering.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1ebed3f3d8c4ba3df004c48344814c1b2ae32b3d GIT binary patch literal 578022 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�Jau`8B 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�Q}tZ`(jj1!Jo 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

x$CA)A6mac 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

gjoDSh1fbsmms*D+zoO0ako6MsIrT;tf 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