Skip to content

make fragmentation tabs closeable#214

Open
ToLeWeiss wants to merge 24 commits intoproductionfrom
dev-tom-close-frag-tabs
Open

make fragmentation tabs closeable#214
ToLeWeiss wants to merge 24 commits intoproductionfrom
dev-tom-close-frag-tabs

Conversation

@ToLeWeiss
Copy link
Copy Markdown
Collaborator

This attempts to make the fragmentation tabs in MORTAR closeable and tries to clean up all data that is not used anymore as discussed in #80 because the fragments are also saved in the general MoleculeDataModel. As of my current knowledge there are two things to regard:

  • The molecules tab should not be closeable (it currently makes no sense to do so as we have only one molecule data set at a time and if one wants a new one it can just be imported which in turn deletes the old one)
  • the fragments can only be cleaned up if both the items tab and the fragments tab of the same fragmentation are closed because both hold references to the fragments

@ToLeWeiss
Copy link
Copy Markdown
Collaborator Author

Make a global setting to turn of warnings about data loss if preferred and add a checkbox to the warnings about data loss or closing all tabs with "do not show this again" or similar.

…e a checkbox to turn off the warning message. To turn the messages back on there is also a global setting in preferences
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements closeable fragmentation tabs in MORTAR, allowing users to close individual fragmentation result tabs (fragments and itemization tabs) or all tabs at once via context menus. The implementation includes data cleanup to free memory when tabs are closed and a user setting to control warning dialogs about data loss.

Changes:

  • Added a new user setting to control whether warnings are shown when data will be lost upon closing tabs or the application
  • Implemented tab closing functionality with context menus for individual and batch tab closure
  • Added cleanup logic to remove fragment data when both related tabs (fragments and itemization) for a fragmentation are closed

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
gradlew Standard Gradle wrapper script addition
Message_en_GB.properties Added warning messages for tab closure and new setting description (contains typo)
SettingsContainer.java Added new setting for controlling data loss warnings
MoleculeDataModel.java Added method to clear fragments for a specific fragmentation
FragmentDataModel.java Made frequency fields final for improved immutability
GuiUtil.java Added confirmation dialog with deactivation checkbox (has critical bug)
MainViewController.java Implemented tab closing logic with cleanup and context menus (has multiple bugs)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1438 to +1440
if (Objects.equals(tmpFragmentationName, ((GridTabForTableView) tmpTab).getFragmentationNameOutOfTitle())) {
return false;
}
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The method isGridTabCleanable has a logical bug. It currently returns false if ANY tab with the same fragmentation name is found, including the tab being checked itself. This means isGridTabCleanable will always return false when checking a tab that's currently in the tab pane, preventing proper cleanup. The method should skip comparing the tab with itself by adding a check like: if (tmpTab == aGridTab) continue;

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

correct

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

could this be the reason why we didn't see a substantial cleanup happening in VisualVM?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I tried it out but it does not seem to make a difference. In the methods of closeAllTabs and closeTab the tab is removed before the cleanup method is called so in these cases it should have been irrelevant (but bad nonetheless to have an implicit order of operations). Right now i can not tell why the setOnClosed method also seems to be unaffected by the additional check.

My best guess would be that the best effort of the gc request does not free the older objects when requesting generational gc (via the "Perform GC" button in visualvm) for the first time.
But this whole behaviour is quite strange as it seems that fragmenting and cleaning up a second time frees more memory then the first cleanup.

The picture below shows:

Action Used Heap (Mb)
1. Loading in the molecules and GC after 135
2. Fragmenting the molecules and GC after 254
3. Closing tabs by clicking the x button and GC after 187
4. Fragmenting again with GC after 255
5. Closing the tabs again with GC after 149
Screenshot From 2026-02-20 15-40-59

I recorded this behaviour with and without the above check.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I recorded this behaviour with and without the above check.

and did it change with/without the check?

For now, just investigate (e.g. by debugging) whether Copilot is right here and fix it if necessary. The GC behaviour would be a problem for afterwards.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It did not change with or without the check but I think it is definitely the right approach to check it. I already fixed it with the simple addition of the check but it did not really solve the GC mystery.

@JonasSchaub JonasSchaub marked this pull request as ready for review February 16, 2026 12:51
Copy link
Copy Markdown
Collaborator

@JonasSchaub JonasSchaub left a comment

Choose a reason for hiding this comment

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

First part of review, almost done with MainViewController

Comment on lines +1438 to +1440
if (Objects.equals(tmpFragmentationName, ((GridTabForTableView) tmpTab).getFragmentationNameOutOfTitle())) {
return false;
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

could this be the reason why we didn't see a substantial cleanup happening in VisualVM?

Copy link
Copy Markdown
Collaborator

@JonasSchaub JonasSchaub left a comment

Choose a reason for hiding this comment

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

Finished review, please look into the things I flagged up.


aGridTab.getProperties().clear();
aGridTab.setId(null);
aGridTab.setText(null);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is all of this really necessary to free the objects for garbage collection? Maybe fix the problem above that (apparently), no tab is really identified as "cleanable" right now and then check again whether memory is actually free up with/without all this explicit null-setting.

@ToLeWeiss
Copy link
Copy Markdown
Collaborator Author

From my point of view the cleanup looks pretty nice now!

The behaviour is like follows:

  1. Loading in the first 928 molecules of the COCONUT and suggesting GC:
Screenshot From 2026-03-09 13-51-26
  1. Fragmenting with the Ertl algorithm and suggesting GC:
Screenshot From 2026-03-09 13-51-45
  1. Closing fragments and items tab and suggesting GC:
Screenshot From 2026-03-09 13-52-04

There is still a difference of about 2.5 MB but the fragment models get fully cleaned up.

The warning window with checkbox has now a different sizes which probably depends on the length of the title as the window of the main application upon trying to close, looks like:
Screenshot From 2026-03-09 13-00-38

which has less width than the same warning window called from the closing of tabs with a longer title:
Screenshot From 2026-03-09 13-00-26

Is the dependance on the length of the title plausible and would that be alright @JonasSchaub ?

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1363 to +1373
mainTabPane.getTabs().removeIf(tmpTab -> {
// it is assumed here that MORTAR does NOT have any other
// types of tabs that could be closed besides the GridTableView
// as the itemization tab and fragmentation tab are both constructed
// as GridTableViews
if (tmpTab.isClosable() && tmpTab instanceof GridTabForTableView tmpGridTab) {
this.cleanupGridTab(tmpGridTab);
return true;
}
return false;
});
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

closeAllTabs() calls cleanupGridTab(tmpGridTab) inside removeIf before the tabs are removed. cleanupGridTab() relies on isGridTabCleanable(), which checks for other tabs with the same fragmentation name. When closing all tabs, the paired tab (items/fragments) still exists at that moment, so isGridTabCleanable() returns false and the fragmentation data is never cleaned up even though both tabs are being closed. Consider a two-pass approach: first collect/remove all closable GridTabForTableView tabs, then run cleanup (or at least data cleanup) after the removals so the “no other tab exists” check reflects the final state.

Suggested change
mainTabPane.getTabs().removeIf(tmpTab -> {
// it is assumed here that MORTAR does NOT have any other
// types of tabs that could be closed besides the GridTableView
// as the itemization tab and fragmentation tab are both constructed
// as GridTableViews
if (tmpTab.isClosable() && tmpTab instanceof GridTabForTableView tmpGridTab) {
this.cleanupGridTab(tmpGridTab);
return true;
}
return false;
});
// First collect all closeable GridTabForTableView tabs
List<GridTabForTableView> tmpTabsToClose = mainTabPane.getTabs().stream()
// it is assumed here that MORTAR does NOT have any other
// types of tabs that could be closed besides the GridTableView
// as the itemization tab and fragmentation tab are both constructed
// as GridTableViews
.filter(tmpTab -> tmpTab.isClosable() && tmpTab instanceof GridTabForTableView)
.map(tmpTab -> (GridTabForTableView) tmpTab)
.collect(Collectors.toList());
// Remove them from the TabPane so that cleanup checks see the final state
mainTabPane.getTabs().removeAll(tmpTabsToClose);
// Now clean up the data for each removed tab
for (GridTabForTableView tmpGridTab : tmpTabsToClose) {
this.cleanupGridTab(tmpGridTab);
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@ToLeWeiss, please have a look at the logic here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@ToLeWeiss, I guess Copilot is wrong here? As I understand it, when the first tab of a pair should be removed, the data is not cleared (since the other tab still exists) but(!) the tab is removed nonetheless. So, when the second tab of the pair is set for removal, all should be cleared. Correct? You also confirmed this in VisualVM, as I understood it.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I was worried that removeIf possible does not remove objects eagerly but this does not seem to be the case but I will convert the functional removeIf to a more procedural approach to make it more readable:
https://github.com/openjdk/jdk/blob/8ecb14cecf1f05ea7c65fcfb5bb05e1e3a895d72/src/java.base/share/classes/java/util/Collection.java#L576-L587

I do not quite get the comment about the isGridTabCleanable as the function skips if it is the same tab that needs to be checked so this seems wrong to me:

private boolean isGridTabCleanable(GridTabForTableView aGridTab) {
String tmpFragmentationName = aGridTab.getFragmentationNameOutOfTitle();
for (Tab tmpTab : this.mainTabPane.getTabs()) {
if (!(tmpTab instanceof GridTabForTableView tmpGridTableTab) || tmpTab == aGridTab) {
continue;
}
if (tmpFragmentationName.equals(tmpGridTableTab.getFragmentationNameOutOfTitle())) {
return false;
}
}
return true;
}

Comment on lines 112 to 125
/**
* Applies the IMoleculeFragmenter.fragment(IAtomContainer container) method on all given
* molecules and counts the occurring exceptions.
* molecules and records the exceptions and various statistics for the resulting fragments as a
* {{@link FragmentationTaskResult}}.
*
* @return a {{@link FragmentationTaskResult}} containing:
* 1. Nr. of exceptions that happened during molecule fragmentation.
* 2. Nr. of molecules that were successfully(!) fragmented but produced no fragments.
* 3. Nr. of molecules that were successfully(!) fragmented and produced fragments (to check whether all molecules are a
* 4. Nr. of molecules that gave no atom container.
* 5. Nr. of molecules that were filtered according to the respective fragmentation algorithm method.
* 6. Nr. of fragments that produced no SMILES code.
* 7. Nr. of unexpected exceptions that happened during molecule fragmentation (i.e. the whole process of preprocessing,
*
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

The updated Javadoc uses invalid syntax and appears incomplete: "{{@link FragmentationTaskResult}}" should be "{@link FragmentationTaskResult}", and the numbered @return description is truncated (e.g., item 3 ends mid-sentence, item 7 is incomplete). This will break Javadoc generation and is confusing for readers; please correct the link markup and complete/format the return description.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@ToLeWeiss, I feel like this was actually my code edit, or did you add the doc?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This function had no documentation for the return type/value so I added some text and copied most of the documentation of the fragmentation result.

@JonasSchaub
Copy link
Copy Markdown
Collaborator

@ToLeWeiss, that looks great! The remaining 2.5 MB are negligible.

Is the dependance on the length of the title plausible and would that be alright @JonasSchaub ?

Sure, that would be alright, since all the dialogs scale according to their contents. What you could do here, of course, would be to turn the title of the first dialog to sth like "The app will be closed and data will be lost" to easily mitigate this here.

@JonasSchaub
Copy link
Copy Markdown
Collaborator

I'll have a closer look at the code tomorrow!

Copy link
Copy Markdown
Collaborator

@JonasSchaub JonasSchaub left a comment

Choose a reason for hiding this comment

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

First part of review, mostly nitpicking.

}
//
/**
* Makes a menu item to close all tabs upon pressing and confirming a warning message.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It would be more precise to always specify fragmentation (result) tabs in this context, since the molecules tab remains. But well, maybe try having an AI correct this everywhere once. If it screws up, forget it; if it succeeds, good. Too much to ask to correct this manually everywhere now.

Copy link
Copy Markdown
Collaborator

@JonasSchaub JonasSchaub left a comment

Choose a reason for hiding this comment

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

Final part of review. I guess if you adjust everything accordingly and we discuss the one or two points that still need discussion, I do not need to review this again.

@JonasSchaub
Copy link
Copy Markdown
Collaborator

@ToLeWeiss, I'm looking at the GUI functionality now. First point: after "close all" or if all fragmentation result tabs were closed, the main menu needs to be adjusted, i.e. the "File -> Export" menu and the "Views -> Histogram" menu item need to be disabled:
this.mainView.getMainMenuBar().getExportMenu().setDisable(true);
this.mainView.getMainMenuBar().getHistogramViewerMenuItem().setDisable(true);

@JonasSchaub
Copy link
Copy Markdown
Collaborator

Related to that: if the items (or fragments) tab has been closed, the items (or fragments) data cannot be exported anymore, currently, even though the corresponding fragments (or items) tab still exists. MORTAR will show a notification saying "no data available for export". I guess this is fine; disabling the respective main menu item seems too complicated. But this behaviour needs to be noted somewhere, e.g. in the tutorial.

@JonasSchaub
Copy link
Copy Markdown
Collaborator

A different thing: if I close the fragments tab and then try to open the histogram view, I get an error. This must be corrected. I think the histogram already has a listener tracking which tab is active, since the menu item is disabled if the molecules tab is active. There, the case should be added that an items tab is active but has no corresponding fragments tab anymore and the menu item should also be disabled in that case.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 9, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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