Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9b952fc
adds fragment frequencies to fragments overview
JonasSchaub Jan 16, 2026
84167e0
encapsulates Graphics2D config to reduce duplication
JonasSchaub Jan 16, 2026
a2b05c7
code formatting and comments according to Copilot review
JonasSchaub Jan 16, 2026
808a718
Merge branch 'production' into issue-186-fragment-freqs-overview
JonasSchaub Apr 1, 2026
db576df
unifies distances in image with text depiction and unifies the the tw…
JonasSchaub Apr 1, 2026
89b7732
adds checks and routines to prevent text labels in depictions (freque…
JonasSchaub Apr 1, 2026
bd47ec3
refactoring
JonasSchaub Apr 1, 2026
e5b37e1
adds screenshot functionality and button to the overview view
JonasSchaub Apr 1, 2026
5164055
makes FORMATS array thread-safe
JonasSchaub Apr 2, 2026
a47dd90
dispose Graphics2D instance after method call
JonasSchaub Apr 2, 2026
bec01dd
use BufferedImage.TYPE_INT_RGB instead of Transparency.OPAQUE
JonasSchaub Apr 2, 2026
fd79b19
revert usage of ThreadLocal again, the clean-up would be too complicated
JonasSchaub Apr 2, 2026
bec7618
removes decimal formats that make no sense for integer values and add…
JonasSchaub Apr 2, 2026
22a514d
implements remaining Copilot suggestions
JonasSchaub Apr 2, 2026
5d7b17f
use constants for standard font
JonasSchaub Apr 2, 2026
ddc98b8
turns integer format patterns into an array, so that the test class d…
JonasSchaub Apr 7, 2026
703a3bd
fix: image height does not reach negative values because of the text …
JonasSchaub Apr 7, 2026
f24eb95
make it a double multiplication
JonasSchaub Apr 7, 2026
bc1a628
fix import order to resolve spotless complaint
JonasSchaub Apr 7, 2026
10820ff
clear up remaining copilot complaints; simplify depictErrorImage()
JonasSchaub Apr 7, 2026
f7f0ef6
implements Copilot suggestions
JonasSchaub Apr 8, 2026
51b4889
implements Copilot suggestions
JonasSchaub Apr 8, 2026
99726eb
actually assign the sanitized file name to the variable and correct doc
JonasSchaub Apr 8, 2026
95fa667
gives speaking names to DepictionUtil methods
JonasSchaub Apr 8, 2026
4355973
fixes spotless complaint
JonasSchaub Apr 8, 2026
a266768
fixes Copilot complaints
JonasSchaub Apr 8, 2026
0dfe467
fixes Copilot complaints
JonasSchaub Apr 8, 2026
81aef13
make file extension check locale-sensitive and extract screenshot tas…
JonasSchaub Apr 8, 2026
e6e8353
implements Copilot suggestions
JonasSchaub Apr 8, 2026
9035b22
reuses font metrics for every cell render in items tab
JonasSchaub Apr 8, 2026
3a7761c
implements Copilot suggestions
JonasSchaub Apr 8, 2026
58f873b
improves initial file name generation for exporter and some general r…
JonasSchaub Apr 10, 2026
fff8e09
use given dimensions for error images if possible
JonasSchaub Apr 10, 2026
eaee0c0
disable the screenshot button earlier
JonasSchaub Apr 10, 2026
279ab95
remove misleading comments
JonasSchaub Apr 10, 2026
b3cc75a
updates the tutorial
JonasSchaub Apr 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Tutorial/MORTAR_Tutorial.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -971,7 +971,7 @@ private StackPane createStackPaneWithContextMenuAndStructureDisplayForBar(ImageV
//Note: the used depiction method returns an error image if image creation fails, so nothing else to do here
}
}
Image tmpImage = DepictionUtil.depictImageWithZoomAndFillToFitAndWhiteBackground(
Image tmpImage = DepictionUtil.depictImage(
this.atomContainerForDisplayCache,
this.imageZoomFactor,
this.imageWidth,
Expand All @@ -993,16 +993,17 @@ private StackPane createStackPaneWithContextMenuAndStructureDisplayForBar(ImageV
Clipboard.getSystemClipboard().setContent(tmpSmilesClipboardContent);
});
tmpCopyStructureMenuItem.setOnAction(event -> {
ClipboardContent tmpStructureClipboardContent = new ClipboardContent();
Image tmpCopyImageOnBar = DepictionUtil.depictImageWithZoomAndFillToFitAndWhiteBackground(
//note: making the background transparent leads to problems on Windows, where the background then appears black
Image tmpCopyImageOnBar = DepictionUtil.depictImage(
this.atomContainerForDisplayCache,
12.0,
GuiDefinitions.GUI_COPY_IMAGE_IMAGE_WIDTH,
GuiDefinitions.GUI_COPY_IMAGE_IMAGE_HEIGHT,
true,
true);
tmpStructureClipboardContent.putImage(tmpCopyImageOnBar);
Clipboard.getSystemClipboard().setContent(tmpStructureClipboardContent);
ClipboardContent tmpImageClipboardContent = new ClipboardContent();
tmpImageClipboardContent.putImage(tmpCopyImageOnBar);
Clipboard.getSystemClipboard().setContent(tmpImageClipboardContent);
});
tmpNodePane.addEventHandler(MouseEvent.MOUSE_EXITED, event -> {
tmpNodePane.setStyle("-fx-bar-fill: " + HistogramViewController.HISTOGRAM_BARS_COLOR_HEX_VALUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ public MainViewController(Stage aStage, MainView aMainView, String anAppDir, ICo
this.fragmentationService = new FragmentationService();
this.fragmentationService.reloadFragmenterSettings();
this.fragmentationService.reloadActiveFragmenterAndPipeline();
this.viewToolsManager = new ViewToolsManager(this.configuration);
this.viewToolsManager = new ViewToolsManager(this.configuration, this.settingsContainer);
this.viewToolsManager.reloadViewToolsSettings();
//<editor-fold desc="show MainView inside primaryStage" defaultstate="collapsed">
this.mainTabPane = new TabPane();
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import de.unijena.cheminf.mortar.message.Message;
import de.unijena.cheminf.mortar.model.data.FragmentDataModel;
import de.unijena.cheminf.mortar.model.data.MoleculeDataModel;
import de.unijena.cheminf.mortar.model.settings.SettingsContainer;
import de.unijena.cheminf.mortar.model.util.BasicDefinitions;
import de.unijena.cheminf.mortar.model.util.FileUtil;
import de.unijena.cheminf.mortar.preference.PreferenceContainer;
Expand Down Expand Up @@ -82,10 +83,6 @@ public class ViewToolsManager {
* OverviewViewController instance.
*/
private final OverviewViewController overviewViewController;
/**
* Configuration class to read resource file paths from.
*/
private final IConfiguration configuration;
//</editor-fold>
//
//<editor-fold desc="constructor" defaultstate="collapsed">
Expand All @@ -94,13 +91,13 @@ public class ViewToolsManager {
* Opens a GUI exception alert if they are not.
*
* @param aConfiguration configuration instance to read resource file paths from
* @param aSettingsContainer settings container to read settings from
*/
public ViewToolsManager(IConfiguration aConfiguration) {
this.configuration = aConfiguration;
public ViewToolsManager(IConfiguration aConfiguration, SettingsContainer aSettingsContainer) {
this.viewToolsArray = new IViewToolController[2];
this.histogramViewController = new HistogramViewController(this.configuration);
this.histogramViewController = new HistogramViewController(aConfiguration);
this.viewToolsArray[0] = this.histogramViewController;
this.overviewViewController = new OverviewViewController(this.configuration);
this.overviewViewController = new OverviewViewController(aConfiguration, aSettingsContainer);
this.viewToolsArray[1] = this.overviewViewController;
try {
this.checkViewTools();
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/de/unijena/cheminf/mortar/gui/util/GuiUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ public static void copySelectedTableViewCellsToClipboard(TableView<?> aTableView
tmpCell = aTableView.getColumns().get(tmpColIndex).getCellData(tmpRowIndex);
}
if (tmpCell == null) {
GuiUtil.LOGGER.log(Level.WARNING, "Selected cell in table view is empty and could not be copied to clipboard.");
return;
} else {
ClipboardContent tmpClipboardContent = new ClipboardContent();
Expand Down Expand Up @@ -516,12 +517,21 @@ public static void copySelectedTableViewCellsToClipboard(TableView<?> aTableView
} else {
tmpAtomContainer = ((MoleculeDataModel) aTableView.getItems().get(tmpRowIndex)).getAtomContainer();
}
Image tmpImage = DepictionUtil.depictImageWithZoomAndFillToFitAndWhiteBackground(tmpAtomContainer, 1, GuiDefinitions.GUI_COPY_IMAGE_IMAGE_WIDTH, GuiDefinitions.GUI_COPY_IMAGE_IMAGE_HEIGHT,true, true);
//note: making the background transparent leads to problems on Windows, where the background then appears black
Image tmpImage = DepictionUtil.depictImage(
tmpAtomContainer,
1.0,
GuiDefinitions.GUI_COPY_IMAGE_IMAGE_WIDTH,
GuiDefinitions.GUI_COPY_IMAGE_IMAGE_HEIGHT,
true,
true);
tmpClipboardContent.putImage(tmpImage);
} catch (CDKException | ClassCastException tmpException) {
//copies the exact image instance already on display in the cell to clipboard, instead of generating a bigger depiction
tmpClipboardContent.putImage(((ImageView) tmpCell).getImage());
}
} else {
GuiUtil.LOGGER.log(Level.WARNING, "Unknown data type in table view cell ({0}) could not be copied to clipboard.", tmpCell.getClass());
return;
}
Clipboard.getSystemClipboard().setContent(tmpClipboardContent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import de.unijena.cheminf.mortar.model.data.DataModelPropertiesForTableView;
import de.unijena.cheminf.mortar.model.data.FragmentDataModel;
import de.unijena.cheminf.mortar.model.data.MoleculeDataModel;
import de.unijena.cheminf.mortar.model.depict.DepictionUtil;
import de.unijena.cheminf.mortar.model.settings.SettingsContainer;

import javafx.beans.binding.Bindings;
Expand All @@ -47,6 +48,8 @@
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;

import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.util.List;

/**
Expand Down Expand Up @@ -95,6 +98,13 @@ public class ItemizationDataTableView extends TableView implements IDataTableVie
* Configuration class to read resource file paths from.
*/
private final IConfiguration configuration;
/**
* Cached {@link FontMetrics} instance (created once in the constructor using
* {@link DepictionUtil#getGraphicsInstanceWithStandardFont(int, int)}) that is reused by the
* fragment-column cell-value factories to avoid allocating a new {@link java.awt.image.BufferedImage}
* and {@link Graphics2D} on every cell render.
*/
private final FontMetrics cachedFontMetrics;
//</editor-fold>
//
/**
Expand All @@ -106,6 +116,11 @@ public class ItemizationDataTableView extends TableView implements IDataTableVie
public ItemizationDataTableView(String aFragmentationName, IConfiguration aConfiguration) {
super();
this.configuration = aConfiguration;
// Build FontMetrics once; the backing Graphics2D is disposed immediately after extraction.
// The FontMetrics object itself is self-contained and can be reused across all cell renders.
Graphics2D tmpGraphics2D = DepictionUtil.getGraphicsInstanceWithStandardFont(1, 1);
this.cachedFontMetrics = tmpGraphics2D.getFontMetrics();
tmpGraphics2D.dispose();
this.setEditable(false);
this.fragmentationName = aFragmentationName;
this.getSelectionModel().setCellSelectionEnabled(true);
Expand Down Expand Up @@ -222,11 +237,14 @@ private void resetFragmentStructureColumns(int anItemAmount) {
return null;
}
FragmentDataModel tmpFragment = cellData.getValue().getFragmentsOfSpecificFragmentation(this.fragmentationName).get(tmpIndex);
if (!cellData.getValue().hasMoleculeUndergoneSpecificFragmentation(this.fragmentationName)) {
return null;
int tmpFrequency = cellData.getValue().getFragmentFrequencyOfSpecificFragmentation(this.fragmentationName).get(tmpFragment.getUniqueSmiles());
String tmpFrequencyString = String.valueOf(tmpFrequency);
// just a precaution; it is highly unlikely that we get that many fragments of the same type for one(!) molecule
// cachedFontMetrics is reused here to avoid allocating a BufferedImage/Graphics2D per cell render
if (!DepictionUtil.isTextNarrowerThanImage(tmpFragment.getStructureImageWidth(), tmpFrequencyString, this.cachedFontMetrics)) {
tmpFrequencyString = DepictionUtil.fitIntegerDisplayToImageWidth(tmpFragment.getStructureImageWidth(), tmpFrequency, this.cachedFontMetrics);
}
String tmpFrequency = cellData.getValue().getFragmentFrequencyOfSpecificFragmentation(this.fragmentationName).get(tmpFragment.getUniqueSmiles()).toString();
return tmpFragment.getStructureWithText(tmpFrequency);
return tmpFragment.getStructureWithText(tmpFrequencyString);
Comment thread
JonasSchaub marked this conversation as resolved.
}));
tmpColumn.setMinWidth(300);
this.fragmentStructureColumn.getColumns().add(tmpColumn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
* @version 1.0.0.0
*/
public class OverviewView extends AnchorPane {
//<editor-fold desc="public static final class constants", defaultstate="collapsed">
//<editor-fold desc="public static final class constants" defaultstate="collapsed">
/**
* Width of columns and rows per page label of the overview view.
*/
Expand Down Expand Up @@ -99,6 +99,10 @@ public class OverviewView extends AnchorPane {
* Button to apply the default configuration to the structure grid pane.
*/
private final Button defaultButton;
/**
* Button to save a screenshot of the structure grid pane.
*/
private final Button screenshotButton;
/**
* Button to close the view.
*/
Expand Down Expand Up @@ -275,7 +279,10 @@ public OverviewView(int aColumnsPerPage, int aRowsPerPage) throws IllegalArgumen
this.closeButton = GuiUtil.getButtonOfStandardSize(Message.get("OverviewView.closeButton.text"));
this.closeButton.setTooltip(GuiUtil.createTooltip(Message.get("OverviewView.closeButton.tooltip")));
//
this.bottomRightHBox.getChildren().add(this.closeButton);
this.screenshotButton = GuiUtil.getButtonOfStandardSize(Message.get("OverviewView.screenshotButton.text"));
this.screenshotButton.setTooltip(GuiUtil.createTooltip(Message.get("OverviewView.screenshotButton.tooltip")));
//
this.bottomRightHBox.getChildren().addAll(this.screenshotButton, this.closeButton);
//
/*
initialization of the imageDimensionsBelowLimitVBox that is meant to be shown when the dimensions of the
Expand Down Expand Up @@ -413,6 +420,15 @@ public Button getCloseButton() {
return this.closeButton;
}
//
/**
* Returns the overview view's screenshot button for saving a PNG image of the current structure grid page.
*
* @return Button
*/
public Button getScreenshotButton() {
return this.screenshotButton;
}
//
/**
* Returns the text field for rows per page entry of the user.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,21 +233,27 @@ public MoleculeDataModel getFirstParentMolecule() {
*/
public ImageView getParentMoleculeStructure() throws NullPointerException {
if (this.parentMolecules.isEmpty()) {
return new ImageView(DepictionUtil.depictErrorImage("No parent molecules", 250, 250));
return new ImageView(DepictionUtil.depictErrorImage("No parent molecules",
(int) super.getStructureImageWidth(),
(int) super.getStructureImageHeight()));
}
if (this.parentMolecule == null) {
this.parentMolecule = this.parentMolecules.stream().findFirst().orElse(null);
}
try {
// throws NullPointerException if parent molecule is null
IAtomContainer tmpAtomContainer = this.parentMolecule.getAtomContainer();
return new ImageView(DepictionUtil.depictImageWithHeight(tmpAtomContainer, super.getStructureImageHeight()));
return new ImageView(
DepictionUtil.depictImageWithDefaultWidthNoZoomNoFillToFitAndTransparentBackground(
tmpAtomContainer, super.getStructureImageHeight()));
} catch (CDKException | NullPointerException anException) {
FragmentDataModel.LOGGER.log(
Level.SEVERE,
String.format("Molecule name: %s; exception: %s", this.parentMolecule.getName(), anException.toString()),
anException);
return new ImageView(DepictionUtil.depictErrorImage(anException.getMessage(), 250, 250));
return new ImageView(DepictionUtil.depictErrorImage(anException.getMessage(),
(int) super.getStructureImageWidth(),
(int) super.getStructureImageHeight()));
}
}
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,10 @@ public boolean hasMoleculeUndergoneSpecificFragmentation(String aKey) {
public ImageView getStructure() {
try {
IAtomContainer tmpAtomContainer = this.getAtomContainer();
return new ImageView(DepictionUtil.depictImageWithZoomAndFillToFit(tmpAtomContainer, 1, this.getStructureImageWidth(), this.getStructureImageHeight(), false));
return new ImageView(DepictionUtil.depictImageWithTransparentBackground(tmpAtomContainer, 1, this.getStructureImageWidth(), this.getStructureImageHeight(), false));
} catch (CDKException aCDKException) {
Logger.getLogger(MoleculeDataModel.class.getName()).log(Level.SEVERE, aCDKException.toString(), aCDKException);
return new ImageView(DepictionUtil.depictErrorImage(aCDKException.getMessage(), 250, 250));
return new ImageView(DepictionUtil.depictErrorImage(aCDKException.getMessage(), (int) this.getStructureImageWidth(), (int) this.getStructureImageHeight()));
}
}
//
Expand All @@ -297,10 +297,10 @@ public ImageView getStructure() {
public ImageView getStructureWithText(String aText){
try {
IAtomContainer tmpAtomContainer = this.getAtomContainer();
return new ImageView(DepictionUtil.depictImageWithText(tmpAtomContainer, 1, this.getStructureImageWidth(), this.getStructureImageHeight(), aText));
return new ImageView(DepictionUtil.depictImageWithTextNoFillToFitAndTransparentBackground(tmpAtomContainer, 1, this.getStructureImageWidth(), this.getStructureImageHeight(), aText));
} catch (CDKException aCDKException) {
Logger.getLogger(MoleculeDataModel.class.getName()).log(Level.SEVERE, aCDKException.toString(), aCDKException);
return new ImageView(DepictionUtil.depictErrorImage(aCDKException.getMessage(), 250, 250));
return new ImageView(DepictionUtil.depictErrorImage(aCDKException.getMessage(), (int) this.getStructureImageWidth(), (int) this.getStructureImageHeight()));
}
}
//
Expand Down
Loading
Loading