All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.swingdpi.plaf.WindowsTweaker Maven / Gradle / Ivy

/*
 * Copyright 2016 the original author or authors.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 * This project is hosted at: https://github.com/lukeu/swing-dpi
 * Comments & collaboration are both welcome.
 */

package com.github.swingdpi.plaf;

import java.awt.Font;
import java.awt.Insets;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;

import com.github.swingdpi.DpiUtils;
import com.github.swingdpi.util.LoopBreakingScaledIcon;

public class WindowsTweaker extends BasicTweaker {

    /**
     * Certain fonts and metrics are pre-scaled, because the PLaF implementation asks Windows to
     * provide them. Therefore we need to 'undo' Window's scaling before applying our desired
     * scale-factor. This value holds the resulting scale-factor to apply to these UI elements.
     */
    protected final float alternateScaleFactor;

    protected final Font optionPaneFont;

    protected final boolean windowsClassic;

    /**
     * UI Defaults starting with these keys are handled by the WindowsIconFactory. The do an
     * instanceof check against some specific private inner classes (such as VistaMenuItemCheckIcon)
     * (rather than something public like IconUIResource) so that limits us: we simply can't
     * override them. It causes infinite loops from methods like getIconWidth().
     * 

* Anyway, it shouldn't be too bad. They're also automatically scaled to the System-default * scaling. So assuming a typical user is trying to match that, they should be the correct * size already, or thereabouts. (NB: The user must log off & back in if they have changed the * system default DPI.) */ private static final String[] PREFIX_TO_NOT_SCALE_ICONS = { "InternalFrame."//, "CheckBox", "Menu.", "MenuItem.", "RadioButton" }; /** * UIDefaults starting with these strings are 'prescaled' and therefore require being scaled by * {@link #alternateScaleFactor}. */ private static final String[] PRESCALED_INTEGER_PREFIXES = { "ScrollBar.", "InternalFrame.", "Menu.", "MenuBar.", "MenuItem.", "CheckBoxMenuItem.", "RadioButtonMenuItem." }; private static final String BUTTON_DASHED_RECT_PREFIX = "Button.dashedRectGap"; public WindowsTweaker(float scaleFactor, boolean classic) { // Windows already scales fonts, scrollbar sizes (etc) according to the system DPI settings. // (the same things which hopefully the heuristics in BasicTweaker manages to locate). super(scaleFactor); alternateScaleFactor = 100f * scaleFactor / DpiUtils.getSystemScaling(); optionPaneFont = UIManager.getFont("OptionPane.font"); windowsClassic = classic; } @Override public void initialTweaks() { super.initialTweaks(); if (doExtraTweaks) { // Java <= 8 uses 0, while Java >= 9 uses 2. This means that menus would jump 20% larger // upon upgrading JDK. We standardise this using the middle value of 1, which also // matches other native menus I found in Windows. I'm not sure why they went with 2. // (Incidentally, this also makes MenuItems the same pixel height as Metal L&F.) UIManager.put("MenuItem.margin", new Insets(1, 1, 1, 1)); // The Menus in a Menu-bar definitely do look better with more spacing between them, // especially since the JDK 8 value of '0' doesn't scale as the font size is increased // and the words look crammed together. Standardise on the Java 9+ values. // (Again this happens to make Windows use the same value that Metal has always used.) UIManager.put("Menu.margin", new Insets(2, 2, 2, 2)); } } @Override public void finalTweaks() { super.finalTweaks(); if (JavaVersion.isDpiAware()) { return; } try { int x = (Integer) UIManager.get(BUTTON_DASHED_RECT_PREFIX + "X"); int y = (Integer) UIManager.get(BUTTON_DASHED_RECT_PREFIX + "Y"); UIManager.put(BUTTON_DASHED_RECT_PREFIX + "Width", x * 2); UIManager.put(BUTTON_DASHED_RECT_PREFIX + "Height", y * 2); } catch (RuntimeException ex) { ex.printStackTrace(); } } @Override public Font modifyFont(Object key, Font original) { String keyString = key.toString(); Font font = maybeSubstituteFont(keyString, original); if (isFontUnscaled(keyString)) { return super.modifyFont(key, font); } return isUnscaled(alternateScaleFactor) ? font : newScaledFontUIResource(font, alternateScaleFactor); } private Font maybeSubstituteFont(String key, Font original) { if (makeModern() && "Tahoma".equals(original.getFamily()) && !key.equals("Panel.font")) { return optionPaneFont; } return original; } private boolean isFontUnscaled(String keyString) { return keyString.endsWith(".acceleratorFont") && !keyString.startsWith("MenuItem.") || keyString.equals("ColorChooser.font") || keyString.equals("TextArea.font"); } @Override public Integer modifyInteger(Object key, Integer original) { if (key.toString().startsWith(BUTTON_DASHED_RECT_PREFIX)) { // Reduce how far into the button the keyboard selection moves. Otherwise it starts // crossing the text at higher scaling levels. return Math.round(original * (1f + scaleFactor) * 0.5f); } for (String prefix : PRESCALED_INTEGER_PREFIXES) { if (String.valueOf(key).startsWith(prefix)) { return scaleIntegerIfMetric(key, original, alternateScaleFactor); } } return super.modifyInteger(key, original); } @Override public Icon modifyIcon(Object key, Icon original) { // InternalFrame icons appear to choose their size programatically. (Possibly based on the // title font size?) They should not be rescaled at all. for (String prefix : PREFIX_TO_NOT_SCALE_ICONS) { if (String.valueOf(key).startsWith(prefix)) { return original; } } // WindowsIconFactory private icon implementations yield to other icons installed in the // UIDefaults. We need to use a special delegate that tricks them into believing that // they are actually still installed in the UIDefaults to get them to paint correctly. // // Examples: CheckBox.icon, Menu.arrowIcon, RadioButtonMenuItem.checkIcon // String className = original.getClass().getName(); if (className.contains("WindowsIconFactory")) { // Deep inside some sun-private UI implementation code, there's some sanity checks // calling "instanceof VistaMenuItemCheckIcon" to ensure the Vista icons are // "compatible" (whatever that means). If we change them, they're not "compatible", // and the menu layout falls back to the default (like Metal) and looks weird. // // Not much we can do. :-( Still, if the user's scaling is between +/- 25% of the // primary monitor then this will probably look all right. This is bound to cover // most typical scenarios. If not, the size of Check and Radio buttons in menus will be // very mismatched. if (className.endsWith("VistaMenuItemCheckIcon")) { return original; } return newLoopBreakingScaledIcon(key, original, alternateScaleFactor); } // These icons appear to NOT include Windows-default scaling applied. Just scale using // the desired scale-factor directly. // // Examples: FileChooser.newFolderIcon, Tree.openIcon if ((original instanceof UIResource || original instanceof ImageIcon) && !className.contains("SkinIcon")) { return super.modifyIcon(key, original); } // Other icons appear to include Windows-default scaling already. That must be taken into // account to arrive at the desired scale-factor. // // Examples: RadioButtonMenuItem.arrowIcon, Table.ascendingSortIcon, Tree.expandedIcon return newScaledIconUIResource(original, alternateScaleFactor); } /** * Stick with Java's Windows 95/XP styling if 'classic' Windows is specified or if tweaking * UI has been globally disabled. Otherwise try and catch up a bit with the current decade. */ private boolean makeModern() { return doExtraTweaks && !windowsClassic; } protected static Icon newLoopBreakingScaledIcon(Object key, Icon original, float scale) { if (isUnscaled(scale) && original instanceof UIResource) { return original; } return new ScaledIconUIResource(new LoopBreakingScaledIcon(key, original, scale)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy