/* New Away System - plugin for Miranda IM Copyright (c) 2005-2007 Chervov Dmitry 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 */ #pragma once #include "Common.h" #include "MsgTree.h" #include "ContactList.h" #include "statusmodes.h" #define DB_STATUSMSG "StatusMsg" #define DB_ENABLEREPLY "EnableReply" #define DB_IGNOREREQUESTS "IgnoreRequests" //#define DB_POPUPNOTIFY "UsePopups" #define DB_UNK_CONTACT_PREFIX "Unk" // DB_ENABLEREPLY, DB_IGNOREREQUESTS and DB_POPUPNOTIFY settings prefix for not-on-list contacts class _CWndUserData { public: _CWndUserData(): MsgTree(NULL), CList(NULL) {} CMsgTree *MsgTree; CCList *CList; }; class CWndUserData { public: CWndUserData(HWND hWnd): hWnd(hWnd) { _ASSERT(hWnd); dat = (_CWndUserData*)GetWindowLongPtr(hWnd, GWLP_USERDATA); if (!dat) { dat = new _CWndUserData; SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)dat); } } ~CWndUserData() { _ASSERT(dat == (_CWndUserData*)GetWindowLongPtr(hWnd, GWLP_USERDATA)); if (!dat->MsgTree && !dat->CList) // TODO: Uninitialized Memory Read on closing the options dialog - fix it { SetWindowLongPtr(hWnd, GWLP_USERDATA, NULL); delete dat; // TODO: memory leak - this is never executed - fix it } } CMsgTree *GetMsgTree() {return dat->MsgTree;} void SetMsgTree(CMsgTree *MsgTree) {dat->MsgTree = MsgTree;} CCList *GetCList() {return dat->CList;} void SetCList(CCList *CList) {dat->CList = CList;} private: HWND hWnd; _CWndUserData *dat; }; #define IL_SKINICON 0x80000000 #define IL_PROTOICON 0x40000000 #define ILI_NOICON (-1) #define ILI_EVENT_MESSAGE 0 #define ILI_EVENT_URL 1 #define ILI_EVENT_FILE 2 #define ILI_PROTO_ONL 3 #define ILI_PROTO_AWAY 4 #define ILI_PROTO_NA 5 #define ILI_PROTO_OCC 6 #define ILI_PROTO_DND 7 #define ILI_PROTO_FFC 8 #define ILI_PROTO_INV 9 #define ILI_PROTO_OTP 10 #define ILI_PROTO_OTL 11 #define ILI_DOT 12 #define ILI_MSGICON 13 #define ILI_IGNORE 14 #define ILI_SOE_DISABLED 15 #define ILI_SOE_ENABLED 16 #define ILI_NEWMESSAGE 17 #define ILI_NEWCATEGORY 18 #define ILI_SAVE 19 #define ILI_SAVEASNEW 20 #define ILI_DELETE 21 #define ILI_SETTINGS 22 #define ILI_STATUS_OTHER 23 static int Icons[] = { SKINICON_EVENT_MESSAGE | IL_SKINICON, SKINICON_EVENT_URL | IL_SKINICON, SKINICON_EVENT_FILE | IL_SKINICON, ID_STATUS_ONLINE | IL_PROTOICON, ID_STATUS_AWAY | IL_PROTOICON, ID_STATUS_NA | IL_PROTOICON, ID_STATUS_OCCUPIED | IL_PROTOICON, ID_STATUS_DND | IL_PROTOICON, ID_STATUS_FREECHAT | IL_PROTOICON, ID_STATUS_INVISIBLE | IL_PROTOICON, ID_STATUS_ONTHEPHONE | IL_PROTOICON, ID_STATUS_OUTTOLUNCH | IL_PROTOICON, IDI_DOT, IDI_MSGICON, IDI_IGNORE, IDI_SOE_ENABLED, IDI_SOE_DISABLED, IDI_NEWMESSAGE, IDI_NEWCATEGORY, IDI_SAVE, IDI_SAVEASNEW, IDI_DELETE, IDI_SETTINGS, IDI_STATUS_OTHER }; class CIconList { public: ~CIconList() { int I; for (I = 0; I < IconList.GetSize(); I++) { if (IconList[I]) { DestroyIcon(IconList[I]); } } } HICON& operator [] (int nIndex) {return IconList[nIndex];} void ReloadIcons() { int cxIcon = GetSystemMetrics(SM_CXSMICON); int cyIcon = GetSystemMetrics(SM_CYSMICON); int I; for (I = 0; I < lengthof(Icons); I++) { if (IconList.GetSize() > I && IconList[I]) { DestroyIcon(IconList[I]); } if (Icons[I] & IL_SKINICON) { IconList.SetAtGrow(I) = (HICON)CopyImage(LoadSkinnedIcon(Icons[I] & ~IL_SKINICON), IMAGE_ICON, cxIcon, cyIcon, LR_COPYFROMRESOURCE); } else if (Icons[I] & IL_PROTOICON) { IconList.SetAtGrow(I) = (HICON)CopyImage(LoadSkinnedProtoIcon(NULL, Icons[I] & ~IL_PROTOICON), IMAGE_ICON, cxIcon, cyIcon, LR_COPYFROMRESOURCE); } else { IconList.SetAtGrow(I) = (HICON)LoadImage(g_hInstance, MAKEINTRESOURCE(Icons[I]), IMAGE_ICON, cxIcon, cyIcon, 0); } } } private: TMyArray IconList; }; extern CIconList g_IconList; class CProtoStates; class CProtoState { public: CProtoState(const char* szProto, CProtoStates* Parent): szProto(szProto), Parent(Parent), Status(szProto, Parent), AwaySince(szProto, Parent) {} class CStatus { public: CStatus(const char* szProto, CProtoStates* GrandParent): szProto(szProto), GrandParent(GrandParent), Status(ID_STATUS_OFFLINE) {} CStatus& operator = (int Status); operator int() {return Status;} friend class CProtoState; private: int Status; CString szProto; CProtoStates* GrandParent; } Status; class CAwaySince { public: CAwaySince(const char* szProto, CProtoStates* GrandParent): szProto(szProto), GrandParent(GrandParent) {Reset();} void Reset(); operator LPSYSTEMTIME() {return &AwaySince;} friend class CProtoState; private: SYSTEMTIME AwaySince; CString szProto; CProtoStates* GrandParent; } AwaySince; class CCurStatusMsg { public: CCurStatusMsg() {*this = NULL;} CCurStatusMsg& operator = (TCString Msg) { CurStatusMsg = Msg; SYSTEMTIME st; GetLocalTime(&st); SystemTimeToFileTime(&st, (LPFILETIME)&LastUpdateTime); // I'm too lazy to declare FILETIME structure and then copy its data to absolutely equivalent ULARGE_INTEGER structure, so we'll just pass a pointer to the ULARGE_INTEGER directly ;-P return *this; } operator TCString() {return CurStatusMsg;} DWORD GetUpdateTimeDifference() { ULARGE_INTEGER CurTime; SYSTEMTIME st; GetLocalTime(&st); SystemTimeToFileTime(&st, (LPFILETIME)&CurTime); return (DWORD)((CurTime.QuadPart - LastUpdateTime.QuadPart) / 10000); // in milliseconds } private: TCString CurStatusMsg; ULARGE_INTEGER LastUpdateTime; } CurStatusMsg; class CTempMsg { // we use temporary messages to keep user-defined per-protocol messages intact, when changing messages through MS_NAS_SETSTATE, or when autoaway becomes active etc.. temporary messages are automatically resetted when protocol status changes public: CTempMsg(): iIsSet(0) {} CTempMsg& operator = (TCString Msg) {this->Msg = Msg; iIsSet = true; return *this;} operator TCString() { _ASSERT(iIsSet); return Msg; } void Unset() {iIsSet = false;} int IsSet() {return iIsSet;} private: int iIsSet; // as we need TempMsg to support every possible value, including NULL and "", we'll use this variable to specify whether TempMsg is set TCString Msg; } TempMsg; void SetParent(CProtoStates* Parent) { this->Parent = Parent; Status.GrandParent = Parent; AwaySince.GrandParent = Parent; } CString &GetProto() {return szProto;} //NightFox: fix? //private: public: CString szProto; CProtoStates* Parent; }; class CProtoStates // this class stores all protocols' dynamic data { public: CProtoStates() {} CProtoStates(const CProtoStates &States) {*this = States;} CProtoStates& operator = (const CProtoStates& States) { ProtoStates = States.ProtoStates; int I; for (I = 0; I < ProtoStates.GetSize(); I++) { ProtoStates[I].SetParent(this); } return *this; } CProtoState& operator[](const char *szProto) { int I; for (I = 0; I < ProtoStates.GetSize(); I++) { if (ProtoStates[I].GetProto() == szProto) { return ProtoStates[I]; } } if (!szProto) // we need to be sure that we have _all_ protocols in the list, before dealing with global status, so we're adding them here. { int ProtoCount; PROTOCOLDESCRIPTOR **proto; CallService(MS_PROTO_ENUMPROTOCOLS, (WPARAM)&ProtoCount, (LPARAM)&proto); int I; for (I = 0; I < ProtoCount; I++) { if (proto[I]->type == PROTOTYPE_PROTOCOL) { (*this)[proto[I]->szName]; // add a protocol if it isn't in the list yet } } } return ProtoStates[ProtoStates.AddElem(CProtoState(szProto, this))]; } friend class CProtoState; friend class CProtoState::CStatus; friend class CProtoState::CAwaySince; private: CProtoState& operator[](int nIndex) {return ProtoStates[nIndex];} int GetSize() {return ProtoStates.GetSize();} TMyArray ProtoStates; }; extern CProtoStates g_ProtoStates; static struct { int Status; char *Setting; } StatusSettings[] = { ID_STATUS_OFFLINE, "Off", ID_STATUS_ONLINE, "Onl", ID_STATUS_AWAY, "Away", ID_STATUS_NA, "Na", ID_STATUS_DND, "Dnd", ID_STATUS_OCCUPIED, "Occ", ID_STATUS_FREECHAT, "Ffc", ID_STATUS_INVISIBLE, "Inv", ID_STATUS_ONTHEPHONE, "Otp", ID_STATUS_OUTTOLUNCH, "Otl", ID_STATUS_IDLE, "Idle" }; class CProtoSettings { public: CProtoSettings(const char *szProto = NULL, int iStatus = 0): szProto(szProto), Status(iStatus, szProto) { Autoreply.Parent = this; } CString ProtoStatusToDBSetting(const char *Prefix, int MoreOpt_PerStatusID = 0) { if (!MoreOpt_PerStatusID || g_MoreOptPage.GetDBValueCopy(MoreOpt_PerStatusID)) { int I; for (I = 0; I < lengthof(StatusSettings); I++) { if (Status == StatusSettings[I].Status) { return szProto ? (CString(Prefix) + "_" + szProto + "_" + StatusSettings[I].Setting) : (CString(Prefix) + StatusSettings[I].Setting); } } } return szProto ? (CString(Prefix) + "_" + szProto) : CString(Prefix); } class CAutoreply { public: CAutoreply& operator = (const int Value) { CString Setting(Parent->szProto ? Parent->ProtoStatusToDBSetting(DB_ENABLEREPLY, IDC_MOREOPTDLG_PERSTATUSPROTOSETTINGS) : DB_ENABLEREPLY); if (DBGetContactSettingByte(NULL, MOD_NAME, Setting, VAL_USEDEFAULT) == Value) { return *this; // prevent deadlocks when calling UpdateSOEButtons } if (Value != VAL_USEDEFAULT) { DBWriteContactSettingByte(NULL, MOD_NAME, Setting, Value != 0); } else { DBDeleteContactSetting(NULL, MOD_NAME, Setting); } /*if (!Parent->szProto) { UpdateSOEButtons(); }*/ return *this; } operator int() {return DBGetContactSettingByte(NULL, MOD_NAME, Parent->szProto ? Parent->ProtoStatusToDBSetting(DB_ENABLEREPLY, IDC_MOREOPTDLG_PERSTATUSPROTOSETTINGS) : DB_ENABLEREPLY, Parent->szProto ? VAL_USEDEFAULT : AUTOREPLY_DEF_REPLY);} int IncludingParents() // takes into account global data also, if per-protocol setting is not defined { _ASSERT(Parent->szProto); int Value = *this; return (Value == VAL_USEDEFAULT) ? CProtoSettings(NULL).Autoreply : Value; } friend class CProtoSettings; private: CProtoSettings *Parent; } Autoreply; class CStatus { public: CStatus(int iStatus = 0, const char *szProto = NULL): Status(iStatus), szProto(szProto) {} CStatus& operator = (int Status) {this->Status = Status; return *this;} operator int() { if (!Status) { Status = g_ProtoStates[szProto].Status; } return Status; } private: int Status; const char *szProto; } Status; void SetMsgFormat(int Flags, TCString Message); TCString GetMsgFormat(int Flags, int *pOrder = NULL); //NightFox: fix? //private: public: const char *szProto; }; __inline CString StatusToDBSetting(int Status, const char *Prefix, int MoreOpt_PerStatusID = 0) { if (!MoreOpt_PerStatusID || g_MoreOptPage.GetDBValueCopy(MoreOpt_PerStatusID)) { int I; for (I = 0; I < lengthof(StatusSettings); I++) { if (Status == StatusSettings[I].Status) { return CString(Prefix) + StatusSettings[I].Setting; } } } return CString(Prefix); } __inline CString ContactStatusToDBSetting(int Status, const char *Prefix, int MoreOpt_PerStatusID, HANDLE hContact) { if (hContact == INVALID_HANDLE_VALUE) { // it's a not-on-list contact return CString(DB_UNK_CONTACT_PREFIX) + Prefix; } if (hContact) { StatusToDBSetting(Status, Prefix, MoreOpt_PerStatusID); } return CString(Prefix); } class CContactSettings { public: CContactSettings(int iStatus = 0, HANDLE hContact = NULL): Status(iStatus, hContact), hContact(hContact) { Ignore.Parent = this; Autoreply.Parent = this; // PopupNotify.Parent = this; } CString ContactStatusToDBSetting(const char *Prefix, int MoreOpt_PerStatusID = 0) { return ::ContactStatusToDBSetting((hContact != INVALID_HANDLE_VALUE) ? Status : NULL, Prefix, MoreOpt_PerStatusID, hContact); } class CIgnore { public: CIgnore& operator = (const int Value) { CString Setting(Parent->ContactStatusToDBSetting(DB_IGNOREREQUESTS, IDC_MOREOPTDLG_PERSTATUSPERSONALSETTINGS)); HANDLE hContact = (Parent->hContact != INVALID_HANDLE_VALUE) ? Parent->hContact : NULL; if (Value) { DBWriteContactSettingByte(hContact, MOD_NAME, Setting, 1); } else { DBDeleteContactSetting(hContact, MOD_NAME, Setting); } return *this; } operator int() {return DBGetContactSettingByte((Parent->hContact != INVALID_HANDLE_VALUE) ? Parent->hContact : NULL, MOD_NAME, Parent->ContactStatusToDBSetting(DB_IGNOREREQUESTS, IDC_MOREOPTDLG_PERSTATUSPERSONALSETTINGS), 0);} friend class CContactSettings; private: CContactSettings *Parent; } Ignore; class CAutoreply { public: CAutoreply& operator = (const int Value) { CString Setting(Parent->ContactStatusToDBSetting(DB_ENABLEREPLY, IDC_MOREOPTDLG_PERSTATUSPERSONALSETTINGS)); HANDLE hContact = (Parent->hContact != INVALID_HANDLE_VALUE) ? Parent->hContact : NULL; if (DBGetContactSettingByte(hContact, MOD_NAME, Setting, VAL_USEDEFAULT) == Value) { return *this; // prevent deadlocks when calling UpdateSOEButtons } if (Value != VAL_USEDEFAULT) { DBWriteContactSettingByte(hContact, MOD_NAME, Setting, Value != 0); } else { DBDeleteContactSetting(hContact, MOD_NAME, Setting); } /*if (Parent->hContact != INVALID_HANDLE_VALUE) { UpdateSOEButtons(Parent->hContact); }*/ return *this; } operator int() {return DBGetContactSettingByte((Parent->hContact != INVALID_HANDLE_VALUE) ? Parent->hContact : NULL, MOD_NAME, Parent->ContactStatusToDBSetting(DB_ENABLEREPLY, IDC_MOREOPTDLG_PERSTATUSPERSONALSETTINGS), Parent->hContact ? VAL_USEDEFAULT : AUTOREPLY_DEF_REPLY);} int IncludingParents(const char *szProtoOverride = NULL) // takes into account protocol and global data also, if per-contact setting is not defined { _ASSERT((Parent->hContact && Parent->hContact != INVALID_HANDLE_VALUE) || szProtoOverride); // we need either correct protocol or a correct hContact to determine its protocol int Value = *this; if (Value == VAL_USEDEFAULT) { const char *szProto = (Parent->hContact && Parent->hContact != INVALID_HANDLE_VALUE) ? (const char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)Parent->hContact, 0) : szProtoOverride; return CProtoSettings(szProto).Autoreply.IncludingParents(); } return Value; } int GetNextToggleValue() { switch ((int)*this) { case VAL_USEDEFAULT: return 0; break; case 0: return 1; break; default: return Parent->hContact ? VAL_USEDEFAULT : AUTOREPLY_DEF_REPLY; break; } } int Toggle() {return *this = GetNextToggleValue();} friend class CContactSettings; private: CContactSettings *Parent; } Autoreply; /* class CPopupNotify { public: CPopupNotify& operator = (const int Value) { //CString Setting(Parent->ContactStatusToDBSetting(DB_POPUPNOTIFY, 0)); //IDC_MOREOPTDLG_PERSTATUSPERSONALSETTINGS HANDLE hContact = (Parent->hContact != INVALID_HANDLE_VALUE) ? Parent->hContact : NULL; if (DBGetContactSettingByte(hContact, MOD_NAME, Setting, VAL_USEDEFAULT) == Value) { return *this; // prevent deadlocks when calling UpdateSOEButtons } if (Value != VAL_USEDEFAULT) { DBWriteContactSettingByte(hContact, MOD_NAME, Setting, Value != 0); } else { DBDeleteContactSetting(hContact, MOD_NAME, Setting); } if (!Parent->hContact) { //UpdateSOEButtons(); } return *this; } operator int() {return DBGetContactSettingByte((Parent->hContact != INVALID_HANDLE_VALUE) ? Parent->hContact : NULL, MOD_NAME, Parent->ContactStatusToDBSetting(DB_POPUPNOTIFY, 0), Parent->hContact ? VAL_USEDEFAULT : POPUP_DEF_USEPOPUPS);} //IDC_MOREOPTDLG_PERSTATUSPERSONALSETTINGS int IncludingParents() // takes into account protocol and global data also, if per-contact setting is not defined { int Value = *this; if (Value == VAL_USEDEFAULT) { // PopupNotify setting is not per-status return CContactSettings(ID_STATUS_ONLINE).PopupNotify; } return Value; } friend class CContactSettings; private: CContactSettings *Parent; } PopupNotify; */ class CStatus { public: CStatus(int iStatus = 0, HANDLE hContact = NULL): Status(iStatus), hContact(hContact) {} CStatus& operator = (int Status) {this->Status = Status; return *this;} operator int() { if (!Status) { _ASSERT(hContact != INVALID_HANDLE_VALUE); char *szProto = hContact ? (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0) : NULL; Status = (szProto || !hContact) ? g_ProtoStates[szProto].Status : ID_STATUS_AWAY; } return Status; } friend class CPopupNotify; friend class CAutoreply; private: int Status; HANDLE hContact; } Status; void SetMsgFormat(int Flags, TCString Message); TCString GetMsgFormat(int Flags, int *pOrder = NULL, char *szProtoOverride = NULL); //NightFox: fix? //private: public: HANDLE hContact; };