/* Miranda IM: the free IM client for Microsoft* Windows* Copyright 2000-12 Miranda IM, 2012-13 Miranda NG project, all portions of this codebase are copyrighted to the people listed in contributors.txt. 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 "..\..\core\commonheaders.h" #include "clc.h" struct CListEvent { int imlIconIndex; int flashesDone; CLISTEVENT cle; }; struct CListImlIcon { int index; HICON hIcon; }; static struct CListImlIcon *imlIcon; static int imlIconCount; extern HIMAGELIST hCListImages; static UINT_PTR flashTimerId; static int iconsOn; static int disableTrayFlash; static int disableIconFlash; int fnGetImlIconIndex(HICON hIcon) { int i; for (i=0; i < imlIconCount; i++) { if (imlIcon[i].hIcon == hIcon) return imlIcon[i].index; } imlIcon = (struct CListImlIcon *) mir_realloc(imlIcon, sizeof(struct CListImlIcon) * (imlIconCount + 1)); imlIconCount++; imlIcon[i].hIcon = hIcon; imlIcon[i].index = ImageList_AddIcon(hCListImages, hIcon); return imlIcon[i].index; } static char * GetEventProtocol(int idx) { if (cli.events.count && idx>=0 && idxcle.hContact == NULL) { if (cli.events.items[idx]->cle.flags&CLEF_PROTOCOLGLOBAL) szProto = cli.events.items[idx]->cle.lpszProtocol; else szProto = NULL; } else szProto = GetContactProto(cli.events.items[idx]->cle.hContact); return szProto; } return NULL; } static void ShowOneEventInTray(int idx) { cli.pfnTrayIconUpdateWithImageList((iconsOn || disableTrayFlash) ? cli.events.items[idx]->imlIconIndex : 0, cli.events.items[idx]->cle.ptszTooltip, GetEventProtocol(idx)); } static void ShowEventsInTray() { int i; char ** pTrayProtos; char nTrayProtoCnt; int nTrayCnt = cli.trayIconCount; if ( !cli.events.count || !nTrayCnt) return; if (cli.events.count == 1 || nTrayCnt == 1) { ShowOneEventInTray(0); //for only one icon in tray show topmost event return; } // in case if we have several icons in tray and several events with different protocols // lets use several icon to show events from protocols in different icons cli.pfnLockTray(); pTrayProtos = (char**)_alloca(sizeof(char*)*cli.trayIconCount); nTrayProtoCnt = 0; for (i=0; i=nTrayProtoCnt) j = 0; //event was not found so assume first icon if (pTrayProtos[j]) //if not already set ShowOneEventInTray(i); //show it pTrayProtos[j] = NULL; //and clear slot } } cli.pfnUnlockTray(); } static VOID CALLBACK IconFlashTimer(HWND, UINT, UINT_PTR idEvent, DWORD) { int i, j; ShowEventsInTray(); for (i=0; i < cli.events.count; i++) { for (j = 0; j < i; j++) if (cli.events.items[j]->cle.hContact == cli.events.items[i]->cle.hContact) break; if (j >= i) cli.pfnChangeContactIcon(cli.events.items[i]->cle.hContact, iconsOn || disableIconFlash ? cli.events.items[i]->imlIconIndex : 0, 0); //decrease eflashes in any case - no need to collect all events if (cli.events.items[i]->cle.flags & CLEF_ONLYAFEW) { if (0 >= --cli.events.items[i]->flashesDone) cli.pfnRemoveEvent(cli.events.items[i]->cle.hContact, cli.events.items[i]->cle.hDbEvent); } } if (cli.events.count == 0) { KillTimer(NULL, idEvent); cli.pfnTrayIconSetToBase(NULL); } iconsOn = !iconsOn; } struct CListEvent* fnAddEvent(CLISTEVENT *cle) { int i; struct CListEvent* p; if (cle == NULL || cle->cbSize != sizeof(CLISTEVENT)) return NULL; if (cle->flags & CLEF_URGENT) { for (i=0; i < cli.events.count; i++) if ( !(cli.events.items[i]->cle.flags & CLEF_URGENT)) break; } else i = cli.events.count; if ((p = (struct CListEvent*)cli.pfnCreateEvent()) == NULL) return NULL; List_Insert((SortedList*)&cli.events, p, i); p->cle = *cle; p->imlIconIndex = fnGetImlIconIndex(cli.events.items[i]->cle.hIcon); p->flashesDone = 12; p->cle.pszService = mir_strdup(cli.events.items[i]->cle.pszService); if (p->cle.flags & CLEF_UNICODE) p->cle.ptszTooltip = mir_tstrdup((TCHAR*)p->cle.ptszTooltip); else p->cle.ptszTooltip = mir_a2u((char*)p->cle.pszTooltip); //if no flag defined it handled as unicode if (cli.events.count == 1) { char *szProto; if (cle->hContact == NULL) { if (cle->flags&CLEF_PROTOCOLGLOBAL) szProto = cle->lpszProtocol; else szProto = NULL; } else szProto = GetContactProto(cle->hContact); iconsOn = 1; flashTimerId = SetTimer(NULL, 0, db_get_w(NULL, "CList", "IconFlashTime", 550), IconFlashTimer); cli.pfnTrayIconUpdateWithImageList(p->imlIconIndex, p->cle.ptszTooltip, szProto); } cli.pfnChangeContactIcon(cle->hContact, p->imlIconIndex, 1); cli.pfnSortContacts(); return p; } // Removes an event from the contact list's queue // Returns 0 if the event was successfully removed, or nonzero if the event was not found int fnRemoveEvent(HANDLE hContact, HANDLE dbEvent) { int i; char *szProto; int nSameProto = 0; // Find the event that should be removed for (i=0; i < cli.events.count; i++) if ((cli.events.items[i]->cle.hContact == hContact) && (cli.events.items[i]->cle.hDbEvent == dbEvent)) break; // Event was not found if (i == cli.events.count) return 1; // Update contact's icon szProto = GetContactProto(hContact); cli.pfnChangeContactIcon(cli.events.items[i]->cle.hContact, CallService(MS_CLIST_GETCONTACTICON, (WPARAM)cli.events.items[i]->cle.hContact, 1), 0); // Free any memory allocated to the event cli.pfnFreeEvent(cli.events.items[i]); List_Remove((SortedList*)&cli.events, i); { //count same protocoled events char * szEventProto; for (i=0; i < cli.events.count; i++) { if (cli.events.items[i]->cle.hContact) szEventProto = GetContactProto((cli.events.items[i]->cle.hContact)); else if (cli.events.items[i]->cle.flags&CLEF_PROTOCOLGLOBAL) szEventProto = (char *) cli.events.items[i]->cle.lpszProtocol; else szEventProto = NULL; if (szEventProto && szProto && !lstrcmpA(szEventProto, szProto)) nSameProto++; } } if (cli.events.count == 0 || nSameProto == 0) { if (cli.events.count == 0) KillTimer(NULL, flashTimerId); cli.pfnTrayIconSetToBase(hContact == NULL ? NULL : szProto); } else { if (cli.events.items[0]->cle.hContact == NULL) szProto = NULL; else szProto = GetContactProto(cli.events.items[0]->cle.hContact); cli.pfnTrayIconUpdateWithImageList(iconsOn ? cli.events.items[0]->imlIconIndex : 0, cli.events.items[0]->cle.ptszTooltip, szProto); } return 0; } CLISTEVENT* fnGetEvent(HANDLE hContact, int idx) { if (hContact == INVALID_HANDLE_VALUE) { if (idx >= cli.events.count) return NULL; return &cli.events.items[idx]->cle; } for (int i=0; i < cli.events.count; i++) if (cli.events.items[i]->cle.hContact == hContact) if (idx-- == 0) return &cli.events.items[i]->cle; return NULL; } int fnEventsProcessContactDoubleClick(HANDLE hContact) { for (int i=0; i < cli.events.count; i++) { if (cli.events.items[i]->cle.hContact == hContact) { HANDLE hDbEvent = cli.events.items[i]->cle.hDbEvent; CallService(cli.events.items[i]->cle.pszService, (WPARAM) (HWND) NULL, (LPARAM) & cli.events.items[i]->cle); cli.pfnRemoveEvent(hContact, hDbEvent); return 0; } } return 1; } int fnEventsProcessTrayDoubleClick(int index) { BOOL click_in_first_icon = FALSE; if (cli.events.count) { HANDLE hContact, hDbEvent; int eventIndex = 0; cli.pfnLockTray(); if (cli.trayIconCount>1 && index>0) { int i; char * szProto = NULL; for (i=0; icle.hContact) eventProto = GetContactProto(cli.events.items[i]->cle.hContact); if ( !eventProto) eventProto = cli.events.items[i]->cle.lpszProtocol; if ( !eventProto || !_strcmpi(eventProto, szProto)) { eventIndex = i; break; } } if (i == cli.events.count) { //EventNotFound //lets process backward try to find first event without desired proto in tray int j; if (click_in_first_icon) for (i=0; icle.hContact) eventProto = GetContactProto(cli.events.items[i]->cle.hContact); if ( !eventProto) eventProto = cli.events.items[i]->cle.lpszProtocol; if (eventProto) { for (j = 0; jcle.hContact; hDbEvent = cli.events.items[eventIndex]->cle.hDbEvent; //if ( !ServiceExists(cli.events.items[eventIndex]->cle.pszService)) // ; may be better to show send msg? CallService(cli.events.items[eventIndex]->cle.pszService, (WPARAM) NULL, (LPARAM) & cli.events.items[eventIndex]->cle); cli.pfnRemoveEvent(hContact, hDbEvent); return 0; } return 1; } static int RemoveEventsForContact(WPARAM wParam, LPARAM) { int j, hit; /* the for (;;) loop is used here since the cli.events.count can not be relied upon to take us thru the cli.events.items[] array without suffering from shortsightedness about how many unseen events remain, e.g. three events, we remove the first, we're left with 2, the event loop exits at 2 and we never see the real new 2. */ for (; cli.events.count > 0;) { for (hit = 0, j = 0; j < cli.events.count; j++) { if (cli.events.items[j]->cle.hContact == (HANDLE)wParam) { cli.pfnRemoveEvent((HANDLE)wParam, cli.events.items[j]->cle.hDbEvent); hit = 1; } } if (j == cli.events.count && hit == 0) return 0; /* got to the end of the array and didnt remove anything */ } return 0; } static int CListEventSettingsChanged(WPARAM wParam, LPARAM lParam) { HANDLE hContact = (HANDLE)wParam; DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam; if (hContact == NULL && cws && cws->szModule && cws->szSetting && strcmp(cws->szModule, "CList") == 0) { if (strcmp(cws->szSetting, "DisableTrayFlash") == 0) disableTrayFlash = (int)cws->value.bVal; else if (strcmp(cws->szSetting, "NoIconBlink") == 0) disableIconFlash = (int)cws->value.bVal; } return 0; } /***************************************************************************************/ INT_PTR AddEventSyncStub(WPARAM wParam, LPARAM lParam) { return CallServiceSync(MS_CLIST_ADDEVENT"_SYNC", wParam, lParam); } INT_PTR AddEventStub(WPARAM, LPARAM lParam) { return cli.pfnAddEvent((CLISTEVENT*)lParam) == NULL; } INT_PTR RemoveEventStub(WPARAM wParam, LPARAM lParam) { return cli.pfnRemoveEvent((HANDLE)wParam, (HANDLE)lParam); } INT_PTR GetEventStub(WPARAM wParam, LPARAM lParam) { return (INT_PTR)cli.pfnGetEvent((HANDLE)wParam, (int)lParam); } int InitCListEvents(void) { memset(&cli.events, 0, sizeof(cli.events)); cli.events.increment = 10; disableTrayFlash = db_get_b(NULL, "CList", "DisableTrayFlash", 0); disableIconFlash = db_get_b(NULL, "CList", "NoIconBlink", 0); CreateServiceFunction(MS_CLIST_ADDEVENT, AddEventSyncStub); //need to be called through sync to keep flash timer workable CreateServiceFunction(MS_CLIST_ADDEVENT"_SYNC", AddEventStub); CreateServiceFunction(MS_CLIST_REMOVEEVENT, RemoveEventStub); CreateServiceFunction(MS_CLIST_GETEVENT, GetEventStub); HookEvent(ME_DB_CONTACT_DELETED, RemoveEventsForContact); HookEvent(ME_DB_CONTACT_SETTINGCHANGED, CListEventSettingsChanged); return 0; } struct CListEvent* fnCreateEvent(void) { return (struct CListEvent*)mir_calloc(sizeof(struct CListEvent)); } void fnFreeEvent(struct CListEvent* p) { mir_free(p->cle.pszService); mir_free(p->cle.pszTooltip); mir_free(p); } void UninitCListEvents(void) { int i; if (cli.events.count) KillTimer(NULL, flashTimerId); for (i=0; i < cli.events.count; i++) cli.pfnFreeEvent((struct CListEvent*)cli.events.items[i]); List_Destroy((SortedList*)&cli.events); if (imlIcon != NULL) mir_free(imlIcon); }