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; package dorkbox.systemTray.swingUI;
import java.awt.event.ActionListener;
import java.io.File; import java.io.File;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JMenuItem;
import dorkbox.systemTray.Checkbox; import dorkbox.systemTray.Checkbox;
import dorkbox.systemTray.Entry; import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu; import dorkbox.systemTray.Menu;
import dorkbox.systemTray.Status; import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Separator;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.util.MenuBase; import dorkbox.systemTray.util.MenuHook;
import dorkbox.systemTray.util.Status;
import dorkbox.systemTray.util.SystemTrayFixes; import dorkbox.systemTray.util.SystemTrayFixes;
import dorkbox.util.SwingUtil; import dorkbox.util.SwingUtil;
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both // this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both (and duplicate code)
@SuppressWarnings("ForLoopReplaceableByForEach") @SuppressWarnings("ForLoopReplaceableByForEach")
class SwingMenu extends MenuBase implements SwingUI { class SwingMenu implements MenuHook {
// sub-menu = AdjustedJMenu private final SwingMenu parent;
// systemtray = TrayPopup final JComponent _native;
volatile JComponent _native;
// this have to be volatile, because they can be changed from any thread
private volatile String text;
private volatile boolean hasLegitIcon = false; private volatile boolean hasLegitIcon = false;
/** // This is NOT a copy constructor!
* Called in the EDT @SuppressWarnings("IncompleteCopyConstructor")
* SwingMenu(final SwingMenu parent) {
* @param systemTray the system tray (which is the object that sits in the system tray) this.parent = parent;
* @param parent the parent of this menu, null if the parent is the system tray
* @param _native the native element that represents this menu if (parent == null) {
*/ this._native = new TrayPopup();
SwingMenu(final SystemTray systemTray, final Menu parent, final JComponent _native) { }
super(systemTray, parent); else {
this._native = _native; this._native = new AdjustedJMenu();
parent._native.add(this._native);
}
} }
@Override
protected final protected final
void dispatch(final Runnable runnable) { void dispatch(final Runnable runnable) {
// this will properly check if we are running on the EDT // this will properly check if we are running on the EDT
SwingUtil.invokeLater(runnable); SwingUtil.invokeLater(runnable);
} }
@Override
protected final protected final
void dispatchAndWait(final Runnable runnable) { void dispatchAndWait(final Runnable runnable) {
// this will properly check if we are running on the EDT // this will properly check if we are running on the EDT
@ -75,131 +71,16 @@ class SwingMenu extends MenuBase implements SwingUI {
} }
} }
// always called in the EDT
protected final
void renderText(final String text) {
((JMenuItem) _native).setText(text);
}
@Override
public final
String getText() {
return text;
}
@Override
public final
void setText(final String newText) {
text = newText;
dispatch(new Runnable() {
@Override
public
void run() {
renderText(newText);
}
});
}
/**
* Will add a new menu entry
* NOT ALWAYS CALLED ON EDT
*/
protected final
Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback) {
if (menuText == null) {
throw new NullPointerException("Menu text cannot be null");
}
final AtomicReference<Entry> value = new AtomicReference<Entry>();
// must always be called on the EDT
dispatchAndWait(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
Entry entry = new SwingEntryItem(SwingMenu.this, callback);
entry.setText(menuText);
entry.setImage(imagePath);
menuEntries.add(entry);
value.set(entry);
}
}
});
return value.get();
}
/**
* Will add a new checkbox menu entry
* NOT ALWAYS CALLED ON DISPATCH
*/
@Override
protected
Checkbox addCheckbox_(final String menuText, final ActionListener callback) {
if (menuText == null) {
throw new NullPointerException("Menu text cannot be null");
}
final AtomicReference<Checkbox> value = new AtomicReference<Checkbox>();
// must always be called on the EDT
dispatchAndWait(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
Entry entry = new SwingEntryCheckbox(SwingMenu.this, callback);
entry.setText(menuText);
menuEntries.add(entry);
value.set((Checkbox) entry);
}
}
});
return value.get();
}
/**
* Will add a new sub-menu entry
* NOT ALWAYS CALLED ON EDT
*/
protected final
Menu addMenu_(final String menuText, final File imagePath) {
if (menuText == null) {
throw new NullPointerException("Menu text cannot be null");
}
final AtomicReference<Menu> value = new AtomicReference<Menu>();
// must always be called on the EDT
dispatchAndWait(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
Entry entry = new SwingMenu(getSystemTray(), SwingMenu.this, new AdjustedJMenu());
_native.add(((SwingMenu) entry)._native); // have to add it separately
entry.setText(menuText);
entry.setImage(imagePath);
menuEntries.add(entry);
value.set((Menu) entry);
}
}
});
return value.get();
}
// public here so that Swing/Gtk/AppIndicator can override this
public public
void setImage_(final File imageFile) { boolean hasImage() {
return hasLegitIcon;
}
// is overridden in tray impl
@Override
public
void setImage(final MenuItem menuItem) {
final File imageFile = menuItem.getImage();
hasLegitIcon = imageFile != null; hasLegitIcon = imageFile != null;
dispatch(new Runnable() { dispatch(new Runnable() {
@ -208,156 +89,115 @@ class SwingMenu extends MenuBase implements SwingUI {
void run() { void run() {
if (imageFile != null) { if (imageFile != null) {
ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath()); ImageIcon origIcon = new ImageIcon(imageFile.getAbsolutePath());
((JMenuItem) _native).setIcon(origIcon); ((AdjustedJMenu) _native).setIcon(origIcon);
} }
else { else {
((JMenuItem) _native).setIcon(null); ((AdjustedJMenu) _native).setIcon(null);
} }
} }
}); });
} }
// is overridden in tray impl
@Override @Override
public public
boolean hasImage() { void setEnabled(final MenuItem menuItem) {
return hasLegitIcon;
}
// public here so that Swing/Gtk/AppIndicator can override this
@Override
public
void setEnabled(final boolean enabled) {
dispatch(new Runnable() { dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
_native.setEnabled(enabled); _native.setEnabled(menuItem.getEnabled());
} }
}); });
} }
// is overridden in tray impl
@Override
public
void setText(final MenuItem menuItem) {
dispatch(new Runnable() {
@Override
public
void run() {
((AdjustedJMenu) _native).setText(menuItem.getText());
}
});
}
@Override
public
void setCallback(final MenuItem menuItem) {
// can't have a callback for menus!
}
// is overridden in tray impl
@Override
public
void setShortcut(final MenuItem menuItem) {
char shortcut = menuItem.getShortcut();
// yikes...
final int vKey = SystemTrayFixes.getVirtualKey(shortcut);
dispatch(new Runnable() {
@Override
public
void run() {
((AdjustedJMenu) _native).setMnemonic(vKey);
}
});
}
@Override
public
void add(final Menu parentMenu, final Entry entry, final int index) {
// must always be called on the EDT
dispatch(new Runnable() {
@Override
public
void run() {
if (entry instanceof Menu) {
SwingMenu swingMenu = new SwingMenu(SwingMenu.this);
((Menu) entry).bind(swingMenu, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Separator) {
SwingMenuItemSeparator swingEntrySeparator = new SwingMenuItemSeparator(SwingMenu.this);
entry.bind(swingEntrySeparator, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Checkbox) {
SwingMenuItemCheckbox swingEntryCheckbox = new SwingMenuItemCheckbox(SwingMenu.this);
((Checkbox) entry).bind(swingEntryCheckbox, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Status) {
SwingMenuItemStatus swingEntryStatus = new SwingMenuItemStatus(SwingMenu.this);
((Status) entry).bind(swingEntryStatus, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof MenuItem) {
SwingMenuItem swingMenuItem = new SwingMenuItem(SwingMenu.this);
((MenuItem) entry).bind(swingMenuItem, parentMenu, parentMenu.getSystemTray());
}
}
});
}
/** /**
* NOT ALWAYS CALLED ON EDT * This removes all menu entries from this menu AND this menu from it's parent
*/ */
@Override @Override
public final public synchronized
void addSeparator() {
dispatch(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
synchronized (menuEntries) {
Entry entry = new SwingEntrySeparator(SwingMenu.this);
menuEntries.add(entry);
}
}
}
});
}
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
// public
// Entry addWidget(final JComponent widget) {
// if (widget == null) {
// throw new NullPointerException("Widget cannot be null");
// }
//
// final AtomicReference<Entry> value = new AtomicReference<Entry>();
//
// dispatchAndWait(new Runnable() {
// @Override
// public
// void run() {
// synchronized (menuEntries) {
// // must always be called on the EDT
// Entry entry = new SwingEntryWidget(SwingMenu.this, widget);
// value.set(entry);
// menuEntries.add(entry);
// }
// }
// });
//
// return value.get();
// }
// public here so that Swing/Gtk/AppIndicator can access this
public final
void setStatus(final String statusText) {
final SwingMenu _this = this;
dispatchAndWait(new Runnable() {
@Override
public
void run() {
synchronized (menuEntries) {
// status is ALWAYS at 0 index...
SwingEntry menuEntry = null;
if (!menuEntries.isEmpty()) {
menuEntry = (SwingEntry) menuEntries.get(0);
}
if (menuEntry instanceof Status) {
// set the text or delete...
if (statusText == null) {
// delete
remove(menuEntry);
}
else {
// set text
menuEntry.setText(statusText);
}
} else {
// create a new one
menuEntry = new SwingEntryStatus(_this, statusText);
// status is ALWAYS at 0 index...
menuEntries.add(0, menuEntry);
}
}
}
});
}
@Override
public final
void setShortcut(final char key) {
if (_native instanceof JMenuItem) {
// yikes...
final int vKey = SystemTrayFixes.getVirtualKey(key);
dispatch(new Runnable() {
@Override
public
void run() {
((JMenuItem) _native).setMnemonic(vKey);
}
});
}
}
@Override
public final
void remove() { void remove() {
dispatchAndWait(new Runnable() { dispatch(new Runnable() {
@Override @Override
public public
void run() { void run() {
_native.setVisible(false); _native.setVisible(false);
if (_native instanceof TrayPopup) { _native.removeAll();
((TrayPopup) _native).close();
}
SwingMenu parent = (SwingMenu) getParent();
if (parent != null) { if (parent != null) {
parent._native.remove(_native); parent._native.remove(_native);
} else {
// have to dispose of the tray popup hidden frame, otherwise the app will never close (because this will hold it open)
((TrayPopup) _native).close();
} }
} }
}); });

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; package dorkbox.systemTray.swingUI;
import java.awt.event.ActionListener;
import java.io.File;
import javax.swing.JSeparator; import javax.swing.JSeparator;
class SwingEntrySeparator extends SwingEntry implements dorkbox.systemTray.Separator { import dorkbox.systemTray.util.EntryHook;
import dorkbox.util.SwingUtil;
class SwingMenuItemSeparator implements EntryHook {
private final SwingMenu parent;
private final JSeparator _native = new JSeparator(JSeparator.HORIZONTAL);
// this is ALWAYS called on the EDT. // this is ALWAYS called on the EDT.
SwingEntrySeparator(final SwingMenu parent) { SwingMenuItemSeparator(final SwingMenu parent) {
super(parent, new JSeparator(JSeparator.HORIZONTAL)); this.parent = parent;
parent._native.add(_native);
} }
// called in the EDT thread
@Override
void renderText(final String text) {
}
@Override
void setImage_(final File imageFile) {
}
@Override
void removePrivate() {
}
@Override
public
void setShortcut(final char key) {
}
@Override
public public
boolean hasImage() { boolean hasImage() {
return false; return false;
@ -53,6 +38,14 @@ class SwingEntrySeparator extends SwingEntry implements dorkbox.systemTray.Separ
@Override @Override
public public
void setCallback(final ActionListener callback) { void remove() {
SwingUtil.invokeLater(new Runnable() {
@Override
public
void run() {
parent._native.remove(_native);
_native.removeAll();
}
});
} }
} }

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.Pointer;
import com.sun.jna.ptr.PointerByReference; import com.sun.jna.ptr.PointerByReference;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.Tray;
import dorkbox.systemTray.jna.linux.AppIndicator; import dorkbox.systemTray.jna.linux.AppIndicator;
import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct; import dorkbox.systemTray.jna.linux.AppIndicatorInstanceStruct;
import dorkbox.systemTray.jna.linux.GEventCallback; import dorkbox.systemTray.jna.linux.GEventCallback;
@ -81,7 +83,7 @@ import dorkbox.util.SwingUtil;
*/ */
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
public public
class _AppIndicatorTray extends SwingMenu { class _AppIndicatorTray extends Tray implements SwingUI {
private volatile AppIndicatorInstanceStruct appIndicator; private volatile AppIndicatorInstanceStruct appIndicator;
private boolean isActive = false; private boolean isActive = false;
private final Runnable popupRunnable; private final Runnable popupRunnable;
@ -101,6 +103,7 @@ class _AppIndicatorTray extends SwingMenu {
// is the system tray visible or not. // is the system tray visible or not.
private volatile boolean visible = true; private volatile boolean visible = true;
private volatile File image;
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus) // appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
// they ALSO do not support tooltips, so we cater to the lowest common denominator // they ALSO do not support tooltips, so we cater to the lowest common denominator
@ -108,9 +111,109 @@ class _AppIndicatorTray extends SwingMenu {
public public
_AppIndicatorTray(final SystemTray systemTray) { _AppIndicatorTray(final SystemTray systemTray) {
super(systemTray,null, new TrayPopup()); super();
TrayPopup popupMenu = (TrayPopup) _native; // we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
final SwingMenu swingMenu = new SwingMenu(null) {
@Override
public
void setEnabled(final MenuItem menuItem) {
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
boolean enabled = menuItem.getEnabled();
if (visible && !enabled) {
// STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
visible = false;
}
else if (!visible && enabled) {
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
visible = true;
}
}
});
}
@Override
public
void setImage(final MenuItem menuItem) {
image = menuItem.getImage();
if (image == null) {
return;
}
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
AppIndicator.app_indicator_set_icon(appIndicator, image.getAbsolutePath());
if (!isActive) {
isActive = true;
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set
hookMenuOpen();
}
}
});
// needs to be on EDT
dispatch(new Runnable() {
@Override
public
void run() {
((TrayPopup) _native).setTitleBarImage(image);
}
});
}
@Override
public
void setText(final MenuItem menuItem) {
// no op
}
@Override
public
void setShortcut(final MenuItem menuItem) {
// no op
}
@Override
public
void remove() {
if (!shuttingDown.getAndSet(true)) {
// must happen asap, so our hook properly notices we are in shutdown mode
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
appIndicator = null;
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
// STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
Pointer p = savedAppIndicator.getPointer();
Gobject.g_object_unref(p);
}
});
// does not need to be called on the dispatch (it does that)
Gtk.shutdownGui();
super.remove();
}
}
};
TrayPopup popupMenu = (TrayPopup) swingMenu._native;
popupMenu.pack(); popupMenu.pack();
popupMenu.setFocusable(true); popupMenu.setFocusable(true);
popupMenu.setOnHideRunnable(new Runnable() { popupMenu.setOnHideRunnable(new Runnable() {
@ -123,7 +226,7 @@ class _AppIndicatorTray extends SwingMenu {
} }
// Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed. // Such ugly hacks to get AppIndicator support properly working. This is so horrible I am ashamed.
Gtk.dispatch(new Runnable() { Gtk.dispatchAndWait(new Runnable() {
@Override @Override
public public
void run() { void run() {
@ -141,7 +244,7 @@ class _AppIndicatorTray extends SwingMenu {
Point point = MouseInfo.getPointerInfo() Point point = MouseInfo.getPointerInfo()
.getLocation(); .getLocation();
TrayPopup popupMenu = (TrayPopup) _native; TrayPopup popupMenu = (TrayPopup) swingMenu._native;
popupMenu.doShow(point, SystemTray.DEFAULT_TRAY_SIZE); popupMenu.doShow(point, SystemTray.DEFAULT_TRAY_SIZE);
} }
}; };
@ -162,6 +265,8 @@ class _AppIndicatorTray extends SwingMenu {
}); });
Gtk.waitForStartup(); Gtk.waitForStartup();
bind(swingMenu, null, systemTray);
} }
private private
@ -195,87 +300,9 @@ class _AppIndicatorTray extends SwingMenu {
AppIndicator.app_indicator_set_menu(appIndicator, dummyMenu); AppIndicator.app_indicator_set_menu(appIndicator, dummyMenu);
} }
public final
void shutdown() {
if (!shuttingDown.getAndSet(true)) {
// must happen asap, so our hook properly notices we are in shutdown mode
final AppIndicatorInstanceStruct savedAppIndicator = appIndicator;
appIndicator = null;
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
// STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(savedAppIndicator, AppIndicator.STATUS_PASSIVE);
Pointer p = savedAppIndicator.getPointer();
Gobject.g_object_unref(p);
}
});
// does not need to be called on the dispatch (it does that)
Gtk.shutdownGui();
// uses EDT
removeAll();
remove(); // remove ourselves from our parent
}
}
@Override @Override
public final public final
boolean hasImage() { boolean hasImage() {
return true; return image != null;
}
@Override
public final
void setImage_(final File imageFile) {
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
AppIndicator.app_indicator_set_icon(appIndicator, imageFile.getAbsolutePath());
if (!isActive) {
isActive = true;
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
// now we have to setup a way for us to catch the "activation" click on this menu. Must be after the menu is set
hookMenuOpen();
}
}
});
// needs to be on EDT
dispatch(new Runnable() {
@Override
public
void run() {
((TrayPopup) _native).setTitleBarImage(imageFile);
}
});
}
@Override
public final
void setEnabled(final boolean setEnabled) {
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
if (visible && !setEnabled) {
// STATUS_PASSIVE hides the indicator
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_PASSIVE);
visible = false;
}
else if (!visible && setEnabled) {
AppIndicator.app_indicator_set_status(appIndicator, AppIndicator.STATUS_ACTIVE);
visible = true;
}
}
});
} }
} }

View File

@ -27,7 +27,9 @@ import javax.swing.JPopupMenu;
import com.sun.jna.NativeLong; import com.sun.jna.NativeLong;
import com.sun.jna.Pointer; import com.sun.jna.Pointer;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.Tray;
import dorkbox.systemTray.jna.linux.GEventCallback; import dorkbox.systemTray.jna.linux.GEventCallback;
import dorkbox.systemTray.jna.linux.GdkEventButton; import dorkbox.systemTray.jna.linux.GdkEventButton;
import dorkbox.systemTray.jna.linux.Gobject; import dorkbox.systemTray.jna.linux.Gobject;
@ -41,7 +43,7 @@ import dorkbox.systemTray.jna.linux.Gtk;
*/ */
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
public public
class _GtkStatusIconTray extends SwingMenu { class _GtkStatusIconTray extends Tray implements SwingUI {
private volatile Pointer trayIcon; private volatile Pointer trayIcon;
// http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c // http://code.metager.de/source/xref/gnome/Platform/gtk%2B/gtk/deprecated/gtkstatusicon.c
@ -50,20 +52,114 @@ class _GtkStatusIconTray extends SwingMenu {
// have to save these in a field to prevent GC on the objects (since they go out-of-scope from java) // have to save these in a field to prevent GC on the objects (since they go out-of-scope from java)
private final List<Object> gtkCallbacks = new ArrayList<Object>(); private final List<Object> gtkCallbacks = new ArrayList<Object>();
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
private AtomicBoolean shuttingDown = new AtomicBoolean(); private AtomicBoolean shuttingDown = new AtomicBoolean();
private volatile boolean isActive = false; private volatile boolean isActive = false;
// is the system tray visible or not. // is the system tray visible or not.
private volatile boolean visible = true; private volatile boolean visible = true;
private volatile File image;
// called on the EDT // called on the EDT
public public
_GtkStatusIconTray(final SystemTray systemTray) { _GtkStatusIconTray(final SystemTray systemTray) {
super(systemTray, null, new TrayPopup()); super();
JPopupMenu popupMenu = (JPopupMenu) _native; // we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
final SwingMenu swingMenu = new SwingMenu(null) {
@Override
public
void setEnabled(final MenuItem menuItem) {
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
boolean enabled = menuItem.getEnabled();
if (visible && !enabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, enabled);
visible = false;
}
else if (!visible && enabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, enabled);
visible = true;
}
}
});
}
@Override
public
void setImage(final MenuItem menuItem) {
image = menuItem.getImage();
if (image == null) {
return;
}
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
Gtk.gtk_status_icon_set_from_file(trayIcon, image.getAbsolutePath());
if (!isActive) {
isActive = true;
Gtk.gtk_status_icon_set_visible(trayIcon, true);
}
}
});
// needs to be on EDT
dispatch(new Runnable() {
@Override
public
void run() {
((TrayPopup) _native).setTitleBarImage(image);
}
});
}
@Override
public
void setText(final MenuItem menuItem) {
// no op
}
@Override
public
void setShortcut(final MenuItem menuItem) {
// no op
}
@Override
public
void remove() {
// This is required if we have JavaFX or SWT shutdown hooks (to prevent us from shutting down twice...)
if (!shuttingDown.getAndSet(true)) {
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
// this hides the indicator
Gtk.gtk_status_icon_set_visible(trayIcon, false);
Gobject.g_object_unref(trayIcon);
// mark for GC
trayIcon = null;
gtkCallbacks.clear();
}
});
// does not need to be called on the dispatch (it does that)
Gtk.shutdownGui();
super.remove();
}
}
};
JPopupMenu popupMenu = (JPopupMenu) swingMenu._native;
popupMenu.pack(); popupMenu.pack();
popupMenu.setFocusable(true); popupMenu.setFocusable(true);
@ -74,15 +170,11 @@ class _GtkStatusIconTray extends SwingMenu {
Point point = MouseInfo.getPointerInfo() Point point = MouseInfo.getPointerInfo()
.getLocation(); .getLocation();
TrayPopup popupMenu = (TrayPopup) _native; TrayPopup popupMenu = (TrayPopup) swingMenu._native;
popupMenu.doShow(point, 0); popupMenu.doShow(point, 0);
} }
}; };
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
// they ALSO do not support tooltips, so we cater to the lowest common denominator
// trayIcon.setToolTip("app name");
Gtk.startGui(); Gtk.startGui();
Gtk.dispatch(new Runnable() { Gtk.dispatch(new Runnable() {
@ -100,7 +192,7 @@ class _GtkStatusIconTray extends SwingMenu {
// BUTTON_PRESS only (any mouse click) // BUTTON_PRESS only (any mouse click)
if (event.type == 4) { if (event.type == 4) {
// show the swing menu on the EDT // show the swing menu on the EDT
dispatch(popupRunnable); swingMenu.dispatch(popupRunnable);
} }
} }
}; };
@ -140,76 +232,13 @@ class _GtkStatusIconTray extends SwingMenu {
} }
} }
}); });
bind(swingMenu, null, systemTray);
} }
@Override
@SuppressWarnings("FieldRepeatedlyAccessedInMethod")
public public
void shutdown() { boolean hasImage() {
if (!shuttingDown.getAndSet(true)) { return image != null;
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
// this hides the indicator
Gtk.gtk_status_icon_set_visible(trayIcon, false);
Gobject.g_object_unref(trayIcon);
// mark for GC
trayIcon = null;
gtkCallbacks.clear();
}
});
// does not need to be called on the dispatch (it does that)
Gtk.shutdownGui();
// uses EDT
removeAll();
remove(); // remove ourselves from our parent
}
}
public
void setImage_(final File iconFile) {
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
Gtk.gtk_status_icon_set_from_file(trayIcon, iconFile.getAbsolutePath());
if (!isActive) {
isActive = true;
Gtk.gtk_status_icon_set_visible(trayIcon, true);
}
}
});
// needs to be on EDT
dispatch(new Runnable() {
@Override
public
void run() {
((TrayPopup) _native).setTitleBarImage(iconFile);
}
});
}
public
void setEnabled(final boolean setEnabled) {
Gtk.dispatch(new Runnable() {
@Override
public
void run() {
if (visible && !setEnabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
visible = false;
} else if (!visible && setEnabled) {
Gtk.gtk_status_icon_set_visible(trayIcon, setEnabled);
visible = true;
}
}
});
} }
} }

View File

@ -26,6 +26,9 @@ import java.io.File;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Tray;
/** /**
* Class for handling all system tray interaction, via Swing. * Class for handling all system tray interaction, via Swing.
* *
@ -35,8 +38,8 @@ import javax.swing.JPopupMenu;
* https://stackoverflow.com/questions/331407/java-trayicon-using-image-with-transparent-background/3882028#3882028 * https://stackoverflow.com/questions/331407/java-trayicon-using-image-with-transparent-background/3882028#3882028
*/ */
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"}) @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "WeakerAccess"})
public public final
class _SwingTray extends SwingMenu { class _SwingTray extends Tray implements SwingUI {
private volatile SystemTray tray; private volatile SystemTray tray;
private volatile TrayIcon trayIcon; private volatile TrayIcon trayIcon;
@ -46,7 +49,7 @@ class _SwingTray extends SwingMenu {
// Called in the EDT // Called in the EDT
public public
_SwingTray(final dorkbox.systemTray.SystemTray systemTray) { _SwingTray(final dorkbox.systemTray.SystemTray systemTray) {
super(systemTray, null, new TrayPopup()); super();
if (!SystemTray.isSupported()) { if (!SystemTray.isSupported()) {
throw new RuntimeException("System Tray is not supported in this configuration! Please write an issue and include your OS " + throw new RuntimeException("System Tray is not supported in this configuration! Please write an issue and include your OS " +
@ -54,87 +57,124 @@ class _SwingTray extends SwingMenu {
} }
_SwingTray.this.tray = SystemTray.getSystemTray(); _SwingTray.this.tray = SystemTray.getSystemTray();
}
public // we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
void shutdown() { final SwingMenu swingMenu = new SwingMenu(null) {
dispatchAndWait(new Runnable() {
@Override @Override
public public
void run() { void setEnabled(final MenuItem menuItem) {
removeAll(); dispatch(new Runnable() {
remove(); @Override
public
void run() {
boolean enabled = menuItem.getEnabled();
tray.remove(trayIcon); if (visible && !enabled) {
} tray.remove(trayIcon);
}); visible = false;
} }
else if (!visible && enabled) {
public try {
void setImage_(final File iconFile) { tray.add(trayIcon);
dispatch(new Runnable() { visible = true;
@Override } catch (AWTException e) {
public dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e);
void run() { }
// stupid java won't scale it right away, so we have to do this twice to get the correct size
final Image trayImage = new ImageIcon(iconFile.getAbsolutePath()).getImage();
trayImage.flush();
if (trayIcon == null) {
// here we init. everything
trayIcon = new TrayIcon(trayImage);
JPopupMenu popupMenu = (JPopupMenu) _native;
popupMenu.pack();
popupMenu.setFocusable(true);
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
// they ALSO do not support tooltips, so we cater to the lowest common denominator
// trayIcon.setToolTip("app name");
trayIcon.addMouseListener(new MouseAdapter() {
@Override
public
void mousePressed(MouseEvent e) {
TrayPopup popupMenu = (TrayPopup) _native;
popupMenu.doShow(e.getPoint(), 0);
} }
});
try {
tray.add(trayIcon);
} catch (AWTException e) {
dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e);
} }
} else { });
trayIcon.setImage(trayImage);
}
((TrayPopup) _native).setTitleBarImage(iconFile);
} }
});
}
@SuppressWarnings("Duplicates")
public
void setEnabled(final boolean setEnabled) {
dispatch(new Runnable() {
@Override @Override
public public
void run() { void setImage(final MenuItem menuItem) {
if (visible && !setEnabled) { final File image = menuItem.getImage();
tray.remove(trayIcon); if (image == null) {
visible = false; return;
} }
else if (!visible && setEnabled) {
try { dispatch(new Runnable() {
tray.add(trayIcon); @Override
visible = true; public
} catch (AWTException e) { void run() {
dorkbox.systemTray.SystemTray.logger.error("Error adding the icon back to the tray", e); // stupid java won't scale it right away, so we have to do this twice to get the correct size
final Image trayImage = new ImageIcon(image.getAbsolutePath()).getImage();
trayImage.flush();
if (trayIcon == null) {
// here we init. everything
trayIcon = new TrayIcon(trayImage);
JPopupMenu popupMenu = (JPopupMenu) _native;
popupMenu.pack();
popupMenu.setFocusable(true);
// appindicators DO NOT support anything other than PLAIN gtk-menus (which we hack to support swing menus)
// they ALSO do not support tooltips, so we cater to the lowest common denominator
// trayIcon.setToolTip("app name");
trayIcon.addMouseListener(new MouseAdapter() {
@Override
public
void mousePressed(MouseEvent e) {
TrayPopup popupMenu = (TrayPopup) _native;
popupMenu.doShow(e.getPoint(), 0);
}
});
try {
tray.add(trayIcon);
} catch (AWTException e) {
dorkbox.systemTray.SystemTray.logger.error("TrayIcon could not be added.", e);
}
} else {
trayIcon.setImage(trayImage);
}
((TrayPopup) _native).setTitleBarImage(image);
} }
} });
} }
});
@Override
public
void setText(final MenuItem menuItem) {
// no op
}
@Override
public
void setShortcut(final MenuItem menuItem) {
// no op
}
@Override
public
void remove() {
dispatch(new Runnable() {
@Override
public
void run() {
if (trayIcon != null) {
tray.remove(trayIcon);
trayIcon = null;
}
tray = null;
}
});
super.remove();
}
};
bind(swingMenu, null, systemTray);
}
@Override
public
boolean hasImage() {
return tray.getTrayIcons().length > 0;
} }
} }

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.Image;
import java.awt.RenderingHints; import java.awt.RenderingHints;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -44,6 +45,7 @@ import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.jna.Windows.User32; import dorkbox.systemTray.jna.Windows.User32;
import dorkbox.util.CacheUtil; import dorkbox.util.CacheUtil;
import dorkbox.util.FileUtil; import dorkbox.util.FileUtil;
import dorkbox.util.IO;
import dorkbox.util.LocationResolver; import dorkbox.util.LocationResolver;
import dorkbox.util.OS; import dorkbox.util.OS;
import dorkbox.util.process.ShellProcessBuilder; import dorkbox.util.process.ShellProcessBuilder;
@ -357,12 +359,16 @@ class ImageUtils {
} }
public static synchronized public static synchronized
File resizeAndCache(final int size, final File file) { File resizeAndCache(final int size, final File file, final boolean cacheResult) {
return resizeAndCache(size, file.getAbsolutePath()); return resizeAndCache(size, file.getAbsolutePath(), cacheResult);
} }
public static synchronized public static synchronized
File resizeAndCache(final int size, final String fileName) { File resizeAndCache(final int size, final String fileName, final boolean cacheResult) {
if (fileName == null) {
return null;
}
// check if we already have this file information saved to disk, based on size // check if we already have this file information saved to disk, based on size
final String cacheName = size + "_" + fileName; final String cacheName = size + "_" + fileName;
@ -393,7 +399,11 @@ class ImageUtils {
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
public static synchronized public static synchronized
File resizeAndCache(final int size, final URL imageUrl) { File resizeAndCache(final int size, final URL imageUrl, final boolean cacheResult) {
if (imageUrl == null) {
return null;
}
final String cacheName = size + "_" + imageUrl.getPath(); final String cacheName = size + "_" + imageUrl.getPath();
// if we already have this fileName, reuse it // if we already have this fileName, reuse it
@ -449,16 +459,90 @@ class ImageUtils {
} }
} }
} }
@SuppressWarnings("Duplicates")
public static synchronized
File resizeAndCache(final int size, final Image image, final boolean cacheResult) {
if (image == null) {
return null;
}
// final String cacheName = size + "_" + imageUrl.getPath();
//
// // if we already have this fileName, reuse it
// final File check = getIfCachedOrError(cacheName);
// if (check != null) {
// return check;
// }
//
// // no cached file, so we resize then save the new one.
// boolean needsResize = true;
// try {
// InputStream inputStream = imageUrl.openStream();
// Dimension imageSize = getImageSize(inputStream);
// //noinspection NumericCastThatLosesPrecision
// if (size == ((int) imageSize.getWidth()) && size == ((int) imageSize.getHeight())) {
// // we can reuse this URL (it's the correct size).
// needsResize = false;
// }
// } catch (IOException e) {
// // have to serve up the error image instead.
// SystemTray.logger.error("Error resizing image. Using error icon instead", e);
// return getErrorImage(cacheName);
// }
//
// if (needsResize) {
// // we have to hop through hoops.
// try {
// File resizedFile = resizeFileNoCheck(size, imageUrl);
//
// // now cache that file
// try {
// return CacheUtil.save(cacheName, resizedFile);
// } catch (IOException e) {
// // have to serve up the error image instead.
// SystemTray.logger.error("Error caching image. Using error icon instead", e);
// return getErrorImage(cacheName);
// }
//
// } catch (IOException e) {
// // have to serve up the error image instead.
// SystemTray.logger.error("Error resizing image. Using error icon instead", e);
// return getErrorImage(cacheName);
// }
//
// } else {
// // no resize necessary, just cache as is.
// try {
// return CacheUtil.save(cacheName, imageUrl);
// } catch (IOException e) {
// // have to serve up the error image instead.
// SystemTray.logger.error("Error caching image. Using error icon instead", e);
// return getErrorImage(cacheName);
// }
// }
return null;
}
@SuppressWarnings("Duplicates") @SuppressWarnings("Duplicates")
public static synchronized public static synchronized
File resizeAndCache(final int size, String cacheName, final InputStream imageStream) { File resizeAndCache(final int size, InputStream imageStream, final boolean cacheResult) {
if (cacheName == null) { if (imageStream == null) {
cacheName = CacheUtil.createNameAsHash(imageStream); return null;
}
// have to make a copy of the inputStream.
try {
ByteArrayOutputStream byteArrayOutputStream = IO.copyStream(imageStream);
imageStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
} catch (IOException e) {
throw new RuntimeException("Unable to read from inputStream.", e);
} }
// check if we already have this file information saved to disk, based on size // check if we already have this file information saved to disk, based on size
cacheName = size + "_" + cacheName; final String cacheName = size + "_" + CacheUtil.createNameAsHash(imageStream);
((ByteArrayInputStream) imageStream).reset();
// if we already have this fileName, reuse it // if we already have this fileName, reuse it
final File check = getIfCachedOrError(cacheName); final File check = getIfCachedOrError(cacheName);
@ -513,12 +597,6 @@ class ImageUtils {
} }
} }
public static
File resizeAndCache(final int size, final InputStream imageStream) {
return resizeAndCache(size, null, imageStream);
}
/** /**
* Resizes the given URL to the specified size. No checks are performed if it's the correct size to begin with. * Resizes the given URL to the specified size. No checks are performed if it's the correct size to begin with.
* *

View File

@ -16,44 +16,27 @@
package dorkbox.systemTray.util; package dorkbox.systemTray.util;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import dorkbox.systemTray.Checkbox;
import dorkbox.systemTray.Entry; import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu; import dorkbox.systemTray.Menu;
import dorkbox.systemTray.Separator;
import dorkbox.systemTray.Status;
import dorkbox.systemTray.SystemTray; import dorkbox.systemTray.SystemTray;
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both // this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
@SuppressWarnings("ForLoopReplaceableByForEach") @SuppressWarnings("ForLoopReplaceableByForEach")
public abstract public abstract
class MenuBase implements Menu { class MenuBase extends Menu {
public static final AtomicInteger MENU_ID_COUNTER = new AtomicInteger(); // /**
private final int id = MenuBase.MENU_ID_COUNTER.getAndIncrement(); // * Called in the EDT/GTK dispatch threads
// *
protected final java.util.List<Entry> menuEntries = new ArrayList<Entry>(); // * @param systemTray the system tray (which is the object that sits in the system tray)
// * @param parent the parent of this menu, null if the parent is the system tray
private final SystemTray systemTray; // */
private final Menu parent; // public
// MenuBase(final SystemTray systemTray, final Menu parent) {
/** // setSystemTray(systemTray);
* Called in the EDT/GTK dispatch threads // setParent(parent);
* // }
* @param systemTray the system tray (which is the object that sits in the system tray)
* @param parent the parent of this menu, null if the parent is the system tray
*/
public
MenuBase(final SystemTray systemTray, final Menu parent) {
this.systemTray = systemTray;
this.parent = parent;
}
protected abstract protected abstract
void dispatch(final Runnable runnable); void dispatch(final Runnable runnable);
@ -62,31 +45,43 @@ class MenuBase implements Menu {
void dispatchAndWait(final Runnable runnable); void dispatchAndWait(final Runnable runnable);
/** /**
* Will add a new menu entry * Will add a new menu entry
* NOT ALWAYS CALLED ON DISPATCH * NOT ALWAYS CALLED ON DISPATCH
*/ */
protected abstract // protected abstract
Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback); // Entry addEntry_(final String menuText, final File imagePath, final ActionListener callback);
/** /**
* Will add a new checkbox menu entry * Will add a new checkbox menu entry
* NOT ALWAYS CALLED ON DISPATCH * NOT ALWAYS CALLED ON DISPATCH
*/ */
protected abstract // protected abstract
Checkbox addCheckbox_(final String menuText, final ActionListener callback); // Checkbox addCheckbox_(final String menuText, final ActionListener callback);
/** /**
* Will add a new sub-menu entry * Will add a new sub-menu entry
* NOT ALWAYS CALLED ON DISPATCH * NOT ALWAYS CALLED ON DISPATCH
*/ */
protected abstract // protected abstract
Menu addMenu_(final String menuText, final File imagePath); // Menu addMenu_(final String menuText, final File imagePath);
// public here so that Swing/Gtk/AppIndicator can override this // // public here so that Swing/Gtk/AppIndicator can override this
protected abstract // protected abstract
void setImage_(final File imageFile); // void setImage_(final File imageFile);
@ -95,30 +90,8 @@ class MenuBase implements Menu {
@Override
public final
Menu getParent() {
return parent;
}
@Override
public final
SystemTray getSystemTray() {
return systemTray;
}
// public here so that Swing/Gtk/AppIndicator can access this
public final
String getStatus() {
synchronized (menuEntries) {
Entry entry = menuEntries.get(0);
if (entry instanceof Status) {
return entry.getText();
}
}
return null;
}
// TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however. // TODO: buggy. The menu will **sometimes** stop responding to the "enter" key after this. Mnemonics still work however.
// public // public
@ -146,316 +119,88 @@ class MenuBase implements Menu {
// } // }
@Override // public final
public final // Entry get(final String menuText) {
Entry get(final String menuText) { // if (menuText == null || menuText.isEmpty()) {
if (menuText == null || menuText.isEmpty()) { // return null;
return null; // }
} //
// // Must be wrapped in a synchronized block for object visibility
// Must be wrapped in a synchronized block for object visibility // synchronized (menuEntries) {
synchronized (menuEntries) { // for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) { // final Entry entry = menuEntries.get(i);
final Entry entry = menuEntries.get(i); //
// if (entry instanceof Separator || entry instanceof Status) {
if (entry instanceof Separator || entry instanceof Status) { // continue;
continue; // }
} //
//// String text = entry.getText();
String text = entry.getText(); //
// // text can be null
// text can be null //// if (menuText.equals(text)) {
if (menuText.equals(text)) { //// return entry;
return entry; //// }
} // }
} // }
} //
// return null;
return null; // }
}
// ignores status + separators
@Override
public final
Entry getFirst() {
return get(0);
}
// ignores status + separators
@Override
public final
Entry getLast() {
// Must be wrapped in a synchronized block for object visibility
synchronized (menuEntries) {
if (!menuEntries.isEmpty()) {
Entry entry;
for (int i = menuEntries.size()-1; i >= 0; i--) {
entry = menuEntries.get(i);
if (!(entry instanceof Separator || entry instanceof Status)) {
return entry;
}
}
}
}
return null;
}
// ignores status + separators
@Override
public final
Entry get(final int menuIndex) {
if (menuIndex < 0) {
return null;
}
// Must be wrapped in a synchronized block for object visibility
synchronized (menuEntries) {
if (!menuEntries.isEmpty()) {
int count = 0;
for (Entry entry : menuEntries) {
if (entry instanceof Separator || entry instanceof Status) {
continue;
}
if (count == menuIndex) {
return entry;
}
count++;
}
}
}
return null;
}
@Override
public final
Entry addEntry(String menuText, ActionListener callback) {
return addEntry(menuText, (String) null, callback);
}
@Override
public final
Entry addEntry(String menuText, String imagePath, ActionListener callback) {
if (imagePath == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath), callback);
}
}
@Override
public final
Entry addEntry(String menuText, URL imageUrl, ActionListener callback) {
if (imageUrl == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl), callback);
}
}
@Override
public final
Entry addEntry(String menuText, String cacheName, InputStream imageStream, ActionListener callback) {
if (imageStream == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream), callback);
}
}
@Override
public final
Entry addEntry(String menuText, InputStream imageStream, ActionListener callback) {
if (imageStream == null) {
return addEntry_(menuText, null, callback);
}
else {
return addEntry_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream), callback);
}
}
@Override
public
Checkbox addCheckbox(final String menuText, final ActionListener callback) {
return addCheckbox_(menuText, callback);
}
@Override
public final
Menu addMenu(String menuText) {
return addMenu(menuText, (String) null);
}
@Override
public final
Menu addMenu(String menuText, String imagePath) {
if (imagePath == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath));
}
}
@Override
public final
Menu addMenu(String menuText, URL imageUrl) {
if (imageUrl == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl));
}
}
@Override
public final
Menu addMenu(String menuText, String cacheName, InputStream imageStream) {
if (imageStream == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream));
}
}
@Override
public final
Menu addMenu(String menuText, InputStream imageStream) {
if (imageStream == null) {
return addMenu_(menuText, null);
}
else {
return addMenu_(menuText, ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream));
}
}
@Override
public final
void setImage(final File imageFile) {
if (imageFile == null) {
setImage_(null);
}
else {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageFile));
}
}
@Override // /**
public final // * This removes a menu entry from the dropdown menu.
void setImage(final String imagePath) { // *
if (imagePath == null) { // * @param entry This is the menu entry to remove
setImage_(null); // */
} // @Override
else { // public final
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imagePath)); // void remove(final Entry entry) {
} // if (entry == null) {
} // throw new NullPointerException("No menu entry exists for entry");
// }
@Override //
public final // dispatchAndWait(new Runnable() {
void setImage(final URL imageUrl) { // @Override
if (imageUrl == null) { // public
setImage_(null); // void run() {
} // remove__(entry);
else { // }
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageUrl)); // });
} // }
} //
// /**
@Override // * This removes a sub-menu entry from the dropdown menu.
public final // *
void setImage(final String cacheName, final InputStream imageStream) { // * @param menu This is the menu entry to remove
if (imageStream == null) { // */
setImage_(null); // @Override
} // public final
else { // void remove(final Menu menu) {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, cacheName, imageStream)); // final Menu parent = getParent();
} // if (parent == null) {
} // // we are the system tray menu, so we just remove from ourselves
// dispatchAndWait(new Runnable() {
@Override // @Override
public final // public
void setImage(final InputStream imageStream) { // void run() {
if (imageStream == null) { // remove__(menu);
setImage_(null); // }
} // });
else { // } else {
setImage_(ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, imageStream)); // final Menu _this = this;
} // // we are a submenu
} // dispatchAndWait(new Runnable() {
// @Override
// public
@Override // void run() {
public final // ((MenuBase) parent).remove__(_this);
void setCallback(final ActionListener callback) { // }
} // });
// }
// }
/**
* This removes a menu entry from the dropdown menu.
*
* @param entry This is the menu entry to remove
*/
@Override
public final
void remove(final Entry entry) {
if (entry == null) {
throw new NullPointerException("No menu entry exists for entry");
}
dispatchAndWait(new Runnable() {
@Override
public
void run() {
remove__(entry);
}
});
}
/**
* This removes a sub-menu entry from the dropdown menu.
*
* @param menu This is the menu entry to remove
*/
@Override
public final
void remove(final Menu menu) {
final Menu parent = getParent();
if (parent == null) {
// we are the system tray menu, so we just remove from ourselves
dispatchAndWait(new Runnable() {
@Override
public
void run() {
remove__(menu);
}
});
} else {
final Menu _this = this;
// we are a submenu
dispatchAndWait(new Runnable() {
@Override
public
void run() {
((MenuBase) parent).remove__(_this);
}
});
}
}
// NOT ALWAYS CALLED ON EDT // NOT ALWAYS CALLED ON EDT
protected protected
@ -493,68 +238,44 @@ class MenuBase implements Menu {
} }
} }
/** // /**
* This removes a menu entry or sub-menu (via the text label) from the dropdown menu. // * This removes a menu entry or sub-menu (via the text label) from the dropdown menu.
* // *
* @param menuText This is the label for the menu entry or sub-menu to remove // * @param menuText This is the label for the menu entry or sub-menu to remove
*/ // */
public final // public final
void remove(final String menuText) { // void remove(final String menuText) {
dispatchAndWait(new Runnable() { // dispatchAndWait(new Runnable() {
@Override // @Override
public // public
void run() { // void run() {
synchronized (menuEntries) { // synchronized (menuEntries) {
Entry entry = get(menuText); // Entry entry = get(menuText);
//
if (entry != null) { // if (entry != null) {
remove(entry); // remove(entry);
} // }
} // }
} // }
}); // });
} // }
//
@Override // @Override
public final // public final
void removeAll() { // void removeAll() {
dispatch(new Runnable() { // dispatch(new Runnable() {
@Override // @Override
public // public
void run() { // void run() {
synchronized (menuEntries) { // synchronized (menuEntries) {
// have to make copy because we are deleting all of them, and sub-menus remove themselves from parents // // have to make copy because we are deleting all of them, and sub-menus remove themselves from parents
ArrayList<Entry> menuEntriesCopy = new ArrayList<Entry>(MenuBase.this.menuEntries); // ArrayList<Entry> menuEntriesCopy = new ArrayList<Entry>(MenuBase.this.menuEntries);
for (Entry entry : menuEntriesCopy) { // for (Entry entry : menuEntriesCopy) {
entry.remove(); // entry.remove();
} // }
menuEntries.clear(); // menuEntries.clear();
} // }
} // }
}); // });
} // }
@Override
public final
int hashCode() {
return id;
}
@Override
public final
boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
MenuBase other = (MenuBase) obj;
return this.id == other.id;
}
} }

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