diff --git a/MekHQ/resources/mekhq/resources/GUI.properties b/MekHQ/resources/mekhq/resources/GUI.properties index 41c3d1b061..2881ad518f 100644 --- a/MekHQ/resources/mekhq/resources/GUI.properties +++ b/MekHQ/resources/mekhq/resources/GUI.properties @@ -380,6 +380,10 @@ HangarReportDialog.title=Hangar Report MaintenanceReportDialog.title=Maintenance Report MaintenanceReportDialog.Unit.title=Maintenance Report for %s +### PartsReportDialog Class +PartQualityReportDialog.title=Part Quality Report +PartQualityReportDialog.Unit.title=Parts Quality Report for %s + ### MonthlyUnitCostReportDialog Class MonthlyUnitCostReportDialog.title=Monthly Cost Report MonthlyUnitCostReportDialog.Unit.title=Monthly Cost Report for %s diff --git a/MekHQ/src/mekhq/campaign/personnel/death/AbstractDeath.java b/MekHQ/src/mekhq/campaign/personnel/death/AbstractDeath.java index 53988dce0b..29ee8487de 100644 --- a/MekHQ/src/mekhq/campaign/personnel/death/AbstractDeath.java +++ b/MekHQ/src/mekhq/campaign/personnel/death/AbstractDeath.java @@ -116,7 +116,7 @@ public Map>> get /** * This is used to determine if a person can die. - * + * * @param person the person to determine for * @param ageGroup the age group of the person in question * @param randomDeath if this is for random death or manual death @@ -144,7 +144,7 @@ public Map>> get // region New Day /** * Processes new day random death for an individual. - * + * * @param campaign the campaign to process * @param today the current day * @param person the person to process diff --git a/MekHQ/src/mekhq/gui/adapter/UnitTableMouseAdapter.java b/MekHQ/src/mekhq/gui/adapter/UnitTableMouseAdapter.java index c9a3b13c56..694bb7b038 100644 --- a/MekHQ/src/mekhq/gui/adapter/UnitTableMouseAdapter.java +++ b/MekHQ/src/mekhq/gui/adapter/UnitTableMouseAdapter.java @@ -18,30 +18,6 @@ */ package mekhq.gui.adapter; -import static megamek.client.ui.WrapLayout.wordWrap; - -import java.awt.event.ActionEvent; -import java.awt.event.MouseEvent; -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.ResourceBundle; -import java.util.UUID; -import java.util.Vector; -import java.util.stream.Stream; - -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JMenu; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; -import javax.swing.JPopupMenu; -import javax.swing.JSplitPane; -import javax.swing.JTable; - import megamek.client.ui.dialogs.BVDisplayDialog; import megamek.client.ui.dialogs.CamoChooserDialog; import megamek.client.ui.swing.UnitEditorDialog; @@ -65,26 +41,14 @@ import mekhq.campaign.parts.equipment.AmmoBin; import mekhq.campaign.personnel.Person; import mekhq.campaign.unit.Unit; -import mekhq.campaign.unit.actions.ActivateUnitAction; -import mekhq.campaign.unit.actions.CancelMothballUnitAction; -import mekhq.campaign.unit.actions.HirePersonnelUnitAction; -import mekhq.campaign.unit.actions.IUnitAction; -import mekhq.campaign.unit.actions.MothballUnitAction; -import mekhq.campaign.unit.actions.RestoreUnitAction; -import mekhq.campaign.unit.actions.StripUnitAction; -import mekhq.campaign.unit.actions.SwapAmmoTypeAction; +import mekhq.campaign.unit.actions.*; import mekhq.gui.CampaignGUI; import mekhq.gui.HangarTab; import mekhq.gui.MekLabTab; -import mekhq.gui.dialog.BombsDialog; -import mekhq.gui.dialog.ChooseRefitDialog; -import mekhq.gui.dialog.LargeCraftAmmoSwapDialog; -import mekhq.gui.dialog.MarkdownEditorDialog; -import mekhq.gui.dialog.MassMothballDialog; -import mekhq.gui.dialog.QuirksDialog; -import mekhq.gui.dialog.SmallSVAmmoSwapDialog; +import mekhq.gui.dialog.*; import mekhq.gui.dialog.reportDialogs.MaintenanceReportDialog; import mekhq.gui.dialog.reportDialogs.MonthlyUnitCostReportDialog; +import mekhq.gui.dialog.reportDialogs.PartQualityReportDialog; import mekhq.gui.enums.MHQTabType; import mekhq.gui.menus.AssignUnitToPersonMenu; import mekhq.gui.menus.ExportUnitSpriteMenu; @@ -92,6 +56,18 @@ import mekhq.gui.utilities.JMenuHelpers; import mekhq.gui.utilities.StaticChecks; +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.*; +import java.util.stream.Stream; + +import static megamek.client.ui.WrapLayout.wordWrap; + public class UnitTableMouseAdapter extends JPopupMenuAdapter { private static final MMLogger logger = MMLogger.create(UnitTableMouseAdapter.class); @@ -129,6 +105,7 @@ public class UnitTableMouseAdapter extends JPopupMenuAdapter { public static final String COMMAND_QUIRKS = "QUIRKS"; public static final String COMMAND_BOMBS = "BOMBS"; public static final String COMMAND_SUPPLY_COST = "SUPPLY_COST"; + public static final String COMMAND_PARTS_REPORT = "PARTS_REPORT"; public static final String COMMAND_TAG_CUSTOM = "TAG_CUSTOM"; public static final String COMMAND_INDI_CAMO = "INDI_CAMO"; public static final String COMMAND_REMOVE_INDI_CAMO = "REMOVE_INDI_CAMO"; @@ -205,6 +182,8 @@ public void actionPerformed(ActionEvent action) { new MaintenanceReportDialog(gui.getFrame(), selectedUnit).setVisible(true); } else if (command.equals(COMMAND_SUPPLY_COST)) { // Single Unit only new MonthlyUnitCostReportDialog(gui.getFrame(), selectedUnit).setVisible(true); + } else if (command.equals(COMMAND_PARTS_REPORT)) { // Single Unit only + new PartQualityReportDialog(gui.getFrame(), selectedUnit).setVisible(true); } else if (command.equals(COMMAND_SET_QUALITY)) { // TODO : Duplicated in PartsTableMouseAdapter#actionPerformed int q = -1; @@ -819,6 +798,13 @@ protected Optional createPopupMenu() { popup.add(menuItem); } + if (oneSelected && gui.getCampaign().getCampaignOptions().isCheckMaintenance()) { + menuItem = new JMenuItem("Show Part Quality Report"); + menuItem.setActionCommand(COMMAND_PARTS_REPORT); + menuItem.addActionListener(this); + popup.add(menuItem); + } + if (areAllConventionalInfantry) { menuItem = new JMenuItem("Disband"); menuItem.setActionCommand(COMMAND_DISBAND); diff --git a/MekHQ/src/mekhq/gui/dialog/reportDialogs/PartQualityReportDialog.java b/MekHQ/src/mekhq/gui/dialog/reportDialogs/PartQualityReportDialog.java new file mode 100644 index 0000000000..d97d699b08 --- /dev/null +++ b/MekHQ/src/mekhq/gui/dialog/reportDialogs/PartQualityReportDialog.java @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved. + * + * This file is part of MekHQ. + * + * MekHQ 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. + * + * MekHQ 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 MekHQ. If not, see . + */ +package mekhq.gui.dialog.reportDialogs; + +import mekhq.MekHQ; +import mekhq.campaign.parts.Part; +import mekhq.campaign.unit.Unit; +import mekhq.utilities.ReportingUtilities; + +import javax.swing.*; +import java.util.*; + +import static mekhq.campaign.parts.Part.QUALITY_A; +import static mekhq.campaign.parts.Part.QUALITY_B; +import static mekhq.campaign.parts.Part.QUALITY_E; +import static mekhq.campaign.parts.Part.QUALITY_F; +import static mekhq.utilities.ReportingUtilities.CLOSING_SPAN_TAG; + +/** + * Represents a dialog for generating a part quality report. + * Extends the {@link AbstractReportDialog} class. + */ +public class PartQualityReportDialog extends AbstractReportDialog { + //region Variable Declarations + private final Unit unit; + //endregion Variable Declarations + + //region Constructors + /** + * Constructs a new instance of {@link PartQualityReportDialog}. + * + * @param frame the parent {@link JFrame} + * @param unit the unit for which the parts quality report is being generated + */ + public PartQualityReportDialog(final JFrame frame, final Unit unit) { + super(frame, "PartQualityReportDialog", "PartQualityReportDialog.title"); + this.unit = unit; + setTitle(String.format(resources.getString("PartQualityReportDialog.Unit.title"), + unit.getName())); + initialize(); + } + //endregion Constructors + + //region Getters + /** + * @return the unit associated with this object + */ + public Unit getUnit() { + return unit; + } + + @Override + protected JTextPane createTxtReport() { + final JTextPane txtReport = new JTextPane(); + txtReport.setContentType("text/html"); + txtReport.setText(getPartsReport(getUnit())); + txtReport.setName("txtReport"); + txtReport.setEditable(false); + txtReport.setCaretPosition(0); + return txtReport; + } + //endregion Getters + + /** + * Produces a Part Quality report for the given unit. The report includes each part's location, + * name, and quality. + * + * @param unit The unit to generate a report for. + * @return An HTML string displaying the status of each part in the unit. + */ + private String getPartsReport(Unit unit) { + // This map will hold part lists, keyed by the location on the unit. + Map> reportMap = new HashMap<>(); + + // Iterate over parts, assigning each to its location in the map. + for (Part part : unit.getParts()) { + String location = part.getLocationName() != null ? part.getLocationName() : unit.getName(); + reportMap.computeIfAbsent(location, k -> new ArrayList<>()).add(part); + } + + // Create a sorted list of locations, excluding the unit's name. + List locations = new ArrayList<>(); + for (String location : reportMap.keySet()) { + if (!location.equals(unit.getName())) { + locations.add(location); + } + } + Collections.sort(locations); + + // Add the unit's name to the start of the sorted locations list. + locations.add(0, unit.getName()); + + // Begin the HTML report. + StringBuilder report = new StringBuilder(""); + + // For each location, add reported details about that location's parts. + for (String location : locations) { + report.append(""); + if (location.equals(unit.getName())) { + String colorCode = getColorCode(unit.getQuality()); + + // Add the location and its colored quality rating to the report. + report.append("") + .append(location) + .append(" - "); + report.append("") + .append(unit.getQualityName()) + .append(""); + report.append(""); + } else { + report.append("").append(location).append(""); + } + report.append("
"); + + // For each part in the current location, add it to the report. + for (Part part : reportMap.get(location)) { + report.append(part.getName()).append(" - "); + + int qualityLevel = part.getQuality(); + String colorCode = getColorCode(qualityLevel); + + report.append(ReportingUtilities.spanOpeningWithCustomColor(colorCode)) + .append(part.getQualityName()).append(CLOSING_SPAN_TAG).append("
"); + } + + // Add a line break between locations. + report.append("
"); + } + + // Finish the HTML report. + report.append(""); + + return report.toString(); + } + + /** + * Returns the color code associated with the given quality level. + * + * @param qualityLevel The quality level for which to retrieve the color code. + * @return The color code associated with the quality level. + */ + private static String getColorCode(int qualityLevel) { + return switch (qualityLevel) { + case QUALITY_A, QUALITY_B -> MekHQ.getMHQOptions().getFontColorNegativeHexColor(); + case QUALITY_E, QUALITY_F -> MekHQ.getMHQOptions().getFontColorPositiveHexColor(); + default -> MekHQ.getMHQOptions().getFontColorWarningHexColor(); + }; + } +}