Miranda UI 分析

本文详细分析了Miranda IM的用户界面构成,包括主窗口、联系人列表窗口和聊天窗口的创建过程。重点讨论了GWL_USERDATA在窗口数据存储中的应用,以及如何通过WM_INITDIALOG和WM_DESTROY消息处理用户数据。同时,解释了如何响应菜单命令,如StatusMenuExecService和JabberSetStatus函数,展示了菜单的加载和命令执行流程。
摘要由CSDN通过智能技术生成

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;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值