forked from External/mage
- added GUI scale support for dialog titles; - added GUI scale support for hints tool; - fixed broken font in chat and game logs on settings change; - fixed wrong size of pick choice dialog in some use cases;
283 lines
10 KiB
Java
283 lines
10 KiB
Java
package mage.client.components;
|
|
|
|
import mage.cards.action.TransferData;
|
|
import mage.cards.repository.CardInfo;
|
|
import mage.cards.repository.CardRepository;
|
|
import mage.client.MageFrame;
|
|
import mage.client.cards.BigCard;
|
|
import mage.client.cards.VirtualCardInfo;
|
|
import mage.client.dialog.PreferencesDialog;
|
|
import mage.client.game.GamePanel;
|
|
import mage.game.command.Plane;
|
|
import mage.util.CardUtil;
|
|
import mage.util.GameLog;
|
|
import mage.view.CardView;
|
|
import mage.view.PlaneView;
|
|
|
|
import javax.swing.*;
|
|
import javax.swing.event.HyperlinkEvent;
|
|
import javax.swing.text.*;
|
|
import javax.swing.text.html.CSS;
|
|
import javax.swing.text.html.HTML;
|
|
import javax.swing.text.html.HTMLDocument;
|
|
import javax.swing.text.html.HTMLEditorKit;
|
|
import java.awt.*;
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
import java.util.*;
|
|
|
|
/**
|
|
* GUI: improved editor pane with html, hyperlinks/popup support
|
|
* <p>
|
|
* Can be used as:
|
|
* - read only text panel (example: chats and game logs)
|
|
* - read only text label (example: header messages in choice dialogs)
|
|
* <p>
|
|
* Call in form's constructor or show dialog:
|
|
* - xxx.enableTextLabelMode to enable text label mode
|
|
* - xxx.enableHyperlinksAndCardPopups + xxx.setGameData to enable card popup support
|
|
*
|
|
* @author JayDi85
|
|
*/
|
|
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;
|
|
|
|
public MageEditorPane() {
|
|
super();
|
|
// merge with UI.setHTMLEditorKit
|
|
this.setEditorKit(kit);
|
|
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; } ");
|
|
|
|
changeGUISize(this.getFont());
|
|
}
|
|
|
|
public void changeGUISize(Font font) {
|
|
this.setFont(font);
|
|
|
|
// workaround to change editor's font at runtime
|
|
String bodyRule = "body { "
|
|
+ " font-family: " + font.getFamily() + "; "
|
|
+ " font-size: " + font.getSize() + "pt; "
|
|
+ "}";
|
|
kit.getStyleSheet().addRule(bodyRule);
|
|
}
|
|
|
|
/**
|
|
* Simulate JLabel (non-editable and transparent background)
|
|
*/
|
|
public void enableTextLabelMode() {
|
|
this.setOpaque(false);
|
|
this.setFocusable(false);
|
|
this.setBorder(null);
|
|
this.setAutoscrolls(false);
|
|
this.setBackground(new Color(0, 0, 0, 0)); // transparent background
|
|
}
|
|
|
|
// cards popup info
|
|
private boolean hyperlinkEnabled = false;
|
|
VirtualCardInfo cardInfo = new VirtualCardInfo();
|
|
UUID gameId = null;
|
|
BigCard bigCard = null;
|
|
|
|
public void setGameData(UUID gameId, BigCard bigCard) {
|
|
this.gameId = gameId;
|
|
this.bigCard = bigCard;
|
|
}
|
|
|
|
public void cleanUp() {
|
|
setCursorToDefault();
|
|
}
|
|
|
|
private void addHyperlinkHandlers() {
|
|
if (Arrays.stream(getHyperlinkListeners()).findAny().isPresent()) {
|
|
throw new IllegalStateException("Wrong code usage: popup links support enabled already");
|
|
}
|
|
addHyperlinkListener(e -> MageUI.threadPoolPopups.submit(() -> {
|
|
if (PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_TOOLTIPS_DELAY, 300) == 0) {
|
|
// if disabled
|
|
return;
|
|
}
|
|
|
|
// finds extra data in html element like object_id, alternative_name, etc
|
|
Map<String, String> extraData = new HashMap<>();
|
|
if (e.getSourceElement() instanceof HTMLDocument.RunElement) {
|
|
HTMLDocument.RunElement el = (HTMLDocument.RunElement) e.getSourceElement();
|
|
Enumeration attNames = el.getAttributeNames();
|
|
while (attNames.hasMoreElements()) {
|
|
Object attName = attNames.nextElement();
|
|
Object attValue = el.getAttribute(attName);
|
|
// custom attributes in SimpleAttributeSet element
|
|
if (attValue instanceof SimpleAttributeSet) {
|
|
SimpleAttributeSet attReal = (SimpleAttributeSet) attValue;
|
|
Enumeration attRealNames = attReal.getAttributeNames();
|
|
while (attRealNames.hasMoreElements()) {
|
|
Object attRealName = attRealNames.nextElement();
|
|
Object attRealValue = attReal.getAttribute(attRealName);
|
|
String name = attRealName.toString();
|
|
String value = attRealValue.toString();
|
|
extraData.put(name, value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// try object first
|
|
CardView needCard = null;
|
|
GamePanel gamePanel = MageFrame.getGame(this.gameId);
|
|
if (gamePanel != null) {
|
|
try {
|
|
UUID needObjectId = UUID.fromString(extraData.getOrDefault("object_id", ""));
|
|
needCard = gamePanel.getLastGameData().findCard(needObjectId);
|
|
} catch (IllegalArgumentException ignore) {
|
|
}
|
|
}
|
|
|
|
String cardName = e.getDescription().substring(1);
|
|
String alternativeName = CardUtil.urlDecode(extraData.getOrDefault("alternative_name", ""));
|
|
if (!alternativeName.isEmpty()) {
|
|
cardName = alternativeName;
|
|
}
|
|
|
|
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;
|
|
|
|
// if no game object found then show default card
|
|
if (cardView == null) {
|
|
CardInfo card = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null);
|
|
if (card != null) {
|
|
cardView = new CardView(card.createMockCard());
|
|
}
|
|
}
|
|
|
|
// plane
|
|
if (cardView == null) {
|
|
Plane plane = Plane.createPlaneByFullName(cardName);
|
|
if (plane != null) {
|
|
cardView = new CardView(new PlaneView(plane, null));
|
|
}
|
|
}
|
|
|
|
// TODO: add other objects like dungeon, emblem, commander
|
|
|
|
if (cardView != null) {
|
|
cardInfo.init(cardView, this.bigCard, this.gameId);
|
|
cardInfo.setTooltipDelay(CHAT_TOOLTIP_DELAY_MS);
|
|
cardInfo.setPopupAutoLocationMode(TransferData.PopupAutoLocationMode.PUT_NEAR_MOUSE_POSITION);
|
|
SwingUtilities.invokeLater(() -> {
|
|
cardInfo.onMouseEntered(MouseInfo.getPointerInfo().getLocation());
|
|
cardInfo.onMouseMoved(MouseInfo.getPointerInfo().getLocation());
|
|
});
|
|
}
|
|
}
|
|
|
|
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
|
|
SwingUtilities.invokeLater(() -> {
|
|
cardInfo.onMouseWheel(MouseInfo.getPointerInfo().getLocation());
|
|
});
|
|
}
|
|
|
|
if (e.getEventType() == HyperlinkEvent.EventType.EXITED) {
|
|
urlHighlightDisable();
|
|
|
|
SwingUtilities.invokeLater(() -> {
|
|
cardInfo.onMouseExited();
|
|
});
|
|
}
|
|
}));
|
|
|
|
addMouseListener(new MouseAdapter() {
|
|
// TODO: add popup mouse wheel here?
|
|
@Override
|
|
public void mouseExited(MouseEvent e) {
|
|
cardInfo.onMouseExited();
|
|
setCursorToDefault();
|
|
}
|
|
});
|
|
}
|
|
|
|
private void setCursorToDefault() {
|
|
Window parent = SwingUtilities.windowForComponent(this);
|
|
if (parent != null) {
|
|
parent.setCursor(Cursor.getDefaultCursor());
|
|
}
|
|
}
|
|
|
|
private void setCursorToHand() {
|
|
Window parent = SwingUtilities.windowForComponent(this);
|
|
if (parent != null) {
|
|
parent.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;
|
|
}
|
|
setCursorToDefault();
|
|
}
|
|
|
|
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
|
|
super.setText("");
|
|
append(text);
|
|
}
|
|
|
|
public void append(String text) {
|
|
try {
|
|
if (hyperlinkEnabled) {
|
|
text = GameLog.injectPopupSupport(text);
|
|
}
|
|
kit.insertHTML(doc, doc.getLength(), text, 0, 0, null);
|
|
int len = getDocument().getLength();
|
|
setCaretPosition(len);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
public void enableHyperlinksAndCardPopups() {
|
|
if (this.isEditable()) {
|
|
throw new IllegalStateException("Wrong code usage: hyper links works with non-editable components");
|
|
}
|
|
|
|
hyperlinkEnabled = true;
|
|
addHyperlinkHandlers();
|
|
}
|
|
}
|