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;
|
||||
|
||||
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JMenuItem;
|
||||
|
||||
import dorkbox.systemTray.Checkbox;
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.Status;
|
||||
import dorkbox.systemTray.MenuItem;
|
||||
import dorkbox.systemTray.Separator;
|
||||
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.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")
|
||||
class SwingMenu extends MenuBase implements SwingUI {
|
||||
class SwingMenu implements MenuHook {
|
||||
|
||||
// sub-menu = AdjustedJMenu
|
||||
// systemtray = TrayPopup
|
||||
volatile JComponent _native;
|
||||
private final SwingMenu parent;
|
||||
final JComponent _native;
|
||||
|
||||
// this have to be volatile, because they can be changed from any thread
|
||||
private volatile String text;
|
||||
private volatile boolean hasLegitIcon = false;
|
||||
|
||||
/**
|
||||
* Called in the EDT
|
||||
*
|
||||
* @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
|
||||
* @param _native the native element that represents this menu
|
||||
*/
|
||||
SwingMenu(final SystemTray systemTray, final Menu parent, final JComponent _native) {
|
||||
super(systemTray, parent);
|
||||
this._native = _native;
|
||||
// This is NOT a copy constructor!
|
||||
@SuppressWarnings("IncompleteCopyConstructor")
|
||||
SwingMenu(final SwingMenu parent) {
|
||||
this.parent = parent;
|
||||
|
||||
if (parent == null) {
|
||||
this._native = new TrayPopup();
|
||||
}
|
||||
else {
|
||||
this._native = new AdjustedJMenu();
|
||||
parent._native.add(this._native);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final
|
||||
void dispatch(final Runnable runnable) {
|
||||
// this will properly check if we are running on the EDT
|
||||
SwingUtil.invokeLater(runnable);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final
|
||||
void dispatchAndWait(final Runnable runnable) {
|
||||
// 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
|
||||
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;
|
||||
|
||||
dispatch(new Runnable() {
|
||||
|
@ -208,156 +89,115 @@ class SwingMenu extends MenuBase implements SwingUI {
|
|||
void run() {
|
||||
if (imageFile != null) {
|
||||
ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath());
|
||||
((JMenuItem) _native).setIcon(origIcon);
|
||||
((AdjustedJMenu) _native).setIcon(origIcon);
|
||||
}
|
||||
else {
|
||||
((JMenuItem) _native).setIcon(null);
|
||||
((AdjustedJMenu) _native).setIcon(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// is overridden in tray impl
|
||||
@Override
|
||||
public
|
||||
boolean hasImage() {
|
||||
return hasLegitIcon;
|
||||
}
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
@Override
|
||||
public
|
||||
void setEnabled(final boolean enabled) {
|
||||
void setEnabled(final MenuItem menuItem) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
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
|
||||
public final
|
||||
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
|
||||
public synchronized
|
||||
void remove() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
_native.setVisible(false);
|
||||
if (_native instanceof TrayPopup) {
|
||||
((TrayPopup) _native).close();
|
||||
}
|
||||
_native.removeAll();
|
||||
|
||||
SwingMenu parent = (SwingMenu) getParent();
|
||||
if (parent != null) {
|
||||
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;
|
||||
|
||||
import java.awt.event.ActionListener;
|
||||
import java.io.File;
|
||||
|
||||
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.
|
||||
SwingEntrySeparator(final SwingMenu parent) {
|
||||
super(parent, new JSeparator(JSeparator.HORIZONTAL));
|
||||
SwingMenuItemSeparator(final SwingMenu parent) {
|
||||
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
|
||||
boolean hasImage() {
|
||||
return false;
|
||||
|
@ -53,6 +38,14 @@ class SwingEntrySeparator extends SwingEntry implements dorkbox.systemTray.Separ
|
|||
|
||||
@Override
|
||||
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.ptr.PointerByReference;
|
||||
|
||||
import dorkbox.systemTray.MenuItem;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.Tray;
|
||||
import dorkbox.systemTray.jna.linux.AppIndicator;
|
||||
import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct;
|
||||
import dorkbox.systemTray.jna.linux.GEventCallback;
|
||||
|
@ -81,7 +83,7 @@ import dorkbox.util.SwingUtil;
|
|||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
class _AppIndicatorTray extends SwingMenu {
|
||||
class _AppIndicatorTray extends Tray implements SwingUI {
|
||||
private volatile AppIndicatorInstanceStruct appIndicator;
|
||||
private boolean isActive = false;
|
||||
private final Runnable popupRunnable;
|
||||
|
@ -101,6 +103,7 @@ class _AppIndicatorTray extends SwingMenu {
|
|||
|
||||
// is the system tray visible or not.
|
||||
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)
|
||||
// they ALSO do not support tooltips, so we cater to the lowest common denominator
|
||||
|
@ -108,9 +111,109 @@ class _AppIndicatorTray extends SwingMenu {
|
|||
|
||||
public
|
||||
_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.setFocusable(true);
|
||||
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.
|
||||
Gtk.dispatch(new Runnable() {
|
||||
Gtk.dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
|
@ -141,7 +244,7 @@ class _AppIndicatorTray extends SwingMenu {
|
|||
Point point = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
TrayPopup popupMenu = (TrayPopup) _native;
|
||||
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
|
||||
popupMenu.doShow(point, SystemTray.DEFAULT_TRAY_SIZE);
|
||||
}
|
||||
};
|
||||
|
@ -162,6 +265,8 @@ class _AppIndicatorTray extends SwingMenu {
|
|||
});
|
||||
|
||||
Gtk.waitForStartup();
|
||||
|
||||
bind(swingMenu, null, systemTray);
|
||||
}
|
||||
|
||||
private
|
||||
|
@ -195,87 +300,9 @@ class _AppIndicatorTray extends SwingMenu {
|
|||
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
|
||||
public final
|
||||
boolean hasImage() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
});
|
||||
return image != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,9 @@ import javax.swing.JPopupMenu;
|
|||
import com.sun.jna.NativeLong;
|
||||
import com.sun.jna.Pointer;
|
||||
|
||||
import dorkbox.systemTray.MenuItem;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
import dorkbox.systemTray.Tray;
|
||||
import dorkbox.systemTray.jna.linux.GEventCallback;
|
||||
import dorkbox.systemTray.jna.linux.GdkEventButton;
|
||||
import dorkbox.systemTray.jna.linux.Gobject;
|
||||
|
@ -41,7 +43,7 @@ import dorkbox.systemTray.jna.linux.Gtk;
|
|||
*/
|
||||
@SuppressWarnings("Duplicates")
|
||||
public
|
||||
class _GtkStatusIconTray extends SwingMenu {
|
||||
class _GtkStatusIconTray extends Tray implements SwingUI {
|
||||
private volatile Pointer trayIcon;
|
||||
|
||||
// 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)
|
||||
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 volatile boolean isActive = false;
|
||||
|
||||
// is the system tray visible or not.
|
||||
private volatile boolean visible = true;
|
||||
private volatile File image;
|
||||
|
||||
// called on the EDT
|
||||
public
|
||||
_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.setFocusable(true);
|
||||
|
||||
|
@ -74,15 +170,11 @@ class _GtkStatusIconTray extends SwingMenu {
|
|||
Point point = MouseInfo.getPointerInfo()
|
||||
.getLocation();
|
||||
|
||||
TrayPopup popupMenu = (TrayPopup) _native;
|
||||
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
|
||||
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.dispatch(new Runnable() {
|
||||
|
@ -100,7 +192,7 @@ class _GtkStatusIconTray extends SwingMenu {
|
|||
// BUTTON_PRESS only (any mouse click)
|
||||
if (event.type == 4) {
|
||||
// show the swing menu on the EDT
|
||||
dispatch(popupRunnable);
|
||||
swingMenu.dispatch(popupRunnable);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -140,76 +232,13 @@ class _GtkStatusIconTray extends SwingMenu {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
bind(swingMenu, null, systemTray);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("FieldRepeatedlyAccessedInMethod")
|
||||
@Override
|
||||
public
|
||||
void shutdown() {
|
||||
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();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
boolean hasImage() {
|
||||
return image != null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,9 @@ import java.io.File;
|
|||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import dorkbox.systemTray.MenuItem;
|
||||
import dorkbox.systemTray.Tray;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
|
||||
public
|
||||
class _SwingTray extends SwingMenu {
|
||||
public final
|
||||
class _SwingTray extends Tray implements SwingUI {
|
||||
private volatile SystemTray tray;
|
||||
private volatile TrayIcon trayIcon;
|
||||
|
||||
|
@ -46,7 +49,7 @@ class _SwingTray extends SwingMenu {
|
|||
// Called in the EDT
|
||||
public
|
||||
_SwingTray(final dorkbox.systemTray.SystemTray systemTray) {
|
||||
super(systemTray, null, new TrayPopup());
|
||||
super();
|
||||
|
||||
if (!SystemTray.isSupported()) {
|
||||
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();
|
||||
}
|
||||
|
||||
public
|
||||
void shutdown() {
|
||||
dispatchAndWait(new Runnable() {
|
||||
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
|
||||
final SwingMenu swingMenu = new SwingMenu(null) {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
removeAll();
|
||||
remove();
|
||||
void setEnabled(final MenuItem menuItem) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
boolean enabled = menuItem.getEnabled();
|
||||
|
||||
tray.remove(trayIcon);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public
|
||||
void setImage_(final File iconFile) {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
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);
|
||||
if (visible && !enabled) {
|
||||
tray.remove(trayIcon);
|
||||
visible = false;
|
||||
}
|
||||
else if (!visible && enabled) {
|
||||
try {
|
||||
tray.add(trayIcon);
|
||||
visible = true;
|
||||
} catch (AWTException e) {
|
||||
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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
|
||||
public
|
||||
void run() {
|
||||
if (visible && !setEnabled) {
|
||||
tray.remove(trayIcon);
|
||||
visible = false;
|
||||
void setImage(final MenuItem menuItem) {
|
||||
final File image = menuItem.getImage();
|
||||
if (image == null) {
|
||||
return;
|
||||
}
|
||||
else if (!visible && setEnabled) {
|
||||
try {
|
||||
tray.add(trayIcon);
|
||||
visible = true;
|
||||
} catch (AWTException e) {
|
||||
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e);
|
||||
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
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(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.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
|
@ -44,6 +45,7 @@ import dorkbox.systemTray.SystemTray;
|
|||
import dorkbox.systemTray.jna.Windows.User32;
|
||||
import dorkbox.util.CacheUtil;
|
||||
import dorkbox.util.FileUtil;
|
||||
import dorkbox.util.IO;
|
||||
import dorkbox.util.LocationResolver;
|
||||
import dorkbox.util.OS;
|
||||
import dorkbox.util.process.ShellProcessBuilder;
|
||||
|
@ -357,12 +359,16 @@ class ImageUtils {
|
|||
}
|
||||
|
||||
public static synchronized
|
||||
File resizeAndCache(final int size, final File file) {
|
||||
return resizeAndCache(size, file.getAbsolutePath());
|
||||
File resizeAndCache(final int size, final File file, final boolean cacheResult) {
|
||||
return resizeAndCache(size, file.getAbsolutePath(), cacheResult);
|
||||
}
|
||||
|
||||
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
|
||||
final String cacheName = size + "_" + fileName;
|
||||
|
||||
|
@ -393,7 +399,11 @@ class ImageUtils {
|
|||
|
||||
@SuppressWarnings("Duplicates")
|
||||
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();
|
||||
|
||||
// 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")
|
||||
public static synchronized
|
||||
File resizeAndCache(final int size, String cacheName, final InputStream imageStream) {
|
||||
if (cacheName == null) {
|
||||
cacheName = CacheUtil.createNameAsHash(imageStream);
|
||||
File resizeAndCache(final int size, InputStream imageStream, final boolean cacheResult) {
|
||||
if (imageStream == null) {
|
||||
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
|
||||
cacheName = size + "_" + cacheName;
|
||||
final String cacheName = size + "_" + CacheUtil.createNameAsHash(imageStream);
|
||||
((ByteArrayInputStream) imageStream).reset();
|
||||
|
||||
|
||||
// if we already have this fileName, reuse it
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -16,44 +16,27 @@
|
|||
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.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import dorkbox.systemTray.Checkbox;
|
||||
import dorkbox.systemTray.Entry;
|
||||
import dorkbox.systemTray.Menu;
|
||||
import dorkbox.systemTray.Separator;
|
||||
import dorkbox.systemTray.Status;
|
||||
import dorkbox.systemTray.SystemTray;
|
||||
|
||||
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
public abstract
|
||||
class MenuBase implements Menu {
|
||||
public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger();
|
||||
private final int id = MenuBase.MENU_ID_COUNTER.getAndIncrement();
|
||||
|
||||
protected final java.util.List<Entry> menuEntries = new ArrayList<Entry>();
|
||||
|
||||
private final SystemTray systemTray;
|
||||
private final Menu parent;
|
||||
|
||||
/**
|
||||
* Called in the EDT/GTK dispatch threads
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
class MenuBase extends Menu {
|
||||
// /**
|
||||
// * Called in the EDT/GTK dispatch threads
|
||||
// *
|
||||
// * @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) {
|
||||
// setSystemTray(systemTray);
|
||||
// setParent(parent);
|
||||
// }
|
||||
|
||||
protected abstract
|
||||
void dispatch(final Runnable runnable);
|
||||
|
@ -62,31 +45,43 @@ class MenuBase implements Menu {
|
|||
void dispatchAndWait(final Runnable runnable);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Will add a new menu entry
|
||||
* NOT ALWAYS CALLED ON DISPATCH
|
||||
*/
|
||||
protected abstract
|
||||
Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback);
|
||||
// protected abstract
|
||||
// Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback);
|
||||
|
||||
/**
|
||||
* Will add a new checkbox menu entry
|
||||
* NOT ALWAYS CALLED ON DISPATCH
|
||||
*/
|
||||
protected abstract
|
||||
Checkbox addCheckbox_(final String menuText, final ActionListener callback);
|
||||
// protected abstract
|
||||
// Checkbox addCheckbox_(final String menuText, final ActionListener callback);
|
||||
|
||||
/**
|
||||
* Will add a new sub-menu entry
|
||||
* NOT ALWAYS CALLED ON DISPATCH
|
||||
*/
|
||||
protected abstract
|
||||
Menu addMenu_(final String menuText, final File imagePath);
|
||||
// protected abstract
|
||||
// Menu addMenu_(final String menuText, final File imagePath);
|
||||
|
||||
|
||||
// public here so that Swing/Gtk/AppIndicator can override this
|
||||
protected abstract
|
||||
void setImage_(final File imageFile);
|
||||
// // public here so that Swing/Gtk/AppIndicator can override this
|
||||
// protected abstract
|
||||
// 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.
|
||||
// public
|
||||
|
@ -146,316 +119,88 @@ class MenuBase implements Menu {
|
|||
// }
|
||||
|
||||
|
||||
@Override
|
||||
public final
|
||||
Entry get(final String menuText) {
|
||||
if (menuText == null || menuText.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Must be wrapped in a synchronized block for object visibility
|
||||
synchronized (menuEntries) {
|
||||
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
final Entry entry = menuEntries.get(i);
|
||||
|
||||
if (entry instanceof Separator || entry instanceof Status) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String text = entry.getText();
|
||||
|
||||
// text can be null
|
||||
if (menuText.equals(text)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
// public final
|
||||
// Entry get(final String menuText) {
|
||||
// if (menuText == null || menuText.isEmpty()) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// // Must be wrapped in a synchronized block for object visibility
|
||||
// synchronized (menuEntries) {
|
||||
// for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
|
||||
// final Entry entry = menuEntries.get(i);
|
||||
//
|
||||
// if (entry instanceof Separator || entry instanceof Status) {
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
//// String text = entry.getText();
|
||||
//
|
||||
// // text can be null
|
||||
//// if (menuText.equals(text)) {
|
||||
//// return entry;
|
||||
//// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public final
|
||||
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 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// /**
|
||||
// * 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
|
||||
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.
|
||||
*
|
||||
* @param menuText This is the label for the menu entry or sub-menu to remove
|
||||
*/
|
||||
public final
|
||||
void remove(final String menuText) {
|
||||
dispatchAndWait(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
Entry entry = get(menuText);
|
||||
|
||||
if (entry != null) {
|
||||
remove(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final
|
||||
void removeAll() {
|
||||
dispatch(new Runnable() {
|
||||
@Override
|
||||
public
|
||||
void run() {
|
||||
synchronized (menuEntries) {
|
||||
// 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);
|
||||
for (Entry entry : menuEntriesCopy) {
|
||||
entry.remove();
|
||||
}
|
||||
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;
|
||||
}
|
||||
// /**
|
||||
// * 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
|
||||
// */
|
||||
// public final
|
||||
// void remove(final String menuText) {
|
||||
// dispatchAndWait(new Runnable() {
|
||||
// @Override
|
||||
// public
|
||||
// void run() {
|
||||
// synchronized (menuEntries) {
|
||||
// Entry entry = get(menuText);
|
||||
//
|
||||
// if (entry != null) {
|
||||
// remove(entry);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public final
|
||||
// void removeAll() {
|
||||
// dispatch(new Runnable() {
|
||||
// @Override
|
||||
// public
|
||||
// void run() {
|
||||
// synchronized (menuEntries) {
|
||||
// // 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);
|
||||
// for (Entry entry : menuEntriesCopy) {
|
||||
// entry.remove();
|
||||
// }
|
||||
// menuEntries.clear();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
|
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