Conversation
…ike not all the fragment data is cleaned up after both items and fragments tab are closed
…ce and the fragments map
|
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
…ommented out line
There was a problem hiding this comment.
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.
src/main/resources/de/unijena/cheminf/mortar/message/Message_en_GB.properties
Outdated
Show resolved
Hide resolved
| if (Objects.equals(tmpFragmentationName, ((GridTabForTableView) tmpTab).getFragmentationNameOutOfTitle())) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
could this be the reason why we didn't see a substantial cleanup happening in VisualVM?
There was a problem hiding this comment.
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 |
I recorded this behaviour with and without the above check.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Show resolved
Hide resolved
JonasSchaub
left a comment
There was a problem hiding this comment.
First part of review, almost done with MainViewController
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
| if (Objects.equals(tmpFragmentationName, ((GridTabForTableView) tmpTab).getFragmentationNameOutOfTitle())) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
could this be the reason why we didn't see a substantial cleanup happening in VisualVM?
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
JonasSchaub
left a comment
There was a problem hiding this comment.
Finished review, please look into the things I flagged up.
|
|
||
| aGridTab.getProperties().clear(); | ||
| aGridTab.setId(null); | ||
| aGridTab.setText(null); |
There was a problem hiding this comment.
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.
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/model/settings/SettingsContainer.java
Outdated
Show resolved
Hide resolved
src/main/resources/de/unijena/cheminf/mortar/message/Message_en_GB.properties
Outdated
Show resolved
Hide resolved
…now especially the setOnDrag listener (confirmed via visualvm)
…he element itself but the fragments in the fragmentationService still remain
… really clear to me
…d removed them properly
… now cleans all generated fragment data models properly and added some documentation and comments
|
From my point of view the cleanup looks pretty nice now! The behaviour is like follows:
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: which has less width than the same warning window called from the closing of tabs with a longer title: Is the dependance on the length of the title plausible and would that be alright @JonasSchaub ? |
There was a problem hiding this comment.
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.
| 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; | ||
| }); |
There was a problem hiding this comment.
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.
| 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); | |
| } |
There was a problem hiding this comment.
@ToLeWeiss, please have a look at the logic here.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
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:
MORTAR/src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Lines 1456 to 1467 in e26bc04
src/main/java/de/unijena/cheminf/mortar/model/settings/SettingsContainer.java
Show resolved
Hide resolved
| /** | ||
| * 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, | ||
| * |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@ToLeWeiss, I feel like this was actually my code edit, or did you add the doc?
There was a problem hiding this comment.
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.
src/main/java/de/unijena/cheminf/mortar/model/fragmentation/FragmentationService.java
Show resolved
Hide resolved
|
@ToLeWeiss, that looks great! The remaining 2.5 MB are negligible.
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. |
|
I'll have a closer look at the code tomorrow! |
JonasSchaub
left a comment
There was a problem hiding this comment.
First part of review, mostly nitpicking.
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/controller/MainViewController.java
Outdated
Show resolved
Hide resolved
| } | ||
| // | ||
| /** | ||
| * Makes a menu item to close all tabs upon pressing and confirming a warning message. |
There was a problem hiding this comment.
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.
src/main/java/de/unijena/cheminf/mortar/gui/controls/GridTabForTableView.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/gui/controls/GridTabForTableView.java
Outdated
Show resolved
Hide resolved
JonasSchaub
left a comment
There was a problem hiding this comment.
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.
src/main/java/de/unijena/cheminf/mortar/model/fragmentation/FragmentationService.java
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/model/fragmentation/FragmentationService.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/model/settings/SettingsContainer.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/model/settings/SettingsContainer.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/model/settings/SettingsContainer.java
Outdated
Show resolved
Hide resolved
src/main/java/de/unijena/cheminf/mortar/model/settings/SettingsContainer.java
Outdated
Show resolved
Hide resolved
src/main/resources/de/unijena/cheminf/mortar/message/Message_en_GB.properties
Outdated
Show resolved
Hide resolved
|
@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: |
|
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. |
|
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. |
…proach with less duplication
…roved the histogram opening logic, also introduced first try at fixing the export
|









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: