www.gusucode.com > JAVA程序的系统托盘源码程序 > JAVA程序的系统托盘源码程序\www.gusucode.com\tray\com\jeans\trayicon\c++\WindowsTrayIcon.cpp

    /**
 * WindowsTrayIcon.cpp - a tray icon for Java
 * Best viewed with tab size 8
 *
 * Written by Jan Struyf
 *
 *  jan.struyf@cs.kuleuven.ac.be
 *  http://jeans.studentenweb.org/java/trayicon/trayicon.html
 *
 * Changelog
 *
 * Version pre1.6c (07/16/01)
 *	* Fixed minor compilation warnings reported by the more strict VC++ 6.0
 *
 * Version pre1.6b (12/16/00)
 *	* Fixed memory leak for 'animating icons'
 *      * ReleaseDC -> DeleteDC
 *
 * Version pre1.6 (09/02/00)
 *	* Support for old JDK/JRE 1.1.x
 *	* TrayIcon 1.6 will support Microsoft Visual J++
 *
 * Version 1.5 (07/03/00)
 *	* Tray icon popup menu support
 *	* Added code for sendWindowsMessage()
 *
 * Version 1.4 (06/29/00)
 *	* Added DllMain function to clean init code
 *	* Removed redundant LoadLibrary/FreeLibrary calls
 *	* Added code for isRunning()
 *
 * Version 1.3 (06/09/00)
 *	* Trail bug fix for NT (no public release)
 *		(Patch from 'Daniel Hope <danielh@inform.co.nz>')
 *
 * Version 1.2 (05/03/00)
 *	* Message handler for first icon fixed
 *	* WM_RBUTTONDOWN message handler fixed
 *	* Classes are now unregistered on exit
 *		(Patch from 'Daniel Rejment <daniel@rejment.com>')
 *
 * Version 1.0 (06/29/99)
 *	* Initial release
 *
 * Please mail me if you
 *	- 've found bugs
 *	- like this program
 *	- don't like a particular feature
 *	- would like something to be modified
 *
 * To compile:
 *	- Use the MDP project file in the VC++ IDE
 *	- Use Makefile.vc as in:
 *		VCVARS32
 *		nmake /f makefile.vc
 *
 * I always give it my best shot to make a program useful and solid, but
 * remeber that there is absolutely no warranty for using this program as
 * stated in the following terms:
 *
 * THERE IS NO WARRANTY FOR THIS PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE
 * LAW. THE COPYRIGHT HOLDER AND/OR OTHER PARTIES WHO MAY HAVE MODIFIED THE
 * PROGRAM, PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
 * TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
 * PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 * REPAIR OR CORRECTION.
 *
 * IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL ANY COPYRIGHT HOLDER,
 * OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM,
 * BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
 * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
 * PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
 * INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE
 * PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
 * PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * May the Force be with you... Just compile it & use it!
 */

#include <windows.h>
#include <windowsx.h>
#include <process.h>
#include <jni.h>
#include "com_jeans_trayicon_WindowsTrayIcon.h"

#if defined(HAS_JVM_LIB) + defined(HAS_OLD_JVM_LIB) == 0
	#define NO_JVM_LIB
#endif

// Message ID for sendWindowsMessage() function
#define MYWM_APPTALK		(WM_APP+10)
// Message ID for Tray icons
#define MYWM_NOTIFYICON		(WM_APP+100)
// Max number of icons supported by this DLL for each application
#define MY_MAX_ICONS		100
// Max length for the name of the hidden window (used for sendWindowsMessage/isRunning)
#define WNDNAME_MAX         20

// Class for the icon bitmap
class IconData {
private:
	HBITMAP hBitmapXOR;	// Colors
	HBITMAP	hBitmapAND;	// Transparancy info
	int wd, hi;
public:
	IconData();
	~IconData();
	int setData(unsigned long *data, int wd, int hi);
	HICON makeIcon(HINSTANCE hInst);
};

// Class for a popup submenu
class PopupSubMenu {
private:
	HMENU hMenu;
	BOOL bSub;
public:
	PopupSubMenu();
	~PopupSubMenu();
	BOOL isSub();
	void makeSub();
	void reNewMenu();
	HMENU getMenu();
	void TrackPopupMenu(UINT flags, POINT* pos, HWND hWnd);
};

typedef PopupSubMenu* PtrPopupSubMenu;

// Class for a popup menu (the main popup)
class PopupMenu {
private:
	PopupSubMenu **popup;
	int nbLevels;
public:
	PopupMenu(int levels);
	~PopupMenu();
	void TrackPopupMenu(UINT flags, POINT* pos, HWND hWnd);
	HMENU getMenu(int level);
	void initMenu(int level);
	PopupSubMenu *getSubMenu(int level);
};

// Growable array for integers
// Used to store the allocated menu id's
class QSIntArray {
	protected:
		int *m_Array;
		long m_Size, m_Grow, m_ArrSize;
	public:
		QSIntArray();
		QSIntArray(long size, long grow);
		~QSIntArray();
		long getSize();
		int getElementAt(long idx);
		void addElement(int element);
		void setElementAt(long idx, int element);
		void removeElementAt(long idx);
		void removeAll();
	private:
		void shrink();
		void grow();
};

#define JNIProcPtr(name) int (FAR *name) (JNIEnv*, int, int)

// Information for each tray icon
typedef struct {
	BOOL used;		// Is this record in use?
	BOOL visible;		// Icon visible on screen or hidden?
	IconData *icon;		// Icon bitmap data
	char *tooltip;		// Icon tooltip
	PopupMenu *popup;	// Main popup for icon
	jobject globalClass;	// Pointer to Java class for callbacks
} TrayIconData;

// Information for Java callback
typedef struct {
	JNIProcPtr(jni_proc);
	int arg1, arg2;
} ThreadJavaCallback;

// Some error codes
#define TRAY_NOERR		0
#define TRAY_NOTIFYPROCERR	-1
#define TRAY_TOOMANYICONS	-2
#define TRAY_NOTENOUGHMEM	-3
#define TRAY_WRONGICONID	-4
#define TRAY_DLLNOTFOUND	-5
#define TRAY_NOVMS		-6
#define TRAY_ERRTHREAD		-7
#define TRAY_METHODID		-8
#define TRAY_NOLISTENER		-9
#define TRAY_JNIERR		-10
#define TRAY_CALLBACKDLLERR	-11

// Constants for the popup menu system
#define POPUP_TYPE_ITEM       0
#define POPUP_TYPE_SEPARATOR  1
#define POPUP_TYPE_CHECKBOX   2
#define POPUP_TYPE_INIT_LEVEL 3
#define POPUP_TYPE_DONE_LEVEL 4

// Current instance of the TrayIcon DLL
HINSTANCE g_hinst = NULL;
// Handle to the hidden window used to receive the tray icon messages
HWND my_hDlg = NULL;
// App name for the hidden window's registered class
CHAR szAppName[] = "QSJavaTray";
// Title for the hidden window (used for sendWindowsMessage/isRunning)
CHAR szWndName[WNDNAME_MAX+1];
// Number of icons allocated
int nb_tray_icons = 0;
// Record for each icon
TrayIconData tray_icons[MY_MAX_ICONS];
// Contains last error message (see error codes above)
jint last_error = TRAY_NOERR;
// Wait while creating the hidden window
HANDLE wait_event = NULL;
// Wait while destroying hidden window
HANDLE exit_event = NULL;
// Maps menu id's to icon id's
QSIntArray *arrUsedMenuIds = NULL;
// Mouse position
POINT mouse_pos;

#ifdef NO_JVM_LIB
    JavaVM *hJavaVM;
#endif

// Get free menu id (for icon id - see arrUsedMenuIds)
long getFreeMenuId(int id_num);
// Free menu id (when removing menu from icon)
void setFreeMenuId(int id_num);
// Map menu id to icon id
int getMenuItemIdNum(int id);
// Add/Remove/Modify tray icon to system tray
BOOL TrayMessage(HWND hDlg, DWORD dwMessage, UINT uID, HICON hIcon, PSTR pszTip);
// Create the hidden window to receive the icon's mouse messages
HWND MakeHWND(HINSTANCE hInst);
// Remove the hidden window (on app closing)
void RemoveHWND();
// The thread proc that creates the hidden window an listens for messages
void DialogThread( void *dummy );
// Free an icon's menu resources
void freeMenu(int id_num);
// Free an icon's other resources
void freeIcon(JNIEnv *env, int id_num);
// Clean up @ exit of app
void cleanUpExit(JNIEnv *env);
// Call TrayMessage for the specified icon id
void updateIcon(jint id_num);
// Make an icon dissapear from the system tray
void makeInvisible(jint id_num);
// Delete global reference to icon's Java class
int DeleteGlobalCallback(JNIEnv *env, int id_num, int dummy);
// Call a Java method in all available VM's (used for mouse/menu callbacks)
int CallJavaVMS(JNIProcPtr(JNIProc), int arg1, int arg2);
// Call a Java method in a given virtual machine
int CallJavaVM(JavaVM* vm, JNIProcPtr(JNIProc), int arg1, int arg2);

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    getLastError
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_getLastError(JNIEnv *, jclass) {
	int err = last_error;
	last_error = TRAY_NOERR;
	return last_error;		// Return the last error
}

/*
 * Class:     WindowsTrayIcon
 * Method:    getFreeId
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_getFreeId(JNIEnv *env, jclass) {
    #ifdef NO_JVM_LIB
        env->GetJavaVM(&hJavaVM);
    #endif
	// Wait for the hidden window to be created (see initTrayIcon())
	if (wait_event != NULL) {
		WaitForSingleObject(wait_event,10000);
		CloseHandle(wait_event);
		wait_event = NULL;
	}
	// Hidden window handle valid?
	if (my_hDlg == NULL) {
		last_error = TRAY_NOTIFYPROCERR;
		return TRAY_NOTIFYPROCERR;
	}
	// Instance handle valid?
	if (g_hinst == NULL) {
		last_error = TRAY_DLLNOTFOUND;
		return TRAY_DLLNOTFOUND;
	}
	// Find unused icon record and return icon id
	for (int ctr = 0; ctr < MY_MAX_ICONS; ctr++) {
		if (tray_icons[ctr].used == FALSE) {
			// Unused record, initialize fields and return id
			tray_icons[ctr].used = TRUE;
			tray_icons[ctr].visible = FALSE;
			tray_icons[ctr].icon = NULL;
			tray_icons[ctr].tooltip = NULL;
			tray_icons[ctr].popup = NULL;
			return ctr;
		}
	}
	// No empty record left, return error
	last_error = TRAY_NOTIFYPROCERR;
	return TRAY_TOOMANYICONS;
}

/*
 * Class:     WindowsTrayIcon
 * Method:    setIconData
 * Signature: (III[I)V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_setIconData(JNIEnv *env, jclass, jint id_num, jint wd, jint hi, jintArray array) {
	// Instance handle valid?
	if (g_hinst == NULL) {
		last_error = TRAY_DLLNOTFOUND;
		return;
	}
	// Icon id valid?
	if (tray_icons[id_num].used == FALSE) {
		last_error = TRAY_WRONGICONID;
		return;
	}
	// Get array length and elements
	jsize len = env->GetArrayLength(array);
	jint *body = env->GetIntArrayElements(array, 0);
	// Allocate data for icon bitmap and store the given pixel array
	IconData *data = new IconData();
	if (data != NULL && data->setData((unsigned long *)body, wd, hi) != -1) {
		IconData *olddata = tray_icons[id_num].icon;
		tray_icons[id_num].icon = data;
		updateIcon(id_num);
		if (olddata != NULL) delete olddata;
	} else {
		delete data;
		last_error = TRAY_NOTENOUGHMEM;
	}
	// Release java array
	env->ReleaseIntArrayElements(array, body, 0);
}

/*
 * Class:     WindowsTrayIcon
 * Method:    showIcon
 * Signature: (IZ)V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_showIcon(JNIEnv *, jclass, jint id_num, jboolean show) {
	// Hidden window handle valid?
	if (my_hDlg == NULL) {
		last_error = TRAY_NOTIFYPROCERR;
		return;
	}
	// Instance handle valid?
	if (g_hinst == NULL) {
		last_error = TRAY_DLLNOTFOUND;
		return;
	}
	// Icon id valid?
	if (tray_icons[id_num].used == FALSE) {
		last_error = TRAY_WRONGICONID;
		return;
	}
	if (show) {
		// Show icon in windows system tray
		if (tray_icons[id_num].visible == FALSE && tray_icons[id_num].icon != NULL) {
			HICON icon = tray_icons[id_num].icon->makeIcon(g_hinst);
			if (icon != NULL) {
				TrayMessage(my_hDlg, NIM_ADD, id_num, icon, tray_icons[id_num].tooltip);
				tray_icons[id_num].visible = TRUE;
			} else {
				last_error = TRAY_NOTENOUGHMEM;
			}
		}
	} else {
		// Hide icon (make invisible)
		if (tray_icons[id_num].visible == TRUE) {
			TrayMessage(my_hDlg, NIM_DELETE, id_num, NULL, NULL);
			tray_icons[id_num].visible = FALSE;
		}
	}
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    testVisible
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_testVisible(JNIEnv *, jclass, jint id_num) {
	// Icon id valid?
	if (tray_icons[id_num].used == FALSE) {
		last_error = TRAY_WRONGICONID;
		return TRAY_WRONGICONID;
	}
	// Is the icon visible now?
	if (tray_icons[id_num].visible == TRUE) {
		return 1;
	} else {
		return 0;
	}
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    clickEnable
 * Signature: (Ljeans/graph/trayicon/WindowsTrayIcon;IZ)V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_clickEnable(JNIEnv *env, jclass, jobject ic_cls, jint id_num, jboolean enable) {
	// Icon id valid?
	if (tray_icons[id_num].used == FALSE) {
		last_error = TRAY_WRONGICONID;
		return;
	}
	if (enable == TRUE) {
		// Make icon clickenable by storing a global reference to it's class
		jobject globalWinTrayClass = env->NewGlobalRef(ic_cls);
		if (globalWinTrayClass != 0) {
			if (tray_icons[id_num].globalClass != 0)
				env->DeleteGlobalRef(tray_icons[id_num].globalClass);
			tray_icons[id_num].globalClass = globalWinTrayClass;
		}
	} else {
		// Remove the icon's global class reference
		if (tray_icons[id_num].globalClass != 0)
			env->DeleteGlobalRef(tray_icons[id_num].globalClass);
		tray_icons[id_num].globalClass = 0;
	}
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    setToolTip
 * Signature: (ILjava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_setToolTip(JNIEnv *env, jclass, jint id_num, jstring tip) {
	// Icon id valid?
	if (tray_icons[id_num].used == FALSE) {
		last_error = TRAY_WRONGICONID;
		return;
	}
	// Get java string for tooltip
	const char *tooltip = env->GetStringUTFChars(tip, 0);
	int len = strlen(tooltip);
	// If already exists delete first..
	if (tray_icons[id_num].tooltip != NULL)	delete tray_icons[id_num].tooltip;
	tray_icons[id_num].tooltip = new char[len+1];
	strcpy(tray_icons[id_num].tooltip, tooltip);
	// Release java string
	env->ReleaseStringUTFChars(tip, tooltip);
	updateIcon(id_num);
}

/*
 * Class:     WindowsTrayIcon
 * Method:    freeIcon
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_freeIcon(JNIEnv *env, jclass, jint id_num) {
	freeIcon(env, id_num);
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    initTrayIcon
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_initTrayIcon(JNIEnv *env, jclass, jstring wndName) {
	if (my_hDlg == NULL) {
		// Copy window name
		const char *cWndName = env->GetStringUTFChars(wndName, 0);
		strncpy(szWndName ,cWndName, WNDNAME_MAX);
		szWndName[WNDNAME_MAX] = 0;
		env->ReleaseStringUTFChars(wndName, cWndName);
		// Initialize icon data struct
		for (int ctr = 0; ctr < MY_MAX_ICONS; ctr++) tray_icons[ctr].used = FALSE;
		// Popup invisible dummy window
		if (g_hinst != NULL) {
			wait_event = CreateEvent(NULL,FALSE,FALSE,NULL);
			_beginthread(DialogThread, 0, NULL );
		}
	}
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    isRunning
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT jboolean JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_isRunning(JNIEnv *env, jclass, jstring wndName) {
	const char *cWndName = env->GetStringUTFChars(wndName, 0);
	// Find out if there's a hidden window with the given title
	HWND mHwnd = FindWindow(szAppName, cWndName);
	env->ReleaseStringUTFChars(wndName, cWndName);
	// If there is, another instance of our app is already running
	return mHwnd != NULL;
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    sendWindowsMessage
 * Signature: (Ljava/lang/String;J)J
 */
JNIEXPORT jint JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_sendWindowsMessage
  (JNIEnv *env, jclass, jstring wndName, jint lParam) {
	const char *cWndName = env->GetStringUTFChars(wndName, 0);
	// Find hidden window handle by name
	HWND mHwnd = FindWindow(szAppName, cWndName);
	env->ReleaseStringUTFChars(wndName, cWndName);
	// If the window exists, send out our message and wait for return value
	if (mHwnd == NULL) return -1;
	else return SendMessage(mHwnd, MYWM_APPTALK, 0, lParam);
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    termTrayIcon
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_termTrayIcon(JNIEnv *env, jclass) {
	cleanUpExit(env);
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    initPopup
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_initPopup
  (JNIEnv *env, jclass, jint id_num, jint nbLevels) {
	// Icon id valid?
	if (tray_icons[id_num].used == FALSE) {
		last_error = TRAY_WRONGICONID;
		return;
	}
	// Free previous allocated menu
	freeMenu(id_num);
	// Create new popup menu with given depth
	tray_icons[id_num].popup = new PopupMenu(nbLevels);
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    checkPopup
 * Signature: (IIZ)V
 */
JNIEXPORT void JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_checkPopup
  (JNIEnv *env, jclass, jint id_num, jint menuId, jboolean selected) {
	// Get main popup menu handle of icon
	PopupMenu *popup = tray_icons[id_num].popup;
	if (popup != NULL) {
		HMENU menu = popup->getMenu(0);
		// Add check mark or remove check mark
		UINT how = selected ?
			MF_BYCOMMAND | MF_CHECKED :
			MF_BYCOMMAND | MF_UNCHECKED;
		// Check the menu item by command id (menuId)
		CheckMenuItem(menu, menuId, how);
	}
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    subPopup
 * Signature: (IILjava/lang/String;I)I
 */
JNIEXPORT jint JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_subPopup
  (JNIEnv *env, jclass, jint id_num, jint level, jstring menuName, jint type) {
	// Return a menu id for the new submenu item
	jint id = -1;
	// Icon id valid?
	if (tray_icons[id_num].used == FALSE) {
		last_error = TRAY_WRONGICONID;
		return -1;
	}
	// Popup valid? (use initPopup())
	if (tray_icons[id_num].popup != NULL) {
		const char *cMenuName;
		PopupMenu *popup = tray_icons[id_num].popup;
		if (type == POPUP_TYPE_INIT_LEVEL || type == POPUP_TYPE_DONE_LEVEL) {
			// Add new level to the popup menu (= new submenu)
			switch (type) {
				case POPUP_TYPE_INIT_LEVEL:
					// Marks the first item of the new level
					popup->initMenu(level);
					break;
				case POPUP_TYPE_DONE_LEVEL:
					// Marks the last item of the current level
					if (level > 0) {
						PopupSubMenu *menu = popup->getSubMenu(level);
						HMENU hMenu = popup->getMenu(level-1);
						HMENU sMenu = menu->getMenu();
						// Get the name for the submenu
						cMenuName = env->GetStringUTFChars(menuName, 0);
						// Append the submenu to the menu one level back (thus the parent menu)
						AppendMenu(hMenu,MF_POPUP | MF_ENABLED | MF_UNCHECKED, (UINT)sMenu, cMenuName);
						env->ReleaseStringUTFChars(menuName, cMenuName);
						// Sub menus must not be destroyed, only parents must: mark as sub
						menu->makeSub();
					}
					break;
			}
		} else {
			// Add a regular item to a (sub)menu
			HMENU hMenu = popup->getMenu(level);
			if (hMenu == NULL) return -1;
			switch (type) {
				case POPUP_TYPE_ITEM:
				case POPUP_TYPE_CHECKBOX:
					// Get menu item name (checkbox or simple menu item)
					cMenuName = env->GetStringUTFChars(menuName, 0);
					// Get free id for the new menu item
					id = getFreeMenuId(id_num);
					// Append the new item to the existing menu
					AppendMenu(hMenu, MF_STRING | MF_ENABLED | MF_UNCHECKED, id, cMenuName);
					env->ReleaseStringUTFChars(menuName, cMenuName);
					break;
				case POPUP_TYPE_SEPARATOR:
					// Append a separator to the menu
					AppendMenu(hMenu,MF_SEPARATOR, 0, NULL);
					break;
			}
		}
	}
	// Return the id of the new menu item (used for callback messages)
	return id;
}

/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    getMouseX
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_getMouseX(JNIEnv *, jclass) {
	return (jint)mouse_pos.x;
}
  
/*
 * Class:     jeans_trayicon_WindowsTrayIcon
 * Method:    getMouseY
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_jeans_trayicon_WindowsTrayIcon_getMouseY(JNIEnv *, jclass) {
	return (jint)mouse_pos.y;
}

// Main proc of DLL, called on initialisation, termination
BOOL WINAPI DllMain(HANDLE hInst, ULONG fdwReason, LPVOID lpReserved) {
    switch(fdwReason) {
    case DLL_PROCESS_ATTACH:
			// Store the instance handle
			g_hinst = (HINSTANCE)hInst;
			// Make new map for menu ids
			if (arrUsedMenuIds == NULL) arrUsedMenuIds = new QSIntArray();
			break;
		case DLL_THREAD_ATTACH:
		case DLL_THREAD_DETACH:
			break;
		case DLL_PROCESS_DETACH:
			// Delete the map for menu ids
			if (arrUsedMenuIds != NULL) {
				delete arrUsedMenuIds;
				arrUsedMenuIds = NULL;
			}
			break;
    }
    // Initialisation OK
    return TRUE;
}

// This proc is called on exit (before Java's System.exit())
// Free all icon resources and remove the hidden window
void cleanUpExit(JNIEnv *env) {
	for (int id_num = 0; id_num < MY_MAX_ICONS; id_num++) freeIcon(env, id_num);
	RemoveHWND();
}

// Free menu resources for icon with given id
void freeMenu(int id_num) {
	// Free allocated menu id's
	setFreeMenuId(id_num);
	// Free popup class
	if (tray_icons[id_num].popup != NULL) {
		delete tray_icons[id_num].popup;
		tray_icons[id_num].popup = NULL;
	}
}

// Free all icon resources for given id
// Make invisible, destroy icon, destroy tooltip, destroy global reference, free menu,..
void freeIcon(JNIEnv *env, int id_num) {
	// Icon handle valid?
	if (tray_icons[id_num].used == TRUE) {
		// Make invisible
		if (tray_icons[id_num].visible == TRUE) {
			makeInvisible(id_num);
		}
		// Make invalid
		tray_icons[id_num].used = FALSE;
		// Free icon
		if (tray_icons[id_num].icon != NULL) {
			delete tray_icons[id_num].icon;
			tray_icons[id_num].icon = NULL;
		}
		// Free tooltip
		if (tray_icons[id_num].tooltip != NULL) {
			delete tray_icons[id_num].tooltip;
			tray_icons[id_num].tooltip = NULL;
		}
		// Free global ref to callback class for mouse/menu events
		if (tray_icons[id_num].globalClass != 0) {
			if (env != NULL) env->DeleteGlobalRef(tray_icons[id_num].globalClass);
			else CallJavaVMS(DeleteGlobalCallback, id_num, 0);
			tray_icons[id_num].globalClass = 0;
		}
		// Free menu ids and resources
		freeMenu(id_num);
	}
}

// Update icon with given id in system tray
// Show or Hide and add tooltip,..
void updateIcon(jint id_num) {
	// Valid hidden window handle, icon id an visible?
	if (my_hDlg != NULL && tray_icons[id_num].used == TRUE && tray_icons[id_num].visible == TRUE) {
		// Valid instance handle and icon resources?
		if (g_hinst != NULL && tray_icons[id_num].icon != NULL) {
			// Get icon handle
			HICON icon = tray_icons[id_num].icon->makeIcon(g_hinst);
			if (icon != NULL) {
				// Modify icon status
				TrayMessage(my_hDlg, NIM_MODIFY, id_num, icon, tray_icons[id_num].tooltip);
			} else {
				last_error = TRAY_NOTENOUGHMEM;
			}
		} else {
			// Make icon invisible if no valid resources
			makeInvisible(id_num);
		}
	}
}

// Hide icon
void makeInvisible(jint id_num) {
	// Valid icon id and currently visible?
	if (tray_icons[id_num].used == TRUE && tray_icons[id_num].visible == TRUE) {
		// Make invisible
		if (my_hDlg != NULL) TrayMessage(my_hDlg, NIM_DELETE, id_num, NULL, NULL);
		tray_icons[id_num].visible = FALSE;
	}
}

// Add/Remove/Modify tray icon to system tray
BOOL TrayMessage(HWND hDlg, DWORD dwMessage, UINT uID, HICON hIcon, PSTR pszTip) {
	BOOL res;
	// Fill data struct for tray icon
	NOTIFYICONDATA tnd;
	tnd.cbSize		= sizeof(NOTIFYICONDATA);
	tnd.hWnd		= hDlg;
	tnd.uID			= uID;
	tnd.uFlags		= NIF_MESSAGE | NIF_ICON | NIF_TIP;
	tnd.uCallbackMessage	= MYWM_NOTIFYICON;
	tnd.hIcon		= hIcon;
	// Include tooltip?
	if (pszTip) {
		lstrcpyn(tnd.szTip, pszTip, sizeof(tnd.szTip));
	} else {
		tnd.szTip[0] = '\0';
	}
	// Call tray icon windows API function
	res = Shell_NotifyIcon(dwMessage, &tnd);
	// Destroy the icon's handle (icon data is copied by Windows function)
	if (hIcon) DestroyIcon(hIcon);
	return res;
}

// Java VM callback function to delete a global reference to a given Java class
// Used to delete the global reference to the icon's class to receive mouse/menu events
int DeleteGlobalCallback(JNIEnv *env, int id_num, int dummy) {
	env->DeleteGlobalRef(tray_icons[id_num].globalClass);
	tray_icons[id_num].globalClass = 0;
	return 0;
}

// Java VM callback function used to notify icon class after incomming sendWindowsMessage()
int WindowsMessageCallback(JNIEnv *env, int dummy, int wParam) {
	// Get reference to WindowsTrayIcon Java class
	jclass cls = env->FindClass("jeans/trayicon/WindowsTrayIcon");
	if (cls == 0) return -1;
	// Get static callback method id
	jmethodID mid = env->GetStaticMethodID(cls, "callWindowsMessage", "(I)I");
	if (mid == 0) return -1;
	// Make call to "callWindowsMessage" with parameter wParam
	return env->CallStaticIntMethod(cls, mid, (jint)wParam);
}

// Java VM callback function used for menu item callbacks
int MenuItemCallback(JNIEnv *env, int id_num, int menu_id) {
	// Valid icon id and valid global reference to icon's Java class?
	if (tray_icons[id_num].used == FALSE) return TRAY_WRONGICONID;
	jobject obj = tray_icons[id_num].globalClass;
	if (obj == 0) return TRAY_NOLISTENER;
	jclass winTrayClass = env->GetObjectClass(obj);
	if (winTrayClass == 0) return TRAY_NOTENOUGHMEM;
	// Get callback method id
	jmethodID mid = env->GetMethodID(winTrayClass, "notifyMenuListeners", "(I)V");
	if (mid == 0) return TRAY_METHODID;
	// Call method "notifyMenuListeners"
	env->CallVoidMethod(obj, mid, menu_id);
	return TRAY_NOERR;
}

// Java VM callback used for mouse pressed callbacks
int MousePressedCallback(JNIEnv *env, int id_num, int button) {
	// Valid icon id and valid global reference to icon's Java class?
	if (tray_icons[id_num].used == FALSE) return TRAY_WRONGICONID;
	jobject obj = tray_icons[id_num].globalClass;
	if (obj == 0) return TRAY_NOLISTENER;
	jclass winTrayClass = env->GetObjectClass(obj);
	if (winTrayClass == 0) return TRAY_NOTENOUGHMEM;
	// Get callback method id
	jmethodID mid = env->GetMethodID(winTrayClass, "notifyMouseListeners", "(I)V");
	if (mid == 0) return TRAY_METHODID;
	// Call method "notifyMouseListeners"
	env->CallVoidMethod(obj, mid, button);
	return TRAY_NOERR;
}

// Call a Java method in a given virtual machine
int CallJavaVM(JavaVM* vm, JNIProcPtr(JNIProc), int arg1, int arg2) {
	int result = TRAY_NOERR;
#ifdef HAS_OLD_JVM_LIB
	JNIEnv* env;
	// Attach current thread to given Java VM
	if (vm->AttachCurrentThread(&env, NULL) < 0) return TRAY_ERRTHREAD;
	// Call method (MousePressedCallback/MenuItemCallback/WindowsMessageCallback/..)
	result = (*JNIProc)(env, arg1, arg2);
	// Check for exception detach thread and exit
	if (env->ExceptionOccurred()) env->ExceptionDescribe();
	vm->DetachCurrentThread();
#endif
#if defined(HAS_JVM_LIB) + defined(NO_JVM_LIB) == 1
	JNIEnv* env;
	// Attach current thread to given Java VM
	if (vm->AttachCurrentThread((void**) &env, NULL) < 0) return TRAY_ERRTHREAD;
	// Call method (MousePressedCallback/MenuItemCallback/WindowsMessageCallback/..)
	result = (*JNIProc)(env, arg1, arg2);
	// Check for exception detach thread and exit
	if (env->ExceptionOccurred()) env->ExceptionDescribe();
	vm->DetachCurrentThread();
#endif
	return result;
}

// Call a Java method in all available VM's (used for mouse/menu callbacks)
int CallJavaVMS(JNIProcPtr(JNIProc), int arg1, int arg2) {
	int value = TRAY_NOERR;
#ifdef HAS_JVM_LIB
	JavaVM *vm_ptr;
	jsize number;
	// Check for existing Java virtual machines
	if (JNI_GetCreatedJavaVMs(NULL, 0, &number) != 0) return TRAY_JNIERR;
	if (number == 0) return TRAY_NOVMS;
	// Call method in each available VM
	for (int ctr = 0; ctr < number; ctr++) {
		if (JNI_GetCreatedJavaVMs(&vm_ptr, ctr+1, NULL) != 0) return TRAY_JNIERR;
		int res = CallJavaVM(vm_ptr, JNIProc, arg1, arg2);
		if (res != TRAY_NOERR) value = res;
	}
#endif
#ifdef HAS_OLD_JVM_LIB
	JavaVM *vm_ptr;
	jsize number;
	// Check for existing Java virtual machines
	if (JNI_GetCreatedJavaVMs(NULL, 0, &number) != 0) return TRAY_JNIERR;
	if (number == 0) return TRAY_NOVMS;
	// Call method in each available VM
	for (int ctr = 0; ctr < number; ctr++) {
		if (JNI_GetCreatedJavaVMs(&vm_ptr, ctr+1, NULL) != 0) return TRAY_JNIERR;
		int res = CallJavaVM(vm_ptr, JNIProc, arg1, arg2);
		if (res != TRAY_NOERR) value = res;
	}
#endif
#ifdef NO_JVM_LIB
    value = CallJavaVM(hJavaVM, JNIProc, arg1, arg2);
#endif
	return value;
}

// Thread proc to call Java method (calls CallJavaVMS but with wrapped params)
void CallJavaThread(void *arg) {
	ThreadJavaCallback *tjc = (ThreadJavaCallback*)arg;
	int result = CallJavaVMS(tjc->jni_proc, tjc->arg1, tjc->arg2);
	if (result != TRAY_NOERR) last_error = result;
	delete tjc;
}

// Call a Java method in a new thread
void CallJavaVMSThread(JNIProcPtr(JNIProc), int arg1, int arg2) {
	// Wrap parameters in struct
	ThreadJavaCallback *tjc = new ThreadJavaCallback;
	tjc->jni_proc = JNIProc;
	tjc->arg1 = arg1;
	tjc->arg2 = arg2;
	// Create new thread and call "CallJavaThread()"
	if (_beginthread(CallJavaThread, 0, tjc) == -1) delete tjc;
}

// Handle popup menu command
void HandleMenuCommand(WPARAM menuId) {
	// Get icon id given menu id
	int id_num = getMenuItemIdNum(menuId);
	// Callback to Java class in new thread, using method "MenuItemCallback()"
	CallJavaVMSThread(MenuItemCallback, id_num, menuId);
}

// Handle icon mouse event (left/right button)
void HandleNotifyIcon(WPARAM id_num, LPARAM lParam) {
	switch (lParam)	{
		case WM_LBUTTONDOWN:
			// Callback Java class in new thread using "MousePressedCallback()"
			if (id_num >= 0) {
				GetCursorPos(&mouse_pos);				
				CallJavaVMSThread(MousePressedCallback,id_num,0);
			}
			break;
		case WM_RBUTTONDOWN:
			if (id_num >= 0) {
				GetCursorPos(&mouse_pos);				
				// If menu defined, then show it
				if (tray_icons[id_num].popup != NULL) {
					// Display and track the popup menu
					PopupMenu *menu = tray_icons[id_num].popup;
					menu->TrackPopupMenu(TPM_RIGHTALIGN | TPM_RIGHTBUTTON,&mouse_pos,my_hDlg);
				} else {
					// Callback Java class in new thread using "MousePressedCallback()"
					CallJavaVMSThread(MousePressedCallback,id_num,1);
				}
			}
			break;
	}
}

// Windows message proc for hidden window
// Receives mouse/menu events for the apps tray icons
LONG APIENTRY WndProc(HWND hWnd, UINT iMessage, UINT wParam, LONG lParam) {
	switch (iMessage) {
		case WM_DESTROY:
			// Exit message thread
			PostQuitMessage(0) ;
			break;
		case MYWM_NOTIFYICON:
			// Mouse event for icon (left/right button)
			HandleNotifyIcon(wParam, lParam);
			break;
		case MYWM_APPTALK:
			// ID used for sendWindowsMessage()
			// Callback Java class using "WindowsMessageCallback()"
			return CallJavaVMS(WindowsMessageCallback,0,(int)lParam);
		case WM_COMMAND:
			// Menu command for icon
			HandleMenuCommand(LOWORD(wParam));
			break;
		// Do nothing for other messages
		case WM_CREATE:
		case WM_GETMINMAXINFO:
		case WM_PAINT:
		case WM_MOUSEMOVE:
		case WM_RBUTTONDOWN:
		case WM_RBUTTONUP:
		case WM_MOVE:
		case WM_SIZE:
		case WM_LBUTTONDOWN:
		case WM_KEYDOWN:
			break;
		// Or use default handler
		default:
			return DefWindowProc (hWnd, iMessage, wParam, lParam) ;

	}
	return 0L ;
}

// Create the hidden window to receive the icon's mouse messages
HWND MakeHWND(HINSTANCE hInst) {
	HWND hWnd;
	// Create window class
	WNDCLASS wndclass;
	wndclass.style         = CS_HREDRAW | CS_VREDRAW;
	wndclass.lpfnWndProc   = (WNDPROC) WndProc;
	wndclass.cbClsExtra    = 0;
	wndclass.cbWndExtra    = 0;
	wndclass.hInstance     = hInst;
	wndclass.hIcon         = LoadIcon(hInst, IDI_APPLICATION);
	wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wndclass.lpszMenuName  = NULL;
	wndclass.lpszClassName = szAppName;
	if (!RegisterClass(&wndclass)) return NULL;
	// Create window
	hWnd = CreateWindow(szAppName, szWndName, WS_OVERLAPPEDWINDOW,
                          0,0,100,100,
                          NULL, NULL, hInst, NULL);
	return hWnd;
}

// Remove the hidden window (on app closing)
void RemoveHWND() {
	// Close dial
	if (my_hDlg != NULL) {
		// Create wait event
		exit_event = CreateEvent(NULL,FALSE,FALSE,NULL);
		// Send destroy messages
		PostMessage(my_hDlg, WM_NCDESTROY, 0, 0);
		PostMessage(my_hDlg, WM_DESTROY, 0, 0);
		// Wait for window to destroy
		WaitForSingleObject(exit_event,10000);
		CloseHandle(exit_event);
		exit_event = NULL;
	}
	// Free handle
	if (wait_event != NULL) {
		CloseHandle(wait_event);
		wait_event = NULL;
	}
}

// The thread proc that creates the hidden window an listens for messages
void DialogThread(void *dummy) {
	MSG msg;
	// Create window
	my_hDlg = MakeHWND(g_hinst);
	// Signal wait event
	if (wait_event != NULL) SetEvent(wait_event);
	// Hide window
	ShowWindow(my_hDlg, SW_HIDE);
	UpdateWindow(my_hDlg);
	// Process messages
	while (GetMessage(&msg, NULL, 0, 0)){
		TranslateMessage(&msg) ;
		DispatchMessage(&msg) ;
	}
	// Unregister window class
	UnregisterClass(szAppName, g_hinst);
	// Signal exit event (so app knows hidden window is destroyed)
	if (exit_event != NULL) SetEvent(exit_event);
}

// New icon resource with no bitmap and zero size
IconData::IconData() {
	wd = hi = 0;
	hBitmapAND = hBitmapXOR = NULL;
}

// Destroy icon resources (and and xor bitmap)
IconData::~IconData() {
	if (hBitmapAND != NULL) DeleteObject(hBitmapAND);
	if (hBitmapXOR != NULL) DeleteObject(hBitmapXOR);
}

// Make icon using and and xor bitmaps (returns handle to new icon)
HICON IconData::makeIcon(HINSTANCE hInst) {
        ICONINFO ii;
        ii.fIcon    = TRUE;
        ii.xHotspot = 0;
        ii.yHotspot = 0;
        ii.hbmMask  = hBitmapAND;
        ii.hbmColor = hBitmapXOR;
        return CreateIconIndirect(&ii);
}

// Set icon's image data (pixel array, width, height)
int IconData::setData(unsigned long *data, int wd, int hi) {
	int res = -1;
	// Set size
	this->wd = wd;
	this->hi = hi;
	// Clean up if setData was called before
	if (hBitmapAND != NULL) DeleteObject(hBitmapAND);
	if (hBitmapXOR != NULL) DeleteObject(hBitmapXOR);
	// To protect against java sending a dud image
	if (wd > 0 && hi > 0) {
		// Set up the header for creating our 24 bit colour bitmap
	    	BITMAPINFOHEADER bih;
		bih.biSize          = sizeof(BITMAPINFOHEADER);
	    	bih.biWidth         = wd;
		bih.biHeight        = hi;
		bih.biPlanes        = 1;
	    	bih.biBitCount      = 24;
	    	bih.biCompression   = BI_RGB;
	    	bih.biSizeImage     = 0;
	    	bih.biXPelsPerMeter = 0;
	    	bih.biYPelsPerMeter = 0;
	    	bih.biClrUsed       = 0;
	    	bih.biClrImportant  = 0;
	    	// Create memory DC
	    	HDC hdc = CreateCompatibleDC(NULL);
	    	// Make the 24-bit DIB
	    	hBitmapXOR = CreateDIBSection(hdc, (LPBITMAPINFO)&bih, DIB_RGB_COLORS, (LPVOID *)NULL, NULL, 0);
	    	// Select it into the DC so we can draw onto it
	    	SelectObject(hdc, hBitmapXOR);
	    	// Calloc memory to be used to create a monochrome bitmask
		long size = (wd*hi/8)+1;
	    	unsigned char *andMask = new unsigned char[size];
		if (andMask != NULL) {
			for (int i = 0; i < size; i++) andMask[i] = 0;
        		// Loop through the given pixels and draw onto the colour and mono bitmaps
		    	unsigned long pixel;
		    	unsigned char red, green, blue, alpha;
		    	for (int row = 0; row < hi; row++) {
				for (int col = 0; col < wd; col++) {
	        			pixel = data[(row*wd)+col];
		        		alpha = (unsigned char)((pixel >> 24) & 0x000000ff);
		        		red   = (unsigned char)((pixel >> 16) & 0x000000ff);
	                		green = (unsigned char)((pixel >>  8) & 0x000000ff);
	                		blue  = (unsigned char)( pixel        & 0x000000ff);
	        		    	if (alpha == 0xFF) {
						// Pixel is not transparent - update xor bitmap
						SetPixel(hdc, col, row, RGB(red, green, blue));
					} else {
						// Pixel is transparent - update and mask
		        			int p = (row*wd) + col;
				        	andMask[p/8] |= 1 << (7-(p%8));
					}
				}
			}
			// Create the monochrome bitmask with transparency info
			hBitmapAND = CreateBitmap(wd, hi, 1, 1, andMask);
			// Free memory
			delete andMask;
			// Return code
			res = 0;
		}
		// Release the memory DC
		DeleteDC(hdc);
	}
	// Error on zero size icons
	return res;
}

// Find free menu id for submenu item (given icon id = owner)
// Each tray icon with a submenu has a number of menu ids, one for each menu item
long getFreeMenuId(int id_num) {
	long size = arrUsedMenuIds->getSize();
	long id = 0;
	BOOL done = FALSE;
	// Find for free id in array that maps menu ids to icon ids
	for (id = 0; id < size && !done; id++)
		if (arrUsedMenuIds->getElementAt(id) == -1) done = TRUE;
	// Found none, create new id
	if (!done) {
		id = size;
		arrUsedMenuIds->addElement(-1);
	}
	// Mark id in array as belonging to icon with id id_num
	arrUsedMenuIds->setElementAt(id, id_num);
	// Return id for new submenu item
	return id;
}

// Free all menu ids for a given icon
void setFreeMenuId(int id_num) {
	long size = arrUsedMenuIds->getSize();
	// Find ids for given icon and free them
	for (long ps = 0; ps < size; ps++) {
		if (arrUsedMenuIds->getElementAt(ps) == id_num)
			arrUsedMenuIds->setElementAt(ps, -1);
	}
	// Shrink array if there are trailing empty places
	long delofs = arrUsedMenuIds->getSize();
	while (delofs > 0 && arrUsedMenuIds->getElementAt(delofs-1) == -1) {
		delofs--;
		arrUsedMenuIds->removeElementAt(delofs);
	}
}

// Map menu id to icon id
int getMenuItemIdNum(int id) {
	long size = arrUsedMenuIds->getSize();
	if (id < size) return arrUsedMenuIds->getElementAt(id);
	else return -1;
}

// Make new popup menu
PopupSubMenu::PopupSubMenu() {
	hMenu = CreatePopupMenu();
	bSub = FALSE;
}

// Make new popup menu (reset all class members)
void PopupSubMenu::reNewMenu() {
	if (!isSub()) DestroyMenu(hMenu);
	hMenu = CreatePopupMenu();
	bSub = FALSE;
}

// Destroy popupmenu
PopupSubMenu::~PopupSubMenu() {
	if (!isSub()) DestroyMenu(hMenu); // Only needed when parent menu, not for submenus
}

// Is this a submenu or a parent popup menu
BOOL PopupSubMenu::isSub() {
	return bSub;
}

// Mark this popup menu as being a submenu
void PopupSubMenu::makeSub() {
	bSub = TRUE;
}

// Get the windows menu handle, used for AppendMenu,..
HMENU PopupSubMenu::getMenu() {
	return hMenu;
}

// Display the popupmenu at given position (in neighbourhood of tray icon)
void PopupSubMenu::TrackPopupMenu(UINT flags, POINT* pos, HWND hWnd) {
	// SetForegroundWindow/PostMessage are hacks because TrackPopupMenu contains bug/feature
	// Otherwise it's not hidden properly when the user clicks on desktop
	SetForegroundWindow(hWnd);
	::TrackPopupMenu(hMenu, flags, (*pos).x, (*pos).y, 0, hWnd, NULL);
	PostMessage(hWnd, WM_NULL, 0, 0);
}

// Make new popup main menu with maximum submenu depth
PopupMenu::PopupMenu(int levels) {
	nbLevels = levels;
	popup = new PtrPopupSubMenu[levels];
	for (int ctr = 0; ctr < levels; ctr++) popup[ctr] = NULL;
}

// Destroy main popup menu
PopupMenu::~PopupMenu() {
	// Destroy all submenu levels
	for (int ctr = 0; ctr < nbLevels; ctr++)
		if (popup[ctr] != NULL) delete popup[ctr];
	delete popup;
}

// Init menu at given level (make new or call reNewMenu())
void PopupMenu::initMenu(int level) {
	if (level >= nbLevels) return;
	if (popup[level] != NULL) popup[level]->reNewMenu();
	else popup[level] = new PopupSubMenu();
}

// Return windows menu handle for given level (should be 0 for main menu)
HMENU PopupMenu::getMenu(int level) {
	PopupSubMenu *menu = getSubMenu(level);
	if (menu == NULL) return NULL;
	else return menu->getMenu();
}

// Return sub menu @ given level (used in getMenu()/TrackPopupMenu())
PopupSubMenu* PopupMenu::getSubMenu(int level) {
	if (level >= nbLevels) return NULL;
	return popup[level];
}

// Display the popupmenu at given position (in neighbourhood of tray icon)
void PopupMenu::TrackPopupMenu(UINT flags, POINT* pos, HWND hWnd) {
	PopupSubMenu *menu = getSubMenu(0);
	if (menu != NULL) menu->TrackPopupMenu(flags, pos, hWnd);
}

// Create new growable integer array
QSIntArray::QSIntArray() {
	m_Array = new int[5];
	m_ArrSize = 5;
	m_Size = 0;
	m_Grow = 10;
}

// Create new growable integer array with initial size and grow term
QSIntArray::QSIntArray(long size, long grow) {
	m_Array = new int[size];
	m_ArrSize = size;
	m_Size = 0;
	m_Grow = grow;
}

// Delete growable integer array
QSIntArray::~QSIntArray() {
	delete m_Array;
}

// Get size
long QSIntArray::getSize() {
	return m_Size;
}

// Get integer @ given position
int QSIntArray::getElementAt(long idx) {
	return m_Array[idx];
}

// Add integer at end of array
void QSIntArray::addElement(int element) {
	m_Size++;
	grow();
	m_Array[m_Size-1] = element;
}

// Set integer @ given position (may be >= getSize() - array grows)
void QSIntArray::setElementAt(long idx, int element) {
	m_Size = max(m_Size, idx+1);
	grow();
	m_Array[idx] = element;
}

// Remove integer at given position
void QSIntArray::removeElementAt(long idx) {
	m_Size--;
	for (long pos = idx; pos < m_Size; pos++)
		m_Array[pos] = m_Array[pos+1];
	shrink();
}

// Empty array
void QSIntArray::removeAll() {
	m_Size = 0;
	shrink();
}

// Shrink array to convenient size
void QSIntArray::shrink() {
	if (m_Size < m_ArrSize - m_Grow) {
		long nSize = m_Size + m_Grow;
		int* nArray = new int[nSize];
		for (long idx = 0; idx < m_Size; idx++)
			nArray[idx] = m_Array[idx];
		delete m_Array;
		m_Array = nArray;
		m_ArrSize = nSize;
	}
}

// Grow array to convenient size
void QSIntArray::grow() {
	if (m_Size > m_ArrSize) {
		long nSize = m_Size + m_Grow;
		int* nArray = new int[nSize];
		for (long idx = 0; idx < m_ArrSize; idx++)
			nArray[idx] = m_Array[idx];
		delete m_Array;
		m_Array = nArray;
		m_ArrSize = nSize;
	}
}