Skip to content

Commit

Permalink
Merge pull request #5055 from WeaverThree/wvr-partsinuse-options-b
Browse files Browse the repository at this point in the history
Parts In Use - Filter Mothballed and Spare Part Quality
  • Loading branch information
HammerGS authored Oct 18, 2024
2 parents b299c52 + 0b4d2c4 commit 63ac932
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 74 deletions.
4 changes: 4 additions & 0 deletions MekHQ/resources/mekhq/resources/PartsReportDialog.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Form.title=Parts in Use
btnClose.text=Close
chkIgnoreMothballed.text=Ignore Parts on Mothballed Units
lblIgnoreSparesUnderQuality.text=Ignore Spare Parts Under Quality
110 changes: 72 additions & 38 deletions MekHQ/src/mekhq/campaign/Campaign.java
Original file line number Diff line number Diff line change
Expand Up @@ -2239,76 +2239,110 @@ private PartInUse getPartInUse(Part p) {
return (null != result.getPartToBuy()) ? result : null;
}

private void updatePartInUseData(PartInUse piu, Part p) {
if ((p.getUnit() != null) || (p instanceof MissingPart)) {
piu.setUseCount(piu.getUseCount() + getQuantity(p));
/**
* Add data from an actual part to a PartInUse data element
* @param partInUse part in use record to update
* @param incomingPart new part that needs to be added to this record
* @param ignoreMothballedUnits don't count parts in mothballed units
* @param ignoreSparesUnderQuality don't count spare parts lower than this quality
*/
private void updatePartInUseData(PartInUse partInUse, Part incomingPart,
boolean ignoreMothballedUnits, PartQuality ignoreSparesUnderQuality) {

if (ignoreMothballedUnits && (null != incomingPart.getUnit()) && incomingPart.getUnit().isMothballed()) {
return;
} else if ((incomingPart.getUnit() != null) || (incomingPart instanceof MissingPart)) {
partInUse.setUseCount(partInUse.getUseCount() + getQuantity(incomingPart));
} else {
if (p.isPresent()) {
piu.setStoreCount(piu.getStoreCount() + getQuantity(p));
piu.addSpare(p);
if (incomingPart.isPresent()) {
if (incomingPart.getQuality().toNumeric() < ignoreSparesUnderQuality.toNumeric()) {
return;
} else {
partInUse.setStoreCount(partInUse.getStoreCount() + getQuantity(incomingPart));
partInUse.addSpare(incomingPart);
}
} else {
piu.setTransferCount(piu.getTransferCount() + getQuantity(p));
partInUse.setTransferCount(partInUse.getTransferCount() + getQuantity(incomingPart));
}
}
}

/** Update the piu with the current campaign data */
public void updatePartInUse(PartInUse piu) {
piu.setUseCount(0);
piu.setStoreCount(0);
piu.setTransferCount(0);
piu.setPlannedCount(0);
getWarehouse().forEachPart(p -> {
PartInUse newPiu = getPartInUse(p);
if (piu.equals(newPiu)) {
updatePartInUseData(piu, p);
/**
* Find all the parts that match this PartInUse and update their data
* @param partInUse part in use record to update
* @param ignoreMothballedUnits don't count parts in mothballed units
* @param ignoreSparesUnderQuality don't count spare parts lower than this quality
*/
public void updatePartInUse(PartInUse partInUse, boolean ignoreMothballedUnits,
PartQuality ignoreSparesUnderQuality) {
partInUse.setUseCount(0);
partInUse.setStoreCount(0);
partInUse.setTransferCount(0);
partInUse.setPlannedCount(0);
getWarehouse().forEachPart(incomingPart -> {
PartInUse newPiu = getPartInUse(incomingPart);
if (partInUse.equals(newPiu)) {
updatePartInUseData(partInUse, incomingPart,
ignoreMothballedUnits, ignoreSparesUnderQuality);
}
});
for (IAcquisitionWork maybePart : shoppingList.getPartList()) {
PartInUse newPiu = getPartInUse((Part) maybePart);
if (piu.equals(newPiu)) {
piu.setPlannedCount(piu.getPlannedCount()
+ getQuantity((maybePart instanceof MissingPart) ? ((MissingPart) maybePart).getNewPart()
: (Part) maybePart) * maybePart.getQuantity());
if (partInUse.equals(newPiu)) {
partInUse.setPlannedCount(partInUse.getPlannedCount()
+ getQuantity((maybePart instanceof MissingPart) ?
((MissingPart) maybePart).getNewPart() :
(Part) maybePart) * maybePart.getQuantity());
}
}
}

public Set<PartInUse> getPartsInUse() {
/**
* Create a data set detailing all the parts being used (or not) and their warehouse spares
* @param ignoreMothballedUnits don't count parts in mothballed units
* @param ignoreSparesUnderQuality don't count spare parts lower than this quality
* @return a Set of PartInUse data for display or inspection
*/
public Set<PartInUse> getPartsInUse(boolean ignoreMothballedUnits,
PartQuality ignoreSparesUnderQuality) {
// java.util.Set doesn't supply a get(Object) method, so we have to use a
// java.util.Map
Map<PartInUse, PartInUse> inUse = new HashMap<>();
getWarehouse().forEachPart(p -> {
PartInUse piu = getPartInUse(p);
if (null == piu) {
getWarehouse().forEachPart(incomingPart -> {
PartInUse partInUse = getPartInUse(incomingPart);
if (null == partInUse) {
return;
}
if (inUse.containsKey(piu)) {
piu = inUse.get(piu);
if (inUse.containsKey(partInUse)) {
partInUse = inUse.get(partInUse);
} else {
inUse.put(piu, piu);
inUse.put(partInUse, partInUse);
}
updatePartInUseData(piu, p);
updatePartInUseData(partInUse, incomingPart, ignoreMothballedUnits, ignoreSparesUnderQuality);
});
for (IAcquisitionWork maybePart : shoppingList.getPartList()) {
if (!(maybePart instanceof Part)) {
continue;
}
PartInUse piu = getPartInUse((Part) maybePart);
if (null == piu) {
PartInUse partInUse = getPartInUse((Part) maybePart);
if (null == partInUse) {
continue;
}
if (inUse.containsKey(piu)) {
piu = inUse.get(piu);
if (inUse.containsKey(partInUse)) {
partInUse = inUse.get(partInUse);
} else {
inUse.put(piu, piu);
inUse.put(partInUse, partInUse);
}
piu.setPlannedCount(piu.getPlannedCount()
+ getQuantity((maybePart instanceof MissingPart) ? ((MissingPart) maybePart).getNewPart()
: (Part) maybePart) * maybePart.getQuantity());
partInUse.setPlannedCount(partInUse.getPlannedCount()
+ getQuantity((maybePart instanceof MissingPart) ?
((MissingPart) maybePart).getNewPart() :
(Part) maybePart) * maybePart.getQuantity());

}
return inUse.keySet();
return inUse.keySet().stream()
// Hacky but otherwise we end up with zero lines when filtering things out
.filter(p -> p.getUseCount() != 0 || p.getStoreCount() != 0 || p.getPlannedCount() != 0)
.collect(Collectors.toSet());
}

@Deprecated
Expand Down
102 changes: 86 additions & 16 deletions MekHQ/src/mekhq/gui/dialog/PartsReportDialog.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 The MegaMek Team. All rights reserved.
* Copyright (c) 2020-2024 The MegaMek Team. All rights reserved.
*
* This file is part of MekHQ.
*
Expand All @@ -18,32 +18,40 @@
*/
package mekhq.gui.dialog;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.util.Collections;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.GroupLayout;

import static javax.swing.GroupLayout.Alignment;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.LayoutStyle;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableRowSorter;

import megamek.client.ui.swing.util.UIUtil;
import mekhq.MekHQ;
import mekhq.campaign.Campaign;
import mekhq.campaign.Quartermaster;
import mekhq.campaign.parts.Part;
import mekhq.campaign.parts.PartInUse;
import mekhq.campaign.parts.enums.PartQuality;
import mekhq.campaign.work.IAcquisitionWork;
import mekhq.gui.CampaignGUI;
import mekhq.gui.model.PartsInUseTableModel;
Expand All @@ -56,13 +64,17 @@
*/
public class PartsReportDialog extends JDialog {

private JPanel overviewPartsPanel;
private JCheckBox ignoreMothballedCheck;
private JComboBox<String> ignoreSparesUnderQualityCB;
private JTable overviewPartsInUseTable;
private PartsInUseTableModel overviewPartsModel;

private Campaign campaign;
private CampaignGUI gui;

private final transient ResourceBundle resourceMap = ResourceBundle.getBundle(
"mekhq.resources.PartsReportDialog", MekHQ.getMHQOptions().getLocale());

public PartsReportDialog(CampaignGUI gui, boolean modal) {
super(gui.getFrame(), modal);
this.gui = gui;
Expand All @@ -74,8 +86,16 @@ public PartsReportDialog(CampaignGUI gui, boolean modal) {
}

private void initComponents() {
overviewPartsPanel = new JPanel(new BorderLayout());

this.setTitle(resourceMap.getString("Form.title"));

Container container = this.getContentPane();

GroupLayout layout = new GroupLayout(container);
layout.setAutoCreateGaps(true);
layout.setAutoCreateContainerGaps(true);
container.setLayout(layout);

overviewPartsModel = new PartsInUseTableModel();
overviewPartsInUseTable = new JTable(overviewPartsModel);
overviewPartsInUseTable.setRowSelectionAllowed(false);
Expand Down Expand Up @@ -239,24 +259,72 @@ public void actionPerformed(ActionEvent e) {
new PartsInUseTableModel.ButtonColumn(overviewPartsInUseTable, addInBulk,
PartsInUseTableModel.COL_BUTTON_GMADD_BULK);

overviewPartsPanel.add(new JScrollPane(overviewPartsInUseTable), BorderLayout.CENTER);

JPanel panButtons = new JPanel(new GridBagLayout());
JScrollPane tableScroll = new JScrollPane(overviewPartsInUseTable);

ignoreMothballedCheck = new JCheckBox(resourceMap.getString("chkIgnoreMothballed.text"));
ignoreMothballedCheck.addActionListener(evt -> refreshOverviewPartsInUse());

boolean reverse = campaign.getCampaignOptions().isReverseQualityNames();
String[] qualities = {
" ", // Combo box is blank for first one because it accepts everything and is default
PartQuality.QUALITY_B.toName(reverse),
PartQuality.QUALITY_C.toName(reverse),
PartQuality.QUALITY_D.toName(reverse),
PartQuality.QUALITY_E.toName(reverse),
PartQuality.QUALITY_F.toName(reverse)
};

ignoreSparesUnderQualityCB = new JComboBox<String>(qualities);
ignoreSparesUnderQualityCB.setMaximumSize(ignoreSparesUnderQualityCB.getPreferredSize());
ignoreSparesUnderQualityCB.addActionListener(evt -> refreshOverviewPartsInUse());
JLabel ignorePartsUnderLabel = new JLabel(resourceMap.getString("lblIgnoreSparesUnderQuality.text"));

JButton btnClose = new JButton("Close");
btnClose.addActionListener(evt -> setVisible(false));
panButtons.add(btnClose, new GridBagConstraints());
overviewPartsPanel.add(panButtons, BorderLayout.PAGE_END);

layout.setHorizontalGroup(layout.createParallelGroup()
.addComponent(tableScroll)
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createSequentialGroup()
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(ignorePartsUnderLabel)
.addComponent(ignoreSparesUnderQualityCB)
.addComponent(ignoreMothballedCheck)
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(btnClose))));

this.setLayout(new BorderLayout());
this.add(overviewPartsPanel, BorderLayout.CENTER);
setPreferredSize(new Dimension(1000, 800));
layout.setVerticalGroup(layout.createSequentialGroup()
.addComponent(tableScroll)
.addGroup(layout.createParallelGroup(Alignment.BASELINE)
.addComponent(ignoreMothballedCheck)
.addComponent(ignorePartsUnderLabel)
.addComponent(ignoreSparesUnderQualityCB)
.addComponent(btnClose)));

setPreferredSize(UIUtil.scaleForGUI(1400,1000));

}


/**
* @param rating String containing A to F or space, from combo box
* @return minimum internal quality level to use
*/
private PartQuality getMinimumQuality(String rating) {
if (rating.equals(" ")) {
// The blank spot always means "everything", so minimum = lowest
return PartQuality.QUALITY_A;
} else {
return PartQuality.fromName(rating, campaign.getCampaignOptions().isReverseQualityNames());
}
}

private void refreshOverviewSpecificPart(int row, PartInUse piu, IAcquisitionWork newPart) {
if (piu.equals(new PartInUse((Part) newPart))) {
// Simple update
campaign.updatePartInUse(piu);
campaign.updatePartInUse(piu, ignoreMothballedCheck.isSelected(),
getMinimumQuality((String) ignoreSparesUnderQualityCB.getSelectedItem()));
overviewPartsModel.fireTableRowsUpdated(row, row);
} else {
// Some other part changed; fire a full refresh to be sure
Expand All @@ -265,7 +333,8 @@ private void refreshOverviewSpecificPart(int row, PartInUse piu, IAcquisitionWor
}

private void refreshOverviewPartsInUse() {
overviewPartsModel.setData(campaign.getPartsInUse());
overviewPartsModel.setData(campaign.getPartsInUse(ignoreMothballedCheck.isSelected(),
getMinimumQuality((String) ignoreSparesUnderQualityCB.getSelectedItem())));
TableColumnModel tcm = overviewPartsInUseTable.getColumnModel();
PartsInUseTableModel.ButtonColumn column = (PartsInUseTableModel.ButtonColumn) tcm
.getColumn(PartsInUseTableModel.COL_BUTTON_GMADD)
Expand All @@ -275,4 +344,5 @@ private void refreshOverviewPartsInUse() {
.getCellRenderer();
column.setEnabled(campaign.isGM());
}

}
29 changes: 9 additions & 20 deletions MekHQ/src/mekhq/gui/model/PartsInUseTableModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -199,26 +199,15 @@ public int getAlignment(int column) {
}

public int getPreferredWidth(int column) {
switch (column) {
case COL_PART:
return 300;
case COL_IN_USE:
case COL_STORED:
case COL_TONNAGE:
case COL_IN_TRANSFER:
case COL_COST:
return 20;
case COL_BUTTON_BUY:
case COL_BUTTON_SELL:
return 50;
case COL_BUTTON_GMADD:
return 70;
case COL_BUTTON_BUY_BULK:
case COL_BUTTON_SELL_BULK:
return 80;
default:
return 100;
}
return switch (column) {
case COL_PART -> 260;
case COL_IN_USE, COL_STORED, COL_TONNAGE, COL_IN_TRANSFER -> 15;
case COL_COST -> 40;
case COL_BUTTON_BUY, COL_BUTTON_SELL -> 25;
case COL_BUTTON_GMADD -> 65;
case COL_BUTTON_BUY_BULK, COL_BUTTON_SELL_BULK -> 65;
default -> 100;
};
}

public boolean hasConstantWidth(int col) {
Expand Down

0 comments on commit 63ac932

Please sign in to comment.