new API front-end + bound backend for SwingUI
This commit is contained in:
parent
86d031ed7c
commit
d665f29f28
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This represents a common menu-status entry, that is cross platform in nature
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
interface Status {
|
|
||||||
}
|
|
|
@ -1,208 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014 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.swingUI;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.Entry;
|
|
||||||
import dorkbox.systemTray.Menu;
|
|
||||||
import dorkbox.systemTray.util.ImageUtils;
|
|
||||||
import dorkbox.systemTray.util.MenuBase;
|
|
||||||
import dorkbox.systemTray.util.SystemTrayFixes;
|
|
||||||
|
|
||||||
abstract
|
|
||||||
class SwingEntry implements Entry, SwingUI {
|
|
||||||
private final int id = MenuBase.MENU_ID_COUNTER.getAndIncrement();
|
|
||||||
|
|
||||||
private final SwingMenu parent;
|
|
||||||
final JComponent _native;
|
|
||||||
|
|
||||||
// this have to be volatile, because they can be changed from any thread
|
|
||||||
private volatile String text;
|
|
||||||
|
|
||||||
// this is ALWAYS called on the EDT.
|
|
||||||
SwingEntry(final SwingMenu parent, final JComponent menuItem) {
|
|
||||||
this.parent = parent;
|
|
||||||
this._native = menuItem;
|
|
||||||
|
|
||||||
parent._native.add(menuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Menu getParent() {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* must always be called in the EDT thread
|
|
||||||
*/
|
|
||||||
abstract
|
|
||||||
void renderText(final String text);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Not always called on the EDT thread
|
|
||||||
*/
|
|
||||||
abstract
|
|
||||||
void setImage_(final File imageFile);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables, or disables the sub-menu entry.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setEnabled(final boolean enabled) {
|
|
||||||
_native.setEnabled(enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setShortcut(final char key) {
|
|
||||||
if (_native instanceof JMenuItem) {
|
|
||||||
// yikes...
|
|
||||||
final int vKey = SystemTrayFixes.getVirtualKey(key);
|
|
||||||
|
|
||||||
parent.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
((JMenuItem) _native).setMnemonic(vKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
String getText() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setText(final String newText) {
|
|
||||||
this.text = newText;
|
|
||||||
|
|
||||||
parent.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
renderText(newText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setImage(final File imageFile) {
|
|
||||||
if (imageFile == null) {
|
|
||||||
setImage_(null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setImage(final String imagePath) {
|
|
||||||
if (imagePath == null) {
|
|
||||||
setImage_(null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setImage(final URL imageUrl) {
|
|
||||||
if (imageUrl == null) {
|
|
||||||
setImage_(null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setImage(final String cacheName, final InputStream imageStream) {
|
|
||||||
if (imageStream == null) {
|
|
||||||
setImage_(null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setImage(final InputStream imageStream) {
|
|
||||||
if (imageStream == null) {
|
|
||||||
setImage_(null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void remove() {
|
|
||||||
parent.dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
removePrivate();
|
|
||||||
parent._native.remove(_native);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// called when this item is removed. Necessary to cleanup/remove itself
|
|
||||||
abstract
|
|
||||||
void removePrivate();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
int hashCode() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SwingEntry other = (SwingEntry) obj;
|
|
||||||
return this.id == other.id;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014 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.swingUI;
|
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.Checkbox;
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
|
||||||
import dorkbox.systemTray.util.ImageUtils;
|
|
||||||
|
|
||||||
class SwingEntryCheckbox extends SwingEntry implements Checkbox {
|
|
||||||
|
|
||||||
private final ActionListener swingCallback;
|
|
||||||
|
|
||||||
private volatile ActionListener callback;
|
|
||||||
|
|
||||||
private static ImageIcon checkedIcon;
|
|
||||||
private static ImageIcon uncheckedIcon;
|
|
||||||
private volatile boolean isChecked = false;
|
|
||||||
|
|
||||||
|
|
||||||
// this is ALWAYS called on the EDT.
|
|
||||||
SwingEntryCheckbox(final SwingMenu parent, final ActionListener callback) {
|
|
||||||
super(parent, new AdjustedJMenuItem());
|
|
||||||
this.callback = callback;
|
|
||||||
|
|
||||||
if (checkedIcon == null) {
|
|
||||||
// from Brankic1979, public domain
|
|
||||||
File checkedFile = ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, ImageUtils.class.getResource("checked_32.png"));
|
|
||||||
checkedIcon = new ImageIcon(checkedFile.getAbsolutePath());
|
|
||||||
|
|
||||||
File uncheckedFile = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
|
|
||||||
uncheckedIcon = new ImageIcon(uncheckedFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
((JMenuItem) _native).setIcon(uncheckedIcon);
|
|
||||||
|
|
||||||
if (callback != null) {
|
|
||||||
_native.setEnabled(true);
|
|
||||||
swingCallback = new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void actionPerformed(ActionEvent e) {
|
|
||||||
// we want it to run on the EDT
|
|
||||||
if (isChecked) {
|
|
||||||
((JMenuItem) _native).setIcon(uncheckedIcon);
|
|
||||||
} else {
|
|
||||||
((JMenuItem) _native).setIcon(checkedIcon);
|
|
||||||
}
|
|
||||||
isChecked = !isChecked;
|
|
||||||
|
|
||||||
handle();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
((JMenuItem) _native).addActionListener(swingCallback);
|
|
||||||
} else {
|
|
||||||
_native.setEnabled(false);
|
|
||||||
swingCallback = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if this checkbox is selected, false if not
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
boolean getState() {
|
|
||||||
return isChecked;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setCallback(final ActionListener callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
void handle() {
|
|
||||||
ActionListener cb = this.callback;
|
|
||||||
if (cb != null) {
|
|
||||||
try {
|
|
||||||
cb.actionPerformed(new ActionEvent((Checkbox)this, ActionEvent.ACTION_PERFORMED, ""));
|
|
||||||
} catch (Throwable throwable) {
|
|
||||||
SystemTray.logger.error("Error calling menu entry {} click event.", getText(), throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkbox image is always present
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean hasImage() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void removePrivate() {
|
|
||||||
((JMenuItem) _native).removeActionListener(swingCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
// always called in the EDT
|
|
||||||
@Override
|
|
||||||
void renderText(final String text) {
|
|
||||||
((JMenuItem) _native).setText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void setImage_(final File imageFile) {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014 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.swingUI;
|
|
||||||
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
|
||||||
import dorkbox.util.SwingUtil;
|
|
||||||
|
|
||||||
class SwingEntryItem extends SwingEntry {
|
|
||||||
|
|
||||||
private final ActionListener swingCallback;
|
|
||||||
|
|
||||||
private volatile boolean hasLegitIcon = false;
|
|
||||||
private volatile ActionListener callback;
|
|
||||||
|
|
||||||
// this is ALWAYS called on the EDT.
|
|
||||||
SwingEntryItem(final SwingMenu parent, final ActionListener callback) {
|
|
||||||
super(parent, new AdjustedJMenuItem());
|
|
||||||
this.callback = callback;
|
|
||||||
|
|
||||||
|
|
||||||
if (callback != null) {
|
|
||||||
_native.setEnabled(true);
|
|
||||||
swingCallback = new ActionListener() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void actionPerformed(ActionEvent e) {
|
|
||||||
// we want it to run on the EDT
|
|
||||||
handle();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
((JMenuItem) _native).addActionListener(swingCallback);
|
|
||||||
} else {
|
|
||||||
_native.setEnabled(false);
|
|
||||||
swingCallback = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setCallback(final ActionListener callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private
|
|
||||||
void handle() {
|
|
||||||
ActionListener cb = this.callback;
|
|
||||||
if (cb != null) {
|
|
||||||
try {
|
|
||||||
cb.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, ""));
|
|
||||||
} catch (Throwable throwable) {
|
|
||||||
SystemTray.logger.error("Error calling menu entry {} click event.", getText(), throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean hasImage() {
|
|
||||||
return hasLegitIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void removePrivate() {
|
|
||||||
((JMenuItem) _native).removeActionListener(swingCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
// always called in the EDT
|
|
||||||
@Override
|
|
||||||
void renderText(final String text) {
|
|
||||||
((JMenuItem) _native).setText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
@Override
|
|
||||||
void setImage_(final File imageFile) {
|
|
||||||
hasLegitIcon = imageFile != null;
|
|
||||||
|
|
||||||
SwingUtil.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
if (imageFile != null) {
|
|
||||||
ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath());
|
|
||||||
((JMenuItem) _native).setIcon(origIcon);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
((JMenuItem) _native).setIcon(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2014 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.swingUI;
|
|
||||||
|
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.Status;
|
|
||||||
|
|
||||||
class SwingEntryStatus extends SwingEntry implements Status {
|
|
||||||
|
|
||||||
// this is ALWAYS called on the EDT.
|
|
||||||
SwingEntryStatus(final SwingMenu parent, final String label) {
|
|
||||||
super(parent, new JMenuItem());
|
|
||||||
setText(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// called in the EDT thread
|
|
||||||
@Override
|
|
||||||
void renderText(final String text) {
|
|
||||||
Font font = _native.getFont();
|
|
||||||
Font font1 = font.deriveFont(Font.BOLD);
|
|
||||||
_native.setFont(font1);
|
|
||||||
|
|
||||||
((JMenuItem) _native).setText(text);
|
|
||||||
|
|
||||||
// this makes sure it can't be selected
|
|
||||||
_native.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void setImage_(final File imageFile) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void removePrivate() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setShortcut(final char key) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean hasImage() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setCallback(final ActionListener callback) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2016 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.swingUI;
|
|
||||||
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import javax.swing.JComponent;
|
|
||||||
|
|
||||||
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
|
|
||||||
class SwingEntryWidget extends SwingEntry implements dorkbox.systemTray.Separator {
|
|
||||||
|
|
||||||
// this is ALWAYS called on the EDT.
|
|
||||||
SwingEntryWidget(final SwingMenu parent, JComponent widget) {
|
|
||||||
super(parent, widget);
|
|
||||||
|
|
||||||
_native.setEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// called in the EDT thread
|
|
||||||
@Override
|
|
||||||
void renderText(final String text) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void setImage_(final File imageFile) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void removePrivate() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setShortcut(final char key) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
boolean hasImage() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setCallback(final ActionListener callback) {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,55 +16,51 @@
|
||||||
package dorkbox.systemTray.swingUI;
|
package dorkbox.systemTray.swingUI;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.Checkbox;
|
import dorkbox.systemTray.Checkbox;
|
||||||
import dorkbox.systemTray.Entry;
|
import dorkbox.systemTray.Entry;
|
||||||
import dorkbox.systemTray.Menu;
|
import dorkbox.systemTray.Menu;
|
||||||
import dorkbox.systemTray.Status;
|
import dorkbox.systemTray.MenuItem;
|
||||||
|
import dorkbox.systemTray.Separator;
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.util.MenuBase;
|
import dorkbox.systemTray.util.MenuHook;
|
||||||
|
import dorkbox.systemTray.util.Status;
|
||||||
import dorkbox.systemTray.util.SystemTrayFixes;
|
import dorkbox.systemTray.util.SystemTrayFixes;
|
||||||
import dorkbox.util.SwingUtil;
|
import dorkbox.util.SwingUtil;
|
||||||
|
|
||||||
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
|
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both (and duplicate code)
|
||||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||||
class SwingMenu extends MenuBase implements SwingUI {
|
class SwingMenu implements MenuHook {
|
||||||
|
|
||||||
// sub-menu = AdjustedJMenu
|
private final SwingMenu parent;
|
||||||
// systemtray = TrayPopup
|
final JComponent _native;
|
||||||
volatile JComponent _native;
|
|
||||||
|
|
||||||
// this have to be volatile, because they can be changed from any thread
|
|
||||||
private volatile String text;
|
|
||||||
private volatile boolean hasLegitIcon = false;
|
private volatile boolean hasLegitIcon = false;
|
||||||
|
|
||||||
/**
|
// This is NOT a copy constructor!
|
||||||
* Called in the EDT
|
@SuppressWarnings("IncompleteCopyConstructor")
|
||||||
*
|
SwingMenu(final SwingMenu parent) {
|
||||||
* @param systemTray the system tray (which is the object that sits in the system tray)
|
this.parent = parent;
|
||||||
* @param parent the parent of this menu, null if the parent is the system tray
|
|
||||||
* @param _native the native element that represents this menu
|
if (parent == null) {
|
||||||
*/
|
this._native = new TrayPopup();
|
||||||
SwingMenu(final SystemTray systemTray, final Menu parent, final JComponent _native) {
|
}
|
||||||
super(systemTray, parent);
|
else {
|
||||||
this._native = _native;
|
this._native = new AdjustedJMenu();
|
||||||
|
parent._native.add(this._native);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final
|
protected final
|
||||||
void dispatch(final Runnable runnable) {
|
void dispatch(final Runnable runnable) {
|
||||||
// this will properly check if we are running on the EDT
|
// this will properly check if we are running on the EDT
|
||||||
SwingUtil.invokeLater(runnable);
|
SwingUtil.invokeLater(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final
|
protected final
|
||||||
void dispatchAndWait(final Runnable runnable) {
|
void dispatchAndWait(final Runnable runnable) {
|
||||||
// this will properly check if we are running on the EDT
|
// this will properly check if we are running on the EDT
|
||||||
|
@ -75,131 +71,16 @@ class SwingMenu extends MenuBase implements SwingUI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// always called in the EDT
|
|
||||||
protected final
|
|
||||||
void renderText(final String text) {
|
|
||||||
((JMenuItem) _native).setText(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
String getText() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setText(final String newText) {
|
|
||||||
text = newText;
|
|
||||||
dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
renderText(newText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will add a new menu entry
|
|
||||||
* NOT ALWAYS CALLED ON EDT
|
|
||||||
*/
|
|
||||||
protected final
|
|
||||||
Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback) {
|
|
||||||
if (menuText == null) {
|
|
||||||
throw new NullPointerException("Menu text cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
final AtomicReference<Entry> value = new AtomicReference<Entry>();
|
|
||||||
|
|
||||||
// must always be called on the EDT
|
|
||||||
dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
Entry entry = new SwingEntryItem(SwingMenu.this, callback);
|
|
||||||
entry.setText(menuText);
|
|
||||||
entry.setImage(imagePath);
|
|
||||||
|
|
||||||
menuEntries.add(entry);
|
|
||||||
value.set(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return value.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will add a new checkbox menu entry
|
|
||||||
* NOT ALWAYS CALLED ON DISPATCH
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
protected
|
|
||||||
Checkbox addCheckbox_(final String menuText, final ActionListener callback) {
|
|
||||||
if (menuText == null) {
|
|
||||||
throw new NullPointerException("Menu text cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
final AtomicReference<Checkbox> value = new AtomicReference<Checkbox>();
|
|
||||||
|
|
||||||
// must always be called on the EDT
|
|
||||||
dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
Entry entry = new SwingEntryCheckbox(SwingMenu.this, callback);
|
|
||||||
entry.setText(menuText);
|
|
||||||
|
|
||||||
menuEntries.add(entry);
|
|
||||||
value.set((Checkbox) entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return value.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will add a new sub-menu entry
|
|
||||||
* NOT ALWAYS CALLED ON EDT
|
|
||||||
*/
|
|
||||||
protected final
|
|
||||||
Menu addMenu_(final String menuText, final File imagePath) {
|
|
||||||
if (menuText == null) {
|
|
||||||
throw new NullPointerException("Menu text cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
final AtomicReference<Menu> value = new AtomicReference<Menu>();
|
|
||||||
|
|
||||||
// must always be called on the EDT
|
|
||||||
dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
Entry entry = new SwingMenu(getSystemTray(), SwingMenu.this, new AdjustedJMenu());
|
|
||||||
_native.add(((SwingMenu) entry)._native); // have to add it separately
|
|
||||||
|
|
||||||
entry.setText(menuText);
|
|
||||||
entry.setImage(imagePath);
|
|
||||||
|
|
||||||
menuEntries.add(entry);
|
|
||||||
value.set((Menu) entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return value.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// public here so that Swing/Gtk/AppIndicator can override this
|
|
||||||
public
|
public
|
||||||
void setImage_(final File imageFile) {
|
boolean hasImage() {
|
||||||
|
return hasLegitIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
// is overridden in tray impl
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setImage(final MenuItem menuItem) {
|
||||||
|
final File imageFile = menuItem.getImage();
|
||||||
hasLegitIcon = imageFile != null;
|
hasLegitIcon = imageFile != null;
|
||||||
|
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
|
@ -208,156 +89,115 @@ class SwingMenu extends MenuBase implements SwingUI {
|
||||||
void run() {
|
void run() {
|
||||||
if (imageFile != null) {
|
if (imageFile != null) {
|
||||||
ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath());
|
ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath());
|
||||||
((JMenuItem) _native).setIcon(origIcon);
|
((AdjustedJMenu) _native).setIcon(origIcon);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
((JMenuItem) _native).setIcon(null);
|
((AdjustedJMenu) _native).setIcon(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is overridden in tray impl
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
boolean hasImage() {
|
void setEnabled(final MenuItem menuItem) {
|
||||||
return hasLegitIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
// public here so that Swing/Gtk/AppIndicator can override this
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setEnabled(final boolean enabled) {
|
|
||||||
dispatch(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
_native.setEnabled(enabled);
|
_native.setEnabled(menuItem.getEnabled());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// is overridden in tray impl
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setText(final MenuItem menuItem) {
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
((AdjustedJMenu) _native).setText(menuItem.getText());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setCallback(final MenuItem menuItem) {
|
||||||
|
// can't have a callback for menus!
|
||||||
|
}
|
||||||
|
|
||||||
|
// is overridden in tray impl
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setShortcut(final MenuItem menuItem) {
|
||||||
|
char shortcut = menuItem.getShortcut();
|
||||||
|
// yikes...
|
||||||
|
final int vKey = SystemTrayFixes.getVirtualKey(shortcut);
|
||||||
|
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
((AdjustedJMenu) _native).setMnemonic(vKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void add(final Menu parentMenu, final Entry entry, final int index) {
|
||||||
|
// must always be called on the EDT
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
if (entry instanceof Menu) {
|
||||||
|
SwingMenu swingMenu = new SwingMenu(SwingMenu.this);
|
||||||
|
((Menu) entry).bind(swingMenu, parentMenu, parentMenu.getSystemTray());
|
||||||
|
}
|
||||||
|
else if (entry instanceof Separator) {
|
||||||
|
SwingMenuItemSeparator swingEntrySeparator = new SwingMenuItemSeparator(SwingMenu.this);
|
||||||
|
entry.bind(swingEntrySeparator, parentMenu, parentMenu.getSystemTray());
|
||||||
|
}
|
||||||
|
else if (entry instanceof Checkbox) {
|
||||||
|
SwingMenuItemCheckbox swingEntryCheckbox = new SwingMenuItemCheckbox(SwingMenu.this);
|
||||||
|
((Checkbox) entry).bind(swingEntryCheckbox, parentMenu, parentMenu.getSystemTray());
|
||||||
|
}
|
||||||
|
else if (entry instanceof Status) {
|
||||||
|
SwingMenuItemStatus swingEntryStatus = new SwingMenuItemStatus(SwingMenu.this);
|
||||||
|
((Status) entry).bind(swingEntryStatus, parentMenu, parentMenu.getSystemTray());
|
||||||
|
}
|
||||||
|
else if (entry instanceof MenuItem) {
|
||||||
|
SwingMenuItem swingMenuItem = new SwingMenuItem(SwingMenu.this);
|
||||||
|
((MenuItem) entry).bind(swingMenuItem, parentMenu, parentMenu.getSystemTray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOT ALWAYS CALLED ON EDT
|
* This removes all menu entries from this menu AND this menu from it's parent
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final
|
public synchronized
|
||||||
void addSeparator() {
|
|
||||||
dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
Entry entry = new SwingEntrySeparator(SwingMenu.this);
|
|
||||||
menuEntries.add(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
|
|
||||||
// public
|
|
||||||
// Entry addWidget(final JComponent widget) {
|
|
||||||
// if (widget == null) {
|
|
||||||
// throw new NullPointerException("Widget cannot be null");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final AtomicReference<Entry> value = new AtomicReference<Entry>();
|
|
||||||
//
|
|
||||||
// dispatchAndWait(new Runnable() {
|
|
||||||
// @Override
|
|
||||||
// public
|
|
||||||
// void run() {
|
|
||||||
// synchronized (menuEntries) {
|
|
||||||
// // must always be called on the EDT
|
|
||||||
// Entry entry = new SwingEntryWidget(SwingMenu.this, widget);
|
|
||||||
// value.set(entry);
|
|
||||||
// menuEntries.add(entry);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// return value.get();
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// public here so that Swing/Gtk/AppIndicator can access this
|
|
||||||
public final
|
|
||||||
void setStatus(final String statusText) {
|
|
||||||
final SwingMenu _this = this;
|
|
||||||
dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
// status is ALWAYS at 0 index...
|
|
||||||
SwingEntry menuEntry = null;
|
|
||||||
if (!menuEntries.isEmpty()) {
|
|
||||||
menuEntry = (SwingEntry) menuEntries.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (menuEntry instanceof Status) {
|
|
||||||
// set the text or delete...
|
|
||||||
|
|
||||||
if (statusText == null) {
|
|
||||||
// delete
|
|
||||||
remove(menuEntry);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// set text
|
|
||||||
menuEntry.setText(statusText);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// create a new one
|
|
||||||
menuEntry = new SwingEntryStatus(_this, statusText);
|
|
||||||
// status is ALWAYS at 0 index...
|
|
||||||
menuEntries.add(0, menuEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setShortcut(final char key) {
|
|
||||||
if (_native instanceof JMenuItem) {
|
|
||||||
// yikes...
|
|
||||||
final int vKey = SystemTrayFixes.getVirtualKey(key);
|
|
||||||
dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
((JMenuItem) _native).setMnemonic(vKey);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void remove() {
|
void remove() {
|
||||||
dispatchAndWait(new Runnable() {
|
dispatch(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
_native.setVisible(false);
|
_native.setVisible(false);
|
||||||
if (_native instanceof TrayPopup) {
|
_native.removeAll();
|
||||||
((TrayPopup) _native).close();
|
|
||||||
}
|
|
||||||
|
|
||||||
SwingMenu parent = (SwingMenu) getParent();
|
|
||||||
if (parent != null) {
|
if (parent != null) {
|
||||||
parent._native.remove(_native);
|
parent._native.remove(_native);
|
||||||
|
} else {
|
||||||
|
// have to dispose of the tray popup hidden frame, otherwise the app will never close (because this will hold it open)
|
||||||
|
((TrayPopup) _native).close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
161
src/dorkbox/systemTray/swingUI/SwingMenuItem.java
Normal file
161
src/dorkbox/systemTray/swingUI/SwingMenuItem.java
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014 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.swingUI;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.MenuItem;
|
||||||
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
import dorkbox.systemTray.util.MenuItemHook;
|
||||||
|
import dorkbox.systemTray.util.SystemTrayFixes;
|
||||||
|
import dorkbox.util.SwingUtil;
|
||||||
|
|
||||||
|
class SwingMenuItem implements MenuItemHook {
|
||||||
|
|
||||||
|
private final SwingMenu parent;
|
||||||
|
private final JMenuItem _native = new AdjustedJMenuItem();
|
||||||
|
|
||||||
|
private volatile boolean hasLegitIcon = false;
|
||||||
|
private volatile ActionListener swingCallback;
|
||||||
|
|
||||||
|
// this is ALWAYS called on the EDT.
|
||||||
|
SwingMenuItem(final SwingMenu parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
parent._native.add(_native);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
boolean hasImage() {
|
||||||
|
return hasLegitIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setImage(final MenuItem menuItem) {
|
||||||
|
final File imageFile = menuItem.getImage();
|
||||||
|
hasLegitIcon = imageFile != null;
|
||||||
|
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
if (imageFile != null) {
|
||||||
|
ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath());
|
||||||
|
_native.setIcon(origIcon);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_native.setIcon(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setEnabled(final MenuItem menuItem) {
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
_native.setEnabled(menuItem.getEnabled());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setText(final MenuItem menuItem) {
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
_native.setText(menuItem.getText());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setCallback(final MenuItem menuItem) {
|
||||||
|
if (swingCallback != null) {
|
||||||
|
_native.removeActionListener(swingCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuItem.getCallback() != null) {
|
||||||
|
_native.setEnabled(true);
|
||||||
|
swingCallback = new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void actionPerformed(ActionEvent e) {
|
||||||
|
// we want it to run on the EDT, but with our own action event info (so it is consistent across all platforms)
|
||||||
|
ActionListener cb = menuItem.getCallback();
|
||||||
|
if (cb != null) {
|
||||||
|
try {
|
||||||
|
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_native.addActionListener(swingCallback);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_native.setEnabled(false);
|
||||||
|
swingCallback = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setShortcut(final MenuItem menuItem) {
|
||||||
|
char shortcut = menuItem.getShortcut();
|
||||||
|
// yikes...
|
||||||
|
final int vKey = SystemTrayFixes.getVirtualKey(shortcut);
|
||||||
|
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
_native.setMnemonic(vKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void remove() {
|
||||||
|
//noinspection Duplicates
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
if (swingCallback != null) {
|
||||||
|
_native.removeActionListener(swingCallback);
|
||||||
|
swingCallback = null;
|
||||||
|
}
|
||||||
|
parent._native.remove(_native);
|
||||||
|
_native.removeAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
171
src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java
Normal file
171
src/dorkbox/systemTray/swingUI/SwingMenuItemCheckbox.java
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014 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.swingUI;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.Checkbox;
|
||||||
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
import dorkbox.systemTray.util.ImageUtils;
|
||||||
|
import dorkbox.systemTray.util.MenuCheckboxHook;
|
||||||
|
import dorkbox.systemTray.util.SystemTrayFixes;
|
||||||
|
import dorkbox.util.SwingUtil;
|
||||||
|
|
||||||
|
class SwingMenuItemCheckbox implements MenuCheckboxHook {
|
||||||
|
|
||||||
|
private final SwingMenu parent;
|
||||||
|
private final JMenuItem _native = new AdjustedJMenuItem();
|
||||||
|
|
||||||
|
private volatile boolean isChecked = false;
|
||||||
|
|
||||||
|
private volatile ActionListener swingCallback;
|
||||||
|
|
||||||
|
private static ImageIcon checkedIcon;
|
||||||
|
private static ImageIcon uncheckedIcon;
|
||||||
|
|
||||||
|
// this is ALWAYS called on the EDT.
|
||||||
|
SwingMenuItemCheckbox(final SwingMenu parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
parent._native.add(_native);
|
||||||
|
|
||||||
|
if (checkedIcon == null) {
|
||||||
|
// from Brankic1979, public domain
|
||||||
|
File checkedFile = ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, ImageUtils.class.getResource("checked_32.png"), true);
|
||||||
|
checkedIcon = new ImageIcon(checkedFile.getAbsolutePath());
|
||||||
|
|
||||||
|
File uncheckedFile = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE);
|
||||||
|
uncheckedIcon = new ImageIcon(uncheckedFile.getAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkbox image is always present
|
||||||
|
public
|
||||||
|
boolean hasImage() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setEnabled(final Checkbox menuItem) {
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
_native.setEnabled(menuItem.getEnabled());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setText(final Checkbox menuItem) {
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
_native.setText(menuItem.getText());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setCallback(final Checkbox menuItem) {
|
||||||
|
if (swingCallback != null) {
|
||||||
|
_native.removeActionListener(swingCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
swingCallback = new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void actionPerformed(ActionEvent e) {
|
||||||
|
// this will run on the EDT, since we are calling it from the EDT
|
||||||
|
menuItem.setChecked(!isChecked);
|
||||||
|
|
||||||
|
// we want it to run on the EDT, but with our own action event info (so it is consistent across all platforms)
|
||||||
|
ActionListener cb = menuItem.getCallback();
|
||||||
|
if (cb != null) {
|
||||||
|
try {
|
||||||
|
cb.actionPerformed(new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
SystemTray.logger.error("Error calling menu entry {} click event.", menuItem.getText(), throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_native.addActionListener(swingCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setShortcut(final Checkbox menuItem) {
|
||||||
|
char shortcut = menuItem.getShortcut();
|
||||||
|
// yikes...
|
||||||
|
final int vKey = SystemTrayFixes.getVirtualKey(shortcut);
|
||||||
|
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
_native.setMnemonic(vKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setChecked(final Checkbox menuItem) {
|
||||||
|
this.isChecked = menuItem.getChecked();
|
||||||
|
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
if (isChecked) {
|
||||||
|
_native.setIcon(checkedIcon);
|
||||||
|
} else {
|
||||||
|
_native.setIcon(uncheckedIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void remove() {
|
||||||
|
//noinspection Duplicates
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
if (swingCallback != null) {
|
||||||
|
_native.removeActionListener(swingCallback);
|
||||||
|
swingCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent._native.remove(_native);
|
||||||
|
_native.removeAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,37 +15,22 @@
|
||||||
*/
|
*/
|
||||||
package dorkbox.systemTray.swingUI;
|
package dorkbox.systemTray.swingUI;
|
||||||
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import javax.swing.JSeparator;
|
import javax.swing.JSeparator;
|
||||||
|
|
||||||
class SwingEntrySeparator extends SwingEntry implements dorkbox.systemTray.Separator {
|
import dorkbox.systemTray.util.EntryHook;
|
||||||
|
import dorkbox.util.SwingUtil;
|
||||||
|
|
||||||
|
class SwingMenuItemSeparator implements EntryHook {
|
||||||
|
|
||||||
|
private final SwingMenu parent;
|
||||||
|
private final JSeparator _native = new JSeparator(JSeparator.HORIZONTAL);
|
||||||
|
|
||||||
// this is ALWAYS called on the EDT.
|
// this is ALWAYS called on the EDT.
|
||||||
SwingEntrySeparator(final SwingMenu parent) {
|
SwingMenuItemSeparator(final SwingMenu parent) {
|
||||||
super(parent, new JSeparator(JSeparator.HORIZONTAL));
|
this.parent = parent;
|
||||||
|
parent._native.add(_native);
|
||||||
}
|
}
|
||||||
|
|
||||||
// called in the EDT thread
|
|
||||||
@Override
|
|
||||||
void renderText(final String text) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void setImage_(final File imageFile) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
void removePrivate() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void setShortcut(final char key) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
public
|
||||||
boolean hasImage() {
|
boolean hasImage() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -53,6 +38,14 @@ class SwingEntrySeparator extends SwingEntry implements dorkbox.systemTray.Separ
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void setCallback(final ActionListener callback) {
|
void remove() {
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
parent._native.remove(_native);
|
||||||
|
_native.removeAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
76
src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java
Normal file
76
src/dorkbox/systemTray/swingUI/SwingMenuItemStatus.java
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2014 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.swingUI;
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
|
||||||
|
import javax.swing.JMenuItem;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.util.MenuStatusHook;
|
||||||
|
import dorkbox.systemTray.util.Status;
|
||||||
|
import dorkbox.util.SwingUtil;
|
||||||
|
|
||||||
|
class SwingMenuItemStatus implements MenuStatusHook {
|
||||||
|
|
||||||
|
private final SwingMenu parent;
|
||||||
|
private final JMenuItem _native = new AdjustedJMenuItem();
|
||||||
|
|
||||||
|
// this is ALWAYS called on the EDT.
|
||||||
|
SwingMenuItemStatus(final SwingMenu parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
|
||||||
|
// status is ALWAYS at 0 index...
|
||||||
|
parent._native.add(_native, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public
|
||||||
|
boolean hasImage() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setText(final Status menuItem) {
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
Font font = _native.getFont();
|
||||||
|
Font font1 = font.deriveFont(Font.BOLD);
|
||||||
|
_native.setFont(font1);
|
||||||
|
|
||||||
|
_native.setText(menuItem.getText());
|
||||||
|
|
||||||
|
// this makes sure it can't be selected
|
||||||
|
_native.setEnabled(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void remove() {
|
||||||
|
SwingUtil.invokeLater(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
parent._native.remove(_native);
|
||||||
|
_native.removeAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,9 @@ import com.sun.jna.NativeLong;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
import com.sun.jna.ptr.PointerByReference;
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.MenuItem;
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
import dorkbox.systemTray.Tray;
|
||||||
import dorkbox.systemTray.jna.linux.AppIndicator;
|
import dorkbox.systemTray.jna.linux.AppIndicator;
|
||||||
import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct;
|
import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct;
|
||||||
import dorkbox.systemTray.jna.linux.GEventCallback;
|
import dorkbox.systemTray.jna.linux.GEventCallback;
|
||||||
|
@ -81,7 +83,7 @@ import dorkbox.util.SwingUtil;
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public
|
public
|
||||||
class _AppIndicatorTray extends SwingMenu {
|
class _AppIndicatorTray extends Tray implements SwingUI {
|
||||||
private volatile AppIndicatorInstanceStruct appIndicator;
|
private volatile AppIndicatorInstanceStruct appIndicator;
|
||||||
private boolean isActive = false;
|
private boolean isActive = false;
|
||||||
private final Runnable popupRunnable;
|
private final Runnable popupRunnable;
|
||||||
|
@ -101,6 +103,7 @@ class _AppIndicatorTray extends SwingMenu {
|
||||||
|
|
||||||
// is the system tray visible or not.
|
// is the system tray visible or not.
|
||||||
private volatile boolean visible = true;
|
private volatile boolean visible = true;
|
||||||
|
private volatile File image;
|
||||||
|
|
||||||
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
||||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||||
|
@ -108,9 +111,109 @@ class _AppIndicatorTray extends SwingMenu {
|
||||||
|
|
||||||
public
|
public
|
||||||
_AppIndicatorTray(final SystemTray systemTray) {
|
_AppIndicatorTray(final SystemTray systemTray) {
|
||||||
super(systemTray,null, new TrayPopup());
|
super();
|
||||||
|
|
||||||
TrayPopup popupMenu = (TrayPopup) _native;
|
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
|
||||||
|
final SwingMenu swingMenu = new SwingMenu(null) {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setEnabled(final MenuItem menuItem) {
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
boolean enabled = menuItem.getEnabled();
|
||||||
|
|
||||||
|
if (visible && !enabled) {
|
||||||
|
// STATUS_PASSIVE hides the indicator
|
||||||
|
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
else if (!visible && enabled) {
|
||||||
|
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setImage(final MenuItem menuItem) {
|
||||||
|
image = menuItem.getImage();
|
||||||
|
if (image == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
AppIndicator.app_indicator_set_icon(appIndicator, image.getAbsolutePath());
|
||||||
|
|
||||||
|
if (!isActive) {
|
||||||
|
isActive = true;
|
||||||
|
|
||||||
|
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
||||||
|
|
||||||
|
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set
|
||||||
|
hookMenuOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// needs to be on EDT
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
((TrayPopup) _native).setTitleBarImage(image);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setText(final MenuItem menuItem) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setShortcut(final MenuItem menuItem) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void remove() {
|
||||||
|
if (!shuttingDown.getAndSet(true)) {
|
||||||
|
// must happen asap, so our hook properly notices we are in shutdown mode
|
||||||
|
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
|
||||||
|
appIndicator = null;
|
||||||
|
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
// STATUS_PASSIVE hides the indicator
|
||||||
|
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
|
||||||
|
Pointer p = savedAppIndicator.getPointer();
|
||||||
|
Gobject.g_object_unref(p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// does not need to be called on the dispatch (it does that)
|
||||||
|
Gtk.shutdownGui();
|
||||||
|
|
||||||
|
super.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
|
||||||
popupMenu.pack();
|
popupMenu.pack();
|
||||||
popupMenu.setFocusable(true);
|
popupMenu.setFocusable(true);
|
||||||
popupMenu.setOnHideRunnable(new Runnable() {
|
popupMenu.setOnHideRunnable(new Runnable() {
|
||||||
|
@ -123,7 +226,7 @@ class _AppIndicatorTray extends SwingMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed.
|
// Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed.
|
||||||
Gtk.dispatch(new Runnable() {
|
Gtk.dispatchAndWait(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void run() {
|
||||||
|
@ -141,7 +244,7 @@ class _AppIndicatorTray extends SwingMenu {
|
||||||
Point point = MouseInfo.getPointerInfo()
|
Point point = MouseInfo.getPointerInfo()
|
||||||
.getLocation();
|
.getLocation();
|
||||||
|
|
||||||
TrayPopup popupMenu = (TrayPopup) _native;
|
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
|
||||||
popupMenu.doShow(point, SystemTray.DEFAULT_TRAY_SIZE);
|
popupMenu.doShow(point, SystemTray.DEFAULT_TRAY_SIZE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -162,6 +265,8 @@ class _AppIndicatorTray extends SwingMenu {
|
||||||
});
|
});
|
||||||
|
|
||||||
Gtk.waitForStartup();
|
Gtk.waitForStartup();
|
||||||
|
|
||||||
|
bind(swingMenu, null, systemTray);
|
||||||
}
|
}
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -195,87 +300,9 @@ class _AppIndicatorTray extends SwingMenu {
|
||||||
AppIndicator.app_indicator_set_menu(appIndicator, dummyMenu);
|
AppIndicator.app_indicator_set_menu(appIndicator, dummyMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final
|
|
||||||
void shutdown() {
|
|
||||||
if (!shuttingDown.getAndSet(true)) {
|
|
||||||
// must happen asap, so our hook properly notices we are in shutdown mode
|
|
||||||
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
|
|
||||||
appIndicator = null;
|
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// STATUS_PASSIVE hides the indicator
|
|
||||||
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
|
|
||||||
Pointer p = savedAppIndicator.getPointer();
|
|
||||||
Gobject.g_object_unref(p);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// does not need to be called on the dispatch (it does that)
|
|
||||||
Gtk.shutdownGui();
|
|
||||||
|
|
||||||
// uses EDT
|
|
||||||
removeAll();
|
|
||||||
remove(); // remove ourselves from our parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final
|
public final
|
||||||
boolean hasImage() {
|
boolean hasImage() {
|
||||||
return true;
|
return image != null;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setImage_(final File imageFile) {
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
AppIndicator.app_indicator_set_icon(appIndicator, imageFile.getAbsolutePath());
|
|
||||||
|
|
||||||
if (!isActive) {
|
|
||||||
isActive = true;
|
|
||||||
|
|
||||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
|
||||||
|
|
||||||
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set
|
|
||||||
hookMenuOpen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// needs to be on EDT
|
|
||||||
dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
((TrayPopup) _native).setTitleBarImage(imageFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setEnabled(final boolean setEnabled) {
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
if (visible && !setEnabled) {
|
|
||||||
// STATUS_PASSIVE hides the indicator
|
|
||||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
|
|
||||||
visible = false;
|
|
||||||
}
|
|
||||||
else if (!visible && setEnabled) {
|
|
||||||
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,9 @@ import javax.swing.JPopupMenu;
|
||||||
import com.sun.jna.NativeLong;
|
import com.sun.jna.NativeLong;
|
||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.MenuItem;
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
import dorkbox.systemTray.Tray;
|
||||||
import dorkbox.systemTray.jna.linux.GEventCallback;
|
import dorkbox.systemTray.jna.linux.GEventCallback;
|
||||||
import dorkbox.systemTray.jna.linux.GdkEventButton;
|
import dorkbox.systemTray.jna.linux.GdkEventButton;
|
||||||
import dorkbox.systemTray.jna.linux.Gobject;
|
import dorkbox.systemTray.jna.linux.Gobject;
|
||||||
|
@ -41,7 +43,7 @@ import dorkbox.systemTray.jna.linux.Gtk;
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public
|
public
|
||||||
class _GtkStatusIconTray extends SwingMenu {
|
class _GtkStatusIconTray extends Tray implements SwingUI {
|
||||||
private volatile Pointer trayIcon;
|
private volatile Pointer trayIcon;
|
||||||
|
|
||||||
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
|
||||||
|
@ -50,20 +52,114 @@ class _GtkStatusIconTray extends SwingMenu {
|
||||||
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
|
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
|
||||||
private final List<Object> gtkCallbacks = new ArrayList<Object>();
|
private final List<Object> gtkCallbacks = new ArrayList<Object>();
|
||||||
|
|
||||||
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
|
||||||
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
private AtomicBoolean shuttingDown = new AtomicBoolean();
|
||||||
|
|
||||||
private volatile boolean isActive = false;
|
private volatile boolean isActive = false;
|
||||||
|
|
||||||
// is the system tray visible or not.
|
// is the system tray visible or not.
|
||||||
private volatile boolean visible = true;
|
private volatile boolean visible = true;
|
||||||
|
private volatile File image;
|
||||||
|
|
||||||
// called on the EDT
|
// called on the EDT
|
||||||
public
|
public
|
||||||
_GtkStatusIconTray(final SystemTray systemTray) {
|
_GtkStatusIconTray(final SystemTray systemTray) {
|
||||||
super(systemTray, null, new TrayPopup());
|
super();
|
||||||
|
|
||||||
JPopupMenu popupMenu = (JPopupMenu) _native;
|
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
|
||||||
|
final SwingMenu swingMenu = new SwingMenu(null) {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setEnabled(final MenuItem menuItem) {
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
boolean enabled = menuItem.getEnabled();
|
||||||
|
|
||||||
|
if (visible && !enabled) {
|
||||||
|
Gtk.gtk_status_icon_set_visible(trayIcon, enabled);
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
else if (!visible && enabled) {
|
||||||
|
Gtk.gtk_status_icon_set_visible(trayIcon, enabled);
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setImage(final MenuItem menuItem) {
|
||||||
|
image = menuItem.getImage();
|
||||||
|
if (image == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
Gtk.gtk_status_icon_set_from_file(trayIcon, image.getAbsolutePath());
|
||||||
|
|
||||||
|
if (!isActive) {
|
||||||
|
isActive = true;
|
||||||
|
Gtk.gtk_status_icon_set_visible(trayIcon, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// needs to be on EDT
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
((TrayPopup) _native).setTitleBarImage(image);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setText(final MenuItem menuItem) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setShortcut(final MenuItem menuItem) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void remove() {
|
||||||
|
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
|
||||||
|
if (!shuttingDown.getAndSet(true)) {
|
||||||
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
// this hides the indicator
|
||||||
|
Gtk.gtk_status_icon_set_visible(trayIcon, false);
|
||||||
|
Gobject.g_object_unref(trayIcon);
|
||||||
|
|
||||||
|
// mark for GC
|
||||||
|
trayIcon = null;
|
||||||
|
gtkCallbacks.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// does not need to be called on the dispatch (it does that)
|
||||||
|
Gtk.shutdownGui();
|
||||||
|
|
||||||
|
super.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
JPopupMenu popupMenu = (JPopupMenu) swingMenu._native;
|
||||||
popupMenu.pack();
|
popupMenu.pack();
|
||||||
popupMenu.setFocusable(true);
|
popupMenu.setFocusable(true);
|
||||||
|
|
||||||
|
@ -74,15 +170,11 @@ class _GtkStatusIconTray extends SwingMenu {
|
||||||
Point point = MouseInfo.getPointerInfo()
|
Point point = MouseInfo.getPointerInfo()
|
||||||
.getLocation();
|
.getLocation();
|
||||||
|
|
||||||
TrayPopup popupMenu = (TrayPopup) _native;
|
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
|
||||||
popupMenu.doShow(point, 0);
|
popupMenu.doShow(point, 0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
|
||||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
|
||||||
// trayIcon.setToolTip("app name");
|
|
||||||
|
|
||||||
Gtk.startGui();
|
Gtk.startGui();
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
Gtk.dispatch(new Runnable() {
|
||||||
|
@ -100,7 +192,7 @@ class _GtkStatusIconTray extends SwingMenu {
|
||||||
// BUTTON_PRESS only (any mouse click)
|
// BUTTON_PRESS only (any mouse click)
|
||||||
if (event.type == 4) {
|
if (event.type == 4) {
|
||||||
// show the swing menu on the EDT
|
// show the swing menu on the EDT
|
||||||
dispatch(popupRunnable);
|
swingMenu.dispatch(popupRunnable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -140,76 +232,13 @@ class _GtkStatusIconTray extends SwingMenu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bind(swingMenu, null, systemTray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
@SuppressWarnings("FieldRepeatedlyAccessedInMethod")
|
|
||||||
public
|
public
|
||||||
void shutdown() {
|
boolean hasImage() {
|
||||||
if (!shuttingDown.getAndSet(true)) {
|
return image != null;
|
||||||
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
// this hides the indicator
|
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon, false);
|
|
||||||
Gobject.g_object_unref(trayIcon);
|
|
||||||
|
|
||||||
// mark for GC
|
|
||||||
trayIcon = null;
|
|
||||||
gtkCallbacks.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// does not need to be called on the dispatch (it does that)
|
|
||||||
Gtk.shutdownGui();
|
|
||||||
|
|
||||||
// uses EDT
|
|
||||||
removeAll();
|
|
||||||
remove(); // remove ourselves from our parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void setImage_(final File iconFile) {
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
Gtk.gtk_status_icon_set_from_file(trayIcon, iconFile.getAbsolutePath());
|
|
||||||
|
|
||||||
if (!isActive) {
|
|
||||||
isActive = true;
|
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// needs to be on EDT
|
|
||||||
dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
((TrayPopup) _native).setTitleBarImage(iconFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public
|
|
||||||
void setEnabled(final boolean setEnabled) {
|
|
||||||
Gtk.dispatch(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
if (visible && !setEnabled) {
|
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
|
|
||||||
visible = false;
|
|
||||||
} else if (!visible && setEnabled) {
|
|
||||||
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
|
|
||||||
visible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,9 @@ import java.io.File;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.MenuItem;
|
||||||
|
import dorkbox.systemTray.Tray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for handling all system tray interaction, via Swing.
|
* Class for handling all system tray interaction, via Swing.
|
||||||
*
|
*
|
||||||
|
@ -35,8 +38,8 @@ import javax.swing.JPopupMenu;
|
||||||
* https://stackoverflow.com/questions/331407/java-trayicon-using-image-with-transparent-background/3882028#3882028
|
* https://stackoverflow.com/questions/331407/java-trayicon-using-image-with-transparent-background/3882028#3882028
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||||
public
|
public final
|
||||||
class _SwingTray extends SwingMenu {
|
class _SwingTray extends Tray implements SwingUI {
|
||||||
private volatile SystemTray tray;
|
private volatile SystemTray tray;
|
||||||
private volatile TrayIcon trayIcon;
|
private volatile TrayIcon trayIcon;
|
||||||
|
|
||||||
|
@ -46,7 +49,7 @@ class _SwingTray extends SwingMenu {
|
||||||
// Called in the EDT
|
// Called in the EDT
|
||||||
public
|
public
|
||||||
_SwingTray(final dorkbox.systemTray.SystemTray systemTray) {
|
_SwingTray(final dorkbox.systemTray.SystemTray systemTray) {
|
||||||
super(systemTray, null, new TrayPopup());
|
super();
|
||||||
|
|
||||||
if (!SystemTray.isSupported()) {
|
if (!SystemTray.isSupported()) {
|
||||||
throw new RuntimeException("System Tray is not supported in this configuration! Please write an issue and include your OS " +
|
throw new RuntimeException("System Tray is not supported in this configuration! Please write an issue and include your OS " +
|
||||||
|
@ -54,87 +57,124 @@ class _SwingTray extends SwingMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
_SwingTray.this.tray = SystemTray.getSystemTray();
|
_SwingTray.this.tray = SystemTray.getSystemTray();
|
||||||
}
|
|
||||||
|
|
||||||
public
|
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
|
||||||
void shutdown() {
|
final SwingMenu swingMenu = new SwingMenu(null) {
|
||||||
dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void setEnabled(final MenuItem menuItem) {
|
||||||
removeAll();
|
dispatch(new Runnable() {
|
||||||
remove();
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
boolean enabled = menuItem.getEnabled();
|
||||||
|
|
||||||
tray.remove(trayIcon);
|
if (visible && !enabled) {
|
||||||
}
|
tray.remove(trayIcon);
|
||||||
});
|
visible = false;
|
||||||
}
|
}
|
||||||
|
else if (!visible && enabled) {
|
||||||
public
|
try {
|
||||||
void setImage_(final File iconFile) {
|
tray.add(trayIcon);
|
||||||
dispatch(new Runnable() {
|
visible = true;
|
||||||
@Override
|
} catch (AWTException e) {
|
||||||
public
|
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e);
|
||||||
void run() {
|
}
|
||||||
// stupid java won't scale it right away, so we have to do this twice to get the correct size
|
|
||||||
final Image trayImage = new ImageIcon(iconFile.getAbsolutePath()).getImage();
|
|
||||||
trayImage.flush();
|
|
||||||
|
|
||||||
if (trayIcon == null) {
|
|
||||||
// here we init. everything
|
|
||||||
trayIcon = new TrayIcon(trayImage);
|
|
||||||
|
|
||||||
JPopupMenu popupMenu = (JPopupMenu) _native;
|
|
||||||
popupMenu.pack();
|
|
||||||
popupMenu.setFocusable(true);
|
|
||||||
|
|
||||||
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
|
||||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
|
||||||
// trayIcon.setToolTip("app name");
|
|
||||||
|
|
||||||
trayIcon.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void mousePressed(MouseEvent e) {
|
|
||||||
TrayPopup popupMenu = (TrayPopup) _native;
|
|
||||||
popupMenu.doShow(e.getPoint(), 0);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
tray.add(trayIcon);
|
|
||||||
} catch (AWTException e) {
|
|
||||||
dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e);
|
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
trayIcon.setImage(trayImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
((TrayPopup) _native).setTitleBarImage(iconFile);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
|
||||||
public
|
|
||||||
void setEnabled(final boolean setEnabled) {
|
|
||||||
dispatch(new Runnable() {
|
|
||||||
@Override
|
@Override
|
||||||
public
|
public
|
||||||
void run() {
|
void setImage(final MenuItem menuItem) {
|
||||||
if (visible && !setEnabled) {
|
final File image = menuItem.getImage();
|
||||||
tray.remove(trayIcon);
|
if (image == null) {
|
||||||
visible = false;
|
return;
|
||||||
}
|
}
|
||||||
else if (!visible && setEnabled) {
|
|
||||||
try {
|
dispatch(new Runnable() {
|
||||||
tray.add(trayIcon);
|
@Override
|
||||||
visible = true;
|
public
|
||||||
} catch (AWTException e) {
|
void run() {
|
||||||
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e);
|
// stupid java won't scale it right away, so we have to do this twice to get the correct size
|
||||||
|
final Image trayImage = new ImageIcon(image.getAbsolutePath()).getImage();
|
||||||
|
trayImage.flush();
|
||||||
|
|
||||||
|
if (trayIcon == null) {
|
||||||
|
// here we init. everything
|
||||||
|
trayIcon = new TrayIcon(trayImage);
|
||||||
|
|
||||||
|
JPopupMenu popupMenu = (JPopupMenu) _native;
|
||||||
|
popupMenu.pack();
|
||||||
|
popupMenu.setFocusable(true);
|
||||||
|
|
||||||
|
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
|
||||||
|
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||||
|
// trayIcon.setToolTip("app name");
|
||||||
|
|
||||||
|
trayIcon.addMouseListener(new MouseAdapter() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void mousePressed(MouseEvent e) {
|
||||||
|
TrayPopup popupMenu = (TrayPopup) _native;
|
||||||
|
popupMenu.doShow(e.getPoint(), 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
tray.add(trayIcon);
|
||||||
|
} catch (AWTException e) {
|
||||||
|
dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trayIcon.setImage(trayImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
((TrayPopup) _native).setTitleBarImage(image);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setText(final MenuItem menuItem) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void setShortcut(final MenuItem menuItem) {
|
||||||
|
// no op
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void remove() {
|
||||||
|
dispatch(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
void run() {
|
||||||
|
if (trayIcon != null) {
|
||||||
|
tray.remove(trayIcon);
|
||||||
|
trayIcon = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
tray = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
super.remove();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
bind(swingMenu, null, systemTray);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public
|
||||||
|
boolean hasImage() {
|
||||||
|
return tray.getTrayIcons().length > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
src/dorkbox/systemTray/util/EntryHook.java
Normal file
9
src/dorkbox/systemTray/util/EntryHook.java
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package dorkbox.systemTray.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
interface EntryHook {
|
||||||
|
void remove();
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import java.awt.Graphics2D;
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
@ -44,6 +45,7 @@ import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.jna.Windows.User32;
|
import dorkbox.systemTray.jna.Windows.User32;
|
||||||
import dorkbox.util.CacheUtil;
|
import dorkbox.util.CacheUtil;
|
||||||
import dorkbox.util.FileUtil;
|
import dorkbox.util.FileUtil;
|
||||||
|
import dorkbox.util.IO;
|
||||||
import dorkbox.util.LocationResolver;
|
import dorkbox.util.LocationResolver;
|
||||||
import dorkbox.util.OS;
|
import dorkbox.util.OS;
|
||||||
import dorkbox.util.process.ShellProcessBuilder;
|
import dorkbox.util.process.ShellProcessBuilder;
|
||||||
|
@ -357,12 +359,16 @@ class ImageUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized
|
public static synchronized
|
||||||
File resizeAndCache(final int size, final File file) {
|
File resizeAndCache(final int size, final File file, final boolean cacheResult) {
|
||||||
return resizeAndCache(size, file.getAbsolutePath());
|
return resizeAndCache(size, file.getAbsolutePath(), cacheResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized
|
public static synchronized
|
||||||
File resizeAndCache(final int size, final String fileName) {
|
File resizeAndCache(final int size, final String fileName, final boolean cacheResult) {
|
||||||
|
if (fileName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// check if we already have this file information saved to disk, based on size
|
// check if we already have this file information saved to disk, based on size
|
||||||
final String cacheName = size + "_" + fileName;
|
final String cacheName = size + "_" + fileName;
|
||||||
|
|
||||||
|
@ -393,7 +399,11 @@ class ImageUtils {
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public static synchronized
|
public static synchronized
|
||||||
File resizeAndCache(final int size, final URL imageUrl) {
|
File resizeAndCache(final int size, final URL imageUrl, final boolean cacheResult) {
|
||||||
|
if (imageUrl == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final String cacheName = size + "_" + imageUrl.getPath();
|
final String cacheName = size + "_" + imageUrl.getPath();
|
||||||
|
|
||||||
// if we already have this fileName, reuse it
|
// if we already have this fileName, reuse it
|
||||||
|
@ -449,16 +459,90 @@ class ImageUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@SuppressWarnings("Duplicates")
|
||||||
|
public static synchronized
|
||||||
|
File resizeAndCache(final int size, final Image image, final boolean cacheResult) {
|
||||||
|
if (image == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// final String cacheName = size + "_" + imageUrl.getPath();
|
||||||
|
//
|
||||||
|
// // if we already have this fileName, reuse it
|
||||||
|
// final File check = getIfCachedOrError(cacheName);
|
||||||
|
// if (check != null) {
|
||||||
|
// return check;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // no cached file, so we resize then save the new one.
|
||||||
|
// boolean needsResize = true;
|
||||||
|
// try {
|
||||||
|
// InputStream inputStream = imageUrl.openStream();
|
||||||
|
// Dimension imageSize = getImageSize(inputStream);
|
||||||
|
// //noinspection NumericCastThatLosesPrecision
|
||||||
|
// if (size == ((int) imageSize.getWidth()) && size == ((int) imageSize.getHeight())) {
|
||||||
|
// // we can reuse this URL (it's the correct size).
|
||||||
|
// needsResize = false;
|
||||||
|
// }
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// // have to serve up the error image instead.
|
||||||
|
// SystemTray.logger.error("Error resizing image. Using error icon instead", e);
|
||||||
|
// return getErrorImage(cacheName);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (needsResize) {
|
||||||
|
// // we have to hop through hoops.
|
||||||
|
// try {
|
||||||
|
// File resizedFile = resizeFileNoCheck(size, imageUrl);
|
||||||
|
//
|
||||||
|
// // now cache that file
|
||||||
|
// try {
|
||||||
|
// return CacheUtil.save(cacheName, resizedFile);
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// // have to serve up the error image instead.
|
||||||
|
// SystemTray.logger.error("Error caching image. Using error icon instead", e);
|
||||||
|
// return getErrorImage(cacheName);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// // have to serve up the error image instead.
|
||||||
|
// SystemTray.logger.error("Error resizing image. Using error icon instead", e);
|
||||||
|
// return getErrorImage(cacheName);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// } else {
|
||||||
|
// // no resize necessary, just cache as is.
|
||||||
|
// try {
|
||||||
|
// return CacheUtil.save(cacheName, imageUrl);
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// // have to serve up the error image instead.
|
||||||
|
// SystemTray.logger.error("Error caching image. Using error icon instead", e);
|
||||||
|
// return getErrorImage(cacheName);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("Duplicates")
|
@SuppressWarnings("Duplicates")
|
||||||
public static synchronized
|
public static synchronized
|
||||||
File resizeAndCache(final int size, String cacheName, final InputStream imageStream) {
|
File resizeAndCache(final int size, InputStream imageStream, final boolean cacheResult) {
|
||||||
if (cacheName == null) {
|
if (imageStream == null) {
|
||||||
cacheName = CacheUtil.createNameAsHash(imageStream);
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// have to make a copy of the inputStream.
|
||||||
|
try {
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = IO.copyStream(imageStream);
|
||||||
|
imageStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Unable to read from inputStream.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we already have this file information saved to disk, based on size
|
// check if we already have this file information saved to disk, based on size
|
||||||
cacheName = size + "_" + cacheName;
|
final String cacheName = size + "_" + CacheUtil.createNameAsHash(imageStream);
|
||||||
|
((ByteArrayInputStream) imageStream).reset();
|
||||||
|
|
||||||
|
|
||||||
// if we already have this fileName, reuse it
|
// if we already have this fileName, reuse it
|
||||||
final File check = getIfCachedOrError(cacheName);
|
final File check = getIfCachedOrError(cacheName);
|
||||||
|
@ -513,12 +597,6 @@ class ImageUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static
|
|
||||||
File resizeAndCache(final int size, final InputStream imageStream) {
|
|
||||||
return resizeAndCache(size, null, imageStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes the given URL to the specified size. No checks are performed if it's the correct size to begin with.
|
* Resizes the given URL to the specified size. No checks are performed if it's the correct size to begin with.
|
||||||
*
|
*
|
||||||
|
|
|
@ -16,44 +16,27 @@
|
||||||
package dorkbox.systemTray.util;
|
package dorkbox.systemTray.util;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import dorkbox.systemTray.Checkbox;
|
|
||||||
import dorkbox.systemTray.Entry;
|
import dorkbox.systemTray.Entry;
|
||||||
import dorkbox.systemTray.Menu;
|
import dorkbox.systemTray.Menu;
|
||||||
import dorkbox.systemTray.Separator;
|
|
||||||
import dorkbox.systemTray.Status;
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
|
||||||
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
|
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
|
||||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||||
public abstract
|
public abstract
|
||||||
class MenuBase implements Menu {
|
class MenuBase extends Menu {
|
||||||
public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
|
// /**
|
||||||
private final int id = MenuBase.MENU_ID_COUNTER.getAndIncrement();
|
// * Called in the EDT/GTK dispatch threads
|
||||||
|
// *
|
||||||
protected final java.util.List<Entry> menuEntries = new ArrayList<Entry>();
|
// * @param systemTray the system tray (which is the object that sits in the system tray)
|
||||||
|
// * @param parent the parent of this menu, null if the parent is the system tray
|
||||||
private final SystemTray systemTray;
|
// */
|
||||||
private final Menu parent;
|
// public
|
||||||
|
// MenuBase(final SystemTray systemTray, final Menu parent) {
|
||||||
/**
|
// setSystemTray(systemTray);
|
||||||
* Called in the EDT/GTK dispatch threads
|
// setParent(parent);
|
||||||
*
|
// }
|
||||||
* @param systemTray the system tray (which is the object that sits in the system tray)
|
|
||||||
* @param parent the parent of this menu, null if the parent is the system tray
|
|
||||||
*/
|
|
||||||
public
|
|
||||||
MenuBase(final SystemTray systemTray, final Menu parent) {
|
|
||||||
this.systemTray = systemTray;
|
|
||||||
this.parent = parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract
|
protected abstract
|
||||||
void dispatch(final Runnable runnable);
|
void dispatch(final Runnable runnable);
|
||||||
|
@ -62,31 +45,43 @@ class MenuBase implements Menu {
|
||||||
void dispatchAndWait(final Runnable runnable);
|
void dispatchAndWait(final Runnable runnable);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will add a new menu entry
|
* Will add a new menu entry
|
||||||
* NOT ALWAYS CALLED ON DISPATCH
|
* NOT ALWAYS CALLED ON DISPATCH
|
||||||
*/
|
*/
|
||||||
protected abstract
|
// protected abstract
|
||||||
Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback);
|
// Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will add a new checkbox menu entry
|
* Will add a new checkbox menu entry
|
||||||
* NOT ALWAYS CALLED ON DISPATCH
|
* NOT ALWAYS CALLED ON DISPATCH
|
||||||
*/
|
*/
|
||||||
protected abstract
|
// protected abstract
|
||||||
Checkbox addCheckbox_(final String menuText, final ActionListener callback);
|
// Checkbox addCheckbox_(final String menuText, final ActionListener callback);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will add a new sub-menu entry
|
* Will add a new sub-menu entry
|
||||||
* NOT ALWAYS CALLED ON DISPATCH
|
* NOT ALWAYS CALLED ON DISPATCH
|
||||||
*/
|
*/
|
||||||
protected abstract
|
// protected abstract
|
||||||
Menu addMenu_(final String menuText, final File imagePath);
|
// Menu addMenu_(final String menuText, final File imagePath);
|
||||||
|
|
||||||
|
|
||||||
// public here so that Swing/Gtk/AppIndicator can override this
|
// // public here so that Swing/Gtk/AppIndicator can override this
|
||||||
protected abstract
|
// protected abstract
|
||||||
void setImage_(final File imageFile);
|
// void setImage_(final File imageFile);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,30 +90,8 @@ class MenuBase implements Menu {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Menu getParent() {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
SystemTray getSystemTray() {
|
|
||||||
return systemTray;
|
|
||||||
}
|
|
||||||
|
|
||||||
// public here so that Swing/Gtk/AppIndicator can access this
|
|
||||||
public final
|
|
||||||
String getStatus() {
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
Entry entry = menuEntries.get(0);
|
|
||||||
if (entry instanceof Status) {
|
|
||||||
return entry.getText();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
|
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
|
||||||
// public
|
// public
|
||||||
|
@ -146,316 +119,88 @@ class MenuBase implements Menu {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
@Override
|
// public final
|
||||||
public final
|
// Entry get(final String menuText) {
|
||||||
Entry get(final String menuText) {
|
// if (menuText == null || menuText.isEmpty()) {
|
||||||
if (menuText == null || menuText.isEmpty()) {
|
// return null;
|
||||||
return null;
|
// }
|
||||||
}
|
//
|
||||||
|
// // Must be wrapped in a synchronized block for object visibility
|
||||||
// Must be wrapped in a synchronized block for object visibility
|
// synchronized (menuEntries) {
|
||||||
synchronized (menuEntries) {
|
// for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
// final Entry entry = menuEntries.get(i);
|
||||||
final Entry entry = menuEntries.get(i);
|
//
|
||||||
|
// if (entry instanceof Separator || entry instanceof Status) {
|
||||||
if (entry instanceof Separator || entry instanceof Status) {
|
// continue;
|
||||||
continue;
|
// }
|
||||||
}
|
//
|
||||||
|
//// String text = entry.getText();
|
||||||
String text = entry.getText();
|
//
|
||||||
|
// // text can be null
|
||||||
// text can be null
|
//// if (menuText.equals(text)) {
|
||||||
if (menuText.equals(text)) {
|
//// return entry;
|
||||||
return entry;
|
//// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// return null;
|
||||||
return null;
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
// ignores status + separators
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Entry getFirst() {
|
|
||||||
return get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignores status + separators
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Entry getLast() {
|
|
||||||
// Must be wrapped in a synchronized block for object visibility
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
if (!menuEntries.isEmpty()) {
|
|
||||||
Entry entry;
|
|
||||||
for (int i = menuEntries.size()-1; i >= 0; i--) {
|
|
||||||
entry = menuEntries.get(i);
|
|
||||||
|
|
||||||
if (!(entry instanceof Separator || entry instanceof Status)) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignores status + separators
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Entry get(final int menuIndex) {
|
|
||||||
if (menuIndex < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be wrapped in a synchronized block for object visibility
|
|
||||||
synchronized (menuEntries) {
|
|
||||||
if (!menuEntries.isEmpty()) {
|
|
||||||
int count = 0;
|
|
||||||
for (Entry entry : menuEntries) {
|
|
||||||
if (entry instanceof Separator || entry instanceof Status) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count == menuIndex) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Entry addEntry(String menuText, ActionListener callback) {
|
|
||||||
return addEntry(menuText, (String) null, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Entry addEntry(String menuText, String imagePath, ActionListener callback) {
|
|
||||||
if (imagePath == null) {
|
|
||||||
return addEntry_(menuText, null, callback);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath), callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Entry addEntry(String menuText, URL imageUrl, ActionListener callback) {
|
|
||||||
if (imageUrl == null) {
|
|
||||||
return addEntry_(menuText, null, callback);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl), callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Entry addEntry(String menuText, String cacheName, InputStream imageStream, ActionListener callback) {
|
|
||||||
if (imageStream == null) {
|
|
||||||
return addEntry_(menuText, null, callback);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream), callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Entry addEntry(String menuText, InputStream imageStream, ActionListener callback) {
|
|
||||||
if (imageStream == null) {
|
|
||||||
return addEntry_(menuText, null, callback);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream), callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
Checkbox addCheckbox(final String menuText, final ActionListener callback) {
|
|
||||||
return addCheckbox_(menuText, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Menu addMenu(String menuText) {
|
|
||||||
return addMenu(menuText, (String) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Menu addMenu(String menuText, String imagePath) {
|
|
||||||
if (imagePath == null) {
|
|
||||||
return addMenu_(menuText, null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Menu addMenu(String menuText, URL imageUrl) {
|
|
||||||
if (imageUrl == null) {
|
|
||||||
return addMenu_(menuText, null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Menu addMenu(String menuText, String cacheName, InputStream imageStream) {
|
|
||||||
if (imageStream == null) {
|
|
||||||
return addMenu_(menuText, null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
Menu addMenu(String menuText, InputStream imageStream) {
|
|
||||||
if (imageStream == null) {
|
|
||||||
return addMenu_(menuText, null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void setImage(final File imageFile) {
|
|
||||||
if (imageFile == null) {
|
|
||||||
setImage_(null);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// /**
|
||||||
public final
|
// * This removes a menu entry from the dropdown menu.
|
||||||
void setImage(final String imagePath) {
|
// *
|
||||||
if (imagePath == null) {
|
// * @param entry This is the menu entry to remove
|
||||||
setImage_(null);
|
// */
|
||||||
}
|
// @Override
|
||||||
else {
|
// public final
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
|
// void remove(final Entry entry) {
|
||||||
}
|
// if (entry == null) {
|
||||||
}
|
// throw new NullPointerException("No menu entry exists for entry");
|
||||||
|
// }
|
||||||
@Override
|
//
|
||||||
public final
|
// dispatchAndWait(new Runnable() {
|
||||||
void setImage(final URL imageUrl) {
|
// @Override
|
||||||
if (imageUrl == null) {
|
// public
|
||||||
setImage_(null);
|
// void run() {
|
||||||
}
|
// remove__(entry);
|
||||||
else {
|
// }
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
|
// });
|
||||||
}
|
// }
|
||||||
}
|
//
|
||||||
|
// /**
|
||||||
@Override
|
// * This removes a sub-menu entry from the dropdown menu.
|
||||||
public final
|
// *
|
||||||
void setImage(final String cacheName, final InputStream imageStream) {
|
// * @param menu This is the menu entry to remove
|
||||||
if (imageStream == null) {
|
// */
|
||||||
setImage_(null);
|
// @Override
|
||||||
}
|
// public final
|
||||||
else {
|
// void remove(final Menu menu) {
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
|
// final Menu parent = getParent();
|
||||||
}
|
// if (parent == null) {
|
||||||
}
|
// // we are the system tray menu, so we just remove from ourselves
|
||||||
|
// dispatchAndWait(new Runnable() {
|
||||||
@Override
|
// @Override
|
||||||
public final
|
// public
|
||||||
void setImage(final InputStream imageStream) {
|
// void run() {
|
||||||
if (imageStream == null) {
|
// remove__(menu);
|
||||||
setImage_(null);
|
// }
|
||||||
}
|
// });
|
||||||
else {
|
// } else {
|
||||||
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
|
// final Menu _this = this;
|
||||||
}
|
// // we are a submenu
|
||||||
}
|
// dispatchAndWait(new Runnable() {
|
||||||
|
// @Override
|
||||||
|
// public
|
||||||
@Override
|
// void run() {
|
||||||
public final
|
// ((MenuBase) parent).remove__(_this);
|
||||||
void setCallback(final ActionListener callback) {
|
// }
|
||||||
}
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
/**
|
|
||||||
* This removes a menu entry from the dropdown menu.
|
|
||||||
*
|
|
||||||
* @param entry This is the menu entry to remove
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void remove(final Entry entry) {
|
|
||||||
if (entry == null) {
|
|
||||||
throw new NullPointerException("No menu entry exists for entry");
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
remove__(entry);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This removes a sub-menu entry from the dropdown menu.
|
|
||||||
*
|
|
||||||
* @param menu This is the menu entry to remove
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
void remove(final Menu menu) {
|
|
||||||
final Menu parent = getParent();
|
|
||||||
if (parent == null) {
|
|
||||||
// we are the system tray menu, so we just remove from ourselves
|
|
||||||
dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
remove__(menu);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
final Menu _this = this;
|
|
||||||
// we are a submenu
|
|
||||||
dispatchAndWait(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public
|
|
||||||
void run() {
|
|
||||||
((MenuBase) parent).remove__(_this);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOT ALWAYS CALLED ON EDT
|
// NOT ALWAYS CALLED ON EDT
|
||||||
protected
|
protected
|
||||||
|
@ -493,68 +238,44 @@ class MenuBase implements Menu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
* This removes a menu entry or sub-menu (via the text label) from the dropdown menu.
|
// * This removes a menu entry or sub-menu (via the text label) from the dropdown menu.
|
||||||
*
|
// *
|
||||||
* @param menuText This is the label for the menu entry or sub-menu to remove
|
// * @param menuText This is the label for the menu entry or sub-menu to remove
|
||||||
*/
|
// */
|
||||||
public final
|
// public final
|
||||||
void remove(final String menuText) {
|
// void remove(final String menuText) {
|
||||||
dispatchAndWait(new Runnable() {
|
// dispatchAndWait(new Runnable() {
|
||||||
@Override
|
// @Override
|
||||||
public
|
// public
|
||||||
void run() {
|
// void run() {
|
||||||
synchronized (menuEntries) {
|
// synchronized (menuEntries) {
|
||||||
Entry entry = get(menuText);
|
// Entry entry = get(menuText);
|
||||||
|
//
|
||||||
if (entry != null) {
|
// if (entry != null) {
|
||||||
remove(entry);
|
// remove(entry);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@Override
|
// @Override
|
||||||
public final
|
// public final
|
||||||
void removeAll() {
|
// void removeAll() {
|
||||||
dispatch(new Runnable() {
|
// dispatch(new Runnable() {
|
||||||
@Override
|
// @Override
|
||||||
public
|
// public
|
||||||
void run() {
|
// void run() {
|
||||||
synchronized (menuEntries) {
|
// synchronized (menuEntries) {
|
||||||
// have to make copy because we are deleting all of them, and sub-menus remove themselves from parents
|
// // have to make copy because we are deleting all of them, and sub-menus remove themselves from parents
|
||||||
ArrayList<Entry> menuEntriesCopy = new ArrayList<Entry>(MenuBase.this.menuEntries);
|
// ArrayList<Entry> menuEntriesCopy = new ArrayList<Entry>(MenuBase.this.menuEntries);
|
||||||
for (Entry entry : menuEntriesCopy) {
|
// for (Entry entry : menuEntriesCopy) {
|
||||||
entry.remove();
|
// entry.remove();
|
||||||
}
|
// }
|
||||||
menuEntries.clear();
|
// menuEntries.clear();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
int hashCode() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final
|
|
||||||
boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuBase other = (MenuBase) obj;
|
|
||||||
return this.id == other.id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
20
src/dorkbox/systemTray/util/MenuCheckboxHook.java
Normal file
20
src/dorkbox/systemTray/util/MenuCheckboxHook.java
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package dorkbox.systemTray.util;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.Checkbox;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
interface MenuCheckboxHook extends EntryHook {
|
||||||
|
|
||||||
|
void setEnabled(Checkbox menuItem);
|
||||||
|
|
||||||
|
void setText(Checkbox menuItem);
|
||||||
|
|
||||||
|
void setCallback(Checkbox menuItem);
|
||||||
|
|
||||||
|
void setShortcut(Checkbox menuItem);
|
||||||
|
|
||||||
|
void setChecked(Checkbox checkbox);
|
||||||
|
}
|
12
src/dorkbox/systemTray/util/MenuHook.java
Normal file
12
src/dorkbox/systemTray/util/MenuHook.java
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package dorkbox.systemTray.util;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.Entry;
|
||||||
|
import dorkbox.systemTray.Menu;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
interface MenuHook extends MenuItemHook {
|
||||||
|
void add(Menu parentMenu, Entry entry, int index);
|
||||||
|
}
|
19
src/dorkbox/systemTray/util/MenuItemHook.java
Normal file
19
src/dorkbox/systemTray/util/MenuItemHook.java
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package dorkbox.systemTray.util;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.MenuItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
interface MenuItemHook extends EntryHook {
|
||||||
|
void setImage(MenuItem menuItem);
|
||||||
|
|
||||||
|
void setEnabled(MenuItem menuItem);
|
||||||
|
|
||||||
|
void setText(MenuItem menuItem);
|
||||||
|
|
||||||
|
void setCallback(MenuItem menuItem);
|
||||||
|
|
||||||
|
void setShortcut(MenuItem menuItem);
|
||||||
|
}
|
9
src/dorkbox/systemTray/util/MenuStatusHook.java
Normal file
9
src/dorkbox/systemTray/util/MenuStatusHook.java
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package dorkbox.systemTray.util;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
interface MenuStatusHook extends EntryHook {
|
||||||
|
void setText(Status menuItem);
|
||||||
|
}
|
67
src/dorkbox/systemTray/util/Status.java
Normal file
67
src/dorkbox/systemTray/util/Status.java
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015 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.util;
|
||||||
|
|
||||||
|
import dorkbox.systemTray.Entry;
|
||||||
|
import dorkbox.systemTray.Menu;
|
||||||
|
import dorkbox.systemTray.SystemTray;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This represents a common menu-status entry, that is cross platform in nature
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
class Status extends Entry {
|
||||||
|
private volatile String text;
|
||||||
|
|
||||||
|
public
|
||||||
|
Status() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param hook the platform specific implementation for all actions for this type
|
||||||
|
* @param parent the parent of this menu, null if the parent is the system tray
|
||||||
|
* @param systemTray the system tray (which is the object that sits in the system tray)
|
||||||
|
*/
|
||||||
|
public synchronized
|
||||||
|
void bind(final MenuStatusHook hook, final Menu parent, final SystemTray systemTray) {
|
||||||
|
super.bind(hook, parent, systemTray);
|
||||||
|
|
||||||
|
hook.setText(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the text label that the menu entry has assigned
|
||||||
|
*/
|
||||||
|
public final
|
||||||
|
String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the new text to set for a menu entry
|
||||||
|
*
|
||||||
|
* @param text the new text to set
|
||||||
|
*/
|
||||||
|
public
|
||||||
|
void setText(final String text) {
|
||||||
|
this.text = text;
|
||||||
|
|
||||||
|
if (hook != null) {
|
||||||
|
((MenuStatusHook) hook).setText(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user