diff --git a/megamek/src/megamek/client/ui/dialogs/ASStatsDialog.java b/megamek/src/megamek/client/ui/dialogs/ASStatsDialog.java index 7037d5ce58d..de010b4191f 100644 --- a/megamek/src/megamek/client/ui/dialogs/ASStatsDialog.java +++ b/megamek/src/megamek/client/ui/dialogs/ASStatsDialog.java @@ -24,6 +24,7 @@ import megamek.common.Entity; import megamek.common.alphaStrike.AlphaStrikeElement; import megamek.common.alphaStrike.AlphaStrikeHelper; +import megamek.common.alphaStrike.cardDrawer.ASCardPrinter; import megamek.common.alphaStrike.conversion.ASConverter; import javax.swing.*; @@ -41,8 +42,10 @@ public class ASStatsDialog extends AbstractDialog { private final Collection entities; private final JButton clipBoardButton = new JButton("Copy to Clipboard"); + private final JButton printButton = new JButton("Print"); private final JScrollPane scrollPane = new JScrollPane(); private final JPanel centerPanel = new JPanel(); + private ASStatsTablePanel tablePanel; private static final String COLUMN_SEPARATOR = "\t"; private static final String INTERNAL_DELIMITER = ","; @@ -68,7 +71,9 @@ protected Container createCenterPane() { var optionsPanel = new UIUtil.FixedYPanel(new FlowLayout(FlowLayout.LEFT)); optionsPanel.add(Box.createVerticalStrut(25)); optionsPanel.add(clipBoardButton); + optionsPanel.add(printButton); clipBoardButton.addActionListener(e -> copyToClipboard()); + printButton.addActionListener(ev -> printCards()); scrollPane.getVerticalScrollBar().setUnitIncrement(16); @@ -81,8 +86,8 @@ protected Container createCenterPane() { private void setupTable() { centerPanel.remove(scrollPane); - JPanel asPanel = new ASStatsTablePanel(getFrame()).add(entities, "Selected Units").getPanel(); - scrollPane.setViewportView(asPanel); + tablePanel = new ASStatsTablePanel(getFrame()).add(entities, "Selected Units"); + scrollPane.setViewportView(tablePanel.getPanel()); centerPanel.add(scrollPane); adaptToGUIScale(); } @@ -143,4 +148,8 @@ private StringBuilder dataLine(AlphaStrikeElement element) { private void adaptToGUIScale() { UIUtil.adjustDialog(this, UIUtil.FONT_SCALE1); } + + private void printCards() { + new ASCardPrinter(tablePanel.getElements(), getFrame()).printCards(); + } } \ No newline at end of file diff --git a/megamek/src/megamek/client/ui/swing/ASStatsTablePanel.java b/megamek/src/megamek/client/ui/swing/ASStatsTablePanel.java index 30f845dbd5e..3d36e7f6e23 100644 --- a/megamek/src/megamek/client/ui/swing/ASStatsTablePanel.java +++ b/megamek/src/megamek/client/ui/swing/ASStatsTablePanel.java @@ -43,6 +43,7 @@ public class ASStatsTablePanel { private int rows; private final List groups = new ArrayList<>(); private final JFrame frame; + private final List elements = new ArrayList<>(); /** * Constructs a panel with a table of AlphaStrike stats for any units that are added to it. @@ -95,6 +96,10 @@ public JPanel getPanel() { return panel; } + public List getElements() { + return elements; + } + /** Assembles the JPanel. It is empty before calling this method. */ private void constructPanel() { addVerticalSpace(); @@ -123,6 +128,7 @@ private void addGrouptoPanel(EntityGroup group) { } } } + elements.addAll(elementList); // Print the elements rows++; diff --git a/megamek/src/megamek/client/ui/swing/alphaStrike/ASCardPanel.java b/megamek/src/megamek/client/ui/swing/alphaStrike/ASCardPanel.java index 27b8746f150..db123047af9 100644 --- a/megamek/src/megamek/client/ui/swing/alphaStrike/ASCardPanel.java +++ b/megamek/src/megamek/client/ui/swing/alphaStrike/ASCardPanel.java @@ -116,4 +116,8 @@ protected void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(cardImage, 0, 0, this); } + + public ASCard getCard() { + return card; + } } \ No newline at end of file diff --git a/megamek/src/megamek/client/ui/swing/alphaStrike/ConfigurableASCardPanel.java b/megamek/src/megamek/client/ui/swing/alphaStrike/ConfigurableASCardPanel.java index 2b0ee9fb79b..502267dd9a8 100644 --- a/megamek/src/megamek/client/ui/swing/alphaStrike/ConfigurableASCardPanel.java +++ b/megamek/src/megamek/client/ui/swing/alphaStrike/ConfigurableASCardPanel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 - The MegaMek Team. All Rights Reserved. + * Copyright (c) 2022-2023 - The MegaMek Team. All Rights Reserved. * * This file is part of MegaMek. * @@ -24,6 +24,7 @@ import megamek.client.ui.swing.util.UIUtil; import megamek.common.alphaStrike.ASCardDisplayable; import megamek.common.alphaStrike.AlphaStrikeElement; +import megamek.common.alphaStrike.cardDrawer.ASCardPrinter; import megamek.common.annotations.Nullable; import org.apache.logging.log4j.LogManager; @@ -34,6 +35,7 @@ import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.net.URL; +import java.util.List; /** * This is a JPanel that displays an AlphaStrike unit card and elements to configure the display of @@ -45,6 +47,7 @@ public class ConfigurableASCardPanel extends JPanel { private final JComboBox fontChooser = new JComboBox<>(); private final JComboBox sizeChooser = new JComboBox<>(); private final JButton copyButton = new JButton("Copy to Clipboard"); + private final JButton printButton = new JButton("Print"); private final JButton mulButton = new JButton("MUL"); private final JButton conversionButton = new JButton("Conversion Report"); private final ASCardPanel cardPanel = new ASCardPanel(); @@ -79,6 +82,7 @@ public ConfigurableASCardPanel(@Nullable ASCardDisplayable element, JFrame paren sizeChooser.setRenderer((list, value, index, isSelected, cellHasFocus) -> new JLabel(Float.toString(value))); copyButton.addActionListener(ev -> copyCardToClipboard()); + printButton.addActionListener(ev -> printCard()); mulButton.addActionListener(ev -> showMUL()); mulButton.setToolTipText("Show the Master Unit List entry for this unit. Opens a browser window."); @@ -96,6 +100,8 @@ public ConfigurableASCardPanel(@Nullable ASCardDisplayable element, JFrame paren chooserLine.add(Box.createHorizontalStrut(15)); chooserLine.add(copyButton); chooserLine.add(Box.createHorizontalStrut(15)); + chooserLine.add(printButton); + chooserLine.add(Box.createHorizontalStrut(15)); chooserLine.add(mulButton); chooserLine.add(Box.createHorizontalStrut(15)); chooserLine.add(conversionButton); @@ -166,6 +172,10 @@ private void showConversionReport() { } } + private void printCard() { + new ASCardPrinter(List.of(element), parent).printCards(); + } + // Taken from https://alvinalexander.com/java/java-copy-image-to-clipboard-example/ private void copyCardToClipboard() { ImageSelection imgSel = new ImageSelection(cardPanel.getCardImage()); diff --git a/megamek/src/megamek/client/ui/swing/util/UIUtil.java b/megamek/src/megamek/client/ui/swing/util/UIUtil.java index 7e6130dd7c6..0c16379301b 100644 --- a/megamek/src/megamek/client/ui/swing/util/UIUtil.java +++ b/megamek/src/megamek/client/ui/swing/util/UIUtil.java @@ -416,7 +416,7 @@ public static void adjustContainer(Container parentCon, int fontSize) { if ((comp instanceof JButton) || (comp instanceof JLabel) || (comp instanceof JComboBox) || (comp instanceof JTextField) || (comp instanceof JSlider) || (comp instanceof JSpinner) || (comp instanceof JTextArea) || (comp instanceof JToggleButton) - || (comp instanceof JTable) || (comp instanceof JList) + || (comp instanceof JTable) || (comp instanceof JList) || (comp instanceof JProgressBar) || (comp instanceof JEditorPane) || (comp instanceof JTree)) { if ((comp.getFont() != null) && (sf != comp.getFont().getSize())) { comp.setFont(comp.getFont().deriveFont((float) sf)); diff --git a/megamek/src/megamek/common/alphaStrike/cardDrawer/ASCard.java b/megamek/src/megamek/common/alphaStrike/cardDrawer/ASCard.java index 281ed983d89..a9f4ecac0fc 100644 --- a/megamek/src/megamek/common/alphaStrike/cardDrawer/ASCard.java +++ b/megamek/src/megamek/common/alphaStrike/cardDrawer/ASCard.java @@ -52,8 +52,9 @@ */ public class ASCard { - protected final static int WIDTH = 1050; - protected final static int HEIGHT = 750; + public final static int WIDTH = 1050; + public final static int HEIGHT = 750; + public final static double PRINT_SCALE = 3.5 * 72 / WIDTH; // 3.5" wide, 1/72 dots per inch protected final static int BORDER = 21; protected final static int ARMOR_PIP_SIZE = 22; protected final static int DAMAGE_PIP_SIZE = 18; @@ -222,7 +223,7 @@ protected void initializeFonts(Font lightFont, Font boldFont, Font blackFont) { } /** This method controls drawing the card. */ - protected final void drawCard(Graphics g) { + public final void drawCard(Graphics g) { initializeFonts(lightFont, boldFont, blackFont); Graphics2D g2D = (Graphics2D) g; GUIPreferences.AntiAliasifSet(g); diff --git a/megamek/src/megamek/common/alphaStrike/cardDrawer/ASCardPrinter.java b/megamek/src/megamek/common/alphaStrike/cardDrawer/ASCardPrinter.java new file mode 100644 index 00000000000..d1488e3e6c9 --- /dev/null +++ b/megamek/src/megamek/common/alphaStrike/cardDrawer/ASCardPrinter.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2022-2023 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MegaMek. + * + * MegaMek is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MegaMek is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MegaMek. If not, see . + */ +package megamek.common.alphaStrike.cardDrawer; + +import megamek.MMConstants; +import megamek.client.ui.swing.GUIPreferences; +import megamek.client.ui.swing.util.UIUtil; +import megamek.codeUtilities.StringUtility; +import megamek.common.alphaStrike.ASCardDisplayable; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.awt.print.PrinterJob; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * This class prints a collection of one or more Alpha Strike cards. The cards to be printed can be created + * from either an {@link megamek.common.alphaStrike.AlphaStrikeElement} or a {@link megamek.common.MechSummary}. + * It shows a progress bar dialog but the printing happens in the background and the calling window is not blocked. + */ +public class ASCardPrinter implements Printable { + + private final JFrame parent; + private final List cardSlots = new ArrayList<>(); + private ProgressPopup progressPopup; + private int row; + private int column; + private AffineTransform baseTransform; + + // The column and row count depend on the page format of a given print job and are set anew for each print call + private int columnCount = 2; + private int rowCount = 4; + + /** + * Creates a new ASCardPrinter object for the given ASCardDisplayable elements (either + * {@link megamek.common.alphaStrike.AlphaStrikeElement} or {@link megamek.common.MechSummary}. + * The parent is used for the progress dialog. Print the cards by calling {@link #printCards()}. + */ + public ASCardPrinter(Collection elements, JFrame parent) { + Font userSelectedFont = userSelectedFont(); + this.parent = parent; + for (ASCardDisplayable element : elements) { + ASCard card = ASCard.createCard(element); + card.setFont(userSelectedFont); + cardSlots.add(new CardSlot(card, false)); + if (element.usesArcs()) { + cardSlots.add(new CardSlot(card, true)); + } + } + } + + /** + * Starts a printing process for the carsd of this ASCardPrinter. This will display the usual printer + * selection dialog and a progress dialog. Printing itself happens in the background and the progress + * dialog can be closed. + */ + public void printCards() { + PrinterJob job = PrinterJob.getPrinterJob(); + job.setPrintable(this); + boolean doPrint = job.printDialog(); + if (doPrint) { + progressPopup = new ProgressPopup(cardSlots.size(), parent); + progressPopup.setVisible(true); + new PrintCardTask(job).execute(); + } + } + + private class PrintCardTask extends SwingWorker { + + private final PrinterJob job; + + PrintCardTask(PrinterJob job) { + this.job = job; + } + + @Override + protected Void doInBackground() throws Exception { + try { + job.print(); + } catch (PrinterException ex) { + JOptionPane.showMessageDialog(parent, ex.getMessage(), "ERROR", JOptionPane.ERROR_MESSAGE); + } + return null; + } + + @Override + protected void done() { + progressPopup.setVisible(false); + } + } + + private static class ProgressPopup extends JDialog { + private final JProgressBar progressBar = new JProgressBar(); + + ProgressPopup(int maximum, JFrame parent) { + super(parent, "Print Cards"); + progressBar.setMaximum(maximum); + progressBar.setStringPainted(true); + progressBar.setBorder(new EmptyBorder(20, 40, 20, 40)); + getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS)); + add(Box.createVerticalStrut(20)); + add(new JLabel("Printing Alpha Strike Cards...", JLabel.CENTER)); + add(progressBar); + + UIUtil.adjustDialog(ProgressPopup.this, UIUtil.FONT_SCALE1); + setLocationRelativeTo(null); + pack(); + } + } + + @Override + public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException { + double height = 2.5 * 72; + rowCount = (int) (pageFormat.getImageableHeight() / height); + columnCount = (int) (pageFormat.getImageableWidth() / (3.5 * 72)); + if (isPageIndexValid(pageIndex)) { + double fullHeight = rowCount * height; + Graphics2D g2D = (Graphics2D) graphics; + double topCardY = pageFormat.getHeight() / 2 - fullHeight / 2; + g2D.translate(pageFormat.getWidth() / 2, topCardY); + g2D.scale(ASCard.PRINT_SCALE, ASCard.PRINT_SCALE); + baseTransform = g2D.getTransform(); + + int elementIndex = pageStartSlotIndex(pageIndex); + while ((elementIndex < pageStartSlotIndex(pageIndex + 1)) && (elementIndex < cardSlots.size())) { + goToPrintSlot(elementIndex - pageStartSlotIndex(pageIndex), g2D); + CardSlot cardSlot = cardSlots.get(elementIndex); + if (cardSlot.flipSide) { + ((ASLargeAeroCard) (cardSlot.card)).drawFlipside(g2D); + } else { + cardSlot.card.drawCard(g2D); + } + elementIndex++; + } + + final int doneCards = elementIndex; + SwingUtilities.invokeLater(() -> + progressPopup.progressBar.setValue(doneCards)); + return Printable.PAGE_EXISTS; + } else { + return Printable.NO_SUCH_PAGE; + } + } + + private Font userSelectedFont() { + String fontName = GUIPreferences.getInstance().getAsCardFont(); + return (StringUtility.isNullOrBlank(fontName) + ? new Font(MMConstants.FONT_SANS_SERIF, Font.PLAIN, 14) + : Font.decode(fontName)); + } + + private int pageStartSlotIndex(int pageIndex) { + return columnCount * rowCount * pageIndex; + } + + private boolean isPageIndexValid(int pageIndex) { + return cardSlots.size() > pageStartSlotIndex(pageIndex); + } + + /** Sets the translate in the given g2D to the given card slot, going down the first column, then the second... */ + private void goToPrintSlot(int slot, Graphics2D g2D) { + g2D.setTransform(baseTransform); + column = slot / rowCount; + row = slot - column * rowCount; + g2D.translate(-ASCard.WIDTH * columnCount / 2 + ASCard.WIDTH * column, ASCard.HEIGHT * row); + } + + /** Holds the card for a card slot on the page together with the info if this is the front or back side. */ + private static class CardSlot { + ASCard card; + boolean flipSide; + + CardSlot(ASCard card, boolean flipSide) { + this.card = card; + this.flipSide = flipSide; + } + } +} \ No newline at end of file diff --git a/megamek/src/megamek/common/alphaStrike/cardDrawer/ASLargeAeroCard.java b/megamek/src/megamek/common/alphaStrike/cardDrawer/ASLargeAeroCard.java index d01b738526e..a1ee2dec49d 100644 --- a/megamek/src/megamek/common/alphaStrike/cardDrawer/ASLargeAeroCard.java +++ b/megamek/src/megamek/common/alphaStrike/cardDrawer/ASLargeAeroCard.java @@ -18,6 +18,7 @@ */ package megamek.common.alphaStrike.cardDrawer; +import megamek.client.ui.swing.GUIPreferences; import megamek.client.ui.swing.util.StringDrawer; import megamek.common.alphaStrike.*; import megamek.common.util.ImageUtil; @@ -48,8 +49,7 @@ public class ASLargeAeroCard extends ASCard { private Font arcSpecialsFont; private Font critTextFont; - private final StringDrawer.StringDrawerConfig damageValueConfig = new StringDrawer.StringDrawerConfig().centerX() - .scaleX(0.9f).color(Color.BLACK).font(damageFont); + private StringDrawer.StringDrawerConfig damageValueConfig; public ASLargeAeroCard(ASCardDisplayable element) { super(element); @@ -89,6 +89,9 @@ protected void initializeFonts(Font lightFont, Font boldFont, Font blackFont) { specialsHeaderConfig = new StringDrawer.StringDrawerConfig().color(Color.BLACK) .font(blackFont.deriveFont((float)largeAeroSpecialFont.getSize())); + + damageValueConfig = new StringDrawer.StringDrawerConfig().centerX() + .scaleX(0.9f).color(Color.BLACK).font(damageFont); } @Override @@ -99,7 +102,13 @@ protected void initialize() { fluffYCenter = 366; } - private void drawFlipside(Graphics2D g) { + public void drawFlipside(Graphics2D g) { + initializeFonts(lightFont, boldFont, blackFont); + GUIPreferences.AntiAliasifSet(g); + g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); + g.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); + g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); paintCardBackground(g, true); new StringDrawer("WEAPON CRITICALS").at(33, 629).font(arcTitleFont).maxWidth(220) .outline(Color.BLACK, 0.4f).centerY().draw(g);