From 3b4a9d9bc146e1ba0d00d6f25cda99f4250bd73b Mon Sep 17 00:00:00 2001 From: Tres Finocchiaro Date: Tue, 28 May 2024 14:53:41 -0400 Subject: [PATCH] Improve Linux HiDPI Support (#1266) Various HiDPI improvements --- src/qz/ui/LogDialog.java | 3 +- src/qz/ui/component/DisplayTable.java | 17 +++++++-- src/qz/ui/component/IconCache.java | 53 +++++++++++++++++++++++++-- src/qz/utils/SystemUtilities.java | 51 ++++++++++++++++++-------- 4 files changed, 100 insertions(+), 24 deletions(-) diff --git a/src/qz/ui/LogDialog.java b/src/qz/ui/LogDialog.java index 7358ebab4..d13c359c0 100644 --- a/src/qz/ui/LogDialog.java +++ b/src/qz/ui/LogDialog.java @@ -40,6 +40,7 @@ public LogDialog(JMenuItem caller, IconCache iconCache) { } public void initComponents() { + int defaultFontSize = new JLabel().getFont().getSize(); LinkLabel logDirLabel = new LinkLabel(FileUtilities.USER_DIR + File.separator); logDirLabel.setLinkLocation(new File(FileUtilities.USER_DIR + File.separator)); setHeader(logDirLabel); @@ -58,7 +59,7 @@ public void flush() { logArea.setEditable(false); logArea.setLineWrap(true); logArea.setWrapStyleWord(true); - logArea.setFont(new Font("", Font.PLAIN, 12)); //force fallback font for character support + logArea.setFont(new Font("", Font.PLAIN, defaultFontSize)); //force fallback font for character support // TODO: Fix button panel resizing issues clearButton = addPanelButton("Clear", IconCache.Icon.DELETE_ICON, KeyEvent.VK_L); diff --git a/src/qz/ui/component/DisplayTable.java b/src/qz/ui/component/DisplayTable.java index c197889f5..e5ab11fa9 100644 --- a/src/qz/ui/component/DisplayTable.java +++ b/src/qz/ui/component/DisplayTable.java @@ -1,8 +1,9 @@ package qz.ui.component; +import qz.utils.SystemUtilities; + import javax.swing.*; import javax.swing.table.DefaultTableModel; -import java.awt.*; /** * Displays information in a JTable @@ -28,6 +29,12 @@ private void initComponents() { model.addColumn("Field"); model.addColumn("Value"); + // Fix Linux row height + int origHeight = getRowHeight(); + if(SystemUtilities.getWindowScaleFactor() != 1) { + setRowHeight((int)(origHeight * SystemUtilities.getWindowScaleFactor())); + } + getTableHeader().setReorderingAllowed(false); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); setRowSelectionAllowed(true); @@ -55,9 +62,11 @@ public void autoSize(int rows, int columns) { model.addRow(new Object[columns]); } - int normalWidth = (int)getPreferredScrollableViewportSize().getWidth(); - int autoHeight = (int)getPreferredSize().getHeight(); - setPreferredScrollableViewportSize(new Dimension(normalWidth, autoHeight)); + setPreferredScrollableViewportSize( + SystemUtilities.scaleWindowDimension( + getPreferredScrollableViewportSize().getWidth(), + getPreferredSize().getHeight()) + ); setFillsViewportHeight(true); refreshComponents(); } diff --git a/src/qz/ui/component/IconCache.java b/src/qz/ui/component/IconCache.java index 0fed3fe78..659f8d103 100644 --- a/src/qz/ui/component/IconCache.java +++ b/src/qz/ui/component/IconCache.java @@ -75,7 +75,7 @@ public enum Icon { BANNER_ICON("qz-banner.png"); private boolean padded = false; - final String[] fileNames; + private String[] fileNames; /** * Default constructor @@ -133,6 +133,11 @@ public String getId() { } public String[] getIds() { return fileNames; } + + private void addId(String id) { + fileNames = Arrays.copyOf(fileNames, fileNames.length + 1); + fileNames[fileNames.length - 1] = id; + } } private final HashMap imageIcons; @@ -161,6 +166,32 @@ private void buildIconCache() { images.put(id, bi); } } + // Stash scaled 2x, 3x versions if missing + int maxScale = 3; + for(Icon i : Icon.values()) { + // Assume single-resource icons are lonely and want scaled instances + if (i.fileNames.length != 1) { + continue; + } + for(int scale = 2; scale <= maxScale; scale++) { + BufferedImage bi = images.get(i.getId()); + // Assume square icon (filename is derived from width only) + String id = i.getId(); + int loc = id.lastIndexOf("."); + if(loc == -1) { + continue; + } + String name = id.substring(0, loc); + String ext = id.substring(loc + 1); + String newSize = String.format("%s-%s.%s", name, bi.getWidth() * scale, ext); + if (!images.containsKey(newSize)) { + i.addId(newSize); + BufferedImage newBi = clone(bi, scale); + imageIcons.put(newSize, new ImageIcon(newBi)); + images.put(newSize, newBi); + } + } + } } /** @@ -170,10 +201,20 @@ private void buildIconCache() { * @return the ImageIcon in the cache */ public ImageIcon getIcon(Icon i) { - return imageIcons.get(i.getId()); + return SystemUtilities.getWindowScaleFactor() != 1 ? + getIcon(i, true) : imageIcons.get(i.getId()); + } + + private ImageIcon getIcon(Icon i, boolean inferScale) { + if(!inferScale) { + return imageIcons.get(i.getId()); + } + ImageIcon baseIcon = imageIcons.get(i.getId()); + Dimension scaled = SystemUtilities.scaleWindowDimension(baseIcon.getIconWidth(), baseIcon.getIconHeight()); + return imageIcons.get(i.getId(scaled)); } - public ImageIcon getIcon(String id) { + private ImageIcon getIcon(String id) { return imageIcons.get(id); } @@ -287,7 +328,11 @@ public void fixTrayIcons(boolean darkTaskbar) { } public static BufferedImage clone(BufferedImage src) { - Image tmp = src.getScaledInstance(src.getWidth(), src.getHeight(), src.getType()); + return clone(src, 1); + } + + public static BufferedImage clone(BufferedImage src, int scaleFactor) { + Image tmp = src.getScaledInstance(src.getWidth() * scaleFactor, src.getHeight() * scaleFactor, src.getType()); BufferedImage dest = new BufferedImage(tmp.getWidth(null), tmp.getHeight(null), BufferedImage.TYPE_INT_ARGB); Graphics g = dest.createGraphics(); g.drawImage(tmp, 0, 0, null); diff --git a/src/qz/utils/SystemUtilities.java b/src/qz/utils/SystemUtilities.java index d821ad5f6..6bbffccbb 100644 --- a/src/qz/utils/SystemUtilities.java +++ b/src/qz/utils/SystemUtilities.java @@ -55,6 +55,8 @@ public class SystemUtilities { private static final Os OS_TYPE = Os.bestMatch(OS_NAME); private static final Arch JRE_ARCH = Arch.bestMatch(OS_ARCH); private static final Logger log = LogManager.getLogger(TrayManager.class); + + private static double windowScaleFactor = -1; private static final Locale defaultLocale = Locale.getDefault(); static { @@ -473,7 +475,7 @@ public static void centerDialog(Dialog dialog, Point position) { } //adjust for dpi scaling - double dpiScale = getWindowScaleFactor(); + double dpiScale = getWindowScaleFactor(true); if (dpiScale == 0) { log.debug("Invalid window scale value: {}, we'll center on the primary monitor instead", dpiScale); dialog.setLocationRelativeTo(null); @@ -517,21 +519,40 @@ public static boolean isWindowLocationValid(Rectangle window) { * See issues #284, #448 * @return Logical dpi scale as dpi/96 */ - public static double getWindowScaleFactor() { - // MacOS is always 1 - if (isMac()) { - return 1; - } - // Windows/Linux on JDK8 honors scaling - if (Constants.JAVA_VERSION.lessThan(Version.valueOf("11.0.0"))) { - return Toolkit.getDefaultToolkit().getScreenResolution() / 96.0; - } - // Windows on JDK11 is always 1 - if(isWindows()) { - return 1; + private static double getWindowScaleFactor(boolean forceRefresh) { + if(windowScaleFactor == -1 || forceRefresh) { + // MacOS is always 1 + if (isMac()) { + return windowScaleFactor = 1; + } + // Windows/Linux on JDK8 honors scaling + if (Constants.JAVA_VERSION.lessThan(Version.valueOf("11.0.0"))) { + return windowScaleFactor = Toolkit.getDefaultToolkit().getScreenResolution() / 96.0; + } + // Windows on JDK11 is always 1 + if (isWindows()) { + return windowScaleFactor = 1; + } + // Linux/Unix on JDK11 requires JNA calls to Gdk + return windowScaleFactor = UnixUtilities.getScaleFactor(); } - // Linux/Unix on JDK11 requires JNA calls to Gdk - return UnixUtilities.getScaleFactor(); + return windowScaleFactor; + } + + public static double getWindowScaleFactor() { + return getWindowScaleFactor(false); + } + + public static Dimension scaleWindowDimension(Dimension orig) { + return scaleWindowDimension(orig.getWidth(), orig.getHeight()); + } + + public static Dimension scaleWindowDimension(double width, double height) { + double scaleFactor = getWindowScaleFactor(); + return new Dimension( + (int)(width * scaleFactor), + (int)(height * scaleFactor) + ); } /**