new API front-end + bound backend for SwingUI

This commit is contained in:
nathan 2016-10-21 12:36:55 +02:00
parent 86d031ed7c
commit d665f29f28
22 changed files with 1244 additions and 1577 deletions

View File

@ -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 {
}

View File

@ -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;
}
}

View File

@ -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) {
}
}

View File

@ -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);
}
}
});
}
}

View File

@ -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) {
}
}

View File

@ -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) {
}
}

View File

@ -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();
}
}
});

View 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();
}
});
}
}

View 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();
}
});
}
}

View File

@ -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();
}
});
}
}

View 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();
}
});
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,9 @@
package dorkbox.systemTray.util;
/**
*
*/
public
interface EntryHook {
void remove();
}

View File

@ -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.
*

View File

@ -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();
// }
// }
// });
// }
}

View 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);
}

View 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);
}

View 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);
}

View File

@ -0,0 +1,9 @@
package dorkbox.systemTray.util;
/**
*
*/
public
interface MenuStatusHook extends EntryHook {
void setText(Status menuItem);
}

View 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);
}
}
}