diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties index 9fde7efc877..5524f0e6c65 100644 --- a/megamek/i18n/megamek/client/messages.properties +++ b/megamek/i18n/megamek/client/messages.properties @@ -475,6 +475,7 @@ BoardView1.Tooltip.Clan=(C) BoardView1.Tooltip.ClanBrackets=[Clan] BoardView1.Tooltip.ClanParens=(Clan) BoardView1.Tooltip.Crippled=CRIPPLED +BoardView1.Tooltip.Destroyed=DESTROYED BoardView1.Tooltip.C3=C3 BoardView1.Tooltip.C3CC=C3CC BoardView1.Tooltip.C3i=C3i @@ -876,6 +877,7 @@ ClientGUI.FatalError.title=Fatal Error ClientGUI.FileSaveDialog.title=Select file to save to... ClientGUI.FileSaveServerDialog.title=Select file to save to... ClientGUI.FileSaveServerDialog.message=Please enter the filename under which the game will be saved to in the server's savegames directory. +ClientGUI.ForceDisplay=Force Display ClientGUI.gameSaveDialogMessage=Do you want to save the game before quitting MegaMek? ClientGUI.gameSaveFirst=Save First? ClientGUI.Hide=Hide @@ -1074,6 +1076,7 @@ CommonMenuBar.viewPlayerSettings=Player Settings CommonMenuBar.viewClientSettings=Client Settings CommonMenuBar.viewGameOptions=Game Options... CommonMenuBar.viewLOSSetting=LOS Setting +CommonMenuBar.viewForceDisplay=Force Display CommonMenuBar.viewMekDisplay=Unit Display CommonMenuBar.viewAccessibilityWindow=Accessibility Window CommonMenuBar.viewIncGUIScale=Increase GUI Scale @@ -1201,6 +1204,9 @@ CommonSettingsDialog.colors.UnitTooltipWeaponColor=Weapon Color CommonSettingsDialog.colors.UnitTooltipQuirkColor=Quirk Color CommonSettingsDialog.colors.VisualRangeColor=Visual Range Color CommonSettingsDialog.colors.warningColor=Warning Color +CommonSettingsDialog.colors.myUnitColor=My Unit Color +CommonSettingsDialog.colors.allyUnitColor=Ally Unit Color +CommonSettingsDialog.colors.enemyUnitColor=Enemy Unit Color CommonSettingsDialog.darkenMapAtNight=Darken Map At Night CommonSettingsDialog.defaultAutoejectDisabled=Disable automatic ejection by default for units added in the lobby. CommonSettingsDialog.defaultWeaponSortOrder.tooltip=Sets the default weapon sort order for chassis/models without a specified default.
Changing this setting won't affect games already in progress. @@ -1862,6 +1868,7 @@ KeyBinds.cmdNames.toggleMinimap=Toggle Minimap KeyBinds.cmdDesc.toggleMinimap=Turns the Minimap on and off KeyBinds.cmdNames.viewLosSetting=LOS Settings KeyBinds.cmdDesc.viewLosSetting=Shows the LOS settings dialog +KeyBinds.cmdNames.toggleForceDisplay=Toggle Force Display KeyBinds.cmdNames.toggleUnitDisplay=Toggle Unit Display KeyBinds.cmdDesc.toggleUnitDisplay=Turns the unit display window on and off KeyBinds.cmdNames.toggleUnitOverview=Toggle Unit Overview diff --git a/megamek/mmconf/defaultKeyBinds.xml b/megamek/mmconf/defaultKeyBinds.xml index d9a694d32da..43ebc238831 100644 --- a/megamek/mmconf/defaultKeyBinds.xml +++ b/megamek/mmconf/defaultKeyBinds.xml @@ -470,8 +470,15 @@ - undoIllegalSteps - 8 + undoIllegalSteps + 8 + 128 + false + + + + toggleForceDisplay + 70 128 false diff --git a/megamek/src/megamek/client/ui/swing/ClientGUI.java b/megamek/src/megamek/client/ui/swing/ClientGUI.java index fa8b123886f..40ef9ae80c7 100644 --- a/megamek/src/megamek/client/ui/swing/ClientGUI.java +++ b/megamek/src/megamek/client/ui/swing/ClientGUI.java @@ -34,6 +34,8 @@ import megamek.client.ui.swing.boardview.BoardView; import megamek.client.ui.swing.dialog.AbstractUnitSelectorDialog; import megamek.client.ui.swing.dialog.MegaMekUnitSelectorDialog; +import megamek.client.ui.swing.forceDisplay.ForceDisplayDialog; +import megamek.client.ui.swing.forceDisplay.ForceDisplayPanel; import megamek.client.ui.swing.lobby.ChatLounge; import megamek.client.ui.swing.lobby.PlayerSettingsDialog; import megamek.client.ui.swing.minimap.Minimap; @@ -146,6 +148,7 @@ public class ClientGUI extends JPanel implements BoardViewListener, // region view menu public static final String VIEW_INCGUISCALE = "viewIncGUIScale"; public static final String VIEW_DECGUISCALE = "viewDecGUIScale"; + public static final String VIEW_FORCE_DISPLAY = "viewForceDisplay"; public static final String VIEW_UNIT_DISPLAY = "viewMekDisplay"; public static final String VIEW_ACCESSIBILITY_WINDOW = "viewAccessibilityWindow"; public static final String VIEW_KEYBINDS_OVERLAY = "viewKeyboardShortcuts"; @@ -242,6 +245,10 @@ public class ClientGUI extends JPanel implements BoardViewListener, public UnitDisplay unitDisplay; private UnitDisplayDialog unitDisplayDialog; + + public ForceDisplayPanel forceDisplayPanel; + private ForceDisplayDialog forceDisplayDialog; + public JDialog minimapW; private MapMenu popup; private UnitOverview uo; @@ -373,6 +380,22 @@ public void setUnitDisplayDialog(final UnitDisplayDialog unitDisplayDialog) { this.unitDisplayDialog = unitDisplayDialog; } + public ForceDisplayPanel getForceDisplayPanel() { + return forceDisplayPanel; + } + + public void setForceDisplayPanel(final ForceDisplayPanel forceDisplayPanel) { + this.forceDisplayPanel = forceDisplayPanel; + } + + public ForceDisplayDialog getForceDisplayDialog() { + return forceDisplayDialog; + } + + public void setForceDisplayDialog(final ForceDisplayDialog forceDisplayDialog) { + this.forceDisplayDialog = forceDisplayDialog; + } + public JDialog getMiniMapDialog() { return minimapW; } @@ -578,6 +601,11 @@ public void windowClosing(WindowEvent e) { setUnitDisplayDialog(new UnitDisplayDialog(getFrame(), this)); getUnitDisplayDialog().setVisible(false); + setForceDisplayPanel(new ForceDisplayPanel(this)); + setForceDisplayDialog(new ForceDisplayDialog(getFrame(), this)); + getForceDisplayDialog().add(getForceDisplayPanel(), BorderLayout.CENTER); + getForceDisplayDialog().setVisible(false); + setMiniReportDisplay(new MiniReportDisplay(this)); setMiniReportDisplayDialog(new MiniReportDisplayDialog(getFrame(), this)); getMiniReportDisplayDialog().setVisible(false); @@ -740,6 +768,9 @@ public void resetWindowPositions() { if (getUnitDisplayDialog() != null) { getUnitDisplayDialog().setBounds(0, 0, getUnitDisplay().getWidth(), getUnitDisplay().getHeight()); } + if (getForceDisplayDialog() != null) { + getForceDisplayDialog().setBounds(0, 0, getForceDisplayPanel().getWidth(), getForceDisplayPanel().getHeight()); + } if (getMiniReportDisplayDialog() != null) { getMiniReportDisplayDialog().setBounds(0, 0, getMiniReportDisplayDialog().getWidth(), getMiniReportDisplayDialog().getHeight()); @@ -856,6 +887,9 @@ public void actionPerformed(ActionEvent event) { case VIEW_UNIT_DISPLAY: GUIP.toggleUnitDisplay(); break; + case VIEW_FORCE_DISPLAY: + GUIP.toggleForceDisplay(); + break; case VIEW_MINI_MAP: GUIP.toggleMinimapEnabled(); break; @@ -1037,6 +1071,11 @@ void saveSettings() { saveSplitPaneLocations(); } + // Force Display Dialog + if (getForceDisplayDialog() != null) { + getForceDisplayDialog().saveSettings(); + } + // Mini Report Dialog if (getMiniReportDisplayDialog() != null) { getMiniReportDisplayDialog().saveSettings(); @@ -1186,6 +1225,7 @@ void switchPanel(GamePhase phase) { maybeShowMinimap(); maybeShowUnitDisplay(); + maybeShowForceDisplay(); maybeShowMiniReport(); maybeShowPlayerList(); @@ -1572,6 +1612,29 @@ public void maybeShowUnitDisplay() { } } + /** Shows or hides the Unit Display based on the current menu setting. */ + public void maybeShowForceDisplay() { + GamePhase phase = getClient().getGame().getPhase(); + + if (phase.isReport()) { + int action = GUIP.getForceDisplayAutoDisplayReportPhase(); + if (action == GUIPreferences.SHOW) { + GUIP.setForceDisplayEnabled(true); + } else if (action == GUIPreferences.HIDE) { + GUIP.setForceDisplayEnabled(false); + } + } else if (phase.isOnMap()) { + int action = GUIP.getForceDisplayAutoDisplayNonReportPhase(); + if (action == GUIPreferences.SHOW) { + GUIP.setForceDisplayEnabled(true); + } else if (action == GUIPreferences.HIDE) { + GUIP.setForceDisplayEnabled(false); + } + } else { + GUIP.setForceDisplayEnabled(false); + } + } + /** * Shows or hides the Unit Display based on the given visible. This works * independently @@ -1608,6 +1671,13 @@ private void setsetDividerLocations() { splitPaneA.setDividerLocation(GUIP.getSplitPaneADividerLocaton()); } + + public void setForceDisplayVisible(boolean visible) { + if (getForceDisplayDialog() != null) { + getForceDisplayDialog().setVisible(visible); + } + } + private void hideEmptyPanel(JPanel p, JSplitPane sp, Double d) { boolean b = false; @@ -2228,6 +2298,7 @@ public void gamePhaseChange(GamePhaseChangeEvent e) { } menuBar.setPhase(phase); + validate(); cb.moveToEnd(); } @@ -2879,6 +2950,8 @@ public void preferenceChange(PreferenceChangeEvent e) { setPlayerListVisible(GUIP.getPlayerListEnabled()); } else if (e.getName().equals(GUIPreferences.UNIT_DISPLAY_ENABLED)) { setUnitDisplayVisible(GUIP.getUnitDisplayEnabled()); + } else if (e.getName().equals(GUIPreferences.FORCE_DISPLAY_ENABLED)) { + setForceDisplayVisible(GUIP.getForceDisplayEnabled()); } else if (e.getName().equals(GUIPreferences.UNIT_DISPLAY_LOCATION)) { setUnitDisplayVisible(GUIP.getUnitDisplayEnabled()); } else if (e.getName().equals(GUIPreferences.MINI_REPORT_ENABLED)) { diff --git a/megamek/src/megamek/client/ui/swing/CommonMenuBar.java b/megamek/src/megamek/client/ui/swing/CommonMenuBar.java index 5383807dbcc..24c4de921ea 100644 --- a/megamek/src/megamek/client/ui/swing/CommonMenuBar.java +++ b/megamek/src/megamek/client/ui/swing/CommonMenuBar.java @@ -107,6 +107,7 @@ public class CommonMenuBar extends JMenuBar implements ActionListener, IPreferen // The View menu private JCheckBoxMenuItem viewMinimap = new JCheckBoxMenuItem(getString("CommonMenuBar.viewMinimap")); private JCheckBoxMenuItem viewMekDisplay = new JCheckBoxMenuItem(getString("CommonMenuBar.viewMekDisplay")); + private JCheckBoxMenuItem viewForceDisplay = new JCheckBoxMenuItem(getString("CommonMenuBar.viewForceDisplay")); private JMenuItem viewAccessibilityWindow = new JMenuItem(getString("CommonMenuBar.viewAccessibilityWindow")); private JCheckBoxMenuItem viewKeybindsOverlay = new JCheckBoxMenuItem(getString("CommonMenuBar.viewKeyboardShortcuts")); private JCheckBoxMenuItem viewPlanetaryConditionsOverlay = new JCheckBoxMenuItem(getString("CommonMenuBar.viewPlanetaryConditions")); @@ -260,6 +261,9 @@ public CommonMenuBar() { initMenuItem(gamePlayerList, menu, VIEW_PLAYER_LIST); GUIP.setPlayerListEnabled(false); gamePlayerList.setSelected(false); + initMenuItem(viewForceDisplay, menu, VIEW_FORCE_DISPLAY); + GUIP.setForceDisplayEnabled(false); + viewForceDisplay.setSelected(false); menu.addSeparator(); initMenuItem(viewKeybindsOverlay, menu, VIEW_KEYBINDS_OVERLAY); @@ -341,6 +345,7 @@ private void setKeyBinds() { viewKeybindsOverlay.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.KEY_BINDS)); viewPlanetaryConditionsOverlay.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.PLANETARY_CONDITIONS)); viewTurnDetailsOverlay.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.TURN_DETAILS)); + viewForceDisplay.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.FORCE_DISPLAY)); viewMekDisplay.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.UNIT_DISPLAY)); viewUnitOverview.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.UNIT_OVERVIEW)); viewLOSSetting.setAccelerator(KeyCommandBind.keyStroke(KeyCommandBind.LOS_SETTING)); @@ -490,6 +495,7 @@ private synchronized void updateEnabledStates() { viewMovModEnvelope.setEnabled(isInGameBoardView); gameRoundReport.setEnabled((isInGame)); viewMekDisplay.setEnabled(isInGameBoardView); + viewForceDisplay.setEnabled(isInGameBoardView); fireSaveWeaponOrder.setEnabled(isInGameBoardView); } @@ -538,6 +544,8 @@ public void preferenceChange(PreferenceChangeEvent e) { setKeyBinds(); } else if (e.getName().equals(GUIPreferences.UNIT_DISPLAY_ENABLED)) { viewMekDisplay.setSelected(GUIP.getUnitDisplayEnabled()); + } else if (e.getName().equals(GUIPreferences.FORCE_DISPLAY_ENABLED)) { + viewForceDisplay.setSelected(GUIP.getForceDisplayEnabled()); } else if (e.getName().equals(GUIPreferences.MINI_REPORT_ENABLED)) { gameRoundReport.setSelected(GUIP.getMiniReportEnabled()); } else if (e.getName().equals(GUIPreferences.PLAYER_LIST_ENABLED)) { diff --git a/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java b/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java index 4dc89834a01..131da058a40 100644 --- a/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java +++ b/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java @@ -163,6 +163,9 @@ private void moveElement(DefaultListModel srcModel, int srcIndex, int trg private ColourSelectorButton csbWarningColor; private ColourSelectorButton csbCautionColor; private ColourSelectorButton csbPrecautionColor; + private ColourSelectorButton csbMyUnitColor; + private ColourSelectorButton csbAllyUnitColor; + private ColourSelectorButton csbEnemyColor; ArrayList playerColours; @@ -354,6 +357,8 @@ private void moveElement(DefaultListModel srcModel, int srcIndex, int trg private JComboBox miniReportAutoDisplayNonReportCombo; private JComboBox playerListAutoDisplayReportCombo; private JComboBox playerListAutoDisplayNonReportCombo; + private JComboBox forceDisplayAutoDisplayReportCombo; + private JComboBox forceDisplayAutoDisplayNonReportCombo; // Report private JTextPane reportKeywordsTextPane; @@ -1546,6 +1551,20 @@ public Component getListCellRendererComponent(JList list, Object value, int i addLineSpacer(comps); + row = new ArrayList<>(); + csbMyUnitColor = new ColourSelectorButton(Messages.getString("CommonSettingsDialog.colors.myUnitColor")); + csbMyUnitColor.setColour(GUIP.getMyUnitColor()); + row.add(csbMyUnitColor); + csbAllyUnitColor = new ColourSelectorButton(Messages.getString("CommonSettingsDialog.colors.allyUnitColor")); + csbAllyUnitColor.setColour(GUIP.getAllyUnitColor()); + row.add(csbAllyUnitColor); + csbEnemyColor = new ColourSelectorButton(Messages.getString("CommonSettingsDialog.colors.enemyUnitColor")); + csbEnemyColor.setColour(GUIP.getEnemyUnitColor()); + row.add(csbEnemyColor); + comps.add(row); + + addLineSpacer(comps); + row = new ArrayList<>(); row.add(getPlayerColourPanel()); comps.add(row); @@ -1866,6 +1885,10 @@ protected void cancelAction() { csbCautionColor.setColour(GUIP.getCautionColor()); csbPrecautionColor.setColour(GUIP.getPrecautionColor()); + csbMyUnitColor.setColour(GUIP.getMyUnitColor()); + csbAllyUnitColor.setColour(GUIP.getAllyUnitColor()); + csbEnemyColor.setColour(GUIP.getEnemyUnitColor()); + for (PlayerColourHelper pch : playerColours) { pch.csb.setColour(GUIP.getColor(pch.pc.getText())); } @@ -1930,6 +1953,8 @@ protected void cancelAction() { miniReportAutoDisplayNonReportCombo.setSelectedItem(GUIP.getMiniReportAutoDisplayNonReportPhase()); playerListAutoDisplayReportCombo.setSelectedItem(GUIP.getPlayerListAutoDisplayReportPhase()); playerListAutoDisplayNonReportCombo.setSelectedItem(GUIP.getPlayerListAutoDisplayNonReportPhase()); + forceDisplayAutoDisplayReportCombo.setSelectedItem(GUIP.getForceDisplayAutoDisplayReportPhase()); + forceDisplayAutoDisplayNonReportCombo.setSelectedItem(GUIP.getForceDisplayAutoDisplayNonReportPhase()); csbUnitDisplayHeatLevel1.setColour(GUIP.getUnitDisplayHeatLevel1()); csbUnitDisplayHeatLevel2.setColour(GUIP.getUnitDisplayHeatLevel2()); @@ -2035,6 +2060,10 @@ protected void okAction() { GUIP.setCautionColor(csbCautionColor.getColour()); GUIP.setPrecautionColor(csbPrecautionColor.getColour()); + GUIP.setMyUnitColor(csbMyUnitColor.getColour()); + GUIP.setAllyUnitColor(csbAllyUnitColor.getColour()); + GUIP.setEnemyUnitColor(csbEnemyColor.getColour()); + for (PlayerColourHelper pch : playerColours) { GUIP.setColor(pch.pc.getText(), pch.csb.getColour()); } @@ -2337,6 +2366,8 @@ protected void okAction() { GUIP.setMiniReportAutoDisplayNonReportPhase(miniReportAutoDisplayNonReportCombo.getSelectedIndex()); GUIP.setPlayerListAutoDisplayReportPhase(playerListAutoDisplayReportCombo.getSelectedIndex()); GUIP.setPlayerListAutoDisplayNonReportPhase(playerListAutoDisplayNonReportCombo.getSelectedIndex()); + GUIP.setForceDisplayAutoDisplayReportPhase(forceDisplayAutoDisplayReportCombo.getSelectedIndex()); + GUIP.setForceDisplayAutoDisplayNonReportPhase(forceDisplayAutoDisplayNonReportCombo.getSelectedIndex()); GUIP.setUnitDisplayHeatColorLevel1(csbUnitDisplayHeatLevel1.getColour()); GUIP.setUnitDisplayHeatColorLevel2(csbUnitDisplayHeatLevel2.getColour()); @@ -2703,132 +2734,114 @@ public void keyTyped(KeyEvent evt) { return outer; } + private JComboBox createHideShowComboBox(int i) { + JComboBox cb = new JComboBox<>(); + cb.addItem(Messages.getString("ClientGUI.Hide")); + cb.addItem(Messages.getString("ClientGUI.Show")); + cb.addItem(Messages.getString("ClientGUI.Manual")); + cb.setMaximumSize(new Dimension(150, 40)); + cb.setSelectedIndex(i); + + return cb; + } + private JPanel getPhasePanel() { List> comps = new ArrayList<>(); ArrayList row; - JLabel unitDisplayLabel = new JLabel(Messages.getString("CommonMenuBar.viewMekDisplay")); row = new ArrayList<>(); + JLabel unitDisplayLabel = new JLabel(Messages.getString("CommonMenuBar.viewMekDisplay")); row.add(unitDisplayLabel); comps.add(row); - - JLabel phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); - unitDisplayAutoDisplayReportCombo = new JComboBox<>(); - unitDisplayAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Hide")); - unitDisplayAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Show")); - unitDisplayAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Manual")); - unitDisplayAutoDisplayReportCombo.setMaximumSize(new Dimension(150, 40)); row = new ArrayList<>(); - unitDisplayAutoDisplayReportCombo.setSelectedIndex(GUIP.getUnitDisplayAutoDisplayReportPhase()); + JLabel phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); row.add(phaseLabel); + unitDisplayAutoDisplayReportCombo = createHideShowComboBox(GUIP.getUnitDisplayAutoDisplayReportPhase()); row.add(unitDisplayAutoDisplayReportCombo); comps.add(row); - - phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); - unitDisplayAutoDisplayNonReportCombo = new JComboBox<>(); - unitDisplayAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Hide")); - unitDisplayAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Show")); - unitDisplayAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Manual")); - unitDisplayAutoDisplayNonReportCombo.setMaximumSize(new Dimension(150, 40)); row = new ArrayList<>(); - unitDisplayAutoDisplayNonReportCombo.setSelectedIndex(GUIP.getUnitDisplayAutoDisplayNonReportPhase()); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); row.add(phaseLabel); + unitDisplayAutoDisplayNonReportCombo = createHideShowComboBox(GUIP.getUnitDisplayAutoDisplayNonReportPhase()); row.add(unitDisplayAutoDisplayNonReportCombo); comps.add(row); addLineSpacer(comps); - JLabel miniMapLabel = new JLabel(Messages.getString("CommonMenuBar.viewMinimap")); row = new ArrayList<>(); + JLabel miniMapLabel = new JLabel(Messages.getString("CommonMenuBar.viewMinimap")); row.add(miniMapLabel); comps.add(row); - - phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); - miniMapAutoDisplayReportCombo = new JComboBox<>(); - miniMapAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Hide")); - miniMapAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Show")); - miniMapAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Manual")); - miniMapAutoDisplayReportCombo.setMaximumSize(new Dimension(150, 40)); row = new ArrayList<>(); - miniMapAutoDisplayReportCombo.setSelectedIndex(GUIP.getMinimapAutoDisplayReportPhase()); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); row.add(phaseLabel); + miniMapAutoDisplayReportCombo = createHideShowComboBox(GUIP.getMinimapAutoDisplayReportPhase()); row.add(miniMapAutoDisplayReportCombo); comps.add(row); - - phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); - miniMapAutoDisplayNonReportCombo = new JComboBox<>(); - miniMapAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Hide")); - miniMapAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Show")); - miniMapAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Manual")); - miniMapAutoDisplayNonReportCombo.setMaximumSize(new Dimension(150, 40)); row = new ArrayList<>(); - miniMapAutoDisplayNonReportCombo.setSelectedIndex(GUIP.getMinimapAutoDisplayNonReportPhase()); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); row.add(phaseLabel); + miniMapAutoDisplayNonReportCombo = createHideShowComboBox(GUIP.getMiniReportAutoDisplayNonReportPhase()); row.add(miniMapAutoDisplayNonReportCombo); comps.add(row); addLineSpacer(comps); - JLabel miniReportLabel = new JLabel(Messages.getString("CommonMenuBar.viewRoundReport")); row = new ArrayList<>(); + JLabel miniReportLabel = new JLabel(Messages.getString("CommonMenuBar.viewRoundReport")); row.add(miniReportLabel); comps.add(row); - - phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); - miniReportAutoDisplayReportCombo = new JComboBox<>(); - miniReportAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Hide")); - miniReportAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Show")); - miniReportAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Manual")); - miniReportAutoDisplayReportCombo.setMaximumSize(new Dimension(150, 40)); row = new ArrayList<>(); - miniReportAutoDisplayReportCombo.setSelectedIndex(GUIP.getMiniReportAutoDisplayReportPhase()); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); row.add(phaseLabel); + miniReportAutoDisplayReportCombo = createHideShowComboBox(GUIP.getMiniReportAutoDisplayReportPhase()); row.add(miniReportAutoDisplayReportCombo); comps.add(row); - - phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); - miniReportAutoDisplayNonReportCombo = new JComboBox<>(); - miniReportAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Hide")); - miniReportAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Show")); - miniReportAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Manual")); - miniReportAutoDisplayNonReportCombo.setMaximumSize(new Dimension(150, 40)); row = new ArrayList<>(); - miniReportAutoDisplayNonReportCombo.setSelectedIndex(GUIP.getMiniReportAutoDisplayNonReportPhase()); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); row.add(phaseLabel); + miniReportAutoDisplayNonReportCombo = createHideShowComboBox(GUIP.getMiniReportAutoDisplayNonReportPhase()); row.add(miniReportAutoDisplayNonReportCombo); comps.add(row); addLineSpacer(comps); - JLabel playerListLabel = new JLabel(Messages.getString("CommonMenuBar.viewPlayerList")); row = new ArrayList<>(); + JLabel playerListLabel = new JLabel(Messages.getString("CommonMenuBar.viewPlayerList")); row.add(playerListLabel); comps.add(row); - - phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); - playerListAutoDisplayReportCombo = new JComboBox<>(); - playerListAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Hide")); - playerListAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Show")); - playerListAutoDisplayReportCombo.addItem(Messages.getString("ClientGUI.Manual")); - playerListAutoDisplayReportCombo.setMaximumSize(new Dimension(150, 40)); row = new ArrayList<>(); - playerListAutoDisplayReportCombo.setSelectedIndex(GUIP.getPlayerListAutoDisplayReportPhase()); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); row.add(phaseLabel); + playerListAutoDisplayReportCombo = createHideShowComboBox(GUIP.getPlayerListAutoDisplayReportPhase()); row.add(playerListAutoDisplayReportCombo); comps.add(row); - - phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); - playerListAutoDisplayNonReportCombo = new JComboBox<>(); - playerListAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Hide")); - playerListAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Show")); - playerListAutoDisplayNonReportCombo.addItem(Messages.getString("ClientGUI.Manual")); - playerListAutoDisplayNonReportCombo.setMaximumSize(new Dimension(150, 40)); row = new ArrayList<>(); - playerListAutoDisplayNonReportCombo.setSelectedIndex(GUIP.getPlayerListAutoDisplayNonReportPhase()); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); row.add(phaseLabel); + playerListAutoDisplayNonReportCombo = createHideShowComboBox(GUIP.getPlayerListAutoDisplayNonReportPhase()); row.add(playerListAutoDisplayNonReportCombo); comps.add(row); + addLineSpacer(comps); + + row = new ArrayList<>(); + JLabel forceDisplayLabel = new JLabel(Messages.getString("CommonMenuBar.viewForceDisplay")); + row.add(forceDisplayLabel); + comps.add(row); + row = new ArrayList<>(); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.reportPhases") + ": "); + row.add(phaseLabel); + forceDisplayAutoDisplayReportCombo = createHideShowComboBox(GUIP.getForceDisplayAutoDisplayReportPhase()); + row.add(forceDisplayAutoDisplayReportCombo); + comps.add(row); + row = new ArrayList<>(); + phaseLabel = new JLabel(Messages.getString("CommonSettingsDialog.nonReportPhases") + ": "); + row.add(phaseLabel); + forceDisplayAutoDisplayNonReportCombo = createHideShowComboBox(GUIP.getForceDisplayAutoDisplayNonReportPhase()); + row.add(forceDisplayAutoDisplayNonReportCombo); + comps.add(row); + return createSettingsPanel(comps); } diff --git a/megamek/src/megamek/client/ui/swing/GUIPreferences.java b/megamek/src/megamek/client/ui/swing/GUIPreferences.java index 0991b29cc18..30de1e6d33a 100644 --- a/megamek/src/megamek/client/ui/swing/GUIPreferences.java +++ b/megamek/src/megamek/client/ui/swing/GUIPreferences.java @@ -129,6 +129,14 @@ public class GUIPreferences extends PreferenceStoreProxy { public static final String CUSTOM_UNIT_HEIGHT = "CustomUnitDialogSizeHeight"; public static final String CUSTOM_UNIT_WIDTH = "CustomUnitDialogSizeWidth"; + public static final String FORCE_DISPLAY_POS_X = "ForceDisplayPosX"; + public static final String FORCE_DISPLAY_POS_Y = "ForcetDisplayPosY"; + public static final String FORCE_DISPLAY_SIZE_HEIGHT = "ForceDisplaySizeHeight"; + public static final String FORCE_DISPLAY_SIZE_WIDTH = "ForceDisplaySizeWidth"; + public static final String FORCE_DISPLAY_AUTO_DISPLAY_REPORT_PHASE = "ForceDisplayAutoDisplayReportPhase"; + public static final String FORCE_DISPLAY_AUTO_DISPLAY_NONREPORT_PHASE = "ForceDisplayAutoDisplayNonReportPhase"; + public static final String FORCE_DISPLAY_ENABLED = "ForceDisplayEnabled"; + public static final String UNIT_DISPLAY_POS_X = "UnitDisplayPosX"; public static final String UNIT_DISPLAY_POS_Y = "UnitDisplayPosY"; public static final String UNIT_DISPLAY_NONTABBED_POS_X = "UnitDisplayNontabbedPosX"; @@ -143,8 +151,8 @@ public class GUIPreferences extends PreferenceStoreProxy { public static final String UNIT_DISPLAY_SIZE_WIDTH = "UnitDisplaySizeWidth"; public static final String UNIT_DISPLAY_NONTABBED_SIZE_HEIGHT = "UnitDisplayNonTabbedSizeHeight"; public static final String UNIT_DISPLAY_NONTABBED_SIZE_WIDTH = "UnitDisplayNontabbedSizeWidth"; - public static final String UNIT_DISPLAY_AUTO_DISPLAY_REPORT_PHASE = "UnitDisplayAutoDiplayReportPhase"; - public static final String UNIT_DISPLAY_AUTO_DISPLAY_NONREPORT_PHASE = "UnitDisplayAutoDiplayNonReportPhase"; + public static final String UNIT_DISPLAY_AUTO_DISPLAY_REPORT_PHASE = "UnitDisplayAutoDisplayReportPhase"; + public static final String UNIT_DISPLAY_AUTO_DISPLAY_NONREPORT_PHASE = "UnitDisplayAutoDisplayNonReportPhase"; public static final String UNIT_DISPLAY_ENABLED = "UnitDisplayEnabled"; public static final String UNIT_DISPLAY_LOCATION = "UnitDisplayLocation"; public static final String UNIT_DISPLAY_HEAT_COLOR_1 = "UnitDisplayHeatColor1"; @@ -264,8 +272,8 @@ public class GUIPreferences extends PreferenceStoreProxy { public static final String MINI_MAP_ZOOM = "MinimapZoom"; public static final String MINI_MAP_HEIGHT_DISPLAY_MODE = "MinimapHeightDisplayMode"; public static final String MINI_MAP_SYMBOLS_DISPLAY_MODE = "MinimapSymbolsDisplayMode"; - public static final String MINI_MAP_AUTO_DISPLAY_REPORT_PHASE = "MinimapAutoDiplayReportPhase"; - public static final String MINI_MAP_AUTO_DISPLAY_NONREPORT_PHASE = "MinimapAutoDiplayNonReportPhase"; + public static final String MINI_MAP_AUTO_DISPLAY_REPORT_PHASE = "MinimapAutoDisplayReportPhase"; + public static final String MINI_MAP_AUTO_DISPLAY_NONREPORT_PHASE = "MinimapAutoDisplayNonReportPhase"; public static final String MINIMUM_SIZE_HEIGHT = "MinimumSizeHeight"; public static final String MINIMUM_SIZE_WIDTH = "MinimumSizeWidth"; public static final String MOUSE_WHEEL_ZOOM = "MouseWheelZoom"; @@ -535,6 +543,10 @@ protected GUIPreferences() { store.setDefault(CUSTOM_UNIT_HEIGHT, 400); store.setDefault(CUSTOM_UNIT_WIDTH, 600); + store.setDefault(FORCE_DISPLAY_AUTO_DISPLAY_REPORT_PHASE, 2); + store.setDefault(FORCE_DISPLAY_AUTO_DISPLAY_NONREPORT_PHASE, 2); + store.setDefault(FORCE_DISPLAY_ENABLED, false); + store.setDefault(UNIT_DISPLAY_SIZE_HEIGHT, 500); store.setDefault(UNIT_DISPLAY_SIZE_WIDTH, 300); store.setDefault(UNIT_DISPLAY_NONTABBED_SIZE_HEIGHT, 900); @@ -812,6 +824,34 @@ public int getCustomUnitWidth() { return store.getInt(CUSTOM_UNIT_WIDTH); } + public int getForceDisplayPosX() { + return store.getInt(FORCE_DISPLAY_POS_X); + } + + public int getForceDisplayPosY() { + return store.getInt(FORCE_DISPLAY_POS_Y); + } + + public int getForceDisplaySizeHeight() { + return store.getInt(FORCE_DISPLAY_SIZE_HEIGHT); + } + + public int getForceDisplaySizeWidth() { + return store.getInt(FORCE_DISPLAY_SIZE_WIDTH); + } + + public int getForceDisplayAutoDisplayReportPhase() { + return store.getInt(FORCE_DISPLAY_AUTO_DISPLAY_REPORT_PHASE); + } + + public int getForceDisplayAutoDisplayNonReportPhase() { + return store.getInt(FORCE_DISPLAY_AUTO_DISPLAY_NONREPORT_PHASE); + } + + public boolean getForceDisplayEnabled() { + return store.getBoolean(FORCE_DISPLAY_ENABLED); + } + public int getUnitDisplayPosX() { return store.getInt(UNIT_DISPLAY_POS_X); } @@ -1587,6 +1627,38 @@ public void setCustomUnitWidth(int state) { store.setValue(CUSTOM_UNIT_WIDTH, state); } + public void setForceDisplayPosX(int i) { + store.setValue(FORCE_DISPLAY_POS_X, i); + } + + public void setForceDisplayPosY(int i) { + store.setValue(FORCE_DISPLAY_POS_Y, i); + } + + public void setForceDisplaySizeHeight(int i) { + store.setValue(FORCE_DISPLAY_SIZE_HEIGHT, i); + } + + public void setForceDisplaySizeWidth(int i) { + store.setValue(FORCE_DISPLAY_SIZE_WIDTH, i); + } + + public void setForceDisplayAutoDisplayReportPhase(int i) { + store.setValue(FORCE_DISPLAY_AUTO_DISPLAY_REPORT_PHASE, i); + } + + public void setForceDisplayAutoDisplayNonReportPhase(int i) { + store.setValue(FORCE_DISPLAY_AUTO_DISPLAY_NONREPORT_PHASE, i); + } + + public void toggleForceDisplay() { + store.setValue(FORCE_DISPLAY_ENABLED, !getBoolean(FORCE_DISPLAY_ENABLED)); + } + + public void setForceDisplayEnabled(boolean b) { + store.setValue(FORCE_DISPLAY_ENABLED, b); + } + public void setUnitDisplayPosX(int i) { store.setValue(UNIT_DISPLAY_POS_X, i); } diff --git a/megamek/src/megamek/client/ui/swing/ReportDisplay.java b/megamek/src/megamek/client/ui/swing/ReportDisplay.java index d2dda549a69..fe40d90282d 100644 --- a/megamek/src/megamek/client/ui/swing/ReportDisplay.java +++ b/megamek/src/megamek/client/ui/swing/ReportDisplay.java @@ -83,7 +83,7 @@ public String getHotKeyDesc() { private Map buttons; private boolean rerolled; // have we rerolled an init? - private static final String RD_REPORTDIPLAY = "ReportDisplay."; + private static final String RD_REPORTDISPLAY = "ReportDisplay."; private static final String RD_TOOLTIP = ".tooltip"; /** @@ -116,7 +116,7 @@ public ReportDisplay(ClientGUI clientgui) { protected void setButtons() { buttons = new HashMap<>((int) (ReportCommand.values().length * 1.25 + 0.5)); for (ReportCommand cmd : ReportCommand.values()) { - buttons.put(cmd, createButton(cmd.getCmd(), RD_REPORTDIPLAY)); + buttons.put(cmd, createButton(cmd.getCmd(), RD_REPORTDISPLAY)); } numButtonGroups = (int) Math.ceil((buttons.size() + 0.0) / buttonsPerGroup); } @@ -124,7 +124,7 @@ protected void setButtons() { @Override protected void setButtonsTooltips() { for (ReportCommand cmd : ReportCommand.values()) { - String tt = createToolTip(cmd.getCmd(), RD_REPORTDIPLAY, cmd.getHotKeyDesc()); + String tt = createToolTip(cmd.getCmd(), RD_REPORTDISPLAY, cmd.getHotKeyDesc()); buttons.get(cmd).setToolTipText(tt); } } diff --git a/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayDialog.java b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayDialog.java new file mode 100644 index 00000000000..cde7dbded05 --- /dev/null +++ b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayDialog.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 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.client.ui.swing.forceDisplay; + +import megamek.client.ui.Messages; +import megamek.client.ui.swing.ClientGUI; +import megamek.client.ui.swing.GUIPreferences; +import megamek.client.ui.swing.util.UIUtil; + +import javax.swing.*; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +public class ForceDisplayDialog extends JDialog { + //region Variable Declarations + private final ClientGUI clientGUI; + private static final GUIPreferences GUIP = GUIPreferences.getInstance(); + //endregion Variable Declarations + + //region Constructors + public ForceDisplayDialog(final JFrame frame, final ClientGUI clientGUI) { + super(frame, "", false); + this.setTitle(Messages.getString("ClientGUI.ForceDisplay")); + + this.clientGUI = clientGUI; + + this.setLocation(GUIP.getForceDisplayPosX(), GUIP.getForceDisplayPosY()); + this.setSize(GUIP.getForceDisplaySizeWidth(), GUIP.getForceDisplaySizeHeight()); + + UIUtil.updateWindowBounds(this); + this.setResizable(true); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent evt) { + GUIP.setUnitDisplayEnabled(false); + } + }); + } + //endregion Constructors + + public void saveSettings() { + if ((getSize().width * getSize().height) > 0) { + GUIP.setForceDisplayPosX(getLocation().x); + GUIP.setForceDisplayPosY(getLocation().y); + GUIP.setForceDisplaySizeWidth(getSize().width); + GUIP.setForceDisplaySizeHeight(getSize().height); + } + } + + @Override + protected void processWindowEvent(WindowEvent e) { + super.processWindowEvent(e); + if ((e.getID() == WindowEvent.WINDOW_DEACTIVATED) || (e.getID() == WindowEvent.WINDOW_CLOSING)) { + saveSettings(); + } + } + + /** + * In addition to the default Dialog processKeyEvent, this method + * dispatches a KeyEvent to the client gui. + * This enables all the gui hotkeys. + */ + @Override + protected void processKeyEvent(KeyEvent evt) { + evt.setSource(clientGUI); + clientGUI.getMenuBar().dispatchEvent(evt); + // Make the source be the ClientGUI and not the dialog + // This prevents a ClassCastException in ToolTipManager + clientGUI.getCurrentPanel().dispatchEvent(evt); + if (!evt.isConsumed()) { + super.processKeyEvent(evt); + } + } +} diff --git a/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekCellFormatter.java b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekCellFormatter.java new file mode 100644 index 00000000000..f698420056d --- /dev/null +++ b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekCellFormatter.java @@ -0,0 +1,400 @@ +/* + * Copyright (c) 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.client.ui.swing.forceDisplay; + +import megamek.MegaMek; +import megamek.client.Client; +import megamek.client.ui.Messages; +import megamek.client.ui.swing.ClientGUI; +import megamek.client.ui.swing.GUIPreferences; +import megamek.client.ui.swing.tooltip.UnitToolTip; +import megamek.client.ui.swing.util.PlayerColour; +import megamek.common.*; +import megamek.common.force.Force; +import megamek.common.options.GameOptions; +import megamek.common.options.OptionsConstants; +import megamek.common.util.CollectionUtil; + +import java.awt.*; +import java.text.MessageFormat; +import java.text.NumberFormat; +import java.util.List; + +import static megamek.client.ui.swing.lobby.MekTableModel.DOT_SPACER; +import static megamek.client.ui.swing.util.UIUtil.*; + +class ForceDisplayMekCellFormatter { + + private static final GUIPreferences GUIP = GUIPreferences.getInstance(); + + private ForceDisplayMekCellFormatter() { + } + + /** + * Creates and returns the display content of the C3-MekTree cell for the given entity and + * for the compact display mode. Assumes that no enemy or blind-drop-hidden units are provided. + */ + static String formatUnitCompact(Entity entity, ClientGUI clientGUI) { + Client client = clientGUI.getClient(); + Game game = client.getGame(); + GameOptions options = game.getOptions(); + Player localPlayer = client.getLocalPlayer(); + Player owner = entity.getOwner(); + + if (entity.isSensorReturn(localPlayer)) { + String value = "  "; + String uType = ""; + + if (entity instanceof Infantry) { + uType = Messages.getString("ChatLounge.0"); + } else if (entity instanceof Protomech) { + uType = Messages.getString("ChatLounge.1"); + } else if (entity instanceof GunEmplacement) { + uType = Messages.getString("ChatLounge.2"); + } else if (entity.isSupportVehicle()) { + uType = entity.getWeightClassName(); + } else if (entity.isFighter()) { + uType = entity.getWeightClassName() + Messages.getString("ChatLounge.4"); + } else if (entity instanceof Mech) { + uType = entity.getWeightClassName() + Messages.getString("ChatLounge.3"); + } else if (entity instanceof Tank) { + uType = entity.getWeightClassName() + Messages.getString("ChatLounge.6"); + } else { + uType = entity.getWeightClassName(); + } + + uType = DOT_SPACER + uType + DOT_SPACER; + value += guiScaledFontHTML() + uType + "";; + return UnitToolTip.wrapWithHTML(value); + } else if (!entity.isVisibleToEnemy()) { + return ""; + } + + StringBuilder result = new StringBuilder("  " + guiScaledFontHTML()); + boolean isCarried = entity.getTransportId() != Entity.NONE; + + Color color = GUIP.getEnemyUnitColor(); + if (owner.getId() == localPlayer.getId()) { + color = GUIP.getMyUnitColor(); + } else if (!localPlayer.isEnemyOf(owner)) { + color = GUIP.getAllyUnitColor(); + } + + if (entity.getForceId() == Force.NO_FORCE) { + result.append(guiScaledFontHTML(color) + "\u25AD" + ""); + } + + String id = MessageFormat.format("[{0}] ", entity.getId()); + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + id + ""); + + // Done + if (!game.getPhase().isReport()) { + String done = ""; + if (!entity.isDone()) { + done = "\u2610 "; + } else { + done = "\u2611 "; + } + result.append(guiScaledFontHTML(color) + done + ""); + } + + // Unit name + // Gray out if the unit is a fighter in a squadron + if (entity.isPartOfFighterSquadron()) { + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + entity.getShortNameRaw() + ""); + } else { + result.append(entity.getShortNameRaw()); + } + + // Pilot + Crew pilot = entity.getCrew(); + result.append(guiScaledFontHTML()); + result.append(DOT_SPACER); + + if (pilot.getSlotCount() > 1 || entity instanceof FighterSquadron) { + result.append("" + Messages.getString("ChatLounge.multipleCrew") + ""); + } else if ((pilot.getNickname(0) != null) && !pilot.getNickname(0).isEmpty()) { + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + "'"); + result.append(pilot.getNickname(0).toUpperCase() + "'"); + if (!pilot.getStatusDesc(0).isEmpty()) { + result.append(" (" + pilot.getStatusDesc(0) + ")"); + } + } else { + result.append(pilot.getDesc(0)); + } + + final boolean rpgSkills = options.booleanOption(OptionsConstants.RPG_RPG_GUNNERY); + result.append(" (" + pilot.getSkillsAsString(rpgSkills) + ")"); + + result.append(DOT_SPACER); + result.append(' ' + UnitToolTip.getDamageLevelDesc(entity, true)); + + // Tonnage + result.append(DOT_SPACER); + NumberFormat formatter = NumberFormat.getNumberInstance(MegaMek.getMMOptions().getLocale()); + String tonnage = formatter.format(entity.getWeight()); + tonnage += Messages.getString("ChatLounge.Tons"); + result.append(guiScaledFontHTML() + tonnage + ""); + + // Alpha Strike Unit Role + if (!entity.isUnitGroup()) { + result.append(DOT_SPACER); + result.append(entity.getRole().toString()); + } + + // Controls the separator dot character + boolean firstEntry = true; + + if (pilot.countOptions() > 0) { + firstEntry = dotSpacerOnlyFirst(result, firstEntry); + String quirks = Messages.getString("ChatLounge.abilities"); + result.append(guiScaledFontHTML(GUIP.getUnitToolTipQuirkColor()) + quirks + ""); + } + + // ECM + if (entity.hasActiveECM()) { + firstEntry = dotSpacerOnlyFirst(result, firstEntry); + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor(), 0.2f) + ECM_SIGN + ""); + } + + // Quirk Count + int quirkCount = entity.countQuirks() + entity.countWeaponQuirks(); + if (quirkCount > 0) { + firstEntry = dotSpacerOnlyFirst(result, firstEntry); + result.append(guiScaledFontHTML(GUIP.getUnitToolTipQuirkColor(), 0.2f) + QUIRKS_SIGN + ""); + } + + // C3 ... + if (entity.hasC3i() || entity.hasNavalC3()) { + firstEntry = dotSpacerOnlyFirst(result, firstEntry); + String msg_c3i = Messages.getString("ChatLounge.C3i"); + String msg_nc3 = Messages.getString("ChatLounge.NC3"); + + String c3Name = entity.hasC3i() ? msg_c3i : msg_nc3; + if (entity.calculateFreeC3Nodes() >= 5) { + c3Name += UNCONNECTED_SIGN; + } else { + c3Name += CONNECTED_SIGN + entity.getC3NetId(); + } + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + c3Name + ""); + } + + if (entity.hasC3()) { + String msg_c3sabrv = Messages.getString("ChatLounge.C3SAbrv"); + String msg_c3m = Messages.getString("ChatLounge.C3M"); + String msg_c3mcc = Messages.getString("ChatLounge.C3MCC"); + String c3 = ""; + firstEntry = dotSpacerOnlyFirst(result, firstEntry); + + if (entity.getC3Master() == null) { + if (entity.hasC3S()) { + c3 = msg_c3sabrv + UNCONNECTED_SIGN; + } + if (entity.hasC3M()) { + c3 = msg_c3m; + } + } else if (entity.C3MasterIs(entity)) { + result.append(msg_c3mcc); + } else { + if (entity.hasC3S()) { + c3 = msg_c3sabrv + CONNECTED_SIGN; + } else { + c3 = msg_c3m + CONNECTED_SIGN; + } + + c3 += entity.getC3Master().getChassis(); + } + + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + c3 + ""); + } + + // Loaded onto another unit + if (isCarried) { + Entity loader = entity.getGame().getEntity(entity.getTransportId()); + result.append(DOT_SPACER); + String carried = "(" + loader.getChassis() + " [" + entity.getTransportId() + "])"; + carried = "" + carried + ""; + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + carried + ""); + } + + if (entity.countPartialRepairs() > 0) { + result.append(DOT_SPACER); + result.append(guiScaledFontHTML(GUIP.getWarningColor()) + "Partial Repairs" + ""); + } + + // Offboard deployment + if (entity.isOffBoard()) { + result.append(DOT_SPACER); + String msg_offboard = Messages.getString("ChatLounge.compact.deploysOffBoard"); + msg_offboard = "" + msg_offboard + ""; + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + msg_offboard + ""); + } else if (!entity.isDeployed()) { + result.append(DOT_SPACER); + String msg_deploy = Messages.getString("ChatLounge.compact.deployRound", entity.getDeployRound()); + String msg_zone = ""; + if (entity.getStartingPos(false) != Board.START_NONE) { + msg_zone = Messages.getString("ChatLounge.compact.deployZone", + IStartingPositions.START_LOCATION_NAMES[entity.getStartingPos(false)]); + } + msg_deploy = "" + msg_deploy + msg_zone + ""; + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + msg_deploy + ""); + } + + // Starting values for Altitude / Velocity / Elevation + if (!isCarried) { + if (entity.isAero()) { + IAero aero = (IAero) entity; + result.append(DOT_SPACER); + String msg_vel = Messages.getString("ChatLounge.compact.velocity") + ": "; + msg_vel += aero.getCurrentVelocity(); + String msg_alt = ""; + String msg_fuel = ""; + if (!game.getBoard().inSpace()) { + msg_alt = ", " + Messages.getString("ChatLounge.compact.altitude") + ": "; + msg_alt += aero.getAltitude(); + } + if (options.booleanOption(OptionsConstants.ADVAERORULES_FUEL_CONSUMPTION)) { + msg_fuel = ", " + Messages.getString("ChatLounge.compact.fuel") + ": "; + msg_fuel += aero.getCurrentFuel(); + } + msg_vel = "" + msg_vel + msg_alt + msg_fuel + ""; + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + msg_vel + ""); + } else if (entity.getPosition() != null && ((entity.getElevation() != 0) || (entity instanceof VTOL))) { + result.append(DOT_SPACER); + String msg_ele = Messages.getString("ChatLounge.compact.elevation") + ": "; + msg_ele += entity.getElevation(); + msg_ele = "" + msg_ele + ";"; + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor()) + msg_ele + ""); + } + } + + // Owner + if (!localPlayer.equals(owner)) { + result.append(DOT_SPACER); + String player = entity.getOwner().getName() + " \u2691 "; + result.append(guiScaledFontHTML(color) + player + ""); + } + + return UnitToolTip.wrapWithHTML(result.toString()); + } + + /** + * Creates and returns the display content of the C3-MekTree cell for the given entity and + * for the compact display mode. Assumes that no enemy or blind-drop-hidden units are provided. + */ + static String formatForceCompact(Force force, ClientGUI clientGUI) { + return formatForce(force, clientGUI, 0); + } + + private static String formatForce(Force force, ClientGUI clientGUI, float size) { + Client client = clientGUI.getClient(); + Game game = client.getGame(); + Player localPlayer = client.getLocalPlayer(); + int ownerId = game.getForces().getOwnerId(force); + Player owner = game.getPlayer(ownerId); + + // Get the my / ally / enemy color + Color color = GUIP.getEnemyUnitColor(); + if (ownerId == localPlayer.getId()) { + color = GUIP.getMyUnitColor(); + } else if (!localPlayer.isEnemyOf(owner)) { + color = GUIP.getAllyUnitColor(); + } + + StringBuilder result = new StringBuilder(""); + + // A top-level / subforce special char + String fLevel = ""; + if (force.isTopLevel()) { + fLevel = "\u2327   "; + } else { + fLevel = "\u25E5   "; + } + result.append(guiScaledFontHTML(color, size) + fLevel + ""); + + // Name + String fName = force.getName(); + fName = "" + fName + ""; + result.append(guiScaledFontHTML(color, size) + fName + ""); + + // ID + String id = " [" + force.getId() + "]"; + result.append(guiScaledFontHTML(GUIP.getUnitToolTipHighlightColor(), size) + id + ""); + + // Display force owner + if ((ownerId != client.getLocalPlayerNumber()) && (owner != null)) { + result.append(DOT_SPACER); + String oName = "\u2691 " + owner.getName(); + result.append(guiScaledFontHTML(color, size) + oName + ""); + } + + // BV + List fullEntities = ForceAssignable.filterToEntityList(game.getForces().getFullEntities(force)); + result.append(DOT_SPACER); + int totalBv = fullEntities.stream().filter(e -> !e.isPartOfFighterSquadron()).mapToInt(Entity::calculateBattleValue).sum(); + + if (totalBv > 0) { + String msg_bvplain = Messages.getString("ChatLounge.BVplain"); + msg_bvplain = msg_bvplain + " " + String.format("%,d", totalBv); + result.append(guiScaledFontHTML(color, size) + msg_bvplain + ""); + + // Unit Type + long unittypes = fullEntities.stream().map(e -> Entity.getEntityMajorTypeName(e.getEntityType())).distinct().count(); + result.append(DOT_SPACER); + + if (unittypes > 1) { + String msg_mixed = Messages.getString("ChatLounge.Mixed"); + result.append(guiScaledFontHTML(color, size) + msg_mixed + ""); + } else if (unittypes == 1) { + Entity entity = CollectionUtil.anyOneElement(fullEntities); + String eType = UnitType.getTypeName(entity.getUnitType()); + result.append(guiScaledFontHTML(color, size) + eType + ""); + } + + } else { + result.append(guiScaledFontHTML(color, size) + "Empty" + ""); + } + + return UnitToolTip.wrapWithHTML(result.toString()); + } + + static void formatSpan(StringBuilder current, Color color) { + current.append(""); + } + + static void formatSpan(StringBuilder current, String hexColor) { + current.append(""); + } + + static void fullidString(StringBuilder current, int id) { + formatSpan(current, uiGray()); + current.append(" [ID: ").append(id).append("]"); + } + + static boolean dotSpacerOnlyFirst(StringBuilder current, boolean firstElement) { + if (firstElement) { + current.append(DOT_SPACER); + } + return false; + } +} diff --git a/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekTreeModel.java b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekTreeModel.java new file mode 100644 index 00000000000..501202dd003 --- /dev/null +++ b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekTreeModel.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 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.client.ui.swing.forceDisplay; + +import megamek.client.ui.swing.ClientGUI; +import megamek.client.ui.swing.lobby.sorters.MekTreeTopLevelSorter; +import megamek.common.Entity; +import megamek.common.ForceAssignable; +import megamek.common.force.Force; +import megamek.common.force.Forces; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import java.util.ArrayList; +import java.util.List; + +public class ForceDisplayMekTreeModel extends DefaultTreeModel { + private ClientGUI clientGUI; + /** A sorted list of all top-level objects: top-level forces and force-less entities. */ + private ArrayList allToplevel; + + public ForceDisplayMekTreeModel(ClientGUI clientGUI) { + super(new DefaultMutableTreeNode("Root")); + this.clientGUI = clientGUI; + } + + public void refreshData() { + allToplevel = null; + nodeStructureChanged(root); + } + + public void refreshDisplay() { + nodeChanged(root); + } + + @Override + public Object getChild(Object parent, int index) { + if (index < 0) { + return null; + } + + if (parent == root) { + if (allToplevel == null) { + createTopLevel(); + } + return allToplevel.get(index); + + } else if (parent instanceof Force) { + Forces forces = clientGUI.getClient().getGame().getForces(); + Force pnt = (Force) parent; + if (index < pnt.entityCount()) { + return clientGUI.getClient().getGame().getEntity(pnt.getEntityId(index)); + } else if (index < pnt.getChildCount()) { + return forces.getForce(pnt.getSubForceId(index - pnt.entityCount())); + } + } + return null; + } + + @Override + public int getChildCount(Object parent) { + if (parent == root) { + if (allToplevel == null) { + createTopLevel(); + } + return allToplevel.size(); + + } else if (parent instanceof Force) { + Force pnt = (Force) parent; + return pnt.getChildCount(); + + } else { // Entity + return 0; + } + } + + /** + * Creates and stores a sorted list of the top-level forces and entities. + * Removes those that aren't visible in real blind drop. + */ + private void createTopLevel() { + clientGUI.getClient().getGame().getForces().correct(); + Forces forces = clientGUI.getClient().getGame().getForces(); + ArrayList toplevel = new ArrayList<>(forces.getTopLevelForces()); + List forceless = ForceAssignable.filterToEntityList(forces.forcelessEntities()); + allToplevel = new ArrayList<>(toplevel); + allToplevel.addAll(forceless); + allToplevel.sort(new MekTreeTopLevelSorter(clientGUI.getClient())); + } + + @Override + public boolean isLeaf(Object node) { + return node instanceof Entity; + } + + @Override + public int getIndexOfChild(Object parent, Object child) { + if (child == root || !(parent instanceof Force) + || !((child instanceof Force) || (child instanceof Entity))) { + return -1; + } + Force pnt = (Force) parent; + if (child instanceof Entity) { + return pnt.entityIndex((Entity) child); + } else { + return pnt.subForceIndex((Force) child); + } + } + +} \ No newline at end of file diff --git a/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekTreeRenderer.java b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekTreeRenderer.java new file mode 100644 index 00000000000..bbbb28bee28 --- /dev/null +++ b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayMekTreeRenderer.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 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.client.ui.swing.forceDisplay; + +import megamek.MMConstants; +import megamek.client.ui.swing.ClientGUI; +import megamek.client.ui.swing.tooltip.PilotToolTip; +import megamek.client.ui.swing.tooltip.UnitToolTip; +import megamek.client.ui.swing.util.UIUtil; +import megamek.common.Configuration; +import megamek.common.Entity; +import megamek.common.Player; +import megamek.common.force.Force; +import megamek.common.icons.Camouflage; +import megamek.common.util.ImageUtil; +import megamek.common.util.fileUtils.MegaMekFile; +import org.apache.logging.log4j.LogManager; + +import javax.swing.*; +import javax.swing.tree.DefaultTreeCellRenderer; +import java.awt.*; +import java.awt.event.MouseEvent; + +/** A specialized renderer for the Mek Force tree. */ +public class ForceDisplayMekTreeRenderer extends DefaultTreeCellRenderer { + private final String UNKNOWN_UNIT = new MegaMekFile(Configuration.miscImagesDir(), + "unknown_unit.gif").toString(); + + private ClientGUI clientGUI; + private boolean isSelected; + private Color selectionColor = Color.BLUE; + private Entity entity; + private Player localPlayer; + private JTree tree; + private int row; + + @Override + public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, + boolean leaf, int row, boolean hasFocus) { + + isSelected = sel; + localPlayer = clientGUI.getClient().getLocalPlayer(); + selectionColor = UIManager.getColor("Tree.selectionBackground"); + setOpaque(true); + + if (isSelected) { + setBackground(new Color(selectionColor.getRGB())); + } else { + setForeground(null); + setBackground(null); + } + + if (value instanceof Entity) { + Font scaledFont = new Font(MMConstants.FONT_DIALOG, Font.PLAIN, UIUtil.scaleForGUI(UIUtil.FONT_SCALE1)); + setFont(scaledFont); + entity = (Entity) value; + this.row = row; + Player owner = entity.getOwner(); + setText(ForceDisplayMekCellFormatter.formatUnitCompact(entity, clientGUI)); + int size = UIUtil.scaleForGUI(20); + boolean showAsUnknown = owner.isEnemyOf(localPlayer) + && !entity.isVisibleToEnemy(); + if (showAsUnknown) { + setIcon(getToolkit().getImage(UNKNOWN_UNIT), size - 5); + } else { + Camouflage camo = entity.getCamouflageOrElseOwners(); + Image image = clientGUI.getBoardView().getTilesetManager().loadPreviewImage(entity, camo, this); + setIconTextGap(UIUtil.scaleForGUI(10)); + setIcon(image, size); + } + } else if (value instanceof Force) { + entity = null; + Font scaledFont = new Font(MMConstants.FONT_DIALOG, Font.PLAIN, UIUtil.scaleForGUI(UIUtil.FONT_SCALE1 + 3)); + setFont(scaledFont); + Force force = (Force) value; + setText(ForceDisplayMekCellFormatter.formatForceCompact(force, clientGUI)); + setIcon(null); + } + return this; + } + + @Override + public String getToolTipText(MouseEvent event) { + if (entity == null) { + return null; + } + + String txt = UnitToolTip.getEntityTipUnitDisplay(entity, localPlayer).toString(); + txt += PilotToolTip.getCrewAdvs(entity, true).toString(); + return UnitToolTip.wrapWithHTML(txt); + } + + private void setIcon(Image image, int height) { + if ((image.getHeight(null) > 0) && (image.getWidth(null) > 0)) { + int width = height * image.getWidth(null) / image.getHeight(null); + setIcon(new ImageIcon(ImageUtil.getScaledImage(image, width, height))); + } else { + LogManager.getLogger().error("Trying to resize a unit icon of height or width 0!"); + setIcon(null); + } + } + + ForceDisplayMekTreeRenderer(ClientGUI clientGUI, JTree tree) { + this.clientGUI = clientGUI; + this.tree = tree; + } +} diff --git a/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayPanel.java b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayPanel.java new file mode 100644 index 00000000000..137978541c2 --- /dev/null +++ b/megamek/src/megamek/client/ui/swing/forceDisplay/ForceDisplayPanel.java @@ -0,0 +1,349 @@ +/* + * MegaMek - Copyright (C) 2000-2002 Ben Mazur (bmazur@sev.org) + * + * This program 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 2 of the License, or (at your option) + * any later version. + * + * This program 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. + */ +package megamek.client.ui.swing.forceDisplay; + +import megamek.client.ui.Messages; +import megamek.client.ui.swing.ClientGUI; +import megamek.client.ui.swing.GUIPreferences; +import megamek.client.ui.swing.lobby.LobbyUtility; +import megamek.client.ui.swing.util.ScalingPopup; +import megamek.client.ui.swing.util.UIUtil; +import megamek.common.Entity; +import megamek.common.Game; +import megamek.common.event.*; +import megamek.common.force.Force; +import megamek.common.force.Forces; +import megamek.common.preference.IPreferenceChangeListener; +import megamek.common.preference.PreferenceChangeEvent; +import org.apache.logging.log4j.LogManager; + +import javax.swing.*; +import javax.swing.event.MouseInputAdapter; +import javax.swing.tree.TreePath; +import javax.swing.tree.TreeSelectionModel; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +/** + * Shows force display + */ +public class ForceDisplayPanel extends JPanel implements GameListener, IPreferenceChangeListener { + + private ForceDisplayMekTreeModel forceTreeModel; + JTree forceTree; + private ForceTreeMouseAdapter mekForceTreeMouseListener = new ForceTreeMouseAdapter(); + private ClientGUI clientgui; + private Game game; + private static final GUIPreferences GUIP = GUIPreferences.getInstance(); + + public ForceDisplayPanel(ClientGUI clientgui) { + if (clientgui == null) { + return; + } + this.clientgui = clientgui; + this.game = clientgui.getClient().getGame(); + + setupForce(); + refreshTree(); + + setLayout(new BorderLayout()); + JScrollPane sp = new JScrollPane(forceTree); + add(sp, BorderLayout.CENTER); + + forceTree.addMouseListener(mekForceTreeMouseListener); + clientgui.getClient().getGame().addGameListener(this); + GUIP.addPreferenceChangeListener(this); + + adaptToGUIScale(); + } + + private void setupForce() { + forceTreeModel = new ForceDisplayMekTreeModel(clientgui); + forceTree = new JTree(forceTreeModel); + forceTree.setRootVisible(false); + forceTree.setDragEnabled(false); + forceTree.setCellRenderer(new ForceDisplayMekTreeRenderer(clientgui, forceTree)); + forceTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); + forceTree.setExpandsSelectedPaths(true); + ToolTipManager.sharedInstance().registerComponent(forceTree); + } + + /** Refreshes the Mek Tree, restoring expansion state and selection. */ + public void refreshTree() { + if (!GUIP.getForceDisplayEnabled()) { + return; + } + + // Refresh the force tree and restore selection/expand status + HashSet selections = new HashSet<>(); + if (!forceTree.isSelectionEmpty()) { + for (TreePath path: forceTree.getSelectionPaths()) { + Object sel = path.getLastPathComponent(); + if (sel instanceof Force || sel instanceof Entity) { + selections.add(path.getLastPathComponent()); + } + } + } + + Forces forces = game.getForces(); + forces.correct(); + List expandedForces = new ArrayList<>(); + + for (int i = 0; i < forceTree.getRowCount(); i++) { + TreePath currPath = forceTree.getPathForRow(i); + if (forceTree.isExpanded(currPath)) { + Object entry = currPath.getLastPathComponent(); + if (entry instanceof Force) { + expandedForces.add(((Force) entry).getId()); + } + } + } + + forceTree.setUI(null); + try { + forceTreeModel.refreshData(); + } finally { + forceTree.updateUI(); + } + for (int id: expandedForces) { + if (!forces.contains(id)) { + continue; + } + forceTree.expandPath(getPath(forces.getForce(id))); + } + + forceTree.clearSelection(); + for (Object sel: selections) { + forceTree.addSelectionPath(getPath(sel)); + } + } + + /** + * Returns a TreePath in the force tree for a possibly outdated entity + * or force. Outdated means a new object of the type was sent by the server + * and has replaced this object. Also works for the game's current objects though. + * Uses the force's/entity's id to get the + * game's real object with the same id. Used to reconstruct the selection + * and expansion state of the force tree after an update. + */ + private TreePath getPath(Object outdatedEntry) { + Forces forces = game.getForces(); + if (outdatedEntry instanceof Force) { + if (!forces.contains((Force) outdatedEntry)) { + return null; + } + int forceId = ((Force) outdatedEntry).getId(); + List chain = forces.forceChain(forces.getForce(forceId)); + Object[] pathObjs = new Object[chain.size() + 1]; + int index = 0; + pathObjs[index++] = forceTreeModel.getRoot(); + for (Force force: chain) { + pathObjs[index++] = force; + } + return new TreePath(pathObjs); + } else if (outdatedEntry instanceof Entity) { + int entityId = ((Entity) outdatedEntry).getId(); + if (game.getEntity(entityId) == null) { + return null; + } + List chain = forces.forceChain(game.getEntity(entityId)); + Object[] pathObjs = new Object[chain.size() + 2]; + int index = 0; + pathObjs[index++] = forceTreeModel.getRoot(); + for (Force force: chain) { + pathObjs[index++] = force; + } + pathObjs[index++] = game.getEntity(entityId); + return new TreePath(pathObjs); + } else { + throw new IllegalArgumentException(Messages.getString("ChatLounge.TreePath.methodRequiresEntityForce")); + } + } + + private JMenuItem viewReadoutJMenuItem(Entity en) { + JMenuItem item = new JMenuItem(Messages.getString("ClientGUI.viewReadoutMenuItem") + + en.getDisplayName()); + + item.setActionCommand(Integer.toString(en.getId())); + item.addActionListener(evt -> { + try { + Entity entity = game.getEntity(Integer.parseInt(evt.getActionCommand())); + LobbyUtility.mechReadout(entity, 0, false, clientgui.getFrame()); + } catch (Exception ex) { + LogManager.getLogger().error("", ex); + } + }); + + return item; + } + + public class ForceTreeMouseAdapter extends MouseInputAdapter { + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + int row = forceTree.getRowForLocation(e.getX(), e.getY()); + TreePath path = forceTree.getPathForRow(row); + if (path != null && path.getLastPathComponent() instanceof Entity) { + Entity entity = (Entity) path.getLastPathComponent(); + clientgui.getUnitDisplay().displayEntity(entity); + GUIP.setUnitDisplayEnabled(true); + + if (entity.isDeployed() && !entity.isOffBoard() && entity.getPosition() != null) { + clientgui.getBoardView().centerOnHex(entity.getPosition()); + } + } + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.isPopupTrigger()) { + // If the right mouse button is pressed over an unselected entity, + // clear the selection and select that entity instead + int row = forceTree.getRowForLocation(e.getX(), e.getY()); + if (!forceTree.isRowSelected(row)) { + forceTree.setSelectionRow(row); + } + showPopup(e); + } + } + + /** Shows the right-click menu on the mek table */ + private void showPopup(MouseEvent e) { + int row = forceTree.getRowForLocation(e.getX(), e.getY()); + TreePath path = forceTree.getPathForRow(row); + if (path != null && path.getLastPathComponent() instanceof Entity) { + Entity entity = (Entity) path.getLastPathComponent(); + ScalingPopup popup = new ScalingPopup(); + popup.add(viewReadoutJMenuItem(entity)); + popup.show(e.getComponent(), e.getX(), e.getY()); + } + } + } + + @Override + public void gamePlayerConnected(GamePlayerConnectedEvent e) { + //noaction default + } + + @Override + public void gamePlayerDisconnected(GamePlayerDisconnectedEvent e) { + //noaction default + } + + @Override + public void gamePlayerChange(GamePlayerChangeEvent e) { + //noaction default + } + + @Override + public void gamePlayerChat(GamePlayerChatEvent e) { + //noaction default + } + + @Override + public void gamePhaseChange(GamePhaseChangeEvent e) { + refreshTree(); + } + + @Override + public void gameTurnChange(GameTurnChangeEvent e) { + refreshTree(); + } + + @Override + public void gameReport(GameReportEvent e) { + //noaction default + } + + @Override + public void gameEnd(GameEndEvent e) { + //noaction default + } + + @Override + public void gameBoardNew(GameBoardNewEvent e) { + //noaction default + } + + @Override + public void gameBoardChanged(GameBoardChangeEvent e) { + //noaction default + } + + @Override + public void gameSettingsChange(GameSettingsChangeEvent e) { + //noaction default + } + + @Override + public void gameMapQuery(GameMapQueryEvent e) { + //noaction default + } + + @Override + public void gameEntityNew(GameEntityNewEvent e) { + //noaction default + } + + @Override + public void gameEntityNewOffboard(GameEntityNewOffboardEvent e) { + //noaction default + } + + @Override + public void gameEntityRemove(GameEntityRemoveEvent e) { + //noaction default + } + + @Override + public void gameEntityChange(GameEntityChangeEvent e) { + //noaction default + } + + @Override + public void gameNewAction(GameNewActionEvent e) { + //noaction default + } + + @Override + public void gameClientFeedbackRequest(GameCFREvent evt) { + //noaction default + } + + @Override + public void gameVictory(GameVictoryEvent e) { + //noaction default + } + private void adaptToGUIScale() { + UIUtil.adjustContainer(this, UIUtil.FONT_SCALE1); + } + + @Override + public void preferenceChange(PreferenceChangeEvent e) { + // Update the text size when the GUI scaling changes + if (e.getName().equals(GUIPreferences.GUI_SCALE)) { + adaptToGUIScale(); + } + if (e.getName().equals(GUIPreferences.FORCE_DISPLAY_ENABLED)) { + refreshTree(); + } + + forceTree.setBackground(GUIP.getUnitToolTipBGColor()); + } +} diff --git a/megamek/src/megamek/client/ui/swing/lobby/LobbyMekCellFormatter.java b/megamek/src/megamek/client/ui/swing/lobby/LobbyMekCellFormatter.java index ccb1729196c..6794096453e 100644 --- a/megamek/src/megamek/client/ui/swing/lobby/LobbyMekCellFormatter.java +++ b/megamek/src/megamek/client/ui/swing/lobby/LobbyMekCellFormatter.java @@ -230,6 +230,7 @@ static String formatUnitFull(Entity entity, ChatLounge lobby, boolean forceView) if (pilot.countOptions() > 0) { result.append(DOT_SPACER + guiScaledFontHTML(uiQuirksColor())); result.append(Messages.getString("ChatLounge.abilities")); + result.append(""); } // Owner diff --git a/megamek/src/megamek/client/ui/swing/tooltip/UnitToolTip.java b/megamek/src/megamek/client/ui/swing/tooltip/UnitToolTip.java index 82eae2d3637..f83c1695c8c 100644 --- a/megamek/src/megamek/client/ui/swing/tooltip/UnitToolTip.java +++ b/megamek/src/megamek/client/ui/swing/tooltip/UnitToolTip.java @@ -1170,8 +1170,7 @@ public static String getOneLineSummary(Entity entity) { String armorStr = entity.getTotalArmor() + " / " + entity.getTotalOArmor(); String internalStr = entity.getTotalInternal() + " / " + entity.getTotalOInternal(); result += Messages.getString("BoardView1.Tooltip.ArmorInternals",armorStr, internalStr); - - result += getDamageLevelDesc(entity); + result += ' ' + getDamageLevelDesc(entity, true); if (!isGunEmplacement && entity.isImmobile()) { result += ' '+guiScaledFontHTML(GUIP.getWarningColor()) + Messages.getString("BoardView1.Tooltip.Immobile") + ""; @@ -1201,28 +1200,34 @@ public static String getSensorDesc(Entity e) { + srh.maxSensorRange + ")"; } - private static String getDamageLevelDesc(Entity entity) { + public static String getDamageLevelDesc(Entity entity, boolean useHtml) { String result; + + if (entity.isDoomed() || entity.isDestroyed()) { + String msg_destroyed = Messages.getString("BoardView1.Tooltip.Destroyed"); + return useHtml ? guiScaledFontHTML(GUIP.getWarningColor()) + msg_destroyed + "" : msg_destroyed; + } + switch (entity.getDamageLevel()) { case Entity.DMG_CRIPPLED: - String msg_crippled = ' '+Messages.getString("BoardView1.Tooltip.Crippled"); - result = guiScaledFontHTML(GUIP.getWarningColor()) + msg_crippled + ""; + String msg_crippled = Messages.getString("BoardView1.Tooltip.Crippled"); + result = useHtml ? guiScaledFontHTML(GUIP.getWarningColor()) + msg_crippled + "" : msg_crippled; break; case Entity.DMG_HEAVY: String msg_heavydmg = Messages.getString("BoardView1.Tooltip.HeavyDmg"); - result = ' '+guiScaledFontHTML(GUIP.getWarningColor()) + msg_heavydmg + ""; + result = useHtml ? guiScaledFontHTML(GUIP.getWarningColor()) + msg_heavydmg + "" : msg_heavydmg; break; case Entity.DMG_MODERATE: String msg_moderatedmg = Messages.getString("BoardView1.Tooltip.ModerateDmg"); - result = ' '+msg_moderatedmg; + result = msg_moderatedmg; break; case Entity.DMG_LIGHT: String msg_lightdmg = Messages.getString("BoardView1.Tooltip.LightDmg"); - result = ' '+msg_lightdmg ; + result = msg_lightdmg ; break; default: String msg_undamaged = Messages.getString("BoardView1.Tooltip.Undamaged"); - result = "  " + msg_undamaged; + result = msg_undamaged; break; } return result; @@ -1255,7 +1260,7 @@ private static StringBuilder inGameValues(Entity entity, Player localPlayer, boo } } - result += getDamageLevelDesc(entity); + result += " " + getDamageLevelDesc(entity, true); // Actual Movement if (!isGunEmplacement) { diff --git a/megamek/src/megamek/client/ui/swing/unitDisplay/UnitDisplay.java b/megamek/src/megamek/client/ui/swing/unitDisplay/UnitDisplay.java index 1d5d383e377..2834f4fe7c4 100644 --- a/megamek/src/megamek/client/ui/swing/unitDisplay/UnitDisplay.java +++ b/megamek/src/megamek/client/ui/swing/unitDisplay/UnitDisplay.java @@ -21,6 +21,7 @@ import megamek.client.ui.swing.ClientGUI; import megamek.client.ui.swing.GUIPreferences; import megamek.client.ui.swing.UnitDisplayOrderPreferences; +import megamek.client.ui.swing.tooltip.UnitToolTip; import megamek.client.ui.swing.util.CommandAction; import megamek.client.ui.swing.util.KeyCommandBind; import megamek.client.ui.swing.util.MegaMekController; @@ -578,23 +579,7 @@ public void displayEntity(Entity en) { protected void updateDisplay() { if (clientgui != null) { String enName = currentlyDisplaying.getShortName(); - switch (currentlyDisplaying.getDamageLevel()) { - case Entity.DMG_CRIPPLED: - enName += " [CRIPPLED]"; - break; - case Entity.DMG_HEAVY: - enName += " [HEAVY DMG]"; - break; - case Entity.DMG_MODERATE: - enName += " [MODERATE DMG]"; - break; - case Entity.DMG_LIGHT: - enName += " [LIGHT DMG]"; - break; - default: - enName += " [UNDAMAGED]"; - } - + enName += " [" + UnitToolTip.getDamageLevelDesc(currentlyDisplaying, false) + "]"; clientgui.getUnitDisplayDialog().setTitle(enName); labTitle.setText(enName); } diff --git a/megamek/src/megamek/client/ui/swing/util/KeyCommandBind.java b/megamek/src/megamek/client/ui/swing/util/KeyCommandBind.java index 06897cdcf0d..8f374a1d894 100644 --- a/megamek/src/megamek/client/ui/swing/util/KeyCommandBind.java +++ b/megamek/src/megamek/client/ui/swing/util/KeyCommandBind.java @@ -118,7 +118,8 @@ public enum KeyCommandBind { REPLACE_PLAYER(true, "replacePlayer", VK_R, CTRL_DOWN_MASK | SHIFT_DOWN_MASK), MOD_ENVELOPE(true, "viewModEnvelope", VK_W, CTRL_DOWN_MASK), SENSOR_RANGE(true, "sensorRange", VK_C), - UNDO_ILLEGAL_STEPS("undoIllegalSteps", VK_BACK_SPACE, CTRL_DOWN_MASK); + UNDO_ILLEGAL_STEPS("undoIllegalSteps", VK_BACK_SPACE, CTRL_DOWN_MASK), + FORCE_DISPLAY(true, "toggleForceDisplay", VK_F, CTRL_DOWN_MASK); /** The command associated with this binding. */ public String cmd;