Removed Windows Native Tray +menu WIP

This commit is contained in:
nathan 2018-04-08 21:58:56 +02:00
parent 2c004d2c46
commit 4b1eb95601
7 changed files with 0 additions and 1542 deletions

View File

@ -1,167 +0,0 @@
/*
* Copyright 2017 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.nativeUI;
import static com.sun.jna.platform.win32.WinDef.HMENU;
import static dorkbox.systemTray.nativeUI.WindowsMenu.MFT_OWNERDRAW;
import static dorkbox.util.jna.windows.User32.MF_BYPOSITION;
import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import com.sun.jna.platform.win32.BaseTSD;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.SwingUtil;
import dorkbox.util.jna.windows.GDI32;
import dorkbox.util.jna.windows.GetLastErrorException;
import dorkbox.util.jna.windows.HBITMAPWrap;
import dorkbox.util.jna.windows.User32;
import dorkbox.util.jna.windows.structs.MENUITEMINFO;
public class WindowsBaseMenuItem {
private final int position;
volatile HBITMAPWrap hbitmapWrapImage;
volatile String text = "";
volatile boolean enabled = true; // default is enabled
// these have to be volatile, because they can be changed from any thread
private volatile ActionListener callback;
private volatile ActionEvent actionEvent;
public
WindowsBaseMenuItem(int position) {
this.position = position;
}
void setText(final String text) {
if (text != null) {
this.text = text;
} else {
this.text = "";
}
}
void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
void setImage(final File imageFile) {
if (imageFile != null) {
SwingUtil.invokeAndWaitQuietly(new Runnable() {
@Override
public
void run() {
// has to run on swing EDT.
ImageIcon imageIcon = new ImageIcon(imageFile.getAbsolutePath());
// fully loads the image and returns when it's done loading the image
imageIcon = new ImageIcon(imageIcon.getImage());
hbitmapWrapImage = convertMenuImage(imageIcon);
}
});
}
else {
hbitmapWrapImage = null;
}
}
void setCallback(final ActionListener callback, final ActionEvent actionEvent) {
this.callback = callback;
this.actionEvent = actionEvent;
}
void fireCallback() {
ActionEvent actionEvent = this.actionEvent;
ActionListener callback = this.callback;
if (callback != null) {
try {
callback.actionPerformed(actionEvent);
} catch (Throwable throwable) {
SystemTray.logger.error("Error calling menu entry {} click event.", this.text, throwable);
}
}
}
private static HBITMAPWrap convertMenuImage(Icon icon) {
// BufferedImage img = createBitmap(icon);
int menubarHeight = WindowsMenu.getSystemMenuImageSize();
BufferedImage scaledImage = new BufferedImage(menubarHeight, menubarHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = scaledImage.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
icon.paintIcon(null, g, 0, 0);
// g.drawImage(img, 0, 0, menubarHeight, menubarHeight, null);
g.dispose();
return new HBITMAPWrap(scaledImage);
}
void remove() {
if (hbitmapWrapImage != null) {
GDI32.DeleteObject(hbitmapWrapImage);
hbitmapWrapImage = null;
}
}
boolean hasImage() {
return false;
}
void onCreateMenu(final HMENU parentNative, final boolean hasImages) {
// setSpacerImage(hasImagesInMenu);
if (!User32.IMPL.AppendMenu(parentNative, MFT_OWNERDRAW, position, null)) {
throw new GetLastErrorException();
}
MENUITEMINFO mmi = new MENUITEMINFO();
if (!User32.IMPL.GetMenuItemInfo(parentNative, position, false, mmi)) {
throw new GetLastErrorException();
}
mmi.dwItemData = new BaseTSD.ULONG_PTR(position);
mmi.fMask |= MENUITEMINFO.MIIM_DATA;
if (!User32.IMPL.SetMenuItemInfo(parentNative, position, false, mmi)) {
throw new GetLastErrorException();
}
}
void onDeleteMenu(final HMENU parentNative) {
remove();
if (!User32.IMPL.DeleteMenu(parentNative, position, MF_BYPOSITION)) {
throw new GetLastErrorException();
}
}
}

View File

@ -1,741 +0,0 @@
/*
* Copyright 2017 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.nativeUI;
import static com.sun.jna.platform.win32.WinDef.HBITMAP;
import static com.sun.jna.platform.win32.WinDef.HDC;
import static com.sun.jna.platform.win32.WinDef.HFONT;
import static com.sun.jna.platform.win32.WinDef.HMENU;
import static com.sun.jna.platform.win32.WinDef.HWND;
import static com.sun.jna.platform.win32.WinDef.LPARAM;
import static com.sun.jna.platform.win32.WinDef.POINT;
import static com.sun.jna.platform.win32.WinDef.RECT;
import static com.sun.jna.platform.win32.WinDef.WPARAM;
import static com.sun.jna.platform.win32.WinNT.HANDLE;
import static com.sun.jna.platform.win32.WinUser.AC_SRC_ALPHA;
import static com.sun.jna.platform.win32.WinUser.AC_SRC_OVER;
import static com.sun.jna.platform.win32.WinUser.SIZE;
import static com.sun.jna.platform.win32.WinUser.SM_CYMENUCHECK;
import static dorkbox.util.jna.windows.WindowsEventDispatch.MF_POPUP;
import static dorkbox.util.jna.windows.WindowsEventDispatch.WM_COMMAND;
import static dorkbox.util.jna.windows.WindowsEventDispatch.WM_DRAWITEM;
import static dorkbox.util.jna.windows.WindowsEventDispatch.WM_MEASUREITEM;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sun.jna.Pointer;
import dorkbox.systemTray.Checkbox;
import dorkbox.systemTray.Entry;
import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Separator;
import dorkbox.systemTray.Status;
import dorkbox.systemTray.peer.MenuPeer;
import dorkbox.util.jna.windows.GDI32;
import dorkbox.util.jna.windows.GetLastErrorException;
import dorkbox.util.jna.windows.Listener;
import dorkbox.util.jna.windows.MsImg32;
import dorkbox.util.jna.windows.User32;
import dorkbox.util.jna.windows.WindowsEventDispatch;
import dorkbox.util.jna.windows.structs.BLENDFUNCTION;
import dorkbox.util.jna.windows.structs.DRAWITEMSTRUCT;
import dorkbox.util.jna.windows.structs.MEASUREITEMSTRUCT;
import dorkbox.util.jna.windows.structs.NONCLIENTMETRICS;
// this is a weird composite class, because it must be a Menu, but ALSO a Entry -- so it has both
@SuppressWarnings("ForLoopReplaceableByForEach")
class WindowsMenu extends WindowsBaseMenuItem implements MenuPeer {
volatile HMENU _nativeMenu; // must ONLY be created at the end of delete!
private final WindowsMenu parent;
private final Listener menuItemListener;
private final Listener menuItemMeasureListener;
private final Listener menuItemDrawListener;
public static final int WM_NULL = 0x0000;
public static final int VK_ESCAPE = 0x1B;
public static final int WM_KEYDOWN = 0x0100;
public static final int TPM_RECURSE = 0x0001;
public static final int TPM_RIGHTBUTTON = 0x0002;
public static final int MFT_OWNERDRAW = 256;
private static final int SPACE_ICONS = 2;
// have to make sure no other methods can call obliterate, delete, or create menu once it's already started
private AtomicBoolean obliterateInProgress = new AtomicBoolean(false);
// this is a list (that mirrors the actual list) BECAUSE we have to create/delete the entire menu in Windows every time something is changed
private final List<WindowsBaseMenuItem> menuEntries = new ArrayList<WindowsBaseMenuItem>();
// called by the system tray constructors
// This is NOT a copy constructor!
@SuppressWarnings("IncompleteCopyConstructor")
WindowsMenu() {
super(0);
this.parent = null;
// Register drawing menu items, etc
menuItemListener = new Listener() {
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
int position = wParam.intValue() & 0xff;
WindowsBaseMenuItem item = menuEntries.get(position);
item.fireCallback();
}
};
menuItemMeasureListener = new Listener() {
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
MEASUREITEMSTRUCT ms = new MEASUREITEMSTRUCT(new Pointer(lParam.longValue()));
int position = ms.itemData.intValue();
WindowsBaseMenuItem item = menuEntries.get(position);
SIZE size = measureItem(hWnd, item);
ms.itemWidth = size.cx;
ms.itemHeight = size.cy;
ms.write();
}
};
menuItemDrawListener = new Listener() {
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
DRAWITEMSTRUCT di = new DRAWITEMSTRUCT(new Pointer(lParam.longValue()));
int position = di.itemData.intValue();
WindowsBaseMenuItem item = menuEntries.get(position);
drawItem(item, di.hDC, di.rcItem, di.itemState);
}
};
WindowsEventDispatch.addListener(WM_COMMAND, menuItemListener);
WindowsEventDispatch.addListener(WM_MEASUREITEM, menuItemMeasureListener);
WindowsEventDispatch.addListener(WM_DRAWITEM, menuItemDrawListener);
}
// This is NOT a copy constructor!
@SuppressWarnings("IncompleteCopyConstructor")
private
WindowsMenu(final WindowsMenu parent, final int index) {
super(index); // is what is added to the parent menu (so images work)
this.parent = parent;
// Register drawing menu items, etc
menuItemListener = new Listener() {
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
int position = wParam.intValue() & 0xff;
WindowsBaseMenuItem item = menuEntries.get(position);
item.fireCallback();
}
};
menuItemMeasureListener = new Listener() {
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
MEASUREITEMSTRUCT ms = new MEASUREITEMSTRUCT(new Pointer(lParam.longValue()));
int position = ms.itemData.intValue();
WindowsBaseMenuItem item = menuEntries.get(position);
SIZE size = measureItem(hWnd, item);
ms.itemWidth = size.cx;
ms.itemHeight = size.cy;
ms.write();
}
};
menuItemDrawListener = new Listener() {
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
DRAWITEMSTRUCT di = new DRAWITEMSTRUCT(new Pointer(lParam.longValue()));
int position = di.itemData.intValue();
WindowsBaseMenuItem item = menuEntries.get(position);
drawItem(item, di.hDC, di.rcItem, di.itemState);
}
};
WindowsEventDispatch.addListener(WM_COMMAND, menuItemListener);
WindowsEventDispatch.addListener(WM_MEASUREITEM, menuItemMeasureListener);
WindowsEventDispatch.addListener(WM_DRAWITEM, menuItemDrawListener);
}
WindowsMenu getParent() {
return parent;
}
/**
* Deletes the menu, and unreferences everything in it. ALSO recreates ONLY the menu object.
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
private
void deleteMenu() {
if (obliterateInProgress.get()) {
return;
}
if (_nativeMenu != null) {
// have to work in reverse so the index is preserved
for (int i = menuEntries.size()-1; i >= 0; i--) {
final WindowsBaseMenuItem menuEntry__ = menuEntries.get(i);
menuEntry__.onDeleteMenu(_nativeMenu);
}
if (!User32.IMPL.DestroyMenu(_nativeMenu)) {
throw new GetLastErrorException();
}
}
if (parent != null) {
parent.deleteMenu();
}
// makes a new one
this._nativeMenu = User32.IMPL.CreatePopupMenu();
// binds sub-menu to entry (if it exists! it does not for the root menu)
if (parent != null) {
// get around windows crap (transfer handle to decimal)
int handle = (int) Pointer.nativeValue(_nativeMenu.getPointer());
if (!User32.IMPL.AppendMenu(getParent()._nativeMenu, MF_POPUP | MFT_OWNERDRAW, handle, null)) {
throw new GetLastErrorException();
}
}
}
/**
* some GTK libraries DO NOT let us add items AFTER the menu has been attached to the indicator.
*
* To work around this issue, we destroy then recreate the menu every time something is changed.
*
* ALWAYS CALLED ON THE EDT
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
private
void createMenu() {
if (obliterateInProgress.get()) {
return;
}
if (parent != null) {
parent.createMenu();
}
// now add back other menu entries
boolean hasImages = false;
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
final WindowsBaseMenuItem menuEntry__ = menuEntries.get(i);
hasImages |= menuEntry__.hasImage();
}
for (int i = 0, menuEntriesSize = menuEntries.size(); i < menuEntriesSize; i++) {
// the menu entry looks FUNKY when there are a mis-match of entries WITH and WITHOUT images
final WindowsBaseMenuItem menuEntry__ = menuEntries.get(i);
menuEntry__.onCreateMenu(_nativeMenu, hasImages);
if (menuEntry__ instanceof WindowsMenu) {
WindowsMenu subMenu = (WindowsMenu) menuEntry__;
if (subMenu.getParent() != WindowsMenu.this) {
// we don't want to "createMenu" on our sub-menu that is assigned to us directly, as they are already doing it
subMenu.createMenu();
}
}
}
// Gtk.gtk_widget_show_all(_nativeMenu); // necessary to guarantee widget is visible (doesn't always show_all for all children)
// onMenuAdded(_nativeMenu); // not needed for windows
}
/**
* Completely obliterates the menu, no possible way to reconstruct it.
*
* ALWAYS CALLED ON THE EDT
*/
@SuppressWarnings("ForLoopReplaceableByForEach")
private
void obliterateMenu() {
if (_nativeMenu != null && !obliterateInProgress.get()) {
obliterateInProgress.set(true);
// have to remove all other menu entries
// a copy is made because sub-menus remove themselves from parents when .remove() is called. If we don't
// do this, errors will be had because indices don't line up anymore.
ArrayList<WindowsBaseMenuItem> menuEntriesCopy = new ArrayList<WindowsBaseMenuItem>(menuEntries);
menuEntries.clear();
// have to work in reverse so the index is preserved
for (int i = menuEntriesCopy.size()-1; i >= 0; i--) {
final WindowsBaseMenuItem menuEntry__ = menuEntriesCopy.get(i);
menuEntry__.onDeleteMenu(_nativeMenu);
}
menuEntriesCopy.clear();
if (!User32.IMPL.DestroyMenu(_nativeMenu)) {
throw new GetLastErrorException();
}
_nativeMenu = null;
obliterateInProgress.set(false);
}
}
@Override
public
void add(final Menu parentMenu, final Entry entry, int index) {
deleteMenu();
if (entry instanceof Menu) {
// WindowsMenu item = new WindowsMenu(WindowsMenu.this, index);
// menuEntries.add(index, item);
// ((Menu) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Separator) {
// WindowsMenuItemSeparator item = new WindowsMenuItemSeparator(WindowsMenu.this);
// menuEntries.add(index, item);
// entry.bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Checkbox) {
// WindowsMenuItemCheckbox item = new WindowsMenuItemCheckbox(WindowsMenu.this);
// menuEntries.add(index, item);
// ((Checkbox) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof Status) {
// WindowsMenuItemStatus item = new WindowsMenuItemStatus(WindowsMenu.this);
// menuEntries.add(index, item);
// ((Status) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
else if (entry instanceof MenuItem) {
WindowsMenuItem item = new WindowsMenuItem(WindowsMenu.this, index);
menuEntries.add(index, item);
((MenuItem) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}
createMenu();
}
// is overridden in tray impl
@Override
public
void setImage(final MenuItem menuItem) {
super.setImage(menuItem.getImage());
}
// is overridden in tray impl
@Override
public
void setEnabled(final MenuItem menuItem) {
super.setEnabled(menuItem.getEnabled());
}
// is overridden in tray impl
@Override
public
void setText(final MenuItem menuItem) {
super.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) {
// // yikes...
// final int vKey = SwingUtil.getVirtualKey(menuItem.getShortcut());
//
// SwingUtil.invokeLater(new Runnable() {
// @Override
// public
// void run() {
// _native.setShortcut(new MenuShortcut(vKey));
// }
// });
}
/**
* called when a child removes itself from the parent menu. Does not work for sub-menus
*
* ALWAYS CALLED ON THE EDT
*/
public
void remove(final WindowsBaseMenuItem item) {
menuEntries.remove(item);
// have to rebuild the menu now...
deleteMenu(); // must be on EDT
createMenu(); // must be on EDT
}
// a child will always remove itself from the parent.
@Override
public
void remove() {
WindowsMenu parent = getParent();
if (parent != null) {
// have to remove from the parent.menuEntries first
parent.menuEntries.remove(WindowsMenu.this);
}
// delete all of the children of this submenu (must happen before the menuEntry is removed)
obliterateMenu();
if (parent != null) {
// have to rebuild the menu now...
parent.deleteMenu(); // must be on EDT
parent.createMenu(); // must be on EDT
}
}
void showContextMenu(final POINT position) {
HWND mainHwnd = WindowsEventDispatch.get();
User32.IMPL.SetForegroundWindow(mainHwnd);
// TrackPopupMenu blocks until popup menu is closed
if (!User32.IMPL.TrackPopupMenu(_nativeMenu, TPM_RIGHTBUTTON, position.x, position.y, 0, mainHwnd, null)) {
// Popup menu already active
if (com.sun.jna.platform.win32.Kernel32.INSTANCE.GetLastError() == 0x000005a6) {
HWND hWnd = null;
while (true) {
// "#32768" - Name of the popup menu class
hWnd = User32.IMPL.FindWindowEx(null, hWnd, "#32768", null);
if (hWnd == null) {
break;
}
// close the previous popup menu
User32.IMPL.SendMessage(hWnd, WM_KEYDOWN, new WPARAM(VK_ESCAPE), new LPARAM(0));
}
return;
} else {
throw new GetLastErrorException();
}
}
User32.IMPL.PostMessage(mainHwnd, WM_NULL, new WPARAM(0), new LPARAM(0));
}
// // menus have to be cleared before each render.
// void clearMenus() {
// if (_native != null) {
// if (!User32.DestroyMenu(_native)) {
// System.err.println("PROBLEM");
//// throw new GetLastErrorException();
// }
// _native = null;
// }
// for (WindowsBaseMenuItem m : menuEntries) {
// m.close();
// }
//
// hMenusIDs.clear();
// }
// void updateMenus() {
// clearMenus();
//
// HMENU hmenu = User32.CreatePopupMenu();
// this._native = hmenu;
//
// for (int i = 0; i < menu.getComponentCount(); i++) {
// Component e = menu.getComponent(i);
//
// if (e instanceof JMenu) {
// JMenu sub = (JMenu) e;
// HMENU hsub = createSubmenu(sub);
//
// int nID = menuEntries.size();
// menuEntries.add(new WindowsBaseMenuItem(sub));
//
// // you know, the usual windows tricks (transfer handle to
// // decimal)
// int handle = (int) Pointer.nativeValue(hsub.getPointer());
//
// if (!User32.AppendMenu(hmenu, MF_POPUP | MFT_OWNERDRAW, handle, null))
// throw new GetLastErrorException();
//
// MENUITEMINFO mi = new MENUITEMINFO();
// if (!User32.GetMenuItemInfo(hmenu, handle, false, mi))
// throw new GetLastErrorException();
//
// mi.dwItemData = new ULONG_PTR(nID);
// mi.fMask |= MENUITEMINFO.MIIM_DATA;
// if (!User32.SetMenuItemInfo(hmenu, handle, false, mi))
// throw new GetLastErrorException();
//
//
// } else if (e instanceof JCheckBoxMenuItem) {
// JCheckBoxMenuItem ch = (JCheckBoxMenuItem) e;
//
// int nID = menuEntries.size();
// menuEntries.add(new WindowsBaseMenuItem(ch));
//
// if (!User32.AppendMenu(hmenu, MFT_OWNERDRAW, nID, null))
// throw new GetLastErrorException();
//
// MENUITEMINFO mmi = new MENUITEMINFO();
// if (!User32.GetMenuItemInfo(hmenu, nID, false, mmi))
// throw new GetLastErrorException();
// mmi.dwItemData = new ULONG_PTR(nID);
// mmi.fMask |= MENUITEMINFO.MIIM_DATA;
// if (!User32.SetMenuItemInfo(hmenu, nID, false, mmi))
// throw new GetLastErrorException();
// } else if (e instanceof JMenuItem) {
// JMenuItem mi = (JMenuItem) e;
//
// int nID = menuEntries.size();
// menuEntries.add(new WindowsBaseMenuItem(mi));
//
// if (!User32.AppendMenu(hmenu, MFT_OWNERDRAW, nID, null))
// throw new GetLastErrorException();
//
// MENUITEMINFO mmi = new MENUITEMINFO();
// if (!User32.GetMenuItemInfo(hmenu, nID, false, mmi))
// throw new GetLastErrorException();
// mmi.dwItemData = new ULONG_PTR(nID);
// mmi.fMask |= MENUITEMINFO.MIIM_DATA;
// if (!User32.SetMenuItemInfo(hmenu, nID, false, mmi))
// throw new GetLastErrorException();
// }
//
// if (e instanceof JPopupMenu.Separator) {
// if (!User32.AppendMenu(hmenu, MF_SEPARATOR, 0, null))
// throw new GetLastErrorException();
// }
// }
// }
// HMENU createSubmenu(JMenu menu) {
// HMENU hmenu = User32.CreatePopupMenu();
// // seems like you dont have to free this menu, since it already attached
// // to main HMENU handler
//
// for (int i = 0; i < menu.getMenuComponentCount(); i++) {
// Component e = menu.getMenuComponent(i);
//
// if (e instanceof JMenu) {
// JMenu sub = (JMenu) e;
// HMENU hsub = createSubmenu(sub);
//
// // you know, the usual windows tricks (transfer handle to
// // decimal)
// int handle = (int) Pointer.nativeValue(hsub.getPointer());
//
// int nID = menuEntries.size();
// menuEntries.add(new WindowsBaseMenuItem(sub));
//
// if (!User32.AppendMenu(hmenu, MF_POPUP | MFT_OWNERDRAW, handle, null))
// throw new GetLastErrorException();
//
// MENUITEMINFO mi = new MENUITEMINFO();
// if (!User32.GetMenuItemInfo(hmenu, handle, false, mi))
// throw new GetLastErrorException();
// mi.dwItemData = new ULONG_PTR(nID);
// mi.fMask |= MENUITEMINFO.MIIM_DATA;
// if (!User32.SetMenuItemInfo(hmenu, handle, false, mi))
// throw new GetLastErrorException();
// } else if (e instanceof JCheckBoxMenuItem) {
// JCheckBoxMenuItem ch = (JCheckBoxMenuItem) e;
//
// int nID = menuEntries.size();
// menuEntries.add(new WindowsBaseMenuItem(ch));
//
// if (!User32.AppendMenu(hmenu, MFT_OWNERDRAW, nID, null))
// throw new GetLastErrorException();
//
// MENUITEMINFO mi = new MENUITEMINFO();
// if (!User32.GetMenuItemInfo(hmenu, nID, false, mi))
// throw new GetLastErrorException();
// mi.dwItemData = new ULONG_PTR(nID);
// mi.fMask |= MENUITEMINFO.MIIM_DATA;
// if (!User32.SetMenuItemInfo(hmenu, nID, false, mi))
// throw new GetLastErrorException();
// } else if (e instanceof JMenuItem) {
// JMenuItem mi = (JMenuItem) e;
//
// int nID = menuEntries.size();
// menuEntries.add(new WindowsBaseMenuItem(mi));
//
// if (!User32.AppendMenu(hmenu, MFT_OWNERDRAW, nID, null))
// throw new GetLastErrorException();
//
// MENUITEMINFO mmi = new MENUITEMINFO();
// if (!User32.GetMenuItemInfo(hmenu, nID, false, mmi))
// throw new GetLastErrorException();
// mmi.dwItemData = new ULONG_PTR(nID);
// mmi.fMask |= MENUITEMINFO.MIIM_DATA;
// if (!User32.SetMenuItemInfo(hmenu, nID, false, mmi))
// throw new GetLastErrorException();
// }
//
// if (e instanceof JPopupMenu.Separator) {
// if (!User32.AppendMenu(hmenu, MF_SEPARATOR, 0, null))
// throw new GetLastErrorException();
// }
// }
//
// return hmenu;
// }
private static
void drawItem(WindowsBaseMenuItem item, HDC hDC, RECT rcItem, int itemState) {
if (!item.enabled) {
GDI32.SetTextColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_GRAYTEXT));
GDI32.SetBkColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_MENU));
}
else if ((itemState & DRAWITEMSTRUCT.ODS_SELECTED) == DRAWITEMSTRUCT.ODS_SELECTED) {
GDI32.SetTextColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_HIGHLIGHTTEXT));
GDI32.SetBkColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_HIGHLIGHT));
}
else {
GDI32.SetTextColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_MENUTEXT));
GDI32.SetBkColor(hDC, User32.IMPL.GetSysColor(User32.COLOR_MENU));
}
int x = rcItem.left;
int y = rcItem.top;
x += (getSystemMenuImageSize() + SPACE_ICONS) * 2;
GDI32.SelectObject(hDC, createSystemMenuFont());
GDI32.ExtTextOut(hDC,
x,
y,
GDI32.ETO_OPAQUE,
rcItem,
item.text,
item.text.length(),
null);
x = rcItem.left;
// if (item.item instanceof JCheckBoxMenuItem) {
// JCheckBoxMenuItem cc = (JCheckBoxMenuItem) item.item;
// if (cc.getState()) {
// // draw checkmark image
//// drawHBITMAP(hbitmapChecked, x, y, hbitmapChecked.getImage().getWidth(),
//// hbitmapChecked.getImage().getHeight(), hDC);
// }
// else {
// // draw blank checkmark image
//// drawHBITMAP(hbitmapUnchecked, x, y, hbitmapUnchecked.getImage().getWidth(),
//// hbitmapUnchecked.getImage().getHeight(), hDC);
// }
// }
x += getSystemMenuImageSize() + SPACE_ICONS;
if (item.hbitmapWrapImage != null) {
drawHBITMAP(item.hbitmapWrapImage,
x,
y,
item.hbitmapWrapImage.getImage().getWidth(),
item.hbitmapWrapImage.getImage().getHeight(),
hDC);
}
}
public static
int getSystemMenuImageSize() {
// get's the height of the default (small) checkmark
return User32.IMPL.GetSystemMetrics(SM_CYMENUCHECK);
}
static
HFONT createSystemMenuFont() {
NONCLIENTMETRICS nm = new NONCLIENTMETRICS();
User32.IMPL.SystemParametersInfo(User32.SPI_GETNONCLIENTMETRICS, 0, nm, 0);
return GDI32.CreateFontIndirect(nm.lfMenuFont);
}
private static
void drawHBITMAP(HBITMAP hbm, int x, int y, int cx, int cy, HDC hdcDst) {
HDC hdcSrc = GDI32.CreateCompatibleDC(hdcDst);
HANDLE old = GDI32.SelectObject(hdcSrc, hbm);
BLENDFUNCTION.ByValue bld = new BLENDFUNCTION.ByValue();
bld.BlendOp = AC_SRC_OVER;
bld.BlendFlags = 0;
bld.SourceConstantAlpha = (byte) 255;
bld.AlphaFormat = AC_SRC_ALPHA;
if (!MsImg32.AlphaBlend(hdcDst, x, y, cx, cy, hdcSrc, 0, 0, cx, cy, bld)) {
throw new GetLastErrorException();
}
GDI32.SelectObject(hdcSrc, old);
if (!GDI32.DeleteDC(hdcSrc)) {
throw new GetLastErrorException();
}
}
private static
SIZE measureItem(HWND hWnd, WindowsBaseMenuItem item) {
HDC hdc = User32.IMPL.GetDC(hWnd);
HANDLE hfntOld = GDI32.SelectObject(hdc, createSystemMenuFont());
SIZE size = new SIZE();
if (!GDI32.GetTextExtentPoint32(hdc,
item.text,
item.text.length(),
size)) {
throw new GetLastErrorException();
}
GDI32.SelectObject(hdc, hfntOld);
User32.IMPL.ReleaseDC(hWnd, hdc);
size.cx += (getSystemMenuImageSize() + SPACE_ICONS) * 2;
return size;
}
}

View File

@ -1,94 +0,0 @@
/*
* Copyright 2017 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.nativeUI;
import java.awt.event.ActionEvent;
import dorkbox.systemTray.peer.MenuItemPeer;
class WindowsMenuItem extends WindowsBaseMenuItem implements MenuItemPeer {
private final WindowsMenu parent;
// this is ALWAYS called on the EDT.
WindowsMenuItem(final WindowsMenu parent, final int index) {
super(index);
this.parent = parent;
}
@Override
public
void setImage(final dorkbox.systemTray.MenuItem menuItem) {
super.setImage(menuItem.getImage());
}
@Override
public
void setEnabled(final dorkbox.systemTray.MenuItem menuItem) {
super.setEnabled(menuItem.getEnabled());
}
@Override
public
void setText(final dorkbox.systemTray.MenuItem menuItem) {
super.setText(menuItem.getText());
}
@SuppressWarnings("Duplicates")
@Override
public
void setCallback(final dorkbox.systemTray.MenuItem menuItem) {
super.setCallback(menuItem.getCallback(), new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, ""));
}
@Override
public
void setShortcut(final dorkbox.systemTray.MenuItem menuItem) {
// char shortcut = menuItem.getShortcut();
// // yikes...
// final int vKey = SwingUtil.getVirtualKey(shortcut);
//
// SwingUtil.invokeLater(new Runnable() {
// @Override
// public
// void run() {
// _native.setShortcut(new MenuShortcut(vKey));
// }
// });
}
@SuppressWarnings("Duplicates")
@Override
public
void remove() {
// SwingUtil.invokeLater(new Runnable() {
// @Override
// public
// void run() {
// _native.deleteShortcut();
// _native.setEnabled(false);
//
// if (swingCallback != null) {
// _native.removeActionListener(swingCallback);
// swingCallback = null;
// }
// parent._native.remove(_native);
//
// _native.removeNotify();
// }
// });
}
}

View File

@ -1,153 +0,0 @@
/*
* Copyright 2017 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.nativeUI;
import java.awt.event.ActionListener;
import dorkbox.systemTray.Checkbox;
import dorkbox.systemTray.peer.CheckboxPeer;
class WindowsMenuItemCheckbox extends WindowsBaseMenuItem implements CheckboxPeer {
private final WindowsMenu parent;
private final java.awt.CheckboxMenuItem _native = new java.awt.CheckboxMenuItem();
// these have to be volatile, because they can be changed from any thread
private volatile ActionListener callback;
private volatile boolean isChecked = false;
// this is ALWAYS called on the EDT.
WindowsMenuItemCheckbox(final WindowsMenu parent) {
super(0);
this.parent = parent;
// parent._native.add(_native);
}
@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.setLabel(menuItem.getText());
// }
// });
}
@SuppressWarnings("Duplicates")
@Override
public
void setCallback(final Checkbox menuItem) {
// if (callback != null) {
// _native.removeActionListener(callback);
// }
//
// callback = menuItem.getCallback(); // can be set to null
//
// if (callback != null) {
// callback = 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(callback);
// }
}
@Override
public
void setShortcut(final Checkbox menuItem) {
// char shortcut = menuItem.getShortcut();
// // yikes...
// final int vKey = SwingUtil.getVirtualKey(shortcut);
//
// SwingUtil.invokeLater(new Runnable() {
// @Override
// public
// void run() {
// _native.setShortcut(new MenuShortcut(vKey));
// }
// });
}
@Override
public
void setChecked(final Checkbox menuItem) {
// boolean checked = menuItem.getChecked();
//
// // only dispatch if it's actually different
// if (checked != this.isChecked) {
// this.isChecked = checked;
//
// SwingUtil.invokeLater(new Runnable() {
// @Override
// public
// void run() {
// _native.setState(isChecked);
// }
// });
// }
}
@SuppressWarnings("Duplicates")
@Override
public
void remove() {
// SwingUtil.invokeLater(new Runnable() {
// @Override
// public
// void run() {
// _native.deleteShortcut();
// _native.setEnabled(false);
//
// if (callback != null) {
// _native.removeActionListener(callback);
// callback = null;
// }
// parent._native.remove(_native);
//
// _native.removeNotify();
// }
// });
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2017 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.nativeUI;
import dorkbox.systemTray.peer.EntryPeer;
class WindowsMenuItemSeparator extends WindowsBaseMenuItem implements EntryPeer {
private final WindowsMenu parent;
private final java.awt.MenuItem _native = new java.awt.MenuItem("-");
// this is ALWAYS called on the EDT.
WindowsMenuItemSeparator(final WindowsMenu parent) {
super(0);
this.parent = parent;
// parent._native.add(_native);
}
@Override
public
void remove() {
// SwingUtil.invokeLater(new Runnable() {
// @Override
// public
// void run() {
// parent._native.remove(_native);
// }
// });
}
}

View File

@ -1,71 +0,0 @@
/*
* Copyright 2017 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.nativeUI;
import java.awt.MenuItem;
import dorkbox.systemTray.Status;
import dorkbox.systemTray.peer.StatusPeer;
class WindowsMenuItemStatus extends WindowsBaseMenuItem implements StatusPeer {
private final WindowsMenu parent;
private final MenuItem _native = new MenuItem();
WindowsMenuItemStatus(final WindowsMenu parent) {
super(0);
this.parent = parent;
// status is ALWAYS at 0 index...
// parent._native.insert(_native, 0);
}
@Override
public
void setText(final Status menuItem) {
// SwingUtil.invokeLater(new Runnable() {
// @Override
// public
// void run() {
// Font font = _native.getFont();
// if (font == null) {
// font = new Font(DIALOG, Font.BOLD, 12); // the default font used for dialogs.
// }
// else {
// font = font.deriveFont(Font.BOLD);
// }
//
// _native.setFont(font);
// _native.setLabel(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);
// }
// });
}
}

View File

@ -1,271 +0,0 @@
/*
* Copyright 2017 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.nativeUI;
import static com.sun.jna.platform.win32.WinDef.HWND;
import static com.sun.jna.platform.win32.WinDef.LPARAM;
import static com.sun.jna.platform.win32.WinDef.POINT;
import static com.sun.jna.platform.win32.WinDef.WPARAM;
import static com.sun.jna.platform.win32.WinUser.WM_QUIT;
import static dorkbox.util.jna.windows.Shell32.NIM_ADD;
import static dorkbox.util.jna.windows.Shell32.NIM_DELETE;
import static dorkbox.util.jna.windows.Shell32.NIM_MODIFY;
import static dorkbox.util.jna.windows.Shell32.Shell_NotifyIcon;
import static dorkbox.util.jna.windows.User32.WM_LBUTTONUP;
import static dorkbox.util.jna.windows.User32.WM_RBUTTONUP;
import static dorkbox.util.jna.windows.WindowsEventDispatch.WM_SHELLNOTIFY;
import static dorkbox.util.jna.windows.WindowsEventDispatch.WM_TASKBARCREATED;
import java.io.File;
import javax.swing.ImageIcon;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.Tray;
import dorkbox.util.ImageUtil;
import dorkbox.util.jna.windows.HBITMAPWrap;
import dorkbox.util.jna.windows.HICONWrap;
import dorkbox.util.jna.windows.Kernel32;
import dorkbox.util.jna.windows.Listener;
import dorkbox.util.jna.windows.Shell32;
import dorkbox.util.jna.windows.User32;
import dorkbox.util.jna.windows.WindowsEventDispatch;
import dorkbox.util.jna.windows.structs.NOTIFYICONDATA;
/**
* Native implementation of a System tray on Windows, derivative of the original implementation by Nathan Sweet (BSD License).
*/
public
class _WindowsNativeTray extends Tray implements NativeUI {
private final Listener quitListener;
private final Listener menuListener;
// is the system tray visible or not.
private volatile boolean visible = true;
private volatile File imageFile;
private volatile HICONWrap imageIcon;
private volatile String tooltipText = "";
private final Listener showListener;
public _WindowsNativeTray (final dorkbox.systemTray.SystemTray systemTray) {
super();
// we override various methods, because each tray implementation is SLIGHTLY different. This allows us customization.
final WindowsMenu windowsMenu = new WindowsMenu() {
@Override
public
void setEnabled(final MenuItem menuItem) {
boolean enabled = menuItem.getEnabled();
if (visible && !enabled) {
// hide
hide();
}
else if (!visible && enabled) {
// show
show();
}
}
@Override
public
void setImage(final MenuItem menuItem) {
imageFile = menuItem.getImage();
if (imageIcon != null) {
imageIcon.close();
}
imageIcon = convertImage(imageFile);
NOTIFYICONDATA nid = new NOTIFYICONDATA();
nid.hWnd = WindowsEventDispatch.get();
nid.setIcon(imageIcon);
if (!Shell32.Shell_NotifyIcon(NIM_MODIFY, nid)) {
SystemTray.logger.error("Error setting the image for the tray. {}", Kernel32.getLastErrorMessage());
}
}
@Override
public
void setText(final MenuItem menuItem) {
// no op.
}
@Override
public
void setShortcut(final MenuItem menuItem) {
// no op
}
@Override
public
void remove() {
hide();
super.remove();
User32.IMPL.PostMessage(WindowsEventDispatch.get(), WM_QUIT, new WPARAM(0), new LPARAM(0));
}
};
// will wait until it's started up.
WindowsEventDispatch.start();
HWND hWnd = WindowsEventDispatch.get();
if (hWnd == null) {
throw new RuntimeException("The Windows System Tray is not supported! Please write an issue and include your OS type and " +
"configuration");
}
showListener = new Listener() {
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
show();
}
};
quitListener = new Listener() {
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
System.err.println("quit listener");
WindowsEventDispatch.stop();
WindowsEventDispatch.removeListener(showListener);
WindowsEventDispatch.removeListener(quitListener);
WindowsEventDispatch.removeListener(menuListener);
}
};
menuListener = new Listener() {
final POINT mousePosition = new POINT();
@Override
public
void run(final HWND hWnd, final WPARAM wParam, final LPARAM lParam) {
int lp = lParam.intValue();
switch (lp) {
case WM_LBUTTONUP:
if (User32.IMPL.GetCursorPos(mousePosition)) {
windowsMenu.showContextMenu(mousePosition);
}
break;
case WM_RBUTTONUP:
if (User32.IMPL.GetCursorPos(mousePosition)) {
windowsMenu.showContextMenu(mousePosition);
}
break;
}
}
};
WindowsEventDispatch.addListener(WM_TASKBARCREATED, showListener);
WindowsEventDispatch.addListener(WM_QUIT, quitListener);
WindowsEventDispatch.addListener(WM_SHELLNOTIFY, menuListener);
show();
// Runtime.getRuntime().addShutdownHook(new Thread() {
// @Override
// public void run () {
// Shell_NotifyIcon(NIM_DELETE, windowNotifyIconData);
// }
// });
bind(windowsMenu, null, systemTray);
}
// public synchronized void balloon (String title, String message, int millis) {
// balloonNotifyIconData.hWnd = this.windowNotifyIconData.hWnd;
// balloonNotifyIconData.uID = this.windowNotifyIconData.uID;
// balloonNotifyIconData.setBalloon(title, message, millis, NIIF_NONE);
// Shell_NotifyIcon(NIM_MODIFY, balloonNotifyIconData);
// }
private void hide() {
if (imageIcon != null) {
imageIcon.close();
imageIcon = null;
}
NOTIFYICONDATA nid = new NOTIFYICONDATA();
nid.hWnd = WindowsEventDispatch.get();
if (!Shell32.Shell_NotifyIcon(NIM_DELETE, nid)) {
SystemTray.logger.error("Error hiding tray. {}", Kernel32.getLastErrorMessage());
}
visible = false;
}
private void show() {
if (imageIcon != null) {
imageIcon.close();
}
imageIcon = convertImage(imageFile);
NOTIFYICONDATA nid = new NOTIFYICONDATA();
nid.hWnd = WindowsEventDispatch.get();
nid.setTooltip(tooltipText);
nid.setIcon(imageIcon);
nid.setCallback(WM_SHELLNOTIFY);
if (!Shell_NotifyIcon(NIM_ADD, nid)) {
SystemTray.logger.error("Error showing tray. {}", Kernel32.getLastErrorMessage());
}
visible = true;
}
@Override
protected
void setTooltip_(final String tooltipText) {
if (this.tooltipText.equals(tooltipText)){
return;
}
this.tooltipText = tooltipText;
NOTIFYICONDATA nid = new NOTIFYICONDATA();
nid.hWnd = WindowsEventDispatch.get();
nid.setTooltip(tooltipText);
Shell_NotifyIcon(NIM_MODIFY, nid);
}
@Override
public
boolean hasImage() {
return imageFile != null;
}
private static
HICONWrap convertImage(final File imageFile) {
if (imageFile != null) {
ImageIcon imageIcon = new ImageIcon(imageFile.getAbsolutePath());
// fully loads the image and returns when it's done loading the image
imageIcon = new ImageIcon(imageIcon.getImage());
HBITMAPWrap hbitmapTrayIcon = new HBITMAPWrap(ImageUtil.getBufferedImage(imageIcon));
return new HICONWrap(hbitmapTrayIcon);
}
return null;
}
}