From 07394b21642fbb0f06270a570597d33c3c92fa17 Mon Sep 17 00:00:00 2001 From: Michael L Heuer Date: Wed, 9 Oct 2019 10:31:56 -0500 Subject: [PATCH] Add Take SVG screenshot menu item. --- pom.xml | 7 ++- src/main/java/amidst/gui/main/Actions.java | 50 +++++++++++++++++-- .../amidst/gui/main/MainWindowDialogs.java | 14 ++++++ .../java/amidst/gui/main/SVGFileFilter.java | 25 ++++++++++ .../gui/main/menu/AmidstMenuBuilder.java | 3 +- .../amidst/gui/main/menu/MenuShortcuts.java | 3 +- .../java/amidst/gui/main/viewer/Viewer.java | 31 +++++++++++- .../amidst/gui/main/viewer/ViewerFacade.java | 11 +++- 8 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 src/main/java/amidst/gui/main/SVGFileFilter.java diff --git a/pom.xml b/pom.xml index 08e0ea8b0..ea201196c 100644 --- a/pom.xml +++ b/pom.xml @@ -80,6 +80,11 @@ + + org.apache.xmlgraphics + batik-all + 1.11 + com.google.code.gson gson @@ -114,4 +119,4 @@ maven-plugin - \ No newline at end of file + diff --git a/src/main/java/amidst/gui/main/Actions.java b/src/main/java/amidst/gui/main/Actions.java index c8eb5177e..98f8aee76 100644 --- a/src/main/java/amidst/gui/main/Actions.java +++ b/src/main/java/amidst/gui/main/Actions.java @@ -212,13 +212,13 @@ public void copySeedToClipboard() { } @CalledOnlyBy(AmidstThread.EDT) - public void takeScreenshot() { + public void takePngScreenshot() { ViewerFacade viewerFacade = viewerFacadeSupplier.get(); if (viewerFacade != null) { BufferedImage image = viewerFacade.createScreenshot(); String suggestedFilename = "screenshot_" + viewerFacade.getWorldType().getFilenameText() + "_" + viewerFacade.getWorldSeed().getLong() + ".png"; - File file = dialogs.askForScreenshotSaveFile(suggestedFilename); + File file = dialogs.askForSvgScreenshotSaveFile(suggestedFilename); if (file != null) { file = appendPNGFileExtensionIfNecessary(file); if (file.exists() && !file.isFile()) { @@ -241,7 +241,51 @@ public void takeScreenshot() { } } - @CalledOnlyBy(AmidstThread.EDT) + @CalledOnlyBy(AmidstThread.EDT) + public void takeSvgScreenshot() { + ViewerFacade viewerFacade = viewerFacadeSupplier.get(); + if (viewerFacade != null) { + String suggestedFilename = "screenshot_" + viewerFacade.getWorldType().getFilenameText() + "_" + + viewerFacade.getWorldSeed().getLong() + ".svg"; + File file = dialogs.askForScreenshotSaveFile(suggestedFilename); + if (file != null) { + file = appendSvgFileExtensionIfNecessary(file); + if (file.exists() && !file.isFile()) { + String message = "Unable to write screenshot, because the target exists but is not a file: " + + file.getAbsolutePath(); + AmidstLogger.warn(message); + dialogs.displayError(message); + } else if (!canWriteToFile(file)) { + String message = "Unable to write screenshot, because you have no writing permissions: " + + file.getAbsolutePath(); + AmidstLogger.warn(message); + dialogs.displayError(message); + } else if (!file.exists() || dialogs.askToConfirmYesNo( + "Replace file?", + "File already exists. Do you want to replace it?\n" + file.getAbsolutePath() + "")) { + + try { + viewerFacade.writeSvgScreenshot(file); + } + catch (IOException e) { + AmidstLogger.warn(e); + dialogs.displayError(e); + } + } + } + } + } + + @CalledOnlyBy(AmidstThread.EDT) + private File appendSvgFileExtensionIfNecessary(File file) { + String filename = file.getAbsolutePath(); + if (!FileExtensionChecker.hasFileExtension(filename, "svg")) { + filename += ".svg"; + } + return new File(filename); + } + + @CalledOnlyBy(AmidstThread.EDT) public void selectBiomeProfile(BiomeProfile profile) { biomeProfileSelection.set(profile); ViewerFacade viewerFacade = viewerFacadeSupplier.get(); diff --git a/src/main/java/amidst/gui/main/MainWindowDialogs.java b/src/main/java/amidst/gui/main/MainWindowDialogs.java index c521057d5..72ab55f3b 100644 --- a/src/main/java/amidst/gui/main/MainWindowDialogs.java +++ b/src/main/java/amidst/gui/main/MainWindowDialogs.java @@ -74,6 +74,11 @@ public File askForScreenshotSaveFile(String suggestedFilename) { return showSaveDialogAndGetSelectedFileOrNull(createScreenshotSaveFileChooser(suggestedFilename)); } + @CalledOnlyBy(AmidstThread.EDT) + public File askForSvgScreenshotSaveFile(String suggestedFilename) { + return showSaveDialogAndGetSelectedFileOrNull(createSvgScreenshotSaveFileChooser(suggestedFilename)); + } + @CalledOnlyBy(AmidstThread.EDT) private JFileChooser createScreenshotSaveFileChooser(String suggestedFilename) { JFileChooser result = new JFileChooser(); @@ -83,6 +88,15 @@ private JFileChooser createScreenshotSaveFileChooser(String suggestedFilename) { return result; } + @CalledOnlyBy(AmidstThread.EDT) + private JFileChooser createSvgScreenshotSaveFileChooser(String suggestedFilename) { + JFileChooser result = new JFileChooser(); + result.setFileFilter(new SVGFileFilter()); + result.setAcceptAllFileFilterUsed(false); + result.setSelectedFile(new File(suggestedFilename)); + return result; + } + @CalledOnlyBy(AmidstThread.EDT) private File showSaveDialogAndGetSelectedFileOrNull(JFileChooser fileChooser) { if (fileChooser.showSaveDialog(frame) == JFileChooser.APPROVE_OPTION) { diff --git a/src/main/java/amidst/gui/main/SVGFileFilter.java b/src/main/java/amidst/gui/main/SVGFileFilter.java new file mode 100644 index 000000000..0a100065a --- /dev/null +++ b/src/main/java/amidst/gui/main/SVGFileFilter.java @@ -0,0 +1,25 @@ +package amidst.gui.main; + +import java.io.File; + +import javax.swing.filechooser.FileFilter; + +import amidst.documentation.NotThreadSafe; +import amidst.util.FileExtensionChecker; + +@NotThreadSafe +public class SVGFileFilter extends FileFilter { + @Override + public boolean accept(File file) { + if (file.isDirectory()) { + return true; + } else { + return FileExtensionChecker.hasFileExtension(file.getName(), "svg"); + } + } + + @Override + public String getDescription() { + return "Scalable Vector Graphics (*.SVG)"; + } +} diff --git a/src/main/java/amidst/gui/main/menu/AmidstMenuBuilder.java b/src/main/java/amidst/gui/main/menu/AmidstMenuBuilder.java index db3867dfb..b199cda88 100644 --- a/src/main/java/amidst/gui/main/menu/AmidstMenuBuilder.java +++ b/src/main/java/amidst/gui/main/menu/AmidstMenuBuilder.java @@ -97,7 +97,8 @@ private JMenu create_World() { Menus.item(result, actions::howCanIMoveAPlayer, "How can I move a player?", KeyEvent.VK_M); result.addSeparator(); Menus.item(result, actions::copySeedToClipboard, "Copy Seed to Clipboard", KeyEvent.VK_B, MenuShortcuts.COPY_SEED_TO_CLIPBOARD); - Menus.item(result, actions::takeScreenshot, "Take Screenshot ...", KeyEvent.VK_T, MenuShortcuts.TAKE_SCREENSHOT); + Menus.item(result, actions::takePngScreenshot, "Take PNG Screenshot ...", KeyEvent.VK_T, MenuShortcuts.TAKE_PNG_SCREENSHOT); + Menus.item(result, actions::takeSvgScreenshot, "Take SVG Screenshot ...", KeyEvent.VK_Y, MenuShortcuts.TAKE_SVG_SCREENSHOT); // @formatter:on return result; } diff --git a/src/main/java/amidst/gui/main/menu/MenuShortcuts.java b/src/main/java/amidst/gui/main/menu/MenuShortcuts.java index 7ecbe3e62..810792500 100644 --- a/src/main/java/amidst/gui/main/menu/MenuShortcuts.java +++ b/src/main/java/amidst/gui/main/menu/MenuShortcuts.java @@ -29,7 +29,8 @@ public enum MenuShortcuts implements MenuShortcut { SAVE_PLAYER_LOCATIONS("menu S"), RELOAD_PLAYER_LOCATIONS("F5"), COPY_SEED_TO_CLIPBOARD("menu C"), - TAKE_SCREENSHOT("menu T"), + TAKE_PNG_SCREENSHOT("menu T"), + TAKE_SVG_SCREENSHOT("menu Y"), DISPLAY_DIMENSION_OVERWORLD("menu shift 1"), DISPLAY_DIMENSION_END("menu shift 2"), diff --git a/src/main/java/amidst/gui/main/viewer/Viewer.java b/src/main/java/amidst/gui/main/viewer/Viewer.java index 46dcb33bc..dd1644951 100644 --- a/src/main/java/amidst/gui/main/viewer/Viewer.java +++ b/src/main/java/amidst/gui/main/viewer/Viewer.java @@ -7,6 +7,10 @@ import java.awt.Point; import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + import javax.swing.JComponent; import amidst.documentation.AmidstThread; @@ -14,6 +18,13 @@ import amidst.documentation.NotThreadSafe; import amidst.gui.main.viewer.widget.Widget; +import org.apache.batik.dom.GenericDOMImplementation; + +import org.apache.batik.svggen.SVGGraphics2D; + +import org.w3c.dom.Document; +import org.w3c.dom.DOMImplementation; + @NotThreadSafe public class Viewer { @SuppressWarnings("serial") @@ -45,7 +56,20 @@ public BufferedImage createScreenshot() { return result; } - /** + public void writeSvgScreenshot(final File file) throws IOException { + int width = getWidth(); + int height = getHeight(); + Point mousePosition = getMousePositionOrNull(); + try (FileWriter writer = new FileWriter(file)) { + DOMImplementation domImpl = GenericDOMImplementation.getDOMImplementation(); + Document document = domImpl.createDocument("http://www.w3.org/2000/svg", "svg", null); + SVGGraphics2D svgGenerator = new SVGGraphics2D(document); + drawer.drawScreenshot(svgGenerator, width, height, mousePosition, widgetFontMetrics); + svgGenerator.stream(writer, true); + } + } + + /** * The method getMousePosition() might throw a null pointer exception in * a multi-monitor setup, as soon as the window is dragged to the other * monitor. @@ -83,6 +107,11 @@ public BufferedImage createScreenshot() { return component.createScreenshot(); } + @CalledOnlyBy(AmidstThread.EDT) + public void writeSvgScreenshot(final File file) throws IOException { + component.writeSvgScreenshot(file); + } + @CalledOnlyBy(AmidstThread.EDT) public Point getMousePositionOrCenter() { Point result = component.getMousePositionOrNull(); diff --git a/src/main/java/amidst/gui/main/viewer/ViewerFacade.java b/src/main/java/amidst/gui/main/viewer/ViewerFacade.java index fdd67d9a4..75cbb689b 100644 --- a/src/main/java/amidst/gui/main/viewer/ViewerFacade.java +++ b/src/main/java/amidst/gui/main/viewer/ViewerFacade.java @@ -3,6 +3,10 @@ import java.awt.Component; import java.awt.Point; import java.awt.image.BufferedImage; + +import java.io.File; +import java.io.IOException; + import java.util.List; import amidst.dependency.injection.Factory1; @@ -127,7 +131,12 @@ public BufferedImage createScreenshot() { return viewer.createScreenshot(); } - @CalledOnlyBy(AmidstThread.EDT) + @CalledOnlyBy(AmidstThread.EDT) + public void writeSvgScreenshot(final File file) throws IOException { + viewer.writeSvgScreenshot(file); + } + + @CalledOnlyBy(AmidstThread.EDT) public void adjustZoom(int notches) { zoom.adjustZoom(viewer.getMousePositionOrCenter(), notches); }