undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,490 @@
/*
* Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.awt;
import java.awt.AWTException;
import java.awt.EventQueue;
import java.awt.event.InputMethodEvent;
import java.awt.font.TextAttribute;
import java.awt.font.TextHitInfo;
import java.awt.peer.ComponentPeer;
import java.text.AttributedString;
import sun.util.logging.PlatformLogger;
/**
* Input Method Adapter for XIM for AIX
*
* @author JavaSoft International
*/
public abstract class X11InputMethod extends X11InputMethodBase {
// to keep the instance of activating if IM resumed
protected static X11InputMethod activatedInstance = null;
/**
* Constructs an X11InputMethod instance. It initializes the XIM
* environment if it's not done yet.
*
* @throws AWTException if XOpenIM() failed.
*/
public X11InputMethod() throws AWTException {
super();
}
/**
* Reset the composition state to the current composition state.
*/
protected void resetCompositionState() {
if (compositionEnableSupported && haveActiveClient()) {
try {
/* Restore the composition mode to the last saved composition
mode. */
setCompositionEnabled(savedCompositionState);
} catch (UnsupportedOperationException e) {
compositionEnableSupported = false;
}
}
}
/**
* Activate input method.
*/
public synchronized void activate() {
activatedInstance = this;
clientComponentWindow = getClientComponentWindow();
if (clientComponentWindow == null)
return;
if (lastXICFocussedComponent != null) {
if (log.isLoggable(PlatformLogger.Level.FINE)) {
log.fine("XICFocused {0}, AWTFocused {1}",
lastXICFocussedComponent, awtFocussedComponent);
}
if (lastXICFocussedComponent != awtFocussedComponent) {
ComponentPeer lastXICFocussedComponentPeer = getPeer(lastXICFocussedComponent);
if (lastXICFocussedComponentPeer != null) {
setXICFocus(lastXICFocussedComponentPeer, false, isLastXICActive);
}
}
lastXICFocussedComponent = null;
}
if (pData == 0) {
if (!createXIC()) {
return;
}
disposed = false;
}
/* reset input context if necessary and set the XIC focus
*/
resetXICifneeded();
ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
setStatusAreaVisible(true, pData);
if (awtFocussedComponentPeer != null) {
setXICFocus(awtFocussedComponentPeer, true, haveActiveClient());
}
lastXICFocussedComponent = awtFocussedComponent;
isLastXICActive = haveActiveClient();
isActive = true;
if (savedCompositionState) {
resetCompositionState();
}
}
/**
* Deactivate input method.
*/
public synchronized void deactivate(boolean isTemporary) {
boolean isAc = haveActiveClient();
/* Usually as the client component, let's call it component A,
loses the focus, this method is called. Then when another client
component, let's call it component B, gets the focus, activate is first called on
the previous focused component which is A, then endComposition is called on A,
deactivate is called on A again. And finally activate is called on the newly
focused component B. Here is the call sequence.
A loses focus B gains focus
-------------> deactivate A -------------> activate A -> endComposition A ->
deactivate A -> activate B ----....
So in order to carry the composition mode across the components sharing the same
input context, we save it when deactivate is called so that when activate is
called, it can be restored correctly till activate is called on the newly focused
component. (See also sun/awt/im/InputContext and bug 6184471).
Last note, getCompositionState should be called before setXICFocus since
setXICFocus here sets the XIC to 0.
*/
activatedInstance = null;
savedCompositionState = getCompositionState();
if (isTemporary) {
//turn the status window off...
turnoffStatusWindow();
/* Delay resetting the XIC focus until activate is called and the newly
* Focused component has a different peer as the last focused component.
*/
lastXICFocussedComponent = awtFocussedComponent;
} else {
if (awtFocussedComponent != null ) {
ComponentPeer awtFocussedComponentPeer = getPeer(awtFocussedComponent);
if (awtFocussedComponentPeer != null) {
setXICFocus(awtFocussedComponentPeer, false, isAc);
}
}
lastXICFocussedComponent = null;
}
isLastXICActive = isAc;
isLastTemporary = isTemporary;
isActive = false;
setStatusAreaVisible(false, pData);
}
// implements java.awt.im.spi.InputMethod.hideWindows
public void hideWindows() {
if (pData != 0) {
setStatusAreaVisible(false, pData);
turnoffStatusWindow();
}
}
/**
* Updates composed text with XIM preedit information and
* posts composed text to the awt event queue. The args of
* this method correspond to the XIM preedit callback
* information. The XIM highlight attributes are translated via
* fixed mapping (i.e., independent from any underlying input
* method engine). This method is invoked in the AWT Toolkit
* (X event loop) thread context and thus inside the AWT Lock.
*/
// NOTE: This method may be called by privileged threads.
// This functionality is implemented in a package-private method
// to insure that it cannot be overridden by client subclasses.
// DO NOT INVOKE CLIENT CODE ON THIS THREAD!
void dispatchComposedText(String chgText,
int[] chgStyles,
int chgOffset,
int chgLength,
int caretPosition,
long when) {
if (disposed) {
return;
}
// Workaround for deadlock bug on solaris2.6_zh bug#4170760
if (chgText == null
&& chgStyles == null
&& chgOffset == 0
&& chgLength == 0
&& caretPosition == 0
&& composedText == null
&& committedText == null)
return;
// Recalculate chgOffset and chgLength for supplementary char
if (composedText != null) {
int tmpChgOffset=chgOffset;
int tmpChgLength=chgLength;
int index = 0;
for (int i=0;i < tmpChgOffset; i++,index++){
if (index < composedText.length()
&& Character.charCount(composedText.codePointAt(index))==2){
index++;
chgOffset++;
}
}
// The index keeps value
for (int i=0;i < tmpChgLength; i++,index++){
if (index < composedText.length()
&& Character.charCount(composedText.codePointAt(index))==2){
index++;
chgLength++;
}
}
}
// Replace control character with a square box
if (chgText != null) {
StringBuilder newChgText = new StringBuilder();
for (int i=0; i < chgText.length(); i++){
char c = chgText.charAt(i);
if (Character.isISOControl(c)){
c = '\u25A1';
}
newChgText.append(c);
}
chgText = new String(newChgText);
}
if (composedText == null) {
// TODO: avoid reallocation of those buffers
composedText = new StringBuffer(INITIAL_SIZE);
rawFeedbacks = new IntBuffer(INITIAL_SIZE);
}
if (chgLength > 0) {
if (chgText == null && chgStyles != null) {
rawFeedbacks.replace(chgOffset, chgStyles);
} else {
if (chgLength == composedText.length()) {
// optimization for the special case to replace the
// entire previous text
composedText = new StringBuffer(INITIAL_SIZE);
rawFeedbacks = new IntBuffer(INITIAL_SIZE);
} else {
if (composedText.length() > 0) {
if (chgOffset+chgLength < composedText.length()) {
String text;
text = composedText.toString().substring(chgOffset+chgLength,
composedText.length());
composedText.setLength(chgOffset);
composedText.append(text);
} else {
// in case to remove substring from chgOffset
// to the end
composedText.setLength(chgOffset);
}
rawFeedbacks.remove(chgOffset, chgLength);
}
}
}
}
if (chgText != null) {
composedText.insert(chgOffset, chgText);
if (chgStyles != null) {
// Recalculate chgStyles for supplementary char
if (chgText.length() > chgStyles.length){
int index=0;
int[] newStyles = new int[chgText.length()];
for (int i=0; i < chgStyles.length; i++, index++){
newStyles[index]=chgStyles[i];
if (index < chgText.length()
&& Character.charCount(chgText.codePointAt(index))==2){
newStyles[++index]=chgStyles[i];
}
}
chgStyles=newStyles;
}
rawFeedbacks.insert(chgOffset, chgStyles);
}
}
else if (chgStyles != null) {
// Recalculate chgStyles to support supplementary char
int count=0;
for (int i=0; i < chgStyles.length; i++){
if (composedText.length() > chgOffset+i+count
&& Character.charCount(composedText.codePointAt(chgOffset+i+count))==2){
count++;
}
}
if (count>0){
int index=0;
int[] newStyles = new int[chgStyles.length+count];
for (int i=0; i < chgStyles.length; i++, index++){
newStyles[index]=chgStyles[i];
if (composedText.length() > chgOffset+index
&& Character.charCount(composedText.codePointAt(chgOffset+index))==2){
newStyles[++index]=chgStyles[i];
}
}
chgStyles=newStyles;
}
rawFeedbacks.replace(chgOffset, chgStyles);
}
if (composedText.length() == 0) {
composedText = null;
rawFeedbacks = null;
// if there is any outstanding committed text stored by
// dispatchCommittedText(), it has to be sent to the
// client component.
if (committedText != null) {
dispatchCommittedText(committedText, when);
committedText = null;
return;
}
// otherwise, send null text to delete client's composed
// text.
postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
null,
0,
null,
null,
when);
return;
}
// Adjust caretPosition for supplementary char
for (int i=0; i< caretPosition; i++){
if (i < composedText.length()
&& Character.charCount(composedText.codePointAt(i))==2){
caretPosition++;
i++;
}
}
// Now sending the composed text to the client
int composedOffset;
AttributedString inputText;
// if there is any partially committed text, concatenate it to
// the composed text.
if (committedText != null) {
composedOffset = committedText.length();
inputText = new AttributedString(committedText + composedText);
committedText = null;
} else {
composedOffset = 0;
inputText = new AttributedString(composedText.toString());
}
int currentFeedback;
int nextFeedback;
int startOffset = 0;
int currentOffset;
int visiblePosition = 0;
TextHitInfo visiblePositionInfo = null;
rawFeedbacks.rewind();
currentFeedback = rawFeedbacks.getNext();
rawFeedbacks.unget();
while ((nextFeedback = rawFeedbacks.getNext()) != -1) {
if (visiblePosition == 0) {
visiblePosition = nextFeedback & XIMVisibleMask;
if (visiblePosition != 0) {
int index = rawFeedbacks.getOffset() - 1;
if (visiblePosition == XIMVisibleToBackward)
visiblePositionInfo = TextHitInfo.leading(index);
else
visiblePositionInfo = TextHitInfo.trailing(index);
}
}
nextFeedback &= ~XIMVisibleMask;
if (currentFeedback != nextFeedback) {
rawFeedbacks.unget();
currentOffset = rawFeedbacks.getOffset();
inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
convertVisualFeedbackToHighlight(currentFeedback),
composedOffset + startOffset,
composedOffset + currentOffset);
startOffset = currentOffset;
currentFeedback = nextFeedback;
}
}
currentOffset = rawFeedbacks.getOffset();
if (currentOffset >= 0) {
inputText.addAttribute(TextAttribute.INPUT_METHOD_HIGHLIGHT,
convertVisualFeedbackToHighlight(currentFeedback),
composedOffset + startOffset,
composedOffset + currentOffset);
}
postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
inputText.getIterator(),
composedOffset,
TextHitInfo.leading(caretPosition),
visiblePositionInfo,
when);
}
/* Some IMs need forced Text clear */
void clearComposedText(long when) {
composedText = null;
postInputMethodEvent(InputMethodEvent.INPUT_METHOD_TEXT_CHANGED,
null, 0, null, null,
when);
if (committedText != null && committedText.length() > 0) {
dispatchCommittedText(committedText, when);
}
committedText = null;
rawFeedbacks = null;
}
void clearComposedText() {
if (EventQueue.isDispatchThread()) {
clearComposedText(EventQueue.getMostRecentEventTime());
}
}
/*
* Subclasses should override disposeImpl() instead of dispose(). Client
* code should always invoke dispose(), never disposeImpl().
*/
protected synchronized void disposeImpl() {
disposeXIC();
awtLock();
try {
clearComposedText();
} finally {
// Put awtUnlock into finally block in case an exception is thrown in clearComposedText.
awtUnlock();
}
awtFocussedComponent = null;
lastXICFocussedComponent = null;
needResetXIC = false;
savedCompositionState = false;
compositionEnableSupported = true;
}
/**
* @see java.awt.im.spi.InputMethod#setCompositionEnabled(boolean)
*/
public void setCompositionEnabled(boolean enable) {
/* If the composition state is successfully changed, set
the savedCompositionState to 'enable'. Otherwise, simply
return.
setCompositionEnabledNative may throw UnsupportedOperationException.
Don't try to catch it since the method may be called by clients.
Use package private method 'resetCompositionState' if you want the
exception to be caught.
*/
boolean pre, post;
pre=getCompositionState();
if (setCompositionEnabledNative(enable)) {
savedCompositionState = enable;
}
post=getCompositionState();
if (pre != post && post == enable){
if (enable == false) flushText();
if (awtFocussedComponent != null && isActive){
setXICFocus(getPeer(awtFocussedComponent),
true, haveActiveClient());
}
}
}
private native void setStatusAreaVisible(boolean value, long data);
}

View file

@ -0,0 +1,419 @@
#
#
# Copyright (c) 2013, 2023 SAP SE. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
#
# Portions Copyright (c) 2014 IBM Corporation
#
# This file references the standard Latin Type1 fonts from the AIX package
# X11.fnt.iso_T1 and the Unicode TrueType fonts from X11.fnt.ucs.ttf. They
# are located by default under "/usr/lpp/X11/lib/X11/fonts/{Type1,TrueType}"
# and sym-linked to "/usr/lib/X11/fonts/".
# Version
version=1
# Component Font Mappings
allfonts.iso10646-extB=-monotype-sansmonowtextb-medium-r-normal--*-%d-75-75-m-*-unicode-2
dialog.plain.latin-1=-*-helvetica-medium-r-normal--*-%d-100-100-p-*-iso8859-1
dialog.plain.thai=-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
dialog.plain.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
dialog.plain.japanese-x0208=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
dialog.plain.japanese-x0201=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
dialog.plain.japanese-udc=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
dialog.plain.japanese-iso10646=-monotype-wt sans j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.plain.korean=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
dialog.plain.korean-iso10646=-monotype-wt sans k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.plain.chinese=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
dialog.plain.chinese-iso10646=-monotype-wt sans sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.plain.taiwanese-iso10646=-monotype-wt sans tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.bold.latin-1=-*-helvetica-bold-r-normal--*-%d-100-100-p-*-iso8859-1
dialog.bold.thai=-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
dialog.bold.ukranian-ibm1124=-*-*-bold-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
dialog.bold.japanese-x0208=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
dialog.bold.japanese-x0201=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
dialog.bold.japanese-udc=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
dialog.bold.japanese-iso10646=-monotype-wt sans j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.bold.korean=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
dialog.bold.korean-iso10646=-monotype-wt sans k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.bold.chinese=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
dialog.bold.chinese-iso10646=-monotype-wt sans sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.bold.taiwanese-iso10646=-monotype-wt sans tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.italic.latin-1=-*-helvetica-medium-o-normal--*-%d-100-100-p-*-iso8859-1
dialog.italic.thai=-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
dialog.italic.ukranian-ibm1124=-*-*-medium-i-normal--*-%d-75-75-p-*-ucs2.i18n-0
dialog.italic.japanese-x0208=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
dialog.italic.japanese-x0201=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
dialog.italic.japanese-udc=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
dialog.italic.japanese-iso10646=-monotype-wt sans j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.italic.korean=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
dialog.italic.korean-iso10646=-monotype-wt sans k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.italic.chinese=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
dialog.italic.chinese-iso10646=-monotype-wt sans sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.italic.taiwanese-iso10646=-monotype-wt sans tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.bolditalic.latin-1=-*-helvetica-bold-o-normal--*-%d-100-100-p-*-iso8859-1
dialog.bolditalic.thai=-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
dialog.bolditalic.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
dialog.bolditalic.japanese-x0208=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
dialog.bolditalic.japanese-x0201=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
dialog.bolditalic.japanese-udc=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
dialog.bolditalic.japanese-iso10646=-monotype-wt sans j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.bolditalic.korean=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
dialog.bolditalic.korean-iso10646=-monotype-wt sans k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.bolditalic.chinese=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
dialog.bolditalic.chinese-iso10646=-monotype-wt sans sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialog.bolditalic.taiwanese-iso10646=-monotype-wt sans tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.plain.latin-1=-*-courier-medium-r-normal--*-%d-100-100-m-*-iso8859-1
dialoginput.plain.thai=-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0
dialoginput.plain.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-m-*-ucs2.i18n-0
dialoginput.plain.japanese-x0208=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
dialoginput.plain.japanese-x0201=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
dialoginput.plain.japanese-udc=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
dialoginput.plain.japanese-iso10646=-monotype-wt sans duo j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.plain.korean=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
dialoginput.plain.korean-iso10646=-monotype-wt sans duo k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.plain.chinese=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
dialoginput.plain.chinese-iso10646=-monotype-wt sans duo sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.plain.taiwanese-iso10646=-monotype-wt sans duo tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.bold.latin-1=-*-courier-bold-r-normal--*-%d-100-100-m-*-iso8859-1
dialoginput.bold.thai=-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0
dialoginput.bold.ukranian-ibm1124=-*-*-bold-r-normal--*-%d-75-75-m-*-ucs2.i18n-0
dialoginput.bold.japanese-x0208=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
dialoginput.bold.japanese-x0201=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
dialoginput.bold.japanese-udc=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
dialoginput.bold.japanese-iso10646=-monotype-wt sans duo j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.bold.korean=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
dialoginput.bold.korean-iso10646=-monotype-wt sans duo k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.bold.chinese=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
dialoginput.bold.chinese-iso10646=-monotype-wt sans duo sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.bold.taiwanese-iso10646=-monotype-wt sans duo tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.italic.latin-1=-*-courier-medium-o-normal--*-%d-100-100-m-*-iso8859-1
dialoginput.italic.thai=-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0
dialoginput.italic.ukranian-ibm1124=-*-*-medium-i-normal--*-%d-75-75-m-*-ucs2.i18n-0
dialoginput.italic.japanese-x0208=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
dialoginput.italic.japanese-x0201=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
dialoginput.italic.japanese-udc=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
dialoginput.italic.japanese-iso10646=-monotype-wt sans duo j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.italic.korean=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
dialoginput.italic.korean-iso10646=-monotype-wt sans duo k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.italic.chinese=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
dialoginput.italic.chinese-iso10646=-monotype-wt sans duo sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.italic.taiwanese-iso10646=-monotype-wt sans duo tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.bolditalic.latin-1=-*-courier-bold-o-normal--*-%d-100-100-m-*-iso8859-1
dialoginput.bolditalic.thai=-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0
dialoginput.bolditalic.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-m-*-ucs2.i18n-0
dialoginput.bolditalic.japanese-x0208=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
dialoginput.bolditalic.japanese-x0201=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
dialoginput.bolditalic.japanese-udc=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
dialoginput.bolditalic.japanese-iso10646=-monotype-wt sans duo j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.bolditalic.korean=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
dialoginput.bolditalic.korean-iso10646=-monotype-wt sans duo k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.bolditalic.chinese=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
dialoginput.bolditalic.chinese-iso10646=-monotype-wt sans duo sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
dialoginput.bolditalic.taiwanese-iso10646=-monotype-wt sans duo tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.plain.latin-1=-*-helvetica-medium-r-normal--*-%d-100-100-p-*-iso8859-1
sansserif.plain.thai=-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
sansserif.plain.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
sansserif.plain.japanese-x0208=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
sansserif.plain.japanese-x0201=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
sansserif.plain.japanese-udc=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
sansserif.plain.japanese-iso10646=-monotype-wt sans j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.plain.korean=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
sansserif.plain.korean-iso10646=-monotype-wt sans k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.plain.chinese=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
sansserif.plain.chinese-iso10646=-monotype-wt sans sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.plain.taiwanese-iso10646=-monotype-wt sans tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.bold.latin-1=-*-helvetica-bold-r-normal--*-%d-100-100-p-*-iso8859-1
sansserif.bold.thai=-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
sansserif.bold.ukranian-ibm1124=-*-*-bold-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
sansserif.bold.japanese-x0208=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
sansserif.bold.japanese-x0201=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
sansserif.bold.japanese-udc=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
sansserif.bold.japanese-iso10646=-monotype-wt sans j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.bold.korean=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
sansserif.bold.korean-iso10646=-monotype-wt sans k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.bold.chinese=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
sansserif.bold.chinese-iso10646=-monotype-wt sans sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.bold.taiwanese-iso10646=-monotype-wt sans tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.italic.latin-1=-*-helvetica-medium-o-normal--*-%d-100-100-p-*-iso8859-1
sansserif.italic.thai=-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
sansserif.italic.ukranian-ibm1124=-*-*-medium-i-normal--*-%d-75-75-p-*-ucs2.i18n-0
sansserif.italic.japanese-x0208=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
sansserif.italic.japanese-x0201=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
sansserif.italic.japanese-udc=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
sansserif.italic.japanese-iso10646=-monotype-wt sans j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.italic.korean=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
sansserif.italic.korean-iso10646=-monotype-wt sans k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.italic.chinese=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
sansserif.italic.chinese-iso10646=-monotype-wt sans sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.italic.taiwanese-iso10646=-monotype-wt sans tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.bolditalic.latin-1=-*-helvetica-bold-o-normal--*-%d-100-100-p-*-iso8859-1
sansserif.bolditalic.thai=-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
sansserif.bolditalic.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
sansserif.bolditalic.japanese-x0208=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
sansserif.bolditalic.japanese-x0201=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
sansserif.bolditalic.japanese-udc=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
sansserif.bolditalic.japanese-iso10646=-monotype-wt sans j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.bolditalic.korean=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
sansserif.bolditalic.korean-iso10646=-monotype-wt sans k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.bolditalic.chinese=-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
sansserif.bolditalic.chinese-iso10646=-monotype-wt sans sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
sansserif.bolditalic.taiwanese-iso10646=-monotype-wt sans tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.plain.latin-1=-*-times new roman-medium-r-normal--*-%d-100-100-p-*-iso8859-1
serif.plain.thai=-ibm-thaitimes-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
serif.plain.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
serif.plain.japanese-x0208=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
serif.plain.japanese-x0201=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
serif.plain.japanese-udc=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
serif.plain.japanese-iso10646=-monotype-wt serif j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.plain.korean=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
serif.plain.korean-iso10646=-monotype-wt serif k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.plain.chinese=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
serif.plain.chinese-iso10646=-monotype-wt serif sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.plain.taiwanese-iso10646=-monotype-wt serif tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.bold.latin-1=-*-times new roman-bold-r-normal--*-%d-100-100-p-*-iso8859-1
serif.bold.thai=-ibm-thaitimes-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
serif.bold.ukranian-ibm1124=-*-*-bold-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
serif.bold.japanese-x0208=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
serif.bold.japanese-x0201=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
serif.bold.japanese-udc=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
serif.bold.japanese-iso10646=-monotype-wt serif j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.bold.korean=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
serif.bold.korean-iso10646=-monotype-wt serif k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.bold.chinese=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
serif.bold.chinese-iso10646=-monotype-wt serif sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.bold.taiwanese-iso10646=-monotype-wt serif tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.italic.latin-1=-*-times new roman-medium-i-normal--*-%d-100-100-p-*-iso8859-1
serif.italic.thai=-ibm-thaitimes-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
serif.italic.ukranian-ibm1124=-*-*-medium-i-normal--*-%d-75-75-p-*-ucs2.i18n-0
serif.italic.japanese-x0208=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
serif.italic.japanese-x0201=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
serif.italic.japanese-udc=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
serif.italic.japanese-iso10646=-monotype-wt serif j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.italic.korean=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
serif.italic.korean-iso10646=-monotype-wt serif k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.italic.chinese=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
serif.italic.chinese-iso10646=-monotype-wt serif sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.italic.taiwanese-iso10646=-monotype-wt serif tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.bolditalic.latin-1=-*-times new roman-bold-i-normal--*-%d-100-100-p-*-iso8859-1
serif.bolditalic.thai=-ibm-thaitimes-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0
serif.bolditalic.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-p-*-ucs2.i18n-0
serif.bolditalic.japanese-x0208=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
serif.bolditalic.japanese-x0201=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
serif.bolditalic.japanese-udc=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
serif.bolditalic.japanese-iso10646=-monotype-wt serif j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.bolditalic.korean=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
serif.bolditalic.korean-iso10646=-monotype-wt serif k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.bolditalic.chinese=-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
serif.bolditalic.chinese-iso10646=-monotype-wt serif sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
serif.bolditalic.taiwanese-iso10646=-monotype-wt serif tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.plain.latin-1=-*-courier-medium-r-normal--*-%d-100-100-m-*-iso8859-1
monospaced.plain.thai=-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0
monospaced.plain.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-m-*-ucs2.i18n-0
monospaced.plain.japanese-x0208=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
monospaced.plain.japanese-x0201=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
monospaced.plain.japanese-udc=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
monospaced.plain.japanese-iso10646=-monotype-wt sans duo j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.plain.korean=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
monospaced.plain.korean-iso10646=-monotype-wt sans duo k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.plain.chinese=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
monospaced.plain.chinese-iso10646=-monotype-wt sans duo sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.plain.taiwanese-iso10646=-monotype-wt sans duo tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.bold.latin-1=-*-courier-bold-r-normal--*-%d-100-100-m-*-iso8859-1
monospaced.bold.thai=-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0
monospaced.bold.ukranian-ibm1124=-*-*-bold-r-normal--*-%d-75-75-m-*-ucs2.i18n-0
monospaced.bold.japanese-x0208=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
monospaced.bold.japanese-x0201=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
monospaced.bold.japanese-udc=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
monospaced.bold.japanese-iso10646=-monotype-wt sans duo j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.bold.korean=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
monospaced.bold.korean-iso10646=-monotype-wt sans duo k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.bold.chinese=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
monospaced.bold.chinese-iso10646=-monotype-wt sans duo sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.bold.taiwanese-iso10646=-monotype-wt sans duo tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.italic.latin-1=-*-courier-medium-o-normal--*-%d-100-100-m-*-iso8859-1
monospaced.italic.thai=-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0
monospaced.italic.ukranian-ibm1124=-*-*-medium-i-normal--*-%d-75-75-m-*-ucs2.i18n-0
monospaced.italic.japanese-x0208=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
monospaced.italic.japanese-x0201=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
monospaced.italic.japanese-udc=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
monospaced.italic.japanese-iso10646=-monotype-wt sans duo j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.italic.korean=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
monospaced.italic.korean-iso10646=-monotype-wt sans duo k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.italic.chinese=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
monospaced.italic.chinese-iso10646=-monotype-wt sans duo sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.italic.taiwanese-iso10646=-monotype-wt sans duo tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.bolditalic.latin-1=-*-courier-bold-o-normal--*-%d-100-100-m-*-iso8859-1
monospaced.bolditalic.thai=-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0
monospaced.bolditalic.ukranian-ibm1124=-*-*-medium-r-normal--*-%d-75-75-m-*-ucs2.i18n-0
monospaced.bolditalic.japanese-x0208=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0
monospaced.bolditalic.japanese-x0201=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0
monospaced.bolditalic.japanese-udc=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp
monospaced.bolditalic.japanese-iso10646=-monotype-wt sans duo j-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.bolditalic.korean=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0
monospaced.bolditalic.korean-iso10646=-monotype-wt sans duo k-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.bolditalic.chinese=-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0
monospaced.bolditalic.chinese-iso10646=-monotype-wt sans duo sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1
monospaced.bolditalic.taiwanese-iso10646=-monotype-wt sans duo tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1
# Search Sequences
sequence.allfonts=latin-1
sequence.allfonts.UTF-8=latin-1,japanese-iso10646
# C and POSIX locale
sequence.allfonts.UTF-8.en.=latin-1
# Uk_UA
sequence.allfonts.x-IBM1124=latin-1,ukranian-ibm1124
# Japanese
sequence.allfonts.x-IBM943C=latin-1,japanese-x0201,japanese-x0208,japanese-udc
sequence.allfonts.x-IBM29626C=latin-1,japanese-x0201,japanese-x0208,japanese-udc
sequence.allfonts.UTF-8.ja=japanese-iso10646,latin-1,iso10646-extB
# Chinese
sequence.allfonts.x-IBM1383=latin-1,chinese
sequence.allfonts.GB18030=latin-1,chinese-iso10646,iso10646-extB
sequence.allfonts.UTF-8.zh.CN=latin-1,chinese-iso10646,iso10646-extB
# Taiwanese
sequence.allfonts.x-IBM964=latin-1,taiwanese-iso10646
sequence.allfonts.x-IBM950=latin-1,taiwanese-iso10646
sequence.allfonts.UTF-8.zh.TW=latin-1,taiwanese-iso10646
# Korean
sequence.allfonts.x-IBM970=latin-1,korean
sequence.allfonts.UTF-8.ko=latin-1,korean-iso10646
# Thai
sequence.allfonts.TIS-620=latin-1,thai
sequence.allfonts.UTF-8.th=latin-1,thai
# fallback
sequence.fallback=thai,chinese-iso10646,taiwanese-iso10646,japanese-iso10646,korean-iso10646,iso10646-extB
# Exclusion Ranges
exclusion.japanese-iso10646=0000-00ff
# Font File Names
filename.-*-courier-medium-r-normal--*-%d-100-100-m-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/cour.pfa
filename.-*-courier-bold-r-normal--*-%d-100-100-m-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/courb.pfa
filename.-*-courier-medium-o-normal--*-%d-100-100-m-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/couri.pfa
filename.-*-courier-bold-o-normal--*-%d-100-100-m-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/courbi.pfa
filename.-*-helvetica-medium-r-normal--*-%d-100-100-p-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/helv.pfa
filename.-*-helvetica-bold-r-normal--*-%d-100-100-p-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/helvb.pfa
filename.-*-helvetica-medium-o-normal--*-%d-100-100-p-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/helvi.pfa
filename.-*-helvetica-bold-o-normal--*-%d-100-100-p-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/helvbi.pfa
filename.-*-times_new_roman-medium-r-normal--*-%d-100-100-p-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/tnr.pfa
filename.-*-times_new_roman-bold-r-normal--*-%d-100-100-p-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/tnrb.pfa
filename.-*-times_new_roman-medium-i-normal--*-%d-100-100-p-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/tnri.pfa
filename.-*-times_new_roman-bold-i-normal--*-%d-100-100-p-*-iso8859-1=/usr/lpp/X11/lib/X11/fonts/Type1/tnrbi.pfa
filename.-monotype-sansmonowtextb-medium-r-normal--*-%d-75-75-m-*-unicode-2=/usr/lpp/X11/lib/X11/fonts/TrueType/MTSanXBA.ttf
filename.-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-monotype-wt_serif_j-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wt__j__b.ttf
filename.-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_k.ttf
filename.-monotype-wt_serif_k-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wt__k__b.ttf
filename.-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_s.ttf
filename.-monotype-wt_serif_sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wt__s__b.ttf
filename.-monotype-wt_serif_tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wt__tt_b.ttf
filename.-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-monotype-wt_sans_duo_j-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wtsdj__b.ttf
filename.-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdk.ttf
filename.-monotype-wt_sans_duo_k-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wtsdk__b.ttf
filename.-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansds.ttf
filename.-monotype-wt_sans_duo_sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wtsds__b.ttf
filename.-monotype-wt_sans_duo_tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wtsdtt_b.ttf
filename.-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0208.1983-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsans_j.ttf
filename.-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-jisx0201.1976-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsans_j.ttf
filename.-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ibm-udcjp=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsans_j.ttf
filename.-monotype-wt_sans_j-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wts_j__b.ttf
filename.-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-ksc5601.1987-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsans_k.ttf
filename.-monotype-wt_sans_k-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wts_k__b.ttf
filename.-monotype-sanswt-medium-r-normal--*-%d-75-75-*-*-gb2312.1980-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsans_s.ttf
filename.-monotype-wt_sans_sc-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wts_s__b.ttf
filename.-monotype-wt_sans_tw-medium-r-normal--*-%d-75-75-*-*-iso10646-1=/usr/lpp/X11/lib/X11/fonts/TrueType/wts_tt_b.ttf
filename.-monotype-timesnewromanwt-medium-r-normal--*-%d-75-75-*-*-iso8859-15=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-monotype-sansmonowt-medium-r-normal--*-%d-75-75-*-*-iso8859-15=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-ibm-thaicourier-medium-r-normal--*-%d-75-75-m-*-ucs2.thai-0=/usr/lpp/X11/lib/X11/fonts/TrueType/courth.ttf
filename.-ibm-thaihelvetica-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0=/usr/lpp/X11/lib/X11/fonts/TrueType/helvth.ttf
filename.-ibm-thaitimes-medium-r-normal--*-%d-75-75-p-*-ucs2.thai-0=/usr/lpp/X11/lib/X11/fonts/TrueType/timeth.ttf
filename.-*-*-medium-r-normal--*-%d-*-*-p-*-ibm-1046=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-*-*-bold-r-normal--*-%d-*-*-p-*-ibm-1046=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-*-*-medium-i-normal--*-%d-*-*-p-*-ibm-1046=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-*-*-bold-i-normal--*-%d-*-*-p-*-ibm-1046=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-*-*-medium-r-normal--*-%d-*-*-m-*-ibm-1046=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-*-*-bold-r-normal--*-%d-*-*-m-*-ibm-1046=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-*-*-medium-i-normal--*-%d-*-*-m-*-ibm-1046=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-*-*-bold-i-normal--*-%d-*-*-m-*-ibm-1046=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-*-*-medium-r-normal--*-%d-75-75-p-*-ucs2.i18n-0=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-*-*-bold-r-normal--*-%d-75-75-p-*-ucs2.i18n-0=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-*-*-medium-i-normal--*-%d-75-75-p-*-ucs2.i18n-0=/usr/lpp/X11/lib/X11/fonts/TrueType/tnrwt_j.ttf
filename.-*-*-medium-r-normal--*-%d-75-75-m-*-ucs2.i18n-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-*-*-bold-r-normal--*-%d-75-75-m-*-ucs2.i18n-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
filename.-*-*-medium-i-normal--*-%d-75-75-m-*-ucs2.i18n-0=/usr/lpp/X11/lib/X11/fonts/TrueType/mtsansdj.ttf
# AWT font path
awtfontpath.japanese-x0201=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.japanese-x0208=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.japanese-udc=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.japanese-iso10646=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.korean=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.korean-iso10646=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.chinese=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.chinese-iso10646=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.taiwanese-iso10646=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.thai=/usr/lpp/X11/lib/X11/fonts/TrueType
awtfontpath.iso10646-extB=/usr/lpp/X11/lib/X11/fonts/TrueType

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2012, 2026 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
#include <stdio.h>
#include <string.h>
#include <sys/ldr.h>
#include <errno.h>
#include "porting_aix.h"
static unsigned char dladdr_buffer[0x8000];
static void fill_dll_info(void) {
int rc = loadquery(L_GETINFO,dladdr_buffer, sizeof(dladdr_buffer));
if (rc == -1) {
fprintf(stderr, "loadquery failed (%d %s)", errno, strerror(errno));
fflush(stderr);
}
}
static int dladdr_dont_reload(void* addr, Dl_info* info) {
const struct ld_info* p = (struct ld_info*) dladdr_buffer;
memset((void *)info, 0, sizeof(Dl_info));
for (;;) {
if (addr >= p->ldinfo_textorg &&
(char*)addr < (char*)(p->ldinfo_textorg) + p->ldinfo_textsize) {
info->dli_fname = p->ldinfo_filename;
return 1;
}
if (!p->ldinfo_next) {
break;
}
p = (struct ld_info*)(((char*)p) + p->ldinfo_next);
}
return 0;
}
#ifdef __cplusplus
extern "C"
#endif
int dladdr(void *addr, Dl_info *info) {
static int loaded = 0;
if (!loaded) {
fill_dll_info();
loaded = 1;
}
if (!addr) {
return 0;
}
/* Address could be AIX function descriptor? */
void* const addr0 = *( (void**) addr );
int rc = dladdr_dont_reload(addr, info);
if (rc == 0) {
rc = dladdr_dont_reload(addr0, info);
if (rc == 0) {
fill_dll_info(); /* refill, maybe loadquery info is outdated */
rc = dladdr_dont_reload(addr, info);
if (rc == 0) {
rc = dladdr_dont_reload(addr0, info);
}
}
}
return rc;
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2012, 2026 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
/*
* Aix' own version of dladdr().
* This function tries to mimic dladdr(3) on Linux
* (see http://linux.die.net/man/3/dladdr)
* dladdr(3) is not POSIX but a GNU extension, and is not available on AIX.
*
*/
#include "dl_info.h"
#ifdef __cplusplus
extern "C"
#endif
int dladdr(void *addr, Dl_info *info);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,189 @@
#
#
# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation. Oracle designates this
# particular file as subject to the "Classpath" exception as provided
# by Oracle in the LICENSE file that accompanied this code.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
# Version
# Uses Fedora Core 6 fonts and file paths.
version=1
# Component Font Mappings
dialog.plain.latin-1=DejaVu LGC Sans
dialog.plain.japanese-x0208=Sazanami Gothic
dialog.plain.korean=Baekmuk Gulim
dialog.plain.chinese-big5=AR PL ShanHeiSun Uni
dialog.plain.chinese-gb18030=AR PL ShanHeiSun Uni
dialog.bold.latin-1=DejaVu LGC Sans Bold
dialog.bold.japanese-x0208=Sazanami Gothic
dialog.bold.korean=Baekmuk Gulim
dialog.bold.chinese-big5=AR PL ShanHeiSun Uni
dialog.bold.chinese-gb18030=AR PL ShanHeiSun Uni
dialog.italic.latin-1=DejaVu LGC Sans Oblique
dialog.italic.japanese-x0208=Sazanami Gothic
dialog.italic.korean=Baekmuk Gulim
dialog.italic.chinese-big5=AR PL ShanHeiSun Uni
dialog.italic.chinese-gb18030=AR PL ShanHeiSun Uni
dialog.bolditalic.latin-1=DejaVu LGC Sans Bold Oblique
dialog.bolditalic.japanese-x0208=Sazanami Gothic
dialog.bolditalic.korean=Baekmuk Gulim
dialog.bolditalic.chinese-big5=AR PL ShanHeiSun Uni
dialog.bolditalic.chinese-gb18030=AR PL ShanHeiSun Uni
sansserif.plain.latin-1=DejaVu LGC Sans
sansserif.plain.japanese-x0208=Sazanami Gothic
sansserif.plain.korean=Baekmuk Gulim
sansserif.plain.chinese-big5=AR PL ShanHeiSun Uni
sansserif.plain.chinese-gb18030=AR PL ShanHeiSun Uni
sansserif.bold.latin-1=DejaVu LGC Sans Bold
sansserif.bold.japanese-x0208=Sazanami Gothic
sansserif.bold.korean=Baekmuk Gulim
sansserif.bold.chinese-big5=AR PL ShanHeiSun Uni
sansserif.bold.chinese-gb18030=AR PL ShanHeiSun Uni
sansserif.italic.latin-1=DejaVu LGC Sans Oblique
sansserif.italic.japanese-x0208=Sazanami Gothic
sansserif.italic.korean=Baekmuk Gulim
sansserif.italic.chinese-big5=AR PL ShanHeiSun Uni
sansserif.italic.chinese-gb18030=AR PL ShanHeiSun Uni
sansserif.bolditalic.latin-1=DejaVu LGC Sans Bold Oblique
sansserif.bolditalic.japanese-x0208=Sazanami Gothic
sansserif.bolditalic.korean=Baekmuk Gulim
sansserif.bolditalic.chinese-big5=AR PL ShanHeiSun Uni
sansserif.bolditalic.chinese-gb18030=AR PL ShanHeiSun Uni
serif.plain.latin-1=DejaVu LGC Serif
serif.plain.japanese-x0208=Sazanami Mincho
serif.plain.korean=Baekmuk Batang
serif.plain.chinese-big5=AR PL ZenKai Uni
serif.plain.chinese-gb18030=AR PL ZenKai Uni
serif.bold.latin-1=DejaVu LGC Serif Bold
serif.bold.japanese-x0208=Sazanami Mincho
serif.bold.korean=Baekmuk Batang
serif.bold.chinese-big5=AR PL ZenKai Uni
serif.bold.chinese-gb18030=AR PL ZenKai Uni
serif.italic.latin-1=DejaVu LGC Serif Oblique
serif.italic.japanese-x0208=Sazanami Mincho
serif.italic.korean=Baekmuk Batang
serif.italic.chinese-big5=AR PL ZenKai Uni
serif.italic.chinese-gb18030=AR PL ZenKai Uni
serif.bolditalic.latin-1=DejaVu LGC Serif Bold Oblique
serif.bolditalic.japanese-x0208=Sazanami Mincho
serif.bolditalic.korean=Baekmuk Batang
serif.bolditalic.chinese-big5=AR PL ZenKai Uni
serif.bolditalic.chinese-gb18030=AR PL ZenKai Uni
monospaced.plain.latin-1=DejaVu LGC Sans Mono
monospaced.plain.japanese-x0208=Sazanami Gothic
monospaced.plain.korean=Baekmuk Gulim
monospaced.plain.chinese-big5=AR PL ShanHeiSun Uni
monospaced.plain.chinese-gb18030=AR PL ShanHeiSun Uni
monospaced.bold.latin-1=DejaVu LGC Sans Mono Bold
monospaced.bold.japanese-x0208=Sazanami Gothic
monospaced.bold.korean=Baekmuk Gulim
monospaced.bold.chinese-big5=AR PL ShanHeiSun Uni
monospaced.bold.chinese-gb18030=AR PL ShanHeiSun Uni
monospaced.italic.latin-1=DejaVu LGC Sans Mono Oblique
monospaced.italic.japanese-x0208=Sazanami Gothic
monospaced.italic.korean=Baekmuk Gulim
monospaced.italic.chinese-big5=AR PL ShanHeiSun Uni
monospaced.italic.chinese-gb18030=AR PL ShanHeiSun Uni
monospaced.bolditalic.latin-1=DejaVu LGC Sans Mono Bold Oblique
monospaced.bolditalic.japanese-x0208=Sazanami Gothic
monospaced.bolditalic.korean=Baekmuk Gulim
monospaced.bolditalic.chinese-big5=AR PL ShanHeiSun Uni
monospaced.bolditalic.chinese-gb18030=AR PL ShanHeiSun Uni
dialoginput.plain.latin-1=DejaVu LGC Sans Mono
dialoginput.plain.japanese-x0208=Sazanami Gothic
dialoginput.plain.korean=Baekmuk Gulim
dialoginput.plain.chinese-big5=AR PL ShanHeiSun Uni
dialoginput.plain.chinese-gb18030=AR PL ShanHeiSun Uni
dialoginput.bold.latin-1=DejaVu LGC Sans Mono Bold
dialoginput.bold.japanese-x0208=Sazanami Gothic
dialoginput.bold.korean=Baekmuk Gulim
dialoginput.bold.chinese-big5=AR PL ShanHeiSun Uni
dialoginput.bold.chinese-gb18030=AR PL ShanHeiSun Uni
dialoginput.italic.latin-1=DejaVu LGC Sans Mono Oblique
dialoginput.italic.japanese-x0208=Sazanami Gothic
dialoginput.italic.korean=Baekmuk Gulim
dialoginput.italic.chinese-big5=AR PL ShanHeiSun Uni
dialoginput.italic.chinese-gb18030=AR PL ShanHeiSun Uni
dialoginput.bolditalic.latin-1=DejaVu LGC Sans Mono Bold Oblique
dialoginput.bolditalic.japanese-x0208=Sazanami Gothic
dialoginput.bolditalic.korean=Baekmuk Gulim
dialoginput.bolditalic.chinese-big5=AR PL ShanHeiSun Uni
dialoginput.bolditalic.chinese-gb18030=AR PL ShanHeiSun Uni
# Search Sequences
sequence.allfonts=latin-1
sequence.allfonts.Big5=chinese-big5,latin-1
sequence.allfonts.x-euc-jp-bsd=japanese-x0208,latin-1
sequence.allfonts.EUC-KR=korean,latin-1
sequence.allfonts.GB18030=chinese-gb18030,latin-1
sequence.fallback=chinese-big5,chinese-gb18030,japanese-x0208,korean
# Font File Names
filename.DejaVu_LGC_Sans=/usr/share/fonts/dejavu-lgc/DejaVuLGCSans.ttf
filename.DejaVu_LGC_Sans_Bold=/usr/share/fonts/dejavu-lgc/DejaVuLGCSans-Bold.ttf
filename.DejaVu_LGC_Sans_Oblique=/usr/share/fonts/dejavu-lgc/DejaVuLGCSans-Oblique.ttf
filename.DejaVu_LGC_Sans_Bold_Oblique=/usr/share/fonts/dejavu-lgc/DejaVuLGCSans-BoldOblique.ttf
filename.DejaVu_LGC_Sans_Mono=/usr/share/fonts/dejavu-lgc/DejaVuLGCSansMono.ttf
filename.DejaVu_LGC_Sans_Mono_Bold=/usr/share/fonts/dejavu-lgc/DejaVuLGCSansMono-Bold.ttf
filename.DejaVu_LGC_Sans_Mono_Oblique=/usr/share/fonts/dejavu-lgc/DejaVuLGCSansMono-Oblique.ttf
filename.DejaVu_LGC_Sans_Mono_Bold_Oblique=/usr/share/fonts/dejavu-lgc/DejaVuLGCSansMono-BoldOblique.ttf
filename.DejaVu_LGC_Serif=/usr/share/fonts/dejavu-lgc/DejaVuLGCSerif.ttf
filename.DejaVu_LGC_Serif_Bold=/usr/share/fonts/dejavu-lgc/DejaVuLGCSerif-Bold.ttf
filename.DejaVu_LGC_Serif_Oblique=/usr/share/fonts/dejavu-lgc/DejaVuLGCSerif-Oblique.ttf
filename.DejaVu_LGC_Serif_Bold_Oblique=/usr/share/fonts/dejavu-lgc/DejaVuLGCSerif-BoldOblique.ttf
filename.Sazanami_Gothic=/usr/share/fonts/japanese/TrueType/sazanami-gothic.ttf
filename.Sazanami_Mincho=/usr/share/fonts/japanese/TrueType/sazanami-mincho.ttf
filename.AR_PL_ShanHeiSun_Uni=/usr/share/fonts/chinese/TrueType/uming.ttf
filename.AR_PL_ZenKai_Uni=/usr/share/fonts/chinese/TrueType/ukai.ttf
filename.Baekmuk_Gulim=/usr/share/fonts/korean/TrueType/gulim.ttf
filename.Baekmuk_Batang=/usr/share/fonts/korean/TrueType/batang.ttf

View file

@ -0,0 +1,182 @@
/*
* Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
//#define USE_ERROR
//#define USE_TRACE
#include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h"
static void alsaDebugOutput(const char *file, int line, const char *function, int err, const char *fmt, ...) {
#ifdef USE_ERROR
va_list args;
va_start(args, fmt);
printf("%s:%d function %s: error %d: %s\n", file, line, function, err, snd_strerror(err));
if (strlen(fmt) > 0) {
vprintf(fmt, args);
}
va_end(args);
#endif
}
static int alsa_inited = 0;
static int alsa_enumerate_pcm_subdevices = FALSE; // default: no
static int alsa_enumerate_midi_subdevices = FALSE; // default: no
void initAlsaSupport() {
char* enumerate;
if (!alsa_inited) {
alsa_inited = TRUE;
snd_lib_error_set_handler(&alsaDebugOutput);
enumerate = getenv(ENV_ENUMERATE_PCM_SUBDEVICES);
if (enumerate != NULL && strlen(enumerate) > 0
&& (enumerate[0] != 'f') // false
&& (enumerate[0] != 'F') // False
&& (enumerate[0] != 'n') // no
&& (enumerate[0] != 'N')) { // NO
alsa_enumerate_pcm_subdevices = TRUE;
}
#ifdef ALSA_MIDI_ENUMERATE_SUBDEVICES
alsa_enumerate_midi_subdevices = TRUE;
#endif
}
}
/* if true (non-zero), ALSA sub devices should be listed as separate devices
*/
int needEnumerateSubdevices(int isMidi) {
initAlsaSupport();
return isMidi ? alsa_enumerate_midi_subdevices
: alsa_enumerate_pcm_subdevices;
}
/*
* deviceID contains packed card, device and subdevice numbers
* each number takes 10 bits
* "default" device has id == ALSA_DEFAULT_DEVICE_ID
*/
UINT32 encodeDeviceID(int card, int device, int subdevice) {
return (((card & 0x3FF) << 20) | ((device & 0x3FF) << 10)
| (subdevice & 0x3FF)) + 1;
}
void decodeDeviceID(UINT32 deviceID, int* card, int* device, int* subdevice,
int isMidi) {
deviceID--;
*card = (deviceID >> 20) & 0x3FF;
*device = (deviceID >> 10) & 0x3FF;
if (needEnumerateSubdevices(isMidi)) {
*subdevice = deviceID & 0x3FF;
} else {
*subdevice = -1; // ALSA will choose any subdevices
}
}
void getDeviceString(char* buffer, size_t bufferSize, int card, int device,
int subdevice, int usePlugHw, int isMidi) {
if (needEnumerateSubdevices(isMidi)) {
snprintf(buffer, bufferSize, "%s:%d,%d,%d",
usePlugHw ? ALSA_PLUGHARDWARE : ALSA_HARDWARE,
card, device, subdevice);
} else {
snprintf(buffer, bufferSize, "%s:%d,%d",
usePlugHw ? ALSA_PLUGHARDWARE : ALSA_HARDWARE,
card, device);
}
}
void getDeviceStringFromDeviceID(char* buffer, size_t bufferSize,
UINT32 deviceID, int usePlugHw, int isMidi) {
int card, device, subdevice;
if (deviceID == ALSA_DEFAULT_DEVICE_ID) {
strcpy(buffer, ALSA_DEFAULT_DEVICE_NAME);
} else {
decodeDeviceID(deviceID, &card, &device, &subdevice, isMidi);
getDeviceString(buffer, bufferSize, card, device, subdevice, usePlugHw, isMidi);
}
}
static int hasGottenALSAVersion = FALSE;
#define ALSAVersionString_LENGTH 200
static char ALSAVersionString[ALSAVersionString_LENGTH];
void getALSAVersion(char* buffer, int len) {
if (!hasGottenALSAVersion) {
// get alsa version from proc interface
FILE* file;
int curr, len, totalLen, inVersionString;
file = fopen(ALSA_VERSION_PROC_FILE, "r");
ALSAVersionString[0] = 0;
if (file) {
if (NULL != fgets(ALSAVersionString, ALSAVersionString_LENGTH, file)) {
// parse for version number
totalLen = strlen(ALSAVersionString);
inVersionString = FALSE;
len = 0;
curr = 0;
while (curr < totalLen) {
if (!inVersionString) {
// is this char the beginning of a version string ?
if (ALSAVersionString[curr] >= '0'
&& ALSAVersionString[curr] <= '9') {
inVersionString = TRUE;
}
}
if (inVersionString) {
// the version string ends with white space
if (ALSAVersionString[curr] <= 32) {
break;
}
if (curr != len) {
// copy this char to the beginning of the string
ALSAVersionString[len] = ALSAVersionString[curr];
}
len++;
}
curr++;
}
// remove trailing dots
while ((len > 0) && (ALSAVersionString[len - 1] == '.')) {
len--;
}
// null terminate
ALSAVersionString[len] = 0;
}
fclose(file);
hasGottenALSAVersion = TRUE;
}
}
strncpy(buffer, ALSAVersionString, len);
}
/* end */

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <alsa/asoundlib.h>
#include "Utilities.h"
#ifndef PLATFORM_API_LINUXOS_ALSA_COMMONUTILS_H_INCLUDED
#define PLATFORM_API_LINUXOS_ALSA_COMMONUTILS_H_INCLUDED
#define ALSA_VERSION_PROC_FILE "/proc/asound/version"
#define ALSA_HARDWARE "hw"
#define ALSA_HARDWARE_CARD ALSA_HARDWARE":%d"
#define ALSA_HARDWARE_DEVICE ALSA_HARDWARE_CARD",%d"
#define ALSA_HARDWARE_SUBDEVICE ALSA_HARDWARE_DEVICE",%d"
#define ALSA_PLUGHARDWARE "plughw"
#define ALSA_DEFAULT_DEVICE_NAME "default"
#define ALSA_DEFAULT_DEVICE_ID (0)
#define ALSA_PCM (0)
#define ALSA_RAWMIDI (1)
// for use in info objects
#define ALSA_VENDOR "ALSA (http://www.alsa-project.org)"
// Environment variable for inclusion of subdevices in device listing.
// If this variable is unset or "no", then subdevices are ignored, and
// it's ALSA's choice which one to use (enables hardware mixing)
#define ENV_ENUMERATE_PCM_SUBDEVICES "ALSA_ENUMERATE_PCM_SUBDEVICES"
// if defined, subdevices are listed.
//#undef ALSA_MIDI_ENUMERATE_SUBDEVICES
#define ALSA_MIDI_ENUMERATE_SUBDEVICES
// must be called before any ALSA calls
void initAlsaSupport();
/* if true (non-zero), ALSA sub devices should be listed as separate devices
*/
int needEnumerateSubdevices(int isMidi);
/*
* deviceID contains packed card, device and subdevice numbers
* each number takes 10 bits
* "default" device has id == ALSA_DEFAULT_DEVICE_ID
*/
UINT32 encodeDeviceID(int card, int device, int subdevice);
void decodeDeviceID(UINT32 deviceID, int* card, int* device, int* subdevice,
int isMidi);
void getDeviceStringFromDeviceID(char* buffer, size_t bufferSize,
UINT32 deviceID, int usePlugHw, int isMidi);
void getALSAVersion(char* buffer, int len);
#endif // PLATFORM_API_LINUXOS_ALSA_COMMONUTILS_H_INCLUDED

View file

@ -0,0 +1,354 @@
/*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#define USE_ERROR
#define USE_TRACE
#if USE_PLATFORM_MIDI_IN == TRUE
#include <alsa/asoundlib.h>
#include "PlatformMidi.h"
#include "PLATFORM_API_LinuxOS_ALSA_MidiUtils.h"
#if defined(i586)
#include <sys/utsname.h>
#endif
/*
* Helper methods
*/
static inline UINT32 packMessage(int status, int data1, int data2) {
return ((status & 0xFF) | ((data1 & 0xFF) << 8) | ((data2 & 0xFF) << 16));
}
static void setShortMessage(MidiMessage* message,
int status, int data1, int data2) {
message->type = SHORT_MESSAGE;
message->data.s.packedMsg = packMessage(status, data1, data2);
}
static void setRealtimeMessage(MidiMessage* message, int status) {
setShortMessage(message, status, 0, 0);
}
static void set14bitMessage(MidiMessage* message, int status, int value) {
TRACE3("14bit value: %d, lsb: %d, msb: %d\n", value, value & 0x7F, (value >> 7) & 0x7F);
value &= 0x3FFF;
TRACE3("14bit value (2): %d, lsb: %d, msb: %d\n", value, value & 0x7F, (value >> 7) & 0x7F);
setShortMessage(message, status,
value & 0x7F,
(value >> 7) & 0x7F);
}
/*
* implementation of the platform-dependent
* MIDI in functions declared in PlatformMidi.h
*/
char* MIDI_IN_GetErrorStr(INT32 err) {
return (char*) getErrorStr(err);
}
INT32 MIDI_IN_GetNumDevices() {
/* Workaround for 6842956: 32bit app on 64bit linux
* gets assertion failure trying to open midiIn ports.
* Until the issue is fixed in ALSA
* (https://bugtrack.alsa-project.org/alsa-bug/view.php?id=4807)
* report no midi in devices in the configuration.
*/
#if defined(i586)
static int jre32onlinux64 = -1;
if (jre32onlinux64 < 0) {
jre32onlinux64 = 0;
/* The workaround may be disabled setting "JAVASOUND_ENABLE_MIDIIN"
* environment variable.
*/
if (getenv("JAVASOUND_ENABLE_MIDIIN") == NULL) {
struct utsname u;
jre32onlinux64 = 0;
if (uname(&u) == 0) {
if (strstr(u.machine, "64") != NULL) {
TRACE0("jre32 on linux64 detected - report no midiIn devices\n");
jre32onlinux64 = 1;
}
}
}
}
if (jre32onlinux64) {
return 0;
}
#endif
TRACE0("MIDI_IN_GetNumDevices()\n");
return getMidiDeviceCount(SND_RAWMIDI_STREAM_INPUT);
}
INT32 MIDI_IN_GetDeviceName(INT32 deviceIndex, char *name, UINT32 nameLength) {
int ret = getMidiDeviceName(SND_RAWMIDI_STREAM_INPUT, deviceIndex,
name, nameLength);
return ret;
}
INT32 MIDI_IN_GetDeviceVendor(INT32 deviceIndex, char *name, UINT32 nameLength) {
int ret = getMidiDeviceVendor(deviceIndex, name, nameLength);
return ret;
}
INT32 MIDI_IN_GetDeviceDescription(INT32 deviceIndex, char *name, UINT32 nameLength) {
int ret = getMidiDeviceDescription(SND_RAWMIDI_STREAM_INPUT, deviceIndex,
name, nameLength);
return ret;
}
INT32 MIDI_IN_GetDeviceVersion(INT32 deviceIndex, char *name, UINT32 nameLength) {
int ret = getMidiDeviceVersion(deviceIndex, name, nameLength);
return ret;
}
/*************************************************************************/
INT32 MIDI_IN_OpenDevice(INT32 deviceIndex, MidiDeviceHandle** handle) {
INT32 ret;
TRACE0("> MIDI_IN_OpenDevice\n");
ret = openMidiDevice(SND_RAWMIDI_STREAM_INPUT, deviceIndex, handle);
TRACE1("< MIDI_IN_OpenDevice: returning %d\n", (int) ret);
return ret;
}
INT32 MIDI_IN_CloseDevice(MidiDeviceHandle* handle) {
INT32 ret;
TRACE0("> MIDI_IN_CloseDevice\n");
ret = closeMidiDevice(handle);
TRACE1("< MIDI_IN_CloseDevice: returning %d\n", (int) ret);
return ret;
}
INT32 MIDI_IN_StartDevice(MidiDeviceHandle* handle) {
TRACE0("MIDI_IN_StartDevice\n");
return MIDI_SUCCESS;
}
INT32 MIDI_IN_StopDevice(MidiDeviceHandle* handle) {
TRACE0("MIDI_IN_StopDevice\n");
return MIDI_SUCCESS;
}
INT64 MIDI_IN_GetTimeStamp(MidiDeviceHandle* handle) {
return getMidiTimestamp(handle);
}
/* read the next message from the queue */
MidiMessage* MIDI_IN_GetMessage(MidiDeviceHandle* handle) {
snd_seq_event_t alsa_message;
MidiMessage* jdk_message;
int err;
char buffer[1];
int status;
TRACE0("> MIDI_IN_GetMessage\n");
if (!handle) {
ERROR0("< ERROR: MIDI_IN_GetMessage(): handle is NULL\n");
return NULL;
}
if (!handle->deviceHandle) {
ERROR0("< ERROR: MIDI_IN_GetMessage(): native handle is NULL\n");
return NULL;
}
if (!handle->platformData) {
ERROR0("< ERROR: MIDI_IN_GetMessage(): platformData is NULL\n");
return NULL;
}
/* For MIDI In, the device is left in non blocking mode. So if there is
no data from the device, snd_rawmidi_read() returns with -11 (EAGAIN).
This results in jumping back to the Java layer. */
while (TRUE) {
TRACE0("before snd_rawmidi_read()\n");
err = snd_rawmidi_read((snd_rawmidi_t*) handle->deviceHandle, buffer, 1);
TRACE0("after snd_rawmidi_read()\n");
if (err != 1) {
ERROR2("< ERROR: MIDI_IN_GetMessage(): snd_rawmidi_read() returned %d : %s\n", err, snd_strerror(err));
return NULL;
}
// printf("received byte: %d\n", buffer[0]);
err = snd_midi_event_encode_byte((snd_midi_event_t*) handle->platformData,
(int) buffer[0],
&alsa_message);
if (err == 1) {
break;
} else if (err < 0) {
ERROR1("< ERROR: MIDI_IN_GetMessage(): snd_midi_event_encode_byte() returned %d\n", err);
return NULL;
}
}
jdk_message = (MidiMessage*) calloc(1, sizeof(MidiMessage));
if (!jdk_message) {
ERROR0("< ERROR: MIDI_IN_GetMessage(): out of memory\n");
return NULL;
}
// TODO: tra
switch (alsa_message.type) {
case SND_SEQ_EVENT_NOTEON:
case SND_SEQ_EVENT_NOTEOFF:
case SND_SEQ_EVENT_KEYPRESS:
status = (alsa_message.type == SND_SEQ_EVENT_KEYPRESS) ? 0xA0 :
(alsa_message.type == SND_SEQ_EVENT_NOTEON) ? 0x90 : 0x80;
status |= alsa_message.data.note.channel;
setShortMessage(jdk_message, status,
alsa_message.data.note.note,
alsa_message.data.note.velocity);
break;
case SND_SEQ_EVENT_CONTROLLER:
status = 0xB0 | alsa_message.data.control.channel;
setShortMessage(jdk_message, status,
alsa_message.data.control.param,
alsa_message.data.control.value);
break;
case SND_SEQ_EVENT_PGMCHANGE:
case SND_SEQ_EVENT_CHANPRESS:
status = (alsa_message.type == SND_SEQ_EVENT_PGMCHANGE) ? 0xC0 : 0xD0;
status |= alsa_message.data.control.channel;
setShortMessage(jdk_message, status,
alsa_message.data.control.value, 0);
break;
case SND_SEQ_EVENT_PITCHBEND:
status = 0xE0 | alsa_message.data.control.channel;
// $$mp 2003-09-23:
// possible hack to work around a bug in ALSA. Necessary for
// ALSA 0.9.2. May be fixed in newer versions of ALSA.
// alsa_message.data.control.value ^= 0x2000;
// TRACE1("pitchbend value: %d\n", alsa_message.data.control.value);
set14bitMessage(jdk_message, status,
alsa_message.data.control.value);
break;
/* System exclusive messages */
case SND_SEQ_EVENT_SYSEX:
jdk_message->type = LONG_MESSAGE;
jdk_message->data.l.size = alsa_message.data.ext.len;
jdk_message->data.l.data = malloc(alsa_message.data.ext.len);
if (jdk_message->data.l.data == NULL) {
ERROR0("< ERROR: MIDI_IN_GetMessage(): out of memory\n");
free(jdk_message);
jdk_message = NULL;
} else {
memcpy(jdk_message->data.l.data, alsa_message.data.ext.ptr, alsa_message.data.ext.len);
}
break;
/* System common messages */
case SND_SEQ_EVENT_QFRAME:
setShortMessage(jdk_message, 0xF1,
alsa_message.data.control.value & 0x7F, 0);
break;
case SND_SEQ_EVENT_SONGPOS:
set14bitMessage(jdk_message, 0xF2,
alsa_message.data.control.value);
break;
case SND_SEQ_EVENT_SONGSEL:
setShortMessage(jdk_message, 0xF3,
alsa_message.data.control.value & 0x7F, 0);
break;
case SND_SEQ_EVENT_TUNE_REQUEST:
setRealtimeMessage(jdk_message, 0xF6);
break;
/* System realtime messages */
case SND_SEQ_EVENT_CLOCK:
setRealtimeMessage(jdk_message, 0xF8);
break;
case SND_SEQ_EVENT_START:
setRealtimeMessage(jdk_message, 0xFA);
break;
case SND_SEQ_EVENT_CONTINUE:
setRealtimeMessage(jdk_message, 0xFB);
break;
case SND_SEQ_EVENT_STOP:
setRealtimeMessage(jdk_message, 0xFC);
break;
case SND_SEQ_EVENT_SENSING:
setRealtimeMessage(jdk_message, 0xFE);
break;
case SND_SEQ_EVENT_RESET:
setRealtimeMessage(jdk_message, 0xFF);
break;
default:
ERROR0("< ERROR: MIDI_IN_GetMessage(): unhandled ALSA MIDI message type\n");
free(jdk_message);
jdk_message = NULL;
}
// set timestamp
if (jdk_message != NULL) {
jdk_message->timestamp = getMidiTimestamp(handle);
}
TRACE1("< MIDI_IN_GetMessage: returning %p\n", jdk_message);
return jdk_message;
}
void MIDI_IN_ReleaseMessage(MidiDeviceHandle* handle, MidiMessage* msg) {
if (!msg) {
ERROR0("< ERROR: MIDI_IN_ReleaseMessage(): message is NULL\n");
return;
}
if (msg->type == LONG_MESSAGE && msg->data.l.data) {
free(msg->data.l.data);
}
free(msg);
}
#endif /* USE_PLATFORM_MIDI_IN */

View file

@ -0,0 +1,179 @@
/*
* Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#define USE_ERROR
#define USE_TRACE
#if USE_PLATFORM_MIDI_OUT == TRUE
#include <alsa/asoundlib.h>
#include "PlatformMidi.h"
#include "PLATFORM_API_LinuxOS_ALSA_MidiUtils.h"
static int CHANNEL_MESSAGE_LENGTH[] = {
-1, -1, -1, -1, -1, -1, -1, -1, 3, 3, 3, 3, 2, 2, 3 };
/* 8x 9x Ax Bx Cx Dx Ex */
static int SYSTEM_MESSAGE_LENGTH[] = {
-1, 2, 3, 2, -1, -1, 1, 1, 1, -1, 1, 1, 1, -1, 1, 1 };
/* F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF */
// the returned length includes the status byte.
// for illegal messages, -1 is returned.
static int getShortMessageLength(int status) {
int dataLength = 0;
if (status < 0xF0) { // channel voice message
dataLength = CHANNEL_MESSAGE_LENGTH[(status >> 4) & 0xF];
} else {
dataLength = SYSTEM_MESSAGE_LENGTH[status & 0xF];
}
return dataLength;
}
/*
* implementation of the platform-dependent
* MIDI out functions declared in PlatformMidi.h
*/
char* MIDI_OUT_GetErrorStr(INT32 err) {
return (char*) getErrorStr(err);
}
INT32 MIDI_OUT_GetNumDevices() {
TRACE0("MIDI_OUT_GetNumDevices()\n");
return getMidiDeviceCount(SND_RAWMIDI_STREAM_OUTPUT);
}
INT32 MIDI_OUT_GetDeviceName(INT32 deviceIndex, char *name, UINT32 nameLength) {
TRACE0("MIDI_OUT_GetDeviceName()\n");
return getMidiDeviceName(SND_RAWMIDI_STREAM_OUTPUT, deviceIndex,
name, nameLength);
}
INT32 MIDI_OUT_GetDeviceVendor(INT32 deviceIndex, char *name, UINT32 nameLength) {
TRACE0("MIDI_OUT_GetDeviceVendor()\n");
return getMidiDeviceVendor(deviceIndex, name, nameLength);
}
INT32 MIDI_OUT_GetDeviceDescription(INT32 deviceIndex, char *name, UINT32 nameLength) {
TRACE0("MIDI_OUT_GetDeviceDescription()\n");
return getMidiDeviceDescription(SND_RAWMIDI_STREAM_OUTPUT, deviceIndex,
name, nameLength);
}
INT32 MIDI_OUT_GetDeviceVersion(INT32 deviceIndex, char *name, UINT32 nameLength) {
TRACE0("MIDI_OUT_GetDeviceVersion()\n");
return getMidiDeviceVersion(deviceIndex, name, nameLength);
}
/* *************************** MidiOutDevice implementation *************** */
INT32 MIDI_OUT_OpenDevice(INT32 deviceIndex, MidiDeviceHandle** handle) {
TRACE1("MIDI_OUT_OpenDevice(): deviceIndex: %d\n", (int) deviceIndex);
return openMidiDevice(SND_RAWMIDI_STREAM_OUTPUT, deviceIndex, handle);
}
INT32 MIDI_OUT_CloseDevice(MidiDeviceHandle* handle) {
TRACE0("MIDI_OUT_CloseDevice()\n");
return closeMidiDevice(handle);
}
INT64 MIDI_OUT_GetTimeStamp(MidiDeviceHandle* handle) {
return getMidiTimestamp(handle);
}
INT32 MIDI_OUT_SendShortMessage(MidiDeviceHandle* handle, UINT32 packedMsg,
UINT32 timestamp) {
int err;
int status;
int data1;
int data2;
char buffer[3];
TRACE2("> MIDI_OUT_SendShortMessage() %x, time: %u\n", packedMsg, (unsigned int) timestamp);
if (!handle) {
ERROR0("< ERROR: MIDI_OUT_SendShortMessage(): handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
if (!handle->deviceHandle) {
ERROR0("< ERROR: MIDI_OUT_SendLongMessage(): native handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
status = (packedMsg & 0xFF);
buffer[0] = (char) status;
buffer[1] = (char) ((packedMsg >> 8) & 0xFF);
buffer[2] = (char) ((packedMsg >> 16) & 0xFF);
TRACE4("status: %d, data1: %d, data2: %d, length: %d\n", (int) buffer[0], (int) buffer[1], (int) buffer[2], getShortMessageLength(status));
err = snd_rawmidi_write((snd_rawmidi_t*) handle->deviceHandle, buffer, getShortMessageLength(status));
if (err < 0) {
ERROR1(" ERROR: MIDI_OUT_SendShortMessage(): snd_rawmidi_write() returned %d\n", err);
}
TRACE0("< MIDI_OUT_SendShortMessage()\n");
return err;
}
INT32 MIDI_OUT_SendLongMessage(MidiDeviceHandle* handle, UBYTE* data,
UINT32 size, UINT32 timestamp) {
int err;
TRACE2("> MIDI_OUT_SendLongMessage() size %u, time: %u\n", (unsigned int) size, (unsigned int) timestamp);
if (!handle) {
ERROR0("< ERROR: MIDI_OUT_SendLongMessage(): handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
if (!handle->deviceHandle) {
ERROR0("< ERROR: MIDI_OUT_SendLongMessage(): native handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
if (!data) {
ERROR0("< ERROR: MIDI_OUT_SendLongMessage(): data is NULL\n");
return MIDI_INVALID_HANDLE;
}
err = snd_rawmidi_write((snd_rawmidi_t*) handle->deviceHandle,
data, size);
if (err < 0) {
ERROR1(" ERROR: MIDI_OUT_SendLongMessage(): snd_rawmidi_write() returned %d\n", err);
}
TRACE0("< MIDI_OUT_SendLongMessage()\n");
return err;
}
#endif /* USE_PLATFORM_MIDI_OUT */

View file

@ -0,0 +1,481 @@
/*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#define USE_ERROR
#define USE_TRACE
#include "PLATFORM_API_LinuxOS_ALSA_MidiUtils.h"
#include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h"
#include <string.h>
#include <sys/time.h>
static INT64 getTimeInMicroseconds() {
struct timeval tv;
gettimeofday(&tv, NULL);
return (tv.tv_sec * 1000000UL) + tv.tv_usec;
}
const char* getErrorStr(INT32 err) {
return snd_strerror((int) err);
}
// callback for iteration through devices
// returns TRUE if iteration should continue
typedef int (*DeviceIteratorPtr)(UINT32 deviceID,
snd_rawmidi_info_t* rawmidi_info,
snd_ctl_card_info_t* cardinfo,
void *userData);
// for each ALSA device, call iterator. userData is passed to the iterator
// returns total number of iterations
static int iterateRawmidiDevices(snd_rawmidi_stream_t direction,
DeviceIteratorPtr iterator,
void* userData) {
int count = 0;
int subdeviceCount;
int card, dev, subDev;
char devname[16];
int err;
snd_ctl_t *handle;
snd_rawmidi_t *rawmidi;
snd_rawmidi_info_t *rawmidi_info;
snd_ctl_card_info_t *card_info, *defcardinfo = NULL;
UINT32 deviceID;
int doContinue = TRUE;
snd_rawmidi_info_malloc(&rawmidi_info);
snd_ctl_card_info_malloc(&card_info);
// 1st try "default" device
if (direction == SND_RAWMIDI_STREAM_INPUT) {
err = snd_rawmidi_open(&rawmidi, NULL, ALSA_DEFAULT_DEVICE_NAME,
SND_RAWMIDI_NONBLOCK);
} else if (direction == SND_RAWMIDI_STREAM_OUTPUT) {
err = snd_rawmidi_open(NULL, &rawmidi, ALSA_DEFAULT_DEVICE_NAME,
SND_RAWMIDI_NONBLOCK);
} else {
ERROR0("ERROR: iterateRawmidiDevices(): direction is neither"
" SND_RAWMIDI_STREAM_INPUT nor SND_RAWMIDI_STREAM_OUTPUT\n");
err = MIDI_INVALID_ARGUMENT;
}
if (err < 0) {
ERROR1("ERROR: snd_rawmidi_open (\"default\"): %s\n",
snd_strerror(err));
} else {
err = snd_rawmidi_info(rawmidi, rawmidi_info);
snd_rawmidi_close(rawmidi);
if (err < 0) {
ERROR1("ERROR: snd_rawmidi_info (\"default\"): %s\n",
snd_strerror(err));
} else {
// try to get card info
card = snd_rawmidi_info_get_card(rawmidi_info);
if (card >= 0) {
snprintf(devname, sizeof(devname), ALSA_HARDWARE_CARD, card);
if (snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK) >= 0) {
if (snd_ctl_card_info(handle, card_info) >= 0) {
defcardinfo = card_info;
}
snd_ctl_close(handle);
}
}
// call callback function for the device
if (iterator != NULL) {
doContinue = (*iterator)(ALSA_DEFAULT_DEVICE_ID, rawmidi_info,
defcardinfo, userData);
}
count++;
}
}
// iterate cards
card = -1;
TRACE0("testing for cards...\n");
if (snd_card_next(&card) >= 0) {
TRACE1("Found card %d\n", card);
while (doContinue && (card >= 0)) {
snprintf(devname, sizeof(devname), ALSA_HARDWARE_CARD, card);
TRACE1("Opening control for alsa rawmidi device \"%s\"...\n", devname);
err = snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK);
if (err < 0) {
ERROR2("ERROR: snd_ctl_open, card=%d: %s\n", card, snd_strerror(err));
} else {
TRACE0("snd_ctl_open() SUCCESS\n");
err = snd_ctl_card_info(handle, card_info);
if (err < 0) {
ERROR2("ERROR: snd_ctl_card_info, card=%d: %s\n", card, snd_strerror(err));
} else {
TRACE0("snd_ctl_card_info() SUCCESS\n");
dev = -1;
while (doContinue) {
if (snd_ctl_rawmidi_next_device(handle, &dev) < 0) {
ERROR0("snd_ctl_rawmidi_next_device\n");
}
TRACE0("snd_ctl_rawmidi_next_device() SUCCESS\n");
if (dev < 0) {
break;
}
snd_rawmidi_info_set_device(rawmidi_info, dev);
snd_rawmidi_info_set_subdevice(rawmidi_info, 0);
snd_rawmidi_info_set_stream(rawmidi_info, direction);
err = snd_ctl_rawmidi_info(handle, rawmidi_info);
TRACE0("after snd_ctl_rawmidi_info()\n");
if (err < 0) {
if (err != -ENOENT) {
ERROR2("ERROR: snd_ctl_rawmidi_info, card=%d: %s", card, snd_strerror(err));
}
} else {
TRACE0("snd_ctl_rawmidi_info() SUCCESS\n");
subdeviceCount = needEnumerateSubdevices(ALSA_RAWMIDI)
? snd_rawmidi_info_get_subdevices_count(rawmidi_info)
: 1;
if (iterator!=NULL) {
for (subDev = 0; subDev < subdeviceCount; subDev++) {
TRACE3(" Iterating %d,%d,%d\n", card, dev, subDev);
deviceID = encodeDeviceID(card, dev, subDev);
doContinue = (*iterator)(deviceID, rawmidi_info,
card_info, userData);
count++;
TRACE0("returned from iterator\n");
if (!doContinue) {
break;
}
}
} else {
count += subdeviceCount;
}
}
} // of while(doContinue)
}
snd_ctl_close(handle);
}
if (snd_card_next(&card) < 0) {
break;
}
}
} else {
ERROR0("No cards found!\n");
}
snd_ctl_card_info_free(card_info);
snd_rawmidi_info_free(rawmidi_info);
return count;
}
int getMidiDeviceCount(snd_rawmidi_stream_t direction) {
int deviceCount;
TRACE0("> getMidiDeviceCount()\n");
initAlsaSupport();
deviceCount = iterateRawmidiDevices(direction, NULL, NULL);
TRACE0("< getMidiDeviceCount()\n");
return deviceCount;
}
/*
userData is assumed to be a pointer to ALSA_MIDIDeviceDescription.
ALSA_MIDIDeviceDescription->index has to be set to the index of the device
we want to get information of before this method is called the first time via
iterateRawmidiDevices(). On each call of this method,
ALSA_MIDIDeviceDescription->index is decremented. If it is equal to zero,
we have reached the desired device, so action is taken.
So after successful completion of iterateRawmidiDevices(),
ALSA_MIDIDeviceDescription->index is zero. If it isn't, this is an
indication of an error.
*/
static int deviceInfoIterator(UINT32 deviceID, snd_rawmidi_info_t *rawmidi_info,
snd_ctl_card_info_t *cardinfo, void *userData) {
char buffer[300];
ALSA_MIDIDeviceDescription* desc = (ALSA_MIDIDeviceDescription*)userData;
#ifdef ALSA_MIDI_USE_PLUGHW
int usePlugHw = 1;
#else
int usePlugHw = 0;
#endif
TRACE0("deviceInfoIterator\n");
initAlsaSupport();
if (desc->index == 0) {
// we found the device with correct index
desc->deviceID = deviceID;
buffer[0]=' '; buffer[1]='[';
// buffer[300] is enough to store the actual device string w/o overrun
getDeviceStringFromDeviceID(&buffer[2], sizeof(buffer) - 2, deviceID, usePlugHw, ALSA_RAWMIDI);
strncat(buffer, "]", sizeof(buffer) - strlen(buffer) - 1);
strncpy(desc->name,
(cardinfo != NULL)
? snd_ctl_card_info_get_id(cardinfo)
: snd_rawmidi_info_get_id(rawmidi_info),
desc->strLen - strlen(buffer));
strncat(desc->name, buffer, desc->strLen - strlen(desc->name));
desc->description[0] = 0;
if (cardinfo != NULL) {
strncpy(desc->description, snd_ctl_card_info_get_name(cardinfo),
desc->strLen);
strncat(desc->description, ", ",
desc->strLen - strlen(desc->description));
}
strncat(desc->description, snd_rawmidi_info_get_id(rawmidi_info),
desc->strLen - strlen(desc->description));
strncat(desc->description, ", ", desc->strLen - strlen(desc->description));
strncat(desc->description, snd_rawmidi_info_get_name(rawmidi_info),
desc->strLen - strlen(desc->description));
TRACE2("Returning %s, %s\n", desc->name, desc->description);
return FALSE; // do not continue iteration
}
desc->index--;
return TRUE;
}
static int getMIDIDeviceDescriptionByIndex(snd_rawmidi_stream_t direction,
ALSA_MIDIDeviceDescription* desc) {
initAlsaSupport();
TRACE1(" getMIDIDeviceDescriptionByIndex (index = %d)\n", desc->index);
iterateRawmidiDevices(direction, &deviceInfoIterator, desc);
return (desc->index == 0) ? MIDI_SUCCESS : MIDI_INVALID_DEVICEID;
}
int initMIDIDeviceDescription(ALSA_MIDIDeviceDescription* desc, int index) {
int ret = MIDI_SUCCESS;
desc->index = index;
desc->strLen = 200;
desc->name = (char*) calloc(desc->strLen + 1, 1);
desc->description = (char*) calloc(desc->strLen + 1, 1);
if (! desc->name ||
! desc->description) {
ret = MIDI_OUT_OF_MEMORY;
}
return ret;
}
void freeMIDIDeviceDescription(ALSA_MIDIDeviceDescription* desc) {
if (desc->name) {
free(desc->name);
}
if (desc->description) {
free(desc->description);
}
}
int getMidiDeviceName(snd_rawmidi_stream_t direction, int index, char *name,
UINT32 nameLength) {
ALSA_MIDIDeviceDescription desc;
int ret;
TRACE1("getMidiDeviceName: nameLength: %d\n", (int) nameLength);
ret = initMIDIDeviceDescription(&desc, index);
if (ret == MIDI_SUCCESS) {
TRACE0("getMidiDeviceName: initMIDIDeviceDescription() SUCCESS\n");
ret = getMIDIDeviceDescriptionByIndex(direction, &desc);
if (ret == MIDI_SUCCESS) {
TRACE1("getMidiDeviceName: desc.name: %s\n", desc.name);
strncpy(name, desc.name, nameLength - 1);
name[nameLength - 1] = 0;
}
}
freeMIDIDeviceDescription(&desc);
return ret;
}
int getMidiDeviceVendor(int index, char *name, UINT32 nameLength) {
strncpy(name, ALSA_VENDOR, nameLength - 1);
name[nameLength - 1] = 0;
return MIDI_SUCCESS;
}
int getMidiDeviceDescription(snd_rawmidi_stream_t direction,
int index, char *name, UINT32 nameLength) {
ALSA_MIDIDeviceDescription desc;
int ret;
ret = initMIDIDeviceDescription(&desc, index);
if (ret == MIDI_SUCCESS) {
ret = getMIDIDeviceDescriptionByIndex(direction, &desc);
if (ret == MIDI_SUCCESS) {
strncpy(name, desc.description, nameLength - 1);
name[nameLength - 1] = 0;
}
}
freeMIDIDeviceDescription(&desc);
return ret;
}
int getMidiDeviceVersion(int index, char *name, UINT32 nameLength) {
getALSAVersion(name, nameLength);
return MIDI_SUCCESS;
}
static int getMidiDeviceID(snd_rawmidi_stream_t direction, int index,
UINT32* deviceID) {
ALSA_MIDIDeviceDescription desc;
int ret;
ret = initMIDIDeviceDescription(&desc, index);
if (ret == MIDI_SUCCESS) {
ret = getMIDIDeviceDescriptionByIndex(direction, &desc);
if (ret == MIDI_SUCCESS) {
// TRACE1("getMidiDeviceName: desc.name: %s\n", desc.name);
*deviceID = desc.deviceID;
}
}
freeMIDIDeviceDescription(&desc);
return ret;
}
/*
direction has to be either SND_RAWMIDI_STREAM_INPUT or
SND_RAWMIDI_STREAM_OUTPUT.
Returns 0 on success. Otherwise, MIDI_OUT_OF_MEMORY, MIDI_INVALID_ARGUMENT
or a negative ALSA error code is returned.
*/
INT32 openMidiDevice(snd_rawmidi_stream_t direction, INT32 deviceIndex,
MidiDeviceHandle** handle) {
snd_rawmidi_t* native_handle;
snd_midi_event_t* event_parser = NULL;
int err;
UINT32 deviceID = 0;
char devicename[100];
#ifdef ALSA_MIDI_USE_PLUGHW
int usePlugHw = 1;
#else
int usePlugHw = 0;
#endif
TRACE0("> openMidiDevice()\n");
(*handle) = (MidiDeviceHandle*) calloc(1, sizeof(MidiDeviceHandle));
if (!(*handle)) {
ERROR0("ERROR: openDevice: out of memory\n");
return MIDI_OUT_OF_MEMORY;
}
// TODO: iterate to get dev ID from index
err = getMidiDeviceID(direction, deviceIndex, &deviceID);
TRACE1(" openMidiDevice(): deviceID: %d\n", (int) deviceID);
getDeviceStringFromDeviceID(devicename, sizeof(devicename), deviceID,
usePlugHw, ALSA_RAWMIDI);
TRACE1(" openMidiDevice(): deviceString: %s\n", devicename);
// finally open the device
if (direction == SND_RAWMIDI_STREAM_INPUT) {
err = snd_rawmidi_open(&native_handle, NULL, devicename,
SND_RAWMIDI_NONBLOCK);
} else if (direction == SND_RAWMIDI_STREAM_OUTPUT) {
err = snd_rawmidi_open(NULL, &native_handle, devicename,
SND_RAWMIDI_NONBLOCK);
} else {
ERROR0(" ERROR: openMidiDevice(): direction is neither SND_RAWMIDI_STREAM_INPUT nor SND_RAWMIDI_STREAM_OUTPUT\n");
err = MIDI_INVALID_ARGUMENT;
}
if (err < 0) {
ERROR1("< ERROR: openMidiDevice(): snd_rawmidi_open() returned %d\n", err);
free(*handle);
(*handle) = NULL;
return err;
}
/* We opened with non-blocking behaviour to not get hung if the device
is used by a different process. Writing, however, should
be blocking. So we change it here. */
if (direction == SND_RAWMIDI_STREAM_OUTPUT) {
err = snd_rawmidi_nonblock(native_handle, 0);
if (err < 0) {
ERROR1(" ERROR: openMidiDevice(): snd_rawmidi_nonblock() returned %d\n", err);
snd_rawmidi_close(native_handle);
free(*handle);
(*handle) = NULL;
return err;
}
}
if (direction == SND_RAWMIDI_STREAM_INPUT) {
err = snd_midi_event_new(EVENT_PARSER_BUFSIZE, &event_parser);
if (err < 0) {
ERROR1(" ERROR: openMidiDevice(): snd_midi_event_new() returned %d\n", err);
snd_rawmidi_close(native_handle);
free(*handle);
(*handle) = NULL;
return err;
}
}
(*handle)->deviceHandle = (void*) native_handle;
(*handle)->startTime = getTimeInMicroseconds();
(*handle)->platformData = event_parser;
TRACE0("< openMidiDevice(): succeeded\n");
return err;
}
INT32 closeMidiDevice(MidiDeviceHandle* handle) {
int err;
TRACE0("> closeMidiDevice()\n");
if (!handle) {
ERROR0("< ERROR: closeMidiDevice(): handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
if (!handle->deviceHandle) {
ERROR0("< ERROR: closeMidiDevice(): native handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
err = snd_rawmidi_close((snd_rawmidi_t*) handle->deviceHandle);
TRACE1(" snd_rawmidi_close() returns %d\n", err);
if (handle->platformData) {
snd_midi_event_free((snd_midi_event_t*) handle->platformData);
}
free(handle);
TRACE0("< closeMidiDevice: succeeded\n");
return err;
}
INT64 getMidiTimestamp(MidiDeviceHandle* handle) {
if (!handle) {
ERROR0("< ERROR: closeMidiDevice(): handle is NULL\n");
return MIDI_INVALID_HANDLE;
}
return getTimeInMicroseconds() - handle->startTime;
}
/* end */

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2003, 2007, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <alsa/asoundlib.h>
#include "Utilities.h"
#include "PlatformMidi.h"
#ifndef PLATFORM_API_LINUXOS_ALSA_MIDIUTILS_H_INCLUDED
#define PLATFORM_API_LINUXOS_ALSA_MIDIUTILS_H_INCLUDED
#define EVENT_PARSER_BUFSIZE (2048)
// if this is defined, use plughw: devices
//#define ALSA_MIDI_USE_PLUGHW
#undef ALSA_MIDI_USE_PLUGHW
typedef struct tag_ALSA_MIDIDeviceDescription {
int index; // in
int strLen; // in
INT32 deviceID; // out
char* name; // out
char* description; // out
} ALSA_MIDIDeviceDescription;
const char* getErrorStr(INT32 err);
/* Returns the number of devices. */
/* direction is either SND_RAWMIDI_STREAM_OUTPUT or
SND_RAWMIDI_STREAM_INPUT. */
int getMidiDeviceCount(snd_rawmidi_stream_t direction);
/* Returns MIDI_SUCCESS or MIDI_INVALID_DEVICEID */
/* direction is either SND_RAWMIDI_STREAM_OUTPUT or
SND_RAWMIDI_STREAM_INPUT. */
int getMidiDeviceName(snd_rawmidi_stream_t direction, int index,
char *name, UINT32 nameLength);
/* Returns MIDI_SUCCESS or MIDI_INVALID_DEVICEID */
int getMidiDeviceVendor(int index, char *name, UINT32 nameLength);
/* Returns MIDI_SUCCESS or MIDI_INVALID_DEVICEID */
/* direction is either SND_RAWMIDI_STREAM_OUTPUT or
SND_RAWMIDI_STREAM_INPUT. */
int getMidiDeviceDescription(snd_rawmidi_stream_t direction, int index,
char *name, UINT32 nameLength);
/* Returns MIDI_SUCCESS or MIDI_INVALID_DEVICEID */
int getMidiDeviceVersion(int index, char *name, UINT32 nameLength);
// returns 0 on success, otherwise MIDI_OUT_OF_MEMORY or ALSA error code
/* direction is either SND_RAWMIDI_STREAM_OUTPUT or
SND_RAWMIDI_STREAM_INPUT. */
INT32 openMidiDevice(snd_rawmidi_stream_t direction, INT32 deviceIndex,
MidiDeviceHandle** handle);
// returns 0 on success, otherwise a (negative) ALSA error code
INT32 closeMidiDevice(MidiDeviceHandle* handle);
INT64 getMidiTimestamp(MidiDeviceHandle* handle);
#endif // PLATFORM_API_LINUXOS_ALSA_MIDIUTILS_H_INCLUDED

View file

@ -0,0 +1,941 @@
/*
* Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#define USE_ERROR
#define USE_TRACE
#include "PLATFORM_API_LinuxOS_ALSA_PCMUtils.h"
#include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h"
#include "DirectAudio.h"
#if USE_DAUDIO == TRUE
// GetPosition method 1: based on how many bytes are passed to the kernel driver
// + does not need much processor resources
// - not very exact, "jumps"
// GetPosition method 2: ask kernel about actual position of playback.
// - very exact
// - switch to kernel layer for each call
// GetPosition method 3: use snd_pcm_avail() call - not yet in official ALSA
// quick tests on a Pentium 200MMX showed max. 1.5% processor usage
// for playing back a CD-quality file and printing 20x per second a line
// on the console with the current time. So I guess performance is not such a
// factor here.
//#define GET_POSITION_METHOD1
#define GET_POSITION_METHOD2
// The default time for a period in microseconds.
// For very small buffers, only 2 periods are used.
#define DEFAULT_PERIOD_TIME 20000 /* 20ms */
///// implemented functions of DirectAudio.h
INT32 DAUDIO_GetDirectAudioDeviceCount() {
return (INT32) getAudioDeviceCount();
}
INT32 DAUDIO_GetDirectAudioDeviceDescription(INT32 mixerIndex, DirectAudioDeviceDescription* description) {
ALSA_AudioDeviceDescription adesc;
adesc.index = (int) mixerIndex;
adesc.strLen = DAUDIO_STRING_LENGTH;
adesc.maxSimultaneousLines = (int*) (&(description->maxSimulLines));
adesc.deviceID = &(description->deviceID);
adesc.name = description->name;
adesc.vendor = description->vendor;
adesc.description = description->description;
adesc.version = description->version;
return getAudioDeviceDescriptionByIndex(&adesc);
}
#define MAX_BIT_INDEX 6
// returns
// 6: for anything above 24-bit
// 5: for 4 bytes sample size, 24-bit
// 4: for 3 bytes sample size, 24-bit
// 3: for 3 bytes sample size, 20-bit
// 2: for 2 bytes sample size, 16-bit
// 1: for 1 byte sample size, 8-bit
// 0: for anything else
int getBitIndex(int sampleSizeInBytes, int significantBits) {
if (significantBits > 24) return 6;
if (sampleSizeInBytes == 4 && significantBits == 24) return 5;
if (sampleSizeInBytes == 3) {
if (significantBits == 24) return 4;
if (significantBits == 20) return 3;
}
if (sampleSizeInBytes == 2 && significantBits == 16) return 2;
if (sampleSizeInBytes == 1 && significantBits == 8) return 1;
return 0;
}
int getSampleSizeInBytes(int bitIndex, int sampleSizeInBytes) {
switch(bitIndex) {
case 1: return 1;
case 2: return 2;
case 3: /* fall through */
case 4: return 3;
case 5: return 4;
}
return sampleSizeInBytes;
}
int getSignificantBits(int bitIndex, int significantBits) {
switch(bitIndex) {
case 1: return 8;
case 2: return 16;
case 3: return 20;
case 4: /* fall through */
case 5: return 24;
}
return significantBits;
}
void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* creator) {
snd_pcm_t* handle;
snd_pcm_format_mask_t* formatMask;
snd_pcm_format_t format;
snd_pcm_hw_params_t* hwParams;
int handledBits[MAX_BIT_INDEX+1];
int ret;
int sampleSizeInBytes, significantBits, isSigned, isBigEndian, enc;
int origSampleSizeInBytes, origSignificantBits;
unsigned int channels, minChannels, maxChannels;
int rate, bitIndex;
for (bitIndex = 0; bitIndex <= MAX_BIT_INDEX; bitIndex++) handledBits[bitIndex] = FALSE;
if (openPCMfromDeviceID(deviceID, &handle, isSource, TRUE /*query hardware*/) < 0) {
return;
}
ret = snd_pcm_format_mask_malloc(&formatMask);
if (ret != 0) {
ERROR1("snd_pcm_format_mask_malloc returned error %d\n", ret);
} else {
ret = snd_pcm_hw_params_malloc(&hwParams);
if (ret != 0) {
ERROR1("snd_pcm_hw_params_malloc returned error %d\n", ret);
} else {
ret = snd_pcm_hw_params_any(handle, hwParams);
/* snd_pcm_hw_params_any can return a positive value on success too */
if (ret < 0) {
ERROR1("snd_pcm_hw_params_any returned error %d\n", ret);
} else {
/* for the logic following this code, set ret to 0 to indicate success */
ret = 0;
}
}
snd_pcm_hw_params_get_format_mask(hwParams, formatMask);
if (ret == 0) {
ret = snd_pcm_hw_params_get_channels_min(hwParams, &minChannels);
if (ret != 0) {
ERROR1("snd_pcm_hw_params_get_channels_min returned error %d\n", ret);
}
}
if (ret == 0) {
ret = snd_pcm_hw_params_get_channels_max(hwParams, &maxChannels);
if (ret != 0) {
ERROR1("snd_pcm_hw_params_get_channels_max returned error %d\n", ret);
}
}
// since we queried the hw: device, for many soundcards, it will only
// report the maximum number of channels (which is the only way to talk
// to the hw: device). Since we will, however, open the plughw: device
// when opening the Source/TargetDataLine, we can safely assume that
// also the channels 1..maxChannels are available.
#ifdef ALSA_PCM_USE_PLUGHW
minChannels = 1;
#endif
if (ret == 0) {
// plughw: supports any sample rate
rate = -1;
for (format = 0; format <= SND_PCM_FORMAT_LAST; format++) {
if (snd_pcm_format_mask_test(formatMask, format)) {
// format exists
if (getFormatFromAlsaFormat(format, &origSampleSizeInBytes,
&origSignificantBits,
&isSigned, &isBigEndian, &enc)) {
// now if we use plughw:, we can use any bit size below the
// natively supported ones. Some ALSA drivers only support the maximum
// bit size, so we add any sample rates below the reported one.
// E.g. this iteration reports support for 16-bit.
// getBitIndex will return 2, so it will add entries for
// 16-bit (bitIndex=2) and in the next do-while loop iteration,
// it will decrease bitIndex and will therefore add 8-bit support.
bitIndex = getBitIndex(origSampleSizeInBytes, origSignificantBits);
do {
if (bitIndex == 0
|| bitIndex == MAX_BIT_INDEX
|| !handledBits[bitIndex]) {
handledBits[bitIndex] = TRUE;
sampleSizeInBytes = getSampleSizeInBytes(bitIndex, origSampleSizeInBytes);
significantBits = getSignificantBits(bitIndex, origSignificantBits);
if (maxChannels - minChannels > MAXIMUM_LISTED_CHANNELS) {
// avoid too many channels explicitly listed
// just add -1, min, and max
DAUDIO_AddAudioFormat(creator, significantBits,
-1, -1, rate,
enc, isSigned, isBigEndian);
DAUDIO_AddAudioFormat(creator, significantBits,
sampleSizeInBytes * minChannels,
minChannels, rate,
enc, isSigned, isBigEndian);
DAUDIO_AddAudioFormat(creator, significantBits,
sampleSizeInBytes * maxChannels,
maxChannels, rate,
enc, isSigned, isBigEndian);
} else {
for (channels = minChannels; channels <= maxChannels; channels++) {
DAUDIO_AddAudioFormat(creator, significantBits,
sampleSizeInBytes * channels,
channels, rate,
enc, isSigned, isBigEndian);
}
}
}
#ifndef ALSA_PCM_USE_PLUGHW
// without plugin, do not add fake formats
break;
#endif
} while (--bitIndex > 0);
} else {
TRACE1("could not get format from alsa for format %d\n", format);
}
} else {
//TRACE1("Format %d not supported\n", format);
}
} // for loop
snd_pcm_hw_params_free(hwParams);
}
snd_pcm_format_mask_free(formatMask);
}
snd_pcm_close(handle);
}
/** Workaround for cr 7033899, 7030629:
* dmix plugin doesn't like flush (snd_pcm_drop) when the buffer is empty
* (just opened, underruned or already flushed).
* Sometimes it causes PCM falls to -EBADFD error,
* sometimes causes bufferSize change.
* To prevent unnecessary flushes AlsaPcmInfo::isRunning & isFlushed are used.
*/
/* ******* ALSA PCM INFO ******************** */
typedef struct tag_AlsaPcmInfo {
snd_pcm_t* handle;
snd_pcm_hw_params_t* hwParams;
snd_pcm_sw_params_t* swParams;
int bufferSizeInBytes;
int frameSize; // storage size in Bytes
unsigned int periods;
snd_pcm_uframes_t periodSize;
short int isRunning; // see comment above
short int isFlushed; // see comment above
#ifdef GET_POSITION_METHOD2
// to be used exclusively by getBytePosition!
snd_pcm_status_t* positionStatus;
#endif
} AlsaPcmInfo;
int setStartThresholdNoCommit(AlsaPcmInfo* info, int useThreshold) {
int ret;
int threshold;
if (useThreshold) {
// start device whenever anything is written to the buffer
threshold = 1;
} else {
// never start the device automatically
threshold = 2000000000; /* near UINT_MAX */
}
ret = snd_pcm_sw_params_set_start_threshold(info->handle, info->swParams, threshold);
if (ret < 0) {
ERROR1("Unable to set start threshold mode: %s\n", snd_strerror(ret));
return FALSE;
}
return TRUE;
}
int setStartThreshold(AlsaPcmInfo* info, int useThreshold) {
int ret = 0;
if (!setStartThresholdNoCommit(info, useThreshold)) {
ret = -1;
}
if (ret == 0) {
// commit it
ret = snd_pcm_sw_params(info->handle, info->swParams);
if (ret < 0) {
ERROR1("Unable to set sw params: %s\n", snd_strerror(ret));
}
}
return (ret == 0)?TRUE:FALSE;
}
// returns TRUE if successful
int setHWParams(AlsaPcmInfo* info,
float sampleRate,
int channels,
int bufferSizeInFrames,
snd_pcm_format_t format) {
unsigned int rrate, periodTime, periods;
int ret, dir;
snd_pcm_uframes_t alsaBufferSizeInFrames = (snd_pcm_uframes_t) bufferSizeInFrames;
/* choose all parameters */
ret = snd_pcm_hw_params_any(info->handle, info->hwParams);
if (ret < 0) {
ERROR1("Broken configuration: no configurations available: %s\n", snd_strerror(ret));
return FALSE;
}
/* set the interleaved read/write format */
ret = snd_pcm_hw_params_set_access(info->handle, info->hwParams, SND_PCM_ACCESS_RW_INTERLEAVED);
if (ret < 0) {
ERROR1("SND_PCM_ACCESS_RW_INTERLEAVED access type not available: %s\n", snd_strerror(ret));
return FALSE;
}
/* set the sample format */
ret = snd_pcm_hw_params_set_format(info->handle, info->hwParams, format);
if (ret < 0) {
ERROR1("Sample format not available: %s\n", snd_strerror(ret));
return FALSE;
}
/* set the count of channels */
ret = snd_pcm_hw_params_set_channels(info->handle, info->hwParams, channels);
if (ret < 0) {
ERROR2("Channels count (%d) not available: %s\n", channels, snd_strerror(ret));
return FALSE;
}
/* set the stream rate */
rrate = (int) (sampleRate + 0.5f);
dir = 0;
ret = snd_pcm_hw_params_set_rate_near(info->handle, info->hwParams, &rrate, &dir);
if (ret < 0) {
ERROR2("Rate %dHz not available for playback: %s\n", (int) (sampleRate+0.5f), snd_strerror(ret));
return FALSE;
}
if ((rrate-sampleRate > 2) || (rrate-sampleRate < - 2)) {
ERROR2("Rate doesn't match (requested %2.2fHz, got %dHz)\n", sampleRate, rrate);
return FALSE;
}
/* set the buffer time */
ret = snd_pcm_hw_params_set_buffer_size_near(info->handle, info->hwParams, &alsaBufferSizeInFrames);
if (ret < 0) {
ERROR2("Unable to set buffer size to %d frames: %s\n",
(int) alsaBufferSizeInFrames, snd_strerror(ret));
return FALSE;
}
bufferSizeInFrames = (int) alsaBufferSizeInFrames;
/* set the period time */
if (bufferSizeInFrames > 1024) {
dir = 0;
periodTime = DEFAULT_PERIOD_TIME;
ret = snd_pcm_hw_params_set_period_time_near(info->handle, info->hwParams, &periodTime, &dir);
if (ret < 0) {
ERROR2("Unable to set period time to %d: %s\n", DEFAULT_PERIOD_TIME, snd_strerror(ret));
return FALSE;
}
} else {
/* set the period count for very small buffer sizes to 2 */
dir = 0;
periods = 2;
ret = snd_pcm_hw_params_set_periods_near(info->handle, info->hwParams, &periods, &dir);
if (ret < 0) {
ERROR2("Unable to set period count to %d: %s\n", /*periods*/ 2, snd_strerror(ret));
return FALSE;
}
}
/* write the parameters to device */
ret = snd_pcm_hw_params(info->handle, info->hwParams);
if (ret < 0) {
ERROR1("Unable to set hw params: %s\n", snd_strerror(ret));
return FALSE;
}
return TRUE;
}
// returns 1 if successful
int setSWParams(AlsaPcmInfo* info) {
int ret;
/* get the current swparams */
ret = snd_pcm_sw_params_current(info->handle, info->swParams);
if (ret < 0) {
ERROR1("Unable to determine current swparams: %s\n", snd_strerror(ret));
return FALSE;
}
/* never start the transfer automatically */
if (!setStartThresholdNoCommit(info, FALSE /* don't use threshold */)) {
return FALSE;
}
/* allow the transfer when at least period_size samples can be processed */
ret = snd_pcm_sw_params_set_avail_min(info->handle, info->swParams, info->periodSize);
if (ret < 0) {
ERROR1("Unable to set avail min for playback: %s\n", snd_strerror(ret));
return FALSE;
}
/* write the parameters to the playback device */
ret = snd_pcm_sw_params(info->handle, info->swParams);
if (ret < 0) {
ERROR1("Unable to set sw params: %s\n", snd_strerror(ret));
return FALSE;
}
return TRUE;
}
static snd_output_t* ALSA_OUTPUT = NULL;
void* DAUDIO_Open(INT32 mixerIndex, INT32 deviceID, int isSource,
int encoding, float sampleRate, int sampleSizeInBits,
int frameSize, int channels,
int isSigned, int isBigEndian, int bufferSizeInBytes) {
snd_pcm_format_mask_t* formatMask;
snd_pcm_format_t format;
int dir;
int ret = 0;
AlsaPcmInfo* info = NULL;
/* snd_pcm_uframes_t is 64 bit on 64-bit systems */
snd_pcm_uframes_t alsaBufferSizeInFrames = 0;
TRACE0("> DAUDIO_Open\n");
#ifdef USE_TRACE
// for using ALSA debug dump methods
if (ALSA_OUTPUT == NULL) {
snd_output_stdio_attach(&ALSA_OUTPUT, stdout, 0);
}
#endif
if (channels <= 0) {
ERROR1("ERROR: Invalid number of channels=%d!\n", channels);
return NULL;
}
info = (AlsaPcmInfo*) malloc(sizeof(AlsaPcmInfo));
if (!info) {
ERROR0("Out of memory\n");
return NULL;
}
memset(info, 0, sizeof(AlsaPcmInfo));
// initial values are: stopped, flushed
info->isRunning = 0;
info->isFlushed = 1;
ret = openPCMfromDeviceID(deviceID, &(info->handle), isSource, FALSE /* do open device*/);
if (ret == 0) {
// set to blocking mode
snd_pcm_nonblock(info->handle, 0);
ret = snd_pcm_hw_params_malloc(&(info->hwParams));
if (ret != 0) {
ERROR1(" snd_pcm_hw_params_malloc returned error %d\n", ret);
} else {
ret = -1;
if (getAlsaFormatFromFormat(&format, frameSize / channels, sampleSizeInBits,
isSigned, isBigEndian, encoding)) {
if (setHWParams(info,
sampleRate,
channels,
bufferSizeInBytes / frameSize,
format)) {
info->frameSize = frameSize;
ret = snd_pcm_hw_params_get_period_size(info->hwParams, &info->periodSize, &dir);
if (ret < 0) {
ERROR1("ERROR: snd_pcm_hw_params_get_period: %s\n", snd_strerror(ret));
}
snd_pcm_hw_params_get_periods(info->hwParams, &(info->periods), &dir);
snd_pcm_hw_params_get_buffer_size(info->hwParams, &alsaBufferSizeInFrames);
info->bufferSizeInBytes = (int) alsaBufferSizeInFrames * frameSize;
TRACE3(" DAUDIO_Open: period size = %d frames, periods = %d. Buffer size: %d bytes.\n",
(int) info->periodSize, info->periods, info->bufferSizeInBytes);
}
}
}
if (ret == 0) {
// set software parameters
ret = snd_pcm_sw_params_malloc(&(info->swParams));
if (ret != 0) {
ERROR1("snd_pcm_hw_params_malloc returned error %d\n", ret);
} else {
if (!setSWParams(info)) {
ret = -1;
}
}
}
if (ret == 0) {
// prepare device
ret = snd_pcm_prepare(info->handle);
if (ret < 0) {
ERROR1("ERROR: snd_pcm_prepare: %s\n", snd_strerror(ret));
}
}
#ifdef GET_POSITION_METHOD2
if (ret == 0) {
ret = snd_pcm_status_malloc(&(info->positionStatus));
if (ret != 0) {
ERROR1("ERROR in snd_pcm_status_malloc: %s\n", snd_strerror(ret));
}
}
#endif
}
if (ret != 0) {
DAUDIO_Close((void*) info, isSource);
info = NULL;
} else {
// set to non-blocking mode
snd_pcm_nonblock(info->handle, 1);
TRACE1("< DAUDIO_Open: Opened device successfully. Handle=%p\n",
(void*) info->handle);
}
return (void*) info;
}
#ifdef USE_TRACE
void printState(snd_pcm_state_t state) {
if (state == SND_PCM_STATE_OPEN) {
TRACE0("State: SND_PCM_STATE_OPEN\n");
}
else if (state == SND_PCM_STATE_SETUP) {
TRACE0("State: SND_PCM_STATE_SETUP\n");
}
else if (state == SND_PCM_STATE_PREPARED) {
TRACE0("State: SND_PCM_STATE_PREPARED\n");
}
else if (state == SND_PCM_STATE_RUNNING) {
TRACE0("State: SND_PCM_STATE_RUNNING\n");
}
else if (state == SND_PCM_STATE_XRUN) {
TRACE0("State: SND_PCM_STATE_XRUN\n");
}
else if (state == SND_PCM_STATE_DRAINING) {
TRACE0("State: SND_PCM_STATE_DRAINING\n");
}
else if (state == SND_PCM_STATE_PAUSED) {
TRACE0("State: SND_PCM_STATE_PAUSED\n");
}
else if (state == SND_PCM_STATE_SUSPENDED) {
TRACE0("State: SND_PCM_STATE_SUSPENDED\n");
}
}
#endif
int DAUDIO_Start(void* id, int isSource) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
int ret;
snd_pcm_state_t state;
TRACE0("> DAUDIO_Start\n");
// set to blocking mode
snd_pcm_nonblock(info->handle, 0);
// set start mode so that it always starts as soon as data is there
setStartThreshold(info, TRUE /* use threshold */);
state = snd_pcm_state(info->handle);
if (state == SND_PCM_STATE_PAUSED) {
// in case it was stopped previously
TRACE0(" Un-pausing...\n");
ret = snd_pcm_pause(info->handle, FALSE);
if (ret != 0) {
ERROR2(" NOTE: error in snd_pcm_pause:%d: %s\n", ret, snd_strerror(ret));
}
}
if (state == SND_PCM_STATE_SUSPENDED) {
TRACE0(" Resuming...\n");
ret = snd_pcm_resume(info->handle);
if (ret < 0) {
if ((ret != -EAGAIN) && (ret != -ENOSYS)) {
ERROR2(" ERROR: error in snd_pcm_resume:%d: %s\n", ret, snd_strerror(ret));
}
}
}
if (state == SND_PCM_STATE_SETUP) {
TRACE0("need to call prepare again...\n");
// prepare device
ret = snd_pcm_prepare(info->handle);
if (ret < 0) {
ERROR1("ERROR: snd_pcm_prepare: %s\n", snd_strerror(ret));
}
}
// in case there is still data in the buffers
ret = snd_pcm_start(info->handle);
if (ret != 0) {
if (ret != -EPIPE) {
ERROR2(" NOTE: error in snd_pcm_start: %d: %s\n", ret, snd_strerror(ret));
}
}
// set to non-blocking mode
ret = snd_pcm_nonblock(info->handle, 1);
if (ret != 0) {
ERROR1(" ERROR in snd_pcm_nonblock: %s\n", snd_strerror(ret));
}
state = snd_pcm_state(info->handle);
#ifdef USE_TRACE
printState(state);
#endif
ret = (state == SND_PCM_STATE_PREPARED)
|| (state == SND_PCM_STATE_RUNNING)
|| (state == SND_PCM_STATE_XRUN)
|| (state == SND_PCM_STATE_SUSPENDED);
if (ret) {
info->isRunning = 1;
// source line should keep isFlushed value until Write() is called;
// for target data line reset it right now.
if (!isSource) {
info->isFlushed = 0;
}
}
TRACE1("< DAUDIO_Start %s\n", ret?"success":"error");
return ret?TRUE:FALSE;
}
int DAUDIO_Stop(void* id, int isSource) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
int ret;
TRACE0("> DAUDIO_Stop\n");
// set to blocking mode
snd_pcm_nonblock(info->handle, 0);
setStartThreshold(info, FALSE /* don't use threshold */); // device will not start after buffer xrun
ret = snd_pcm_pause(info->handle, 1);
// set to non-blocking mode
snd_pcm_nonblock(info->handle, 1);
if (ret != 0) {
ERROR1("ERROR in snd_pcm_pause: %s\n", snd_strerror(ret));
return FALSE;
}
info->isRunning = 0;
TRACE0("< DAUDIO_Stop success\n");
return TRUE;
}
void DAUDIO_Close(void* id, int isSource) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
TRACE0("DAUDIO_Close\n");
if (info != NULL) {
if (info->handle != NULL) {
snd_pcm_close(info->handle);
}
if (info->hwParams) {
snd_pcm_hw_params_free(info->hwParams);
}
if (info->swParams) {
snd_pcm_sw_params_free(info->swParams);
}
#ifdef GET_POSITION_METHOD2
if (info->positionStatus) {
snd_pcm_status_free(info->positionStatus);
}
#endif
free(info);
}
}
/*
* Underrun and suspend recovery
* returns
* 0: exit native and return 0
* 1: try again to write/read
* -1: error - exit native with return value -1
*/
int xrun_recovery(AlsaPcmInfo* info, int err) {
int ret;
if (err == -EPIPE) { /* underrun / overflow */
TRACE0("xrun_recovery: underrun/overflow.\n");
ret = snd_pcm_prepare(info->handle);
if (ret < 0) {
ERROR1("Can't recover from underrun/overflow, prepare failed: %s\n", snd_strerror(ret));
return -1;
}
return 1;
} else if (err == -ESTRPIPE) {
TRACE0("xrun_recovery: suspended.\n");
ret = snd_pcm_resume(info->handle);
if (ret < 0) {
if (ret == -EAGAIN) {
return 0; /* wait until the suspend flag is released */
}
return -1;
}
ret = snd_pcm_prepare(info->handle);
if (ret < 0) {
ERROR1("Can't recover from underrun/overflow, prepare failed: %s\n", snd_strerror(ret));
return -1;
}
return 1;
} else if (err == -EAGAIN) {
TRACE0("xrun_recovery: EAGAIN try again flag.\n");
return 0;
}
TRACE2("xrun_recovery: unexpected error %d: %s\n", err, snd_strerror(err));
return -1;
}
// returns -1 on error
int DAUDIO_Write(void* id, char* data, int byteSize) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
int ret, count;
snd_pcm_sframes_t frameSize, writtenFrames;
TRACE1("> DAUDIO_Write %d bytes\n", byteSize);
/* sanity */
if (byteSize <= 0 || info->frameSize <= 0) {
ERROR2(" DAUDIO_Write: byteSize=%d, frameSize=%d!\n",
(int) byteSize, (int) info->frameSize);
TRACE0("< DAUDIO_Write returning -1\n");
return -1;
}
count = 2; // maximum number of trials to recover from underrun
//frameSize = snd_pcm_bytes_to_frames(info->handle, byteSize);
frameSize = (snd_pcm_sframes_t) (byteSize / info->frameSize);
do {
writtenFrames = snd_pcm_writei(info->handle, (const void*) data, (snd_pcm_uframes_t) frameSize);
if (writtenFrames < 0) {
ret = xrun_recovery(info, (int) writtenFrames);
if (ret <= 0) {
TRACE1("DAUDIO_Write: xrun recovery returned %d -> return.\n", ret);
return ret;
}
if (count-- <= 0) {
ERROR0("DAUDIO_Write: too many attempts to recover from xrun/suspend\n");
return -1;
}
} else {
break;
}
} while (TRUE);
//ret = snd_pcm_frames_to_bytes(info->handle, writtenFrames);
if (writtenFrames > 0) {
// reset "flushed" flag
info->isFlushed = 0;
}
ret = (int) (writtenFrames * info->frameSize);
TRACE1("< DAUDIO_Write: returning %d bytes.\n", ret);
return ret;
}
// returns -1 on error
int DAUDIO_Read(void* id, char* data, int byteSize) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
int ret, count;
snd_pcm_sframes_t frameSize, readFrames;
TRACE1("> DAUDIO_Read %d bytes\n", byteSize);
/*TRACE3(" info=%p, data=%p, byteSize=%d\n",
(void*) info, (void*) data, (int) byteSize);
TRACE2(" info->frameSize=%d, info->handle=%p\n",
(int) info->frameSize, (void*) info->handle);
*/
/* sanity */
if (byteSize <= 0 || info->frameSize <= 0) {
ERROR2(" DAUDIO_Read: byteSize=%d, frameSize=%d!\n",
(int) byteSize, (int) info->frameSize);
TRACE0("< DAUDIO_Read returning -1\n");
return -1;
}
if (!info->isRunning && info->isFlushed) {
// PCM has nothing to read
return 0;
}
count = 2; // maximum number of trials to recover from error
//frameSize = snd_pcm_bytes_to_frames(info->handle, byteSize);
frameSize = (snd_pcm_sframes_t) (byteSize / info->frameSize);
do {
readFrames = snd_pcm_readi(info->handle, (void*) data, (snd_pcm_uframes_t) frameSize);
if (readFrames < 0) {
ret = xrun_recovery(info, (int) readFrames);
if (ret <= 0) {
TRACE1("DAUDIO_Read: xrun recovery returned %d -> return.\n", ret);
return ret;
}
if (count-- <= 0) {
ERROR0("DAUDIO_Read: too many attempts to recover from xrun/suspend\n");
return -1;
}
} else {
break;
}
} while (TRUE);
//ret = snd_pcm_frames_to_bytes(info->handle, readFrames);
ret = (int) (readFrames * info->frameSize);
TRACE1("< DAUDIO_Read: returning %d bytes.\n", ret);
return ret;
}
int DAUDIO_GetBufferSize(void* id, int isSource) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
return info->bufferSizeInBytes;
}
int DAUDIO_StillDraining(void* id, int isSource) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
snd_pcm_state_t state;
state = snd_pcm_state(info->handle);
//printState(state);
//TRACE1("Still draining: %s\n", (state != SND_PCM_STATE_XRUN)?"TRUE":"FALSE");
return (state == SND_PCM_STATE_RUNNING)?TRUE:FALSE;
}
int DAUDIO_Flush(void* id, int isSource) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
int ret;
TRACE0("DAUDIO_Flush\n");
if (info->isFlushed) {
// nothing to drop
return 1;
}
ret = snd_pcm_drop(info->handle);
if (ret != 0) {
ERROR1("ERROR in snd_pcm_drop: %s\n", snd_strerror(ret));
return FALSE;
}
info->isFlushed = 1;
if (info->isRunning) {
ret = DAUDIO_Start(id, isSource);
}
return ret;
}
int DAUDIO_GetAvailable(void* id, int isSource) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
snd_pcm_sframes_t availableInFrames;
snd_pcm_state_t state;
int ret;
state = snd_pcm_state(info->handle);
if (info->isFlushed || state == SND_PCM_STATE_XRUN) {
// if in xrun state then we have the entire buffer available,
// not 0 as alsa reports
ret = info->bufferSizeInBytes;
} else {
availableInFrames = snd_pcm_avail_update(info->handle);
if (availableInFrames < 0) {
ret = 0;
} else {
//ret = snd_pcm_frames_to_bytes(info->handle, availableInFrames);
ret = (int) (availableInFrames * info->frameSize);
}
}
TRACE1("DAUDIO_GetAvailable returns %d bytes\n", ret);
return ret;
}
INT64 estimatePositionFromAvail(AlsaPcmInfo* info, int isSource, INT64 javaBytePos, int availInBytes) {
// estimate the current position with the buffer size and
// the available bytes to read or write in the buffer.
// not an elegant solution - bytePos will stop on xruns,
// and in race conditions it may jump backwards
// Advantage is that it is indeed based on the samples that go through
// the system (rather than time-based methods)
if (isSource) {
// javaBytePos is the position that is reached when the current
// buffer is played completely
return (INT64) (javaBytePos - info->bufferSizeInBytes + availInBytes);
} else {
// javaBytePos is the position that was when the current buffer was empty
return (INT64) (javaBytePos + availInBytes);
}
}
INT64 DAUDIO_GetBytePosition(void* id, int isSource, INT64 javaBytePos) {
AlsaPcmInfo* info = (AlsaPcmInfo*) id;
int ret;
INT64 result = javaBytePos;
snd_pcm_state_t state;
state = snd_pcm_state(info->handle);
if (!info->isFlushed && state != SND_PCM_STATE_XRUN) {
#ifdef GET_POSITION_METHOD2
snd_timestamp_t* ts;
snd_pcm_uframes_t framesAvail;
// note: slight race condition if this is called simultaneously from 2 threads
ret = snd_pcm_status(info->handle, info->positionStatus);
if (ret != 0) {
ERROR1("ERROR in snd_pcm_status: %s\n", snd_strerror(ret));
result = javaBytePos;
} else {
// calculate from time value, or from available bytes
framesAvail = snd_pcm_status_get_avail(info->positionStatus);
result = estimatePositionFromAvail(info, isSource, javaBytePos, framesAvail * info->frameSize);
}
#endif
#ifdef GET_POSITION_METHOD3
snd_pcm_uframes_t framesAvail;
ret = snd_pcm_avail(info->handle, &framesAvail);
if (ret != 0) {
ERROR1("ERROR in snd_pcm_avail: %s\n", snd_strerror(ret));
result = javaBytePos;
} else {
result = estimatePositionFromAvail(info, isSource, javaBytePos, framesAvail * info->frameSize);
}
#endif
#ifdef GET_POSITION_METHOD1
result = estimatePositionFromAvail(info, isSource, javaBytePos, DAUDIO_GetAvailable(id, isSource));
#endif
}
//printf("getbyteposition: javaBytePos=%d , return=%d\n", (int) javaBytePos, (int) result);
return result;
}
void DAUDIO_SetBytePosition(void* id, int isSource, INT64 javaBytePos) {
/* save to ignore, since GetBytePosition
* takes the javaBytePos param into account
*/
}
int DAUDIO_RequiresServicing(void* id, int isSource) {
// never need servicing on Linux
return FALSE;
}
void DAUDIO_Service(void* id, int isSource) {
// never need servicing on Linux
}
#endif // USE_DAUDIO

View file

@ -0,0 +1,292 @@
/*
* Copyright (c) 2003, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
//#define USE_ERROR
//#define USE_TRACE
#include "PLATFORM_API_LinuxOS_ALSA_PCMUtils.h"
#include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h"
// callback for iteration through devices
// returns TRUE if iteration should continue
// NOTE: cardinfo may be NULL (for "default" device)
typedef int (*DeviceIteratorPtr)(UINT32 deviceID, snd_pcm_info_t* pcminfo,
snd_ctl_card_info_t* cardinfo, void *userData);
// for each ALSA device, call iterator. userData is passed to the iterator
// returns total number of iterations
int iteratePCMDevices(DeviceIteratorPtr iterator, void* userData) {
int count = 0;
int subdeviceCount;
int card, dev, subDev;
char devname[16];
int err;
snd_ctl_t *handle;
snd_pcm_t *pcm;
snd_pcm_info_t* pcminfo;
snd_ctl_card_info_t *cardinfo, *defcardinfo = NULL;
UINT32 deviceID;
int doContinue = TRUE;
snd_pcm_info_malloc(&pcminfo);
snd_ctl_card_info_malloc(&cardinfo);
// 1st try "default" device
err = snd_pcm_open(&pcm, ALSA_DEFAULT_DEVICE_NAME,
SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (err < 0) {
// try with the other direction
err = snd_pcm_open(&pcm, ALSA_DEFAULT_DEVICE_NAME,
SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK);
}
if (err < 0) {
ERROR1("ERROR: snd_pcm_open (\"default\"): %s\n", snd_strerror(err));
} else {
err = snd_pcm_info(pcm, pcminfo);
snd_pcm_close(pcm);
if (err < 0) {
ERROR1("ERROR: snd_pcm_info (\"default\"): %s\n",
snd_strerror(err));
} else {
// try to get card info
card = snd_pcm_info_get_card(pcminfo);
if (card >= 0) {
snprintf(devname, sizeof(devname), ALSA_HARDWARE_CARD, card);
if (snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK) >= 0) {
if (snd_ctl_card_info(handle, cardinfo) >= 0) {
defcardinfo = cardinfo;
}
snd_ctl_close(handle);
}
}
// call callback function for the device
if (iterator != NULL) {
doContinue = (*iterator)(ALSA_DEFAULT_DEVICE_ID, pcminfo,
defcardinfo, userData);
}
count++;
}
}
// iterate cards
card = -1;
while (doContinue) {
if (snd_card_next(&card) < 0) {
break;
}
if (card < 0) {
break;
}
snprintf(devname, sizeof(devname), ALSA_HARDWARE_CARD, card);
TRACE1("Opening alsa device \"%s\"...\n", devname);
err = snd_ctl_open(&handle, devname, SND_CTL_NONBLOCK);
if (err < 0) {
ERROR2("ERROR: snd_ctl_open, card=%d: %s\n",
card, snd_strerror(err));
} else {
err = snd_ctl_card_info(handle, cardinfo);
if (err < 0) {
ERROR2("ERROR: snd_ctl_card_info, card=%d: %s\n",
card, snd_strerror(err));
} else {
dev = -1;
while (doContinue) {
if (snd_ctl_pcm_next_device(handle, &dev) < 0) {
ERROR0("snd_ctl_pcm_next_device\n");
}
if (dev < 0) {
break;
}
snd_pcm_info_set_device(pcminfo, dev);
snd_pcm_info_set_subdevice(pcminfo, 0);
snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK);
err = snd_ctl_pcm_info(handle, pcminfo);
if (err == -ENOENT) {
// try with the other direction
snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_CAPTURE);
err = snd_ctl_pcm_info(handle, pcminfo);
}
if (err < 0) {
if (err != -ENOENT) {
ERROR2("ERROR: snd_ctl_pcm_info, card=%d: %s",
card, snd_strerror(err));
}
} else {
subdeviceCount = needEnumerateSubdevices(ALSA_PCM) ?
snd_pcm_info_get_subdevices_count(pcminfo) : 1;
if (iterator!=NULL) {
for (subDev = 0; subDev < subdeviceCount; subDev++) {
deviceID = encodeDeviceID(card, dev, subDev);
doContinue = (*iterator)(deviceID, pcminfo,
cardinfo, userData);
count++;
if (!doContinue) {
break;
}
}
} else {
count += subdeviceCount;
}
}
} // of while(doContinue)
}
snd_ctl_close(handle);
}
}
snd_ctl_card_info_free(cardinfo);
snd_pcm_info_free(pcminfo);
return count;
}
int getAudioDeviceCount() {
initAlsaSupport();
return iteratePCMDevices(NULL, NULL);
}
int deviceInfoIterator(UINT32 deviceID, snd_pcm_info_t* pcminfo,
snd_ctl_card_info_t* cardinfo, void* userData) {
char buffer[300];
ALSA_AudioDeviceDescription* desc = (ALSA_AudioDeviceDescription*)userData;
#ifdef ALSA_PCM_USE_PLUGHW
int usePlugHw = 1;
#else
int usePlugHw = 0;
#endif
initAlsaSupport();
if (desc->index == 0) {
// we found the device with correct index
*(desc->maxSimultaneousLines) = needEnumerateSubdevices(ALSA_PCM) ?
1 : snd_pcm_info_get_subdevices_count(pcminfo);
*desc->deviceID = deviceID;
buffer[0]=' '; buffer[1]='[';
// buffer[300] is enough to store the actual device string w/o overrun
getDeviceStringFromDeviceID(&buffer[2], sizeof(buffer) - 2, deviceID, usePlugHw, ALSA_PCM);
strncat(buffer, "]", sizeof(buffer) - strlen(buffer) - 1);
strncpy(desc->name,
(cardinfo != NULL)
? snd_ctl_card_info_get_id(cardinfo)
: snd_pcm_info_get_id(pcminfo),
desc->strLen - strlen(buffer));
strncat(desc->name, buffer, desc->strLen - strlen(desc->name));
strncpy(desc->vendor, "ALSA (http://www.alsa-project.org)", desc->strLen);
strncpy(desc->description,
(cardinfo != NULL)
? snd_ctl_card_info_get_name(cardinfo)
: snd_pcm_info_get_name(pcminfo),
desc->strLen);
strncat(desc->description, ", ", desc->strLen - strlen(desc->description));
strncat(desc->description, snd_pcm_info_get_id(pcminfo), desc->strLen - strlen(desc->description));
strncat(desc->description, ", ", desc->strLen - strlen(desc->description));
strncat(desc->description, snd_pcm_info_get_name(pcminfo), desc->strLen - strlen(desc->description));
getALSAVersion(desc->version, desc->strLen);
TRACE4("Returning %s, %s, %s, %s\n", desc->name, desc->vendor, desc->description, desc->version);
return FALSE; // do not continue iteration
}
desc->index--;
return TRUE;
}
// returns 0 if successful
int openPCMfromDeviceID(int deviceID, snd_pcm_t** handle, int isSource, int hardware) {
char buffer[200];
int ret;
initAlsaSupport();
getDeviceStringFromDeviceID(buffer, sizeof(buffer), deviceID, !hardware, ALSA_PCM);
TRACE1("Opening ALSA device %s\n", buffer);
ret = snd_pcm_open(handle, buffer,
isSource?SND_PCM_STREAM_PLAYBACK:SND_PCM_STREAM_CAPTURE,
SND_PCM_NONBLOCK);
if (ret != 0) {
ERROR1("snd_pcm_open returned error code %d \n", ret);
*handle = NULL;
}
return ret;
}
int getAudioDeviceDescriptionByIndex(ALSA_AudioDeviceDescription* desc) {
initAlsaSupport();
TRACE1(" getAudioDeviceDescriptionByIndex(mixerIndex = %d\n", desc->index);
iteratePCMDevices(&deviceInfoIterator, desc);
return (desc->index == 0)?TRUE:FALSE;
}
// returns 1 if successful
// enc: 0 for PCM, 1 for ULAW, 2 for ALAW (see DirectAudio.h)
int getFormatFromAlsaFormat(snd_pcm_format_t alsaFormat,
int* sampleSizeInBytes, int* significantBits,
int* isSigned, int* isBigEndian, int* enc) {
*sampleSizeInBytes = (snd_pcm_format_physical_width(alsaFormat) + 7) / 8;
*significantBits = snd_pcm_format_width(alsaFormat);
// defaults
*enc = 0; // PCM
*isSigned = (snd_pcm_format_signed(alsaFormat) > 0);
*isBigEndian = (snd_pcm_format_big_endian(alsaFormat) > 0);
// non-PCM formats
if (alsaFormat == SND_PCM_FORMAT_MU_LAW) { // Mu-Law
*sampleSizeInBytes = 8; *enc = 1; *significantBits = *sampleSizeInBytes;
}
else if (alsaFormat == SND_PCM_FORMAT_A_LAW) { // A-Law
*sampleSizeInBytes = 8; *enc = 2; *significantBits = *sampleSizeInBytes;
}
else if (snd_pcm_format_linear(alsaFormat) < 1) {
return 0;
}
return (*sampleSizeInBytes > 0);
}
// returns 1 if successful
int getAlsaFormatFromFormat(snd_pcm_format_t* alsaFormat,
int sampleSizeInBytes, int significantBits,
int isSigned, int isBigEndian, int enc) {
*alsaFormat = SND_PCM_FORMAT_UNKNOWN;
if (enc == 0) {
*alsaFormat = snd_pcm_build_linear_format(significantBits,
sampleSizeInBytes * 8,
isSigned?0:1,
isBigEndian?1:0);
}
else if ((sampleSizeInBytes == 1) && (significantBits == 8)) {
if (enc == 1) { // ULAW
*alsaFormat = SND_PCM_FORMAT_MU_LAW;
}
else if (enc == 2) { // ALAW
*alsaFormat = SND_PCM_FORMAT_A_LAW;
}
}
return (*alsaFormat == SND_PCM_FORMAT_UNKNOWN)?0:1;
}
/* end */

View file

@ -0,0 +1,73 @@
/*
* Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// define this with a later version of ALSA than 0.9.0rc3
// (starting from 1.0.0 it became default behaviour)
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include "Utilities.h"
#ifndef PLATFORM_API_LINUXOS_ALSA_PCMUTILS_H_INCLUDED
#define PLATFORM_API_LINUXOS_ALSA_PCMUTILS_H_INCLUDED
// if this is defined, use plughw: devices
#define ALSA_PCM_USE_PLUGHW
//#undef ALSA_PCM_USE_PLUGHW
// maximum number of channels that is listed in the formats. If more, than
// just -1 for channel count is used.
#define MAXIMUM_LISTED_CHANNELS 32
typedef struct tag_ALSA_AudioDeviceDescription {
int index; // in
int strLen; // in
INT32* deviceID; // out
int* maxSimultaneousLines; // out
char* name; // out
char* vendor; // out
char* description; // out
char* version; // out
} ALSA_AudioDeviceDescription;
int getAudioDeviceCount();
int getAudioDeviceDescriptionByIndex(ALSA_AudioDeviceDescription* desc);
// returns ALSA error code, or 0 if successful
int openPCMfromDeviceID(int deviceID, snd_pcm_t** handle, int isSource, int hardware);
// returns 1 if successful
// enc: 0 for PCM, 1 for ULAW, 2 for ALAW (see DirectAudio.h)
int getFormatFromAlsaFormat(snd_pcm_format_t alsaFormat,
int* sampleSizeInBytes, int* significantBits,
int* isSigned, int* isBigEndian, int* enc);
int getAlsaFormatFromFormat(snd_pcm_format_t* alsaFormat,
int sampleSizeInBytes, int significantBits,
int isSigned, int isBigEndian, int enc);
#endif // PLATFORM_API_LINUXOS_ALSA_PCMUTILS_H_INCLUDED

View file

@ -0,0 +1,724 @@
/*
* Copyright (c) 2003, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#define USE_ERROR
//#define USE_TRACE
#include "Ports.h"
#include "PLATFORM_API_LinuxOS_ALSA_CommonUtils.h"
#include <alsa/asoundlib.h>
#if USE_PORTS == TRUE
#define MAX_ELEMS (300)
#define MAX_CONTROLS (MAX_ELEMS * 4)
#define CHANNELS_MONO (SND_MIXER_SCHN_LAST + 1)
#define CHANNELS_STEREO (SND_MIXER_SCHN_LAST + 2)
typedef struct {
snd_mixer_elem_t* elem;
INT32 portType; /* one of PORT_XXX_xx */
char* controlType; /* one of CONTROL_TYPE_xx */
/* Values: either SND_MIXER_SCHN_FRONT_xx, CHANNELS_MONO or CHANNELS_STEREO.
For SND_MIXER_SCHN_FRONT_xx, exactly this channel is set/retrieved directly.
For CHANNELS_MONO, ALSA channel SND_MIXER_SCHN_MONO is set/retrieved directly.
For CHANNELS_STEREO, ALSA channels SND_MIXER_SCHN_FRONT_LEFT and SND_MIXER_SCHN_FRONT_RIGHT
are set after a calculation that takes balance into account. Retrieved? Average of both
channels? (Using a cached value is not a good idea since the value in the HW may have been
altered.) */
INT32 channel;
} PortControl;
typedef struct tag_PortMixer {
snd_mixer_t* mixer_handle;
/* Number of array elements used in elems and types. */
int numElems;
snd_mixer_elem_t** elems;
/* Array of port types (PORT_SRC_UNKNOWN etc.). Indices are the same as in elems. */
INT32* types;
/* Number of array elements used in controls. */
int numControls;
PortControl* controls;
} PortMixer;
///// implemented functions of Ports.h
INT32 PORT_GetPortMixerCount() {
INT32 mixerCount;
int card;
char devname[16];
int err;
snd_ctl_t *handle;
snd_ctl_card_info_t* info;
TRACE0("> PORT_GetPortMixerCount\n");
initAlsaSupport();
snd_ctl_card_info_malloc(&info);
card = -1;
mixerCount = 0;
if (snd_card_next(&card) >= 0) {
while (card >= 0) {
snprintf(devname, sizeof(devname), ALSA_HARDWARE_CARD, card);
TRACE1("PORT_GetPortMixerCount: Opening alsa device \"%s\"...\n", devname);
err = snd_ctl_open(&handle, devname, 0);
if (err < 0) {
ERROR2("ERROR: snd_ctl_open, card=%d: %s\n", card, snd_strerror(err));
} else {
mixerCount++;
snd_ctl_close(handle);
}
if (snd_card_next(&card) < 0) {
break;
}
}
}
snd_ctl_card_info_free(info);
TRACE0("< PORT_GetPortMixerCount\n");
return mixerCount;
}
INT32 PORT_GetPortMixerDescription(INT32 mixerIndex, PortMixerDescription* description) {
snd_ctl_t* handle;
snd_ctl_card_info_t* card_info;
char devname[16];
int err;
char buffer[100];
TRACE0("> PORT_GetPortMixerDescription\n");
snd_ctl_card_info_malloc(&card_info);
snprintf(devname, sizeof(devname), ALSA_HARDWARE_CARD, (int) mixerIndex);
TRACE1("Opening alsa device \"%s\"...\n", devname);
err = snd_ctl_open(&handle, devname, 0);
if (err < 0) {
ERROR2("ERROR: snd_ctl_open, card=%d: %s\n", (int) mixerIndex, snd_strerror(err));
return FALSE;
}
err = snd_ctl_card_info(handle, card_info);
if (err < 0) {
ERROR2("ERROR: snd_ctl_card_info, card=%d: %s\n", (int) mixerIndex, snd_strerror(err));
}
strncpy(description->name, snd_ctl_card_info_get_id(card_info), PORT_STRING_LENGTH - 1);
snprintf(buffer, sizeof(buffer), " [%s]", devname);
strncat(description->name, buffer, PORT_STRING_LENGTH - 1 - strlen(description->name));
strncpy(description->vendor, "ALSA (http://www.alsa-project.org)", PORT_STRING_LENGTH - 1);
strncpy(description->description, snd_ctl_card_info_get_name(card_info), PORT_STRING_LENGTH - 1);
strncat(description->description, ", ", PORT_STRING_LENGTH - 1 - strlen(description->description));
strncat(description->description, snd_ctl_card_info_get_mixername(card_info), PORT_STRING_LENGTH - 1 - strlen(description->description));
getALSAVersion(description->version, PORT_STRING_LENGTH - 1);
snd_ctl_close(handle);
snd_ctl_card_info_free(card_info);
TRACE0("< PORT_GetPortMixerDescription\n");
return TRUE;
}
void* PORT_Open(INT32 mixerIndex) {
char devname[16];
snd_mixer_t* mixer_handle;
int err;
PortMixer* handle;
TRACE0("> PORT_Open\n");
snprintf(devname, sizeof(devname), ALSA_HARDWARE_CARD, (int) mixerIndex);
if ((err = snd_mixer_open(&mixer_handle, 0)) < 0) {
ERROR2("Mixer %s open error: %s", devname, snd_strerror(err));
return NULL;
}
if ((err = snd_mixer_attach(mixer_handle, devname)) < 0) {
ERROR2("Mixer attach %s error: %s", devname, snd_strerror(err));
snd_mixer_close(mixer_handle);
return NULL;
}
if ((err = snd_mixer_selem_register(mixer_handle, NULL, NULL)) < 0) {
ERROR1("Mixer register error: %s", snd_strerror(err));
snd_mixer_close(mixer_handle);
return NULL;
}
err = snd_mixer_load(mixer_handle);
if (err < 0) {
ERROR2("Mixer %s load error: %s", devname, snd_strerror(err));
snd_mixer_close(mixer_handle);
return NULL;
}
handle = (PortMixer*) calloc(1, sizeof(PortMixer));
if (handle == NULL) {
ERROR0("malloc() failed.");
snd_mixer_close(mixer_handle);
return NULL;
}
handle->numElems = 0;
handle->elems = (snd_mixer_elem_t**) calloc(MAX_ELEMS, sizeof(snd_mixer_elem_t*));
if (handle->elems == NULL) {
ERROR0("malloc() failed.");
snd_mixer_close(mixer_handle);
free(handle);
return NULL;
}
handle->types = (INT32*) calloc(MAX_ELEMS, sizeof(INT32));
if (handle->types == NULL) {
ERROR0("malloc() failed.");
snd_mixer_close(mixer_handle);
free(handle->elems);
free(handle);
return NULL;
}
handle->controls = (PortControl*) calloc(MAX_CONTROLS, sizeof(PortControl));
if (handle->controls == NULL) {
ERROR0("malloc() failed.");
snd_mixer_close(mixer_handle);
free(handle->elems);
free(handle->types);
free(handle);
return NULL;
}
handle->mixer_handle = mixer_handle;
// necessary to initialize data structures
PORT_GetPortCount(handle);
TRACE0("< PORT_Open\n");
return handle;
}
void PORT_Close(void* id) {
TRACE0("> PORT_Close\n");
if (id != NULL) {
PortMixer* handle = (PortMixer*) id;
if (handle->mixer_handle != NULL) {
snd_mixer_close(handle->mixer_handle);
}
if (handle->elems != NULL) {
free(handle->elems);
}
if (handle->types != NULL) {
free(handle->types);
}
if (handle->controls != NULL) {
free(handle->controls);
}
free(handle);
}
TRACE0("< PORT_Close\n");
}
INT32 PORT_GetPortCount(void* id) {
PortMixer* portMixer;
snd_mixer_elem_t *elem;
TRACE0("> PORT_GetPortCount\n");
if (id == NULL) {
// $$mp: Should become a descriptive error code (invalid handle).
return -1;
}
portMixer = (PortMixer*) id;
if (portMixer->numElems == 0) {
for (elem = snd_mixer_first_elem(portMixer->mixer_handle); elem; elem = snd_mixer_elem_next(elem)) {
if (!snd_mixer_selem_is_active(elem))
continue;
TRACE2("Simple mixer control '%s',%i\n",
snd_mixer_selem_get_name(elem),
snd_mixer_selem_get_index(elem));
if (snd_mixer_selem_has_playback_volume(elem)) {
portMixer->elems[portMixer->numElems] = elem;
portMixer->types[portMixer->numElems] = PORT_DST_UNKNOWN;
portMixer->numElems++;
}
// to prevent buffer overflow
if (portMixer->numElems >= MAX_ELEMS) {
break;
}
/* If an element has both playback an capture volume, it is put into the arrays
twice. */
if (snd_mixer_selem_has_capture_volume(elem)) {
portMixer->elems[portMixer->numElems] = elem;
portMixer->types[portMixer->numElems] = PORT_SRC_UNKNOWN;
portMixer->numElems++;
}
// to prevent buffer overflow
if (portMixer->numElems >= MAX_ELEMS) {
break;
}
}
}
TRACE0("< PORT_GetPortCount\n");
return portMixer->numElems;
}
INT32 PORT_GetPortType(void* id, INT32 portIndex) {
PortMixer* portMixer;
INT32 type;
TRACE0("> PORT_GetPortType\n");
if (id == NULL) {
// $$mp: Should become a descriptive error code (invalid handle).
return -1;
}
portMixer = (PortMixer*) id;
if (portIndex < 0 || portIndex >= portMixer->numElems) {
// $$mp: Should become a descriptive error code (index out of bounds).
return -1;
}
type = portMixer->types[portIndex];
TRACE0("< PORT_GetPortType\n");
return type;
}
INT32 PORT_GetPortName(void* id, INT32 portIndex, char* name, INT32 len) {
PortMixer* portMixer;
const char* nam;
TRACE0("> PORT_GetPortName\n");
if (id == NULL) {
// $$mp: Should become a descriptive error code (invalid handle).
return -1;
}
portMixer = (PortMixer*) id;
if (portIndex < 0 || portIndex >= portMixer->numElems) {
// $$mp: Should become a descriptive error code (index out of bounds).
return -1;
}
nam = snd_mixer_selem_get_name(portMixer->elems[portIndex]);
strncpy(name, nam, len - 1);
name[len - 1] = 0;
TRACE0("< PORT_GetPortName\n");
return TRUE;
}
static int isPlaybackFunction(INT32 portType) {
return (portType & PORT_DST_MASK);
}
/* Sets portControl to a pointer to the next free array element in the PortControl (pointer)
array of the passed portMixer. Returns TRUE if successful. May return FALSE if there is no
free slot. In this case, portControl is not altered */
static int getControlSlot(PortMixer* portMixer, PortControl** portControl) {
if (portMixer->numControls >= MAX_CONTROLS) {
return FALSE;
} else {
*portControl = &(portMixer->controls[portMixer->numControls]);
portMixer->numControls++;
return TRUE;
}
}
/* Protect against illegal min-max values, preventing divisions by zero.
*/
inline static long getRange(long min, long max) {
if (max > min) {
return max - min;
} else {
return 1;
}
}
/* Idea: we may specify that if unit is an empty string, the values are linear and if unit is "dB",
the values are logarithmic.
*/
static void* createVolumeControl(PortControlCreator* creator,
PortControl* portControl,
snd_mixer_elem_t* elem, int isPlayback) {
void* control;
float precision;
long min, max;
if (isPlayback) {
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
} else {
snd_mixer_selem_get_capture_volume_range(elem, &min, &max);
}
/* $$mp: The volume values retrieved with the ALSA API are strongly supposed to be logarithmic.
So the following calculation is wrong. However, there is no correct calculation, since
for equal-distant logarithmic steps, the precision expressed in linear varies over the
scale. */
precision = 1.0F / getRange(min, max);
control = (creator->newFloatControl)(creator, portControl, CONTROL_TYPE_VOLUME, 0.0F, +1.0F, precision, "");
return control;
}
void PORT_GetControls(void* id, INT32 portIndex, PortControlCreator* creator) {
PortMixer* portMixer;
snd_mixer_elem_t* elem;
void* control;
PortControl* portControl;
void* controls[10];
int numControls;
char* portName;
int isPlayback = 0;
int isMono;
int isStereo;
char* type;
snd_mixer_selem_channel_id_t channel;
memset(controls, 0, sizeof(controls));
TRACE0("> PORT_GetControls\n");
if (id == NULL) {
ERROR0("Invalid handle!");
// $$mp: an error code should be returned.
return;
}
portMixer = (PortMixer*) id;
if (portIndex < 0 || portIndex >= portMixer->numElems) {
ERROR0("Port index out of range!");
// $$mp: an error code should be returned.
return;
}
numControls = 0;
elem = portMixer->elems[portIndex];
if (snd_mixer_selem_has_playback_volume(elem) || snd_mixer_selem_has_capture_volume(elem)) {
/* Since we've split/duplicated elements with both playback and capture on the recovery
of elements, we now can assume that we handle only to deal with either playback or
capture. */
isPlayback = isPlaybackFunction(portMixer->types[portIndex]);
isMono = (isPlayback && snd_mixer_selem_is_playback_mono(elem)) ||
(!isPlayback && snd_mixer_selem_is_capture_mono(elem));
isStereo = (isPlayback &&
snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_FRONT_LEFT) &&
snd_mixer_selem_has_playback_channel(elem, SND_MIXER_SCHN_FRONT_RIGHT)) ||
(!isPlayback &&
snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_FRONT_LEFT) &&
snd_mixer_selem_has_capture_channel(elem, SND_MIXER_SCHN_FRONT_RIGHT));
// single volume control
if (isMono || isStereo) {
if (getControlSlot(portMixer, &portControl)) {
portControl->elem = elem;
portControl->portType = portMixer->types[portIndex];
portControl->controlType = CONTROL_TYPE_VOLUME;
if (isMono) {
portControl->channel = CHANNELS_MONO;
} else {
portControl->channel = CHANNELS_STEREO;
}
control = createVolumeControl(creator, portControl, elem, isPlayback);
if (control != NULL) {
controls[numControls++] = control;
}
}
} else { // more than two channels, each channels has its own control.
for (channel = SND_MIXER_SCHN_FRONT_LEFT; channel <= SND_MIXER_SCHN_LAST; channel++) {
if ((isPlayback && snd_mixer_selem_has_playback_channel(elem, channel)) ||
(!isPlayback && snd_mixer_selem_has_capture_channel(elem, channel))) {
if (getControlSlot(portMixer, &portControl)) {
portControl->elem = elem;
portControl->portType = portMixer->types[portIndex];
portControl->controlType = CONTROL_TYPE_VOLUME;
portControl->channel = channel;
control = createVolumeControl(creator, portControl, elem, isPlayback);
// We wrap in a compound control to provide the channel name.
if (control != NULL) {
/* $$mp 2003-09-14: The following cast shouldn't be necessary. Instead, the
declaration of PORT_NewCompoundControlPtr in Ports.h should be changed
to take a const char* parameter. */
control = (creator->newCompoundControl)(creator, (char*) snd_mixer_selem_channel_name(channel), &control, 1);
}
if (control != NULL) {
controls[numControls++] = control;
}
}
}
}
}
// BALANCE control
if (isStereo) {
if (getControlSlot(portMixer, &portControl)) {
portControl->elem = elem;
portControl->portType = portMixer->types[portIndex];
portControl->controlType = CONTROL_TYPE_BALANCE;
portControl->channel = CHANNELS_STEREO;
/* $$mp: The value for precision is chosen more or less arbitrarily. */
control = (creator->newFloatControl)(creator, portControl, CONTROL_TYPE_BALANCE, -1.0F, 1.0F, 0.01F, "");
if (control != NULL) {
controls[numControls++] = control;
}
}
}
}
if (snd_mixer_selem_has_playback_switch(elem) || snd_mixer_selem_has_capture_switch(elem)) {
if (getControlSlot(portMixer, &portControl)) {
type = isPlayback ? CONTROL_TYPE_MUTE : CONTROL_TYPE_SELECT;
portControl->elem = elem;
portControl->portType = portMixer->types[portIndex];
portControl->controlType = type;
control = (creator->newBooleanControl)(creator, portControl, type);
if (control != NULL) {
controls[numControls++] = control;
}
}
}
/* $$mp 2003-09-14: The following cast shouldn't be necessary. Instead, the
declaration of PORT_NewCompoundControlPtr in Ports.h should be changed
to take a const char* parameter. */
portName = (char*) snd_mixer_selem_get_name(elem);
control = (creator->newCompoundControl)(creator, portName, controls, numControls);
if (control != NULL) {
(creator->addControl)(creator, control);
}
TRACE0("< PORT_GetControls\n");
}
INT32 PORT_GetIntValue(void* controlIDV) {
PortControl* portControl = (PortControl*) controlIDV;
int value = 0;
snd_mixer_selem_channel_id_t channel;
if (portControl != NULL) {
switch (portControl->channel) {
case CHANNELS_MONO:
channel = SND_MIXER_SCHN_MONO;
break;
case CHANNELS_STEREO:
channel = SND_MIXER_SCHN_FRONT_LEFT;
break;
default:
channel = portControl->channel;
}
if (portControl->controlType == CONTROL_TYPE_MUTE ||
portControl->controlType == CONTROL_TYPE_SELECT) {
if (isPlaybackFunction(portControl->portType)) {
snd_mixer_selem_get_playback_switch(portControl->elem, channel, &value);
} else {
snd_mixer_selem_get_capture_switch(portControl->elem, channel, &value);
}
if (portControl->controlType == CONTROL_TYPE_MUTE) {
value = ! value;
}
} else {
ERROR1("PORT_GetIntValue(): inappropriate control type: %s\n",
portControl->controlType);
}
}
return (INT32) value;
}
void PORT_SetIntValue(void* controlIDV, INT32 value) {
PortControl* portControl = (PortControl*) controlIDV;
snd_mixer_selem_channel_id_t channel;
if (portControl != NULL) {
if (portControl->controlType == CONTROL_TYPE_MUTE) {
value = ! value;
}
if (portControl->controlType == CONTROL_TYPE_MUTE ||
portControl->controlType == CONTROL_TYPE_SELECT) {
if (isPlaybackFunction(portControl->portType)) {
snd_mixer_selem_set_playback_switch_all(portControl->elem, value);
} else {
snd_mixer_selem_set_capture_switch_all(portControl->elem, value);
}
} else {
ERROR1("PORT_SetIntValue(): inappropriate control type: %s\n",
portControl->controlType);
}
}
}
static float scaleVolumeValueToNormalized(long value, long min, long max) {
return (float) (value - min) / getRange(min, max);
}
static long scaleVolumeValueToHardware(float value, long min, long max) {
return (long)(value * getRange(min, max) + min);
}
float getRealVolume(PortControl* portControl,
snd_mixer_selem_channel_id_t channel) {
float fValue;
long lValue = 0;
long min = 0;
long max = 0;
if (isPlaybackFunction(portControl->portType)) {
snd_mixer_selem_get_playback_volume_range(portControl->elem,
&min, &max);
snd_mixer_selem_get_playback_volume(portControl->elem,
channel, &lValue);
} else {
snd_mixer_selem_get_capture_volume_range(portControl->elem,
&min, &max);
snd_mixer_selem_get_capture_volume(portControl->elem,
channel, &lValue);
}
fValue = scaleVolumeValueToNormalized(lValue, min, max);
return fValue;
}
void setRealVolume(PortControl* portControl,
snd_mixer_selem_channel_id_t channel, float value) {
long lValue = 0;
long min = 0;
long max = 0;
if (isPlaybackFunction(portControl->portType)) {
snd_mixer_selem_get_playback_volume_range(portControl->elem,
&min, &max);
lValue = scaleVolumeValueToHardware(value, min, max);
snd_mixer_selem_set_playback_volume(portControl->elem,
channel, lValue);
} else {
snd_mixer_selem_get_capture_volume_range(portControl->elem,
&min, &max);
lValue = scaleVolumeValueToHardware(value, min, max);
snd_mixer_selem_set_capture_volume(portControl->elem,
channel, lValue);
}
}
static float getFakeBalance(PortControl* portControl) {
float volL, volR;
// pan is the ratio of left and right
volL = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT);
volR = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT);
if (volL > volR) {
return -1.0f + (volR / volL);
}
else if (volR > volL) {
return 1.0f - (volL / volR);
}
return 0.0f;
}
static float getFakeVolume(PortControl* portControl) {
float valueL;
float valueR;
float value;
valueL = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT);
valueR = getRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT);
// volume is the greater value of both
value = valueL > valueR ? valueL : valueR ;
return value;
}
/*
* sets the unsigned values for left and right volume according to
* the given volume (0...1) and balance (-1..0..+1)
*/
static void setFakeVolume(PortControl* portControl, float vol, float bal) {
float volumeLeft;
float volumeRight;
if (bal < 0.0f) {
volumeLeft = vol;
volumeRight = vol * (bal + 1.0f);
} else {
volumeLeft = vol * (1.0f - bal);
volumeRight = vol;
}
setRealVolume(portControl, SND_MIXER_SCHN_FRONT_LEFT, volumeLeft);
setRealVolume(portControl, SND_MIXER_SCHN_FRONT_RIGHT, volumeRight);
}
float PORT_GetFloatValue(void* controlIDV) {
PortControl* portControl = (PortControl*) controlIDV;
float value = 0.0F;
if (portControl != NULL) {
if (portControl->controlType == CONTROL_TYPE_VOLUME) {
switch (portControl->channel) {
case CHANNELS_MONO:
value = getRealVolume(portControl, SND_MIXER_SCHN_MONO);
break;
case CHANNELS_STEREO:
value = getFakeVolume(portControl);
break;
default:
value = getRealVolume(portControl, portControl->channel);
}
} else if (portControl->controlType == CONTROL_TYPE_BALANCE) {
if (portControl->channel == CHANNELS_STEREO) {
value = getFakeBalance(portControl);
} else {
ERROR0("PORT_GetFloatValue(): Balance only allowed for stereo channels!\n");
}
} else {
ERROR1("PORT_GetFloatValue(): inappropriate control type: %s!\n",
portControl->controlType);
}
}
return value;
}
void PORT_SetFloatValue(void* controlIDV, float value) {
PortControl* portControl = (PortControl*) controlIDV;
if (portControl != NULL) {
if (portControl->controlType == CONTROL_TYPE_VOLUME) {
switch (portControl->channel) {
case CHANNELS_MONO:
setRealVolume(portControl, SND_MIXER_SCHN_MONO, value);
break;
case CHANNELS_STEREO:
setFakeVolume(portControl, value, getFakeBalance(portControl));
break;
default:
setRealVolume(portControl, portControl->channel, value);
}
} else if (portControl->controlType == CONTROL_TYPE_BALANCE) {
if (portControl->channel == CHANNELS_STEREO) {
setFakeVolume(portControl, getFakeVolume(portControl), value);
} else {
ERROR0("PORT_SetFloatValue(): Balance only allowed for stereo channels!\n");
}
} else {
ERROR1("PORT_SetFloatValue(): inappropriate control type: %s!\n",
portControl->controlType);
}
}
}
#endif // USE_PORTS

View file

@ -0,0 +1,870 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package apple.laf;
import java.nio.ByteBuffer;
import java.lang.annotation.Native;
public final class JRSUIConstants {
/**
* There is no way to get width of focus border, so it is hardcoded here.
* All components, which can be focused should take care about it.
*/
public static final int FOCUS_SIZE = 4;
private static native long getPtrForConstant(final int constant);
static final class Key {
@Native protected static final int _value = 20;
public static final Key VALUE = new Key(_value);
@Native protected static final int _thumbProportion = 24;
public static final Key THUMB_PROPORTION = new Key(_thumbProportion);
@Native protected static final int _thumbStart = 25;
public static final Key THUMB_START = new Key(_thumbStart);
@Native protected static final int _windowTitleBarHeight = 28;
public static final Key WINDOW_TITLE_BAR_HEIGHT = new Key(_windowTitleBarHeight);
@Native protected static final int _animationFrame = 23;
public static final Key ANIMATION_FRAME = new Key(_animationFrame);
final int constant;
private long ptr;
private Key(final int constant) {
this.constant = constant;
}
long getConstantPtr() {
if (ptr != 0) return ptr;
ptr = getPtrForConstant(constant);
if (ptr != 0) return ptr;
throw new RuntimeException("Constant not implemented in native: " + this);
}
private String getConstantName(Key hit) {
if (hit == VALUE) {
return "VALUE";
} else if (hit == THUMB_PROPORTION) {
return "THUMB_PROPORTION";
} else if (hit == THUMB_START) {
return "THUMB_START";
} else if (hit == WINDOW_TITLE_BAR_HEIGHT) {
return "WINDOW_TITLE_BAR_HEIGHT";
} else if (hit == ANIMATION_FRAME) {
return "ANIMATION_FRAME";
}
return getClass().getSimpleName();
}
@Override
public String toString() {
return getConstantName(this) + (ptr == 0 ? "(unlinked)" : "");
}
}
static final class DoubleValue {
@Native protected static final byte TYPE_CODE = 1;
final double doubleValue;
DoubleValue(final double doubleValue) {
this.doubleValue = doubleValue;
}
public byte getTypeCode() {
return TYPE_CODE;
}
public void putValueInBuffer(final ByteBuffer buffer) {
buffer.putDouble(doubleValue);
}
@Override
public boolean equals(final Object obj) {
return (obj instanceof DoubleValue) && (((DoubleValue)obj).doubleValue == doubleValue);
}
@Override
public int hashCode() {
return Double.hashCode(doubleValue);
}
@Override
public String toString() {
return Double.toString(doubleValue);
}
}
static final class PropertyEncoding {
final long mask;
final byte shift;
PropertyEncoding(final long mask, final byte shift) {
this.mask = mask;
this.shift = shift;
}
}
static class Property {
final PropertyEncoding encoding;
final long value;
final byte ordinal;
Property(final PropertyEncoding encoding, final byte ordinal) {
this.encoding = encoding;
this.value = ((long)ordinal) << encoding.shift;
this.ordinal = ordinal;
}
/**
* Applies this property value to the provided state
* @param encodedState the incoming JRSUI encoded state
* @return the composite of the provided JRSUI encoded state and this value
*/
public long apply(final long encodedState) {
return (encodedState & ~encoding.mask) | value;
}
@Override
public String toString() {
return getClass().getSimpleName();
}
}
public static final class Size extends Property {
@Native private static final byte SHIFT = 0;
@Native private static final byte SIZE = 3;
@Native private static final long MASK = (long)0x7 << SHIFT;
private static final PropertyEncoding size = new PropertyEncoding(MASK, SHIFT);
Size(final byte value) {
super(size, value);
}
@Native private static final byte _mini = 1;
public static final Size MINI = new Size(_mini);
@Native private static final byte _small = 2;
public static final Size SMALL = new Size(_small);
@Native private static final byte _regular = 3;
public static final Size REGULAR = new Size(_regular);
@Native private static final byte _large = 4;
public static final Size LARGE = new Size(_large);
}
public static final class State extends Property {
@Native private static final byte SHIFT = Size.SHIFT + Size.SIZE;
@Native private static final byte SIZE = 4;
@Native private static final long MASK = (long)0xF << SHIFT;
private static final PropertyEncoding state = new PropertyEncoding(MASK, SHIFT);
State(final byte value) {
super(state, value);
}
@Native private static final byte _active = 1;
public static final State ACTIVE = new State(_active);
@Native private static final byte _inactive = 2;
public static final State INACTIVE = new State(_inactive);
@Native private static final byte _disabled = 3;
public static final State DISABLED = new State(_disabled);
@Native private static final byte _pressed = 4;
public static final State PRESSED = new State(_pressed);
@Native private static final byte _pulsed = 5;
/**
* This identifies the default button in a window/dialog.
* The name PULSED has become misleading over time.
* The default button used to continually pulse up until
* Mac OS 10.9, but now there is no pulsing animation.
* We still need this State constant to render default
* buttons correctly, though.
*/
public static final State PULSED = new State(_pulsed);
@Native private static final byte _rollover = 6;
public static final State ROLLOVER = new State(_rollover);
@Native private static final byte _drag = 7;
public static final State DRAG = new State(_drag);
}
public static final class Direction extends Property {
@Native private static final byte SHIFT = State.SHIFT + State.SIZE;
@Native private static final byte SIZE = 4;
@Native private static final long MASK = (long)0xF << SHIFT;
private static final PropertyEncoding direction = new PropertyEncoding(MASK, SHIFT);
Direction(final byte value) {
super(direction, value);
}
@Native private static final byte _none = 1;
public static final Direction NONE = new Direction(_none);
@Native private static final byte _up = 2;
public static final Direction UP = new Direction(_up);
@Native private static final byte _down = 3;
public static final Direction DOWN = new Direction(_down);
@Native private static final byte _left = 4;
public static final Direction LEFT = new Direction(_left);
@Native private static final byte _right = 5;
public static final Direction RIGHT = new Direction(_right);
@Native private static final byte _north = 6;
public static final Direction NORTH = new Direction(_north);
@Native private static final byte _south = 7;
public static final Direction SOUTH = new Direction(_south);
@Native private static final byte _east = 8;
public static final Direction EAST = new Direction(_east);
@Native private static final byte _west = 9;
public static final Direction WEST = new Direction(_west);
}
public static final class Orientation extends Property {
@Native private static final byte SHIFT = Direction.SHIFT + Direction.SIZE;
@Native private static final byte SIZE = 2;
@Native private static final long MASK = (long)0x3 << SHIFT;
private static final PropertyEncoding orientation = new PropertyEncoding(MASK, SHIFT);
Orientation(final byte value) {
super(orientation, value);
}
@Native private static final byte _horizontal = 1;
public static final Orientation HORIZONTAL = new Orientation(_horizontal);
@Native private static final byte _vertical = 2;
public static final Orientation VERTICAL = new Orientation(_vertical);
}
public static final class AlignmentVertical extends Property {
@Native private static final byte SHIFT = Orientation.SHIFT + Orientation.SIZE;
@Native private static final byte SIZE = 2;
@Native private static final long MASK = (long)0x3 << SHIFT;
private static final PropertyEncoding alignmentVertical = new PropertyEncoding(MASK, SHIFT);
AlignmentVertical(final byte value){
super(alignmentVertical, value);
}
@Native private static final byte _top = 1;
public static final AlignmentVertical TOP = new AlignmentVertical(_top);
@Native private static final byte _center = 2;
public static final AlignmentVertical CENTER = new AlignmentVertical(_center);
@Native private static final byte _bottom = 3;
public static final AlignmentVertical BOTTOM = new AlignmentVertical(_bottom);
}
public static final class AlignmentHorizontal extends Property {
@Native private static final byte SHIFT = AlignmentVertical.SHIFT + AlignmentVertical.SIZE;
@Native private static final byte SIZE = 2;
@Native private static final long MASK = (long)0x3 << SHIFT;
private static final PropertyEncoding alignmentHorizontal = new PropertyEncoding(MASK, SHIFT);
AlignmentHorizontal(final byte value){
super(alignmentHorizontal, value);
}
@Native private static final byte _left = 1;
public static final AlignmentHorizontal LEFT = new AlignmentHorizontal(_left);
@Native private static final byte _center = 2;
public static final AlignmentHorizontal CENTER = new AlignmentHorizontal(_center);
@Native private static final byte _right = 3;
public static final AlignmentHorizontal RIGHT = new AlignmentHorizontal(_right);
}
public static final class SegmentPosition extends Property {
@Native private static final byte SHIFT = AlignmentHorizontal.SHIFT + AlignmentHorizontal.SIZE;
@Native private static final byte SIZE = 3;
@Native private static final long MASK = (long)0x7 << SHIFT;
private static final PropertyEncoding segmentPosition = new PropertyEncoding(MASK, SHIFT);
SegmentPosition(final byte value) {
super(segmentPosition, value);
}
@Native private static final byte _first = 1;
public static final SegmentPosition FIRST = new SegmentPosition(_first);
@Native private static final byte _middle = 2;
public static final SegmentPosition MIDDLE = new SegmentPosition(_middle);
@Native private static final byte _last = 3;
public static final SegmentPosition LAST = new SegmentPosition(_last);
@Native private static final byte _only = 4;
public static final SegmentPosition ONLY = new SegmentPosition(_only);
}
public static final class ScrollBarPart extends Property {
@Native private static final byte SHIFT = SegmentPosition.SHIFT + SegmentPosition.SIZE;
@Native private static final byte SIZE = 4;
@Native private static final long MASK = (long)0xF << SHIFT;
private static final PropertyEncoding scrollBarPart = new PropertyEncoding(MASK, SHIFT);
ScrollBarPart(final byte value) {
super(scrollBarPart, value);
}
@Native private static final byte _none = 1;
public static final ScrollBarPart NONE = new ScrollBarPart(_none);
@Native private static final byte _thumb = 2;
public static final ScrollBarPart THUMB = new ScrollBarPart(_thumb);
@Native private static final byte _arrowMin = 3;
public static final ScrollBarPart ARROW_MIN = new ScrollBarPart(_arrowMin);
@Native private static final byte _arrowMax = 4;
public static final ScrollBarPart ARROW_MAX = new ScrollBarPart(_arrowMax);
@Native private static final byte _arrowMaxInside = 5;
public static final ScrollBarPart ARROW_MAX_INSIDE = new ScrollBarPart(_arrowMaxInside);
@Native private static final byte _arrowMinInside = 6;
public static final ScrollBarPart ARROW_MIN_INSIDE = new ScrollBarPart(_arrowMinInside);
@Native private static final byte _trackMin = 7;
public static final ScrollBarPart TRACK_MIN = new ScrollBarPart(_trackMin);
@Native private static final byte _trackMax = 8;
public static final ScrollBarPart TRACK_MAX = new ScrollBarPart(_trackMax);
}
public static final class Variant extends Property {
@Native private static final byte SHIFT = ScrollBarPart.SHIFT + ScrollBarPart.SIZE;
@Native private static final byte SIZE = 4;
@Native private static final long MASK = (long)0xF << SHIFT;
private static final PropertyEncoding variant = new PropertyEncoding(MASK, SHIFT);
Variant(final byte value) {
super(variant, value);
}
@Native private static final byte _menuGlyph = 1;
public static final Variant MENU_GLYPH = new Variant(_menuGlyph);
@Native private static final byte _menuPopup = Variant._menuGlyph + 1;
public static final Variant MENU_POPUP = new Variant(_menuPopup);
@Native private static final byte _menuPulldown = Variant._menuPopup + 1;
public static final Variant MENU_PULLDOWN = new Variant(_menuPulldown);
@Native private static final byte _menuHierarchical = Variant._menuPulldown + 1;
public static final Variant MENU_HIERARCHICAL = new Variant(_menuHierarchical);
@Native private static final byte _gradientListBackgroundEven = Variant._menuHierarchical + 1;
public static final Variant GRADIENT_LIST_BACKGROUND_EVEN = new Variant(_gradientListBackgroundEven);
@Native private static final byte _gradientListBackgroundOdd = Variant._gradientListBackgroundEven + 1;
public static final Variant GRADIENT_LIST_BACKGROUND_ODD = new Variant(_gradientListBackgroundOdd);
@Native private static final byte _gradientSideBar = Variant._gradientListBackgroundOdd + 1;
public static final Variant GRADIENT_SIDE_BAR = new Variant(_gradientSideBar);
@Native private static final byte _gradientSideBarSelection = Variant._gradientSideBar + 1;
public static final Variant GRADIENT_SIDE_BAR_SELECTION = new Variant(_gradientSideBarSelection);
@Native private static final byte _gradientSideBarFocusedSelection = Variant._gradientSideBarSelection + 1;
public static final Variant GRADIENT_SIDE_BAR_FOCUSED_SELECTION = new Variant(_gradientSideBarFocusedSelection);
}
public static final class WindowType extends Property {
@Native private static final byte SHIFT = Variant.SHIFT + Variant.SIZE;
@Native private static final byte SIZE = 2;
@Native private static final long MASK = (long)0x3 << SHIFT;
private static final PropertyEncoding windowType = new PropertyEncoding(MASK, SHIFT);
WindowType(final byte value){
super(windowType, value);
}
@Native private static final byte _document = 1;
public static final WindowType DOCUMENT = new WindowType(_document);
@Native private static final byte _utility = 2;
public static final WindowType UTILITY = new WindowType(_utility);
@Native private static final byte _titlelessUtility = 3;
public static final WindowType TITLELESS_UTILITY = new WindowType(_titlelessUtility);
}
public static final class Focused extends Property {
@Native private static final byte SHIFT = WindowType.SHIFT + WindowType.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding focused = new PropertyEncoding(MASK, SHIFT);
Focused(final byte value) {
super(focused, value);
}
@Native private static final byte _no = 0;
public static final Focused NO = new Focused(_no);
@Native private static final byte _yes = 1;
public static final Focused YES = new Focused(_yes);
}
public static final class IndicatorOnly extends Property {
@Native private static final byte SHIFT = Focused.SHIFT + Focused.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding indicatorOnly = new PropertyEncoding(MASK, SHIFT);
IndicatorOnly(final byte value) {
super(indicatorOnly, value);
}
@Native private static final byte _no = 0;
public static final IndicatorOnly NO = new IndicatorOnly(_no);
@Native private static final byte _yes = 1;
public static final IndicatorOnly YES = new IndicatorOnly(_yes);
}
public static final class NoIndicator extends Property {
@Native private static final byte SHIFT = IndicatorOnly.SHIFT + IndicatorOnly.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding noIndicator = new PropertyEncoding(MASK, SHIFT);
NoIndicator(final byte value) {
super(noIndicator, value);
}
@Native private static final byte _no = 0;
public static final NoIndicator NO = new NoIndicator(_no);
@Native private static final byte _yes = 1;
public static final NoIndicator YES = new NoIndicator(_yes);
}
public static final class ArrowsOnly extends Property {
@Native private static final byte SHIFT = NoIndicator.SHIFT + NoIndicator.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding focused = new PropertyEncoding(MASK, SHIFT);
ArrowsOnly(final byte value) {
super(focused, value);
}
@Native private static final byte _no = 0;
public static final ArrowsOnly NO = new ArrowsOnly(_no);
@Native private static final byte _yes = 1;
public static final ArrowsOnly YES = new ArrowsOnly(_yes);
}
public static final class FrameOnly extends Property {
@Native private static final byte SHIFT = ArrowsOnly.SHIFT + ArrowsOnly.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding focused = new PropertyEncoding(MASK, SHIFT);
FrameOnly(final byte value) {
super(focused, value);
}
@Native private static final byte _no = 0;
public static final FrameOnly NO = new FrameOnly(_no);
@Native private static final byte _yes = 1;
public static final FrameOnly YES = new FrameOnly(_yes);
}
public static final class SegmentTrailingSeparator extends Property {
@Native private static final byte SHIFT = FrameOnly.SHIFT + FrameOnly.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding focused = new PropertyEncoding(MASK, SHIFT);
SegmentTrailingSeparator(final byte value) {
super(focused, value);
}
@Native private static final byte _no = 0;
public static final SegmentTrailingSeparator NO = new SegmentTrailingSeparator(_no);
@Native private static final byte _yes = 1;
public static final SegmentTrailingSeparator YES = new SegmentTrailingSeparator(_yes);
}
public static final class SegmentLeadingSeparator extends Property {
@Native private static final byte SHIFT = SegmentTrailingSeparator.SHIFT + SegmentTrailingSeparator.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding leadingSeparator = new PropertyEncoding(MASK, SHIFT);
SegmentLeadingSeparator(final byte value) {
super(leadingSeparator, value);
}
@Native private static final byte _no = 0;
public static final SegmentLeadingSeparator NO = new SegmentLeadingSeparator(_no);
@Native private static final byte _yes = 1;
public static final SegmentLeadingSeparator YES = new SegmentLeadingSeparator(_yes);
}
public static final class NothingToScroll extends Property {
@Native private static final byte SHIFT = SegmentLeadingSeparator.SHIFT + SegmentLeadingSeparator.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding focused = new PropertyEncoding(MASK, SHIFT);
NothingToScroll(final byte value) {
super(focused, value);
}
@Native private static final byte _no = 0;
public static final NothingToScroll NO = new NothingToScroll(_no);
@Native private static final byte _yes = 1;
public static final NothingToScroll YES = new NothingToScroll(_yes);
}
public static final class WindowTitleBarSeparator extends Property {
@Native private static final byte SHIFT = NothingToScroll.SHIFT + NothingToScroll.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding focused = new PropertyEncoding(MASK, SHIFT);
WindowTitleBarSeparator(final byte value) {
super(focused, value);
}
@Native private static final byte _no = 0;
public static final WindowTitleBarSeparator NO = new WindowTitleBarSeparator(_no);
@Native private static final byte _yes = 1;
public static final WindowTitleBarSeparator YES = new WindowTitleBarSeparator(_yes);
}
public static final class WindowClipCorners extends Property {
@Native private static final byte SHIFT = WindowTitleBarSeparator.SHIFT + WindowTitleBarSeparator.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding focused = new PropertyEncoding(MASK, SHIFT);
WindowClipCorners(final byte value) {
super(focused, value);
}
@Native private static final byte _no = 0;
public static final WindowClipCorners NO = new WindowClipCorners(_no);
@Native private static final byte _yes = 1;
public static final WindowClipCorners YES = new WindowClipCorners(_yes);
}
public static final class ShowArrows extends Property {
@Native private static final byte SHIFT = WindowClipCorners.SHIFT + WindowClipCorners.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding showArrows = new PropertyEncoding(MASK, SHIFT);
ShowArrows(final byte value) {
super(showArrows, value);
}
@Native private static final byte _no = 0;
public static final ShowArrows NO = new ShowArrows(_no);
@Native private static final byte _yes = 1;
public static final ShowArrows YES = new ShowArrows(_yes);
}
public static final class BooleanValue extends Property {
@Native private static final byte SHIFT = ShowArrows.SHIFT + ShowArrows.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding booleanValue = new PropertyEncoding(MASK, SHIFT);
BooleanValue(final byte value) {
super(booleanValue, value);
}
@Native private static final byte _no = 0;
public static final BooleanValue NO = new BooleanValue(_no);
@Native private static final byte _yes = 1;
public static final BooleanValue YES = new BooleanValue(_yes);
}
public static final class Animating extends Property {
@Native private static final byte SHIFT = BooleanValue.SHIFT + BooleanValue.SIZE;
@Native private static final byte SIZE = 1;
@Native private static final long MASK = (long)0x1 << SHIFT;
private static final PropertyEncoding animating = new PropertyEncoding(MASK, SHIFT);
Animating(final byte value) {
super(animating, value);
}
@Native private static final byte _no = 0;
public static final Animating NO = new Animating(_no);
@Native private static final byte _yes = 1;
public static final Animating YES = new Animating(_yes);
}
public static final class Widget extends Property {
@Native private static final byte SHIFT = Animating.SHIFT + Animating.SIZE;
@Native private static final byte SIZE = 7;
@Native private static final long MASK = (long)0x7F << SHIFT;
private static final PropertyEncoding widget = new PropertyEncoding(MASK, SHIFT);
Widget(final byte constant) {
super(widget, constant);
}
@Native private static final byte _background = 1;
public static final Widget BACKGROUND = new Widget(_background);
@Native private static final byte _buttonBevel = _background + 1;
public static final Widget BUTTON_BEVEL = new Widget(_buttonBevel);
@Native private static final byte _buttonBevelInset = _buttonBevel + 1;
public static final Widget BUTTON_BEVEL_INSET = new Widget(_buttonBevelInset);
@Native private static final byte _buttonBevelRound = _buttonBevelInset + 1;
public static final Widget BUTTON_BEVEL_ROUND = new Widget(_buttonBevelRound);
@Native private static final byte _buttonCheckBox = _buttonBevelRound + 1;
public static final Widget BUTTON_CHECK_BOX = new Widget(_buttonCheckBox);
@Native private static final byte _buttonComboBox = _buttonCheckBox + 1;
public static final Widget BUTTON_COMBO_BOX = new Widget(_buttonComboBox);
@Native private static final byte _buttonComboBoxInset = _buttonComboBox + 1;
public static final Widget BUTTON_COMBO_BOX_INSET = new Widget(_buttonComboBoxInset); // not hooked up in JRSUIConstants.m
@Native private static final byte _buttonDisclosure = _buttonComboBoxInset + 1;
public static final Widget BUTTON_DISCLOSURE = new Widget(_buttonDisclosure);
@Native private static final byte _buttonListHeader = _buttonDisclosure + 1;
public static final Widget BUTTON_LIST_HEADER = new Widget(_buttonListHeader);
@Native private static final byte _buttonLittleArrows = _buttonListHeader + 1;
public static final Widget BUTTON_LITTLE_ARROWS = new Widget(_buttonLittleArrows);
@Native private static final byte _buttonPopDown = _buttonLittleArrows + 1;
public static final Widget BUTTON_POP_DOWN = new Widget(_buttonPopDown);
@Native private static final byte _buttonPopDownInset = _buttonPopDown + 1;
public static final Widget BUTTON_POP_DOWN_INSET = new Widget(_buttonPopDownInset);
@Native private static final byte _buttonPopDownSquare = _buttonPopDownInset + 1;
public static final Widget BUTTON_POP_DOWN_SQUARE = new Widget(_buttonPopDownSquare);
@Native private static final byte _buttonPopUp = _buttonPopDownSquare + 1;
public static final Widget BUTTON_POP_UP = new Widget(_buttonPopUp);
@Native private static final byte _buttonPopUpInset = _buttonPopUp + 1;
public static final Widget BUTTON_POP_UP_INSET = new Widget(_buttonPopUpInset);
@Native private static final byte _buttonPopUpSquare = _buttonPopUpInset + 1;
public static final Widget BUTTON_POP_UP_SQUARE = new Widget(_buttonPopUpSquare);
@Native private static final byte _buttonPush = _buttonPopUpSquare + 1;
public static final Widget BUTTON_PUSH = new Widget(_buttonPush);
@Native private static final byte _buttonPushScope = _buttonPush + 1;
public static final Widget BUTTON_PUSH_SCOPE = new Widget(_buttonPushScope);
@Native private static final byte _buttonPushScope2 = _buttonPushScope + 1;
public static final Widget BUTTON_PUSH_SCOPE2 = new Widget(_buttonPushScope2);
@Native private static final byte _buttonPushTextured = _buttonPushScope2 + 1;
public static final Widget BUTTON_PUSH_TEXTURED = new Widget(_buttonPushTextured);
@Native private static final byte _buttonPushInset = _buttonPushTextured + 1;
public static final Widget BUTTON_PUSH_INSET = new Widget(_buttonPushInset);
@Native private static final byte _buttonPushInset2 = _buttonPushInset + 1;
public static final Widget BUTTON_PUSH_INSET2 = new Widget(_buttonPushInset2);
@Native private static final byte _buttonRadio = _buttonPushInset2 + 1;
public static final Widget BUTTON_RADIO = new Widget(_buttonRadio);
@Native private static final byte _buttonRound = _buttonRadio + 1;
public static final Widget BUTTON_ROUND = new Widget(_buttonRound);
@Native private static final byte _buttonRoundHelp = _buttonRound + 1;
public static final Widget BUTTON_ROUND_HELP = new Widget(_buttonRoundHelp);
@Native private static final byte _buttonRoundInset = _buttonRoundHelp + 1;
public static final Widget BUTTON_ROUND_INSET = new Widget(_buttonRoundInset);
@Native private static final byte _buttonRoundInset2 =_buttonRoundInset + 1;
public static final Widget BUTTON_ROUND_INSET2 = new Widget(_buttonRoundInset2);
@Native private static final byte _buttonSearchFieldCancel = _buttonRoundInset2 + 1;
public static final Widget BUTTON_SEARCH_FIELD_CANCEL = new Widget(_buttonSearchFieldCancel);
@Native private static final byte _buttonSearchFieldFind = _buttonSearchFieldCancel + 1;
public static final Widget BUTTON_SEARCH_FIELD_FIND = new Widget(_buttonSearchFieldFind);
@Native private static final byte _buttonSegmented = _buttonSearchFieldFind + 1;
public static final Widget BUTTON_SEGMENTED = new Widget(_buttonSegmented);
@Native private static final byte _buttonSegmentedInset = _buttonSegmented + 1;
public static final Widget BUTTON_SEGMENTED_INSET = new Widget(_buttonSegmentedInset);
@Native private static final byte _buttonSegmentedInset2 = _buttonSegmentedInset + 1;
public static final Widget BUTTON_SEGMENTED_INSET2 = new Widget(_buttonSegmentedInset2);
@Native private static final byte _buttonSegmentedSCurve = _buttonSegmentedInset2 + 1;
public static final Widget BUTTON_SEGMENTED_SCURVE = new Widget(_buttonSegmentedSCurve);
@Native private static final byte _buttonSegmentedTextured = _buttonSegmentedSCurve + 1;
public static final Widget BUTTON_SEGMENTED_TEXTURED = new Widget(_buttonSegmentedTextured);
@Native private static final byte _buttonSegmentedToolbar = _buttonSegmentedTextured + 1;
public static final Widget BUTTON_SEGMENTED_TOOLBAR = new Widget(_buttonSegmentedToolbar);
@Native private static final byte _dial = _buttonSegmentedToolbar + 1;
public static final Widget DIAL = new Widget(_dial);
@Native private static final byte _disclosureTriangle = _dial + 1;
public static final Widget DISCLOSURE_TRIANGLE = new Widget(_disclosureTriangle);
@Native private static final byte _dividerGrabber = _disclosureTriangle + 1;
public static final Widget DIVIDER_GRABBER = new Widget(_dividerGrabber);
@Native private static final byte _dividerSeparatorBar = _dividerGrabber + 1;
public static final Widget DIVIDER_SEPARATOR_BAR = new Widget(_dividerSeparatorBar);
@Native private static final byte _dividerSplitter = _dividerSeparatorBar + 1;
public static final Widget DIVIDER_SPLITTER = new Widget(_dividerSplitter);
@Native private static final byte _focus = _dividerSplitter + 1;
public static final Widget FOCUS = new Widget(_focus);
@Native private static final byte _frameGroupBox = _focus + 1;
public static final Widget FRAME_GROUP_BOX = new Widget(_frameGroupBox);
@Native private static final byte _frameGroupBoxSecondary = _frameGroupBox + 1;
public static final Widget FRAME_GROUP_BOX_SECONDARY = new Widget(_frameGroupBoxSecondary);
@Native private static final byte _frameListBox = _frameGroupBoxSecondary + 1;
public static final Widget FRAME_LIST_BOX = new Widget(_frameListBox);
@Native private static final byte _framePlacard = _frameListBox + 1;
public static final Widget FRAME_PLACARD = new Widget(_framePlacard);
@Native private static final byte _frameTextField = _framePlacard + 1;
public static final Widget FRAME_TEXT_FIELD = new Widget(_frameTextField);
@Native private static final byte _frameTextFieldRound = _frameTextField + 1;
public static final Widget FRAME_TEXT_FIELD_ROUND = new Widget(_frameTextFieldRound);
@Native private static final byte _frameWell = _frameTextFieldRound + 1;
public static final Widget FRAME_WELL = new Widget(_frameWell);
@Native private static final byte _growBox = _frameWell + 1;
public static final Widget GROW_BOX = new Widget(_growBox);
@Native private static final byte _growBoxTextured = _growBox + 1;
public static final Widget GROW_BOX_TEXTURED = new Widget(_growBoxTextured);
@Native private static final byte _gradient = _growBoxTextured + 1;
public static final Widget GRADIENT = new Widget(_gradient);
@Native private static final byte _menu = _gradient + 1;
public static final Widget MENU = new Widget(_menu);
@Native private static final byte _menuItem = _menu + 1;
public static final Widget MENU_ITEM = new Widget(_menuItem);
@Native private static final byte _menuBar = _menuItem + 1;
public static final Widget MENU_BAR = new Widget(_menuBar);
@Native private static final byte _menuTitle = _menuBar + 1;
public static final Widget MENU_TITLE = new Widget(_menuTitle);
@Native private static final byte _progressBar = _menuTitle + 1;
public static final Widget PROGRESS_BAR = new Widget(_progressBar);
@Native private static final byte _progressIndeterminateBar = _progressBar + 1;
public static final Widget PROGRESS_INDETERMINATE_BAR = new Widget(_progressIndeterminateBar);
@Native private static final byte _progressRelevance = _progressIndeterminateBar + 1;
public static final Widget PROGRESS_RELEVANCE = new Widget(_progressRelevance);
@Native private static final byte _progressSpinner = _progressRelevance + 1;
public static final Widget PROGRESS_SPINNER = new Widget(_progressSpinner);
@Native private static final byte _scrollBar = _progressSpinner + 1;
public static final Widget SCROLL_BAR = new Widget(_scrollBar);
@Native private static final byte _scrollColumnSizer = _scrollBar + 1;
public static final Widget SCROLL_COLUMN_SIZER = new Widget(_scrollColumnSizer);
@Native private static final byte _slider = _scrollColumnSizer + 1;
public static final Widget SLIDER = new Widget(_slider);
@Native private static final byte _sliderThumb = _slider + 1;
public static final Widget SLIDER_THUMB = new Widget(_sliderThumb);
@Native private static final byte _synchronization = _sliderThumb + 1;
public static final Widget SYNCHRONIZATION = new Widget(_synchronization);
@Native private static final byte _tab = _synchronization + 1;
public static final Widget TAB = new Widget(_tab);
@Native private static final byte _titleBarCloseBox = _tab + 1;
public static final Widget TITLE_BAR_CLOSE_BOX = new Widget(_titleBarCloseBox);
@Native private static final byte _titleBarCollapseBox = _titleBarCloseBox + 1;
public static final Widget TITLE_BAR_COLLAPSE_BOX = new Widget(_titleBarCollapseBox);
@Native private static final byte _titleBarZoomBox = _titleBarCollapseBox + 1;
public static final Widget TITLE_BAR_ZOOM_BOX = new Widget(_titleBarZoomBox);
@Native private static final byte _titleBarToolbarButton = _titleBarZoomBox + 1;
public static final Widget TITLE_BAR_TOOLBAR_BUTTON = new Widget(_titleBarToolbarButton);
@Native private static final byte _toolbarItemWell = _titleBarToolbarButton + 1;
public static final Widget TOOLBAR_ITEM_WELL = new Widget(_toolbarItemWell);
@Native private static final byte _windowFrame = _toolbarItemWell + 1;
public static final Widget WINDOW_FRAME = new Widget(_windowFrame);
}
public static class Hit {
@Native private static final int _unknown = -1;
public static final Hit UNKNOWN = new Hit(_unknown);
@Native private static final int _none = 0;
public static final Hit NONE = new Hit(_none);
@Native private static final int _hit = 1;
public static final Hit HIT = new Hit(_hit);
final int hit;
Hit(final int hit) { this.hit = hit; }
public boolean isHit() {
return hit > 0;
}
private String getConstantName(Hit hit) {
if (hit == UNKNOWN) {
return "UNKNOWN";
} else if (hit == NONE) {
return "NONE";
} else if (hit == HIT) {
return "HIT";
}
return getClass().getSimpleName();
}
@Override
public String toString() {
return getConstantName(this);
}
}
public static final class ScrollBarHit extends Hit {
@Native private static final int _thumb = 2;
public static final ScrollBarHit THUMB = new ScrollBarHit(_thumb);
@Native private static final int _trackMin = 3;
public static final ScrollBarHit TRACK_MIN = new ScrollBarHit(_trackMin);
@Native private static final int _trackMax = 4;
public static final ScrollBarHit TRACK_MAX = new ScrollBarHit(_trackMax);
@Native private static final int _arrowMin = 5;
public static final ScrollBarHit ARROW_MIN = new ScrollBarHit(_arrowMin);
@Native private static final int _arrowMax = 6;
public static final ScrollBarHit ARROW_MAX = new ScrollBarHit(_arrowMax);
@Native private static final int _arrowMaxInside = 7;
public static final ScrollBarHit ARROW_MAX_INSIDE = new ScrollBarHit(_arrowMaxInside);
@Native private static final int _arrowMinInside = 8;
public static final ScrollBarHit ARROW_MIN_INSIDE = new ScrollBarHit(_arrowMinInside);
ScrollBarHit(final int hit) { super(hit); }
}
static Hit getHit(final int hit) {
switch (hit) {
case Hit._none:
return Hit.NONE;
case Hit._hit:
return Hit.HIT;
case ScrollBarHit._thumb:
return ScrollBarHit.THUMB;
case ScrollBarHit._trackMin:
return ScrollBarHit.TRACK_MIN;
case ScrollBarHit._trackMax:
return ScrollBarHit.TRACK_MAX;
case ScrollBarHit._arrowMin:
return ScrollBarHit.ARROW_MIN;
case ScrollBarHit._arrowMax:
return ScrollBarHit.ARROW_MAX;
case ScrollBarHit._arrowMaxInside:
return ScrollBarHit.ARROW_MAX_INSIDE;
case ScrollBarHit._arrowMinInside:
return ScrollBarHit.ARROW_MIN_INSIDE;
}
return Hit.UNKNOWN;
}
}

View file

@ -0,0 +1,365 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package apple.laf;
import java.nio.*;
import java.util.*;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
import apple.laf.JRSUIConstants.*;
public final class JRSUIControl {
private static native int initNativeJRSUI();
private static native long getPtrOfBuffer(ByteBuffer byteBuffer);
private static native long getCFDictionary(boolean flipped);
private static native void disposeCFDictionary(long cfDictionaryPtr);
private static native int syncChanges(long cfDictionaryPtr, long byteBufferPtr);
// private static native int paint(long cfDictionaryPtr, long oldProperties, long newProperties, OSXSurfaceData osxsd, double x, double y, double w, double h);
// private static native int paintChanges(long cfDictionaryPtr, long byteBufferPtr, long oldProperties, long newProperties, OSXSurfaceData osxsd, double x, double y, double w, double h);
private static native int paintToCGContext (long cgContext, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h);
private static native int paintChangesToCGContext (long cgContext, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr);
private static native int paintImage (int[] data, int imgW, int imgH, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h);
private static native int paintChangesImage (int[] data, int imgW, int imgH, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, long byteBufferPtr);
private static native int getNativeHitPart( long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, double hitX, double hitY);
private static native void getNativePartBounds(final double[] rect, long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int part);
private static native double getNativeScrollBarOffsetChange( long cfDictionaryPtr, long oldProperties, long newProperties, double x, double y, double w, double h, int offset, int visibleAmount, int extent);
private static final int INCOHERENT = 2;
private static final int NOT_INIT = 1;
private static final int SUCCESS = 0;
private static final int NULL_PTR = -1;
private static final int NULL_CG_REF = -2;
private static int nativeJRSInitialized = NOT_INIT;
public static void initJRSUI() {
if (nativeJRSInitialized == SUCCESS) return;
nativeJRSInitialized = initNativeJRSUI();
if (nativeJRSInitialized != SUCCESS) throw new RuntimeException("JRSUI could not be initialized (" + nativeJRSInitialized + ").");
}
private static final int NIO_BUFFER_SIZE = 128;
private static final class ThreadLocalByteBuffer {
final ByteBuffer buffer;
final long ptr;
public ThreadLocalByteBuffer() {
buffer = ByteBuffer.allocateDirect(NIO_BUFFER_SIZE);
buffer.order(ByteOrder.nativeOrder());
ptr = getPtrOfBuffer(buffer);
}
}
private static final ThreadLocal<ThreadLocalByteBuffer> threadLocal = new ThreadLocal<ThreadLocalByteBuffer>();
private static ThreadLocalByteBuffer getThreadLocalBuffer() {
ThreadLocalByteBuffer byteBuffer = threadLocal.get();
if (byteBuffer != null) return byteBuffer;
byteBuffer = new ThreadLocalByteBuffer();
threadLocal.set(byteBuffer);
return byteBuffer;
}
private final HashMap<Key, DoubleValue> nativeMap;
private final HashMap<Key, DoubleValue> changes;
private final long cfDictionaryPtr;
private final Object disposerReferent = new Object();
private long priorEncodedProperties;
private long currentEncodedProperties;
private final boolean flipped;
public JRSUIControl(final boolean flipped){
this.flipped = flipped;
cfDictionaryPtr = getCFDictionary(flipped);
if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation");
Disposer.addRecord(disposerReferent, new JRSUIControlDisposerRecord(cfDictionaryPtr));
nativeMap = new HashMap<Key, DoubleValue>();
changes = new HashMap<Key, DoubleValue>();
}
JRSUIControl(final JRSUIControl other) {
flipped = other.flipped;
cfDictionaryPtr = getCFDictionary(flipped);
if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation");
Disposer.addRecord(disposerReferent, new JRSUIControlDisposerRecord(cfDictionaryPtr));
nativeMap = new HashMap<Key, DoubleValue>();
changes = new HashMap<Key, DoubleValue>(other.nativeMap);
changes.putAll(other.changes);
}
private static class JRSUIControlDisposerRecord implements DisposerRecord {
private final long cfDictionaryPtr;
JRSUIControlDisposerRecord(long ptr) {
cfDictionaryPtr = ptr;
}
public void dispose() {
try {
disposeCFDictionary(cfDictionaryPtr);
} catch (Throwable t) {
}
}
}
enum BufferState {
NO_CHANGE,
ALL_CHANGES_IN_BUFFER,
SOME_CHANGES_IN_BUFFER,
CHANGE_WONT_FIT_IN_BUFFER
}
private BufferState loadBufferWithChanges(final ThreadLocalByteBuffer localByteBuffer) {
final ByteBuffer buffer = localByteBuffer.buffer;
buffer.rewind();
for (final JRSUIConstants.Key key : new HashSet<JRSUIConstants.Key>(changes.keySet())) {
final int changeIndex = buffer.position();
final JRSUIConstants.DoubleValue value = changes.get(key);
try {
buffer.putLong(key.getConstantPtr());
buffer.put(value.getTypeCode());
value.putValueInBuffer(buffer);
} catch (final BufferOverflowException e) {
return handleBufferOverflow(buffer, changeIndex);
} catch (final RuntimeException e) {
System.err.println(this);
throw e;
}
if (buffer.position() >= NIO_BUFFER_SIZE - 8) {
return handleBufferOverflow(buffer, changeIndex);
}
changes.remove(key);
nativeMap.put(key, value);
}
buffer.putLong(0);
return BufferState.ALL_CHANGES_IN_BUFFER;
}
private BufferState handleBufferOverflow(final ByteBuffer buffer, final int changeIndex) {
if (changeIndex == 0) {
buffer.putLong(0, 0);
return BufferState.CHANGE_WONT_FIT_IN_BUFFER;
}
buffer.putLong(changeIndex, 0);
return BufferState.SOME_CHANGES_IN_BUFFER;
}
private synchronized void set(final JRSUIConstants.Key key, final JRSUIConstants.DoubleValue value) {
final JRSUIConstants.DoubleValue existingValue = nativeMap.get(key);
if (existingValue != null && existingValue.equals(value)) {
changes.remove(key);
return;
}
changes.put(key, value);
}
public void set(final JRSUIState state) {
state.apply(this);
}
void setEncodedState(final long state) {
currentEncodedProperties = state;
}
void set(final JRSUIConstants.Key key, final double value) {
set(key, new JRSUIConstants.DoubleValue(value));
}
// private static final Color blue = new Color(0x00, 0x00, 0xFF, 0x40);
// private static void paintDebug(Graphics2D g, double x, double y, double w, double h) {
// final Color prev = g.getColor();
// g.setColor(blue);
// g.drawRect((int)x, (int)y, (int)w, (int)h);
// g.setColor(prev);
// }
// private static int paintsWithNoChange = 0;
// private static int paintsWithChangesThatFit = 0;
// private static int paintsWithChangesThatOverflowed = 0;
public void paint(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h) {
paintImage(data, imgW, imgH, x, y, w, h);
priorEncodedProperties = currentEncodedProperties;
}
private synchronized int paintImage(final int[] data, final int imgW, final int imgH, final double x, final double y, final double w, final double h) {
if (changes.isEmpty()) {
// paintsWithNoChange++;
return paintImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h);
}
final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
BufferState bufferState = loadBufferWithChanges(localByteBuffer);
// fast tracking this, since it's the likely scenario
if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
// paintsWithChangesThatFit++;
return paintChangesImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
}
while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
bufferState = loadBufferWithChanges(localByteBuffer);
}
if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
}
// implicitly ALL_CHANGES_IN_BUFFER, now that we sync'd the buffer down to native a few times
// paintsWithChangesThatOverflowed++;
return paintChangesImage(data, imgW, imgH, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
}
public void paint(final long cgContext, final double x, final double y, final double w, final double h) {
paintToCGContext(cgContext, x, y, w, h);
priorEncodedProperties = currentEncodedProperties;
}
private synchronized int paintToCGContext(final long cgContext, final double x, final double y, final double w, final double h) {
if (changes.isEmpty()) {
// paintsWithNoChange++;
return paintToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h);
}
final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
BufferState bufferState = loadBufferWithChanges(localByteBuffer);
// fast tracking this, since it's the likely scenario
if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
// paintsWithChangesThatFit++;
return paintChangesToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
}
while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
bufferState = loadBufferWithChanges(localByteBuffer);
}
if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
}
// implicitly ALL_CHANGES_IN_BUFFER, now that we sync'd the buffer down to native a few times
// paintsWithChangesThatOverflowed++;
return paintChangesToCGContext(cgContext, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, localByteBuffer.ptr);
}
Hit getHitForPoint(final int x, final int y, final int w, final int h, final int hitX, final int hitY) {
sync();
// reflect hitY about the midline of the control before sending to native
final Hit hit = JRSUIConstants.getHit(getNativeHitPart(cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, hitX, 2 * y + h - hitY));
priorEncodedProperties = currentEncodedProperties;
return hit;
}
void getPartBounds(final double[] rect, final int x, final int y, final int w, final int h, final int part) {
if (rect == null) throw new NullPointerException("Cannot load null rect");
if (rect.length != 4) throw new IllegalArgumentException("Rect must have four elements");
sync();
getNativePartBounds(rect, cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, part);
priorEncodedProperties = currentEncodedProperties;
}
double getScrollBarOffsetChange(final int x, final int y, final int w, final int h, final int offset, final int visibleAmount, final int extent) {
sync();
final double offsetChange = getNativeScrollBarOffsetChange(cfDictionaryPtr, priorEncodedProperties, currentEncodedProperties, x, y, w, h, offset, visibleAmount, extent);
priorEncodedProperties = currentEncodedProperties;
return offsetChange;
}
private void sync() {
if (changes.isEmpty()) return;
final ThreadLocalByteBuffer localByteBuffer = getThreadLocalBuffer();
BufferState bufferState = loadBufferWithChanges(localByteBuffer);
if (bufferState == BufferState.ALL_CHANGES_IN_BUFFER) {
final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
return;
}
while (bufferState == BufferState.SOME_CHANGES_IN_BUFFER) {
final int status = syncChanges(cfDictionaryPtr, localByteBuffer.ptr);
if (status != SUCCESS) throw new RuntimeException("JRSUI failed to sync changes into the native buffer: " + this);
bufferState = loadBufferWithChanges(localByteBuffer);
}
if (bufferState == BufferState.CHANGE_WONT_FIT_IN_BUFFER) {
throw new RuntimeException("JRSUI failed to sync changes to the native buffer, because some change was too big: " + this);
}
}
@Override
public int hashCode() {
int bits = Long.hashCode(currentEncodedProperties);
bits ^= nativeMap.hashCode();
bits ^= changes.hashCode();
return bits;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof JRSUIControl)) return false;
final JRSUIControl other = (JRSUIControl)obj;
if (currentEncodedProperties != other.currentEncodedProperties) return false;
if (!nativeMap.equals(other.nativeMap)) return false;
if (!changes.equals(other.changes)) return false;
return true;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder("JRSUIControl[inNative:");
builder.append(Arrays.toString(nativeMap.entrySet().toArray()));
builder.append(", changes:");
builder.append(Arrays.toString(changes.entrySet().toArray()));
builder.append("]");
return builder.toString();
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package apple.laf;
public final class JRSUIFocus {
private static final int SUCCESS = 0;
private static final int NULL_PTR = -1;
private static final int NULL_CG_REF = -2;
// from HITheme.h
public static final int RING_ONLY = 0;
public static final int RING_ABOVE = 1;
public static final int RING_BELOW = 2;
private static native int beginNativeFocus(final long cgContext, final int ringStyle);
private static native int endNativeFocus(final long cgContext);
final long cgContext;
public JRSUIFocus(final long cgContext) {
this.cgContext = cgContext;
}
public void beginFocus(final int ringStyle) {
testForFailure(beginNativeFocus(cgContext, ringStyle));
}
public void endFocus() {
testForFailure(endNativeFocus(cgContext));
}
static void testForFailure(final int status) {
if (status == SUCCESS) return;
switch(status) {
case NULL_PTR: throw new RuntimeException("Null pointer exception in native JRSUI");
case NULL_CG_REF: throw new RuntimeException("Null CG reference in native JRSUI");
default: throw new RuntimeException("JRSUI draw focus problem: " + status);
}
}
}

View file

@ -0,0 +1,263 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package apple.laf;
import apple.laf.JRSUIConstants.*;
@SuppressWarnings("unchecked")
public class JRSUIState {
// static HashSet<JRSUIState> states = new HashSet<JRSUIState>();
final long encodedState;
long derivedEncodedState;
static JRSUIState prototype = new JRSUIState(0);
public static JRSUIState getInstance() {
return prototype.derive();
}
JRSUIState(final Widget widget) {
this(widget.apply(0));
}
JRSUIState(final long encodedState) {
this.encodedState = derivedEncodedState = encodedState;
}
boolean isDerivationSame() {
return encodedState == derivedEncodedState;
}
public <T extends JRSUIState> T derive() {
if (isDerivationSame()) return (T)this;
final T derivation = (T)createDerivation();
// if (!states.add(derivation)) {
// System.out.println("dupe: " + states.size());
// }
return derivation;
}
public <T extends JRSUIState> T createDerivation() {
return (T)new JRSUIState(derivedEncodedState);
}
public void reset() {
derivedEncodedState = encodedState;
}
public void set(final Property property) {
derivedEncodedState = property.apply(derivedEncodedState);
}
public void apply(final JRSUIControl control) {
control.setEncodedState(encodedState);
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof JRSUIState)) return false;
return encodedState == ((JRSUIState)obj).encodedState && getClass().equals(obj.getClass());
}
public boolean is(Property property) {
return (byte)((derivedEncodedState & property.encoding.mask) >> property.encoding.shift) == property.ordinal;
}
@Override
public int hashCode() {
return Long.hashCode(encodedState) ^ getClass().hashCode();
}
public static final class AnimationFrameState extends JRSUIState {
final int animationFrame;
int derivedAnimationFrame;
AnimationFrameState(final long encodedState, final int animationFrame) {
super(encodedState);
this.animationFrame = derivedAnimationFrame = animationFrame;
}
@Override
boolean isDerivationSame() {
return super.isDerivationSame() && (animationFrame == derivedAnimationFrame);
}
@Override
public <T extends JRSUIState> T createDerivation() {
return (T)new AnimationFrameState(derivedEncodedState, derivedAnimationFrame);
}
@Override
public void reset() {
super.reset();
derivedAnimationFrame = animationFrame;
}
public void setAnimationFrame(final int frame) {
this.derivedAnimationFrame = frame;
}
@Override
public void apply(final JRSUIControl control) {
super.apply(control);
control.set(Key.ANIMATION_FRAME, animationFrame);
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof AnimationFrameState)) return false;
return animationFrame == ((AnimationFrameState)obj).animationFrame && super.equals(obj);
}
@Override
public int hashCode() {
return super.hashCode() ^ animationFrame;
}
}
public static class ValueState extends JRSUIState {
final double value;
double derivedValue;
ValueState(final long encodedState, final double value) {
super(encodedState);
this.value = derivedValue = value;
}
@Override
boolean isDerivationSame() {
return super.isDerivationSame() && (value == derivedValue);
}
@Override
public <T extends JRSUIState> T createDerivation() {
return (T)new ValueState(derivedEncodedState, derivedValue);
}
@Override
public void reset() {
super.reset();
derivedValue = value;
}
public void setValue(final double value) {
derivedValue = value;
}
@Override
public void apply(final JRSUIControl control) {
super.apply(control);
control.set(Key.VALUE, value);
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof ValueState)) return false;
return value == ((ValueState)obj).value && super.equals(obj);
}
@Override
public int hashCode() {
return super.hashCode() ^ Double.hashCode(value);
}
}
public static final class TitleBarHeightState extends ValueState {
TitleBarHeightState(final long encodedState, final double value) {
super(encodedState, value);
}
@Override
public <T extends JRSUIState> T createDerivation() {
return (T)new TitleBarHeightState(derivedEncodedState, derivedValue);
}
@Override
public void apply(final JRSUIControl control) {
super.apply(control);
control.set(Key.WINDOW_TITLE_BAR_HEIGHT, value);
}
}
public static final class ScrollBarState extends ValueState {
final double thumbProportion;
double derivedThumbProportion;
final double thumbStart;
double derivedThumbStart;
ScrollBarState(final long encodedState, final double value, final double thumbProportion, final double thumbStart) {
super(encodedState, value);
this.thumbProportion = derivedThumbProportion = thumbProportion;
this.thumbStart = derivedThumbStart = thumbStart;
}
@Override
boolean isDerivationSame() {
return super.isDerivationSame() && (thumbProportion == derivedThumbProportion) && (thumbStart == derivedThumbStart);
}
@Override
public <T extends JRSUIState> T createDerivation() {
return (T)new ScrollBarState(derivedEncodedState, derivedValue, derivedThumbProportion, derivedThumbStart);
}
@Override
public void reset() {
super.reset();
derivedThumbProportion = thumbProportion;
derivedThumbStart = thumbStart;
}
public void setThumbPercent(final double thumbPercent) {
derivedThumbProportion = thumbPercent;
}
public void setThumbStart(final double thumbStart) {
derivedThumbStart = thumbStart;
}
@Override
public void apply(final JRSUIControl control) {
super.apply(control);
control.set(Key.THUMB_PROPORTION, thumbProportion);
control.set(Key.THUMB_START, thumbStart);
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof ScrollBarState)) return false;
return (thumbProportion == ((ScrollBarState)obj).thumbProportion) && (thumbStart == ((ScrollBarState)obj).thumbStart) && super.equals(obj);
}
@Override
public int hashCode() {
final long bits = Double.doubleToRawLongBits(thumbProportion) ^ Double.doubleToRawLongBits(thumbStart);
return super.hashCode() ^ Long.hashCode(bits);
}
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package apple.laf;
import apple.laf.JRSUIConstants.*;
import apple.laf.JRSUIState.*;
public final class JRSUIStateFactory {
public static JRSUIState getSliderTrack() {
return new JRSUIState(Widget.SLIDER.apply(NoIndicator.YES.apply(0)));
}
public static JRSUIState getSliderThumb() {
return new JRSUIState(Widget.SLIDER_THUMB.apply(0));
}
public static JRSUIState getSpinnerArrows() {
return new JRSUIState(Widget.BUTTON_LITTLE_ARROWS.apply(0));
}
public static JRSUIState getSplitPaneDivider() {
return new JRSUIState(Widget.DIVIDER_SPLITTER.apply(0));
}
public static JRSUIState getTab() {
return new JRSUIState(Widget.TAB.apply(SegmentTrailingSeparator.YES.apply(0)));
}
public static AnimationFrameState getDisclosureTriangle() {
return new AnimationFrameState(Widget.DISCLOSURE_TRIANGLE.apply(0), 0);
}
public static ScrollBarState getScrollBar() {
return new ScrollBarState(Widget.SCROLL_BAR.apply(0), 0, 0, 0);
}
public static TitleBarHeightState getTitleBar() {
return new TitleBarHeightState(Widget.WINDOW_FRAME.apply(0), 0);
}
public static ValueState getProgressBar() {
return new ValueState(0, 0);
}
public static ValueState getLabeledButton() {
return new ValueState(0, 0);
}
}

View file

@ -0,0 +1,158 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package apple.laf;
import apple.laf.JRSUIConstants.Hit;
import apple.laf.JRSUIConstants.ScrollBarPart;
import com.apple.laf.AquaImageFactory.NineSliceMetrics;
public final class JRSUIUtils {
static boolean isLeopard = isMacOSXLeopard();
static boolean isSnowLeopardOrBelow = isMacOSXSnowLeopardOrBelow();
public static boolean isMacOSXBigSurOrAbove() {
return currentMacOSXVersionMatchesGivenVersionRange(10, 16, true,
false, true);
}
static boolean isMacOSXLeopard() {
return isCurrentMacOSXVersion(5);
}
static boolean isMacOSXSnowLeopardOrBelow() {
return currentMacOSXVersionMatchesGivenVersionRange(10, 6, true,
true, false);
}
static boolean isCurrentMacOSXVersion(final int version) {
return isCurrentMacOSXVersion(10, version);
}
static boolean isCurrentMacOSXVersion(final int major, final int minor) {
return currentMacOSXVersionMatchesGivenVersionRange(major, minor, true, false, false);
}
static boolean currentMacOSXVersionMatchesGivenVersionRange(
final int version, final boolean inclusive,
final boolean matchBelow, final boolean matchAbove) {
return currentMacOSXVersionMatchesGivenVersionRange(10, version, inclusive, matchBelow, matchAbove);
}
static boolean currentMacOSXVersionMatchesGivenVersionRange(
final int majorVersion, final int minorVersion, final boolean inclusive,
final boolean matchBelow, final boolean matchAbove) {
// split the "x.y.z" version number
String osVersion = System.getProperty("os.version");
String[] fragments = osVersion.split("\\.");
if (fragments.length < 2) return false;
// check if os.version matches the given version using the given match method
try {
int majorVers = Integer.parseInt(fragments[0]);
int minorVers = Integer.parseInt(fragments[1]);
if (inclusive && majorVers == majorVersion && minorVers == minorVersion) return true;
if (matchBelow &&
(majorVers < majorVersion ||
(majorVers == majorVersion && minorVers < minorVersion))) return true;
if (matchAbove &&
(majorVers > majorVersion ||
(majorVers == majorVersion && minorVers > minorVersion))) return true;
} catch (NumberFormatException e) {
// was not an integer
}
return false;
}
public static final class TabbedPane {
public static boolean useLegacyTabs() {
return isLeopard;
}
public static boolean shouldUseTabbedPaneContrastUI() {
return !isSnowLeopardOrBelow;
}
}
public static final class InternalFrame {
public static boolean shouldUseLegacyBorderMetrics() {
return isSnowLeopardOrBelow;
}
}
public static final class Tree {
public static boolean useLegacyTreeKnobs() {
return isLeopard;
}
}
public static final class ScrollBar {
private static native boolean shouldUseScrollToClick();
public static boolean useScrollToClick() {
return shouldUseScrollToClick();
}
public static void getPartBounds(final double[] rect,
final JRSUIControl control,
final int x, final int y, final int w,
final int h,
final ScrollBarPart part) {
control.getPartBounds(rect, x, y, w, h, part.ordinal);
}
public static double getNativeOffsetChange(final JRSUIControl control,
final int x, final int y,
final int w, final int h,
final int offset,
final int visibleAmount,
final int extent) {
return control.getScrollBarOffsetChange(x, y, w, h, offset,
visibleAmount, extent);
}
}
public static final class Images {
public static boolean shouldUseLegacySecurityUIPath() {
return isSnowLeopardOrBelow;
}
}
public static final class HitDetection {
public static Hit getHitForPoint(final JRSUIControl control,
final int x, final int y, final int w,
final int h, final int hitX,
final int hitY) {
return control.getHitForPoint(x, y, w, h, hitX, hitY);
}
}
public interface NineSliceMetricsProvider {
public NineSliceMetrics getNineSliceMetricsForState(JRSUIState state);
}
}

View file

@ -0,0 +1,420 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import java.awt.Image;
import java.awt.PopupMenu;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.desktop.AboutHandler;
import java.awt.desktop.AppForegroundListener;
import java.awt.desktop.AppHiddenListener;
import java.awt.desktop.AppReopenedListener;
import java.awt.desktop.OpenFilesEvent;
import java.awt.desktop.OpenFilesHandler;
import java.awt.desktop.OpenURIEvent;
import java.awt.desktop.OpenURIHandler;
import java.awt.desktop.PreferencesHandler;
import java.awt.desktop.PrintFilesEvent;
import java.awt.desktop.PrintFilesHandler;
import java.awt.desktop.QuitHandler;
import java.awt.desktop.QuitResponse;
import java.awt.desktop.QuitStrategy;
import java.awt.desktop.ScreenSleepListener;
import java.awt.desktop.SystemEventListener;
import java.awt.desktop.SystemSleepListener;
import java.awt.desktop.UserSessionListener;
import java.beans.Beans;
import javax.swing.JMenuBar;
import sun.awt.AWTAccessor;
import sun.lwawt.LWWindowPeer;
import sun.lwawt.macosx.CPlatformWindow;
/**
* The {@code Application} class allows you to integrate your Java application with the native Mac OS X environment.
* You can provide your Mac OS X users a greatly enhanced experience by implementing a few basic handlers for standard system events.
*
* For example:
* <ul>
* <li>Open an about dialog when a user chooses About from the application menu.</li>
* <li>Open a preferences window when the users chooses Preferences from the application menu.</li>
* <li>Create a new document when the user clicks on your Dock icon, and no windows are open.</li>
* <li>Open a document that the user double-clicked on in the Finder.</li>
* <li>Open a custom URL scheme when a user clicks on link in a web browser.</li>
* <li>Reconnect to network services after the system has awoke from sleep.</li>
* <li>Cleanly shutdown your application when the user chooses Quit from the application menu, Dock icon, or types Command-Q.</li>
* <li>Cancel shutdown/logout if the user has unsaved changes in your application.</li>
* </ul>
*
* @since 1.4
*/
public final class Application {
private static native void nativeInitializeApplicationDelegate();
private static native void nativeInstallOpenURLEventHandler();
static Application sApplication = null;
static {
Toolkit.getDefaultToolkit(); // Start AppKit
if (!Beans.isDesignTime()) {
nativeInitializeApplicationDelegate();
}
sApplication = new Application();
}
/**
* @return the singleton representing this Mac OS X Application
*
* @since 1.4
*/
public static Application getApplication() {
return sApplication;
}
// some singletons, since they get called back into from native
final _AppEventHandler eventHandler = _AppEventHandler.getInstance();
final _AppMenuBarHandler menuBarHandler = _AppMenuBarHandler.getInstance();
final _AppDockIconHandler iconHandler = new _AppDockIconHandler();
/**
* Creates an Application instance. Should only be used in JavaBean environments.
* @deprecated use {@link #getApplication()}
*
* @since 1.4
*/
@Deprecated
public Application() {
}
/**
* Adds sub-types of {@link SystemEventListener} to listen for notifications from the native Mac OS X system.
*
* @see AppForegroundListener
* @see AppHiddenListener
* @see AppReopenedListener
* @see ScreenSleepListener
* @see SystemSleepListener
* @see UserSessionListener
*
* @param listener
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void addAppEventListener(final SystemEventListener listener) {
eventHandler.addListener(listener);
}
/**
* Removes sub-types of {@link SystemEventListener} from listening for notifications from the native Mac OS X system.
*
* @see AppForegroundListener
* @see AppHiddenListener
* @see AppReopenedListener
* @see ScreenSleepListener
* @see SystemSleepListener
* @see UserSessionListener
*
* @param listener
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void removeAppEventListener(final SystemEventListener listener) {
eventHandler.removeListener(listener);
}
/**
* Installs a handler to show a custom About window for your application.
*
* Setting the {@link AboutHandler} to {@code null} reverts it to the default Cocoa About window.
*
* @param aboutHandler the handler to respond to the {@link AboutHandler#handleAbout} message
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void setAboutHandler(final AboutHandler aboutHandler) {
eventHandler.aboutDispatcher.setHandler(aboutHandler);
}
/**
* Installs a handler to create the Preferences menu item in your application's app menu.
*
* Setting the {@link PreferencesHandler} to {@code null} will remove the Preferences item from the app menu.
*
* @param preferencesHandler
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void setPreferencesHandler(final PreferencesHandler preferencesHandler) {
eventHandler.preferencesDispatcher.setHandler(preferencesHandler);
}
/**
* Installs the handler which is notified when the application is asked to open a list of files.
* The {@link OpenFilesHandler#openFiles(OpenFilesEvent)} notifications are only sent if the Java app is a bundled application, with a {@code CFBundleDocumentTypes} array present in it's Info.plist.
* See the <a href="http://developer.apple.com/mac/library/documentation/General/Reference/InfoPlistKeyReference">Info.plist Key Reference</a> for more information about adding a {@code CFBundleDocumentTypes} key to your app's Info.plist.
*
* @param openFileHandler
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void setOpenFileHandler(final OpenFilesHandler openFileHandler) {
eventHandler.openFilesDispatcher.setHandler(openFileHandler);
}
/**
* Installs the handler which is notified when the application is asked to print a list of files.
* The {@link PrintFilesHandler#printFiles(PrintFilesEvent)} notifications are only sent if the Java app is a bundled application, with a {@code CFBundleDocumentTypes} array present in it's Info.plist.
* See the <a href="http://developer.apple.com/mac/library/documentation/General/Reference/InfoPlistKeyReference">Info.plist Key Reference</a> for more information about adding a {@code CFBundleDocumentTypes} key to your app's Info.plist.
*
* @param printFileHandler
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void setPrintFileHandler(final PrintFilesHandler printFileHandler) {
eventHandler.printFilesDispatcher.setHandler(printFileHandler);
}
/**
* Installs the handler which is notified when the application is asked to open a URL.
* The {@link OpenURIHandler#openURI(OpenURIEvent)} notifications are only sent if the Java app is a bundled application, with a {@code CFBundleURLTypes} array present in it's Info.plist.
* See the <a href="http://developer.apple.com/mac/library/documentation/General/Reference/InfoPlistKeyReference">Info.plist Key Reference</a> for more information about adding a {@code CFBundleURLTypes} key to your app's Info.plist.
*
* Setting the handler to {@code null} causes all {@link OpenURIHandler#openURI(OpenURIEvent)} requests to be enqueued until another handler is set.
*
* @param openURIHandler
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void setOpenURIHandler(final OpenURIHandler openURIHandler) {
nativeInstallOpenURLEventHandler();
eventHandler.openURIDispatcher.setHandler(openURIHandler);
}
/**
* Installs the handler which determines if the application should quit.
* The handler is passed a one-shot {@link QuitResponse} which can cancel or proceed with the quit.
* Setting the handler to {@code null} causes all quit requests to directly perform the default {@link QuitStrategy}.
*
* @param quitHandler the handler that is called when the application is asked to quit
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void setQuitHandler(final QuitHandler quitHandler) {
eventHandler.quitDispatcher.setHandler(quitHandler);
}
/**
* Sets the default strategy used to quit this application. The default is calling SYSTEM_EXIT_0.
*
* The quit strategy can also be set with the "apple.eawt.quitStrategy" system property.
*
* @param strategy the way this application should be shutdown
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void setQuitStrategy(final QuitStrategy strategy) {
eventHandler.setDefaultQuitStrategy(strategy);
}
/**
* Enables this application to be suddenly terminated.
*
* Call this method to indicate your application's state is saved, and requires no notification to be terminated.
* Letting your application remain terminatable improves the user experience by avoiding re-paging in your application when it's asked to quit.
*
* <b>Note: enabling sudden termination will allow your application to be quit without notifying your QuitHandler, or running any shutdown hooks.</b>
* User initiated Cmd-Q, logout, restart, or shutdown requests will effectively "kill -KILL" your application.
*
* This call has no effect on Mac OS X versions prior to 10.6.
*
* @see <a href="http://developer.apple.com/mac/library/documentation/cocoa/reference/foundation/Classes/NSProcessInfo_Class">NSProcessInfo class references</a> for more information about Sudden Termination.
* @see Application#disableSuddenTermination()
*
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void enableSuddenTermination() {
_AppMiscHandlers.enableSuddenTermination();
}
/**
* Prevents this application from being suddenly terminated.
*
* Call this method to indicate that your application has unsaved state, and may not be terminated without notification.
*
* This call has no effect on Mac OS X versions prior to 10.6.
*
* @see <a href="http://developer.apple.com/mac/library/documentation/cocoa/reference/foundation/Classes/NSProcessInfo_Class">NSProcessInfo class references</a> for more information about Sudden Termination.
* @see Application#enableSuddenTermination()
*
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public void disableSuddenTermination() {
_AppMiscHandlers.disableSuddenTermination();
}
/**
* Requests this application to move to the foreground.
*
* @param allWindows if all windows of this application should be moved to the foreground, or only the foremost one
*
* @since Java for Mac OS X 10.6 Update 1
* @since Java for Mac OS X 10.5 Update 6 - 1.6, 1.5
*/
public void requestForeground(final boolean allWindows) {
_AppMiscHandlers.requestActivation(allWindows);
}
/**
* Requests user attention to this application (usually through bouncing the Dock icon). Critical
* requests will continue to bounce the Dock icon until the app is activated. An already active
* application requesting attention does nothing.
*
* @param critical if this is an important request
*
* @since Java for Mac OS X 10.6 Update 1
* @since Java for Mac OS X 10.5 Update 6 - 1.6, 1.5
*/
public void requestUserAttention(final boolean critical) {
_AppMiscHandlers.requestUserAttention(critical);
}
/**
* Opens the native help viewer application if a Help Book has been added to the
* application bundler and registered in the Info.plist with CFBundleHelpBookFolder.
*
* See http://developer.apple.com/qa/qa2001/qa1022.html for more information.
*
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public void openHelpViewer() {
_AppMiscHandlers.openHelpViewer();
}
/**
* Attaches the contents of the provided PopupMenu to the application's Dock icon.
*
* @param menu the PopupMenu to attach to this application's Dock icon
*
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public void setDockMenu(final PopupMenu menu) {
iconHandler.setDockMenu(menu);
}
/**
* @return the PopupMenu used to add items to this application's Dock icon
*
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public PopupMenu getDockMenu() {
return iconHandler.getDockMenu();
}
/**
* Changes this application's Dock icon to the provided image.
*
* @param image
*
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public void setDockIconImage(final Image image) {
iconHandler.setDockIconImage(image);
}
/**
* Obtains an image of this application's Dock icon.
*
* @return an image of this application's Dock icon
*
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public Image getDockIconImage() {
return iconHandler.getDockIconImage();
}
/**
* Affixes a small system provided badge to this application's Dock icon. Usually a number.
*
* @param badge textual label to affix to the Dock icon
*
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public void setDockIconBadge(final String badge) {
iconHandler.setDockIconBadge(badge);
}
/**
* Displays a progress bar to this application's Dock icon.
* Acceptable values are from 0 to 100, any other disables progress indication.
*
* @param value progress value
*/
public void setDockIconProgress(final int value) {
iconHandler.setDockIconProgress(value);
}
/**
* Sets the default menu bar to use when there are no active frames.
* Only used when the system property "apple.laf.useScreenMenuBar" is "true", and
* the Aqua Look and Feel is active.
*
* @param menuBar to use when no other frames are active
*
* @since Java for Mac OS X 10.6 Update 1
* @since Java for Mac OS X 10.5 Update 6 - 1.6, 1.5
*/
public void setDefaultMenuBar(final JMenuBar menuBar) {
menuBarHandler.setDefaultMenuBar(menuBar);
}
/**
* Requests that a {@link Window} should animate into or out of full screen mode.
* Only {@link Window}s marked as full screenable by {@link FullScreenUtilities#setWindowCanFullScreen(Window, boolean)} can be toggled.
*
* @param window to animate into or out of full screen mode
*
* @since Java for Mac OS X 10.7 Update 1
*/
public void requestToggleFullScreen(final Window window) {
final Object peer = AWTAccessor.getComponentAccessor().getPeer(window);
if (!(peer instanceof LWWindowPeer)) return;
Object platformWindow = ((LWWindowPeer) peer).getPlatformWindow();
if (!(platformWindow instanceof CPlatformWindow)) return;
((CPlatformWindow)platformWindow).toggleFullScreen();
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import java.awt.*;
import java.beans.SimpleBeanInfo;
/**
* This class is used by JavaBeans tools and should not be used directly by applications.
*/
public final class ApplicationBeanInfo extends SimpleBeanInfo {
@Override
public Image getIcon(final int iconKind) {
return Toolkit.getDefaultToolkit().getImage("NSImage://NSGenericApplication");
}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import java.awt.Window;
import com.apple.eawt.event.FullScreenEvent;
/**
* Abstract adapter class for receiving fullscreen events. This class is provided
* as a convenience for creating listeners.
*
* Subclasses registered with {@link FullScreenUtilities#addFullScreenListenerTo(Window, FullScreenListener)}
* will receive all entering/entered/exiting/exited full screen events.
*
* @see FullScreenUtilities
*
* @since Java for Mac OS X 10.7 Update 1
*/
public abstract class FullScreenAdapter implements FullScreenListener {
@Override
public void windowEnteringFullScreen(final FullScreenEvent e) {}
@Override
public void windowEnteredFullScreen(final FullScreenEvent e) {}
@Override
public void windowExitingFullScreen(final FullScreenEvent e) {}
@Override
public void windowExitedFullScreen(final FullScreenEvent e) {}
}

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import com.apple.eawt.event.FullScreenEvent;
import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.RootPaneContainer;
import sun.awt.SunToolkit;
import java.lang.annotation.Native;
final class FullScreenHandler {
private static final String CLIENT_PROPERTY = "com.apple.eawt.event.internalFullScreenHandler";
@Native static final int FULLSCREEN_WILL_ENTER = 1;
@Native static final int FULLSCREEN_DID_ENTER = 2;
@Native static final int FULLSCREEN_WILL_EXIT = 3;
@Native static final int FULLSCREEN_DID_EXIT = 4;
// installs a private instance of the handler, if necessary
static void addFullScreenListenerTo(final RootPaneContainer window, final FullScreenListener listener) {
final Object value = window.getRootPane().getClientProperty(CLIENT_PROPERTY);
if (value instanceof FullScreenHandler) {
((FullScreenHandler)value).addListener(listener);
return;
}
if (value != null) return; // some other garbage is in our client property
final FullScreenHandler newHandler = new FullScreenHandler();
newHandler.addListener(listener);
window.getRootPane().putClientProperty(CLIENT_PROPERTY, newHandler);
}
// asks the installed FullScreenHandler to remove it's listener (does not uninstall the FullScreenHandler)
static void removeFullScreenListenerFrom(final RootPaneContainer window, final FullScreenListener listener) {
final Object value = window.getRootPane().getClientProperty(CLIENT_PROPERTY);
if (!(value instanceof FullScreenHandler)) return;
((FullScreenHandler)value).removeListener(listener);
}
static FullScreenHandler getHandlerFor(final RootPaneContainer window) {
final Object value = window.getRootPane().getClientProperty(CLIENT_PROPERTY);
if (value instanceof FullScreenHandler) return (FullScreenHandler)value;
return null;
}
// called from native
static void handleFullScreenEventFromNative(final Window window, final int type) {
if (!(window instanceof RootPaneContainer)) return; // handles null
SunToolkit.executeOnEventHandlerThread(window, new Runnable() {
public void run() {
final FullScreenHandler handler = getHandlerFor((RootPaneContainer)window);
if (handler != null) handler.notifyListener(new FullScreenEvent(window), type);
}
});
}
final List<FullScreenListener> listeners = new LinkedList<FullScreenListener>();
FullScreenHandler() { }
void addListener(final FullScreenListener listener) {
listeners.add(listener);
}
void removeListener(final FullScreenListener listener) {
listeners.remove(listener);
}
void notifyListener(final FullScreenEvent e, final int op) {
for (final FullScreenListener listener : listeners) {
switch (op) {
case FULLSCREEN_WILL_ENTER:
listener.windowEnteringFullScreen(e);
return;
case FULLSCREEN_DID_ENTER:
listener.windowEnteredFullScreen(e);
return;
case FULLSCREEN_WILL_EXIT:
listener.windowExitingFullScreen(e);
return;
case FULLSCREEN_DID_EXIT:
listener.windowExitedFullScreen(e);
return;
}
}
}
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import com.apple.eawt.event.FullScreenEvent;
import java.util.EventListener;
/**
*
*
* @since Java for Mac OS X 10.7 Update 1
*/
public interface FullScreenListener extends EventListener {
/**
* Invoked when a window has started to enter full screen.
* @param e containing the specific window entering full screen.
*/
public void windowEnteringFullScreen(final FullScreenEvent e);
/**
* Invoked when a window has fully entered full screen.
* @param e containing the specific window which has entered full screen.
*/
public void windowEnteredFullScreen(final FullScreenEvent e);
/**
* Invoked when a window has started to exit full screen.
* @param e containing the specific window exiting full screen.
*/
public void windowExitingFullScreen(final FullScreenEvent e);
/**
* Invoked when a window has fully exited full screen.
* @param e containing the specific window which has exited full screen.
*/
public void windowExitedFullScreen(final FullScreenEvent e);
}

View file

@ -0,0 +1,94 @@
/*
* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import java.awt.Component;
import java.awt.Window;
import javax.swing.RootPaneContainer;
import com.apple.eawt.event.GestureUtilities;
import sun.lwawt.macosx.CPlatformWindow;
/**
* Utility class perform animated full screen actions to top-level {@link Window}s.
*
* This class manages the relationship between {@link Window}s and the {@link FullScreenListener}s
* attached to them. It adds additional functionality to AWT Windows, without adding new API to the
* {@link java.awt.Window} class.
*
* Full screen operations can only be performed on top-level {@link Window}s that are also {@link RootPaneContainer}s.
*
* @see FullScreenAdapter
* @see GestureUtilities
*
* @since Java for Mac OS X 10.7 Update 1
*/
public final class FullScreenUtilities {
FullScreenUtilities() {
// package private
}
/**
* Marks a {@link Window} as able to animate into or out of full screen mode.
*
* Only top-level {@link Window}s which are {@link RootPaneContainer}s are able to be animated into and out of full screen mode.
* The {@link Window} must be marked as full screen-able before the native peer is created with {@link Component#addNotify()}.
*
* @param window
* @param canFullScreen
* @throws IllegalArgumentException if window is not a {@link RootPaneContainer}
*/
public static void setWindowCanFullScreen(final Window window, final boolean canFullScreen) {
if (!(window instanceof RootPaneContainer)) throw new IllegalArgumentException("Can't mark a non-RootPaneContainer as full screen-able");
final RootPaneContainer rpc = (RootPaneContainer)window;
rpc.getRootPane().putClientProperty(CPlatformWindow.WINDOW_FULLSCREENABLE, Boolean.valueOf(canFullScreen));
}
/**
* Attaches a {@link FullScreenListener} to the specified top-level {@link Window}.
* @param window to attach the {@link FullScreenListener} to
* @param listener to be notified when a full screen event occurs
* @throws IllegalArgumentException if window is not a {@link RootPaneContainer}
*/
public static void addFullScreenListenerTo(final Window window, final FullScreenListener listener) {
if (!(window instanceof RootPaneContainer)) throw new IllegalArgumentException("Can't attach FullScreenListener to a non-RootPaneContainer");
if (listener == null) throw new NullPointerException();
FullScreenHandler.addFullScreenListenerTo((RootPaneContainer)window, listener);
}
/**
* Removes a {@link FullScreenListener} from the specified top-level {@link Window}.
* @param window to remove the {@link FullScreenListener} from
* @param listener to be removed
* @throws IllegalArgumentException if window is not a {@link RootPaneContainer}
*/
public static void removeFullScreenListenerFrom(final Window window, final FullScreenListener listener) {
if (!(window instanceof RootPaneContainer)) throw new IllegalArgumentException("Can't remove FullScreenListener from non-RootPaneContainer");
if (listener == null) throw new NullPointerException();
FullScreenHandler.removeFullScreenListenerFrom((RootPaneContainer)window, listener);
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import java.awt.desktop.QuitEvent;
import java.awt.desktop.QuitHandler;
import java.awt.desktop.QuitResponse;
import java.awt.desktop.QuitStrategy;
/**
* Used to respond to a request to quit the application.
* The QuitResponse may be used after the {@link QuitHandler#handleQuitRequestWith(QuitEvent, QuitResponse)} method has returned, and may be used from any thread.
*
* @see Application#setQuitHandler(QuitHandler)
* @see QuitHandler
* @see Application#setQuitStrategy(QuitStrategy)
*
* @since Java for Mac OS X 10.6 Update 3
* @since Java for Mac OS X 10.5 Update 8
*/
public final class MacQuitResponse implements QuitResponse {
final _AppEventHandler appEventHandler;
MacQuitResponse(final _AppEventHandler appEventHandler) {
this.appEventHandler = appEventHandler;
}
/**
* Notifies the external quit requester that the quit will proceed, and performs the default {@link QuitStrategy}.
*/
@Override
public void performQuit() {
if (appEventHandler.currentQuitResponse != this) return;
appEventHandler.performQuit();
}
/**
* Notifies the external quit requester that the user has explicitly canceled the pending quit, and leaves the application running.
* <b>Note: this will cancel a pending log-out, restart, or shutdown.</b>
*/
@Override
public void cancelQuit() {
if (appEventHandler.currentQuitResponse != this) return;
appEventHandler.cancelQuit();
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import java.awt.*;
import java.lang.reflect.*;
import sun.awt.AWTAccessor;
import sun.lwawt.macosx.*;
import sun.lwawt.macosx.CImage.Creator;
final class _AppDockIconHandler {
private static native void nativeSetDockMenu(final long cmenu);
private static native void nativeSetDockIconImage(final long image);
private static native void nativeSetDockIconProgress(final int value);
private static native long nativeGetDockIconImage();
private static native void nativeSetDockIconBadge(final String badge);
PopupMenu fDockMenu = null;
_AppDockIconHandler() { }
public void setDockMenu(final PopupMenu menu) {
fDockMenu = menu;
// clear the menu if explicitly passed null
if (menu == null) {
nativeSetDockMenu(0);
return;
}
// check if the menu needs a parent (8343136)
final MenuContainer container = menu.getParent();
if (container == null) {
final MenuBar newParent = new MenuBar();
newParent.add(menu);
newParent.addNotify();
}
// instantiate the menu peer and set the native fDockMenu ivar
menu.addNotify();
CMenu peer = AWTAccessor.getMenuComponentAccessor().getPeer(fDockMenu);
nativeSetDockMenu(peer.getNativeMenu());
}
public PopupMenu getDockMenu() {
return fDockMenu;
}
public void setDockIconImage(final Image image) {
try {
final CImage cImage = CImage.createFromImage(image);
cImage.execute(_AppDockIconHandler::nativeSetDockIconImage);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
Image getDockIconImage() {
try {
final long dockNSImage = nativeGetDockIconImage();
if (dockNSImage == 0) return null;
final Method getCreatorMethod = CImage.class.getDeclaredMethod(
"getCreator", new Class<?>[]{});
getCreatorMethod.setAccessible(true);
Creator imageCreator = (Creator) getCreatorMethod.invoke(null, new Object[]{});
return imageCreator.createImageUsingNativeSize(dockNSImage);
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
void setDockIconBadge(final String badge) {
nativeSetDockIconBadge(badge);
}
void setDockIconProgress(int value) {
nativeSetDockIconProgress(value);
}
}

View file

@ -0,0 +1,612 @@
/*
* Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.desktop.AboutEvent;
import java.awt.desktop.AboutHandler;
import java.awt.desktop.AppForegroundEvent;
import java.awt.desktop.AppForegroundListener;
import java.awt.desktop.AppHiddenEvent;
import java.awt.desktop.AppHiddenListener;
import java.awt.desktop.AppReopenedEvent;
import java.awt.desktop.AppReopenedListener;
import java.awt.desktop.OpenFilesEvent;
import java.awt.desktop.OpenFilesHandler;
import java.awt.desktop.OpenURIEvent;
import java.awt.desktop.OpenURIHandler;
import java.awt.desktop.PreferencesEvent;
import java.awt.desktop.PreferencesHandler;
import java.awt.desktop.PrintFilesEvent;
import java.awt.desktop.PrintFilesHandler;
import java.awt.desktop.QuitEvent;
import java.awt.desktop.QuitHandler;
import java.awt.desktop.QuitStrategy;
import java.awt.desktop.ScreenSleepEvent;
import java.awt.desktop.ScreenSleepListener;
import java.awt.desktop.SystemEventListener;
import java.awt.desktop.SystemSleepEvent;
import java.awt.desktop.SystemSleepListener;
import java.awt.desktop.UserSessionEvent;
import java.awt.desktop.UserSessionEvent.Reason;
import java.awt.desktop.UserSessionListener;
import java.awt.event.WindowEvent;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import sun.awt.SunToolkit;
final class _AppEventHandler {
private static final int NOTIFY_ABOUT = 1;
private static final int NOTIFY_PREFS = 2;
private static final int NOTIFY_OPEN_APP = 3;
private static final int NOTIFY_REOPEN_APP = 4;
private static final int NOTIFY_QUIT = 5;
private static final int NOTIFY_SHUTDOWN = 6;
private static final int NOTIFY_ACTIVE_APP_GAINED = 7;
private static final int NOTIFY_ACTIVE_APP_LOST = 8;
private static final int NOTIFY_APP_HIDDEN = 9;
private static final int NOTIFY_APP_SHOWN = 10;
private static final int NOTIFY_USER_SESSION_ACTIVE = 11;
private static final int NOTIFY_USER_SESSION_INACTIVE = 12;
private static final int NOTIFY_SCREEN_SLEEP = 13;
private static final int NOTIFY_SCREEN_WAKE = 14;
private static final int NOTIFY_SYSTEM_SLEEP = 15;
private static final int NOTIFY_SYSTEM_WAKE = 16;
private static final int REGISTER_USER_SESSION = 1;
private static final int REGISTER_SCREEN_SLEEP = 2;
private static final int REGISTER_SYSTEM_SLEEP = 3;
private static native void nativeOpenCocoaAboutWindow();
private static native void nativeReplyToAppShouldTerminate(final boolean shouldTerminate);
private static native void nativeRegisterForNotification(final int notification);
static final _AppEventHandler instance = new _AppEventHandler();
static _AppEventHandler getInstance() {
return instance;
}
// single shot dispatchers (some queuing, others not)
final _AboutDispatcher aboutDispatcher = new _AboutDispatcher();
final _PreferencesDispatcher preferencesDispatcher = new _PreferencesDispatcher();
final _OpenFileDispatcher openFilesDispatcher = new _OpenFileDispatcher();
final _PrintFileDispatcher printFilesDispatcher = new _PrintFileDispatcher();
final _OpenURIDispatcher openURIDispatcher = new _OpenURIDispatcher();
final _QuitDispatcher quitDispatcher = new _QuitDispatcher();
final _OpenAppDispatcher openAppDispatcher = new _OpenAppDispatcher();
// multiplexing dispatchers (contains listener lists)
final _AppReOpenedDispatcher reOpenAppDispatcher = new _AppReOpenedDispatcher();
final _AppForegroundDispatcher foregroundAppDispatcher = new _AppForegroundDispatcher();
final _HiddenAppDispatcher hiddenAppDispatcher = new _HiddenAppDispatcher();
final _UserSessionDispatcher userSessionDispatcher = new _UserSessionDispatcher();
final _ScreenSleepDispatcher screenSleepDispatcher = new _ScreenSleepDispatcher();
final _SystemSleepDispatcher systemSleepDispatcher = new _SystemSleepDispatcher();
QuitStrategy defaultQuitAction = QuitStrategy.NORMAL_EXIT;
_AppEventHandler() {
final String strategyProp = System.getProperty("apple.eawt.quitStrategy");
if (strategyProp == null) return;
if ("CLOSE_ALL_WINDOWS".equals(strategyProp)) {
setDefaultQuitStrategy(QuitStrategy.CLOSE_ALL_WINDOWS);
} else if ("SYSTEM_EXIT_O".equals(strategyProp)
|| "NORMAL_EXIT".equals(strategyProp)) {
setDefaultQuitStrategy(QuitStrategy.NORMAL_EXIT);
} else {
System.err.println("unrecognized apple.eawt.quitStrategy: " + strategyProp);
}
}
void addListener(final SystemEventListener listener) {
if (listener instanceof AppReopenedListener) reOpenAppDispatcher.addListener((AppReopenedListener)listener);
if (listener instanceof AppForegroundListener) foregroundAppDispatcher.addListener((AppForegroundListener)listener);
if (listener instanceof AppHiddenListener) hiddenAppDispatcher.addListener((AppHiddenListener)listener);
if (listener instanceof UserSessionListener) userSessionDispatcher.addListener((UserSessionListener)listener);
if (listener instanceof ScreenSleepListener) screenSleepDispatcher.addListener((ScreenSleepListener)listener);
if (listener instanceof SystemSleepListener) systemSleepDispatcher.addListener((SystemSleepListener)listener);
}
void removeListener(final SystemEventListener listener) {
if (listener instanceof AppReopenedListener) reOpenAppDispatcher.removeListener((AppReopenedListener)listener);
if (listener instanceof AppForegroundListener) foregroundAppDispatcher.removeListener((AppForegroundListener)listener);
if (listener instanceof AppHiddenListener) hiddenAppDispatcher.removeListener((AppHiddenListener)listener);
if (listener instanceof UserSessionListener) userSessionDispatcher.removeListener((UserSessionListener)listener);
if (listener instanceof ScreenSleepListener) screenSleepDispatcher.removeListener((ScreenSleepListener)listener);
if (listener instanceof SystemSleepListener) systemSleepDispatcher.removeListener((SystemSleepListener)listener);
}
void openCocoaAboutWindow() {
nativeOpenCocoaAboutWindow();
}
void setDefaultQuitStrategy(final QuitStrategy defaultQuitAction) {
this.defaultQuitAction = defaultQuitAction;
}
MacQuitResponse currentQuitResponse;
synchronized MacQuitResponse obtainQuitResponse() {
if (currentQuitResponse != null) return currentQuitResponse;
return currentQuitResponse = new MacQuitResponse(this);
}
synchronized void cancelQuit() {
currentQuitResponse = null;
nativeReplyToAppShouldTerminate(false);
}
synchronized void performQuit() {
currentQuitResponse = null;
try {
if (defaultQuitAction == QuitStrategy.NORMAL_EXIT
|| _AppMiscHandlers.isSuddenTerminationEnbaled()) System.exit(0);
if (defaultQuitAction != QuitStrategy.CLOSE_ALL_WINDOWS) {
throw new RuntimeException("Unknown quit action");
}
EventQueue.invokeLater(new Runnable() {
public void run() {
// walk frames from back to front
final Frame[] allFrames = Frame.getFrames();
for (int i = allFrames.length - 1; i >= 0; i--) {
final Frame frame = allFrames[i];
frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
}
}
});
} finally {
// Either we've just called System.exit(), or the app will call
// it when processing a WINDOW_CLOSING event. Either way, we reply
// to Cocoa that we don't want to exit the event loop yet.
nativeReplyToAppShouldTerminate(false);
}
}
/*
* callbacks from native delegate
*/
private static void handlePrintFiles(final List<String> filenames) {
instance.printFilesDispatcher.dispatch(new _NativeEvent(filenames));
}
private static void handleOpenFiles(final List<String> filenames, final String searchTerm) {
instance.openFilesDispatcher.dispatch(new _NativeEvent(filenames, searchTerm));
}
private static void handleOpenURI(final String uri) {
instance.openURIDispatcher.dispatch(new _NativeEvent(uri));
}
// default funnel for non-complex events
private static void handleNativeNotification(final int code) {
// System.out.println(code);
switch (code) {
case NOTIFY_ABOUT:
instance.aboutDispatcher.dispatch(new _NativeEvent());
break;
case NOTIFY_PREFS:
instance.preferencesDispatcher.dispatch(new _NativeEvent());
break;
case NOTIFY_OPEN_APP:
instance.openAppDispatcher.dispatch(new _NativeEvent());
break;
case NOTIFY_REOPEN_APP:
instance.reOpenAppDispatcher.dispatch(new _NativeEvent());
break;
case NOTIFY_QUIT:
instance.quitDispatcher.dispatch(new _NativeEvent());
break;
case NOTIFY_SHUTDOWN:
// do nothing for now
break;
case NOTIFY_ACTIVE_APP_GAINED:
instance.foregroundAppDispatcher.dispatch(new _NativeEvent(Boolean.TRUE));
break;
case NOTIFY_ACTIVE_APP_LOST:
instance.foregroundAppDispatcher.dispatch(new _NativeEvent(Boolean.FALSE));
break;
case NOTIFY_APP_HIDDEN:
instance.hiddenAppDispatcher.dispatch(new _NativeEvent(Boolean.TRUE));
break;
case NOTIFY_APP_SHOWN:
instance.hiddenAppDispatcher.dispatch(new _NativeEvent(Boolean.FALSE));
break;
case NOTIFY_USER_SESSION_ACTIVE:
instance.userSessionDispatcher.dispatch(new _NativeEvent(Boolean.TRUE));
break;
case NOTIFY_USER_SESSION_INACTIVE:
instance.userSessionDispatcher.dispatch(new _NativeEvent(Boolean.FALSE));
break;
case NOTIFY_SCREEN_SLEEP:
instance.screenSleepDispatcher.dispatch(new _NativeEvent(Boolean.TRUE));
break;
case NOTIFY_SCREEN_WAKE:
instance.screenSleepDispatcher.dispatch(new _NativeEvent(Boolean.FALSE));
break;
case NOTIFY_SYSTEM_SLEEP:
instance.systemSleepDispatcher.dispatch(new _NativeEvent(Boolean.TRUE));
break;
case NOTIFY_SYSTEM_WAKE:
instance.systemSleepDispatcher.dispatch(new _NativeEvent(Boolean.FALSE));
break;
default:
System.err.println("EAWT unknown native notification: " + code);
break;
}
}
final class _AboutDispatcher extends _AppEventDispatcher<AboutHandler> {
@Override
void performDefaultAction(final _NativeEvent event) {
openCocoaAboutWindow(); // if the handler is null, fall back to showing the Cocoa default
}
@Override
void performUsing(final AboutHandler handler, final _NativeEvent event) {
handler.handleAbout(new AboutEvent());
}
}
static final class _PreferencesDispatcher extends _AppEventDispatcher<PreferencesHandler> {
@Override
synchronized void setHandler(final PreferencesHandler handler) {
super.setHandler(handler);
_AppMenuBarHandler.getInstance().setPreferencesMenuItemVisible(handler != null);
_AppMenuBarHandler.getInstance().setPreferencesMenuItemEnabled(handler != null);
}
@Override
void performUsing(final PreferencesHandler handler, final _NativeEvent event) {
handler.handlePreferences(new PreferencesEvent());
}
}
static final class _OpenAppDispatcher extends _QueuingAppEventDispatcher<com.apple.eawt._OpenAppHandler> {
@Override
void performUsing(com.apple.eawt._OpenAppHandler handler, _NativeEvent event) {
handler.handleOpenApp();
}
}
static final class _AppReOpenedDispatcher extends _AppEventMultiplexor<AppReopenedListener> {
@Override
void performOnListener(AppReopenedListener listener, final _NativeEvent event) {
final AppReopenedEvent e = new AppReopenedEvent();
listener.appReopened(e);
}
}
static final class _AppForegroundDispatcher extends _BooleanAppEventMultiplexor<AppForegroundListener, AppForegroundEvent> {
@Override
AppForegroundEvent createEvent(final boolean isTrue) { return new AppForegroundEvent(); }
@Override
void performFalseEventOn(final AppForegroundListener listener, final AppForegroundEvent e) {
listener.appMovedToBackground(e);
}
@Override
void performTrueEventOn(final AppForegroundListener listener, final AppForegroundEvent e) {
listener.appRaisedToForeground(e);
}
}
static final class _HiddenAppDispatcher extends _BooleanAppEventMultiplexor<AppHiddenListener, AppHiddenEvent> {
@Override
AppHiddenEvent createEvent(final boolean isTrue) { return new AppHiddenEvent(); }
@Override
void performFalseEventOn(final AppHiddenListener listener, final AppHiddenEvent e) {
listener.appUnhidden(e);
}
@Override
void performTrueEventOn(final AppHiddenListener listener, final AppHiddenEvent e) {
listener.appHidden(e);
}
}
static final class _UserSessionDispatcher extends _BooleanAppEventMultiplexor<UserSessionListener, UserSessionEvent> {
@Override
UserSessionEvent createEvent(final boolean isTrue) {
return new UserSessionEvent(Reason.UNSPECIFIED);
}
@Override
void performFalseEventOn(final UserSessionListener listener, final UserSessionEvent e) {
listener.userSessionDeactivated(e);
}
@Override
void performTrueEventOn(final UserSessionListener listener, final UserSessionEvent e) {
listener.userSessionActivated(e);
}
@Override
void registerNativeListener() {
nativeRegisterForNotification(REGISTER_USER_SESSION);
}
}
static final class _ScreenSleepDispatcher extends _BooleanAppEventMultiplexor<ScreenSleepListener, ScreenSleepEvent> {
@Override
ScreenSleepEvent createEvent(final boolean isTrue) { return new ScreenSleepEvent(); }
@Override
void performFalseEventOn(final ScreenSleepListener listener, final ScreenSleepEvent e) {
listener.screenAwoke(e);
}
@Override
void performTrueEventOn(final ScreenSleepListener listener, final ScreenSleepEvent e) {
listener.screenAboutToSleep(e);
}
@Override
void registerNativeListener() {
nativeRegisterForNotification(REGISTER_SCREEN_SLEEP);
}
}
static final class _SystemSleepDispatcher extends _BooleanAppEventMultiplexor<SystemSleepListener, SystemSleepEvent> {
@Override
SystemSleepEvent createEvent(final boolean isTrue) { return new SystemSleepEvent(); }
@Override
void performFalseEventOn(final SystemSleepListener listener, final SystemSleepEvent e) {
listener.systemAwoke(e);
}
@Override
void performTrueEventOn(final SystemSleepListener listener, final SystemSleepEvent e) {
listener.systemAboutToSleep(e);
}
@Override
void registerNativeListener() {
nativeRegisterForNotification(REGISTER_SYSTEM_SLEEP);
}
}
static final class _OpenFileDispatcher extends _QueuingAppEventDispatcher<OpenFilesHandler> {
@Override
void performUsing(final OpenFilesHandler handler, final _NativeEvent event) {
// create file list from fileNames
final List<String> fileNameList = event.get(0);
final ArrayList<File> files = new ArrayList<File>(fileNameList.size());
for (final String fileName : fileNameList) files.add(new File(fileName));
// populate the properties map
final String searchTerm = event.get(1);
handler.openFiles(new OpenFilesEvent(files, searchTerm));
}
}
static final class _PrintFileDispatcher extends _QueuingAppEventDispatcher<PrintFilesHandler> {
@Override
void performUsing(final PrintFilesHandler handler, final _NativeEvent event) {
// create file list from fileNames
final List<String> fileNameList = event.get(0);
final ArrayList<File> files = new ArrayList<File>(fileNameList.size());
for (final String fileName : fileNameList) files.add(new File(fileName));
handler.printFiles(new PrintFilesEvent(files));
}
}
// Java URLs can't handle unknown protocol types, which is why we use URIs
static final class _OpenURIDispatcher extends _QueuingAppEventDispatcher<OpenURIHandler> {
@Override
void performUsing(final OpenURIHandler handler, final _NativeEvent event) {
final String urlString = event.get(0);
try {
handler.openURI(new OpenURIEvent(new URI(urlString)));
} catch (final URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
final class _QuitDispatcher extends _AppEventDispatcher<QuitHandler> {
@Override
void performDefaultAction(final _NativeEvent event) {
obtainQuitResponse().performQuit();
}
@Override
void performUsing(final QuitHandler handler, final _NativeEvent event) {
if (_AppMiscHandlers.isSuddenTerminationEnbaled()) {
performDefaultAction(event);
return;
}
final MacQuitResponse response = obtainQuitResponse(); // obtains the "current" quit response
handler.handleQuitRequestWith(new QuitEvent(), response);
}
}
// -- ABSTRACT QUEUE/EVENT/LISTENER HELPERS --
// generic little "raw event" that's constructed easily from the native callbacks
static final class _NativeEvent {
Object[] args;
public _NativeEvent(final Object... args) {
this.args = args;
}
@SuppressWarnings("unchecked")
<T> T get(final int i) {
if (args == null) return null;
return (T)args[i];
}
}
abstract static class _AppEventMultiplexor<L> {
private final Set<L> listeners = new HashSet<L>();
boolean nativeListenerRegistered;
// called from AppKit Thread-0
void dispatch(final _NativeEvent event, final Object... args) {
// grab a local ref to the listeners as an array
final ArrayList<L> localList;
synchronized (this) {
if (listeners.size() == 0) {
return;
}
localList = new ArrayList<L>(listeners.size());
localList.addAll(listeners);
}
for (final L listener : localList) {
SunToolkit.invokeLater(new Runnable() {
public void run() {
performOnListener(listener, event);
}
});
}
}
synchronized void addListener(final L listener) {
listeners.add(listener);
if (!nativeListenerRegistered) {
registerNativeListener();
nativeListenerRegistered = true;
}
}
synchronized void removeListener(final L listener) {
listeners.remove(listener);
}
abstract void performOnListener(L listener, final _NativeEvent event);
void registerNativeListener() { }
}
abstract static class _BooleanAppEventMultiplexor<L, E> extends _AppEventMultiplexor<L> {
@Override
void performOnListener(L listener, final _NativeEvent event) {
final boolean isTrue = Boolean.TRUE.equals(event.get(0));
final E e = createEvent(isTrue);
if (isTrue) {
performTrueEventOn(listener, e);
} else {
performFalseEventOn(listener, e);
}
}
abstract E createEvent(final boolean isTrue);
abstract void performTrueEventOn(final L listener, final E e);
abstract void performFalseEventOn(final L listener, final E e);
}
/*
* Ensures that setting and obtaining an app event handler is done in
* both a thread-safe manner, and that user code is performed on the
* AWT EventQueue thread.
*
* Allows native to blindly lob new events into the dispatcher,
* knowing that they will only be dispatched once a handler is set.
*
* User code is not (and should not be) run under any synchronized lock.
*/
abstract static class _AppEventDispatcher<H> {
H _handler;
// called from AppKit Thread-0
void dispatch(final _NativeEvent event) {
// grab a local ref to the handler
final H localHandler;
synchronized (_AppEventDispatcher.this) {
localHandler = _handler;
}
if (localHandler == null) {
performDefaultAction(event);
} else {
SunToolkit.invokeLater(new Runnable() {
public void run() {
performUsing(localHandler, event);
}
});
}
}
synchronized void setHandler(final H handler) {
this._handler = handler;
}
void performDefaultAction(final _NativeEvent event) { } // by default, do nothing
abstract void performUsing(final H handler, final _NativeEvent event);
}
abstract static class _QueuingAppEventDispatcher<H> extends _AppEventDispatcher<H> {
List<_NativeEvent> queuedEvents = new LinkedList<_NativeEvent>();
@Override
void dispatch(final _NativeEvent event) {
synchronized (this) {
// dispatcher hasn't started yet
if (queuedEvents != null) {
queuedEvents.add(event);
return;
}
}
super.dispatch(event);
}
@Override
synchronized void setHandler(final H handler) {
this._handler = handler;
// dispatch any events in the queue
if (queuedEvents != null) {
// grab a local ref to the queue, so the real one can be nulled out
final java.util.List<_NativeEvent> localQueuedEvents = queuedEvents;
queuedEvents = null;
if (localQueuedEvents.size() != 0) {
for (final _NativeEvent arg : localQueuedEvents) {
dispatch(arg);
}
}
}
}
}
}

View file

@ -0,0 +1,203 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
import java.awt.Container;
import java.awt.Frame;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JMenuBar;
import javax.swing.plaf.MenuBarUI;
import com.apple.laf.ScreenMenuBar;
import sun.awt.AWTAccessor;
import sun.lwawt.macosx.CMenuBar;
import com.apple.laf.AquaMenuBarUI;
final class _AppMenuBarHandler {
private static final int MENU_ABOUT = 1;
private static final int MENU_PREFS = 2;
private static native void nativeSetMenuState(final int menu, final boolean visible, final boolean enabled);
private static native void nativeSetDefaultMenuBar(final long menuBarPeer);
private static native void nativeActivateDefaultMenuBar(final long menuBarPeer);
static final _AppMenuBarHandler instance = new _AppMenuBarHandler();
static _AppMenuBarHandler getInstance() {
return instance;
}
private static ScreenMenuBar defaultMenuBar;
// callback from the native delegate -init function
private static void initMenuStates(final boolean aboutMenuItemVisible,
final boolean aboutMenuItemEnabled,
final boolean prefsMenuItemVisible,
final boolean prefsMenuItemEnabled) {
synchronized (instance) {
instance.aboutMenuItemVisible = aboutMenuItemVisible;
instance.aboutMenuItemEnabled = aboutMenuItemEnabled;
instance.prefsMenuItemVisible = prefsMenuItemVisible;
instance.prefsMenuItemEnabled = prefsMenuItemEnabled;
}
}
_AppMenuBarHandler() { }
boolean aboutMenuItemVisible;
boolean aboutMenuItemEnabled;
boolean prefsMenuItemVisible;
boolean prefsMenuItemEnabled;
boolean prefsMenuItemExplicitlySet;
void setDefaultMenuBar(final JMenuBar menuBar) {
installDefaultMenuBar(menuBar);
}
static boolean isMenuBarActivationNeeded() {
// scan the current frames, and see if any are foreground
final Frame[] frames = Frame.getFrames();
for (final Frame frame : frames) {
if (frame.isVisible() && !isFrameMinimized(frame)) {
return false;
}
}
return true;
}
static boolean isFrameMinimized(final Frame frame) {
return (frame.getExtendedState() & Frame.ICONIFIED) != 0;
}
static void installDefaultMenuBar(final JMenuBar menuBar) {
if (menuBar == null) {
// intentionally clearing the default menu
if (defaultMenuBar != null) {
defaultMenuBar.removeNotify();
defaultMenuBar = null;
}
nativeSetDefaultMenuBar(0);
return;
}
Container parent = menuBar.getParent();
if (parent instanceof JLayeredPane) {
((JLayeredPane) parent).remove(menuBar);
}
MenuBarUI ui = menuBar.getUI();
if (!(ui instanceof AquaMenuBarUI)) {
ui = new AquaMenuBarUI();
menuBar.setUI(ui);
}
final AquaMenuBarUI aquaUI = (AquaMenuBarUI)ui;
final ScreenMenuBar screenMenuBar = aquaUI.getScreenMenuBar();
if (screenMenuBar == null) {
// Aqua is installed, but we aren't using the screen menu bar
throw new IllegalStateException("Application.setDefaultMenuBar() only works if apple.laf.useScreenMenuBar=true");
}
if (screenMenuBar != defaultMenuBar) {
if (defaultMenuBar != null) {
defaultMenuBar.removeNotify();
}
defaultMenuBar = screenMenuBar;
screenMenuBar.addNotify();
}
final Object peer = AWTAccessor.getMenuComponentAccessor().getPeer(screenMenuBar);
if (!(peer instanceof CMenuBar)) {
// such a thing should not be possible
throw new IllegalStateException("Unable to determine native menu bar from provided JMenuBar");
}
// grab the pointer to the CMenuBar, and retain it in native
((CMenuBar) peer).execute(_AppMenuBarHandler::nativeSetDefaultMenuBar);
// if there is no currently active frame, install the default menu bar in the application main menu
if (isMenuBarActivationNeeded()) {
((CMenuBar) peer).execute(_AppMenuBarHandler::nativeActivateDefaultMenuBar);
}
}
void setAboutMenuItemVisible(final boolean present) {
synchronized (this) {
if (aboutMenuItemVisible == present) return;
aboutMenuItemVisible = present;
}
nativeSetMenuState(MENU_ABOUT, aboutMenuItemVisible, aboutMenuItemEnabled);
}
void setPreferencesMenuItemVisible(final boolean present) {
synchronized (this) {
prefsMenuItemExplicitlySet = true;
if (prefsMenuItemVisible == present) return;
prefsMenuItemVisible = present;
}
nativeSetMenuState(MENU_PREFS, prefsMenuItemVisible, prefsMenuItemEnabled);
}
void setAboutMenuItemEnabled(final boolean enable) {
synchronized (this) {
if (aboutMenuItemEnabled == enable) return;
aboutMenuItemEnabled = enable;
}
nativeSetMenuState(MENU_ABOUT, aboutMenuItemVisible, aboutMenuItemEnabled);
}
void setPreferencesMenuItemEnabled(final boolean enable) {
synchronized (this) {
prefsMenuItemExplicitlySet = true;
if (prefsMenuItemEnabled == enable) return;
prefsMenuItemEnabled = enable;
}
nativeSetMenuState(MENU_PREFS, prefsMenuItemVisible, prefsMenuItemEnabled);
}
boolean isAboutMenuItemVisible() {
return aboutMenuItemVisible;
}
boolean isPreferencesMenuItemVisible() {
return prefsMenuItemVisible;
}
boolean isAboutMenuItemEnabled() {
return aboutMenuItemEnabled;
}
boolean isPreferencesMenuItemEnabled() {
return prefsMenuItemEnabled;
}
}

View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
final class _AppMiscHandlers {
private static boolean isSuddenTerminationEnabled;
private static native void nativeOpenHelpViewer();
private static native void nativeRequestActivation(final boolean allWindows);
private static native void nativeRequestUserAttention(final boolean critical);
private static native void nativeEnableSuddenTermination();
private static native void nativeDisableSuddenTermination();
static void openHelpViewer() {
nativeOpenHelpViewer();
}
static void requestActivation(final boolean allWindows) {
nativeRequestActivation(allWindows);
}
static void requestUserAttention(final boolean critical) {
nativeRequestUserAttention(critical);
}
static void enableSuddenTermination() {
isSuddenTerminationEnabled = true;
nativeEnableSuddenTermination();
}
static void disableSuddenTermination() {
isSuddenTerminationEnabled = false;
nativeDisableSuddenTermination();
}
public static boolean isSuddenTerminationEnbaled() {
return isSuddenTerminationEnabled;
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt;
interface _OpenAppHandler {
void handleOpenApp();
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
import com.apple.eawt.Application;
import java.awt.Window;
import java.util.EventObject;
@SuppressWarnings("serial") // JDK implementation class
public final class FullScreenEvent extends EventObject {
final Window window;
/**
* @param window window
*/
public FullScreenEvent(final Window window) {
super(Application.getApplication());
this.window = window;
}
/**
* @return window transitioning between full screen states
*/
public Window getWindow() {
return window;
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Abstract adapter class for receiving gesture events. This class is provided
* as a convenience for creating listeners.
*
* Subclasses registered with {@link GestureUtilities#addGestureListenerTo}
* will receive all phase, magnification, rotation, and swipe events.
*
* @see GestureUtilities
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public abstract class GestureAdapter implements GesturePhaseListener, MagnificationListener, RotationListener, SwipeListener {
@Override
public void gestureBegan(final GesturePhaseEvent e) { }
@Override
public void gestureEnded(final GesturePhaseEvent e) { }
@Override
public void magnify(final MagnificationEvent e) { }
@Override
public void rotate(final RotationEvent e) { }
@Override
public void swipedDown(final SwipeEvent e) { }
@Override
public void swipedLeft(final SwipeEvent e) { }
@Override
public void swipedRight(final SwipeEvent e) { }
@Override
public void swipedUp(final SwipeEvent e) { }
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
import java.awt.*;
import java.awt.event.InputEvent;
/**
* Abstract event all gestures inherit from.
*
* Note: GestureEvent is not subclass of {@link AWTEvent} and is not dispatched
* directly from the {@link EventQueue}. This is an intentional design decision
* to prevent collision with an official java.awt.* gesture event types subclassing
* {@link InputEvent}.
*
* {@link GestureListener}s are only notified from the AWT Event Dispatch thread.
*
* @see GestureUtilities
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public abstract class GestureEvent {
boolean consumed;
GestureEvent() {
// package private
}
/**
* Consuming an event prevents listeners later in the chain or higher in the
* component hierarchy from receiving the event.
*/
public void consume() {
consumed = true;
}
/**
* @return {@code true} if the event has been consumed, otherwise
* {@code false}
*/
protected boolean isConsumed() {
return consumed;
}
}

View file

@ -0,0 +1,205 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
import sun.awt.SunToolkit;
import java.awt.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import java.lang.annotation.Native;
final class GestureHandler {
private static final String CLIENT_PROPERTY = "com.apple.eawt.event.internalGestureHandler";
// native constants for the supported types of high-level gestures
@Native static final int PHASE = 1;
@Native static final int ROTATE = 2;
@Native static final int MAGNIFY = 3;
@Native static final int SWIPE = 4;
// installs a private instance of GestureHandler, if necessary
static void addGestureListenerTo(final JComponent component, final GestureListener listener) {
final Object value = component.getClientProperty(CLIENT_PROPERTY);
if (value instanceof GestureHandler) {
((GestureHandler)value).addListener(listener);
return;
}
if (value != null) return; // some other garbage is in our client property
final GestureHandler newHandler = new GestureHandler();
newHandler.addListener(listener);
component.putClientProperty(CLIENT_PROPERTY, newHandler);
}
// asks the installed GestureHandler to remove it's listener (does not uninstall the GestureHandler)
static void removeGestureListenerFrom(final JComponent component, final GestureListener listener) {
final Object value = component.getClientProperty(CLIENT_PROPERTY);
if (!(value instanceof GestureHandler)) return;
((GestureHandler)value).removeListener(listener);
}
// called from native - finds the deepest component with an installed GestureHandler,
// creates a single event, and dispatches it to a recursive notifier
static void handleGestureFromNative(final Window window, final int type, final double x, final double y, final double a, final double b) {
if (window == null) return; // should never happen...
SunToolkit.executeOnEventHandlerThread(window, new Runnable() {
public void run() {
final Component component = SwingUtilities.getDeepestComponentAt(window, (int)x, (int)y);
final PerComponentNotifier firstNotifier;
if (component instanceof RootPaneContainer) {
firstNotifier = getNextNotifierForComponent(((RootPaneContainer)component).getRootPane());
} else {
firstNotifier = getNextNotifierForComponent(component);
}
if (firstNotifier == null) return;
switch (type) {
case PHASE:
firstNotifier.recursivelyHandlePhaseChange(a, new GesturePhaseEvent());
return;
case ROTATE:
firstNotifier.recursivelyHandleRotate(new RotationEvent(a));
return;
case MAGNIFY:
firstNotifier.recursivelyHandleMagnify(new MagnificationEvent(a));
return;
case SWIPE:
firstNotifier.recursivelyHandleSwipe(a, b, new SwipeEvent());
return;
}
}
});
}
final List<GesturePhaseListener> phasers = new LinkedList<GesturePhaseListener>();
final List<RotationListener> rotaters = new LinkedList<RotationListener>();
final List<MagnificationListener> magnifiers = new LinkedList<MagnificationListener>();
final List<SwipeListener> swipers = new LinkedList<SwipeListener>();
GestureHandler() { }
void addListener(final GestureListener listener) {
if (listener instanceof GesturePhaseListener) phasers.add((GesturePhaseListener)listener);
if (listener instanceof RotationListener) rotaters.add((RotationListener)listener);
if (listener instanceof MagnificationListener) magnifiers.add((MagnificationListener)listener);
if (listener instanceof SwipeListener) swipers.add((SwipeListener)listener);
}
void removeListener(final GestureListener listener) {
phasers.remove(listener);
rotaters.remove(listener);
magnifiers.remove(listener);
swipers.remove(listener);
}
// notifies all listeners in a particular component/handler pair
// and recursively notifies up the component hierarchy
static final class PerComponentNotifier {
final Component component;
final GestureHandler handler;
public PerComponentNotifier(final Component component, final GestureHandler handler) {
this.component = component;
this.handler = handler;
}
void recursivelyHandlePhaseChange(final double phase, final GesturePhaseEvent e) {
for (final GesturePhaseListener listener : handler.phasers) {
if (phase < 0) {
listener.gestureBegan(e);
} else {
listener.gestureEnded(e);
}
if (e.isConsumed()) return;
}
final PerComponentNotifier next = getNextNotifierForComponent(component.getParent());
if (next != null) next.recursivelyHandlePhaseChange(phase, e);
}
void recursivelyHandleRotate(final RotationEvent e) {
for (final RotationListener listener : handler.rotaters) {
listener.rotate(e);
if (e.isConsumed()) return;
}
final PerComponentNotifier next = getNextNotifierForComponent(component.getParent());
if (next != null) next.recursivelyHandleRotate(e);
}
void recursivelyHandleMagnify(final MagnificationEvent e) {
for (final MagnificationListener listener : handler.magnifiers) {
listener.magnify(e);
if (e.isConsumed()) return;
}
final PerComponentNotifier next = getNextNotifierForComponent(component.getParent());
if (next != null) next.recursivelyHandleMagnify(e);
}
void recursivelyHandleSwipe(final double x, final double y, final SwipeEvent e) {
for (final SwipeListener listener : handler.swipers) {
if (x < 0) listener.swipedLeft(e);
if (x > 0) listener.swipedRight(e);
if (y < 0) listener.swipedDown(e);
if (y > 0) listener.swipedUp(e);
if (e.isConsumed()) return;
}
final PerComponentNotifier next = getNextNotifierForComponent(component.getParent());
if (next != null) next.recursivelyHandleSwipe(x, y, e);
}
}
// helper function to get a handler from a Component
static GestureHandler getHandlerForComponent(final Component c) {
if (!(c instanceof JComponent)) return null;
final Object value = ((JComponent)c).getClientProperty(CLIENT_PROPERTY);
if (!(value instanceof GestureHandler)) return null;
return (GestureHandler)value;
}
// recursive helper to find the next component/handler pair
static PerComponentNotifier getNextNotifierForComponent(final Component c) {
if (c == null) return null;
final GestureHandler handler = getHandlerForComponent(c);
if (handler != null) {
return new PerComponentNotifier(c, handler);
}
return getNextNotifierForComponent(c.getParent());
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
import java.util.EventListener;
/**
* Marker interface for all gesture sub-types.
*
* @see GesturePhaseListener
* @see MagnificationListener
* @see RotationListener
* @see SwipeListener
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public interface GestureListener extends EventListener {
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Event indicating the user has started or stopped a continuous gesture.
*
* @see GesturePhaseListener
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public final class GesturePhaseEvent extends GestureEvent {
GesturePhaseEvent() {
// package private
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Listener interface for receiving notification that a continuous gesture
* (like rotate or scale) has started or stopped.
*
* @see MagnificationListener
* @see RotationListener
* @see GesturePhaseEvent
* @see GestureUtilities
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public interface GesturePhaseListener extends GestureListener {
/**
* Invoked when the user has started a continuous gesture.
* @param e representing the start of a continuous gesture.
*/
public void gestureBegan(final GesturePhaseEvent e);
/**
* Invoked when the user has stopped a continuous gesture.
* @param e representing the end of a continuous gesture.
*/
public void gestureEnded(final GesturePhaseEvent e);
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JRootPane;
/**
* Registration utility class to add {@link GestureListener}s to Swing components.
*
* This class manages the relationship between {@link JComponent}s and the {@link GestureListener}s
* attached to them. It adds additional functionality to AWT Windows, without adding new API to the
* {@link java.awt.Window} class.
*
* To add a {@link GestureListener} to a top-level Swing window, use the {@link JRootPane} of the
* top-level window type.
*
* @see GestureAdapter
* @see JFrame#getRootPane()
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public final class GestureUtilities {
GestureUtilities() {
// package private
}
/**
* Attaches a {@link GestureListener} to the specified {@link JComponent}.
* @param component to attach the {@link GestureListener} to
* @param listener to be notified when a gesture occurs
*/
public static void addGestureListenerTo(final JComponent component, final GestureListener listener) {
if (component == null || listener == null) throw new NullPointerException();
GestureHandler.addGestureListenerTo(component, listener);
}
/**
* Removes a {@link GestureListener} from the specified {@link JComponent}
* @param component to remove the {@link GestureListener} from
* @param listener to be removed
*/
public static void removeGestureListenerFrom(final JComponent component, final GestureListener listener) {
if (component == null || listener == null) throw new NullPointerException();
GestureHandler.removeGestureListenerFrom(component, listener);
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Event encapsulating a relative magnification scale performed by the user.
*
* @see MagnificationListener
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public final class MagnificationEvent extends GestureEvent {
final double magnification;
MagnificationEvent(final double magnification) {
// package private
this.magnification = magnification;
}
/**
* @return an abstract measure of magnification scale (both positive and negative)
*/
public double getMagnification() {
return magnification;
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Listener interface for receiving magnification events.
*
* @see MagnificationEvent
* @see GesturePhaseListener
* @see GestureUtilities
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public interface MagnificationListener extends GestureListener {
/**
* Invoked when a magnification gesture is performed by the user.
* @param e containing the scale of the magnification.
*/
public void magnify(final MagnificationEvent e);
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Event encapsulating a relative rotation performed by the user.
*
* @see RotationListener
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public final class RotationEvent extends GestureEvent {
final double rotation;
RotationEvent(final double rotation) {
// package private
this.rotation = rotation;
}
/**
* @return an abstract measure of rotation (clockwise is negative)
*/
public double getRotation() {
return rotation;
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Listener interface for receiving rotation events.
*
* @see RotationEvent
* @see GesturePhaseListener
* @see GestureUtilities
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public interface RotationListener extends GestureListener {
/**
* Invoked when a rotation gesture is performed by the user.
* @param e containing an abstract measure of rotation.
*/
public void rotate(final RotationEvent e);
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Event indicating a swipe was performed by the user.
*
* @see SwipeListener
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public final class SwipeEvent extends GestureEvent {
SwipeEvent() {
// package private
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eawt.event;
/**
* Listener interface for receiving swipe events. A single swipe event
* may be both vertical and horizontal simultaneously, invoking both
* a vertical and horizontal method.
*
* @see SwipeEvent
* @see GestureUtilities
*
* @since Java for Mac OS X 10.5 Update 7, Java for Mac OS X 10.6 Update 2
*/
public interface SwipeListener extends GestureListener {
/**
* Invoked when an upwards swipe gesture is performed by the user.
* @param e representing the occurrence of a swipe.
*/
public void swipedUp(final SwipeEvent e);
/**
* Invoked when a downward swipe gesture is performed by the user.
* @param e representing the occurrence of a swipe.
*/
public void swipedDown(final SwipeEvent e);
/**
* Invoked when a leftward swipe gesture is performed by the user.
* @param e representing the occurrence of a swipe.
*/
public void swipedLeft(final SwipeEvent e);
/**
* Invoked when a rightward swipe gesture is performed by the user.
* @param e representing the occurrence of a swipe.
*/
public void swipedRight(final SwipeEvent e);
}

View file

@ -0,0 +1,34 @@
/*
* Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Classes for receiving gesture events. Provides a mechanism to receive various
* gesture events on JComponents. Gesture notifications are relayed up the
* component hierarchy from the deepest component under the cursor to the
* top-level container. Events may be consumed by deeper components to prevent
* them from propagating to higher components. Gesture listeners are added to
* components using the GestureUtilities helper class.
*/
package com.apple.eawt.event;

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Provides classes for integrating Java applications with the native
* application environment. These classes provide a simple way to implement
* native features to fine tune Java applications on Mac OS X. These listeners
* and handlers can help make Java applications behaviors and user interface
* indistinguishable from native applications. For further information on the
* Mac OS X user interface, consult the <a target=_blank
* href="http://developer.apple.com/mac/library/documentation/UserExperience/Conceptual/AppleHIGuidelines">
* Aqua Human Interface Guidelines</a>.
*/
package com.apple.eawt;

View file

@ -0,0 +1,360 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.eio;
import java.io.*;
/**
* Provides functionality to query and modify Mac-specific file attributes. The methods in this class are based on Finder
* attributes. These attributes in turn are dependent on HFS and HFS+ file systems. As such, it is important to recognize
* their limitation when writing code that must function well across multiple platforms.<p>
*
* In addition to file name suffixes, Mac OS X can use Finder attributes like file {@code type} and {@code creator} codes to
* identify and handle files. These codes are unique 4-byte identifiers. The file {@code type} is a string that describes the
* contents of a file. For example, the file type {@code APPL} identifies the file as an application and therefore
* executable. A file type of {@code TEXT} means that the file contains raw text. Any application that can read raw
* text can open a file of type {@code TEXT}. Applications that use proprietary file types might assign their files a proprietary
* file {@code type} code.
* <p>
* To identify the application that can handle a document, the Finder can look at the {@code creator}. For example, if a user
* double-clicks on a document with the {@code ttxt creator}, it opens up in Text Edit, the application registered
* with the {@code ttxt creator} code. Note that the {@code creator}
* code can be set to any application, not necessarily the application that created it. For example, if you
* use an editor to create an HTML document, you might want to assign a browser's {@code creator} code for the file rather than
* the HTML editor's {@code creator} code. Double-clicking on the document then opens the appropriate browser rather than the
*HTML editor.
*<p>
* If you plan to publicly distribute your application, you must register its creator and any proprietary file types with the Apple
* Developer Connection to avoid collisions with codes used by other developers. You can register a codes online at the
* <a target=_blank href=http://developer.apple.com/dev/cftype/>Creator Code Registration</a> site.
*
* @since 1.4
*/
public final class FileManager {
static {
loadOSXLibrary();
}
@SuppressWarnings("restricted")
private static void loadOSXLibrary() {
System.loadLibrary("osx");
}
/**
* The default
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public static final short kOnAppropriateDisk = -32767;
/**
* Read-only system hierarchy.
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public static final short kSystemDomain = -32766;
/**
* All users of a single machine have access to these resources.
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public static final short kLocalDomain = -32765;
/**
* All users configured to use a common network server has access to these resources.
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public static final short kNetworkDomain = -32764;
/**
* Read/write. Resources that are private to the user.
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
public static final short kUserDomain = -32763;
/**
* Converts an OSType (e.g. "macs"
* from {@literal <CarbonCore/Folders.h>}) into an int.
*
* @param type the 4 character type to convert.
* @return an int representing the 4 character value
*
* @since Java for Mac OS X 10.5 - 1.5
* @since Java for Mac OS X 10.5 Update 1 - 1.6
*/
@SuppressWarnings("deprecation")
public static int OSTypeToInt(String type) {
int result = 0;
byte[] b = { (byte) 0, (byte) 0, (byte) 0, (byte) 0 };
int len = type.length();
if (len > 0) {
if (len > 4) len = 4;
type.getBytes(0, len, b, 4 - len);
}
for (int i = 0; i < len; i++) {
if (i > 0) result <<= 8;
result |= (b[i] & 0xff);
}
return result;
}
/**
* Sets the file {@code type} and {@code creator} codes for a file or folder.
*
* @since 1.4
*/
public static void setFileTypeAndCreator(String filename, int type, int creator) throws IOException {
_setFileTypeAndCreator(filename, type, creator);
}
private static native void _setFileTypeAndCreator(String filename, int type, int creator) throws IOException;
/**
* Sets the file {@code type} code for a file or folder.
*
* @since 1.4
*/
public static void setFileType(String filename, int type) throws IOException {
_setFileType(filename, type);
}
private static native void _setFileType(String filename, int type) throws IOException;
/**
* Sets the file {@code creator} code for a file or folder.
*
* @since 1.4
*/
public static void setFileCreator(String filename, int creator) throws IOException {
_setFileCreator(filename, creator);
}
private static native void _setFileCreator(String filename, int creator) throws IOException;
/**
* Obtains the file {@code type} code for a file or folder.
*
* @since 1.4
*/
public static int getFileType(String filename) throws IOException {
return _getFileType(filename);
}
private static native int _getFileType(String filename) throws IOException;
/**
* Obtains the file {@code creator} code for a file or folder.
*
* @since 1.4
*/
public static int getFileCreator(String filename) throws IOException {
return _getFileCreator(filename);
}
private static native int _getFileCreator(String filename) throws IOException;
/**
* Locates a folder of a particular type. Mac OS X recognizes certain specific folders that have distinct purposes.
* For example, the user's desktop or temporary folder. These folders have corresponding codes. Given one of these codes,
* this method returns the path to that particular folder. Certain folders of a given type may appear in more than
* one domain. For example, although there is only one {@code root} folder, there are multiple {@code pref}
* folders. If this method is called to find the {@code pref} folder, it will return the first one it finds,
* the user's preferences folder in {@code ~/Library/Preferences}. To explicitly locate a folder in a certain
* domain use {@code findFolder(short domain, int folderType)} or
* {@code findFolder(short domain, int folderType, boolean createIfNeeded)}.
*
* @return the path to the folder searched for
*
* @since 1.4
*/
public static String findFolder(int folderType) throws FileNotFoundException {
return findFolder(kOnAppropriateDisk, folderType);
}
/**
* Locates a folder of a particular type, within a given domain. Similar to {@code findFolder(int folderType)}
* except that the domain to look in can be specified. Valid values for {@code domain} include:
* <dl>
* <dt>user</dt>
* <dd>The User domain contains resources specific to the user who is currently logged in</dd>
* <dt>local</dt>
* <dd>The Local domain contains resources shared by all users of the system but are not needed for the system
* itself to run.</dd>
* <dt>network</dt>
* <dd>The Network domain contains resources shared by users of a local area network.</dd>
* <dt>system</dt>
* <dd>The System domain contains the operating system resources installed by Apple.</dd>
* </dl>
*
* @return the path to the folder searched for
*
* @since 1.4
*/
public static String findFolder(short domain, int folderType) throws FileNotFoundException {
return findFolder(domain, folderType, false);
}
/**
* Locates a folder of a particular type within a given domain and optionally creating the folder if it does
* not exist. The behavior is similar to {@code findFolder(int folderType)} and
* {@code findFolder(short domain, int folderType)} except that it can create the folder if it does not already exist.
*
* @param createIfNeeded
* set to {@code true}, by setting to {@code false} the behavior will be the
* same as {@code findFolder(short domain, int folderType, boolean createIfNeeded)}
* @return the path to the folder searched for
*
* @since 1.4
*/
public static String findFolder(short domain, int folderType, boolean createIfNeeded) throws FileNotFoundException {
final String foundFolder = _findFolder(domain, folderType, createIfNeeded);
if (foundFolder == null) throw new FileNotFoundException("Can't find folder: " + Integer.toHexString(folderType));
return foundFolder;
}
private static native String _findFolder(short domain, int folderType, boolean createIfNeeded);
/**
* Opens the path specified by a URL in the appropriate application for that URL. HTTP URL's ({@code http://})
* open in the default browser as set in the Internet pane of System Preferences. File ({@code file://}) and
* FTP URL's ({@code ftp://}) open in the Finder. Note that opening an FTP URL will prompt the user for where
* they want to save the downloaded file(s).
*
* @param url
* the URL for the file you want to open, it can either be an HTTP, FTP, or file url
*
* @deprecated this functionality has been superseded by java.awt.Desktop.browse() and java.awt.Desktop.open()
*
* @since 1.4
*/
@Deprecated
public static void openURL(String url) throws IOException {
_openURL(url);
}
private static native void _openURL(String url) throws IOException;
/**
* @return full pathname for the resource identified by a given name.
*
* @since 1.4
*/
public static String getResource(String resourceName) throws FileNotFoundException {
return getResourceFromBundle(resourceName, null, null);
}
/**
* @return full pathname for the resource identified by a given name and located in the specified bundle subdirectory.
*
* @since 1.4
*/
public static String getResource(String resourceName, String subDirName) throws FileNotFoundException {
return getResourceFromBundle(resourceName, subDirName, null);
}
/**
* Returns the full pathname for the resource identified by the given name and file extension
* and located in the specified bundle subdirectory.
*
* If extension is an empty string or null, the returned pathname is the first one encountered where the
* file name exactly matches name.
*
* If subpath is null, this method searches the top-level nonlocalized resource directory (typically Resources)
* and the top-level of any language-specific directories. For example, suppose you have a modern bundle and
* specify "Documentation" for the subpath parameter. This method would first look in the
* Contents/Resources/Documentation directory of the bundle, followed by the Documentation subdirectories of
* each language-specific .lproj directory. (The search order for the language-specific directories
* corresponds to the user's preferences.) This method does not recurse through any other subdirectories at
* any of these locations. For more details see the AppKit NSBundle documentation.
*
* @return full pathname for the resource identified by the given name and file extension and located in the specified bundle subdirectory.
*
* @since 1.4
*/
public static String getResource(String resourceName, String subDirName, String type) throws FileNotFoundException {
return getResourceFromBundle(resourceName, subDirName, type);
}
private static native String getNativeResourceFromBundle(String resourceName, String subDirName, String type) throws FileNotFoundException;
private static String getResourceFromBundle(String resourceName, String subDirName, String type) throws FileNotFoundException {
final String resourceFromBundle = getNativeResourceFromBundle(resourceName, subDirName, type);
if (resourceFromBundle == null) throw new FileNotFoundException(resourceName);
return resourceFromBundle;
}
/**
* Obtains the path to the current application's NSBundle, may not
* return a valid path if Java was launched from the command line.
*
* @return full pathname of the NSBundle of the current application executable.
*
* @since Java for Mac OS X 10.5 Update 1 - 1.6
* @since Java for Mac OS X 10.5 Update 2 - 1.5
*/
public static String getPathToApplicationBundle() {
return getNativePathToApplicationBundle();
}
private static native String getNativePathToApplicationBundle();
/**
* Moves the specified file to the Trash
*
* @param file the file
* @return returns true if the NSFileManager successfully moved the file to the Trash.
* @throws FileNotFoundException
*
* @since Java for Mac OS X 10.6 Update 1 - 1.6
* @since Java for Mac OS X 10.5 Update 6 - 1.6, 1.5
*/
public static boolean moveToTrash(final File file) throws FileNotFoundException {
if (file == null) throw new FileNotFoundException();
final String fileName = file.getAbsolutePath();
return _moveToTrash(fileName);
}
private static native boolean _moveToTrash(String fileName);
/**
* Reveals the specified file in the Finder
*
* @param file
* the file to reveal
* @return returns true if the NSFileManager successfully revealed the file in the Finder.
* @throws FileNotFoundException
*
* @since Java for Mac OS X 10.6 Update 1 - 1.6
* @since Java for Mac OS X 10.5 Update 6 - 1.6, 1.5
*/
public static boolean revealInFinder(final File file) throws FileNotFoundException {
if (file == null || !file.exists()) throw new FileNotFoundException();
final String fileName = file.getAbsolutePath();
return _revealInFinder(fileName);
}
private static native boolean _revealInFinder(String fileName);
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* Provides classes to allow you to take advantage of functionality of the
* underlying Mac OS X operating system. The classes in this package provide
* access to features of Mac OS X that may or may not be present on other
* platforms. It should be noted that depending on any of these features ties
* Java applications to Mac OS X. For more thorough explanation of some of the
* topics discussed here, please refer to <a target=_blank
* href="http://developer.apple.com/techpubs/macosx/Essentials/SystemOverview/index.html">
* Inside Mac OS X: System Overview</a>.
*/
package com.apple.eio;

View file

@ -0,0 +1,145 @@
/*
* Copyright (c) 2011, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import javax.swing.text.JTextComponent;
import apple.laf.*;
import apple.laf.JRSUIConstants.*;
import com.apple.laf.AquaUtilControlSize.*;
public abstract class AquaBorder implements Border, UIResource {
protected final AquaPainter<? extends JRSUIState> painter;
protected final SizeDescriptor sizeDescriptor;
protected SizeVariant sizeVariant;
protected AquaBorder(final SizeDescriptor sizeDescriptor) {
this.sizeDescriptor = sizeDescriptor;
this.sizeVariant = sizeDescriptor.get(Size.REGULAR);
this.painter = createPainter();
}
protected AquaPainter<? extends JRSUIState> createPainter() {
final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIState.getInstance());
painter.state.set(AlignmentVertical.CENTER);
painter.state.set(AlignmentHorizontal.CENTER);
return painter;
}
protected AquaBorder(final AquaBorder other) {
this.sizeDescriptor = other.sizeDescriptor;
this.sizeVariant = other.sizeVariant;
this.painter = AquaPainter.create(other.painter.state.derive());
painter.state.set(AlignmentVertical.CENTER);
painter.state.set(AlignmentHorizontal.CENTER);
}
protected void setSize(final Size size) {
sizeVariant = sizeDescriptor.get(size);
painter.state.set(size);
}
@Override
public Insets getBorderInsets(final Component c) {
return (Insets) sizeVariant.margins.clone();
}
protected AquaBorder deriveBorderForSize(final Size size) {
try {
final Class<? extends AquaBorder> clazz = getClass();
final AquaBorder border = clazz.getConstructor(new Class<?>[] { clazz }).newInstance(new Object[] { this });
border.setSize(size);
return border;
} catch (final Throwable e) {
return null;
}
}
public static void repaintBorder(final JComponent c) {
JComponent borderedComponent = c;
Border border = c.getBorder();
if (border == null) {
// See if it's inside a JScrollpane or something
final Container p = c.getParent();
if (p instanceof JViewport) {
borderedComponent = (JComponent)p.getParent();
if (borderedComponent != null) border = borderedComponent.getBorder();
}
}
// If we really don't have a border, then bail
// It might be a compound border with a ThemeBorder inside
// The check for that case is tricky, so we just go ahead and repaint any border
if (border == null || borderedComponent == null) return;
final int width = borderedComponent.getWidth();
final int height = borderedComponent.getHeight();
final Insets i = borderedComponent.getInsets();
borderedComponent.repaint(0, 0, width, i.top); // Top edge
borderedComponent.repaint(0, 0, i.left, height); // Left edge
borderedComponent.repaint(0, height - i.bottom, width, i.bottom); // Bottom edge
borderedComponent.repaint(width - i.right, 0, i.right, height); // Right edge
}
// The JScrollPane doesn't let us know if its viewport view has focus
protected boolean isFocused(final Component c) {
// Being really paranoid in case this Component isn't a Swing component
Component focusable = c;
if (c instanceof JScrollPane) {
final JViewport vp = ((JScrollPane)c).getViewport();
if (vp != null) {
focusable = vp.getView();
// Lists, Tables & Trees get focus rings, TextAreas don't (JBuilder puts TextField border on TextAreas)
if (focusable instanceof JTextComponent) return false;
}
} else if (focusable instanceof JTextComponent) {
// non-editable text areas don't draw the focus ring
if (!((javax.swing.text.JTextComponent)focusable).isEditable()) return false;
}
return (focusable instanceof JComponent jComponent) && jComponent.hasFocus();
}
@Override
public boolean isBorderOpaque() { return false; }
@Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
painter.paint(g, c, x, y, w, h);
}
static class Default extends AquaBorder {
Default() { super(new SizeDescriptor(new SizeVariant())); }
}
}

View file

@ -0,0 +1,320 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.*;
import apple.laf.JRSUIConstants.*;
import com.apple.laf.AquaUtilControlSize.*;
import com.apple.laf.AquaUtils.*;
public abstract class AquaButtonBorder extends AquaBorder implements Border, UIResource {
private static final RecyclableSingleton<Dynamic> fDynamic = new RecyclableSingletonFromDefaultConstructor<Dynamic>(Dynamic.class);
public static AquaButtonBorder getDynamicButtonBorder() {
return fDynamic.get();
}
private static final RecyclableSingleton<Toggle> fToggle = new RecyclableSingletonFromDefaultConstructor<Toggle>(Toggle.class);
public static AquaButtonBorder getToggleButtonBorder() {
return fToggle.get();
}
private static final RecyclableSingleton<Toolbar> fToolBar = new RecyclableSingletonFromDefaultConstructor<Toolbar>(Toolbar.class);
public static Border getToolBarButtonBorder() {
return fToolBar.get();
}
private static final RecyclableSingleton<Named> fBevel = new RecyclableSingleton<Named>() {
protected Named getInstance() {
return new Named(Widget.BUTTON_BEVEL, new SizeDescriptor(new SizeVariant().alterMargins(2, 4, 2, 4)));
}
};
public static AquaButtonBorder getBevelButtonBorder() {
return fBevel.get();
}
public AquaButtonBorder(final SizeDescriptor sizeDescriptor) {
super(sizeDescriptor);
}
public AquaButtonBorder(final AquaButtonBorder other) {
super(other);
}
@Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
// for now we don't paint a border. We let the button paint it since there
// needs to be a strict ordering for aqua components.
//paintButton(c, g, x, y, width, height);
}
public void paintButton(final Component c, final Graphics g, int x, int y, int width, int height) {
final AbstractButton b = (AbstractButton)c;
final ButtonModel model = b.getModel();
final State state = getButtonState(b, model);
painter.state.set(state);
painter.state.set((state != State.DISABLED && state != State.INACTIVE) && b.isFocusPainted() && isFocused(b) ? Focused.YES : Focused.NO);
// Full border size of the component.
// g.setColor(new Color(0, 255, 0, 70));
// g.drawRect(x, y, width - 1, height - 1);
final Insets subInsets = sizeVariant.insets;
x += subInsets.left;
y += subInsets.top;
width -= (subInsets.left + subInsets.right);
height -= (subInsets.top + subInsets.bottom);
// Where the native border should start to paint.
// g.setColor(new Color(255, 0, 255, 70));
// g.drawRect(x, y, width - 1, height - 1);
doButtonPaint(b, model, g, x, y, width, height);
}
protected void doButtonPaint(final AbstractButton b, final ButtonModel model, final Graphics g, final int x, final int y, final int width, final int height) {
painter.paint(g, b, x, y, width, height);
}
protected State getButtonState(final AbstractButton b, final ButtonModel model) {
if (!b.isEnabled()) return State.DISABLED;
// The default button shouldn't draw its color when the window is inactive.
// Changed for <rdar://problem/3614421>: Aqua LAF Buttons are incorrectly drawn disabled
// all we need to do is make sure we aren't the default button any more and that
// we aren't active, but we still are enabled if the button is enabled.
// if we set dimmed we would appear disabled despite being enabled and click through
// works so this now matches the text drawing and most importantly the HIG
if (!AquaFocusHandler.isActive(b)) return State.INACTIVE;
if (model.isArmed() && model.isPressed()) return State.PRESSED;
if (model.isSelected() && isSelectionPressing()) return State.PRESSED;
if ((b instanceof JButton) && ((JButton)b).isDefaultButton()) return State.PULSED;
return State.ACTIVE;
}
protected boolean isSelectionPressing() {
return true;
}
public boolean hasSmallerInsets(final JComponent c) {
final Insets inset = c.getInsets();
final Insets margin = sizeVariant.margins;
if (margin.equals(inset)) return false;
return (
(inset.top < margin.top) ||
(inset.left < margin.left) ||
(inset.right < margin.right) ||
(inset.bottom < margin.bottom)
);
}
/**
* Returns the insets of the border.
* @param c the component for which this border insets value applies
*/
@Override
public Insets getBorderInsets(final Component c) {
if (!(c instanceof AbstractButton button)) return new Insets(0, 0, 0, 0);
Insets margin = button.getMargin();
margin = (margin == null) ? new InsetsUIResource(0, 0, 0, 0) : (Insets)margin.clone();
margin.top += sizeVariant.margins.top;
margin.bottom += sizeVariant.margins.bottom;
margin.left += sizeVariant.margins.left;
margin.right += sizeVariant.margins.right;
return margin;
}
public Insets getContentInsets(final AbstractButton b, final int w, final int h) {
return null;
}
public void alterPreferredSize(final Dimension d) {
if (sizeVariant.h > 0 && sizeVariant.h > d.height) d.height = sizeVariant.h;
if (sizeVariant.w > 0 && sizeVariant.w > d.width) d.width = sizeVariant.w;
}
/**
* Returns whether or not the border is opaque. If the border
* is opaque, it is responsible for filling in it's own
* background when painting.
*/
@Override
public boolean isBorderOpaque() {
return false;
}
static final class SizeConstants {
protected static final int fNormalButtonHeight = 29;
protected static final int fNormalMinButtonWidth = 40;
protected static final int fSquareButtonHeightThreshold = 23;
protected static final int fSquareButtonWidthThreshold = 16;
}
public static final class Dynamic extends AquaButtonBorder {
final Insets ALTERNATE_PUSH_INSETS = new Insets(3, 12, 5, 12);
final Insets ALTERNATE_BEVEL_INSETS = new Insets(0, 5, 0, 5);
final Insets ALTERNATE_SQUARE_INSETS = new Insets(0, 2, 0, 2);
public Dynamic() {
super(new SizeDescriptor(new SizeVariant(75, 29).alterMargins(3, 20, 5, 20)) {
public SizeVariant deriveSmall(final SizeVariant v) {
return super.deriveSmall(v.alterMinSize(0, -2).alterMargins(0, -3, 0, -3).alterInsets(-3, -3, -4, -3));
}
public SizeVariant deriveMini(final SizeVariant v) {
return super.deriveMini(v.alterMinSize(0, -2).alterMargins(0, -3, 0, -3).alterInsets(-3, -3, -1, -3));
}
});
}
public Dynamic(final Dynamic other) {
super(other);
}
@Override
public Insets getContentInsets(final AbstractButton b, final int width, final int height) {
final Size size = AquaUtilControlSize.getUserSizeFrom(b);
final Widget style = getStyleForSize(b, size, width, height);
if (style == Widget.BUTTON_PUSH) {
return ALTERNATE_PUSH_INSETS;
}
if (style == Widget.BUTTON_BEVEL_ROUND) {
return ALTERNATE_BEVEL_INSETS;
}
if (style == Widget.BUTTON_BEVEL) {
return ALTERNATE_SQUARE_INSETS;
}
return null;
}
@Override
protected void doButtonPaint(final AbstractButton b, final ButtonModel model, final Graphics g, int x, int y, int width, int height) {
final Size size = AquaUtilControlSize.getUserSizeFrom(b);
painter.state.set(size);
final Widget style = getStyleForSize(b, size, width, height);
painter.state.set(style);
// custom adjusting
if (style == Widget.BUTTON_PUSH && y % 2 == 0) {
if (size == Size.REGULAR) { y += 1; height -= 1; }
if (size == Size.MINI) { height -= 1; x += 4; width -= 8; }
}
super.doButtonPaint(b, model, g, x, y, width, height);
}
protected Widget getStyleForSize(final AbstractButton b, final Size size, final int width, final int height) {
if (size != null && size != Size.REGULAR) {
return Widget.BUTTON_PUSH;
}
if (height < SizeConstants.fSquareButtonHeightThreshold || width < SizeConstants.fSquareButtonWidthThreshold) {
return Widget.BUTTON_BEVEL;
}
if (height <= SizeConstants.fNormalButtonHeight + 3 && width < SizeConstants.fNormalMinButtonWidth) {
return Widget.BUTTON_BEVEL;
}
if ((height > SizeConstants.fNormalButtonHeight + 3) || (b.getIcon() != null) || hasSmallerInsets(b)){
return Widget.BUTTON_BEVEL_ROUND;
}
return Widget.BUTTON_PUSH;
}
}
public static final class Toggle extends AquaButtonBorder {
public Toggle() {
super(new SizeDescriptor(new SizeVariant().alterMargins(6, 6, 6, 6)));
}
public Toggle(final Toggle other) {
super(other);
}
@Override
protected void doButtonPaint(final AbstractButton b, final ButtonModel model, final Graphics g, final int x, final int y, final int width, final int height) {
if (height < SizeConstants.fSquareButtonHeightThreshold || width < SizeConstants.fSquareButtonWidthThreshold) {
painter.state.set(Widget.BUTTON_BEVEL);
super.doButtonPaint(b, model, g, x, y, width, height);
return;
}
painter.state.set(Widget.BUTTON_BEVEL_ROUND);
super.doButtonPaint(b, model, g, x, y + 1, width, height - 1);
}
}
public static class Named extends AquaButtonBorder {
public Named(final Widget widget, final SizeDescriptor sizeDescriptor) {
super(sizeDescriptor);
painter.state.set(widget);
}
// called by reflection
public Named(final Named sizeDescriptor) {
super(sizeDescriptor);
}
@Override
protected void doButtonPaint(final AbstractButton b, final ButtonModel model, final Graphics g, final int x, final int y, final int width, final int height) {
painter.state.set(model.isSelected() ? BooleanValue.YES : BooleanValue.NO);
super.doButtonPaint(b, model, g, x, y, width, height);
}
}
public static final class Toolbar extends AquaButtonBorder {
public Toolbar() {
super(new SizeDescriptor(new SizeVariant().alterMargins(5, 5, 5, 5)));
painter.state.set(Widget.TOOLBAR_ITEM_WELL);
}
public Toolbar(final Toolbar other) {
super(other);
}
@Override
protected void doButtonPaint(final AbstractButton b, final ButtonModel model, final Graphics g, final int x, final int y, final int w, final int h) {
if (!model.isSelected()) return; // only paint when the toolbar button is selected
super.doButtonPaint(b, model, g, x, y, w, h);
}
}
}

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import apple.laf.JRSUIConstants.*;
import com.apple.laf.AquaUtilControlSize.*;
import com.apple.laf.AquaUtils.*;
public final class AquaButtonCheckBoxUI extends AquaButtonLabeledUI {
private static final RecyclableSingleton<AquaButtonCheckBoxUI> instance = new RecyclableSingletonFromDefaultConstructor<AquaButtonCheckBoxUI>(AquaButtonCheckBoxUI.class);
private static final RecyclableSingleton<ImageIcon> sizingIcon = new RecyclableSingleton<ImageIcon>() {
protected ImageIcon getInstance() {
return new ImageIcon(AquaNativeResources.getRadioButtonSizerImage());
}
};
public static ComponentUI createUI(final JComponent b) {
return instance.get();
}
public static Icon getSizingCheckBoxIcon() {
return sizingIcon.get();
}
@Override
public String getPropertyPrefix() {
return "CheckBox" + ".";
}
@Override
protected AquaButtonBorder getPainter() {
return new CheckBoxButtonBorder();
}
public static final class CheckBoxButtonBorder extends LabeledButtonBorder {
public CheckBoxButtonBorder() {
super(new SizeDescriptor(new SizeVariant().replaceMargins("CheckBox.margin")));
painter.state.set(Widget.BUTTON_CHECK_BOX);
}
public CheckBoxButtonBorder(final CheckBoxButtonBorder sizeDescriptor) {
super(sizeDescriptor);
}
}
}

View file

@ -0,0 +1,235 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Insets;
import java.util.*;
import javax.swing.*;
import javax.swing.border.Border;
import apple.laf.JRSUIConstants.*;
import com.apple.laf.AquaUtilControlSize.*;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import static apple.laf.JRSUIConstants.FOCUS_SIZE;
/**
* All the "magic numbers" in this class should go away once
* <rdar://problem/4613866> "default font" and sizes for controls in Java Aqua Look and Feel
* has been addressed, and we can cull widget metrics from the native system.
*/
public final class AquaButtonExtendedTypes {
protected static Border getBorderForPosition(final AbstractButton c, final Object type, final Object logicalPosition) {
final String name = (logicalPosition == null ? (String)type : type + "-" + getRealPositionForLogicalPosition((String)logicalPosition, c.getComponentOrientation().isLeftToRight()));
final TypeSpecifier specifier = getSpecifierByName(name);
if (specifier == null) return null;
final Border border = specifier.getBorder();
if (!(border instanceof AquaBorder)) return border;
return ((AquaBorder)border).deriveBorderForSize(AquaUtilControlSize.getUserSizeFrom(c));
}
protected static String getRealPositionForLogicalPosition(String logicalPosition, boolean leftToRight) {
if (!leftToRight) {
if ("first".equalsIgnoreCase(logicalPosition)) return "last";
if ("last".equalsIgnoreCase(logicalPosition)) return "first";
}
return logicalPosition;
}
abstract static class TypeSpecifier {
final String name;
final boolean setIconFont;
TypeSpecifier(final String name, final boolean setIconFont) {
this.name = name; this.setIconFont = setIconFont;
}
abstract Border getBorder();
}
static class BorderDefinedTypeSpecifier extends TypeSpecifier {
final AquaBorder border;
BorderDefinedTypeSpecifier(final String name, final Widget widget, final SizeVariant variant) {
this(name, widget, variant, 0, 0, 0, 0);
}
BorderDefinedTypeSpecifier(final String name, final Widget widget, final SizeVariant variant, final int smallW, final int smallH, final int miniW, final int miniH) {
super(name, false);
border = initBorder(widget, new SizeDescriptor(variant) {
public SizeVariant deriveSmall(final SizeVariant v) {
v.alterMinSize(smallW, smallH);
return super.deriveSmall(v);
}
public SizeVariant deriveMini(final SizeVariant v) {
v.alterMinSize(miniW, miniH);
return super.deriveMini(v);
}
});
patchUp(border.sizeDescriptor);
}
@Override
Border getBorder() { return border; }
void patchUp(final SizeDescriptor descriptor) {}
AquaBorder initBorder(final Widget widget, final SizeDescriptor desc) {
return new AquaButtonBorder.Named(widget, desc);
}
}
static final class SegmentedBorderDefinedTypeSpecifier extends BorderDefinedTypeSpecifier {
public SegmentedBorderDefinedTypeSpecifier(final String name, final Widget widget, final SegmentPosition position, final SizeVariant variant) {
this(name, widget, position, variant, 0, 0, 0, 0);
}
public SegmentedBorderDefinedTypeSpecifier(final String name, final Widget widget, final SegmentPosition position, final SizeVariant variant, final int smallW, final int smallH, final int miniW, final int miniH) {
super(name, widget, variant, smallW, smallH, miniW, miniH);
border.painter.state.set(SegmentTrailingSeparator.YES);
border.painter.state.set(position);
}
@Override
AquaBorder initBorder(final Widget widget, final SizeDescriptor desc) {
return new SegmentedNamedBorder(widget, desc);
}
}
public static final class SegmentedNamedBorder extends AquaButtonBorder.Named {
public SegmentedNamedBorder(final SegmentedNamedBorder sizeDescriptor) {
super(sizeDescriptor);
}
public SegmentedNamedBorder(final Widget widget, final SizeDescriptor sizeDescriptor) {
super(widget, sizeDescriptor);
}
@Override
protected boolean isSelectionPressing() {
return false;
}
}
protected static TypeSpecifier getSpecifierByName(final String name) {
return typeDefinitions.get().get(name);
}
private static final RecyclableSingleton<Map<String, TypeSpecifier>> typeDefinitions = new RecyclableSingleton<Map<String, TypeSpecifier>>() {
protected Map<String, TypeSpecifier> getInstance() {
return getAllTypes();
}
};
protected static Map<String, TypeSpecifier> getAllTypes() {
final Map<String, TypeSpecifier> specifiersByName = new HashMap<String, TypeSpecifier>();
final Insets focusInsets = new Insets(FOCUS_SIZE, FOCUS_SIZE,
FOCUS_SIZE, FOCUS_SIZE);
final TypeSpecifier[] specifiers = {
new TypeSpecifier("toolbar", true) {
Border getBorder() { return AquaButtonBorder.getToolBarButtonBorder(); }
},
new TypeSpecifier("icon", true) {
Border getBorder() { return AquaButtonBorder.getToggleButtonBorder(); }
},
new TypeSpecifier("text", false) {
Border getBorder() { return UIManager.getBorder("Button.border"); }
},
new TypeSpecifier("toggle", false) {
Border getBorder() { return AquaButtonBorder.getToggleButtonBorder(); }
},
new BorderDefinedTypeSpecifier("combobox", Widget.BUTTON_POP_DOWN, new SizeVariant().alterMargins(7, 10, 6, 30).alterInsets(1, 2, 0, 2).alterMinSize(0, 29), 0, -3, 0, -6) {
void patchUp(final SizeDescriptor descriptor) { descriptor.small.alterMargins(0, 0, 0, -4); descriptor.mini.alterMargins(0, 0, 0, -6); }
},
new BorderDefinedTypeSpecifier("comboboxInternal", Widget.BUTTON_POP_DOWN, new SizeVariant().alterInsets(1, 2, 0, 2).alterMinSize(0, 29), 0, -3, 0, -6),
new BorderDefinedTypeSpecifier("comboboxEndCap", Widget.BUTTON_COMBO_BOX, new SizeVariant().alterMargins(5, 10, 6, 10).alterInsets(1, 2, 0, 2).alterMinSize(0, 29), 0, -3, 0, -6){
void patchUp(final SizeDescriptor descriptor) { border.painter.state.set(IndicatorOnly.YES); }
},
new BorderDefinedTypeSpecifier("square", Widget.BUTTON_BEVEL, new SizeVariant(16, 16).alterMargins(5, 7, 5, 7).replaceInsets(focusInsets)),
new BorderDefinedTypeSpecifier("gradient", Widget.BUTTON_BEVEL_INSET, new SizeVariant(18, 18).alterMargins(8, 9, 8, 9).replaceInsets(focusInsets)) {
void patchUp(SizeDescriptor descriptor) { descriptor.small.alterMargins(0, 0, 0, 0); }
},
new BorderDefinedTypeSpecifier("bevel", Widget.BUTTON_BEVEL_ROUND, new SizeVariant(22, 22).alterMargins(7, 8, 9, 8).alterInsets(0, 0, 0, 0)),
new BorderDefinedTypeSpecifier("textured", Widget.BUTTON_PUSH_TEXTURED, new SizeVariant(28, 28).alterMargins(5, 10, 6, 10).alterInsets(1, 2, 0, 2)),
new BorderDefinedTypeSpecifier("roundRect", Widget.BUTTON_PUSH_INSET, new SizeVariant(28, 28).alterMargins(4, 14, 4, 14).replaceInsets(focusInsets)),
new BorderDefinedTypeSpecifier("recessed", Widget.BUTTON_PUSH_SCOPE, new SizeVariant(28, 28).alterMargins(4, 14, 4, 14).replaceInsets(focusInsets)),
new BorderDefinedTypeSpecifier("well", Widget.FRAME_WELL, new SizeVariant(32, 32)),
new BorderDefinedTypeSpecifier("help", Widget.BUTTON_ROUND_HELP, new SizeVariant().alterInsets(2, 0, 0, 0).alterMinSize(28, 28), -3, -3, -3, -3),
new BorderDefinedTypeSpecifier("round", Widget.BUTTON_ROUND, new SizeVariant().alterInsets(2, 0, 0, 0).alterMinSize(28, 28), -3, -3, -3, -3),
new BorderDefinedTypeSpecifier("texturedRound", Widget.BUTTON_ROUND_INSET, new SizeVariant().alterInsets(0, 0, 0, 0).alterMinSize(26, 26), -2, -2, 0, 0),
new SegmentedBorderDefinedTypeSpecifier("segmented-first", Widget.BUTTON_SEGMENTED, SegmentPosition.FIRST, new SizeVariant().alterMargins(6, 16, 6, 10).alterInsets(2, 3, 2, 0).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmented-middle", Widget.BUTTON_SEGMENTED, SegmentPosition.MIDDLE, new SizeVariant().alterMargins(6, 9, 6, 10).alterInsets(2, 0, 2, 0).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmented-last", Widget.BUTTON_SEGMENTED, SegmentPosition.LAST, new SizeVariant().alterMargins(6, 9, 6, 16).alterInsets(2, 0, 2, 3).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmented-only", Widget.BUTTON_SEGMENTED, SegmentPosition.ONLY, new SizeVariant().alterMargins(6, 16, 6, 16).alterInsets(2, 3, 2, 3).alterMinSize(34, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedRoundRect-first", Widget.BUTTON_SEGMENTED_INSET, SegmentPosition.FIRST, new SizeVariant().alterMargins(6, 12, 6, 8).alterInsets(2, 2, 2, 0).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedRoundRect-middle", Widget.BUTTON_SEGMENTED_INSET, SegmentPosition.MIDDLE, new SizeVariant().alterMargins(6, 8, 6, 8).alterInsets(2, 0, 2, 0).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedRoundRect-last", Widget.BUTTON_SEGMENTED_INSET, SegmentPosition.LAST, new SizeVariant().alterMargins(6, 8, 6, 12).alterInsets(2, 0, 2, 2).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedRoundRect-only", Widget.BUTTON_SEGMENTED_INSET, SegmentPosition.ONLY, new SizeVariant().alterMargins(6, 12, 6, 12).alterInsets(2, 2, 2, 2).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedTexturedRounded-first", Widget.BUTTON_SEGMENTED_SCURVE, SegmentPosition.FIRST, new SizeVariant().alterMargins(6, 12, 6, 8).alterInsets(2, 2, 2, 0).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedTexturedRounded-middle", Widget.BUTTON_SEGMENTED_SCURVE, SegmentPosition.MIDDLE, new SizeVariant().alterMargins(6, 8, 6, 8).alterInsets(2, 0, 2, 0).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedTexturedRounded-last", Widget.BUTTON_SEGMENTED_SCURVE, SegmentPosition.LAST, new SizeVariant().alterMargins(6, 8, 6, 12).alterInsets(2, 0, 2, 2).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedTexturedRounded-only", Widget.BUTTON_SEGMENTED_SCURVE, SegmentPosition.ONLY, new SizeVariant().alterMargins(6, 12, 6, 12).alterInsets(2, 2, 2, 2).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedTextured-first", Widget.BUTTON_SEGMENTED_TEXTURED, SegmentPosition.FIRST, new SizeVariant().alterMargins(6, 12, 6, 8).alterInsets(2, 3, 2, 0).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedTextured-middle", Widget.BUTTON_SEGMENTED_TEXTURED, SegmentPosition.MIDDLE, new SizeVariant().alterMargins(6, 8, 6, 8).alterInsets(2, 0, 2, 0).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedTextured-last", Widget.BUTTON_SEGMENTED_TEXTURED, SegmentPosition.LAST, new SizeVariant().alterMargins(6, 8, 6, 12).alterInsets(2, 0, 2, 3).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedTextured-only", Widget.BUTTON_SEGMENTED_TEXTURED, SegmentPosition.ONLY, new SizeVariant().alterMargins(6, 12, 6, 12).alterInsets(2, 3, 2, 3).alterMinSize(0, 28), 0, -3, 0, -3),
new SegmentedBorderDefinedTypeSpecifier("segmentedCapsule-first", Widget.BUTTON_SEGMENTED_TOOLBAR, SegmentPosition.FIRST, new SizeVariant().alterMargins(6, 12, 6, 8).alterInsets(2, 2, 2, 0).alterMinSize(0, 28), 0, 0, 0, 0),
new SegmentedBorderDefinedTypeSpecifier("segmentedCapsule-middle", Widget.BUTTON_SEGMENTED_TOOLBAR, SegmentPosition.MIDDLE, new SizeVariant().alterMargins(6, 8, 6, 8).alterInsets(2, 0, 2, 0).alterMinSize(0, 28), 0, 0, 0, 0),
new SegmentedBorderDefinedTypeSpecifier("segmentedCapsule-last", Widget.BUTTON_SEGMENTED_TOOLBAR, SegmentPosition.LAST, new SizeVariant().alterMargins(6, 8, 6, 12).alterInsets(2, 0, 2, 2).alterMinSize(0, 28), 0, 0, 0, 0),
new SegmentedBorderDefinedTypeSpecifier("segmentedCapsule-only", Widget.BUTTON_SEGMENTED_TOOLBAR, SegmentPosition.ONLY, new SizeVariant().alterMargins(6, 12, 6, 12).alterInsets(2, 2, 2, 2).alterMinSize(34, 28), 0, 0, 0, 0),
new BorderDefinedTypeSpecifier("segmentedGradient-first", Widget.BUTTON_BEVEL_INSET, new SizeVariant(18, 18).alterMargins(4, 5, 4, 5).replaceInsets(new Insets(-2,-0,-2,-0))),
new BorderDefinedTypeSpecifier("segmentedGradient-middle", Widget.BUTTON_BEVEL_INSET, new SizeVariant(18, 18).alterMargins(4, 5, 4, 5).replaceInsets(new Insets(-2,-1,-2,-0))),
new BorderDefinedTypeSpecifier("segmentedGradient-last", Widget.BUTTON_BEVEL_INSET, new SizeVariant(18, 18).alterMargins(4, 5, 4, 5).replaceInsets(new Insets(-2,-1,-2,-0))),
new BorderDefinedTypeSpecifier("segmentedGradient-only", Widget.BUTTON_BEVEL_INSET, new SizeVariant(18, 18).alterMargins(4, 5, 4, 5).replaceInsets(new Insets(-2,-1,-2,-1))),
new BorderDefinedTypeSpecifier("disclosure", Widget.BUTTON_DISCLOSURE, new SizeVariant().alterMargins(10, 10, 10, 10).replaceInsets(focusInsets).alterMinSize(27, 27), -1, -1, -1, -1),
//new BorderDefinedTypeSpecifier("disclosureTriangle", false, Widget.DISCLOSURE_TRIANGLE, new SizeVariant()),
new BorderDefinedTypeSpecifier("scrollColumnSizer", Widget.SCROLL_COLUMN_SIZER, new SizeVariant(14, 14)),
};
for (final TypeSpecifier specifier : specifiers) {
specifiersByName.put(specifier.name, specifier);
}
return specifiersByName;
}
}

View file

@ -0,0 +1,275 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import apple.laf.*;
import apple.laf.JRSUIConstants.*;
import apple.laf.JRSUIState.ValueState;
import com.apple.laf.AquaUtilControlSize.*;
import com.apple.laf.AquaUtils.RecyclableSingleton;
public abstract class AquaButtonLabeledUI extends AquaButtonToggleUI implements Sizeable {
private static final RecyclableSizingIcon regularIcon = new RecyclableSizingIcon(18);
private static final RecyclableSizingIcon smallIcon = new RecyclableSizingIcon(16);
private static final RecyclableSizingIcon miniIcon = new RecyclableSizingIcon(14);
protected static final class RecyclableSizingIcon extends RecyclableSingleton<Icon> {
final int iconSize;
public RecyclableSizingIcon(final int iconSize) { this.iconSize = iconSize; }
@Override
protected Icon getInstance() {
return new ImageIcon(new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB_PRE));
}
}
protected AquaButtonBorder widgetBorder;
public AquaButtonLabeledUI() {
widgetBorder = getPainter();
}
@Override
public void applySizeFor(final JComponent c, final Size newSize) {
super.applySizeFor(c, newSize);
widgetBorder = (AquaButtonBorder)widgetBorder.deriveBorderForSize(newSize);
}
public Icon getDefaultIcon(final JComponent c) {
final Size componentSize = AquaUtilControlSize.getUserSizeFrom(c);
if (componentSize == Size.REGULAR) return regularIcon.get();
if (componentSize == Size.SMALL) return smallIcon.get();
if (componentSize == Size.MINI) return miniIcon.get();
return regularIcon.get();
}
@Override
protected void setThemeBorder(final AbstractButton b) {
super.setThemeBorder(b);
Border border = b.getBorder();
if (border == null || border instanceof UIResource) {
// Set the correct border
b.setBorder(AquaButtonBorder.getBevelButtonBorder());
}
}
protected abstract AquaButtonBorder getPainter();
@Override
public synchronized void paint(final Graphics g, final JComponent c) {
final AbstractButton b = (AbstractButton)c;
final ButtonModel model = b.getModel();
final Font f = c.getFont();
g.setFont(f);
final FontMetrics fm = g.getFontMetrics();
Dimension size = b.getSize();
final Insets i = c.getInsets();
Rectangle viewRect = new Rectangle(b.getWidth(), b.getHeight());
Rectangle iconRect = new Rectangle();
Rectangle textRect = new Rectangle();
Icon altIcon = b.getIcon();
final boolean isCellEditor = c.getParent() instanceof CellRendererPane;
// This was erroneously removed to fix [3155996] but really we wanted the controls to just be
// opaque. So we put this back in to fix [3179839] (radio buttons not being translucent)
if (b.isOpaque() || isCellEditor) {
g.setColor(b.getBackground());
g.fillRect(0, 0, size.width, size.height);
}
// only do this if borders are on!
if (((AbstractButton)c).isBorderPainted() && !isCellEditor) {
final Border border = c.getBorder();
if (border instanceof AquaButtonBorder) {
((AquaButtonBorder)border).paintButton(c, g, viewRect.x, viewRect.y, viewRect.width, viewRect.height);
}
}
viewRect.x = i.left;
viewRect.y = i.top;
viewRect.width = b.getWidth() - (i.right + viewRect.x);
viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
// normal size ??
// at some point we substitute the small icon instead of the normal icon
// we should base this on height. Use normal unless we are under a certain size
// see our button code!
final String text = SwingUtilities.layoutCompoundLabel(c, fm, b.getText(), altIcon != null ? altIcon : getDefaultIcon(b), b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, b.getText() == null ? 0 : b.getIconTextGap());
// fill background
// draw the native radio button stuff here.
if (altIcon == null) {
widgetBorder.paintButton(c, g, iconRect.x, iconRect.y, iconRect.width, iconRect.height);
} else {
// Paint the button
if (!model.isEnabled()) {
if (model.isSelected()) {
altIcon = b.getDisabledSelectedIcon();
} else {
altIcon = b.getDisabledIcon();
}
} else if (model.isPressed() && model.isArmed()) {
altIcon = b.getPressedIcon();
if (altIcon == null) {
// Use selected icon
altIcon = b.getSelectedIcon();
}
} else if (model.isSelected()) {
if (b.isRolloverEnabled() && model.isRollover()) {
altIcon = b.getRolloverSelectedIcon();
if (altIcon == null) {
altIcon = b.getSelectedIcon();
}
} else {
altIcon = b.getSelectedIcon();
}
} else if (b.isRolloverEnabled() && model.isRollover()) {
altIcon = b.getRolloverIcon();
}
if (altIcon == null) {
altIcon = b.getIcon();
}
int offset = 0;
if (b.isFocusOwner() && b.isFocusPainted()) {
offset = 2;
altIcon = AquaFocus.createFocusedIcon(altIcon, c, 2);
}
altIcon.paintIcon(c, g, iconRect.x - offset, iconRect.y - offset);
}
// Draw the Text
if (text != null) {
final View v = (View)c.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
v.paint(g, textRect);
} else {
paintText(g, b, textRect, text);
}
}
}
/**
* The preferred size of the button
*/
@Override
public Dimension getPreferredSize(final JComponent c) {
if (c.getComponentCount() > 0) { return null; }
final AbstractButton b = (AbstractButton)c;
final String text = b.getText();
Icon buttonIcon = b.getIcon();
if (buttonIcon == null) {
buttonIcon = getDefaultIcon(b);
}
final Font font = b.getFont();
final FontMetrics fm = b.getFontMetrics(font);
Rectangle prefViewRect = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);
Rectangle prefIconRect = new Rectangle();
Rectangle prefTextRect = new Rectangle();
SwingUtilities.layoutCompoundLabel(c, fm, text, buttonIcon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), prefViewRect, prefIconRect, prefTextRect, text == null ? 0 : b.getIconTextGap());
// find the union of the icon and text rects (from Rectangle.java)
final int x1 = Math.min(prefIconRect.x, prefTextRect.x);
final int x2 = Math.max(prefIconRect.x + prefIconRect.width, prefTextRect.x + prefTextRect.width);
final int y1 = Math.min(prefIconRect.y, prefTextRect.y);
final int y2 = Math.max(prefIconRect.y + prefIconRect.height, prefTextRect.y + prefTextRect.height);
int width = x2 - x1;
int height = y2 - y1;
Insets prefInsets = b.getInsets();
width += prefInsets.left + prefInsets.right;
height += prefInsets.top + prefInsets.bottom;
return new Dimension(width, height);
}
public abstract static class LabeledButtonBorder extends AquaButtonBorder {
public LabeledButtonBorder(final SizeDescriptor sizeDescriptor) {
super(sizeDescriptor);
}
public LabeledButtonBorder(final LabeledButtonBorder other) {
super(other);
}
@Override
protected AquaPainter<? extends JRSUIState> createPainter() {
final AquaPainter<ValueState> painter = AquaPainter.create(JRSUIStateFactory.getLabeledButton());
painter.state.set(AlignmentVertical.CENTER);
painter.state.set(AlignmentHorizontal.CENTER);
return painter;
}
@Override
protected void doButtonPaint(final AbstractButton b, final ButtonModel model, final Graphics g, final int x, final int y, final int width, final int height) {
painter.state.set(AquaUtilControlSize.getUserSizeFrom(b));
((ValueState)painter.state).setValue(model.isSelected() ? isIndeterminate(b) ? 2 : 1 : 0); // 2=mixed, 1=on, 0=off
super.doButtonPaint(b, model, g, x, y, width, height);
}
@Override
protected State getButtonState(final AbstractButton b, final ButtonModel model) {
final State state = super.getButtonState(b, model);
if (state == State.INACTIVE) return State.INACTIVE;
if (state == State.DISABLED) return State.DISABLED;
if (model.isArmed() && model.isPressed()) return State.PRESSED;
if (model.isSelected()) return State.ACTIVE;
return state;
}
static boolean isIndeterminate(final AbstractButton b) {
return "indeterminate".equals(b.getClientProperty("JButton.selectedState"));
}
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import javax.swing.JComponent;
import javax.swing.ImageIcon;
import javax.swing.JRadioButton;
import javax.swing.Icon;
import javax.swing.AbstractButton;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import javax.swing.DefaultButtonModel;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.plaf.ComponentUI;
import java.awt.Component;
import java.awt.AWTKeyStroke;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import apple.laf.JRSUIConstants.Widget;
import com.apple.laf.AquaUtilControlSize.SizeVariant;
import com.apple.laf.AquaUtilControlSize.SizeDescriptor;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
import java.util.HashSet;
import java.util.Set;
import java.util.Enumeration;
public final class AquaButtonRadioUI extends AquaButtonLabeledUI {
private static final RecyclableSingleton<AquaButtonRadioUI> instance = new RecyclableSingletonFromDefaultConstructor<AquaButtonRadioUI>(AquaButtonRadioUI.class);
private static final RecyclableSingleton<ImageIcon> sizingIcon = new RecyclableSingleton<ImageIcon>() {
protected ImageIcon getInstance() {
return new ImageIcon(AquaNativeResources.getRadioButtonSizerImage());
}
};
public static ComponentUI createUI(final JComponent b) {
return instance.get();
}
public static Icon getSizingRadioButtonIcon() {
return sizingIcon.get();
}
@Override
protected String getPropertyPrefix() {
return "RadioButton" + ".";
}
@Override
protected AquaButtonBorder getPainter() {
return new RadioButtonBorder();
}
public static final class RadioButtonBorder extends LabeledButtonBorder {
public RadioButtonBorder() {
super(new SizeDescriptor(new SizeVariant().replaceMargins("RadioButton.margin")));
painter.state.set(Widget.BUTTON_RADIO);
}
public RadioButtonBorder(final RadioButtonBorder other) {
super(other);
}
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import com.apple.laf.AquaUtils.*;
public class AquaButtonToggleUI extends AquaButtonUI {
// Create PLAF
private static final RecyclableSingleton<AquaButtonToggleUI> aquaToggleButtonUI = new RecyclableSingletonFromDefaultConstructor<AquaButtonToggleUI>(AquaButtonToggleUI.class);
public static ComponentUI createUI(final JComponent b) {
return aquaToggleButtonUI.get();
}
@Override
protected String getPropertyPrefix() {
return "ToggleButton" + ".";
}
}

View file

@ -0,0 +1,675 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.event.FocusEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JRadioButton;
import javax.swing.JRootPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.ButtonUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.RootPaneUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicButtonListener;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import apple.laf.JRSUIConstants.Size;
import com.apple.laf.AquaButtonExtendedTypes.TypeSpecifier;
import com.apple.laf.AquaUtilControlSize.Sizeable;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
import sun.swing.MnemonicHandler;
import sun.swing.SwingUtilities2;
public class AquaButtonUI extends BasicButtonUI implements Sizeable {
private static final String BUTTON_TYPE = "JButton.buttonType";
private static final String SEGMENTED_BUTTON_POSITION = "JButton.segmentPosition";
private static final RecyclableSingleton<AquaButtonUI> buttonUI = new RecyclableSingletonFromDefaultConstructor<AquaButtonUI>(AquaButtonUI.class);
public static ComponentUI createUI(final JComponent c) {
return buttonUI.get();
}
// Has the shared instance defaults been initialized?
private boolean defaults_initialized = false;
private Color defaultDisabledTextColor = null;
@Override
protected void installDefaults(final AbstractButton b) {
// load shared instance defaults
final String pp = getPropertyPrefix();
if (!defaults_initialized) {
defaultDisabledTextColor = UIManager.getColor(pp + "disabledText");
defaults_initialized = true;
}
setButtonMarginIfNeeded(b, UIManager.getInsets(pp + "margin"));
LookAndFeel.installColorsAndFont(b, pp + "background", pp + "foreground", pp + "font");
LookAndFeel.installProperty(b, "opaque", UIManager.getBoolean(pp + "opaque"));
final Object borderProp = b.getClientProperty(BUTTON_TYPE);
boolean hasBorder = false;
if (borderProp != null) {
hasBorder = setButtonType(b, borderProp);
}
if (!hasBorder) setThemeBorder(b);
final Object segmentProp = b.getClientProperty(SEGMENTED_BUTTON_POSITION);
if (segmentProp != null) {
final Border border = b.getBorder();
if (!(border instanceof AquaBorder)) return;
b.setBorder(AquaButtonExtendedTypes.getBorderForPosition(b, b.getClientProperty(BUTTON_TYPE), segmentProp));
}
}
@Override
public void applySizeFor(final JComponent c, final Size size) {
// this space intentionally left blank
// (subclasses need to do work here)
}
protected void setThemeBorder(final AbstractButton b) {
// Set the correct border
final ButtonUI genericUI = b.getUI();
if (!(genericUI instanceof AquaButtonUI)) return;
final AquaButtonUI ui = (AquaButtonUI)genericUI;
Border border = b.getBorder();
if (!ui.isBorderFromProperty(b) && (border == null || border instanceof UIResource || border instanceof AquaButtonBorder)) {
// See BasicGraphicsUtils.getPreferredButtonSize - it returns null for preferred size,
// causing it to use the subcomponent's size, which doesn't allow space for Aqua pushbuttons
boolean iconFont = true;
if (isOnToolbar(b)) {
if (b instanceof JToggleButton) {
border = AquaButtonBorder.getToolBarButtonBorder();
} else {
border = AquaButtonBorder.getBevelButtonBorder();
}
} else if (b.getIcon() != null || b.getComponentCount() > 0) {
// radar 3308129 && (b.getText() == null || b.getText().equals("")))
// we used to only do this for buttons that had images and no text
// now we do it for all buttons that have any images - they cannot
// be a default button.
border = AquaButtonBorder.getToggleButtonBorder();
} else {
border = UIManager.getBorder(getPropertyPrefix() + "border");
iconFont = false;
}
b.setBorder(border);
final Font currentFont = b.getFont();
if (iconFont && (currentFont == null || currentFont instanceof UIResource)) {
b.setFont(UIManager.getFont("IconButton.font"));
}
}
}
protected static boolean isOnToolbar(final AbstractButton b) {
Component parent = b.getParent();
while (parent != null) {
if (parent instanceof JToolBar) return true;
parent = parent.getParent();
}
return false;
}
// A state that affects border has changed. Make sure we have the right one
protected static void updateBorder(final AbstractButton b) {
// See if the button has overridden the automatic button type
final Object prop = b.getClientProperty(BUTTON_TYPE);
if (prop != null) return;
final ButtonUI ui = b.getUI();
if (!(ui instanceof AquaButtonUI)) return;
if (b.getBorder() != null) ((AquaButtonUI)ui).setThemeBorder(b);
}
protected void setButtonMarginIfNeeded(final AbstractButton b, final Insets insets) {
final Insets margin = b.getMargin();
if (margin == null || (margin instanceof UIResource)) {
b.setMargin(insets);
}
}
public boolean isBorderFromProperty(final AbstractButton button) {
return button.getClientProperty(BUTTON_TYPE) != null;
}
protected boolean setButtonType(final AbstractButton b, final Object prop) {
if (!(prop instanceof String)) {
b.putClientProperty(BUTTON_TYPE, null); // so we know to use the automatic button type
return false;
}
final String buttonType = (String)prop;
boolean iconFont = true;
final TypeSpecifier specifier = AquaButtonExtendedTypes.getSpecifierByName(buttonType);
if (specifier != null) {
b.setBorder(specifier.getBorder());
iconFont = specifier.setIconFont;
}
final Font currentFont = b.getFont();
if (currentFont == null || currentFont instanceof UIResource) {
b.setFont(UIManager.getFont(iconFont ? "IconButton.font" : "Button.font"));
}
return true;
}
@Override
protected void installListeners(final AbstractButton b) {
super.installListeners(b);
AquaButtonListener listener = getAquaButtonListener(b);
if (listener != null) {
// put the listener in the button's client properties so that
// we can get at it later
b.putClientProperty(this, listener);
}
installHierListener(b);
AquaUtilControlSize.addSizePropertyListener(b);
}
@Override
protected void installKeyboardActions(final AbstractButton b) {
final BasicButtonListener listener = (BasicButtonListener)b.getClientProperty(this);
if (listener != null) listener.installKeyboardActions(b);
}
// Uninstall PLAF
@Override
public void uninstallUI(final JComponent c) {
uninstallKeyboardActions((AbstractButton)c);
uninstallListeners((AbstractButton)c);
uninstallDefaults((AbstractButton)c);
//BasicHTML.updateRenderer(c, "");
}
@Override
protected void uninstallKeyboardActions(final AbstractButton b) {
final BasicButtonListener listener = (BasicButtonListener)b.getClientProperty(this);
if (listener != null) listener.uninstallKeyboardActions(b);
}
@Override
protected void uninstallListeners(final AbstractButton b) {
super.uninstallListeners(b);
b.putClientProperty(this, null);
uninstallHierListener(b);
AquaUtilControlSize.removeSizePropertyListener(b);
}
@Override
protected void uninstallDefaults(final AbstractButton b) {
LookAndFeel.uninstallBorder(b);
defaults_initialized = false;
}
// Create Listeners
@Override
protected AquaButtonListener createButtonListener(final AbstractButton b) {
return new AquaButtonListener(b);
}
/**
* Returns the AquaButtonListener for the passed in Button, or null if one
* could not be found.
*/
private AquaButtonListener getAquaButtonListener(AbstractButton b) {
MouseMotionListener[] listeners = b.getMouseMotionListeners();
if (listeners != null) {
for (MouseMotionListener listener : listeners) {
if (listener instanceof AquaButtonListener) {
return (AquaButtonListener) listener;
}
}
}
return null;
}
// Paint Methods
@Override
public void paint(final Graphics g, final JComponent c) {
final AbstractButton b = (AbstractButton)c;
final ButtonModel model = b.getModel();
final Insets i = c.getInsets();
Rectangle viewRect = new Rectangle(b.getWidth(), b.getHeight());
Rectangle iconRect = new Rectangle();
Rectangle textRect = new Rectangle();
// we are overdrawing here with translucent colors so we get
// a darkening effect. How can we avoid it. Try clear rect?
if (b.isOpaque()) {
g.setColor(c.getBackground());
g.fillRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);
}
AquaButtonBorder aquaBorder = null;
if (((AbstractButton)c).isBorderPainted()) {
final Border border = c.getBorder();
if (border instanceof AquaButtonBorder) {
// only do this if borders are on!
// this also takes care of focus painting.
aquaBorder = (AquaButtonBorder)border;
aquaBorder.paintButton(c, g, viewRect.x, viewRect.y, viewRect.width, viewRect.height);
}
} else {
if (b.isOpaque()) {
viewRect.x = i.left - 2;
viewRect.y = i.top - 2;
viewRect.width = b.getWidth() - (i.right + viewRect.x) + 4;
viewRect.height = b.getHeight() - (i.bottom + viewRect.y) + 4;
if (b.isContentAreaFilled() || model.isSelected()) {
if (model.isSelected()) // Toggle buttons
g.setColor(c.getBackground().darker());
else g.setColor(c.getBackground());
g.fillRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);
}
}
// needs focus to be painted
// for now we don't know exactly what to do...we'll see!
if (b.isFocusPainted() && b.hasFocus()) {
// paint UI specific focus
paintFocus(g, b, viewRect, textRect, iconRect);
}
}
// performs icon and text rect calculations
final String text = layoutAndGetText(g, b, aquaBorder, i, viewRect, iconRect, textRect);
// Paint the Icon
if (b.getIcon() != null) {
paintIcon(g, b, iconRect);
}
if (textRect.width == 0) {
textRect.width = 50;
}
if (text != null && !text.isEmpty()) {
final View v = (View)c.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
v.paint(g, textRect);
} else {
paintText(g, b, textRect, text);
}
}
}
@Override
protected void paintFocus(Graphics g, AbstractButton b,
Rectangle viewRect, Rectangle textRect, Rectangle iconRect) {
Graphics2D g2d = null;
Stroke oldStroke = null;
Object oldAntialiasingHint = null;
Color oldColor = g.getColor();
if (g instanceof Graphics2D) {
g2d = (Graphics2D)g;
oldStroke = g2d.getStroke();
oldAntialiasingHint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g2d.setStroke(new BasicStroke(3));
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
}
Color ringColor = UIManager.getColor("Focus.color");
g.setColor(ringColor);
g.drawRoundRect(5, 3, b.getWidth() - 10, b.getHeight() - 7, 15, 15);
if (g2d != null) {
// Restore old state of Java2D renderer
g2d.setStroke(oldStroke);
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
oldAntialiasingHint);
}
g.setColor(oldColor);
}
protected String layoutAndGetText(final Graphics g, final AbstractButton b, final AquaButtonBorder aquaBorder, final Insets i, Rectangle viewRect, Rectangle iconRect, Rectangle textRect) {
// re-initialize the view rect to the selected insets
viewRect.x = i.left;
viewRect.y = i.top;
viewRect.width = b.getWidth() - (i.right + viewRect.x);
viewRect.height = b.getHeight() - (i.bottom + viewRect.y);
// reset the text and icon rects
textRect.x = textRect.y = textRect.width = textRect.height = 0;
iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
// setup the font
g.setFont(b.getFont());
final FontMetrics fm = g.getFontMetrics();
// layout the text and icon
final String originalText = b.getText();
final String text = SwingUtilities.layoutCompoundLabel(b, fm, originalText, b.getIcon(), b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, originalText == null ? 0 : b.getIconTextGap());
if (text == originalText || aquaBorder == null) return text; // everything fits
// if the text didn't fit - check if the aqua border has alternate Insets that are more adhering
final Insets alternateContentInsets = aquaBorder.getContentInsets(b, b.getWidth(), b.getHeight());
if (alternateContentInsets != null) {
// recursively call and don't pass AquaBorder
return layoutAndGetText(g, b, null, alternateContentInsets, viewRect, iconRect, textRect);
}
// there is no Aqua border, go with what we've got
return text;
}
protected void paintIcon(final Graphics g, final AbstractButton b, final Rectangle localIconRect) {
final ButtonModel model = b.getModel();
Icon icon = b.getIcon();
Icon tmpIcon = null;
if (icon == null) return;
Icon selectedIcon = null;
// the fallback icon should be based on the selected state
if (model.isSelected()) {
selectedIcon = b.getSelectedIcon();
if (selectedIcon != null) {
icon = selectedIcon;
}
}
if (!model.isEnabled()) {
if (model.isSelected()) {
tmpIcon = b.getDisabledSelectedIcon();
if (tmpIcon == null) {
tmpIcon = selectedIcon;
}
}
if (tmpIcon == null) {
tmpIcon = b.getDisabledIcon();
}
} else if (model.isPressed() && model.isArmed()) {
tmpIcon = b.getPressedIcon();
if (tmpIcon == null) {
if (icon instanceof ImageIcon) {
tmpIcon = new ImageIcon(AquaUtils.generateSelectedDarkImage(((ImageIcon)icon).getImage()));
}
}
} else if (b.isRolloverEnabled() && model.isRollover()) {
if (model.isSelected()) {
tmpIcon = b.getRolloverSelectedIcon();
if (tmpIcon == null) {
tmpIcon = selectedIcon;
}
}
if (tmpIcon == null) {
tmpIcon = b.getRolloverIcon();
}
} else if (model.isSelected()) {
tmpIcon = b.getSelectedIcon();
}
if (model.isEnabled() && b.isFocusOwner() && b.getBorder() instanceof AquaButtonBorder.Toolbar) {
if (tmpIcon == null) tmpIcon = icon;
if (tmpIcon instanceof ImageIcon) {
tmpIcon = AquaFocus.createFocusedIcon(tmpIcon, b, 3);
tmpIcon.paintIcon(b, g, localIconRect.x - 3, localIconRect.y - 3);
return;
}
}
if (tmpIcon != null) {
icon = tmpIcon;
}
icon.paintIcon(b, g, localIconRect.x, localIconRect.y);
}
/**
* As of Java 2 platform v 1.4 this method should not be used or overridden.
* Use the paintText method which takes the AbstractButton argument.
*/
@Override
protected void paintText(final Graphics g, final JComponent c, final Rectangle localTextRect, final String text) {
final AbstractButton b = (AbstractButton)c;
final ButtonModel model = b.getModel();
final FontMetrics fm = g.getFontMetrics();
final int mnemonicIndex = MnemonicHandler.isMnemonicHidden() ? -1 : b.getDisplayedMnemonicIndex();
/* Draw the Text */
if (model.isEnabled()) {
/*** paint the text normally */
g.setColor(b.getForeground());
} else {
/*** paint the text disabled ***/
g.setColor(defaultDisabledTextColor);
}
SwingUtilities2.drawStringUnderlineCharAt(c, g, text, mnemonicIndex, localTextRect.x, localTextRect.y + fm.getAscent());
}
@Override
protected void paintText(final Graphics g, final AbstractButton b, final Rectangle localTextRect, final String text) {
paintText(g, (JComponent)b, localTextRect, text);
}
@Override
protected void paintButtonPressed(final Graphics g, final AbstractButton b) {
paint(g, b);
}
// Layout Methods
@Override
public Dimension getMinimumSize(final JComponent c) {
final Dimension d = getPreferredSize(c);
final View v = (View)c.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS);
}
return d;
}
@Override
public Dimension getPreferredSize(final JComponent c) {
final AbstractButton b = (AbstractButton)c;
// fix for Radar #3134273
final Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, b.getIconTextGap());
if (d == null) return null;
final Border border = b.getBorder();
if (border instanceof AquaButtonBorder) {
((AquaButtonBorder)border).alterPreferredSize(d);
}
return d;
}
@Override
public Dimension getMaximumSize(final JComponent c) {
final Dimension d = getPreferredSize(c);
final View v = (View)c.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS);
}
return d;
}
private static final RecyclableSingleton<AquaHierarchyButtonListener> fHierListener = new RecyclableSingletonFromDefaultConstructor<AquaHierarchyButtonListener>(AquaHierarchyButtonListener.class);
static AquaHierarchyButtonListener getAquaHierarchyButtonListener() {
return fHierListener.get();
}
// We need to know when ordinary JButtons are put on JToolbars, but not JComboBoxButtons
// JToggleButtons always have the same border
private boolean shouldInstallHierListener(final AbstractButton b) {
return (b instanceof JButton || b instanceof JToggleButton && !(b instanceof AquaComboBoxButton) && !(b instanceof JCheckBox) && !(b instanceof JRadioButton));
}
protected void installHierListener(final AbstractButton b) {
if (shouldInstallHierListener(b)) {
// super put the listener in the button's client properties
b.addHierarchyListener(getAquaHierarchyButtonListener());
}
}
protected void uninstallHierListener(final AbstractButton b) {
if (shouldInstallHierListener(b)) {
b.removeHierarchyListener(getAquaHierarchyButtonListener());
}
}
static final class AquaHierarchyButtonListener implements HierarchyListener {
// Every time a hierarchy is changed we need to check if the button is moved on or from
// the toolbar. If that is the case, we need to re-set the border of the button.
@Override
public void hierarchyChanged(final HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) == 0) return;
final Object o = e.getSource();
if (!(o instanceof AbstractButton)) return;
final AbstractButton b = (AbstractButton)o;
final ButtonUI ui = b.getUI();
if (!(ui instanceof AquaButtonUI)) return;
if (!(b.getBorder() instanceof UIResource)) return; // if the border is not one of ours, or null
((AquaButtonUI)ui).setThemeBorder(b);
}
}
final class AquaButtonListener extends BasicButtonListener {
protected final AbstractButton b;
public AquaButtonListener(final AbstractButton b) {
super(b);
this.b = b;
}
@Override
public void focusGained(final FocusEvent e) {
((Component)e.getSource()).repaint();
}
@Override
public void focusLost(final FocusEvent e) {
// 10-06-03 VL: [Radar 3187049]
// If focusLost arrives while the button has been left-clicked this would disarm the button,
// causing actionPerformed not to fire on mouse release!
//b.getModel().setArmed(false);
b.getModel().setPressed(false);
((Component)e.getSource()).repaint();
}
@Override
public void propertyChange(final PropertyChangeEvent e) {
super.propertyChange(e);
final String propertyName = e.getPropertyName();
// Repaint the button, since its border needs to handle the new state.
if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(propertyName)) {
b.repaint();
return;
}
if ("icon".equals(propertyName) || "text".equals(propertyName)) {
setThemeBorder(b);
return;
}
if (BUTTON_TYPE.equals(propertyName)) {
// Forced border types
final String value = (String)e.getNewValue();
final Border border = AquaButtonExtendedTypes.getBorderForPosition(b, value, b.getClientProperty(SEGMENTED_BUTTON_POSITION));
if (border != null) {
b.setBorder(border);
}
return;
}
if (SEGMENTED_BUTTON_POSITION.equals(propertyName)) {
final Border border = b.getBorder();
if (!(border instanceof AquaBorder)) return;
b.setBorder(AquaButtonExtendedTypes.getBorderForPosition(b, b.getClientProperty(BUTTON_TYPE), e.getNewValue()));
}
if ("componentOrientation".equals(propertyName)) {
final Border border = b.getBorder();
if (!(border instanceof AquaBorder)) return;
Object buttonType = b.getClientProperty(BUTTON_TYPE);
Object buttonPosition = b.getClientProperty(SEGMENTED_BUTTON_POSITION);
if (buttonType != null && buttonPosition != null) {
b.setBorder(AquaButtonExtendedTypes.getBorderForPosition(b, buttonType, buttonPosition));
}
}
}
}
}

View file

@ -0,0 +1,227 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JEditorPane;
import javax.swing.JTextArea;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import javax.swing.text.DefaultCaret;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import javax.swing.SwingUtilities;
@SuppressWarnings("serial") // Superclass is not serializable across versions
public final class AquaCaret extends DefaultCaret
implements UIResource, PropertyChangeListener {
private boolean isMultiLineEditor;
private boolean mFocused = false;
private boolean fPainting = false;
@Override
public void install(final JTextComponent c) {
super.install(c);
isMultiLineEditor = c instanceof JTextArea || c instanceof JEditorPane;
c.addPropertyChangeListener(this);
}
@Override
public void deinstall(final JTextComponent c) {
c.removePropertyChangeListener(this);
super.deinstall(c);
}
@Override
protected Highlighter.HighlightPainter getSelectionPainter() {
return AquaHighlighter.getInstance();
}
/**
* Only show the flashing caret if the selection range is zero
*/
@Override
public void setVisible(boolean e) {
if (e) e = getDot() == getMark();
super.setVisible(e);
}
@Override
protected void fireStateChanged() {
// If we have focus the caret should only flash if the range length is zero
if (mFocused) setVisible(getComponent().isEditable());
super.fireStateChanged();
}
@Override
public void propertyChange(final PropertyChangeEvent evt) {
final String propertyName = evt.getPropertyName();
if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(propertyName)) {
final JTextComponent comp = ((JTextComponent)evt.getSource());
if (evt.getNewValue() == Boolean.TRUE) {
setVisible(comp.hasFocus());
} else {
setVisible(false);
}
if (getDot() != getMark()) comp.getUI().damageRange(comp, getDot(), getMark());
}
}
// --- FocusListener methods --------------------------
private boolean shouldSelectAllOnFocus = true;
@Override
public void focusGained(final FocusEvent e) {
final JTextComponent component = getComponent();
if (!component.isEnabled() || !component.isEditable()) {
super.focusGained(e);
return;
}
mFocused = true;
if (!shouldSelectAllOnFocus) {
shouldSelectAllOnFocus = true;
super.focusGained(e);
return;
}
if (isMultiLineEditor) {
super.focusGained(e);
return;
}
final int end = component.getDocument().getLength();
final int dot = getDot();
final int mark = getMark();
if (dot == mark) {
if (dot == 0) {
component.setCaretPosition(end);
component.moveCaretPosition(0);
} else if (dot == end) {
component.setCaretPosition(0);
component.moveCaretPosition(end);
}
}
super.focusGained(e);
}
@Override
public void focusLost(final FocusEvent e) {
mFocused = false;
shouldSelectAllOnFocus = true;
if (isMultiLineEditor) {
setVisible(false);
getComponent().repaint();
} else {
super.focusLost(e);
}
}
// This fixes the problem where when on the mac you have to ctrl left click to
// get popup triggers the caret has code that only looks at button number.
// see radar # 3125390
@Override
public void mousePressed(final MouseEvent e) {
if (!e.isPopupTrigger() && !(SwingUtilities.isLeftMouseButton(e) &&
e.getClickCount() == 3)) {
super.mousePressed(e);
shouldSelectAllOnFocus = false;
}
}
/**
* Damages the area surrounding the caret to cause
* it to be repainted in a new location. If paint()
* is reimplemented, this method should also be
* reimplemented. This method should update the
* caret bounds (x, y, width, and height).
*
* @param r the current location of the caret
* @see #paint
*/
@Override
protected synchronized void damage(final Rectangle r) {
if (r == null || fPainting) return;
x = r.x - 4;
y = r.y;
width = 10;
height = r.height;
// Don't damage the border area. We can't paint a partial border, so get the
// intersection of the caret rectangle and the component less the border, if any.
final Rectangle caretRect = new Rectangle(x, y, width, height);
final Border border = getComponent().getBorder();
final Rectangle alloc = getComponent().getBounds();
alloc.x = alloc.y = 0;
if (border != null && border.isBorderOpaque()) {
final Insets borderInsets = border.getBorderInsets(getComponent());
alloc.x += borderInsets.left;
alloc.y += borderInsets.top;
alloc.width -= borderInsets.left + borderInsets.right;
alloc.height -= borderInsets.top + borderInsets.bottom;
Rectangle2D.intersect(caretRect, alloc, caretRect);
x = caretRect.x;
y = caretRect.y;
width = Math.max(caretRect.width, 1);
height = Math.max(caretRect.height, 1);
} else {
x = alloc.x;
y = alloc.y;
width = alloc.width;
height = alloc.height;
}
repaint();
}
// See <rdar://problem/3833837> 1.4.2_05-141.3: JTextField performance with
// Aqua L&F. We are getting into a circular condition with the BasicCaret
// paint code since it doesn't know about the fact that our damage routine
// above eliminates the border. Sadly we can't easily change either one, so
// we will add a painting flag and not damage during a repaint.
@Override
public void paint(final Graphics g) {
if (isVisible()) {
fPainting = true;
super.paint(g);
fPainting = false;
}
}
}

View file

@ -0,0 +1,278 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import javax.accessibility.AccessibleContext;
import javax.swing.ButtonModel;
import javax.swing.CellRendererPane;
import javax.swing.DefaultButtonModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListCellRenderer;
import javax.swing.UIManager;
import javax.swing.plaf.UIResource;
import apple.laf.JRSUIConstants.AlignmentHorizontal;
import apple.laf.JRSUIConstants.AlignmentVertical;
import apple.laf.JRSUIConstants.ArrowsOnly;
import apple.laf.JRSUIConstants.Focused;
import apple.laf.JRSUIConstants.IndicatorOnly;
import apple.laf.JRSUIConstants.Size;
import apple.laf.JRSUIConstants.State;
import apple.laf.JRSUIConstants.Widget;
import apple.laf.JRSUIState;
@SuppressWarnings("serial") // Superclass is not serializable across versions
final class AquaComboBoxButton extends JButton {
protected final JComboBox<Object> comboBox;
protected final JList<?> list;
protected final CellRendererPane rendererPane;
protected final AquaComboBoxUI ui;
protected final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIState.getInstance());
boolean isPopDown;
boolean isSquare;
protected AquaComboBoxButton(final AquaComboBoxUI ui,
final JComboBox<Object> comboBox,
final CellRendererPane rendererPane,
final JList<?> list) {
super("");
putClientProperty("JButton.buttonType", "comboboxInternal");
this.ui = ui;
this.comboBox = comboBox;
this.rendererPane = rendererPane;
this.list = list;
setModel(new DefaultButtonModel() {
@Override
public void setArmed(final boolean armed) {
super.setArmed(isPressed() ? true : armed);
}
});
setEnabled(comboBox.isEnabled());
}
@Override
public boolean isEnabled() {
return comboBox == null ? true : comboBox.isEnabled();
}
@Override
public boolean isFocusable() {
return false;
}
protected void setIsPopDown(final boolean isPopDown) {
this.isPopDown = isPopDown;
repaint();
}
protected void setIsSquare(final boolean isSquare) {
this.isSquare = isSquare;
repaint();
}
protected State getState(final ButtonModel buttonModel) {
if (!comboBox.isEnabled()) return State.DISABLED;
if (!AquaFocusHandler.isActive(comboBox)) return State.INACTIVE;
if (buttonModel.isArmed()) return State.PRESSED;
return State.ACTIVE;
}
@Override
public void paintComponent(final Graphics g) {
// Don't Paint the button as usual
// super.paintComponent( g );
final boolean editable = comboBox.isEditable();
int top = 0;
int left = 0;
int width = getWidth();
int height = getHeight();
if (comboBox.isOpaque()) {
g.setColor(getBackground());
g.fillRect(0, 0, width, height);
}
final Size size = AquaUtilControlSize.getUserSizeFrom(comboBox);
painter.state.set(size == null ? Size.REGULAR : size);
final ButtonModel buttonModel = getModel();
painter.state.set(getState(buttonModel));
painter.state.set(AlignmentVertical.CENTER);
if (AquaComboBoxUI.isTableCellEditor(comboBox)) {
painter.state.set(AlignmentHorizontal.RIGHT);
painter.state.set(Widget.BUTTON_POP_UP);
painter.state.set(ArrowsOnly.YES);
painter.paint(g, this, left, top, width, height);
doRendererPaint(g, buttonModel, editable, getInsets(), left, top, width, height);
return;
}
painter.state.set(AlignmentHorizontal.CENTER);
final Insets insets = getInsets();
if (!editable) {
top += insets.top;
left += insets.left;
width -= insets.left + insets.right;
height -= insets.top + insets.bottom;
}
if (height <= 0 || width <= 0) {
return;
}
boolean hasFocus = comboBox.hasFocus();
if (editable) {
painter.state.set(Widget.BUTTON_COMBO_BOX);
painter.state.set(IndicatorOnly.YES);
painter.state.set(AlignmentHorizontal.LEFT);
hasFocus |= comboBox.getEditor().getEditorComponent().hasFocus();
} else {
painter.state.set(IndicatorOnly.NO);
painter.state.set(AlignmentHorizontal.CENTER);
if (isPopDown) {
painter.state.set(isSquare ? Widget.BUTTON_POP_DOWN_SQUARE : Widget.BUTTON_POP_DOWN);
} else {
painter.state.set(isSquare ? Widget.BUTTON_POP_UP_SQUARE : Widget.BUTTON_POP_UP);
}
}
painter.state.set(hasFocus ? Focused.YES : Focused.NO);
if (isSquare) {
painter.paint(g, comboBox, left + 2, top - 1, width - 4, height);
} else {
painter.paint(g, comboBox, left, top, width, height);
}
// Let the renderer paint
if (!editable && comboBox != null) {
doRendererPaint(g, buttonModel, editable, insets, left, top, width, height);
}
}
private Component getRendererComponent() {
final ListCellRenderer<Object> renderer = comboBox.getRenderer();
return renderer.getListCellRendererComponent(list, comboBox.getSelectedItem(), -1, false, false);
}
protected void doRendererPaint(final Graphics g, final ButtonModel buttonModel, final boolean editable, final Insets insets, int left, int top, int width, int height) {
// fake it out! not renderPressed
final Component c = getRendererComponent();
if (!editable && !AquaComboBoxUI.isTableCellEditor(comboBox)) {
final int indentLeft = 10;
final int buttonWidth = 24;
// hardcoded for now. We should adjust as necessary.
top += 1;
height -= 4;
left += indentLeft;
width -= (indentLeft + buttonWidth);
}
c.setFont(rendererPane.getFont());
if (buttonModel.isArmed() && buttonModel.isPressed()) {
if (isOpaque()) {
c.setBackground(UIManager.getColor("Button.select"));
}
c.setForeground(comboBox.getForeground());
} else if (!comboBox.isEnabled()) {
if (isOpaque()) {
c.setBackground(UIManager.getColor("ComboBox.disabledBackground"));
}
c.setForeground(UIManager.getColor("ComboBox.disabledForeground"));
} else {
c.setForeground(comboBox.getForeground());
c.setBackground(comboBox.getBackground());
}
// Sun Fix for 4238829: should lay out the JPanel.
boolean shouldValidate = false;
if (c instanceof JPanel) {
shouldValidate = true;
}
final int iconWidth = 0;
final int cWidth = width - (insets.right + iconWidth);
// fix for 3156483 we need to crop images that are too big.
// if (height > 18)
// always crop.
{
top = height / 2 - 8;
height = 19;
}
// It doesn't need to draw its background, we handled it
final Color bg = c.getBackground();
final boolean inhibitBackground = bg instanceof UIResource;
if (inhibitBackground) c.setBackground(new Color(0, 0, 0, 0));
rendererPane.paintComponent(g, c, this, left, top, cWidth, height, shouldValidate); // h - (insets.top + insets.bottom) );
if (inhibitBackground) c.setBackground(bg);
// Remove component from renderer pane, allowing it to be gc'ed.
rendererPane.remove(c);
}
@Override
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleAquaComboBoxButton();
}
return accessibleContext;
}
private final class AccessibleAquaComboBoxButton extends AccessibleJButton {
@Override
public String getAccessibleName() {
String name = super.getAccessibleName();
if ((name == null || name.isEmpty())
&& (!comboBox.isEditable() && comboBox.getSelectedItem() != null)) {
Component c = getRendererComponent();
name = c.getAccessibleContext().getAccessibleName();
}
return name;
}
}
}

View file

@ -0,0 +1,426 @@
/*
* Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import javax.swing.Box;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicComboPopup;
import sun.lwawt.macosx.CPlatformWindow;
@SuppressWarnings("serial") // Superclass is not serializable across versions
final class AquaComboBoxPopup extends BasicComboPopup {
static final int FOCUS_RING_PAD_LEFT = 6;
static final int FOCUS_RING_PAD_RIGHT = 6;
static final int FOCUS_RING_PAD_BOTTOM = 5;
protected Component topStrut;
protected Component bottomStrut;
protected boolean isPopDown = false;
public AquaComboBoxPopup(final JComboBox<Object> cBox) {
super(cBox);
}
@Override
protected void configurePopup() {
super.configurePopup();
setBorderPainted(false);
setBorder(null);
updateContents(false);
// TODO: CPlatformWindow?
putClientProperty(CPlatformWindow.WINDOW_FADE_OUT, Integer.valueOf(150));
}
public void updateContents(final boolean remove) {
// for more background on this issue, see AquaMenuBorder.getBorderInsets()
isPopDown = isPopdown();
if (isPopDown) {
if (remove) {
if (topStrut != null) {
this.remove(topStrut);
}
if (bottomStrut != null) {
this.remove(bottomStrut);
}
} else {
add(scroller);
}
} else {
if (topStrut == null) {
topStrut = Box.createVerticalStrut(4);
bottomStrut = Box.createVerticalStrut(4);
}
if (remove) remove(scroller);
this.add(topStrut);
this.add(scroller);
this.add(bottomStrut);
}
}
protected Dimension getBestPopupSizeForRowCount(final int maxRowCount) {
final int currentElementCount = comboBox.getModel().getSize();
final int rowCount = Math.min(maxRowCount, currentElementCount);
final Dimension popupSize = new Dimension();
final ListCellRenderer<Object> renderer = list.getCellRenderer();
for (int i = 0; i < rowCount; i++) {
final Object value = list.getModel().getElementAt(i);
final Component c = renderer.getListCellRendererComponent(list, value, i, false, false);
final Dimension prefSize = c.getPreferredSize();
popupSize.height += prefSize.height;
popupSize.width = Math.max(prefSize.width, popupSize.width);
}
popupSize.width += 10;
return popupSize;
}
protected boolean shouldScroll() {
return comboBox.getItemCount() > comboBox.getMaximumRowCount();
}
protected boolean isPopdown() {
return shouldScroll() || AquaComboBoxUI.isPopdown(comboBox);
}
@Override
public void show() {
final int startItemCount = comboBox.getItemCount();
final Rectangle popupBounds = adjustPopupAndGetBounds();
if (popupBounds == null) return; // null means don't show
comboBox.firePopupMenuWillBecomeVisible();
show(comboBox, popupBounds.x, popupBounds.y);
// hack for <rdar://problem/4905531> JComboBox does not fire popupWillBecomeVisible if item count is 0
final int afterShowItemCount = comboBox.getItemCount();
if (afterShowItemCount == 0) {
hide();
return;
}
if (startItemCount != afterShowItemCount) {
final Rectangle newBounds = adjustPopupAndGetBounds();
list.setSize(newBounds.width, newBounds.height);
pack();
final Point newLoc = comboBox.getLocationOnScreen();
setLocation(newLoc.x + newBounds.x, newLoc.y + newBounds.y);
}
// end hack
list.requestFocusInWindow();
}
@Override
protected JList<Object> createList() {
return new JList<Object>(comboBox.getModel()) {
@Override
@SuppressWarnings("deprecation")
public void processMouseEvent(MouseEvent e) {
if (e.isMetaDown()) {
e = new MouseEvent((Component) e.getSource(), e.getID(),
e.getWhen(),
e.getModifiers() ^ InputEvent.META_MASK,
e.getX(), e.getY(), e.getXOnScreen(),
e.getYOnScreen(), e.getClickCount(),
e.isPopupTrigger(), MouseEvent.NOBUTTON);
}
super.processMouseEvent(e);
}
};
}
protected Rectangle adjustPopupAndGetBounds() {
if (isPopDown != isPopdown()) {
updateContents(true);
}
int popupBoundsY = comboBox.getBounds().height;
if (comboBox.isEditable() && comboBox.getBorder() != null) {
Insets inset = comboBox.getBorder().getBorderInsets(comboBox);
popupBoundsY += inset.top + inset.bottom;
}
final Dimension popupSize = getBestPopupSizeForRowCount(comboBox.getMaximumRowCount());
final Rectangle popupBounds = computePopupBounds(0, popupBoundsY, popupSize.width, popupSize.height);
if (popupBounds == null) return null; // returning null means don't show anything
final Dimension realPopupSize = popupBounds.getSize();
scroller.setMaximumSize(realPopupSize);
scroller.setPreferredSize(realPopupSize);
scroller.setMinimumSize(realPopupSize);
list.invalidate();
final int selectedIndex = comboBox.getSelectedIndex();
if (selectedIndex == -1) {
list.clearSelection();
} else {
list.setSelectedIndex(selectedIndex);
}
list.ensureIndexIsVisible(list.getSelectedIndex());
return popupBounds;
}
// Get the bounds of the screen where the menu should appear
// p is the origin of the combo box in screen bounds
Rectangle getBestScreenBounds(final Point p) {
//System.err.println("GetBestScreenBounds p: "+ p.x + ", " + p.y);
final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice[] gs = ge.getScreenDevices();
for (final GraphicsDevice gd : gs) {
final GraphicsConfiguration[] gc = gd.getConfigurations();
for (final GraphicsConfiguration element0 : gc) {
final Rectangle gcBounds = element0.getBounds();
if (gcBounds.contains(p)) {
return getAvailableScreenArea(gcBounds, element0);
}
}
}
// Hmm. Origin's off screen, but is any part on?
final Rectangle comboBoxBounds = comboBox.getBounds();
comboBoxBounds.setLocation(p);
for (final GraphicsDevice gd : gs) {
final GraphicsConfiguration[] gc = gd.getConfigurations();
for (final GraphicsConfiguration element0 : gc) {
final Rectangle gcBounds = element0.getBounds();
if (gcBounds.intersects(comboBoxBounds)) {
return getAvailableScreenArea(gcBounds, element0);
}
}
}
return null;
}
private Rectangle getAvailableScreenArea(Rectangle bounds,
GraphicsConfiguration gc) {
Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
return new Rectangle(bounds.x + insets.left, bounds.y + insets.top,
bounds.width - insets.left - insets.right,
bounds.height - insets.top - insets.bottom);
}
private int getComboBoxEdge(int py, boolean bottom) {
int offset = bottom ? 9 : -9;
// if py is less than new y we have a clipped combo, so leave it alone.
return Math.min((py / 2) + offset, py);
}
@Override
protected Rectangle computePopupBounds(int px, int py, int pw, int ph) {
final int itemCount = comboBox.getModel().getSize();
final boolean isPopdown = isPopdown();
final boolean isTableCellEditor = AquaComboBoxUI.isTableCellEditor(comboBox);
if (isPopdown && !isTableCellEditor) {
// place the popup just below the button, which is
// near the center of a large combo box
py = getComboBoxEdge(py, true);
}
// px & py are relative to the combo box
// **** Common calculation - applies to the scrolling and menu-style ****
final Point p = new Point(0, 0);
SwingUtilities.convertPointToScreen(p, comboBox);
//System.err.println("First Converting from point to screen: 0,0 is now " + p.x + ", " + p.y);
final Rectangle scrBounds = getBestScreenBounds(p);
//System.err.println("BestScreenBounds is " + scrBounds);
// If the combo box is totally off screen, do whatever super does
if (scrBounds == null) return super.computePopupBounds(px, py, pw, ph);
// line up with the bottom of the text field/button (or top, if we have to go above it)
// and left edge if left-to-right, right edge if right-to-left
final Insets comboBoxInsets = comboBox.getInsets();
final Rectangle comboBoxBounds = comboBox.getBounds();
if (shouldScroll()) {
pw += 15;
}
if (isPopdown) {
pw += 4;
}
// the popup should be wide enough for the items but not wider than the screen it's on
final int minWidth = comboBoxBounds.width - (comboBoxInsets.left + comboBoxInsets.right);
pw = Math.max(minWidth, pw);
final boolean leftToRight = AquaUtils.isLeftToRight(comboBox);
if (leftToRight) {
px += comboBoxInsets.left;
if (!isPopDown) px -= FOCUS_RING_PAD_LEFT;
} else {
px = comboBoxBounds.width - pw - comboBoxInsets.right;
if (!isPopDown) px += FOCUS_RING_PAD_RIGHT;
}
py -= (comboBoxInsets.bottom); //sja fix was +kInset
// Make sure it's all on the screen - shift it by the amount it's off
p.x += px;
p.y += py; // Screen location of px & py
if (p.x < scrBounds.x) {
px = px + (scrBounds.x - p.x);
}
if (p.y < scrBounds.y) {
py = py + (scrBounds.y - p.y);
}
final Point top = new Point(0, 0);
SwingUtilities.convertPointFromScreen(top, comboBox);
//System.err.println("Converting from point to screen: 0,0 is now " + top.x + ", " + top.y);
// Since the popup is at zero in this coord space, the maxWidth == the X coord of the screen right edge
// (it might be wider than the screen, if the combo is off the left edge)
final int maxWidth = Math.min(scrBounds.width, top.x + scrBounds.x + scrBounds.width) - 2; // subtract some buffer space
pw = Math.min(maxWidth, pw);
if (pw < minWidth) {
px -= (minWidth - pw);
pw = minWidth;
}
// this is a popup window, and will continue calculations below
if (!isPopdown) {
// popup windows are slightly inset from the combo end-cap
pw -= 6;
return computePopupBoundsForMenu(px, py, pw, ph, itemCount, scrBounds);
}
// don't attempt to inset table cell editors
if (!isTableCellEditor) {
pw -= (FOCUS_RING_PAD_LEFT + FOCUS_RING_PAD_RIGHT);
if (leftToRight) {
px += FOCUS_RING_PAD_LEFT;
}
}
final Rectangle r = new Rectangle(px, py, pw, ph);
if (r.y + r.height < top.y + scrBounds.y + scrBounds.height) {
// Adjust popup location to match popup for non-editable without border
if (!comboBox.isEditable()) {
r.y += (comboBoxInsets.top + comboBoxInsets.bottom) / 2;
}
return r;
}
// Check whether it goes below the bottom of the screen, if so flip it
int newY = getComboBoxEdge(comboBoxBounds.height, false) - ph - comboBoxInsets.top;
if (newY > top.y + scrBounds.y) {
return new Rectangle(px, newY, r.width, r.height);
} else {
// There are no place at top, move popup to the center of the screen
r.y = top.y + scrBounds.y + Math.max(0, (scrBounds.height - ph) / 2 );
r.height = Math.min(scrBounds.height, ph);
}
return r;
}
// The one to use when itemCount <= maxRowCount. Size never adjusts for arrows
// We want it positioned so the selected item is right above the combo box
protected Rectangle computePopupBoundsForMenu(final int px, final int py,
final int pw, final int ph,
final int itemCount,
final Rectangle scrBounds) {
//System.err.println("computePopupBoundsForMenu: " + px + "," + py + " " + pw + "," + ph);
//System.err.println("itemCount: " +itemCount +" src: "+ scrBounds);
int elementSize = 0; //kDefaultItemSize;
if (list != null && itemCount > 0) {
final Rectangle cellBounds = list.getCellBounds(0, 0);
if (cellBounds != null) elementSize = cellBounds.height;
}
int offsetIndex = comboBox.getSelectedIndex();
if (offsetIndex < 0) offsetIndex = 0;
list.setSelectedIndex(offsetIndex);
final int selectedLocation = elementSize * offsetIndex;
final Point top = new Point(0, scrBounds.y);
final Point bottom = new Point(0, scrBounds.y + scrBounds.height - 20); // Allow some slack
SwingUtilities.convertPointFromScreen(top, comboBox);
SwingUtilities.convertPointFromScreen(bottom, comboBox);
final Rectangle popupBounds = new Rectangle(px, py, pw, ph);// Relative to comboBox
final int theRest = ph - selectedLocation;
// If the popup fits on the screen and the selection appears under the mouse w/o scrolling, cool!
// If the popup won't fit on the screen, adjust its position but not its size
// and rewrite this to support arrows - JLists always move the contents so they all show
// Test to see if it extends off the screen
final boolean extendsOffscreenAtTop = selectedLocation > -top.y;
final boolean extendsOffscreenAtBottom = theRest > bottom.y;
if (extendsOffscreenAtTop) {
popupBounds.y = top.y + 1;
// Round it so the selection lines up with the combobox
popupBounds.y = (popupBounds.y / elementSize) * elementSize;
} else if (extendsOffscreenAtBottom) {
// Provide blank space at top for off-screen stuff to scroll into
popupBounds.y = bottom.y - popupBounds.height; // popupBounds.height has already been adjusted to fit
} else { // fits - position it so the selectedLocation is under the mouse
popupBounds.y = -selectedLocation;
}
// Center the selected item on the combobox
final int height = comboBox.getHeight();
final Insets insets = comboBox.getInsets();
final int buttonSize = height - (insets.top + insets.bottom);
final int diff = (buttonSize - elementSize) / 2 + insets.top;
popupBounds.y += diff - FOCUS_RING_PAD_BOTTOM;
return popupBounds;
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import javax.swing.*;
import javax.swing.plaf.UIResource;
@SuppressWarnings("serial") // Superclass is not serializable across versions
final class AquaComboBoxRenderer extends AquaComboBoxRendererInternal<Object> implements UIResource {
public AquaComboBoxRenderer(final JComboBox<?> comboBox) {
super(comboBox);
}
}

View file

@ -0,0 +1,167 @@
/*
* Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import sun.swing.SwingUtilities2;
import javax.swing.*;
import java.awt.*;
@SuppressWarnings("serial") // Superclass is not serializable across versions
class AquaComboBoxRendererInternal<E> extends JLabel implements ListCellRenderer<E> {
final JComboBox<?> fComboBox;
boolean fSelected;
boolean fChecked;
boolean fInList;
boolean fEditable;
boolean fDrawCheckedItem = true;
// Provides space for a checkbox, and is translucent
public AquaComboBoxRendererInternal(final JComboBox<?> comboBox) {
super();
fComboBox = comboBox;
}
// Don't include checkIcon space, because this is also used for button size calculations
// - the popup-size calc will get checkIcon space from getInsets
@Override
public Dimension getPreferredSize() {
// From BasicComboBoxRenderer - trick to avoid zero-height items
final Dimension size;
final String text = getText();
if (text == null || text.isEmpty()) {
setText(" ");
size = super.getPreferredSize();
setText("");
} else {
size = super.getPreferredSize();
}
return size;
}
// Don't paint the border here, it gets painted by the UI
@Override
protected void paintBorder(final Graphics g) {
}
@Override
public int getBaseline(int width, int height) {
return super.getBaseline(width, height) - 1;
}
// Really means is the one with the mouse over it
@Override
public Component getListCellRendererComponent(final JList<? extends E> list,
final E value, int index,
final boolean isSelected,
final boolean cellHasFocus) {
fInList = (index >= 0); // When the button wants the item painted, it passes in -1
fSelected = isSelected;
if (index < 0) {
index = fComboBox.getSelectedIndex();
}
// changed this to not ask for selected index but directly compare the current item and selected item
// different from basic because basic has no concept of checked, just has the last one selected,
// and the user changes selection. We have selection and a check mark.
// we used to call fComboBox.getSelectedIndex which ends up being a very bad call for large checkboxes
// it does a linear compare of every object in the checkbox until it finds the selected one, so if
// we have a 5000 element list we will 5000 * (selected index) .equals() of objects.
// See Radar #3141307
// Fix for Radar # 3204287 where we ask for an item at a negative index!
if (index >= 0) {
final Object item = fComboBox.getItemAt(index);
fChecked = fInList && item != null && item.equals(fComboBox.getSelectedItem());
} else {
fChecked = false;
}
fEditable = fComboBox.isEditable();
if (isSelected) {
if (fEditable) {
setBackground(UIManager.getColor("List.selectionBackground"));
setForeground(UIManager.getColor("List.selectionForeground"));
} else {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
} else {
if (fEditable) {
setBackground(UIManager.getColor("List.background"));
setForeground(UIManager.getColor("List.foreground"));
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
}
setFont(list.getFont());
if (value instanceof Icon) {
setIcon((Icon)value);
} else {
setText((value == null) ? " " : value.toString());
}
return this;
}
@Override
public Insets getInsets(Insets insets) {
if (insets == null) insets = new Insets(0, 0, 0, 0);
insets.top = 1;
insets.bottom = 1;
insets.right = 5;
insets.left = (fInList && !fEditable ? 16 + 7 : 5);
return insets;
}
protected void setDrawCheckedItem(final boolean drawCheckedItem) {
this.fDrawCheckedItem = drawCheckedItem;
}
// Paint this component, and a checkbox if it's the selected item and not in the button
@Override
protected void paintComponent(final Graphics g) {
if (fInList) {
if (fSelected && !fEditable) {
AquaMenuPainter.instance().paintSelectedMenuItemBackground(g, getWidth(), getHeight());
} else {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
if (fChecked && !fEditable && fDrawCheckedItem) {
final int y = getHeight() - 4;
g.setColor(getForeground());
SwingUtilities2.drawString(fComboBox, g, "\u2713", 6, y);
}
}
super.paintComponent(g);
}
}

View file

@ -0,0 +1,770 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.accessibility.AccessibleState;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.ComboBoxEditor;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JRootPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComboBoxUI;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.ListUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import apple.laf.JRSUIConstants.Size;
import com.apple.laf.AquaUtilControlSize.Sizeable;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.ClientPropertyApplicator.Property;
// Inspired by MetalComboBoxUI, which also has a combined text-and-arrow button for noneditables
public final class AquaComboBoxUI extends BasicComboBoxUI implements Sizeable {
static final String POPDOWN_CLIENT_PROPERTY_KEY = "JComboBox.isPopDown";
static final String ISSQUARE_CLIENT_PROPERTY_KEY = "JComboBox.isSquare";
public static ComponentUI createUI(final JComponent c) {
return new AquaComboBoxUI();
}
private boolean wasOpaque;
@Override
public void installUI(final JComponent c) {
super.installUI(c);
// this doesn't work right now, because the JComboBox.init() method calls
// .setOpaque(false) directly, and doesn't allow the LaF to decided. Bad Sun!
LookAndFeel.installProperty(c, "opaque", Boolean.FALSE);
wasOpaque = c.isOpaque();
c.setOpaque(false);
}
@Override
public void uninstallUI(final JComponent c) {
c.setOpaque(wasOpaque);
super.uninstallUI(c);
}
@Override
protected void installListeners() {
super.installListeners();
AquaUtilControlSize.addSizePropertyListener(comboBox);
}
@Override
protected void uninstallListeners() {
AquaUtilControlSize.removeSizePropertyListener(comboBox);
super.uninstallListeners();
}
@Override
protected void installComponents() {
super.installComponents();
// client properties must be applied after the components have been installed,
// because isSquare and isPopdown are applied to the installed button
getApplicator().attachAndApplyClientProperties(comboBox);
}
@Override
protected void uninstallComponents() {
getApplicator().removeFrom(comboBox);
// AquaButtonUI install some listeners to all parents, which means that
// we need to uninstall UI here to remove those listeners, because after
// we remove them from ComboBox we lost the latest reference to them,
// and our standard uninstallUI machinery will not call them.
arrowButton.getUI().uninstallUI(arrowButton);
super.uninstallComponents();
}
@Override
protected ItemListener createItemListener() {
return new ItemListener() {
long lastBlink = 0L;
public void itemStateChanged(final ItemEvent e) {
if (e.getStateChange() != ItemEvent.SELECTED) return;
if (!popup.isVisible()) return;
// sometimes, multiple selection changes can occur while the popup is up,
// and blinking more than "once" (in a second) is not desirable
final long now = System.currentTimeMillis();
if (now - 1000 < lastBlink) return;
lastBlink = now;
final JList<Object> itemList = popup.getList();
final ListUI listUI = itemList.getUI();
if (!(listUI instanceof AquaListUI)) return;
final AquaListUI aquaListUI = (AquaListUI)listUI;
final int selectedIndex = comboBox.getSelectedIndex();
final ListModel<Object> dataModel = itemList.getModel();
if (dataModel == null) return;
final Object value = dataModel.getElementAt(selectedIndex);
AquaUtils.blinkMenu(new AquaUtils.Selectable() {
public void paintSelected(final boolean selected) {
aquaListUI.repaintCell(value, selectedIndex, selected);
}
});
}
};
}
@Override
public void paint(final Graphics g, final JComponent c) {
// this space intentionally left blank
}
@Override
protected ListCellRenderer<Object> createRenderer() {
return new AquaComboBoxRenderer(comboBox);
}
@Override
protected ComboPopup createPopup() {
return new AquaComboBoxPopup(comboBox);
}
@Override
protected JButton createArrowButton() {
return new AquaComboBoxButton(this, comboBox, currentValuePane, listBox);
}
@Override
protected ComboBoxEditor createEditor() {
return new AquaComboBoxEditor();
}
final class AquaComboBoxEditor extends BasicComboBoxEditor
implements UIResource, DocumentListener {
AquaComboBoxEditor() {
super();
editor = new AquaCustomComboTextField();
editor.addFocusListener(this);
editor.getDocument().addDocumentListener(this);
}
@Override
public void changedUpdate(final DocumentEvent e) {
editorTextChanged();
}
@Override
public void insertUpdate(final DocumentEvent e) {
editorTextChanged();
}
@Override
public void removeUpdate(final DocumentEvent e) {
editorTextChanged();
}
private void editorTextChanged() {
if (!popup.isVisible()) return;
final Object text = editor.getText();
final ListModel<Object> model = listBox.getModel();
final int items = model.getSize();
for (int i = 0; i < items; i++) {
final Object element = model.getElementAt(i);
if (element == null) continue;
final String asString = element.toString();
if (asString == null || !asString.equals(text)) continue;
popup.getList().setSelectedIndex(i);
return;
}
popup.getList().clearSelection();
}
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
final class AquaCustomComboTextField extends JTextField {
public AquaCustomComboTextField() {
final InputMap inputMap = getInputMap();
inputMap.put(KeyStroke.getKeyStroke("DOWN"), highlightNextAction);
inputMap.put(KeyStroke.getKeyStroke("KP_DOWN"), highlightNextAction);
inputMap.put(KeyStroke.getKeyStroke("UP"), highlightPreviousAction);
inputMap.put(KeyStroke.getKeyStroke("KP_UP"), highlightPreviousAction);
inputMap.put(KeyStroke.getKeyStroke("HOME"), highlightFirstAction);
inputMap.put(KeyStroke.getKeyStroke("END"), highlightLastAction);
inputMap.put(KeyStroke.getKeyStroke("PAGE_UP"), highlightPageUpAction);
inputMap.put(KeyStroke.getKeyStroke("PAGE_DOWN"), highlightPageDownAction);
final Action action = getActionMap().get(JTextField.notifyAction);
inputMap.put(KeyStroke.getKeyStroke("ENTER"), new AbstractAction() {
public void actionPerformed(final ActionEvent e) {
if (popup.isVisible()) {
triggerSelectionEvent(comboBox, e);
if (editor instanceof AquaCustomComboTextField) {
((AquaCustomComboTextField)editor).selectAll();
}
}
action.actionPerformed(e);
}
});
}
// workaround for 4530952
@Override
public void setText(final String s) {
if (getText().equals(s)) {
return;
}
super.setText(s);
}
}
/**
* This listener hides the popup when the focus is lost. It also repaints
* when focus is gained or lost.
*
* This override is necessary because the Basic L&F for the combo box is working
* around a Solaris-only bug that we don't have on Mac OS X. So, remove the lightweight
* popup check here. rdar://Problem/3518582
*/
@Override
protected FocusListener createFocusListener() {
return new BasicComboBoxUI.FocusHandler() {
@Override
public void focusGained(FocusEvent e) {
super.focusGained(e);
if (arrowButton != null) {
arrowButton.repaint();
}
}
@Override
public void focusLost(final FocusEvent e) {
hasFocus = false;
if (!e.isTemporary()) {
setPopupVisible(comboBox, false);
}
comboBox.repaint();
// Notify assistive technologies that the combo box lost focus
final AccessibleContext ac = ((Accessible)comboBox).getAccessibleContext();
if (ac != null) {
ac.firePropertyChange(AccessibleContext.ACCESSIBLE_STATE_PROPERTY, AccessibleState.FOCUSED, null);
}
if (arrowButton != null) {
arrowButton.repaint();
}
}
};
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
ActionMap actionMap = new ActionMapUIResource();
actionMap.put("aquaSelectNext", highlightNextAction);
actionMap.put("aquaSelectPrevious", highlightPreviousAction);
actionMap.put("enterPressed", triggerSelectionAction);
actionMap.put("aquaSpacePressed", toggleSelectionAction);
actionMap.put("aquaSelectHome", highlightFirstAction);
actionMap.put("aquaSelectEnd", highlightLastAction);
actionMap.put("aquaSelectPageUp", highlightPageUpAction);
actionMap.put("aquaSelectPageDown", highlightPageDownAction);
actionMap.put("aquaHidePopup", hideAction);
actionMap.put("aquaOpenPopupOrhighlightLast", openPopupOrHighlightLast);
actionMap.put("aquaOpenPopupOrhighlightFirst", openPopupOrHighlightFirst);
SwingUtilities.replaceUIActionMap(comboBox, actionMap);
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
private abstract class ComboBoxAction extends AbstractAction {
@Override
public void actionPerformed(final ActionEvent e) {
if (!comboBox.isEnabled() || !comboBox.isShowing()) {
return;
}
if (comboBox.isPopupVisible()) {
final AquaComboBoxUI ui = (AquaComboBoxUI)comboBox.getUI();
performComboBoxAction(ui);
} else {
comboBox.setPopupVisible(true);
}
}
abstract void performComboBoxAction(final AquaComboBoxUI ui);
}
/**
* Highlight _but do not select_ the next item in the list.
*/
private Action highlightNextAction = new ComboBoxAction() {
@Override
public void performComboBoxAction(AquaComboBoxUI ui) {
final int si = listBox.getSelectedIndex();
if (si < comboBox.getModel().getSize() - 1) {
listBox.setSelectedIndex(si + 1);
listBox.ensureIndexIsVisible(si + 1);
}
comboBox.repaint();
}
};
/**
* Highlight _but do not select_ the previous item in the list.
*/
private Action highlightPreviousAction = new ComboBoxAction() {
@Override
void performComboBoxAction(final AquaComboBoxUI ui) {
final int si = listBox.getSelectedIndex();
if (si > 0) {
listBox.setSelectedIndex(si - 1);
listBox.ensureIndexIsVisible(si - 1);
}
comboBox.repaint();
}
};
private Action highlightFirstAction = new ComboBoxAction() {
@Override
void performComboBoxAction(final AquaComboBoxUI ui) {
listBox.setSelectedIndex(0);
listBox.ensureIndexIsVisible(0);
}
};
private Action highlightLastAction = new ComboBoxAction() {
@Override
void performComboBoxAction(final AquaComboBoxUI ui) {
final int size = listBox.getModel().getSize();
listBox.setSelectedIndex(size - 1);
listBox.ensureIndexIsVisible(size - 1);
}
};
private Action highlightPageUpAction = new ComboBoxAction() {
@Override
void performComboBoxAction(final AquaComboBoxUI ui) {
final int current = listBox.getSelectedIndex();
final int first = listBox.getFirstVisibleIndex();
if (current != first) {
listBox.setSelectedIndex(first);
return;
}
final int page = listBox.getVisibleRect().height / listBox.getCellBounds(0, 0).height;
int target = first - page;
if (target < 0) target = 0;
listBox.ensureIndexIsVisible(target);
listBox.setSelectedIndex(target);
}
};
private Action highlightPageDownAction = new ComboBoxAction() {
@Override
void performComboBoxAction(final AquaComboBoxUI ui) {
final int current = listBox.getSelectedIndex();
final int last = listBox.getLastVisibleIndex();
if (current != last) {
listBox.setSelectedIndex(last);
return;
}
final int page = listBox.getVisibleRect().height / listBox.getCellBounds(0, 0).height;
final int end = listBox.getModel().getSize() - 1;
int target = last + page;
if (target > end) target = end;
listBox.ensureIndexIsVisible(target);
listBox.setSelectedIndex(target);
}
};
// For <rdar://problem/3759984> Java 1.4.2_5: Serializing Swing components not working
// Inner classes were using a this reference and then trying to serialize the AquaComboBoxUI
// We shouldn't do that. But we need to be able to get the popup from other classes, so we need
// a public accessor.
public ComboPopup getPopup() {
return popup;
}
@Override
protected LayoutManager createLayoutManager() {
return new AquaComboBoxLayoutManager();
}
final class AquaComboBoxLayoutManager extends BasicComboBoxUI.ComboBoxLayoutManager {
protected Rectangle rectangleForCurrentValue() {
int width = comboBox.getWidth();
int height = comboBox.getBorder() == null ? 22 : comboBox.getHeight();
Insets insets = getInsets();
int buttonSize = height - (insets.top + insets.bottom);
if ( arrowButton != null ) {
buttonSize = arrowButton.getWidth();
}
int midHeight = (comboBox.getHeight() - height - (insets.top + insets.bottom)) / 2 - 1;
if (midHeight < 0) {
midHeight = 0;
}
if (comboBox.getComponentOrientation().isLeftToRight()) {
return new Rectangle(insets.left, insets.top + midHeight,
width - (insets.left + insets.right + buttonSize) + 3,
height - (insets.top + insets.bottom));
}
else {
return new Rectangle(insets.left + buttonSize, insets.top + midHeight,
width - (insets.left + insets.right + buttonSize) + 3,
height - (insets.top + insets.bottom));
}
}
@Override
public void layoutContainer(final Container parent) {
if (arrowButton != null && !comboBox.isEditable()) {
final Insets insets = comboBox.getInsets();
final int width = comboBox.getWidth();
final int height = comboBox.getHeight();
arrowButton.setBounds(insets.left, insets.top, width - (insets.left + insets.right), height - (insets.top + insets.bottom));
return;
}
final JComboBox<?> cb = (JComboBox<?>) parent;
final int width = cb.getWidth();
final int height = cb.getHeight();
final Insets insets = getInsets();
final int buttonHeight = height - (insets.top + insets.bottom);
final int buttonWidth = 20;
if (arrowButton != null) {
arrowButton.setBounds(width - (insets.right + buttonWidth), insets.top, buttonWidth, buttonHeight);
}
if (editor != null) {
final Rectangle editorRect = rectangleForCurrentValue();
editor.setBounds(editorRect);
}
}
}
// This is here because Sun can't use protected like they should!
protected static final String IS_TABLE_CELL_EDITOR = "JComboBox.isTableCellEditor";
protected static boolean isTableCellEditor(final JComponent c) {
return Boolean.TRUE.equals(c.getClientProperty(AquaComboBoxUI.IS_TABLE_CELL_EDITOR));
}
protected static boolean isPopdown(final JComboBox<?> c) {
return c.isEditable() || Boolean.TRUE.equals(c.getClientProperty(AquaComboBoxUI.POPDOWN_CLIENT_PROPERTY_KEY));
}
protected static void triggerSelectionEvent(final JComboBox<?> comboBox, final ActionEvent e) {
if (!comboBox.isEnabled()) return;
final AquaComboBoxUI aquaUi = (AquaComboBoxUI)comboBox.getUI();
if (aquaUi.getPopup().getList().getSelectedIndex() < 0) {
comboBox.setPopupVisible(false);
}
if (isTableCellEditor(comboBox)) {
// Forces the selection of the list item if the combo box is in a JTable
comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());
return;
}
if (comboBox.isPopupVisible()) {
comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());
comboBox.setPopupVisible(false);
return;
}
// Call the default button binding.
// This is a pretty messy way of passing an event through to the root pane
final JRootPane root = SwingUtilities.getRootPane(comboBox);
if (root == null) return;
final InputMap im = root.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
final ActionMap am = root.getActionMap();
if (im == null || am == null) return;
final Object obj = im.get(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
if (obj == null) return;
final Action action = am.get(obj);
if (action == null) return;
action.actionPerformed(new ActionEvent(root, e.getID(), e.getActionCommand(), e.getWhen(), e.getModifiers()));
}
// This is somewhat messy. The difference here from BasicComboBoxUI.EnterAction is that
// arrow up or down does not automatically select the
private final Action triggerSelectionAction = new AbstractAction() {
public void actionPerformed(final ActionEvent e) {
triggerSelectionEvent((JComboBox)e.getSource(), e);
}
@Override
public boolean isEnabled() {
return comboBox.isPopupVisible() && super.isEnabled();
}
};
private static final Action toggleSelectionAction = new AbstractAction() {
public void actionPerformed(final ActionEvent e) {
final JComboBox<?> comboBox = (JComboBox<?>) e.getSource();
if (!comboBox.isEnabled()) return;
if (comboBox.isEditable()) return;
final AquaComboBoxUI aquaUi = (AquaComboBoxUI)comboBox.getUI();
if (comboBox.isPopupVisible()) {
comboBox.setSelectedIndex(aquaUi.getPopup().getList().getSelectedIndex());
comboBox.setPopupVisible(false);
return;
}
comboBox.setPopupVisible(true);
}
};
private final Action hideAction = new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
final JComboBox<?> comboBox = (JComboBox<?>) e.getSource();
comboBox.firePopupMenuCanceled();
comboBox.setPopupVisible(false);
}
@Override
public boolean isEnabled() {
return comboBox.isPopupVisible() && super.isEnabled();
}
};
private final Action openPopupOrHighlightLast = new ComboBoxAction() {
@Override
void performComboBoxAction(final AquaComboBoxUI ui) {
final int size = listBox.getModel().getSize();
listBox.setSelectedIndex(size - 1);
listBox.ensureIndexIsVisible(size - 1);
comboBox.setSelectedIndex(ui.getPopup().getList().getSelectedIndex());
}
};
private final Action openPopupOrHighlightFirst = new ComboBoxAction() {
@Override
void performComboBoxAction(final AquaComboBoxUI ui) {
listBox.setSelectedIndex(0);
listBox.ensureIndexIsVisible(0);
comboBox.setSelectedIndex(ui.getPopup().getList().getSelectedIndex());
}
};
@Override
public void applySizeFor(final JComponent c, final Size size) {
if (arrowButton == null) return;
final Border border = arrowButton.getBorder();
if (!(border instanceof AquaButtonBorder)) return;
final AquaButtonBorder aquaBorder = (AquaButtonBorder)border;
arrowButton.setBorder(aquaBorder.deriveBorderForSize(size));
}
@Override
public Dimension getMinimumSize(final JComponent c) {
if (!isMinimumSizeDirty) {
return new Dimension(cachedMinimumSize);
}
final boolean editable = comboBox.isEditable();
final Dimension size;
if (!editable && arrowButton instanceof final AquaComboBoxButton button) {
final Insets buttonInsets = button.getInsets();
// Insets insets = comboBox.getInsets();
final Insets insets = new Insets(0, 5, 0, 25);//comboBox.getInsets();
size = getDisplaySize();
size.width += insets.left + insets.right;
size.width += buttonInsets.left + buttonInsets.right;
size.width += buttonInsets.right + 10;
size.height += insets.top + insets.bottom;
size.height += buttonInsets.top + buttonInsets.bottom;
// Min height = Height of arrow button plus 2 pixels fuzz above plus 2 below. 23 + 2 + 2
size.height = Math.max(27, size.height);
} else if (editable && arrowButton != null && editor != null) {
size = super.getMinimumSize(c);
final Insets margin = arrowButton.getMargin();
size.height += margin.top + margin.bottom;
} else {
size = super.getMinimumSize(c);
}
cachedMinimumSize.setSize(size.width, size.height);
isMinimumSizeDirty = false;
return new Dimension(cachedMinimumSize);
}
@SuppressWarnings("unchecked")
private static final RecyclableSingleton<ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI>> APPLICATOR = new
RecyclableSingleton<ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI>>() {
@Override
protected ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI> getInstance() {
return new ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI>(
new Property<AquaComboBoxUI>(AquaFocusHandler.FRAME_ACTIVE_PROPERTY) {
public void applyProperty(final AquaComboBoxUI target, final Object value) {
if (Boolean.FALSE.equals(value)) {
if (target.comboBox != null) target.comboBox.hidePopup();
}
if (target.listBox != null) target.listBox.repaint();
}
},
new Property<AquaComboBoxUI>("editable") {
public void applyProperty(final AquaComboBoxUI target, final Object value) {
if (target.comboBox == null) return;
target.comboBox.repaint();
}
},
new Property<AquaComboBoxUI>("background") {
public void applyProperty(final AquaComboBoxUI target, final Object value) {
final Color color = (Color)value;
if (target.arrowButton != null) target.arrowButton.setBackground(color);
if (target.listBox != null) target.listBox.setBackground(color);
}
},
new Property<AquaComboBoxUI>("foreground") {
public void applyProperty(final AquaComboBoxUI target, final Object value) {
final Color color = (Color)value;
if (target.arrowButton != null) target.arrowButton.setForeground(color);
if (target.listBox != null) target.listBox.setForeground(color);
}
},
new Property<AquaComboBoxUI>(POPDOWN_CLIENT_PROPERTY_KEY) {
public void applyProperty(final AquaComboBoxUI target, final Object value) {
if (!(target.arrowButton instanceof AquaComboBoxButton)) return;
((AquaComboBoxButton)target.arrowButton).setIsPopDown(Boolean.TRUE.equals(value));
}
},
new Property<AquaComboBoxUI>(ISSQUARE_CLIENT_PROPERTY_KEY) {
public void applyProperty(final AquaComboBoxUI target, final Object value) {
if (!(target.arrowButton instanceof AquaComboBoxButton)) return;
((AquaComboBoxButton)target.arrowButton).setIsSquare(Boolean.TRUE.equals(value));
}
}
) {
public AquaComboBoxUI convertJComponentToTarget(final JComboBox<?> combo) {
final ComboBoxUI comboUI = combo.getUI();
if (comboUI instanceof AquaComboBoxUI) return (AquaComboBoxUI)comboUI;
return null;
}
};
}
};
static ClientPropertyApplicator<JComboBox<?>, AquaComboBoxUI> getApplicator() {
return APPLICATOR.get();
}
@Override
public int getAccessibleChildrenCount(JComponent c) {
return 2;
}
@Override
public Accessible getAccessibleChild(JComponent c, int i) {
// 0 = the popup
// 1 = the editor for editable combobox and the arrow button for non-editable combobox
switch ( i ) {
case 0:
if (popup instanceof Accessible accessiblePopup) {
AccessibleContext ac = accessiblePopup.getAccessibleContext();
ac.setAccessibleParent(comboBox);
return accessiblePopup;
}
break;
case 1:
if (comboBox.isEditable()
&& (editor instanceof Accessible accessibleEditor)) {
AccessibleContext ac = accessibleEditor.getAccessibleContext();
ac.setAccessibleParent(comboBox);
return accessibleEditor;
} else if (!comboBox.isEditable()) {
return arrowButton;
}
break;
}
return null;
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.event.FocusListener;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicEditorPaneUI;
import javax.swing.text.*;
public final class AquaEditorPaneUI extends BasicEditorPaneUI {
public static ComponentUI createUI(final JComponent c){
return new AquaEditorPaneUI();
}
boolean oldDragState = false;
@Override
protected void installDefaults(){
super.installDefaults();
if(!GraphicsEnvironment.isHeadless()){
oldDragState = getComponent().getDragEnabled();
getComponent().setDragEnabled(true);
}
}
@Override
protected void uninstallDefaults(){
if(!GraphicsEnvironment.isHeadless()){
getComponent().setDragEnabled(oldDragState);
}
super.uninstallDefaults();
}
FocusListener focusListener;
@Override
protected void installListeners(){
super.installListeners();
focusListener = createFocusListener();
getComponent().addFocusListener(focusListener);
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
AquaKeyBindings bindings = AquaKeyBindings.instance();
bindings.setDefaultAction(getKeymapName());
final JTextComponent c = getComponent();
bindings.installAquaUpDownActions(c);
}
@Override
protected void uninstallListeners(){
getComponent().removeFocusListener(focusListener);
super.uninstallListeners();
}
protected FocusListener createFocusListener(){
return new AquaFocusHandler();
}
@Override
protected Caret createCaret() {
return new AquaCaret();
}
@Override
protected Highlighter createHighlighter(){
return new AquaHighlighter();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,501 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.ComponentOrientation;
import java.beans.*;
import java.io.File;
import java.util.*;
import javax.swing.*;
import javax.swing.event.ListDataEvent;
import javax.swing.filechooser.FileSystemView;
import javax.swing.table.AbstractTableModel;
/**
* NavServices-like implementation of a file Table
*
* Some of it came from BasicDirectoryModel
*/
@SuppressWarnings("serial") // Superclass is not serializable across versions
final class AquaFileSystemModel extends AbstractTableModel implements PropertyChangeListener {
private final JTable fFileList;
private FilesLoader filesLoader = null;
private Vector<File> files = null;
JFileChooser filechooser = null;
ArrayList<SortableFile> fileCache = null;
final Object fileCacheLock;
Vector<File> directories = null;
int fetchID = 0;
private final boolean[] fSortAscending = {true, true};
// private boolean fSortAscending = true;
private boolean fSortNames = true;
private final String[] fColumnNames;
public static final String SORT_BY_CHANGED = "sortByChanged";
public static final String SORT_ASCENDING_CHANGED = "sortAscendingChanged";
public AquaFileSystemModel(final JFileChooser filechooser, final JTable filelist, final String[] colNames) {
fileCacheLock = new Object();
this.filechooser = filechooser;
fFileList = filelist;
fColumnNames = colNames;
validateFileCache();
updateSelectionMode();
}
void updateSelectionMode() {
// Save dialog lists can't be multi select, because all we're selecting is the next folder to open
final boolean b = filechooser.isMultiSelectionEnabled() && filechooser.getDialogType() != JFileChooser.SAVE_DIALOG;
fFileList.setSelectionMode(b ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION : ListSelectionModel.SINGLE_SELECTION);
}
@Override
public void propertyChange(final PropertyChangeEvent e) {
final String prop = e.getPropertyName();
if (prop == JFileChooser.DIRECTORY_CHANGED_PROPERTY || prop == JFileChooser.FILE_VIEW_CHANGED_PROPERTY || prop == JFileChooser.FILE_FILTER_CHANGED_PROPERTY || prop == JFileChooser.FILE_HIDING_CHANGED_PROPERTY) {
invalidateFileCache();
validateFileCache();
} else if (prop.equals(JFileChooser.MULTI_SELECTION_ENABLED_CHANGED_PROPERTY)) {
updateSelectionMode();
} else if (prop == JFileChooser.FILE_SELECTION_MODE_CHANGED_PROPERTY) {
invalidateFileCache();
validateFileCache();
} else if (prop.equals("componentOrientation")) {
ComponentOrientation o = (ComponentOrientation) e.getNewValue();
JFileChooser cc = (JFileChooser) e.getSource();
if (o != e.getOldValue()) {
cc.applyComponentOrientation(o);
}
fFileList.setComponentOrientation(o);
fFileList.getParent().getParent().setComponentOrientation(o);
}
if (prop == SORT_BY_CHANGED) {// $ Ought to just resort
fSortNames = (((Integer)e.getNewValue()).intValue() == 0);
invalidateFileCache();
validateFileCache();
fFileList.repaint();
}
if (prop == SORT_ASCENDING_CHANGED) {
final int sortColumn = (fSortNames ? 0 : 1);
fSortAscending[sortColumn] = ((Boolean)e.getNewValue()).booleanValue();
invalidateFileCache();
validateFileCache();
fFileList.repaint();
}
}
public void invalidateFileCache() {
files = null;
directories = null;
synchronized(fileCacheLock) {
if (fileCache != null) {
final int lastRow = fileCache.size();
fileCache = null;
fireTableRowsDeleted(0, lastRow);
}
}
}
public Vector<File> getDirectories() {
if (directories != null) { return directories; }
return directories;
}
public Vector<File> getFiles() {
if (files != null) { return files; }
files = new Vector<File>();
directories = new Vector<File>();
directories.addElement(filechooser.getFileSystemView().createFileObject(filechooser.getCurrentDirectory(), ".."));
synchronized(fileCacheLock) {
for (int i = 0; i < fileCache.size(); i++) {
final SortableFile sf = fileCache.get(i);
final File f = sf.fFile;
if (filechooser.isTraversable(f)) {
directories.addElement(f);
} else {
files.addElement(f);
}
}
}
return files;
}
public void runWhenDone(final Runnable runnable){
synchronized (fileCacheLock) {
if (filesLoader != null) {
if (filesLoader.loadThread.isAlive()) {
filesLoader.queuedTasks.add(runnable);
return;
}
}
SwingUtilities.invokeLater(runnable);
}
}
public void validateFileCache() {
final File currentDirectory = filechooser.getCurrentDirectory();
if (currentDirectory == null) {
invalidateFileCache();
return;
}
if (filesLoader != null) {
// interrupt
filesLoader.loadThread.interrupt();
}
fetchID++;
// PENDING(jeff) pick the size more sensibly
invalidateFileCache();
synchronized(fileCacheLock) {
fileCache = new ArrayList<>(50);
}
filesLoader = new FilesLoader(currentDirectory, fetchID);
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public String getColumnName(final int col) {
return fColumnNames[col];
}
@Override
public Class<? extends Object> getColumnClass(final int col) {
if (col == 0) return File.class;
return Date.class;
}
@Override
public int getRowCount() {
synchronized(fileCacheLock) {
if (fileCache != null) {
return fileCache.size();
}
return 0;
}
}
// SAK: Part of fix for 3168263. The fileCache contains
// SortableFiles, so when finding a file in the list we need to
// first create a sortable file.
public boolean contains(final File o) {
synchronized(fileCacheLock) {
if (fileCache != null) {
return fileCache.contains(new SortableFile(o));
}
return false;
}
}
public int indexOf(final File o) {
synchronized(fileCacheLock) {
if (fileCache != null) {
final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1];
final int row = fileCache.indexOf(new SortableFile(o));
return isAscending ? row : fileCache.size() - row - 1;
}
return 0;
}
}
// AbstractListModel interface
public Object getElementAt(final int row) {
return getValueAt(row, 0);
}
// AbstractTableModel interface
@Override
public Object getValueAt(int row, final int col) {
if (row < 0 || col < 0) return null;
final boolean isAscending = fSortNames ? fSortAscending[0] : fSortAscending[1];
synchronized(fileCacheLock) {
if (fileCache != null) {
if (!isAscending) row = fileCache.size() - row - 1;
return fileCache.get(row).getValueAt(col);
}
return null;
}
}
// PENDING(jeff) - implement
public void intervalAdded(final ListDataEvent e) {
}
// PENDING(jeff) - implement
public void intervalRemoved(final ListDataEvent e) {
}
protected void sort(final Vector<Object> v) {
if (fSortNames) sSortNames.quickSort(v, 0, v.size() - 1);
else sSortDates.quickSort(v, 0, v.size() - 1);
}
// Liberated from the 1.1 SortDemo
//
// This is a generic version of C.A.R Hoare's Quick Sort
// algorithm. This will handle arrays that are already
// sorted, and arrays with duplicate keys.<BR>
//
// If you think of a one dimensional array as going from
// the lowest index on the left to the highest index on the right
// then the parameters to this function are lowest index or
// left and highest index or right. The first time you call
// this function it will be with the parameters 0, a.length - 1.
//
// @param a an integer array
// @param lo0 left boundary of array partition
// @param hi0 right boundary of array partition
abstract static class QuickSort {
final void quickSort(final Vector<Object> v, final int lo0, final int hi0) {
int lo = lo0;
int hi = hi0;
SortableFile mid;
if (hi0 > lo0) {
// Arbitrarily establishing partition element as the midpoint of
// the array.
mid = (SortableFile)v.elementAt((lo0 + hi0) / 2);
// loop through the array until indices cross
while (lo <= hi) {
// find the first element that is greater than or equal to
// the partition element starting from the left Index.
//
// Nasty to have to cast here. Would it be quicker
// to copy the vectors into arrays and sort the arrays?
while ((lo < hi0) && lt((SortableFile)v.elementAt(lo), mid)) {
++lo;
}
// find an element that is smaller than or equal to
// the partition element starting from the right Index.
while ((hi > lo0) && lt(mid, (SortableFile)v.elementAt(hi))) {
--hi;
}
// if the indexes have not crossed, swap
if (lo <= hi) {
swap(v, lo, hi);
++lo;
--hi;
}
}
// If the right index has not reached the left side of array
// must now sort the left partition.
if (lo0 < hi) {
quickSort(v, lo0, hi);
}
// If the left index has not reached the right side of array
// must now sort the right partition.
if (lo < hi0) {
quickSort(v, lo, hi0);
}
}
}
private void swap(final Vector<Object> a, final int i, final int j) {
final Object T = a.elementAt(i);
a.setElementAt(a.elementAt(j), i);
a.setElementAt(T, j);
}
protected abstract boolean lt(SortableFile a, SortableFile b);
}
static final class QuickSortNames extends QuickSort {
@Override
protected boolean lt(final SortableFile a, final SortableFile b) {
final String aLower = a.fName.toLowerCase();
final String bLower = b.fName.toLowerCase();
return aLower.compareTo(bLower) < 0;
}
}
static final class QuickSortDates extends QuickSort {
@Override
protected boolean lt(final SortableFile a, final SortableFile b) {
return a.fDateValue < b.fDateValue;
}
}
// for speed in sorting, displaying
static final class SortableFile /* extends FileView */{
File fFile;
String fName;
long fDateValue;
Date fDate;
SortableFile(final File f) {
fFile = f;
fName = fFile.getName();
fDateValue = fFile.lastModified();
fDate = new Date(fDateValue);
}
public Object getValueAt(final int col) {
if (col == 0) return fFile;
return fDate;
}
@Override
public boolean equals(final Object other) {
final SortableFile otherFile = (SortableFile)other;
return otherFile.fFile.equals(fFile);
}
@Override
public int hashCode() {
return Objects.hashCode(fFile);
}
}
final class FilesLoader implements Runnable {
ArrayList<Runnable> queuedTasks = new ArrayList<>();
File currentDirectory = null;
int fid;
Thread loadThread;
public FilesLoader(final File currentDirectory, final int fid) {
this.currentDirectory = currentDirectory;
this.fid = fid;
String name = "Aqua L&F File Loading Thread";
this.loadThread = new Thread(null, this, name, 0, false);
this.loadThread.start();
}
@Override
public void run() {
final Vector<DoChangeContents> runnables = new Vector<DoChangeContents>(10);
final FileSystemView fileSystem = filechooser.getFileSystemView();
final File[] list = fileSystem.getFiles(currentDirectory, filechooser.isFileHidingEnabled());
final Vector<Object> acceptsList = new Vector<Object>();
for (final File element : list) {
// Return all files to the file chooser. The UI will disable or enable
// the file name if the current filter approves.
acceptsList.addElement(new SortableFile(element));
}
// Sort based on settings.
sort(acceptsList);
// Don't separate directories from files
Vector<SortableFile> chunk = new Vector<SortableFile>(10);
final int listSize = acceptsList.size();
// run through list grabbing file/dirs in chunks of ten
for (int i = 0; i < listSize;) {
SortableFile f;
for (int j = 0; j < 10 && i < listSize; j++, i++) {
f = (SortableFile)acceptsList.elementAt(i);
chunk.addElement(f);
}
final DoChangeContents runnable = new DoChangeContents(chunk, fid);
runnables.addElement(runnable);
SwingUtilities.invokeLater(runnable);
chunk = new Vector<SortableFile>(10);
if (loadThread.isInterrupted()) {
// interrupted, cancel all runnables
cancelRunnables(runnables);
return;
}
}
synchronized (fileCacheLock) {
for (final Runnable r : queuedTasks) {
SwingUtilities.invokeLater(r);
}
}
}
public void cancelRunnables(final Vector<DoChangeContents> runnables) {
for (int i = 0; i < runnables.size(); i++) {
runnables.elementAt(i).cancel();
}
}
}
final class DoChangeContents implements Runnable {
private Vector<SortableFile> contentFiles;
private boolean doFire = true;
private final Object lock = new Object();
private final int fid;
public DoChangeContents(final Vector<SortableFile> files, final int fid) {
this.contentFiles = files;
this.fid = fid;
}
synchronized void cancel() {
synchronized(lock) {
doFire = false;
}
}
@Override
public void run() {
if (fetchID == fid) {
synchronized(lock) {
if (doFire) {
synchronized(fileCacheLock) {
if (fileCache != null) {
for (int i = 0; i < contentFiles.size(); i++) {
fileCache.add(contentFiles.elementAt(i));
fireTableRowsInserted(i, i);
}
}
}
}
contentFiles = null;
directories = null;
}
}
}
}
final QuickSortNames sSortNames = new QuickSortNames();
final QuickSortDates sSortDates = new QuickSortDates();
}

View file

@ -0,0 +1,351 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.Icon;
import javax.swing.filechooser.FileView;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import static java.nio.charset.StandardCharsets.UTF_8;
final class AquaFileView extends FileView {
private static final boolean DEBUG = false;
private static final int UNINITALIZED_LS_INFO = -1;
// Constants from LaunchServices.h
static final int kLSItemInfoIsPlainFile = 0x00000001; /* Not a directory, volume, or symlink*/
static final int kLSItemInfoIsPackage = 0x00000002; /* Packaged directory*/
static final int kLSItemInfoIsApplication = 0x00000004; /* Single-file or packaged application*/
static final int kLSItemInfoIsContainer = 0x00000008; /* Directory (includes packages) or volume*/
static final int kLSItemInfoIsAliasFile = 0x00000010; /* Alias file (includes sym links)*/
static final int kLSItemInfoIsSymlink = 0x00000020; /* UNIX sym link*/
static final int kLSItemInfoIsInvisible = 0x00000040; /* Invisible by any known mechanism*/
static final int kLSItemInfoIsNativeApp = 0x00000080; /* Carbon or Cocoa native app*/
static final int kLSItemInfoIsClassicApp = 0x00000100; /* CFM/68K Classic app*/
static final int kLSItemInfoAppPrefersNative = 0x00000200; /* Carbon app that prefers to be launched natively*/
static final int kLSItemInfoAppPrefersClassic = 0x00000400; /* Carbon app that prefers to be launched in Classic*/
static final int kLSItemInfoAppIsScriptable = 0x00000800; /* App can be scripted*/
static final int kLSItemInfoIsVolume = 0x00001000; /* Item is a volume*/
static final int kLSItemInfoExtensionIsHidden = 0x00100000; /* Item has a hidden extension*/
static {
loadOSXUILibrary();
}
@SuppressWarnings("restricted")
private static void loadOSXUILibrary() {
System.loadLibrary("osxui");
}
// TODO: Un-comment this out when the native version exists
//private static native String getNativePathToRunningJDKBundle();
private static native String getNativePathToSharedJDKBundle();
private static native String getNativeMachineName();
private static native String getNativeDisplayName(final byte[] pathBytes, final boolean isDirectory);
private static native int getNativeLSInfo(final byte[] pathBytes, final boolean isDirectory);
private static native String getNativePathForResolvedAlias(final byte[] absolutePath, final boolean isDirectory);
private static final RecyclableSingleton<String> machineName = new RecyclableSingleton<String>() {
@Override
protected String getInstance() {
return getNativeMachineName();
}
};
private static String getMachineName() {
return machineName.get();
}
protected static String getPathToRunningJDKBundle() {
// TODO: Return empty string for now
return "";//getNativePathToRunningJDKBundle();
}
protected static String getPathToSharedJDKBundle() {
return getNativePathToSharedJDKBundle();
}
static final class FileInfo {
final boolean isDirectory;
final String absolutePath;
byte[] pathBytes;
String displayName;
Icon icon;
int launchServicesInfo = UNINITALIZED_LS_INFO;
FileInfo(final File file){
isDirectory = file.isDirectory();
absolutePath = file.getAbsolutePath();
pathBytes = absolutePath.getBytes(UTF_8);
}
}
final int MAX_CACHED_ENTRIES = 256;
protected final Map<File, FileInfo> cache = new LinkedHashMap<File, FileInfo>(){
protected boolean removeEldestEntry(final Entry<File, FileInfo> eldest) {
return size() > MAX_CACHED_ENTRIES;
}
};
FileInfo getFileInfoFor(final File file) {
final FileInfo info = cache.get(file);
if (info != null) return info;
final FileInfo newInfo = new FileInfo(file);
cache.put(file, newInfo);
return newInfo;
}
final AquaFileChooserUI fFileChooserUI;
public AquaFileView(final AquaFileChooserUI fileChooserUI) {
fFileChooserUI = fileChooserUI;
}
String _directoryDescriptionText() {
return fFileChooserUI.directoryDescriptionText;
}
String _fileDescriptionText() {
return fFileChooserUI.fileDescriptionText;
}
boolean _packageIsTraversable() {
return fFileChooserUI.fPackageIsTraversable == AquaFileChooserUI.kOpenAlways;
}
boolean _applicationIsTraversable() {
return fFileChooserUI.fApplicationIsTraversable == AquaFileChooserUI.kOpenAlways;
}
@Override
public String getName(final File f) {
final FileInfo info = getFileInfoFor(f);
if (info.displayName != null) return info.displayName;
final String nativeDisplayName = getNativeDisplayName(info.pathBytes, info.isDirectory);
if (nativeDisplayName != null) {
info.displayName = nativeDisplayName;
return nativeDisplayName;
}
final String displayName = f.getName();
if (f.isDirectory() && fFileChooserUI.getFileChooser().getFileSystemView().isRoot(f)) {
final String localMachineName = getMachineName();
info.displayName = localMachineName;
return localMachineName;
}
info.displayName = displayName;
return displayName;
}
@Override
public String getDescription(final File f) {
return f.getName();
}
@Override
public String getTypeDescription(final File f) {
if (f.isDirectory()) return _directoryDescriptionText();
return _fileDescriptionText();
}
@Override
public Icon getIcon(final File f) {
final FileInfo info = getFileInfoFor(f);
if (info.icon != null) return info.icon;
if (f == null) {
info.icon = AquaIcon.SystemIcon.getDocumentIconUIResource();
} else {
// Look for the document's icon
final AquaIcon.FileIcon fileIcon = new AquaIcon.FileIcon(f);
info.icon = fileIcon;
if (!fileIcon.hasIconRef()) {
// Fall back on the default icons
if (f.isDirectory()) {
if (fFileChooserUI.getFileChooser().getFileSystemView().isRoot(f)) {
info.icon = AquaIcon.SystemIcon.getComputerIconUIResource();
} else if (f.getParent() == null || f.getParent().equals("/")) {
info.icon = AquaIcon.SystemIcon.getHardDriveIconUIResource();
} else {
info.icon = AquaIcon.SystemIcon.getFolderIconUIResource();
}
} else {
info.icon = AquaIcon.SystemIcon.getDocumentIconUIResource();
}
}
}
return info.icon;
}
// aliases are traversable though they aren't directories
@Override
public Boolean isTraversable(final File f) {
if (f.isDirectory()) {
// Doesn't matter if it's a package or app, because they're traversable
if (_packageIsTraversable() && _applicationIsTraversable()) {
return Boolean.TRUE;
} else if (!_packageIsTraversable() && !_applicationIsTraversable()) {
if (isPackage(f) || isApplication(f)) return Boolean.FALSE;
} else if (!_applicationIsTraversable()) {
if (isApplication(f)) return Boolean.FALSE;
} else if (!_packageIsTraversable()) {
// [3101730] All applications are packages, but not all packages are applications.
if (isPackage(f) && !isApplication(f)) return Boolean.FALSE;
}
// We're allowed to traverse it
return Boolean.TRUE;
}
if (isAlias(f)) {
final File realFile = resolveAlias(f);
return realFile.isDirectory() ? Boolean.TRUE : Boolean.FALSE;
}
return Boolean.FALSE;
}
int getLSInfoFor(final File f) {
final FileInfo info = getFileInfoFor(f);
if (info.launchServicesInfo == UNINITALIZED_LS_INFO) {
info.launchServicesInfo = getNativeLSInfo(info.pathBytes, info.isDirectory);
}
return info.launchServicesInfo;
}
boolean isAlias(final File f) {
final int lsInfo = getLSInfoFor(f);
return ((lsInfo & kLSItemInfoIsAliasFile) != 0) && ((lsInfo & kLSItemInfoIsSymlink) == 0);
}
boolean isApplication(final File f) {
return (getLSInfoFor(f) & kLSItemInfoIsApplication) != 0;
}
boolean isPackage(final File f) {
return (getLSInfoFor(f) & kLSItemInfoIsPackage) != 0;
}
/**
* Things that need to be handled:
* -Change getFSRef to use CFURLRef instead of FSPathMakeRef
* -Use the HFS-style path from CFURLRef in resolveAlias() to avoid
* path length limitations
* -In resolveAlias(), simply resolve immediately if this is an alias
*/
/**
* Returns the actual file represented by this object. This will
* resolve any aliases in the path, including this file if it is an
* alias. No alias resolution requiring user interaction (e.g.
* mounting servers) will occur. Note that aliases to servers may
* take a significant amount of time to resolve. This method
* currently does not have any provisions for a more fine-grained
* timeout for alias resolution beyond that used by the system.
*
* In the event of a path that does not contain any aliases, or if the file
* does not exist, this method will return the file that was passed in.
* @return The canonical path to the file
* @throws IOException If an I/O error occurs while attempting to
* construct the path
*/
File resolveAlias(final File mFile) {
// If the file exists and is not an alias, there aren't
// any aliases along its path, so the standard version
// of getCanonicalPath() will work.
if (mFile.exists() && !isAlias(mFile)) {
if (DEBUG) System.out.println("not an alias");
return mFile;
}
// If it doesn't exist, either there's an alias in the
// path or this is an alias. Traverse the path and
// resolve all aliases in it.
final LinkedList<String> components = getPathComponents(mFile);
if (components == null) {
if (DEBUG) System.out.println("getPathComponents is null ");
return mFile;
}
File file = new File("/");
for (final String nextComponent : components) {
file = new File(file, nextComponent);
final FileInfo info = getFileInfoFor(file);
// If any point along the way doesn't exist,
// just return the file.
if (!file.exists()) { return mFile; }
if (isAlias(file)) {
// Resolve it!
final String path = getNativePathForResolvedAlias(info.pathBytes, info.isDirectory);
// <rdar://problem/3582601> If the alias doesn't resolve (on a non-existent volume, for example)
// just return the file.
if (path == null) return mFile;
file = new File(path);
}
}
return file;
}
/**
* Returns a linked list of Strings consisting of the components of
* the path of this file, in order, including the filename as the
* last element. The first element in the list will be the first
* directory in the path, or "".
* @return A linked list of the components of this file's path
*/
private static LinkedList<String> getPathComponents(final File mFile) {
final LinkedList<String> componentList = new LinkedList<String>();
String parent;
File file = new File(mFile.getAbsolutePath());
componentList.add(0, file.getName());
while ((parent = file.getParent()) != null) {
file = new File(parent);
componentList.add(0, file.getName());
}
return componentList;
}
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
//
// AquaFocus.java
// Copyright (c) 2009 Apple Inc. All rights reserved.
//
package com.apple.laf;
import java.awt.*;
import javax.swing.*;
import sun.java2d.*;
import apple.laf.JRSUIFocus;
import com.apple.laf.AquaUtils.Painter;
public final class AquaFocus {
interface Drawable {
public void draw(final Graphics2D sg2d);
}
static boolean paintFocus(final Graphics g, final Drawable drawable) {
// TODO: requires OSXSurfaceData
return false;
/*if (!(g instanceof SunGraphics2D)) return false;
final SunGraphics2D sg2d = (SunGraphics2D)g;
final SurfaceData surfaceData = sg2d.getSurfaceData();
if (!(surfaceData instanceof OSXSurfaceData)) return false;
try {
((OSXSurfaceData)surfaceData).performCocoaDrawing(sg2d, new OSXSurfaceData.CGContextDrawable() {
@Override
public void drawIntoCGContext(final long cgContext) {
final JRSUIFocus focus = new JRSUIFocus(cgContext);
focus.beginFocus(JRSUIFocus.RING_BELOW);
drawable.draw(sg2d);
focus.endFocus();
}
});
} finally {
sg2d.dispose();
}
return true;*/
}
public static Icon createFocusedIcon(final Icon tmpIcon, final Component c, final int slack) {
return new FocusedIcon(tmpIcon, slack);
}
/* -- disabled until we can get the real JRSUI focus painter working
static class FocusedIcon implements Icon {
final Icon icon;
final int slack;
public FocusedIcon(final Icon icon, final int slack) {
this.icon = icon;
this.slack = slack;
}
@Override
public int getIconHeight() {
return icon.getIconHeight() + slack + slack;
}
@Override
public int getIconWidth() {
return icon.getIconWidth() + slack + slack;
}
@Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
final boolean painted = paintFocus(g, new Drawable() {
@Override
public void draw(SunGraphics2D sg2d) {
icon.paintIcon(c, sg2d, x + slack, y + slack);
}
});
if (!painted) {
icon.paintIcon(c, g, x + slack, y + slack);
}
}
}
*/
static final class FocusedIcon extends AquaUtils.ShadowBorder implements Icon {
final Icon icon;
final int slack;
public FocusedIcon(final Icon icon, final int slack) {
super(
new Painter() {
public void paint(Graphics g, int x, int y, int w, int h) {
Graphics2D imgG = (Graphics2D)g;
imgG.setComposite(AlphaComposite.Src);
imgG.setColor(UIManager.getColor("Focus.color"));
imgG.fillRect(x, y, w - (slack * 2), h - (slack * 2));
imgG.setComposite(AlphaComposite.DstAtop);
icon.paintIcon(null, imgG, x, y);
}
},
new Painter() {
public void paint(Graphics g, int x, int y, int w, int h) {
((Graphics2D)g).setComposite(AlphaComposite.SrcAtop);
icon.paintIcon(null, g, x, y);
}
},
slack, slack, 0.0f, 1.8f, 7
);
this.icon = icon;
this.slack = slack;
}
@Override
public int getIconHeight() {
return icon.getIconHeight() + slack + slack;
}
@Override
public int getIconWidth() {
return icon.getIconWidth() + slack + slack;
}
@Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
paintBorder(c, g, x, y, getIconWidth(), getIconHeight());
icon.paintIcon(c, g, x + slack, y + slack);
}
}
}

View file

@ -0,0 +1,191 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Color;
import java.awt.Window;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.JTree;
import javax.swing.JTable;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.BorderUIResource;
import javax.swing.plaf.UIResource;
/**
* This class is used by the text components, AquaEditorPaneUI, AquaTextAreaUI, AquaTextFieldUI and AquaTextPaneUI to control painting of the
* component's border. NOTE: It is assumed that this handler is added to components that extend JComponent.
*/
public class AquaFocusHandler implements FocusListener, PropertyChangeListener {
// Flag to help focusGained() determine whether the origin focus loss was due to a temporary focus loss or not.
private boolean wasTemporary = false;
// Flag to track when a border needs a repaint due to a window becoming activate/inactive.
private boolean repaintBorder = false;
protected static final String FRAME_ACTIVE_PROPERTY = "Frame.active";
@Override
public void focusGained(final FocusEvent ev) {
// If we gained focus and it wasn't due to a previous temporary focus loss
// or the frame became active again, then repaint the border on the component.
if (!wasTemporary || repaintBorder) {
AquaBorder.repaintBorder((JComponent)ev.getSource());
repaintBorder = false;
}
wasTemporary = false;
}
@Override
public void focusLost(final FocusEvent ev) {
wasTemporary = ev.isTemporary();
// If we lost focus due to a permanent focus loss then repaint the border on the component.
if (!wasTemporary) {
AquaBorder.repaintBorder((JComponent)ev.getSource());
}
}
@Override
public void propertyChange(final PropertyChangeEvent ev) {
if (!FRAME_ACTIVE_PROPERTY.equals(ev.getPropertyName())) return;
if (Boolean.TRUE.equals(ev.getNewValue())) {
// The FRAME_ACTIVE_PROPERTY change event is sent before a component gains focus.
// We set a flag to help the focusGained() determine when they should be repainting
// the components focus.
repaintBorder = true;
} else if (wasTemporary) {
// The FRAME_ACTIVE_PROPERTY change event is sent after a component loses focus.
// We use the wasTemporary flag to determine if we need to repaint the border.
AquaBorder.repaintBorder((JComponent)ev.getSource());
}
}
protected static boolean isActive(final JComponent c) {
if (c == null) return true;
final Object activeObj = c.getClientProperty(AquaFocusHandler.FRAME_ACTIVE_PROPERTY);
if (Boolean.FALSE.equals(activeObj)) return false;
return true;
}
static final PropertyChangeListener REPAINT_LISTENER = new PropertyChangeListener() {
public void propertyChange(final PropertyChangeEvent evt) {
final Object source = evt.getSource();
if (source instanceof JComponent) {
((JComponent)source).repaint();
}
}
};
protected static void install(final JComponent c) {
c.addPropertyChangeListener(FRAME_ACTIVE_PROPERTY, REPAINT_LISTENER);
}
protected static void uninstall(final JComponent c) {
c.removePropertyChangeListener(FRAME_ACTIVE_PROPERTY, REPAINT_LISTENER);
}
static void swapSelectionColors(final String prefix, final JTree c, final Object value) {
// <rdar://problem/8166173> JTree: selection color does not dim when window becomes inactive
// TODO inject our colors into the DefaultTreeCellRenderer
}
static void swapSelectionColors(final String prefix, final JTable c, final Object value) {
if (!isComponentValid(c)) return;
final Color bg = c.getSelectionBackground();
final Color fg = c.getSelectionForeground();
if (!(bg instanceof UIResource) || !(fg instanceof UIResource)) return;
if (Boolean.FALSE.equals(value)) {
setSelectionColors(c, "Table.selectionInactiveForeground",
"Table.selectionInactiveBackground");
return;
}
if (Boolean.TRUE.equals(value)) {
setSelectionColors(c, "Table.selectionForeground",
"Table.selectionBackground");
return;
}
}
static void setSelectionColors(final JTable c, final String fgName, final String bgName) {
c.setSelectionForeground(UIManager.getColor(fgName));
c.setSelectionBackground(UIManager.getColor(bgName));
// focus ring changes for on-the-fly accent color changes
Color prominentFocusRing = AquaLookAndFeel.deriveProminentFocusRing(
UIManager.getColor("Table.cellFocusRing"));
BorderUIResource.LineBorderUIResource focusCellHighlightBorder =
new BorderUIResource.LineBorderUIResource(prominentFocusRing, 2);
UIManager.getDefaults().put("Table.focusCellHighlightBorder",
focusCellHighlightBorder);
}
static void swapSelectionColors(final String prefix, final JList<?> c, final Object value) {
if (!isComponentValid(c)) return;
final Color bg = c.getSelectionBackground();
final Color fg = c.getSelectionForeground();
if (!(bg instanceof UIResource) || !(fg instanceof UIResource)) return;
if (Boolean.FALSE.equals(value)) {
setSelectionColors(c, "List.selectionInactiveForeground",
"List.selectionInactiveBackground");
return;
}
if (Boolean.TRUE.equals(value)) {
setSelectionColors(c, "List.selectionForeground",
"List.selectionBackground");
return;
}
}
static void setSelectionColors(final JList<?> c, final String fgName, final String bgName) {
c.setSelectionForeground(UIManager.getColor(fgName));
c.setSelectionBackground(UIManager.getColor(bgName));
}
static boolean isComponentValid(final JComponent c) {
if (c == null) return false;
final Window window = SwingUtilities.getWindowAncestor(c);
if (window == null) return false;
return true;
}
}

View file

@ -0,0 +1,164 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Font;
import java.awt.geom.AffineTransform;
import java.text.AttributedCharacterIterator.Attribute;
import java.util.Map;
import javax.swing.plaf.*;
import com.apple.laf.AquaUtils.RecyclableSingleton;
@SuppressWarnings("serial") // JDK implementation class
public final class AquaFonts {
private static final String MAC_DEFAULT_FONT_NAME = "Lucida Grande";
private static final RecyclableSingleton<FontUIResource> lucida9Pt = new RecyclableSingleton<FontUIResource>() {
@Override
protected FontUIResource getInstance() {
return new DerivedUIResourceFont(MAC_DEFAULT_FONT_NAME, Font.PLAIN, 9);
}
};
//private static final FontUIResource lucida10Pt = new DerivedUIResourceFont(MAC_DEFAULT_FONT_NAME, Font.PLAIN, 10);
private static final RecyclableSingleton<FontUIResource> lucida11Pt = new RecyclableSingleton<FontUIResource>() {
@Override
protected FontUIResource getInstance() {
return new DerivedUIResourceFont(MAC_DEFAULT_FONT_NAME, Font.PLAIN, 11);
}
};
private static final RecyclableSingleton<FontUIResource> lucida12Pt = new RecyclableSingleton<FontUIResource>() {
@Override
protected FontUIResource getInstance() {
return new DerivedUIResourceFont(MAC_DEFAULT_FONT_NAME, Font.PLAIN, 12);
}
};
private static final RecyclableSingleton<FontUIResource> lucida13Pt = new RecyclableSingleton<FontUIResource>() {
@Override
protected FontUIResource getInstance() {
return new DerivedUIResourceFont(MAC_DEFAULT_FONT_NAME, Font.PLAIN, 13);
}
};
private static final RecyclableSingleton<FontUIResource> lucida14Pt = new RecyclableSingleton<FontUIResource>() {
@Override
protected FontUIResource getInstance() {
return new DerivedUIResourceFont(MAC_DEFAULT_FONT_NAME, Font.PLAIN, 14);
}
};
private static final RecyclableSingleton<FontUIResource> lucida13PtBold = new RecyclableSingleton<FontUIResource>() {
@Override
protected FontUIResource getInstance() {
return new DerivedUIResourceFont(MAC_DEFAULT_FONT_NAME, Font.BOLD, 13);
}
};
private static final RecyclableSingleton<FontUIResource> lucida14PtBold = new RecyclableSingleton<FontUIResource>() {
@Override
protected FontUIResource getInstance() {
return new DerivedUIResourceFont(MAC_DEFAULT_FONT_NAME, Font.BOLD, 14);
}
};
protected static FontUIResource getMiniControlTextFont() {
return lucida9Pt.get();
}
protected static FontUIResource getSmallControlTextFont() {
return lucida11Pt.get();
}
public static FontUIResource getControlTextFont() {
return lucida13Pt.get();
}
public static FontUIResource getControlTextSmallFont() {
return lucida11Pt.get();
}
public static FontUIResource getMenuFont() {
return lucida14Pt.get();
}
public static Font getDockIconFont() {
return lucida14PtBold.get();
}
public static FontUIResource getAlertHeaderFont() {
return lucida13PtBold.get();
}
public static FontUIResource getAlertMessageFont() {
return lucida11Pt.get();
}
public static FontUIResource getViewFont() {
return lucida12Pt.get();
}
/**
* All fonts derived from this type will also be of this type, and not a plain java.awt.Font
*/
static class DerivedUIResourceFont extends FontUIResource implements UIResource {
public DerivedUIResourceFont(final Font font) {
super(font);
}
public DerivedUIResourceFont(final String name, final int style, final int size) {
super(name, style, size);
}
@Override
public Font deriveFont(final AffineTransform trans) {
return new DerivedUIResourceFont(super.deriveFont(trans));
}
@Override
public Font deriveFont(final float derivedSize) {
return new DerivedUIResourceFont(super.deriveFont(derivedSize));
}
@Override
public Font deriveFont(final int derivedStyle) {
return new DerivedUIResourceFont(super.deriveFont(derivedStyle));
}
@Override
public Font deriveFont(final int derivedStyle, final AffineTransform trans) {
return new DerivedUIResourceFont(super.deriveFont(derivedStyle, trans));
}
@Override
public Font deriveFont(final int derivedStyle, final float derivedSize) {
return new DerivedUIResourceFont(super.deriveFont(derivedStyle, derivedSize));
}
@Override
public Font deriveFont(final Map<? extends Attribute, ?> attributes) {
return new DerivedUIResourceFont(super.deriveFont(attributes));
}
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import javax.swing.border.Border;
import apple.laf.JRSUIConstants.Widget;
import com.apple.laf.AquaUtilControlSize.*;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
public abstract class AquaGroupBorder extends AquaBorder {
private static final RecyclableSingletonFromDefaultConstructor<? extends Border> tabbedPaneGroupBorder = new RecyclableSingletonFromDefaultConstructor<TabbedPane>(TabbedPane.class);
public static Border getTabbedPaneGroupBorder() {
return tabbedPaneGroupBorder.get();
}
private static final RecyclableSingletonFromDefaultConstructor<? extends Border> titleBorderGroupBorder = new RecyclableSingletonFromDefaultConstructor<Titled>(Titled.class);
public static Border getBorderForTitledBorder() {
return titleBorderGroupBorder.get();
}
private static final RecyclableSingletonFromDefaultConstructor<? extends Border> titlelessGroupBorder = new RecyclableSingletonFromDefaultConstructor<Titleless>(Titleless.class);
public static Border getTitlelessBorder() {
return titlelessGroupBorder.get();
}
protected AquaGroupBorder(final SizeVariant sizeVariant) {
super(new SizeDescriptor(sizeVariant));
painter.state.set(Widget.FRAME_GROUP_BOX);
}
@Override
public void paintBorder(final Component c, final Graphics g, int x, int y, int width, int height) {
// sg2d.setColor(Color.MAGENTA);
// sg2d.drawRect(x, y, width - 1, height - 1);
final Insets internalInsets = sizeVariant.insets;
x += internalInsets.left;
y += internalInsets.top;
width -= (internalInsets.left + internalInsets.right);
height -= (internalInsets.top + internalInsets.bottom);
painter.paint(g, c, x, y, width, height);
// sg2d.setColor(Color.ORANGE);
// sg2d.drawRect(x, y, width, height);
}
protected static final class TabbedPane extends AquaGroupBorder {
public TabbedPane() {
super(new SizeVariant().alterMargins(8, 12, 8, 12).alterInsets(5, 5, 7, 5));
}
}
protected static final class Titled extends AquaGroupBorder {
public Titled() {
super(new SizeVariant().alterMargins(16, 20, 16, 20).alterInsets(16, 5, 4, 5));
}
}
protected static final class Titleless extends AquaGroupBorder {
public Titleless() {
super(new SizeVariant().alterMargins(8, 12, 8, 12).alterInsets(3, 5, 1, 5));
}
}
}

View file

@ -0,0 +1,98 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
// Based on 1.3.1's AquaHighlighter, with the main difference that an inactive selection should be gray
// rather than a darker version of the current highlight color.
package com.apple.laf;
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.UIResource;
import javax.swing.text.*;
import com.apple.laf.AquaUtils.RecyclableSingleton;
public final class AquaHighlighter extends DefaultHighlighter implements UIResource {
private static final RecyclableSingleton<LayerPainter> instance = new RecyclableSingleton<LayerPainter>() {
protected LayerPainter getInstance() {
return new AquaHighlightPainter(null);
}
};
protected static LayeredHighlighter.LayerPainter getInstance() {
return instance.get();
}
public static final class AquaHighlightPainter extends DefaultHighlightPainter {
Color selectionColor;
Color disabledSelectionColor;
public AquaHighlightPainter(final Color c) {
super(c);
}
@Override
public Color getColor() {
return selectionColor == null ? super.getColor() : selectionColor;
}
protected Color getInactiveSelectionColor() {
if (disabledSelectionColor != null) return disabledSelectionColor;
return disabledSelectionColor = UIManager.getColor("TextComponent.selectionBackgroundInactive");
}
void setColor(final JTextComponent c) {
selectionColor = super.getColor();
if (selectionColor == null) selectionColor = c.getSelectionColor();
final Window owningWindow = SwingUtilities.getWindowAncestor(c);
// If window is not currently active selection color is a gray with RGB of (212, 212, 212).
if (owningWindow != null && !owningWindow.isActive()) {
selectionColor = getInactiveSelectionColor();
}
if (!c.hasFocus()) {
selectionColor = getInactiveSelectionColor();
}
}
@Override
public void paint(final Graphics g, final int offs0, final int offs1, final Shape bounds, final JTextComponent c) {
setColor(c);
super.paint(g, offs0, offs1, bounds, c);
}
@Override
public Shape paintLayer(final Graphics g, final int offs0, final int offs1, final Shape bounds, final JTextComponent c, final View view) {
setColor(c);
return super.paintLayer(g, offs0, offs1, bounds, c, view);
}
}
}

View file

@ -0,0 +1,316 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.swing.*;
import javax.swing.plaf.*;
import apple.laf.JRSUIConstants.Size;
import apple.laf.*;
import com.apple.laf.AquaUtilControlSize.*;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import sun.lwawt.macosx.CImage;
public final class AquaIcon {
interface InvertableIcon extends Icon {
public Icon getInvertedIcon();
}
static UIResource getIconFor(final JRSUIControlSpec spec, final int width, final int height) {
return new ScalingJRSUIIcon(width, height) {
@Override
public void initIconPainter(final AquaPainter<JRSUIState> painter) {
spec.initIconPainter(painter);
}
};
}
// converts an object that is an icon into an image so we can hand it off to AppKit
public static Image getImageForIcon(final Icon i) {
if (i instanceof ImageIcon) return ((ImageIcon)i).getImage();
final int w = i.getIconWidth();
final int h = i.getIconHeight();
if (w <= 0 || h <= 0) return null;
// This could be any kind of icon, so we need to make a buffer for it, draw it and then pass the new image off to appkit.
final BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
final Graphics g = image.getGraphics();
i.paintIcon(null, g, 0, 0);
g.dispose();
return image;
}
public interface JRSUIControlSpec {
public void initIconPainter(final AquaPainter<? extends JRSUIState> painter);
}
abstract static class JRSUIIcon implements Icon, UIResource {
protected final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIState.getInstance());
@Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
painter.paint(g, c, x, y, getIconWidth(), getIconHeight());
}
}
abstract static class DynamicallySizingJRSUIIcon extends JRSUIIcon {
protected final SizeDescriptor sizeDescriptor;
protected SizeVariant sizeVariant;
public DynamicallySizingJRSUIIcon(final SizeDescriptor sizeDescriptor) {
this.sizeDescriptor = sizeDescriptor;
this.sizeVariant = sizeDescriptor.regular;
initJRSUIState();
}
public abstract void initJRSUIState();
@Override
public int getIconHeight() {
return sizeVariant == null ? 0 : sizeVariant.h;
}
@Override
public int getIconWidth() {
return sizeVariant == null ? 0 : sizeVariant.w;
}
@Override
public void paintIcon(final Component c, final Graphics g, final int x, final int y) {
final Size size = c instanceof JComponent ? AquaUtilControlSize.getUserSizeFrom((JComponent)c) : Size.REGULAR;
sizeVariant = sizeDescriptor.get(size);
painter.state.set(size);
super.paintIcon(c, g, x, y);
}
}
abstract static class CachingScalingIcon implements Icon, UIResource {
int width;
int height;
Image image;
public CachingScalingIcon(final int width, final int height) {
this.width = width;
this.height = height;
}
void setSize(final int width, final int height) {
this.width = width;
this.height = height;
this.image = null;
}
Image getImage() {
if (image != null) return image;
if (!GraphicsEnvironment.isHeadless()) {
image = createImage();
}
return image;
}
abstract Image createImage();
public boolean hasIconRef() {
return getImage() != null;
}
@Override
public void paintIcon(final Component c, Graphics g, final int x, final int y) {
g = g.create();
if (g instanceof Graphics2D) {
// improves icon rendering quality in Quartz
((Graphics2D)g).setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
}
final Image myImage = getImage();
if (myImage != null) {
g.drawImage(myImage, x, y, getIconWidth(), getIconHeight(), null);
}
g.dispose();
}
@Override
public int getIconWidth() {
return width;
}
@Override
public int getIconHeight() {
return height;
}
}
abstract static class ScalingJRSUIIcon implements Icon, UIResource {
final int width;
final int height;
public ScalingJRSUIIcon(final int width, final int height) {
this.width = width;
this.height = height;
}
@Override
public void paintIcon(final Component c, Graphics g,
final int x, final int y) {
if (GraphicsEnvironment.isHeadless()) {
return;
}
g = g.create();
if (g instanceof Graphics2D) {
// improves icon rendering quality in Quartz
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
}
final AquaPainter<JRSUIState> painter =
AquaPainter.create(JRSUIState.getInstance());
initIconPainter(painter);
g.clipRect(x, y, width, height);
painter.paint(g, c, x, y, width, height);
g.dispose();
}
public abstract void initIconPainter(final AquaPainter<JRSUIState> painter);
@Override
public int getIconWidth() {
return width;
}
@Override
public int getIconHeight() {
return height;
}
}
static final class FileIcon extends CachingScalingIcon {
final File file;
public FileIcon(final File file, final int width, final int height) {
super(width, height);
this.file = file;
}
public FileIcon(final File file) {
this(file, 16, 16);
}
@Override
Image createImage() {
return CImage.createImageOfFile(file.getAbsolutePath(), getIconWidth(), getIconHeight());
}
}
static final class SystemIconSingleton extends RecyclableSingleton<SystemIcon> {
final String selector;
public SystemIconSingleton(String selector) {
this.selector = selector;
}
@Override
protected SystemIcon getInstance() {
return new SystemIcon(selector);
}
}
static final class SystemIconUIResourceSingleton extends RecyclableSingleton<IconUIResource> {
final String selector;
public SystemIconUIResourceSingleton(String selector) {
this.selector = selector;
}
@Override
protected IconUIResource getInstance() {
return new IconUIResource(new SystemIcon(selector));
}
}
static final class SystemIcon extends CachingScalingIcon {
private static final SystemIconUIResourceSingleton folderIcon = new SystemIconUIResourceSingleton("fldr");
static IconUIResource getFolderIconUIResource() { return folderIcon.get(); }
private static final SystemIconUIResourceSingleton openFolderIcon = new SystemIconUIResourceSingleton("ofld");
static IconUIResource getOpenFolderIconUIResource() { return openFolderIcon.get(); }
private static final SystemIconUIResourceSingleton desktopIcon = new SystemIconUIResourceSingleton("desk");
static IconUIResource getDesktopIconUIResource() { return desktopIcon.get(); }
private static final SystemIconUIResourceSingleton computerIcon = new SystemIconUIResourceSingleton("FNDR");
static IconUIResource getComputerIconUIResource() { return computerIcon.get(); }
private static final SystemIconUIResourceSingleton documentIcon = new SystemIconUIResourceSingleton("docu");
static IconUIResource getDocumentIconUIResource() { return documentIcon.get(); }
private static final SystemIconUIResourceSingleton hardDriveIcon = new SystemIconUIResourceSingleton("hdsk");
static IconUIResource getHardDriveIconUIResource() { return hardDriveIcon.get(); }
private static final SystemIconUIResourceSingleton floppyIcon = new SystemIconUIResourceSingleton("flpy");
static IconUIResource getFloppyIconUIResource() { return floppyIcon.get(); }
//private static final SystemIconUIResourceSingleton noteIcon = new SystemIconUIResourceSingleton("note");
//static IconUIResource getNoteIconUIResource() { return noteIcon.get(); }
private static final SystemIconSingleton caut = new SystemIconSingleton("caut");
static SystemIcon getCautionIcon() { return caut.get(); }
private static final SystemIconSingleton stop = new SystemIconSingleton("stop");
static SystemIcon getStopIcon() { return stop.get(); }
final String selector;
public SystemIcon(final String iconSelector, final int width, final int height) {
super(width, height);
selector = iconSelector;
}
public SystemIcon(final String iconSelector) {
this(iconSelector, 16, 16);
}
@Override
Image createImage() {
return CImage.createSystemImageFromSelector(
selector, getIconWidth(), getIconHeight());
}
}
}

View file

@ -0,0 +1,515 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.plaf.*;
import com.apple.eawt.Application;
import sun.lwawt.macosx.LWCToolkit;
import apple.laf.JRSUIConstants.AlignmentHorizontal;
import apple.laf.JRSUIConstants.AlignmentVertical;
import apple.laf.JRSUIConstants.Direction;
import apple.laf.JRSUIConstants.State;
import apple.laf.JRSUIConstants.Widget;
import apple.laf.*;
import com.apple.eio.FileManager;
import com.apple.laf.AquaIcon.InvertableIcon;
import com.apple.laf.AquaIcon.JRSUIControlSpec;
import com.apple.laf.AquaIcon.SystemIcon;
import com.apple.laf.AquaUtils.RecyclableObject;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import sun.awt.image.MultiResolutionCachedImage;
import sun.lwawt.macosx.CImage;
import sun.swing.ImageIconUIResource;
public final class AquaImageFactory {
public static IconUIResource getConfirmImageIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return new IconUIResource(new AquaIcon.CachingScalingIcon(kAlertIconSize, kAlertIconSize) {
Image createImage() {
return getGenericJavaIcon();
}
});
}
public static IconUIResource getCautionImageIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return getAppIconCompositedOn(AquaIcon.SystemIcon.getCautionIcon());
}
public static IconUIResource getStopImageIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return getAppIconCompositedOn(AquaIcon.SystemIcon.getStopIcon());
}
public static IconUIResource getLockImageIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
if (JRSUIUtils.Images.shouldUseLegacySecurityUIPath()) {
final Image lockIcon = CImage.createImageFromFile("/System/Library/CoreServices/SecurityAgent.app/Contents/Resources/Security.icns", kAlertIconSize, kAlertIconSize);
return getAppIconCompositedOn(lockIcon);
}
final Image lockIcon = Toolkit.getDefaultToolkit().getImage("NSImage://NSSecurity");
return getAppIconCompositedOn(lockIcon);
}
static Image getGenericJavaIcon() {
return Application.getApplication().getDockIconImage();
}
static String getPathToThisApplication() {
return FileManager.getPathToApplicationBundle();
}
static IconUIResource getAppIconCompositedOn(final SystemIcon systemIcon) {
systemIcon.setSize(kAlertIconSize, kAlertIconSize);
return getAppIconCompositedOn(systemIcon.createImage());
}
private static final int kAlertIconSize = 64;
static IconUIResource getAppIconCompositedOn(final Image background) {
if (background instanceof MultiResolutionCachedImage) {
int width = background.getWidth(null);
Image mrIconImage = ((MultiResolutionCachedImage) background).map(
rv -> getAppIconImageCompositedOn(rv, rv.getWidth(null) / width));
return new IconUIResource(new ImageIcon(mrIconImage));
}
BufferedImage iconImage = getAppIconImageCompositedOn(background, 1);
return new IconUIResource(new ImageIcon(iconImage));
}
static BufferedImage getAppIconImageCompositedOn(final Image background, int scaleFactor) {
final int scaledAlertIconSize = kAlertIconSize * scaleFactor;
final int kAlertSubIconSize = (int) (scaledAlertIconSize * 0.5);
final int kAlertSubIconInset = scaledAlertIconSize - kAlertSubIconSize;
final Icon smallAppIconScaled = new AquaIcon.CachingScalingIcon(
kAlertSubIconSize, kAlertSubIconSize) {
Image createImage() {
return getGenericJavaIcon();
}
};
final BufferedImage image = new BufferedImage(scaledAlertIconSize,
scaledAlertIconSize, BufferedImage.TYPE_INT_ARGB_PRE);
final Graphics g = image.getGraphics();
g.drawImage(background, 0, 0,
scaledAlertIconSize, scaledAlertIconSize, null);
if (g instanceof Graphics2D) {
// improves icon rendering quality in Quartz
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
}
smallAppIconScaled.paintIcon(null, g,
kAlertSubIconInset, kAlertSubIconInset);
g.dispose();
return image;
}
public static IconUIResource getTreeFolderIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return AquaIcon.SystemIcon.getFolderIconUIResource();
}
public static IconUIResource getTreeOpenFolderIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return AquaIcon.SystemIcon.getOpenFolderIconUIResource();
}
public static IconUIResource getTreeDocumentIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return AquaIcon.SystemIcon.getDocumentIconUIResource();
}
public static UIResource getTreeExpandedIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return AquaIcon.getIconFor(new JRSUIControlSpec() {
public void initIconPainter(final AquaPainter<? extends JRSUIState> painter) {
painter.state.set(Widget.DISCLOSURE_TRIANGLE);
painter.state.set(State.ACTIVE);
painter.state.set(Direction.DOWN);
painter.state.set(AlignmentHorizontal.CENTER);
painter.state.set(AlignmentVertical.CENTER);
}
}, 20, 20);
}
public static UIResource getTreeCollapsedIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return AquaIcon.getIconFor(new JRSUIControlSpec() {
public void initIconPainter(final AquaPainter<? extends JRSUIState> painter) {
painter.state.set(Widget.DISCLOSURE_TRIANGLE);
painter.state.set(State.ACTIVE);
painter.state.set(Direction.RIGHT);
painter.state.set(AlignmentHorizontal.CENTER);
painter.state.set(AlignmentVertical.CENTER);
}
}, 20, 20);
}
public static UIResource getTreeRightToLeftCollapsedIcon() {
// public, because UIDefaults.ProxyLazyValue uses reflection to get this value
return AquaIcon.getIconFor(new JRSUIControlSpec() {
public void initIconPainter(final AquaPainter<? extends JRSUIState> painter) {
painter.state.set(Widget.DISCLOSURE_TRIANGLE);
painter.state.set(State.ACTIVE);
painter.state.set(Direction.LEFT);
painter.state.set(AlignmentHorizontal.CENTER);
painter.state.set(AlignmentVertical.CENTER);
}
}, 20, 20);
}
static final class NamedImageSingleton extends RecyclableSingleton<Image> {
final String namedImage;
NamedImageSingleton(final String namedImage) {
this.namedImage = namedImage;
}
@Override
protected Image getInstance() {
return getNSIcon(namedImage);
}
}
static final class IconUIResourceSingleton extends RecyclableSingleton<IconUIResource> {
final NamedImageSingleton holder;
public IconUIResourceSingleton(final NamedImageSingleton holder) {
this.holder = holder;
}
@Override
protected IconUIResource getInstance() {
return new IconUIResource(new ImageIcon(holder.get()));
}
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
static final class InvertableImageIcon extends ImageIcon implements InvertableIcon, UIResource {
Icon invertedImage;
private Icon disabledIcon;
public InvertableImageIcon(final Image image) {
super(image);
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
if (!c.isEnabled()) {
if (disabledIcon == null) {
disabledIcon = new ImageIcon(GrayFilter.
createDisabledImage(((ImageIcon)this).getImage()));
}
disabledIcon.paintIcon(c, g, x, y);
} else {
super.paintIcon(c, g, x, y);
}
}
@Override
public Icon getInvertedIcon() {
if (invertedImage != null) return invertedImage;
return invertedImage = new InvertableImageIcon(AquaUtils.generateLightenedImage(getImage(), 100));
}
}
private static final NamedImageSingleton northArrow = new NamedImageSingleton("NSMenuScrollUp");
private static final IconUIResourceSingleton northArrowIcon = new IconUIResourceSingleton(northArrow);
private static final NamedImageSingleton southArrow = new NamedImageSingleton("NSMenuScrollDown");
private static final IconUIResourceSingleton southArrowIcon = new IconUIResourceSingleton(southArrow);
private static final NamedImageSingleton westArrow = new NamedImageSingleton("NSMenuSubmenuLeft");
private static final IconUIResourceSingleton westArrowIcon = new IconUIResourceSingleton(westArrow);
private static final NamedImageSingleton eastArrow = new NamedImageSingleton("NSMenuSubmenu");
private static final IconUIResourceSingleton eastArrowIcon = new IconUIResourceSingleton(eastArrow);
static Image getArrowImageForDirection(final int direction) {
switch(direction) {
case SwingConstants.NORTH: return northArrow.get();
case SwingConstants.SOUTH: return southArrow.get();
case SwingConstants.EAST: return eastArrow.get();
case SwingConstants.WEST: return westArrow.get();
}
return null;
}
static Icon getArrowIconForDirection(int direction) {
switch(direction) {
case SwingConstants.NORTH: return northArrowIcon.get();
case SwingConstants.SOUTH: return southArrowIcon.get();
case SwingConstants.EAST: return eastArrowIcon.get();
case SwingConstants.WEST: return westArrowIcon.get();
}
return null;
}
public static Icon getMenuArrowIcon() {
return new InvertableImageIcon(AquaUtils.generateLightenedImage(eastArrow.get(), 25));
}
public static Icon getMenuItemCheckIcon() {
return new InvertableImageIcon(AquaUtils.generateLightenedImage(
getNSIcon("NSMenuItemSelection"), 25));
}
public static Icon getMenuItemDashIcon() {
return new InvertableImageIcon(AquaUtils.generateLightenedImage(
getNSIcon("NSMenuMixedState"), 25));
}
private static Image getNSIcon(String imageName) {
Image icon = Toolkit.getDefaultToolkit()
.getImage("NSImage://" + imageName);
return icon;
}
public static final class NineSliceMetrics {
public final int wCut, eCut, nCut, sCut;
public final int minW, minH;
public final boolean showMiddle, stretchH, stretchV;
public NineSliceMetrics(final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut) {
this(minWidth, minHeight, westCut, eastCut, northCut, southCut, true);
}
public NineSliceMetrics(final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean showMiddle) {
this(minWidth, minHeight, westCut, eastCut, northCut, southCut, showMiddle, true, true);
}
public NineSliceMetrics(final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean showMiddle, final boolean stretchHorizontally, final boolean stretchVertically) {
this.wCut = westCut; this.eCut = eastCut; this.nCut = northCut; this.sCut = southCut;
this.minW = minWidth; this.minH = minHeight;
this.showMiddle = showMiddle; this.stretchH = stretchHorizontally; this.stretchV = stretchVertically;
}
}
/*
* A "paintable" which holds nine images, which represent a sliced up initial
* image that can be stretched from its middles.
*/
public static final class SlicedImageControl {
final BufferedImage NW, N, NE;
final BufferedImage W, C, E;
final BufferedImage SW, S, SE;
final NineSliceMetrics metrics;
final int totalWidth, totalHeight;
final int centerColWidth, centerRowHeight;
public SlicedImageControl(final Image img, final int westCut, final int eastCut, final int northCut, final int southCut) {
this(img, westCut, eastCut, northCut, southCut, true);
}
public SlicedImageControl(final Image img, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean useMiddle) {
this(img, westCut, eastCut, northCut, southCut, useMiddle, true, true);
}
public SlicedImageControl(final Image img, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean useMiddle, final boolean stretchHorizontally, final boolean stretchVertically) {
this(img, new NineSliceMetrics(img.getWidth(null), img.getHeight(null), westCut, eastCut, northCut, southCut, useMiddle, stretchHorizontally, stretchVertically));
}
public SlicedImageControl(final Image img, final NineSliceMetrics metrics) {
this.metrics = metrics;
if (img.getWidth(null) != metrics.minW || img.getHeight(null) != metrics.minH) {
throw new IllegalArgumentException("SlicedImageControl: template image and NineSliceMetrics don't agree on minimum dimensions");
}
totalWidth = metrics.minW;
totalHeight = metrics.minH;
centerColWidth = totalWidth - metrics.wCut - metrics.eCut;
centerRowHeight = totalHeight - metrics.nCut - metrics.sCut;
NW = createSlice(img, 0, 0, metrics.wCut, metrics.nCut);
N = createSlice(img, metrics.wCut, 0, centerColWidth, metrics.nCut);
NE = createSlice(img, totalWidth - metrics.eCut, 0, metrics.eCut, metrics.nCut);
W = createSlice(img, 0, metrics.nCut, metrics.wCut, centerRowHeight);
C = metrics.showMiddle ? createSlice(img, metrics.wCut, metrics.nCut, centerColWidth, centerRowHeight) : null;
E = createSlice(img, totalWidth - metrics.eCut, metrics.nCut, metrics.eCut, centerRowHeight);
SW = createSlice(img, 0, totalHeight - metrics.sCut, metrics.wCut, metrics.sCut);
S = createSlice(img, metrics.wCut, totalHeight - metrics.sCut, centerColWidth, metrics.sCut);
SE = createSlice(img, totalWidth - metrics.eCut, totalHeight - metrics.sCut, metrics.eCut, metrics.sCut);
}
static BufferedImage createSlice(final Image img, final int x, final int y, final int w, final int h) {
if (w == 0 || h == 0) return null;
final BufferedImage slice = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB_PRE);
final Graphics2D g2d = slice.createGraphics();
g2d.drawImage(img, 0, 0, w, h, x, y, x + w, y + h, null);
g2d.dispose();
return slice;
}
public void paint(final Graphics g, final int x, final int y, final int w, final int h) {
g.translate(x, y);
if (w < totalWidth || h < totalHeight) {
paintCompressed(g, w, h);
} else {
paintStretchedMiddles(g, w, h);
}
g.translate(-x, -y);
}
void paintStretchedMiddles(final Graphics g, final int w, final int h) {
int baseX = metrics.stretchH ? 0 : ((w / 2) - (totalWidth / 2));
int baseY = metrics.stretchV ? 0 : ((h / 2) - (totalHeight / 2));
int adjustedWidth = metrics.stretchH ? w : totalWidth;
int adjustedHeight = metrics.stretchV ? h : totalHeight;
if (NW != null) g.drawImage(NW, baseX, baseY, null);
if (N != null) g.drawImage(N, baseX + metrics.wCut, baseY, adjustedWidth - metrics.eCut - metrics.wCut, metrics.nCut, null);
if (NE != null) g.drawImage(NE, baseX + adjustedWidth - metrics.eCut, baseY, null);
if (W != null) g.drawImage(W, baseX, baseY + metrics.nCut, metrics.wCut, adjustedHeight - metrics.nCut - metrics.sCut, null);
if (C != null) g.drawImage(C, baseX + metrics.wCut, baseY + metrics.nCut, adjustedWidth - metrics.eCut - metrics.wCut, adjustedHeight - metrics.nCut - metrics.sCut, null);
if (E != null) g.drawImage(E, baseX + adjustedWidth - metrics.eCut, baseY + metrics.nCut, metrics.eCut, adjustedHeight - metrics.nCut - metrics.sCut, null);
if (SW != null) g.drawImage(SW, baseX, baseY + adjustedHeight - metrics.sCut, null);
if (S != null) g.drawImage(S, baseX + metrics.wCut, baseY + adjustedHeight - metrics.sCut, adjustedWidth - metrics.eCut - metrics.wCut, metrics.sCut, null);
if (SE != null) g.drawImage(SE, baseX + adjustedWidth - metrics.eCut, baseY + adjustedHeight - metrics.sCut, null);
/*
if (NW != null) {g.setColor(Color.GREEN); g.fillRect(baseX, baseY, NW.getWidth(), NW.getHeight());}
if (N != null) {g.setColor(Color.RED); g.fillRect(baseX + metrics.wCut, baseY, adjustedWidth - metrics.eCut - metrics.wCut, metrics.nCut);}
if (NE != null) {g.setColor(Color.BLUE); g.fillRect(baseX + adjustedWidth - metrics.eCut, baseY, NE.getWidth(), NE.getHeight());}
if (W != null) {g.setColor(Color.PINK); g.fillRect(baseX, baseY + metrics.nCut, metrics.wCut, adjustedHeight - metrics.nCut - metrics.sCut);}
if (C != null) {g.setColor(Color.ORANGE); g.fillRect(baseX + metrics.wCut, baseY + metrics.nCut, adjustedWidth - metrics.eCut - metrics.wCut, adjustedHeight - metrics.nCut - metrics.sCut);}
if (E != null) {g.setColor(Color.CYAN); g.fillRect(baseX + adjustedWidth - metrics.eCut, baseY + metrics.nCut, metrics.eCut, adjustedHeight - metrics.nCut - metrics.sCut);}
if (SW != null) {g.setColor(Color.MAGENTA); g.fillRect(baseX, baseY + adjustedHeight - metrics.sCut, SW.getWidth(), SW.getHeight());}
if (S != null) {g.setColor(Color.DARK_GRAY); g.fillRect(baseX + metrics.wCut, baseY + adjustedHeight - metrics.sCut, adjustedWidth - metrics.eCut - metrics.wCut, metrics.sCut);}
if (SE != null) {g.setColor(Color.YELLOW); g.fillRect(baseX + adjustedWidth - metrics.eCut, baseY + adjustedHeight - metrics.sCut, SE.getWidth(), SE.getHeight());}
*/
}
void paintCompressed(final Graphics g, final int w, final int h) {
final double heightRatio = h > totalHeight ? 1.0 : (double)h / (double)totalHeight;
final double widthRatio = w > totalWidth ? 1.0 : (double)w / (double)totalWidth;
final int northHeight = (int)(metrics.nCut * heightRatio);
final int southHeight = (int)(metrics.sCut * heightRatio);
final int centerHeight = h - northHeight - southHeight;
final int westWidth = (int)(metrics.wCut * widthRatio);
final int eastWidth = (int)(metrics.eCut * widthRatio);
final int centerWidth = w - westWidth - eastWidth;
if (NW != null) g.drawImage(NW, 0, 0, westWidth, northHeight, null);
if (N != null) g.drawImage(N, westWidth, 0, centerWidth, northHeight, null);
if (NE != null) g.drawImage(NE, w - eastWidth, 0, eastWidth, northHeight, null);
if (W != null) g.drawImage(W, 0, northHeight, westWidth, centerHeight, null);
if (C != null) g.drawImage(C, westWidth, northHeight, centerWidth, centerHeight, null);
if (E != null) g.drawImage(E, w - eastWidth, northHeight, eastWidth, centerHeight, null);
if (SW != null) g.drawImage(SW, 0, h - southHeight, westWidth, southHeight, null);
if (S != null) g.drawImage(S, westWidth, h - southHeight, centerWidth, southHeight, null);
if (SE != null) g.drawImage(SE, w - eastWidth, h - southHeight, eastWidth, southHeight, null);
}
}
public abstract static class RecyclableSlicedImageControl extends RecyclableObject<SlicedImageControl> {
final NineSliceMetrics metrics;
public RecyclableSlicedImageControl(final NineSliceMetrics metrics) {
this.metrics = metrics;
}
@Override
protected SlicedImageControl create() {
return new SlicedImageControl(createTemplateImage(metrics.minW, metrics.minH), metrics);
}
protected abstract Image createTemplateImage(final int width, final int height);
}
// when we use SystemColors, we need to proxy the color with something that implements UIResource,
// so that it will be uninstalled when the look and feel is changed.
@SuppressWarnings("serial") // JDK implementation class
private static class SystemColorProxy extends Color implements UIResource {
final Color color;
public SystemColorProxy(final Color color) {
super(color.getRGB());
this.color = color;
}
@Override
public int getRGB() {
return color.getRGB();
}
}
public static Color getWindowBackgroundColorUIResource() {
//return AquaNativeResources.getWindowBackgroundColorUIResource();
return new SystemColorProxy(SystemColor.window);
}
public static Color getTextSelectionBackgroundColorUIResource() {
return new SystemColorProxy(SystemColor.textHighlight);
}
public static Color getTextSelectionForegroundColorUIResource() {
return new SystemColorProxy(SystemColor.textHighlightText);
}
public static Color getSelectionBackgroundColorUIResource() {
return new SystemColorProxy(SystemColor.controlHighlight);
}
public static Color getSelectionForegroundColorUIResource() {
return new SystemColorProxy(SystemColor.controlLtHighlight);
}
public static Color getFocusRingColorUIResource() {
return new SystemColorProxy(LWCToolkit.getAppleColor(LWCToolkit.KEYBOARD_FOCUS_COLOR));
}
public static Color getCellHighlightColorUIResource() {
return new SystemColorProxy(LWCToolkit.getAppleColor(LWCToolkit.CELL_HIGHLIGHT_COLOR));
}
public static Color getSelectionInactiveBackgroundColorUIResource() {
return new SystemColorProxy(LWCToolkit.getAppleColor(LWCToolkit.INACTIVE_SELECTION_BACKGROUND_COLOR));
}
public static Color getSelectionInactiveForegroundColorUIResource() {
return new SystemColorProxy(LWCToolkit.getAppleColor(LWCToolkit.INACTIVE_SELECTION_FOREGROUND_COLOR));
}
public static Color getSelectedControlColorUIResource() {
return new SystemColorProxy(LWCToolkit.getAppleColor(LWCToolkit.SELECTED_CONTROL_TEXT_COLOR));
}
}

View file

@ -0,0 +1,518 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.beans.PropertyVetoException;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import sun.swing.SwingUtilities2;
import apple.laf.*;
import apple.laf.JRSUIConstants.*;
import apple.laf.JRSUIState.TitleBarHeightState;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaInternalFrameBorderMetrics;
import java.awt.geom.AffineTransform;
public final class AquaInternalFrameBorder implements Border, UIResource {
private static final int kCloseButton = 0;
private static final int kIconButton = 1;
private static final int kGrowButton = 2;
private static final int sMaxIconWidth = 15;
private static final int sMaxIconHeight = sMaxIconWidth;
private static final int sAfterButtonPad = 11;
private static final int sAfterIconPad = 5;
private static final int sRightSideTitleClip = 0;
private static final int kContentTester = 100; // For getting region insets
private static final RecyclableSingleton<AquaInternalFrameBorder> documentWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
protected AquaInternalFrameBorder getInstance() {
return new AquaInternalFrameBorder(WindowType.DOCUMENT);
}
};
protected static AquaInternalFrameBorder window() {
return documentWindowFrame.get();
}
private static final RecyclableSingleton<AquaInternalFrameBorder> utilityWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
protected AquaInternalFrameBorder getInstance() {
return new AquaInternalFrameBorder(WindowType.UTILITY);
}
};
protected static AquaInternalFrameBorder utility() {
return utilityWindowFrame.get();
}
private static final RecyclableSingleton<AquaInternalFrameBorder> dialogWindowFrame = new RecyclableSingleton<AquaInternalFrameBorder>() {
protected AquaInternalFrameBorder getInstance() {
return new AquaInternalFrameBorder(WindowType.DOCUMENT);
}
};
protected static AquaInternalFrameBorder dialog() {
return dialogWindowFrame.get();
}
private final AquaInternalFrameBorderMetrics metrics;
private final int fThisButtonSpan;
private final int fThisLeftSideTotal;
private final boolean fIsUtility;
// Instance variables
private final WindowType fWindowKind; // Which kind of window to draw
private Insets fBorderInsets; // Cached insets object
private Color selectedTextColor;
private Color notSelectedTextColor;
private Rectangle fInBounds; // Cached bounds rect object
protected final AquaPainter<TitleBarHeightState> titleBarPainter = AquaPainter.create(JRSUIStateFactory.getTitleBar());
protected final AquaPainter<JRSUIState> widgetPainter = AquaPainter.create(JRSUIState.getInstance());
protected AquaInternalFrameBorder(final WindowType kind) {
fWindowKind = kind;
titleBarPainter.state.set(WindowClipCorners.YES);
if (fWindowKind == WindowType.UTILITY) {
fIsUtility = true;
metrics = AquaInternalFrameBorderMetrics.getMetrics(true);
widgetPainter.state.set(WindowType.UTILITY);
titleBarPainter.state.set(WindowType.UTILITY);
} else {
fIsUtility = false;
metrics = AquaInternalFrameBorderMetrics.getMetrics(false);
widgetPainter.state.set(WindowType.DOCUMENT);
titleBarPainter.state.set(WindowType.DOCUMENT);
}
titleBarPainter.state.setValue(metrics.titleBarHeight);
titleBarPainter.state.set(WindowTitleBarSeparator.YES);
widgetPainter.state.set(AlignmentVertical.CENTER);
fThisButtonSpan = (metrics.buttonWidth * 3) + (metrics.buttonPadding * 2);
fThisLeftSideTotal = metrics.leftSidePadding + fThisButtonSpan + sAfterButtonPad;
}
public void setColors(final Color inSelectedTextColor, final Color inNotSelectedTextColor) {
selectedTextColor = inSelectedTextColor;
notSelectedTextColor = inNotSelectedTextColor;
}
// Utility to lazy-init and fill in fInBounds
protected void setInBounds(final int x, final int y, final int w, final int h) {
if (fInBounds == null) fInBounds = new Rectangle();
fInBounds.x = x;
fInBounds.y = y;
fInBounds.width = w;
fInBounds.height = h;
}
// Border interface
@Override
public boolean isBorderOpaque() {
return false;
}
// Border interface
@Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
// For expanded InternalFrames, the frame & component are the same object
paintBorder((JInternalFrame)c, c, g, x, y, w, h);
}
protected void paintTitleContents(final Graphics g, final JInternalFrame frame, final int x, final int y, final int w, final int h) {
final boolean isSelected = frame.isSelected();
final Font f = g.getFont();
g.setFont(metrics.font);
// Center text vertically.
final FontMetrics fm = g.getFontMetrics();
final int baseline = (metrics.titleBarHeight + fm.getAscent() - fm.getLeading() - fm.getDescent()) / 2;
// max button is the rightmost so use it
final int usedWidth = fThisLeftSideTotal + sRightSideTitleClip;
int iconWidth = getIconWidth(frame);
if (iconWidth > 0) iconWidth += sAfterIconPad;
final int totalWidth = w;
// window title looks like: | 0 0 0(sAfterButtonPad)IconWidth Title(right pad) |
final int availTextWidth = totalWidth - usedWidth - iconWidth - sAfterButtonPad;
final String title = frame.getTitle();
String text = title;
int totalTextWidth = 0;
int startXPosition = fThisLeftSideTotal;
boolean wasTextShortened = false;
// shorten the string to fit in the
if (text != null && !text.isEmpty()) {
totalTextWidth = SwingUtilities.computeStringWidth(fm, text);
final String clipString = "\u2026";
if (totalTextWidth > availTextWidth) {
wasTextShortened = true;
totalTextWidth = SwingUtilities.computeStringWidth(fm, clipString);
int nChars;
for (nChars = 0; nChars < text.length(); nChars++) {
final int nextCharWidth = fm.charWidth(text.charAt(nChars));
if ((totalTextWidth + nextCharWidth) > availTextWidth) {
break;
}
totalTextWidth += nextCharWidth;
}
text = text.substring(0, nChars) + clipString;
}
if (!wasTextShortened) {
// center it!
startXPosition = (totalWidth - (totalTextWidth + iconWidth)) / 2;
if (startXPosition < fThisLeftSideTotal) {
startXPosition = fThisLeftSideTotal;
}
}
if (isSelected || fIsUtility) {
g.setColor(Color.lightGray);
} else {
g.setColor(Color.white);
}
SwingUtilities2.drawString(frame, g, text, x + startXPosition + iconWidth, y + baseline + 1);
if (isSelected || fIsUtility) {
g.setColor(selectedTextColor);
} else {
g.setColor(notSelectedTextColor);
}
SwingUtilities2.drawString(frame, g, text, x + startXPosition + iconWidth, y + baseline);
g.setFont(f);
}
// sja fix x & y
final int iconYPostion = (metrics.titleBarHeight - getIconHeight(frame)) / 2;
paintTitleIcon(g, frame, x + startXPosition, y + iconYPostion);
}
public int getWhichButtonHit(final JInternalFrame frame, final int x, final int y) {
int buttonHit = -1;
final Insets i = frame.getInsets();
int startX = i.left + metrics.leftSidePadding - 1;
if (isInsideYButtonArea(i, y) && x >= startX) {
if (x <= (startX + metrics.buttonWidth)) {
if (frame.isClosable()) {
buttonHit = kCloseButton;
}
} else {
startX += metrics.buttonWidth + metrics.buttonPadding;
if (x >= startX && x <= (startX + metrics.buttonWidth)) {
if (frame.isIconifiable()) {
buttonHit = kIconButton;
}
} else {
startX += metrics.buttonWidth + metrics.buttonPadding;
if (x >= startX && x <= (startX + metrics.buttonWidth)) {
if (frame.isMaximizable()) {
buttonHit = kGrowButton;
}
}
}
}
}
return buttonHit;
}
public void doButtonAction(final JInternalFrame frame, final int whichButton) {
switch (whichButton) {
case kCloseButton:
frame.doDefaultCloseAction();
break;
case kIconButton:
if (frame.isIconifiable()) {
if (!frame.isIcon()) {
try {
frame.setIcon(true);
} catch(final PropertyVetoException e1) {}
} else {
try {
frame.setIcon(false);
} catch(final PropertyVetoException e1) {}
}
}
break;
case kGrowButton:
if (frame.isMaximizable()) {
if (!frame.isMaximum()) {
try {
frame.setMaximum(true);
} catch(final PropertyVetoException e5) {}
} else {
try {
frame.setMaximum(false);
} catch(final PropertyVetoException e6) {}
}
}
break;
default:
System.err.println("AquaInternalFrameBorder should never get here!!!!");
Thread.dumpStack();
break;
}
}
public boolean isInsideYButtonArea(final Insets i, final int y) {
final int startY = (i.top - metrics.titleBarHeight / 2) - (metrics.buttonHeight / 2) - 1;
final int endY = startY + metrics.buttonHeight;
return y >= startY && y <= endY;
}
public boolean getWithinRolloverArea(final Insets i, final int x, final int y) {
final int startX = i.left + metrics.leftSidePadding;
final int endX = startX + fThisButtonSpan;
return isInsideYButtonArea(i, y) && x >= startX && x <= endX;
}
protected void paintTitleIcon(final Graphics g, final JInternalFrame frame,
final int x, final int y) {
Icon icon = frame.getFrameIcon();
if (icon == null) {
icon = UIManager.getIcon("InternalFrame.icon");
}
if (icon == null) {
return;
}
if (icon.getIconWidth() > sMaxIconWidth
|| icon.getIconHeight() > sMaxIconHeight) {
final Graphics2D g2 = (Graphics2D) g;
final AffineTransform savedAT = g2.getTransform();
double xScaleFactor = (double) sMaxIconWidth / icon.getIconWidth();
double yScaleFactor = (double) sMaxIconHeight / icon.getIconHeight();
//Coordinates are after a translation hence relative origin shifts
g2.translate(x, y);
//scaling factor is needed to scale while maintaining aspect ratio
double scaleMaintainAspectRatio = Math.min(xScaleFactor, yScaleFactor);
//minimum value is taken to set to a maximum Icon Dimension
g2.scale(scaleMaintainAspectRatio, scaleMaintainAspectRatio);
icon.paintIcon(frame, g2, 0, 0);
g2.setTransform(savedAT);
} else {
icon.paintIcon(frame, g, x, y);
}
}
protected int getIconWidth(final JInternalFrame frame) {
int width = 0;
Icon icon = frame.getFrameIcon();
if (icon == null) {
icon = UIManager.getIcon("InternalFrame.icon");
}
if (icon != null) {
width = Math.min(icon.getIconWidth(), sMaxIconWidth);
}
return width;
}
protected int getIconHeight(final JInternalFrame frame) {
int height = 0;
Icon icon = frame.getFrameIcon();
if (icon == null) {
icon = UIManager.getIcon("InternalFrame.icon");
}
if (icon != null) {
height = Math.min(icon.getIconHeight(), sMaxIconHeight);
}
return height;
}
public void drawWindowTitle(final Graphics g, final JInternalFrame frame, final int inX, final int inY, final int inW, final int inH) {
final int x = inX;
final int y = inY;
final int w = inW;
final int h = inH;
// paint the background
titleBarPainter.state.set(frame.isSelected() ? State.ACTIVE : State.INACTIVE);
titleBarPainter.paint(g, frame, x, y, w, h);
// now the title and the icon
paintTitleContents(g, frame, x, y, w, h);
// finally the widgets
drawAllWidgets(g, frame); // rollover is last attribute
}
// Component could be a JInternalFrame or a JDesktopIcon
void paintBorder(final JInternalFrame frame, final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
if (fBorderInsets == null) getBorderInsets(c);
// Set the contentRect - inset by border size
setInBounds(x + fBorderInsets.left, y + fBorderInsets.top, w - (fBorderInsets.right + fBorderInsets.left), h - (fBorderInsets.top + fBorderInsets.bottom));
// Set parameters
setMetrics(frame, c);
// Draw the frame
drawWindowTitle(g, frame, x, y, w, h);
}
// defaults to false
boolean isDirty(final JInternalFrame frame) {
final Object dirty = frame.getClientProperty("windowModified");
if (dirty == null || dirty == Boolean.FALSE) return false;
return true;
}
// Border interface
@Override
public Insets getBorderInsets(final Component c) {
if (fBorderInsets == null) fBorderInsets = new Insets(0, 0, 0, 0);
// Paranoia check
if (!(c instanceof JInternalFrame)) return fBorderInsets;
final JInternalFrame frame = (JInternalFrame)c;
// Set the contentRect to an arbitrary value (in case the current real one is too small)
setInBounds(0, 0, kContentTester, kContentTester);
// Set parameters
setMetrics(frame, c);
fBorderInsets.left = 0;
fBorderInsets.top = metrics.titleBarHeight;
fBorderInsets.right = 0;
fBorderInsets.bottom = 0;
return fBorderInsets;
}
public void repaintButtonArea(final JInternalFrame frame) {
final Insets i = frame.getInsets();
final int x = i.left + metrics.leftSidePadding;
final int y = i.top - metrics.titleBarHeight + 1;
frame.repaint(x, y, fThisButtonSpan, metrics.titleBarHeight - 2);
}
// Draw all the widgets this frame supports
void drawAllWidgets(final Graphics g, final JInternalFrame frame) {
int x = metrics.leftSidePadding;
int y = (metrics.titleBarHeight - metrics.buttonHeight) / 2 - metrics.titleBarHeight;
final Insets insets = frame.getInsets();
x += insets.left;
y += insets.top + metrics.downShift;
final AquaInternalFrameUI ui = (AquaInternalFrameUI)frame.getUI();
final int buttonPressedIndex = ui.getWhichButtonPressed();
final boolean overButton = ui.getMouseOverPressedButton();
final boolean rollover = ui.getRollover();
final boolean frameSelected = frame.isSelected() || fIsUtility;
final boolean generalActive = rollover || frameSelected;
final boolean dirty = isDirty(frame);
paintButton(g, frame, x, y, kCloseButton, buttonPressedIndex, overButton, frame.isClosable(), generalActive, rollover, dirty);
x += metrics.buttonPadding + metrics.buttonWidth;
paintButton(g, frame, x, y, kIconButton, buttonPressedIndex, overButton, frame.isIconifiable(), generalActive, rollover, false);
x += metrics.buttonPadding + metrics.buttonWidth;
paintButton(g, frame, x, y, kGrowButton, buttonPressedIndex, overButton, frame.isMaximizable(), generalActive, rollover, false);
}
public void paintButton(final Graphics g, final JInternalFrame frame, final int x, final int y, final int buttonType, final int buttonPressedIndex, final boolean overButton, final boolean enabled, final boolean active, final boolean anyRollover, final boolean dirty) {
widgetPainter.state.set(getWidget(frame, buttonType));
widgetPainter.state.set(getState(buttonPressedIndex == buttonType && overButton, anyRollover, active, enabled));
widgetPainter.state.set(dirty ? BooleanValue.YES : BooleanValue.NO);
widgetPainter.paint(g, frame, x, y, metrics.buttonWidth, metrics.buttonHeight);
}
static Widget getWidget(final JInternalFrame frame, final int buttonType) {
switch (buttonType) {
case kIconButton: return Widget.TITLE_BAR_COLLAPSE_BOX;
case kGrowButton: return Widget.TITLE_BAR_ZOOM_BOX;
}
return Widget.TITLE_BAR_CLOSE_BOX;
}
static State getState(final boolean pressed, final boolean rollover, final boolean active, final boolean enabled) {
if (!enabled) return State.DISABLED;
if (!active) return State.INACTIVE;
if (pressed) return State.PRESSED;
if (rollover) return State.ROLLOVER;
return State.ACTIVE;
}
protected void setMetrics(final JInternalFrame frame, final Component window) {
final String title = frame.getTitle();
final FontMetrics fm = frame.getFontMetrics(UIManager.getFont("InternalFrame.titleFont"));
int titleWidth = 0;
int titleHeight = fm.getAscent();
if (title != null) {
titleWidth = SwingUtilities.computeStringWidth(fm, title);
}
// Icon space
final Icon icon = frame.getFrameIcon();
if (icon != null) {
titleWidth += icon.getIconWidth();
titleHeight = Math.max(titleHeight, icon.getIconHeight());
}
}
protected int getTitleHeight() {
return metrics.titleBarHeight;
}
}

View file

@ -0,0 +1,125 @@
/*
* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Font;
import apple.laf.JRSUIUtils;
import com.apple.laf.AquaUtils.RecyclableSingleton;
public abstract class AquaInternalFrameBorderMetrics {
private static final boolean useLegacyBorderMetrics = JRSUIUtils.InternalFrame.shouldUseLegacyBorderMetrics();
public Font font;
public int titleBarHeight;
public int leftSidePadding;
public int buttonHeight;
public int buttonWidth;
public int buttonPadding;
public int downShift;
private AquaInternalFrameBorderMetrics() {
initialize();
}
protected abstract void initialize();
public static AquaInternalFrameBorderMetrics getMetrics(boolean isUtility) {
if (useLegacyBorderMetrics) {
return isUtility ? legacyUtilityMetrics.get() : legacyStandardMetrics.get();
} else {
return isUtility ? utilityMetrics.get() : standardMetrics.get();
}
}
private static final RecyclableSingleton<AquaInternalFrameBorderMetrics> standardMetrics = new RecyclableSingleton<AquaInternalFrameBorderMetrics>() {
@Override
protected AquaInternalFrameBorderMetrics getInstance() {
return new AquaInternalFrameBorderMetrics() {
protected void initialize() {
font = new Font("Lucida Grande", Font.PLAIN, 13);
titleBarHeight = 22;
leftSidePadding = 7;
buttonHeight = 15;
buttonWidth = 15;
buttonPadding = 5;
downShift = 0;
}
};
}
};
private static final RecyclableSingleton<AquaInternalFrameBorderMetrics> utilityMetrics = new RecyclableSingleton<AquaInternalFrameBorderMetrics>() {
@Override
protected AquaInternalFrameBorderMetrics getInstance() {
return new AquaInternalFrameBorderMetrics() {
protected void initialize() {
font = new Font("Lucida Grande", Font.PLAIN, 11);
titleBarHeight = 16;
leftSidePadding = 6;
buttonHeight = 12;
buttonWidth = 12;
buttonPadding = 6;
downShift = 0;
}
};
}
};
private static final RecyclableSingleton<AquaInternalFrameBorderMetrics> legacyStandardMetrics = new RecyclableSingleton<AquaInternalFrameBorderMetrics>() {
@Override
protected AquaInternalFrameBorderMetrics getInstance() {
return new AquaInternalFrameBorderMetrics() {
protected void initialize() {
font = new Font("Lucida Grande", Font.PLAIN, 13);
titleBarHeight = 22;
leftSidePadding = 8;
buttonHeight = 15;
buttonWidth = 15;
buttonPadding = 6;
downShift = 1;
}
};
}
};
private static final RecyclableSingleton<AquaInternalFrameBorderMetrics> legacyUtilityMetrics = new RecyclableSingleton<AquaInternalFrameBorderMetrics>() {
@Override
protected AquaInternalFrameBorderMetrics getInstance() {
return new AquaInternalFrameBorderMetrics() {
protected void initialize() {
font = new Font("Lucida Grande", Font.PLAIN, 11);
titleBarHeight = 16;
leftSidePadding = 5;
buttonHeight = 13;
buttonWidth = 13;
buttonPadding = 5;
downShift = 0;
}
};
}
};
}

View file

@ -0,0 +1,305 @@
/*
* Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyVetoException;
import javax.swing.*;
import javax.swing.plaf.*;
import sun.swing.SwingUtilities2;
/**
* From MacDockIconUI
*
* A JRSUI L&F implementation of JInternalFrame.JDesktopIcon
*/
public final class AquaInternalFrameDockIconUI extends DesktopIconUI
implements MouseListener, MouseMotionListener {
private JInternalFrame.JDesktopIcon fDesktopIcon;
private JInternalFrame fFrame;
private ScaledImageLabel fIconPane;
private DockLabel fDockLabel;
private boolean fTrackingIcon;
public static ComponentUI createUI(final JComponent c) {
return new AquaInternalFrameDockIconUI();
}
@Override
public void installUI(final JComponent c) {
fDesktopIcon = (JInternalFrame.JDesktopIcon)c;
installComponents();
installListeners();
}
@Override
public void uninstallUI(final JComponent c) {
uninstallComponents();
uninstallListeners();
fDesktopIcon = null;
fFrame = null;
}
private void installComponents() {
fFrame = fDesktopIcon.getInternalFrame();
fIconPane = new ScaledImageLabel();
fDesktopIcon.setLayout(new BorderLayout());
fDesktopIcon.add(fIconPane, BorderLayout.CENTER);
}
private void uninstallComponents() {
fDesktopIcon.setLayout(null);
fDesktopIcon.remove(fIconPane);
}
private void installListeners() {
fDesktopIcon.addMouseListener(this);
fDesktopIcon.addMouseMotionListener(this);
}
private void uninstallListeners() {
fDesktopIcon.removeMouseMotionListener(this);
fDesktopIcon.removeMouseListener(this);
}
@Override
public Dimension getMinimumSize(final JComponent c) {
return new Dimension(32, 32);
}
@Override
public Dimension getMaximumSize(final JComponent c) {
return new Dimension(128, 128);
}
@Override
public Dimension getPreferredSize(final JComponent c) {
return new Dimension(64, 64); //$ Dock preferred size
}
void updateIcon() {
fIconPane.updateIcon();
}
@Override
public void mousePressed(final MouseEvent e) {
fTrackingIcon = fIconPane.mouseInIcon(e);
if (fTrackingIcon) fIconPane.repaint();
}
@Override
public void mouseReleased(final MouseEvent e) {// only when it's actually in the image
if (fFrame.isIconifiable() && fFrame.isIcon()) {
if (fTrackingIcon) {
fTrackingIcon = false;
if (fIconPane.mouseInIcon(e)) {
if (fDockLabel != null) fDockLabel.hide();
try {
fFrame.setIcon(false);
} catch(final PropertyVetoException e2) {}
} else {
fIconPane.repaint();
}
}
}
// if the mouse was completely outside fIconPane, hide the label
if (fDockLabel != null && !fIconPane.getBounds().contains(e.getX(), e.getY())) fDockLabel.hide();
}
@Override
@SuppressWarnings("deprecation")
public void mouseEntered(final MouseEvent e) {
if ((e.getModifiers() & InputEvent.BUTTON1_MASK) != 0) return;
String title = fFrame.getTitle();
if (title == null || title.isEmpty()) title = "Untitled";
fDockLabel = new DockLabel(title);
fDockLabel.show(fDesktopIcon);
}
@Override
@SuppressWarnings("deprecation")
public void mouseExited(final MouseEvent e) {
if (fDockLabel != null && (e.getModifiers() & InputEvent.BUTTON1_MASK) == 0) fDockLabel.hide();
}
@Override
public void mouseClicked(final MouseEvent e) { }
@Override
public void mouseDragged(final MouseEvent e) { }
@Override
public void mouseMoved(final MouseEvent e) { }
@SuppressWarnings("serial") // Superclass is not serializable across versions
private final class ScaledImageLabel extends JLabel {
ScaledImageLabel() {
super(null, null, CENTER);
}
void updateIcon() {
int width = fFrame.getWidth();
int height = fFrame.getHeight();
// Protect us from unsized frames, like in JCK test DefaultDesktopManager2008
if (width <= 0 || height <= 0) {
width = 128;
height = 128;
}
final Image fImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
final Graphics g = fImage.getGraphics();
fFrame.paint(g);
g.dispose();
final float scale = (float)fDesktopIcon.getWidth() / (float)Math.max(width, height) * 0.89f;
// Sending in -1 for width xor height causes it to maintain aspect ratio
setIcon(new ImageIcon(fImage.getScaledInstance((int)(width * scale), -1, Image.SCALE_SMOOTH)));
}
@Override
public void paint(final Graphics g) {
if (getIcon() == null) updateIcon();
g.translate(0, 2);
if (!fTrackingIcon) {
super.paint(g);
return;
}
final ImageIcon prev = (ImageIcon)getIcon();
final ImageIcon pressedIcon = new ImageIcon(AquaUtils.generateSelectedDarkImage(prev.getImage()));
setIcon(pressedIcon);
super.paint(g);
setIcon(prev);
}
boolean mouseInIcon(final MouseEvent e) {
return getBounds().contains(e.getX(), e.getY());
}
@Override
public Dimension getPreferredSize() {
return new Dimension(64, 64); //$ Dock preferred size
}
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
private static final class DockLabel extends JLabel {
static final int NUB_HEIGHT = 7;
static final int ROUND_ADDITIONAL_HEIGHT = 8;
static final int ROUND_ADDITIONAL_WIDTH = 12;
DockLabel(final String text) {
super(text);
setBorder(null);
setOpaque(false);
setFont(AquaFonts.getDockIconFont());
final FontMetrics metrics = getFontMetrics(getFont());
setSize(SwingUtilities.computeStringWidth(metrics, getText()) + ROUND_ADDITIONAL_WIDTH * 2, metrics.getAscent() + NUB_HEIGHT + ROUND_ADDITIONAL_HEIGHT);
}
@Override
public void paint(final Graphics g) {
final int width = getWidth();
final int height = getHeight();
final Font font = getFont();
final FontMetrics metrics = getFontMetrics(font);
g.setFont(font);
final String text = getText().trim();
final int ascent = metrics.getAscent();
final Rectangle2D stringBounds = metrics.getStringBounds(text, g);
final int halfway = width / 2;
final int x = (halfway - (int)stringBounds.getWidth() / 2);
final Graphics2D g2d = g instanceof Graphics2D ? (Graphics2D)g : null;
if (g2d != null) {
g.setColor(UIManager.getColor("DesktopIcon.labelBackground"));
final Object origAA = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
final int roundHeight = height - ROUND_ADDITIONAL_HEIGHT + 1;
g.fillRoundRect(0, 0, width, roundHeight, roundHeight, roundHeight);
final int[] xpts = { halfway, halfway + NUB_HEIGHT, halfway - NUB_HEIGHT };
final int[] ypts = { height, height - NUB_HEIGHT, height - NUB_HEIGHT };
g.fillPolygon(xpts, ypts, 3);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, origAA);
}
g.setColor(Color.black);
SwingUtilities2.drawString(this, g, text, x, 2 + ascent);
g.setColor(Color.white);
SwingUtilities2.drawString(this, g, text, x, 1 + ascent);
}
public void show(final Component invoker) {
final int desiredLocationX = (invoker.getWidth() - getWidth()) / 2;
final int desiredLocationY = -(getHeight() + 6);
Container parent = invoker.getParent();
for (Container p = parent; p != null; p = p.getParent()) {
if (p instanceof JRootPane) {
if (p.getParent() instanceof JInternalFrame) continue;
parent = ((JRootPane)p).getLayeredPane();
for (p = parent.getParent(); p != null && (!(p instanceof java.awt.Window)); p = p.getParent());
break;
}
}
final Point p = SwingUtilities.convertPoint(invoker, desiredLocationX, desiredLocationY, parent);
setLocation(p.x, p.y);
if (parent instanceof JLayeredPane) {
((JLayeredPane)parent).add(this, JLayeredPane.POPUP_LAYER, 0);
}
}
@Override
@Deprecated
public void hide() {
final Container parent = getParent();
final Rectangle r = this.getBounds();
if (parent == null) return;
parent.remove(this);
parent.repaint(r.x, r.y, r.width, r.height);
}
}
}

View file

@ -0,0 +1,211 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.beans.PropertyVetoException;
import java.util.Vector;
import javax.swing.*;
/**
* Based on AquaInternalFrameManager
*
* DesktopManager implementation for Aqua
*
* Mac is more like Windows than it's like Motif/Basic
*
* From WindowsDesktopManager:
*
* This class implements a DesktopManager which more closely follows
* the MDI model than the DefaultDesktopManager. Unlike the
* DefaultDesktopManager policy, MDI requires that the selected
* and activated child frames are the same, and that that frame
* always be the top-most window.
* <p>
* The maximized state is managed by the DesktopManager with MDI,
* instead of just being a property of the individual child frame.
* This means that if the currently selected window is maximized
* and another window is selected, that new window will be maximized.
*
* @see com.sun.java.swing.plaf.windows.WindowsDesktopManager
*/
@SuppressWarnings("serial") // JDK implementation class
public class AquaInternalFrameManager extends DefaultDesktopManager {
// Variables
/* The frame which is currently selected/activated.
* We store this value to enforce Mac's single-selection model.
*/
JInternalFrame fCurrentFrame;
JInternalFrame fInitialFrame;
AquaInternalFramePaneUI fCurrentPaneUI;
/* The list of frames, sorted by order of creation.
* This list is necessary because by default the order of
* child frames in the JDesktopPane changes during frame
* activation (the activated frame is moved to index 0).
* We preserve the creation order so that "next" and "previous"
* frame actions make sense.
*/
Vector<JInternalFrame> fChildFrames = new Vector<JInternalFrame>(1);
@Override
public void closeFrame(final JInternalFrame f) {
if (f == fCurrentFrame) {
activateNextFrame();
}
fChildFrames.removeElement(f);
super.closeFrame(f);
}
@Override
public void deiconifyFrame(final JInternalFrame f) {
JInternalFrame.JDesktopIcon desktopIcon;
desktopIcon = f.getDesktopIcon();
// If the icon moved, move the frame to that spot before expanding it
// reshape does delta checks for us
f.reshape(desktopIcon.getX(), desktopIcon.getY(), f.getWidth(), f.getHeight());
super.deiconifyFrame(f);
}
void addIcon(final Container c, final JInternalFrame.JDesktopIcon desktopIcon) {
c.add(desktopIcon);
}
/** Removes the frame from its parent and adds its desktopIcon to the parent. */
@Override
public void iconifyFrame(final JInternalFrame f) {
// Same as super except doesn't deactivate it
JInternalFrame.JDesktopIcon desktopIcon;
Container c;
desktopIcon = f.getDesktopIcon();
// Position depends on *current* position of frame, unlike super which reuses the first position
final Rectangle r = getBoundsForIconOf(f);
desktopIcon.setBounds(r.x, r.y, r.width, r.height);
if (!wasIcon(f)) {
setWasIcon(f, Boolean.TRUE);
}
c = f.getParent();
if (c == null) return;
c.remove(f);
addIcon(c, desktopIcon);
c.repaint(f.getX(), f.getY(), f.getWidth(), f.getHeight());
}
// WindowsDesktopManager code
@Override
public void activateFrame(final JInternalFrame f) {
try {
if (f != null) super.activateFrame(f);
// If this is the first activation, add to child list.
if (fChildFrames.indexOf(f) == -1) {
fChildFrames.addElement(f);
}
if (fCurrentFrame != null && f != fCurrentFrame) {
if (fCurrentFrame.isSelected()) {
fCurrentFrame.setSelected(false);
}
}
if (f != null && !f.isSelected()) {
f.setSelected(true);
}
fCurrentFrame = f;
} catch(final PropertyVetoException e) {}
}
private void switchFrame(final boolean next) {
if (fCurrentFrame == null) {
// initialize first frame we find
if (fInitialFrame != null) activateFrame(fInitialFrame);
return;
}
final int count = fChildFrames.size();
if (count <= 1) {
// No other child frames.
return;
}
final int currentIndex = fChildFrames.indexOf(fCurrentFrame);
if (currentIndex == -1) {
// the "current frame" is no longer in the list
fCurrentFrame = null;
return;
}
int nextIndex;
if (next) {
nextIndex = currentIndex + 1;
if (nextIndex == count) {
nextIndex = 0;
}
} else {
nextIndex = currentIndex - 1;
if (nextIndex == -1) {
nextIndex = count - 1;
}
}
final JInternalFrame f = fChildFrames.elementAt(nextIndex);
activateFrame(f);
fCurrentFrame = f;
}
/**
* Activate the next child JInternalFrame, as determined by
* the frames' Z-order. If there is only one child frame, it
* remains activated. If there are no child frames, nothing
* happens.
*/
public void activateNextFrame() {
switchFrame(true);
}
/** same as above but will activate a frame if none
* have been selected
*/
public void activateNextFrame(final JInternalFrame f) {
fInitialFrame = f;
switchFrame(true);
}
/**
* Activate the previous child JInternalFrame, as determined by
* the frames' Z-order. If there is only one child frame, it
* remains activated. If there are no child frames, nothing
* happens.
*/
public void activatePreviousFrame() {
switchFrame(false);
}
}

View file

@ -0,0 +1,268 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyVetoException;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicDesktopPaneUI;
public final class AquaInternalFramePaneUI extends BasicDesktopPaneUI implements MouseListener {
JComponent fDock;
DockLayoutManager fLayoutMgr;
public static ComponentUI createUI(final JComponent c) {
return new AquaInternalFramePaneUI();
}
@Override
public void update(final Graphics g, final JComponent c) {
if (c.isOpaque()) {
super.update(g, c);
return;
}
paint(g, c);
}
@Override
public void installUI(final JComponent c) {
super.installUI(c);
fLayoutMgr = new DockLayoutManager();
c.setLayout(fLayoutMgr);
c.addMouseListener(this);
}
@Override
public void uninstallUI(final JComponent c) {
c.removeMouseListener(this);
if (fDock != null) {
c.remove(fDock);
fDock = null;
}
if (fLayoutMgr != null) {
c.setLayout(null);
fLayoutMgr = null;
}
super.uninstallUI(c);
}
// Our superclass hardcodes DefaultDesktopManager - how rude!
@Override
protected void installDesktopManager() {
if (desktop.getDesktopManager() == null) {
desktopManager = new AquaDockingDesktopManager();
desktop.setDesktopManager(desktopManager);
}
}
@Override
protected void uninstallDesktopManager() {
final DesktopManager manager = desktop.getDesktopManager();
if (manager instanceof AquaDockingDesktopManager) {
desktop.setDesktopManager(null);
}
}
JComponent getDock() {
if (fDock == null) {
fDock = new Dock(desktop);
desktop.add(fDock, Integer.valueOf(399)); // Just below the DRAG_LAYER
}
return fDock;
}
final class DockLayoutManager implements LayoutManager {
@Override
public void addLayoutComponent(final String name, final Component comp) {
}
@Override
public void removeLayoutComponent(final Component comp) {
}
@Override
public Dimension preferredLayoutSize(final Container parent) {
return parent.getSize();
}
@Override
public Dimension minimumLayoutSize(final Container parent) {
return parent.getSize();
}
@Override
public void layoutContainer(final Container parent) {
if (fDock != null) ((Dock)fDock).updateSize();
}
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
final class Dock extends JComponent implements Border {
static final int DOCK_EDGE_SLACK = 8;
Dock(final JComponent parent) {
setBorder(this);
setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
setVisible(false);
}
@Override
public void removeNotify() {
fDock = null;
super.removeNotify();
}
void updateSize() {
final Dimension d = getPreferredSize();
setBounds((getParent().getWidth() - d.width) / 2, getParent().getHeight() - d.height, d.width, d.height);
}
@Override
public Component add(final Component c) {
super.add(c);
if (!isVisible()) {
setVisible(true);
}
updateSize();
validate();
return c;
}
@Override
public void remove(final Component c) {
super.remove(c);
if (getComponentCount() == 0) {
setVisible(false);
} else {
updateSize();
validate();
}
}
@Override
public Insets getBorderInsets(final Component c) {
return new Insets(DOCK_EDGE_SLACK / 4, DOCK_EDGE_SLACK, 0, DOCK_EDGE_SLACK);
}
@Override
public boolean isBorderOpaque() {
return false;
}
@Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
if (!(g instanceof Graphics2D)) return;
final Graphics2D g2d = (Graphics2D)g;
final int height = getHeight();
final int width = getWidth();
final Object priorAA = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(UIManager.getColor("DesktopIcon.borderColor"));
g2d.fillRoundRect(4, 4, width - 9, height + DOCK_EDGE_SLACK, DOCK_EDGE_SLACK, DOCK_EDGE_SLACK);
g2d.setColor(UIManager.getColor("DesktopIcon.borderRimColor"));
g2d.setStroke(new BasicStroke(2.0f));
g2d.drawRoundRect(4, 4, width - 9, height + DOCK_EDGE_SLACK, DOCK_EDGE_SLACK, DOCK_EDGE_SLACK);
if (priorAA != null) g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, priorAA);
}
}
@SuppressWarnings("serial") // JDK implementation class
static final class AquaDockingDesktopManager extends AquaInternalFrameManager {
@Override
public void openFrame(final JInternalFrame f) {
final JInternalFrame.JDesktopIcon desktopIcon = f.getDesktopIcon();
final Container dock = desktopIcon.getParent();
if (dock == null) return;
if (dock.getParent() != null) dock.getParent().add(f);
removeIconFor(f);
}
@Override
public void deiconifyFrame(final JInternalFrame f) {
final JInternalFrame.JDesktopIcon desktopIcon = f.getDesktopIcon();
final Container dock = desktopIcon.getParent();
if (dock == null) return;
if (dock.getParent() != null) dock.getParent().add(f);
removeIconFor(f);
// <rdar://problem/3712485> removed f.show(). show() is now deprecated and
// it wasn't sending our frame to front nor selecting it. Now, we move it
// to front and select it manually. (vm)
f.moveToFront();
try {
f.setSelected(true);
} catch(final PropertyVetoException pve) { /* do nothing */ }
}
@Override
public void iconifyFrame(final JInternalFrame f) {
final JInternalFrame.JDesktopIcon desktopIcon = f.getDesktopIcon();
// paint the frame onto the icon before hiding the frame, else the contents won't show
((AquaInternalFrameDockIconUI)desktopIcon.getUI()).updateIcon();
super.iconifyFrame(f);
}
@Override
void addIcon(final Container c, final JInternalFrame.JDesktopIcon desktopIcon) {
final DesktopPaneUI ui = ((JDesktopPane)c).getUI();
((AquaInternalFramePaneUI)ui).getDock().add(desktopIcon);
}
}
@Override
public void mousePressed(final MouseEvent e) {
JInternalFrame selectedFrame = desktop.getSelectedFrame();
if (selectedFrame != null) {
try {
selectedFrame.setSelected(false);
} catch (PropertyVetoException ex) {}
desktop.getDesktopManager().deactivateFrame(selectedFrame);
}
}
@Override
public void mouseReleased(final MouseEvent e) { }
@Override
public void mouseClicked(final MouseEvent e) { }
@Override
public void mouseEntered(final MouseEvent e) { }
@Override
public void mouseExited(final MouseEvent e) { }
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,617 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.event.ActionEvent;
import java.util.*;
import javax.swing.*;
import javax.swing.UIDefaults.LazyValue;
import javax.swing.text.*;
import javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
public final class AquaKeyBindings {
private static final RecyclableSingleton<AquaKeyBindings> instance = new RecyclableSingletonFromDefaultConstructor<AquaKeyBindings>(AquaKeyBindings.class);
static AquaKeyBindings instance() {
return instance.get();
}
final DefaultKeyTypedAction defaultKeyTypedAction = new DefaultKeyTypedAction();
void setDefaultAction(final String keymapName) {
final javax.swing.text.Keymap map = JTextComponent.getKeymap(keymapName);
map.setDefaultAction(defaultKeyTypedAction);
}
static final String upMultilineAction = "aqua-move-up";
static final String downMultilineAction = "aqua-move-down";
static final String pageUpMultiline = "aqua-page-up";
static final String pageDownMultiline = "aqua-page-down";
final String[] commonTextEditorBindings = {
"ENTER", JTextField.notifyAction,
"COPY", DefaultEditorKit.copyAction,
"CUT", DefaultEditorKit.cutAction,
"PASTE", DefaultEditorKit.pasteAction,
"meta A", DefaultEditorKit.selectAllAction,
"meta C", DefaultEditorKit.copyAction,
"meta V", DefaultEditorKit.pasteAction,
"meta X", DefaultEditorKit.cutAction,
"meta BACK_SLASH", "unselect",
"DELETE", DefaultEditorKit.deleteNextCharAction,
"alt DELETE", "delete-next-word",
"BACK_SPACE", DefaultEditorKit.deletePrevCharAction,
"shift BACK_SPACE", DefaultEditorKit.deletePrevCharAction,
"alt BACK_SPACE", "delete-previous-word",
"LEFT", DefaultEditorKit.backwardAction,
"KP_LEFT", DefaultEditorKit.backwardAction,
"RIGHT", DefaultEditorKit.forwardAction,
"KP_RIGHT", DefaultEditorKit.forwardAction,
"shift LEFT", DefaultEditorKit.selectionBackwardAction,
"shift KP_LEFT", DefaultEditorKit.selectionBackwardAction,
"shift RIGHT", DefaultEditorKit.selectionForwardAction,
"shift KP_RIGHT", DefaultEditorKit.selectionForwardAction,
"meta LEFT", DefaultEditorKit.beginLineAction,
"meta KP_LEFT", DefaultEditorKit.beginLineAction,
"meta RIGHT", DefaultEditorKit.endLineAction,
"meta KP_RIGHT", DefaultEditorKit.endLineAction,
"shift meta LEFT", DefaultEditorKit.selectionBeginLineAction,
"shift meta KP_LEFT", DefaultEditorKit.selectionBeginLineAction,
"shift meta RIGHT", DefaultEditorKit.selectionEndLineAction,
"shift meta KP_RIGHT", DefaultEditorKit.selectionEndLineAction,
"alt LEFT", DefaultEditorKit.previousWordAction,
"alt KP_LEFT", DefaultEditorKit.previousWordAction,
"alt RIGHT", DefaultEditorKit.nextWordAction,
"alt KP_RIGHT", DefaultEditorKit.nextWordAction,
"alt UP", DefaultEditorKit.beginLineUpAction,
"alt DOWN", DefaultEditorKit.endLineDownAction,
"shift alt LEFT", DefaultEditorKit.selectionPreviousWordAction,
"shift alt KP_LEFT", DefaultEditorKit.selectionPreviousWordAction,
"shift alt RIGHT", DefaultEditorKit.selectionNextWordAction,
"shift alt KP_RIGHT", DefaultEditorKit.selectionNextWordAction,
"control A", DefaultEditorKit.beginLineAction,
"control B", DefaultEditorKit.backwardAction,
"control D", DefaultEditorKit.deleteNextCharAction,
"control E", DefaultEditorKit.endLineAction,
"control F", DefaultEditorKit.forwardAction,
"control H", DefaultEditorKit.deletePrevCharAction,
"control W", "delete-previous-word",
"control shift O", "toggle-componentOrientation",
"END", DefaultEditorKit.endAction,
"HOME", DefaultEditorKit.beginAction,
"shift END", DefaultEditorKit.selectionEndAction,
"shift HOME", DefaultEditorKit.selectionBeginAction,
"PAGE_DOWN", pageDownMultiline,
"PAGE_UP", pageUpMultiline,
"shift PAGE_DOWN", "selection-page-down",
"shift PAGE_UP", "selection-page-up",
"meta shift PAGE_DOWN", "selection-page-right",
"meta shift PAGE_UP", "selection-page-left",
"meta DOWN", DefaultEditorKit.endAction,
"meta KP_DOWN", DefaultEditorKit.endAction,
"meta UP", DefaultEditorKit.beginAction,
"meta KP_UP", DefaultEditorKit.beginAction,
"shift meta DOWN", DefaultEditorKit.selectionEndAction,
"shift meta KP_DOWN", DefaultEditorKit.selectionEndAction,
"shift meta UP", DefaultEditorKit.selectionBeginAction,
"shift meta KP_UP", DefaultEditorKit.selectionBeginAction,
};
LateBoundInputMap getTextFieldInputMap() {
return new LateBoundInputMap(new SimpleBinding(commonTextEditorBindings), new SimpleBinding(new String[] {
"DOWN", DefaultEditorKit.endLineAction,
"KP_DOWN", DefaultEditorKit.endLineAction,
"UP", DefaultEditorKit.beginLineAction,
"KP_UP", DefaultEditorKit.beginLineAction,
"shift DOWN", DefaultEditorKit.selectionEndLineAction,
"shift KP_DOWN", DefaultEditorKit.selectionEndLineAction,
"shift UP", DefaultEditorKit.selectionBeginLineAction,
"shift KP_UP", DefaultEditorKit.selectionBeginLineAction,
"control P", DefaultEditorKit.beginAction,
"control N", DefaultEditorKit.endAction,
"control V", DefaultEditorKit.endAction,
}));
}
LateBoundInputMap getPasswordFieldInputMap() {
return new LateBoundInputMap(new SimpleBinding(getTextFieldInputMap().getBindings()),
// nullify all the bindings that may discover space characters in the text
new SimpleBinding(new String[] {
"alt LEFT", null,
"alt KP_LEFT", null,
"alt RIGHT", null,
"alt KP_RIGHT", null,
"shift alt LEFT", null,
"shift alt KP_LEFT", null,
"shift alt RIGHT", null,
"shift alt KP_RIGHT", null,
"alt BACK_SPACE", null,
"ctrl W", null,
"alt DELETE", null,
}));
}
LateBoundInputMap getMultiLineTextInputMap() {
return new LateBoundInputMap(new SimpleBinding(commonTextEditorBindings), new SimpleBinding(new String[] {
"ENTER", DefaultEditorKit.insertBreakAction,
"DOWN", downMultilineAction,
"KP_DOWN", downMultilineAction,
"UP", upMultilineAction,
"KP_UP", upMultilineAction,
"shift DOWN", DefaultEditorKit.selectionDownAction,
"shift KP_DOWN", DefaultEditorKit.selectionDownAction,
"shift UP", DefaultEditorKit.selectionUpAction,
"shift KP_UP", DefaultEditorKit.selectionUpAction,
"alt shift DOWN", DefaultEditorKit.selectionEndParagraphAction,
"alt shift KP_DOWN", DefaultEditorKit.selectionEndParagraphAction,
"alt shift UP", DefaultEditorKit.selectionBeginParagraphAction,
"alt shift KP_UP", DefaultEditorKit.selectionBeginParagraphAction,
"control P", DefaultEditorKit.upAction,
"control N", DefaultEditorKit.downAction,
"control V", pageDownMultiline,
"TAB", DefaultEditorKit.insertTabAction,
"meta SPACE", "activate-link-action",
"meta T", "next-link-action",
"meta shift T", "previous-link-action",
"END", DefaultEditorKit.endAction,
"HOME", DefaultEditorKit.beginAction,
"shift END", DefaultEditorKit.selectionEndAction,
"shift HOME", DefaultEditorKit.selectionBeginAction,
"PAGE_DOWN", pageDownMultiline,
"PAGE_UP", pageUpMultiline,
"shift PAGE_DOWN", "selection-page-down",
"shift PAGE_UP", "selection-page-up",
"meta shift PAGE_DOWN", "selection-page-right",
"meta shift PAGE_UP", "selection-page-left",
}));
}
LateBoundInputMap getFormattedTextFieldInputMap() {
return new LateBoundInputMap(getTextFieldInputMap(), new SimpleBinding(new String[] {
"UP", "increment",
"KP_UP", "increment",
"DOWN", "decrement",
"KP_DOWN", "decrement",
"ESCAPE", "reset-field-edit",
}));
}
LateBoundInputMap getComboBoxInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"ESCAPE", "aquaHidePopup",
"PAGE_UP", "aquaSelectPageUp",
"PAGE_DOWN", "aquaSelectPageDown",
"HOME", "aquaSelectHome",
"END", "aquaSelectEnd",
"ENTER", "enterPressed",
"UP", "aquaSelectPrevious",
"KP_UP", "aquaSelectPrevious",
"DOWN", "aquaSelectNext",
"KP_DOWN", "aquaSelectNext",
"alt DOWN", "aquaOpenPopupOrhighlightLast",
"alt KP_DOWN", "aquaOpenPopupOrhighlightLast",
"alt UP", "aquaOpenPopupOrhighlightFirst",
"alt KP_UP", "aquaOpenPopupOrhighlightFirst",
"SPACE", "aquaSpacePressed" // "spacePopup"
}));
}
LateBoundInputMap getListInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
"COPY", "copy",
"PASTE", "paste",
"CUT", "cut",
"UP", "selectPreviousRow",
"KP_UP", "selectPreviousRow",
"shift UP", "selectPreviousRowExtendSelection",
"shift KP_UP", "selectPreviousRowExtendSelection",
"DOWN", "selectNextRow",
"KP_DOWN", "selectNextRow",
"shift DOWN", "selectNextRowExtendSelection",
"shift KP_DOWN", "selectNextRowExtendSelection",
"LEFT", "selectPreviousColumn",
"KP_LEFT", "selectPreviousColumn",
"shift LEFT", "selectPreviousColumnExtendSelection",
"shift KP_LEFT", "selectPreviousColumnExtendSelection",
"RIGHT", "selectNextColumn",
"KP_RIGHT", "selectNextColumn",
"shift RIGHT", "selectNextColumnExtendSelection",
"shift KP_RIGHT", "selectNextColumnExtendSelection",
"meta A", "selectAll",
// aquaHome and aquaEnd are new actions that just move the view so the first or last item is visible.
"HOME", "aquaHome",
"shift HOME", "selectFirstRowExtendSelection",
"END", "aquaEnd",
"shift END", "selectLastRowExtendSelection",
// Unmodified PAGE_UP and PAGE_DOWN are handled by their scroll pane, if any.
"shift PAGE_UP", "scrollUpExtendSelection",
"shift PAGE_DOWN", "scrollDownExtendSelection"
}));
}
LateBoundInputMap getScrollBarInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "positiveUnitIncrement",
"KP_RIGHT", "positiveUnitIncrement",
"DOWN", "positiveUnitIncrement",
"KP_DOWN", "positiveUnitIncrement",
"PAGE_DOWN", "positiveBlockIncrement",
"LEFT", "negativeUnitIncrement",
"KP_LEFT", "negativeUnitIncrement",
"UP", "negativeUnitIncrement",
"KP_UP", "negativeUnitIncrement",
"PAGE_UP", "negativeBlockIncrement",
"HOME", "minScroll",
"END", "maxScroll"
}));
}
LateBoundInputMap getScrollBarRightToLeftInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "negativeUnitIncrement",
"KP_RIGHT", "negativeUnitIncrement",
"LEFT", "positiveUnitIncrement",
"KP_LEFT", "positiveUnitIncrement"
}));
}
LateBoundInputMap getScrollPaneInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "unitScrollRight",
"KP_RIGHT", "unitScrollRight",
"DOWN", "unitScrollDown",
"KP_DOWN", "unitScrollDown",
"LEFT", "unitScrollLeft",
"KP_LEFT", "unitScrollLeft",
"UP", "unitScrollUp",
"KP_UP", "unitScrollUp",
"PAGE_UP", "scrollUp",
"PAGE_DOWN", "scrollDown",
"HOME", "scrollHome",
"END", "scrollEnd"
}));
}
LateBoundInputMap getSliderInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "positiveUnitIncrement",
"KP_RIGHT", "positiveUnitIncrement",
"DOWN", "negativeUnitIncrement",
"KP_DOWN", "negativeUnitIncrement",
"PAGE_DOWN", "negativeBlockIncrement",
"LEFT", "negativeUnitIncrement",
"KP_LEFT", "negativeUnitIncrement",
"UP", "positiveUnitIncrement",
"KP_UP", "positiveUnitIncrement",
"PAGE_UP", "positiveBlockIncrement",
"HOME", "minScroll",
"END", "maxScroll"
}));
}
LateBoundInputMap getSliderRightToLeftInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "negativeUnitIncrement",
"KP_RIGHT", "negativeUnitIncrement",
"LEFT", "positiveUnitIncrement",
"KP_LEFT", "positiveUnitIncrement"
}));
}
LateBoundInputMap getSpinnerInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"UP", "increment",
"KP_UP", "increment",
"DOWN", "decrement",
"KP_DOWN", "decrement"
}));
}
LateBoundInputMap getTableInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
"COPY", "copy",
"PASTE", "paste",
"CUT", "cut",
"RIGHT", "selectNextColumn",
"KP_RIGHT", "selectNextColumn",
"LEFT", "selectPreviousColumn",
"KP_LEFT", "selectPreviousColumn",
"DOWN", "selectNextRow",
"KP_DOWN", "selectNextRow",
"UP", "selectPreviousRow",
"KP_UP", "selectPreviousRow",
"shift RIGHT", "selectNextColumnExtendSelection",
"shift KP_RIGHT", "selectNextColumnExtendSelection",
"shift LEFT", "selectPreviousColumnExtendSelection",
"shift KP_LEFT", "selectPreviousColumnExtendSelection",
"shift DOWN", "selectNextRowExtendSelection",
"shift KP_DOWN", "selectNextRowExtendSelection",
"shift UP", "selectPreviousRowExtendSelection",
"shift KP_UP", "selectPreviousRowExtendSelection",
"PAGE_UP", "scrollUpChangeSelection",
"PAGE_DOWN", "scrollDownChangeSelection",
"HOME", "selectFirstColumn",
"END", "selectLastColumn",
"shift PAGE_UP", "scrollUpExtendSelection",
"shift PAGE_DOWN", "scrollDownExtendSelection",
"shift HOME", "selectFirstColumnExtendSelection",
"shift END", "selectLastColumnExtendSelection",
"TAB", "selectNextColumnCell",
"shift TAB", "selectPreviousColumnCell",
"meta A", "selectAll",
"ESCAPE", "cancel",
"ENTER", "selectNextRowCell",
"shift ENTER", "selectPreviousRowCell",
"alt TAB", "focusHeader",
"alt shift TAB", "focusHeader",
"F8", "focusHeader",
"ctrl shift UP", "selectFirstRowExtendSelection",
"ctrl shift DOWN", "selectLastRowExtendSelection",
"ctrl shift RIGHT", "selectLastColumnExtendSelection",
"ctrl shift LEFT", "selectFirstColumnExtendSelection"
}));
}
LateBoundInputMap getTableRightToLeftInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "selectPreviousColumn",
"KP_RIGHT", "selectPreviousColumn",
"LEFT", "selectNextColumn",
"KP_LEFT", "selectNextColumn",
"shift RIGHT", "selectPreviousColumnExtendSelection",
"shift KP_RIGHT", "selectPreviousColumnExtendSelection",
"shift LEFT", "selectNextColumnExtendSelection",
"shift KP_LEFT", "selectNextColumnExtendSelection",
"ctrl PAGE_UP", "scrollRightChangeSelection",
"ctrl PAGE_DOWN", "scrollLeftChangeSelection",
"ctrl shift PAGE_UP", "scrollRightExtendSelection",
"ctrl shift PAGE_DOWN", "scrollLeftExtendSelection"
}));
}
LateBoundInputMap getTreeInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"meta C", "copy",
"meta V", "paste",
"meta X", "cut",
"COPY", "copy",
"PASTE", "paste",
"CUT", "cut",
"UP", "selectPrevious",
"KP_UP", "selectPrevious",
"shift UP", "selectPreviousExtendSelection",
"shift KP_UP", "selectPreviousExtendSelection",
"shift ctrl UP", "selectPreviousExtendSelection",
"shift ctrl KP_UP", "selectPreviousExtendSelection",
"DOWN", "selectNext",
"KP_DOWN", "selectNext",
"shift DOWN", "selectNextExtendSelection",
"shift KP_DOWN", "selectNextExtendSelection",
"shift ctrl DOWN", "selectNextExtendSelection",
"shift ctrl KP_DOWN", "selectNextExtendSelection",
"RIGHT", "aquaExpandNode",
"KP_RIGHT", "aquaExpandNode",
"LEFT", "aquaCollapseNode",
"KP_LEFT", "aquaCollapseNode",
"shift RIGHT", "aquaExpandNode",
"shift KP_RIGHT", "aquaExpandNode",
"shift LEFT", "aquaCollapseNode",
"shift KP_LEFT", "aquaCollapseNode",
"ctrl LEFT", "aquaCollapseNode",
"ctrl KP_LEFT", "aquaCollapseNode",
"ctrl RIGHT", "aquaExpandNode",
"ctrl KP_RIGHT", "aquaExpandNode",
"alt RIGHT", "aquaFullyExpandNode",
"alt KP_RIGHT", "aquaFullyExpandNode",
"alt LEFT", "aquaFullyCollapseNode",
"alt KP_LEFT", "aquaFullyCollapseNode",
"meta A", "selectAll",
"RETURN", "startEditing"
}));
}
LateBoundInputMap getTreeRightToLeftInputMap() {
return new LateBoundInputMap(new SimpleBinding(new String[] {
"RIGHT", "aquaCollapseNode",
"KP_RIGHT", "aquaCollapseNode",
"LEFT", "aquaExpandNode",
"KP_LEFT", "aquaExpandNode",
"shift RIGHT", "aquaCollapseNode",
"shift KP_RIGHT", "aquaCollapseNode",
"shift LEFT", "aquaExpandNode",
"shift KP_LEFT", "aquaExpandNode",
"ctrl LEFT", "aquaExpandNode",
"ctrl KP_LEFT", "aquaExpandNode",
"ctrl RIGHT", "aquaCollapseNode",
"ctrl KP_RIGHT", "aquaCollapseNode"
}));
}
// common interface between a string array, and a dynamic provider of string arrays ;-)
interface BindingsProvider {
public String[] getBindings();
}
// wraps basic string arrays
static final class SimpleBinding implements BindingsProvider {
final String[] bindings;
public SimpleBinding(final String[] bindings) { this.bindings = bindings; }
@Override
public String[] getBindings() { return bindings; }
}
// patches all providers together at the moment the UIManager needs the real InputMap
static final class LateBoundInputMap implements LazyValue, BindingsProvider {
private final BindingsProvider[] providerList;
private String[] mergedBindings;
public LateBoundInputMap(final BindingsProvider ... providerList) {
this.providerList = providerList;
}
@Override
public Object createValue(final UIDefaults table) {
return LookAndFeel.makeInputMap(getBindings());
}
@Override
public String[] getBindings() {
if (mergedBindings != null) return mergedBindings;
final String[][] bindingsList = new String[providerList.length][];
int size = 0;
for (int i = 0; i < providerList.length; i++) {
bindingsList[i] = providerList[i].getBindings();
size += bindingsList[i].length;
}
if (bindingsList.length == 1) {
return mergedBindings = bindingsList[0];
}
final ArrayList<String> unifiedList = new ArrayList<String>(size);
Collections.addAll(unifiedList, bindingsList[0]); // System.arrayCopy() the first set
for (int i = 1; i < providerList.length; i++) {
mergeBindings(unifiedList, bindingsList[i]);
}
return mergedBindings = unifiedList.toArray(new String[unifiedList.size()]);
}
static void mergeBindings(final ArrayList<String> unifiedList, final String[] overrides) {
for (int i = 0; i < overrides.length; i+=2) {
final String key = overrides[i];
final String value = overrides[i+1];
final int keyIndex = unifiedList.indexOf(key);
if (keyIndex == -1) {
unifiedList.add(key);
unifiedList.add(value);
} else {
unifiedList.set(keyIndex, key);
unifiedList.set(keyIndex + 1, value);
}
}
}
}
void installAquaUpDownActions(final JTextComponent component) {
final ActionMap actionMap = component.getActionMap();
actionMap.put(upMultilineAction, moveUpMultilineAction);
actionMap.put(downMultilineAction, moveDownMultilineAction);
actionMap.put(pageUpMultiline, pageUpMultilineAction);
actionMap.put(pageDownMultiline, pageDownMultilineAction);
}
// extracted and adapted from DefaultEditorKit in 1.6
@SuppressWarnings("serial") // Superclass is not serializable across versions
abstract static class DeleteWordAction extends TextAction {
public DeleteWordAction(final String name) { super(name); }
@Override
public void actionPerformed(final ActionEvent e) {
if (e == null) return;
final JTextComponent target = getTextComponent(e);
if (target == null) return;
if (!target.isEditable() || !target.isEnabled()) {
UIManager.getLookAndFeel().provideErrorFeedback(target);
return;
}
try {
final int start = target.getSelectionStart();
final Element line = Utilities.getParagraphElement(target, start);
final int end = getEnd(target, line, start);
final int offs = Math.min(start, end);
final int len = Math.abs(end - start);
if (offs >= 0) {
target.getDocument().remove(offs, len);
return;
}
} catch (final BadLocationException ignore) {}
UIManager.getLookAndFeel().provideErrorFeedback(target);
}
abstract int getEnd(final JTextComponent target, final Element line, final int start) throws BadLocationException;
}
final TextAction moveUpMultilineAction = new AquaMultilineAction(upMultilineAction, DefaultEditorKit.upAction, DefaultEditorKit.beginAction);
final TextAction moveDownMultilineAction = new AquaMultilineAction(downMultilineAction, DefaultEditorKit.downAction, DefaultEditorKit.endAction);
final TextAction pageUpMultilineAction = new AquaMultilineAction(pageUpMultiline, DefaultEditorKit.pageUpAction, DefaultEditorKit.beginAction);
final TextAction pageDownMultilineAction = new AquaMultilineAction(pageDownMultiline, DefaultEditorKit.pageDownAction, DefaultEditorKit.endAction);
@SuppressWarnings("serial") // Superclass is not serializable across versions
static final class AquaMultilineAction extends TextAction {
final String targetActionName;
final String proxyActionName;
public AquaMultilineAction(final String actionName, final String targetActionName, final String proxyActionName) {
super(actionName);
this.targetActionName = targetActionName;
this.proxyActionName = proxyActionName;
}
@Override
public void actionPerformed(final ActionEvent e) {
final JTextComponent c = getTextComponent(e);
final ActionMap actionMap = c.getActionMap();
final Action targetAction = actionMap.get(targetActionName);
final int startPosition = c.getCaretPosition();
targetAction.actionPerformed(e);
if (startPosition != c.getCaretPosition()) return;
final Action proxyAction = actionMap.get(proxyActionName);
proxyAction.actionPerformed(e);
}
}
}

View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicLabelUI;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
import sun.swing.MnemonicHandler;
import sun.swing.SwingUtilities2;
public final class AquaLabelUI extends BasicLabelUI {
private static final RecyclableSingleton<AquaLabelUI> aquaLabelUI = new RecyclableSingletonFromDefaultConstructor<AquaLabelUI>(AquaLabelUI.class);
public static ComponentUI createUI(final JComponent c) {
return aquaLabelUI.get();
}
@Override
protected void installListeners(final JLabel c) {
super.installListeners(c);
AquaUtilControlSize.addSizePropertyListener(c);
}
@Override
protected void uninstallListeners(final JLabel c) {
AquaUtilControlSize.removeSizePropertyListener(c);
super.uninstallListeners(c);
}
@Override
protected void paintEnabledText(final JLabel l, final Graphics g, final String s, final int textX, final int textY) {
int mnemIndex = l.getDisplayedMnemonicIndex();
if (MnemonicHandler.isMnemonicHidden()) {
mnemIndex = -1;
}
g.setColor(l.getForeground());
SwingUtilities2.drawStringUnderlineCharAt(l, g, s, mnemIndex, textX, textY);
}
/**
* Paint clippedText at textX, textY with background.lighter() and then
* shifted down and to the right by one pixel with background.darker().
*
* @see #paint
* @see #paintEnabledText
*/
@Override
protected void paintDisabledText(final JLabel l, final Graphics g, final String s, final int textX, final int textY) {
int accChar = l.getDisplayedMnemonicIndex();
if (MnemonicHandler.isMnemonicHidden()) {
accChar = -1;
}
final Color background = l.getBackground();
// if our background is still something we set then we can use our happy background color.
if (background instanceof UIResource) {
g.setColor(getDisabledLabelColor(l));
SwingUtilities2.drawStringUnderlineCharAt(l, g, s, accChar, textX, textY);
} else {
super.paintDisabledText(l, g, s, textX, textY);
}
}
static final String DISABLED_COLOR_KEY = "Label.disabledForegroundColor";
protected Color getDisabledLabelColor(final JLabel label) {
final Color fg = label.getForeground();
final Object colorProperty = label.getClientProperty(DISABLED_COLOR_KEY);
if (colorProperty instanceof Color) {
final Color disabledColor = (Color)colorProperty;
if ((fg.getRGB() << 8) == (disabledColor.getRGB() << 8)) return disabledColor;
}
final Color newDisabledColor = new Color(fg.getRed(), fg.getGreen(), fg.getBlue(), fg.getAlpha() / 2);
label.putClientProperty(DISABLED_COLOR_KEY, newDisabledColor);
return newDisabledColor;
}
}

View file

@ -0,0 +1,217 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicListUI;
import apple.laf.JRSUIConstants.*;
/**
* A Mac L&F implementation of JList
*
* All this does is look for a ThemeBorder and invalidate it when the focus changes
*/
public final class AquaListUI extends BasicListUI {
public static ComponentUI createUI(final JComponent c) {
return new AquaListUI();
}
/**
* Creates the focus listener to repaint the focus ring
*/
@Override
protected FocusListener createFocusListener() {
return new AquaListUI.FocusHandler();
}
/**
* Creates a delegate that implements MouseInputListener.
*/
@Override
protected MouseInputListener createMouseInputListener() {
return new AquaListUI.MouseInputHandler();
}
@Override
protected void installKeyboardActions() {
super.installKeyboardActions();
list.getActionMap().put("aquaHome", new AquaHomeEndAction(true));
list.getActionMap().put("aquaEnd", new AquaHomeEndAction(false));
}
@SuppressWarnings("serial") // Superclass is not serializable across versions
static final class AquaHomeEndAction extends AbstractAction {
private boolean fHomeAction = false;
protected AquaHomeEndAction(final boolean isHomeAction) {
fHomeAction = isHomeAction;
}
/**
* For a Home action, scrolls to the top. Otherwise, scroll to the end.
*/
@Override
public void actionPerformed(final ActionEvent e) {
final JList<?> list = (JList<?>)e.getSource();
if (fHomeAction) {
list.ensureIndexIsVisible(0);
} else {
final int size = list.getModel().getSize();
list.ensureIndexIsVisible(size - 1);
}
}
}
/**
* This inner class is marked &quot;public&quot; due to a compiler bug. This class should be treated as a
* &quot;protected&quot; inner class. Instantiate it only within subclasses of BasicListUI.
*/
final class FocusHandler extends BasicListUI.FocusHandler {
@Override
public void focusGained(final FocusEvent e) {
super.focusGained(e);
AquaBorder.repaintBorder(getComponent());
}
@Override
public void focusLost(final FocusEvent e) {
super.focusLost(e);
AquaBorder.repaintBorder(getComponent());
}
}
@Override
protected PropertyChangeListener createPropertyChangeListener() {
return new AquaPropertyChangeHandler();
}
final class AquaPropertyChangeHandler extends PropertyChangeHandler {
@Override
public void propertyChange(final PropertyChangeEvent e) {
final String prop = e.getPropertyName();
if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(prop)) {
AquaBorder.repaintBorder(getComponent());
AquaFocusHandler.swapSelectionColors("List", getComponent(), e.getNewValue());
} else {
super.propertyChange(e);
}
}
}
// TODO: Using default handler for now, need to handle cmd-key
// Replace the mouse event with one that returns the cmd-key state when asked
// for the control-key state, which super assumes is what everyone does to discontiguously extend selections
final class MouseInputHandler extends BasicListUI.MouseInputHandler {
/*public void mousePressed(final MouseEvent e) {
super.mousePressed(new SelectionMouseEvent(e));
}
public void mouseDragged(final MouseEvent e) {
super.mouseDragged(new SelectionMouseEvent(e));
}*/
}
JList<Object> getComponent() {
return list;
}
// this is used for blinking combobox popup selections when they are selected
protected void repaintCell(final Object value, final int selectedIndex, final boolean selected) {
final Rectangle rowBounds = getCellBounds(list, selectedIndex, selectedIndex);
if (rowBounds == null) return;
final ListCellRenderer<Object> renderer = list.getCellRenderer();
if (renderer == null) return;
final Component rendererComponent = renderer.getListCellRendererComponent(list, value, selectedIndex, selected, true);
if (rendererComponent == null) return;
final AquaComboBoxRenderer aquaRenderer = renderer instanceof AquaComboBoxRenderer ? (AquaComboBoxRenderer)renderer : null;
if (aquaRenderer != null) aquaRenderer.setDrawCheckedItem(false);
rendererPane.paintComponent(list.getGraphics().create(), rendererComponent, list, rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height, true);
if (aquaRenderer != null) aquaRenderer.setDrawCheckedItem(true);
}
/*
Insert note on JIDESoft naughtiness
*/
public static Border getSourceListBackgroundPainter() {
final AquaBorder border = new ComponentPainter();
border.painter.state.set(Widget.GRADIENT);
border.painter.state.set(Variant.GRADIENT_SIDE_BAR);
return border;
}
public static Border getSourceListSelectionBackgroundPainter() {
final AquaBorder border = new ComponentPainter();
border.painter.state.set(Widget.GRADIENT);
border.painter.state.set(Variant.GRADIENT_SIDE_BAR_SELECTION);
return border;
}
public static Border getSourceListFocusedSelectionBackgroundPainter() {
final AquaBorder border = new ComponentPainter();
border.painter.state.set(Widget.GRADIENT);
border.painter.state.set(Variant.GRADIENT_SIDE_BAR_FOCUSED_SELECTION);
return border;
}
public static Border getListEvenBackgroundPainter() {
final AquaBorder border = new ComponentPainter();
border.painter.state.set(Widget.GRADIENT);
border.painter.state.set(Variant.GRADIENT_LIST_BACKGROUND_EVEN);
return border;
}
public static Border getListOddBackgroundPainter() {
final AquaBorder border = new ComponentPainter();
border.painter.state.set(Widget.GRADIENT);
border.painter.state.set(Variant.GRADIENT_LIST_BACKGROUND_ODD);
return border;
}
static final class ComponentPainter extends AquaBorder.Default {
@Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int w, final int h) {
final JComponent jc = c instanceof JComponent ? (JComponent)c : null;
if (jc != null && !AquaFocusHandler.isActive(jc)) {
painter.state.set(State.INACTIVE);
} else {
painter.state.set(State.ACTIVE);
}
super.paintBorder(c, g, x, y, w, h);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Insets;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
import sun.swing.SwingUtilities2;
/**
* The class represents the border of a {@code JMenuBar}.
*/
public final class AquaMenuBarBorder implements Border, UIResource {
@Override
public void paintBorder(final Component c, final Graphics g, final int x,
final int y, final int width, final int height) {
g.setColor(Color.gray);
SwingUtilities2.drawHLine(g, x, x + width - 1, y + height - 1);
}
@Override
public Insets getBorderInsets(final Component c) {
return new Insets(0, 0, 1, 0);
}
@Override
public boolean isBorderOpaque() {
return false;
}
}

View file

@ -0,0 +1,156 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.MenuBar;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenuBar;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuBarUI;
import sun.lwawt.macosx.LWCToolkit;
// MenuBar implementation for Mac L&F
@SuppressWarnings("restricted")
public final class AquaMenuBarUI extends BasicMenuBarUI implements ScreenMenuBarProvider {
static {
System.loadLibrary("osxui");
}
// Utilities
@Override
public void uninstallUI(final JComponent c) {
if (fScreenMenuBar != null) {
final JFrame frame = (JFrame)(c.getTopLevelAncestor());
if (frame != null && frame.getMenuBar() == fScreenMenuBar) {
frame.setMenuBar((MenuBar)null);
}
fScreenMenuBar = null;
}
super.uninstallUI(c);
}
// Create PLAF
public static ComponentUI createUI(final JComponent c) {
return new AquaMenuBarUI();
}
// [3320390] -- If the screen menu bar is in use, don't register keyboard actions that
// show the menus when F10 is pressed.
@Override
protected void installKeyboardActions() {
if (!useScreenMenuBar) {
super.installKeyboardActions();
}
}
@Override
protected void uninstallKeyboardActions() {
if (!useScreenMenuBar) {
super.uninstallKeyboardActions();
}
}
// Paint Methods
@Override
public void paint(final Graphics g, final JComponent c) {
AquaMenuPainter.instance().paintMenuBarBackground(g, c.getWidth(), c.getHeight(), c);
}
@Override
public Dimension getPreferredSize(final JComponent c) {
if (isScreenMenuBar((JMenuBar)c)) {
if (setScreenMenuBar((JFrame)(c.getTopLevelAncestor()))) {
return new Dimension(0, 0);
}
}
return null;
}
void clearScreenMenuBar(final JFrame frame) {
if (useScreenMenuBar) {
frame.setMenuBar(null);
}
}
boolean setScreenMenuBar(final JFrame frame) {
if (useScreenMenuBar) {
try {
getScreenMenuBar();
} catch(final Throwable t) {
return false;
}
frame.setMenuBar(fScreenMenuBar);
}
return true;
}
@Override
public ScreenMenuBar getScreenMenuBar() {
// Lazy init of member variables means we should use a synchronized block.
synchronized(this) {
if (fScreenMenuBar == null) fScreenMenuBar = new ScreenMenuBar(this.menuBar);
}
return fScreenMenuBar;
}
// JMenuBars are in frame unless we're using ScreenMenuBars *and* it's
// been set by JFrame.setJMenuBar
// unless the JFrame has a normal java.awt.MenuBar (it's possible!)
// Other JMenuBars appear where the programmer puts them - top of window or elsewhere
public static final boolean isScreenMenuBar(final JMenuBar c) {
final javax.swing.plaf.ComponentUI ui = c.getUI();
if (ui instanceof AquaMenuBarUI) {
if (!((AquaMenuBarUI)ui).useScreenMenuBar) return false;
final Component parent = c.getTopLevelAncestor();
if (parent instanceof JFrame) {
final MenuBar mb = ((JFrame)parent).getMenuBar();
final boolean thisIsTheJMenuBar = (((JFrame)parent).getJMenuBar() == c);
if (mb == null) return thisIsTheJMenuBar;
return (mb instanceof ScreenMenuBar && thisIsTheJMenuBar);
}
}
return false;
}
ScreenMenuBar fScreenMenuBar;
boolean useScreenMenuBar = getScreenMenuBarProperty();
public static boolean getScreenMenuBarProperty() {
// Do not allow AWT to set the screen menu bar if it's embedded in another UI toolkit
if (LWCToolkit.isEmbedded()) return false;
return Boolean.getBoolean(AquaLookAndFeel.sPropertyPrefix + "useScreenMenuBar");
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.plaf.UIResource;
public final class AquaMenuBorder implements Border, UIResource {
public AquaMenuBorder() { }
/**
* Paints the border for the specified component with the specified
* position and size.
* @param c the component for which this border is being painted
* @param g the paint graphics
* @param x the x position of the painted border
* @param y the y position of the painted border
* @param width the width of the painted border
* @param height the height of the painted border
*/
@Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
// for now we don't paint a border. We let the button paint it since there
// needs to be a strict ordering for aqua components.
//paintButton(c, g, x, y, width, height);
//if (c instanceof JPopupMenu) {
//g.setColor(Color.red);
//g.drawRect(x,y, width-1, height-1);
//}
}
/**
* Returns whether or not the border is opaque. If the border
* is opaque, it is responsible for filling in it's own
* background when painting.
*/
@Override
public boolean isBorderOpaque() {
return false;
}
protected static Insets getItemInsets() {
return new Insets(1, 5, 1, 5);
}
protected static Insets getEmptyInsets() {
return new Insets(0, 0, 0, 0);
}
protected static Insets getPopupInsets() {
return new Insets(4, 0, 4, 0);
}
/**
* Returns the insets of the border.
* @param c the component for which this border insets value applies
*/
@Override
public Insets getBorderInsets(final Component c) {
if (!(c instanceof JPopupMenu)) {
return getItemInsets();
}
// for more info on this issue, see AquaComboBoxPopup.updateContents()
final JPopupMenu menu = (JPopupMenu)c;
final int nChildren = menu.getComponentCount();
if (nChildren > 0) {
final Component firstChild = menu.getComponent(0);
if (firstChild instanceof Box.Filler) return getEmptyInsets();
if (firstChild instanceof JScrollPane) return getEmptyInsets();
}
// just need top and bottom, and not right and left.
// but only for non-list popups.
return getPopupInsets();
}
}

View file

@ -0,0 +1,208 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicMenuItemUI;
import apple.laf.JRSUIConstants.Size;
// TODO: no screen menu bar for now
public final class AquaMenuItemUI extends BasicMenuItemUI implements AquaMenuPainter.Client/*, ScreenMenuItemUI*/ {
static final int kPlain = 0, kCheckBox = 1, kRadioButton = 2;
static final String[] sPropertyPrefixes = { "MenuItem", "CheckBoxMenuItem", "RadioButtonMenuItem" };
boolean fIsScreenMenuItem = false;
boolean fIsIndeterminate = false;
int fType;
AquaMenuItemUI(final int type) {
super();
fType = type;
}
public static ComponentUI createUI(final JComponent c) {
int type = kPlain;
if (c instanceof JCheckBoxMenuItem) type = kCheckBox;
if (c instanceof JRadioButtonMenuItem) type = kRadioButton;
return new AquaMenuItemUI(type);
}
// The only real difference between the three is which property prefix it returns
// and therefore which icons!
@Override
protected String getPropertyPrefix() {
return sPropertyPrefixes[fType];
}
@Override
protected void installListeners() {
super.installListeners();
IndeterminateListener.install(menuItem);
}
@Override
protected void uninstallListeners() {
IndeterminateListener.uninstall(menuItem);
super.uninstallListeners();
}
public void updateListenersForScreenMenuItem() {
setIsScreenMenu(true);
}
// Users can dynamically change the kind of menu we're on by calling JPopupMenu.setInvoker
// so we need to be prepared to put the listeners back on
protected void setIsScreenMenu(final boolean isScreenMenuItem) {
if (fIsScreenMenuItem != isScreenMenuItem) {
fIsScreenMenuItem = isScreenMenuItem;
if (fIsScreenMenuItem) removeListeners();
else addListeners();
}
}
protected void removeListeners() {
menuItem.removeMouseListener(mouseInputListener);
menuItem.removeMouseMotionListener(mouseInputListener);
menuItem.removeMenuDragMouseListener(menuDragMouseListener);
}
protected void addListeners() {
menuItem.addMouseListener(mouseInputListener);
menuItem.addMouseMotionListener(mouseInputListener);
menuItem.addMenuDragMouseListener(menuDragMouseListener);
}
@Override
protected void paintMenuItem(final Graphics g, final JComponent c, final Icon localCheckIcon, final Icon localArrowIcon, final Color background, final Color foreground, final int localDefaultTextIconGap) {
AquaMenuPainter.instance().paintMenuItem(this, g, c, localCheckIcon, localArrowIcon, background, foreground, disabledForeground, selectionForeground, localDefaultTextIconGap, acceleratorFont);
}
@Override
protected Dimension getPreferredMenuItemSize(final JComponent c, final Icon localCheckIcon, final Icon localArrowIcon, final int localDefaultTextIconGap) {
return AquaMenuPainter.instance().getPreferredMenuItemSize(c, localCheckIcon, localArrowIcon, localDefaultTextIconGap, acceleratorFont);
}
@Override
public void update(final Graphics g, final JComponent c) {
if (c.isOpaque()) {
// sja fix ((PenGraphics)g).alphaClearRect(0,0,c.getWidth(),c.getHeight());
final Color oldColor = g.getColor();
g.setColor(c.getBackground());
g.fillRect(0, 0, c.getWidth(), c.getHeight());
g.setColor(oldColor);
}
paint(g, c);
}
@Override
public void paintBackground(final Graphics g, final JComponent c, final int menuWidth, final int menuHeight) {
if ((c.getParent() instanceof JMenuBar)) return;
final Color oldColor = g.getColor();
g.setColor(c.getBackground());
g.fillRect(0, 0, menuWidth, menuHeight);
if (((JMenuItem)c).isBorderPainted()) {
if (((JMenuItem)c).getModel().isArmed()) {
AquaMenuPainter.instance().paintSelectedMenuItemBackground(g, menuWidth, menuHeight);
}
//getTheme().drawMenuItem(c, g, 0, 0, menuWidth, menuHeight);
} else {
// If selected, use black (see AquaLookAndFeel "Menu.selectionBackground")
if (((JMenuItem)c).getModel().isArmed()) {
final Color holdc = g.getColor();
g.setColor(Color.black);
g.fillRect(0, 0, menuWidth, menuHeight);
g.setColor(holdc);
} else {
g.setColor(Color.green);
g.fillRect(0, 0, menuWidth, menuHeight);
//super.paintBackground(g,c,menuWidth, menuHeight); //getTheme().drawMenuBackground((Component)c, g, (short)1, 0, 0, menuWidth, menuHeight);
}
}
g.setColor(oldColor);
}
@Override
protected void doClick(final MenuSelectionManager msm) {
final Dimension size = menuItem.getSize();
AquaUtils.blinkMenu(new AquaUtils.Selectable() {
public void paintSelected(final boolean selected) {
menuItem.setArmed(selected);
menuItem.paintImmediately(0, 0, size.width, size.height);
}
});
super.doClick(msm);
}
static final IndeterminateListener INDETERMINATE_LISTENER = new IndeterminateListener();
static final class IndeterminateListener implements PropertyChangeListener {
static final String CLIENT_PROPERTY_KEY = "JMenuItem.selectedState";
static void install(final JMenuItem menuItem) {
menuItem.addPropertyChangeListener(CLIENT_PROPERTY_KEY, INDETERMINATE_LISTENER);
apply(menuItem, menuItem.getClientProperty(CLIENT_PROPERTY_KEY));
}
static void uninstall(final JMenuItem menuItem) {
menuItem.removePropertyChangeListener(CLIENT_PROPERTY_KEY, INDETERMINATE_LISTENER);
}
@Override
public void propertyChange(final PropertyChangeEvent evt) {
final String key = evt.getPropertyName();
if (!CLIENT_PROPERTY_KEY.equalsIgnoreCase(key)) return;
final Object source = evt.getSource();
if (!(source instanceof JMenuItem)) return;
final JMenuItem c = (JMenuItem)source;
apply(c, evt.getNewValue());
}
static void apply(final JMenuItem menuItem, final Object value) {
final ButtonUI ui = menuItem.getUI();
if (!(ui instanceof AquaMenuItemUI)) return;
final AquaMenuItemUI aquaUI = (AquaMenuItemUI)ui;
if (aquaUI.fIsIndeterminate = "indeterminate".equals(value)) {
aquaUI.checkIcon = UIManager.getIcon(aquaUI.getPropertyPrefix() + ".dashIcon");
} else {
aquaUI.checkIcon = UIManager.getIcon(aquaUI.getPropertyPrefix() + ".checkIcon");
}
}
public static boolean isIndeterminate(final JMenuItem menuItem) {
return "indeterminate".equals(menuItem.getClientProperty(CLIENT_PROPERTY_KEY));
}
}
}

View file

@ -0,0 +1,564 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import javax.swing.ButtonModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicHTML;
import javax.swing.text.View;
import apple.laf.JRSUIConstants.State;
import apple.laf.JRSUIConstants.Widget;
import com.apple.laf.AquaIcon.InvertableIcon;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
import sun.swing.MnemonicHandler;
import sun.swing.SwingUtilities2;
/**
* AquaMenuPainter, implements paintMenuItem to avoid code duplication
*
* BasicMenuItemUI didn't factor out the various parts of the Menu, and
* we subclass it and its subclasses BasicMenuUI
* Our classes need an implementation of paintMenuItem
* that allows them to paint their own backgrounds
*/
public final class AquaMenuPainter {
// Glyph statics:
// ASCII character codes
static final byte
kShiftGlyph = 0x05,
kOptionGlyph = 0x07,
kControlGlyph = 0x06,
kPencilGlyph = 0x0F,
kCommandMark = 0x11;
// Unicode character codes
static final char
kUBlackDiamond = 0x25C6,
kUCheckMark = 0x2713,
kUControlGlyph = 0x2303,
kUOptionGlyph = 0x2325,
kUEnterGlyph = 0x2324,
kUCommandGlyph = 0x2318,
kULeftDeleteGlyph = 0x232B,
kURightDeleteGlyph = 0x2326,
kUShiftGlyph = 0x21E7,
kUCapsLockGlyph = 0x21EA;
static final int ALT_GRAPH_MASK = 1 << 5; // New to Java2
@SuppressWarnings("deprecation")
static final int sUnsupportedModifiersMask =
~(InputEvent.CTRL_MASK | InputEvent.ALT_MASK | InputEvent.SHIFT_MASK
| InputEvent.META_MASK | ALT_GRAPH_MASK);
interface Client {
public void paintBackground(Graphics g, JComponent c, int menuWidth, int menuHeight);
}
// Return a string with the proper modifier glyphs
static String getKeyModifiersText(final int modifiers, final boolean isLeftToRight) {
return getKeyModifiersUnicode(modifiers, isLeftToRight);
}
// Return a string with the proper modifier glyphs
@SuppressWarnings("deprecation")
private static String getKeyModifiersUnicode(final int modifiers, final boolean isLeftToRight) {
final StringBuilder buf = new StringBuilder(2);
// Order (from StandardMenuDef.c): control, option(alt), shift, cmd
// reverse for right-to-left
//$ check for substitute key glyphs for localization
if (isLeftToRight) {
if ((modifiers & InputEvent.CTRL_MASK) != 0) {
buf.append(kUControlGlyph);
}
if ((modifiers & (InputEvent.ALT_MASK | ALT_GRAPH_MASK)) != 0) {
buf.append(kUOptionGlyph);
}
if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
buf.append(kUShiftGlyph);
}
if ((modifiers & InputEvent.META_MASK) != 0) {
buf.append(kUCommandGlyph);
}
} else {
if ((modifiers & InputEvent.META_MASK) != 0) {
buf.append(kUCommandGlyph);
}
if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
buf.append(kUShiftGlyph);
}
if ((modifiers & (InputEvent.ALT_MASK | ALT_GRAPH_MASK)) != 0) {
buf.append(kUOptionGlyph);
}
if ((modifiers & InputEvent.CTRL_MASK) != 0) {
buf.append(kUControlGlyph);
}
}
return buf.toString();
}
private static final RecyclableSingleton<AquaMenuPainter> sPainter = new RecyclableSingletonFromDefaultConstructor<AquaMenuPainter>(AquaMenuPainter.class);
static AquaMenuPainter instance() {
return sPainter.get();
}
static final int defaultMenuItemGap = 2;
static final int kAcceleratorArrowSpace = 16; // Accel space doesn't overlap arrow space, even though items can't have both
static final class RecyclableBorder extends RecyclableSingleton<Border> {
final String borderName;
RecyclableBorder(final String borderName) { this.borderName = borderName; }
@Override
protected Border getInstance() { return UIManager.getBorder(borderName); }
}
private static final RecyclableBorder menuBarPainter = new RecyclableBorder("MenuBar.backgroundPainter");
private static final RecyclableBorder selectedMenuBarItemPainter = new RecyclableBorder("MenuBar.selectedBackgroundPainter");
private static final RecyclableBorder selectedMenuItemPainter = new RecyclableBorder("MenuItem.selectedBackgroundPainter");
public void paintMenuBarBackground(final Graphics g, final int width, final int height, final JComponent c) {
g.setColor(c == null ? Color.white : c.getBackground());
g.fillRect(0, 0, width, height);
menuBarPainter.get().paintBorder(null, g, 0, 0, width, height);
}
public void paintSelectedMenuTitleBackground(final Graphics g, final int width, final int height) {
selectedMenuBarItemPainter.get().paintBorder(null, g, -1, 0, width + 2, height);
}
public void paintSelectedMenuItemBackground(final Graphics g, final int width, final int height) {
selectedMenuItemPainter.get().paintBorder(null, g, 0, 0, width, height);
}
protected void paintMenuItem(final Client client, final Graphics g, final JComponent c, final Icon checkIcon, final Icon arrowIcon, final Color background, final Color foreground, final Color disabledForeground, final Color selectionForeground, final int defaultTextIconGap, final Font acceleratorFont) {
final JMenuItem b = (JMenuItem)c;
final ButtonModel model = b.getModel();
// Dimension size = b.getSize();
final int menuWidth = b.getWidth();
final int menuHeight = b.getHeight();
final Insets i = c.getInsets();
Rectangle viewRect = new Rectangle(0, 0, menuWidth, menuHeight);
viewRect.x += i.left;
viewRect.y += i.top;
viewRect.width -= (i.right + viewRect.x);
viewRect.height -= (i.bottom + viewRect.y);
final Font holdf = g.getFont();
final Color holdc = g.getColor();
final Font f = c.getFont();
g.setFont(f);
final FontMetrics fm = g.getFontMetrics(f);
final FontMetrics fmAccel = g.getFontMetrics(acceleratorFont);
// Paint background (doesn't touch the Graphics object's color)
if (c.isOpaque()) {
client.paintBackground(g, c, menuWidth, menuHeight);
}
// get Accelerator text
final KeyStroke accelerator = b.getAccelerator();
String modifiersString = "", keyString = "";
final boolean leftToRight = AquaUtils.isLeftToRight(c);
if (accelerator != null) {
final int modifiers = accelerator.getModifiers();
if (modifiers > 0) {
modifiersString = getKeyModifiersText(modifiers, leftToRight);
}
final int keyCode = accelerator.getKeyCode();
if (keyCode != 0) {
keyString = KeyEvent.getKeyText(keyCode);
} else {
keyString += accelerator.getKeyChar();
}
}
Rectangle iconRect = new Rectangle();
Rectangle textRect = new Rectangle();
Rectangle acceleratorRect = new Rectangle();
Rectangle checkIconRect = new Rectangle();
Rectangle arrowIconRect = new Rectangle();
// layout the text and icon
final String text = layoutMenuItem(b, fm, b.getText(), fmAccel, keyString, modifiersString, b.getIcon(), checkIcon, arrowIcon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, acceleratorRect, checkIconRect, arrowIconRect, b.getText() == null ? 0 : defaultTextIconGap, defaultTextIconGap);
// if this is in a AquaScreenMenuBar that's attached to a DialogPeer
// the native menu will be disabled, though the awt Menu won't know about it
// so the JPopupMenu will not have visibility set and the items should draw disabled
// If it's not on a JPopupMenu then it should just use the model's enable state
final Container parent = b.getParent();
final boolean parentIsMenuBar = parent instanceof JMenuBar;
Container ancestor = parent;
while (ancestor != null && !(ancestor instanceof JPopupMenu)) ancestor = ancestor.getParent();
boolean isEnabled = model.isEnabled() && (ancestor == null || ancestor.isVisible());
// Set the accel/normal text color
boolean isSelected = false;
if (!isEnabled) {
// *** paint the text disabled
g.setColor(disabledForeground);
} else {
// *** paint the text normally
if (model.isArmed() || (c instanceof JMenu && model.isSelected())) {
g.setColor(selectionForeground);
isSelected = true;
} else {
g.setColor(parentIsMenuBar ? parent.getForeground() : b.getForeground()); // Which is either MenuItem.foreground or the user's choice
}
}
// We want to paint the icon after the text color is set since some icon painting depends on the correct
// graphics color being set
// See <rdar://problem/3792383> Menu icons missing in Java2D's Lines.Joins demo
// Paint the Icon
if (b.getIcon() != null) {
paintIcon(g, b, iconRect, isEnabled);
}
// Paint the Check using the current text color
if (checkIcon != null) {
paintCheck(g, b, checkIcon, checkIconRect);
}
// Draw the accelerator first in case the HTML renderer changes the color
if (keyString != null && !keyString.isEmpty()) {
final int yAccel = acceleratorRect.y + fm.getAscent();
if (modifiersString.isEmpty()) {
// just draw the keyString
SwingUtilities2.drawString(c, g, keyString, acceleratorRect.x, yAccel);
} else {
final int modifiers = accelerator.getModifiers();
int underlinedChar = 0;
if ((modifiers & ALT_GRAPH_MASK) > 0) underlinedChar = kUOptionGlyph; // This is a Java2 thing, we won't be getting kOptionGlyph
// The keyStrings should all line up, so always adjust the width by the same amount
// (if they're multi-char, they won't line up but at least they won't be cut off)
final int emWidth = Math.max(fm.charWidth('M'), SwingUtilities.computeStringWidth(fm, keyString));
if (leftToRight) {
g.setFont(acceleratorFont);
drawString(g, c, modifiersString, underlinedChar, acceleratorRect.x, yAccel, isEnabled, isSelected);
g.setFont(f);
SwingUtilities2.drawString(c, g, keyString, acceleratorRect.x + acceleratorRect.width - emWidth, yAccel);
} else {
final int xAccel = acceleratorRect.x + emWidth;
g.setFont(acceleratorFont);
drawString(g, c, modifiersString, underlinedChar, xAccel, yAccel, isEnabled, isSelected);
g.setFont(f);
SwingUtilities2.drawString(c, g, keyString, xAccel - fm.stringWidth(keyString), yAccel);
}
}
}
// Draw the Text
if (text != null && !text.isEmpty()) {
final View v = (View)c.getClientProperty(BasicHTML.propertyKey);
if (v != null) {
v.paint(g, textRect);
} else {
final int mnemonic = (MnemonicHandler.isMnemonicHidden() ? -1 : model.getMnemonic());
drawString(g, c, text, mnemonic, textRect.x, textRect.y + fm.getAscent(), isEnabled, isSelected);
}
}
// Paint the Arrow
if (arrowIcon != null) {
paintArrow(g, b, model, arrowIcon, arrowIconRect);
}
g.setColor(holdc);
g.setFont(holdf);
}
// All this had to be copied from BasicMenuItemUI, just to get the right keyModifiersText fn
// and a few Mac tweaks
protected Dimension getPreferredMenuItemSize(final JComponent c, final Icon checkIcon, final Icon arrowIcon, final int defaultTextIconGap, final Font acceleratorFont) {
final JMenuItem b = (JMenuItem)c;
final Icon icon = b.getIcon();
final String text = b.getText();
final KeyStroke accelerator = b.getAccelerator();
String keyString = "", modifiersString = "";
if (accelerator != null) {
final int modifiers = accelerator.getModifiers();
if (modifiers > 0) {
modifiersString = getKeyModifiersText(modifiers, true); // doesn't matter, this is just for metrics
}
final int keyCode = accelerator.getKeyCode();
if (keyCode != 0) {
keyString = KeyEvent.getKeyText(keyCode);
} else {
keyString += accelerator.getKeyChar();
}
}
final Font font = b.getFont();
final FontMetrics fm = b.getFontMetrics(font);
final FontMetrics fmAccel = b.getFontMetrics(acceleratorFont);
Rectangle iconRect = new Rectangle();
Rectangle textRect = new Rectangle();
Rectangle acceleratorRect = new Rectangle();
Rectangle checkIconRect = new Rectangle();
Rectangle arrowIconRect = new Rectangle();
Rectangle viewRect = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);
layoutMenuItem(b, fm, text, fmAccel, keyString, modifiersString, icon, checkIcon, arrowIcon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, acceleratorRect, checkIconRect, arrowIconRect, text == null ? 0 : defaultTextIconGap, defaultTextIconGap);
// find the union of the icon and text rects
Rectangle r = new Rectangle();
r.setBounds(textRect);
r = SwingUtilities.computeUnion(iconRect.x, iconRect.y, iconRect.width, iconRect.height, r);
// r = iconRect.union(textRect);
// Add in the accelerator
boolean acceleratorTextIsEmpty = (keyString == null) || keyString.isEmpty();
if (!acceleratorTextIsEmpty) {
r.width += acceleratorRect.width;
}
if (!isTopLevelMenu(b)) {
// Add in the checkIcon
r.width += checkIconRect.width;
r.width += defaultTextIconGap;
// Add in the arrowIcon space
r.width += defaultTextIconGap;
r.width += arrowIconRect.width;
}
final Insets insets = b.getInsets();
if (insets != null) {
r.width += insets.left + insets.right;
r.height += insets.top + insets.bottom;
}
// Tweak for Mac
r.width += 4 + defaultTextIconGap;
r.height = Math.max(r.height, 18);
return r.getSize();
}
protected void paintCheck(final Graphics g, final JMenuItem item, Icon checkIcon, Rectangle checkIconRect) {
if (isTopLevelMenu(item) || !item.isSelected()) return;
if (item.isArmed() && checkIcon instanceof InvertableIcon) {
((InvertableIcon)checkIcon).getInvertedIcon().paintIcon(item, g, checkIconRect.x, checkIconRect.y);
} else {
checkIcon.paintIcon(item, g, checkIconRect.x, checkIconRect.y);
}
}
protected void paintIcon(final Graphics g, final JMenuItem c, final Rectangle localIconRect, boolean isEnabled) {
final ButtonModel model = c.getModel();
Icon icon;
if (!isEnabled) {
icon = c.getDisabledIcon();
} else if (model.isPressed() && model.isArmed()) {
icon = c.getPressedIcon();
if (icon == null) {
// Use default icon
icon = c.getIcon();
}
} else {
icon = c.getIcon();
}
if (icon != null) icon.paintIcon(c, g, localIconRect.x, localIconRect.y);
}
protected void paintArrow(Graphics g, JMenuItem c, ButtonModel model, Icon arrowIcon, Rectangle arrowIconRect) {
if (isTopLevelMenu(c)) return;
if (c instanceof JMenu && (model.isArmed() || model.isSelected()) && arrowIcon instanceof InvertableIcon) {
((InvertableIcon)arrowIcon).getInvertedIcon().paintIcon(c, g, arrowIconRect.x, arrowIconRect.y);
} else {
arrowIcon.paintIcon(c, g, arrowIconRect.x, arrowIconRect.y);
}
}
/** Draw a string with the graphics g at location (x,y) just like g.drawString() would.
* The first occurrence of underlineChar in text will be underlined. The matching is
* not case sensitive.
*/
public void drawString(final Graphics g, final JComponent c, final String text, final int underlinedChar, final int x, final int y, final boolean isEnabled, final boolean isSelected) {
char lc, uc;
int index = -1, lci, uci;
if (underlinedChar != '\0') {
uc = Character.toUpperCase((char)underlinedChar);
lc = Character.toLowerCase((char)underlinedChar);
uci = text.indexOf(uc);
lci = text.indexOf(lc);
if (uci == -1) index = lci;
else if (lci == -1) index = uci;
else index = (lci < uci) ? lci : uci;
}
SwingUtilities2.drawStringUnderlineCharAt(c, g, text, index, x, y);
}
/*
* Returns false if the component is a JMenu and it is a top
* level menu (on the menubar).
*/
private static boolean isTopLevelMenu(final JMenuItem menuItem) {
return (menuItem instanceof JMenu) && (((JMenu)menuItem).isTopLevelMenu());
}
private String layoutMenuItem(final JMenuItem menuItem, final FontMetrics fm, final String text, final FontMetrics fmAccel, String keyString, final String modifiersString, final Icon icon, final Icon checkIcon, final Icon arrowIcon, final int verticalAlignment, final int horizontalAlignment, final int verticalTextPosition, final int horizontalTextPosition, final Rectangle viewR, final Rectangle iconR, final Rectangle textR, final Rectangle acceleratorR, final Rectangle checkIconR, final Rectangle arrowIconR, final int textIconGap, final int menuItemGap) {
// Force it to do "LEFT", then flip the rects if we're right-to-left
SwingUtilities.layoutCompoundLabel(menuItem, fm, text, icon, verticalAlignment, SwingConstants.LEFT, verticalTextPosition, horizontalTextPosition, viewR, iconR, textR, textIconGap);
final boolean acceleratorTextIsEmpty = (keyString == null) || keyString.isEmpty();
if (acceleratorTextIsEmpty) {
acceleratorR.width = acceleratorR.height = 0;
keyString = "";
} else {
// Accel space doesn't overlap arrow space, even though items can't have both
acceleratorR.width = SwingUtilities.computeStringWidth(fmAccel, modifiersString);
// The keyStrings should all line up, so always adjust the width by the same amount
// (if they're multi-char, they won't line up but at least they won't be cut off)
acceleratorR.width += Math.max(fm.charWidth('M'), SwingUtilities.computeStringWidth(fm, keyString));
acceleratorR.height = fmAccel.getHeight();
}
/* Initialize the checkIcon bounds rectangle checkIconR.
*/
final boolean isTopLevelMenu = isTopLevelMenu(menuItem);
if (!isTopLevelMenu) {
if (checkIcon != null) {
checkIconR.width = checkIcon.getIconWidth();
checkIconR.height = checkIcon.getIconHeight();
} else {
checkIconR.width = checkIconR.height = 16;
}
/* Initialize the arrowIcon bounds rectangle arrowIconR.
*/
if (arrowIcon != null) {
arrowIconR.width = arrowIcon.getIconWidth();
arrowIconR.height = arrowIcon.getIconHeight();
} else {
arrowIconR.width = arrowIconR.height = 16;
}
textR.x += 12;
iconR.x += 12;
}
final Rectangle labelR = iconR.union(textR);
// Position the Accelerator text rect
// Menu shortcut text *ought* to have the letters left-justified - look at a menu with an "M" in it
acceleratorR.x += (viewR.width - arrowIconR.width - acceleratorR.width);
acceleratorR.y = viewR.y + (viewR.height / 2) - (acceleratorR.height / 2);
if (!isTopLevelMenu) {
// if ( GetSysDirection() < 0 ) hierRect.right = hierRect.left + w + 4;
// else hierRect.left = hierRect.right - w - 4;
arrowIconR.x = Math.abs((viewR.width - arrowIconR.width) + 1);
arrowIconR.y = Math.abs(viewR.y + (labelR.height / 2) - (arrowIconR.height / 2) + 1);
checkIconR.y = Math.abs(viewR.y + (labelR.height / 2) - (checkIconR.height / 2));
checkIconR.x = 5;
textR.width += 8;
}
/*System.out.println("Layout: " +horizontalAlignment+ " v=" +viewR+" c="+checkIconR+" i="+
iconR+" t="+textR+" acc="+acceleratorR+" a="+arrowIconR);*/
if (!AquaUtils.isLeftToRight(menuItem)) {
// Flip the rectangles so that instead of [check][icon][text][accel/arrow] it's [accel/arrow][text][icon][check]
final int w = viewR.width;
checkIconR.x = w - (checkIconR.x + checkIconR.width);
iconR.x = w - (iconR.x + iconR.width);
textR.x = w - (textR.x + textR.width);
acceleratorR.x = w - (acceleratorR.x + acceleratorR.width);
arrowIconR.x = w - (arrowIconR.x + arrowIconR.width);
}
textR.x += menuItemGap;
iconR.x += menuItemGap;
return text;
}
public static Border getMenuBarPainter() {
final AquaBorder border = new AquaBorder.Default();
border.painter.state.set(Widget.MENU_BAR);
return border;
}
public static Border getSelectedMenuBarItemPainter() {
final AquaBorder border = new AquaBorder.Default();
border.painter.state.set(Widget.MENU_TITLE);
border.painter.state.set(State.PRESSED);
return border;
}
public static Border getSelectedMenuItemPainter() {
final AquaBorder border = new AquaBorder.Default();
border.painter.state.set(Widget.MENU_ITEM);
border.painter.state.set(State.PRESSED);
return border;
}
}

View file

@ -0,0 +1,176 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicMenuUI;
public final class AquaMenuUI extends BasicMenuUI implements AquaMenuPainter.Client {
public static ComponentUI createUI(final JComponent x) {
return new AquaMenuUI();
}
@Override
protected ChangeListener createChangeListener(final JComponent c) {
return new ChangeHandler((JMenu)c, this);
}
@Override
protected void installDefaults() {
super.installDefaults();
// [3361625]
// In Aqua, the menu delay is 8 ticks, according to Eric Schlegel.
// That makes the millisecond delay 8 ticks * 1 second / 60 ticks * 1000 milliseconds/second
((JMenu)menuItem).setDelay(8 * 1000 / 60);
}
@Override
protected void paintMenuItem(final Graphics g, final JComponent c, final Icon localCheckIcon, final Icon localArrowIcon, final Color background, final Color foreground, final int localDefaultTextIconGap) {
AquaMenuPainter.instance().paintMenuItem(this, g, c, localCheckIcon, localArrowIcon, background, foreground, disabledForeground, selectionForeground, localDefaultTextIconGap, acceleratorFont);
}
@Override
protected Dimension getPreferredMenuItemSize(final JComponent c, final Icon localCheckIcon, final Icon localArrowIcon, final int localDefaultTextIconGap) {
final Dimension d = AquaMenuPainter.instance().getPreferredMenuItemSize(c, localCheckIcon, localArrowIcon, localDefaultTextIconGap, acceleratorFont);
if (c.getParent() instanceof JMenuBar) d.height = Math.max(d.height, 21);
return d;
}
@Override
public void paintBackground(final Graphics g, final JComponent c, final int menuWidth, final int menuHeight) {
final Container parent = c.getParent();
final boolean parentIsMenuBar = parent instanceof JMenuBar;
final ButtonModel model = ((JMenuItem)c).getModel();
if (model.isArmed() || model.isSelected()) {
if (parentIsMenuBar) {
AquaMenuPainter.instance().paintSelectedMenuTitleBackground(g, menuWidth, menuHeight);
} else {
AquaMenuPainter.instance().paintSelectedMenuItemBackground(g, menuWidth, menuHeight);
}
} else {
if (parentIsMenuBar) {
AquaMenuPainter.instance().paintMenuBarBackground(g, menuWidth, menuHeight, c);
} else {
g.setColor(c.getBackground());
g.fillRect(0, 0, menuWidth, menuHeight);
}
}
}
@Override
protected MouseInputListener createMouseInputListener(final JComponent c) {
return new AquaMouseInputHandler();
}
@Override
protected MenuDragMouseListener createMenuDragMouseListener(final JComponent c) {
//return super.createMenuDragMouseListener(c);
return new MenuDragMouseHandler();
}
final class MenuDragMouseHandler implements MenuDragMouseListener {
@Override
public void menuDragMouseDragged(final MenuDragMouseEvent e) {
if (menuItem.isEnabled() == false) return;
final MenuSelectionManager manager = e.getMenuSelectionManager();
final MenuElement[] path = e.getPath();
// In Aqua, we always respect the menu's delay, if one is set.
// Doesn't matter how the menu is clicked on or otherwise moused over.
final Point p = e.getPoint();
if (p.x >= 0 && p.x < menuItem.getWidth() && p.y >= 0 && p.y < menuItem.getHeight()) {
final JMenu menu = (JMenu)menuItem;
final MenuElement[] selectedPath = manager.getSelectedPath();
if (!(selectedPath.length > 0 && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) {
if (menu.getDelay() == 0) {
appendPath(path, menu.getPopupMenu());
} else {
manager.setSelectedPath(path);
setupPostTimer(menu);
}
}
} else if (e.getID() == MouseEvent.MOUSE_RELEASED) {
final Component comp = manager.componentForPoint(e.getComponent(), e.getPoint());
if (comp == null) manager.clearSelectedPath();
}
}
@Override
public void menuDragMouseEntered(final MenuDragMouseEvent e) { }
@Override
public void menuDragMouseExited(final MenuDragMouseEvent e) { }
@Override
public void menuDragMouseReleased(final MenuDragMouseEvent e) { }
}
static void appendPath(final MenuElement[] path, final MenuElement elem) {
final MenuElement[] newPath = new MenuElement[path.length + 1];
System.arraycopy(path, 0, newPath, 0, path.length);
newPath[path.length] = elem;
MenuSelectionManager.defaultManager().setSelectedPath(newPath);
}
protected final class AquaMouseInputHandler extends MouseInputHandler {
/**
* Invoked when the cursor enters the menu. This method sets the selected
* path for the MenuSelectionManager and handles the case
* in which a menu item is used to pop up an additional menu, as in a
* hierarchical menu system.
*
* @param e the mouse event; not used
*/
@Override
public void mouseEntered(final MouseEvent e) {
final JMenu menu = (JMenu)menuItem;
if (!menu.isEnabled()) return;
final MenuSelectionManager manager = MenuSelectionManager.defaultManager();
final MenuElement[] selectedPath = manager.getSelectedPath();
// In Aqua, we always have a menu delay, regardless of where the menu is.
if (!(selectedPath.length > 0 && selectedPath[selectedPath.length - 1] == menu.getPopupMenu())) {
// the condition below prevents from activating menu in other frame
if (!menu.isTopLevelMenu() || (selectedPath.length > 0 &&
selectedPath[0] == menu.getParent())) {
if (menu.getDelay() == 0) {
appendPath(getPath(), menu.getPopupMenu());
} else {
manager.setSelectedPath(getPath());
setupPostTimer(menu);
}
}
}
}
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.plaf.UIResource;
import com.apple.laf.AquaUtils.RecyclableSingleton;
@SuppressWarnings("restricted")
public final class AquaNativeResources {
static {
System.loadLibrary("osxui");
}
// TODO: removing CColorPaint for now
@SuppressWarnings("serial") // JDK implementation class
static final class CColorPaintUIResource extends Color/*CColorPaint*/ implements UIResource {
// The color passed to this MUST be a retained NSColor, and the CColorPaintUIResource
// takes ownership of that retain.
public CColorPaintUIResource(long color, int r, int g, int b, int a) {
super(r, g, b, a);
//super(color, r, g, b, a);
}
}
private static final RecyclableSingleton<Color> sBackgroundColor = new RecyclableSingleton<Color>() {
@Override
protected Color getInstance() {
final long backgroundID = getWindowBackgroundColor();
return new CColorPaintUIResource(backgroundID, 0xEE, 0xEE, 0xEE, 0xFF);
}
};
private static native long getWindowBackgroundColor();
public static Color getWindowBackgroundColorUIResource() {
return sBackgroundColor.get();
}
static BufferedImage getRadioButtonSizerImage() {
final BufferedImage img = new BufferedImage(20, 20, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = img.getGraphics();
g.setColor(Color.pink);
g.fillRect(0, 0, 20, 20);
g.dispose();
return img;
}
}

View file

@ -0,0 +1,170 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicOptionPaneUI;
public final class AquaOptionPaneUI extends BasicOptionPaneUI {
private static final int kOKCancelButtonWidth = 79;
private static final int kButtonHeight = 23;
private static final int kDialogSmallPadding = 4;
private static final int kDialogLargePadding = 23;
/**
* Creates a new BasicOptionPaneUI instance.
*/
public static ComponentUI createUI(final JComponent x) {
return new AquaOptionPaneUI();
}
/**
* Creates and returns a Container containin the buttons. The buttons
* are created by calling {@code getButtons}.
*/
@Override
protected Container createButtonArea() {
final Container bottom = super.createButtonArea();
// Now replace the Layout
bottom.setLayout(new AquaButtonAreaLayout(true, kDialogSmallPadding));
return bottom;
}
/**
* Messaged from installComponents to create a Container containing the
* body of the message.
* The icon and body should be aligned on their top edges
*/
@Override
protected Container createMessageArea() {
final JPanel top = new JPanel();
top.setBorder(UIManager.getBorder("OptionPane.messageAreaBorder"));
top.setLayout(new BoxLayout(top, BoxLayout.X_AXIS));
/* Fill the body. */
final Container body = new JPanel();
final Icon sideIcon = getIcon();
if (sideIcon != null) {
final JLabel iconLabel = new JLabel(sideIcon);
iconLabel.setVerticalAlignment(SwingConstants.TOP);
final JPanel iconPanel = new JPanel();
iconPanel.add(iconLabel);
top.add(iconPanel);
top.add(Box.createHorizontalStrut(kDialogLargePadding));
}
body.setLayout(new GridBagLayout());
final GridBagConstraints cons = new GridBagConstraints();
cons.gridx = cons.gridy = 0;
cons.gridwidth = GridBagConstraints.REMAINDER;
cons.gridheight = 1;
cons.anchor = GridBagConstraints.WEST;
cons.insets = new Insets(0, 0, 3, 0);
addMessageComponents(body, cons, getMessage(), getMaxCharactersPerLineCount(), false);
top.add(body);
return top;
}
/**
* AquaButtonAreaLayout lays out all
* components according to the HI Guidelines:
* The most important button is always on the far right
* The group of buttons is on the right for left-to-right,
* left for right-to-left
* The widths of each component will be set to the largest preferred size width.
*
*
* This inner class is marked &quot;public&quot; due to a compiler bug.
* This class should be treated as a &quot;protected&quot; inner class.
* Instantiate it only within subclasses of BasicOptionPaneUI.
*
* BasicOptionPaneUI expects that its buttons are laid out with
* a subclass of ButtonAreaLayout
*/
public static final class AquaButtonAreaLayout extends ButtonAreaLayout {
public AquaButtonAreaLayout(final boolean syncAllWidths, final int padding) {
super(true, padding);
}
@Override
public void layoutContainer(final Container container) {
final Component[] children = container.getComponents();
if (children == null || 0 >= children.length) return;
final int numChildren = children.length;
final int yLocation = container.getInsets().top;
// Always syncAllWidths - and heights!
final Dimension maxSize = new Dimension(kOKCancelButtonWidth, kButtonHeight);
for (int i = 0; i < numChildren; i++) {
final Dimension sizes = children[i].getPreferredSize();
maxSize.width = Math.max(maxSize.width, sizes.width);
maxSize.height = Math.max(maxSize.height, sizes.height);
}
// ignore getCentersChildren, because we don't
int xLocation = container.getSize().width - (maxSize.width * numChildren + (numChildren - 1) * padding);
final int xOffset = maxSize.width + padding;
// most important button (button zero) on far right
for (int i = numChildren - 1; i >= 0; i--) {
children[i].setBounds(xLocation, yLocation, maxSize.width, maxSize.height);
xLocation += xOffset;
}
}
@Override
public Dimension minimumLayoutSize(Container c) {
if (c != null) {
Component[] children = c.getComponents();
if (children != null && children.length > 0) {
int numChildren = children.length;
Insets cInsets = c.getInsets();
int extraHeight = cInsets.top + cInsets.bottom;
int extraWidth = cInsets.left + cInsets.right;
int okCancelButtonWidth = extraWidth
+ (kOKCancelButtonWidth * numChildren)
+ (numChildren - 1) * padding;
int okbuttonHeight = extraHeight + kButtonHeight;
Dimension minSize = super.minimumLayoutSize(c);
return new Dimension(Math.max(minSize.width,
okCancelButtonWidth),
Math.max(minSize.height, okbuttonHeight));
}
}
return new Dimension(0, 0);
}
}
}

View file

@ -0,0 +1,310 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.image.*;
import java.util.HashMap;
import com.apple.laf.AquaImageFactory.RecyclableSlicedImageControl;
import com.apple.laf.AquaImageFactory.NineSliceMetrics;
import com.apple.laf.AquaImageFactory.SlicedImageControl;
import sun.awt.image.*;
import sun.java2d.*;
import sun.print.*;
import apple.laf.*;
import apple.laf.JRSUIUtils.NineSliceMetricsProvider;
import sun.awt.image.ImageCache;
abstract class AquaPainter <T extends JRSUIState> {
static <T extends JRSUIState> AquaPainter<T> create(final T state) {
return new AquaSingleImagePainter<>(state);
}
static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut) {
return create(state, minWidth, minHeight, westCut, eastCut, northCut, southCut, true);
}
static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean useMiddle) {
return create(state, minWidth, minHeight, westCut, eastCut, northCut, southCut, useMiddle, true, true);
}
static <T extends JRSUIState> AquaPainter<T> create(final T state, final int minWidth, final int minHeight, final int westCut, final int eastCut, final int northCut, final int southCut, final boolean useMiddle, final boolean stretchHorizontally, final boolean stretchVertically) {
return create(state, new NineSliceMetricsProvider() {
@Override
public NineSliceMetrics getNineSliceMetricsForState(JRSUIState state) {
return new NineSliceMetrics(minWidth, minHeight, westCut, eastCut, northCut, southCut, useMiddle, stretchHorizontally, stretchVertically);
}
});
}
static <T extends JRSUIState> AquaPainter<T> create(final T state, final NineSliceMetricsProvider metricsProvider) {
return new AquaNineSlicingImagePainter<>(state, metricsProvider);
}
abstract void paint(Graphics2D g, T stateToPaint);
final Rectangle boundsRect = new Rectangle();
final JRSUIControl control;
T state;
AquaPainter(final JRSUIControl control, final T state) {
this.control = control;
this.state = state;
}
final JRSUIControl getControl() {
control.set(state = state.derive());
return control;
}
final void paint(final Graphics g, final Component c, final int x,
final int y, final int w, final int h) {
boundsRect.setBounds(x, y, w, h);
final T nextState = state.derive();
final Graphics2D g2d = getGraphics2D(g);
if (g2d != null) paint(g2d, nextState);
state = nextState;
}
private static final class AquaNineSlicingImagePainter<T extends JRSUIState>
extends AquaPainter<T> {
private final HashMap<T, RecyclableJRSUISlicedImageControl> slicedControlImages;
private final NineSliceMetricsProvider metricsProvider;
AquaNineSlicingImagePainter(final T state) {
this(state, null);
}
AquaNineSlicingImagePainter(final T state, final NineSliceMetricsProvider metricsProvider) {
super(new JRSUIControl(false), state);
this.metricsProvider = metricsProvider;
slicedControlImages = new HashMap<>();
}
@Override
void paint(final Graphics2D g, final T stateToPaint) {
if (metricsProvider == null) {
AquaSingleImagePainter.paintFromSingleCachedImage(g, control, stateToPaint, boundsRect);
return;
}
RecyclableJRSUISlicedImageControl slicesRef = slicedControlImages.get(stateToPaint);
if (slicesRef == null) {
final NineSliceMetrics metrics = metricsProvider.getNineSliceMetricsForState(stateToPaint);
if (metrics == null) {
AquaSingleImagePainter.paintFromSingleCachedImage(g, control, stateToPaint, boundsRect);
return;
}
slicesRef = new RecyclableJRSUISlicedImageControl(control, stateToPaint, metrics);
slicedControlImages.put(stateToPaint, slicesRef);
}
final SlicedImageControl slices = slicesRef.get();
slices.paint(g, boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height);
}
}
private static final class AquaSingleImagePainter<T extends JRSUIState>
extends AquaPainter<T> {
AquaSingleImagePainter(final T state) {
super(new JRSUIControl(false), state);
}
@Override
void paint(final Graphics2D g, final T stateToPaint) {
paintFromSingleCachedImage(g, control, stateToPaint, boundsRect);
}
/**
* Paints a native control, which identified by its size and a set of
* additional arguments using a cached image.
*
* @param g Graphics to draw the control
* @param control the reference to the native control
* @param controlState the state of the native control
* @param bounds the rectangle where the native part should be drawn.
* Note: the focus can/will be drawn outside of this bounds.
*/
static void paintFromSingleCachedImage(final Graphics2D g,
final JRSUIControl control,
final JRSUIState controlState,
final Rectangle bounds) {
if (bounds.width <= 0 || bounds.height <= 0) {
return;
}
int focus = 0;
if (controlState.is(JRSUIConstants.Focused.YES)) {
focus = JRSUIConstants.FOCUS_SIZE;
}
final int imgX = bounds.x - focus;
final int imgY = bounds.y - focus;
final int imgW = bounds.width + (focus << 1);
final int imgH = bounds.height + (focus << 1);
final GraphicsConfiguration config = g.getDeviceConfiguration();
final ImageCache cache = ImageCache.getInstance();
final AquaPixelsKey key = new AquaPixelsKey(config, imgW, imgH,
bounds, controlState);
Image img = cache.getImage(key);
if (img == null) {
img = new MultiResolutionCachedImage(imgW, imgH,
(rvWidth, rvHeight) -> createImage(imgX, imgY,
rvWidth, rvHeight, bounds, control, controlState));
if (!controlState.is(JRSUIConstants.Animating.YES)) {
cache.setImage(key, img);
}
}
g.drawImage(img, imgX, imgY, imgW, imgH, null);
}
private static Image createImage(int imgX, int imgY, int imgW, int imgH,
final Rectangle bounds,
final JRSUIControl control,
JRSUIState controlState) {
BufferedImage img = new BufferedImage(imgW, imgH,
BufferedImage.TYPE_INT_ARGB_PRE);
final WritableRaster raster = img.getRaster();
final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
control.set(controlState);
control.paint(SunWritableRaster.stealData(buffer, 0), imgW, imgH,
bounds.x - imgX, bounds.y - imgY, bounds.width,
bounds.height);
SunWritableRaster.markDirty(buffer);
return img;
}
}
private static final class AquaPixelsKey implements ImageCache.PixelsKey {
private final int pixelCount;
private final int hash;
// key parts
private final GraphicsConfiguration config;
private final int w;
private final int h;
private final Rectangle bounds;
private final JRSUIState state;
AquaPixelsKey(final GraphicsConfiguration config,
final int w, final int h, final Rectangle bounds,
final JRSUIState state) {
this.pixelCount = w * h;
this.config = config;
this.w = w;
this.h = h;
this.bounds = bounds;
this.state = state;
this.hash = hash();
}
@Override
public int getPixelCount() {
return pixelCount;
}
private int hash() {
int hash = config != null ? config.hashCode() : 0;
hash = 31 * hash + w;
hash = 31 * hash + h;
hash = 31 * hash + bounds.hashCode();
hash = 31 * hash + state.hashCode();
return hash;
}
@Override
public int hashCode() {
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AquaPixelsKey) {
AquaPixelsKey key = (AquaPixelsKey) obj;
return config == key.config && w == key.w && h == key.h
&& bounds.equals(key.bounds) && state.equals(key.state);
}
return false;
}
}
private static final class RecyclableJRSUISlicedImageControl
extends RecyclableSlicedImageControl {
private final JRSUIControl control;
private final JRSUIState state;
RecyclableJRSUISlicedImageControl(final JRSUIControl control, final JRSUIState state, final NineSliceMetrics metrics) {
super(metrics);
this.control = control;
this.state = state;
}
@Override
protected Image createTemplateImage(int width, int height) {
BufferedImage image = new BufferedImage(metrics.minW, metrics.minH, BufferedImage.TYPE_INT_ARGB_PRE);
final WritableRaster raster = image.getRaster();
final DataBufferInt buffer = (DataBufferInt)raster.getDataBuffer();
control.set(state);
control.paint(SunWritableRaster.stealData(buffer, 0), metrics.minW, metrics.minH, 0, 0, metrics.minW, metrics.minH);
SunWritableRaster.markDirty(buffer);
return image;
}
}
private Graphics2D getGraphics2D(final Graphics g) {
try {
return (SunGraphics2D)g; // doing a blind try is faster than checking instanceof
} catch (Exception ignored) {
if (g instanceof PeekGraphics) {
// if it is a peek just dirty the region
g.fillRect(boundsRect.x, boundsRect.y, boundsRect.width, boundsRect.height);
} else if (g instanceof ProxyGraphics2D) {
final ProxyGraphics2D pg = (ProxyGraphics2D)g;
final Graphics2D g2d = pg.getDelegate();
if (g2d instanceof SunGraphics2D) {
return g2d;
}
} else if (g instanceof Graphics2D) {
return (Graphics2D) g;
}
}
return null;
}
}

View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPanelUI;
import com.apple.laf.AquaUtils.RecyclableSingleton;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
import java.awt.Graphics;
public final class AquaPanelUI extends BasicPanelUI {
private static final RecyclableSingleton<AquaPanelUI> instance = new RecyclableSingletonFromDefaultConstructor<AquaPanelUI>(AquaPanelUI.class);
public static ComponentUI createUI(final JComponent c) {
return instance.get();
}
@Override
public final void update(final Graphics g, final JComponent c) {
if (c.isOpaque()) {
AquaUtils.fillRect(g, c);
}
paint(g, c);
}
}

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicSeparatorUI;
import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;
public final class AquaPopupMenuSeparatorUI extends BasicSeparatorUI {
private static final RecyclableSingletonFromDefaultConstructor<AquaPopupMenuSeparatorUI> instance = new RecyclableSingletonFromDefaultConstructor<AquaPopupMenuSeparatorUI>(AquaPopupMenuSeparatorUI.class);
public static ComponentUI createUI(final JComponent c) {
return instance.get();
}
@Override
public void update(final Graphics g, final JComponent c) {
paint(g, c);
}
@Override
public void paint(final Graphics g, final JComponent c) {
final Dimension s = c.getSize();
if (((JSeparator)c).getOrientation() == SwingConstants.VERTICAL) {
g.setColor(c.getForeground());
g.drawLine(5, 1, 5, s.height - 2);
g.setColor(c.getBackground());
g.drawLine(6, 1, 6, s.height - 2);
} else {
g.setColor(c.getForeground());
g.drawLine(1, 5, s.width - 2, 5);
g.setColor(c.getBackground());
g.drawLine(1, 6, s.width - 2, 6);
}
}
@Override
public Dimension getPreferredSize(final JComponent c) {
if (((JSeparator)c).getOrientation() == SwingConstants.VERTICAL) {
return new Dimension(12, 0);
}
return new Dimension(0, 12);
}
}

View file

@ -0,0 +1,138 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.event.MouseEvent;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicPopupMenuUI;
public final class AquaPopupMenuUI extends BasicPopupMenuUI {
public static ComponentUI createUI(final JComponent x) {
return new AquaPopupMenuUI();
}
@Override
public boolean isPopupTrigger(final MouseEvent e) {
// Use the awt popup trigger code since this only runs on our OS!
return e.isPopupTrigger();
}
@Override
public void paint(final Graphics g, final JComponent c) {
if (!(g instanceof Graphics2D)) {
super.paint(g, c);
return;
}
if (!(PopupFactory.getSharedInstance() instanceof ScreenPopupFactory)) {
super.paint(g, c);
return;
}
// round off and put back edges in a new Graphics
final Graphics2D g2d = (Graphics2D)g.create();
final Rectangle popupBounds = popupMenu.getBounds(); // NB: origin is still at 0,0
paintRoundRect(g2d, popupBounds);
clipEdges(g2d, popupBounds);
g2d.dispose();
// if any subsequent drawing occurs over these corners, the window is square again
super.paint(g, c);
}
protected void paintRoundRect(final Graphics2D g2d, final Rectangle popupBounds) {
// setup the graphics context to blast alpha for every primitive we draw
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.Clear);
// draw the 3px round-rect line around the outer bounds of the window,
// this gives the appearance of rounded corners
g2d.setStroke(new BasicStroke(3.0f));
g2d.drawRoundRect(-2, -2, popupBounds.width + 3, popupBounds.height + 3, 12, 12);
}
static final int OVERLAP_SLACK = 10;
protected void clipEdges(final Graphics2D g2d, final Rectangle popupBounds) {
final Component invoker = popupMenu.getInvoker();
if (!(invoker instanceof JMenu)) return; // only point corners originating from menu items
final Rectangle invokerBounds = invoker.getBounds();
// only get location on screen when necessary
invokerBounds.setLocation(invoker.getLocationOnScreen());
popupBounds.setLocation(popupMenu.getLocationOnScreen());
final Point invokerCenter = new Point((int)invokerBounds.getCenterX(), (int)invokerBounds.getCenterY());
if (popupBounds.contains(invokerCenter)) {
// invoker is "behind" the popup, no corners should be pointed
return;
}
// blast opaque background over the corners we want to "put back"
g2d.setComposite(AlphaComposite.SrcOver);
g2d.setColor(popupMenu.getBackground());
final Point popupCenter = new Point((int)popupBounds.getCenterX(), (int)popupBounds.getCenterY());
final boolean invokerMidpointAbovePopupMidpoint = invokerCenter.y <= popupCenter.y;
if (invokerBounds.x + invokerBounds.width < popupBounds.x + OVERLAP_SLACK) {
// popup is far right of invoker
if (invokerMidpointAbovePopupMidpoint) {
// point upper left corner, most common case
g2d.fillRect(-2, -2, 8, 8);
return;
}
// point lower left corner
g2d.fillRect(-2, popupBounds.height - 6, 8, 8);
return;
}
if (popupBounds.x + popupBounds.width < invokerBounds.x + OVERLAP_SLACK) {
// popup is far left of invoker
if (invokerMidpointAbovePopupMidpoint) {
// point upper right corner
g2d.fillRect(popupBounds.width - 6, -2, 8, 8);
return;
}
// point lower right corner
g2d.fillRect(popupBounds.width - 6, popupBounds.height - 6, 8, 8);
return;
}
// popup is neither "far right" or "far left" of it's invoker
if (invokerBounds.y + invokerBounds.height < popupBounds.y + OVERLAP_SLACK) {
// popup is "middle" below it's invoker,
// this is probably the "connected" case where both upper corners should touch
g2d.fillRect(-2, -2, popupBounds.width + 4, 8);
return;
}
// if none of these cases match...don't make any corners pointed
}
}

View file

@ -0,0 +1,517 @@
/*
* Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.apple.laf;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.beans.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.*;
import sun.swing.SwingUtilities2;
import apple.laf.JRSUIStateFactory;
import apple.laf.JRSUIConstants.*;
import apple.laf.JRSUIState.ValueState;
import com.apple.laf.AquaUtilControlSize.*;
import com.apple.laf.AquaUtils.RecyclableSingleton;
public final class AquaProgressBarUI extends ProgressBarUI implements ChangeListener, PropertyChangeListener, AncestorListener, Sizeable {
private static final boolean ADJUSTTIMER = true;
private static final RecyclableSingleton<SizeDescriptor> sizeDescriptor = new RecyclableSingleton<SizeDescriptor>() {
@Override
protected SizeDescriptor getInstance() {
return new SizeDescriptor(new SizeVariant(146, 20)) {
public SizeVariant deriveSmall(final SizeVariant v) { v.alterMinSize(0, -6); return super.deriveSmall(v); }
};
}
};
static SizeDescriptor getSizeDescriptor() {
return sizeDescriptor.get();
}
protected Size sizeVariant = Size.REGULAR;
protected Color selectionForeground;
private Animator animator;
protected boolean isAnimating;
protected boolean isCircular;
protected final AquaPainter<ValueState> painter = AquaPainter.create(JRSUIStateFactory.getProgressBar());
protected JProgressBar progressBar;
public static ComponentUI createUI(final JComponent x) {
return new AquaProgressBarUI();
}
protected AquaProgressBarUI() { }
@Override
public void installUI(final JComponent c) {
progressBar = (JProgressBar)c;
installDefaults();
installListeners();
}
@Override
public void uninstallUI(final JComponent c) {
uninstallDefaults();
uninstallListeners();
stopAnimationTimer();
progressBar = null;
}
protected void installDefaults() {
progressBar.setOpaque(false);
LookAndFeel.installBorder(progressBar, "ProgressBar.border");
LookAndFeel.installColorsAndFont(progressBar, "ProgressBar.background", "ProgressBar.foreground", "ProgressBar.font");
selectionForeground = UIManager.getColor("ProgressBar.selectionForeground");
}
protected void uninstallDefaults() {
LookAndFeel.uninstallBorder(progressBar);
}
protected void installListeners() {
progressBar.addChangeListener(this); // Listen for changes in the progress bar's data
progressBar.addPropertyChangeListener(this); // Listen for changes between determinate and indeterminate state
progressBar.addAncestorListener(this);
AquaUtilControlSize.addSizePropertyListener(progressBar);
}
protected void uninstallListeners() {
AquaUtilControlSize.removeSizePropertyListener(progressBar);
progressBar.removeAncestorListener(this);
progressBar.removePropertyChangeListener(this);
progressBar.removeChangeListener(this);
}
@Override
public void stateChanged(final ChangeEvent e) {
progressBar.repaint();
}
@Override
public void propertyChange(final PropertyChangeEvent e) {
final String prop = e.getPropertyName();
if ("indeterminate".equals(prop)) {
if (!progressBar.isIndeterminate()) return;
stopAnimationTimer();
// start the animation thread
if (progressBar.isDisplayable()) {
startAnimationTimer();
}
}
if ("JProgressBar.style".equals(prop)) {
isCircular = "circular".equalsIgnoreCase(e.getNewValue() + "");
progressBar.repaint();
}
}
// listen for Ancestor events to stop our timer when we are no longer visible
// <rdar://problem/5405035> JProgressBar: UI in Aqua look and feel causes memory leaks
@Override
public void ancestorRemoved(final AncestorEvent e) {
stopAnimationTimer();
}
@Override
public void ancestorAdded(final AncestorEvent e) {
if (!progressBar.isIndeterminate()) return;
if (progressBar.isDisplayable()) {
startAnimationTimer();
}
}
@Override
public void ancestorMoved(final AncestorEvent e) { }
@Override
public void paint(final Graphics g, final JComponent c) {
revalidateAnimationTimers(); // revalidate to turn on/off timers when values change
painter.state.set(getState(c));
painter.state.set(isHorizontal() ? Orientation.HORIZONTAL : Orientation.VERTICAL);
painter.state.set(isAnimating ? Animating.YES : Animating.NO);
if (progressBar.isIndeterminate()) {
if (isCircular) {
painter.state.set(Widget.PROGRESS_SPINNER);
painter.paint(g, c, 2, 2, 16, 16);
return;
}
painter.state.set(Widget.PROGRESS_INDETERMINATE_BAR);
paint(g);
return;
}
painter.state.set(Widget.PROGRESS_BAR);
painter.state.setValue(checkValue(progressBar.getPercentComplete()));
paint(g);
}
static double checkValue(final double value) {
return Double.isNaN(value) ? 0 : value;
}
protected void paint(final Graphics g) {
// this is questionable. We may want the insets to mean something different.
final Insets i = progressBar.getInsets();
final int width = progressBar.getWidth() - (i.right + i.left);
final int height = progressBar.getHeight() - (i.bottom + i.top);
Graphics2D g2 = (Graphics2D) g;
final AffineTransform savedAT = g2.getTransform();
if (!progressBar.getComponentOrientation().isLeftToRight()) {
//Scale operation: Flips component about pivot
//Translate operation: Moves component back into original position
g2.scale(-1, 1);
g2.translate(-progressBar.getWidth(), 0);
}
painter.paint(g, progressBar, i.left, i.top, width, height);
g2.setTransform(savedAT);
if (progressBar.isStringPainted() && !progressBar.isIndeterminate()) {
paintString(g, i.left, i.top, width, height);
}
}
protected State getState(final JComponent c) {
if (!c.isEnabled()) return State.INACTIVE;
if (!AquaFocusHandler.isActive(c)) return State.INACTIVE;
return State.ACTIVE;
}
protected void paintString(final Graphics g, final int x, final int y, final int width, final int height) {
if (!(g instanceof Graphics2D)) return;
final Graphics2D g2 = (Graphics2D)g;
final String progressString = progressBar.getString();
g2.setFont(progressBar.getFont());
final Point renderLocation = getStringPlacement(g2, progressString, x, y, width, height);
final Rectangle oldClip = g2.getClipBounds();
if (isHorizontal()) {
g2.setColor(selectionForeground);
SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y);
} else { // VERTICAL
// We rotate it -90 degrees, then translate it down since we are going to be bottom up.
final AffineTransform savedAT = g2.getTransform();
g2.transform(AffineTransform.getRotateInstance(0.0f - (Math.PI / 2.0f), 0, 0));
g2.translate(-progressBar.getHeight(), 0);
// 0,0 is now the bottom left of the viewable area, so we just draw our image at
// the render location since that calculation knows about rotation.
g2.setColor(selectionForeground);
SwingUtilities2.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y);
g2.setTransform(savedAT);
}
g2.setClip(oldClip);
}
/**
* Designate the place where the progress string will be painted. This implementation places it at the center of the
* progress bar (in both x and y). Override this if you want to right, left, top, or bottom align the progress
* string or if you need to nudge it around for any reason.
*/
protected Point getStringPlacement(final Graphics g, final String progressString, int x, int y, int width, int height) {
final FontMetrics fontSizer = progressBar.getFontMetrics(progressBar.getFont());
final int stringWidth = fontSizer.stringWidth(progressString);
if (!isHorizontal()) {
// Calculate the location for the rotated text in real component coordinates.
// swapping x & y and width & height
final int oldH = height;
height = width;
width = oldH;
final int oldX = x;
x = y;
y = oldX;
}
return new Point(x + (width / 2 - stringWidth / 2), y + ((height + fontSizer.getAscent() - fontSizer.getLeading() - fontSizer.getDescent()) / 2) - 1);
}
static Dimension getCircularPreferredSize() {
return new Dimension(20, 20);
}
@Override
public Dimension getPreferredSize(final JComponent c) {
if (isCircular) {
return getCircularPreferredSize();
}
final FontMetrics metrics = progressBar.getFontMetrics(progressBar.getFont());
final Dimension size = isHorizontal() ? getPreferredHorizontalSize(metrics) : getPreferredVerticalSize(metrics);
final Insets insets = progressBar.getInsets();
size.width += insets.left + insets.right;
size.height += insets.top + insets.bottom;
return size;
}
protected Dimension getPreferredHorizontalSize(final FontMetrics metrics) {
final SizeVariant variant = getSizeDescriptor().get(sizeVariant);
final Dimension size = new Dimension(variant.w, variant.h);
if (!progressBar.isStringPainted()) return size;
// Ensure that the progress string will fit
final String progString = progressBar.getString();
final int stringWidth = metrics.stringWidth(progString);
if (stringWidth > size.width) {
size.width = stringWidth;
}
// This uses both Height and Descent to be sure that
// there is more than enough room in the progress bar
// for everything.
// This does have a strange dependency on
// getStringPlacememnt() in a funny way.
final int stringHeight = metrics.getHeight() + metrics.getDescent();
if (stringHeight > size.height) {
size.height = stringHeight;
}
return size;
}
protected Dimension getPreferredVerticalSize(final FontMetrics metrics) {
final SizeVariant variant = getSizeDescriptor().get(sizeVariant);
final Dimension size = new Dimension(variant.h, variant.w);
if (!progressBar.isStringPainted()) return size;
// Ensure that the progress string will fit.
final String progString = progressBar.getString();
final int stringHeight = metrics.getHeight() + metrics.getDescent();
if (stringHeight > size.width) {
size.width = stringHeight;
}
// This is also for completeness.
final int stringWidth = metrics.stringWidth(progString);
if (stringWidth > size.height) {
size.height = stringWidth;
}
return size;
}
@Override
public Dimension getMinimumSize(final JComponent c) {
if (isCircular) {
return getCircularPreferredSize();
}
final Dimension pref = getPreferredSize(progressBar);
// The Minimum size for this component is 10.
// The rationale here is that there should be at least one pixel per 10 percent.
if (isHorizontal()) {
pref.width = 10;
} else {
pref.height = 10;
}
return pref;
}
@Override
public Dimension getMaximumSize(final JComponent c) {
if (isCircular) {
return getCircularPreferredSize();
}
final Dimension pref = getPreferredSize(progressBar);
if (isHorizontal()) {
pref.width = Short.MAX_VALUE;
} else {
pref.height = Short.MAX_VALUE;
}
return pref;
}
@Override
public void applySizeFor(final JComponent c, final Size size) {
painter.state.set(sizeVariant = size == Size.MINI ? Size.SMALL : sizeVariant); // CUI doesn't support mini progress bars right now
}
protected void startAnimationTimer() {
if (animator == null) animator = new Animator();
animator.start();
isAnimating = true;
}
protected void stopAnimationTimer() {
if (animator != null) animator.stop();
isAnimating = false;
}
private final Rectangle fUpdateArea = new Rectangle(0, 0, 0, 0);
private final Dimension fLastSize = new Dimension(0, 0);
protected Rectangle getRepaintRect() {
int height = progressBar.getHeight();
int width = progressBar.getWidth();
if (isCircular) {
return new Rectangle(20, 20);
}
if (fLastSize.height == height && fLastSize.width == width) {
return fUpdateArea;
}
int x = 0;
int y = 0;
fLastSize.height = height;
fLastSize.width = width;
final int maxHeight = getMaxProgressBarHeight();
if (isHorizontal()) {
final int excessHeight = height - maxHeight;
y += excessHeight / 2;
height = maxHeight;
} else {
final int excessHeight = width - maxHeight;
x += excessHeight / 2;
width = maxHeight;
}
fUpdateArea.setBounds(x, y, width, height);
return fUpdateArea;
}
protected int getMaxProgressBarHeight() {
return getSizeDescriptor().get(sizeVariant).h;
}
protected boolean isHorizontal() {
return progressBar.getOrientation() == SwingConstants.HORIZONTAL;
}
protected void revalidateAnimationTimers() {
if (progressBar.isIndeterminate()) return;
if (!isAnimating) {
startAnimationTimer(); // only starts if supposed to!
return;
}
final BoundedRangeModel model = progressBar.getModel();
final double currentValue = model.getValue();
if ((currentValue == model.getMaximum()) || (currentValue == model.getMinimum())) {
stopAnimationTimer();
}
}
protected void repaint() {
final Rectangle repaintRect = getRepaintRect();
if (repaintRect == null) {
progressBar.repaint();
return;
}
progressBar.repaint(repaintRect);
}
protected final class Animator implements ActionListener {
private static final int MINIMUM_DELAY = 5;
private Timer timer;
private long previousDelay; // used to tune the repaint interval
private long lastCall; // the last time actionPerformed was called
private int repaintInterval;
public Animator() {
repaintInterval = UIManager.getInt("ProgressBar.repaintInterval");
// Make sure repaintInterval is reasonable.
if (repaintInterval <= 0) repaintInterval = 100;
}
protected void start() {
previousDelay = repaintInterval;
lastCall = 0;
if (timer == null) {
timer = new Timer(repaintInterval, this);
} else {
timer.setDelay(repaintInterval);
}
if (ADJUSTTIMER) {
timer.setRepeats(false);
timer.setCoalesce(false);
}
timer.start();
}
protected void stop() {
timer.stop();
}
@Override
public void actionPerformed(final ActionEvent e) {
if (!ADJUSTTIMER) {
repaint();
return;
}
final long time = System.currentTimeMillis();
if (lastCall > 0) {
// adjust nextDelay
int nextDelay = (int)(previousDelay - time + lastCall + repaintInterval);
if (nextDelay < MINIMUM_DELAY) {
nextDelay = MINIMUM_DELAY;
}
timer.setInitialDelay(nextDelay);
previousDelay = nextDelay;
}
timer.start();
lastCall = time;
repaint();
}
}
}

Some files were not shown because too many files have changed in this diff Show more