diff --git a/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java b/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java
new file mode 100644
index 0000000000..a01b2dd7e9
--- /dev/null
+++ b/Mage.Client/src/main/java/mage/client/cards/VirtualCardInfo.java
@@ -0,0 +1,134 @@
+package mage.client.cards;
+
+import mage.abilities.icon.CardIconRenderSettings;
+import mage.cards.MageCard;
+import mage.cards.action.TransferData;
+import mage.cards.repository.CardInfo;
+import mage.cards.repository.CardRepository;
+import mage.client.dialog.PreferencesDialog;
+import mage.client.plugins.adapters.MageActionCallback;
+import mage.client.plugins.impl.Plugins;
+import mage.client.util.ClientDefaultSettings;
+import mage.view.CardView;
+
+import java.awt.*;
+import java.util.UUID;
+
+/**
+ * GUI: virtual card component for popup hint (move mouse over card to show a hint)
+ *
+ * Use case: you don't have a real card but want to show a popup card hint.
+ * Howto use:
+ * - call "init" on new card;
+ * - call "onMouseXXX" on start, update and close
+ *
+ * @author JayDi85
+ */
+public class VirtualCardInfo {
+
+ CardView cardView;
+ MageCard cardComponent;
+ BigCard bigCard;
+ MageActionCallback actionCallback;
+ TransferData data = new TransferData();
+ Dimension cardDimension = null;
+
+ public VirtualCardInfo() {
+ super();
+ }
+
+ private void clear() {
+ this.cardView = null;
+ this.cardComponent = null;
+ }
+
+ public void init(String cardName, BigCard bigCard, UUID gameId) {
+ CardInfo cardInfo = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null);
+ if (cardInfo == null) {
+ clear();
+ return;
+ }
+
+ this.init(new CardView(cardInfo.getCard()), bigCard, gameId);
+ }
+
+ public void init(CardView cardView, BigCard bigCard, UUID gameId) {
+ clear();
+
+ this.bigCard = bigCard != null ? bigCard : new BigCard();
+ this.cardDimension = new Dimension(ClientDefaultSettings.dimensions.getFrameWidth(), ClientDefaultSettings.dimensions.getFrameHeight());
+ this.actionCallback = (MageActionCallback) Plugins.instance.getActionCallback();
+
+ this.cardView = cardView;
+ this.cardComponent = Plugins.instance.getMageCard(
+ this.cardView,
+ this.bigCard,
+ new CardIconRenderSettings(),
+ this.cardDimension,
+ null,
+ true,
+ true,
+ PreferencesDialog.getRenderMode(),
+ true
+ );
+ this.cardComponent.update(cardView);
+
+ data.setComponent(this.cardComponent);
+ data.setCard(this.cardView);
+ data.setGameId(gameId);
+ }
+
+ public boolean prepared() {
+ return this.cardView != null
+ && this.cardComponent != null
+ && this.actionCallback != null;
+ }
+
+ private void updateLocation(Point point) {
+ Point newPoint = new Point(point);
+ if (this.cardComponent != null) {
+ // offset popup
+ newPoint.translate(50, 50);
+ }
+ data.setLocationOnScreen(newPoint);
+ }
+
+ public void onMouseEntered() {
+ onMouseMoved(null);
+ }
+
+ public void onMouseEntered(Point newLocation) {
+ if (!prepared()) {
+ return;
+ }
+
+ if (newLocation != null) {
+ updateLocation(newLocation);
+ }
+
+ this.actionCallback.mouseEntered(null, this.data);
+ }
+
+ public void onMouseMoved() {
+ onMouseMoved(null);
+ }
+
+ public void onMouseMoved(Point newLocation) {
+ if (!prepared()) {
+ return;
+ }
+
+ if (newLocation != null) {
+ updateLocation(newLocation);
+ }
+
+ this.actionCallback.mouseMoved(null, this.data);
+ }
+
+ public void onMouseExited() {
+ if (!prepared()) {
+ return;
+ }
+ this.actionCallback.mouseExited(null, this.data);
+ }
+}
diff --git a/Mage.Client/src/main/java/mage/client/components/ext/dlg/DlgParams.java b/Mage.Client/src/main/java/mage/client/components/ext/dlg/DlgParams.java
index 51dc575b29..0dfa74a456 100644
--- a/Mage.Client/src/main/java/mage/client/components/ext/dlg/DlgParams.java
+++ b/Mage.Client/src/main/java/mage/client/components/ext/dlg/DlgParams.java
@@ -12,7 +12,7 @@ import java.util.Set;
import java.util.UUID;
/**
- * Class is used to save parameters and to send them to dialog.
+ * GUI: parameters for dialogs, uses to store useful data
*
* @author mw, noxx
*/
diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java
index 7e4fd068a6..3ecb24adfd 100644
--- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java
+++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java
@@ -6,12 +6,19 @@ import java.util.*;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
+
import mage.choices.Choice;
+import mage.choices.ChoiceHintType;
import mage.client.MageFrame;
+import mage.client.cards.BigCard;
+import mage.client.cards.VirtualCardInfo;
import mage.client.util.gui.MageDialogState;
+import mage.game.command.Dungeon;
+import mage.view.CardView;
+import mage.view.DungeonView;
/**
- * Game GUI: choosing one of the list's item
+ * GUI: choosing one of the list's item. Uses in game's and non game's GUI like fast search
*
* @author JayDi85
*/
@@ -19,25 +26,25 @@ public class PickChoiceDialog extends MageDialog {
Choice choice;
+ // popup card info
+ int lastModelIndex = -1;
+ VirtualCardInfo cardInfo = new VirtualCardInfo();
+ BigCard bigCard;
+ UUID gameId;
+
java.util.List allItems = new ArrayList<>();
DefaultListModel dataModel = new DefaultListModel<>();
final private static String HTML_HEADERS_TEMPLATE = "%s
";
- public void showDialog(Choice choice) {
- showDialog(choice, null, null, null);
- }
-
public void showDialog(Choice choice, String startSelectionValue) {
- showDialog(choice, null, null, startSelectionValue);
+ showDialog(choice, startSelectionValue, null, null, null);
}
- public void showDialog(Choice choice, UUID objectId, MageDialogState mageDialogState) {
- showDialog(choice, objectId, mageDialogState, null);
- }
-
- public void showDialog(Choice choice, UUID objectId, MageDialogState mageDialogState, String startSelectionValue) {
+ public void showDialog(Choice choice, String startSelectionValue, UUID objectId, MageDialogState mageDialogState, BigCard bigCard) {
this.choice = choice;
+ this.bigCard = bigCard;
+ this.gameId = objectId;
setLabelText(this.labelMessage, choice.getMessage());
setLabelText(this.labelSubMessage, choice.getSubMessage());
@@ -54,20 +61,20 @@ public class PickChoiceDialog extends MageDialog {
this.allItems.clear();
if (choice.isKeyChoice()) {
for (Map.Entry entry : choice.getKeyChoices().entrySet()) {
- this.allItems.add(new KeyValueItem(entry.getKey(), entry.getValue()));
+ this.allItems.add(new KeyValueItem(entry.getKey(), entry.getValue(), choice.getHintType()));
}
} else {
for (String value : choice.getChoices()) {
- this.allItems.add(new KeyValueItem(value, value));
+ this.allItems.add(new KeyValueItem(value, value, choice.getHintType()));
}
}
// sorting
if (choice.isSortEnabled()) {
this.allItems.sort((o1, o2) -> {
- Integer n1 = choice.getSortData().get(o1.Key);
- Integer n2 = choice.getSortData().get(o2.Key);
- return n1.compareTo(n2);
+ Integer n1 = choice.getSortData().get(o1.getKey());
+ Integer n2 = choice.getSortData().get(o2.getKey());
+ return Integer.compare(n1, n2);
});
}
@@ -123,14 +130,36 @@ public class PickChoiceDialog extends MageDialog {
}
});
- // listeners double click choose
+ // listeners double click
+ // you can't use mouse wheel to switch hint type, cause wheel move a scrollbar
listChoices.addMouseListener(new MouseAdapter() {
+
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
doChoose();
}
}
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ choiceHintHide();
+ }
+ });
+
+ listChoices.addMouseMotionListener(new MouseMotionAdapter() {
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ // hint show
+ JList listSource = (JList) e.getSource();
+ int index = listSource.locationToIndex(e.getPoint());
+ if (index > -1) {
+ choiceHintShow(index);
+ } else {
+ choiceHintHide();
+ }
+ }
});
// listeners for ESC close
@@ -163,11 +192,11 @@ public class PickChoiceDialog extends MageDialog {
loadData();
// start selection
- if ((startSelectionValue != null)) {
+ if (startSelectionValue != null) {
int selectIndex = -1;
for (int i = 0; i < this.listChoices.getModel().getSize(); i++) {
KeyValueItem listItem = (KeyValueItem) this.listChoices.getModel().getElementAt(i);
- if (listItem.Key.equals(startSelectionValue)) {
+ if (listItem.getKey().equals(startSelectionValue)) {
selectIndex = i;
break;
}
@@ -182,10 +211,77 @@ public class PickChoiceDialog extends MageDialog {
this.setVisible(true);
}
+ private void choiceHintShow(int modelIndex) {
+
+ switch (choice.getHintType()) {
+ case CARD:
+ case CARD_DUNGEON: {
+ // as popup card
+ if (lastModelIndex != modelIndex) {
+ // new card
+ KeyValueItem item = (KeyValueItem) listChoices.getModel().getElementAt(modelIndex);
+ String cardName = item.getValue();
+
+ if (choice.getHintType() == ChoiceHintType.CARD) {
+ cardInfo.init(cardName, this.bigCard, this.gameId);
+ } else if (choice.getHintType() == ChoiceHintType.CARD_DUNGEON) {
+ CardView cardView = new CardView(new DungeonView(Dungeon.createDungeon(cardName)));
+ cardInfo.init(cardView, this.bigCard, this.gameId);
+ }
+
+ cardInfo.onMouseEntered(MouseInfo.getPointerInfo().getLocation());
+ } else {
+ // old card
+ cardInfo.onMouseMoved(MouseInfo.getPointerInfo().getLocation());
+ }
+ lastModelIndex = modelIndex;
+ break;
+ }
+
+ default:
+ case TEXT: {
+ // as popup text
+ if (lastModelIndex != modelIndex) {
+ // new hint
+ listChoices.setToolTipText(null);
+ KeyValueItem item = (KeyValueItem) listChoices.getModel().getElementAt(modelIndex);
+ listChoices.setToolTipText(item.getValue());
+ }
+ lastModelIndex = modelIndex;
+ break;
+ }
+ }
+ }
+
+ private void choiceHintHide() {
+ switch (choice.getHintType()) {
+ case CARD: {
+ // as popup card
+ cardInfo.onMouseExited();
+ break;
+ }
+
+ default:
+ case TEXT: {
+ // as popup text
+ listChoices.setToolTipText(null);
+ break;
+ }
+ }
+
+ lastModelIndex = -1;
+ }
+
public void setWindowSize(int width, int height) {
this.setSize(new Dimension(width, height));
}
+ @Override
+ public void hideDialog() {
+ choiceHintHide();
+ super.hideDialog();
+ }
+
private void loadData() {
// load data to datamodel after filter or on startup
String filter = choice.getSearchText();
@@ -196,7 +292,7 @@ public class PickChoiceDialog extends MageDialog {
this.dataModel.clear();
for (KeyValueItem item : this.allItems) {
- if (!choice.isSearchEnabled() || item.Value.toLowerCase(Locale.ENGLISH).contains(filter)) {
+ if (!choice.isSearchEnabled() || item.getValue().toLowerCase(Locale.ENGLISH).contains(filter)) {
this.dataModel.addElement(item);
}
}
@@ -284,27 +380,33 @@ public class PickChoiceDialog extends MageDialog {
}
}
- class KeyValueItem {
+ static class KeyValueItem {
- private final String Key;
- private final String Value;
+ protected final String key;
+ protected final String value;
+ protected final ChoiceHintType hint;
- public KeyValueItem(String value, String label) {
- this.Key = value;
- this.Value = label;
+ public KeyValueItem(String key, String value, ChoiceHintType hint) {
+ this.key = key;
+ this.value = value;
+ this.hint = hint;
}
public String getKey() {
- return this.Key;
+ return this.key;
}
public String getValue() {
- return this.Value;
+ return this.value;
+ }
+
+ public ChoiceHintType getHint() {
+ return this.hint;
}
@Override
public String toString() {
- return this.Value;
+ return this.value;
}
}
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 2a99e8369a..d5e1165067 100644
--- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java
+++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java
@@ -1746,7 +1746,7 @@ public final class GamePanel extends javax.swing.JPanel {
hideAll();
// TODO: remember last choices and search incremental for same events?
PickChoiceDialog pickChoice = new PickChoiceDialog();
- pickChoice.showDialog(choice, objectId, choiceWindowState);
+ pickChoice.showDialog(choice, null, objectId, choiceWindowState, bigCard);
// special mode adds # to the answer (server side code must process that prefix, see replacementEffectChoice)
String specialPrefix = choice.isChosenSpecial() ? "#" : "";
diff --git a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java
index 78578a8084..685ab57f1c 100644
--- a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java
+++ b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java
@@ -132,6 +132,7 @@ public class MageActionCallback implements ActionCallback {
@Override
public void mouseEntered(MouseEvent e, final TransferData data) {
+ // MouseEvent can be null for custom hints calls, e.g. from choose dialog
this.popupData = data;
handleMouseMoveOverNewCard(data);
}
@@ -288,10 +289,13 @@ public class MageActionCallback implements ActionCallback {
@Override
public void mouseMoved(MouseEvent e, TransferData data) {
+ // MouseEvent can be null for custom hints calls, e.g. from choose dialog
+
if (!Plugins.instance.isCardPluginLoaded()) {
return;
}
- if (!popupData.getCard().equals(data.getCard())) {
+ if (this.popupData == null
+ || !popupData.getCard().equals(data.getCard())) {
this.popupData = data;
handleMouseMoveOverNewCard(data);
}
@@ -336,6 +340,7 @@ public class MageActionCallback implements ActionCallback {
@Override
public void mouseExited(MouseEvent e, final TransferData data) {
+ // MouseEvent can be null for custom hints calls, e.g. from choose dialog
if (data != null) {
hideAll(data.getGameId());
} else {
@@ -452,6 +457,10 @@ public class MageActionCallback implements ActionCallback {
hideTooltipPopup();
cancelTimeout();
Component parentComponent = SwingUtilities.getRoot(cardPanel);
+ if (parentComponent == null) {
+ // virtual card (example: show card popup in non cards panel like PickChoiceDialog)
+ parentComponent = MageFrame.getDesktop();
+ }
Point parentPoint = parentComponent.getLocationOnScreen();
if (data.getLocationOnScreen() == null) {
diff --git a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java
index 352b88f610..0e1dec0090 100644
--- a/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java
+++ b/Mage.Client/src/main/java/mage/client/util/GUISizeHelper.java
@@ -162,6 +162,14 @@ public final class GUISizeHelper {
enlargedImageHeight = 25 * PreferencesDialog.getCachedValue(PreferencesDialog.KEY_GUI_ENLARGED_IMAGE_SIZE, 20);
}
+ public static int getTooltipCardWidth() {
+ return 20 * GUISizeHelper.cardTooltipFontSize - 50;
+ }
+
+ public static int getTooltipCardHeight() {
+ return 12 * GUISizeHelper.cardTooltipFontSize - 20;
+ }
+
public static void changePopupMenuFont(JPopupMenu popupMenu) {
for (Component comp : popupMenu.getComponents()) {
if (comp instanceof JMenuItem) {
diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java b/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java
index 4cf531e0d5..43b1273b91 100644
--- a/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java
+++ b/Mage.Client/src/main/java/org/mage/plugins/card/info/CardInfoPaneImpl.java
@@ -44,8 +44,8 @@ public class CardInfoPaneImpl extends JEditorPane implements CardInfoPane {
}
private void setGUISize() {
- addWidth = 20 * GUISizeHelper.cardTooltipFontSize - 50;
- addHeight = 12 * GUISizeHelper.cardTooltipFontSize - 20;
+ addWidth = GUISizeHelper.getTooltipCardWidth();
+ addHeight = GUISizeHelper.getTooltipCardHeight();
setSize = true;
}
diff --git a/Mage.Common/src/main/java/mage/cards/action/TransferData.java b/Mage.Common/src/main/java/mage/cards/action/TransferData.java
index ac1f6c7d99..9f273f182b 100644
--- a/Mage.Common/src/main/java/mage/cards/action/TransferData.java
+++ b/Mage.Common/src/main/java/mage/cards/action/TransferData.java
@@ -9,6 +9,7 @@ import java.util.UUID;
/**
* Data for main card panel events like mouse moves or clicks
+ *
*/
public class TransferData {
diff --git a/Mage/src/main/java/mage/choices/Choice.java b/Mage/src/main/java/mage/choices/Choice.java
index 21b1ed6a4f..2323cbf42e 100644
--- a/Mage/src/main/java/mage/choices/Choice.java
+++ b/Mage/src/main/java/mage/choices/Choice.java
@@ -39,6 +39,8 @@ public interface Choice extends Serializable, Copyable {
String getSpecialHint();
+ ChoiceHintType getHintType();
+
// string choice
void setChoices(Set choices);
diff --git a/Mage/src/main/java/mage/choices/ChoiceHintType.java b/Mage/src/main/java/mage/choices/ChoiceHintType.java
new file mode 100644
index 0000000000..86e7580f4c
--- /dev/null
+++ b/Mage/src/main/java/mage/choices/ChoiceHintType.java
@@ -0,0 +1,13 @@
+package mage.choices;
+
+/**
+ * For GUI: popup hint in choose dialog
+ *
+ * @author JayDi85
+ */
+public enum ChoiceHintType {
+
+ TEXT,
+ CARD,
+ CARD_DUNGEON
+}
diff --git a/Mage/src/main/java/mage/choices/ChoiceImpl.java b/Mage/src/main/java/mage/choices/ChoiceImpl.java
index e45fb02d2e..12246f9915 100644
--- a/Mage/src/main/java/mage/choices/ChoiceImpl.java
+++ b/Mage/src/main/java/mage/choices/ChoiceImpl.java
@@ -21,6 +21,7 @@ public class ChoiceImpl implements Choice {
protected String subMessage;
protected boolean searchEnabled = true; // enable for all windows by default
protected String searchText;
+ protected ChoiceHintType hintType;
// special button with #-answer
// warning, only for human's GUI, not AI
@@ -34,7 +35,12 @@ public class ChoiceImpl implements Choice {
}
public ChoiceImpl(boolean required) {
+ this(required, ChoiceHintType.TEXT);
+ }
+
+ public ChoiceImpl(boolean required, ChoiceHintType hintType) {
this.required = required;
+ this.hintType = hintType;
}
public ChoiceImpl(final ChoiceImpl choice) {
@@ -46,6 +52,7 @@ public class ChoiceImpl implements Choice {
this.subMessage = choice.subMessage;
this.searchEnabled = choice.searchEnabled;
this.searchText = choice.searchText;
+ this.hintType = choice.hintType;
this.choices.addAll(choice.choices);
this.choiceKey = choice.choiceKey;
this.keyChoices = choice.keyChoices; // list should never change for the same object so copy by reference TODO: check errors with that, it that ok? Color list is static
@@ -302,4 +309,9 @@ public class ChoiceImpl implements Choice {
public String getSpecialHint() {
return this.specialHint;
}
+
+ @Override
+ public ChoiceHintType getHintType() {
+ return this.hintType;
+ }
}
diff --git a/Mage/src/main/java/mage/game/command/Dungeon.java b/Mage/src/main/java/mage/game/command/Dungeon.java
index 2e6e89e44e..eca4f340a1 100644
--- a/Mage/src/main/java/mage/game/command/Dungeon.java
+++ b/Mage/src/main/java/mage/game/command/Dungeon.java
@@ -14,6 +14,7 @@ import mage.abilities.effects.Effect;
import mage.abilities.text.TextPart;
import mage.cards.FrameStyle;
import mage.choices.Choice;
+import mage.choices.ChoiceHintType;
import mage.choices.ChoiceImpl;
import mage.constants.CardType;
import mage.constants.Outcome;
@@ -130,11 +131,15 @@ public class Dungeon implements CommandObject {
public static Dungeon selectDungeon(UUID playerId, Game game) {
Player player = game.getPlayer(playerId);
- Choice choice = new ChoiceImpl(true);
+ Choice choice = new ChoiceImpl(true, ChoiceHintType.CARD_DUNGEON);
choice.setMessage("Choose a dungeon to venture into");
choice.setChoices(dungeonNames);
player.choose(Outcome.Neutral, choice, game);
- switch (choice.getChoice()) {
+ return createDungeon(choice.getChoice());
+ }
+
+ public static Dungeon createDungeon(String name) {
+ switch (name) {
case "Tomb of Annihilation":
return new TombOfAnnihilation();
case "Lost Mine of Phandelver":