mirror of
https://github.com/magefree/mage.git
synced 2025-12-20 10:40:06 -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);
|
data.setTooltipDelay(tooltipDelay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CardView getCardView() {
|
||||||
|
return this.cardView;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean prepared() {
|
public boolean prepared() {
|
||||||
return this.cardView != null
|
return this.cardView != null
|
||||||
&& this.cardComponent != null
|
&& this.cardComponent != null
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@ public class ChatPanelBasic extends javax.swing.JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableHyperlinks() {
|
public void enableHyperlinks() {
|
||||||
txtConversation.enableHyperlinks();
|
txtConversation.enableHyperlinksAndCardPopups();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void txtMessageKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtMessageKeyTyped
|
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.client.dialog.PreferencesDialog;
|
||||||
import mage.game.command.Plane;
|
import mage.game.command.Plane;
|
||||||
import mage.util.CardUtil;
|
import mage.util.CardUtil;
|
||||||
|
import mage.util.GameLog;
|
||||||
import mage.utils.ThreadUtils;
|
import mage.utils.ThreadUtils;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.PlaneView;
|
import mage.view.PlaneView;
|
||||||
|
|
@ -87,6 +88,8 @@ public class ColorPane extends JEditorPane {
|
||||||
if (e.getEventType() == EventType.ENTERED) {
|
if (e.getEventType() == EventType.ENTERED) {
|
||||||
CardView cardView = null;
|
CardView cardView = null;
|
||||||
|
|
||||||
|
// TODO: add real game object first
|
||||||
|
|
||||||
// card
|
// card
|
||||||
CardInfo card = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null);
|
CardInfo card = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null);
|
||||||
if (card != null) {
|
if (card != null) {
|
||||||
|
|
@ -144,19 +147,20 @@ public class ColorPane extends JEditorPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setText(String string) {
|
public void setText(String text) {
|
||||||
super.setText(string);
|
// must use append to enable popup/hyperlinks support
|
||||||
|
super.setText("");
|
||||||
|
append(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void append(String text) {
|
public void append(String text) {
|
||||||
try {
|
try {
|
||||||
if (hyperlinkEnabled) {
|
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);
|
kit.insertHTML(doc, doc.getLength(), text, 0, 0, null);
|
||||||
int len = getDocument().getLength();
|
int len = getDocument().getLength();
|
||||||
setCaretPosition(len);
|
setCaretPosition(len);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +186,7 @@ public class ColorPane extends JEditorPane {
|
||||||
super.paintChildren(g);
|
super.paintChildren(g);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableHyperlinks() {
|
public void enableHyperlinksAndCardPopups() {
|
||||||
hyperlinkEnabled = true;
|
hyperlinkEnabled = true;
|
||||||
addHyperlinkHandlers();
|
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 java.awt.BorderLayout;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import mage.client.dialog.CardHintsHelperDialog;
|
||||||
import mage.client.dialog.CardInfoWindowDialog;
|
import mage.client.dialog.CardInfoWindowDialog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* GUI: helper class to improve work with desktop frames
|
||||||
*
|
*
|
||||||
* @author LevelX2
|
* @author LevelX2
|
||||||
*/
|
*/
|
||||||
public class MageDesktopManager extends DefaultDesktopManager {
|
public class MageDesktopManager extends DefaultDesktopManager {
|
||||||
|
|
||||||
static final int DESKTOP_ICON_WIDTH = 250;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void iconifyFrame(JInternalFrame f) {
|
public void iconifyFrame(JInternalFrame f) {
|
||||||
super.iconifyFrame(f);
|
super.iconifyFrame(f);
|
||||||
if (f instanceof CardInfoWindowDialog) {
|
if (f instanceof MageDesktopIconifySupport) {
|
||||||
|
int needIconWidth = ((MageDesktopIconifySupport) f).getDesktopIconWidth();
|
||||||
JInternalFrame.JDesktopIcon icon = f.getDesktopIcon();
|
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
|
@Override
|
||||||
public void deiconifyFrame(JInternalFrame f) {
|
public void deiconifyFrame(JInternalFrame f) {
|
||||||
super.deiconifyFrame(f);
|
super.deiconifyFrame(f);
|
||||||
if (f instanceof CardInfoWindowDialog) {
|
if (f instanceof MageDesktopIconifySupport) {
|
||||||
|
int needIconWidth = ((MageDesktopIconifySupport) f).getDesktopIconWidth();
|
||||||
JInternalFrame.JDesktopIcon icon = f.getDesktopIcon();
|
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.cards.MageCard;
|
||||||
import mage.client.cards.BigCard;
|
import mage.client.cards.BigCard;
|
||||||
|
import mage.client.components.MageDesktopIconifySupport;
|
||||||
import mage.client.util.GUISizeHelper;
|
import mage.client.util.GUISizeHelper;
|
||||||
import mage.client.util.ImageHelper;
|
import mage.client.util.ImageHelper;
|
||||||
import mage.client.util.SettingsManager;
|
import mage.client.util.SettingsManager;
|
||||||
import mage.client.util.gui.GuiDisplayUtil;
|
import mage.client.util.gui.GuiDisplayUtil;
|
||||||
import mage.constants.CardType;
|
import mage.constants.CardType;
|
||||||
|
import mage.util.RandomUtil;
|
||||||
import mage.view.CardView;
|
import mage.view.CardView;
|
||||||
import mage.view.CardsView;
|
import mage.view.CardsView;
|
||||||
import mage.view.ExileView;
|
import mage.view.ExileView;
|
||||||
|
|
@ -29,7 +31,7 @@ import org.mage.plugins.card.utils.impl.ImageManagerImpl;
|
||||||
*
|
*
|
||||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
* @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);
|
private static final Logger LOGGER = Logger.getLogger(CardInfoWindowDialog.class);
|
||||||
|
|
||||||
|
|
@ -48,22 +50,6 @@ public class CardInfoWindowDialog extends MageDialog {
|
||||||
this.positioned = false;
|
this.positioned = false;
|
||||||
initComponents();
|
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);
|
this.setModal(false);
|
||||||
switch (this.showType) {
|
switch (this.showType) {
|
||||||
case LOOKED_AT:
|
case LOOKED_AT:
|
||||||
|
|
@ -192,6 +178,7 @@ public class CardInfoWindowDialog extends MageDialog {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void show() {
|
public void show() {
|
||||||
|
// hide empty exile windows
|
||||||
if (showType == ShowType.EXILE) {
|
if (showType == ShowType.EXILE) {
|
||||||
if (cards == null || cards.getNumberOfCards() == 0) {
|
if (cards == null || cards.getNumberOfCards() == 0) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -199,7 +186,9 @@ public class CardInfoWindowDialog extends MageDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
super.show();
|
super.show();
|
||||||
if (positioned) { // check if in frame rectangle
|
|
||||||
|
// auto-position on first usage
|
||||||
|
if (positioned) {
|
||||||
showAndPositionWindow();
|
showAndPositionWindow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,8 +200,10 @@ public class CardInfoWindowDialog extends MageDialog {
|
||||||
if (width > 0 && height > 0) {
|
if (width > 0 && height > 0) {
|
||||||
Point centered = SettingsManager.instance.getComponentPosition(width, height);
|
Point centered = SettingsManager.instance.getComponentPosition(width, height);
|
||||||
if (!positioned) {
|
if (!positioned) {
|
||||||
int xPos = centered.x / 2;
|
// starting position
|
||||||
int yPos = centered.y / 2;
|
// 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);
|
CardInfoWindowDialog.this.setLocation(xPos, yPos);
|
||||||
show();
|
show();
|
||||||
positioned = true;
|
positioned = true;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,16 @@
|
||||||
package mage.client.dialog;
|
package mage.client.dialog;
|
||||||
|
|
||||||
import mage.client.MageFrame;
|
import mage.client.MageFrame;
|
||||||
|
import mage.client.components.MageDesktopIconifySupport;
|
||||||
import mage.client.util.SettingsManager;
|
import mage.client.util.SettingsManager;
|
||||||
import mage.client.util.gui.GuiDisplayUtil;
|
import mage.client.util.gui.GuiDisplayUtil;
|
||||||
import org.apache.log4j.Logger;
|
import org.apache.log4j.Logger;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.plaf.basic.BasicInternalFrameUI;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.InvocationEvent;
|
import java.awt.event.InvocationEvent;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.beans.PropertyVetoException;
|
import java.beans.PropertyVetoException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
@ -26,6 +29,25 @@ public class MageDialog extends javax.swing.JInternalFrame {
|
||||||
*/
|
*/
|
||||||
public MageDialog() {
|
public MageDialog() {
|
||||||
initComponents();
|
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() {
|
public void changeGUISize() {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
import mage.client.util.SettingsManager;
|
import mage.client.util.SettingsManager;
|
||||||
import mage.client.util.gui.GuiDisplayUtil;
|
import mage.client.util.gui.GuiDisplayUtil;
|
||||||
import mage.game.events.PlayerQueryEvent.QueryType;
|
import mage.game.events.PlayerQueryEvent.QueryType;
|
||||||
|
import mage.util.RandomUtil;
|
||||||
import mage.view.CardsView;
|
import mage.view.CardsView;
|
||||||
import org.mage.card.arcane.CardPanel;
|
import org.mage.card.arcane.CardPanel;
|
||||||
|
|
||||||
|
|
@ -38,7 +39,6 @@
|
||||||
initComponents();
|
initComponents();
|
||||||
|
|
||||||
this.setModal(false);
|
this.setModal(false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanUp() {
|
public void cleanUp() {
|
||||||
|
|
@ -58,9 +58,40 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setGUISize() {
|
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,
|
public void loadCards(String name, CardsView showCards, BigCard bigCard,
|
||||||
UUID gameId, boolean modal, Map<String, Serializable> options,
|
UUID gameId, boolean modal, Map<String, Serializable> options,
|
||||||
JPopupMenu popupMenu, Listener<Event> eventListener) {
|
JPopupMenu popupMenu, Listener<Event> eventListener) {
|
||||||
|
|
@ -101,20 +132,6 @@
|
||||||
} else {
|
} else {
|
||||||
MageFrame.getDesktop().add(this, JLayeredPane.PALETTE_LAYER);
|
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() {
|
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 Map<String, CardInfoWindowDialog> sideboardWindows = new HashMap<>();
|
||||||
private final ArrayList<ShowCardsDialog> pickTarget = new ArrayList<>();
|
private final ArrayList<ShowCardsDialog> pickTarget = new ArrayList<>();
|
||||||
private final ArrayList<PickPileDialog> pickPile = new ArrayList<>();
|
private final ArrayList<PickPileDialog> pickPile = new ArrayList<>();
|
||||||
|
private final Map<String, CardHintsHelperDialog> cardHintsWindows = new LinkedHashMap<>();
|
||||||
|
|
||||||
private UUID gameId;
|
private UUID gameId;
|
||||||
private UUID playerId; // playerId of the player
|
private UUID playerId; // playerId of the player
|
||||||
GamePane gamePane;
|
GamePane gamePane;
|
||||||
|
|
@ -275,6 +277,10 @@ public final class GamePanel extends javax.swing.JPanel {
|
||||||
windowDialog.cleanUp();
|
windowDialog.cleanUp();
|
||||||
windowDialog.removeDialog();
|
windowDialog.removeDialog();
|
||||||
}
|
}
|
||||||
|
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
|
||||||
|
windowDialog.cleanUp();
|
||||||
|
windowDialog.removeDialog();
|
||||||
|
}
|
||||||
|
|
||||||
clearPickDialogs();
|
clearPickDialogs();
|
||||||
|
|
||||||
|
|
@ -350,6 +356,9 @@ public final class GamePanel extends javax.swing.JPanel {
|
||||||
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
||||||
windowDialog.changeGUISize();
|
windowDialog.changeGUISize();
|
||||||
}
|
}
|
||||||
|
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
|
||||||
|
windowDialog.changeGUISize();
|
||||||
|
}
|
||||||
for (ShowCardsDialog windowDialog : pickTarget) {
|
for (ShowCardsDialog windowDialog : pickTarget) {
|
||||||
windowDialog.changeGUISize();
|
windowDialog.changeGUISize();
|
||||||
}
|
}
|
||||||
|
|
@ -886,16 +895,25 @@ public final class GamePanel extends javax.swing.JPanel {
|
||||||
GameManager.instance.setStackSize(lastGameData.game.getStack().size());
|
GameManager.instance.setStackSize(lastGameData.game.getStack().size());
|
||||||
displayStack(lastGameData.game, bigCard, feedbackPanel, gameId);
|
displayStack(lastGameData.game, bigCard, feedbackPanel, gameId);
|
||||||
|
|
||||||
|
// auto-show exile views
|
||||||
for (ExileView exile : lastGameData.game.getExile()) {
|
for (ExileView exile : lastGameData.game.getExile()) {
|
||||||
if (!exiles.containsKey(exile.getId())) {
|
CardInfoWindowDialog exileWindow = exiles.getOrDefault(exile.getId(), null);
|
||||||
CardInfoWindowDialog newExile = new CardInfoWindowDialog(ShowType.EXILE, exile.getName());
|
if (exileWindow == null) {
|
||||||
exiles.put(exile.getId(), newExile);
|
exileWindow = new CardInfoWindowDialog(ShowType.EXILE, exile.getName());
|
||||||
MageFrame.getDesktop().add(newExile, JLayeredPane.PALETTE_LAYER);
|
exiles.put(exile.getId(), exileWindow);
|
||||||
newExile.show();
|
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
|
// reveal and look at dialogs can unattached, so windows opened by game doesn't have it
|
||||||
showRevealed(lastGameData.game);
|
showRevealed(lastGameData.game);
|
||||||
showLookedAt(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
|
// Called if the game frame is deactivated because the tabled the deck editor or other frames go to foreground
|
||||||
public void deactivated() {
|
public void deactivated() {
|
||||||
// hide the non modal windows (because otherwise they are shown on top of the new active pane)
|
// 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()) {
|
for (CardInfoWindowDialog windowDialog : exiles.values()) {
|
||||||
windowDialog.hideDialog();
|
windowDialog.hideDialog();
|
||||||
}
|
}
|
||||||
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
|
|
||||||
windowDialog.hideDialog();
|
|
||||||
}
|
|
||||||
for (CardInfoWindowDialog windowDialog : revealed.values()) {
|
for (CardInfoWindowDialog windowDialog : revealed.values()) {
|
||||||
windowDialog.hideDialog();
|
windowDialog.hideDialog();
|
||||||
}
|
}
|
||||||
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
|
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
|
||||||
windowDialog.hideDialog();
|
windowDialog.hideDialog();
|
||||||
}
|
}
|
||||||
|
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
|
||||||
|
windowDialog.hideDialog();
|
||||||
|
}
|
||||||
for (CardInfoWindowDialog windowDialog : companion.values()) {
|
for (CardInfoWindowDialog windowDialog : companion.values()) {
|
||||||
windowDialog.hideDialog();
|
windowDialog.hideDialog();
|
||||||
}
|
}
|
||||||
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
||||||
windowDialog.hideDialog();
|
windowDialog.hideDialog();
|
||||||
}
|
}
|
||||||
|
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
|
||||||
|
windowDialog.hideDialog();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called if the game frame comes to front again
|
// 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()) {
|
for (CardInfoWindowDialog windowDialog : exiles.values()) {
|
||||||
windowDialog.show();
|
windowDialog.show();
|
||||||
}
|
}
|
||||||
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
|
|
||||||
windowDialog.show();
|
|
||||||
}
|
|
||||||
for (CardInfoWindowDialog windowDialog : revealed.values()) {
|
for (CardInfoWindowDialog windowDialog : revealed.values()) {
|
||||||
windowDialog.show();
|
windowDialog.show();
|
||||||
}
|
}
|
||||||
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
|
for (CardInfoWindowDialog windowDialog : lookedAt.values()) {
|
||||||
windowDialog.show();
|
windowDialog.show();
|
||||||
}
|
}
|
||||||
|
for (CardInfoWindowDialog windowDialog : graveyardWindows.values()) {
|
||||||
|
windowDialog.show();
|
||||||
|
}
|
||||||
for (CardInfoWindowDialog windowDialog : companion.values()) {
|
for (CardInfoWindowDialog windowDialog : companion.values()) {
|
||||||
windowDialog.show();
|
windowDialog.show();
|
||||||
}
|
}
|
||||||
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
for (CardInfoWindowDialog windowDialog : sideboardWindows.values()) {
|
||||||
windowDialog.show();
|
windowDialog.show();
|
||||||
}
|
}
|
||||||
|
for (CardHintsHelperDialog windowDialog : cardHintsWindows.values()) {
|
||||||
|
windowDialog.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void openGraveyardWindow(String playerName) {
|
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);
|
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) {
|
public void openSideboardWindow(UUID playerId) {
|
||||||
if (lastGameData == null) {
|
if (lastGameData == null) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
||||||
private final JPopupMenu popupMenu;
|
private final JPopupMenu popupMenu;
|
||||||
private UUID playerId;
|
private UUID playerId;
|
||||||
private UUID gameId;
|
private UUID gameId;
|
||||||
|
private boolean isMe = false;
|
||||||
private boolean smallMode = false;
|
private boolean smallMode = false;
|
||||||
private boolean playingMode = true;
|
private boolean playingMode = true;
|
||||||
private final GamePanel gamePanel;
|
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 = 263;
|
||||||
public static final int PANEL_HEIGHT_SMALL = 210;
|
public static final int PANEL_HEIGHT_SMALL = 210;
|
||||||
|
private static final int PANEL_HEIGHT_EXTRA_FOR_ME = 25;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form PlayAreaPanel
|
* Creates new form PlayAreaPanel
|
||||||
|
|
@ -515,6 +517,7 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
||||||
this.battlefieldPanel.init(gameId, bigCard);
|
this.battlefieldPanel.init(gameId, bigCard);
|
||||||
this.gameId = gameId;
|
this.gameId = gameId;
|
||||||
this.playerId = player.getPlayerId();
|
this.playerId = player.getPlayerId();
|
||||||
|
this.isMe = player.getControlled();
|
||||||
this.btnCheat.setVisible(SessionHandler.isTestMode());
|
this.btnCheat.setVisible(SessionHandler.isTestMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -562,14 +565,15 @@ public class PlayAreaPanel extends javax.swing.JPanel {
|
||||||
public void sizePlayer(boolean smallMode) {
|
public void sizePlayer(boolean smallMode) {
|
||||||
this.playerPanel.sizePlayerPanel(smallMode);
|
this.playerPanel.sizePlayerPanel(smallMode);
|
||||||
this.smallMode = smallMode;
|
this.smallMode = smallMode;
|
||||||
|
int extraForMe = this.isMe ? PANEL_HEIGHT_EXTRA_FOR_ME : 0;
|
||||||
if (smallMode) {
|
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.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 {
|
} 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.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 playerId;
|
||||||
private UUID gameId;
|
private UUID gameId;
|
||||||
private PlayerView player;
|
private PlayerView player;
|
||||||
|
private boolean isMe;
|
||||||
|
|
||||||
private BigCard bigCard;
|
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_WIDTH = 94;
|
||||||
private static final int PANEL_HEIGHT = 262;
|
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 int MANA_LABEL_SIZE_HORIZONTAL = 20;
|
||||||
|
|
||||||
private static final Border GREEN_BORDER = new LineBorder(Color.green, 3);
|
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 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 static final Border EMPTY_BORDER = BorderFactory.createEmptyBorder(0, 0, 0, 0);
|
||||||
private final Color inactiveBackgroundColor;
|
private final Color inactiveBackgroundColor;
|
||||||
private final Color activeBackgroundColor;
|
private final Color activeBackgroundColor;
|
||||||
|
|
@ -92,8 +95,10 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
this.gameId = gameId;
|
this.gameId = gameId;
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
this.bigCard = bigCard;
|
this.bigCard = bigCard;
|
||||||
cheat.setVisible(SessionHandler.isTestMode() && controlled);
|
this.isMe = controlled;
|
||||||
|
cheat.setVisible(SessionHandler.isTestMode() && this.isMe);
|
||||||
cheat.setFocusable(false);
|
cheat.setFocusable(false);
|
||||||
|
toolHintsHelper.setVisible(this.isMe);
|
||||||
flagName = null;
|
flagName = null;
|
||||||
if (priorityTime > 0) {
|
if (priorityTime > 0) {
|
||||||
long delay = 1000L;
|
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());
|
update(player.getManaPool());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -568,6 +579,13 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
cheat.setToolTipText("Cheat button");
|
cheat.setToolTipText("Cheat button");
|
||||||
cheat.addActionListener(e -> btnCheatActionPerformed(e));
|
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 = new JPanel();
|
||||||
zonesPanel.setPreferredSize(new Dimension(100, 60));
|
zonesPanel.setPreferredSize(new Dimension(100, 60));
|
||||||
zonesPanel.setSize(100, 60);
|
zonesPanel.setSize(100, 60);
|
||||||
|
|
@ -591,6 +609,9 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
cheat.setBounds(40, 2, 25, 21);
|
cheat.setBounds(40, 2, 25, 21);
|
||||||
zonesPanel.add(cheat);
|
zonesPanel.add(cheat);
|
||||||
|
|
||||||
|
toolHintsHelper.setBounds(3, 2 + 21 + 2, 75, 21);
|
||||||
|
zonesPanel.add(toolHintsHelper);
|
||||||
|
|
||||||
energyExperiencePanel = new JPanel();
|
energyExperiencePanel = new JPanel();
|
||||||
energyExperiencePanel.setPreferredSize(new Dimension(100, 20));
|
energyExperiencePanel.setPreferredSize(new Dimension(100, 20));
|
||||||
energyExperiencePanel.setSize(100, 20);
|
energyExperiencePanel.setSize(100, 20);
|
||||||
|
|
@ -619,6 +640,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
btnPlayer.setText("Player");
|
btnPlayer.setText("Player");
|
||||||
btnPlayer.setVisible(false);
|
btnPlayer.setVisible(false);
|
||||||
btnPlayer.setToolTipText("Player");
|
btnPlayer.setToolTipText("Player");
|
||||||
|
btnPlayer.setPreferredSize(new Dimension(20, 40));
|
||||||
btnPlayer.addActionListener(e -> SessionHandler.sendPlayerUUID(gameId, playerId));
|
btnPlayer.addActionListener(e -> SessionHandler.sendPlayerUUID(gameId, playerId));
|
||||||
|
|
||||||
// Add mana symbols
|
// Add mana symbols
|
||||||
|
|
@ -808,7 +830,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
.addGap(6)
|
.addGap(6)
|
||||||
.addComponent(avatar, GroupLayout.PREFERRED_SIZE, 80, GroupLayout.PREFERRED_SIZE)
|
.addComponent(avatar, GroupLayout.PREFERRED_SIZE, 80, GroupLayout.PREFERRED_SIZE)
|
||||||
.addPreferredGap(ComponentPlacement.RELATED)
|
.addPreferredGap(ComponentPlacement.RELATED)
|
||||||
.addComponent(btnPlayer)
|
.addComponent(btnPlayer, GroupLayout.PREFERRED_SIZE, 30, GroupLayout.PREFERRED_SIZE)
|
||||||
.addComponent(timerLabel)
|
.addComponent(timerLabel)
|
||||||
.addGap(2)
|
.addGap(2)
|
||||||
// Life & Hand
|
// Life & Hand
|
||||||
|
|
@ -912,18 +934,19 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
}// </editor-fold>//GEN-END:initComponents
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
protected void sizePlayerPanel(boolean smallMode) {
|
protected void sizePlayerPanel(boolean smallMode) {
|
||||||
|
int extraForMe = this.isMe ? PANEL_HEIGHT_EXTRA_FOR_ME : 0;
|
||||||
if (smallMode) {
|
if (smallMode) {
|
||||||
avatar.setVisible(false);
|
avatar.setVisible(false);
|
||||||
btnPlayer.setVisible(true);
|
btnPlayer.setVisible(true);
|
||||||
timerLabel.setVisible(true);
|
timerLabel.setVisible(true);
|
||||||
panelBackground.setPreferredSize(new Dimension(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);
|
panelBackground.setBounds(0, 0, PANEL_WIDTH - 2, PANEL_HEIGHT_SMALL + extraForMe);
|
||||||
} else {
|
} else {
|
||||||
avatar.setVisible(true);
|
avatar.setVisible(true);
|
||||||
btnPlayer.setVisible(false);
|
btnPlayer.setVisible(false);
|
||||||
timerLabel.setVisible(false);
|
timerLabel.setVisible(false);
|
||||||
panelBackground.setPreferredSize(new Dimension(PANEL_WIDTH - 2, PANEL_HEIGHT));
|
panelBackground.setPreferredSize(new Dimension(PANEL_WIDTH - 2, PANEL_HEIGHT + extraForMe));
|
||||||
panelBackground.setBounds(0, 0, PANEL_WIDTH - 2, PANEL_HEIGHT);
|
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));
|
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() {
|
public PlayerView getPlayer() {
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
@ -984,6 +1011,7 @@ public class PlayerPanelExt extends javax.swing.JPanel {
|
||||||
private HoverButton grave;
|
private HoverButton grave;
|
||||||
private HoverButton library;
|
private HoverButton library;
|
||||||
private JButton cheat;
|
private JButton cheat;
|
||||||
|
private JButton toolHintsHelper;
|
||||||
private MageRoundPane panelBackground;
|
private MageRoundPane panelBackground;
|
||||||
|
|
||||||
private JLabel timerLabel;
|
private JLabel timerLabel;
|
||||||
|
|
|
||||||
|
|
@ -1386,4 +1386,8 @@ public class CardView extends SimpleCardView {
|
||||||
public boolean showPT() {
|
public boolean showPT() {
|
||||||
return this.isCreature() || this.getSubTypes().contains(SubType.VEHICLE);
|
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.MageObject;
|
||||||
import mage.ObjectColor;
|
import mage.ObjectColor;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -54,11 +55,59 @@ public final class GameLog {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getColoredObjectIdName(MageObject mageObject, MageObject alternativeObject) {
|
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"
|
return "<font"
|
||||||
+ " color='" + getColorName(mageObject.getColor(null)) + "'"
|
+ " color='" + getColorName(color) + "'"
|
||||||
+ " object_id='" + mageObject.getId() + "'"
|
+ " object_id='" + objectID + "'"
|
||||||
+ (alternativeObject == null ? "" : " alternative_name='" + CardUtil.urlEncode(alternativeObject.getName()) + "'")
|
+ (alternativeName == null ? "" : " alternative_name='" + CardUtil.urlEncode(alternativeName) + "'")
|
||||||
+ ">" + mageObject.getIdName() + "</font>";
|
+ ">" + 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) {
|
public static String getColoredObjectIdNameForTooltip(MageObject mageObject) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue