SystemTray/src/dorkbox/systemTray/ui/osx/_OsxNativeTray.java

198 lines
6.3 KiB
Java

/*
* Copyright 2021 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.ui.osx;
import java.io.File;
import dorkbox.jna.macos.cocoa.NSImage;
import dorkbox.jna.macos.cocoa.NSStatusBar;
import dorkbox.jna.macos.cocoa.NSStatusItem;
import dorkbox.jna.macos.cocoa.NSString;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Tray;
import dorkbox.systemTray.util.ImageResizeUtil;
/**
*
*/
public
class _OsxNativeTray extends Tray {
// is the system tray visible or not.
private volatile boolean visible = false;
private volatile File imageFile;
private volatile String tooltipText = "";
private final Object keepAliveLock = new Object[0];
@SuppressWarnings("FieldCanBeLocal")
private final Thread keepAliveThread;
// references are ALSO to prevent GC
private final NSStatusBar statusBar;
private volatile NSStatusItem statusItem;
private volatile NSString statusItemTooltip;
private volatile NSImage statusItemImage;
@SuppressWarnings("unused")
public
_OsxNativeTray(final String trayName, final ImageResizeUtil imageResizeUtil, final Runnable onRemoveEvent) {
super(onRemoveEvent);
// THIS WILL NOT keep the app running, so we use a "keep-alive" thread so this behavior is THE SAME across
// all platforms.
keepAliveThread = new Thread(()->{
synchronized (keepAliveLock) {
keepAliveLock.notifyAll();
try {
keepAliveLock.wait();
} catch (InterruptedException ignored) {
}
}
}, "TrayKeepAliveThread");
keepAliveThread.start();
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
final OsxMenu osxMenu = new OsxMenu() {
@Override
public
void setEnabled(final MenuItem menuItem) {
if (statusItem == null) {
statusItem = statusBar.newStatusItem();
statusItem.setHighlightMode(true);
statusItem.setMenu(this._nativeMenu);
}
boolean enabled = menuItem.getEnabled();
if (visible && !enabled) {
statusBar.removeStatusItem(statusItem);
visible = false;
}
else if (!visible && enabled) {
visible = true;
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
statusItem.setToolTip(statusItemTooltip);
statusItem.setImage(statusItemImage);
}
}
@Override
public
void setImage(final MenuItem menuItem) {
imageFile = menuItem.getImage();
if (statusItem == null) {
statusItem = statusBar.newStatusItem();
statusItem.setHighlightMode(true);
statusItem.setMenu(this._nativeMenu);
}
if (imageFile == null) {
statusItemImage = null;
}
else {
statusItemImage = new NSImage(imageFile);
}
statusItem.setImage(statusItemImage);
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
statusItem.setToolTip(statusItemTooltip);
}
@Override
public
void setText(final MenuItem menuItem) {
// no op.
}
@Override
public
void setShortcut(final MenuItem menuItem) {
// no op
}
@Override
public
void setTooltip(final MenuItem menuItem) {
if (statusItem == null) {
statusItem = statusBar.newStatusItem();
statusItem.setHighlightMode(true);
statusItem.setMenu(this._nativeMenu);
}
final String text = menuItem.getTooltip();
if (tooltipText != null && tooltipText.equals(text) ||
tooltipText == null && text != null) {
return;
}
if (text == null) {
tooltipText = "";
}
else {
tooltipText = text;
}
statusItemTooltip = new NSString(tooltipText);
// don't want to matter which (setImage/setTooltip/setEnabled) is done first, and if the image/enabled is changed, we
// want to make sure keep the tooltip text the same as before.
statusItem.setImage(statusItemImage);
statusItem.setToolTip(statusItemTooltip);
}
@Override
public
void remove() {
if (statusItem != null) {
statusBar.removeStatusItem(statusItem);
}
statusItem = null;
statusItemTooltip = null;
statusItemImage = null;
// make sure this thread doesn't keep the JVM alive anymore
synchronized (keepAliveLock) {
keepAliveLock.notifyAll();
}
super.remove();
}
};
statusBar = NSStatusBar.systemStatusBar();
bind(osxMenu, null, imageResizeUtil);
}
@Override
public
boolean hasImage() {
return imageFile != null;
}
}