Miranda UI 分析

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;

}

 

阅读更多
个人分类: Miranda IM
上一篇利用 Oracle 10g 技能学习 DB2 9.1 for Linux, UNIX and Windows
下一篇Miranda插件分析
想对作者说点什么? 我来说一句

miranda IM 插件大全

2011年05月27日 254KB 下载

miranda IM 动态外观

2011年05月27日 5.72MB 下载

miranda

2008年05月09日 3.56MB 下载

没有更多推荐了,返回首页

关闭
关闭