/* Name: NewEventNotify - Plugin for Miranda IM File: popup.c - Displays a popup using the popup-plugin by hrk Version: 2.2.3 Description: Notifies you about some events Author: icebreaker, Date: 21.07.02 15:46 / Update: 16.09.02 17:45 Copyright: (C) 2002 Starzinger Michael 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; either version 2 of the License, or (at your option) any later version. 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "stdafx.h" extern int g_IsServiceAvail; static mir_cs csPopupList; static OBJLIST arPopupList(10, PtrKeySortT); PLUGIN_DATA* PU_GetByContact(MCONTACT hContact, UINT eventType) { for (auto &it : arPopupList) if (it->hContact == hContact && (eventType == -1 || it->eventType == (UINT)eventType)) return it; return nullptr; } static INT_PTR CALLBACK DoDefaultHandling(void *param) { auto *pdata = (PLUGIN_DATA *)param; if (pdata->hContact) { auto *pDlg = Srmm_FindDialog(pdata->hContact); if (pDlg && IsWindow(pDlg->GetHwnd())) CallService(MS_MSG_SENDMESSAGE, pdata->hContact, 0); else Clist_ContactDoubleClicked(pdata->hContact); } return 0; } static INT_PTR CALLBACK DropPopup(void *param) { auto *pdata = (PLUGIN_DATA *)param; if (pdata) { for (auto &it : pdata->events) { Clist_RemoveEvent(pdata->hContact, it); db_event_markRead(pdata->hContact, it); } } return 0; } int PopupAct(HWND hWnd, UINT mask, PLUGIN_DATA *pdata) { if (mask & MASK_OPEN) { if (pdata) { if (g_plugin.bMsgReplyWindow && pdata->eventType == EVENTTYPE_MESSAGE) CallFunctionSync(DoDefaultHandling, pdata); else { if (pdata->events.empty()) return 0; MEVENT hEvent = pdata->events[0]; for (int idx = 0;; idx++) { auto *cle = Clist_GetEvent(pdata->hContact, idx); if (cle == nullptr) { CallFunctionSync(DoDefaultHandling, pdata); break; } if (cle->hDbEvent == hEvent) { if (ServiceExists(cle->pszService)) CallServiceSync(cle->pszService, 0, (LPARAM)cle); // JK, use core (since 0.3.3+) break; } } } } } if (mask & MASK_REMOVE) CallFunctionSync(DropPopup, pdata); if (mask & MASK_DISMISS) { KillTimer(hWnd, TIMER_TO_ACTION); PUDeletePopup(hWnd); } return 0; } static LRESULT CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { auto *pdata = (PLUGIN_DATA*)PUGetPluginData(hWnd); if (pdata == nullptr) return FALSE; RECT rc; POINT pt; switch (message) { case WM_COMMAND: PopupAct(hWnd, g_plugin.maskActL, pdata); break; case WM_CONTEXTMENU: PopupAct(hWnd, g_plugin.maskActR, pdata); break; case UM_FREEPLUGINDATA: { mir_cslock lck(csPopupList); arPopupList.remove(pdata); } return TRUE; case UM_INITPOPUP: pdata->hWnd = hWnd; if (pdata->iSeconds > 0) SetTimer(hWnd, TIMER_TO_ACTION, pdata->iSeconds * 1000, nullptr); break; case WM_SETCURSOR: SetFocus(hWnd); break; case WM_TIMER: if (wParam != TIMER_TO_ACTION) break; GetCursorPos(&pt); GetWindowRect(hWnd, &rc); if (PtInRect(&rc, pt)) break; if (pdata->iSeconds > 0) KillTimer(hWnd, TIMER_TO_ACTION); PopupAct(hWnd, g_plugin.maskActTE, pdata); break; } return DefWindowProc(hWnd, message, wParam, lParam); } ///////////////////////////////////////////////////////////////////////////////////////// static wchar_t* ShortenPreview(DB::EventInfo &dbei) { bool fAddEllipsis = false; size_t iPreviewLimit = g_plugin.iLimitPreview; if (iPreviewLimit > 500 || iPreviewLimit == 0) iPreviewLimit = 500; wchar_t *buf = dbei.getText(); if (mir_wstrlen(buf) > iPreviewLimit) { fAddEllipsis = true; size_t iIndex = iPreviewLimit; size_t iWordThreshold = 20; while (iIndex && buf[iIndex] != ' ' && iWordThreshold--) buf[iIndex--] = 0; buf[iIndex] = 0; } if (fAddEllipsis) { buf = (wchar_t *)mir_realloc(buf, (mir_wstrlen(buf) + 5) * sizeof(wchar_t)); mir_wstrcat(buf, L"..."); } return buf; } static wchar_t* GetEventPreview(DB::EventInfo &dbei) { wchar_t *comment1 = nullptr; wchar_t *comment2 = nullptr; char *commentFix = nullptr; char *pBlob = (char *)dbei.pBlob; // now get text switch (dbei.eventType) { case EVENTTYPE_MESSAGE: if (pBlob) comment1 = ShortenPreview(dbei); commentFix = POPUP_COMMENT_MESSAGE; break; case EVENTTYPE_FILE: if (pBlob) { DB::FILE_BLOB blob(dbei); // filenames comment2 = mir_wstrdup(blob.getName()); // description comment1 = mir_wstrdup(blob.getDescr()); } commentFix = POPUP_COMMENT_FILE; break; case EVENTTYPE_CONTACTS: // blob format is: // ASCIIZ nick // ASCIIZ UID if (pBlob) { // count contacts in event char* pcBlob = pBlob; char* pcEnd = pBlob + dbei.cbBlob; int nContacts; wchar_t szBuf[512]; for (nContacts = 1; ; nContacts++) { // Nick pcBlob += mir_strlen(pcBlob) + 1; // UIN pcBlob += mir_strlen(pcBlob) + 1; // check for end of contacts if (pcBlob >= pcEnd) break; } mir_snwprintf(szBuf, TranslateT("Received %d contacts."), nContacts); comment1 = mir_wstrdup(szBuf); } commentFix = POPUP_COMMENT_CONTACTS; break; case EVENTTYPE_ADDED: // blob format is: // uint32_t numeric uin (ICQ only afaik) // uint32_t HANDLE to contact // ASCIIZ nick (or text UID) // ASCIIZ first name // ASCIIZ last name // ASCIIZ email (or YID) if (pBlob) { char szUin[16]; wchar_t szBuf[2048]; wchar_t* szNick = nullptr; char *pszNick = pBlob + 8; char *pszFirst = pszNick + mir_strlen(pszNick) + 1; char *pszLast = pszFirst + mir_strlen(pszFirst) + 1; char *pszEmail = pszLast + mir_strlen(pszLast) + 1; mir_snprintf(szUin, "%d", *((uint32_t*)pBlob)); if (mir_strlen(pszNick) > 0) { if (dbei.flags & DBEF_UTF) szNick = mir_utf8decodeW(pszNick); else szNick = mir_a2u(pszNick); } else if (mir_strlen(pszEmail) > 0) { if (dbei.flags & DBEF_UTF) szNick = mir_utf8decodeW(pszEmail); else szNick = mir_a2u(pszEmail); } else if (*((uint32_t*)pBlob) > 0) szNick = mir_a2u(szUin); if (szNick) { mir_wstrcpy(szBuf, szNick); mir_wstrcat(szBuf, TranslateT(" added you to the contact list")); mir_free(szNick); comment1 = mir_wstrdup(szBuf); } } commentFix = POPUP_COMMENT_ADDED; break; case EVENTTYPE_AUTHREQUEST: if (pBlob) { DB::AUTH_BLOB blob(dbei.pBlob); wchar_t *szNick = nullptr; if (blob.get_nick()) szNick = dbei.getString(blob.get_nick()); else if (blob.get_email()) szNick = dbei.getString(blob.get_email()); else if (blob.get_uin()) { char szUin[16]; szNick = mir_a2u(itoa(blob.get_uin(), szUin, 10)); } if (szNick) comment1 = CMStringW(FORMAT, L"%s%s", szNick, TranslateT(" requested authorization")).Detach(); } commentFix = POPUP_COMMENT_AUTH; break; default: // support for custom database event types DBEVENTTYPEDESCR *pei = DbEvent_GetType(dbei.szModule, dbei.eventType); if (pei && pBlob) { comment1 = dbei.getText(); commentFix = pei->descr; } else commentFix = POPUP_COMMENT_OTHER; } if (mir_wstrlen(comment1) > 0) { mir_free(comment2); return comment1; } if (mir_wstrlen(comment2) > 0) { mir_free(comment1); return comment2; } mir_free(comment1); mir_free(comment2); return TranslateW(mir_a2u(commentFix)); } int PopupShow(MCONTACT hContact, MEVENT hEvent, UINT eventType) { wchar_t *sampleEvent; long iSeconds; // check if we should report this kind of event // get the prefered icon as well // CHANGE: iSeconds is -1 because I use my timer to hide popup POPUPDATAW pudw = {}; pudw.iSeconds = -1; switch (eventType) { case EVENTTYPE_MESSAGE: if (!(g_plugin.maskNotify & MASK_MESSAGE)) return 1; pudw.lchIcon = Skin_LoadIcon(SKINICON_EVENT_MESSAGE); pudw.colorBack = g_plugin.msg.getBackColor(); pudw.colorText = g_plugin.msg.getTextColor(); iSeconds = g_plugin.msg.iDelay; sampleEvent = TranslateT("This is a sample message event :-)"); break; case EVENTTYPE_FILE: if (!(g_plugin.maskNotify & MASK_FILE)) return 1; pudw.lchIcon = Skin_LoadIcon(SKINICON_EVENT_FILE); pudw.colorBack = g_plugin.file.getBackColor(); pudw.colorText = g_plugin.file.getTextColor(); iSeconds = g_plugin.file.iDelay; sampleEvent = TranslateT("This is a sample file event :-D"); break; case EVENTTYPE_ERRMSG: if (!(g_plugin.maskNotify & MASK_ERROR)) return 1; pudw.lchIcon = Skin_LoadIcon(SKINICON_OTHER_DELETE); pudw.colorBack = g_plugin.err.getBackColor(); pudw.colorText = g_plugin.err.getTextColor(); iSeconds = g_plugin.err.iDelay; sampleEvent = TranslateT("This is a sample error event :-D"); break; default: if (!(g_plugin.maskNotify & MASK_OTHER)) return 1; pudw.lchIcon = Skin_LoadIcon(SKINICON_OTHER_MIRANDA); pudw.colorBack = g_plugin.other.getBackColor(); pudw.colorText = g_plugin.other.getTextColor(); iSeconds = g_plugin.other.iDelay; sampleEvent = TranslateT("This is a sample other event ;-D"); break; } // get DBEVENTINFO with pBlob if preview is needed (when is test then is off) DB::EventInfo dbe(hEvent, g_plugin.bPreview || eventType == EVENTTYPE_ADDED || eventType == EVENTTYPE_AUTHREQUEST); // retrieve correct hContact for AUTH events if (dbe.pBlob && (eventType == EVENTTYPE_ADDED || eventType == EVENTTYPE_AUTHREQUEST)) hContact = DbGetAuthEventContact(&dbe); // set plugin_data ... will be usable within PopupDlgProc PLUGIN_DATA *pdata = new PLUGIN_DATA(); pdata->eventType = eventType; pdata->hContact = hContact; pdata->events.push_back(hEvent); switch (iSeconds) { case -1: pdata->iSeconds = 65535; break; // kinda forrever case 0: pdata->iSeconds = g_plugin.iDelayDefault; break; default: pdata->iSeconds = iSeconds; break; } // finally create the popup pudw.lchContact = hContact; pudw.PluginWindowProc = PopupDlgProc; pudw.PluginData = pdata; // if hContact is NULL, && hEvent is NULL then popup is only Test if (hContact == 0 && hEvent == 0) { wcsncpy(pudw.lpwzContactName, TranslateT("Plugin Test"), MAX_CONTACTNAME); wcsncpy(pudw.lpwzText, TranslateW(sampleEvent), MAX_SECONDLINE); } else { // get the needed event data wcsncpy(pudw.lpwzContactName, Clist_GetContactDisplayName(hContact), MAX_CONTACTNAME); wcsncpy(pudw.lpwzText, ptrW(GetEventPreview(dbe)), MAX_SECONDLINE); } // send data to popup plugin if (PUAddPopupW(&pudw) < 0) delete pdata; mir_cslock lck(csPopupList); arPopupList.insert(pdata); return 0; } int PopupUpdate(PLUGIN_DATA &pdata, MEVENT hEvent) { if (hEvent) { pdata.events.push_back(hEvent); // re-init timer delay KillTimer(pdata.hWnd, TIMER_TO_ACTION); SetTimer(pdata.hWnd, TIMER_TO_ACTION, pdata.iSeconds * 1000, nullptr); } CMStringW wszText; if (g_plugin.bShowHeaders) wszText.AppendFormat(TranslateT("[b]Number of new message(s): %d[/b]\n"), pdata.events.size()); size_t iStart, iEnd, iGap = (g_plugin.iNumberMsg == 0) ? 1 : g_plugin.iNumberMsg; if (pdata.events.size() < iGap) { iStart = 0; iEnd = pdata.events.size(); } else if (g_plugin.bShowON) { // reverse sorting iStart = 0; iEnd = iGap; } else { iEnd = pdata.events.size(); iStart = iEnd - g_plugin.iNumberMsg; } // take the active event as starting one for (size_t i = iStart; i < iEnd; i++) { if (!wszText.IsEmpty()) wszText.AppendChar('\n'); // get DBEVENTINFO with pBlob if preview is needed (when is test then is off) DB::EventInfo dbe(pdata.events[i], g_plugin.bPreview); CMStringW wszFormat; if (g_plugin.bShowDate) wszFormat.Append(L"%Y.%m.%d "); if (g_plugin.bShowTime) wszFormat.Append(L"%H:%M"); if (!wszFormat.IsEmpty()) { wchar_t timestamp[MAX_DATASIZE]; time_t localTime = dbe.timestamp; wcsftime(timestamp, _countof(timestamp), wszFormat, localtime(&localTime)); wszText.AppendFormat(L"[b][i]%s[/i][/b]\n", timestamp); } // prepare event preview wszText.Append(ptrW(GetEventPreview(dbe))); wszText.AppendChar('\n'); } if (pdata.events.empty()) wszText.Append(L"\n..."); PUChangeTextW(pdata.hWnd, wszText); return 0; }