package mage.client.util.gui; import mage.client.MageFrame; import mage.client.components.MageComponents; import mage.client.dialog.PreferencesDialog; import mage.client.table.PlayersChatPanel; import mage.client.util.GUISizeHelper; import mage.constants.*; import mage.view.CardView; import mage.view.CounterView; import mage.view.PermanentView; import net.java.truevfs.access.TFile; import org.apache.log4j.Logger; import org.jdesktop.swingx.JXPanel; import org.mage.card.arcane.ManaSymbols; import org.mage.card.arcane.UI; import org.mage.plugins.card.utils.CardImageUtils; import javax.swing.*; import java.awt.*; import java.util.List; import java.util.*; import java.util.stream.Collectors; import static mage.client.dialog.PreferencesDialog.KEY_MAGE_PANEL_LAST_SIZE; /** * Helper class for GUI * * @author JayDi85 */ public final class GuiDisplayUtil { private static final Logger logger = Logger.getLogger(GuiDisplayUtil.class); private static final Font cardNameFont = new Font("Calibri", Font.BOLD, 15); private static final Insets DEFAULT_INSETS = new Insets(0, 0, 70, 25); private static final Insets COMPONENT_INSETS = new Insets(0, 0, 40, 40); public static class TextLines { private int basicTextLength; private java.util.List lines; public int getBasicTextLength() { return basicTextLength; } public void setBasicTextLength(int basicTextLength) { this.basicTextLength = basicTextLength; } public List getLines() { return lines; } public void setLines(java.util.List lines) { this.lines = lines; } } public static void restoreDividerLocations(Rectangle bounds, String lastDividerLocation, JComponent component) { String currentBounds = Double.toString(bounds.getWidth()) + 'x' + bounds.getHeight(); String savedBounds = PreferencesDialog.getCachedValue(KEY_MAGE_PANEL_LAST_SIZE, null); // use divider positions only if screen size is the same as it was the time the settings were saved if (savedBounds != null && savedBounds.equals(currentBounds)) { if (lastDividerLocation != null && component != null) { if (component instanceof JSplitPane) { JSplitPane jSplitPane = (JSplitPane) component; jSplitPane.setDividerLocation(Integer.parseInt(lastDividerLocation)); } if (component instanceof PlayersChatPanel) { PlayersChatPanel playerChatPanel = (PlayersChatPanel) component; playerChatPanel.setSplitDividerLocation(Integer.parseInt(lastDividerLocation)); } } } } public static void saveCurrentBoundsToPrefs() { Rectangle rec = MageFrame.getDesktop().getBounds(); String currentBounds = Double.toString(rec.getWidth()) + 'x' + rec.getHeight(); PreferencesDialog.saveValue(KEY_MAGE_PANEL_LAST_SIZE, currentBounds); } public static void saveDividerLocationToPrefs(String dividerPrefKey, int position) { PreferencesDialog.saveValue(dividerPrefKey, Integer.toString(position)); } public static JXPanel getDescription(CardView card, int width, int height) { JXPanel descriptionPanel = new JXPanel(); //descriptionPanel.setAlpha(.8f); descriptionPanel.setBounds(0, 0, width, height); descriptionPanel.setVisible(false); descriptionPanel.setLayout(null); JButton j = new JButton(""); j.setBounds(0, 0, width, height); j.setBackground(Color.black); j.setLayout(null); JLabel cardText = new JLabel(); cardText.setBounds(5, 5, width - 10, height - 10); cardText.setForeground(Color.white); cardText.setFont(cardNameFont); cardText.setVerticalAlignment(SwingConstants.TOP); j.add(cardText); TextLines textLines = GuiDisplayUtil.getTextLinesfromCardView(card); cardText.setText(getRulesFromCardView(card, textLines).toString()); descriptionPanel.add(j); return descriptionPanel; } public static String cleanString(String in) { StringBuilder out = new StringBuilder(); char c; for (int i = 0; i < in.length(); i++) { c = in.charAt(i); if (c == ' ' || c == '-') { out.append('_'); } else if (Character.isLetterOrDigit(c)) { out.append(c); } } return out.toString().toLowerCase(Locale.ENGLISH); } public static void keepComponentInsideScreen(int centerX, int centerY, Component component) { Dimension screenDim = component.getToolkit().getScreenSize(); GraphicsConfiguration g = component.getGraphicsConfiguration(); if (g != null) { Insets insets = component.getToolkit().getScreenInsets(g); // no usable space like toolbar boolean setLocation = false; if (centerX + component.getWidth() > screenDim.width - insets.right) { centerX = (screenDim.width - insets.right) - component.getWidth(); setLocation = true; } else if (centerX < insets.left) { centerX = insets.left; setLocation = true; } if (centerY + component.getHeight() > screenDim.height - insets.bottom) { centerY = (screenDim.height - insets.bottom) - component.getHeight(); setLocation = true; } else if (centerY < insets.top) { centerY = insets.top; setLocation = true; } if (setLocation) { component.setLocation(centerX, centerY); } } else { System.out.println("GuiDisplayUtil::keepComponentInsideScreen -> no GraphicsConfiguration"); } } static final int OVERLAP_LIMIT = 10; public static void keepComponentInsideFrame(int centerX, int centerY, Component component) { Rectangle frameRec = MageFrame.getInstance().getBounds(); boolean setLocation = false; if (component.getX() > (frameRec.width - OVERLAP_LIMIT)) { setLocation = true; } if (component.getY() > (frameRec.height - OVERLAP_LIMIT)) { setLocation = true; } if (setLocation) { component.setLocation(centerX, centerY); } } public static Point keepComponentInsideParent(Point l, Point parentPoint, Component c, Component parent) { int dx = parentPoint.x + parent.getWidth() - DEFAULT_INSETS.right - COMPONENT_INSETS.right; if (l.x + c.getWidth() > dx) { l.x = dx - c.getWidth(); } int dy = parentPoint.y + parent.getHeight() - DEFAULT_INSETS.bottom - COMPONENT_INSETS.bottom; if (l.y + c.getHeight() > dy) { l.y = Math.max(10, dy - c.getHeight()); } return l; } public static TextLines getTextLinesfromCardView(CardView card) { TextLines textLines = new TextLines(); // rules textLines.setLines(new ArrayList<>(card.getRules())); for (String rule : card.getRules()) { textLines.setBasicTextLength(textLines.getBasicTextLength() + rule.length()); } // counters if (card.getMageObjectType().canHaveCounters()) { java.util.List counters = new ArrayList<>(); if (card.getCounters() != null) { counters.addAll(card.getCounters()); } if (!counters.isEmpty()) { StringBuilder sb = new StringBuilder(); int index = 0; for (CounterView counter : counters) { if (counter.getCount() > 0) { if (index == 0) { sb.append("Counters: "); } else { sb.append(", "); } sb.append(counter.getCount()).append(" x ").append(counter.getName()).append(""); index++; } } textLines.getLines().add(sb.toString()); textLines.setBasicTextLength(textLines.getBasicTextLength() + 50); } } // damage if (card.getMageObjectType().isPermanent() && card instanceof PermanentView) { int damage = ((PermanentView) card).getDamage(); if (damage > 0) { textLines.getLines().add("Damage dealt: " + damage + ""); textLines.setBasicTextLength(textLines.getBasicTextLength() + 50); } } return textLines; } public static String getHintIconHtml(String iconName, int symbolSize) { return "" + iconName + ""; } public static StringBuilder getRulesFromCardView(CardView card, TextLines textLines) { String manaCost = card.getManaCostStr(); String castingCost = UI.getDisplayManaCost(manaCost); castingCost = ManaSymbols.replaceSymbolsWithHTML(castingCost, ManaSymbols.Type.TOOLTIP); int symbolCount = 0; int offset = 0; while ((offset = castingCost.indexOf(""); buffer.append(""); buffer.append("
"); buffer.append(card.getDisplayName()); if (card.isGameObject()) { buffer.append(" [").append(card.getId().toString(), 0, 3).append(']'); } buffer.append(""); if (!card.isSplitCard()) { buffer.append(castingCost); } buffer.append("
"); buffer.append("
"); String imageSize = " width=" + fontSize + " height=" + fontSize + '>'; if (card.getColor().isWhite()) { buffer.append("W"); String rarity; if (card.getRarity() == null) { rarity = Rarity.COMMON.getCode(); buffer.append(""); } else { switch (card.getRarity()) { case RARE: buffer.append(""); break; case UNCOMMON: buffer.append(""); break; case COMMON: buffer.append(""); break; case MYTHIC: buffer.append(""); break; } rarity = card.getRarity().getCode(); } if (card.getExpansionSetCode() != null) { buffer.append(ManaSymbols.replaceSetCodeWithHTML(card.getExpansionSetCode().toUpperCase(Locale.ENGLISH), rarity, GUISizeHelper.symbolTooltipSize)); } buffer.append("
"); String pt; if (card.isCreature()) { pt = card.getPower() + '/' + card.getToughness(); } else if (card.showPT()) { // Vehicles have a P/T set to display even when not creatures. pt = "(" + card.getPower() + '/' + card.getToughness() + ")"; } else if (card.isPlaneswalker()) { pt = card.getLoyalty(); } else if (card.isBattle()) { pt = card.getDefense(); } else { pt = ""; } buffer.append(""); buffer.append("
"); buffer.append(pt).append(""); if (!card.isControlledByOwner()) { if (card instanceof PermanentView) { buffer.append('[').append(((PermanentView) card).getNameOwner()).append("] "); } else { buffer.append("[only controlled] "); } } if (card instanceof PermanentView && ((PermanentView) card).isAttachedToDifferentlyControlledPermanent()) { buffer.append('(').append(((PermanentView) card).getNameController()).append(") "); } if (card.getMageObjectType() != MageObjectType.NULL) { buffer.append(card.getMageObjectType().toString()); } buffer.append("
"); // split card rules shows up by parts, so no needs to duplicate it later (only dynamic abilities must be shown) Set duplicatedRules = new HashSet<>(); StringBuilder rule = new StringBuilder("
"); if (card.isSplitCard()) { rule.append(""); rule.append("
"); rule.append(card.getLeftSplitName()); rule.append(""); rule.append(card.getLeftSplitCostsStr()); rule.append("
"); for (String ruling : card.getLeftSplitRules()) { if (ruling != null && !ruling.replace(".", "").trim().isEmpty()) { // split names must be replaced duplicatedRules.add(ruling); rule.append("

").append(replaceNamesInRule(ruling, card.getLeftSplitName())).append("

"); } } rule.append(""); rule.append("
"); rule.append(card.getRightSplitName()); rule.append(""); rule.append(card.getRightSplitCostsStr()); rule.append("
"); for (String ruling : card.getRightSplitRules()) { if (ruling != null && !ruling.replace(".", "").trim().isEmpty()) { // split names must be replaced duplicatedRules.add(ruling); rule.append("

").append(replaceNamesInRule(ruling, card.getRightSplitName())).append("

"); } } } if (!textLines.getLines().isEmpty()) { for (String textLine : textLines.getLines()) { if (textLine != null && !textLine.replace(".", "").trim().isEmpty()) { if (duplicatedRules.contains(textLine)) { continue; } rule.append("

").append(textLine).append("

"); } } } String legal = rule.toString(); if (!legal.isEmpty()) { legal = replaceNamesInRule(legal, card.getDisplayName()); // must show real display name (e.g. split part, not original card) buffer.append(ManaSymbols.replaceSymbolsWithHTML(legal, ManaSymbols.Type.TOOLTIP)); } Zone zone = card.getZone(); if (zone != null) { buffer.append("

Card Zone: ").append(zone).append("

"); } // missing image info in card popup boolean displayFullImagePath = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SHOW_FULL_IMAGE_PATH, "false").equals("true"); if (displayFullImagePath) { String imageFile = CardImageUtils.buildImagePathToCardView(card); if (imageFile.startsWith("ERROR") || !(new TFile(imageFile).exists())) { buffer.append("

Missing image: ").append(imageFile).append("

"); } } buffer.append("
"); return buffer; } private static String replaceNamesInRule(String rule, String cardName) { return rule.replaceAll("\\{this\\}", cardName.isEmpty() ? "this" : cardName); } private static String getResourcePath(String image) { return GuiDisplayUtil.class.getClassLoader().getResource(image).toString(); } private static String getTypes(CardView card) { String types = ""; for (SuperType superType : card.getSuperTypes()) { types += superType.toString() + ' '; } for (CardType cardType : card.getCardTypes()) { types += cardType.toString() + ' '; } if (!card.getSubTypes().isEmpty()) { types += "- "; } for (SubType subType : card.getSubTypes()) { types += subType + " "; } return types.trim(); } public static void setPanelEnabled(JPanel panel, Boolean isEnabled) { panel.setEnabled(isEnabled); Component[] components = panel.getComponents(); for (Component component : components) { if (component instanceof JPanel) { setPanelEnabled((JPanel) component, isEnabled); } component.setEnabled(isEnabled); } } /** * Fast refresh of GUI settings after theme change. * Warning, use it for: * - startup (before any components create) * - preview only (for settings dialog) * Existing hidden components can miss new settings (will render with old colors), so only app restart can help. */ public static void refreshThemeSettings() { // apply Nimbus's look and fill // possible settings: // https://docs.oracle.com/en%2Fjava%2Fjavase%2F17%2Fdocs%2Fapi%2F%2F/java.desktop/javax/swing/plaf/nimbus/doc-files/properties.html // enable nimbus try { UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel"); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) { logger.error("Can't apply current theme: " + PreferencesDialog.getCurrentTheme() + " - " + e, e); } // enable new style from current theme //UIManager.put("desktop", new Color(0, 0, 0, 0)); UIManager.put("nimbusBlueGrey", PreferencesDialog.getCurrentTheme().getNimbusBlueGrey()); // buttons, scrollbar background, disabled inputs UIManager.put("control", PreferencesDialog.getCurrentTheme().getControl()); // window bg UIManager.put("nimbusLightBackground", PreferencesDialog.getCurrentTheme().getNimbusLightBackground()); // inputs, table rows UIManager.put("info", PreferencesDialog.getCurrentTheme().getInfo()); // tooltips UIManager.put("nimbusBase", PreferencesDialog.getCurrentTheme().getNimbusBase()); // title bars, scrollbar foreground //UIManager.put("nimbusDisabledText", Color.green); // TODO: improve disabled color //UIManager.put("Table.rowHeight", GUISizeHelper.tableRowHeight); // for debug only - print full LaF params if (false) { System.out.println(""); System.out.println(UIManager.getLookAndFeel().getDefaults().size()); String s = UIManager.getLookAndFeel().getDefaults().keySet().stream() .map(key -> key + " = " + UIManager.getLookAndFeel().getDefaults().get(key)) .sorted() .collect(Collectors.joining("\n")); System.out.println(""); System.out.println(s); } // re-render existing components with new style for (Frame frame : Frame.getFrames()) { refreshLookAndFill(frame); } // re-render hidden/shared components Arrays.stream(MageComponents.values()).forEach(compName -> { try { Component comp = MageFrame.getUI().getComponent(compName, false); if (comp != null) { SwingUtilities.updateComponentTreeUI(comp); } } catch (InterruptedException ignore) { } }); } private static void refreshLookAndFill(Window window) { for (Window childWindow : window.getOwnedWindows()) { refreshLookAndFill(childWindow); } SwingUtilities.updateComponentTreeUI(window); } }