Repository: MegaMek/megameklab
Pull request #1763: Sheet preview with zoom+pan
Author: login: exeea
Created: 2025-03-11T04:04:47Z | Merged: 2025-03-12T02:58:31Z

Implemented FR #1638 

- Zoom+Panning in the Record Sheet Preview tab
- Reset zoom/pan with double click or right click menu option
- GPU acceleration for rendering


Diff:
diff --git a/megameklab/src/megameklab/printing/PrintRecordSheet.java b/megameklab/src/megameklab/printing/PrintRecordSheet.java
index 09357af4ad6..0385f09fdc8 100644
--- a/megameklab/src/megameklab/printing/PrintRecordSheet.java
+++ b/megameklab/src/megameklab/printing/PrintRecordSheet.java
@@ -743,12 +743,7 @@ protected int addMultilineTextElement(Element canvas, double x, double y, double
         return lines;
     }
 
-    // Constants used for approximating circles with Bezier curves.
-
-    // Ratio of distance from end point to control point to the radius.
-    private final static double CONST_C = 0.55191502449;
     // Format String for writing a curve to a path definition attribute
-    private final static String FMT_CURVE = " c %f %f,%f %f,%f %f";
     private final static String FMT_LINE = " l %f %f";
 
     protected Element createPip(double x, double y, double radius, double strokeWidth) {
@@ -765,31 +760,35 @@ protected Element createPip(double x, double y, double radius, double strokeWidt
      */
     protected Element createPip(double x, double y, double radius, double strokeWidth,
             PipType type, String fill) {
-        Element path = getSVGDocument().createElementNS(svgNS, SVGConstants.SVG_PATH_TAG);
-        path.setAttributeNS(null, SVGConstants.SVG_FILL_ATTRIBUTE, fill);
-        path.setAttributeNS(null, SVGConstants.SVG_STROKE_ATTRIBUTE, FILL_BLACK);
-        path.setAttributeNS(null, SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, Double.toString(strokeWidth));
 
         // Move to start of pip, at (1, 0)
-        StringBuilder d = new StringBuilder("M").append(x + radius * 2).append(",").append(y + radius);
         if (type == PipType.DIAMOND) {
+            // Use diamond shape for hardened armor pips
+            Element path = getSVGDocument().createElementNS(svgNS, SVGConstants.SVG_PATH_TAG);
+            path.setAttributeNS(null, SVGConstants.SVG_FILL_ATTRIBUTE, fill);
+            path.setAttributeNS(null, SVGConstants.SVG_STROKE_ATTRIBUTE, FILL_BLACK);
+            path.setAttributeNS(null, SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, Double.toString(strokeWidth));
+            StringBuilder d = new StringBuilder("M").append(x + radius * 2).append(",").append(y + radius);
             d.append(String.format(FMT_LINE, -radius, -radius));
             d.append(String.format(FMT_LINE, -radius, radius));
             d.append(String.format(FMT_LINE, radius, radius));
             d.append(String.format(FMT_LINE, radius, -radius));
+            path.setAttributeNS(null, SVGConstants.SVG_D_ATTRIBUTE, d.toString());
+            return path;
         } else {
-            // c is the length of each control line
-            double c = CONST_C * radius;
-
-            // Draw arcs anticlockwise. The coordinates are relative to the beginning of the
-            // arc.
-            d.append(String.format(FMT_CURVE, 0.0, -c, c - radius, -radius, -radius, -radius));
-            d.append(String.format(FMT_CURVE, -c, 0.0, -radius, radius - c, -radius, radius));
-            d.append(String.format(FMT_CURVE, 0.0, c, radius - c, radius, radius, radius));
-            d.append(String.format(FMT_CURVE, c, 0.0, radius, c - radius, radius, -radius));
+            // Use circle element for normal pips
+            Element circle = getSVGDocument().createElementNS(svgNS, SVGConstants.SVG_CIRCLE_TAG);
+            double centerX = x + radius;
+            double centerY = y + radius;
+            circle.setAttributeNS(null, SVGConstants.SVG_CX_ATTRIBUTE, Double.toString(centerX));
+            circle.setAttributeNS(null, SVGConstants.SVG_CY_ATTRIBUTE, Double.toString(centerY));
+            circle.setAttributeNS(null, SVGConstants.SVG_R_ATTRIBUTE, Double.toString(radius));
+            circle.setAttributeNS(null, SVGConstants.SVG_FILL_ATTRIBUTE, fill);
+            circle.setAttributeNS(null, SVGConstants.SVG_STROKE_ATTRIBUTE, FILL_BLACK);
+            circle.setAttributeNS(null, SVGConstants.SVG_STROKE_WIDTH_ATTRIBUTE, Double.toString(strokeWidth));
+            return circle;
+        
         }
-        path.setAttributeNS(null, SVGConstants.SVG_D_ATTRIBUTE, d.toString());
-        return path;
     }
 
     /**
diff --git a/megameklab/src/megameklab/ui/battleArmor/BAMainUI.java b/megameklab/src/megameklab/ui/battleArmor/BAMainUI.java
index 8a973a7aeff..ced6a9b415e 100644
--- a/megameklab/src/megameklab/ui/battleArmor/BAMainUI.java
+++ b/megameklab/src/megameklab/ui/battleArmor/BAMainUI.java
@@ -103,13 +103,12 @@ public void createNewUnit(long entityType, boolean isPrimitive, boolean isIndust
 
     @Override
     public void refreshAll() {
-        statusbar.refresh();
-        structureTab.refresh();
-        refreshEquipment();
+        refreshStatus();
+        refreshStructure();
+        refreshEquipmentTable();
         refreshBuild();
         refreshPreview();
         refreshHeader();
-        floatingEquipmentDatabase.refresh();
     }
 
     @Override
diff --git a/megameklab/src/megameklab/ui/battleArmor/BAStructureTab.java b/megameklab/src/megameklab/ui/battleArmor/BAStructureTab.java
index adc07eff54e..d7f7047c118 100644
--- a/megameklab/src/megameklab/ui/battleArmor/BAStructureTab.java
+++ b/megameklab/src/megameklab/ui/battleArmor/BAStructureTab.java
@@ -14,12 +14,15 @@
  */
 package megameklab.ui.battleArmor;
 
+import java.awt.BorderLayout;
 import java.awt.Dimension;
 import java.awt.GridBagConstraints;
 import java.awt.GridBagLayout;
 import java.awt.Insets;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
 import java.util.List;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -92,9 +95,6 @@ public void setUpPanels() {
         JPanel leftPanel = new JPanel();
         leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS));
 
-        JPanel rightPanel = new JPanel();
-        rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS));
-
         previewTab = new PreviewTab(eSource);
         panBasicInfo = new BasicInfoView(getBattleArmor().getConstructionTechAdvancement());
         panChassis = new BAChassisView(panBasicInfo);
@@ -145,23 +145,31 @@ public void setUpPanels() {
         leftPanel.add(panChassis);
         leftPanel.add(panMovement);
         leftPanel.add(panArmor);
+        leftPanel.add(panEnhancements);
+        leftPanel.add(manipulatorPanel);
+        leftPanel.add(Box.createVerticalGlue());
 
-        rightPanel.add(Box.createVerticalStrut(5));
-        rightPanel.add(previewTab);
-        rightPanel.add(panEnhancements);
-        rightPanel.add(manipulatorPanel);
+        // Right panel setup
+        JPanel rightPanel = new JPanel(new BorderLayout());
+        rightPanel.setPreferredSize(new Dimension(400, 1));
+        rightPanel.add(previewTab, BorderLayout.CENTER);
 
         setLayout(new GridBagLayout());
         gbc = new GridBagConstraints();
-        gbc.insets = new Insets(0, 30, 0, 30);
+        gbc.insets = new Insets(0, 5, 0, 5);
         gbc.gridx = 0;
         gbc.gridy = 0;
-        gbc.anchor = GridBagConstraints.NORTH;
-        gbc.fill = java.awt.GridBagConstraints.NONE;
+        gbc.anchor = GridBagConstraints.NORTHWEST;
+        gbc.fill = java.awt.GridBagConstraints.VERTICAL;
         gbc.weightx = 0.0;
-        gbc.weighty = 0;
+        gbc.weighty = 1.0;
         add(leftPanel, gbc);
+
         gbc.gridx = 1;
+        gbc.gridy = 0;
+        gbc.fill = GridBagConstraints.BOTH;  // Fill both horizontally and vertically
+        gbc.weightx = 1.0;                         // Expand to fill horizontal space
+        gbc.weighty = 1.0;                         // Expand to fill vertical space
         add(rightPanel, gbc);
     }
 
diff --git a/megameklab/src/megameklab/ui/generalUnit/PreviewTab.java b/megameklab/src/megameklab/ui/generalUnit/PreviewTab.java
index bdad788e9a1..549032964fd 100644
--- a/megameklab/src/megameklab/ui/generalUnit/PreviewTab.java
+++ b/megameklab/src/megameklab/ui/generalUnit/PreviewTab.java
@@ -20,10 +20,12 @@
 package megameklab.ui.generalUnit;
 
 import java.awt.BorderLayout;
+import java.awt.Dimension;
 import java.awt.event.ComponentAdapter;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
 
+import javax.swing.JScrollPane;
 import javax.swing.JTabbedPane;
 
 import megamek.client.ui.panes.ConfigurableMekViewPanel;
@@ -45,19 +47,65 @@ public class PreviewTab extends ITab {
     private final MekViewPanel panelTROView = new MekViewPanel();
     private final ConfigurableASCardPanel cardPanel = new ConfigurableASCardPanel(null);
     private final RecordSheetPreviewPanel rsPanel = new RecordSheetPreviewPanel();
+    private JTabbedPane panPreview;
 
     public PreviewTab(EntitySource eSource) {
         super(eSource);
         setLayout(new BorderLayout());
-        JTabbedPane panPreview = new JTabbedPane();
-        panPreview.addTab("Summary", panelMekView);
-        panPreview.addTab("TRO", panelTROView);
+        panelMekView.setMinimumSize(new Dimension(400, panelMekView.getMinimumSize().height));
+        panelTROView.setMinimumSize(new Dimension(400, panelTROView.getMinimumSize().height));
+        
+        // Create scroll panes for each panel
+        JScrollPane mekViewScroll = new JScrollPane(panelMekView);
+        mekViewScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        mekViewScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        mekViewScroll.setBorder(null); // Remove border if desired
+        
+        JScrollPane troViewScroll = new JScrollPane(panelTROView);
+        troViewScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        troViewScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        troViewScroll.setBorder(null);
+
+        panPreview = new JTabbedPane();
+        panPreview.addTab("Summary", mekViewScroll);
+        panPreview.addTab("TRO", troViewScroll);
         panPreview.addTab("AS Card", cardPanel);
         panPreview.addTab("Record Sheet", rsPanel);
         add(panPreview, BorderLayout.CENTER);
+
+        // Add a resize listener to detect container width changes
+        addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(ComponentEvent e) {
+                // Get the actual available width
+                int availableWidth = getWidth();
+                if (availableWidth > 0) {
+                    setPreferredWidth(availableWidth);
+                }
+            }
+        });
+
         addComponentListener(refreshOnShow);
     }
 
+    /**
+     * Sets the preferred width of this component and its child components
+     * @param width The desired width
+     */
+    public void setPreferredWidth(int width) {
+        // Don't change the component's preferred size, just update panel widths
+        int panelWidth = Math.max(400, width - 40);
+        
+        panelMekView.setPreferredSize(new Dimension(panelWidth, panelMekView.getPreferredSize().height));
+        panelTROView.setPreferredSize(new Dimension(panelWidth, panelTROView.getPreferredSize().height));
+        cardPanel.setPreferredSize(new Dimension(panelWidth, cardPanel.getPreferredSize().height));
+        rsPanel.setPreferredSize(new Dimension(panelWidth, rsPanel.getPreferredSize().height));
+        
+        // Force a refresh to ensure content uses the new width
+        revalidate();
+        repaint();
+    }
+    
     public void update() {
         boolean populateTextFields = true;
         final Entity selectedUnit = eSource.getEntity();
diff --git a/megameklab/src/megameklab/ui/generalUnit/RecordSheetPreviewPanel.java b/megameklab/src/megameklab/ui/generalUnit/RecordSheetPreviewPanel.java
index a494c576ec3..35f2e047a8a 100644
--- a/megameklab/src/megameklab/ui/generalUnit/RecordSheetPreviewPanel.java
+++ b/megameklab/src/megameklab/ui/generalUnit/RecordSheetPreviewPanel.java
@@ -20,8 +20,10 @@
 package megameklab.ui.generalUnit;
 
 import java.awt.Color;
+import java.awt.Cursor;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
+import java.awt.Point;
 import java.awt.RenderingHints;
 import java.awt.Toolkit;
 import java.awt.datatransfer.DataFlavor;
@@ -29,10 +31,15 @@
 import java.awt.datatransfer.UnsupportedFlavorException;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
 import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
 import java.awt.image.BufferedImage;
 import java.awt.print.PageFormat;
 import java.io.IOException;
+import java.lang.ref.WeakReference;
 import java.util.List;
 
 import javax.swing.JMenuItem;
@@ -40,6 +47,8 @@
 import javax.swing.JPopupMenu;
 
 import org.apache.batik.gvt.GraphicsNode;
+import org.apache.batik.ext.awt.RenderingHintsKeyExt;
+import org.apache.batik.ext.awt.image.GraphicsUtil;
 
 import megamek.common.Entity;
 import megameklab.printing.PaperSize;
@@ -50,41 +59,21 @@
 
 /**
  * @author pavelbraginskiy
- *
  *         Simply fills itself with the record sheet for the given unit.
  */
 public class RecordSheetPreviewPanel extends JPanel {
     private class RightClickListener extends MouseAdapter {
         private final JPopupMenu popup = new JPopupMenu();
         {
-            var item = new JMenuItem("Copy to clipboard");
-            item.addActionListener(l -> {
-                var parent = RecordSheetPreviewPanel.this;
-                PaperSize pz = new RecordSheetOptions().getPaperSize();
-                BufferedImage img = new BufferedImage(pz.pxWidth * 2, pz.pxHeight * 2, BufferedImage.TYPE_INT_ARGB);
-                parent.paintComponent(img.createGraphics(), pz.pxWidth * 2, pz.pxHeight * 2);
-
-                Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new Transferable() {
-                    @Override
-                    public DataFlavor[] getTransferDataFlavors() {
-                        return new DataFlavor[] { DataFlavor.imageFlavor };
-                    }
-
-                    @Override
-                    public boolean isDataFlavorSupported(DataFlavor flavor) {
-                        return DataFlavor.imageFlavor.equals(flavor);
-                    }
-
-                    @Override
-                    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
-                        if (!isDataFlavorSupported(flavor)) {
-                            throw new UnsupportedFlavorException(flavor);
-                        }
-                        return img;
-                    }
-                }, null);
+            var copyItem = new JMenuItem("Copy to clipboard");
+            copyItem.addActionListener(l -> {
+                copyRecordSheetToClipboard();
             });
-            popup.add(item);
+            popup.add(copyItem);
+
+            var resetItem = new JMenuItem("Reset view");
+            resetItem.addActionListener(l -> resetView());
+            popup.add(resetItem);
         }
 
         @Override
@@ -97,38 +86,83 @@ public void mouseClicked(MouseEvent e) {
         }
     }
 
+    // Zoom and pan state
+    private final double MIN_ZOOM = 1.0;
+    private final double MAX_ZOOM = 4.0;
+    private final double ZOOM_STEP = 0.2;
+    private final double CLIPBOARD_ZOOM_SCALE = 4.0;
+    private double minZoom = getMinimumZoom();
+    private double zoomFactor = minZoom;
+    private double initialZoomFactor = zoomFactor;
+
+    private Point2D panOffset = new Point2D.Double(0, 0);
+    private Point lastMousePoint;
+    private boolean isPanning = false;
+    private boolean isHighQuality = true; // Track rendering quality mode
+
+    // High-resolution cached image for optimized rendering
+    private BufferedImage cachedImage;
+
     public RecordSheetPreviewPanel() {
         addMouseListener(new RightClickListener());
+        setupMouseHandlers();
+        // Enable better rendering
+        setDoubleBuffered(true);
     }
 
-    private Entity entity;
-
-    public void setEntity(Entity entity) {
-        this.entity = entity;
+    /**
+     * Calculate the minimum zoom level needed to fit the record sheet vertically
+     * within the window.
+     * 
+     * @return The minimum zoom factor
+     */
+    private double getMinimumZoom() {
+        RecordSheetOptions options = new RecordSheetOptions();
+        PaperSize pz = options.getPaperSize();
+        double minZoom = getHeight() / (double) pz.pxHeight;
+        return Math.max(MIN_ZOOM, minZoom);
     }
 
-    private void paintComponent(Graphics2D g, int width, int height) {
-        // Set render quality, antialiasing is extremely necessary
+    /**
+     * Creates a complete image of the record sheet and copies it to the clipboard
+     */
+    private void copyRecordSheetToClipboard() {
+        if (entity == null) {
+            return;
+        }
+
+        // Use standard paper size for the clipboard image
+        RecordSheetOptions options = new RecordSheetOptions();
+        PaperSize pz = options.getPaperSize();
+        int imgWidth = (int) Math.ceil(pz.pxWidth * CLIPBOARD_ZOOM_SCALE);
+        int imgHeight = (int) Math.ceil(pz.pxHeight * CLIPBOARD_ZOOM_SCALE);
+
+        BufferedImage img = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB);
+        Graphics2D g = GraphicsUtil.createGraphics(img);
+
+        // Set high quality rendering
         RenderingHints rh = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
         rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
         rh.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        rh.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+        rh.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+        rh.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+        rh.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+        rh.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+        rh.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
+        rh.put(org.apache.batik.ext.awt.RenderingHintsKeyExt.KEY_TRANSCODING,
+                org.apache.batik.ext.awt.RenderingHintsKeyExt.VALUE_TRANSCODING_PRINTING);
         g.setRenderingHints(rh);
 
-        // White background for image
+        // White background
         g.setBackground(Color.WHITE);
-        g.clearRect(0, 0, width, height);
+        g.clearRect(0, 0, imgWidth, imgHeight);
 
-        if (entity != null) {
-            RecordSheetOptions options = new RecordSheetOptions();
-            PrintRecordSheet sheet = UnitPrintManager.createSheets(List.of(entity), true, options)
+        // Render the record sheet directly without zoom/pan
+        PrintRecordSheet sheet = UnitPrintManager.createSheets(List.of(entity), true, options)
                 .stream().findFirst().orElse(null);
-            if (sheet == null) {
-                return;
-            }
 
-            // 5-pixel margin around rs
-            // Except for SmallUnitSheets which have weird clipping issues with nonstandard
-            // margin
+        if (sheet != null) {
             PageFormat pf = new PageFormat();
             if (sheet instanceof PrintSmallUnitSheet) {
                 pf.setPaper(options.getPaperSize().createPaper());
@@ -140,16 +174,294 @@ private void paintComponent(Graphics2D g, int width, int height) {
 
             GraphicsNode gn = sheet.build();
 
-            // Scale record sheet to the size of the panel
+            // Scale to fit the clipboard image
             var bounds = gn.getBounds();
-            var yscale = (height - 5) / bounds.getHeight();
-            var xscale = (width - 5) / bounds.getWidth();
+            var yscale = (imgHeight - 20) / bounds.getHeight();
+            var xscale = (imgWidth - 20) / bounds.getWidth();
             var scale = Math.min(yscale, xscale);
-            gn.setTransform(AffineTransform.getScaleInstance(scale, scale));
 
-            // Draw the completed record sheet SVG to the panel
+            // Center the sheet in the image
+            double centerX = (imgWidth - (bounds.getWidth() * scale)) / 2;
+            double centerY = (imgHeight - (bounds.getHeight() * scale)) / 2;
+
+            AffineTransform transform = new AffineTransform();
+            transform.translate(centerX, centerY);
+            transform.scale(scale, scale);
+            gn.setTransform(transform);
+
+            // Draw to the clipboard image
             gn.paint(g);
         }
+
+        g.dispose();
+
+        // Copy to clipboard
+        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new Transferable() {
+            @Override
+            public DataFlavor[] getTransferDataFlavors() {
+                return new DataFlavor[] { DataFlavor.imageFlavor };
+            }
+
+            @Override
+            public boolean isDataFlavorSupported(DataFlavor flavor) {
+                return DataFlavor.imageFlavor.equals(flavor);
+            }
+
+            @Override
+            public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
+                if (!isDataFlavorSupported(flavor)) {
+                    throw new UnsupportedFlavorException(flavor);
+                }
+                return img;
+            }
+        }, null);
+    }
+
+    private void setupMouseHandlers() {
+        // Set up mouse wheel for zooming
+        addMouseWheelListener(new MouseWheelListener() {
+            @Override
+            public void mouseWheelMoved(MouseWheelEvent e) {
+                // Calculate zoom centered on mouse position
+                Point mousePoint = e.getPoint();
+                double oldZoom = zoomFactor;
+
+                // Adjust zoom by scroll amount
+                zoomFactor -= e.getPreciseWheelRotation() * ZOOM_STEP;
+                zoomFactor = Math.max(minZoom, Math.min(MAX_ZOOM, zoomFactor));
+
+                if (oldZoom != zoomFactor) {
+                    // Adjust pan to keep mouse position fixed
+                    double zoomRatio = zoomFactor / oldZoom;
+
+                    panOffset.setLocation(
+                            mousePoint.getX() - (mousePoint.getX() - panOffset.getX()) * zoomRatio,
+                            mousePoint.getY() - (mousePoint.getY() - panOffset.getY()) * zoomRatio);
+                    cachedImage = null; // Invalidate cached image on zoom change
+                    repaint();
+                }
+            }
+        });
+
+        // Mouse listener for double-click and drag init
+        addMouseListener(new MouseAdapter() {
+            @Override
+            public void mousePressed(MouseEvent e) {
+                if (e.getButton() == MouseEvent.BUTTON1) {
+                    lastMousePoint = e.getPoint();
+                    setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+                    isPanning = true;
+                    // Switch to low quality during panning for better performance
+                    isHighQuality = false;
+                    // repaint();
+                }
+            }
+
+            @Override
+            public void mouseReleased(MouseEvent e) {
+                if (e.getButton() == MouseEvent.BUTTON1) {
+                    setCursor(Cursor.getDefaultCursor());
+                    isPanning = false;
+                    isHighQuality = true;
+                    repaint();
+                }
+            }
+
+            @Override
+            public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
+                    resetView();
+                }
+            }
+        });
+
+        // Mouse motion listener for panning
+        addMouseMotionListener(new MouseMotionAdapter() {
+            @Override
+            public void mouseDragged(MouseEvent e) {
+                if (isPanning && lastMousePoint != null) {
+                    int dx = e.getX() - lastMousePoint.x;
+                    int dy = e.getY() - lastMousePoint.y;
+
+                    panOffset.setLocation(panOffset.getX() + dx, panOffset.getY() + dy);
+
+                    lastMousePoint = e.getPoint();
+                    repaint();
+                }
+            }
+        });
+    }
+
+    private void resetView() {
+        isHighQuality = true;
+        zoomFactor = getMinimumZoom();
+        initialZoomFactor = zoomFactor;
+        renderHighResolutionImage();
+        if (cachedImage != null) {
+
+            // Calculate offsets to center the image
+            double xOffset = (getWidth() - cachedImage.getWidth()) / 2;
+            double yOffset = (getHeight() - cachedImage.getHeight()) / 2;
+
+            // Set pan offset to center the image
+            // Use max(0, value) to avoid negative offsets if image is bigger than panel
+            panOffset.setLocation(
+                    Math.max(0, xOffset),
+                    Math.max(0, yOffset));
+        } else {
+            // Default to 0,0 if no image
+            panOffset.setLocation(0, 0);
+        }
+
+        repaint();
+    }
+
+    private Entity entity;
+    private GraphicsNode gnSheet;
+
+    public void setEntity(Entity entity) {
+        this.entity = entity;
+        // Reset view and invalidate cached image when entity changes
+        cachedImage = null;
+        gnSheet = null;
+        resetView();
+    }
+
+    private void renderHighResolutionImage() {
+        if (entity == null) {
+            cachedImage = null;
+            return;
+        }
+
+        RecordSheetOptions options = new RecordSheetOptions();
+        PaperSize pz = options.getPaperSize();
+        int fullWidth = (int) Math.ceil(pz.pxWidth * zoomFactor);
+        int fullHeight = (int) Math.ceil(pz.pxHeight * zoomFactor);
+
+        // Try to use hardware-accelerated image if possible
+        try {
+            // Get hardware acceleration configuration
+            java.awt.GraphicsEnvironment ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
+            java.awt.GraphicsDevice gs = ge.getDefaultScreenDevice();
+            java.awt.GraphicsConfiguration gc = gs.getDefaultConfiguration();
+
+            // Create a compatible image (should work better with hardware)
+            cachedImage = gc.createCompatibleImage(fullWidth, fullHeight, java.awt.Transparency.OPAQUE);
+        } catch (Exception e) {
+            // Fallback to standard image if hardware acceleration fails
+            cachedImage = new BufferedImage(fullWidth, fullHeight, BufferedImage.TYPE_INT_RGB);
+        }
+        Graphics2D g = GraphicsUtil.createGraphics(cachedImage);
+        g.setComposite(java.awt.AlphaComposite.SrcOver);
+
+        // Set render quality for the high-res image
+        RenderingHints rh = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+        rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        rh.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        rh.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+        rh.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+        rh.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+        rh.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+        rh.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+        rh.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
+        rh.put(org.apache.batik.ext.awt.RenderingHintsKeyExt.KEY_TRANSCODING,
+                org.apache.batik.ext.awt.RenderingHintsKeyExt.VALUE_TRANSCODING_VECTOR);
+        rh.put(RenderingHintsKeyExt.KEY_BUFFERED_IMAGE, new WeakReference<BufferedImage>(cachedImage));
+        g.setRenderingHints(rh);
+
+        // White background
+        g.setBackground(Color.WHITE);
+        g.clearRect(0, 0, fullWidth, fullHeight);
+
+        // Render the record sheet to the high-res image
+        if (gnSheet == null) {
+            PrintRecordSheet sheet = UnitPrintManager.createSheets(List.of(entity), true, options)
+            .stream().findFirst().orElse(null);
+            PageFormat pf = new PageFormat();
+            if (sheet instanceof PrintSmallUnitSheet) {
+                pf.setPaper(options.getPaperSize().createPaper());
+                sheet.createDocument(0, pf, false);
+            } else {
+                pf.setPaper(options.getPaperSize().createPaper(5, 5, 5, 5));
+                sheet.createDocument(0, pf, true);
+            }
+            gnSheet = sheet.build();
+        }
+        if (gnSheet != null) {
+            AffineTransform originalTransform = gnSheet.getTransform();
+            try {
+                var bounds = gnSheet.getBounds();
+                var yscale = (fullHeight - 20) / bounds.getHeight();
+                var xscale = (fullWidth - 20) / bounds.getWidth();
+                var scale = Math.min(yscale, xscale);
+    
+                double centerX = (fullWidth - (bounds.getWidth() * scale)) / 2;
+                double centerY = (fullHeight - (bounds.getHeight() * scale)) / 2;
+    
+                AffineTransform transform = new AffineTransform();
+                transform.translate(centerX, centerY);
+                transform.scale(scale, scale);
+                gnSheet.setTransform(transform);
+    
+                gnSheet.paint(g);
+            } finally {
+                if (originalTransform != null) {
+                    gnSheet.setTransform(originalTransform);
+                }
+            }
+        }
+
+        g.dispose();
+    }
+
+    private void paintComponent(Graphics2D g, int width, int height) {
+        // Set render hints based on quality mode
+        RenderingHints rh = new RenderingHints(
+                RenderingHints.KEY_RENDERING,
+                isHighQuality ? RenderingHints.VALUE_RENDER_QUALITY : RenderingHints.VALUE_RENDER_SPEED);
+        rh.put(RenderingHints.KEY_INTERPOLATION,
+                isHighQuality ? RenderingHints.VALUE_INTERPOLATION_BICUBIC
+                        : RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+        rh.put(RenderingHints.KEY_ANTIALIASING,
+                isHighQuality ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF);
+        rh.put(RenderingHints.KEY_TEXT_ANTIALIASING,
+                isHighQuality ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
+        rh.put(RenderingHints.KEY_FRACTIONALMETRICS,
+                isHighQuality ? RenderingHints.VALUE_FRACTIONALMETRICS_ON : RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
+        rh.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
+
+        g.setRenderingHints(rh);
+
+        // White background
+        g.setBackground(Color.WHITE);
+        g.clearRect(0, 0, width, height);
+
+        if (entity != null) {
+            // Generate high-resolution image if needed
+            if (cachedImage == null) {
+                renderHighResolutionImage();
+            }
+
+            if (cachedImage != null) {
+                // Store original transform for restoration later
+                AffineTransform originalTransform = g.getTransform();
+
+                // Calculate source region (in image coordinates)
+                double srcX = Math.max(0, -panOffset.getX());
+                double srcY = Math.max(0, -panOffset.getY());
+
+                // Create a transform that handles positioning and scaling in one step
+                AffineTransform at = new AffineTransform(originalTransform);
+                at.translate(Math.max(0, panOffset.getX()), Math.max(0, panOffset.getY()));
+                g.setTransform(at);
+
+                // Draw the image
+                g.drawImage(cachedImage, -(int) srcX, -(int) srcY, null);
+
+                // Restore original transform
+                g.setTransform(originalTransform);
+            }
+        }
     }
 
     @Override
@@ -157,4 +469,21 @@ public void paintComponent(Graphics g) {
         super.paintComponent(g);
         paintComponent((Graphics2D) g, getWidth(), getHeight());
     }
+
+    // Add a component resize listener to adjust the view on resize
+    @Override
+    public void addNotify() {
+        super.addNotify();
+        addComponentListener(new java.awt.event.ComponentAdapter() {
+            @Override
+            public void componentResized(java.awt.event.ComponentEvent e) {
+                minZoom = getMinimumZoom();
+                // Only auto-fit if we haven't manually zoomed or panned yet
+                // (This prevents resetting user's view when window is resized)
+                if (cachedImage != null && zoomFactor == initialZoomFactor) {
+                    resetView();
+                }
+            }
+        });
+    }
 }
diff --git a/run.bat b/run.bat
new file mode 100644
index 00000000000..c7acb4545b5
--- /dev/null
+++ b/run.bat
@@ -0,0 +1,16 @@
+@echo off
+echo Starting MegaMekLab...
+
+if exist gradlew.bat (
+    echo Using Gradle wrapper...
+    call gradlew.bat run
+) else (
+    echo Using system Gradle...
+    gradle run
+)
+
+if %ERRORLEVEL% neq 0 (
+    echo Failed to start MegaMekLab
+    pause
+    exit /b %ERRORLEVEL%
+)
\ No newline at end of file


Reviews:
user: login: SJuliez
body: Very nice.
I found a problem: on BattleArmor, the RS preview remains empty. 
Also, I was surprised that dragging the RS is a bit laggy even though it seems to use a low-quality image. Java isn't Unity but map dragging in MM is usually smooth and also has to paint the whole window. I am not requesting to fix this, just wanted to comment.
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-11T08:06:37Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T20:32:10Z
user: login: pavelbraginskiy
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-12T02:57:16Z

Review comments:
diff_hunk: @@ -85,50 +153,276 @@ public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorExcepti
                 }, null);
             });
             popup.add(item);
+
+            // Add zoom reset option
+            var resetZoom = new JMenuItem("Reset view");
+            resetZoom.addActionListener(l -> {
+                zoomFactor = 1.0;
+                panOffset.setLocation(0, 0);
+                isHighQuality = true; // Ensure high quality rendering
+                bufferDirty = true; // Mark buffer as dirty to force redraw
+                repaint();
+            });
+            popup.add(resetZoom);
         }
 
         @Override
         public void mouseClicked(MouseEvent e) {
             super.mouseClicked(e);
-            if (e.getButton() != MouseEvent.BUTTON3) {
-                return;
+            if (e.getButton() == MouseEvent.BUTTON3) {
+                popup.show(e.getComponent(), e.getX(), e.getY());
+            } else if (e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1) {
+                // Reset zoom and pan on double-click
+                zoomFactor = 1.0;
+                panOffset.setLocation(0, 0);
+                isHighQuality = true; // Ensure high quality rendering
+                bufferDirty = true; // Mark buffer as dirty to force redraw
+                repaint();
path: megameklab/src/megameklab/ui/generalUnit/RecordSheetPreviewPanel.java
user: login: SJuliez
body: It seems that this can be placed in a method and unified with the listener above
created_at: 2025-03-11T07:57:37Z
author_association: MEMBER
diff_hunk: @@ -45,19 +47,65 @@
     private final MekViewPanel panelTROView = new MekViewPanel();
     private final ConfigurableASCardPanel cardPanel = new ConfigurableASCardPanel(null);
     private final RecordSheetPreviewPanel rsPanel = new RecordSheetPreviewPanel();
+    private JTabbedPane panPreview;
 
     public PreviewTab(EntitySource eSource) {
         super(eSource);
         setLayout(new BorderLayout());
-        JTabbedPane panPreview = new JTabbedPane();
-        panPreview.addTab("Summary", panelMekView);
-        panPreview.addTab("TRO", panelTROView);
+        panelMekView.setMinimumSize(new Dimension(400, panelMekView.getMinimumSize().height));
path: megameklab/src/megameklab/ui/generalUnit/PreviewTab.java
user: login: github-advanced-security[bot]
body: ## Expression always evaluates to the same value

Expression always evaluates to the same value.

[Show more details](https://github.com/MegaMek/megameklab/security/code-scanning/1925)
created_at: 2025-03-11T20:32:10Z
author_association: NONE
diff_hunk: @@ -45,19 +47,65 @@
     private final MekViewPanel panelTROView = new MekViewPanel();
     private final ConfigurableASCardPanel cardPanel = new ConfigurableASCardPanel(null);
     private final RecordSheetPreviewPanel rsPanel = new RecordSheetPreviewPanel();
+    private JTabbedPane panPreview;
 
     public PreviewTab(EntitySource eSource) {
         super(eSource);
         setLayout(new BorderLayout());
-        JTabbedPane panPreview = new JTabbedPane();
-        panPreview.addTab("Summary", panelMekView);
-        panPreview.addTab("TRO", panelTROView);
+        panelMekView.setMinimumSize(new Dimension(400, panelMekView.getMinimumSize().height));
+        panelTROView.setMinimumSize(new Dimension(400, panelTROView.getMinimumSize().height));
path: megameklab/src/megameklab/ui/generalUnit/PreviewTab.java
user: login: github-advanced-security[bot]
body: ## Expression always evaluates to the same value

Expression always evaluates to the same value.

[Show more details](https://github.com/MegaMek/megameklab/security/code-scanning/1926)
created_at: 2025-03-11T20:32:10Z
author_association: NONE
diff_hunk: @@ -45,19 +47,65 @@
     private final MekViewPanel panelTROView = new MekViewPanel();
     private final ConfigurableASCardPanel cardPanel = new ConfigurableASCardPanel(null);
     private final RecordSheetPreviewPanel rsPanel = new RecordSheetPreviewPanel();
+    private JTabbedPane panPreview;
 
     public PreviewTab(EntitySource eSource) {
         super(eSource);
         setLayout(new BorderLayout());
-        JTabbedPane panPreview = new JTabbedPane();
-        panPreview.addTab("Summary", panelMekView);
-        panPreview.addTab("TRO", panelTROView);
+        panelMekView.setMinimumSize(new Dimension(400, panelMekView.getMinimumSize().height));
+        panelTROView.setMinimumSize(new Dimension(400, panelTROView.getMinimumSize().height));
+        
+        // Create scroll panes for each panel
+        JScrollPane mekViewScroll = new JScrollPane(panelMekView);
+        mekViewScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        mekViewScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        mekViewScroll.setBorder(null); // Remove border if desired
+        
+        JScrollPane troViewScroll = new JScrollPane(panelTROView);
+        troViewScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        troViewScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        troViewScroll.setBorder(null);
+
+        panPreview = new JTabbedPane();
+        panPreview.addTab("Summary", mekViewScroll);
+        panPreview.addTab("TRO", troViewScroll);
         panPreview.addTab("AS Card", cardPanel);
         panPreview.addTab("Record Sheet", rsPanel);
         add(panPreview, BorderLayout.CENTER);
+
+        // Add a resize listener to detect container width changes
+        addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(ComponentEvent e) {
+                // Get the actual available width
+                int availableWidth = getWidth();
+                if (availableWidth > 0) {
+                    setPreferredWidth(availableWidth);
+                }
+            }
+        });
+
         addComponentListener(refreshOnShow);
     }
 
+    /**
+     * Sets the preferred width of this component and its child components
+     * @param width The desired width
+     */
+    public void setPreferredWidth(int width) {
+        // Don't change the component's preferred size, just update panel widths
+        int panelWidth = Math.max(400, width - 40);
+        
+        panelMekView.setPreferredSize(new Dimension(panelWidth, panelMekView.getPreferredSize().height));
path: megameklab/src/megameklab/ui/generalUnit/PreviewTab.java
user: login: github-advanced-security[bot]
body: ## Expression always evaluates to the same value

Expression always evaluates to the same value.

[Show more details](https://github.com/MegaMek/megameklab/security/code-scanning/1927)
created_at: 2025-03-11T20:32:10Z
author_association: NONE
diff_hunk: @@ -45,19 +47,65 @@
     private final MekViewPanel panelTROView = new MekViewPanel();
     private final ConfigurableASCardPanel cardPanel = new ConfigurableASCardPanel(null);
     private final RecordSheetPreviewPanel rsPanel = new RecordSheetPreviewPanel();
+    private JTabbedPane panPreview;
 
     public PreviewTab(EntitySource eSource) {
         super(eSource);
         setLayout(new BorderLayout());
-        JTabbedPane panPreview = new JTabbedPane();
-        panPreview.addTab("Summary", panelMekView);
-        panPreview.addTab("TRO", panelTROView);
+        panelMekView.setMinimumSize(new Dimension(400, panelMekView.getMinimumSize().height));
+        panelTROView.setMinimumSize(new Dimension(400, panelTROView.getMinimumSize().height));
+        
+        // Create scroll panes for each panel
+        JScrollPane mekViewScroll = new JScrollPane(panelMekView);
+        mekViewScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        mekViewScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        mekViewScroll.setBorder(null); // Remove border if desired
+        
+        JScrollPane troViewScroll = new JScrollPane(panelTROView);
+        troViewScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        troViewScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        troViewScroll.setBorder(null);
+
+        panPreview = new JTabbedPane();
+        panPreview.addTab("Summary", mekViewScroll);
+        panPreview.addTab("TRO", troViewScroll);
         panPreview.addTab("AS Card", cardPanel);
         panPreview.addTab("Record Sheet", rsPanel);
         add(panPreview, BorderLayout.CENTER);
+
+        // Add a resize listener to detect container width changes
+        addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(ComponentEvent e) {
+                // Get the actual available width
+                int availableWidth = getWidth();
+                if (availableWidth > 0) {
+                    setPreferredWidth(availableWidth);
+                }
+            }
+        });
+
         addComponentListener(refreshOnShow);
     }
 
+    /**
+     * Sets the preferred width of this component and its child components
+     * @param width The desired width
+     */
+    public void setPreferredWidth(int width) {
+        // Don't change the component's preferred size, just update panel widths
+        int panelWidth = Math.max(400, width - 40);
+        
+        panelMekView.setPreferredSize(new Dimension(panelWidth, panelMekView.getPreferredSize().height));
+        panelTROView.setPreferredSize(new Dimension(panelWidth, panelTROView.getPreferredSize().height));
path: megameklab/src/megameklab/ui/generalUnit/PreviewTab.java
user: login: github-advanced-security[bot]
body: ## Expression always evaluates to the same value

Expression always evaluates to the same value.

[Show more details](https://github.com/MegaMek/megameklab/security/code-scanning/1928)
created_at: 2025-03-11T20:32:10Z
author_association: NONE
diff_hunk: @@ -45,19 +47,65 @@
     private final MekViewPanel panelTROView = new MekViewPanel();
     private final ConfigurableASCardPanel cardPanel = new ConfigurableASCardPanel(null);
     private final RecordSheetPreviewPanel rsPanel = new RecordSheetPreviewPanel();
+    private JTabbedPane panPreview;
 
     public PreviewTab(EntitySource eSource) {
         super(eSource);
         setLayout(new BorderLayout());
-        JTabbedPane panPreview = new JTabbedPane();
-        panPreview.addTab("Summary", panelMekView);
-        panPreview.addTab("TRO", panelTROView);
+        panelMekView.setMinimumSize(new Dimension(400, panelMekView.getMinimumSize().height));
+        panelTROView.setMinimumSize(new Dimension(400, panelTROView.getMinimumSize().height));
+        
+        // Create scroll panes for each panel
+        JScrollPane mekViewScroll = new JScrollPane(panelMekView);
+        mekViewScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        mekViewScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        mekViewScroll.setBorder(null); // Remove border if desired
+        
+        JScrollPane troViewScroll = new JScrollPane(panelTROView);
+        troViewScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+        troViewScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+        troViewScroll.setBorder(null);
+
+        panPreview = new JTabbedPane();
+        panPreview.addTab("Summary", mekViewScroll);
+        panPreview.addTab("TRO", troViewScroll);
         panPreview.addTab("AS Card", cardPanel);
         panPreview.addTab("Record Sheet", rsPanel);
         add(panPreview, BorderLayout.CENTER);
+
+        // Add a resize listener to detect container width changes
+        addComponentListener(new ComponentAdapter() {
+            @Override
+            public void componentResized(ComponentEvent e) {
+                // Get the actual available width
+                int availableWidth = getWidth();
+                if (availableWidth > 0) {
+                    setPreferredWidth(availableWidth);
+                }
+            }
+        });
+
         addComponentListener(refreshOnShow);
     }
 
+    /**
+     * Sets the preferred width of this component and its child components
+     * @param width The desired width
+     */
+    public void setPreferredWidth(int width) {
+        // Don't change the component's preferred size, just update panel widths
+        int panelWidth = Math.max(400, width - 40);
+        
+        panelMekView.setPreferredSize(new Dimension(panelWidth, panelMekView.getPreferredSize().height));
+        panelTROView.setPreferredSize(new Dimension(panelWidth, panelTROView.getPreferredSize().height));
+        cardPanel.setPreferredSize(new Dimension(panelWidth, cardPanel.getPreferredSize().height));
path: megameklab/src/megameklab/ui/generalUnit/PreviewTab.java
user: login: github-advanced-security[bot]
body: ## Expression always evaluates to the same value

Expression always evaluates to the same value.

[Show more details](https://github.com/MegaMek/megameklab/security/code-scanning/1929)
created_at: 2025-03-11T20:32:10Z
author_association: NONE

PR comments:
user: login: exeea
created_at: 2025-03-11T04:09:41Z
body: Please do not merge yet, found a bug with the entity refresh
author_association: COLLABORATOR
user: login: pavelbraginskiy
created_at: 2025-03-11T04:20:41Z
body: Thanks for working on all the fixes I've been procrastinating on! 😅
If you're interested in helping out with a project, I'm working on Handheld Weapons and I haven't started on record sheets for them yet, you can reach out to me `@cat_dev_random` on our Discord if that sounds like something you might want to work on. 
author_association: MEMBER
user: login: codecov[bot]
created_at: 2025-03-11T04:23:42Z
body: ## [Codecov](https://app.codecov.io/gh/MegaMek/megameklab/pull/1763?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MegaMek) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 2.10%. Comparing base [(`74669a5`)](https://app.codecov.io/gh/MegaMek/megameklab/commit/74669a5461de39f1d3f7e190261c48b3a0defcbd?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MegaMek) to head [(`80ce30c`)](https://app.codecov.io/gh/MegaMek/megameklab/commit/80ce30cf7b93dfd5abbf3c9d8eda0764c8890797?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MegaMek).
> Report is 23 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##             master   #1763      +/-   ##
===========================================
- Coverage      2.11%   2.10%   -0.02%     
  Complexity      211     211              
===========================================
  Files           281     281              
  Lines         31904   32120     +216     
  Branches       5396    5411      +15     
===========================================
  Hits            675     675              
- Misses        31069   31285     +216     
  Partials        160     160              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/MegaMek/megameklab/pull/1763?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MegaMek).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=MegaMek).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: exeea
created_at: 2025-03-11T04:54:36Z
body: corrected the repaint issue when edits occurs on the Entity
author_association: COLLABORATOR
user: login: SJuliez
created_at: 2025-03-11T09:15:21Z
body: Looking at the code in the IDE, I also notice that there are a few variables/methods that arent used. Also some fields can be made final (which, ideally, the IDE should give warning about)
author_association: MEMBER
user: login: exeea
created_at: 2025-03-11T13:31:19Z
body: I am refactoring the whole implementation for better performances.
author_association: COLLABORATOR
user: login: exeea
created_at: 2025-03-11T19:04:17Z
body: done, improved the performances and solved the issue of the Battle Armor form
author_association: COLLABORATOR