diff --git a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java index 8bcbe9c898f..e05fe58586b 100644 --- a/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java +++ b/Mage.Client/src/main/java/mage/client/chat/ChatPanelBasic.java @@ -108,6 +108,8 @@ public class ChatPanelBasic extends javax.swing.JPanel { } public void cleanUp() { + this.disconnect(); + this.txtConversation.cleanUp(); } public void setGameData(UUID gameId, BigCard bigCard) { @@ -158,7 +160,7 @@ public class ChatPanelBasic extends javax.swing.JPanel { } } - public void disconnect() { + private void disconnect() { if (SessionHandler.getSession() != null) { SessionHandler.leaveChat(chatId); MageFrame.removeChat(chatId); diff --git a/Mage.Client/src/main/java/mage/client/chat/ChatPanelSeparated.java b/Mage.Client/src/main/java/mage/client/chat/ChatPanelSeparated.java index 4dee6a5cf95..44b90c7a817 100644 --- a/Mage.Client/src/main/java/mage/client/chat/ChatPanelSeparated.java +++ b/Mage.Client/src/main/java/mage/client/chat/ChatPanelSeparated.java @@ -16,6 +16,14 @@ public class ChatPanelSeparated extends ChatPanelBasic { private ColorPane systemMessagesPane = null; + @Override + public void cleanUp() { + super.cleanUp(); + if (this.systemMessagesPane != null) { + this.systemMessagesPane.cleanUp(); + } + } + /** * Display message in the chat. Use different colors for timestamp, username * and message. diff --git a/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java b/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java index b466307618d..10b3bd1e718 100644 --- a/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java +++ b/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java @@ -17,9 +17,8 @@ import mage.view.PlaneView; import javax.swing.*; import javax.swing.event.HyperlinkEvent; -import javax.swing.text.SimpleAttributeSet; -import javax.swing.text.html.HTMLDocument; -import javax.swing.text.html.HTMLEditorKit; +import javax.swing.text.*; +import javax.swing.text.html.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -33,14 +32,20 @@ import java.util.*; public class MageEditorPane extends JEditorPane { private static final int CHAT_TOOLTIP_DELAY_MS = 50; // cards popup from chat must be fast all time + private static Element lastUrlElementEntered = null; // for cursor changes + final HTMLEditorKit kit = new HTMLEditorKit(); final HTMLDocument doc; + MageEditorPane() { super(); // merge with UI.setHTMLEditorKit this.setEditorKit(kit); - this.doc = (HTMLDocument) this.getDocument(); // HTMLEditorKit must creates HTMLDocument, os use it here + this.doc = (HTMLDocument) this.getDocument(); // HTMLEditorKit must create HTMLDocument, os use it here + + // improved style: browser's url style with underline on mouse over and hand cursor + kit.getStyleSheet().addRule(" a { text-decoration: none; } "); } // cards popup info @@ -54,6 +59,10 @@ public class MageEditorPane extends JEditorPane { this.bigCard = bigCard; } + public void cleanUp() { + resetCursor(); + } + private void addHyperlinkHandlers() { if (Arrays.stream(getHyperlinkListeners()).findAny().isPresent()) { throw new IllegalStateException("Wrong code usage: popup links support enabled already"); @@ -91,8 +100,11 @@ public class MageEditorPane extends JEditorPane { CardView needCard = null; GamePanel gamePanel = MageFrame.getGame(this.gameId); if (gamePanel != null) { - UUID needObjectId = UUID.fromString(extraData.getOrDefault("object_id", "")); - needCard = gamePanel.getLastGameData().findCard(needObjectId); + try { + UUID needObjectId = UUID.fromString(extraData.getOrDefault("object_id", "")); + needCard = gamePanel.getLastGameData().findCard(needObjectId); + } catch (IllegalArgumentException ignore) { + } } String cardName = e.getDescription().substring(1); @@ -102,6 +114,12 @@ public class MageEditorPane extends JEditorPane { } if (e.getEventType() == HyperlinkEvent.EventType.ENTERED) { + AttributeSet as = e.getSourceElement().getAttributes(); + AttributeSet asAnchor = (AttributeSet) as.getAttribute(HTML.Tag.A); + if (asAnchor != null) { + urlHighlightEnable(e.getSourceElement()); + } + // show real object by priority (workable card hints and actual info) CardView cardView = needCard; @@ -141,6 +159,8 @@ public class MageEditorPane extends JEditorPane { } if (e.getEventType() == HyperlinkEvent.EventType.EXITED) { + urlHighlightDisable(); + SwingUtilities.invokeLater(() -> { cardInfo.onMouseExited(); }); @@ -152,10 +172,47 @@ public class MageEditorPane extends JEditorPane { @Override public void mouseExited(MouseEvent e) { cardInfo.onMouseExited(); + resetCursor(); } }); } + private void resetCursor() { + SwingUtilities.windowForComponent(this).setCursor(Cursor.getDefaultCursor()); + } + + private void setCursorToHand() { + SwingUtilities.windowForComponent(this).setCursor(new Cursor(Cursor.HAND_CURSOR)); + } + + private void urlHighlightEnable(Element hyperlinkElement) { + if (hyperlinkElement != lastUrlElementEntered) { + lastUrlElementEntered = hyperlinkElement; + changeUrlTextDecoration(hyperlinkElement, "underline"); + } + setCursorToHand(); + } + + private void urlHighlightDisable() { + if (lastUrlElementEntered != null) { + changeUrlTextDecoration(lastUrlElementEntered, "none"); + lastUrlElementEntered = null; + } + resetCursor(); + } + + private void changeUrlTextDecoration(Element el, String decoration) { + if (lastUrlElementEntered != null) { + HTMLDocument doc = (HTMLDocument) this.getDocument(); + int start = el.getStartOffset(); + int end = el.getEndOffset(); + StyleContext ss = doc.getStyleSheet(); + Style style = ss.addStyle("HighlightedUrl", null); + style.addAttribute(CSS.Attribute.TEXT_DECORATION, decoration); + doc.setCharacterAttributes(start, end - start, style, false); + } + } + @Override public void setText(String text) { // must use append to enable popup/hyperlinks support @@ -177,7 +234,13 @@ public class MageEditorPane extends JEditorPane { } public void enableHyperlinksAndCardPopups() { + if (this.isEditable()) { + throw new IllegalStateException("Wrong code usage: hyper links works with non-editable components"); + } + hyperlinkEnabled = true; addHyperlinkHandlers(); } + + } diff --git a/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java index 7ebb872e589..c4df463b271 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TableWaitingDialog.java @@ -158,7 +158,7 @@ public class TableWaitingDialog extends MageDialog { updateTask.cancel(true); } - this.chatPanel.disconnect(); + this.chatPanel.cleanUp(); MageFrame.getUI().removeButton(MageComponents.TABLE_WAITING_START_BUTTON); this.removeDialog(); TableUtil.saveColumnWidthAndOrderToPrefs(jTableSeats, KEY_TABLE_WAITING_COLUMNS_WIDTH, KEY_TABLE_WAITING_COLUMNS_ORDER); diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index aa61e870b35..e98a78cbf0d 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -274,8 +274,8 @@ public final class GamePanel extends javax.swing.JPanel { public void cleanUp() { MageFrame.removeGame(gameId); saveDividerLocations(); - this.gameChatPanel.disconnect(); - this.userChatPanel.disconnect(); + this.gameChatPanel.cleanUp();; + this.userChatPanel.cleanUp(); this.removeListener(); @@ -531,7 +531,7 @@ public final class GamePanel extends javax.swing.JPanel { MageFrame.addGame(gameId, this); this.feedbackPanel.init(gameId); this.feedbackPanel.clear(); - this.abilityPicker.init(gameId); + this.abilityPicker.init(gameId, bigCard); this.btnConcede.setVisible(true); this.btnStopWatching.setVisible(false); this.btnSwitchHands.setVisible(false); diff --git a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java index f48350c2c4d..1ebc5ac7379 100644 --- a/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java +++ b/Mage.Client/src/main/java/mage/client/plugins/adapters/MageActionCallback.java @@ -753,7 +753,8 @@ public class MageActionCallback implements ActionCallback { location.setLocation(location.x - popupContainer.getWidth(), location.y); } if (!hasBottomSpace) { - location.setLocation(location.x, location.y - popupContainer.getHeight()); + // if no upper space, then put at the top by Math.max + location.setLocation(location.x, Math.max(parentComponent.getY(), location.y - popupContainer.getHeight())); } break; } diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index d9d934cb591..3f7ad37f667 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -187,7 +187,7 @@ public class CallbackClientImpl implements CallbackClient { case REPLAY_INIT: { GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { - panel.init(callback.getMessageId(), (GameView) callback.getData()); + panel.init(callback.getMessageId(), (GameView) callback.getData(), true); } break; } @@ -212,7 +212,7 @@ public class CallbackClientImpl implements CallbackClient { GamePanel panel = MageFrame.getGame(callback.getObjectId()); if (panel != null) { appendJsonEvent("GAME_INIT", callback.getObjectId(), callback.getData()); - panel.init(callback.getMessageId(), (GameView) callback.getData()); + panel.init(callback.getMessageId(), (GameView) callback.getData(), true); } break; } diff --git a/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java b/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java index 4ca7a4f4120..c93d929366d 100644 --- a/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/PlayersChatPanel.java @@ -93,7 +93,6 @@ public class PlayersChatPanel extends javax.swing.JPanel { jScrollPanePlayers.getViewport().setBackground(new Color(0, 0, 0, CHAT_ALPHA)); jScrollPanePlayers.setViewportBorder(null); } - } public ChatPanelBasic getUserChatPanel() { diff --git a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java index 42db749eb08..2878566b3a2 100644 --- a/Mage.Client/src/main/java/mage/client/table/TablesPanel.java +++ b/Mage.Client/src/main/java/mage/client/table/TablesPanel.java @@ -827,7 +827,7 @@ public class TablesPanel extends javax.swing.JPanel { } } stopTasks(); - this.chatPanelMain.getUserChatPanel().disconnect(); + this.chatPanelMain.cleanUp();; Component c = this.getParent(); while (c != null && !(c instanceof TablesPane)) { diff --git a/Mage.Client/src/main/java/mage/client/tournament/TournamentPanel.java b/Mage.Client/src/main/java/mage/client/tournament/TournamentPanel.java index d2112da2dee..a2ab15ad355 100644 --- a/Mage.Client/src/main/java/mage/client/tournament/TournamentPanel.java +++ b/Mage.Client/src/main/java/mage/client/tournament/TournamentPanel.java @@ -108,9 +108,8 @@ public class TournamentPanel extends javax.swing.JPanel { public void cleanUp() { this.stopTasks(); if (this.chatPanel1 != null) { - this.chatPanel1.disconnect(); + this.chatPanel1.cleanUp(); } - } public void changeGUISize() { @@ -185,7 +184,9 @@ public class TournamentPanel extends javax.swing.JPanel { public void hideTournament() { stopTasks(); - this.chatPanel1.disconnect(); + if (this.chatPanel1 != null) { + this.chatPanel1.cleanUp(); + } this.saveDividerLocations(); TableUtil.saveColumnWidthAndOrderToPrefs(tablePlayers, KEY_TOURNAMENT_PLAYER_COLUMNS_WIDTH, KEY_TOURNAMENT_PLAYER_COLUMNS_ORDER); TableUtil.saveColumnWidthAndOrderToPrefs(tableMatches, KEY_TOURNAMENT_MATCH_COLUMNS_WIDTH, KEY_TOURNAMENT_MATCH_COLUMNS_ORDER);