mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 02:30:08 -08:00
GUI - new card hints window features:
* new help window can be opened from a player panel; * it collect and show all visible game hints from all players and all zones; * it updates in real time on game update; * allows to customize visible data; * allows to open multiple windows (current limit is 5 windows, can be slow to render); * allows to minimize opened windows; * workable card popup on mouse move over card name or card id; * filter modes: * all - show hints from all players; * player - show hints from single player; * group mode: * by hints - show same hints as one with all used cards; * by cards - show full cards list with own hints; * search mode: * allows to filter card hints by player name, card name, card id or card hint; * allows to search multiple words (equals to "or") * current limitation: * card popup shows a card instead a real object, e.g. miss card hints in it (relelated to game logs problem); * unsupport of emblems, dungeons and other non card objects from a command zone; * unsupport of revealed and library's top cards; GUI - player's panel improves: * added hints helper button; * added player hithlight as possible target in choose dialogs; * improved player name button in small mode; * fixed wrong height in small mode; Other fixes: * game logs: added card popup support for logs with custom object name;
This commit is contained in:
parent
ca80849249
commit
2bbe2b3c43
16 changed files with 1229 additions and 75 deletions
|
|
@ -90,6 +90,10 @@ public class VirtualCardInfo {
|
|||
data.setTooltipDelay(tooltipDelay);
|
||||
}
|
||||
|
||||
public CardView getCardView() {
|
||||
return this.cardView;
|
||||
}
|
||||
|
||||
public boolean prepared() {
|
||||
return this.cardView != null
|
||||
&& this.cardComponent != null
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ public class ChatPanelBasic extends javax.swing.JPanel {
|
|||
}
|
||||
|
||||
public void enableHyperlinks() {
|
||||
txtConversation.enableHyperlinks();
|
||||
txtConversation.enableHyperlinksAndCardPopups();
|
||||
}
|
||||
|
||||
private void txtMessageKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtMessageKeyTyped
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import mage.client.cards.VirtualCardInfo;
|
|||
import mage.client.dialog.PreferencesDialog;
|
||||
import mage.game.command.Plane;
|
||||
import mage.util.CardUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.utils.ThreadUtils;
|
||||
import mage.view.CardView;
|
||||
import mage.view.PlaneView;
|
||||
|
|
@ -87,6 +88,8 @@ public class ColorPane extends JEditorPane {
|
|||
if (e.getEventType() == EventType.ENTERED) {
|
||||
CardView cardView = null;
|
||||
|
||||
// TODO: add real game object first
|
||||
|
||||
// card
|
||||
CardInfo card = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null);
|
||||
if (card != null) {
|
||||
|
|
@ -144,19 +147,20 @@ public class ColorPane extends JEditorPane {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setText(String string) {
|
||||
super.setText(string);
|
||||
public void setText(String text) {
|
||||
// must use append to enable popup/hyperlinks support
|
||||
super.setText("");
|
||||
append(text);
|
||||
}
|
||||
|
||||
public void append(String text) {
|
||||
try {
|
||||
if (hyperlinkEnabled) {
|
||||
text = text.replaceAll("(<font color=[^>]*>([^<]*)) (\\[[0-9a-fA-F]*\\])</font>", "<a href=\"#$2\">$1</a> $3");
|
||||
text = GameLog.injectPopupSupport(text);
|
||||
}
|
||||
kit.insertHTML(doc, doc.getLength(), text, 0, 0, null);
|
||||
int len = getDocument().getLength();
|
||||
setCaretPosition(len);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
@ -182,7 +186,7 @@ public class ColorPane extends JEditorPane {
|
|||
super.paintChildren(g);
|
||||
}
|
||||
|
||||
public void enableHyperlinks() {
|
||||
public void enableHyperlinksAndCardPopups() {
|
||||
hyperlinkEnabled = true;
|
||||
addHyperlinkHandlers();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package mage.client.components;
|
||||
|
||||
/**
|
||||
* GUI: support windows mimize (iconify) by double clicks
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public interface MageDesktopIconifySupport {
|
||||
|
||||
/**
|
||||
* Window's header width after minimized to icon
|
||||
*/
|
||||
default int getDesktopIconWidth() {
|
||||
return 250;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,31 +3,33 @@ package mage.client.components;
|
|||
import java.awt.BorderLayout;
|
||||
import javax.swing.*;
|
||||
|
||||
import mage.client.dialog.CardHintsHelperDialog;
|
||||
import mage.client.dialog.CardInfoWindowDialog;
|
||||
|
||||
/**
|
||||
* GUI: helper class to improve work with desktop frames
|
||||
*
|
||||
* @author LevelX2
|
||||
*/
|
||||
public class MageDesktopManager extends DefaultDesktopManager {
|
||||
|
||||
static final int DESKTOP_ICON_WIDTH = 250;
|
||||
|
||||
@Override
|
||||
public void iconifyFrame(JInternalFrame f) {
|
||||
super.iconifyFrame(f);
|
||||
if (f instanceof CardInfoWindowDialog) {
|
||||
if (f instanceof MageDesktopIconifySupport) {
|
||||
int needIconWidth = ((MageDesktopIconifySupport) f).getDesktopIconWidth();
|
||||
JInternalFrame.JDesktopIcon icon = f.getDesktopIcon();
|
||||
icon.setBounds(f.getX() + (f.getWidth() - DESKTOP_ICON_WIDTH), f.getY(), DESKTOP_ICON_WIDTH, icon.getHeight());
|
||||
icon.setBounds(f.getX() + (f.getWidth() - needIconWidth), f.getY(), needIconWidth, icon.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deiconifyFrame(JInternalFrame f) {
|
||||
super.deiconifyFrame(f);
|
||||
if (f instanceof CardInfoWindowDialog) {
|
||||
if (f instanceof MageDesktopIconifySupport) {
|
||||
int needIconWidth = ((MageDesktopIconifySupport) f).getDesktopIconWidth();
|
||||
JInternalFrame.JDesktopIcon icon = f.getDesktopIcon();
|
||||
f.setBounds(icon.getX() + (DESKTOP_ICON_WIDTH - f.getWidth()), icon.getY(), f.getWidth(), f.getHeight());
|
||||
f.setBounds(icon.getX() + (needIconWidth - f.getWidth()), icon.getY(), f.getWidth(), f.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.3" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JInternalFrameFormInfo">
|
||||
<Properties>
|
||||
<Property name="closable" type="boolean" value="true"/>
|
||||
<Property name="iconifiable" type="boolean" value="true"/>
|
||||
<Property name="resizable" type="boolean" value="true"/>
|
||||
<Property name="title" type="java.lang.String" value="Card hints helper"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[200, 100]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[400, 300]"/>
|
||||
</Property>
|
||||
<Property name="titelBarToolTip" type="java.lang.String" value="<Not Set>"/>
|
||||
</Properties>
|
||||
<SyntheticProperties>
|
||||
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
|
||||
</SyntheticProperties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,79,0,0,1,52"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JScrollPane" name="scrollView">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="null"/>
|
||||
</Property>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||
<BorderConstraints direction="Center"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="mage.client.components.ColorPane" name="hintsView">
|
||||
<Properties>
|
||||
<Property name="editable" type="boolean" value="false"/>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
|
||||
<EmptyBorder bottom="5" left="5" right="5" top="5"/>
|
||||
</Border>
|
||||
</Property>
|
||||
<Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
|
||||
<Font name="Arial" size="14" style="0"/>
|
||||
</Property>
|
||||
<Property name="text" type="java.lang.String" value="<html> test text"/>
|
||||
<Property name="focusable" type="boolean" value="false"/>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="commands">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.EmptyBorderInfo">
|
||||
<EmptyBorder bottom="5" left="5" right="5" top="5"/>
|
||||
</Border>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||
<BorderConstraints direction="North"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridLayout">
|
||||
<Property name="columns" type="int" value="3"/>
|
||||
<Property name="horizontalGap" type="int" value="5"/>
|
||||
<Property name="rows" type="int" value="1"/>
|
||||
<Property name="verticalGap" type="int" value="5"/>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JComboBox" name="comboFilterBy">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="4">
|
||||
<StringItem index="0" value="Item 1"/>
|
||||
<StringItem index="1" value="Item 2"/>
|
||||
<StringItem index="2" value="Item 3"/>
|
||||
<StringItem index="3" value="Item 4"/>
|
||||
</StringArray>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="comboGroupBy">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="4">
|
||||
<StringItem index="0" value="Item 1"/>
|
||||
<StringItem index="1" value="Item 2"/>
|
||||
<StringItem index="2" value="Item 3"/>
|
||||
<StringItem index="3" value="Item 4"/>
|
||||
</StringArray>
|
||||
</Property>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="searchPanel">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTextField" name="search">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="search"/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="focusGained" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="searchFocusGained"/>
|
||||
</Events>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||
<BorderConstraints direction="Center"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="searchClear">
|
||||
<Properties>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/buttons/sideboard_out.png"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" value="Clear search field"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[23, 23]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchClearActionPerformed"/>
|
||||
</Events>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout" value="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout$BorderConstraintsDescription">
|
||||
<BorderConstraints direction="East"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
|
|
@ -0,0 +1,675 @@
|
|||
package mage.client.dialog;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import mage.abilities.hint.HintUtils;
|
||||
import mage.client.cards.BigCard;
|
||||
import mage.client.chat.ChatPanelBasic;
|
||||
import mage.client.components.MageDesktopIconifySupport;
|
||||
import mage.client.util.GUISizeHelper;
|
||||
import mage.client.util.SettingsManager;
|
||||
import mage.client.util.gui.GuiDisplayUtil;
|
||||
import mage.util.GameLog;
|
||||
import mage.util.RandomUtil;
|
||||
import mage.view.CardView;
|
||||
import mage.view.EmblemView;
|
||||
import mage.view.GameView;
|
||||
import mage.view.PlayerView;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.mage.card.arcane.ManaSymbols;
|
||||
import org.mage.plugins.card.utils.impl.ImageManagerImpl;
|
||||
|
||||
/**
|
||||
* Game GUI: collect card hints from all visible game's cards
|
||||
*
|
||||
* @author JayDi85
|
||||
*/
|
||||
public class CardHintsHelperDialog extends MageDialog implements MageDesktopIconifySupport {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(CardHintsHelperDialog.class);
|
||||
|
||||
public static final int GUI_MAX_CARD_HINTS_DIALOGS_PER_GAME = 5; // warning, GUI has a bad performance on too much windows render
|
||||
|
||||
private static final String WINDOW_TITLE = "Card hints helper";
|
||||
private static final String FILTER_ALL = "ALL";
|
||||
private static final String SEARCH_EMPTY_TEXT = "search";
|
||||
private static final String SEARCH_TOOLTIP = "Filter hints by any words in player, card, id or hint";
|
||||
private static final String EMPTY_HINTS_WARNING = "<br>Game not started or nothing to show";
|
||||
|
||||
private static final int MAX_CARD_IDS_PER_HINT = 3; // for group by hints
|
||||
|
||||
private boolean positioned;
|
||||
|
||||
private GameView lastGameView = null;
|
||||
private final List<CardHintInfo> lastHints = new ArrayList<>();
|
||||
|
||||
private String currentFilter = FILTER_ALL;
|
||||
private CardHintGroupBy currentGroup = CardHintGroupBy.GROUP_BY_HINTS;
|
||||
private String currentSearch = "";
|
||||
|
||||
private static class CardHintInfo {
|
||||
final PlayerView player;
|
||||
final String zone;
|
||||
final CardView card;
|
||||
final List<String> hints;
|
||||
String searchBase;
|
||||
String searchHints;
|
||||
|
||||
public CardHintInfo(PlayerView player, String zone, CardView card) {
|
||||
this.player = player; // can be null for watcher player
|
||||
this.zone = zone;
|
||||
this.card = card;
|
||||
this.hints = new ArrayList<>();
|
||||
|
||||
// additional data
|
||||
if (this.card != null) {
|
||||
List<String> newHints = parseCardHints(this.card.getRules());
|
||||
if (newHints != null) {
|
||||
this.hints.addAll(newHints);
|
||||
}
|
||||
}
|
||||
this.searchBase = (player == null ? "" : player.getName())
|
||||
+ "@" + zone
|
||||
+ "@" + (card == null ? "" : card.getIdName());
|
||||
this.searchHints = String.join("@", this.hints);
|
||||
|
||||
// searching by lower case
|
||||
this.searchBase = this.searchBase.toLowerCase(Locale.ENGLISH);
|
||||
this.searchHints = this.searchHints.toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
public boolean containsText(List<String> searches) {
|
||||
return searches.stream().anyMatch(s -> this.searchBase.contains(s) || this.searchHints.contains(s));
|
||||
}
|
||||
}
|
||||
|
||||
private enum CardHintGroupBy {
|
||||
GROUP_BY_HINTS("Hints"),
|
||||
GROUP_BY_CARDS("Cards");
|
||||
|
||||
private final String name;
|
||||
|
||||
CardHintGroupBy(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
public CardHintsHelperDialog() {
|
||||
this.positioned = false;
|
||||
initComponents();
|
||||
|
||||
// TODO: there are draw artifacts at the last symbol like selection
|
||||
this.hintsView.enableHyperlinksAndCardPopups(); // enable cards popup
|
||||
Color backColor = Color.gray;
|
||||
this.setOpaque(true);
|
||||
this.setBackground(backColor);
|
||||
this.hintsView.setExtBackgroundColor(backColor);
|
||||
this.hintsView.setSelectionColor(Color.gray);
|
||||
this.scrollView.setOpaque(true);
|
||||
this.scrollView.setBackground(backColor);
|
||||
this.scrollView.setViewportBorder(null);
|
||||
this.scrollView.getViewport().setOpaque(true);
|
||||
this.scrollView.getViewport().setBackground(backColor);
|
||||
|
||||
this.setModal(false);
|
||||
|
||||
this.setFrameIcon(new ImageIcon(ImageManagerImpl.instance.getLookedAtImage()));
|
||||
this.setClosable(true);
|
||||
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
|
||||
// incremental search support
|
||||
this.search.getDocument().addDocumentListener(new DocumentListener() {
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
onSearchStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
onSearchStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
onSearchStart();
|
||||
}
|
||||
});
|
||||
|
||||
updateTitle();
|
||||
changeGUISize();
|
||||
}
|
||||
|
||||
private void updateTitle() {
|
||||
// dynamic title
|
||||
List<String> settings = new ArrayList<>();
|
||||
|
||||
// from filter
|
||||
if (!Objects.equals(this.currentFilter, FILTER_ALL)) {
|
||||
settings.add(this.currentFilter);
|
||||
}
|
||||
|
||||
// from group
|
||||
settings.add(this.currentGroup.toString());
|
||||
|
||||
// from search
|
||||
if (this.currentSearch.length() > 0 && !this.currentSearch.equals(SEARCH_EMPTY_TEXT)) {
|
||||
settings.add(this.currentSearch);
|
||||
}
|
||||
|
||||
String newTitle = String.format("%s [%s]", WINDOW_TITLE, String.join(", ", settings));
|
||||
this.setTitle(newTitle);
|
||||
this.setTitelBarToolTip(newTitle);
|
||||
}
|
||||
|
||||
public void cleanUp() {
|
||||
// clean inner objects/components here
|
||||
this.lastGameView = null;
|
||||
this.lastHints.clear();
|
||||
}
|
||||
|
||||
public void setGameData(GameView gameView, UUID gameId, BigCard bigCard) {
|
||||
// one time init on open window
|
||||
this.hintsView.setGameData(gameId, bigCard);
|
||||
|
||||
// prepare gui filter
|
||||
List<String> filters = new ArrayList<>();
|
||||
filters.add(FILTER_ALL);
|
||||
// me
|
||||
gameView.getPlayers()
|
||||
.stream()
|
||||
.filter(PlayerView::getControlled)
|
||||
.map(PlayerView::getName)
|
||||
.findFirst()
|
||||
.ifPresent(filters::add);
|
||||
// other players
|
||||
filters.addAll(gameView.getPlayers()
|
||||
.stream()
|
||||
.filter(p -> !p.getControlled())
|
||||
.map(PlayerView::getName)
|
||||
.collect(Collectors.toList()));
|
||||
this.comboFilterBy.setModel(new DefaultComboBoxModel<>(filters.toArray(new String[0])));
|
||||
this.comboFilterBy.addActionListener(evt -> {
|
||||
this.currentFilter = (String) this.comboFilterBy.getSelectedItem();
|
||||
updateHints();
|
||||
});
|
||||
|
||||
// prepare gui group
|
||||
this.comboGroupBy.setModel(new DefaultComboBoxModel(CardHintGroupBy.values()));
|
||||
this.comboGroupBy.addActionListener(evt -> {
|
||||
this.currentGroup = (CardHintGroupBy) this.comboGroupBy.getSelectedItem();
|
||||
updateHints();
|
||||
});
|
||||
}
|
||||
|
||||
public void loadHints(GameView newGameView) {
|
||||
if (newGameView == null) {
|
||||
return;
|
||||
}
|
||||
if (this.lastGameView != newGameView) {
|
||||
this.lastGameView = newGameView;
|
||||
}
|
||||
|
||||
// collect full hints data
|
||||
this.lastHints.clear();
|
||||
|
||||
// player can be null on watcher
|
||||
PlayerView currentPlayer = this.lastGameView.getPlayers()
|
||||
.stream()
|
||||
.filter(PlayerView::getControlled)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
// hand
|
||||
this.lastGameView.getHand().values().forEach(card -> {
|
||||
this.lastHints.add(new CardHintInfo(currentPlayer, "hand", card));
|
||||
});
|
||||
|
||||
// stack
|
||||
this.lastGameView.getStack().values().forEach(card -> {
|
||||
this.lastHints.add(new CardHintInfo(currentPlayer, "stack", card));
|
||||
});
|
||||
|
||||
// TODO: add support of revealed, view, player-top and other non CardView?
|
||||
|
||||
// per player
|
||||
for (PlayerView player : this.lastGameView.getPlayers()) {
|
||||
// battlefield
|
||||
player.getBattlefield().values().forEach(card -> {
|
||||
this.lastHints.add(new CardHintInfo(player, "battlefield", card));
|
||||
});
|
||||
|
||||
// commander
|
||||
player.getCommandObjectList().forEach(object -> {
|
||||
// TODO: add support of emblem, dungeon and other non CardView?
|
||||
if (object instanceof CardView) {
|
||||
this.lastHints.add(new CardHintInfo(player, "command", (CardView) object));
|
||||
} else if (object instanceof EmblemView) {
|
||||
//((EmblemView) object).getRules()
|
||||
}
|
||||
});
|
||||
|
||||
// graveyard
|
||||
player.getGraveyard().values().forEach(card -> {
|
||||
this.lastHints.add(new CardHintInfo(player, "graveyard", card));
|
||||
});
|
||||
|
||||
// exile
|
||||
player.getExile().values().forEach(card -> {
|
||||
this.lastHints.add(new CardHintInfo(player, "exile", card));
|
||||
});
|
||||
}
|
||||
|
||||
// keep cards with hints only
|
||||
this.lastHints.removeIf(info -> !info.card.getRules().contains(HintUtils.HINT_START_MARK));
|
||||
|
||||
updateHints();
|
||||
}
|
||||
|
||||
public void updateHints() {
|
||||
// use already prepared data from loadHints
|
||||
|
||||
List<CardHintInfo> filteredCards = new ArrayList<>(this.lastHints);
|
||||
|
||||
// apply player filter
|
||||
if (!this.currentFilter.equals(FILTER_ALL)) {
|
||||
filteredCards.removeIf(info -> info.player == null || !info.player.getName().equals(this.currentFilter));
|
||||
}
|
||||
|
||||
// apply search filter
|
||||
if (!this.currentSearch.isEmpty()) {
|
||||
List<String> needSearches = new ArrayList<>(Arrays.asList(this.currentSearch.toLowerCase(Locale.ENGLISH).trim().split(" ")));
|
||||
filteredCards.removeIf(info -> !(info.containsText(needSearches)));
|
||||
}
|
||||
|
||||
// apply group type
|
||||
List<String> renderData = new ArrayList<>();
|
||||
switch (this.currentGroup) {
|
||||
// data must be sorted and grouped already in prepared code
|
||||
|
||||
default:
|
||||
case GROUP_BY_HINTS: {
|
||||
// player -> hint + cards
|
||||
Map<String, List<CardHintInfo>> groupsByZone = prepareGroupByPlayerAndZones(filteredCards, true, false);
|
||||
groupsByZone.forEach((group, groupCards) -> {
|
||||
if (groupCards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!renderData.isEmpty()) {
|
||||
renderData.add("<br>");
|
||||
}
|
||||
|
||||
// header: player
|
||||
CardHintInfo sampleData = groupCards.get(0);
|
||||
renderData.add(String.format("<b>%s</b>:",
|
||||
GameLog.getColoredPlayerName(sampleData.player == null ? "watcher" : sampleData.player.getName())
|
||||
));
|
||||
|
||||
// data: unique hints
|
||||
Map<String, List<CardHintInfo>> groupByHints = prepareGroupByHints(groupCards);
|
||||
|
||||
groupByHints.forEach((groupByHint, groupByHintCards) -> {
|
||||
if (groupByHintCards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// hint
|
||||
String renderLine = groupByHint;
|
||||
|
||||
// data: cards list like [123], [456], [789] and 2 other
|
||||
String cardNames = groupByHintCards
|
||||
.stream()
|
||||
.limit(MAX_CARD_IDS_PER_HINT)
|
||||
.map(info -> {
|
||||
// workable card hints
|
||||
return GameLog.getColoredObjectIdName(
|
||||
info.card.getColor(),
|
||||
info.card.getId(),
|
||||
String.format("[%s]", info.card.getId().toString().substring(0, 3)),
|
||||
"",
|
||||
info.card.getName()
|
||||
);
|
||||
})
|
||||
.collect(Collectors.joining(", "));
|
||||
if (groupByHintCards.size() > MAX_CARD_IDS_PER_HINT) {
|
||||
cardNames += String.format(" and %d other", groupByHintCards.size() - MAX_CARD_IDS_PER_HINT);
|
||||
}
|
||||
renderLine += " (" + cardNames + ")";
|
||||
|
||||
renderData.add(renderLine);
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case GROUP_BY_CARDS: {
|
||||
// player + zone -> card + hint
|
||||
Map<String, List<CardHintInfo>> groupsByZone = prepareGroupByPlayerAndZones(filteredCards, true, true);
|
||||
groupsByZone.forEach((group, groupCards) -> {
|
||||
if (groupCards.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (!renderData.isEmpty()) {
|
||||
renderData.add("<br>");
|
||||
}
|
||||
|
||||
// header: player/zone
|
||||
CardHintInfo sampleData = groupCards.get(0);
|
||||
renderData.add(String.format("<b>%s - %s</b>:",
|
||||
GameLog.getColoredPlayerName(sampleData.player == null ? "watcher" : sampleData.player.getName()),
|
||||
sampleData.zone
|
||||
));
|
||||
|
||||
// data: cards list
|
||||
groupCards.forEach(info -> {
|
||||
List<String> hints = parseCardHints(info.card.getRules());
|
||||
if (hints != null) {
|
||||
String cardName = GameLog.getColoredObjectIdName(
|
||||
info.card.getColor(),
|
||||
info.card.getId(),
|
||||
info.card.getName(),
|
||||
String.format("[%s]", info.card.getId().toString().substring(0, 3)),
|
||||
null
|
||||
);
|
||||
renderData.addAll(hints.stream().map(s -> cardName + ": " + s).collect(Collectors.toList()));
|
||||
}
|
||||
});
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// empty
|
||||
if (renderData.isEmpty()) {
|
||||
renderData.add(EMPTY_HINTS_WARNING);
|
||||
}
|
||||
|
||||
// final render
|
||||
String renderFinal = String.join("<br>", renderData);
|
||||
// keep scrolls position between updates
|
||||
int oldPos = this.scrollView.getVerticalScrollBar().getValue();
|
||||
this.hintsView.setText("<html>" + ManaSymbols.replaceSymbolsWithHTML(renderFinal, ManaSymbols.Type.CHAT));
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
this.scrollView.getVerticalScrollBar().setValue(oldPos);
|
||||
});
|
||||
|
||||
updateTitle();
|
||||
showAndPositionWindow();
|
||||
}
|
||||
|
||||
private Map<String, List<CardHintInfo>> prepareGroupByPlayerAndZones(
|
||||
List<CardHintInfo> currentHints,
|
||||
boolean groupByPlayer,
|
||||
boolean groupByZone
|
||||
) {
|
||||
if (!groupByPlayer && !groupByZone) {
|
||||
throw new IllegalArgumentException("Wrong code usage: must use minimum one group param");
|
||||
}
|
||||
|
||||
Map<String, List<CardHintInfo>> res = new LinkedHashMap<>();
|
||||
if (currentHints.isEmpty()) {
|
||||
return res;
|
||||
}
|
||||
|
||||
String lastGroupName = null;
|
||||
List<CardHintInfo> lastGroupCards = null;
|
||||
for (CardHintInfo info : currentHints) {
|
||||
String currentGroupName = "";
|
||||
if (groupByPlayer) {
|
||||
currentGroupName += "@" + (info.player == null ? "null" : info.player.getName());
|
||||
}
|
||||
if (groupByZone) {
|
||||
currentGroupName += "@" + info.zone;
|
||||
}
|
||||
|
||||
if (lastGroupCards == null || !lastGroupName.equals(currentGroupName)) {
|
||||
lastGroupName = currentGroupName;
|
||||
lastGroupCards = new ArrayList<>();
|
||||
res.put(currentGroupName, lastGroupCards);
|
||||
}
|
||||
lastGroupCards.add(info);
|
||||
}
|
||||
|
||||
// sort cards by card name inside
|
||||
res.forEach((zone, infos) -> {
|
||||
infos.sort(Comparator.comparing(o -> o.card.getName()));
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private Map<String, List<CardHintInfo>> prepareGroupByHints(List<CardHintInfo> currentHints) {
|
||||
Map<String, List<CardHintInfo>> res = new LinkedHashMap<>();
|
||||
if (currentHints.isEmpty()) {
|
||||
return res;
|
||||
}
|
||||
|
||||
// unique and sorted hints
|
||||
List<String> allPossibleHints = currentHints
|
||||
.stream()
|
||||
.map(info -> parseCardHints(info.card.getRules()))
|
||||
.filter(Objects::nonNull)
|
||||
.flatMap(Collection::stream)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
allPossibleHints.forEach(currentHintName -> {
|
||||
List<CardHintInfo> cards = res.getOrDefault(currentHintName, null);
|
||||
if (cards == null) {
|
||||
cards = new ArrayList<>();
|
||||
res.put(currentHintName, cards);
|
||||
}
|
||||
|
||||
for (CardHintInfo info : currentHints) {
|
||||
if (info.card.getRules().contains(currentHintName)) {
|
||||
cards.add(info);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// sort by card id (cause it show id instead name)
|
||||
res.forEach((hint, cards) -> {
|
||||
cards.sort(Comparator.comparing(o -> o.card.getId()));
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static List<String> parseCardHints(List<String> rules) {
|
||||
if (rules.isEmpty() || !rules.contains(HintUtils.HINT_START_MARK)) {
|
||||
return null;
|
||||
}
|
||||
List<String> hints = new ArrayList<>();
|
||||
boolean started = false;
|
||||
for (String rule : rules) {
|
||||
if (rule.equals(HintUtils.HINT_START_MARK)) {
|
||||
started = true;
|
||||
continue;
|
||||
}
|
||||
if (started) {
|
||||
hints.add(rule);
|
||||
}
|
||||
}
|
||||
return hints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeGUISize() {
|
||||
setGUISize(GUISizeHelper.chatFont);
|
||||
this.validate();
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
private void setGUISize(Font font) {
|
||||
this.hintsView.setFont(font);
|
||||
this.scrollView.setFont(font);
|
||||
this.scrollView.getVerticalScrollBar().setPreferredSize(new Dimension(GUISizeHelper.scrollBarSize, 0));
|
||||
this.scrollView.getHorizontalScrollBar().setPreferredSize(new Dimension(0, GUISizeHelper.scrollBarSize));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
// auto-position on first usage
|
||||
if (positioned) {
|
||||
showAndPositionWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void showAndPositionWindow() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
int width = CardHintsHelperDialog.this.getWidth();
|
||||
int height = CardHintsHelperDialog.this.getHeight();
|
||||
if (width > 0 && height > 0) {
|
||||
Point centered = SettingsManager.instance.getComponentPosition(width, height);
|
||||
if (!positioned) {
|
||||
// starting position
|
||||
// little randomize to see multiple opened windows
|
||||
int xPos = centered.x / 2 + RandomUtil.nextInt(50);
|
||||
int yPos = centered.y / 2 + RandomUtil.nextInt(50);
|
||||
CardHintsHelperDialog.this.setLocation(xPos, yPos);
|
||||
show();
|
||||
positioned = true;
|
||||
}
|
||||
GuiDisplayUtil.keepComponentInsideFrame(centered.x, centered.y, CardHintsHelperDialog.this);
|
||||
}
|
||||
|
||||
// workaround to fix draw artifacts
|
||||
//CardHintsHelperDialog.this.validate();
|
||||
//CardHintsHelperDialog.this.repaint();
|
||||
});
|
||||
}
|
||||
|
||||
private void onSearchFocused() {
|
||||
// fast select
|
||||
if (SEARCH_EMPTY_TEXT.equals(search.getText())) {
|
||||
search.setText("");
|
||||
} else if (search.getText().length() > 0) {
|
||||
search.selectAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void onSearchStart() {
|
||||
String newSearch = SEARCH_EMPTY_TEXT.equals(search.getText()) ? "" : search.getText();
|
||||
if (!this.currentSearch.equals(newSearch)) {
|
||||
this.currentSearch = newSearch;
|
||||
updateHints();
|
||||
}
|
||||
}
|
||||
|
||||
private void onSearchClear() {
|
||||
this.search.setText(SEARCH_EMPTY_TEXT);
|
||||
this.currentSearch = "";
|
||||
updateHints();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
scrollView = new javax.swing.JScrollPane();
|
||||
hintsView = new mage.client.components.ColorPane();
|
||||
commands = new javax.swing.JPanel();
|
||||
comboFilterBy = new javax.swing.JComboBox<>();
|
||||
comboGroupBy = new javax.swing.JComboBox<>();
|
||||
searchPanel = new javax.swing.JPanel();
|
||||
search = new javax.swing.JTextField();
|
||||
searchClear = new javax.swing.JButton();
|
||||
|
||||
setClosable(true);
|
||||
setIconifiable(true);
|
||||
setResizable(true);
|
||||
setTitle("Card hints helper");
|
||||
setMinimumSize(new java.awt.Dimension(200, 100));
|
||||
setPreferredSize(new java.awt.Dimension(400, 300));
|
||||
setTitelBarToolTip("<Not Set>");
|
||||
getContentPane().setLayout(new java.awt.BorderLayout());
|
||||
|
||||
scrollView.setBorder(null);
|
||||
scrollView.setOpaque(true);
|
||||
|
||||
hintsView.setEditable(false);
|
||||
hintsView.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
hintsView.setFont(new java.awt.Font("Arial", 0, 14)); // NOI18N
|
||||
hintsView.setText("<html> test text");
|
||||
hintsView.setFocusable(false);
|
||||
hintsView.setOpaque(true);
|
||||
scrollView.setViewportView(hintsView);
|
||||
|
||||
getContentPane().add(scrollView, java.awt.BorderLayout.CENTER);
|
||||
|
||||
commands.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
commands.setLayout(new java.awt.GridLayout(1, 3, 5, 5));
|
||||
|
||||
comboFilterBy.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
|
||||
commands.add(comboFilterBy);
|
||||
|
||||
comboGroupBy.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
|
||||
commands.add(comboGroupBy);
|
||||
|
||||
searchPanel.setLayout(new java.awt.BorderLayout());
|
||||
|
||||
search.setText(SEARCH_EMPTY_TEXT);
|
||||
search.setToolTipText(SEARCH_TOOLTIP);
|
||||
search.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusGained(java.awt.event.FocusEvent evt) {
|
||||
searchFocusGained(evt);
|
||||
}
|
||||
});
|
||||
searchPanel.add(search, java.awt.BorderLayout.CENTER);
|
||||
|
||||
searchClear.setIcon(new javax.swing.ImageIcon(getClass().getResource("/buttons/sideboard_out.png"))); // NOI18N
|
||||
searchClear.setToolTipText("Clear search field");
|
||||
searchClear.setPreferredSize(new java.awt.Dimension(23, 23));
|
||||
searchClear.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
searchClearActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
searchPanel.add(searchClear, java.awt.BorderLayout.EAST);
|
||||
|
||||
commands.add(searchPanel);
|
||||
|
||||
getContentPane().add(commands, java.awt.BorderLayout.NORTH);
|
||||
|
||||
pack();
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void searchFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_searchFocusGained
|
||||
onSearchFocused();
|
||||
}//GEN-LAST:event_searchFocusGained
|
||||
|
||||
private void searchClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchClearActionPerformed
|
||||
onSearchClear();
|
||||
}//GEN-LAST:event_searchClearActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JComboBox<String> comboFilterBy;
|
||||
private javax.swing.JComboBox<String> comboGroupBy;
|
||||
private javax.swing.JPanel commands;
|
||||
private mage.client.components.ColorPane hintsView;
|
||||
private javax.swing.JScrollPane scrollView;
|
||||
private javax.swing.JTextField search;
|
||||
private javax.swing.JButton searchClear;
|
||||
private javax.swing.JPanel searchPanel;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
}
|
||||
|
|
@ -12,11 +12,13 @@ import javax.swing.plaf.basic.BasicInternalFrameUI;
|
|||
|
||||
import mage.cards.MageCard;
|
||||
import mage.client.cards.BigCard;
|
||||
import mage.client.components.MageDesktopIconifySupport;
|
||||
import mage.client.util.GUISizeHelper;
|
||||
import mage.client.util.ImageHelper;
|
||||
import mage.client.util.SettingsManager;
|
||||
import mage.client.util.gui.GuiDisplayUtil;
|
||||
import mage.constants.CardType;
|
||||
import mage.util.RandomUtil;
|
||||
import mage.view.CardView;
|
||||
import mage.view.CardsView;
|
||||
import mage.view.ExileView;
|
||||
|
|
@ -29,7 +31,7 @@ import org.mage.plugins.card.utils.impl.ImageManagerImpl;
|
|||
*
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
public class CardInfoWindowDialog extends MageDialog {
|
||||
public class CardInfoWindowDialog extends MageDialog implements MageDesktopIconifySupport {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(CardInfoWindowDialog.class);
|
||||
|
||||
|
|
@ -48,22 +50,6 @@ public class CardInfoWindowDialog extends MageDialog {
|
|||
this.positioned = false;
|
||||
initComponents();
|
||||
|
||||
// ENABLE a minimizing window on double clicks
|
||||
BasicInternalFrameUI ui = (BasicInternalFrameUI) this.getUI();
|
||||
ui.getNorthPane().addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
if ((e.getClickCount() & 1) == 0 && (e.getClickCount() > 0) && !e.isConsumed()) { // double clicks and repeated double clicks
|
||||
e.consume();
|
||||
try {
|
||||
CardInfoWindowDialog.this.setIcon(!CardInfoWindowDialog.this.isIcon());
|
||||
} catch (PropertyVetoException exp) {
|
||||
// ignore read only
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.setModal(false);
|
||||
switch (this.showType) {
|
||||
case LOOKED_AT:
|
||||
|
|
@ -192,6 +178,7 @@ public class CardInfoWindowDialog extends MageDialog {
|
|||
|
||||
@Override
|
||||
public void show() {
|
||||
// hide empty exile windows
|
||||
if (showType == ShowType.EXILE) {
|
||||
if (cards == null || cards.getNumberOfCards() == 0) {
|
||||
return;
|
||||
|
|
@ -199,7 +186,9 @@ public class CardInfoWindowDialog extends MageDialog {
|
|||
}
|
||||
|
||||
super.show();
|
||||
if (positioned) { // check if in frame rectangle
|
||||
|
||||
// auto-position on first usage
|
||||
if (positioned) {
|
||||
showAndPositionWindow();
|
||||
}
|
||||
}
|
||||
|
|
@ -211,8 +200,10 @@ public class CardInfoWindowDialog extends MageDialog {
|
|||
if (width > 0 && height > 0) {
|
||||
Point centered = SettingsManager.instance.getComponentPosition(width, height);
|
||||
if (!positioned) {
|
||||
int xPos = centered.x / 2;
|
||||
int yPos = centered.y / 2;
|
||||
// starting position
|
||||
// little randomize to see multiple opened windows
|
||||
int xPos = centered.x / 2 + RandomUtil.nextInt(50);
|
||||
int yPos = centered.y / 2 + RandomUtil.nextInt(50);
|
||||
CardInfoWindowDialog.this.setLocation(xPos, yPos);
|
||||
show();
|
||||
positioned = true;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
package mage.client.dialog;
|
||||
|
||||
import mage.client.MageFrame;
|
||||
import mage.client.components.MageDesktopIconifySupport;
|
||||
import mage.client.util.SettingsManager;
|
||||
import mage.client.util.gui.GuiDisplayUtil;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.plaf.basic.BasicInternalFrameUI;
|
||||
import java.awt.*;
|
||||
import java.awt.event.InvocationEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
|
@ -26,6 +29,25 @@ public class MageDialog extends javax.swing.JInternalFrame {
|
|||
*/
|
||||
public MageDialog() {
|
||||
initComponents();
|
||||
|
||||
// enable a minimizing window on double clicks
|
||||
if (this instanceof MageDesktopIconifySupport) {
|
||||
BasicInternalFrameUI ui = (BasicInternalFrameUI) this.getUI();
|
||||
ui.getNorthPane().addMouseListener(new MouseAdapter() {
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
// double clicks and repeated double clicks
|
||||
if ((e.getClickCount() & 1) == 0 && (e.getClickCount() > 0) && !e.isConsumed()) {
|
||||
e.consume();
|
||||
try {
|
||||
MageDialog.this.setIcon(!MageDialog.this.isIcon());
|
||||
} catch (PropertyVetoException exp) {
|
||||
// ignore read only
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void changeGUISize() {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import mage.client.util.SettingsManager;
|
||||
import mage.client.util.gui.GuiDisplayUtil;
|
||||
import mage.game.events.PlayerQueryEvent.QueryType;
|
||||
import mage.util.RandomUtil;
|
||||
import mage.view.CardsView;
|
||||
import org.mage.card.arcane.CardPanel;
|
||||
|
||||
|
|
@ -38,7 +39,6 @@
|
|||
initComponents();
|
||||
|
||||
this.setModal(false);
|
||||
|
||||
}
|
||||
|
||||
public void cleanUp() {
|
||||
|
|
@ -58,9 +58,40 @@
|
|||
}
|
||||
|
||||
private void setGUISize() {
|
||||
|
||||
// nothing to change (all components in cardArea)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
|
||||
// auto-position on first usage
|
||||
if (positioned) {
|
||||
showAndPositionWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void showAndPositionWindow() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
int width = ShowCardsDialog.this.getWidth();
|
||||
int height = ShowCardsDialog.this.getHeight();
|
||||
if (width > 0 && height > 0) {
|
||||
Point centered = SettingsManager.instance.getComponentPosition(width, height);
|
||||
if (!positioned) {
|
||||
// starting position
|
||||
// little randomize to see multiple opened windows
|
||||
int xPos = centered.x / 2 + RandomUtil.nextInt(50);
|
||||
int yPos = centered.y / 2 + RandomUtil.nextInt(50);
|
||||
ShowCardsDialog.this.setLocation(xPos, yPos);
|
||||
show();
|
||||
positioned = true;
|
||||
}
|
||||
GuiDisplayUtil.keepComponentInsideFrame(centered.x, centered.y, ShowCardsDialog.this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void loadCards(String name, CardsView showCards, BigCard bigCard,
|
||||
UUID gameId, boolean modal, Map<String, Serializable> options,
|
||||
JPopupMenu popupMenu, Listener<Event> eventListener) {
|
||||
|
|
@ -101,20 +132,6 @@
|
|||
} else {
|
||||
MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER);
|
||||
}
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (!positioned) {
|
||||
int width = ShowCardsDialog.this.getWidth();
|
||||
int height = ShowCardsDialog.this.getHeight();
|
||||
if (width > 0 && height > 0) {
|
||||
Point centered = SettingsManager.instance.getComponentPosition(width, height);
|
||||
ShowCardsDialog.this.setLocation(centered.x, centered.y);
|
||||
positioned = true;
|
||||
GuiDisplayUtil.keepComponentInsideScreen(centered.x, centered.y, ShowCardsDialog.this);
|
||||
}
|
||||
}
|
||||
ShowCardsDialog.this.setVisible(true);
|
||||
});
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
private final Map<String, CardInfoWindowDialog> sideboardWindows = new HashMap<>();
|
||||
private final ArrayList<ShowCardsDialog> pickTarget = new ArrayList<>();
|
||||
private final ArrayList<PickPileDialog> pickPile = new ArrayList<>();
|
||||
private final Map<String, CardHintsHelperDialog> cardHintsWindows = new LinkedHashMap<>();
|
||||
|
||||
private UUID gameId;
|
||||
private UUID playerId; // playerId of the player
|
||||
GamePane gamePane;
|
||||
|
|
@ -275,6 +277,10 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
windowDialog.cleanUp();
|
||||
windowDialog.removeDialog();
|
||||
}
|
||||
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
|
||||
windowDialog.cleanUp();
|
||||
windowDialog.removeDialog();
|
||||
}
|
||||
|
||||
clearPickDialogs();
|
||||
|
||||
|
|
@ -350,6 +356,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
||||
windowDialog.changeGUISize();
|
||||
}
|
||||
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
|
||||
windowDialog.changeGUISize();
|
||||
}
|
||||
for (ShowCardsDialog windowDialog : pickTarget) {
|
||||
windowDialog.changeGUISize();
|
||||
}
|
||||
|
|
@ -886,16 +895,25 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
GameManager.instance.setStackSize(lastGameData.game.getStack().size());
|
||||
displayStack(lastGameData.game, bigCard, feedbackPanel, gameId);
|
||||
|
||||
// auto-show exile views
|
||||
for (ExileView exile : lastGameData.game.getExile()) {
|
||||
if (!exiles.containsKey(exile.getId())) {
|
||||
CardInfoWindowDialog newExile = new CardInfoWindowDialog(ShowType.EXILE, exile.getName());
|
||||
exiles.put(exile.getId(), newExile);
|
||||
MageFrame.getDesktop().add(newExile, JLayeredPane.PALETTE_LAYER);
|
||||
newExile.show();
|
||||
CardInfoWindowDialog exileWindow = exiles.getOrDefault(exile.getId(), null);
|
||||
if (exileWindow == null) {
|
||||
exileWindow = new CardInfoWindowDialog(ShowType.EXILE, exile.getName());
|
||||
exiles.put(exile.getId(), exileWindow);
|
||||
MageFrame.getDesktop().add(exileWindow, JLayeredPane.PALETTE_LAYER);
|
||||
exileWindow.show();
|
||||
}
|
||||
exiles.get(exile.getId()).loadCards(exile, bigCard, gameId);
|
||||
exileWindow.loadCards(exile, bigCard, gameId);
|
||||
}
|
||||
|
||||
// update open or remove closed card hints windows
|
||||
clearClosedCardHintsWindows();
|
||||
cardHintsWindows.forEach((s, windowDialog) -> {
|
||||
// TODO: optimize for multiple windows (prepare data here and send it for filters/groups)
|
||||
windowDialog.loadHints(lastGameData.game);
|
||||
});
|
||||
|
||||
// reveal and look at dialogs can unattached, so windows opened by game doesn't have it
|
||||
showRevealed(lastGameData.game);
|
||||
showLookedAt(lastGameData.game);
|
||||
|
|
@ -1197,24 +1215,28 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
// Called if the game frame is deactivated because the tabled the deck editor or other frames go to foreground
|
||||
public void deactivated() {
|
||||
// hide the non modal windows (because otherwise they are shown on top of the new active pane)
|
||||
// TODO: is it need to hide other dialogs like graveyards (CardsView)?
|
||||
for (CardInfoWindowDialog windowDialog : exiles.values()) {
|
||||
windowDialog.hideDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
|
||||
windowDialog.hideDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : revealed.values()) {
|
||||
windowDialog.hideDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
|
||||
windowDialog.hideDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
|
||||
windowDialog.hideDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : companion.values()) {
|
||||
windowDialog.hideDialog();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
||||
windowDialog.hideDialog();
|
||||
}
|
||||
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
|
||||
windowDialog.hideDialog();
|
||||
}
|
||||
}
|
||||
|
||||
// Called if the game frame comes to front again
|
||||
|
|
@ -1223,21 +1245,24 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
for (CardInfoWindowDialog windowDialog : exiles.values()) {
|
||||
windowDialog.show();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
|
||||
windowDialog.show();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : revealed.values()) {
|
||||
windowDialog.show();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
|
||||
windowDialog.show();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
|
||||
windowDialog.show();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : companion.values()) {
|
||||
windowDialog.show();
|
||||
}
|
||||
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
||||
windowDialog.show();
|
||||
}
|
||||
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
|
||||
windowDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
public void openGraveyardWindow(String playerName) {
|
||||
|
|
@ -1257,6 +1282,29 @@ public final class GamePanel extends javax.swing.JPanel {
|
|||
newGraveyard.loadCards(graveyards.get(playerName), bigCard, gameId, false);
|
||||
}
|
||||
|
||||
private void clearClosedCardHintsWindows() {
|
||||
cardHintsWindows.entrySet().removeIf(entry -> entry.getValue().isClosed());
|
||||
}
|
||||
|
||||
public void openCardHintsWindow(String code) {
|
||||
// clear closed
|
||||
clearClosedCardHintsWindows();
|
||||
|
||||
// too many dialogs can cause bad GUI performance, so limit it
|
||||
if (cardHintsWindows.size() > CardHintsHelperDialog.GUI_MAX_CARD_HINTS_DIALOGS_PER_GAME) {
|
||||
// show last one instead
|
||||
cardHintsWindows.values().stream().reduce((a, b) -> b).ifPresent(CardHintsHelperDialog::show);
|
||||
return;
|
||||
}
|
||||
|
||||
// open new
|
||||
CardHintsHelperDialog newDialog = new CardHintsHelperDialog();
|
||||
newDialog.setGameData(this.lastGameData.game, this.gameId, this.bigCard);
|
||||
cardHintsWindows.put(code + UUID.randomUUID(), newDialog);
|
||||
MageFrame.getDesktop().add(newDialog, JLayeredPane.PALETTE_LAYER);
|
||||
newDialog.loadHints(lastGameData.game);
|
||||
}
|
||||
|
||||
public void openSideboardWindow(UUID playerId) {
|
||||
if (lastGameData == null) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
|||
private final JPopupMenu popupMenu;
|
||||
private UUID playerId;
|
||||
private UUID gameId;
|
||||
private boolean isMe = false;
|
||||
private boolean smallMode = false;
|
||||
private boolean playingMode = true;
|
||||
private final GamePanel gamePanel;
|
||||
|
|
@ -46,6 +47,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
|||
|
||||
public static final int PANEL_HEIGHT = 263;
|
||||
public static final int PANEL_HEIGHT_SMALL = 210;
|
||||
private static final int PANEL_HEIGHT_EXTRA_FOR_ME = 25;
|
||||
|
||||
/**
|
||||
* Creates new form PlayAreaPanel
|
||||
|
|
@ -515,6 +517,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
|||
this.battlefieldPanel.init(gameId, bigCard);
|
||||
this.gameId = gameId;
|
||||
this.playerId = player.getPlayerId();
|
||||
this.isMe = player.getControlled();
|
||||
this.btnCheat.setVisible(SessionHandler.isTestMode());
|
||||
}
|
||||
|
||||
|
|
@ -562,14 +565,15 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
|||
public void sizePlayer(boolean smallMode) {
|
||||
this.playerPanel.sizePlayerPanel(smallMode);
|
||||
this.smallMode = smallMode;
|
||||
int extraForMe = this.isMe ? PANEL_HEIGHT_EXTRA_FOR_ME : 0;
|
||||
if (smallMode) {
|
||||
this.playerPanel.setPreferredSize(new Dimension(92, PANEL_HEIGHT_SMALL));
|
||||
this.playerPanel.setPreferredSize(new Dimension(92, PANEL_HEIGHT_SMALL + extraForMe));
|
||||
//this.jScrollPane1.setPreferredSize(new Dimension(160, 160));
|
||||
this.battlefieldPanel.setPreferredSize(new Dimension(160, PANEL_HEIGHT_SMALL));
|
||||
this.battlefieldPanel.setPreferredSize(new Dimension(160, PANEL_HEIGHT_SMALL + extraForMe));
|
||||
} else {
|
||||
this.playerPanel.setPreferredSize(new Dimension(92, PANEL_HEIGHT));
|
||||
this.playerPanel.setPreferredSize(new Dimension(92, PANEL_HEIGHT + extraForMe));
|
||||
//this.jScrollPane1.setPreferredSize(new Dimension(160, 212));
|
||||
this.battlefieldPanel.setPreferredSize(new Dimension(160, PANEL_HEIGHT));
|
||||
this.battlefieldPanel.setPreferredSize(new Dimension(160, PANEL_HEIGHT + extraForMe));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
private UUID playerId;
|
||||
private UUID gameId;
|
||||
private PlayerView player;
|
||||
private boolean isMe;
|
||||
|
||||
private BigCard bigCard;
|
||||
|
||||
|
|
@ -53,11 +54,13 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
|
||||
private static final int PANEL_WIDTH = 94;
|
||||
private static final int PANEL_HEIGHT = 262;
|
||||
private static final int PANEL_HEIGHT_SMALL = 242;
|
||||
private static final int PANEL_HEIGHT_SMALL = 210;
|
||||
private static final int PANEL_HEIGHT_EXTRA_FOR_ME = 25;
|
||||
private static final int MANA_LABEL_SIZE_HORIZONTAL = 20;
|
||||
|
||||
private static final Border GREEN_BORDER = new LineBorder(Color.green, 3);
|
||||
private static final Border RED_BORDER = new LineBorder(Color.red, 2);
|
||||
private static final Border YELLOW_BORDER = new LineBorder(Color.yellow, 3);
|
||||
private static final Border EMPTY_BORDER = BorderFactory.createEmptyBorder(0, 0, 0, 0);
|
||||
private final Color inactiveBackgroundColor;
|
||||
private final Color activeBackgroundColor;
|
||||
|
|
@ -92,8 +95,10 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
this.gameId = gameId;
|
||||
this.playerId = playerId;
|
||||
this.bigCard = bigCard;
|
||||
cheat.setVisible(SessionHandler.isTestMode() && controlled);
|
||||
this.isMe = controlled;
|
||||
cheat.setVisible(SessionHandler.isTestMode() && this.isMe);
|
||||
cheat.setFocusable(false);
|
||||
toolHintsHelper.setVisible(this.isMe);
|
||||
flagName = null;
|
||||
if (priorityTime > 0) {
|
||||
long delay = 1000L;
|
||||
|
|
@ -355,6 +360,12 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
}
|
||||
}
|
||||
|
||||
// possible targeting
|
||||
if (possibleTargets != null && possibleTargets.contains(this.playerId)) {
|
||||
this.avatar.setBorder(YELLOW_BORDER);
|
||||
this.btnPlayer.setBorder(YELLOW_BORDER);
|
||||
}
|
||||
|
||||
update(player.getManaPool());
|
||||
}
|
||||
|
||||
|
|
@ -568,6 +579,13 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
cheat.setToolTipText("Cheat button");
|
||||
cheat.addActionListener(e -> btnCheatActionPerformed(e));
|
||||
|
||||
// Tools button
|
||||
r = new Rectangle(75, 21);
|
||||
toolHintsHelper = new JButton();
|
||||
toolHintsHelper.setText("hints");
|
||||
toolHintsHelper.setToolTipText("Open new card hints helper window");
|
||||
toolHintsHelper.addActionListener(e -> btnToolHintsHelperActionPerformed(e));
|
||||
|
||||
zonesPanel = new JPanel();
|
||||
zonesPanel.setPreferredSize(new Dimension(100, 60));
|
||||
zonesPanel.setSize(100, 60);
|
||||
|
|
@ -591,6 +609,9 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
cheat.setBounds(40, 2, 25, 21);
|
||||
zonesPanel.add(cheat);
|
||||
|
||||
toolHintsHelper.setBounds(3, 2 + 21 + 2, 75, 21);
|
||||
zonesPanel.add(toolHintsHelper);
|
||||
|
||||
energyExperiencePanel = new JPanel();
|
||||
energyExperiencePanel.setPreferredSize(new Dimension(100, 20));
|
||||
energyExperiencePanel.setSize(100, 20);
|
||||
|
|
@ -619,6 +640,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
btnPlayer.setText("Player");
|
||||
btnPlayer.setVisible(false);
|
||||
btnPlayer.setToolTipText("Player");
|
||||
btnPlayer.setPreferredSize(new Dimension(20, 40));
|
||||
btnPlayer.addActionListener(e -> SessionHandler.sendPlayerUUID(gameId, playerId));
|
||||
|
||||
// Add mana symbols
|
||||
|
|
@ -808,7 +830,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
.addGap(6)
|
||||
.addComponent(avatar, GroupLayout.PREFERRED_SIZE, 80, GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(ComponentPlacement.RELATED)
|
||||
.addComponent(btnPlayer)
|
||||
.addComponent(btnPlayer, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(timerLabel)
|
||||
.addGap(2)
|
||||
// Life & Hand
|
||||
|
|
@ -912,18 +934,19 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
protected void sizePlayerPanel(boolean smallMode) {
|
||||
int extraForMe = this.isMe ? PANEL_HEIGHT_EXTRA_FOR_ME : 0;
|
||||
if (smallMode) {
|
||||
avatar.setVisible(false);
|
||||
btnPlayer.setVisible(true);
|
||||
timerLabel.setVisible(true);
|
||||
panelBackground.setPreferredSize(new Dimension(PANEL_WIDTH - 2, PANEL_HEIGHT_SMALL));
|
||||
panelBackground.setBounds(0, 0, PANEL_WIDTH - 2, PANEL_HEIGHT_SMALL);
|
||||
panelBackground.setPreferredSize(new Dimension(PANEL_WIDTH - 2, PANEL_HEIGHT_SMALL + extraForMe));
|
||||
panelBackground.setBounds(0, 0, PANEL_WIDTH - 2, PANEL_HEIGHT_SMALL + extraForMe);
|
||||
} else {
|
||||
avatar.setVisible(true);
|
||||
btnPlayer.setVisible(false);
|
||||
timerLabel.setVisible(false);
|
||||
panelBackground.setPreferredSize(new Dimension(PANEL_WIDTH - 2, PANEL_HEIGHT));
|
||||
panelBackground.setBounds(0, 0, PANEL_WIDTH - 2, PANEL_HEIGHT);
|
||||
panelBackground.setPreferredSize(new Dimension(PANEL_WIDTH - 2, PANEL_HEIGHT + extraForMe));
|
||||
panelBackground.setBounds(0, 0, PANEL_WIDTH - 2, PANEL_HEIGHT + extraForMe);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -952,6 +975,10 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
SessionHandler.cheat(gameId, playerId, deckImporter.importDeck("cheat.dck", false));
|
||||
}
|
||||
|
||||
private void btnToolHintsHelperActionPerformed(java.awt.event.ActionEvent evt) {
|
||||
MageFrame.getGame(gameId).openCardHintsWindow("main");
|
||||
}
|
||||
|
||||
public PlayerView getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
|
@ -984,6 +1011,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
|||
private HoverButton grave;
|
||||
private HoverButton library;
|
||||
private JButton cheat;
|
||||
private JButton toolHintsHelper;
|
||||
private MageRoundPane panelBackground;
|
||||
|
||||
private JLabel timerLabel;
|
||||
|
|
|
|||
|
|
@ -1386,4 +1386,8 @@ public class CardView extends SimpleCardView {
|
|||
public boolean showPT() {
|
||||
return this.isCreature() || this.getSubTypes().contains(SubType.VEHICLE);
|
||||
}
|
||||
|
||||
public String getIdName() {
|
||||
return getName() + " [" + getId().toString().substring(0, 3) + ']';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
129
Mage.Tests/src/test/java/org/mage/test/utils/CardHintsTest.java
Normal file
129
Mage.Tests/src/test/java/org/mage/test/utils/CardHintsTest.java
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package org.mage.test.utils;
|
||||
|
||||
import mage.MageObject;
|
||||
import mage.abilities.keyword.FlyingAbility;
|
||||
import mage.constants.CommanderCardType;
|
||||
import mage.constants.PhaseStep;
|
||||
import mage.constants.Zone;
|
||||
import mage.util.GameLog;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.mage.test.serverside.base.CardTestCommanderDuelBase;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CardHintsTest extends CardTestCommanderDuelBase {
|
||||
|
||||
// html logs/names used all around xmage (game logs, chats, choose dialogs, etc)
|
||||
// usage steps:
|
||||
// * server side: find game object for logs;
|
||||
// * server side: prepare html compatible log (name [id] + color + additional info)
|
||||
// * client side: inject additional elements for popup support (e.g. "a" with "href")
|
||||
// * client side: process mouse move over a href and show object data like a card popup
|
||||
|
||||
private void assertObjectHtmlLog(String originalLog, String needVisibleColorPart, String needVisibleNormalPart, String needId) {
|
||||
String needVisibleFull = needVisibleColorPart;
|
||||
if (!needVisibleNormalPart.isEmpty()) {
|
||||
needVisibleFull += !needVisibleFull.isEmpty() ? " " : "";
|
||||
needVisibleFull += needVisibleNormalPart;
|
||||
}
|
||||
String mesPrefix = needVisibleFull + ": ";
|
||||
String mesPostfix = " in " + originalLog;
|
||||
|
||||
// simple check
|
||||
Assert.assertTrue(mesPrefix + "can't find color part" + mesPostfix, originalLog.contains(needVisibleColorPart));
|
||||
Assert.assertTrue(mesPrefix + "can't find normal part" + mesPostfix, originalLog.contains(needVisibleNormalPart));
|
||||
Assert.assertTrue(mesPrefix + "can't find id" + mesPostfix, originalLog.contains(needId));
|
||||
|
||||
// html check
|
||||
Element html = Jsoup.parse(originalLog);
|
||||
Assert.assertEquals(mesPrefix + "can't find full text" + mesPostfix, needVisibleFull, html.text());
|
||||
Element htmlFont = html.getElementsByTag("font").stream().findFirst().orElse(null);
|
||||
Assert.assertNotNull(mesPrefix + "can't find tag [font]" + mesPostfix, htmlFont);
|
||||
Assert.assertNotEquals(mesPrefix + "can't find attribute [color]" + mesPostfix, "", htmlFont.attr("color"));
|
||||
Assert.assertEquals(mesPrefix + "can't find attribute [object_id]" + mesPostfix, needId, htmlFont.attr("object_id"));
|
||||
|
||||
// improved log from client (with href and popup support)
|
||||
String popupLog = GameLog.injectPopupSupport(originalLog);
|
||||
html = Jsoup.parse(popupLog);
|
||||
Assert.assertEquals(mesPrefix + "injected, can't find full text" + mesPostfix, needVisibleFull, html.text());
|
||||
// href
|
||||
Element htmlA = html.getElementsByTag("a").stream().findFirst().orElse(null);
|
||||
Assert.assertNotNull(mesPrefix + "injected, can't find tag [a]" + mesPostfix, htmlA);
|
||||
Assert.assertTrue(mesPrefix + "injected, can't find attribute [href]" + mesPostfix, htmlA.attr("href").startsWith("#"));
|
||||
Assert.assertEquals(mesPrefix + "injected, popup tag [a] must contains colored part only" + mesPostfix, needVisibleColorPart, htmlA.text());
|
||||
// object
|
||||
htmlFont = html.getElementsByTag("font").stream().findFirst().orElse(null);
|
||||
Assert.assertNotNull(mesPrefix + "injected, can't find tag [font]" + mesPostfix, htmlFont);
|
||||
Assert.assertNotEquals(mesPrefix + "can't find attribute [color]" + mesPostfix, "", htmlFont.attr("color"));
|
||||
Assert.assertEquals(mesPrefix + "can't find attribute [object_id]" + mesPostfix, needId, htmlFont.attr("object_id"));
|
||||
}
|
||||
|
||||
private void assertObjectSupport(MageObject object) {
|
||||
String shortId = String.format("[%s]", object.getId().toString().substring(0, 3));
|
||||
|
||||
// original mode with both color and normal parts (name + id)
|
||||
String log = GameLog.getColoredObjectIdName(object, null);
|
||||
assertObjectHtmlLog(
|
||||
log,
|
||||
object.getName(),
|
||||
shortId,
|
||||
object.getId().toString()
|
||||
);
|
||||
|
||||
// custom mode with both color and normal parts
|
||||
String customName = "custom" + shortId + "name";
|
||||
String customPart = "xxx";
|
||||
log = GameLog.getColoredObjectIdName(object.getColor(currentGame), object.getId(), customName, customPart, null);
|
||||
assertObjectHtmlLog(log, customName, customPart, object.getId().toString());
|
||||
|
||||
// custom mode with colored part only
|
||||
customName = "custom" + shortId + "name";
|
||||
customPart = "";
|
||||
log = GameLog.getColoredObjectIdName(object.getColor(currentGame), object.getId(), customName, customPart, null);
|
||||
assertObjectHtmlLog(log, customName, customPart, object.getId().toString());
|
||||
|
||||
// custom mode with normal part only (popup will not work in GUI due empty a-href part)
|
||||
customName = "";
|
||||
customPart = "xxx";
|
||||
log = GameLog.getColoredObjectIdName(object.getColor(currentGame), object.getId(), customName, customPart, null);
|
||||
assertObjectHtmlLog(log, customName, customPart, object.getId().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_ObjectNamesInHtml() {
|
||||
skipInitShuffling();
|
||||
|
||||
addCard(Zone.HAND, playerA, "Grizzly Bears"); // card
|
||||
addCard(Zone.BATTLEFIELD, playerA, "Mountain"); // permanent
|
||||
addCard(Zone.COMMAND, playerA, "Soldier of Fortune"); // commander
|
||||
// diff names
|
||||
addCustomCardWithAbility("name normal", playerA, FlyingAbility.getInstance());
|
||||
addCustomCardWithAbility("123", playerA, FlyingAbility.getInstance());
|
||||
addCustomCardWithAbility("", playerA, FlyingAbility.getInstance());
|
||||
addCustomCardWithAbility("special \" name 1", playerA, FlyingAbility.getInstance());
|
||||
addCustomCardWithAbility("\"special name 2", playerA, FlyingAbility.getInstance());
|
||||
addCustomCardWithAbility("special name 3\"", playerA, FlyingAbility.getInstance());
|
||||
addCustomCardWithAbility("\"special name 4\"", playerA, FlyingAbility.getInstance());
|
||||
|
||||
setStrictChooseMode(true);
|
||||
setStopAt(1, PhaseStep.DECLARE_ATTACKERS);
|
||||
execute();
|
||||
|
||||
// colleat all possible objects and test logs with it
|
||||
List<MageObject> sampleObjects = new ArrayList<>();
|
||||
sampleObjects.addAll(currentGame.getBattlefield().getAllPermanents());
|
||||
sampleObjects.addAll(playerA.getHand().getCards(currentGame));
|
||||
sampleObjects.addAll(currentGame.getCommandersIds(playerA, CommanderCardType.ANY, false)
|
||||
.stream()
|
||||
.map(c -> currentGame.getObject(c))
|
||||
.collect(Collectors.toList()));
|
||||
Assert.assertEquals(3 + 7 + 1, sampleObjects.size()); // defaul commander game already contains +1 commander
|
||||
|
||||
sampleObjects.forEach(this::assertObjectSupport);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package mage.util;
|
|||
import mage.MageObject;
|
||||
import mage.ObjectColor;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
|
|
@ -54,11 +55,59 @@ public final class GameLog {
|
|||
}
|
||||
|
||||
public static String getColoredObjectIdName(MageObject mageObject, MageObject alternativeObject) {
|
||||
return getColoredObjectIdName(
|
||||
mageObject.getColor(null),
|
||||
mageObject.getId(),
|
||||
mageObject.getName(),
|
||||
String.format("[%s]", mageObject.getId().toString().substring(0, 3)),
|
||||
alternativeObject == null ? null : alternativeObject.getName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare html text with additional object info (can be used for card popup in GUI)
|
||||
*
|
||||
* @param color text color of the colored part
|
||||
* @param objectID object id
|
||||
* @param visibleColorPart colored part, popup will be work on it
|
||||
* @param visibleNormalPart additional part with default color
|
||||
* @param alternativeName alternative name, popup will use it on unknown object id or name
|
||||
* @return
|
||||
*/
|
||||
public static String getColoredObjectIdName(ObjectColor color,
|
||||
UUID objectID,
|
||||
String visibleColorPart,
|
||||
String visibleNormalPart,
|
||||
String alternativeName) {
|
||||
String additionalText = !visibleColorPart.isEmpty() && !visibleNormalPart.isEmpty() ? " " : "";
|
||||
additionalText += visibleNormalPart;
|
||||
return "<font"
|
||||
+ " color='" + getColorName(mageObject.getColor(null)) + "'"
|
||||
+ " object_id='" + mageObject.getId() + "'"
|
||||
+ (alternativeObject == null ? "" : " alternative_name='" + CardUtil.urlEncode(alternativeObject.getName()) + "'")
|
||||
+ ">" + mageObject.getIdName() + "</font>";
|
||||
+ " color='" + getColorName(color) + "'"
|
||||
+ " object_id='" + objectID + "'"
|
||||
+ (alternativeName == null ? "" : " alternative_name='" + CardUtil.urlEncode(alternativeName) + "'")
|
||||
+ ">" + visibleColorPart + "</font>"
|
||||
+ additionalText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add popup card support in game logs (client will process all href links)
|
||||
*/
|
||||
public static String injectPopupSupport(String htmlLogs) {
|
||||
// input/output examples:
|
||||
// some text
|
||||
// ignore
|
||||
// <font color='red'>some text</font>
|
||||
// ignore
|
||||
// <font color='#B0C4DE' object_id='xxx'>Mountain</font> [233]
|
||||
// <a href="#Mountain"><font color='#B0C4DE' object_id='xxx'>Mountain</font></a> [233]
|
||||
// <font color='#FF6347' object_id='xxx'>[fc7]</font>
|
||||
// <a href="#[fc7]"><font color='#FF6347' object_id='xxx'>[fc7]</font></a>
|
||||
// <font color='#CCCC33'>16:45, T1.M1: </font><font color='White'><font color='#20B2AA'>Human</font> puts <font color='#B0C4DE' object_id='3d2cae7c-9785-47b6-a636-84b07d939425'>Mountain</font> [3d2] from hand onto the Battlefield</font> [123]
|
||||
// <font color='#CCCC33'>16:45, T1.M1: </font><font color='White'><font color='#20B2AA'>Human</font> puts <a href="#Mountain"><font color='#B0C4DE' object_id='3d2cae7c-9785-47b6-a636-84b07d939425'>Mountain</font></a> [3d2] from hand onto the Battlefield</font> [123]
|
||||
return htmlLogs.replaceAll("<br>", "\r\n<br>\r\n").replaceAll(
|
||||
"<font (color=[^<]*object_id=[^>]*)>([^<]*)</font>",
|
||||
"<a href=\"#$2\"><font $1>$2</font></a>"
|
||||
);
|
||||
}
|
||||
|
||||
public static String getColoredObjectIdNameForTooltip(MageObject mageObject) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue