From 8add25fa1218a1269940388e3ad4b3a326ee882f Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 18 Jan 2020 06:30:44 +0400 Subject: [PATCH] * UI: choose modes dialog improves: * Added hotkeys to select options (1-9 for choice, SPACE/ENTER for done, ESC for cancel); * "Up to" modes choose dialog - added "done" button in dialog; * "Up to" modes choose dialog - fixed that user can't cancel if already selected one mode; * Added extra info about source object, selected and remaining modes to select, ability number for hotkey; * Fixed that mode choose dialog doesn't close on cancel (#6199); --- .../components/ability/AbilityPicker.java | 145 ++++++++++++++---- .../main/java/mage/client/game/GamePanel.java | 12 +- .../java/mage/view/AbilityPickerView.java | 13 +- .../src/mage/player/human/HumanPlayer.java | 39 ++++- .../java/mage/server/game/GameController.java | 6 +- Mage/src/main/java/mage/abilities/Modes.java | 4 + 6 files changed, 179 insertions(+), 40 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java b/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java index 90eff9c8ec5..3faf0f12f5b 100644 --- a/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java +++ b/Mage.Client/src/main/java/mage/client/components/ability/AbilityPicker.java @@ -1,7 +1,9 @@ package mage.client.components.ability; +import mage.abilities.Modes; import mage.client.SessionHandler; import mage.client.dialog.MageDialog; +import mage.client.game.GamePanel; import mage.client.util.ImageHelper; import mage.remote.Session; import mage.view.AbilityPickerView; @@ -22,7 +24,7 @@ import java.util.*; /** * Dialog for choosing abilities. * - * @author nantuko + * @author nantuko, JayDi85 */ public class AbilityPicker extends JXPanel implements MouseWheelListener { @@ -41,7 +43,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { private BackgroundPainter mwPanelPainter; private JScrollPane jScrollPane2; - private JTextField title; + private JLabel title; private Image rightImage; private Image rightImageHovered; @@ -68,9 +70,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { public AbilityPicker(List choices, String message) { this.choices = choices; setSize(DIALOG_WIDTH, DIALOG_HEIGHT); - if (message != null) { - this.message = message + " (single-click)"; - } + setMessageAndPrepare(message); initComponents(); jScrollPane2.setOpaque(false); jScrollPane2.getViewport().setOpaque(false); @@ -80,7 +80,6 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { } public void init(UUID gameId) { - this.gameId = gameId; } @@ -93,11 +92,17 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { public void show(AbilityPickerView choices, Point p) { this.choices = new ArrayList<>(); this.selected = true; // to stop previous modal + setMessageAndPrepare(choices.getMessage()); + // if not cancel from server then add own + boolean wasCancelButton = false; for (Map.Entry choice : choices.getChoices().entrySet()) { + wasCancelButton = wasCancelButton || choice.getKey().equals(Modes.CHOOSE_OPTION_CANCEL_ID); this.choices.add(new AbilityPickerAction(choice.getKey(), choice.getValue())); } - this.choices.add(new AbilityPickerAction(null, "Cancel")); + if (!wasCancelButton) { + this.choices.add(new AbilityPickerAction(null, "Cancel")); + } show(this.choices); } @@ -109,6 +114,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { rows.setListData(this.choices.toArray()); this.rows.setSelectedIndex(0); this.selected = false; // back to false - waiting for selection + this.title.setText(this.message); setVisible(true); MageDialog.makeWindowCentered(this, DIALOG_WIDTH, DIALOG_HEIGHT); @@ -116,30 +122,17 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { } private void initComponents() { - JLabel jLabel1; - JLabel jLabel3; - Color textColor = Color.white; - mwPanelPainter = new BackgroundPainter(); - jLabel1 = new JLabel(); - jLabel3 = new JLabel(); - - title = new JTextField(); jScrollPane2 = new JScrollPane(); setBackground(textColor); setBackgroundPainter(mwPanelPainter); - jLabel1.setFont(new Font("Times New Roman", 1, 18)); - jLabel1.setForeground(textColor); - jLabel1.setText(message); - jLabel3.setForeground(textColor); - jLabel3.setHorizontalAlignment(SwingConstants.TRAILING); - jLabel3.setText("Selected:"); - - title.setFont(new Font("Tahoma", 1, 11)); - title.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2)); + title = new JLabel(); + title.setFont(new Font("Times New Roman", 1, 15)); + title.setForeground(textColor); + title.setText(message); jScrollPane2.setBorder(null); jScrollPane2.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); @@ -161,6 +154,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { rows.setMinimumSize(new Dimension(67, 16)); rows.setOpaque(false); + // mouse actions rows.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent evt) { @@ -174,6 +168,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { rows.setBorder(BorderFactory.createEmptyBorder()); rows.addMouseWheelListener(this); + jScrollPane2.setViewportView(rows); jScrollPane2.setViewportBorder(BorderFactory.createEmptyBorder()); @@ -184,7 +179,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { GroupLayout.TRAILING, layout.createSequentialGroup().addContainerGap().add( layout.createParallelGroup(GroupLayout.TRAILING).add(GroupLayout.LEADING, jScrollPane2, GroupLayout.DEFAULT_SIZE, 422, Short.MAX_VALUE).add(GroupLayout.LEADING, - layout.createSequentialGroup().add(jLabel1).addPreferredGap(LayoutStyle.RELATED, 175, Short.MAX_VALUE).add(1, 1, 1)).add( + layout.createSequentialGroup().add(title).addPreferredGap(LayoutStyle.RELATED, 175, Short.MAX_VALUE).add(1, 1, 1)).add( GroupLayout.LEADING, layout.createSequentialGroup().add(layout.createParallelGroup(GroupLayout.LEADING) ) @@ -197,7 +192,7 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.LEADING).add( layout.createSequentialGroup().add( layout.createParallelGroup(GroupLayout.LEADING).add( - layout.createSequentialGroup().add(jLabel1, GroupLayout.PREFERRED_SIZE, 36, GroupLayout.PREFERRED_SIZE) + layout.createSequentialGroup().add(title, GroupLayout.PREFERRED_SIZE, 72, GroupLayout.PREFERRED_SIZE) .add(5, 5, 5) .add( layout.createParallelGroup(GroupLayout.BASELINE) @@ -463,4 +458,102 @@ public class AbilityPicker extends JXPanel implements MouseWheelListener { log.error("Couldn't cancel choose dialog: " + e, e); } } + + private void setMessageAndPrepare(String message) { + if (message != null) { + this.message = message + " (single-click or hotkeys)"; + } else { + this.message = DEFAULT_MESSAGE; + } + this.message = "" + this.message; + } + + private void tryChoiceDone() { + // done by keyboard + if (!isVisible() || choices == null) { + return; + } + for (Object obj : choices) { + AbilityPickerAction action = (AbilityPickerAction) obj; + if (Modes.CHOOSE_OPTION_DONE_ID.equals(action.id)) { + action.actionPerformed(null); + break; + } + } + } + + private void tryChoiceCancel() { + // cancel by keyboard + if (!isVisible() || choices == null) { + return; + } + for (Object obj : choices) { + AbilityPickerAction action = (AbilityPickerAction) obj; + if (Modes.CHOOSE_OPTION_DONE_ID.equals(action.id)) { + action.actionPerformed(null); + break; + } + } + } + + private void tryChoiceOption(int choiceNumber) { + // choice by keyboard + if (!isVisible() || choices == null) { + return; + } + String need = choiceNumber + "."; + for (Object obj : choices) { + AbilityPickerAction action = (AbilityPickerAction) obj; + if (action.toString().startsWith(need)) { + action.actionPerformed(null); + break; + } + } + } + + public void injectHotkeys(GamePanel panel, String commandsPrefix) { + // TODO: fix that GamePanel recive imput from any place, not only active (e.g. F9 works from lobby) + int c = JComponent.WHEN_IN_FOCUSED_WINDOW; + + // choice keys + Map numbers = new HashMap<>(); + numbers.put(KeyEvent.VK_1, 1); + numbers.put(KeyEvent.VK_2, 2); + numbers.put(KeyEvent.VK_3, 3); + numbers.put(KeyEvent.VK_4, 4); + numbers.put(KeyEvent.VK_5, 5); + numbers.put(KeyEvent.VK_6, 6); + numbers.put(KeyEvent.VK_7, 7); + numbers.put(KeyEvent.VK_8, 8); + numbers.put(KeyEvent.VK_9, 9); + numbers.forEach((vk, num) -> { + KeyStroke ks = KeyStroke.getKeyStroke(vk, 0); + panel.getInputMap(c).put(ks, commandsPrefix + "_CHOOSE_" + num); + panel.getActionMap().put(commandsPrefix + "_CHOOSE_" + num, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + tryChoiceOption(num); + } + }); + }); + + // done key (space, enter) + panel.getInputMap(c).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), commandsPrefix + "_CHOOSE_DONE"); + panel.getInputMap(c).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), commandsPrefix + "_CHOOSE_DONE"); + panel.getActionMap().put(commandsPrefix + "_CHOOSE_DONE", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + tryChoiceDone(); + } + }); + + // cancel key (esc) + panel.getInputMap(c).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), commandsPrefix + "_CHOOSE_CANCEL"); + panel.getActionMap().put(commandsPrefix + "_CHOOSE_CANCEL", new AbstractAction() { + @Override + public void actionPerformed(ActionEvent actionEvent) { + tryChoiceCancel(); + } + }); + } } diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index 0b2cf483d3e..d5b103a2d80 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -394,7 +394,6 @@ public final class GamePanel extends javax.swing.JPanel { this.feedbackPanel.init(gameId); this.feedbackPanel.clear(); this.abilityPicker.init(gameId); - this.btnConcede.setVisible(true); this.btnStopWatching.setVisible(false); this.btnSwitchHands.setVisible(false); @@ -1399,6 +1398,8 @@ public final class GamePanel extends javax.swing.JPanel { } public void select(String message, GameView gameView, int messageId, Map options) { + this.abilityPicker.setVisible(false); + holdingPriority = false; txtHoldPriority.setVisible(false); setMenuStates( @@ -1464,6 +1465,7 @@ public final class GamePanel extends javax.swing.JPanel { } private void hideAll() { + this.abilityPicker.setVisible(false); ActionCallback callback = Plugins.instance.getActionCallback(); ((MageActionCallback) callback).hideGameUpdate(gameId); } @@ -1900,6 +1902,9 @@ public final class GamePanel extends javax.swing.JPanel { } }); + // special hotkeys for custom rendered dialogs without focus + this.abilityPicker.injectHotkeys(this, "ABILITY_PICKER"); + final BasicSplitPaneUI myUi = (BasicSplitPaneUI) jSplitPane0.getUI(); final BasicSplitPaneDivider divider = myUi.getDivider(); final JButton upArrowButton = (JButton) divider.getComponent(0); @@ -2268,6 +2273,10 @@ public final class GamePanel extends javax.swing.JPanel { for (ComponentListener cl : this.getComponentListeners()) { this.removeComponentListener(cl); } + + for (KeyListener kl : this.getKeyListeners()) { + this.removeKeyListener(kl); + } } private void btnConcedeActionPerformed(java.awt.event.ActionEvent evt) { @@ -2711,5 +2720,4 @@ class ReplayTask extends SwingWorker> { } catch (CancellationException ex) { } } - } diff --git a/Mage.Common/src/main/java/mage/view/AbilityPickerView.java b/Mage.Common/src/main/java/mage/view/AbilityPickerView.java index 460327f155e..1f4575c1998 100644 --- a/Mage.Common/src/main/java/mage/view/AbilityPickerView.java +++ b/Mage.Common/src/main/java/mage/view/AbilityPickerView.java @@ -1,15 +1,14 @@ - package mage.view; +import mage.abilities.Ability; + import java.io.Serializable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; -import mage.abilities.Ability; /** - * * @author BetaSteward_at_googlemail.com */ public class AbilityPickerView implements Serializable { @@ -17,6 +16,7 @@ public class AbilityPickerView implements Serializable { private static final long serialVersionUID = 1L; private Map choices = new LinkedHashMap<>(); + private String message = null; public AbilityPickerView(String objectName, List abilities) { for (Ability ability : abilities) { @@ -32,11 +32,16 @@ public class AbilityPickerView implements Serializable { } } - public AbilityPickerView(Map modes) { + public AbilityPickerView(Map modes, String message) { this.choices = modes; + this.message = message; } public Map getChoices() { return choices; } + + public String getMessage() { + return message; + } } diff --git a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java index c45f81e8772..236e2ada061 100644 --- a/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java +++ b/Mage.Server.Plugins/Mage.Player.Human/src/mage/player/human/HumanPlayer.java @@ -2040,10 +2040,14 @@ public class HumanPlayer extends PlayerImpl { } if (modes.size() > 1) { + // done option for up to choices + boolean canEndChoice = modes.getSelectedModes().size() >= modes.getMinModes(); MageObject obj = game.getObject(source.getSourceId()); Map modeMap = new LinkedHashMap<>(); + int modeIndex = 0; AvailableModes: for (Mode mode : modes.getAvailableModes(source, game)) { + modeIndex++; int timesSelected = modes.getSelectedStats(mode.getId()); for (UUID selectedModeId : modes.getSelectedModes()) { Mode selectedMode = modes.get(selectedModeId); @@ -2068,18 +2072,31 @@ public class HumanPlayer extends PlayerImpl { modeText = "(selected " + timesSelected + "x) " + modeText; } } - modeMap.put(mode.getId(), modeText); + modeMap.put(mode.getId(), modeIndex + ". " + modeText); } } if (!modeMap.isEmpty()) { + + // can done for up to + if (canEndChoice) { + modeMap.put(Modes.CHOOSE_OPTION_DONE_ID, "Done"); + } + modeMap.put(Modes.CHOOSE_OPTION_CANCEL_ID, "Cancel"); + boolean done = false; while (!done && canRespond()) { + String message = "Choose mode (selected " + modes.getSelectedModes().size() + " of " + modes.getMaxModes() + + ", min " + modes.getMinModes() + ")"; + if (obj != null) { + message = message + "
" + obj.getLogName(); + } + updateGameStatePriority("chooseMode", game); prepareForResponse(game); if (!isExecutingMacro()) { - game.fireGetModeEvent(playerId, "Choose Mode", modeMap); + game.fireGetModeEvent(playerId, message, modeMap); } waitForResponse(game); @@ -2091,9 +2108,21 @@ public class HumanPlayer extends PlayerImpl { return mode; } } - } else if (modes.getSelectedModes().size() >= modes.getMinModes()) { - /* let the player cancel mode selection if they do not need to select any further modes */ - done = true; + + // end choice by done option in ability pickup dialog + if (canEndChoice && Modes.CHOOSE_OPTION_DONE_ID.equals(response.getUUID())) { + done = true; + } + + // cancel choice (remove all selections) + if (Modes.CHOOSE_OPTION_CANCEL_ID.equals(response.getUUID())) { + modes.getSelectedModes().clear(); + return null; + } + } else if (canEndChoice) { + // end choice by done button in feedback panel + // disable after done option implemented + // done = true; } if (source.getAbilityType() != AbilityType.TRIGGERED) { done = true; diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 12ff0551ca6..70795de7167 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -207,7 +207,7 @@ public class GameController implements GameCallback { choosePile(event.getPlayerId(), event.getMessage(), event.getPile1(), event.getPile2()); break; case CHOOSE_MODE: - chooseMode(event.getPlayerId(), event.getModes()); + chooseMode(event.getPlayerId(), event.getModes(), event.getMessage()); break; case CHOOSE_CHOICE: chooseChoice(event.getPlayerId(), event.getChoice()); @@ -769,8 +769,8 @@ public class GameController implements GameCallback { perform(playerId, playerId1 -> getGameSession(playerId1).choosePile(message, new CardsView(pile1), new CardsView(pile2))); } - private synchronized void chooseMode(UUID playerId, final Map modes) throws MageException { - perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(modes))); + private synchronized void chooseMode(UUID playerId, final Map modes, final String message) throws MageException { + perform(playerId, playerId1 -> getGameSession(playerId1).chooseAbility(new AbilityPickerView(modes, message))); } private synchronized void chooseChoice(UUID playerId, final Choice choice) throws MageException { diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index 2417b494e32..f05cb990d96 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -18,6 +18,10 @@ import java.util.*; */ public class Modes extends LinkedHashMap { + // choose ID for options in ability/mode picker dialogs + public static final UUID CHOOSE_OPTION_DONE_ID = UUID.fromString("33e72ad6-17ae-4bfb-a097-6e7aa06b49e9"); + public static final UUID CHOOSE_OPTION_CANCEL_ID = UUID.fromString("0125bd0c-5610-4eba-bc80-fc6d0a7b9de6"); + private Mode currentMode; // the current mode of the selected modes private final List selectedModes = new ArrayList<>(); // all selected modes (this + duplicate) private final Map duplicateModes = new LinkedHashMap<>(); // for 2x selects: copy mode and put it to duplicate list