diff --git a/Mage.Client/src/main/java/mage/client/MageFrame.java b/Mage.Client/src/main/java/mage/client/MageFrame.java index f012e99bd95..522d45505c0 100644 --- a/Mage.Client/src/main/java/mage/client/MageFrame.java +++ b/Mage.Client/src/main/java/mage/client/MageFrame.java @@ -108,7 +108,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private final ConnectDialog connectDialog; private final ErrorDialog errorDialog; private static CallbackClient callbackClient; - private static final Preferences PREFS = Preferences.userNodeForPackage(MageFrame.class); + private static Preferences PREFS = null; private final JPanel fakeTopPanel; private WhatsNewDialog whatsNewDialog; // can be null private JLabel title; @@ -142,14 +142,16 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { private static long startTime; - /** - * @return the session - */ public static JDesktopPane getDesktop() { return desktopPane; } + // TODO: migrate to own preferences like MageSettings and add ready-only and fresh install modes support + // current workaround - delete or rename whole registry tree in HKEY_CURRENT_USER\Software\JavaSoft\Prefs\mage\client public static Preferences getPreferences() { + if (PREFS == null) { + PREFS = Preferences.userNodeForPackage(MageFrame.class); + } return PREFS; } @@ -885,7 +887,7 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { } public boolean autoConnect() { - boolean autoConnectParamValue = startUser != null || Boolean.parseBoolean(PREFS.get("autoConnect", "false")); + boolean autoConnectParamValue = startUser != null || Boolean.parseBoolean(MageFrame.getPreferences().get("autoConnect", "false")); boolean status = false; if (autoConnectParamValue) { LOGGER.info("Auto-connecting to " + MagePreferences.getServerAddress()); @@ -900,11 +902,11 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { int port = MagePreferences.getLastServerPort(); String userName = MagePreferences.getLastServerUser(); String password = MagePreferences.getLastServerPassword(); - String proxyServer = PREFS.get("proxyAddress", ""); - int proxyPort = Integer.parseInt(PREFS.get("proxyPort", "0")); - ProxyType proxyType = ProxyType.valueByText(PREFS.get("proxyType", "None")); - String proxyUsername = PREFS.get("proxyUsername", ""); - String proxyPassword = PREFS.get("proxyPassword", ""); + String proxyServer = MageFrame.getPreferences().get("proxyAddress", ""); + int proxyPort = Integer.parseInt(MageFrame.getPreferences().get("proxyPort", "0")); + ProxyType proxyType = ProxyType.valueByText(MageFrame.getPreferences().get("proxyType", "None")); + String proxyUsername = MageFrame.getPreferences().get("proxyUsername", ""); + String proxyPassword = MageFrame.getPreferences().get("proxyPassword", ""); setCursor(new Cursor(Cursor.WAIT_CURSOR)); currentConnection = new Connection(); currentConnection.setUsername(userName); @@ -1147,16 +1149,16 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(desktopPane, javax.swing.GroupLayout.DEFAULT_SIZE, 838, Short.MAX_VALUE) - .addComponent(mageToolbar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(desktopPane, javax.swing.GroupLayout.DEFAULT_SIZE, 838, Short.MAX_VALUE) + .addComponent(mageToolbar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) ); layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(mageToolbar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(2, 2, 2) - .addComponent(desktopPane, javax.swing.GroupLayout.DEFAULT_SIZE, 145, Short.MAX_VALUE)) + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(mageToolbar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(2, 2, 2) + .addComponent(desktopPane, javax.swing.GroupLayout.DEFAULT_SIZE, 145, Short.MAX_VALUE)) ); pack(); @@ -1559,6 +1561,35 @@ public class MageFrame extends javax.swing.JFrame implements MageClient { splash.update(); } } + + // auto-update user settings here + // use case examples: + // - delete outdated data + // - migrate to new files formats + // - etc + int settingsVersion = PreferencesDialog.getCachedValue(PreferencesDialog.KEY_SETTINGS_VERSION, 0); + if (settingsVersion == 0) { + // fresh install or first run after 2024-08-14 + // find best GUI size settings due screen resolution and DPI + LOGGER.info("settings: it's a first run, trying to apply GUI size settings"); + + int screenDPI = Toolkit.getDefaultToolkit().getScreenResolution(); + int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height; + LOGGER.info(String.format("settings: screen DPI - %d, screen height - %d", screenDPI, screenHeight)); + + // find preset for + String preset = PreferencesDialog.getDefaultSizeSettings().findBestPreset(screenDPI, screenHeight); + if (preset != null) { + LOGGER.info("settings: selected preset " + preset); + PreferencesDialog.getDefaultSizeSettings().applyPreset(preset); + } else { + LOGGER.info("settings: WARNING, can't find compatible preset, use Preferences - GUI Size to setup your app"); + } + + PreferencesDialog.saveValue(PreferencesDialog.KEY_SETTINGS_VERSION, String.valueOf(1)); + } + + // FIRST GUI CALL (create main window with all prepared frames, dialogs, etc) try { instance = new MageFrame(); } catch (Throwable e) { diff --git a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java index 2aad65268bf..25144c8f093 100644 --- a/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java +++ b/Mage.Client/src/main/java/mage/client/deckeditor/CardSelector.java @@ -160,9 +160,6 @@ public class CardSelector extends javax.swing.JPanel implements ComponentListene jToggleCardView.setToolTipText(jToggleCardView.getToolTipText() + " (works only up to " + CardGrid.MAX_IMAGES + " cards)."); } - /** - * Free all references - */ public void cleanUp() { this.cardGrid.clear(); this.mainModel.clear(); diff --git a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java index 6ef97cb4920..7124fa88729 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PreferencesDialog.java @@ -34,6 +34,7 @@ import java.util.List; import java.util.*; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; +import java.util.stream.Collectors; import static mage.client.constants.Constants.AUTO_TARGET_NON_FEEL_BAD; import static mage.constants.Constants.*; @@ -323,6 +324,9 @@ public class PreferencesDialog extends javax.swing.JDialog { public static final String KEY_CONNECT_AUTO_CONNECT = "autoConnect"; public static final String KEY_CONNECT_FLAG = "connectFlag"; + // auto-update settings on first run + public static final String KEY_SETTINGS_VERSION = "settingsVersion"; + private static final Map CACHE = new HashMap<>(); public static final String OPEN_CONNECTION_TAB = "Open-Connection-Tab"; @@ -344,12 +348,119 @@ public class PreferencesDialog extends javax.swing.JDialog { private static boolean isLoadingSizes = false; private static boolean isLoadingTheme = false; - // GUI size default settings - private final Map defaultSizeSettings = new LinkedHashMap<>(); + // GUI default size settings + private static final DefaultSizeSettings defaultSizeSettings = new DefaultSizeSettings(); - static class DefaultSizeSetting { - String name; - Map values; // settings key, value + public static class DefaultSizeSettings { + + private final List settingKeys = new ArrayList<>(); + private final Map> presetValues = new LinkedHashMap<>(); + private final Map presetMinHeights = new LinkedHashMap<>(); // preset name, minimum screen height + + public DefaultSizeSettings() { + // prepare default size settings + // warning, make sure it use same order as createSizeSetting below + settingKeys.add(KEY_GUI_DIALOG_FONT_SIZE); + settingKeys.add(KEY_GUI_CHAT_FONT_SIZE); + settingKeys.add(KEY_GUI_CARD_EDITOR_SIZE); + settingKeys.add(KEY_GUI_TOOLTIP_SIZE); + // + settingKeys.add(KEY_GUI_PLAYER_PANEL_SIZE); + settingKeys.add(KEY_GUI_CARD_BATTLEFIELD_SIZE); + settingKeys.add(KEY_GUI_CARD_HAND_SIZE); + settingKeys.add(KEY_GUI_CARD_OTHER_ZONES_SIZE); + + // x6 groups allowed here + // minimum system requirements: screen height > 750 + // lower settings possible, but it's hard to use due low text and image quality + presetMinHeights.put("1366 x 768", 768); + presetValues.put("1366 x 768", Arrays.asList( + 10, 15, 17, 15, + 10, 22, 14, 13 + )); + presetMinHeights.put("1920 x 1080", 1080); + presetValues.put("1920 x 1080", Arrays.asList( + 17, 18, 23, 20, + 14, 30, 22, 21 + )); + presetMinHeights.put("2560 x 1440", 1440); + presetValues.put("2560 x 1440", Arrays.asList( + 23, 25, 35, 31, + 18, 42, 31, 28 + )); + presetMinHeights.put("3840 x 2160", 2160); + presetValues.put("3840 x 2160", Arrays.asList( + 34, 37, 50, 55, + 27, 64, 50, 44 + )); + } + + public List getAllPresets() { + return new ArrayList<>(presetValues.keySet()); + } + + public List getPresetValues(String presetName) { + List res = presetValues.getOrDefault(presetName, null); + if (res == null) { + throw new IllegalArgumentException("Wrong code usage: unknown size settings preset name " + presetName); + } + return res; + } + + public String findBestPreset() { + int screenDPI = Toolkit.getDefaultToolkit().getScreenResolution(); + int screenHeight = Toolkit.getDefaultToolkit().getScreenSize().height; + return findBestPreset(screenDPI, screenHeight); + } + + public String findBestPreset(int screenDPI, int screenHeight) { + // TODO: add java HiDPI monitors support here (or do not use preset on gui scale?) + // TODO: test with windows DPI settings + // TODO: test with windows compatibility settings https://github.com/magefree/mage/issues/969#issuecomment-2016809163 + // TODO: test with java 9 scale command line params https://github.com/magefree/mage/issues/969#issuecomment-671055642 + + // find min preset (for too small screens) + String minPossiblePreset = null; + int minPossibleRes = Integer.MAX_VALUE; + for (String preset : presetMinHeights.keySet()) { + int res = presetMinHeights.get(preset); + if (res < minPossibleRes) { + minPossibleRes = res; + minPossiblePreset = preset; + } + } + if (minPossiblePreset == null) { + throw new IllegalArgumentException("must found min preset all the time"); + } + + // find max preset + String maxPossiblePreset = null; + int maxPossibleRes = Integer.MIN_VALUE; + for (String preset : presetMinHeights.keySet()) { + int res = presetMinHeights.get(preset); + if (res <= screenHeight && res > maxPossibleRes) { + maxPossibleRes = res; + maxPossiblePreset = preset; + } + } + + return maxPossiblePreset != null ? maxPossiblePreset : minPossiblePreset; + } + + public String getSettingsKeyByIndex(int index) { + return settingKeys.get(index); + } + + public void applyPreset(String presetName) { + // WARNING, it's apply settings directly to storage and cache, so opened preferences dialog will be outdated + // so usage example: app's starting routine + List values = getPresetValues(presetName); + for (int i = 0; i < values.size(); i++) { + String settingsKey = getSettingsKeyByIndex(i); + int settingsValue = values.get(i); + saveValue(settingsKey, String.valueOf(settingsValue)); + } + } } // GUI size settings @@ -566,7 +677,8 @@ public class PreferencesDialog extends javax.swing.JDialog { addAvatars(); // prepare size table (you can change settings order by new position index) - // warning, if you change default values then make sure calculateGUISizes uses same + // WARNING, if you change default values then make sure calculateGUISizes uses same + // WARNING, make sure DefaultSizeSettings uses same settings keys // App's elements (from position 1) createSizeSetting(1, KEY_GUI_DIALOG_FONT_SIZE, 14, false, "Font in dialogs and menu", "The size of the font of messages, menu, dialogs and other windows"); createSizeSetting(2, KEY_GUI_CHAT_FONT_SIZE, 14, false, "Font in logs and chats", "The size of the font used to display the chat text"); @@ -578,55 +690,40 @@ public class PreferencesDialog extends javax.swing.JDialog { createSizeSetting(10, KEY_GUI_CARD_HAND_SIZE, 14, false, "Size of cards in hand and stack", "The size of the card images in hand and on the stack"); createSizeSetting(11, KEY_GUI_CARD_OTHER_ZONES_SIZE, 14, false, "Size of cards in other zones", "The size of card in other game zone (e.g. graveyard, revealed cards etc.)"); + // protection from wrong keys amount + if (sizeSettings.size() != defaultSizeSettings.presetValues.values().stream().findFirst().get().size()) { + throw new IllegalArgumentException("Wrong code usage: size and default size settings must contains same records"); + } else { + // protection from wrong keys order + List keys = new ArrayList<>(sizeSettings.keySet()); + for (int i = 0; i < keys.size(); i++) { + if (!defaultSizeSettings.getSettingsKeyByIndex(i).equals(keys.get(i))) { + throw new IllegalArgumentException("Wrong code usage: size and default size settings must use same ordered keys"); + } + } + } + // hide unused controls hideUnusedSizeSettings(); // prepare default size settings - // warning, make sure it use same order as createSizeSetting above - List codes = new ArrayList<>(); - codes.add(KEY_GUI_DIALOG_FONT_SIZE); - codes.add(KEY_GUI_CHAT_FONT_SIZE); - codes.add(KEY_GUI_CARD_EDITOR_SIZE); - codes.add(KEY_GUI_TOOLTIP_SIZE); - // - codes.add(KEY_GUI_PLAYER_PANEL_SIZE); - codes.add(KEY_GUI_CARD_BATTLEFIELD_SIZE); - codes.add(KEY_GUI_CARD_HAND_SIZE); - codes.add(KEY_GUI_CARD_OTHER_ZONES_SIZE); - - // x6 groups allowed here - // minimum system requirements: screen height > 750 - // lower settings possible, but it's hard to use due low text and image quality - Map> sizes = new LinkedHashMap<>(); - sizes.put("1366 x 768", Arrays.asList( - 10, 15, 17, 15, - 10, 22, 14, 13 - )); - sizes.put("1920 x 1080", Arrays.asList( - 17, 18, 23, 20, - 14, 30, 22, 21 - )); - sizes.put("2560 x 1440", Arrays.asList( - 23, 25, 35, 31, - 18, 42, 31, 28 - )); - sizes.put("3840 x 2160", Arrays.asList( - 34, 37, 50, 55, - 27, 64, 50, 44 - )); - // set new settings on button clicks int position = 0; - for (String groupName : sizes.keySet()) { + String recommendedPreset = defaultSizeSettings.findBestPreset(); + for (String presetName : defaultSizeSettings.getAllPresets()) { position++; JButton button = GUISizeHelper.getComponentByFieldName(this, "buttonSizeDefault" + position); - button.setText(groupName); + String buttonName = presetName; + if (presetName.equals(recommendedPreset)) { + buttonName += " (recommended)"; + } + button.setText(buttonName); button.addActionListener(e -> { isLoadingSizes = true; try { - List values = sizes.get(groupName); + List values = defaultSizeSettings.getPresetValues(presetName); for (int i = 0; i < values.size(); i++) { - sizeSettings.get(codes.get(i)).slider.setValue(values.get(i)); + sizeSettings.get(defaultSizeSettings.getSettingsKeyByIndex(i)).slider.setValue(values.get(i)); } } finally { isLoadingSizes = false; @@ -3791,9 +3888,8 @@ public class PreferencesDialog extends javax.swing.JDialog { prefs.put(key, value); try { prefs.flush(); - } catch (BackingStoreException ex) { - ex.printStackTrace(); - JOptionPane.showMessageDialog(null, "Error: couldn't save preferences. Please try once again."); + } catch (BackingStoreException e) { + logger.error("Can't save preferences " + key + " due " + e, e); } updateCache(key, value); } @@ -3917,6 +4013,10 @@ public class PreferencesDialog extends javax.swing.JDialog { ); } + public static DefaultSizeSettings getDefaultSizeSettings() { + return defaultSizeSettings; + } + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JScrollPane avatarPane; private javax.swing.JPanel avatarPanel; diff --git a/Mage.Client/src/test/java/mage/client/preference/MagePreferencesTest.java b/Mage.Client/src/test/java/mage/client/preference/MagePreferencesTest.java index 99a3daa8a98..7645df7660d 100644 --- a/Mage.Client/src/test/java/mage/client/preference/MagePreferencesTest.java +++ b/Mage.Client/src/test/java/mage/client/preference/MagePreferencesTest.java @@ -1,6 +1,8 @@ package mage.client.preference; +import mage.client.dialog.PreferencesDialog; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -43,4 +45,37 @@ public class MagePreferencesTest { assertFalse(MagePreferences.ignoreList("test.com.xx").contains("test")); assertFalse(MagePreferences.ignoreList("test.com.xx").contains("lul")); } + + @Test + public void testGuiSizeRecommends() { + // possible presets + // 1366 x 768 + // 1920 x 1080 + // 2560 x 1440 + // 3840 x 2160 + PreferencesDialog.DefaultSizeSettings defaultSizeSettings = PreferencesDialog.getDefaultSizeSettings(); + + String needPreset = "1366 x 768"; + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 0)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 100)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 767)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 768)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 769)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 1079)); + + needPreset = "1920 x 1080"; + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 1080)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 1081)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 1439)); + + needPreset = "2560 x 1440"; + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 1440)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 1441)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 2159)); + + needPreset = "3840 x 2160"; + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 2160)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 5000)); + Assert.assertEquals(needPreset, defaultSizeSettings.findBestPreset(96, 5000)); + } } \ No newline at end of file