This is an automated email from the ASF dual-hosted git repository.
ebakke pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new 0f53295 [NETBEANS-1238,NETBEANS-1260] HiDPI icons for window system
icons on Windows and Mac
0f53295 is described below
commit 0f53295eaa11cc831cab9c2db43b12f017ed0180
Author: Eirik Bakke <[email protected]>
AuthorDate: Tue Oct 30 17:16:41 2018 -0400
[NETBEANS-1238,NETBEANS-1260] HiDPI icons for window system icons on
Windows and Mac
This commit introduces vector-drawn Icon implementations for the icons
used in the window system's Windows 8 and Aqua (MacOS) LAFs. Specifically,
this
replaces most of the bitmap icons for these two LAFs o.n.swing.tabcontrol
and
openide.awt modules.
An abstract class VectorIcon is added in the UI Utilities Module
(openide.awt)
as a general-purpose starting point for creating new vector icons. It
handles
and documents a number of tricky adjustments that are needed to draw icons
that
appear sharp on HiDPI screens. This class can be used to implement HiDPI
icons
in other LAFs at a later time, e.g. on Linux.
A small utility, VectorIconTester, was written to preview new icons at
multiple
resolutions, as well as to compare them with existing bitmap icons. This is
less
polished code that is nevertheless included here, in tabcontrol's test
package.
A screenshot of its output, as well as screenshots of the NetBeans IDE
before
and after the patch on both Windows and MacOS, are attached to the JIRA
ticket NETBEANS-1260.
---
.../netbeans/swing/plaf/aqua/AquaLFCustoms.java | 4 +-
.../swing/plaf/windows8/Windows8LFCustoms.java | 4 +-
.../o.n.swing.tabcontrol/nbproject/project.xml | 2 +-
.../tabcontrol/plaf/AquaEditorTabCellRenderer.java | 38 +-
.../plaf/AquaVectorEditorTabCellRenderer.java | 41 ++
.../plaf/AquaVectorEditorTabDisplayerUI.java | 49 ++
.../tabcontrol/plaf/AquaVectorTabControlIcon.java | 491 ++++++++++++++++++
.../plaf/AquaVectorViewTabDisplayerUI.java | 44 ++
.../tabcontrol/plaf/AquaViewTabDisplayerUI.java | 4 +-
.../tabcontrol/plaf/TabControlButtonFactory.java | 1 -
.../plaf/WinVistaEditorTabCellRenderer.java | 22 +-
.../plaf/Windows8EditorTabCellRenderer.java | 21 +-
.../plaf/Windows8EditorTabDisplayerUI.java | 2 +-
.../plaf/Windows8VectorEditorTabCellRenderer.java | 38 ++
.../plaf/Windows8VectorEditorTabDisplayerUI.java | 49 ++
.../plaf/Windows8VectorTabControlIcon.java | 242 +++++++++
.../plaf/Windows8VectorViewTabDisplayerUI.java | 44 ++
.../plaf/Windows8ViewTabDisplayerUI.java | 4 +-
.../swing/tabcontrol/plaf/VectorIconTester.java | 556 +++++++++++++++++++++
platform/openide.awt/nbproject/project.xml | 2 +-
.../src/org/openide/awt/AquaVectorCloseButton.java | 86 ++++
.../src/org/openide/awt/CloseButtonFactory.java | 24 +-
.../src/org/openide/awt/DropDownButton.java | 10 +-
.../src/org/openide/awt/DropDownToggleButton.java | 11 +-
.../src/org/openide/awt/IconWithArrow.java | 39 +-
.../src/org/openide/awt/ToolbarWithOverflow.java | 53 +-
.../org/openide/awt/Windows8VectorCloseButton.java | 65 +++
platform/openide.util.ui/apichanges.xml | 26 +
.../src/org/openide/util/VectorIcon.java | 202 ++++++++
29 files changed, 2085 insertions(+), 89 deletions(-)
diff --git
a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/aqua/AquaLFCustoms.java
b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/aqua/AquaLFCustoms.java
index 69f6234..a300812 100644
---
a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/aqua/AquaLFCustoms.java
+++
b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/aqua/AquaLFCustoms.java
@@ -117,8 +117,8 @@ public final class AquaLFCustoms extends LFCustoms {
//UI Delegates for the tab control
- EDITOR_TAB_DISPLAYER_UI,
"org.netbeans.swing.tabcontrol.plaf.AquaEditorTabDisplayerUI",
- VIEW_TAB_DISPLAYER_UI,
"org.netbeans.swing.tabcontrol.plaf.AquaViewTabDisplayerUI",
+ EDITOR_TAB_DISPLAYER_UI,
"org.netbeans.swing.tabcontrol.plaf.AquaVectorEditorTabDisplayerUI",
+ VIEW_TAB_DISPLAYER_UI,
"org.netbeans.swing.tabcontrol.plaf.AquaVectorViewTabDisplayerUI",
SLIDING_TAB_BUTTON_UI,
"org.netbeans.swing.tabcontrol.plaf.SlidingTabDisplayerButtonUI$Aqua",
"NbTabControl.focusedTabBackground", new Color(135,189,255),
"NbTabControl.selectedTabBrighterBackground", new
Color(252,252,252),
diff --git
a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java
b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java
index 9e5b098..851ffe7 100644
---
a/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java
+++
b/platform/o.n.swing.plaf/src/org/netbeans/swing/plaf/windows8/Windows8LFCustoms.java
@@ -91,9 +91,9 @@ public final class Windows8LFCustoms extends LFCustoms {
@Override
public Object[] createApplicationSpecificKeysAndValues () {
UIBootstrapValue editorTabsUI = new Windows8EditorColorings (
-
"org.netbeans.swing.tabcontrol.plaf.Windows8EditorTabDisplayerUI");
+
"org.netbeans.swing.tabcontrol.plaf.Windows8VectorEditorTabDisplayerUI");
- Object viewTabsUI =
editorTabsUI.createShared("org.netbeans.swing.tabcontrol.plaf.Windows8ViewTabDisplayerUI");
+ Object viewTabsUI =
editorTabsUI.createShared("org.netbeans.swing.tabcontrol.plaf.Windows8VectorViewTabDisplayerUI");
//TODO change icon (copy & paste)
Image explorerIcon =
UIUtils.loadImage("org/netbeans/swing/plaf/resources/vista_folder.png");
diff --git a/platform/o.n.swing.tabcontrol/nbproject/project.xml
b/platform/o.n.swing.tabcontrol/nbproject/project.xml
index 7412416..5c88d98 100644
--- a/platform/o.n.swing.tabcontrol/nbproject/project.xml
+++ b/platform/o.n.swing.tabcontrol/nbproject/project.xml
@@ -38,7 +38,7 @@
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
- <specification-version>9.3</specification-version>
+ <specification-version>9.12</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java
index 3ff4248..fe42708 100644
---
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaEditorTabCellRenderer.java
@@ -28,7 +28,7 @@ import org.netbeans.swing.tabcontrol.TabDisplayer;
*
* @author S. Aubrecht
*/
-final class AquaEditorTabCellRenderer extends AbstractTabCellRenderer {
+class AquaEditorTabCellRenderer extends AbstractTabCellRenderer {
//Default insets values for Mac look and feel
private static final int TOP_INSET = 0;
private static final int LEFT_INSET = 3;
@@ -106,6 +106,21 @@ final class AquaEditorTabCellRenderer extends
AbstractTabCellRenderer {
return getTxtColor();
}
+ /**
+ * Returns icon which is correct for currect state of tab at given index
+ */
+ protected Icon findIcon() {
+ final String file;
+ if( inCloseButton() && isPressed() ) {
+ file = "org/openide/awt/resources/mac_close_pressed.png"; // NOI18N
+ } else if( inCloseButton() ) {
+ file = "org/openide/awt/resources/mac_close_rollover.png"; //
NOI18N
+ } else {
+ file = "org/openide/awt/resources/mac_close_enabled.png"; // NOI18N
+ }
+ return TabControlButtonFactory.getIcon(file);
+ }
+
private static void paintTabGradient( Graphics g,
AquaEditorTabCellRenderer ren, Polygon poly ) {
Rectangle rect = poly.getBounds();
boolean selected = ren.isSelected();
@@ -166,8 +181,7 @@ final class AquaEditorTabCellRenderer extends
AbstractTabCellRenderer {
rect.height = 0;
return;
}
- String iconPath = findIconPath(ren);
- Icon icon = TabControlButtonFactory.getIcon(iconPath);
+ Icon icon = ren.findIcon();
int iconWidth = icon.getIconWidth();
int iconHeight = icon.getIconHeight();
rect.x = bounds.x + bounds.width - iconWidth - 5;
@@ -176,21 +190,6 @@ final class AquaEditorTabCellRenderer extends
AbstractTabCellRenderer {
rect.height = iconHeight;
}
-
- /**
- * Returns path of icon which is correct for currect state of tab at
given
- * index
- */
- private String findIconPath( AquaEditorTabCellRenderer renderer ) {
- if( renderer.inCloseButton() && renderer.isPressed() ) {
- return "org/openide/awt/resources/mac_close_pressed.png"; //
NOI18N
- }
- if( renderer.inCloseButton() ) {
- return "org/openide/awt/resources/mac_close_rollover.png"; //
NOI18N
- }
- return "org/openide/awt/resources/mac_close_enabled.png"; // NOI18N
- }
-
public Polygon getInteriorPolygon(Component c) {
AquaEditorTabCellRenderer ren = (AquaEditorTabCellRenderer) c;
@@ -278,8 +277,7 @@ final class AquaEditorTabCellRenderer extends
AbstractTabCellRenderer {
}
//paint close button
- String iconPath = findIconPath( ren );
- Icon icon = TabControlButtonFactory.getIcon( iconPath );
+ Icon icon = ren.findIcon();
icon.paintIcon(ren, g, r.x, r.y);
}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabCellRenderer.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabCellRenderer.java
new file mode 100644
index 0000000..b37007c
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabCellRenderer.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+
+/**
+ * A variation on the Aqua editor tab cell renderer that uses scalable icons
for Retina screens.
+ * See {@link AquaVectorTabControlIcon}.
+ */
+final class AquaVectorEditorTabCellRenderer extends AquaEditorTabCellRenderer {
+ @Override
+ protected Icon findIcon() {
+ /* The "mac_close_(enabled|pressed|rollover).png" files were confirmed
to be identical to
+ the mac_bigclose_(enabled|pressed|rollover).png ones. So we can use
the same icons as in the
+ tab control here. */
+ if( inCloseButton() && isPressed() ) {
+ return
AquaVectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON,
TabControlButton.STATE_PRESSED);
+ } else if( inCloseButton() ) {
+ return
AquaVectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON,
TabControlButton.STATE_ROLLOVER);
+ } else {
+ return
AquaVectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON,
TabControlButton.STATE_DEFAULT);
+ }
+ }
+}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabDisplayerUI.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabDisplayerUI.java
new file mode 100644
index 0000000..446afd5
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorEditorTabDisplayerUI.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+
+/**
+ * A variation on the Aqua editor tab displayer UI that uses scalable icons
for Retina screens.
+ * See {@link AquaVectorTabControlIcon}.
+ */
+public final class AquaVectorEditorTabDisplayerUI extends
AquaEditorTabDisplayerUI {
+ public AquaVectorEditorTabDisplayerUI(TabDisplayer displayer) {
+ super(displayer);
+ }
+
+ public static ComponentUI createUI(JComponent c) {
+ return new AquaVectorEditorTabDisplayerUI((TabDisplayer) c);
+ }
+
+ @Override
+ protected TabCellRenderer createDefaultRenderer() {
+ return new AquaVectorEditorTabCellRenderer();
+ }
+
+ @Override
+ public Icon getButtonIcon(int buttonId, int buttonState) {
+ Icon ret = AquaVectorTabControlIcon.get(buttonId, buttonState);
+ return ret != null ? ret : super.getButtonIcon(buttonId, buttonState);
+ }
+}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorTabControlIcon.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorTabControlIcon.java
new file mode 100644
index 0000000..f1bbdfa
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorTabControlIcon.java
@@ -0,0 +1,491 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import org.openide.util.VectorIcon;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.GradientPaint;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Area;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.swing.Icon;
+
+/**
+ * Scalable vector icons for the Aqua tab control L&F. These icons look
good both at the
+ * regular 100% scale as well as at the 200% scale used on Retina screens. At
100% scale, they are
+ * sized and aligned exactly like the bitmap icons that were previously used
for the Aqua LAF, and
+ * look mostly the same, with a few updates to match current design standards:
+ *
+ * <ul>
+ * <li>The slight bevel effect that was present on some of the bitmap
buttons, and the slight
+ * gradient that was present on the circular bitmap buttons, has been
dropped in the name of
+ * "flat design". (Apple HID guidelines say
+ * <a
href="https://developer.apple.com/design/human-interface-guidelines/macos/buttons/bevel-buttons">"Avoid
+ * using bevel buttons"</a>.)
+ * <li>Rounded rectangle buttons, including the segmented "scroll
left/right" buttons, have had
+ * their rounding radius increased slightly, to match that of
+ * <a
href="https://developer.apple.com/design/human-interface-guidelines/macos/selectors/segmented-controls">segmented
controls</a>
+ * on MacOS High Sierra.
+ * <li>The smaller buttons now only show their solid backgrounds on rollover
and press. This
+ * reduces visual clutter, and is consistent with buttons in XCode,
Finder, Chrome, and
+ * Photoshop on MacOS High Sierra. To remain visible, some of these
icons have had their
+ * contents enlarged slightly.
+ * <li>Except for the "x" button that closes a tab, the background shape of
the small buttons have
+ * been changed from a circle to a rounded rectangle, for consistency
with other MacOS
+ * apps (e.g. the "Mailboxes" icon toolbar button in the Mail app, or
the "Refresh" button on
+ * Chrome). This also allows the symbols inside to be made slightly
larger; see above. (Apple
+ * HID guidelines:
+ * <a
href="https://developer.apple.com/design/human-interface-guidelines/macos/buttons/round-buttons">"Avoid
+ * using round buttons."</a>.)
+ * <li>The circular "x" that closes a tab is given a red color on rollover
and press, like in
+ * Chrome for MacOS (and Windows), and like on NetBeans' Windows 8 LAF.
+ * <li>Some slight brightness variations that were present between the
previous bitmap button
+ * types have been dropped, as these seemed to be accidental.
+ * </ul>
+ *
+ * @author Eirik Bakke
+ */
+@SuppressWarnings("serial")
+final class AquaVectorTabControlIcon extends VectorIcon {
+ private static final Map<Entry<Integer,Integer>,Icon> INSTANCES =
populateInstances();
+ private final int buttonId;
+ private final int buttonState;
+
+ private static void populateOne(
+ Map<Entry<Integer,Integer>,Icon> toMap, int buttonId, int
buttonState)
+ {
+ final int width;
+ final int height;
+ switch (buttonId) {
+ case TabControlButton.ID_CLOSE_BUTTON:
+ width = 14;
+ height = 12;
+ break;
+ case TabControlButton.ID_RESTORE_GROUP_BUTTON:
+ case TabControlButton.ID_SLIDE_GROUP_BUTTON:
+ width = 16;
+ height = 16;
+ break;
+ case TabControlButton.ID_PIN_BUTTON:
+ /* The pin button is shown next to the close button of a
minimized panel that is
+ shown temporarily when the user hovers over its icon. So it
must be the same size as
+ the close button. */
+ width = 14;
+ height = 12;
+ break;
+ case TabControlButton.ID_SCROLL_LEFT_BUTTON:
+ width = 26;
+ height = 15;
+ break;
+ case TabControlButton.ID_SCROLL_RIGHT_BUTTON:
+ width = 25;
+ height = 15;
+ break;
+ case TabControlButton.ID_DROP_DOWN_BUTTON:
+ case TabControlButton.ID_MAXIMIZE_BUTTON:
+ case TabControlButton.ID_RESTORE_BUTTON:
+ width = 20;
+ height = 15;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+ toMap.put(new SimpleEntry<Integer,Integer>(buttonId, buttonState),
+ new AquaVectorTabControlIcon(buttonId, buttonState, width,
height));
+ }
+
+ private static Map<Entry<Integer,Integer>,Icon> populateInstances() {
+ // The string keys of these maps aren't currently used, but are useful
for debugging.
+ Map<String, Integer> buttonIDs = new LinkedHashMap<String, Integer>();
+ // ViewTabDisplayerUI
+ buttonIDs.put("close", TabControlButton.ID_CLOSE_BUTTON);
+ // These don't seem to be in use anymore.
+ //buttonIDs.put("slide_right", TabControlButton.ID_SLIDE_RIGHT_BUTTON);
+ //buttonIDs.put("slide_left", TabControlButton.ID_SLIDE_LEFT_BUTTON);
+ //buttonIDs.put("slide_down", TabControlButton.ID_SLIDE_DOWN_BUTTON);
+ buttonIDs.put("pin", TabControlButton.ID_PIN_BUTTON);
+ buttonIDs.put("restore_group",
TabControlButton.ID_RESTORE_GROUP_BUTTON);
+ buttonIDs.put("slide_group", TabControlButton.ID_SLIDE_GROUP_BUTTON);
+ // EditorTabDisplayerUI
+ buttonIDs.put("scroll_left", TabControlButton.ID_SCROLL_LEFT_BUTTON);
+ buttonIDs.put("scroll_right", TabControlButton.ID_SCROLL_RIGHT_BUTTON);
+ buttonIDs.put("drop_down", TabControlButton.ID_DROP_DOWN_BUTTON);
+ buttonIDs.put("maximize", TabControlButton.ID_MAXIMIZE_BUTTON);
+ buttonIDs.put("restore", TabControlButton.ID_RESTORE_BUTTON);
+ Map<String, Integer> buttonStates = new LinkedHashMap<String,
Integer>();
+ buttonStates.put("default", TabControlButton.STATE_DEFAULT);
+ buttonStates.put("pressed", TabControlButton.STATE_PRESSED);
+ buttonStates.put("disabled", TabControlButton.STATE_DISABLED);
+ buttonStates.put("rollover", TabControlButton.STATE_ROLLOVER);
+ Map<Entry<Integer,Integer>,Icon> ret = new
LinkedHashMap<Entry<Integer,Integer>,Icon>();
+ for (Entry<String,Integer> buttonID : buttonIDs.entrySet()) {
+ for (Entry<String,Integer> buttonState : buttonStates.entrySet()) {
+ populateOne(ret, buttonID.getValue(), buttonState.getValue());
+ }
+ }
+ // Effectively immutable upon assignment to the final static variable.
+ return Collections.unmodifiableMap(ret);
+ }
+
+ private AquaVectorTabControlIcon(int buttonId, int buttonState, int width,
int height) {
+ super(width, height);
+ this.buttonId = buttonId;
+ this.buttonState = buttonState;
+ }
+
+ /**
+ * @return null if the requested icon is not available in vector format
+ */
+ public static Icon get(int buttonId, int buttonState) {
+ return INSTANCES.get(new SimpleEntry<Integer,Integer>(buttonId,
buttonState));
+ }
+
+ @Override
+ protected void paintIcon(Component c, Graphics2D g, int width, int height,
double scaling) {
+ if (buttonId == TabControlButton.ID_MAXIMIZE_BUTTON ||
+ buttonId == TabControlButton.ID_RESTORE_BUTTON ||
+ buttonId == TabControlButton.ID_DROP_DOWN_BUTTON ||
+ buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON ||
+ buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON)
+ {
+ paintLargerRectangleIcon(c, g, width, height, scaling);
+ } else if (buttonId == TabControlButton.ID_CLOSE_BUTTON) {
+ paintSmallCircleCloseIcon(c, g, width, height, scaling);
+ } else {
+ paintSmallRectangleIcon(c, g, width, height, scaling);
+ }
+ }
+
+ private void paintSmallCircleCloseIcon(
+ Component c, Graphics2D g, int width, int height, double scaling)
+ {
+ // Background circle diameter.
+ double d = Math.min(width, height);
+ Color bgColor = new Color(0, 0, 0, 0); // Alpha zero means no
background.
+ /* Use transparency to achieve the right dark gray level, to make sure
symbols are equally
+ visible on all backgrounds. */
+ Color fgColor = new Color(0, 0, 0, 168);
+ if (buttonState == TabControlButton.STATE_ROLLOVER) {
+ fgColor = Color.WHITE;
+ /* Red, with some transparency to blend onto the background.
Chrome would have
+ (244, 65, 54, 255), here, but the value below works better with
our expected
+ backgrounds. */
+ bgColor = new Color(255, 35, 25, 215);
+ } else if (buttonState == TabControlButton.STATE_PRESSED) {
+ fgColor = Color.WHITE;
+ // Slightly darker red. Chrome would have (196, 53, 43, 255) here;
see above.
+ bgColor = new Color(185, 43, 33, 215);
+ } else if (buttonState == TabControlButton.STATE_DISABLED) {
+ // Light grey (via transparent black to work well on any
background).
+ fgColor = new Color(0, 0, 0, 60);
+ }
+ if (bgColor.getAlpha() > 0) {
+ double circPosX = (width - d) / 2.0;
+ double circPosY = (height - d) / 2.0;
+ Shape bgCircle = new Ellipse2D.Double(circPosX, circPosY, d, d);
+ g.setColor(bgColor);
+ g.fill(bgCircle);
+ }
+ g.setColor(fgColor);
+ double strokeWidth = 1.4 * scaling;
+ // Middle x and y.
+ double mx = width / 2.0;
+ double my = height / 2.0;
+ // Radius of the cross ("X") symbol.
+ double cr = 0.45 * (d / 2.0);
+ /* Draw the "X". Fill the Shape of the entire cross rather than
painting each line
+ separately as a Stroke, to avoid the intersecting area getting a
higher opacity. */
+ Stroke stroke = new BasicStroke(
+ (float) strokeWidth, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND);
+ Area area = new Area();
+ area.add(new Area(stroke.createStrokedShape(
+ new Line2D.Double(mx - cr, my - cr, mx + cr, my + cr))));
+ area.add(new Area(stroke.createStrokedShape(
+ new Line2D.Double(mx + cr, my - cr, mx - cr, my + cr))));
+ g.fill(area);
+ }
+
+ private void paintSmallRectangleIcon(
+ Component c, Graphics2D g, int width, int height, double scaling)
+ {
+ Color bgColor = new Color(0, 0, 0, 0); // Alpha zero means no
background.
+ /* Use transparency to achieve the right dark gray level, to make sure
symbols are equally
+ visible on all backgrounds. */
+ Color fgColor = new Color(0, 0, 0, 168);
+ if (buttonState == TabControlButton.STATE_DISABLED) {
+ // Light grey (via transparent black to work well on any
background).
+ fgColor = new Color(0, 0, 0, 60);
+ } else if (buttonState == TabControlButton.STATE_ROLLOVER) {
+ /* Light grey (via transparent black), like in XCode tab close
buttons, the Chrome
+ "refresh" button, or the Mail app's "Mailboxes" button (used the
slightly darker level
+ from the latter). */
+ bgColor = new Color(0, 0, 0, 51);
+ fgColor = Color.WHITE;
+ } else if (buttonState == TabControlButton.STATE_PRESSED) {
+ /* Slightly darker light grey (via transparent black). Same as in
the aforementioned
+ "Mailboxes" icon. */
+ bgColor = new Color(0, 0, 0, 94);
+ fgColor = Color.WHITE;
+ }
+ if (bgColor.getAlpha() > 0) {
+ /* Use the same rounding radius as in paintLargerRectangleIcon.
Same as in the
+ aforementioned "Mailboxes" icon. */
+ double arc = scaling * 6.0;
+ Shape bgRect = new RoundRectangle2D.Double(0, 0, width, height,
arc, arc);
+ g.setColor(bgColor);
+ g.fill(bgRect);
+ }
+ g.setColor(fgColor);
+ if (buttonId == TabControlButton.ID_RESTORE_GROUP_BUTTON) {
+ // Draw one little window on top of another.
+ int marginX = round(3 * scaling);
+ int marginY = round(3 * scaling);
+ int winWidth = round(7.0 * scaling);
+ int winHeight = round(6.0 * scaling);
+ // Upper right-hand corner.
+ int win1X = width - marginX - winWidth;
+ int win1Y = marginY;
+ /* Lower left-hand corner. Make sure the window symbols are not
too close on any scaling
+ level. */
+ int win2X = Math.min((int) Math.floor(win1X - 2 * scaling),
marginX);
+ int win2Y = Math.max((int) Math.ceil(win1Y + 2 * scaling),
round(height - 9.5 * scaling));
+ Area win1 = getWindowSymbol(scaling, win1X, win1Y, winWidth,
winHeight);
+ Area win2 = getWindowSymbol(scaling, win2X, win2Y, winWidth,
winHeight);
+ // Make window 2 appear "on top of" window 1.
+ win1.subtract(new Area(win2.getBounds2D()));
+ g.fill(win1);
+ g.fill(win2);
+ } else if (buttonId == TabControlButton.ID_SLIDE_GROUP_BUTTON) {
+ int marginX = (int) (3 * scaling);
+ int marginTop = (int) (4 * scaling);
+ int marginBot = (int) (4 * scaling);
+ Area win = getWindowSymbol(scaling, marginX, marginTop,
+ width - 2 * marginX,
+ height - marginTop - marginBot);
+ g.fill(win);
+ } else if (buttonId == TabControlButton.ID_PIN_BUTTON) {
+ int marginX = (int) (3 * scaling);
+ int marginTop = (int) (2 * scaling);
+ int marginBot = (int) (2 * scaling);
+ Area win = getWindowSymbol(scaling, marginX, marginTop,
+ width - 2 * marginX,
+ height - marginTop - marginBot);
+ g.fill(win);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+
+ private void paintLargerRectangleIcon(
+ Component c, Graphics2D g, int width, int height, double scaling)
+ {
+ final Color bgTopColor;
+ final Color bgBotColor;
+ final Color symbolColor;
+ final Color borderColor;
+ // These colors taken from the previous bitmap icons.
+ if (buttonState == TabControlButton.STATE_DEFAULT) {
+ bgTopColor = new Color(191, 191, 191);
+ bgBotColor = new Color(135, 135, 135);
+ borderColor = new Color(81, 81, 81);
+ symbolColor = new Color(48, 48, 48);
+ } else if (buttonState == TabControlButton.STATE_PRESSED) {
+ bgTopColor = new Color(182, 182, 182);
+ bgBotColor = new Color(129, 129, 129);
+ borderColor = new Color(81, 81, 81);
+ symbolColor = new Color(45, 45, 45);
+ } else if (buttonState == TabControlButton.STATE_DISABLED) {
+ bgTopColor = new Color(166, 166, 166);
+ bgBotColor = new Color(137, 137, 137);
+ borderColor = new Color(111, 111, 111);
+ symbolColor = new Color(97, 97, 97);
+ } else if (buttonState == TabControlButton.STATE_ROLLOVER) {
+ bgTopColor = new Color(198, 198, 198);
+ bgBotColor = new Color(149, 149, 149);
+ borderColor = new Color(81, 81, 81);
+ symbolColor = new Color(77, 77, 77);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ /* Pick a stroke width that will make the outer border 1 physical
pixel wide on both 100%
+ and 200% (Retina) scaling, for consistency with native segmented
controls. */
+ int strokeWidth = round(0.6 * scaling);
+ g.setPaint(new GradientPaint(new Point2D.Double(0, strokeWidth),
+ bgTopColor,
+ new Point2D.Double(0, height - strokeWidth),
+ bgBotColor));
+ /* Make the scroll left and right buttons extend beyond their right
and left edges,
+ respectively. Then clip them at the icon's dimensions to get the
correct segmented control
+ effect. */
+ int rectExtraDir;
+ if (buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON) {
+ rectExtraDir = 1;
+ } else if (buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON) {
+ rectExtraDir = -1;
+ } else {
+ rectExtraDir = 0;
+ }
+ /* Use a rounded rectangle radius consistent with that of segmented
buttons and comboboxes
+ on MacOS High Sierra. (To match the old bitmap Aqua LAF exactly, we
could have used 4.0
+ here instead.) */
+ double arc = scaling * 6.0;
+ double rectExtraX = rectExtraDir * (strokeWidth + arc);
+ Shape rect = new RoundRectangle2D.Double(
+ strokeWidth / 2.0 + (rectExtraDir < 0 ? rectExtraX : 0),
+ strokeWidth / 2.0,
+ width - strokeWidth + Math.abs(rectExtraX),
+ height - strokeWidth, arc, arc);
+ g.clipRect(0, 0, width, height);
+ // Draw the gradient background of the rounded rectangle.
+ g.fill(rect);
+ // Now draw the border around the rounded rectangle.
+ g.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND));
+ g.setColor(borderColor);
+ g.draw(rect);
+ // The width to use for centering.
+ int useWidth;
+ if (buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON) {
+ // The scroll left button includes the separator line against the
scroll right button.
+ g.fillRect(width - strokeWidth, 0, strokeWidth, height);
+ useWidth = width - strokeWidth;
+ } else {
+ useWidth = width;
+ }
+ g.setColor(symbolColor);
+ if (buttonId == TabControlButton.ID_MAXIMIZE_BUTTON) {
+ int marginX = round(4 * scaling);
+ int marginTop = round(3 * scaling);
+ int marginBot = round(3 * scaling);
+ /* Draw one larger window symbol. The getWindowSymbol method
ensures we are using the
+ same window border thickness as for ID_RESTORE_BUTTON. */
+ g.fill(getWindowSymbol(scaling, marginX, marginTop,
+ width - 2 * marginX, height - marginTop - marginBot));
+ } else if (buttonId == TabControlButton.ID_RESTORE_BUTTON) {
+ // Draw one little window on top of another.
+ int marginX = round(4 * scaling);
+ int marginTop = round(2 * scaling);
+ int marginBot = round(2.5 * scaling);
+ int winWidth = round(9 * scaling);
+ int winHeight = round(7.0 * scaling);
+ // Upper right-hand corner.
+ int win1X = width - marginX - winWidth;
+ int win1Y = marginTop;
+ /* Lower left-hand corner. Make sure the window symbols are not
too close on any scaling
+ level. */
+ int win2X = Math.min((int) Math.floor(win1X - 2 * scaling),
marginX);
+ int win2Y = Math.max(win1Y + round(2.7 * scaling), height -
winHeight - marginBot);
+ Area win1 = getWindowSymbol(scaling, win1X, win1Y, winWidth,
winHeight);
+ Area win2 = getWindowSymbol(scaling, win2X, win2Y, winWidth,
winHeight);
+ // Make window 2 appear "on top of" window 1.
+ win1.subtract(new Area(win2.getBounds2D()));
+ g.fill(win1);
+ g.fill(win2);
+ } else if (buttonId == TabControlButton.ID_DROP_DOWN_BUTTON ||
+ buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON ||
+ buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON)
+ {
+ if (buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON) {
+ // Rotate 90 degrees clockwise, with a small position
adjustment.
+ g.translate(round(1 * scaling), 0);
+ g.rotate(Math.PI / 2.0, useWidth / 2.0, height / 2.0);
+ } else if (buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON) {
+ // Rotate 90 degrees counterclockwise, with a small position
adjustment.
+ g.translate(-round(1 * scaling), 0);
+ g.rotate(-Math.PI / 2.0, useWidth / 2.0, height / 2.0);
+ }
+ double arrowWidth, arrowHeight;
+ if (buttonId == TabControlButton.ID_DROP_DOWN_BUTTON) {
+ /* Make the arrow a tiny bit wider than in the old bitmap
icons here. Using
+ arrowWidth = 5.0 would have given the exact same dimensions as
the old bitmap icons
+ at 100% scaling. */
+ arrowWidth = 6.0 * scaling;
+ arrowHeight = 4.0 * scaling;
+ } else {
+ // These dimensions match the old bitmap icons at 100% scaling.
+ arrowWidth = 6.7 * scaling;
+ arrowHeight = 3.8 * scaling;
+ }
+
+ /* Draw a simple arrowhead triangle pointing downwards (before any
rotations). Keep the
+ top line aligned to device pixels. No need to round the other
positions. */
+ final int y = round((height - arrowHeight) / 2.0);
+ final double marginX = (useWidth - arrowWidth) / 2.0;
+ final double arrowMidX = marginX + arrowWidth / 2.0;
+ Path2D.Double arrowPath = new Path2D.Double();
+ arrowPath.moveTo(arrowMidX - arrowWidth / 2.0, y);
+ arrowPath.lineTo(arrowMidX, y + arrowHeight);
+ arrowPath.lineTo(arrowMidX + arrowWidth / 2.0, y);
+ arrowPath.closePath();
+ g.fill(arrowPath);
+ }
+ }
+
+ /**
+ * Make a small window symbol. This is used in several of the icons here.
All coordinates are
+ * in device pixels.
+ */
+ private Area getWindowSymbol(double scaling, int x, int y, int width, int
height) {
+ /* Pick a thickness that will make the window symbol border 2 physical
pixels wide at 200%
+ scaling, to look consistent with the rest of the UI, including borders
and icons that do not
+ have any special Retina support. */
+ int borderThickness = round(0.8 * scaling);
+ int titleBarHeight =
+ (buttonId == TabControlButton.ID_SLIDE_GROUP_BUTTON ||
+ buttonId == TabControlButton.ID_PIN_BUTTON)
+ ? borderThickness
+ : Math.max(round(1.6 * scaling), borderThickness + height / 7);
+ int windowX = round(x);
+ int windowY = round(y);
+ Area ret = new Area(new Rectangle2D.Double(
+ windowX, windowY, width, height));
+ ret.subtract(new Area(new Rectangle2D.Double(
+ windowX + borderThickness, windowY + titleBarHeight,
+ width - borderThickness * 2,
+ height - borderThickness - titleBarHeight)));
+ if (buttonId == TabControlButton.ID_SLIDE_GROUP_BUTTON) {
+ ret.add(new Area(new Rectangle2D.Double(
+ windowX + borderThickness * 2,
+ windowY + height - borderThickness * 4,
+ round((width - borderThickness * 4) * 0.67),
+ borderThickness * 2)));
+ } else if (buttonId == TabControlButton.ID_PIN_BUTTON) {
+ int marginX = round(width * 0.3);
+ int marginY = round(height * 0.3);
+ ret.add(new Area(new Rectangle2D.Double(
+ windowX + marginX, windowY + marginY,
+ width - marginX * 2,
+ height - marginY * 2)));
+ }
+ return ret;
+ }
+}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorViewTabDisplayerUI.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorViewTabDisplayerUI.java
new file mode 100644
index 0000000..711ff29
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaVectorViewTabDisplayerUI.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+
+/**
+ * A variation on the Aqua view tab displayer UI that uses scalable icons for
Retina screens.
+ * See {@link AquaVectorTabControlIcon}.
+ */
+public final class AquaVectorViewTabDisplayerUI extends AquaViewTabDisplayerUI
{
+ private AquaVectorViewTabDisplayerUI(TabDisplayer displayer) {
+ super(displayer);
+ }
+
+ public static ComponentUI createUI(JComponent c) {
+ return new AquaVectorViewTabDisplayerUI((TabDisplayer) c);
+ }
+
+ @Override
+ public Icon getButtonIcon(int buttonId, int buttonState) {
+ Icon ret = AquaVectorTabControlIcon.get(buttonId, buttonState);
+ return ret != null ? ret : super.getButtonIcon(buttonId, buttonState);
+ }
+}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java
index 5d48bc1..c5afc65 100644
---
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/AquaViewTabDisplayerUI.java
@@ -46,7 +46,7 @@ import
org.netbeans.swing.tabcontrol.WinsysInfoForTabbedContainer;
*
* @author Tim Boudreau
*/
-public final class AquaViewTabDisplayerUI extends AbstractViewTabDisplayerUI {
+public class AquaViewTabDisplayerUI extends AbstractViewTabDisplayerUI {
private static final int TXT_X_PAD = 5;
private static final int ICON_X_PAD = 2;
@@ -64,7 +64,7 @@ public final class AquaViewTabDisplayerUI extends
AbstractViewTabDisplayerUI {
/**
* Should be constructed only from createUI method.
*/
- private AquaViewTabDisplayerUI(TabDisplayer displayer) {
+ protected AquaViewTabDisplayerUI(TabDisplayer displayer) {
super(displayer);
prefSize = new Dimension(100, 19); //XXX huh?
}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabControlButtonFactory.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabControlButtonFactory.java
index 25ec177..e789d34 100644
---
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabControlButtonFactory.java
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/TabControlButtonFactory.java
@@ -29,7 +29,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.Icon;
-import javax.swing.JButton;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.ToolTipManager;
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinVistaEditorTabCellRenderer.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinVistaEditorTabCellRenderer.java
index 2396eae..b6d729b 100644
---
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinVistaEditorTabCellRenderer.java
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/WinVistaEditorTabCellRenderer.java
@@ -256,17 +256,19 @@ class WinVistaEditorTabCellRenderer extends
AbstractTabCellRenderer {
}
/**
- * Returns path of icon which is correct for currect state of tab at given
+ * Returns the icon which is correct for currect state of tab at given
* index
*/
- String findIconPath() {
+ Icon findIcon() {
+ final String file;
if( inCloseButton() && isPressed() ) {
- return "org/openide/awt/resources/vista_close_pressed.png"; //
NOI18N
- }
- if( inCloseButton() ) {
- return "org/openide/awt/resources/vista_close_rollover.png"; //
NOI18N
+ file = "org/openide/awt/resources/vista_close_pressed.png"; //
NOI18N
+ } else if ( inCloseButton() ) {
+ file = "org/openide/awt/resources/vista_close_rollover.png"; //
NOI18N
+ } else {
+ file = "org/openide/awt/resources/vista_close_enabled.png"; //
NOI18N
}
- return "org/openide/awt/resources/vista_close_enabled.png"; // NOI18N
+ return TabControlButtonFactory.getIcon(file);
}
private static class WinVistaPainter implements TabPainter {
@@ -290,8 +292,7 @@ class WinVistaEditorTabCellRenderer extends
AbstractTabCellRenderer {
rect.height = 0;
return;
}
- String iconPath = ren.findIconPath();
- Icon icon = TabControlButtonFactory.getIcon(iconPath);
+ Icon icon = ren.findIcon();
int iconWidth = icon.getIconWidth();
int iconHeight = icon.getIconHeight();
rect.x = bounds.x + bounds.width - iconWidth - 2;
@@ -389,8 +390,7 @@ class WinVistaEditorTabCellRenderer extends
AbstractTabCellRenderer {
}
//paint close button
- String iconPath = ren.findIconPath();
- Icon icon = TabControlButtonFactory.getIcon( iconPath );
+ Icon icon = ren.findIcon();
icon.paintIcon(ren, g, r.x, r.y);
}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabCellRenderer.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabCellRenderer.java
index 5c58a37..6723ad7 100644
---
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabCellRenderer.java
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabCellRenderer.java
@@ -24,6 +24,7 @@
package org.netbeans.swing.tabcontrol.plaf;
import java.awt.*;
+import javax.swing.Icon;
/**
* Windows 8 implementation of tab renderer
@@ -31,7 +32,7 @@ import java.awt.*;
* @author S. Aubrecht
* @since 1.41
*/
-final class Windows8EditorTabCellRenderer extends
WinVistaEditorTabCellRenderer {
+class Windows8EditorTabCellRenderer extends WinVistaEditorTabCellRenderer {
public Windows8EditorTabCellRenderer() {
}
@@ -46,18 +47,16 @@ final class Windows8EditorTabCellRenderer extends
WinVistaEditorTabCellRenderer
Windows8ViewTabDisplayerUI.paintTabBackground( (Graphics2D)g, rect.x,
rect.y, rect.width, rect.height, selected, focused, attention, mouseOver);
}
- /**
- * Returns path of icon which is correct for currect state of tab at given
- * index
- */
@Override
- String findIconPath() {
+ Icon findIcon() {
+ final String file;
if( inCloseButton() && isPressed() ) {
- return "org/openide/awt/resources/win8_bigclose_pressed.png"; //
NOI18N
+ file = "org/openide/awt/resources/win8_bigclose_pressed.png"; //
NOI18N
+ } else if( inCloseButton() ) {
+ file = "org/openide/awt/resources/win8_bigclose_rollover.png"; //
NOI18N
+ } else {
+ file = "org/openide/awt/resources/win8_bigclose_enabled.png"; //
NOI18N
}
- if( inCloseButton() ) {
- return "org/openide/awt/resources/win8_bigclose_rollover.png"; //
NOI18N
- }
- return "org/openide/awt/resources/win8_bigclose_enabled.png"; // NOI18N
+ return TabControlButtonFactory.getIcon(file);
}
}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabDisplayerUI.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabDisplayerUI.java
index 74612d0..f8d2d8b 100644
---
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabDisplayerUI.java
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8EditorTabDisplayerUI.java
@@ -32,7 +32,7 @@ import org.netbeans.swing.tabcontrol.TabDisplayer;
* @author S. Aubrecht
* @since 1.41
*/
-public final class Windows8EditorTabDisplayerUI extends
AbstractWinEditorTabDisplayerUI {
+public class Windows8EditorTabDisplayerUI extends
AbstractWinEditorTabDisplayerUI {
private static Map<Integer, String[]> buttonIconPaths;
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabCellRenderer.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabCellRenderer.java
new file mode 100644
index 0000000..984bfc7
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabCellRenderer.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+
+/**
+ * A variation on the Windows 8 editor tab cell renderer that uses scalable
icons for HiDPI screens.
+ * See {@link Windows8VectorTabControlIcon}. The icons should otherwise look
the same.
+ */
+final class Windows8VectorEditorTabCellRenderer extends
Windows8EditorTabCellRenderer {
+ @Override
+ Icon findIcon() {
+ if( inCloseButton() && isPressed() ) {
+ return
Windows8VectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON,
TabControlButton.STATE_PRESSED);
+ } else if( inCloseButton() ) {
+ return
Windows8VectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON,
TabControlButton.STATE_ROLLOVER);
+ } else {
+ return
Windows8VectorTabControlIcon.get(TabControlButton.ID_CLOSE_BUTTON,
TabControlButton.STATE_DEFAULT);
+ }
+ }
+}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabDisplayerUI.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabDisplayerUI.java
new file mode 100644
index 0000000..a4ae65f
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorEditorTabDisplayerUI.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+
+/**
+ * A variation on the Windows 8 editor tab displayer UI that uses scalable
icons for HiDPI screens.
+ * See {@link Windows8VectorTabControlIcon}. The icons should otherwise look
the same.
+ */
+public final class Windows8VectorEditorTabDisplayerUI extends
Windows8EditorTabDisplayerUI {
+ public Windows8VectorEditorTabDisplayerUI(TabDisplayer displayer) {
+ super(displayer);
+ }
+
+ public static ComponentUI createUI(JComponent c) {
+ return new Windows8VectorEditorTabDisplayerUI((TabDisplayer) c);
+ }
+
+ @Override
+ protected TabCellRenderer createDefaultRenderer() {
+ return new Windows8VectorEditorTabCellRenderer();
+ }
+
+ @Override
+ public Icon getButtonIcon(int buttonId, int buttonState) {
+ Icon ret = Windows8VectorTabControlIcon.get(buttonId, buttonState);
+ return ret != null ? ret : super.getButtonIcon(buttonId, buttonState);
+ }
+}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorTabControlIcon.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorTabControlIcon.java
new file mode 100644
index 0000000..9e14b5f
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorTabControlIcon.java
@@ -0,0 +1,242 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import org.openide.util.VectorIcon;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.geom.Area;
+import java.awt.geom.Line2D;
+import java.awt.geom.Path2D;
+import java.awt.geom.Rectangle2D;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.swing.Icon;
+
+/**
+ * Scalable vector icons for the Windows tab control L&F, for use with
HiDPI screens. These
+ * icons look good at all of the standard scaling factors available on Windows
(see superclass
+ * Javadoc). At 100% scale, they look nearly identical to bitmap icons that
were used previously for
+ * the Windows 8 LAF.
+ *
+ * @author Eirik Bakke
+ */
+@SuppressWarnings("serial")
+final class Windows8VectorTabControlIcon extends VectorIcon {
+ private static final Map<Entry<Integer,Integer>,Icon> INSTANCES =
populateInstances();
+ private final int buttonId;
+ private final int buttonState;
+
+ private static void populateOne(
+ Map<Entry<Integer,Integer>,Icon> toMap, int buttonId, int
buttonState)
+ {
+ toMap.put(new SimpleEntry<Integer,Integer>(buttonId, buttonState),
+ new Windows8VectorTabControlIcon(buttonId, buttonState));
+ }
+
+ private static Map<Entry<Integer,Integer>,Icon> populateInstances() {
+ // The string keys of these maps aren't currently used, but are useful
for debugging.
+ Map<String, Integer> buttonIDs = new LinkedHashMap<String, Integer>();
+ // ViewTabDisplayerUI
+ buttonIDs.put("close", TabControlButton.ID_CLOSE_BUTTON);
+ /* These don't seem to be in use anymore. Or at least they don't have
modernized icons in
+ the Windows8 LAF. */
+ //buttonIDs.put("slide_right", TabControlButton.ID_SLIDE_RIGHT_BUTTON);
+ //buttonIDs.put("slide_left", TabControlButton.ID_SLIDE_LEFT_BUTTON);
+ //buttonIDs.put("slide_down", TabControlButton.ID_SLIDE_DOWN_BUTTON);
+ buttonIDs.put("pin", TabControlButton.ID_PIN_BUTTON);
+ buttonIDs.put("restore_group",
TabControlButton.ID_RESTORE_GROUP_BUTTON);
+ buttonIDs.put("slide_group", TabControlButton.ID_SLIDE_GROUP_BUTTON);
+ // EditorTabDisplayerUI
+ buttonIDs.put("scroll_left", TabControlButton.ID_SCROLL_LEFT_BUTTON);
+ buttonIDs.put("scroll_right", TabControlButton.ID_SCROLL_RIGHT_BUTTON);
+ buttonIDs.put("drop_down", TabControlButton.ID_DROP_DOWN_BUTTON);
+ buttonIDs.put("maximize", TabControlButton.ID_MAXIMIZE_BUTTON);
+ buttonIDs.put("restore", TabControlButton.ID_RESTORE_BUTTON);
+ Map<String, Integer> buttonStates = new LinkedHashMap<String,
Integer>();
+ buttonStates.put("default", TabControlButton.STATE_DEFAULT);
+ buttonStates.put("pressed", TabControlButton.STATE_PRESSED);
+ buttonStates.put("disabled", TabControlButton.STATE_DISABLED);
+ buttonStates.put("rollover", TabControlButton.STATE_ROLLOVER);
+ Map<Entry<Integer,Integer>,Icon> ret = new
LinkedHashMap<Entry<Integer,Integer>,Icon>();
+ for (Entry<String,Integer> buttonID : buttonIDs.entrySet()) {
+ for (Entry<String,Integer> buttonState : buttonStates.entrySet()) {
+ populateOne(ret, buttonID.getValue(), buttonState.getValue());
+ }
+ }
+ // Effectively immutable upon assignment to the final static variable.
+ return Collections.unmodifiableMap(ret);
+ }
+
+ private Windows8VectorTabControlIcon(int buttonId, int buttonState) {
+ super(14, 14);
+ this.buttonId = buttonId;
+ this.buttonState = buttonState;
+ }
+
+ /**
+ * @return null if the requested icon is not available in vector format
+ */
+ public static Icon get(int buttonId, int buttonState) {
+ return INSTANCES.get(new SimpleEntry<Integer,Integer>(buttonId,
buttonState));
+ }
+
+ @Override
+ protected void paintIcon(Component c, Graphics2D g, int width, int height,
double scaling) {
+ Color bgColor = new Color(0, 0, 0, 0); // Alpha zero means no
background.
+ Color fgColor = new Color(86, 86, 86, 255);
+ {
+ Color closeColor = (buttonId == TabControlButton.ID_CLOSE_BUTTON)
+ // A nice red.
+ ? new Color(199, 79, 80, 255) : null;
+ if (buttonState == TabControlButton.STATE_DISABLED) {
+ // Light grey (via transparent black to work well on any
background).
+ fgColor = new Color(0, 0, 0, 45);
+ } else if (buttonState == TabControlButton.STATE_PRESSED) {
+ // A nice blue.
+ bgColor = closeColor != null ? closeColor : new Color(57, 100,
178, 255);
+ fgColor = Color.WHITE;
+ } else if (buttonState == TabControlButton.STATE_ROLLOVER) {
+ bgColor = closeColor != null ? closeColor
+ // Grey (via transparent black to work well on any
background).
+ : new Color(0, 0, 0, 70);
+ fgColor = Color.WHITE;
+ }
+ }
+ if (bgColor.getAlpha() > 0) {
+ g.setColor(bgColor);
+ g.fillRect(0, 0, width, height);
+ }
+ g.setColor(fgColor);
+ if (buttonId == TabControlButton.ID_CLOSE_BUTTON) {
+ // Draw an "X" with a flat top and bottom.
+ if (getIconWidth() == width && getIconHeight() == height) {
+ // For the unscaled case, this icon looks better without
anti-aliasing.
+ setAntiAliasing(g, false);
+ }
+ // Use a slightly heavier line when there's a non-light background.
+ double strokeWidth = (bgColor.getAlpha() > 0 ? 1.0 : 0.8) *
scaling;
+ if (scaling > 1.0) {
+ // Use a heavier line when we have more pixels available.
+ strokeWidth *= 1.5f;
+ }
+ double marginX = 3.5 * scaling; // Don't round this one.
+ int topMarginY = round(3 * scaling);
+ int botMarginY = round(4 * scaling);
+ // Flatten the top and bottom.
+ g.clip(new Rectangle2D.Double(0, topMarginY, width, height -
topMarginY - botMarginY));
+ // Draw the "X".
+ g.setStroke(new BasicStroke((float) strokeWidth));
+ g.draw(new Line2D.Double(marginX, topMarginY, width - marginX,
height - botMarginY));
+ g.draw(new Line2D.Double(width - marginX, topMarginY, marginX,
height - botMarginY));
+ } else if (buttonId == TabControlButton.ID_PIN_BUTTON ||
+ buttonId == TabControlButton.ID_RESTORE_GROUP_BUTTON ||
+ buttonId == TabControlButton.ID_RESTORE_BUTTON)
+ {
+ // Draw one little window on top of another.
+ int margin = round(2 * scaling);
+ int winWidth = round(6.5 * scaling);
+ int winHeight = round(5.5 * scaling);
+ // Upper right-hand corner.
+ int win1X = width - margin - winWidth;
+ int win1Y = margin;
+ // Lower left-hand corner.
+ int win2X = margin;
+ int win2Y = round(5.5 * scaling);
+ Area win1 = getWindowSymbol(scaling, win1X, win1Y, winWidth,
winHeight);
+ Area win2 = getWindowSymbol(scaling, win2X, win2Y, winWidth,
winHeight);
+ // Make window 2 appear "on top of" window 1.
+ win1.subtract(new Area(win2.getBounds2D()));
+ g.fill(win1);
+ g.fill(win2);
+ } else if (buttonId == TabControlButton.ID_MAXIMIZE_BUTTON) {
+ int marginX = round(2.2 * scaling);
+ int marginY = round(3 * scaling);
+ int windowHeight = round(7.5 * scaling);
+ /* Draw one larger window. The getWindowSymbol method ensures we
are using the same
+ window border thickness as for ID_RESTORE_BUTTON. */
+ g.fill(getWindowSymbol(scaling, marginX, marginY, width - 2 *
marginX, windowHeight));
+ } else if (buttonId == TabControlButton.ID_SLIDE_GROUP_BUTTON) {
+ // Draw a simple bar towards the bottom of the icon.
+ int marginX = round(2 * scaling);
+ int barX = marginX;
+ int barY = round(8 * scaling);
+ int barWidth = width - marginX * 2;
+ // Use the same thickness as the title bar in getWindowSymbol.
+ int barThickness = round(1.8 * scaling);
+ g.fill(new Rectangle2D.Double(barX, barY, barWidth, barThickness));
+ } else if (buttonId == TabControlButton.ID_DROP_DOWN_BUTTON ||
+ buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON ||
+ buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON)
+ {
+ if (getIconWidth() == width && getIconHeight() == height) {
+ // For the regular 100% scaling level, this icon looks better
without anti-aliasing.
+ setAntiAliasing(g, false);
+ }
+ if (buttonId == TabControlButton.ID_SCROLL_LEFT_BUTTON) {
+ // Rotate 90 degrees clockwise, with a small position
adjustment.
+ g.translate(-round(1 * scaling), 0);
+ g.rotate(Math.PI / 2.0, width / 2.0, height / 2.0);
+ } else if (buttonId == TabControlButton.ID_SCROLL_RIGHT_BUTTON) {
+ // Rotate 90 degrees counterclockwise, with a small position
adjustment.
+ g.translate(round(1 * scaling), 0);
+ g.rotate(-Math.PI / 2.0, width / 2.0, height / 2.0);
+ }
+ /* Draw a simple arrowhead triangle pointing downwards (before any
rotations). Keep the
+ top line pixel-aligned. No need to round the other positions. */
+ final int y = round(4.0 * scaling);
+ final double arrowWidth = (scaling == 1.0 ? 12.0 : 10.0) * scaling;
+ final double arrowHeight = 5.0 * scaling;
+ final double marginX = (width - arrowWidth) / 2.0;
+ final double arrowMidX = marginX + arrowWidth / 2.0;
+ Path2D.Double arrowPath = new Path2D.Double();
+ arrowPath.moveTo(arrowMidX - arrowWidth / 2.0, y);
+ arrowPath.lineTo(arrowMidX, y + arrowHeight);
+ arrowPath.lineTo(arrowMidX + arrowWidth / 2.0, y);
+ arrowPath.closePath();
+ g.fill(arrowPath);
+ }
+ }
+
+ /**
+ * Make a small window symbol (hollow rectangle with a thicker "title bar"
on top). This is used
+ * in a couple of the icons here. All coordinates are in device pixels.
+ */
+ private static Area getWindowSymbol(
+ double scaling, int x, int y, int width, int height)
+ {
+ /* Pick a thickness that will make the window symbol border 2 physical
pixels wide at 200%
+ scaling, to look consistent with the rest of the UI, including
existing icons that do not
+ have any special HiDPI support. Lower scaling levels will yield a 1
physical pixel wide
+ border. */
+ int borderThickness = round(0.8 * scaling);
+ int titleBarHeight = Math.max(round(1.8 * scaling), borderThickness +
1);
+ Area ret = new Area(new Rectangle2D.Double(x, y, width, height));
+ ret.subtract(new Area(new Rectangle2D.Double(
+ x + borderThickness, y + titleBarHeight,
+ width - borderThickness * 2,
+ height - borderThickness - titleBarHeight)));
+ return ret;
+ }
+}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorViewTabDisplayerUI.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorViewTabDisplayerUI.java
new file mode 100644
index 0000000..b7a8a6f
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8VectorViewTabDisplayerUI.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+
+/**
+ * A variation on the Windows 8 view tab displayer UI that uses scalable icons
for HiDPI screens.
+ * See {@link Windows8VectorTabControlIcon}. The icons should otherwise look
the same.
+ */
+public final class Windows8VectorViewTabDisplayerUI extends
Windows8ViewTabDisplayerUI {
+ private Windows8VectorViewTabDisplayerUI(TabDisplayer displayer) {
+ super(displayer);
+ }
+
+ public static ComponentUI createUI(JComponent c) {
+ return new Windows8VectorViewTabDisplayerUI((TabDisplayer) c);
+ }
+
+ @Override
+ public Icon getButtonIcon(int buttonId, int buttonState) {
+ Icon ret = Windows8VectorTabControlIcon.get(buttonId, buttonState);
+ return ret != null ? ret : super.getButtonIcon(buttonId, buttonState);
+ }
+}
diff --git
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8ViewTabDisplayerUI.java
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8ViewTabDisplayerUI.java
index d5bbd57..208e4c2 100644
---
a/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8ViewTabDisplayerUI.java
+++
b/platform/o.n.swing.tabcontrol/src/org/netbeans/swing/tabcontrol/plaf/Windows8ViewTabDisplayerUI.java
@@ -36,7 +36,7 @@ import org.netbeans.swing.tabcontrol.TabDisplayer;
* @author S. Aubrecht
* @since 1.41
*/
-public final class Windows8ViewTabDisplayerUI extends
AbstractWinViewTabDisplayerUI {
+public class Windows8ViewTabDisplayerUI extends AbstractWinViewTabDisplayerUI {
/**
* True when colors were already initialized, false otherwise
@@ -58,7 +58,7 @@ public final class Windows8ViewTabDisplayerUI extends
AbstractWinViewTabDisplaye
/**
* Should be constructed only from createUI method.
*/
- private Windows8ViewTabDisplayerUI(TabDisplayer displayer) {
+ protected Windows8ViewTabDisplayerUI(TabDisplayer displayer) {
super(displayer);
}
diff --git
a/platform/o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java
b/platform/o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java
new file mode 100644
index 0000000..0f0be3e
--- /dev/null
+++
b/platform/o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java
@@ -0,0 +1,556 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.swing.tabcontrol.plaf;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import javax.imageio.ImageIO;
+import javax.swing.Icon;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.KeyStroke;
+import javax.swing.Scrollable;
+import javax.swing.SwingUtilities;
+import javax.swing.Timer;
+import javax.swing.UIManager;
+import org.netbeans.swing.tabcontrol.TabDisplayer;
+import org.netbeans.swing.tabcontrol.TabDisplayerUI;
+import org.openide.util.ImageUtilities;
+
+/**
+ * Utility for previewing custom-painted vector icons, at various resolutions
and comparing
+ * side-by-side with existing LAFs. When implementing new vector icons, invoke
this utility using
+ * the "Debug" command in NetBeans, then invoke "Apply Code Changes" to have
your latest changes
+ * immediately be reflected on the screen in this utility.
+ *
+ * <p>Each displayed column corresponds to one LAF or ComponentUI
implementation, as specified in
+ * the getIcons method below. The last column displays either the first or the
second LAF in the
+ * list, toggleable with the Shift key, to facilitate comparison and visual
alignment. Pressing
+ * Space, or holding down Shift, will will enable a mode where the last column
quickly toggles back
+ * and forth between the first two LAFs for comparison. A copy of the output
with also be written to
+ * a PNG file in the temporary directory when the utility is first launched.
+ *
+ * <p>This utility is currently configured to show icons related to the
NetBeans tabcontrol widget.
+ * It can be copied to other modules during development and reconfigured to
show other icons, by
+ * modifying the {@code getIcons()} method.
+ *
+ * @author Eirik Bakke
+ */
+public class VectorIconTester extends javax.swing.JFrame {
+ private static final boolean TEST_AQUA = true;
+ private static final boolean TEST_WIN8 = true;
+ private final JScrollPane scrollPane;
+ private final IconPreviewPane iconPreviewPane;
+
+ public VectorIconTester() {
+ iconPreviewPane = new IconPreviewPane(getIcons());
+ scrollPane = new JScrollPane(iconPreviewPane);
+ initComponents();
+ }
+
+ public void dumpGraphicsToFile() {
+ final BufferedImage bi = new BufferedImage(
+ iconPreviewPane.getSize().width,
iconPreviewPane.getSize().height,
+ BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = bi.createGraphics();
+ try {
+ iconPreviewPane.paintComponent(g);
+ } finally {
+ g.dispose();
+ }
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ File tempFile = File.createTempFile("VectorIconTester",
".png");
+ ImageIO.write(bi, "PNG", tempFile);
+ System.out.println("Output was written to " + tempFile);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+ }
+
+ private void initComponents() {
+ setTitle("Vector Icon Tester");
+ setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
+ setLayout(new BorderLayout());
+ add(scrollPane, BorderLayout.CENTER);
+ setSize(800, 600);
+ setExtendedState(JFrame.MAXIMIZED_BOTH);
+ }
+
+ private static Map<String, Icon> getIcons() {
+ Map<String, Icon> ret = new LinkedHashMap<String, Icon>();
+ if (TEST_AQUA) {
+ addTabDisplayerIcons(ret, "mac", (TabDisplayerUI)
AquaViewTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "mac", (TabDisplayerUI)
AquaEditorTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "macvec", (TabDisplayerUI)
AquaVectorViewTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "macvec", (TabDisplayerUI)
AquaVectorEditorTabDisplayerUI.createUI(new TabDisplayer()));
+ }
+ if (TEST_WIN8) {
+ addTabDisplayerIcons(ret, "win8", (TabDisplayerUI)
Windows8ViewTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "win8", (TabDisplayerUI)
Windows8EditorTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "win8vec", (TabDisplayerUI)
Windows8VectorViewTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "win8vec", (TabDisplayerUI)
Windows8VectorEditorTabDisplayerUI.createUI(new TabDisplayer()));
+ }
+
+ if (false) {
+ addTabDisplayerIcons(ret, "gtk", (TabDisplayerUI)
GtkViewTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "gtk", (TabDisplayerUI)
GtkEditorTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "nimbus", (TabDisplayerUI)
NimbusViewTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "nimbus", (TabDisplayerUI)
NimbusEditorTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "metal", (TabDisplayerUI)
MetalViewTabDisplayerUI.createUI(new TabDisplayer()));
+ addTabDisplayerIcons(ret, "metal", (TabDisplayerUI)
MetalEditorTabDisplayerUI.createUI(new TabDisplayer()));
+ }
+ if (false) {
+ // Icons that are not specialized by LAF.
+ ret.put("gen_arrow",
ImageUtilities.loadImageIcon("org/openide/awt/resources/arrow.png", false));
+ ret.put("gen_busy_icon",
ImageUtilities.loadImageIcon("org/netbeans/swing/tabcontrol/resources/busy_icon.png",
false));
+ ret.put("gen_toolbar_arrow_horizontal",
ImageUtilities.loadImageIcon("org/openide/awt/resources/toolbar_arrow_horizontal.png",
false));
+ ret.put("gen_toolbar_arrow_vertical",
ImageUtilities.loadImageIcon("org/openide/awt/resources/toolbar_arrow_vertical.png",
false));
+ /* These are actually private classes in the openide.awt module.
They must be copied in here if
+ they are to be shown with the utility. */
+ /*
+ ret.put("genvec_arrow", ArrowIcon.INSTANCE_DEFAULT);
+ ret.put("genvec_toolbar_arrow_horizontal",
ToolbarArrowIcon.INSTANCE_HORIZONTAL);
+ ret.put("genvec_toolbar_arrow_vertical",
ToolbarArrowIcon.INSTANCE_VERTICAL);
+ */
+ }
+ return Collections.unmodifiableMap(ret);
+ }
+
+ /**
+ * Utility method to add icons specific to the tabcontrol LAFs. Irrelevant
when testing icons in
+ * other modules.
+ */
+ private static void addTabDisplayerIcons(
+ Map<String, Icon> toMap, String prefix, TabDisplayerUI
tabDisplayerUI)
+ {
+ Map<String, Integer> buttonIDs = new LinkedHashMap<String, Integer>();
+ // ViewTabDisplayerUI
+ buttonIDs.put("close", TabControlButton.ID_CLOSE_BUTTON);
+ buttonIDs.put("slide_right", TabControlButton.ID_SLIDE_RIGHT_BUTTON);
+ buttonIDs.put("slide_left", TabControlButton.ID_SLIDE_LEFT_BUTTON);
+ buttonIDs.put("slide_down", TabControlButton.ID_SLIDE_DOWN_BUTTON);
+ buttonIDs.put("pin", TabControlButton.ID_PIN_BUTTON);
+ buttonIDs.put("restore_group",
TabControlButton.ID_RESTORE_GROUP_BUTTON);
+ buttonIDs.put("slide_group", TabControlButton.ID_SLIDE_GROUP_BUTTON);
+ // EditorTabDisplayerUI
+ buttonIDs.put("scroll_left", TabControlButton.ID_SCROLL_LEFT_BUTTON);
+ buttonIDs.put("scroll_right", TabControlButton.ID_SCROLL_RIGHT_BUTTON);
+ buttonIDs.put("drop_down", TabControlButton.ID_DROP_DOWN_BUTTON);
+ buttonIDs.put("maximize", TabControlButton.ID_MAXIMIZE_BUTTON);
+ buttonIDs.put("restore", TabControlButton.ID_RESTORE_BUTTON);
+
+ Map<String, Integer> buttonStates = new LinkedHashMap<String,
Integer>();
+ buttonStates.put("default", TabControlButton.STATE_DEFAULT);
+ buttonStates.put("pressed", TabControlButton.STATE_PRESSED);
+ buttonStates.put("disabled", TabControlButton.STATE_DISABLED);
+ buttonStates.put("rollover", TabControlButton.STATE_ROLLOVER);
+ for (Entry<String, Integer> buttonID : buttonIDs.entrySet()) {
+ for (Entry<String, Integer> buttonState : buttonStates.entrySet())
{
+ Icon icon = tabDisplayerUI.getButtonIcon(buttonID.getValue(),
buttonState.getValue());
+ if (icon == null) {
+ continue;
+ }
+ String key = prefix + "_" + buttonID.getKey() + "_" +
buttonState.getKey();
+ Icon otherIcon = toMap.put(key, icon);
+ if (otherIcon != null && !otherIcon.equals(icon)) {
+ throw new RuntimeException("Two related LAF classes both
returned icons for key "
+ + key + "; not sure which one to display");
+ }
+ }
+ }
+ }
+
+ public static void main(String args[]) {
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ final VectorIconTester vit = new VectorIconTester();
+ vit.setVisible(true);
+ vit.validate();
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ vit.dumpGraphicsToFile();
+ }
+ });
+ }
+ });
+ }
+
+ private static final class IconPreviewPane extends JPanel implements
Scrollable {
+ private static final boolean INCLUDE_HUGE_ICON = true;
+ private static final int ICON_BASE_SIZE_X = TEST_AQUA ? 26 : 16;
+ private static final int ICON_BASE_SIZE_Y = 16;
+ private static final int ICON_ROW_HEIGHT
+ = Math.max(ICON_BASE_SIZE_Y * 3 + 8, (INCLUDE_HUGE_ICON ? 16 *
8 + 16 : 0));
+ private final Map<String, Map<String, Icon>> iconsByLAF;
+ private final Set<String> namesAfterLAF;
+ private int preferredWidth = 300;
+ /**
+ * If true, show the first entry in namesAfterLAF in the last
timer-switched column,
+ * otherwise show the second.
+ */
+ private boolean timerSwitchState = false;
+ /**
+ * Continuously switch between the two first LAFs in the last column
when Shift is held down
+ * (or toggled with space).
+ */
+ private final Timer lafSwitchTimer = new Timer(300, new
ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ switchLastColumnLAF();
+ }
+ });
+ /**
+ * Continuously repaint in case "Apply Code Changes" was applied in
the debugger to modify
+ * the drawing routine.
+ */
+ private final Timer repaintTimer = new Timer(300, new ActionListener()
{
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ repaint();
+ }
+ });
+
+ public void switchLastColumnLAF() {
+ timerSwitchState = !timerSwitchState;
+ repaint();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(preferredWidth,
+ namesAfterLAF.size() * ICON_ROW_HEIGHT + 2 *
ICON_ROW_HEIGHT);
+ }
+
+ public IconPreviewPane(Map<String, Icon> icons) {
+ this.iconsByLAF = new LinkedHashMap<String, Map<String, Icon>>();
+ this.namesAfterLAF = new LinkedHashSet<String>();
+ for (Entry<String, Icon> iconEntry : icons.entrySet()) {
+ String name = iconEntry.getKey();
+ int pos = name.indexOf("_");
+ if (pos < 2) {
+ throw new RuntimeException();
+ }
+ String lafPrefix = name.substring(0, pos);
+ Map<String, Icon> inLAFmap = iconsByLAF.get(lafPrefix);
+ if (inLAFmap == null) {
+ inLAFmap = new LinkedHashMap<String, Icon>();
+ iconsByLAF.put(lafPrefix, inLAFmap);
+ }
+ String nameAfterLAF = name.substring(pos + 1);
+ if (nameAfterLAF.isEmpty()) {
+ throw new RuntimeException();
+ }
+ inLAFmap.put(nameAfterLAF, iconEntry.getValue());
+ namesAfterLAF.add(nameAfterLAF);
+ // Some of the mac icons are actually 26 pixels wide.
+ if (false) {
+ Icon icon = iconEntry.getValue();
+ if (icon.getIconWidth() > ICON_BASE_SIZE_X) {
+ throw new RuntimeException();
+ }
+ if (icon.getIconHeight() > ICON_BASE_SIZE_Y) {
+ throw new RuntimeException();
+ }
+ }
+ }
+ this.lafSwitchTimer.setRepeats(true);
+ this.repaintTimer.setRepeats(true);
+ this.repaintTimer.start();
+ addKeyListener(new KeyAdapter() {
+ @Override
+ public void keyPressed(KeyEvent evt) {
+ KeyStroke ks = KeyStroke.getKeyStrokeForEvent(evt);
+ if (KeyStroke.getAWTKeyStroke("shift pressed
SHIFT").equals(ks)) {
+ if (!lafSwitchTimer.isRunning()) {
+ switchLastColumnLAF();
+ lafSwitchTimer.start();
+ }
+ } else if (KeyStroke.getAWTKeyStroke("pressed
SPACE").equals(ks)) {
+ if (lafSwitchTimer.isRunning()) {
+ lafSwitchTimer.stop();
+ } else {
+ switchLastColumnLAF();
+ lafSwitchTimer.start();
+ }
+ }
+ }
+
+ @Override
+ public void keyReleased(KeyEvent evt) {
+ if (KeyStroke.getAWTKeyStroke("released
SHIFT").equals(KeyStroke.getKeyStrokeForEvent(evt))) {
+ lafSwitchTimer.stop();
+ }
+ }
+ });
+ setFocusable(true);
+ requestFocusInWindow();
+ }
+
+ // This should really be a utility method somewhere...
+ // See VectorIcon.createGraphicsWithRenderingHintsConfigured.
+ private static Graphics2D
createGraphicsWithRenderingHintsConfigured(Graphics basedOn) {
+ Graphics2D ret = (Graphics2D) basedOn.create();
+ Object desktopHints
+ =
Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
+ Map<Object, Object> hints = new LinkedHashMap<Object, Object>();
+ if (desktopHints != null && desktopHints instanceof Map<?, ?>) {
+ hints.putAll((Map<?, ?>) desktopHints);
+ }
+ hints.put(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
+ hints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ ret.addRenderingHints(hints);
+ return ret;
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ Graphics2D g2 = createGraphicsWithRenderingHintsConfigured(g);
+ try {
+ paintComponent(g2, (Graphics2D) g);
+ } finally {
+ g2.dispose();
+ }
+ }
+
+ private void paintComponent(Graphics2D g, Graphics2D originalGraphics)
{
+ List<String> columnsToShow = new
ArrayList<String>(iconsByLAF.keySet());
+ if (columnsToShow.size() >= 2) {
+ columnsToShow.add(timerSwitchState ? columnsToShow.get(0) :
columnsToShow.get(1));
+ }
+ Font font = new Font("Arial", Font.PLAIN, 12);
+ final int fontAscent = g.getFontMetrics(font).getAscent();
+ int x = 30;
+ final int START_Y = 30;
+ final int LAF_HEADING_Y_MARGIN = 30;
+ int y = START_Y;
+ g.setFont(font);
+ g.setColor(Color.BLACK);
+ // First column: name of button types.
+ y += LAF_HEADING_Y_MARGIN;
+ for (String nameAfterLAF : namesAfterLAF) {
+ g.drawString(nameAfterLAF, x, y + fontAscent);
+ y += ICON_ROW_HEIGHT;
+ }
+ x += 200;
+ int columnIndex = 0;
+ for (String lafName : columnsToShow) {
+ Map<String, Icon> lafIcons = iconsByLAF.get(lafName);
+ if (lafIcons == null) {
+ throw new RuntimeException();
+ }
+ y = START_Y;
+ g.setFont(font);
+ g.setColor(Color.BLACK);
+ String columnTitle;
+ if (columnIndex == columnsToShow.size() - 1 &&
columnsToShow.size() > 1) {
+ columnTitle = lafName + " (shift/space to toggle)";
+ } else {
+ columnTitle = lafName;
+ }
+ g.drawString(columnTitle, x, y + fontAscent);
+ y += LAF_HEADING_Y_MARGIN;
+ int xAdvance = 0;
+ for (String nameAfterLAF : namesAfterLAF) {
+ Icon icon = lafIcons.get(nameAfterLAF);
+ if (icon != null) {
+ /* Use the original graphics object here to make sure
that icon painters set
+ their own rendering hints as necessary. */
+ xAdvance = Math.max(xAdvance, paintIconRow(g,
originalGraphics, icon, x, y));
+ }
+ y += ICON_ROW_HEIGHT;
+ }
+ x += xAdvance;
+ columnIndex++;
+ }
+ if (x != preferredWidth) {
+ preferredWidth = x;
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ revalidate();
+ }
+ });
+ }
+ }
+
+ /**
+ * @return the X advance
+ */
+ private int paintIconRow(Graphics2D g, Graphics2D originalGraphics,
Icon icon, int x, int y) {
+ // Show one column of icons in a different background, to test
transparency.
+ g.setColor(Color.GREEN);
+ // Darker gray, for testing against darker LAF backgrounds.
+ //g.setColor(new Color(150, 150, 150, 255));
+ g.fillRect(x - 5, y - 5, ICON_BASE_SIZE_X + 10, ICON_ROW_HEIGHT +
10);
+ double useX = x;
+ int ret = 0;
+ for (int i = 0; i < 2; i++) {
+ if (!INCLUDE_HUGE_ICON && i == 1) {
+ break; // Not enough space for a row simulating
misalignment.
+ }
+ useX = x;
+ double useY = y + (i == 0 ? 0 : (16 * 4));
+ if (i == 1) {
+ /* Simulate misalignment. Note that there won't really be
misalignment at 100% scaling,
+ since it's typically an artifact of non-integral HiDPI
scaling. */
+ useX += 0.49;
+ useY += 0.49;
+ }
+ // Misalignment only needs to be simulated on non-integral
HiDPI scalings.
+ useX += paintIcon(g, originalGraphics, i == 0 ? icon : null,
useX, useY, 1.0);
+ useX += paintIcon(g, originalGraphics, i == 0 ? icon : null,
useX, useY, 1.0);
+ useX += paintIcon(g, originalGraphics, icon, useX, useY, 1.25);
+ useX += paintIcon(g, originalGraphics, icon, useX, useY, 1.5);
+ useX += paintIcon(g, originalGraphics, icon, useX, useY, 1.75);
+ useX += paintIcon(g, originalGraphics, i == 0 ? icon : null,
useX, useY, 2.0);
+ useX += paintIcon(g, originalGraphics, icon, useX, useY, 2.25);
+ useX += paintIcon(g, originalGraphics, i == 0 ? icon : null,
useX, useY, 3.0);
+ if (i == 0) {
+ if (INCLUDE_HUGE_ICON) {
+ useX += paintIcon(g, originalGraphics, icon, useX, y,
8.0);
+ }
+ ret = (int) (useX - x);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * @param icon if null, don't paint, just return the advance
+ * @return the X advance
+ */
+ private int paintIcon(
+ Graphics2D newg, Graphics2D originalGraphics, Icon icon,
double x, double y, double scaling)
+ {
+ String resstr;
+ boolean aligned = x == (int) x && y == (int) y;
+ if (icon == null) {
+ resstr = "";
+ } else if (!aligned) {
+ resstr = "misalign";
+ } else if (scaling > 1.0) {
+ resstr = ((int) Math.round(scaling * 100)) + "%";
+ } else {
+ resstr = icon.getIconWidth() + "x" + icon.getIconHeight();
+ }
+ newg.setFont(new Font("Arial", Font.PLAIN, 8));
+ newg.setColor(Color.BLACK);
+ if (!resstr.isEmpty()) {
+ newg.drawString(resstr, (int) x,
+ ((int) y) -
originalGraphics.getFontMetrics().getDescent());
+ }
+ AffineTransform oldTransform = originalGraphics.getTransform();
+ originalGraphics.translate(x, y);
+ originalGraphics.scale(scaling, scaling);
+ if (icon != null) {
+ // Make it evident if the icon forgets to set the color or
shape.
+ originalGraphics.setColor(Color.PINK);
+ originalGraphics.setStroke(new BasicStroke((int) (10 *
scaling)));
+ /* Paint the icon with a non-zero x/y offset, to make sure its
implementation
+ handles this correctly. */
+ originalGraphics.translate(-10, -15);
+ icon.paintIcon(this, originalGraphics, 10, 15);
+ originalGraphics.translate(10, 15);
+ }
+ originalGraphics.setTransform(oldTransform);
+ if (icon != null && scaling > 4 && ((int) scaling) == scaling) {
+ int s = (int) scaling;
+ // Display a pixel grid on top
+ originalGraphics.setColor(new Color(0, 0, 0, 60));
+ originalGraphics.setStroke(new BasicStroke(1));
+ int w = icon.getIconWidth();
+ int h = icon.getIconHeight();
+ // Vertical lines.
+ for (int gridX = 0; gridX <= w; gridX++) {
+ originalGraphics.drawLine(
+ (int) x + gridX * s, (int) y, (int) x + gridX * s,
(int) y + h * s);
+ }
+ // Horizontal lines.
+ for (int gridY = 0; gridY <= h; gridY++) {
+ originalGraphics.drawLine(
+ (int) x, (int) y + gridY * s, (int) x + w * s,
(int) y + gridY * s);
+ }
+ }
+ return ICON_BASE_SIZE_X + (int) Math.ceil(scaling *
ICON_BASE_SIZE_X);
+ }
+
+ @Override
+ public Dimension getPreferredScrollableViewportSize() {
+ return getPreferredSize();
+ }
+
+ @Override
+ public int getScrollableUnitIncrement(Rectangle visibleRect, int
orientation, int direction) {
+ return ICON_ROW_HEIGHT;
+ }
+
+ @Override
+ public int getScrollableBlockIncrement(Rectangle visibleRect, int
orientation, int direction) {
+ return ICON_ROW_HEIGHT;
+ }
+
+ @Override
+ public boolean getScrollableTracksViewportWidth() {
+ return false;
+ }
+
+ @Override
+ public boolean getScrollableTracksViewportHeight() {
+ return false;
+ }
+ }
+}
diff --git a/platform/openide.awt/nbproject/project.xml
b/platform/openide.awt/nbproject/project.xml
index 7d008a9..0398c10 100644
--- a/platform/openide.awt/nbproject/project.xml
+++ b/platform/openide.awt/nbproject/project.xml
@@ -47,7 +47,7 @@
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
- <specification-version>9.11</specification-version>
+ <specification-version>9.12</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git
a/platform/openide.awt/src/org/openide/awt/AquaVectorCloseButton.java
b/platform/openide.awt/src/org/openide/awt/AquaVectorCloseButton.java
new file mode 100644
index 0000000..4adfc32
--- /dev/null
+++ b/platform/openide.awt/src/org/openide/awt/AquaVectorCloseButton.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.awt;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.geom.Area;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Line2D;
+import javax.swing.Icon;
+import org.openide.util.VectorIcon;
+
+/* For use by CloseButtonFactory only. The
"mac_close_(enabled|pressed|rollover).png" files were
+confirmed to be identical to the mac_bigclose_(enabled|pressed|rollover).png
ones, so the same
+vector icons can be used here for either case. */
+final class AquaVectorCloseButton extends VectorIcon {
+ public static final Icon DEFAULT = new
AquaVectorCloseButton(State.DEFAULT);
+ public static final Icon PRESSED = new
AquaVectorCloseButton(State.PRESSED);
+ public static final Icon ROLLOVER = new
AquaVectorCloseButton(State.ROLLOVER);
+ private final State state;
+
+ private enum State { DEFAULT, PRESSED, ROLLOVER}
+
+ private AquaVectorCloseButton(State state) {
+ super(14, 12);
+ this.state = state;
+ }
+
+ @Override
+ protected void paintIcon(Component c, Graphics2D g, int width, int height,
double scaling) {
+ /* Identical logic to that in
o.n.swing.tabcontrol.plaf.AquaVectorTabControlIcon for the
+ TabControlButton.ID_CLOSE_BUTTON case. We can't depend on that module,
however, and it makes
+ little sense for this module to expose a new API just to share this
little piece of
+ platform-dependent code. */
+ double d = Math.min(width, height);
+ Color bgColor = new Color(0, 0, 0, 0);
+ Color fgColor = new Color(0, 0, 0, 168);
+ if (state == State.ROLLOVER) {
+ fgColor = Color.WHITE;
+ bgColor = new Color(255, 35, 25, 215);
+ } else if (state == State.PRESSED) {
+ fgColor = Color.WHITE;
+ bgColor = new Color(185, 43, 33, 215);
+ }
+ if (bgColor.getAlpha() > 0) {
+ double circPosX = (width - d) / 2.0;
+ double circPosY = (height - d) / 2.0;
+ Shape bgCircle = new Ellipse2D.Double(circPosX, circPosY, d, d);
+ g.setColor(bgColor);
+ g.fill(bgCircle);
+ }
+ g.setColor(fgColor);
+ double strokeWidth = 1.4 * scaling;
+ double mx = width / 2.0;
+ double my = height / 2.0;
+ double cr = 0.45 * (d / 2.0);
+ Stroke stroke = new BasicStroke(
+ (float) strokeWidth, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND);
+ Area area = new Area();
+ area.add(new Area(stroke.createStrokedShape(
+ new Line2D.Double(mx - cr, my - cr, mx + cr, my + cr))));
+ area.add(new Area(stroke.createStrokedShape(
+ new Line2D.Double(mx + cr, my - cr, mx - cr, my + cr))));
+ g.fill(area);
+ }
+}
diff --git a/platform/openide.awt/src/org/openide/awt/CloseButtonFactory.java
b/platform/openide.awt/src/org/openide/awt/CloseButtonFactory.java
index 88278d0..b8b41bd 100644
--- a/platform/openide.awt/src/org/openide/awt/CloseButtonFactory.java
+++ b/platform/openide.awt/src/org/openide/awt/CloseButtonFactory.java
@@ -149,7 +149,7 @@ public final class CloseButtonFactory{
}
if( null == closeTabImage ) {
if( isWindows8LaF() || isWindows10LaF() ) {
- closeTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_enabled.png",
true); // NOI18N
+ closeTabImage = Windows8VectorCloseButton.DEFAULT;
} else if( isWindowsVistaLaF() ) {
closeTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_close_enabled.png",
true); // NOI18N
} else if( isWindowsXPLaF() ) {
@@ -157,7 +157,7 @@ public final class CloseButtonFactory{
} else if( isWindowsLaF() ) {
closeTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win_close_enabled.png",
true); // NOI18N
} else if( isAquaLaF() ) {
- closeTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_close_enabled.png",
true); // NOI18N
+ closeTabImage = AquaVectorCloseButton.DEFAULT;
} else if( isGTKLaF() ) {
closeTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_close_enabled.png",
true); // NOI18N
} else {
@@ -176,7 +176,7 @@ public final class CloseButtonFactory{
}
if( null == closeTabPressedImage ) {
if( isWindows8LaF() || isWindows10LaF() ) {
- closeTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_pressed.png",
true); // NOI18N
+ closeTabPressedImage = Windows8VectorCloseButton.PRESSED;
} else if( isWindowsVistaLaF() ) {
closeTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_close_pressed.png",
true); // NOI18N
} else if( isWindowsXPLaF() ) {
@@ -184,7 +184,7 @@ public final class CloseButtonFactory{
} else if( isWindowsLaF() ) {
closeTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win_close_pressed.png",
true); // NOI18N
} else if( isAquaLaF() ) {
- closeTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_close_pressed.png",
true); // NOI18N
+ closeTabPressedImage = AquaVectorCloseButton.PRESSED;
} else if( isGTKLaF() ) {
closeTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_close_pressed.png",
true); // NOI18N
} else {
@@ -203,7 +203,7 @@ public final class CloseButtonFactory{
}
if( null == closeTabMouseOverImage ) {
if( isWindows8LaF() || isWindows10LaF() ) {
- closeTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_rollover.png",
true); // NOI18N
+ closeTabMouseOverImage = Windows8VectorCloseButton.PRESSED;
} else if( isWindowsVistaLaF() ) {
closeTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_close_rollover.png",
true); // NOI18N
} else if( isWindowsXPLaF() ) {
@@ -211,7 +211,7 @@ public final class CloseButtonFactory{
} else if( isWindowsLaF() ) {
closeTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win_close_rollover.png",
true); // NOI18N
} else if( isAquaLaF() ) {
- closeTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_close_rollover.png",
true); // NOI18N
+ closeTabMouseOverImage = AquaVectorCloseButton.ROLLOVER;
} else if( isGTKLaF() ) {
closeTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_close_rollover.png",
true); // NOI18N
} else {
@@ -231,7 +231,7 @@ public final class CloseButtonFactory{
}
if( null == bigCloseTabImage ) {
if( isWindows8LaF() || isWindows10LaF() ) {
- bigCloseTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_enabled.png",
true); // NOI18N
+ bigCloseTabImage = Windows8VectorCloseButton.DEFAULT;
} else if( isWindowsVistaLaF() ) {
bigCloseTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_bigclose_enabled.png",
true); // NOI18N
} else if( isWindowsXPLaF() ) {
@@ -239,7 +239,7 @@ public final class CloseButtonFactory{
} else if( isWindowsLaF() ) {
bigCloseTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win_bigclose_enabled.png",
true); // NOI18N
} else if( isAquaLaF() ) {
- bigCloseTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_bigclose_enabled.png",
true); // NOI18N
+ bigCloseTabImage = AquaVectorCloseButton.DEFAULT;
} else if( isGTKLaF() ) {
bigCloseTabImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_bigclose_enabled.png",
true); // NOI18N
} else {
@@ -258,7 +258,7 @@ public final class CloseButtonFactory{
}
if( null == bigCloseTabPressedImage ) {
if( isWindows8LaF() || isWindows10LaF() ) {
- bigCloseTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_pressed.png",
true); // NOI18N
+ bigCloseTabPressedImage = Windows8VectorCloseButton.PRESSED;
} else if( isWindowsVistaLaF() ) {
bigCloseTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_bigclose_pressed.png",
true); // NOI18N
} else if( isWindowsXPLaF() ) {
@@ -266,7 +266,7 @@ public final class CloseButtonFactory{
} else if( isWindowsLaF() ) {
bigCloseTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win_bigclose_pressed.png",
true); // NOI18N
} else if( isAquaLaF() ) {
- bigCloseTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_bigclose_pressed.png",
true); // NOI18N
+ bigCloseTabPressedImage = AquaVectorCloseButton.PRESSED;
} else if( isGTKLaF() ) {
bigCloseTabPressedImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_bigclose_pressed.png",
true); // NOI18N
} else {
@@ -285,7 +285,7 @@ public final class CloseButtonFactory{
}
if( null == bigCloseTabMouseOverImage ) {
if( isWindows8LaF() || isWindows10LaF() ) {
- bigCloseTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win8_bigclose_rollover.png",
true); // NOI18N
+ bigCloseTabMouseOverImage = Windows8VectorCloseButton.PRESSED;
} else if( isWindowsVistaLaF() ) {
bigCloseTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/vista_bigclose_rollover.png",
true); // NOI18N
} else if( isWindowsXPLaF() ) {
@@ -293,7 +293,7 @@ public final class CloseButtonFactory{
} else if( isWindowsLaF() ) {
bigCloseTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/win_bigclose_rollover.png",
true); // NOI18N
} else if( isAquaLaF() ) {
- bigCloseTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/mac_bigclose_rollover.png",
true); // NOI18N
+ bigCloseTabMouseOverImage = AquaVectorCloseButton.ROLLOVER;
} else if( isGTKLaF() ) {
bigCloseTabMouseOverImage =
ImageUtilities.loadImageIcon("org/openide/awt/resources/gtk_bigclose_rollover.png",
true); // NOI18N
} else {
diff --git a/platform/openide.awt/src/org/openide/awt/DropDownButton.java
b/platform/openide.awt/src/org/openide/awt/DropDownButton.java
index 8013b93..e2fc84d 100644
--- a/platform/openide.awt/src/org/openide/awt/DropDownButton.java
+++ b/platform/openide.awt/src/org/openide/awt/DropDownButton.java
@@ -31,7 +31,6 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultButtonModel;
import javax.swing.Icon;
-import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
@@ -218,7 +217,7 @@ class DropDownButton extends JButton {
Icon orig = regIcons.get( ICON_ROLLOVER );
if( null == orig )
orig = regIcons.get( ICON_NORMAL );
- icon = new IconWithArrow( orig, !mouseInArrowArea );
+ icon = new IconWithArrow( orig, !mouseInArrowArea, false );
arrowIcons.put( mouseInArrowArea ? ICON_ROLLOVER :
ICON_ROLLOVER_LINE, icon );
}
return icon;
@@ -233,7 +232,7 @@ class DropDownButton extends JButton {
orig = regIcons.get( ICON_ROLLOVER );
if( null == orig )
orig = regIcons.get( ICON_NORMAL );
- icon = new IconWithArrow( orig, !mouseInArrowArea );
+ icon = new IconWithArrow( orig, !mouseInArrowArea, false );
arrowIcons.put( mouseInArrowArea ? ICON_ROLLOVER_SELECTED :
ICON_ROLLOVER_SELECTED_LINE, icon );
}
return icon;
@@ -274,7 +273,8 @@ class DropDownButton extends JButton {
arrowIcons.remove( iconType );
} else {
regIcons.put( iconType, orig );
- arrow = new ImageIcon(ImageUtilities.icon2Image(new IconWithArrow(
orig, false )));
+ arrow = new IconWithArrow( orig, false,
+ iconType.equals(ICON_DISABLED) ||
iconType.equals(ICON_DISABLED_SELECTED));
arrowIcons.put( iconType, arrow );
}
return arrow;
@@ -309,14 +309,12 @@ class DropDownButton extends JButton {
@Override
public void setDisabledIcon(Icon icon) {
- //TODO use 'disabled' arrow icon
Icon arrow = updateIcons( icon, ICON_DISABLED );
super.setDisabledIcon( hasPopupMenu() ? arrow : icon );
}
@Override
public void setDisabledSelectedIcon(Icon icon) {
- //TODO use 'disabled' arrow icon
Icon arrow = updateIcons( icon, ICON_DISABLED_SELECTED );
super.setDisabledSelectedIcon( hasPopupMenu() ? arrow : icon );
}
diff --git a/platform/openide.awt/src/org/openide/awt/DropDownToggleButton.java
b/platform/openide.awt/src/org/openide/awt/DropDownToggleButton.java
index 7dec11a..7e1b5e9 100644
--- a/platform/openide.awt/src/org/openide/awt/DropDownToggleButton.java
+++ b/platform/openide.awt/src/org/openide/awt/DropDownToggleButton.java
@@ -30,12 +30,10 @@ import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Icon;
-import javax.swing.ImageIcon;
import javax.swing.JPopupMenu;
import javax.swing.JToggleButton;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
-import org.openide.util.ImageUtilities;
import org.openide.util.Parameters;
/**
@@ -221,7 +219,7 @@ class DropDownToggleButton extends JToggleButton {
Icon orig = regIcons.get( ICON_ROLLOVER );
if( null == orig )
orig = regIcons.get( ICON_NORMAL );
- icon = new IconWithArrow( orig, !mouseInArrowArea );
+ icon = new IconWithArrow( orig, !mouseInArrowArea, false );
arrowIcons.put( mouseInArrowArea ? ICON_ROLLOVER :
ICON_ROLLOVER_LINE, icon );
}
return icon;
@@ -236,7 +234,7 @@ class DropDownToggleButton extends JToggleButton {
orig = regIcons.get( ICON_ROLLOVER );
if( null == orig )
orig = regIcons.get( ICON_NORMAL );
- icon = new IconWithArrow( orig, !mouseInArrowArea );
+ icon = new IconWithArrow( orig, !mouseInArrowArea, false );
arrowIcons.put( mouseInArrowArea ? ICON_ROLLOVER_SELECTED :
ICON_ROLLOVER_SELECTED_LINE, icon );
}
return icon;
@@ -276,7 +274,8 @@ class DropDownToggleButton extends JToggleButton {
arrowIcons.remove( iconType );
} else {
regIcons.put( iconType, orig );
- arrow = new ImageIcon(ImageUtilities.icon2Image(new IconWithArrow(
orig, false )));
+ arrow = new IconWithArrow( orig, false,
+ iconType.equals(ICON_DISABLED) ||
iconType.equals(ICON_DISABLED_SELECTED) );
arrowIcons.put( iconType, arrow );
}
return arrow;
@@ -311,14 +310,12 @@ class DropDownToggleButton extends JToggleButton {
@Override
public void setDisabledIcon(Icon icon) {
- //TODO use 'disabled' arrow icon
Icon arrow = updateIcons( icon, ICON_DISABLED );
super.setDisabledIcon( hasPopupMenu() ? arrow : icon );
}
@Override
public void setDisabledSelectedIcon(Icon icon) {
- //TODO use 'disabled' arrow icon
Icon arrow = updateIcons( icon, ICON_DISABLED_SELECTED );
super.setDisabledSelectedIcon( hasPopupMenu() ? arrow : icon );
}
diff --git a/platform/openide.awt/src/org/openide/awt/IconWithArrow.java
b/platform/openide.awt/src/org/openide/awt/IconWithArrow.java
index 2c9c029..1c87da8 100644
--- a/platform/openide.awt/src/org/openide/awt/IconWithArrow.java
+++ b/platform/openide.awt/src/org/openide/awt/IconWithArrow.java
@@ -22,10 +22,12 @@ package org.openide.awt;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.Path2D;
import javax.swing.Icon;
import javax.swing.UIManager;
-import org.openide.util.ImageUtilities;
import org.openide.util.Parameters;
+import org.openide.util.VectorIcon;
/**
* An icon that paints a small arrow to the right of the provided icon.
@@ -34,20 +36,18 @@ import org.openide.util.Parameters;
* @since 6.11
*/
class IconWithArrow implements Icon {
-
- private static final String ARROW_IMAGE_NAME =
"org/openide/awt/resources/arrow.png"; //NOI18N
-
private Icon orig;
- private Icon arrow = ImageUtilities.loadImageIcon(ARROW_IMAGE_NAME, false);
+ private Icon arrow;
private boolean paintRollOver;
private static final int GAP = 6;
/** Creates a new instance of IconWithArrow */
- public IconWithArrow( Icon orig, boolean paintRollOver ) {
+ public IconWithArrow( Icon orig, boolean paintRollOver, boolean
disabledArrow ) {
Parameters.notNull("original icon", orig); //NOI18N
this.orig = orig;
this.paintRollOver = paintRollOver;
+ this.arrow = disabledArrow ? ArrowIcon.INSTANCE_DISABLED :
ArrowIcon.INSTANCE_DEFAULT;
}
@Override
@@ -88,4 +88,31 @@ class IconWithArrow implements Icon {
public static int getArrowAreaWidth() {
return GAP/2 + 5;
}
+
+ private static class ArrowIcon extends VectorIcon {
+ public static final Icon INSTANCE_DEFAULT = new ArrowIcon(false);
+ public static final Icon INSTANCE_DISABLED = new ArrowIcon(true);
+ private final boolean disabled;
+
+ private ArrowIcon(boolean disabled) {
+ super(5, 4);
+ this.disabled = disabled;
+ }
+
+ @Override
+ protected void paintIcon(Component c, Graphics2D g, int width, int
height, double scaling) {
+ g.setColor(disabled ? new Color(201, 201, 201, 255) : new
Color(86, 86, 86, 255));
+ final double overshoot = 2.0 / scaling;
+ final double arrowWidth = width + overshoot * scaling;
+ final double arrowHeight = height - 0.2 * scaling;
+ final double arrowMidX = arrowWidth / 2.0 - (overshoot / 2.0) *
scaling;
+ g.clipRect(0, 0, width, height);
+ Path2D.Double arrowPath = new Path2D.Double();
+ arrowPath.moveTo(arrowMidX - arrowWidth / 2.0, 0);
+ arrowPath.lineTo(arrowMidX, arrowHeight);
+ arrowPath.lineTo(arrowMidX + arrowWidth / 2.0, 0);
+ arrowPath.closePath();
+ g.fill(arrowPath);
+ }
+ }
}
diff --git a/platform/openide.awt/src/org/openide/awt/ToolbarWithOverflow.java
b/platform/openide.awt/src/org/openide/awt/ToolbarWithOverflow.java
index 4cad7bf..b2c03dd 100644
--- a/platform/openide.awt/src/org/openide/awt/ToolbarWithOverflow.java
+++ b/platform/openide.awt/src/org/openide/awt/ToolbarWithOverflow.java
@@ -19,9 +19,11 @@
package org.openide.awt;
import java.awt.AWTEvent;
+import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
+import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
@@ -30,14 +32,16 @@ import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
+import java.awt.geom.Path2D;
import javax.swing.BorderFactory;
+import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JToolBar;
import javax.swing.UIManager;
-import org.openide.util.ImageUtilities;
import org.openide.util.Mutex;
+import org.openide.util.VectorIcon;
/**
* ToolbarWithOverflow provides a component which is useful for displaying
commonly used
@@ -53,8 +57,6 @@ public class ToolbarWithOverflow extends JToolBar {
private JPopupMenu popup;
private JToolBar overflowToolbar;
private boolean displayOverflowOnHover = true;
- private final String toolbarArrowHorizontal =
"org/openide/awt/resources/toolbar_arrow_horizontal.png"; //NOI18N
- private final String toolbarArrowVertical =
"org/openide/awt/resources/toolbar_arrow_vertical.png"; //NOI18N
private final String PROP_PREF_ICON_SIZE = "PreferredIconSize"; //NOI18N
private final String PROP_DRAGGER = "_toolbar_dragger_"; //NOI18N
private final String PROP_JDEV_DISABLE_OVERFLOW =
"nb.toolbar.overflow.disable"; //NOI18N
@@ -279,7 +281,9 @@ public class ToolbarWithOverflow extends JToolBar {
}
private void setupOverflowButton() {
- overflowButton = new
JButton(ImageUtilities.loadImageIcon(getOrientation() == HORIZONTAL ?
toolbarArrowVertical : toolbarArrowHorizontal, false)) {
+ overflowButton = new JButton(getOrientation() == HORIZONTAL
+ ? ToolbarArrowIcon.INSTANCE_VERTICAL :
ToolbarArrowIcon.INSTANCE_HORIZONTAL)
+ {
@Override
public void updateUI() {
Mutex.EVENT.readAccess(new Runnable() {
@@ -483,4 +487,45 @@ public class ToolbarWithOverflow extends JToolBar {
super.updateUI();
}
}
+
+ /**
+ * Vectorized version of {@code toolbar_arrow_horizontal.png} and
+ * {@code toolbar_arrow_vertical.png}.
+ */
+ private static final class ToolbarArrowIcon extends VectorIcon {
+ public static final Icon INSTANCE_HORIZONTAL = new
ToolbarArrowIcon(true);
+ public static final Icon INSTANCE_VERTICAL = new
ToolbarArrowIcon(false);
+ private final boolean horizontal;
+
+ private ToolbarArrowIcon(boolean horizontal) {
+ super(11, 11);
+ this.horizontal = horizontal;
+ }
+
+ @Override
+ protected void paintIcon(Component c, Graphics2D g, int width, int
height, double scaling) {
+ if (horizontal) {
+ // Rotate 90 degrees counterclockwise.
+ g.rotate(-Math.PI / 2.0, width / 2.0, height / 2.0);
+ }
+ // Draw two chevrons pointing downwards. Make strokes a little
thicker at low scalings.
+ double strokeWidth = 0.8 * scaling + 0.3;
+ g.setStroke(new BasicStroke((float) strokeWidth));
+ g.setColor(new Color(50, 50, 50, 255));
+ for (int i = 0; i < 2; i++) {
+ final int y = round((1.4 + 4.1 * i) * scaling);
+ final double arrowWidth = round(5.0 * scaling);
+ final double arrowHeight = round(3.0 * scaling);
+ final double marginX = (width - arrowWidth) / 2.0;
+ final double arrowMidX = marginX + arrowWidth / 2.0;
+ // Clip the top of the chevrons.
+ g.clipRect(0, y, width, height);
+ Path2D.Double arrowPath = new Path2D.Double();
+ arrowPath.moveTo(arrowMidX - arrowWidth / 2.0, y);
+ arrowPath.lineTo(arrowMidX, y + arrowHeight);
+ arrowPath.lineTo(arrowMidX + arrowWidth / 2.0, y);
+ g.draw(arrowPath);
+ }
+ }
+ }
}
diff --git
a/platform/openide.awt/src/org/openide/awt/Windows8VectorCloseButton.java
b/platform/openide.awt/src/org/openide/awt/Windows8VectorCloseButton.java
new file mode 100644
index 0000000..ca2db83
--- /dev/null
+++ b/platform/openide.awt/src/org/openide/awt/Windows8VectorCloseButton.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.awt;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics2D;
+import java.awt.geom.Line2D;
+import java.awt.geom.Rectangle2D;
+import javax.swing.Icon;
+import org.openide.util.VectorIcon;
+
+// For use by CloseButtonFactory only.
+final class Windows8VectorCloseButton extends VectorIcon {
+ public static final Icon DEFAULT = new Windows8VectorCloseButton(false);
+ public static final Icon PRESSED = new Windows8VectorCloseButton(true);
+ private final boolean pressed;
+
+ private Windows8VectorCloseButton(boolean pressed) {
+ super(14, 14);
+ this.pressed = pressed;
+ }
+
+ @Override
+ protected void paintIcon(Component c, Graphics2D g, int width, int height,
double scaling) {
+ /* Identical logic to that in
o.n.swing.tabcontrol.plaf.Windows8VectorTabControlIcon for the
+ TabControlButton.ID_CLOSE_BUTTON case. We can't depend on that module,
however, and it makes
+ little sense for this module to expose a new API just to share this
little piece of
+ platform-dependent code. */
+ if (pressed) {
+ g.setColor(new Color(199, 79, 80, 255));
+ g.fillRect(0, 0, width, height);
+ }
+ g.setColor(pressed ? Color.WHITE : new Color(86, 86, 86, 255));
+ if (getIconWidth() == width && getIconHeight() == height)
+ setAntiAliasing(g, false);
+ double strokeWidth = (pressed ? 1.0 : 0.8) * scaling;
+ if (scaling > 1.0)
+ strokeWidth *= 1.5f;
+ double marginX = 3.5 * scaling;
+ int topMarginY = round(3 * scaling);
+ int botMarginY = round(4 * scaling);
+ g.clip(new Rectangle2D.Double(0, topMarginY, width, height -
topMarginY - botMarginY));
+ g.setStroke(new BasicStroke((float) strokeWidth));
+ g.draw(new Line2D.Double(marginX, topMarginY, width - marginX, height
- botMarginY));
+ g.draw(new Line2D.Double(width - marginX, topMarginY, marginX, height
- botMarginY));
+ }
+}
diff --git a/platform/openide.util.ui/apichanges.xml
b/platform/openide.util.ui/apichanges.xml
index 50eeb68..465346a 100644
--- a/platform/openide.util.ui/apichanges.xml
+++ b/platform/openide.util.ui/apichanges.xml
@@ -27,6 +27,32 @@
<apidef name="actions">Actions API</apidef>
</apidefs>
<changes>
+ <change id="VectorIcon">
+ <api name="util"/>
+ <summary>Added abstract class VectorIcon to support creation of
custom-painted HiDPI icons.</summary>
+ <version major="9" minor="12"/>
+ <date year="2018" month="9" day="29"/>
+ <author login="ebakke"/>
+ <compatibility addition="yes" binary="compatible" source="compatible"
semantic="compatible"/>
+ <description>
+ <p>
+ It is now increasingly common for NetBeans to run on Windows,
Linux, or MacOS machines with
+ so-called "HiDPI" screens, aka. "retina" screens in the Apple
world. These screens have about
+ twice the physical pixel density of traditional screens, making it
necessary to scale GUI
+ graphics up by some amount, e.g. 150% or 200% (depending on OS and
OS-level user settings), in
+ order to remain readable. Since Java 9, this scaling is done
automatically by AWT by means of a
+ scaling default transform in each Component's Graphics2D
instances. This makes text sharp on
+ HiDPI screens, but leaves bitmap icons blurry.
+ </p>
+ <p>
+ This change introduces a new abstract class VectorIcon, which can
be extended to create
+ custom-painted Icon instances that will look sharp on HiDPI
screens, regardless of scaling level.
+ See VectorIcon's Javadoc for a discussion of appropriate use cases.
+ </p>
+ </description>
+ <class package="org.openide.util" name="VectorIcon"/>
+ <issue number="NETBEANS-1238"/>
+ </change>
<change id="DeprecateBooleanStateAction">
<api name="util"/>
<summary><code>BooleanStateAction</code> deprecated in favour of
<code>Actions</code> API and <code>@ActionState</code> annotation.</summary>
diff --git a/platform/openide.util.ui/src/org/openide/util/VectorIcon.java
b/platform/openide.util.ui/src/org/openide/util/VectorIcon.java
new file mode 100644
index 0000000..e961753
--- /dev/null
+++ b/platform/openide.util.ui/src/org/openide/util/VectorIcon.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.util;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.geom.AffineTransform;
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.swing.Icon;
+
+/**
+ * A scalable icon that can be drawn at any resolution, for use with HiDPI
displays. Implementations
+ * will typically use hand-crafted painting code that may take special care to
align graphics to
+ * device pixels, and which may perform small tweaks to make the icon look
good at all resolutions.
+ * The API of this class intends to make this straightforward.
+ *
+ * <p>HiDPI support now exists on MacOS, Windows, and Linux. On MacOS, scaling
is 200% for Retina
+ * displays, while on Windows 10, the "Change display settings" panel provides
the options 100%,
+ * 125%, 150%, 175%, 200%, and 225%, as well as the option to enter an
arbitrary scaling factor.
+ * Non-integral scaling factors can lead to various alignment problems that
makes otherwise
+ * well-aligned icons look unsharp; this class takes special care to avoid
such problems.
+ *
+ * <p>Hand-crafted painting code is a good design choice for icons that are
simple, ubiqutious in
+ * the UI (e.g. part of the Look-and-Feel), or highly parameterized. Swing's
native Windows L&F
+ * uses this approach for many of its basic icons; see
+ * {@link com.sun.java.swing.plaf.windows.WindowsIconFactory}.
+ *
+ * <p>When developing new icons, or adjusting existing ones, use the {@code
VectorIconTester}
+ * utility found in
+ * {@code
o.n.swing.tabcontrol/test/unit/src/org/netbeans/swing/tabcontrol/plaf/VectorIconTester.java}
+ * to preview and compare icons at different resolutions.
+ *
+ * @since 9.12
+ * @author Eirik Bakke
+ */
+public abstract class VectorIcon implements Icon, Serializable {
+ private final int width;
+ private final int height;
+
+ protected VectorIcon(int width, int height) {
+ if (width < 0 || height < 0)
+ throw new IllegalArgumentException();
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public final int getIconWidth() {
+ return width;
+ }
+
+ @Override
+ public final int getIconHeight() {
+ return height;
+ }
+
+ private static Graphics2D
createGraphicsWithRenderingHintsConfigured(Graphics basedOn) {
+ Graphics2D ret = (Graphics2D) basedOn.create();
+ Object desktopHints =
+
Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints");
+ Map<Object, Object> hints = new LinkedHashMap<Object, Object>();
+ if (desktopHints != null && desktopHints instanceof Map<?, ?>)
+ hints.putAll((Map<?, ?>) desktopHints);
+ /* Enable antialiasing by default. Adding this is required in order to
get non-text
+ antialiasing on Windows. */
+ hints.put(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
+ /* In case a subclass decides to render text inside an icon,
standardize the text
+ antialiasing setting as well. Don't try to follow the editor's
anti-aliasing setting, or
+ to do subpixel rendering. It's more important that icons render in a
predictable fashion, so
+ the icon designer can get can review the appearance at design time. */
+ hints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+ // Make stroke behavior as predictable as possible.
+ hints.put(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
+ ret.addRenderingHints(hints);
+ return ret;
+ }
+
+ /**
+ * Selectively enable or disable antialiasing during painting. Certain
shapes may look slightly
+ * better without antialiasing, e.g. entirely regular diagonal lines in
very small icons when
+ * there is no HiDPI scaling. Text antialiasing is unaffected by this
setting.
+ *
+ * @param g the graphics to set antialiasing setting for
+ * @param enabled whether antialiasing should be enabled or disabled
+ */
+ protected static final void setAntiAliasing(Graphics2D g, boolean enabled)
{
+ Map<Object, Object> hints = new LinkedHashMap<Object, Object>();
+ hints.put(RenderingHints.KEY_ANTIALIASING, enabled
+ ? RenderingHints.VALUE_ANTIALIAS_ON :
RenderingHints.VALUE_ANTIALIAS_OFF);
+ g.addRenderingHints(hints);
+ }
+
+ protected static final int round(double d) {
+ int ret = (int) Math.round(d);
+ return d > 0 && ret == 0 ? 1 : ret;
+ }
+
+ @Override
+ public final void paintIcon(Component c, Graphics g0, int x, int y) {
+ final Graphics2D g2 = createGraphicsWithRenderingHintsConfigured(g0);
+ try {
+ // Make sure the subclass can't paint outside its stated
dimensions.
+ g2.clipRect(x, y, getIconWidth(), getIconHeight());
+ g2.translate(x, y);
+ /**
+ * On HiDPI monitors, the Graphics object will have a default
transform that maps
+ * logical pixels, like those you'd pass to Graphics.drawLine, to
a higher number of
+ * device pixels on the screen. For instance, painting a line 10
pixels long on the
+ * current Graphics object would actually produce a line 20 device
pixels long on a
+ * MacOS retina screen, which has a DPI scaling factor of 2.0. On
Windows 10, many
+ * different scaling factors may be encountered, including
non-integral ones such as
+ * 1.5. Detect the scaling factor here so we can use it to inform
the drawing routines.
+ */
+ final double scaling;
+ final AffineTransform tx = g2.getTransform();
+ int txType = tx.getType();
+ if (txType == AffineTransform.TYPE_UNIFORM_SCALE ||
+ txType == (AffineTransform.TYPE_UNIFORM_SCALE |
AffineTransform.TYPE_TRANSLATION))
+ {
+ scaling = tx.getScaleX();
+ } else {
+ // Unrecognized transform type. Don't do any custom scaling
handling.
+ paintIcon(c, g2, getIconWidth(), getIconHeight(), 1.0);
+ return;
+ }
+ /* When using a non-integral scaling factor, such as 175%,
preceding Swing components
+ often end up being a non-integral number of device pixels tall or
wide. This will cause
+ our initial position to be "off the grid" with respect to device
pixels, causing blurry
+ graphics even if we subsequently take care to use only integral
numbers of device pixels
+ during painting. Fix this here by consuming a little bit of the
top and left of the
+ icon's dimensions to offset any error. */
+ // The initial position, in device pixels.
+ final double previousDevicePosX = tx.getTranslateX();
+ final double previousDevicePosY = tx.getTranslateY();
+ /* The new, aligned position, after a small portion of the icon's
dimensions may have
+ been consumed to correct it. */
+ final double alignedDevicePosX = Math.ceil(previousDevicePosX);
+ final double alignedDevicePosY = Math.ceil(previousDevicePosY);
+ // Use the aligned position.
+ g2.setTransform(new AffineTransform(
+ 1, 0, 0, 1, alignedDevicePosX, alignedDevicePosY));
+ /* The portion of the icon's dimensions that was consumed to
correct any initial
+ translation misalignment, in device pixels. May be zero. */
+ final double transDeviceAdjX = alignedDevicePosX -
previousDevicePosX;
+ final double transDeviceAdjY = alignedDevicePosY -
previousDevicePosY;
+ /* Now calculate the dimensions available for painting, also
aligned to an integral
+ number of device pixels. */
+ final int deviceWidth = (int) Math.floor(getIconWidth() *
scaling - transDeviceAdjX);
+ final int deviceHeight = (int) Math.floor(getIconHeight() *
scaling - transDeviceAdjY);
+ paintIcon(c, g2, deviceWidth, deviceHeight, scaling);
+ } finally {
+ g2.dispose();
+ }
+ }
+
+ /**
+ * Paint the icon at the given width and height. The dimensions given are
the device pixels onto
+ * which the icon must be drawn after it has been scaled up from its
originally constant logical
+ * dimensions and aligned onto the device pixel grid. Painting onto the
supplied
+ * {@code Graphics2D} instance using whole number coordinates (for
horizontal and veritcal
+ * lines) will encourage sharp and well-aligned icons.
+ *
+ * <p>The icon should be painted with its upper left-hand corner at
position (0, 0). Icons need
+ * not be opaque. Due to rounding errors and alignment correction, the
aspect ratio of the
+ * device dimensions supplied here may not be exactly the same as that of
the logical pixel
+ * dimensions specified in the constructor.
+ *
+ * @param c may be used to get properties useful for painting, as in
+ * {@link Icon#paintIcon(Component,Graphics,int,int)}
+ * @param width the target width of the icon, after scaling and alignment
adjustments, in device
+ * pixels
+ * @param height the target height of the icon, after scaling and
alignment adjustments, in
+ * device pixels
+ * @param scaling the scaling factor that was used to scale the icon
dimensions up to their
+ * stated value
+ * @param g need <em>not</em> be cleaned up or restored to its previous
state after use; will
+ * have anti-aliasing already enabled by default
+ */
+ protected abstract void paintIcon(
+ Component c, Graphics2D g, int width, int height, double scaling);
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists