/* Miranda SmileyAdd Plugin Copyright (C) 2005 - 2012 Boris Krasnovskiy All Rights Reserved Copyright (C) 2003 - 2004 Rein-Peter de Boer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "general.h" //***************************************************// // DISCLAIMER!!! // we are not supposed to use this object, so be aware typedef struct NewMessageWindowLParam { MCONTACT hContact; int isSend; const char *szInitialText; } msgData; // this is an undocumented object!!!!!!! // subject to change in miranda versions...!!!!!! // DISCLAIMER!!! //***************************************************// static HHOOK g_hMessageHookPre = NULL; static HANDLE g_hMutex = NULL; static HANDLE g_hHookMsgWnd = NULL; static LRESULT CALLBACK MessageDlgSubclass(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); //type definitions class MsgWndData { public: HWND hwnd; char ProtocolName[52]; HWND REdit; HWND QuoteB; HWND MEdit; HWND MOK; HWND LButton; mutable HWND hSmlButton; mutable HBITMAP hSmlBmp; mutable HICON hSmlIco; int idxLastChar; MCONTACT hContact; bool doSmileyReplace; bool doSmileyButton; bool OldButtonPlace; bool isSplit; bool isSend; MsgWndData() { ProtocolName[0] = 0; REdit = NULL; QuoteB = NULL; MEdit = NULL; MOK = NULL; LButton = NULL; hSmlButton = NULL; hSmlBmp = NULL; hSmlIco = NULL; idxLastChar = 0; hContact = NULL; doSmileyReplace = false; doSmileyButton = false; OldButtonPlace = false; isSplit = false; isSend = false; } MsgWndData(const MsgWndData &dsb) { *this = dsb; dsb.hSmlBmp = NULL; dsb.hSmlIco = NULL; dsb.hSmlButton = NULL; } ~MsgWndData() { clear(); } void clear(void) { if (hSmlBmp != NULL) DeleteObject(hSmlBmp); if (hSmlIco != NULL) DestroyIcon(hSmlIco); if (hSmlButton != NULL) DestroyWindow(hSmlButton); hSmlBmp = NULL; hSmlIco = NULL; hSmlButton = NULL; } RECT CalcSmileyButtonPos(void) { RECT rect; POINT pt; if (OldButtonPlace) { if (isSplit && db_get_b(NULL, "SRMsg", "ShowQuote", FALSE)) { GetWindowRect(QuoteB, &rect); pt.x = rect.right + 12; } else { GetWindowRect(MEdit, &rect); pt.x = rect.left; } GetWindowRect(MOK, &rect); pt.y = rect.top; } else { GetWindowRect(LButton, &rect); pt.y = rect.top; if ((GetWindowLongPtr(LButton, GWL_STYLE) & WS_VISIBLE) != 0) pt.x = rect.left - 28; else pt.x = rect.left; } ScreenToClient(GetParent(LButton), &pt); rect.bottom += pt.y - rect.top; rect.right += pt.x - rect.left; rect.top = pt.y; rect.left = pt.x; return rect; } //helper function //identifies the message dialog bool IsMessageSendDialog(HWND hwnd) { TCHAR szClassName[32] = _T(""); GetClassName(hwnd, szClassName, SIZEOF(szClassName)); if (_tcscmp(szClassName, _T("#32770"))) return false; if ((REdit = GetDlgItem(hwnd, MI_IDC_LOG)) != NULL) { GetClassName(REdit, szClassName, SIZEOF(szClassName)); if (_tcscmp(szClassName, _T("RichEdit20A")) != 0 && _tcscmp(szClassName, _T("RichEdit20W")) != 0 && _tcscmp(szClassName, _T("RICHEDIT50W")) != 0) return false; } else return false; if ((MEdit = GetDlgItem(hwnd, MI_IDC_MESSAGE)) != NULL) { GetClassName(MEdit, szClassName, SIZEOF(szClassName)); if (_tcscmp(szClassName, _T("Edit")) != 0 && _tcscmp(szClassName, _T("RichEdit20A")) != 0 && _tcscmp(szClassName, _T("RichEdit20W")) != 0 && _tcscmp(szClassName, _T("RICHEDIT50W")) != 0) return false; } else return false; QuoteB = GetDlgItem(hwnd, MI_IDC_QUOTE); if ((LButton = GetDlgItem(hwnd, MI_IDC_ADD)) == NULL) return false; if (GetDlgItem(hwnd, MI_IDC_NAME) == NULL) return false; if ((MOK = GetDlgItem(hwnd, IDOK)) == NULL) return false; return true; } void CreateSmileyButton(void) { doSmileyButton = opt.ButtonStatus != 0; OldButtonPlace = opt.ButtonStatus == 2; SmileyPackType* SmileyPack = GetSmileyPack(ProtocolName, hContact); doSmileyButton &= SmileyPack != NULL && SmileyPack->VisibleSmileyCount() != 0; bool showButtonLine; if (IsOldSrmm()) { isSplit = db_get_b(NULL,"SRMsg","Split", TRUE) != 0; doSmileyReplace = (isSplit || !isSend); doSmileyButton &= isSplit || isSend; showButtonLine = db_get_b(NULL, "SRMsg", "ShowButtonLine", TRUE) != 0; } else { doSmileyReplace = true; OldButtonPlace = false; showButtonLine = db_get_b(NULL, "SRMM", "ShowButtonLine", TRUE) != 0; } doSmileyButton &= OldButtonPlace || showButtonLine; if (ProtocolName[0] != 0) { INT_PTR cap = CallProtoService(ProtocolName, PS_GETCAPS, PFLAGNUM_1, 0); doSmileyButton &= ((cap & (PF1_IMSEND | PF1_CHAT)) != 0); doSmileyReplace &= ((cap & (PF1_IMRECV | PF1_CHAT)) != 0); } if (doSmileyButton && opt.PluginSupportEnabled) { //create smiley button RECT rect = CalcSmileyButtonPos(); hSmlButton = CreateWindowEx( WS_EX_LEFT | WS_EX_NOPARENTNOTIFY | WS_EX_TOPMOST, MIRANDABUTTONCLASS, _T("S"), WS_CHILD|WS_VISIBLE|WS_TABSTOP, // window style rect.left, // horizontal position of window rect.top, // vertical position of window rect.bottom - rect.top + 1, // window width rect.bottom - rect.top + 1, // window height GetParent(LButton), // handle to parent or owner window (HMENU) IDC_SMLBUTTON, // menu handle or child identifier NULL, // handle to application instance NULL); // window-creation data // Conversion to bitmap done to prevent Miranda from scaling the image SmileyType* sml = FindButtonSmiley(SmileyPack); if (sml != NULL) { hSmlBmp = sml->GetBitmap(GetSysColor(COLOR_BTNFACE), 0, 0); SendMessage(hSmlButton, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hSmlBmp); } else { hSmlIco = GetDefaultIcon(); SendMessage(hSmlButton, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hSmlIco); } SendMessage(hSmlButton, BUTTONADDTOOLTIP, (WPARAM)LPGEN("Show smiley selection window"), 0); SendMessage(hSmlButton, BUTTONSETASFLATBTN, TRUE, 0); } } }; static int CompareMsgWndData(const MsgWndData* p1, const MsgWndData* p2) { return (int)((INT_PTR)p1->hwnd - (INT_PTR)p2->hwnd); } static LIST g_MsgWndList(10, CompareMsgWndData); bool IsOldSrmm(void) { return ServiceExists(MS_MSG_GETWINDOWCLASS) == 0; } int UpdateSrmmDlg(WPARAM wParam, LPARAM /* lParam */) { WaitForSingleObject(g_hMutex, 2000); for (int i=0; i < g_MsgWndList.getCount(); ++i) { if (wParam == 0 || g_MsgWndList[i]->hContact == (MCONTACT)wParam) { SendMessage(g_MsgWndList[i]->hwnd, WM_SETREDRAW, FALSE, 0); SendMessage(g_MsgWndList[i]->hwnd, DM_OPTIONSAPPLIED, 0, 0); SendMessage(g_MsgWndList[i]->hwnd, WM_SETREDRAW, TRUE, 0); } } ReleaseMutex(g_hMutex); return 0; } //find the dialog info in the stored list static MsgWndData* IsMsgWnd(HWND hwnd) { WaitForSingleObject(g_hMutex, 2000); MsgWndData* res = g_MsgWndList.find((MsgWndData*)&hwnd); ReleaseMutex(g_hMutex); return res; } static void MsgWndDetect(HWND hwndDlg, MCONTACT hContact, msgData* datm) { MsgWndData dat; if (dat.IsMessageSendDialog(hwndDlg)) { dat.hwnd = hwndDlg; if (datm != NULL) { dat.isSend = datm->isSend != 0; dat.hContact = datm->hContact; } else dat.hContact = hContact; // Get the protocol for this contact to display correct smileys. char *protonam = GetContactProto(DecodeMetaContact(dat.hContact)); if (protonam) { strncpy(dat.ProtocolName, protonam, sizeof(dat.ProtocolName)); dat.ProtocolName[sizeof(dat.ProtocolName)-1] = 0; } WaitForSingleObject(g_hMutex, 2000); MsgWndData* msgwnd = g_MsgWndList.find((MsgWndData*)&hwndDlg); if (msgwnd == NULL) { msgwnd = new MsgWndData(dat); g_MsgWndList.insert(msgwnd); } else msgwnd = NULL; ReleaseMutex(g_hMutex); if (msgwnd != NULL) { mir_subclassWindow(hwndDlg, MessageDlgSubclass); msgwnd->CreateSmileyButton(); if (hContact == NULL) SetRichCallback(msgwnd->REdit, msgwnd->hContact, true, true); } } } //global subclass function for all dialogs static LRESULT CALLBACK MessageDlgSubclass(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { MsgWndData* dat = IsMsgWnd(hwnd); if (dat == NULL) return 0; switch(uMsg) { case DM_OPTIONSAPPLIED: dat->clear(); dat->CreateSmileyButton(); break; case DM_APPENDTOLOG: if (opt.PluginSupportEnabled) { //get length of text now before things can get added... GETTEXTLENGTHEX gtl; gtl.codepage = 1200; gtl.flags = GTL_PRECISE | GTL_NUMCHARS; dat->idxLastChar = (int)SendMessage(dat->REdit, EM_GETTEXTLENGTHEX, (WPARAM) >l, 0); } break; } LRESULT result = mir_callNextSubclass(hwnd, MessageDlgSubclass, uMsg, wParam, lParam); if (!opt.PluginSupportEnabled) return result; switch(uMsg) { case WM_DESTROY: WaitForSingleObject(g_hMutex, 2000); { int ind = g_MsgWndList.getIndex((MsgWndData*)&hwnd); if ( ind != -1 ) { delete g_MsgWndList[ind]; g_MsgWndList.remove(ind); } } ReleaseMutex(g_hMutex); break; case WM_SIZE: if (dat->doSmileyButton) { RECT rect = dat->CalcSmileyButtonPos(); SetWindowPos(dat->hSmlButton, NULL, rect.left, rect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); } break; case DM_APPENDTOLOG: if (dat->doSmileyReplace) { SmileyPackCType* smcp; SmileyPackType* SmileyPack = GetSmileyPack(dat->ProtocolName, dat->hContact, &smcp); if (SmileyPack != NULL) { const CHARRANGE sel = { dat->idxLastChar, LONG_MAX }; ReplaceSmileys(dat->REdit, SmileyPack, smcp, sel, false, false, false); } } break; case DM_REMAKELOG: if (dat->doSmileyReplace) { SmileyPackCType* smcp; SmileyPackType* SmileyPack = GetSmileyPack(dat->ProtocolName, dat->hContact, &smcp); if (SmileyPack != NULL) { static const CHARRANGE sel = { 0, LONG_MAX }; ReplaceSmileys(dat->REdit, SmileyPack, smcp, sel, false, false, false); } } break; case WM_COMMAND: if (LOWORD(wParam) == IDC_SMLBUTTON && HIWORD(wParam) == BN_CLICKED) { SmileyToolWindowParam *stwp = new SmileyToolWindowParam; stwp->pSmileyPack = GetSmileyPack(dat->ProtocolName, dat->hContact); stwp->hWndParent = hwnd; stwp->hWndTarget = dat->MEdit; stwp->targetMessage = EM_REPLACESEL; stwp->targetWParam = TRUE; RECT rect; GetWindowRect(dat->hSmlButton, &rect); if (dat->OldButtonPlace) { stwp->direction = 3; stwp->xPosition = rect.left; stwp->yPosition = rect.top + 4; } else { stwp->direction = 0; stwp->xPosition = rect.left; stwp->yPosition = rect.top + 24; } mir_forkthread(SmileyToolThread, stwp); } if (LOWORD(wParam) == MI_IDC_ADD && HIWORD(wParam) == BN_CLICKED && dat->doSmileyButton) { RECT rect = dat->CalcSmileyButtonPos(); SetWindowPos(dat->hSmlButton, NULL, rect.left, rect.top, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE); } break; } return result; } static int MsgDlgHook(WPARAM, LPARAM lParam) { const MessageWindowEventData *wndEvtData = (MessageWindowEventData*)lParam; switch(wndEvtData->uType) { case MSG_WINDOW_EVT_OPENING: MsgWndDetect(wndEvtData->hwndWindow, wndEvtData->hContact, NULL); if (wndEvtData->cbSize >= sizeof(MessageWindowEventData)) { SetRichOwnerCallback(wndEvtData->hwndWindow, wndEvtData->hwndInput, wndEvtData->hwndLog); if (wndEvtData->hwndLog) SetRichCallback(wndEvtData->hwndLog, wndEvtData->hContact, false, false); if (wndEvtData->hwndInput) SetRichCallback(wndEvtData->hwndInput, wndEvtData->hContact, false, false); } break; case MSG_WINDOW_EVT_OPEN: if (wndEvtData->cbSize >= sizeof(MessageWindowEventData)) { SetRichOwnerCallback(wndEvtData->hwndWindow, wndEvtData->hwndInput, wndEvtData->hwndLog); if (wndEvtData->hwndLog) SetRichCallback(wndEvtData->hwndLog, wndEvtData->hContact, true, true); if (wndEvtData->hwndInput) { SetRichCallback(wndEvtData->hwndInput, wndEvtData->hContact, true, true); SendMessage(wndEvtData->hwndInput, WM_REMAKERICH, 0, 0); } } break; case MSG_WINDOW_EVT_CLOSE: if (wndEvtData->cbSize >= sizeof(MessageWindowEventData) && wndEvtData->hwndLog) { CloseRichCallback(wndEvtData->hwndLog); CloseRichOwnerCallback(wndEvtData->hwndWindow); } break; } return 0; } //global subclass function for all dialogs static LRESULT CALLBACK MsgDlgHookProcPre(int code, WPARAM wParam, LPARAM lParam) { const CWPSTRUCT *msg = (CWPSTRUCT*)lParam; if (code == HC_ACTION && msg->message == WM_INITDIALOG) MsgWndDetect(msg->hwnd, NULL, (msgData*)msg->lParam); return CallNextHookEx(g_hMessageHookPre, code, wParam, lParam); } void InstallDialogBoxHook(void) { g_hMutex = CreateMutex(NULL, FALSE, NULL); g_hHookMsgWnd = HookEvent(ME_MSG_WINDOWEVENT, MsgDlgHook); // Hook message API if (g_hHookMsgWnd == NULL) g_hMessageHookPre = SetWindowsHookEx(WH_CALLWNDPROC, MsgDlgHookProcPre, NULL, GetCurrentThreadId()); } void RemoveDialogBoxHook(void) { if (g_hHookMsgWnd) UnhookEvent(g_hHookMsgWnd); if (g_hMessageHookPre) UnhookWindowsHookEx(g_hMessageHookPre); WaitForSingleObject(g_hMutex, 2000); for (int i=0; i