soc.2009.vulture: 8aaab5ec: Most of the functionality for the buddy-...

gdick at soc.pidgin.im gdick at soc.pidgin.im
Thu Aug 13 22:54:12 EDT 2009


-----------------------------------------------------------------
Revision: 8aaab5ecfd3fb6a9aca6f396c9a64127290589a0
Ancestor: e3d2126104f3a40c863b972aa2ec0fd47c937ed0
Author: gdick at soc.pidgin.im
Date: 2009-08-13T20:56:52
Branch: im.pidgin.soc.2009.vulture
URL: http://d.pidgin.im/viewmtn/revision/info/8aaab5ecfd3fb6a9aca6f396c9a64127290589a0

Modified files:
        vulture/cmdline.c vulture/cmdline.h vulture/purplebicon.c
        vulture/purplebicon.h vulture/purplemain.c
        vulture/purplemain.h vulture/purplequeue.c
        vulture/purplequeue.h vulture/resource.h
        vulture/vulture-res.rc vulture/vulture.c vulture/vulture.h
        vulture/vultureblist.c

ChangeLog: 

Most of the functionality for the buddy-icon control in the status box. Also
added a command-line debug switch.

-------------- next part --------------
============================================================
--- vulture/cmdline.c	87aeb64a0a020b28219c272fca9fd6c2d39a786b
+++ vulture/cmdline.c	d01c044e16452da9b761fa757c93c279772c70db
@@ -25,15 +25,18 @@
 
 #include "vulture.h"
 #include "cmdline.h"
+#include "purple.h"
 
 
 /** Command-line options set by VultureParseCommandLine. */
 static gchar *g_szCustomUserDir = NULL;
+static gboolean g_bDebug = FALSE;
 
 static GOptionContext *g_lpgopcontext;
 static GOptionEntry g_rggopentry[] =
 {
 	{ "config", 'c', 0, G_OPTION_ARG_STRING, &g_szCustomUserDir, "use DIR for config files", "DIR" },
+	{ "debug", 'd', 0, G_OPTION_ARG_NONE, &g_bDebug, "enable debug messages", NULL },
 	{ NULL, 0, 0, 0, NULL, NULL, NULL }
 };
 
@@ -76,10 +79,20 @@ void VultureCommandLineCleanup(void)
 
 
 /**
- * Retrieves the user directory specified on the command-line, or NULL if none
+ * Retrieves the user directory specified on the command line, or NULL if none
  * set.
  */
 gchar* VultureGetCustomUserDir(void)
 {
 	return g_szCustomUserDir;
 }
+
+
+/** Enables debug mode if specified on the command line. */
+void VultureSetDebugFromCmdLine(void)
+{
+	purple_debug_set_enabled(g_bDebug);
+
+	if(g_bDebug)
+		g_set_print_handler(VultureGPrintHandler);
+}
============================================================
--- vulture/cmdline.h	288d6762e77b8ccb8c131b3836fbfdba4b3caaa5
+++ vulture/cmdline.h	c5b79eb75b7aae943ae88eac001c1e8003842a0b
@@ -28,5 +28,6 @@ gchar* VultureGetCustomUserDir(void);
 void VultureParseCommandLine(void);
 void VultureCommandLineCleanup(void);
 gchar* VultureGetCustomUserDir(void);
+void VultureSetDebugFromCmdLine(void);
 
 #endif
============================================================
--- vulture/purplebicon.c	ca36daa9e4113e6b14d4c907c8c1154f6f0eba7d
+++ vulture/purplebicon.c	40a3abcb7c8a529a3feb4f80220cb0e3cdbbc279
@@ -23,14 +23,19 @@
 
 #include <windows.h>
 #include <glib.h>
+#include <glib/gstdio.h>
 #include <gdk-pixbuf/gdk-pixbuf.h>
+#include <string.h>
 
 #include "vulture.h"
 #include "purple.h"
 #include "purplebicon.h"
+#include "purplemain.h"
 
 
 static HBITMAP GetBuddyIcon(gconstpointer lpvBuddyIconData, size_t cbBuddyIconData, int cxMax, int cyMax);
+static gpointer ConvertAndScaleBuddyIcon(const gchar *szFilename, PurplePlugin *lpplugin, gsize *lpcbImage);
+static void SetGlobalBuddyIcon(const gchar *szFilename);
 
 
 
@@ -275,3 +280,213 @@ HBITMAP PurpleGetBlistNodeIcon(PurpleBli
 
 	return hbitmap;
 }
+
+
+/**
+ * Retrieves contents of an image file, scaled and converted into a format
+ * suitable for a buddy icon. Based very closely on Pidgin's
+ * pidgin_convert_buddy_icon.
+ *
+ * @param	szFilename	Filename of buddy icon.
+ * @param	lpplugin	Prpl.
+ * @param[out]	lpcbImage	Returns the length of the data. May be
+ *				clobbered even on error.
+ *
+ * @return Image data, or NULL on error.
+ */
+static gpointer ConvertAndScaleBuddyIcon(const gchar *szFilename, PurplePlugin *lpplugin, gsize *lpcbImage)
+{
+	gchar *szTempFile = NULL;
+	PurplePluginProtocolInfo *lpprplinfo;
+	int cx, cy;
+	int i, j;
+	gchar **rgszPixbufFormats, **rgszPrplFormats;
+	BOOL bFormatSupported, bNeedScale;
+	GdkPixbufFormat *lppixbufformat;
+	gchar *lpcImage;
+
+	lpprplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(lpplugin);
+	if(!lpprplinfo->icon_spec.format)
+		return NULL;
+
+	lppixbufformat = gdk_pixbuf_get_file_info(szFilename, &cx, &cy);
+	if(!lppixbufformat)
+		return NULL;
+
+	/* Which formats does the prpl support? */
+	rgszPrplFormats = g_strsplit(lpprplinfo->icon_spec.format, ",", 0);
+
+	/* Attempt to match supported formats against the format of the file we
+	 * were given.
+	 */
+
+	rgszPixbufFormats = gdk_pixbuf_format_get_extensions(lppixbufformat);
+
+	bFormatSupported = FALSE;
+	for(i = 0; rgszPixbufFormats[i] && !bFormatSupported; i++)
+		for(j = 0; rgszPrplFormats[j] && !bFormatSupported; j++)
+			if(!g_ascii_strcasecmp(rgszPixbufFormats[i], rgszPrplFormats[j]))
+				bFormatSupported = TRUE;
+
+	/* We need to scale iff the prpl requires it and we're out of range. */
+	bNeedScale = (lpprplinfo->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) &&
+		  (cx < lpprplinfo->icon_spec.min_width ||
+		   cx > lpprplinfo->icon_spec.max_width ||
+		   cy < lpprplinfo->icon_spec.min_height ||
+		   cy > lpprplinfo->icon_spec.max_height);
+
+	/* Mangle the image, unless it's already very nice. */
+	if(!bFormatSupported || bNeedScale)
+	{
+		GdkPixbuf *lppixbuf, *lppixbufScaled;
+		GError *lpgerror = NULL;
+
+		lppixbuf = gdk_pixbuf_new_from_file(szFilename, &lpgerror);
+
+		if(lppixbuf)
+		{
+			int cxScaled = cx, cyScaled = cy;
+			BOOL bHaveCompression = gdk_pixbuf_major_version > 2 || (gdk_pixbuf_major_version == 2 && gdk_pixbuf_minor_version >= 8);
+
+			/* Get aspect-correct scaled size. */
+			purple_buddy_icon_get_scale_size(&lpprplinfo->icon_spec, &cxScaled, &cyScaled);
+
+			lppixbufScaled = gdk_pixbuf_scale_simple(lppixbuf, cxScaled, cyScaled, GDK_INTERP_HYPER);
+			g_object_unref(lppixbuf);
+
+			szFilename = NULL;
+
+			/* Attempt each of the supported formats. */
+			for(i = 0; rgszPrplFormats[i]; i++)
+			{
+				FILE *lpfile;
+
+				/* libpurple's routine is less fiddly than
+				 * GetTempFileName here.
+				 */
+				if(!(lpfile = purple_mkstemp(&szTempFile, TRUE)))
+					break;
+				fclose(lpfile);
+
+				if(bHaveCompression && strcmp(rgszPrplFormats[i], "png") == 0)
+				{
+					if (gdk_pixbuf_save(lppixbufScaled, szTempFile, rgszPrplFormats[i], &lpgerror, "compression", "9", NULL))
+						break;
+				}
+				else if(gdk_pixbuf_save(lppixbufScaled, szTempFile, rgszPrplFormats[i], &lpgerror, NULL))
+					break;
+
+				/* If we get here, we failed. */
+				if(lpgerror)
+				{
+					g_error_free(lpgerror);
+					lpgerror = NULL;
+				}
+
+				g_unlink(szTempFile);
+				g_free(szTempFile);
+				szTempFile = NULL;
+			}
+		}
+		else
+		{
+			g_error_free(lpgerror);
+			szFilename = NULL;
+		}
+	}
+
+	g_strfreev(rgszPixbufFormats);
+	g_strfreev(rgszPrplFormats);
+
+	if(szFilename || (szFilename = szTempFile))
+		g_file_get_contents(szFilename, &lpcImage, lpcbImage, NULL);
+
+	if(szTempFile)
+	{
+		g_unlink(szTempFile);
+		g_free(szTempFile);
+	}
+
+	/* Make sure we're not too big. */
+	if((lpprplinfo->icon_spec.max_filesize != 0) && (*lpcbImage > lpprplinfo->icon_spec.max_filesize))
+	{
+		g_free(lpcImage);
+		return NULL;
+	}
+
+	return lpcImage;
+}
+
+
+/**
+ * Sets the buddy icons for accounts using the global icon, and instructs the
+ * UI to show the new icon. Called by the preference hook.
+ *
+ * @param	szFilename	Filename of buddy icon.
+ */
+static void SetGlobalBuddyIcon(const gchar *szFilename)
+{
+	GList *lpglistAccounts;
+	PurpleStoredImage *lpstoredimg;
+
+	for(lpglistAccounts = purple_accounts_get_all(); lpglistAccounts; lpglistAccounts = lpglistAccounts->next)
+	{
+		PurpleAccount *lpaccount = lpglistAccounts->data;
+		PurplePlugin *lpplugin = purple_find_prpl(purple_account_get_protocol_id(lpaccount));
+
+		if(lpplugin)
+		{
+			PurplePluginProtocolInfo *lpprplinfo = PURPLE_PLUGIN_PROTOCOL_INFO(lpplugin);
+
+			/* Set icon for accounts that don't override the global
+			 * setting.
+			 */
+			if(lpprplinfo && purple_account_get_bool(lpaccount, "use-global-buddyicon", TRUE) && lpprplinfo->icon_spec.format)
+			{
+				if(szFilename)
+				{
+					gsize cbImage;
+					gpointer lpvImage = ConvertAndScaleBuddyIcon(szFilename, lpplugin, &cbImage);
+
+					purple_buddy_icons_set_account_icon(lpaccount, lpvImage, cbImage);
+				}
+				else
+					purple_buddy_icons_set_account_icon(lpaccount, NULL, 0);
+
+				purple_account_set_buddy_icon_path(lpaccount, szFilename);
+			}
+		}
+	}
+
+	if(szFilename)
+	{
+		/* Make an HBITMAP and send it back to the UI for display in
+		 * the main window.
+		 */
+		if((lpstoredimg = purple_imgstore_new_from_file(szFilename)))
+		{
+			HBITMAP hbmIcon = GetBuddyIcon(purple_imgstore_get_data(lpstoredimg), purple_imgstore_get_size(lpstoredimg), 0, 0);
+			VulturePostUIMessage(VUIMSG_NEWGLOBALBICON, hbmIcon);
+			purple_imgstore_unref(lpstoredimg);
+		}
+	}
+	else
+		VulturePostUIMessage(VUIMSG_NEWGLOBALBICON, NULL);
+}
+
+
+/**
+ * Called when the global buddy-icon preference is set.
+ *
+ * @param	szName		Unused.
+ * @param	preftype	Unused.
+ * @param	lpvValue	Filename of buddy icon.
+ * @param	lpvData		Unused.
+ */
+void PurpleGlobalBuddyIconPrefChanged(const char *szName, PurplePrefType preftype, gconstpointer lpvValue, gpointer lpvData)
+{
+	UNREFERENCED_PARAMETER(szName);
+	UNREFERENCED_PARAMETER(preftype);
+	UNREFERENCED_PARAMETER(lpvData);
+	SetGlobalBuddyIcon(lpvValue);
+}
============================================================
--- vulture/purplebicon.h	ae61a69f5ec73d830e00c73ac9e1ae909da47d33
+++ vulture/purplebicon.h	fb88563160f2a5847c33cf19aabb7e5a107c82d1
@@ -31,6 +31,7 @@ HBITMAP PurpleGetBlistNodeIcon(PurpleBli
 
 HBITMAP PurpleGetIMBuddyIcon(PurpleConversation *lpconv, int cxMax, int cyMax);
 HBITMAP PurpleGetBlistNodeIcon(PurpleBlistNode *lpblistnode, int cxMax, int cyMax);
+void PurpleGlobalBuddyIconPrefChanged(const char *szName, PurplePrefType preftype, gconstpointer lpvValue, gpointer lpvData);
 
 
 #endif
============================================================
--- vulture/purplemain.c	c5fe877770c6e70a9fb3946d34ee4eb085b741e2
+++ vulture/purplemain.c	966a55819a18db2e46a1df71291294f355c5288a
@@ -50,15 +50,14 @@
 #include "purpleconv.h"
 #include "purplestatus.h"
 #include "purpleacct.h"
+#include "purplebicon.h"
 
 
-#define VULTURE_PREFS_ROOT "/vulture"
-
-
 static UINT CALLBACK PurpleThread(void *lpvData);
 static int InitLibpurple(void);
 static void InitUI(void);
 static void Quitting(void);
+static void InitPrefs(void);
 static void LoadFlags(void);
 static void SaveFlags(void);
 
@@ -152,6 +151,8 @@ static int InitLibpurple(void)
 
 	gchar *szCustomUserDir;
 
+	VultureSetDebugFromCmdLine();
+
 	if((szCustomUserDir = VultureGetCustomUserDir()))
 		purple_util_set_user_dir(szCustomUserDir);
 
@@ -210,6 +211,7 @@ static void InitUI(void)
 		NULL,				/* reserved		*/
 	};
 
+	InitPrefs();
 	LoadFlags();
 
 	purple_blist_set_ui_ops(&s_blistuiops);
@@ -221,6 +223,8 @@ static void InitUI(void)
 	purple_signal_connect(purple_blist_get_handle(), "buddy-icon-changed", GINT_TO_POINTER(VSH_BLIST), PURPLE_CALLBACK(PurpleBuddyIconChanged), NULL);
 	purple_signal_connect(purple_connections_get_handle(), "signed-on", GINT_TO_POINTER(VSH_BLIST), PURPLE_CALLBACK(PurpleAccountSignedOn), NULL);
 
+	purple_prefs_connect_callback(GINT_TO_POINTER(VSH_BICON), VULTURE_PREFS_ROOT "/accounts/buddyicon", PurpleGlobalBuddyIconPrefChanged, NULL);
+
 	/* Create and load libpurple's buddy-list. */
 	purple_set_blist(purple_blist_new());
 	purple_blist_load();
@@ -325,14 +329,22 @@ void PurpleInsertDynamicMenu(HMENU hmenu
 }
 
 
-/** Loads flags from libpurple's preference store. */
-static void LoadFlags(void)
+/** Preference initialisation. Call before any other preference functions. */
+static void InitPrefs(void)
 {
 	purple_prefs_add_none(VULTURE_PREFS_ROOT);
 	purple_prefs_add_none(VULTURE_PREFS_ROOT "/blist");
 	purple_prefs_add_bool(VULTURE_PREFS_ROOT "/blist/show_offline_buddies", FALSE);
 	purple_prefs_add_bool(VULTURE_PREFS_ROOT "/blist/show_empty_groups", TRUE);
 
+	purple_prefs_add_none(VULTURE_PREFS_ROOT "/accounts");
+	purple_prefs_add_path(VULTURE_PREFS_ROOT "/accounts/buddyicon", "");
+}
+
+
+/** Loads flags from libpurple's preference store. */
+static void LoadFlags(void)
+{
 	g_vflags.bShowOffline = purple_prefs_get_bool(VULTURE_PREFS_ROOT "/blist/show_offline_buddies");
 	g_vflags.bShowEmptyGroups = purple_prefs_get_bool(VULTURE_PREFS_ROOT "/blist/show_empty_groups");
 }
============================================================
--- vulture/purplemain.h	e12b252d7f0db0b03520757baec24498284e11b4
+++ vulture/purplemain.h	4e5cdc340a212e1052581277ba026e6b936133f4
@@ -49,6 +49,10 @@ typedef struct _VULTURE_MAKE_CONTEXT_MEN
 } VULTURE_MAKE_CONTEXT_MENU;
 
 
+
+#define VULTURE_PREFS_ROOT "/vulture"
+
+
 /* The significance of lpvParam is given for each message. */
 enum ENUM_VULTURE_UI_MESSAGES
 {
@@ -91,6 +95,9 @@ enum ENUM_VULTURE_UI_MESSAGES
 
 	/* (VULTURE_BLIST_NODE*) Node whose cache should be invalidated. */
 	VUIMSG_INVALIDATEICONCACHE,
+
+	/* (HBITMAP) */
+	VUIMSG_NEWGLOBALBICON,
 };
 
 /* HandlEs for reigistering signal handlERs. */
@@ -98,7 +105,8 @@ enum ENUM_VULTURE_SIGNAL_HANDLES
 {
 	VSH_STATUS = 1,
 	VSH_CONV,
-	VSH_BLIST
+	VSH_BLIST,
+	VSH_BICON
 };
 
 void VultureInitLibpurple(HANDLE *lphthread);
============================================================
--- vulture/purplequeue.c	03539c890a5b7bb241d33a7a0eb3760588c3abf1
+++ vulture/purplequeue.c	1298af63d37b82bc9a75ff52630fa68d343da8be
@@ -441,6 +441,18 @@ static void DispatchPurpleCall(PURPLE_CA
 		PurpleAddGroup(lppurplecall->lpvParam);
 		break;
 
+	case PC_SETGLOBALBICON:
+		if(lppurplecall->lpvParam)
+		{
+			gchar *szFilename = VultureTCHARToUTF8(lppurplecall->lpvParam);
+			purple_prefs_set_path(VULTURE_PREFS_ROOT "/accounts/buddyicon", szFilename);
+			g_free(szFilename);
+		}
+		else
+			purple_prefs_set_path(VULTURE_PREFS_ROOT "/accounts/buddyicon", NULL);
+
+		break;
+
 	case PC_QUIT:
 		purple_core_quit();
 		g_main_loop_quit(g_lpgmainloop);
============================================================
--- vulture/purplequeue.h	1ae1750c146a3d98504655978f404e67afe00b2d
+++ vulture/purplequeue.h	dfd8e22f3d026cfbe4e52c003d53c9060732fc3f
@@ -129,6 +129,9 @@ enum PURPLE_CALL_ID
 
 	/* (LPTSTR) Group name. */
 	PC_ADDGROUP,
+
+	/* (LPTSTR) Icon filename. */
+	PC_SETGLOBALBICON,
 };
 
 
============================================================
--- vulture/resource.h	b3f60211860b0038910dc212efe4b516fffdf262
+++ vulture/resource.h	75c8f9d017bf7975a2f954220476faecf5183480
@@ -73,6 +73,10 @@
 #define IDM_CONV			1003
 #define IDM_CONV_CONV_CLOSE		40401
 
+#define IDM_BUDDYICON_CONTEXT           1004
+#define IDM_BUDDYICON_CONTEXT_SET       40601
+#define IDM_BUDDYICON_CONTEXT_REMOVE    40602
+
 /* For dynamic menu items not sent as WM_COMMAND notifications. */
 #define IDM_DYNAMIC_FIRST		50000
 
============================================================
--- vulture/vulture-res.rc	cc94c74d7b211e3e438bbac44f0e2f4e833a7ebc
+++ vulture/vulture-res.rc	212f7e33b0eecf590cc4821ed9acfa7f54578d44
@@ -140,7 +140,18 @@ IDM_BLIST_CONTEXT MENUEX
 }
 
 
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+IDM_BUDDYICON_CONTEXT MENUEX
+{
+	POPUP "Dummy Label"
+	{
+		MENUITEM "&Set buddy icon...", IDM_BUDDYICON_CONTEXT_SET, MFT_STRING, MFS_DEFAULT
+		MENUITEM "&Remove buddy icon", IDM_BUDDYICON_CONTEXT_REMOVE, MFT_STRING
+	}
+}
 
+
+
 //
 // Dialog resources
 //
@@ -309,7 +320,7 @@ FONT 8, "Ms Shell Dlg"
 STYLE DS_3DLOOK | DS_CENTER | DS_CONTROL | DS_SHELLFONT | WS_VISIBLE | WS_CHILDWINDOW
 FONT 8, "Ms Shell Dlg"
 {
-	CONTROL		"", IDC_BUDDY_ICON, "Static", WS_TABSTOP | SS_BLACKFRAME | SS_NOTIFY | SS_SUNKEN, 5, 5, 30, 30
+	CONTROL		"", IDC_BUDDY_ICON, "Static", WS_TABSTOP | SS_BITMAP | SS_CENTERIMAGE | SS_SUNKEN | SS_NOTIFY, 5, 5, 30, 30
 	CONTROL		"1", IDC_CBEX_STATUS, "ComboBoxEx32", 0x50000003, 40, 5, 115, 90
 	EDITTEXT	IDC_EDIT_STATUSMSG, 40, 20, 115, 15, ES_AUTOHSCROLL
 }
@@ -331,7 +342,7 @@ STRINGTABLE
 	IDS_QUERY_DELGROUP		"Deleting this group will also delete everything contained in it. Do you wish to continue?"
 	IDS_QUERY_DELCONTACT		"Deleting this contact will also delete all buddies associated with it. Do you wish to continue?"
 	IDS_BUDDYICON_FILTER		"Image Files\t*.bmp;*.png;*.gif;*.jpg\tAll Files (*.*)\t*.*\t"
-	IDS_BUDDYICON_TITLE		"Choose Custom Icon"
+	IDS_BUDDYICON_TITLE		"Choose Icon"
 	IDS_OFFLINE			"Offline"
 }
 
============================================================
--- vulture/vulture.c	3d1f361ef52adb86342fd470fdd08f6c25e6f112
+++ vulture/vulture.c	262cf079fcb75236b313d1a1d4df2b7e0f8413e7
@@ -355,3 +355,16 @@ void VultureLoadAndFormatFilterString(US
 	}
 	while(*(++szInFilter));
 }
+
+
+/**
+ * Our g_print handler. Only used when debugging is enabled.
+ *
+ * @param	sz	String to print.
+ */
+void VultureGPrintHandler(const gchar *sz)
+{
+	LPTSTR szT = VultureUTF8ToTCHAR(sz);
+	OutputDebugString(szT);
+	g_free(szT);
+}
============================================================
--- vulture/vulture.h	1ffdbf86f02bdb38494deb08c4f36995d25f9e43
+++ vulture/vulture.h	74f168560d192dfefc8e6bf1565af6c794d0d09d
@@ -77,6 +77,7 @@ void VultureLoadAndFormatFilterString(US
 int VultureGetMenuPosFromID(HMENU hmenu, UINT uiID);
 int VultureCommDlgOpen(HWND hwnd, LPTSTR szFileNameReturn, UINT cchFileNameReturn, LPCTSTR szTitle, LPCTSTR szFilter, LPCTSTR szDefExt, LPCTSTR szInitFilename, int iFlags);
 void VultureLoadAndFormatFilterString(USHORT unStringID, LPTSTR szFilter, UINT cchFilter);
+void VultureGPrintHandler(const gchar *sz);
 
 
 
============================================================
--- vulture/vultureblist.c	29a9b7b5a6ecdf75e3ae0d5a4e3e11dba9a35248
+++ vulture/vultureblist.c	0ed8d48df54645a70195293f7854a6b4d19bb1fc
@@ -45,6 +45,7 @@ typedef struct _STATUSDLGDATA
 typedef struct _STATUSDLGDATA
 {
 	WNDPROC	wndprocStatusMsgOrig;
+	WNDPROC wndprocBuddyIconOrig;
 } STATUSDLGDATA;
 
 /* MinGW doesn't have NMTVKEYDOWN. */
@@ -76,6 +77,9 @@ static void RequestAddGroup(HWND hwndPar
 static void RequestAddChat(HWND hwndParent, LPTSTR szAlias, LPTSTR szInitGroup);
 static void RequestAddBuddy(HWND hwndParent, LPTSTR szUsername, LPTSTR szAlias, LPTSTR szInitGroup);
 static void RequestAddGroup(HWND hwndParent);
+static LRESULT CALLBACK BuddyIconSubclassProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam);
+static void ChooseBuddyIcon(void);
+static void ClearBuddyIcon(void);
 
 
 #define BLIST_MARGIN		6
@@ -258,6 +262,14 @@ static LRESULT CALLBACK MainWndProc(HWND
 		case IDM_BLIST_ACCOUNTS_MANAGE:
 			ManageAccounts(hwnd);
 			return 0;
+
+		case IDM_BUDDYICON_CONTEXT_SET:
+			ChooseBuddyIcon();
+			return 0;
+
+		case IDM_BUDDYICON_CONTEXT_REMOVE:
+			ClearBuddyIcon();
+			return 0;
 		}
 
 		break;
@@ -395,6 +407,78 @@ static LRESULT CALLBACK MainWndProc(HWND
 
 				break;
 
+			case VUIMSG_NEWGLOBALBICON:
+				{
+					HBITMAP hbmScaled = NULL;
+					HBITMAP hbmOld;
+
+					if(lParam)
+					{
+						RECT rcIconCtrl;
+						HDC hdcSrc, hdcDest;
+						HBITMAP hbmSrcOrig, hbmDestOrig;
+						int cxScaled, cyScaled, cxIconCtrl, cyIconCtrl;
+						BITMAP bitmap;
+
+						GetClientRect(GetDlgItem(g_hwndStatusDlg, IDC_BUDDY_ICON), &rcIconCtrl);
+
+						hdcSrc = CreateCompatibleDC(NULL);
+						hdcDest = CreateCompatibleDC(NULL);
+
+						GetObject((HBITMAP)lParam, sizeof(bitmap), &bitmap);
+
+						cxIconCtrl = rcIconCtrl.right - rcIconCtrl.left;
+						cyIconCtrl = rcIconCtrl.bottom - rcIconCtrl.top;
+
+						/* Scale if necessary. */
+						if(bitmap.bmWidth > cxIconCtrl || bitmap.bmHeight > cyIconCtrl)
+						{
+							if(bitmap.bmWidth * cyIconCtrl > bitmap.bmHeight * cxIconCtrl)
+							{
+								/* Scale to fit width. */
+								cxScaled = cxIconCtrl;
+								cyScaled = MulDiv(bitmap.bmHeight, cxIconCtrl, bitmap.bmWidth);
+							}
+							else
+							{
+								/* Scaled to fit height. */
+								cxScaled = MulDiv(bitmap.bmWidth, cyIconCtrl, bitmap.bmHeight);
+								cyScaled = cyIconCtrl;
+							}
+						}
+						else
+						{
+							cxScaled = bitmap.bmWidth;
+							cyScaled = bitmap.bmHeight;
+						}
+
+						/* CreateCompatibleBitmap must be called after the first SelectObject
+						 * so that we don't end up with a monochrome bitmap.
+						 */
+						hbmSrcOrig = SelectObject(hdcSrc, (HBITMAP)lParam);
+						hbmScaled = CreateCompatibleBitmap(hdcSrc, cxScaled, cyScaled);
+						hbmDestOrig = SelectObject(hdcDest, hbmScaled);
+
+						SetStretchBltMode(hdcDest, COLORONCOLOR);
+
+						StretchBlt(hdcDest, 0, 0, cxScaled, cyScaled, hdcSrc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, SRCCOPY);
+
+						SelectObject(hdcDest, hbmDestOrig);
+						SelectObject(hdcSrc, hbmSrcOrig);
+
+						DeleteDC(hdcDest);
+						DeleteDC(hdcSrc);
+
+						DeleteObject((HBITMAP)lParam);
+					}
+
+					hbmOld = (HBITMAP)SendDlgItemMessage(g_hwndStatusDlg, IDC_BUDDY_ICON, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbmScaled);
+					if(hbmOld)
+						DeleteObject(hbmOld);
+				}
+
+				break;
+
 			case VUIMSG_QUIT:
 				DestroyWindow(hwnd);
 				break;
@@ -467,6 +551,7 @@ static INT_PTR CALLBACK StatusDlgProc(HW
 			RECT rcIcon;
 			POINT ptIcon;
 			HWND hwndStatusMsg = GetDlgItem(hwndDlg, IDC_EDIT_STATUSMSG);
+			HWND hwndBuddyIcon = GetDlgItem(hwndDlg, IDC_BUDDY_ICON);
 			STATUSDLGDATA *lpsdd;
 
 			GetWindowRect(GetDlgItem(hwndDlg, IDC_BUDDY_ICON), &rcIcon);
@@ -479,11 +564,13 @@ static INT_PTR CALLBACK StatusDlgProc(HW
 			 */
 			SetWindowPos(GetDlgItem(hwndDlg, IDC_BUDDY_ICON), NULL, BLIST_MARGIN, ptIcon.y, rcIcon.bottom - rcIcon.top, rcIcon.bottom - rcIcon.top, SWP_NOACTIVATE | SWP_NOZORDER);
 
-			/* Subclass status message box. */
+			/* Subclass controls. */
 			lpsdd = ProcHeapAlloc(sizeof(STATUSDLGDATA));
 			lpsdd->wndprocStatusMsgOrig = (WNDPROC)GetWindowLongPtr(hwndStatusMsg, GWLP_WNDPROC);
+			lpsdd->wndprocBuddyIconOrig = (WNDPROC)GetWindowLongPtr(hwndBuddyIcon, GWLP_WNDPROC);
 			SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG)lpsdd);
 			SetWindowLongPtr(hwndStatusMsg, GWLP_WNDPROC, (LONG)StatusMsgBoxSubclassProc);
+			SetWindowLongPtr(hwndBuddyIcon, GWLP_WNDPROC, (LONG)BuddyIconSubclassProc);
 
 			/* Set the combo's image-lists, and tell it to
 			 * recalculate its size.
@@ -557,6 +644,15 @@ static INT_PTR CALLBACK StatusDlgProc(HW
 			}
 
 			break;
+
+		case IDC_BUDDY_ICON:
+			if(HIWORD(wParam) == STN_CLICKED)
+			{
+				ChooseBuddyIcon();
+				return TRUE;
+			}
+			
+			break;
 		}
 
 		break;
@@ -1665,3 +1761,70 @@ static void RequestAddGroup(HWND hwndPar
 		ProcHeapFree(szGroup);
 	}
 }
+
+
+/**
+ * Subclassing window procedure for buddy-icon control in status dialogue.
+ *
+ * @param	hwnd		Input box window handle.
+ * @param	uiMsg		Message ID.
+ * @param	wParam		Message-specific.
+ * @param	lParam		Message-specific.
+ *
+ * @return Message-specific.
+ */
+static LRESULT CALLBACK BuddyIconSubclassProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
+{
+	STATUSDLGDATA *lpsdd;
+	HWND hwndParent = GetParent(hwnd);
+
+	switch(uiMsg)
+	{
+	/* Need >= Win98/2000 for IDC_HAND. */
+#if WINVER >= 0x0410
+	case WM_SETCURSOR:
+		SetCursor(LoadCursor(NULL, IDC_HAND));
+		return TRUE;
+#endif
+	case WM_RBUTTONUP:
+		{
+			HMENU hmenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDM_BUDDYICON_CONTEXT));
+			HMENU hmenuSubmenu = GetSubMenu(hmenu, 0);
+			
+			POINT ptMouse = {(short)LOWORD(lParam), (short)HIWORD(lParam)};
+			ClientToScreen(hwnd, &ptMouse);
+
+			EnableMenuItem(hmenuSubmenu, IDM_BUDDYICON_CONTEXT_REMOVE, SendMessage(hwnd, STM_GETIMAGE, 0, 0) ? MF_ENABLED : MF_DISABLED);
+
+			TrackPopupMenu(hmenuSubmenu, TPM_RIGHTBUTTON, ptMouse.x, ptMouse.y, 0, g_hwndMain, NULL);
+			DestroyMenu(hmenu);
+		}
+
+		return 0;
+	}
+
+	lpsdd = (STATUSDLGDATA*)GetWindowLongPtr(hwndParent, GWLP_USERDATA);
+	return CallWindowProc(lpsdd->wndprocBuddyIconOrig, hwnd, uiMsg, wParam, lParam);
+}
+
+
+/** Prompts the user to set the global buddy icon. */
+static void ChooseBuddyIcon(void)
+{
+	TCHAR szFilename[MAX_PATH];
+	TCHAR szFilter[256];
+	TCHAR szTitle[256];
+
+	VultureLoadAndFormatFilterString(IDS_BUDDYICON_FILTER, szFilter, NUM_ELEMENTS(szFilter));
+	LoadString(g_hInstance, IDS_BUDDYICON_TITLE, szTitle, NUM_ELEMENTS(szTitle));
+
+	if(VultureCommDlgOpen(g_hwndMain, szFilename, NUM_ELEMENTS(szFilename), szTitle, szFilter, TEXT("png"), NULL, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY))
+		VultureSingleSyncPurpleCall(PC_SETGLOBALBICON, szFilename);
+}
+
+
+/** Clears the global buddy icon. */
+static void ClearBuddyIcon(void)
+{
+	VultureSingleSyncPurpleCall(PC_SETGLOBALBICON, NULL);
+}


More information about the Commits mailing list