-
Notifications
You must be signed in to change notification settings - Fork 391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Display progress indicator while exporting screenshot #1419
Merged
DanVanAtta
merged 1 commit into
triplea-game:master
from
ssoloff:issue-982-progress-indicator
Jan 11, 2017
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
src/games/strategy/triplea/ui/export/ScreenshotExporter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
package games.strategy.triplea.ui.export; | ||
|
||
import static com.google.common.base.Preconditions.checkNotNull; | ||
|
||
import java.awt.Color; | ||
import java.awt.Font; | ||
import java.awt.Graphics2D; | ||
import java.awt.geom.AffineTransform; | ||
import java.awt.image.BufferedImage; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.Optional; | ||
|
||
import javax.imageio.ImageIO; | ||
import javax.swing.JOptionPane; | ||
import javax.swing.SwingUtilities; | ||
|
||
import games.strategy.engine.data.GameData; | ||
import games.strategy.engine.history.HistoryNode; | ||
import games.strategy.engine.history.Round; | ||
import games.strategy.triplea.ui.IUIContext; | ||
import games.strategy.triplea.ui.MapPanel; | ||
import games.strategy.triplea.ui.TripleAFrame; | ||
import games.strategy.triplea.ui.mapdata.MapData; | ||
import games.strategy.ui.SwingComponents; | ||
import games.strategy.ui.Util; | ||
|
||
public final class ScreenshotExporter { | ||
private final TripleAFrame frame; | ||
|
||
private ScreenshotExporter(final TripleAFrame frame) { | ||
this.frame = frame; | ||
} | ||
|
||
/** | ||
* Prompts the user for the file to which the screenshot will be saved and saves the screenshot for the specified game | ||
* at the specified history step to that file. | ||
* | ||
* @param frame The frame associated with the game screenshot to export; must not be {@code null}. | ||
* @param gameData The game data; must not be {@code null}. | ||
* @param node The history step at which the game screenshot is to be captured; must not be {@code null}. | ||
*/ | ||
public static void exportScreenshot(final TripleAFrame frame, final GameData gameData, final HistoryNode node) { | ||
checkNotNull(frame); | ||
checkNotNull(gameData); | ||
checkNotNull(node); | ||
|
||
final ScreenshotExporter exporter = new ScreenshotExporter(frame); | ||
exporter.promptSaveFile().ifPresent(file -> exporter.runSave(gameData, node, file)); | ||
} | ||
|
||
private Optional<File> promptSaveFile() { | ||
return SwingComponents.promptSaveFile(frame, "png", "Saved Map Snapshots"); | ||
} | ||
|
||
private void runSave(final GameData gameData, final HistoryNode node, final File file) { | ||
SwingComponents.runWithProgressBar(frame, "Saving map snapshot...", () -> { | ||
save(gameData, node, file); | ||
return null; | ||
}).whenComplete((ignore, e) -> { | ||
SwingUtilities.invokeLater(() -> { | ||
if (e == null) { | ||
JOptionPane.showMessageDialog(frame, "Map Snapshot Saved", "Map Snapshot Saved", | ||
JOptionPane.INFORMATION_MESSAGE); | ||
} else { | ||
JOptionPane.showMessageDialog(frame, e.getMessage(), "Error Saving Map Snapshot", JOptionPane.ERROR_MESSAGE); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
private void save(final GameData gameData, final HistoryNode node, final File file) throws IOException { | ||
// get round/step/player from history tree | ||
int round = 0; | ||
final Object[] pathFromRoot = node.getPath(); | ||
for (final Object pathNode : pathFromRoot) { | ||
final HistoryNode curNode = (HistoryNode) pathNode; | ||
if (curNode instanceof Round) { | ||
round = ((Round) curNode).getRoundNo(); | ||
} | ||
} | ||
final IUIContext iuiContext = frame.getUIContext(); | ||
final double scale = iuiContext.getScale(); | ||
// print map panel to image | ||
final MapPanel mapPanel = frame.getMapPanel(); | ||
final BufferedImage mapImage = | ||
Util.createImage((int) (scale * mapPanel.getImageWidth()), (int) (scale * mapPanel.getImageHeight()), false); | ||
final Graphics2D mapGraphics = mapImage.createGraphics(); | ||
try { | ||
// workaround to get the whole map | ||
// (otherwise the map is cut if current window is not on top of map) | ||
final int xOffset = mapPanel.getXOffset(); | ||
final int yOffset = mapPanel.getYOffset(); | ||
mapPanel.setTopLeft(0, 0); | ||
mapPanel.drawMapImage(mapGraphics); | ||
mapPanel.setTopLeft(xOffset, yOffset); | ||
// overlay title | ||
Color title_color = iuiContext.getMapData().getColorProperty(MapData.PROPERTY_SCREENSHOT_TITLE_COLOR); | ||
if (title_color == null) { | ||
title_color = Color.BLACK; | ||
} | ||
final String s_title_x = iuiContext.getMapData().getProperty(MapData.PROPERTY_SCREENSHOT_TITLE_X); | ||
final String s_title_y = iuiContext.getMapData().getProperty(MapData.PROPERTY_SCREENSHOT_TITLE_Y); | ||
final String s_title_size = iuiContext.getMapData().getProperty(MapData.PROPERTY_SCREENSHOT_TITLE_FONT_SIZE); | ||
int title_x; | ||
int title_y; | ||
int title_size; | ||
try { | ||
title_x = (int) (Integer.parseInt(s_title_x) * scale); | ||
title_y = (int) (Integer.parseInt(s_title_y) * scale); | ||
title_size = Integer.parseInt(s_title_size); | ||
} catch (final NumberFormatException nfe) { | ||
// choose safe defaults | ||
title_x = (int) (15 * scale); | ||
title_y = (int) (15 * scale); | ||
title_size = 15; | ||
} | ||
// everything else should be scaled down onto map image | ||
final AffineTransform transform = new AffineTransform(); | ||
transform.scale(scale, scale); | ||
mapGraphics.setTransform(transform); | ||
mapGraphics.setFont(new Font("Ariel", Font.BOLD, title_size)); | ||
mapGraphics.setColor(title_color); | ||
if (iuiContext.getMapData().getBooleanProperty(MapData.PROPERTY_SCREENSHOT_TITLE_ENABLED)) { | ||
mapGraphics.drawString(gameData.getGameName() + " Round " + round, title_x, title_y); | ||
} | ||
|
||
// save Image as .png | ||
ImageIO.write(mapImage, "png", file); | ||
} finally { | ||
// Clean up objects. There might be some overkill here, | ||
// but there were memory leaks that are fixed by some/all of these. | ||
mapImage.flush(); | ||
mapGraphics.dispose(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,6 @@ | ||
package games.strategy.triplea.ui.menubar; | ||
|
||
import java.awt.Color; | ||
import java.awt.Font; | ||
import java.awt.Graphics2D; | ||
import java.awt.event.KeyEvent; | ||
import java.awt.geom.AffineTransform; | ||
import java.awt.image.BufferedImage; | ||
import java.io.File; | ||
import java.io.FileWriter; | ||
import java.io.IOException; | ||
|
@@ -20,7 +15,6 @@ | |
import java.util.List; | ||
import java.util.Set; | ||
|
||
import javax.imageio.ImageIO; | ||
import javax.swing.AbstractAction; | ||
import javax.swing.Action; | ||
import javax.swing.JComponent; | ||
|
@@ -30,7 +24,6 @@ | |
import javax.swing.JMenuItem; | ||
import javax.swing.JOptionPane; | ||
import javax.swing.WindowConstants; | ||
import javax.swing.filechooser.FileFilter; | ||
import javax.swing.tree.DefaultMutableTreeNode; | ||
|
||
import games.strategy.debug.ClientLogger; | ||
|
@@ -41,7 +34,6 @@ | |
import games.strategy.engine.data.UnitType; | ||
import games.strategy.engine.data.export.GameDataExporter; | ||
import games.strategy.engine.framework.GameDataUtils; | ||
import games.strategy.engine.framework.ui.SaveGameFileChooser; | ||
import games.strategy.engine.history.HistoryNode; | ||
import games.strategy.engine.history.Round; | ||
import games.strategy.engine.history.Step; | ||
|
@@ -51,13 +43,11 @@ | |
import games.strategy.triplea.printgenerator.SetupFrame; | ||
import games.strategy.triplea.ui.ExtendedStats; | ||
import games.strategy.triplea.ui.IUIContext; | ||
import games.strategy.triplea.ui.MapPanel; | ||
import games.strategy.triplea.ui.TripleAFrame; | ||
import games.strategy.triplea.ui.export.ScreenshotExporter; | ||
import games.strategy.triplea.ui.history.HistoryPanel; | ||
import games.strategy.triplea.ui.mapdata.MapData; | ||
import games.strategy.triplea.util.PlayerOrderComparator; | ||
import games.strategy.ui.SwingAction; | ||
import games.strategy.ui.Util; | ||
import games.strategy.util.IllegalCharacterRemover; | ||
import games.strategy.util.LocalizeHTML; | ||
|
||
|
@@ -128,140 +118,19 @@ private void exportXMLFile() { | |
|
||
private void addSaveScreenshot(final JMenu parentMenu) { | ||
final AbstractAction abstractAction = SwingAction.of("Export Map Snapshot", e -> { | ||
|
||
// get current history node. if we are in history view, get the selected node. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment was originally in |
||
final HistoryPanel historyPanel = frame.getHistoryPanel(); | ||
final HistoryNode curNode; | ||
if (historyPanel == null) { | ||
curNode = gameData.getHistory().getLastNode(); | ||
} else { | ||
curNode = historyPanel.getCurrentNode(); | ||
} | ||
saveScreenshot(curNode, frame, gameData); | ||
ScreenshotExporter.exportScreenshot(frame, gameData, curNode); | ||
}); | ||
parentMenu.add(abstractAction).setMnemonic(KeyEvent.VK_E); | ||
} | ||
|
||
public static void saveScreenshot(final HistoryNode node, final TripleAFrame frame, final GameData gameData) { | ||
final FileFilter pngFilter = new FileFilter() { | ||
@Override | ||
public boolean accept(final File f) { | ||
if (f.isDirectory()) { | ||
return true; | ||
} else { | ||
return f.getName().endsWith(".png"); | ||
} | ||
} | ||
|
||
@Override | ||
public String getDescription() { | ||
return "Saved Screenshots, *.png"; | ||
} | ||
}; | ||
final JFileChooser fileChooser = new SaveGameFileChooser(); | ||
fileChooser.setFileFilter(pngFilter); | ||
final int rVal = fileChooser.showSaveDialog(null); | ||
if (rVal == JFileChooser.APPROVE_OPTION) { | ||
File f = fileChooser.getSelectedFile(); | ||
if (!f.getName().toLowerCase().endsWith(".png")) { | ||
f = new File(f.getParent(), f.getName() + ".png"); | ||
} | ||
// A small warning so users will not over-write a file, | ||
if (f.exists()) { | ||
final int choice = | ||
JOptionPane.showConfirmDialog(null, "A file by that name already exists. Do you wish to over write it?", | ||
"Over-write?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); | ||
if (choice != JOptionPane.OK_OPTION) { | ||
return; | ||
} | ||
} | ||
final File file = f; | ||
final Runnable t = () -> { | ||
if (saveScreenshot(node, file, frame, gameData)) { | ||
JOptionPane.showMessageDialog(null, "Map Snapshot Saved", "Map Snapshot Saved", | ||
JOptionPane.INFORMATION_MESSAGE); | ||
} | ||
}; | ||
SwingAction.invokeAndWait(t); | ||
} | ||
} | ||
|
||
private static boolean saveScreenshot(final HistoryNode node, final File file, final TripleAFrame frame, | ||
final GameData gameData) { | ||
// get current history node. if we are in history view, get the selected node. | ||
boolean retval = true; | ||
// get round/step/player from history tree | ||
int round = 0; | ||
final Object[] pathFromRoot = node.getPath(); | ||
for (final Object pathNode : pathFromRoot) { | ||
final HistoryNode curNode = (HistoryNode) pathNode; | ||
if (curNode instanceof Round) { | ||
round = ((Round) curNode).getRoundNo(); | ||
} | ||
} | ||
final IUIContext iuiContext = frame.getUIContext(); | ||
final double scale = iuiContext.getScale(); | ||
// print map panel to image | ||
|
||
final MapPanel mapPanel = frame.getMapPanel(); | ||
final BufferedImage mapImage = | ||
Util.createImage((int) (scale * mapPanel.getImageWidth()), (int) (scale * mapPanel.getImageHeight()), false); | ||
final Graphics2D mapGraphics = mapImage.createGraphics(); | ||
try { | ||
// workaround to get the whole map | ||
// (otherwise the map is cut if current window is not on top of map) | ||
final int xOffset = mapPanel.getXOffset(); | ||
final int yOffset = mapPanel.getYOffset(); | ||
mapPanel.setTopLeft(0, 0); | ||
mapPanel.print(mapGraphics); | ||
mapPanel.setTopLeft(xOffset, yOffset); | ||
// overlay title | ||
Color title_color = iuiContext.getMapData().getColorProperty(MapData.PROPERTY_SCREENSHOT_TITLE_COLOR); | ||
if (title_color == null) { | ||
title_color = Color.BLACK; | ||
} | ||
final String s_title_x = iuiContext.getMapData().getProperty(MapData.PROPERTY_SCREENSHOT_TITLE_X); | ||
final String s_title_y = iuiContext.getMapData().getProperty(MapData.PROPERTY_SCREENSHOT_TITLE_Y); | ||
final String s_title_size = iuiContext.getMapData().getProperty(MapData.PROPERTY_SCREENSHOT_TITLE_FONT_SIZE); | ||
int title_x; | ||
int title_y; | ||
int title_size; | ||
try { | ||
title_x = (int) (Integer.parseInt(s_title_x) * scale); | ||
title_y = (int) (Integer.parseInt(s_title_y) * scale); | ||
title_size = Integer.parseInt(s_title_size); | ||
} catch (final NumberFormatException nfe) { | ||
// choose safe defaults | ||
title_x = (int) (15 * scale); | ||
title_y = (int) (15 * scale); | ||
title_size = 15; | ||
} | ||
// everything else should be scaled down onto map image | ||
final AffineTransform transform = new AffineTransform(); | ||
transform.scale(scale, scale); | ||
mapGraphics.setTransform(transform); | ||
mapGraphics.setFont(new Font("Ariel", Font.BOLD, title_size)); | ||
mapGraphics.setColor(title_color); | ||
if (iuiContext.getMapData().getBooleanProperty(MapData.PROPERTY_SCREENSHOT_TITLE_ENABLED)) { | ||
mapGraphics.drawString(gameData.getGameName() + " Round " + round, title_x, title_y); | ||
} | ||
|
||
// save Image as .png | ||
try { | ||
ImageIO.write(mapImage, "png", file); | ||
} catch (final Exception e2) { | ||
e2.printStackTrace(); | ||
JOptionPane.showMessageDialog(frame, e2.getMessage(), "Error saving Screenshot", JOptionPane.OK_OPTION); | ||
retval = false; | ||
} | ||
// Clean up objects. There might be some overkill here, | ||
// but there were memory leaks that are fixed by some/all of these. | ||
} finally { | ||
mapImage.flush(); | ||
mapGraphics.dispose(); | ||
} | ||
return retval; | ||
} | ||
|
||
private void addExportStatsFull(final JMenu parentMenu) { | ||
final Action showDiceStats = SwingAction.of("Export Full Game Stats", e -> createAndSaveStats(true)); | ||
parentMenu.add(showDiceStats).setMnemonic(KeyEvent.VK_F); | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I discussed in a previous comment, it wasn't clear to me what is the best, if any, way to remove the reference to
TripleAFrame
. Please advise how you'd like to proceed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's reasonable enough to have a
TripleAFrame
object for the moment. A next iteration probably could get rid of it. Instead would still need references for:The rest is Swing code that is mainly used to know where to center the next icon/dialog. It's possible to get rid of those, and to have pop-up windows simply be centered. In one sense we should try to prefer those kinds of APIs since there is no extra JFrame component being passed around and wired all over the place.