1. 概述
1.1. Miranda IM运行以后的主界面:
1.2. 用spy++可以看到窗口的构成:
从上图可以看出miranda的窗口的基本构成,下面对比较重要的几个窗口进行具体的分析.
1.3. 关于GWL_USERDATA
Miranda的窗口里经常会有一些全局的数据需要保存, Miranda的做法是在处理
WM_INITDIALOG时动态创建一个对象并初始化, 然后以GWL_USERDATA为参数调用SetWindowLong将之作为该窗口的用户数据,这样通过调用GetWindowLong就可以返回该用户数据的指针.
在WM_DESTROY消息处理里释放该动态创建的对象,并将窗口的用户数据设置为空.
这种做法估计在SDK编程里是很常用的手法. 在MFC程序里每个对应窗口(指桌面上看到的窗口)都会有一个从CWnd派生类的实例与之对应,所以与该窗口相关的数据尽管作为类的一个成员变量就好了,不用象sdk里这么麻烦.
BOOL CALLBACK DlgProcMessage(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
struct MessageWindowData *dat;
dat = (struct MessageWindowData *) GetWindowLong(hwndDlg, GWL_USERDATA);
switch (msg)
{
case WM_INITDIALOG:
{
struct NewMessageWindowLParam *newData = (struct NewMessageWindowLParam *) lParam;
TranslateDialogDefault(hwndDlg);
dat = (struct MessageWindowData *) calloc(sizeof(struct MessageWindowData),1);
SetWindowLong(hwndDlg, GWL_USERDATA, (LONG) dat);
……
}
……
case WM_DESTROY:
{
……
free(dat);
SetWindowLong(hwndDlg, GWL_USERDATA, 0);
……
}
}
……
}
1.4. MessageWindowData
struct MessageWindowData
{
HANDLE hContact;
HANDLE hDbEventFirst, hDbEventLast;
HANDLE hSendId;
int sendCount;
HBRUSH hBkgBrush;
int splitterPos, originalSplitterPos;
char *sendBuffer;
SIZE minEditBoxSize;
RECT minEditInit;
int lineHeight;
int windowWasCascaded;
int nFlash;
int nFlashMax;
int nLabelRight;
int nTypeSecs;
int nTypeMode;
int avatarWidth;
int avatarHeight;
int limitAvatarH;
HBITMAP avatarPic;
DWORD nLastTyping;
int showTyping;
HWND hwndStatus;
DWORD lastMessage;
char *szProto;
WORD wStatus;
WORD wOldStatus;
TCmdList *cmdList;
TCmdList *cmdListCurrent;
int bIsRtl, bIsFirstAppend, bIsAutoRTL;
int lastEventType;
};
2. 主窗口
2.1. WindowClass:
#define MIRANDACLASS "Miranda"
2.2. 窗口过程:
ContactListWndProc
2.3. 主窗口句柄:
cli.hwndContactList
2.4. 窗口创建过程:
2.4.1. 代码位置:
/miranda/src/modules/clist, cl 是contact list的缩写
2.4.2. CLIST_INTERFACE
CLIST_INTERFACE 是一个关键的struct,里面登记了主窗口,联系人列表窗口里各种api函数指针,程序里有一个全局的变量
CLIST_INTERFACE cli;
cli在加载模块时在函数srvRetrieveInterface里被初始化
模块加载完毕以后,WinMain会激发ModulesLoaded事件
NotifyEventHooks(hModulesLoadedEvent,0,0);
2.4.3. 注册主窗口类
clui.c中的LoadCLUIModule被调用
在int LoadCLUIModule(void) 里注册主窗口类:
wndclass.style = CS_HREDRAW | CS_VREDRAW | (IsWinVerXPPlus() && DBGetContactSettingByte(NULL, "CList", "WindowShadow", 0) == 1 ? CS_DROPSHADOW : 0);
wndclass.lpfnWndProc = ContactListWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = cli.hInst;
wndclass.hIcon = LoadSkinnedIcon(SKINICON_OTHER_MIRANDA);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) (COLOR_3DFACE + 1);
wndclass.lpszMenuName = MAKEINTRESOURCE(IDR_CLISTMENU);
wndclass.lpszClassName = _T(MIRANDACLASS);//
RegisterClass(&wndclass);
2.4.4. 创建主窗口:
//创建主窗口
cli.hwndContactList = CreateWindowEx(
DBGetContactSettingByte(NULL, "CList", "ToolWindow", SETTING_TOOLWINDOW_DEFAULT) ? WS_EX_TOOLWINDOW : 0,
_T(MIRANDACLASS),//主窗口类
titleText,
(DBGetContactSettingByte(NULL, "CLUI", "ShowCaption", SETTING_SHOWCAPTION_DEFAULT) ?
WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX : 0) | WS_POPUPWINDOW | WS_THICKFRAME | WS_CLIPCHILDREN,
(int) DBGetContactSettingDword(NULL, "CList", "x", 700),
(int) DBGetContactSettingDword(NULL, "CList", "y", 221),
(int) DBGetContactSettingDword(NULL, "CList", "Width", 108),
(int) DBGetContactSettingDword(NULL, "CList", "Height", 310),
NULL, NULL, cli.hInst, NULL);
2.4.5. 创建子窗口
主窗口创建以后, 调用cli.pfnOnCreateClc(); 依次创建出其他字窗口
3. contact list窗口
3.1. WindowClass:
#define CLISTCONTROL_CLASS _T("CListControl")
3.2. 窗口过程:
fnContactListControlWndProc
3.3. 窗口句柄:
pcli->hwndContactTree
3.4. 窗口创建过程:
3.4.1. 代码位置:
/miranda/src/modules/clist, cl 是contact list的缩写
3.4.2. CLIST_INTERFACE
全局的变量
CLIST_INTERFACE cli;
cli在加载模块时在函数srvRetrieveInterface里被初始化,其中contact list窗口过程为:
cli.pfnContactListControlWndProc = fnContactListControlWndProc;//函数定义在clc.c里
模块加载完毕以后,WinMain会激发ModulesLoaded事件
NotifyEventHooks(hModulesLoadedEvent,0,0);
clui.c中的LoadCLUIModule被调用:
3.4.3. 注册contact list窗口类
//首先注册contact list窗口类
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_GLOBALCLASS;
wndclass.lpfnWndProc = cli.pfnContactListControlWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = sizeof(void *);
wndclass.hInstance = cli.hInst;
wndclass.hIcon = NULL;
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = NULL;
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = CLISTCONTROL_CLASS;
RegisterClass(&wndclass);
//注册主窗口类
……
if (DBGetContactSettingTString(NULL, "CList", "TitleText", &dbv))
lstrcpyn(titleText, _T(MIRANDANAME), SIZEOF( titleText ));
else {
lstrcpyn(titleText, dbv.ptszVal, SIZEOF(titleText));
DBFreeVariant(&dbv);
}
//创建主窗口
cli.hwndContactList = CreateWindowEx(
DBGetContactSettingByte(NULL, "CList", "ToolWindow", SETTING_TOOLWINDOW_DEFAULT) ? WS_EX_TOOLWINDOW
: 0,
_T(MIRANDACLASS),
titleText,
(DBGetContactSettingByte(NULL, "CLUI", "ShowCaption", SETTING_SHOWCAPTION_DEFAULT) ?
WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX : 0) | WS_POPUPWINDOW | WS_THICKFRAME |
WS_CLIPCHILDREN,
(int) DBGetContactSettingDword(NULL, "CList", "x", 700),
(int) DBGetContactSettingDword(NULL, "CList", "y", 221),
(int) DBGetContactSettingDword(NULL, "CList", "Width", 108),
(int) DBGetContactSettingDword(NULL, "CList", "Height", 310),
NULL, NULL, cli.hInst, NULL);
//主窗口创建完毕以后,创建各个子窗口
cli.pfnOnCreateClc();
3.4.4. 创建contact list窗口
在函数CLUI_PreCreateCLC里创建出contact list窗口
pcli->hwndContactTree=CreateWindow(CLISTCONTROL_CLASS,TEXT(""),
WS_CHILD|WS_CLIPCHILDREN|CLS_CONTACTLIST
|(DBGetContactSettingByte(NULL,"CList","UseGroups",SETTING_USEGROUPS_DEFAULT)?CLS_USEGROUPS:0)
//|CLS_HIDEOFFLINE
|(DBGetContactSettingByte(NULL,"CList","HideOffline",SETTING_HIDEOFFLINE_DEFAULT)?CLS_HIDEOFFLINE:0)
|(DBGetContactSettingByte(NULL,"CList","HideEmptyGroups",SETTING_HIDEEMPTYGROUPS_DEFAULT)?CLS_HIDEEMPTYGROUPS:0
|CLS_MULTICOLUMN
//|DBGetContactSettingByte(NULL,"CLUI","ExtraIconsAlignToLeft",1)?CLS_EX_MULTICOLUMNALIGNLEFT:0
),
0,0,0,0,parent,NULL,g_hInst,NULL);
4. 聊天窗口
4.1. 对话框模板:
IDD_MSG
4.2. 窗口过程:
DlgProcMessage
4.3. 创建过程:
4.3.1. 双击联系人
当我们双击联系人列表里的某个联系人时,由于双击的是contact list窗口,其窗口过程fnContactListControlWndProc将收到消息WM_LBUTTONDBLCLK, 没有了mfc里的消息映射可以看到函数fnContactListControlWndProc真是奇长无比,对消息LBUTTONDBLCLK处理如下:
case WM_LBUTTONDBLCLK:
{
struct ClcContact *contact;
DWORD hitFlags;
ReleaseCapture();
dat->iHotTrack = -1;
cli.pfnHideInfoTip(hwnd, dat);
KillTimer(hwnd, TIMERID_RENAME);
KillTimer(hwnd, TIMERID_INFOTIP);
dat->szQuickSearch[0] = 0;
dat->selection = cli.pfnHitTest(hwnd, dat, (short) LOWORD(lParam), (short) HIWORD(lParam), &contact, NULL, &hitFlags);
cli.pfnInvalidateRect(hwnd, NULL, FALSE);
if (dat->selection != -1)
cli.pfnEnsureVisible(hwnd, dat, dat->selection, 0);
if (!(hitFlags & (CLCHT_ONITEMICON | CLCHT_ONITEMLABEL)))
break;
UpdateWindow(hwnd);
cli.pfnDoSelectionDefaultAction(hwnd, dat);
break;
}
4.3.2. 函数fnDoSelectionDefaultAction
在该函数里,通过单击的位置可以得到对应的联系人item, 一个struct ClcContact *contact指针, (*contact).proto里记录了该联系人使用的协议,jabber,qq等.
void fnDoSelectionDefaultAction(HWND hwnd, struct ClcData *dat)
{
struct ClcContact *contact;
if (dat->selection == -1)
return;
dat->szQuickSearch[0] = 0;
//(*contact).proto里记录了该联系人使用的协议
if (cli.pfnGetRowByIndex(dat, dat->selection, &contact, NULL) == -1)
return;
if (contact->type == CLCIT_GROUP)
cli.pfnSetGroupExpand(hwnd, dat, contact->group, -1);
if (contact->type == CLCIT_CONTACT)
CallService(MS_CLIST_CONTACTDOUBLECLICKED, (WPARAM) contact->hContact, 0);
4.3.2.1. MS_CLIST_CONTACTDOUBLECLICKED
static int ContactDoubleClicked(WPARAM wParam, LPARAM lParam)
{
// Check and an event from the CList queue for this hContact
if (cli.pfnEventsProcessContactDoubleClick((HANDLE) wParam))
NotifyEventHooks(hContactDoubleClicked, wParam, 0);
return 0;
}
4.3.2.1.1. SendMessageCommand
static int SendMessageCommand(WPARAM wParam, LPARAM lParam)
{
HWND hwnd;
struct NewMessageWindowLParam newData = { 0 };
{
/* does the HCONTACT's protocol support IM messages? */
//得到当前联系人的协议名称
char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, wParam, 0);
if (szProto) {
//获得协议选项,判断是否支持发送消息
if (!CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IMSEND)
return 1;
}
else {
/* unknown contact */
return 1;
} //if
}
//判断是否已经打开聊天窗口
if (hwnd = WindowList_Find(g_dat->hMessageWindowList, (HANDLE) wParam)) {
if (lParam) {
HWND hEdit;
hEdit = GetDlgItem(hwnd, IDC_MESSAGE);
SendMessage(hEdit, EM_SETSEL, -1, SendMessage(hEdit, WM_GETTEXTLENGTH, 0, 0));
SendMessageA(hEdit, EM_REPLACESEL, FALSE, (LPARAM) (char *) lParam);
}
ShowWindow(hwnd, SW_SHOWNORMAL);
SetForegroundWindow(hwnd);
SetFocus(hwnd);
}
else {
newData.hContact = (HANDLE) wParam;
newData.szInitialText = (const char *) lParam;
newData.isWchar = 0;
//创建聊天对话框
CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_MSG), NULL, DlgProcMessage, (LPARAM) & newData);
}
return 0;
}
4.3.2.1.1.1. MS_PROTO_GETCONTACTBASEPROTO 获得协议名称
static int Proto_GetContactBaseProto(WPARAM wParam,LPARAM lParam)
{
DBVARIANT dbv;
PROTOCOLDESCRIPTOR *pd;
DBCONTACTGETSETTING dbcgs;
char name[32];
dbv.type=DBVT_ASCIIZ;
dbv.pszVal=name;
dbv.cchVal=SIZEOF(name);
dbcgs.pValue=&dbv;
dbcgs.szModule="Protocol";
dbcgs.szSetting="p";
if(CallService(MS_DB_CONTACT_GETSETTINGSTATIC,wParam,(LPARAM)&dbcgs)) return (int)(char*)NULL;
pd=(PROTOCOLDESCRIPTOR*)Proto_IsProtocolLoaded(0,(LPARAM)dbv.pszVal);
if(pd==NULL) return (int)(char*)NULL;
return (int)pd->szName;
}
MS_DB_CONTACT_GETSETTINGSTATIC 获得协议名称
static int GetContactSettingStatic(WPARAM wParam,LPARAM lParam)
{
DBCONTACTGETSETTING* dgs = (DBCONTACTGETSETTING*)lParam;
// dgs->pValue->pszVal中将得到协议的名称
if ( GetContactSettingWorker(( HANDLE )wParam, dgs, 1 ))
return 1;
if ( dgs->pValue->type == DBVT_UTF8 ) {
mir_utf8decode( dgs->pValue->pszVal, NULL );
dgs->pValue->type = DBVT_ASCIIZ;
}
return 0;
}
4.3.2.1.1.2. protocol capabilities bits
//CallProtoService(szProto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IMSEND)
int JabberGetCaps( WPARAM wParam, LPARAM lParam )
{
switch( wParam ) {
case PFLAGNUM_1:
return PF1_IM|PF1_AUTHREQ|PF1_SERVERCLIST|PF1_MODEMSG|PF1_BASICSEARCH|PF1_SEARCHBYEMAIL|PF1_SEARCHBYNAME|PF1_FILE|PF1_VISLIST|PF1_INVISLIST;
case PFLAGNUM_2:
return PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_HEAVYDND | PF2_FREECHAT;
case PFLAGNUM_3:
return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_HEAVYDND | PF2_FREECHAT;
case PFLAGNUM_4:
return PF4_FORCEAUTH | PF4_NOCUSTOMAUTH | PF4_SUPPORTTYPING | PF4_AVATARS;
case PFLAG_UNIQUEIDTEXT:
return ( int ) JTranslate( "JID" );
case PFLAG_UNIQUEIDSETTING:
return ( int ) "jid";
}
return 0;
}
4.3.2.1.1.3. 创建出聊天对话框
最终会调用CreateDialogParam创建出聊天对话框:
CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_MSG), NULL, DlgProcMessage, (LPARAM) & newData);
4.3.2.1.1.3.1. case WM_INITDIALOG
创建窗口以后在WM_INITDIALOG处理里,做初始化操作
struct MessageWindowData *dat;
dat = (struct MessageWindowData *) GetWindowLong(hwndDlg, GWL_USERDATA);
switch (msg) {
case WM_INITDIALOG:
{
struct NewMessageWindowLParam *newData = (struct NewMessageWindowLParam *) lParam;
TranslateDialogDefault(hwndDlg);
dat = (struct MessageWindowData *) calloc(sizeof(struct MessageWindowData),1);
SetWindowLong(hwndDlg, GWL_USERDATA, (LONG) dat);
{
dat->hContact = newData->hContact;
NotifyLocalWinEvent(dat->hContact, hwndDlg, MSG_WINDOW_EVT_OPENING);
if (newData->szInitialText) {
int len;
#if defined(_UNICODE)
if(newData->isWchar)
SetDlgItemText(hwndDlg, IDC_MESSAGE, (TCHAR *)newData->szInitialText);
else
SetDlgItemTextA(hwndDlg, IDC_MESSAGE, newData->szInitialText);
#else
SetDlgItemTextA(hwndDlg, IDC_MESSAGE, newData->szInitialText);
#endif
len = GetWindowTextLength(GetDlgItem(hwndDlg, IDC_MESSAGE));
PostMessage(GetDlgItem(hwndDlg, IDC_MESSAGE), EM_SETSEL, len, len);
}
}
dat->szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) dat->hContact, 0);
RichUtil_SubClass(GetDlgItem(hwndDlg, IDC_LOG));
{ // avatar stuff
dat->avatarPic = 0;
dat->avatarWidth = 0;
dat->avatarHeight = 0;
dat->limitAvatarH = DBGetContactSettingByte(NULL, SRMMMOD, SRMSGSET_LIMITAVHEIGHT, SRMSGDEFSET_LIMITAVHEIGHT)?DBGetContactSettingDword(NULL, SRMMMOD, SRMSGSET_AVHEIGHT, SRMSGDEFSET_AVHEIGHT):0;
}
if (dat->hContact && dat->szProto != NULL)
dat->wStatus = DBGetContactSettingWord(dat->hContact, dat->szProto, "Status", ID_STATUS_OFFLINE);
else
dat->wStatus = ID_STATUS_OFFLINE;
dat->wOldStatus = dat->wStatus;
dat->hSendId = NULL;
dat->hBkgBrush = NULL;
dat->hDbEventFirst = NULL;
dat->sendBuffer = NULL;
dat->splitterPos = (int) DBGetContactSettingDword(DBGetContactSettingByte(NULL, SRMMMOD, SRMSGSET_SAVEPERCONTACT, SRMSGDEFSET_SAVEPERCONTACT)?dat->hContact:NULL, SRMMMOD, "splitterPos", (DWORD) - 1);
dat->windowWasCascaded = 0;
dat->nFlash = 0;
dat->nTypeSecs = 0;
dat->nLastTyping = 0;
dat->showTyping = 0;
dat->cmdList = 0;
dat->cmdListCurrent = 0;
dat->nTypeMode = PROTOTYPE_SELFTYPING_OFF;
SetTimer(hwndDlg, TIMERID_TYPE, 1000, NULL);
dat->lastMessage = 0;
dat->lastEventType = -1;
dat->nFlashMax = DBGetContactSettingByte(NULL, SRMMMOD, SRMSGSET_FLASHCOUNT, SRMSGDEFSET_FLASHCOUNT);
{
RECT rc, rc2;
GetWindowRect(GetDlgItem(hwndDlg, IDC_USERMENU), &rc);
GetWindowRect(hwndDlg, &rc2);
dat->nLabelRight = rc2.right - rc.left;
}
{
RECT rc;
POINT pt;
GetWindowRect(GetDlgItem(hwndDlg, IDC_SPLITTER), &rc);
pt.y = (rc.top + rc.bottom) / 2;
pt.x = 0;
ScreenToClient(hwndDlg, &pt);
dat->originalSplitterPos = pt.y;
if (dat->splitterPos == -1)
dat->splitterPos = dat->originalSplitterPos;// + 60;
GetWindowRect(GetDlgItem(hwndDlg, IDC_ADD), &rc);
dat->lineHeight = rc.bottom - rc.top + 3;
}
WindowList_Add(g_dat->hMessageWindowList, hwndDlg, dat->hContact);
GetWindowRect(GetDlgItem(hwndDlg, IDC_MESSAGE), &dat->minEditInit);
SendMessage(hwndDlg, DM_UPDATESIZEBAR, 0, 0);
dat->hwndStatus = NULL;
SendDlgItemMessage(hwndDlg, IDC_ADD, BM_SETIMAGE, IMAGE_ICON, (LPARAM) g_dat->hIcons[SMF_ICON_ADD]);
SendDlgItemMessage(hwndDlg, IDC_DETAILS, BM_SETIMAGE, IMAGE_ICON, (LPARAM) g_dat->hIcons[SMF_ICON_USERDETAIL]);
SendDlgItemMessage(hwndDlg, IDC_HISTORY, BM_SETIMAGE, IMAGE_ICON, (LPARAM) g_dat->hIcons[SMF_ICON_HISTORY]);
SendDlgItemMessage(hwndDlg, IDC_USERMENU, BM_SETIMAGE, IMAGE_ICON, (LPARAM) g_dat->hIcons[SMF_ICON_ARROW]);
// Make them flat buttons
{
int i;
SendMessage(GetDlgItem(hwndDlg, IDC_NAME), BUTTONSETASFLATBTN, 0, 0);
for (i = 0; i < SIZEOF(buttonLineControls); i++)
SendMessage(GetDlgItem(hwndDlg, buttonLineControls[i]), BUTTONSETASFLATBTN, 0, 0);
}
SendMessage(GetDlgItem(hwndDlg, IDC_ADD), BUTTONADDTOOLTIP, (WPARAM) Translate("Add Contact Permanently to List"), 0);
SendMessage(GetDlgItem(hwndDlg, IDC_USERMENU), BUTTONADDTOOLTIP, (WPARAM) Translate("User Menu"), 0);
SendMessage(GetDlgItem(hwndDlg, IDC_DETAILS), BUTTONADDTOOLTIP, (WPARAM) Translate("View User's Details"), 0);
SendMessage(GetDlgItem(hwndDlg, IDC_HISTORY), BUTTONADDTOOLTIP, (WPARAM) Translate("View User's History"), 0);
EnableWindow(GetDlgItem(hwndDlg, IDC_PROTOCOL), FALSE);
EnableWindow(GetDlgItem(hwndDlg, IDC_AVATAR), FALSE);
SendDlgItemMessage(hwndDlg, IDC_LOG, EM_SETOLECALLBACK, 0, (LPARAM) & reOleCallback);
SendDlgItemMessage(hwndDlg, IDC_LOG, EM_SETEVENTMASK, 0, ENM_MOUSEEVENTS | ENM_LINK);
/* duh, how come we didnt use this from the start? */
SendDlgItemMessage(hwndDlg, IDC_LOG, EM_AUTOURLDETECT, (WPARAM) TRUE, 0);
if (dat->hContact) {
if (dat->szProto) {
int nMax;
nMax = CallProtoService(dat->szProto, PS_GETCAPS, PFLAG_MAXLENOFMESSAGE, (LPARAM) dat->hContact);
if (nMax)
SendDlgItemMessage(hwndDlg, IDC_MESSAGE, EM_LIMITTEXT, (WPARAM) nMax, 0);
/* get around a lame bug in the Windows template resource code where richedits are limited to 0x7FFF */
SendDlgItemMessage(hwndDlg, IDC_LOG, EM_LIMITTEXT, (WPARAM) sizeof(TCHAR) * 0x7FFFFFFF, 0);
}
}
OldMessageEditProc = (WNDPROC) SetWindowLong(GetDlgItem(hwndDlg, IDC_MESSAGE), GWL_WNDPROC, (LONG) MessageEditSubclassProc);
SendDlgItemMessage(hwndDlg, IDC_MESSAGE, EM_SUBCLASSED, 0, 0);
OldSplitterProc = (WNDPROC) SetWindowLong(GetDlgItem(hwndDlg, IDC_SPLITTER), GWL_WNDPROC, (LONG) SplitterSubclassProc);
if (dat->hContact) {
int historyMode = DBGetContactSettingByte(NULL, SRMMMOD, SRMSGSET_LOADHISTORY, SRMSGDEFSET_LOADHISTORY);
// This finds the first message to display, it works like shit
dat->hDbEventFirst = (HANDLE) CallService(MS_DB_EVENT_FINDFIRSTUNREAD, (WPARAM) dat->hContact, 0);
switch (historyMode) {
case LOADHISTORY_COUNT:
{
int i;
HANDLE hPrevEvent;
DBEVENTINFO dbei = { 0 };
dbei.cbSize = sizeof(dbei);
for (i = DBGetContactSettingWord(NULL, SRMMMOD, SRMSGSET_LOADCOUNT, SRMSGDEFSET_LOADCOUNT); i > 0; i--) {
if (dat->hDbEventFirst == NULL)
hPrevEvent = (HANDLE) CallService(MS_DB_EVENT_FINDLAST, (WPARAM) dat->hContact, 0);
else
hPrevEvent = (HANDLE) CallService(MS_DB_EVENT_FINDPREV, (WPARAM) dat->hDbEventFirst, 0);
if (hPrevEvent == NULL)
break;
dbei.cbBlob = 0;
dat->hDbEventFirst = hPrevEvent;
CallService(MS_DB_EVENT_GET, (WPARAM) dat->hDbEventFirst, (LPARAM) & dbei);
if (!DbEventIsShown(&dbei, dat))
i++;
}
break;
}
case LOADHISTORY_TIME:
{
HANDLE hPrevEvent;
DBEVENTINFO dbei = { 0 };
DWORD firstTime;
dbei.cbSize = sizeof(dbei);
if (dat->hDbEventFirst == NULL)
dbei.timestamp = (DWORD)time(NULL);
else
CallService(MS_DB_EVENT_GET, (WPARAM) dat->hDbEventFirst, (LPARAM) & dbei);
firstTime = dbei.timestamp - 60 * DBGetContactSettingWord(NULL, SRMMMOD, SRMSGSET_LOADTIME, SRMSGDEFSET_LOADTIME);
for (;;) {
if (dat->hDbEventFirst == NULL)
hPrevEvent = (HANDLE) CallService(MS_DB_EVENT_FINDLAST, (WPARAM) dat->hContact, 0);
else
hPrevEvent = (HANDLE) CallService(MS_DB_EVENT_FINDPREV, (WPARAM) dat->hDbEventFirst, 0);
if (hPrevEvent == NULL)
break;
dbei.cbBlob = 0;
CallService(MS_DB_EVENT_GET, (WPARAM) hPrevEvent, (LPARAM) & dbei);
if (dbei.timestamp < firstTime)
break;
dat->hDbEventFirst = hPrevEvent;
}
break;
}
}
}
SendMessage(hwndDlg, DM_OPTIONSAPPLIED, 1, 0);
{
int savePerContact = DBGetContactSettingByte(NULL, SRMMMOD, SRMSGSET_SAVEPERCONTACT, SRMSGDEFSET_SAVEPERCONTACT);
if (Utils_RestoreWindowPosition(hwndDlg, savePerContact ? dat->hContact : NULL, SRMMMOD, "")) {
if (savePerContact) {
if (Utils_RestoreWindowPositionNoMove(hwndDlg, NULL, SRMMMOD, ""))
SetWindowPos(hwndDlg, 0, 0, 0, 450, 300, SWP_NOZORDER | SWP_NOMOVE);
}
else
SetWindowPos(hwndDlg, 0, 0, 0, 450, 300, SWP_NOZORDER | SWP_NOMOVE);
}
if (!savePerContact && DBGetContactSettingByte(NULL, SRMMMOD, SRMSGSET_CASCADE, SRMSGDEFSET_CASCADE))
WindowList_Broadcast(g_dat->hMessageWindowList, DM_CASCADENEWWINDOW, (WPARAM) hwndDlg, (LPARAM) & dat->windowWasCascaded);
}
{
DBEVENTINFO dbei = { 0 };
HANDLE hdbEvent;
dbei.cbSize = sizeof(dbei);
hdbEvent = (HANDLE) CallService(MS_DB_EVENT_FINDLAST, (WPARAM) dat->hContact, 0);
if (hdbEvent) {
do {
ZeroMemory(&dbei, sizeof(dbei));
dbei.cbSize = sizeof(dbei);
CallService(MS_DB_EVENT_GET, (WPARAM) hdbEvent, (LPARAM) & dbei);
if (dbei.eventType == EVENTTYPE_MESSAGE && !(dbei.flags & DBEF_SENT)) {
dat->lastMessage = dbei.timestamp;
SendMessage(hwndDlg, DM_UPDATELASTMESSAGE, 0, 0);
break;
}
}
while (hdbEvent = (HANDLE) CallService(MS_DB_EVENT_FINDPREV, (WPARAM) hdbEvent, 0));
}
}
SendMessage(hwndDlg, DM_GETAVATAR, 0, 0);
ShowWindow(hwndDlg, SW_SHOWNORMAL);
NotifyLocalWinEvent(dat->hContact, hwndDlg, MSG_WINDOW_EVT_OPEN);
return TRUE;
}
5. 菜单的加载与响应
这里主要分析了Miranda菜单处理的过程, 对于插件加载并处理自己的菜单命令,参考<Miranda 插件分析>.
以jabber为例,说明菜单的加载及响应菜单命令的过程. 菜单主要有:
主菜单 : MainMenuExecService
状态菜单: StatusMenuExecService
5.1. MenuObjects全局变量
//menu object array
PIntMenuObject MenuObjects=NULL;
MenuObjects数组里记录的是大的菜单项,比如主窗口的file菜单项,
MenuObjects[i].ExecService里登记了单击这个菜单时执行的函数名xxx (调用方式:CallService(xxx)). 比如状态菜单里的名字为
StatusMenuExecService.MenuObjects[i].MenuItems为子菜单项数组.
5.1.1. PMO_IntMenuItem
typedef struct
{
int id;
int globalid;
int iconId;
TMO_MenuItem mi;
boolean OverrideShow;
char *UniqName;
TCHAR *CustomName;
HMENU hSubMenu;
boolean IconRegistred;
}
TMO_IntMenuItem,*PMO_IntMenuItem;
5.1.2. PIntMenuObject
typedef struct
{
char *Name;//for debug purposes
int id;
//ExecService
//LPARAM lParam;//owner data
//WPARAM wParam;//allways lparam from winproc
char *ExecService; // 单击大(比如file菜单)菜单项时调用的函数
//CheckService called when building menu
//return false to skip item.
//LPARAM lParam;//0
//WPARAM wParam;//CheckParam
char *CheckService;//analog to check_proc
//LPARAM lParam;//ownerdata
//WPARAM wParam;//menuitemhandle
char *FreeService;//callback service used to free ownerdata for menuitems
//LPARAM lParam;//MENUITEMINFOA filled with all needed data
//WPARAM wParam;//menuitemhandle
char *onAddService;//called just before add MENUITEMINFOA to hMenu
PMO_IntMenuItem MenuItems; //子菜单项数组
int MenuItemsCount;
HANDLE hMenuIcons;
BOOL bUseUserDefinedItems;
}
TIntMenuObject,*PIntMenuObject;
5.2. JabberMenuInit()
在插件的Load函数里,调用JabberMenuInit()
void JabberMenuInit()
{
CLISTMENUITEM mi = { 0 };
mi.cbSize = sizeof( CLISTMENUITEM );
char text[ 200 ];
strcpy( text, jabberProtoName );
char* tDest = text + strlen( text );
// "Request authorization"
strcpy( tDest, "/RequestAuth" );
//创建一个与菜单命令对应的处理函数
CreateServiceFunction( text, JabberMenuHandleRequestAuth );
mi.pszName = JTranslate( "Request authorization" );
mi.position = -2000001000;
mi.hIcon = LoadIcon( hInst, MAKEINTRESOURCE( IDI_REQUEST ));
//靠mi.pszService来调用对应的菜单处理函数
mi.pszService = text; //"JABBER/RequestAuth"
mi.pszContactOwner = jabberProtoName;
hMenuRequestAuth = ( HANDLE ) JCallService( MS_CLIST_ADDCONTACTMENUITEM, 0, ( LPARAM )&mi );
……
}
5.2.1. CLISTMENUITEM结构
typedef struct {
int cbSize; //size in bytes of this structure
union {
char* pszName; //text of the menu item
TCHAR* ptszName; //Unicode text of the menu item
};
DWORD flags; //flags
int position; //approx position on the menu. lower numbers go nearer the top
HICON hIcon; //icon to put by the item. If this was not loaded from
//a resource, you can delete it straight after the call
char* pszService; //name of service to call when the item gets selected
union {
char* pszPopupName; //name of the popup menu that this item is on. If this
//is NULL the item is on the root of the menu
TCHAR* ptszPopupName;
};
int popupPosition; //position of the popup menu on the root menu. Ignored
//if pszPopupName is NULL or the popup menu already
//existed
DWORD hotKey; //keyboard accelerator, same as lParam of WM_HOTKEY
//0 for none
char *pszContactOwner; //contact menus only. The protocol module that owns
//the contacts to which this menu item applies. NULL if it
//applies to all contacts. If it applies to multiple but not all
//protocols, add multiple menu items or use ME_CLIST_PREBUILDCONTACTMENU
} CLISTMENUITEM;
5.3. 响应菜单命令
MenuProcessCommand
MO_ProcessCommandByMenuIdent
MO_ProcessCommand
int MO_ProcessCommand(WPARAM wParam,LPARAM lParam)
{
int objidx,menuitemidx;//pos in array
int globid=wParam;
char *srvname;
void *ownerdata;
if (!isGenMenuInited) return -1;
lockmo();
// globid 是将上级菜单ID左移16位+子菜单ID构成的
//所以这里很容易就可以根据globid得到当前处理的菜单ID
if (GetAllIdx(globid,&objidx,&menuitemidx)==0)
{unlockmo();return(0);}
srvname=MenuObjects[objidx].ExecService;
ownerdata=MenuObjects[objidx].MenuItems[menuitemidx].mi.ownerdata;
unlockmo();
CallService(srvname,(WPARAM)ownerdata,lParam);
return(1);
}
5.3.1. StatusMenuExecService
int StatusMenuExecService(WPARAM wParam,LPARAM lParam)
{
lpStatusMenuExecParam smep=(lpStatusMenuExecParam)wParam;
……
else if ((smep->proto!=NULL))
{
CallProtoService(smep->proto,PS_SETSTATUS,smep->status,0);
NotifyEventHooks(hStatusModeChangeEvent, smep->status, (LPARAM)smep->proto);
//CallProtoService(smep->proto,PS_SETSTATUS,smep->status,0);
}else
……
};
5.3.1.1. JabberSetStatus
//处理离线,在线等菜单命令
int JabberSetStatus( WPARAM wParam, LPARAM lParam )
{
JabberLog( "PS_SETSTATUS( %d )", wParam );
int desiredStatus = wParam;
jabberDesiredStatus = desiredStatus;
if ( desiredStatus == ID_STATUS_OFFLINE ) {
if ( jabberThreadInfo ) {
jabberThreadInfo->send( "</stream:stream>" );
jabberThreadInfo = NULL;
if ( jabberConnected )
jabberConnected = jabberOnline = FALSE;
}
int oldStatus = jabberStatus;
jabberStatus = jabberDesiredStatus = ID_STATUS_OFFLINE;
JSendBroadcast( NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, ( HANDLE ) oldStatus, jabberStatus );
}
else if ( !jabberConnected && !( jabberStatus >= ID_STATUS_CONNECTING && jabberStatus < ID_STATUS_CONNECTING + MAX_CONNECT_RETRIES )) {
if ( jabberConnected )
return 0;
ThreadData* thread = new ThreadData( JABBER_SESSION_NORMAL );
jabberDesiredStatus = desiredStatus;
int oldStatus = jabberStatus;
jabberStatus = ID_STATUS_CONNECTING;
JSendBroadcast( NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, ( HANDLE ) oldStatus, jabberStatus );
thread->hThread = ( HANDLE ) mir_forkthread(( pThreadFunc )JabberServerThread, thread );
}
else JabberSetServerStatus( desiredStatus );
return 0;
}