processing.app.ui.Toolkit Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pde Show documentation
Show all versions of pde Show documentation
Processing is a programming language, development environment, and online community.
This PDE package contains the Processing IDE.
The newest version!
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2012-15 The Processing Foundation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
as published by the Free Software Foundation.
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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.app.ui;
import java.awt.BasicStroke;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.image.ImageObserver;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JRootPane;
import javax.swing.KeyStroke;
import processing.app.Language;
import processing.app.Messages;
import processing.app.Platform;
import processing.app.Preferences;
import processing.app.Util;
import processing.core.PApplet;
import processing.data.StringList;
/**
* Utility functions for base that require a java.awt.Toolkit object. These
* are broken out from Base as we start moving toward the possibility of the
* code running in headless mode.
*/
public class Toolkit {
static final java.awt.Toolkit awtToolkit =
java.awt.Toolkit.getDefaultToolkit();
/** Command on Mac OS X, Ctrl on Windows and Linux */
static final int SHORTCUT_KEY_MASK =
awtToolkit.getMenuShortcutKeyMask();
/** Command-Option on Mac OS X, Ctrl-Alt on Windows and Linux */
static final int SHORTCUT_ALT_KEY_MASK =
ActionEvent.ALT_MASK | SHORTCUT_KEY_MASK;
/** Command-Shift on Mac OS X, Ctrl-Shift on Windows and Linux */
static final int SHORTCUT_SHIFT_KEY_MASK =
ActionEvent.SHIFT_MASK | SHORTCUT_KEY_MASK;
/** Command-W on Mac OS X, Ctrl-W on Windows and Linux */
static public final KeyStroke WINDOW_CLOSE_KEYSTROKE =
KeyStroke.getKeyStroke('W', SHORTCUT_KEY_MASK);
/**
* Standardized width for buttons. Mac OS X 10.3 wants 70 as its default,
* Windows XP needs 66, and my Ubuntu machine needs 80+, so 80 seems proper.
* This is now stored in the languages file since this may need to be larger
* for languages that are consistently wider than English.
*/
static public int getButtonWidth() {
// Made into a method so that calling Toolkit methods doesn't require
// the languages to be loaded, and with that, Base initialized completely
return zoom(Integer.parseInt(Language.text("preferences.button.width")));
}
/**
* A software engineer, somewhere, needs to have their abstraction
* taken away. Who crafts the sort of API that would require a
* five-line helper function just to set the shortcut key for a
* menu item?
*/
static public JMenuItem newJMenuItem(String title, int what) {
JMenuItem menuItem = new JMenuItem(title);
int modifiers = awtToolkit.getMenuShortcutKeyMask();
menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
return menuItem;
}
/**
* @param action: use an Action, which sets the title, reaction
* and enabled-ness all by itself.
*/
static public JMenuItem newJMenuItem(Action action, int what) {
JMenuItem menuItem = new JMenuItem(action);
int modifiers = awtToolkit.getMenuShortcutKeyMask();
menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
return menuItem;
}
/**
* Like newJMenuItem() but adds shift as a modifier for the shortcut.
*/
static public JMenuItem newJMenuItemShift(String title, int what) {
JMenuItem menuItem = new JMenuItem(title);
int modifiers = awtToolkit.getMenuShortcutKeyMask();
modifiers |= ActionEvent.SHIFT_MASK;
menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
return menuItem;
}
/**
* Like newJMenuItem() but adds shift as a modifier for the shortcut.
*/
static public JMenuItem newJMenuItemShift(Action action, int what) {
JMenuItem menuItem = new JMenuItem(action);
int modifiers = awtToolkit.getMenuShortcutKeyMask();
modifiers |= ActionEvent.SHIFT_MASK;
menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
return menuItem;
}
/**
* Same as newJMenuItem(), but adds the ALT (on Linux and Windows)
* or OPTION (on Mac OS X) key as a modifier. This function should almost
* never be used, because it's bad for non-US keyboards that use ALT in
* strange and wondrous ways.
*/
static public JMenuItem newJMenuItemAlt(String title, int what) {
JMenuItem menuItem = new JMenuItem(title);
menuItem.setAccelerator(KeyStroke.getKeyStroke(what, SHORTCUT_ALT_KEY_MASK));
return menuItem;
}
static public JCheckBoxMenuItem newJCheckBoxMenuItem(String title, int what) {
JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(title);
int modifiers = awtToolkit.getMenuShortcutKeyMask();
menuItem.setAccelerator(KeyStroke.getKeyStroke(what, modifiers));
return menuItem;
}
static public void addDisabledItem(JMenu menu, String title) {
JMenuItem item = new JMenuItem(title);
item.setEnabled(false);
menu.add(item);
}
/**
* Removes all mnemonics, then sets a mnemonic for each menu and menu item
* recursively by these rules:
*
* - It tries to assign one of
* KDE's defaults.
* - Failing that, it loops through the first letter of each word, where a word
* is a block of Unicode "alphabetical" chars, looking for an upper-case ASCII mnemonic
* that is not taken. This is to try to be relevant, by using a letter well-associated
* with the command. (MS guidelines)
* - Ditto, but with lowercase.
* - Next, it tries the second ASCII character, if its width >= half the width of
* 'A'.
* - If the first letters are all taken/non-ASCII, then it loops through the
* ASCII letters in the item, widest to narrowest, seeing if any of them is not taken.
* To improve readability, it discriminates against decenders (qypgj), imagining they
* have 2/3 their actual width. (MS guidelines: avoid decenders). It also discriminates
* against vowels, imagining they have 2/3 their actual width. (MS and Gnome guidelines:
* avoid vowels.)
* - Failing that, it will loop left-to-right for an available digit. This is a last
* resort because the normal setMnemonic dislikes them.
* - If that doesn't work, it doesn't assign a mnemonic.
*
*
* As a special case, strings starting "sketchbook \u2192 " have that bit ignored
* because otherwise the Recent menu looks awful. However, the name "sketchbook \u2192
* Sketch", for example, will have the 'S' of "Sketch" chosen, but the 's' of 'sketchbook
* will get underlined.
* No letter by an underscore will be assigned.
* Disabled on Mac, per Apple guidelines.
* menu may contain nulls.
*
* Author: George Bateman. Initial work Myer Nore.
* @param menu
* A menu, a list of menus or an array of menu items to set mnemonics for.
*/
static public void setMenuMnemonics(JMenuItem... menu) {
if (Platform.isMacOS()) return;
if (menu.length == 0) return;
// The English is http://techbase.kde.org/Projects/Usability/HIG/Keyboard_Accelerators,
// made lowercase.
// Nothing but [a-z] except for '&' before mnemonics and regexes for changable text.
final String[] kdePreDefStrs = { "&file", "&new", "&open", "open&recent",
"&save", "save&as", "saveacop&y", "saveas&template", "savea&ll", "reloa&d",
"&print", "printpre&view", "&import", "e&xport", "&closefile",
"clos&eallfiles", "&quit", "&edit", "&undo", "re&do", "cu&t", "©",
"&paste", "&delete", "select&all", "dese&lect", "&find", "find&next",
"findpre&vious", "&replace", "&gotoline", "&view", "&newview",
"close&allviews", "&splitview", "&removeview", "splitter&orientation",
"&horizontal", "&vertical", "view&mode", "&fullscreenmode", "&zoom",
"zoom&in", "zoom&out", "zoomtopage&width", "zoomwhole&page", "zoom&factor",
"&insert", "&format", "&go", "&up", "&back", "&forward", "&home", "&go",
"&previouspage", "&nextpage", "&firstpage", "&lastpage", "read&updocument",
"read&downdocument", "&back", "&forward", "&gotopage", "&bookmarks",
"&addbookmark", "bookmark&tabsasfolder", "&editbookmarks",
"&newbookmarksfolder", "&tools", "&settings", "&toolbars",
"configure&shortcuts", "configuretool&bars", "&configure.*", "&help",
".+&handbook", "&whatsthis", "report&bug", "&aboutprocessing", "about&kde",
"&beenden", "&suchen", // de
"&preferncias", "&sair", // Preferências; pt
"&rechercher" }; // fr
Pattern[] kdePreDefPats = new Pattern[kdePreDefStrs.length];
for (int i = 0; i < kdePreDefStrs.length; i++) {
kdePreDefPats[i] = Pattern.compile(kdePreDefStrs[i].replace("&",""));
}
final Pattern nonAAlpha = Pattern.compile("[^A-Za-z]");
FontMetrics fmTmp = null;
for (JMenuItem m : menu) {
if (m != null) {
fmTmp = m.getFontMetrics(m.getFont());
break;
}
}
if (fmTmp == null) return; // All null menuitems; would fail.
final FontMetrics fm = fmTmp; // Hack for accessing variable in comparator.
final Comparator charComparator = new Comparator() {
char[] baddies = "qypgjaeiouQAEIOU".toCharArray();
public int compare(Character ch1, Character ch2) {
// Discriminates against descenders for readability, per MS
// Human Interface Guide, and vowels per MS and Gnome.
float w1 = fm.charWidth(ch1), w2 = fm.charWidth(ch2);
for (char bad : baddies) {
if (bad == ch1) w1 *= 0.66f;
if (bad == ch2) w2 *= 0.66f;
}
return (int)Math.signum(w2 - w1);
}
};
// Holds only [0-9a-z], not uppercase.
// Prevents X != x, so "Save" and "Save As" aren't both given 'a'.
final List taken = new ArrayList<>(menu.length);
char firstChar;
char[] cleanChars;
Character[] cleanCharas;
// METHOD 1: attempt to assign KDE defaults.
for (JMenuItem jmi : menu) {
if (jmi == null) continue;
if (jmi.getText() == null) continue;
jmi.setMnemonic(0); // Reset all mnemonics.
String asciiName = nonAAlpha.matcher(jmi.getText()).replaceAll("");
String lAsciiName = asciiName.toLowerCase();
for (int i = 0; i < kdePreDefStrs.length; i++) {
if (kdePreDefPats[i].matcher(lAsciiName).matches()) {
char mnem = asciiName.charAt(kdePreDefStrs[i].indexOf("&"));
jmi.setMnemonic(mnem);
jmi.setDisplayedMnemonicIndex(jmi.getText().indexOf(mnem));
taken.add((char)(mnem | 32)); // to lowercase
break;
}
}
}
// Where KDE defaults fail, use an algorithm.
algorithmicAssignment:
for (JMenuItem jmi : menu) {
if (jmi == null) continue;
if (jmi.getText() == null) continue;
if (jmi.getMnemonic() != 0) continue; // Already assigned.
// The string can't be made lower-case as that would spoil
// the width comparison.
String cleanString = jmi.getText();
if (cleanString.startsWith("sketchbook \u2192 "))
cleanString = cleanString.substring(13);
if (cleanString.length() == 0) continue;
// First, ban letters by underscores.
final List banned = new ArrayList<>();
for (int i = 0; i < cleanString.length(); i++) {
if (cleanString.charAt(i) == '_') {
if (i > 0)
banned.add(Character.toLowerCase(cleanString.charAt(i - 1)));
if (i + 1 < cleanString.length())
banned.add(Character.toLowerCase(cleanString.charAt(i + 1)));
}
}
// METHOD 2: Uppercase starts of words.
// Splitting into blocks of ASCII letters wouldn't work
// because there could be non-ASCII letters in a word.
for (String wd : cleanString.split("[^\\p{IsAlphabetic}]")) {
if (wd.length() == 0) continue;
firstChar = wd.charAt(0);
if (taken.contains(Character.toLowerCase(firstChar))) continue;
if (banned.contains(Character.toLowerCase(firstChar))) continue;
if ('A' <= firstChar && firstChar <= 'Z') {
jmi.setMnemonic(firstChar);
jmi.setDisplayedMnemonicIndex(jmi.getText().indexOf(firstChar));
taken.add((char)(firstChar | 32)); // tolowercase
continue algorithmicAssignment;
}
}
// METHOD 3: Lowercase starts of words.
for (String wd : cleanString.split("[^\\p{IsAlphabetic}]")) {
if (wd.length() == 0) continue;
firstChar = wd.charAt(0);
if (taken.contains(Character.toLowerCase(firstChar))) continue;
if (banned.contains(Character.toLowerCase(firstChar))) continue;
if ('a' <= firstChar && firstChar <= 'z') {
jmi.setMnemonic(firstChar);
jmi.setDisplayedMnemonicIndex(jmi.getText().indexOf(firstChar));
taken.add(firstChar); // is lowercase
continue algorithmicAssignment;
}
}
// METHOD 4: Second wide-enough ASCII letter.
cleanString = nonAAlpha.matcher(jmi.getText()).replaceAll("");
if (cleanString.length() >= 2) {
char ascii2nd = cleanString.charAt(1);
if (!taken.contains((char)(ascii2nd|32)) &&
!banned.contains((char)(ascii2nd|32)) &&
fm.charWidth('A') <= 2*fm.charWidth(ascii2nd)) {
jmi.setMnemonic(ascii2nd);
jmi.setDisplayedMnemonicIndex(jmi.getText().indexOf(ascii2nd));
taken.add((char)(ascii2nd|32));
continue algorithmicAssignment;
}
}
// METHOD 5: charComparator over all ASCII letters.
cleanChars = cleanString.toCharArray();
cleanCharas = new Character[cleanChars.length];
for (int i = 0; i < cleanChars.length; i++) {
cleanCharas[i] = cleanChars[i];
}
Arrays.sort(cleanCharas, charComparator); // sorts in increasing order
for (char mnem : cleanCharas) {
if (taken.contains(Character.toLowerCase(mnem))) continue;
if (banned.contains(Character.toLowerCase(mnem))) continue;
// NB: setMnemonic(char) doesn't want [^A-Za-z]
jmi.setMnemonic(mnem);
jmi.setDisplayedMnemonicIndex(jmi.getText().indexOf(mnem));
taken.add(Character.toLowerCase(mnem));
continue algorithmicAssignment;
}
// METHOD 6: Digits as last resort.
for (char digit : jmi.getText().replaceAll("[^0-9]", "").toCharArray()) {
if (taken.contains(digit)) continue;
if (banned.contains(digit)) continue;
jmi.setMnemonic(KeyEvent.VK_0 + digit - '0');
// setDisplayedMnemonicIndex() unneeded: no case issues.
taken.add(digit);
continue algorithmicAssignment;
}
}
// Finally, RECURSION.
for (JMenuItem jmi : menu) {
if (jmi instanceof JMenu) setMenuMnemsInside((JMenu) jmi);
}
}
/**
* As setMenuMnemonics(JMenuItem...).
*/
static public void setMenuMnemonics(JMenuBar menubar) {
JMenuItem[] items = new JMenuItem[menubar.getMenuCount()];
for (int i = 0; i < items.length; i++) {
items[i] = menubar.getMenu(i);
}
setMenuMnemonics(items);
}
/**
* As setMenuMnemonics(JMenuItem...).
*/
static public void setMenuMnemonics(JPopupMenu menu) {
ArrayList items = new ArrayList<>();
for (Component c : menu.getComponents()) {
if (c instanceof JMenuItem) items.add((JMenuItem)c);
}
setMenuMnemonics(items.toArray(new JMenuItem[items.size()]));
}
/**
* Calls setMenuMnemonics(JMenuItem...) on the sub-elements only.
*/
static public void setMenuMnemsInside(JMenu menu) {
JMenuItem[] items = new JMenuItem[menu.getItemCount()];
for (int i = 0; i < items.length; i++) {
items[i] = menu.getItem(i);
}
setMenuMnemonics(items);
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static public Dimension getScreenSize() {
return awtToolkit.getScreenSize();
}
/**
* Return an Image object from inside the Processing 'lib' folder.
* Moved here so that Base can stay headless.
*/
static public Image getLibImage(String filename) {
ImageIcon icon = getLibIcon(filename);
return (icon == null) ? null : icon.getImage();
}
/**
* Get an ImageIcon from the Processing 'lib' folder.
* @since 3.0a6
*/
static public ImageIcon getLibIcon(String filename) {
File file = Platform.getContentFile("lib/" + filename);
if (!file.exists()) {
// System.err.println("does not exist: " + file);
return null;
}
return new ImageIcon(file.getAbsolutePath());
}
static public ImageIcon getIconX(File dir, String base) {
return getIconX(dir, base, 0);
}
/**
* Get an icon of the format base-NN.png where NN is the size, but if it's
* a hidpi display, get the NN*2 version automatically, sized at NN
*/
static public ImageIcon getIconX(File dir, String base, int size) {
final int scale = Toolkit.highResImages() ? 2 : 1;
String filename = (size == 0) ?
(base + "-" + scale + "x.png") :
(base + "-" + (size*scale) + ".png");
File file = new File(dir, filename);
if (!file.exists()) {
return null;
}
ImageIcon outgoing = new ImageIcon(file.getAbsolutePath()) {
@Override
public int getIconWidth() {
return Toolkit.zoom(super.getIconWidth()) / scale;
}
@Override
public int getIconHeight() {
return Toolkit.zoom(super.getIconHeight()) / scale;
}
@Override
public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
ImageObserver imageObserver = getImageObserver();
if (imageObserver == null) {
imageObserver = c;
}
g.drawImage(getImage(), x, y, getIconWidth(), getIconHeight(), imageObserver);
}
};
return outgoing;
}
/**
* Get an image icon with hi-dpi support. Pulls 1x or 2x versions of the
* file depending on the display type, but sizes them based on 1x.
*/
static public ImageIcon getLibIconX(String base) {
return getLibIconX(base, 0);
}
static public ImageIcon getLibIconX(String base, int size) {
return getIconX(Platform.getContentFile("lib"), base, size);
}
/**
* Create a JButton with an icon, and set its disabled and pressed images
* to be the same image, so that 2x versions of the icon work properly.
*/
static public JButton createIconButton(String title, String base) {
ImageIcon icon = Toolkit.getLibIconX(base);
return createIconButton(title, icon);
}
/** Same as above, but with no text title (follows JButton constructor) */
static public JButton createIconButton(String base) {
return createIconButton(null, base);
}
static public JButton createIconButton(String title, Icon icon) {
JButton button = new JButton(title, icon);
button.setDisabledIcon(icon);
button.setPressedIcon(icon);
return button;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static List iconImages;
// Deprecated version of the function, but can't get rid of it without
// breaking tools and modes (they'd only require a recompile, but they would
// no longer be backwards compatible.
static public void setIcon(Frame frame) {
setIcon((Window) frame);
}
/**
* Give this Frame the Processing icon set. Ignored on OS X, because they
* thought different and made this function set the minified image of the
* window, not the window icon for the dock or cmd-tab.
*/
static public void setIcon(Window window) {
if (!Platform.isMacOS()) {
if (iconImages == null) {
iconImages = new ArrayList<>();
final int[] sizes = { 16, 32, 48, 64, 128, 256, 512 };
for (int sz : sizes) {
iconImages.add(Toolkit.getLibImage("icons/pde-" + sz + ".png"));
}
}
window.setIconImages(iconImages);
}
}
static public Shape createRoundRect(float x1, float y1, float x2, float y2,
float tl, float tr, float br, float bl) {
GeneralPath path = new GeneralPath();
// vertex(x1+tl, y1);
if (tr != 0) {
path.moveTo(x2-tr, y1);
path.quadTo(x2, y1, x2, y1+tr);
} else {
path.moveTo(x2, y1);
}
if (br != 0) {
path.lineTo(x2, y2-br);
path.quadTo(x2, y2, x2-br, y2);
} else {
path.lineTo(x2, y2);
}
if (bl != 0) {
path.lineTo(x1+bl, y2);
path.quadTo(x1, y2, x1, y2-bl);
} else {
path.lineTo(x1, y2);
}
if (tl != 0) {
path.lineTo(x1, y1+tl);
path.quadTo(x1, y1, x1+tl, y1);
} else {
path.lineTo(x1, y1);
}
path.closePath();
return path;
}
/**
* Registers key events for a Ctrl-W and ESC with an ActionListener
* that will take care of disposing the window.
*/
static public void registerWindowCloseKeys(JRootPane root,
ActionListener disposer) {
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
root.registerKeyboardAction(disposer, stroke,
JComponent.WHEN_IN_FOCUSED_WINDOW);
int modifiers = awtToolkit.getMenuShortcutKeyMask();
stroke = KeyStroke.getKeyStroke('W', modifiers);
root.registerKeyboardAction(disposer, stroke,
JComponent.WHEN_IN_FOCUSED_WINDOW);
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static public void beep() {
awtToolkit.beep();
}
static public Clipboard getSystemClipboard() {
return awtToolkit.getSystemClipboard();
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/**
* Create an Image to be used as an offscreen drawing context,
* automatically doubling the size if running on a retina display.
*/
static public Image offscreenGraphics(Component comp, int width, int height) {
int m = Toolkit.isRetina() ? 2 : 1;
//return comp.createImage(m * dpi(width), m * dpi(height));
return comp.createImage(m * width, m * height);
}
/**
* Handles scaling for high-res displays, also sets text anti-aliasing
* options to be far less ugly than the defaults.
* Moved to a utility function because it's used in several classes.
* @return a Graphics2D object, as a bit o sugar
*/
static public Graphics2D prepareGraphics(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
//float z = zoom * (Toolkit.isRetina() ? 2 : 1);
if (Toolkit.isRetina()) {
// scale everything 2x, will be scaled down when drawn to the screen
g2.scale(2, 2);
}
//g2.scale(z, z);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
if (Toolkit.isRetina()) {
// Looks great on retina, not so great (with our font) on 1x
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
}
zoomStroke(g2);
return g2;
}
// /**
// * Prepare and offscreen image that's sized for this Component, 1x or 2x
// * depending on whether this is a retina display or not.
// * @param comp
// * @param image
// * @return
// */
// static public Image prepareOffscreen(Component comp, Image image) {
// Dimension size = comp.getSize();
// Image offscreen = image;
// if (image == null ||
// image.getWidth(null) != size.width ||
// image.getHeight(null) != size.height) {
// if (Toolkit.highResDisplay()) {
// offscreen = comp.createImage(size.width*2, size.height*2);
// } else {
// offscreen = comp.createImage(size.width, size.height);
// }
// }
// return offscreen;
// }
// static final Color CLEAR_COLOR = new Color(0, true);
//
// static public void clearGraphics(Graphics g, int width, int height) {
// g.setColor(CLEAR_COLOR);
// g.fillRect(0, 0, width, height);
// }
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static float zoom = 0;
/*
// http://stackoverflow.com/a/35029265
static public void zoomSwingFonts() {
Set