Skip to content

feat: Open and Save from the GUI in Dissolve 2 - #2623

Open
rprospero wants to merge 7 commits into
develop2from
dissolve2/open-save
Open

rprospero wants to merge 7 commits into
develop2from
dissolve2/open-save

Conversation

@rprospero

Copy link
Copy Markdown
Contributor

This adds support for saving and loading files from the GUI. It also clears up a couple of bugs in the TOML processing of optional<Number> and Function1DWrapper

Known issues outside the scope of this PR:

  • The open and save shortcuts (Ctrl+O and Ctrl+S) only work intermittently. This may be an issue with my specific setup or it may be a deeper problem. Either way, I will investigate further.
  • Opening a file while editing an existing file adds the nodes from the new file into the existing simulation, instead of clearing the data before starting. We currently don't have a good way to tell the DissolveGraph to drop all its nodes and edges and start over. This is my next planned piece of work and should go fairly quickly.

@rprospero
rprospero force-pushed the dissolve2/open-save branch from 383a418 to 09fabfd Compare October 6, 2026 16:06
@rprospero
rprospero marked this pull request as ready for review October 6, 2026 16:07
@rprospero
rprospero requested review from RobBuchananCompPhys and trisyoungs and removed request for trisyoungs October 6, 2026 16:07
@rprospero
rprospero force-pushed the dissolve2/open-save branch from 09fabfd to 731b85d Compare October 7, 2026 09:33
Base automatically changed from dissolve2/gui2-stack/part-4-qml-dev-phase2 to develop2 October 7, 2026 09:51
@rprospero
rprospero force-pushed the dissolve2/open-save branch from 731b85d to 759eaff Compare October 7, 2026 12:12

@trisyoungs trisyoungs left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I'm reminded of the proposed move towards project folders, but that is somewhere in the distance and we need the capability of this PR right now!

@RobBuchananCompPhys RobBuchananCompPhys left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved with a couple of suggestions (one strictly code, the other more architectural)

Comment on lines +92 to +98
{
saveDialog.open()
}
else
{
dissolve.save()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{
saveDialog.open()
}
else
{
dissolve.save()
}
saveDialog.open()
else
dissolve.save()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RobBuchananCompPhys I'm not going to apply this suggestion, despite it being a good suggestion. The reason is that we will eventually be running qmlformat to bring all the QML documents back in line (and get our QC tests passing again), so I think we should defer these types for formatting discussions until after we know what we will be working with.

Comment on lines +56 to 57
bool save();
bool saveAs(QUrl filename);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up until now, I've been solely using the Q_INVOKABLE macro to decorate functions which are intended to be called on an object from QML. It's my understanding that listing them under Q_SLOTS basically achieves the same thing, but I wonder if at some point we should commit to using one or the other exclusively (and refactor accordingly). The reason for that is that - at least for me- the Q_SLOT feels makes more sense as a function that only exists as a call back corresponding to a Q_SIGNAL. Making a decision to use slots in this way, rather than for exposing methods to QML could help us organise our header files more clearly, especially as one thing I've found challenging writing integrated QML and C++ is tripping up on runtime errors whose source is obscured by emissions of signals on the C++ side which can be slightly more challenging to debug. Would be great to get your thoughts on this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my own incomplete research, the only practical difference that I can find between between Q_INVOKEABLE and Q_SLOTS is that constructors can be Q_INVOKEABLE, but can't be a slot. Beyond that, they seem to operate identically under the hood (e.g. a signal can be connected to an invokeable instead of a slot).

Since we do have two things that do the same thing, that leaves us with two questions:

  1. Should we use this extra, redundant bit of information to communicate information to the programmer, even if it's identical within the software itself?
  2. If we do unify under a single banner, which one would we choose?

I'll address the second issue first. Unifying under invokeable gives the advantage that we can also include constructors, though we have not done this to date. On the other hand, unifying under slots gives the advantage that slots can be declared together as a group (e.g. public slots:) while invokeable needs to be manually declared for each and every instance. Given the improvement to readability, I would lean toward going with slots in this situation.

However, that is assuming that we are choosing a single syntax. We could make the distinction that invokeable is for methods called from QML, while slots are for connecting to signals. This does provide intent to the coder, but it also raises a couple of issues. First, I find the line blurry on a QML level. For example, does the line

onTriggered: dissolveModel.save()

represent the triggered signal being connected to the save slot on DissolveModel, or is it the save method being invoked within the onTriggered method? If it's a signal, does it remain a signal when we do any of

onTriggered: newfile != "" ? dissolveModel.saveAs(newFile) : dissolveModel.save()

onTriggered: {
    console.log("Saved file")
    dissoleModel.save()
}

onTriggered: {
    if (newfile != "")
      dissolveModel.saveAs(newfile)
    else
      dissolveModel.save()
}

Also, if we have been invoking a function and we later find that we need to connect it to a signal, do we change it to a signal or does it remain invokeable? Do we write a copy of the function literally to maintain the distinction?

My personal feeling at the moment is that there is no useful distinction between the invokeable and the slot, so we should stick to declaring slots, as the syntax is nicer. However, I am happy to hear alternate viewpoints on this matter, as there may be much that I have not considered.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants