From 177c84716db384c8be095219c58d0a68f87101fe Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Mon, 23 Jul 2012 11:56:59 +0000 Subject: NotesAndReminders, Nudge, OpenFolder, PackUpdater: changed folder structure git-svn-id: http://svn.miranda-ng.org/main/trunk@1116 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/NotesAndReminders/src/globals.h | 147 ++ plugins/NotesAndReminders/src/hotkeys.cpp | 137 ++ plugins/NotesAndReminders/src/main.cpp | 389 ++++ plugins/NotesAndReminders/src/miscutils.cpp | 246 +++ plugins/NotesAndReminders/src/miscutils.h | 47 + plugins/NotesAndReminders/src/notes.cpp | 2180 ++++++++++++++++++++ plugins/NotesAndReminders/src/options.cpp | 637 ++++++ plugins/NotesAndReminders/src/reminders.cpp | 2890 +++++++++++++++++++++++++++ plugins/NotesAndReminders/src/resource.h | 65 + 9 files changed, 6738 insertions(+) create mode 100644 plugins/NotesAndReminders/src/globals.h create mode 100644 plugins/NotesAndReminders/src/hotkeys.cpp create mode 100644 plugins/NotesAndReminders/src/main.cpp create mode 100644 plugins/NotesAndReminders/src/miscutils.cpp create mode 100644 plugins/NotesAndReminders/src/miscutils.h create mode 100644 plugins/NotesAndReminders/src/notes.cpp create mode 100644 plugins/NotesAndReminders/src/options.cpp create mode 100644 plugins/NotesAndReminders/src/reminders.cpp create mode 100644 plugins/NotesAndReminders/src/resource.h (limited to 'plugins/NotesAndReminders/src') diff --git a/plugins/NotesAndReminders/src/globals.h b/plugins/NotesAndReminders/src/globals.h new file mode 100644 index 0000000000..f8cc794e4a --- /dev/null +++ b/plugins/NotesAndReminders/src/globals.h @@ -0,0 +1,147 @@ +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "win2k.h" +#include "newpluginapi.h" +#include "m_system.h" +#include "m_database.h" +#include "m_clist.h" +#include "m_langpack.h" +#include "m_options.h" +#include "m_skin.h" +#include "m_fontservice.h" +#include "m_hotkeys.h" +#include "m_icolib.h" + +#include "m_toptoolbar.h" + +#include "miscutils.h" +#include "resource.h" + +// {842A6668-F9DA-4968-BFD7-D2BD9DF848EE} +#define MIID_NNR {0x842a6668, 0xf9da, 0x4968, {0xbf, 0xd7, 0xd2, 0xbd, 0x9d, 0xf8, 0x48, 0xee}} + +#define MODULENAME "StickyNotes" +#define SECTIONNAME "Notes & Reminders" + +// normal timer interval for reminder update processing +#define REMINDER_UPDATE_INTERVAL 10000 +// short timer interval for reminder updates used as long as there are pending alarams in the event queue +#define REMINDER_UPDATE_INTERVAL_SHORT 5000 + + +// font IDs used with LoadNRFont +#define NR_FONTID_CAPTION 0 +#define NR_FONTID_BODY 1 +#define NR_FONTID_MAX NR_FONTID_BODY + + +typedef struct { + HFONT hFont; + char size; + BYTE style; // see the DBFONTF_* flags + BYTE charset; + char szFace[LF_FACESIZE]; +} STICKYNOTEFONT; + +typedef struct { + HWND SNHwnd,REHwnd; + BOOL Visible,OnTop; + char *data; + ULARGE_INTEGER ID; // FILETIME in UTC + char *title; + BOOL CustomTitle; + DWORD BgColor; // custom bg color override (only valid if non-zero) + DWORD FgColor; // custom fg/text color override (only valid if non-zero) + STICKYNOTEFONT *pCustomFont;// custom (body) font override (NULL if default font is used) +} STICKYNOTE; + +typedef struct { + HWND handle; + BOOL RemVisible; + DWORD uid; + char *Reminder; + ULARGE_INTEGER When; // FILETIME in UTC + UINT RepeatSound; + UINT RepeatSoundTTL; + int SoundSel; // -1 if sound disabled + BOOL SystemEventQueued; +} REMINDERDATA; + + +extern void CreateMsgWindow(void); +extern void DestroyMsgWindow(void); + +extern STICKYNOTE* NewNote(int Ax,int Ay,int Aw,int Ah,char *Data, + ULARGE_INTEGER *ID,BOOL Visible,BOOL OnTop,int scrollV); +extern void LoadNotes(BOOL bIsStartup); +extern void SaveNotes(void); +extern void DeleteNotes(void); +extern void ShowHideNotes(void); +extern void ListNotes(void); + +extern void NewReminder(void); +extern void LoadReminders(void); +extern void SaveReminders(void); +extern void DeleteReminders(void); +extern void ListReminders(void); +extern BOOL CheckRemindersAndStart(void); + +extern void InitSettings(void); +extern void TermSettings(void); +extern int CALLBACK DlgProcOptions(HWND hdlg,UINT message, + WPARAM wParam,LPARAM lParam); +extern void LoadNRFont(int i, LOGFONT *lf, COLORREF *colour); + +extern BOOL WS_Init(); +extern void WS_CleanUp(); + +extern LPCSTR GetDateFormatStr(); +extern LPCSTR GetTimeFormatStr(); + +extern HINSTANCE hinstance; +extern HINSTANCE hmiranda; +extern PLUGINLINK *pluginLink; + +extern BOOL g_CloseAfterAddReminder, g_UseDefaultPlaySound; +extern HICON g_hReminderIcon; + +extern LOGFONT lfBody,lfCaption; +extern HFONT hBodyFont,hCaptionFont; + +extern long BodyColor; +extern long CaptionFontColor,BodyFontColor; + +extern BOOL g_ShowNotesAtStart,g_ShowScrollbar,g_AddContListMI,g_ShowNoteButtons; +extern int g_NoteTitleDate, g_NoteTitleTime; + +extern int g_NoteWidth,g_NoteHeight; + +extern int g_Transparency; + +extern char *g_RemindSMS; + +extern BOOL g_isWin2kPlus; + +extern TCHAR *g_lpszAltBrowser; + +extern int g_reminderListGeom[4]; +extern int g_reminderListColGeom[2]; +extern int g_notesListGeom[4]; +extern int g_notesListColGeom[4]; + +extern HWND HKHwnd; +extern HANDLE hIconLibItem[]; + +// these defs are only used to emphasize that SYSTEMTIMEtoFILETIME/FILETIMEtoSYSTEMTIME only convert the data type, +// it does not apply any time conversion/correction like UTC to local etc. (if input is local, then output is local too) +#define SYSTEMTIMEtoFILETIME SystemTimeToFileTime +#define FILETIMEtoSYSTEMTIME FileTimeToSystemTime diff --git a/plugins/NotesAndReminders/src/hotkeys.cpp b/plugins/NotesAndReminders/src/hotkeys.cpp new file mode 100644 index 0000000000..a8bd8d3c53 --- /dev/null +++ b/plugins/NotesAndReminders/src/hotkeys.cpp @@ -0,0 +1,137 @@ +#include "globals.h" + +#define MSG_WND_CLASS "MIM_SNMsgWindow" + + +HWND HKHwnd; + + +enum KB_ACTIONS {KB_NEW_NOTE = 1, KB_TOGGLE_NOTES, KB_NEW_REMINDER}; + + +void RegisterKeyBindings() +{ + HOTKEYDESC desc; + + ZeroMemory(&desc, sizeof(desc)); + desc.cbSize = sizeof(desc); + desc.pszSection = SECTIONNAME; + + desc.pszName = MODULENAME"/NewNote"; + desc.pszDescription = "New Note"; + desc.lParam = KB_NEW_NOTE; + desc.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, VK_INSERT); + desc.pszService = MODULENAME"/MenuCommandAddNew"; + Hotkey_Register(&desc); + + desc.pszName = MODULENAME"/ToggleNotesVis"; + desc.pszDescription = "Toggle Notes Visibility"; + desc.lParam = KB_TOGGLE_NOTES; + desc.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, VK_ADD); + desc.pszService = MODULENAME"/MenuCommandShowHide"; + Hotkey_Register(&desc); + + desc.pszName = MODULENAME"/BringNotesFront"; + desc.pszDescription = "Bring All Notes to Front"; + desc.lParam = KB_TOGGLE_NOTES; + desc.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, VK_HOME); + desc.pszService = MODULENAME"/MenuCommandBringAllFront"; + Hotkey_Register(&desc); + + desc.pszName = MODULENAME"/NewReminder"; + desc.pszDescription = "New Reminder"; + desc.lParam = KB_NEW_REMINDER; + desc.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL|HOTKEYF_SHIFT, VK_SUBTRACT); + desc.pszService = MODULENAME"/MenuCommandNewReminder"; + Hotkey_Register(&desc); +} + + +/*int HandleNRShortcuts(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + STICKYNOTE* PSN; + + BOOL isShift = GetKeyState(VK_SHIFT) & 0x8000; + BOOL isAlt = GetKeyState(VK_MENU) & 0x8000; + BOOL isCtrl = (GetKeyState(VK_CONTROL) & 0x8000) && !isAlt; + + int action; + MSG amsg; + amsg.hwnd = hwnd; + amsg.message = msg; + amsg.wParam = wParam; + amsg.lParam = lParam; + + switch ( (action = CallService(MS_HOTKEY_CHECK, (WPARAM)&amsg, (LPARAM)SECTIONNAME)) ) + { + case KB_NEW_NOTE: + PSN = NewNote(0,0,0,0,NULL,NULL,TRUE,TRUE,0); + SetFocus(PSN->REHwnd); + return FALSE; + case KB_TOGGLE_NOTES: + ShowHideNotes(); + return FALSE; + case KB_NEW_REMINDER: + NewReminder(); + return FALSE; + } + + return -1; +}*/ + + +int CALLBACK NotifyHotKeyWndProc(HWND AHwnd,UINT Message,WPARAM wParam,LPARAM lParam) +{ + BOOL b; + + switch (Message) + { + case WM_TIMER: + { + KillTimer(HKHwnd,1026); + b = CheckRemindersAndStart(); + SetTimer(HKHwnd,1026,b ? REMINDER_UPDATE_INTERVAL_SHORT : REMINDER_UPDATE_INTERVAL,0); + + return FALSE; + } + } + + return DefWindowProc(AHwnd,Message,wParam,lParam); +} + +void CreateMsgWindow(void) +{ + HWND hParent = NULL; + WNDCLASSEX TWC = {0}; + + if (!GetClassInfoEx(hmiranda,MSG_WND_CLASS,&TWC)) + { + TWC.style = 0; + TWC.cbClsExtra = 0; + TWC.cbWndExtra = 0; + TWC.hInstance = hmiranda; + TWC.hIcon = 0; + TWC.hCursor = 0; + TWC.hbrBackground = 0; + TWC.lpszMenuName = NULL; + TWC.lpszClassName = MSG_WND_CLASS; + TWC.cbSize = sizeof(TWC); + TWC.lpfnWndProc = (WNDPROC)NotifyHotKeyWndProc; + RegisterClassEx(&TWC); + } + + if ( IsWinVer2000Plus() ) + { + // win2k+ has special message-only windows support + hParent = HWND_MESSAGE; + } + + HKHwnd = CreateWindowEx(WS_EX_TOOLWINDOW,MSG_WND_CLASS,_T("StickyNotes"),0,0,0,0,0,hParent,NULL,hmiranda,NULL); + SetTimer(HKHwnd,1026,REMINDER_UPDATE_INTERVAL,0); +} + +void DestroyMsgWindow(void) +{ + KillTimer(HKHwnd,1026); + DestroyWindow(HKHwnd); +} diff --git a/plugins/NotesAndReminders/src/main.cpp b/plugins/NotesAndReminders/src/main.cpp new file mode 100644 index 0000000000..2165a8e49b --- /dev/null +++ b/plugins/NotesAndReminders/src/main.cpp @@ -0,0 +1,389 @@ +#include "globals.h" + +HINSTANCE hinstance = NULL; +HINSTANCE hmiranda = NULL; +int hLangpack; + +HANDLE hkOptInit = NULL; +HANDLE hkTopToolbarInit = NULL; +HANDLE hkModulesLoaded = NULL; +HANDLE hkFontChange = NULL; +HANDLE hkColorChange = NULL; +HMODULE hUserDll = NULL; +HMODULE hRichedDll = NULL; +HMODULE hKernelDll = NULL; + +extern TREEELEMENT *g_Stickies; +extern TREEELEMENT *RemindersList; + +static PLUGININFOEX pluginInfo = +{ + sizeof(PLUGININFOEX), + "Sticky Notes & Reminders", + PLUGIN_MAKE_VERSION(0,0,5,1), + "Sticky Notes & Reminders Implementation for Miranda IM.", + "Joe Kucera, Lubomir Kolev Ivanov, Georg Fischer", + "jokusoftware@users.sourceforge.net; d00mEr@dir.bg", + "(C) 2003,2005 Joe Kucera, Lubomir Ivanov", + "http://d00mer.freeshell.org/miranda/", + UNICODE_AWARE, + MIID_NNR +}; + + +void RegisterFontServiceFonts(); +void RegisterKeyBindings(); +int OpenTriggeredReminder(WPARAM w, LPARAM l); +void BringAllNotesToFront(STICKYNOTE *pActive); +void CloseNotesList(); +void CloseReminderList(); + +int PluginMenuCommandAddNew(WPARAM w,LPARAM l) +{ + STICKYNOTE* PSN; + PSN = NewNote(0,0,0,0,NULL,NULL,TRUE,TRUE,0); + SetFocus(PSN->REHwnd); + return 0; +} + +int PluginMenuCommandDeleteAll(WPARAM w,LPARAM l) +{ + if (g_Stickies && MessageBox(NULL, Translate("Are you sure you want to delete all notes?"), SECTIONNAME, MB_OKCANCEL) == IDOK) + DeleteNotes(); + return 0; +} + +int PluginMenuCommandShowHide(WPARAM w,LPARAM l) +{ + ShowHideNotes(); + return 0; +} + +int PluginMenuCommandViewNotes(WPARAM w,LPARAM l) +{ + ListNotes(); + return 0; +} + +int PluginMenuCommandAllBringFront(WPARAM w,LPARAM l) +{ + BringAllNotesToFront(NULL); + return 0; +} + +int PluginMenuCommandNewReminder(WPARAM w,LPARAM l) +{ + NewReminder(); + return 0; +} + +int PluginMenuCommandViewReminders(WPARAM w,LPARAM l) +{ + ListReminders(); + return 0; +} + +int PluginMenuCommandDeleteReminders(WPARAM w,LPARAM l) +{ + if (RemindersList && MessageBox(NULL, Translate("Are you sure you want to delete all reminders?"), SECTIONNAME, MB_OKCANCEL) == IDOK) + DeleteReminders(); + return 0; +} + +struct +{ + char* szDescr; + char* szName; + int defIconID; +} +static const iconList[] = +{ + {"New Reminder", "AddReminder", IDI_ADDREMINDER}, + {"Delete All Notes", "DeleteIcon", IDI_DELETEICON}, + {"New Note", "NoteIcon", IDI_NOTEICON}, + {"Show/Hide Notes", "ShowHide", IDI_SHOWHIDE}, + {"On Top Caption Icon", "CaptionIcon", IDI_CAPTIONICON}, + {"Delete All Reminders", "DeleteReminder", IDI_DELETEREMINDER}, + {"View Reminders", "ViewReminders", IDI_VIEWREMINDERS}, + {"Not on Top Caption Icon", "CaptionIconNotTop", IDI_CAPTIONICONNOTTOP}, + {"Hide Note Icon", "HideNote", IDI_HIDENOTE}, + {"Remove Note Icon", "RemoveNote", IDI_REMOVENOTE}, + {"Reminder Icon", "Reminder", IDI_REMINDER}, + {"Bring All to Front", "BringFront", IDI_BRINGFRONT}, + {"Play Sound Icon", "PlaySound", IDI_PLAYSOUND}, + {"View Notes", "ViewNotes", IDI_VIEWNOTES}, + {"New Note", "NewNote", IDI_NOTEICON}, + {"New Reminder", "NewReminder", IDI_ADDREMINDER} +}; + +HANDLE hIconLibItem[SIZEOF(iconList)]; + +void InitIcons(void) +{ + int i; + char szSettingName[100]; + SKINICONDESC sid = {0}; + TCHAR szFile[MAX_PATH]; + + GetModuleFileName(hinstance, szFile, SIZEOF(szFile)); + + sid.cbSize = sizeof(SKINICONDESC); + sid.ptszDefaultFile = szFile; + sid.pszName = szSettingName; + sid.pszSection = MODULENAME; + sid.flags = SIDF_PATH_TCHAR; + + for (i = 0; i < SIZEOF(iconList); i++) + { + mir_snprintf(szSettingName, SIZEOF(szSettingName), "%s_%s", MODULENAME, iconList[i].szName); + + sid.pszDescription = iconList[i].szDescr; + sid.iDefaultIndex = -iconList[i].defIconID; + hIconLibItem[i] = Skin_AddIcon(&sid); + } +} + +int OnOptInitialise(WPARAM w, LPARAM L) +{ + OPTIONSDIALOGPAGE odp = {0}; + odp.cbSize = sizeof(odp); + odp.position = 900002000; + odp.hInstance = hinstance; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_STNOTEOPTIONS); + odp.pszTitle = _T(SECTIONNAME); + odp.pszGroup = _T("Plugins"); + odp.pfnDlgProc = DlgProcOptions; + Options_AddPage(w, &odp); + return 0; +} + +int OnTopToolBarInit(WPARAM w,LPARAM L) +{ + TTBButton ttb = {0}; + ttb.cbSize = sizeof(TTBButton); + ttb.dwFlags = TTBBF_VISIBLE | TTBBF_SHOWTOOLTIP; + + ttb.hIconHandleUp = hIconLibItem[14]; + ttb.pszService = MODULENAME"/MenuCommandAddNew"; + ttb.name = ttb.pszTooltipUp = LPGEN("Add New Note"); + TopToolbar_AddButton(&ttb); + + ttb.hIconHandleUp = hIconLibItem[15]; + ttb.pszService = MODULENAME"/MenuCommandNewReminder"; + ttb.name = ttb.pszTooltipUp = LPGEN("Add New Reminder"); + TopToolbar_AddButton(&ttb); + + UnhookEvent(hkTopToolbarInit); + return 0; +} + +static void InitServices() +{ + // register sounds + + SkinAddNewSoundEx("AlertReminder", "Alerts", "Reminder triggered"); + SkinAddNewSoundEx("AlertReminder2", "Alerts", "Reminder triggered (Alternative 1)"); + SkinAddNewSoundEx("AlertReminder3", "Alerts", "Reminder triggered (Alternative 2)"); + + // register menu command services + + CreateServiceFunction(MODULENAME"/MenuCommandAddNew",PluginMenuCommandAddNew); + CreateServiceFunction(MODULENAME"/MenuCommandShowHide",PluginMenuCommandShowHide); + CreateServiceFunction(MODULENAME"/MenuCommandViewNotes",PluginMenuCommandViewNotes); + CreateServiceFunction(MODULENAME"/MenuCommandDeleteAll",PluginMenuCommandDeleteAll); + CreateServiceFunction(MODULENAME"/MenuCommandBringAllFront",PluginMenuCommandAllBringFront); + + // + + CreateServiceFunction(MODULENAME"/MenuCommandNewReminder",PluginMenuCommandNewReminder); + CreateServiceFunction(MODULENAME"/MenuCommandViewReminders",PluginMenuCommandViewReminders); + CreateServiceFunction(MODULENAME"/MenuCommandDeleteReminders",PluginMenuCommandDeleteReminders); + + // register misc + + CreateServiceFunction(MODULENAME"/OpenTriggeredReminder",OpenTriggeredReminder); +} + +int OnModulesLoaded(WPARAM wparam,LPARAM lparam) +{ + CLISTMENUITEM cmi = {0}; + + // register fonts and hotkeys + + RegisterFontServiceFonts(); + RegisterKeyBindings(); + + // register menus + + cmi.cbSize = sizeof(cmi); + cmi.pszContactOwner = NULL; + cmi.pszPopupName = "Notes && Reminders"; + cmi.flags = CMIM_ICON | CMIF_ICONFROMICOLIB; + + cmi.position = 1600000000; + cmi.icolibItem = hIconLibItem[2]; + cmi.pszName = "New &Note"; + cmi.pszService = MODULENAME"/MenuCommandAddNew"; + if (g_AddContListMI) Menu_AddContactMenuItem(&cmi); + Menu_AddMainMenuItem(&cmi); + + cmi.position = 1600000001; + cmi.icolibItem = hIconLibItem[0]; + cmi.pszName = "New &Reminder"; + cmi.pszService = MODULENAME"/MenuCommandNewReminder"; + if (g_AddContListMI) Menu_AddContactMenuItem(&cmi); + Menu_AddMainMenuItem(&cmi); + + // + + cmi.position = 1600100000; + cmi.icolibItem = hIconLibItem[3]; + cmi.pszName = "&Show / Hide Notes"; + cmi.pszService = MODULENAME"/MenuCommandShowHide"; + if (g_AddContListMI) Menu_AddContactMenuItem(&cmi); + Menu_AddMainMenuItem(&cmi); + + cmi.position = 1600100001; + cmi.icolibItem = hIconLibItem[13]; + cmi.pszName = "Vie&w Notes"; + cmi.pszService = MODULENAME"/MenuCommandViewNotes"; + if (g_AddContListMI) Menu_AddContactMenuItem(&cmi); + Menu_AddMainMenuItem(&cmi); + + cmi.position = 1600100002; + cmi.icolibItem = hIconLibItem[1]; + cmi.pszName = "&Delete All Notes"; + cmi.pszService = MODULENAME"/MenuCommandDeleteAll"; + if (g_AddContListMI) Menu_AddContactMenuItem(&cmi); + Menu_AddMainMenuItem(&cmi); + + cmi.position = 1600100003; + cmi.icolibItem = hIconLibItem[11]; + cmi.pszName = "&Bring All to Front"; + cmi.pszService = MODULENAME"/MenuCommandBringAllFront"; + if (g_AddContListMI) Menu_AddContactMenuItem(&cmi); + Menu_AddMainMenuItem(&cmi); + + // + + cmi.position = 1600200000; + cmi.icolibItem = hIconLibItem[6]; + cmi.pszName = "&View Reminders"; + cmi.pszService = MODULENAME"/MenuCommandViewReminders"; + if (g_AddContListMI) Menu_AddContactMenuItem(&cmi); + Menu_AddMainMenuItem(&cmi); + + cmi.position = 1600200001; + cmi.icolibItem = hIconLibItem[5]; + cmi.pszName = "D&elete All Reminders"; + cmi.pszService = MODULENAME"/MenuCommandDeleteReminders"; + if (g_AddContListMI) Menu_AddContactMenuItem(&cmi); + Menu_AddMainMenuItem(&cmi); + + // register misc + + hkOptInit = HookEvent(ME_OPT_INITIALISE, OnOptInitialise); + hkTopToolbarInit = HookEvent("TopToolBar/ModuleLoaded", OnTopToolBarInit); + UnhookEvent(hkModulesLoaded); + + // init vars and load all data + + InitSettings(); + CreateMsgWindow(); + LoadNotes(TRUE); + LoadReminders(); + + return 0; +} + +extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion) +{ + return &pluginInfo; +} + +extern "C" __declspec(dllexport) int Unload(void) +{ + CloseNotesList(); + CloseReminderList(); + SaveNotes(); + SaveReminders(); + DestroyMsgWindow(); + WS_CleanUp(); + TermSettings(); + + UnhookEvent(hkFontChange); + UnhookEvent(hkColorChange); + + UnhookEvent(hkOptInit); + + Skin_ReleaseIcon(g_hReminderIcon); + DeleteObject(hBodyFont); + DeleteObject(hCaptionFont); + + if (hRichedDll) + FreeLibrary(hRichedDll); + if (hUserDll) + FreeLibrary(hUserDll); + if (hKernelDll) + FreeLibrary(hKernelDll); + + return 0; +} + +BOOL WINAPI DllMain(HINSTANCE hinst, DWORD fdwReason, LPVOID lpvReserved) +{ + hinstance = hinst; + return TRUE; +} + +extern "C" __declspec(dllexport) int Load(void) +{ + mir_getLP(&pluginInfo); + + INITCOMMONCONTROLSEX ctrls = {0}; + ctrls.dwSize = sizeof(INITCOMMONCONTROLSEX); + ctrls.dwICC = ICC_DATE_CLASSES; + InitCommonControlsEx(&ctrls); + + g_isWin2kPlus = IsWinVer2000Plus(); + + hRichedDll = LoadLibrary("RICHED20.DLL"); + if (!hRichedDll) + { + if (MessageBox(0, Translate("Miranda could not load the Note & Reminders plugin, RICHED20.DLL is missing. If you are using Windows 95 or WINE please make sure you have riched20.dll installed. Press 'Yes' to continue loading Miranda."), SECTIONNAME, MB_YESNO | MB_ICONINFORMATION) != IDYES) + return 1; + return 0; + } + + hUserDll = LoadLibrary("user32.dll"); + if (hUserDll) + { + MySetLayeredWindowAttributes = (BOOL (WINAPI *)(HWND,COLORREF,BYTE,DWORD))GetProcAddress(hUserDll,"SetLayeredWindowAttributes"); + MyMonitorFromWindow = (HANDLE (WINAPI*)(HWND,DWORD))GetProcAddress(hUserDll,"MonitorFromWindow"); + } + else + { + MySetLayeredWindowAttributes = NULL; + MyMonitorFromWindow = NULL; + } + + hKernelDll = LoadLibrary("kernel32.dll"); + if (hKernelDll) + { + MyTzSpecificLocalTimeToSystemTime = (BOOL (WINAPI*)(LPTIME_ZONE_INFORMATION,LPSYSTEMTIME,LPSYSTEMTIME))GetProcAddress(hKernelDll,"TzSpecificLocalTimeToSystemTime"); + MySystemTimeToTzSpecificLocalTime = (BOOL (WINAPI*)(LPTIME_ZONE_INFORMATION,LPSYSTEMTIME,LPSYSTEMTIME))GetProcAddress(hKernelDll,"SystemTimeToTzSpecificLocalTime"); + } + else + { + MyTzSpecificLocalTimeToSystemTime = NULL; + MySystemTimeToTzSpecificLocalTime = NULL; + } + + InitServices(); + WS_Init(); + + hkModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED,OnModulesLoaded); + InitIcons(); + + return 0; +} \ No newline at end of file diff --git a/plugins/NotesAndReminders/src/miscutils.cpp b/plugins/NotesAndReminders/src/miscutils.cpp new file mode 100644 index 0000000000..b4de98404c --- /dev/null +++ b/plugins/NotesAndReminders/src/miscutils.cpp @@ -0,0 +1,246 @@ +#include "globals.h" + +BOOL (WINAPI *MySetLayeredWindowAttributes)(HWND,COLORREF,BYTE,DWORD); +HANDLE (WINAPI *MyMonitorFromWindow)(HWND,DWORD); +BOOL (WINAPI *MyTzSpecificLocalTimeToSystemTime)(LPTIME_ZONE_INFORMATION,LPSYSTEMTIME,LPSYSTEMTIME); +BOOL (WINAPI *MySystemTimeToTzSpecificLocalTime)(LPTIME_ZONE_INFORMATION,LPSYSTEMTIME,LPSYSTEMTIME); + + +WORD ConvertHotKeyToControl(WORD HK) +{ + WORD R = 0; + if ((HK & MOD_CONTROL) == MOD_CONTROL) R = R | HOTKEYF_CONTROL; + if ((HK & MOD_ALT) == MOD_ALT) R = R | HOTKEYF_ALT; + if ((HK & MOD_SHIFT) == MOD_SHIFT) R = R | HOTKEYF_SHIFT; + return R; +} + +WORD ConvertControlToHotKey(WORD HK) +{ + WORD R = 0; + if ((HK & HOTKEYF_CONTROL) == HOTKEYF_CONTROL) R = R | MOD_CONTROL; + if ((HK & HOTKEYF_ALT) == HOTKEYF_ALT) R = R | MOD_ALT; + if ((HK & HOTKEYF_SHIFT) == HOTKEYF_SHIFT) R = R | MOD_SHIFT; + return R; +} + +void WriteSettingInt(HANDLE hContact,char *ModuleName,char *SettingName,int Value) +{ + DBCONTACTWRITESETTING cws = {0}; + DBVARIANT dbv = {0}; + dbv.type = DBVT_DWORD; + dbv.dVal = Value; + cws.szModule = ModuleName; + cws.szSetting = SettingName; + cws.value = dbv; + CallService(MS_DB_CONTACT_WRITESETTING,(DWORD)hContact,(DWORD)&cws); +} + +int ReadSettingInt(HANDLE hContact,char *ModuleName,char *SettingName,int Default) +{ + DBCONTACTGETSETTING cws = {0}; + DBVARIANT dbv = {0}; + dbv.type = DBVT_DWORD; + dbv.dVal = Default; + cws.szModule = ModuleName; + cws.szSetting = SettingName; + cws.pValue = &dbv; + if (CallService(MS_DB_CONTACT_GETSETTING,(DWORD)hContact,(DWORD)&cws)) + return Default; + else + return dbv.dVal; +} + +void DeleteSetting(HANDLE hContact,char *ModuleName,char *SettingName) +{ + DBCONTACTGETSETTING dbcgs = {0}; + dbcgs.szModule = ModuleName; + dbcgs.szSetting = SettingName; + dbcgs.pValue = NULL; + CallService(MS_DB_CONTACT_DELETESETTING,(DWORD)hContact,(DWORD)&dbcgs); +} + +void FreeSettingBlob(WORD pSize,void * pbBlob) +{ + DBVARIANT dbv = {0}; + dbv.type = DBVT_BLOB; + dbv.cpbVal = pSize; + dbv.pbVal = (BYTE*)pbBlob; + CallService(MS_DB_CONTACT_FREEVARIANT,0,(DWORD)&dbv); +} + +void WriteSettingBlob(HANDLE hContact,char *ModuleName,char *SettingName,WORD pSize,void *pbBlob) +{ + DBCONTACTWRITESETTING cgs = {0}; + DBVARIANT dbv = {0}; + dbv.type = DBVT_BLOB; + dbv.cpbVal = pSize; + dbv.pbVal = (BYTE*)pbBlob; + cgs.szModule = ModuleName; + cgs.szSetting = SettingName; + cgs.value = dbv; + CallService(MS_DB_CONTACT_WRITESETTING,(DWORD)hContact,(DWORD)&cgs); +} + +void ReadSettingBlob(HANDLE hContact, char *ModuleName, char *SettingName, WORD *pSize, void **pbBlob) +{ + DBCONTACTGETSETTING cgs = {0}; + DBVARIANT dbv = {0}; + dbv.type = DBVT_BLOB; +// dbv.cpbVal = (LOBYTE(pSize) | HIBYTE((BYTE)pbBlob)); // this is not used anyway + cgs.szModule = ModuleName; + cgs.szSetting = SettingName; + cgs.pValue = &dbv; + if (CallService(MS_DB_CONTACT_GETSETTING,(DWORD)hContact,(DWORD)&cgs)) + { + pSize = 0; // tohle asi ve 4.2 neni + pbBlob = NULL; + } + else + { + *pSize = LOWORD(dbv.cpbVal); + *pbBlob = (int*)dbv.pbVal; + } +} + +void WriteSettingIntArray(HANDLE hContact,char *ModuleName,char *SettingName,const int *Value, int Size) +{ + WriteSettingBlob(hContact,ModuleName,SettingName,sizeof(int)*Size,(void*)Value); +} + +BOOL ReadSettingIntArray(HANDLE hContact,char *ModuleName,char *SettingName,int *Value, int Size) +{ + WORD sz = 4096; + int *pData; + BOOL bResult; + + ReadSettingBlob(hContact, ModuleName, SettingName, &sz, (void**)&pData); + + if (!pData) + return FALSE; + + bResult = FALSE; + + if (sz == sizeof(int)*Size) + { + memcpy(Value, pData, sizeof(int)*Size); + bResult = TRUE; + } + + FreeSettingBlob(sz,pData); + + return bResult; +} + +void TreeAdd(TREEELEMENT **root, void *Data) +{ + TREEELEMENT *NTE; + NTE = (TREEELEMENT*)malloc(sizeof(TREEELEMENT)); + if (NTE) + { + NTE->ptrdata = Data; + NTE->next = *root; + *root = NTE; + } +} + +void TreeAddSorted(TREEELEMENT **root,void *Data,int (*CompareCb)(TREEELEMENT*,TREEELEMENT*)) +{ + TREEELEMENT *TTE,*Prev; + TREEELEMENT *NTE; + + if (!*root) + { + // list empty, just add normally + TreeAdd(root,Data); + return; + } + + NTE = (TREEELEMENT*)malloc(sizeof(TREEELEMENT)); + if (!NTE) + return; + + NTE->ptrdata = Data; + NTE->next = NULL; + + // insert sorted + + Prev = NULL; + TTE = *root; + + while (TTE) + { + if (CompareCb(NTE, TTE) < 0) + { + if (Prev) + { + Prev->next = NTE; + NTE->next = TTE; + } + else + { + // first in list + NTE->next = TTE; + *root = NTE; + } + return; + } + + Prev = TTE; + TTE = (TREEELEMENT*)TTE->next; + } + + // add last + Prev->next = NTE; +} + +void TreeDelete(TREEELEMENT **root,void *Item) +{ + TREEELEMENT *TTE,*Prev = NULL; + TTE = *root; + if (!TTE) return; + while ((TTE) && (TTE->ptrdata != Item)) + { + Prev = TTE; + TTE = (TREEELEMENT*)TTE->next; + } + if (TTE) + { + if (Prev) + Prev->next = TTE->next; + else + *root = (TREEELEMENT*)TTE->next; + SAFE_FREE((void**)&TTE); + } +} + +void *TreeGetAt(TREEELEMENT *root,int Item) +{ + TREEELEMENT *TTE; + int I = 0; + if (!root) return NULL; + TTE = root; + while ((TTE) && (I != Item)) + { + TTE = (TREEELEMENT*)TTE->next; + I++; + } + if (!TTE) + return NULL; + else + return TTE->ptrdata; +} + +int TreeGetCount(TREEELEMENT *root) +{ + int I = 0; + TREEELEMENT *TTE; + if (!root) return 0; + TTE = root; + while (TTE) + { + TTE = (TREEELEMENT*)TTE->next; + I++; + } + return I; +} diff --git a/plugins/NotesAndReminders/src/miscutils.h b/plugins/NotesAndReminders/src/miscutils.h new file mode 100644 index 0000000000..104de5313c --- /dev/null +++ b/plugins/NotesAndReminders/src/miscutils.h @@ -0,0 +1,47 @@ +void WriteSettingInt(HANDLE hContact,char *ModuleName, + char *SettingName,int Value); +int ReadSettingInt(HANDLE hContact,char *ModuleName, + char *SettingName,int Default); +void ReadSettingBlob(HANDLE hContact, char *ModuleName, + char *SettingName, WORD *pSize, void **pbBlob); +void WriteSettingBlob(HANDLE hContact,char *ModuleName, + char *SettingName,WORD pSize,void *pbBlob); +void FreeSettingBlob(WORD pSize,void * pbBlob); +BOOL ReadSettingBool(HANDLE hContact,char *ModuleName, + char *SettingName,BOOL Default); +void WriteSettingBool(HANDLE hContact,char *ModuleName, + char *SettingName,BOOL Value); +void DeleteSetting(HANDLE hContact,char *ModuleName, + char *SettingName); +void WriteSettingIntArray(HANDLE hContact,char *ModuleName, + char *SettingName,const int *Value, int Size); +BOOL ReadSettingIntArray(HANDLE hContact,char *ModuleName, + char *SettingName,int *Value, int Size); + +extern BOOL (WINAPI *MySetLayeredWindowAttributes)(HWND,COLORREF,BYTE,DWORD); +extern HANDLE (WINAPI *MyMonitorFromWindow)(HWND,DWORD); +extern BOOL (WINAPI *MyTzSpecificLocalTimeToSystemTime)(LPTIME_ZONE_INFORMATION,LPSYSTEMTIME,LPSYSTEMTIME); +extern BOOL (WINAPI *MySystemTimeToTzSpecificLocalTime)(LPTIME_ZONE_INFORMATION,LPSYSTEMTIME,LPSYSTEMTIME); + +WORD ConvertHotKeyToControl(WORD HK); +WORD ConvertControlToHotKey(WORD HK); + +typedef struct { + void *ptrdata; + void *next; +} TREEELEMENT; + +void TreeAdd(TREEELEMENT **root,void *Data); +void TreeAddSorted(TREEELEMENT **root,void *Data,int (*CompareCb)(TREEELEMENT*,TREEELEMENT*)); +void TreeDelete(TREEELEMENT **root,void *Item); +void *TreeGetAt(TREEELEMENT *root,int Item); +int TreeGetCount(TREEELEMENT *root); + +static void __inline SAFE_FREE(void** p) +{ + if (*p) + { + free(*p); + *p = NULL; + } +} diff --git a/plugins/NotesAndReminders/src/notes.cpp b/plugins/NotesAndReminders/src/notes.cpp new file mode 100644 index 0000000000..5f98768d8b --- /dev/null +++ b/plugins/NotesAndReminders/src/notes.cpp @@ -0,0 +1,2180 @@ +#include "globals.h" + +#ifndef MONITOR_DEFAULTTONULL +#define MONITOR_DEFAULTTONULL 0x00000000 +#endif + + +// NotesData DB data params +#define DATATAG_TEXT 1 // %s +#define DATATAG_SCROLLPOS 2 // %u (specifies rich edit controls scroll post as first visible line) +#define DATATAG_BGCOL 3 // %x (custom background color) +#define DATATAG_FGCOL 4 // %x (custom text/fg colors) +#define DATATAG_TITLE 5 // %s (custom note title) +#define DATATAG_FONT 6 // %d:%u:%u:%s (custom font) + + +#define MAX_TITLE_LEN 63 +#define MAX_NOTE_LEN 16384 + +// delay before saving note changes (ms) +#define NOTE_CHANGE_COMMIT_DELAY 1000 + + +#ifndef WS_EX_NOACTIVATE +#define WS_EX_NOACTIVATE 0x08000000 +#endif +#define IDM_REMOVENOTE 40001 +#define IDM_HIDENOTE 40002 +#define IDM_TOGGLEONTOP 40003 +#define IDM_UNDO 40004 +#define IDM_COPY 40005 +#define IDM_PASTE 40006 +#define IDM_CUT 40007 +#define IDM_CLEAR 40008 +#define WS_EX_LAYERED 0x00080000 +#define LWA_ALPHA 0x00000002 + +#define IDC_LISTREMINDERS 1000 +#define IDC_LISTREMINDERS_HEADER 2000 +#define IDC_REMINDERDATA 1001 +#define IDC_ADDNEWREMINDER 1002 +#define IDC_CLOSE 1003 +#define WM_RELOAD (WM_USER + 100) + +#define NOTIFY_LIST() if (ListNotesVisible) PostMessage(LV,WM_RELOAD,0,0) + +#define PENLINK ENLINK * + +#define NOTE_WND_CLASS "MIM_StickyNote" + + +#define IDM_COLORPRESET_BG 41000 +#define IDM_COLORPRESET_FG 41100 + + +static BOOL ListNotesVisible = FALSE; +static HWND LV; + + +struct ColorPreset +{ + TCHAR *szName; + COLORREF color; +}; + +static struct ColorPreset clrPresets[] = +{ + {"Black", RGB(0,0,0)}, + {"Maroon", RGB(128,0,0)}, + {"Green", RGB(0,128,0)}, + {"Olive", RGB(128,128,0)}, + {"Navy", RGB(0,0,128)}, + {"Purple", RGB(128,0,128)}, + {"Teal", RGB(0,128,128)}, + {"Gray", RGB(128,128,128)}, + {"Silver", RGB(192,192,192)}, + {"Red", RGB(255,0,0)}, + {"Orange", RGB(255,155,0)}, + {"Lime", RGB(0,255,0)}, + {"Yellow", RGB(255,255,0)}, + {"Blue", RGB(0,0,255)}, + {"Fuchsia", RGB(255,0,255)}, + {"Aqua", RGB(0,255,255)}, + {"White", RGB(255,255,255)} +}; + + +TREEELEMENT *g_Stickies = NULL; + + +int CALLBACK StickyNoteWndProc(HWND hdlg,UINT message, + WPARAM wParam,LPARAM lParam); +int CALLBACK DlgProcViewNotes(HWND Dialog,UINT Message,WPARAM wParam, + LPARAM lParam); +void JustSaveNotes(void); +int PluginMenuCommandAddNew(WPARAM w,LPARAM l); +int PluginMenuCommandDeleteAll(WPARAM w,LPARAM l); +void GetTriggerTimeString(const ULARGE_INTEGER *When, char *s, UINT strSize, BOOL bUtc); +void OnListResize(HWND Dialog); +void UpdateGeomFromWnd(HWND Dialog, int *geom, int *colgeom, int nCols); +void FileTimeToTzLocalST(const FILETIME *lpUtc, SYSTEMTIME *tmLocal); + + +COLORREF GetCaptionColor(COLORREF bodyClr) +{ + const DWORD r = ((bodyClr & 0xff) * 4) / 5; + const DWORD g = (((bodyClr & 0xff00) * 4) / 5) & 0xff00; + const DWORD b = (((bodyClr & 0xff0000) * 4) / 5) & 0xff0000; + + return (COLORREF)(r|g|b); +} + + +static void EnsureUniqueID(STICKYNOTE *TSN) +{ + TREEELEMENT *TTE; + + if (!g_Stickies) + return; + +try_next: + + // check existing notes if id is in use + TTE = g_Stickies; + while (TTE) + { + if (((STICKYNOTE*)TTE->ptrdata)->ID.QuadPart == TSN->ID.QuadPart) + { + // id in use, try new (increases the ID/time stamp by 100 nanosecond steps until an unused time is found, + // allthough it's very unlikely that there will be duplicated id's it's better to make 100% sure) + TSN->ID.QuadPart++; + goto try_next; + } + + TTE = (TREEELEMENT*)TTE->next; + } +} + + +static void InitNoteTitle(STICKYNOTE *TSN) +{ + if (g_NoteTitleDate) + { + char TempStr[MAX_PATH]; + SYSTEMTIME tm; + LCID lc = GetUserDefaultLCID(); + + TempStr[0] = 0; + + memset(&tm, 0, sizeof(tm)); + FileTimeToTzLocalST((FILETIME*)&TSN->ID, &tm); + + if ( GetDateFormat(lc, 0, &tm, GetDateFormatStr(), TempStr, MAX_PATH) ) + { + // append time if requested + if (g_NoteTitleTime) + { + int n = strlen(TempStr); + TempStr[n++] = ' '; + TempStr[n] = 0; + + GetTimeFormat(MAKELCID(MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US),0), 0, &tm, GetTimeFormatStr(), TempStr+n, MAX_PATH-n); + } + + TSN->title = _strdup(TempStr); + } + } + + TSN->CustomTitle = FALSE; +} + + +static void InitStickyNoteLogFont(STICKYNOTEFONT *pCustomFont, LOGFONT *lf) +{ + if (!pCustomFont->size) + { + HDC hdc; + + SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(LOGFONT), &lf, FALSE); + lf->lfHeight = 10; + hdc = GetDC(0); + lf->lfHeight = -MulDiv(lf->lfHeight,GetDeviceCaps(hdc, LOGPIXELSY), 72); + ReleaseDC(0, hdc); + } + else + { + lf->lfHeight = pCustomFont->size; + } + + _tcscpy(lf->lfFaceName, pCustomFont->szFace); + + lf->lfWidth = lf->lfEscapement = lf->lfOrientation = 0; + lf->lfWeight = pCustomFont->style & DBFONTF_BOLD ? FW_BOLD : FW_NORMAL; + lf->lfItalic = (pCustomFont->style & DBFONTF_ITALIC) != 0; + lf->lfUnderline = (pCustomFont->style & DBFONTF_UNDERLINE) != 0; + lf->lfStrikeOut = (pCustomFont->style & DBFONTF_STRIKEOUT) != 0; + lf->lfCharSet = pCustomFont->charset; + lf->lfOutPrecision = OUT_DEFAULT_PRECIS; + lf->lfClipPrecision = CLIP_DEFAULT_PRECIS; + lf->lfQuality = DEFAULT_QUALITY; + lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; +} + +static BOOL CreateStickyNoteFont(STICKYNOTEFONT *pCustomFont, LOGFONT *plf) +{ + LOGFONT lf = {0}; + + if (!plf) + { + InitStickyNoteLogFont(pCustomFont, &lf); + plf = &lf; + } + + if (pCustomFont->hFont) + DeleteObject(pCustomFont->hFont); + + pCustomFont->hFont = CreateFontIndirect(plf); + + return pCustomFont->hFont != NULL; +} + + +STICKYNOTE* NewNoteEx(int Ax,int Ay,int Aw,int Ah,char *Data,ULARGE_INTEGER *ID,BOOL Visible,BOOL OnTop,int scrollV,COLORREF bgClr,COLORREF fgClr,char *Title,STICKYNOTEFONT *pCustomFont,BOOL bLoading) +{ + STICKYNOTE* TSN; + WNDCLASSEX TWC = {0}; + WINDOWPLACEMENT TWP; + DWORD L1,L2; + SYSTEMTIME tm; + char TempStr[MAX_PATH] = {0}; + char *TData; + + const BOOL bIsStartup = Visible & 0x10000; + Visible &= ~0x10000; + + if (Data) TData = Data; else TData = NULL; + + if (!GetClassInfoEx(hmiranda, NOTE_WND_CLASS, &TWC)) + { + TWC.style = CS_HREDRAW | CS_VREDRAW | CS_NOCLOSE; + TWC.cbClsExtra = 0; + TWC.cbWndExtra = 0; + TWC.hInstance = hmiranda; + TWC.hIcon = LoadIcon(0,IDI_APPLICATION); + TWC.hCursor = LoadCursor(0,IDC_ARROW); + TWC.hbrBackground = 0; + TWC.lpszMenuName = 0; + TWC.lpszClassName = NOTE_WND_CLASS; + TWC.cbSize = sizeof(WNDCLASSEX); + TWC.lpfnWndProc = (WNDPROC)StickyNoteWndProc; + if (!RegisterClassEx(&TWC)) return NULL; + } + + if (!TData || Aw < 0 || Ah < 0) + { + TWP.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(GetDesktopWindow(),&TWP); + Aw = g_NoteWidth; Ah = g_NoteHeight; + Ax = ((TWP.rcNormalPosition.right - TWP.rcNormalPosition.left) / 2) - (Aw / 2); + Ay = ((TWP.rcNormalPosition.bottom - TWP.rcNormalPosition.top) / 2) - (Ah / 2); + } + + TSN = (STICKYNOTE*)malloc(sizeof(STICKYNOTE)); + + if (ID) + { + TSN->ID = *ID; + } + else + { + GetSystemTime(&tm); + SYSTEMTIMEtoFILETIME(&tm, (FILETIME*)&TSN->ID); + } + + EnsureUniqueID(TSN); + + TreeAdd(&g_Stickies,TSN); + + if (!TData) + { + TData = _strdup(""); + TSN->data = TData; + } + else + TSN->data = TData; + + // init note title (time-stamp) + if (Title) + { + TSN->title = Title; + TSN->CustomTitle = TRUE; + } + else + { + TSN->title = NULL; + InitNoteTitle(TSN); + } + + TSN->Visible = Visible; + TSN->OnTop = OnTop; + + TSN->BgColor = bgClr; + TSN->FgColor = fgClr; + + TSN->pCustomFont = pCustomFont; + + L1 = WS_EX_TOOLWINDOW; + if (MySetLayeredWindowAttributes && g_Transparency < 255) L1 |= WS_EX_LAYERED; + if (OnTop) L1 |= WS_EX_TOPMOST; + + L2 = WS_POPUP | WS_THICKFRAME | WS_CAPTION; + + // NOTE: loaded note positions stem from GetWindowPlacement, which normally have a different coord space than + // CreateWindow/SetWindowPos, BUT since we now use WS_EX_TOOLWINDOW they use the same coord space so + // we don't have to worry about notes "drifting" between sessions + TSN->SNHwnd = CreateWindowEx(L1, NOTE_WND_CLASS, _T("StickyNote"), L2, Ax,Ay,Aw,Ah, NULL, 0, hmiranda, TSN); + + if (MySetLayeredWindowAttributes && g_Transparency < 255) + MySetLayeredWindowAttributes(TSN->SNHwnd,0,(BYTE)g_Transparency,LWA_ALPHA); + + // ensure that window is not placed off-screen (if previous session had different monitor count or resolution) + // NOTE: SetWindowPlacement should do this, but it's extremly flakey + if (Data) + { + if (MyMonitorFromWindow && !MyMonitorFromWindow(TSN->SNHwnd, MONITOR_DEFAULTTONULL) ) + { + TWP.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(GetDesktopWindow(), &TWP); + + if (Aw > 500) Aw = 500; + if (Ay < TWP.rcNormalPosition.left+10 || Ax > TWP.rcNormalPosition.right-120) + Ax = ((TWP.rcNormalPosition.right - TWP.rcNormalPosition.left) / 2) - (Aw / 2) + (rand() & 0x3f); + if (Ay < TWP.rcNormalPosition.top+50 || Ay > TWP.rcNormalPosition.bottom-50) + Ay = ((TWP.rcNormalPosition.bottom - TWP.rcNormalPosition.top) / 4) + (rand() & 0x1f); + + SetWindowPos(TSN->SNHwnd, NULL, Ax, Ay, Aw, Ah, SWP_NOZORDER|SWP_NOACTIVATE); + } + } + + if (Visible) + { + ShowWindow(TSN->SNHwnd, SW_SHOWNA); + + // when loading notes (only at startup), place all non-top notes at the bottom so they don't cover other windows + if (Data && !OnTop && bIsStartup) + SetWindowPos(TSN->SNHwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|SWP_ASYNCWINDOWPOS); + } + + if (scrollV) + { + SendMessage(TSN->REHwnd, EM_LINESCROLL, 0, scrollV); + } + + // make sure that any event triggered by init doesn't cause a meaningless save + KillTimer(TSN->SNHwnd, 1025); + + if (!bLoading) + { + NOTIFY_LIST(); + } + + return TSN; +} + +STICKYNOTE* NewNote(int Ax,int Ay,int Aw,int Ah,char *Data,ULARGE_INTEGER *ID,BOOL Visible,BOOL OnTop,int scrollV) +{ + return NewNoteEx(Ax,Ay,Aw,Ah,Data,ID,Visible,OnTop,scrollV,0,0,NULL,NULL,FALSE); +} + +void LoadNotes(BOOL bIsStartup) +{ + int I; + int NotesCount; + WORD Size; + char *Value = NULL, *TVal = NULL; + char ValueName[32]; + + g_Stickies = NULL; + + NotesCount = ReadSettingInt(0,MODULENAME,"NotesData",0); + + for (I = 0; I < NotesCount; I++) + { + char *DelPos; + + sprintf(ValueName, "NotesData%d", I); + + if (Value) + { + FreeSettingBlob(Size, Value); + Value = NULL; + } + + Size = 65535; // does not get used + + ReadSettingBlob(0, MODULENAME, ValueName, &Size, (void**)&Value); + + if (!Size || !Value) + continue; // the setting could not be read from DB -> skip + + if (Value[0] == 'X') + { + // new eXtended/fleXible data format + + STICKYNOTE note = {0}; + int i, rect[4]; + int scrollV = 0; + STICKYNOTEFONT *pCustomFont = NULL; + DWORD flags; + + DelPos = strchr(Value+1,0x1B); + if (DelPos) + *DelPos = 0; + + // id:x:y:w:h:flags + + TVal = strchr(Value+1, ':'); + if (!TVal || (DelPos && TVal > DelPos)) + continue; + *TVal++ = 0; + + note.ID.QuadPart = _strtoui64(Value+1, NULL, 16); + + for (i=0; i<4; i++) + { + char *sep = strchr(TVal, ':'); + if (!sep || (DelPos && sep > DelPos)) + goto skip; + *sep++ = 0; + + rect[i] = strtol(TVal, NULL, 10); + + TVal = sep; + } + + flags = strtoul(TVal, NULL, 16); + + if (flags & 1) + note.Visible = TRUE; + if (flags & 2) + note.OnTop = TRUE; + + // optional \033 separated params + while (DelPos) + { + char *sep; + UINT tag; + + TVal = DelPos + 1; + // find param end and make sure it's null-terminated (if end of data then it's already null-terminated) + DelPos = strchr(TVal, 0x1B); + if (DelPos) + *DelPos = 0; + + // tag: + + sep = strchr(TVal, ':'); + if (!sep || (DelPos && sep > DelPos)) + goto skip; + + tag = strtoul(TVal, NULL, 10); + TVal = sep + 1; + + switch (tag) + { + case DATATAG_TEXT: + note.data = _strdup(TVal); + break; + + case DATATAG_SCROLLPOS: + scrollV = (int)strtoul(TVal, NULL, 10); + break; + + case DATATAG_BGCOL: + note.BgColor = strtoul(TVal, NULL, 16) | 0xff000000; + break; + + case DATATAG_FGCOL: + note.FgColor = strtoul(TVal, NULL, 16) | 0xff000000; + break; + + case DATATAG_TITLE: + if (strlen(TVal) > MAX_TITLE_LEN) + TVal[MAX_TITLE_LEN] = 0; + note.title = _strdup(TVal); + note.CustomTitle = TRUE; + break; + + case DATATAG_FONT: + { + int fsize; + UINT fstyle, fcharset; + + char *TVal2 = TVal; + sep = strchr(TVal2, ':'); + if (!sep || (DelPos && sep > DelPos)) + goto skip; + *sep++ = 0; + fsize = strtol(TVal2, NULL, 10); + TVal2 = sep; + + sep = strchr(TVal2, ':'); + if (!sep || (DelPos && sep > DelPos)) + goto skip; + *sep++ = 0; + fstyle = strtoul(TVal2, NULL, 10); + TVal2 = sep; + + sep = strchr(TVal2, ':'); + if (!sep || (DelPos && sep > DelPos)) + goto skip; + *sep++ = 0; + fcharset = strtoul(TVal2, NULL, 10); + TVal2 = sep; + + if (TVal2 >= DelPos) + goto skip; + + pCustomFont = (STICKYNOTEFONT*)malloc(sizeof(STICKYNOTEFONT)); + pCustomFont->size = (char)fsize; + pCustomFont->style = (BYTE)fstyle; + pCustomFont->charset = (BYTE)fcharset; + _tcscpy(pCustomFont->szFace, TVal2); + pCustomFont->hFont = NULL; + + if ( !CreateStickyNoteFont(pCustomFont, NULL) ) + { + free(pCustomFont); + pCustomFont = NULL; + } + } + break; + } + } + + if (!note.data) + note.data = _strdup(""); + + note.Visible = note.Visible && (!bIsStartup || g_ShowNotesAtStart); + if (bIsStartup) + note.Visible |= 0x10000; + + NewNoteEx(rect[0],rect[1],rect[2],rect[3],note.data,¬e.ID,note.Visible,note.OnTop,scrollV,note.BgColor,note.FgColor,note.title,pCustomFont,TRUE); + } + else + { + // old format (for DB backward compatibility) + + int Tx,Ty,Tw,Th,TV,OT; + BOOL V; + char *Data,*ID; + ULARGE_INTEGER newid; + + OT = 1; TV = 1; + Tx = 100; Ty = 100; + Tw = 179; Th = 35; + Data = NULL; ID = NULL; + + if (DelPos = strchr(Value,0x1B)) + { // get first delimiter + int PartLen = DelPos - TVal; + + Data = NULL; + ID = NULL; + TVal = Value; + DelPos[0] = 0x0; + Tx = strtol(TVal, NULL, 10); + + TVal = DelPos + 1; + DelPos = strchr(TVal, 0x1B); + if (!DelPos) continue; // setting is broken, do not crash + DelPos[0] = 0x0; + Ty = strtol(TVal, NULL, 10); + + TVal = DelPos + 1; + DelPos = strchr(TVal, 0x1B); + if (!DelPos) continue; // setting is broken, do not crash + DelPos[0] = 0x0; + Tw = strtol(TVal, NULL, 10); + + TVal = DelPos + 1; + DelPos = strchr(TVal, 0x1B); + if (!DelPos) continue; // setting is broken, do not crash + DelPos[0] = 0x0; + Th = strtol(TVal, NULL, 10); + + TVal = DelPos + 1; + DelPos = strchr(TVal, 0x1B); + if (!DelPos) continue; // setting is broken, do not crash + DelPos[0] = 0x0; + TV = strtol(TVal, NULL, 10); + + TVal = DelPos + 1; + DelPos = strchr(TVal, 0x1B); + if (!DelPos) continue; // setting is broken, do not crash + DelPos[0] = 0x0; + OT = strtol(TVal, NULL, 10); + + TVal = DelPos + 1; + DelPos = strchr(TVal, 0x1B); + if (!DelPos) continue; // setting is broken, do not crash + DelPos[0] = 0x0; + Data = _strdup(TVal); + + TVal = DelPos + 1; + ID = TVal; + + V = (BOOL)TV && (!bIsStartup || g_ShowNotesAtStart); + + if (bIsStartup) + V |= 0x10000; + + // convert old ID format to new + if ( strchr(ID, '-') ) + { + // validate format (otherwise create new) + if (strlen(ID) < 19 || ID[2] != '-' || ID[5] != '-' || ID[10] != ' ' || ID[13] != ':' || ID[16] != ':') + { + ID = NULL; + } + else + { + SYSTEMTIME tm; + + ID[2] = ID[5] = ID[10] = ID[13] = ID[16] = 0; + + memset(&tm, 0, sizeof(tm)); + tm.wDay = (WORD)strtoul(ID, NULL, 10); + tm.wMonth = (WORD)strtoul(ID+3, NULL, 10); + tm.wYear = (WORD)strtoul(ID+6, NULL, 10); + tm.wHour = (WORD)strtoul(ID+11, NULL, 10); + tm.wMinute = (WORD)strtoul(ID+14, NULL, 10); + tm.wSecond = (WORD)strtoul(ID+17, NULL, 10); + + SYSTEMTIMEtoFILETIME(&tm, (FILETIME*)&newid); + } + } + else + { + ID = NULL; + } + + NewNoteEx(Tx,Ty,Tw,Th,Data,ID?&newid:NULL,V,(BOOL)OT,0,0,0,NULL,NULL,TRUE); + } + } +skip:; + } + + if (Value) + FreeSettingBlob(Size, Value); // we do not leak on bad setting + + NOTIFY_LIST(); +} + +void CloseNotesList() +{ + if (ListNotesVisible) + { + DestroyWindow(LV); + ListNotesVisible = FALSE; + } +} + +static void PurgeNotesTree() +{ + STICKYNOTE *pt; + + while (g_Stickies) // empty whole tree + { + pt = (STICKYNOTE*)g_Stickies->ptrdata; + if (pt->SNHwnd) DestroyWindow(pt->SNHwnd); + SAFE_FREE((void**)&pt->title); + SAFE_FREE((void**)&pt->data); + if (pt->pCustomFont) + { + DeleteObject(pt->pCustomFont->hFont); + free(pt->pCustomFont); + } + TreeDelete(&g_Stickies,pt); + SAFE_FREE((void**)&pt); + } + g_Stickies = NULL; +} + +void SaveNotes(void) +{ + JustSaveNotes(); + PurgeNotesTree(); +} + +void PurgeNotes(void) +{ + int NotesCount, I; + char ValueName[16]; + + NotesCount = ReadSettingInt(0,MODULENAME,"NotesData",0); + for(I = 0; I < NotesCount; I++) + { + sprintf(ValueName, "NotesData%d", I); + DBDeleteContactSetting(0,MODULENAME,ValueName); + } +} + +void OnDeleteNote(HWND hdlg, STICKYNOTE *SN) +{ + if (MessageBox(hdlg, Translate("Are you sure you want to delete this note?"), SECTIONNAME, MB_OKCANCEL) == IDOK) + { + DestroyWindow(hdlg); + TreeDelete(&g_Stickies,SN); + SAFE_FREE((void**)&SN->data); + if (SN->pCustomFont) + { + DeleteObject(SN->pCustomFont->hFont); + free(SN->pCustomFont); + } + SAFE_FREE((void**)&SN); + JustSaveNotes(); + } +} + +void DeleteNotes(void) +{ + PurgeNotes(); + WriteSettingInt(0, MODULENAME, "NotesData", 0); + PurgeNotesTree(); +} + +void ShowHideNotes(void) +{ + TREEELEMENT *TTE; + int bShow; + UINT nHideCount, nVisCount; + BOOL Visible; + + if (!g_Stickies) + return; + + // if some notes are hidden but others visible then first make all visible + // only toggle vis state if all are hidden or all are visible + + nHideCount = nVisCount = 0; + TTE = g_Stickies; + while (TTE) + { + if (((STICKYNOTE*)TTE->ptrdata)->Visible) + nVisCount++; + else + nHideCount++; + + TTE = (TREEELEMENT*)TTE->next; + } + + if (!nVisCount) + Visible = TRUE; + else if (!nHideCount) + Visible = FALSE; + else + Visible = TRUE; + + bShow = Visible ? SW_SHOWNA : SW_HIDE; + + TTE = g_Stickies; + while (TTE) + { + STICKYNOTE *SN = (STICKYNOTE*)TTE->ptrdata; + + if ((!Visible) != (!SN->Visible)) + { + ShowWindow(SN->SNHwnd, bShow); + SN->Visible = Visible; + } + + TTE = (TREEELEMENT*)TTE->next; + } + + JustSaveNotes(); +} + +void BringAllNotesToFront(STICKYNOTE *pActive) +{ + TREEELEMENT *TTE; + + if (!g_Stickies) + return; + + // NOTE: for some reason there are issues when bringing to top through hotkey while another app (like Explorer) + // is active, it refuses to move notes to top like it should with HWND_TOP. as a workaround still doesn't + // work 100% of the time, but at least more often, we first move not to top-most then for non-always-on-top + // notes we demote them back as a non top-most window + + TTE = g_Stickies; + while (TTE) + { + STICKYNOTE *SN = (STICKYNOTE*)TTE->ptrdata; + + if (SN->Visible && pActive != SN) + { + SetWindowPos(SN->SNHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); + if (!SN->OnTop) + SetWindowPos(SN->SNHwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); + } + + TTE = (TREEELEMENT*)TTE->next; + } + + if (pActive) + { + SetWindowPos(pActive->SNHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); + if (!pActive->OnTop) + SetWindowPos(pActive->SNHwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); + } +} + +static void JustSaveNotesEx(STICKYNOTE *pModified) +{ + // pModified optionally points to the modified note that invoked the JustSaveNotesEx call + + TREEELEMENT *TTE; + int I, NotesCount = TreeGetCount(g_Stickies); + int n, l; + char ValueName[32]; + WINDOWPLACEMENT wp; + int TX,TY,TW,TH; + DWORD flags; + int SzT; + int scrollV; + char *tData, *Value; + + const int OldNotesCount = ReadSettingInt(0, MODULENAME, "NotesData", 0); + + WriteSettingInt(0, MODULENAME, "NotesData", NotesCount); + + for (TTE = g_Stickies, I = 0; TTE; TTE = (TREEELEMENT*)TTE->next, I++) + { + STICKYNOTE *pNote = (STICKYNOTE*)TTE->ptrdata; + BOOL bDeleteTData = TRUE; + scrollV = 0; + tData = NULL; + + // window pos and size + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(pNote->SNHwnd, &wp); + TX = wp.rcNormalPosition.left; + TY = wp.rcNormalPosition.top; + TW = wp.rcNormalPosition.right - wp.rcNormalPosition.left; + TH = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top; + + // set flags + flags = 0; + if (pNote->Visible) flags |= 1; + if (pNote->OnTop) flags |= 2; + + // get note text + SzT = SendMessage(pNote->REHwnd, WM_GETTEXTLENGTH, 0, 0); + if (SzT) // TODO: change to support unicode and rtf, use EM_STREAMOUT + { + if (SzT > MAX_NOTE_LEN) SzT = MAX_NOTE_LEN; // we want to be far below the 64k limit + tData = (char*)malloc(SzT+1); + if (tData) + SendMessage(pNote->REHwnd, WM_GETTEXT, SzT+1, (LPARAM)tData); + } + + if (pNote == pModified) + { + // update the data of the modified note + if (pNote->data) + free(pNote->data); + pNote->data = tData ? tData : _strdup(""); + bDeleteTData = FALSE; + } + + if (!tData) + // empty note + SzT = 0; + else + // get current scroll position + scrollV = SendMessage(pNote->REHwnd, EM_GETFIRSTVISIBLELINE, 0, 0); + + // + + Value = (char*)malloc(SzT + 512); + + if (!Value) + { + if (bDeleteTData) + SAFE_FREE((void**)&tData); + continue; + } + + n = 0; + + // data header + l = sprintf(Value, "X%I64x:%d:%d:%d:%d:%x", pNote->ID.QuadPart, TX, TY, TW, TH, flags); + if (l > 0) n += l; + + // scroll pos + if (scrollV > 0) + { + l = sprintf(Value+n, "\033""%u:%u", DATATAG_SCROLLPOS, (UINT)scrollV); + if (l > 0) n += l; + } + + // custom bg color + if (pNote->BgColor) + { + l = sprintf(Value+n, "\033""%u:%x", DATATAG_BGCOL, (UINT)(pNote->BgColor&0xffffff)); + if (l > 0) n += l; + } + + // custom fg color + if (pNote->FgColor) + { + l = sprintf(Value+n, "\033""%u:%x", DATATAG_FGCOL, (UINT)(pNote->FgColor&0xffffff)); + if (l > 0) n += l; + } + + if (pNote->pCustomFont) + { + l = sprintf(Value+n, "\033""%u:%d:%u:%u:%s", DATATAG_FONT, + (int)pNote->pCustomFont->size, (UINT)pNote->pCustomFont->style, (UINT)pNote->pCustomFont->charset, + pNote->pCustomFont->szFace); + if (l > 0) n += l; + } + + // custom title + if (pNote->CustomTitle && pNote->title) + { + l = sprintf(Value+n, "\033""%u:%s", DATATAG_TITLE, pNote->title); + if (l > 0) n += l; + } + + // note text (ALWAYS PUT THIS PARAM LAST) + if (tData) + { + l = sprintf(Value+n, "\033""%u:%s", DATATAG_TEXT, tData); + if (l > 0) n += l; + } + + // clamp data size to WORD (including null terminator) + if (n >= 0xffff) + { + // huston, we have a problem, strip some reminder text + n = 0xfffe; + ValueName[0xffff] = 0; + } + + sprintf(ValueName, "NotesData%d", NotesCount - I - 1); // we do not reverse notes in DB + + WriteSettingBlob(0, MODULENAME, ValueName, (WORD)(n+1), Value); + + SAFE_FREE((void**)&Value); + if (bDeleteTData) + SAFE_FREE((void**)&tData); + + // make no save is queued for the note + if (pNote->SNHwnd) + KillTimer(pNote->SNHwnd, 1025); + } + + // delete any left over DB note entries + for(; I < OldNotesCount; I++) + { + sprintf(ValueName, "NotesData%d", I); + DBDeleteContactSetting(0,MODULENAME,ValueName); + } + + NOTIFY_LIST(); +} + +__inline void JustSaveNotes(void) +{ + JustSaveNotesEx(NULL); +} + + +///////////////////////////////////////////////////////////////////// +// Note Window + +static int FindMenuItem(HMENU h, LPTSTR lpszName) +{ + int n; + UINT i; + TCHAR s[128]; + + n = GetMenuItemCount(h); + + if (n <= 0) + { + return -1; + } + + // searches for a menu item based on name (used to avoid hardcoding item indices for sub-menus) + for (i=0; i<(UINT)n; i++) + { + if ( GetMenuString(h, i, s, 128, MF_BYPOSITION) ) + { + if ( !_tcscmp(s, lpszName) ) + { + return (int)i; + } + } + } + + return -1; +} + +static BOOL DoContextMenu(HWND AhWnd,WPARAM wParam,LPARAM lParam) +{ + int n, i; + STICKYNOTE *SN = (STICKYNOTE*)GetProp(AhWnd,"ctrldata"); + + HMENU hMenuLoad, FhMenu, hSub; + hMenuLoad = LoadMenu(hinstance,"MNU_NOTEPOPUP"); + FhMenu = GetSubMenu(hMenuLoad,0); + + if (SN->OnTop) + CheckMenuItem(FhMenu, IDM_TOGGLEONTOP, MF_CHECKED|MF_BYCOMMAND); + + EnableMenuItem(FhMenu, ID_CONTEXTMENUNOTEPOPUP_PASTETITLE, MF_BYCOMMAND | (IsClipboardFormatAvailable(CF_TEXT) ? MF_ENABLED : MF_GRAYED)); + + if (!SN->CustomTitle) + EnableMenuItem(FhMenu, ID_CONTEXTMENUNOTEPOPUP_RESETTITLE, MF_BYCOMMAND | MF_GRAYED); + + // NOTE: names used for FindMenuItem would need to include & chars if such shortcuts are added to the menus + + // color preset menu items uses features that require win2k or later, I'm too lazy to make a fallback path + // for obsolete OS versions (win95/98/ME users can still use the "Custom" menu option) + if (g_isWin2kPlus) + { + n = FindMenuItem(FhMenu, _T("Appearance")); + if (n >= 0 && (hSub = GetSubMenu(FhMenu, n))) + { + HMENU hBg = GetSubMenu(hSub, FindMenuItem(hSub, _T("Background Color"))); + HMENU hFg = GetSubMenu(hSub, FindMenuItem(hSub, _T("Text Color"))); + + for (i=0; iszName); + SIZE sz; + GetTextExtentPoint32(hdc, lpsz, _tcslen(lpsz), &sz); + ReleaseDC(hdlg, hdc); + + lpMeasureItem->itemWidth = 50 + sz.cx; + lpMeasureItem->itemHeight = (sz.cy+2)>18 ? sz.cy+2 : 18; +} + +static void PaintColorPresetMenuItem(LPDRAWITEMSTRUCT lpDrawItem, struct ColorPreset *clrPresets) +{ + UINT n = lpDrawItem->itemID - IDM_COLORPRESET_BG; + RECT rect; + rect.left = lpDrawItem->rcItem.left + 50; + rect.top = lpDrawItem->rcItem.top; + rect.right = lpDrawItem->rcItem.right; + rect.bottom = lpDrawItem->rcItem.bottom; + + if (lpDrawItem->itemState & ODS_SELECTED) + { + SetDCBrushColor(lpDrawItem->hDC, GetSysColor(COLOR_MENUHILIGHT)); + FillRect(lpDrawItem->hDC, &lpDrawItem->rcItem, (HBRUSH)GetStockObject(DC_BRUSH)); + + SetTextColor(lpDrawItem->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT)); + } + else + { + SetDCBrushColor(lpDrawItem->hDC, GetSysColor(COLOR_MENU)); + FillRect(lpDrawItem->hDC, &lpDrawItem->rcItem, (HBRUSH)GetStockObject(DC_BRUSH)); + + SetTextColor(lpDrawItem->hDC, GetSysColor(COLOR_MENUTEXT)); + } + + SetBkMode(lpDrawItem->hDC, TRANSPARENT); + DrawText(lpDrawItem->hDC,clrPresets->szName,-1,&rect,DT_LEFT | DT_SINGLELINE | DT_END_ELLIPSIS | DT_VCENTER); + + { + int h = lpDrawItem->rcItem.bottom - lpDrawItem->rcItem.top; + rect.left = lpDrawItem->rcItem.left + 5; + rect.top = lpDrawItem->rcItem.top + ((h-14)>>1); + rect.right = rect.left + 40; + rect.bottom = rect.top + 14; + + FrameRect(lpDrawItem->hDC, &rect, (HBRUSH)GetStockObject(BLACK_BRUSH)); + rect.left++; rect.top++; + rect.right--; rect.bottom--; + SetDCBrushColor(lpDrawItem->hDC, clrPresets->color); + FillRect(lpDrawItem->hDC, &rect, (HBRUSH)GetStockObject(DC_BRUSH)); + } +} + +static BOOL GetClipboardText_Title(char *pOut, int size) +{ + BOOL bResult = FALSE; + + if ( OpenClipboard(NULL) ) + { + HANDLE hData = GetClipboardData(CF_TEXT); + LPCSTR buffer; + + if (hData && (buffer = (LPCSTR)GlobalLock(hData))) + { + // trim initial white spaces + while (*buffer && isspace(*buffer)) + buffer++; + + if (buffer) + { + char *p; + + int n = (int)strlen(buffer); + if (n >= size) + n = size-1; + memcpy(pOut, buffer, n); + pOut[n] = 0; + + // end string on line break and convert tabs to spaces + p = pOut; + while (*p) + { + if (*p == '\r' || *p == '\n') + { + *p = 0; + n = strlen(pOut); + break; + } + else if (*p == '\t') + { + *p = ' '; + } + p++; + } + + // trim trailing white spaces + while (n && isspace(pOut[n-1])) + pOut[--n] = 0; + + if (n) + bResult = TRUE; + } + + GlobalUnlock(hData); + } + + CloseClipboard(); + } + + return bResult; +} + +static void SetNoteTextControl(STICKYNOTE *SN) +{ + CHARFORMAT CF = {0}; + CF.cbSize = sizeof(CHARFORMAT); + CF.dwMask = CFM_COLOR; + CF.crTextColor = SN->FgColor ? (SN->FgColor&0xffffff) : BodyFontColor; + SendMessage(SN->REHwnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&CF); + + if (SN->data) // TODO: use EM_STREAMIN + SendMessage(SN->REHwnd, WM_SETTEXT, 0, (LPARAM)(SN->data)); +} + + +static UINT_PTR CALLBACK CFHookProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_INITDIALOG) + { + // hide color selector + ShowWindow(GetDlgItem(hdlg,0x443), SW_HIDE); + ShowWindow(GetDlgItem(hdlg,0x473), SW_HIDE); + TranslateDialogDefault(hdlg); + } + + return 0; +} + + +int CALLBACK StickyNoteWndProc(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + switch (message) + { + case WM_CLOSE: + return TRUE; + + case WM_SIZE: + { + HWND H; + RECT SZ; + + GetClientRect(hdlg,&SZ); + H = GetDlgItem(hdlg,1); + MoveWindow(H, 0, 0, SZ.right,SZ.bottom, TRUE); + + KillTimer(hdlg, 1025); + SetTimer(hdlg, 1025, NOTE_CHANGE_COMMIT_DELAY, 0); + + return TRUE; + } + case WM_TIMER: + if (wParam == 1025) + { + STICKYNOTE *SN = (STICKYNOTE*)GetProp(hdlg,"ctrldata"); + + KillTimer(hdlg, 1025); + JustSaveNotesEx(SN); + } + break; + case WM_MOVE: + { + KillTimer(hdlg, 1025); + SetTimer(hdlg, 1025, NOTE_CHANGE_COMMIT_DELAY, 0); + return TRUE; + } + case WM_CREATE: + { + STICKYNOTE *SN = (STICKYNOTE*)GetProp(hdlg,"ctrldata"); + + CREATESTRUCT *CS = (CREATESTRUCT *)lParam; + HWND H; + DWORD mystyle; + + SN = (STICKYNOTE*)CS->lpCreateParams; + SetProp(hdlg,"ctrldata",(HANDLE)SN); + BringWindowToTop(hdlg); + mystyle = WS_CHILD | WS_VISIBLE | ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN; + if (g_ShowScrollbar) mystyle |= WS_VSCROLL; + H = CreateWindow(RICHEDIT_CLASS, 0, mystyle, 0, 0, CS->cx-3-3, CS->cy-3-(3+14), hdlg, (HMENU)1, hmiranda, 0); + SN->REHwnd = H; + SendMessage(H, EM_SETTEXTMODE, TM_PLAINTEXT, 0); + SendMessage(H, EM_LIMITTEXT, MAX_NOTE_LEN, 0); + SendMessage(H, WM_SETFONT, (WPARAM)(SN->pCustomFont ? SN->pCustomFont->hFont : hBodyFont), 1); + SendMessage(H, EM_SETEVENTMASK, 0, ENM_CHANGE | ENM_LINK); + SendMessage(H, EM_SETBKGNDCOLOR, 0, SN->BgColor ? (SN->BgColor&0xffffff) : BodyColor); + SendMessage(H, EM_AUTOURLDETECT, 1, 0); + SetNoteTextControl(SN); + return TRUE; + } + case WM_GETMINMAXINFO: + { + MINMAXINFO *mm = (MINMAXINFO*)lParam; + // min width accomodates frame, buttons and some extra space for sanity + mm->ptMinTrackSize.x = 48+3+3+8 + 40; + // min height allows collapsing entire client area, only leaving frame and caption + mm->ptMinTrackSize.y = 3+3+14; + } + return 0; + case WM_ERASEBKGND: + // no BG needed as edit control takes up entire client area + return TRUE; + case WM_NCPAINT: + // make window borders have the same color as caption + { + STICKYNOTE *SN = (STICKYNOTE*)GetProp(hdlg,"ctrldata"); + + HBRUSH hBkBrush; + RECT rect, wr, r; + //HDC hdc = GetDCEx(hdlg, (HRGN)wParam, DCX_WINDOW|DCX_INTERSECTRGN); + HDC hdc = GetWindowDC(hdlg); + + GetWindowRect(hdlg, &wr); + if (wParam && wParam != 1) + { + SelectClipRgn(hdc, (HRGN)wParam); + OffsetClipRgn(hdc, -wr.left, -wr.top); + } + + rect = wr; + OffsetRect(&rect, -wr.left, -wr.top); + + if (g_isWin2kPlus) + { + hBkBrush = (HBRUSH)GetStockObject(DC_BRUSH); + SetDCBrushColor(hdc, GetCaptionColor((SN && SN->BgColor) ? SN->BgColor : BodyColor)); + } + else + { + hBkBrush = (HBRUSH)CreateSolidBrush( GetCaptionColor((SN && SN->BgColor) ? SN->BgColor : BodyColor) ); + } + + //FillRect(hdc, &rect, hBkBrush); + // draw all frame sides separately to avoid filling client area (which flickers) + { + // top + r.left = rect.left; r.right = rect.right; + r.top = rect.top; r.bottom = r.top + 3+14; + FillRect(hdc, &r, hBkBrush); + // bottom + r.top = rect.bottom - 3; r.bottom = rect.bottom; + FillRect(hdc, &r, hBkBrush); + // left + r.left = rect.left; r.right = r.left + 3; + r.top = rect.top + 3+14; r.bottom = rect.bottom - 3; + FillRect(hdc, &r, hBkBrush); + // right + r.left = rect.right - 3; r.right = rect.right; + FillRect(hdc, &r, hBkBrush); + } + if (hBkBrush && !g_isWin2kPlus) DeleteObject(hBkBrush); + + // paint title bar contents (time stamp and buttons) + + if (SN && SN->title) + { + RECT R; + SelectObject(hdc,hCaptionFont); + R.top = 3+1; R.bottom = 3+11; R.left = 3+2; R.right = rect.right-3-1; + if (g_ShowNoteButtons) + R.right -= 48; + + SetTextColor(hdc,SN->FgColor ? (SN->FgColor&0xffffff) : CaptionFontColor); + SetBkMode(hdc, TRANSPARENT); + DrawText(hdc,SN->title,-1,&R,DT_LEFT | DT_SINGLELINE | DT_END_ELLIPSIS | DT_VCENTER); + } + + if (g_ShowNoteButtons) + { + HICON hcIcon; + if (SN->OnTop) + hcIcon = Skin_GetIconByHandle(hIconLibItem[4]); + else + hcIcon = Skin_GetIconByHandle(hIconLibItem[7]); + DrawIcon(hdc, wr.right - wr.left - 16, 0 + 3, hcIcon); + Skin_ReleaseIcon(hcIcon); + + hcIcon = Skin_GetIconByHandle(hIconLibItem[9]); + DrawIcon(hdc, wr.right - wr.left - 32, 1 + 3, hcIcon); + Skin_ReleaseIcon(hcIcon); + + hcIcon = Skin_GetIconByHandle(hIconLibItem[8]); + DrawIcon(hdc, wr.right - wr.left - 48, 1 + 3, hcIcon); + Skin_ReleaseIcon(hcIcon); + } + + if (wParam && wParam != 1) + { + SelectClipRgn(hdc, NULL); + } + + ReleaseDC(hdlg, hdc); + return TRUE; + } + case WM_NCCALCSIZE: + { + RECT *pRect = wParam ? &((NCCALCSIZE_PARAMS*)lParam)->rgrc[0] : (RECT*)lParam; + pRect->bottom -= 3; + pRect->right -= 3; + pRect->left += 3; + pRect->top += 3+14; + return WVR_REDRAW; + } + case WM_NCACTIVATE: + // update window (so that parts that potentially became visible through activation get redrawn immediately) + RedrawWindow(hdlg, NULL, NULL, RDW_UPDATENOW); + return TRUE; + case WM_NOTIFY: + if (LOWORD(wParam) == 1) + { + char *Buff; + PENLINK PEnLnk = (PENLINK)lParam; + + if (PEnLnk->msg == WM_LBUTTONDOWN) + { + SendDlgItemMessage(hdlg,1,EM_EXSETSEL,0,(LPARAM)&(PEnLnk->chrg)); + Buff = (char*)malloc(PEnLnk->chrg.cpMax - PEnLnk->chrg.cpMin + 1); + SendDlgItemMessage(hdlg,1,EM_GETSELTEXT,0,(LPARAM)Buff); + if ((GetAsyncKeyState(VK_CONTROL) >> 15) != 0) + ShellExecute(hdlg,"open","iexplore",Buff,"",SW_SHOWNORMAL); + else if (g_lpszAltBrowser && *g_lpszAltBrowser) + ShellExecute(hdlg,"open",g_lpszAltBrowser,Buff,"",SW_SHOWNORMAL); + else + ShellExecute(hdlg,"open",Buff,"","",SW_SHOWNORMAL); + SAFE_FREE((void**)&Buff); + return TRUE; + } + return FALSE; + } + break; + case WM_NCHITTEST: + { + int r = DefWindowProc(hdlg,message,wParam,lParam); + // filter out potential hits on windows default title bar buttons + switch (r) + { + case HTSYSMENU: + case HTCLOSE: + case HTMINBUTTON: + case HTMAXBUTTON: + return HTCAPTION; + } + return r; + } + case WM_NCLBUTTONDOWN: + if (wParam == HTCAPTION && g_ShowNoteButtons) + { + long X,Y; + RECT rect; + int Tw; + + GetWindowRect(hdlg, &rect); + Tw = rect.right - rect.left; + + X = LOWORD(lParam) - rect.left; + Y = HIWORD(lParam) - rect.top; + + if (X > Tw - 16) + { + SendMessage(hdlg,WM_COMMAND,IDM_TOGGLEONTOP,0); + return TRUE; + } + else if (X > Tw - 31 && X < Tw - 16) + { + SendMessage(hdlg,WM_COMMAND,IDM_REMOVENOTE,0); + return TRUE; + } + else if (X > Tw - 48 && X < Tw - 32) + { + SendMessage(hdlg,WM_COMMAND,IDM_HIDENOTE,0); + return TRUE; + } + } + return DefWindowProc(hdlg,message,wParam,lParam); + case WM_MEASUREITEM: + { + LPMEASUREITEMSTRUCT lpMeasureItem = (LPMEASUREITEMSTRUCT)lParam; + + if (lpMeasureItem->CtlType != ODT_MENU) + break; + + if (lpMeasureItem->itemID >= IDM_COLORPRESET_BG && lpMeasureItem->itemID <= IDM_COLORPRESET_BG+SIZEOF(clrPresets)) + { + MeasureColorPresetMenuItem(hdlg, lpMeasureItem, clrPresets + (lpMeasureItem->itemID - IDM_COLORPRESET_BG)); + return TRUE; + } + else if (lpMeasureItem->itemID >= IDM_COLORPRESET_FG && lpMeasureItem->itemID <= IDM_COLORPRESET_FG+SIZEOF(clrPresets)) + { + MeasureColorPresetMenuItem(hdlg, lpMeasureItem, clrPresets + (lpMeasureItem->itemID - IDM_COLORPRESET_FG)); + return TRUE; + } + } + break; + case WM_DRAWITEM: + if (!wParam) + { + LPDRAWITEMSTRUCT lpDrawItem = (LPDRAWITEMSTRUCT)lParam; + + if (lpDrawItem->CtlType != ODT_MENU) + break; + + if (lpDrawItem->itemID >= IDM_COLORPRESET_BG && lpDrawItem->itemID <= IDM_COLORPRESET_BG+SIZEOF(clrPresets)) + { + PaintColorPresetMenuItem(lpDrawItem, clrPresets + (lpDrawItem->itemID - IDM_COLORPRESET_BG)); + return TRUE; + } + else if (lpDrawItem->itemID >= IDM_COLORPRESET_FG && lpDrawItem->itemID <= IDM_COLORPRESET_FG+SIZEOF(clrPresets)) + { + PaintColorPresetMenuItem(lpDrawItem, clrPresets + (lpDrawItem->itemID - IDM_COLORPRESET_FG)); + return TRUE; + } + } + break; + case WM_COMMAND: + { + STICKYNOTE *SN = (STICKYNOTE*)GetProp(hdlg,"ctrldata"); + + HWND H; + UINT id; + + switch ( HIWORD(wParam) ) + { + case EN_CHANGE: + case EN_VSCROLL: + case EN_HSCROLL: + { + KillTimer(hdlg,1025); + SetTimer(hdlg, 1025, NOTE_CHANGE_COMMIT_DELAY, 0); + } + break; + } + + id = (UINT) LOWORD(wParam); + + H = SN->REHwnd; + + if (id >= IDM_COLORPRESET_BG && id <= IDM_COLORPRESET_BG+SIZEOF(clrPresets)) + { + SN->BgColor = clrPresets[id-IDM_COLORPRESET_BG].color | 0xff000000; + SendMessage(H, EM_SETBKGNDCOLOR, 0, (LPARAM)(SN->BgColor&0xffffff)); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + return FALSE; + } + else if (id >= IDM_COLORPRESET_FG && id <= IDM_COLORPRESET_FG+SIZEOF(clrPresets)) + { + CHARFORMAT CF = {0}; + SN->FgColor = clrPresets[id-IDM_COLORPRESET_FG].color | 0xff000000; + CF.cbSize = sizeof(CHARFORMAT); + CF.dwMask = CFM_COLOR; + CF.crTextColor = SN->FgColor & 0xffffff; + SendMessage(H, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&CF); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + return FALSE; + } + + switch (id) + { + case ID_CONTEXTMENUNOTEPOPUP_NEWNOTE: + { + PluginMenuCommandAddNew(0,0); + } + break; + case ID_APPEARANCE_CUSTOMBG: + { + COLORREF custclr[16] = {0}; + CHOOSECOLOR cc = {0}; + COLORREF orgclr = SN->BgColor ? (COLORREF)(SN->BgColor&0xffffff) : (COLORREF)(BodyColor&0xffffff); + cc.lStructSize = sizeof(cc); + cc.hwndOwner = SN->SNHwnd; + cc.rgbResult = orgclr; + cc.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_RGBINIT | CC_SOLIDCOLOR; + cc.lpCustColors = custclr; + + if (ChooseColor(&cc) && cc.rgbResult != orgclr) + { + SN->BgColor = cc.rgbResult | 0xff000000; + SendMessage(H, EM_SETBKGNDCOLOR, 0, (LPARAM)(SN->BgColor&0xffffff)); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + } + } + break; + case ID_APPEARANCE_CUSTOMTEXT: + { + COLORREF custclr[16] = {0}; + CHOOSECOLOR cc = {0}; + COLORREF orgclr = SN->FgColor ? (COLORREF)(SN->FgColor&0xffffff) : (COLORREF)(BodyFontColor&0xffffff); + cc.lStructSize = sizeof(cc); + cc.hwndOwner = SN->SNHwnd; + cc.rgbResult = orgclr; + cc.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_RGBINIT | CC_SOLIDCOLOR; + cc.lpCustColors = custclr; + + if (ChooseColor(&cc) && cc.rgbResult != orgclr) + { + CHARFORMAT CF = {0}; + SN->FgColor = cc.rgbResult | 0xff000000; + CF.cbSize = sizeof(CHARFORMAT); + CF.dwMask = CFM_COLOR; + CF.crTextColor = SN->FgColor & 0xffffff; + SendMessage(H, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&CF); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + } + } + break; + case ID_APPEARANCE_CUSTOMFONT: + { + CHOOSEFONT cf = {0}; + LOGFONT lf = {0}; + + if (SN->pCustomFont) + InitStickyNoteLogFont(SN->pCustomFont, &lf); + else + LoadNRFont(NR_FONTID_BODY, &lf, NULL); + + cf.lStructSize = sizeof(cf); + cf.hwndOwner = SN->SNHwnd; + cf.lpLogFont = &lf; + cf.Flags = CF_EFFECTS | CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS | CF_ENABLEHOOK; + cf.lpfnHook = CFHookProc; + + if ( ChooseFont(&cf) ) + { + if (!SN->pCustomFont) + { + SN->pCustomFont = (STICKYNOTEFONT*)malloc(sizeof(STICKYNOTEFONT)); + SN->pCustomFont->hFont = NULL; + } + + SN->pCustomFont->size = (char)lf.lfHeight; + SN->pCustomFont->style = (lf.lfWeight >= FW_BOLD ? DBFONTF_BOLD : 0) | (lf.lfItalic ? DBFONTF_ITALIC : 0) | (lf.lfUnderline ? DBFONTF_UNDERLINE : 0) | (lf.lfStrikeOut ? DBFONTF_STRIKEOUT : 0); + SN->pCustomFont->charset = lf.lfCharSet; + _tcscpy(SN->pCustomFont->szFace, lf.lfFaceName); + + if ( !CreateStickyNoteFont(SN->pCustomFont, &lf) ) + { + // failed + free(SN->pCustomFont); + SN->pCustomFont = NULL; + } + + // clear text first to force a reformatting w.r.t scrollbar + SendMessage(H, WM_SETTEXT, 0, (LPARAM)""); + SendMessage(H, WM_SETFONT, (WPARAM)(SN->pCustomFont ? SN->pCustomFont->hFont : hBodyFont), FALSE); + SetNoteTextControl(SN); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + } + } + break; + case ID_BACKGROUNDCOLOR_RESET: + { + SN->BgColor = 0; + SendMessage(H, EM_SETBKGNDCOLOR, 0, (LPARAM)BodyColor); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + } + break; + case ID_TEXTCOLOR_RESET: + { + CHARFORMAT CF = {0}; + SN->FgColor = 0; + CF.cbSize = sizeof(CHARFORMAT); + CF.dwMask = CFM_COLOR; + CF.crTextColor = BodyFontColor; + SendMessage(H, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&CF); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + } + break; + case ID_FONT_RESET: + { + if (SN->pCustomFont) + { + DeleteObject(SN->pCustomFont->hFont); + free(SN->pCustomFont); + SN->pCustomFont = NULL; + + // clear text first to force a reformatting w.r.t scrollbar + SendMessage(H, WM_SETTEXT, 0, (LPARAM)""); + SendMessage(H, WM_SETFONT, (WPARAM)hBodyFont, FALSE); + SetNoteTextControl(SN); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + } + } + break; + case ID_CONTEXTMENUNOTEPOPUP_PASTETITLE: + { + char s[MAX_TITLE_LEN+1]; + if ( GetClipboardText_Title(s, sizeof(s)) ) + { + if (SN->title) + free(SN->title); + SN->title = _strdup(s); + SN->CustomTitle = TRUE; + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + } + } + break; + case ID_CONTEXTMENUNOTEPOPUP_RESETTITLE: + if (SN->CustomTitle) + { + if (SN->title) + { + free(SN->title); + SN->title = NULL; + } + InitNoteTitle(SN); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_INVALIDATE|RDW_FRAME|RDW_UPDATENOW); + JustSaveNotes(); + } + break; + case IDM_REMOVENOTE: + OnDeleteNote(hdlg, SN); + break; + case IDM_HIDENOTE: + { + SN->Visible = FALSE; + ShowWindow(hdlg,SW_HIDE); + JustSaveNotes(); + } + break; + case IDM_COPY: SendMessage(H,WM_COPY,0,0); break; + case IDM_PASTE: SendMessage(H,WM_PASTE,0,0); break; + case IDM_CUT: SendMessage(H,WM_CUT,0,0); break; + case IDM_CLEAR: SendMessage(H,WM_CLEAR,0,0); break; + case IDM_UNDO: SendMessage(H,WM_UNDO,0,0); break; + case IDM_TOGGLEONTOP: + { + SN->OnTop = !SN->OnTop; + SetWindowPos(hdlg, SN->OnTop ? HWND_TOPMOST : HWND_NOTOPMOST, 0,0,0,0, SWP_SHOWWINDOW|SWP_NOMOVE|SWP_NOSIZE); + RedrawWindow(hdlg, NULL, NULL, RDW_FRAME|RDW_INVALIDATE|RDW_UPDATENOW); + JustSaveNotes(); + } + break; + case ID_CONTEXTMENUNOTEPOPUP_VIEWNOTES: + ListNotes(); + break; + case ID_CONTEXTMENUNOTEPOPUP_BRINGALLTOTOP: + BringAllNotesToFront(SN); + break; + } + return TRUE; + } + case WM_NCDESTROY: + { + RemoveProp(hdlg, "ctrldata"); + } + break; + case WM_CONTEXTMENU: + if (DoContextMenu(hdlg,wParam,lParam)) return FALSE; + + default: + return DefWindowProc(hdlg,message,wParam,lParam); + } + return FALSE; +} + + +///////////////////////////////////////////////////////////////////// +// Notes List Dialog (uses same dialog template as reminder list) + +void ListNotes(void) +{ + if (!ListNotesVisible) + { + CreateDialog(hinstance, MAKEINTRESOURCE(IDD_LISTREMINDERS), 0, DlgProcViewNotes); + ListNotesVisible = TRUE; + } + else + { + BringWindowToTop(LV); + } +} + +static void EditNote(STICKYNOTE *SN) +{ + if (!SN) + return; + + if (!SN->Visible) + { + SN->Visible = TRUE; + JustSaveNotes(); + } + + SetWindowPos(SN->SNHwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); + if (!SN->OnTop) + SetWindowPos(SN->SNHwnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE); + + SetFocus(SN->REHwnd); +} + +char* GetPreviewString(const char *lpsz) +{ + int l; + char *p; + const int MaxLen = 80; + static char s[80+8]; + + if (!lpsz) + return ""; + + // trim leading spaces + while ( iswspace(*lpsz) ) + lpsz++; + + l = strlen(lpsz); + + if (!l) + return ""; + + if (l <= MaxLen) + { + strcpy(s, lpsz); + } + else + { + memcpy(s, lpsz, MaxLen); + s[MaxLen] = '.'; + s[MaxLen+1] = '.'; + s[MaxLen+2] = '.'; + s[MaxLen+3] = 0; + } + + if (!s) + return NULL; + + // convert line breaks and tabs to spaces + + p = s; + + while (*p) + { + if ( iswspace(*p) ) + *p = ' '; + p++; + } + + return s; +} + +static void InitListView(HWND AHLV) +{ + LV_ITEM lvTIt; + int I; + char *S; + char S1[128]; + STICKYNOTE *pNote; + TREEELEMENT *TTE; + + char *V = Translate("V"); + char *T = Translate("T"); + + ListView_SetHoverTime(AHLV,700); + ListView_SetExtendedListViewStyle(AHLV,LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_TRACKSELECT); + ListView_DeleteAllItems(AHLV); + + I = 0; + TTE = g_Stickies; + while (TTE) + { + pNote = (STICKYNOTE*)TTE->ptrdata; + + lvTIt.mask = LVIF_TEXT; + + if (!pNote->CustomTitle || !pNote->title) + GetTriggerTimeString(&pNote->ID, S1, sizeof(S1), TRUE); + + lvTIt.iItem = I; + lvTIt.iSubItem = 0; + lvTIt.pszText = (pNote->CustomTitle && pNote->title) ? pNote->title : S1; + lvTIt.cchTextMax = strlen(S1); + ListView_InsertItem(AHLV,&lvTIt); + + if (pNote->Visible) + { + lvTIt.iItem = I; + lvTIt.iSubItem = 1; + lvTIt.pszText = V; + lvTIt.cchTextMax = strlen(lvTIt.pszText); + ListView_SetItem(AHLV,&lvTIt); + } + + if (pNote->OnTop) + { + lvTIt.iItem = I; + lvTIt.iSubItem = 2; + lvTIt.pszText = T; + lvTIt.cchTextMax = strlen(lvTIt.pszText); + ListView_SetItem(AHLV,&lvTIt); + } + + S = GetPreviewString(pNote->data); + lvTIt.iItem = I; + lvTIt.iSubItem = 3; + lvTIt.pszText = S; + lvTIt.cchTextMax = strlen(S); + ListView_SetItem(AHLV,&lvTIt); + + I++; + TTE = (TREEELEMENT*)TTE->next; + } + + ListView_SetItemState(AHLV,0,LVIS_SELECTED,LVIS_SELECTED); +} + +static BOOL DoListContextMenu(HWND AhWnd,WPARAM wParam,LPARAM lParam,STICKYNOTE *pNote) +{ + HWND hwndListView; + HMENU hMenuLoad,FhMenu; + MENUITEMINFO mii; + + hwndListView = (HWND)wParam; + if (hwndListView != GetDlgItem(AhWnd,IDC_LISTREMINDERS)) return FALSE; + hMenuLoad = LoadMenu(hinstance,"MNU_NOTELISTPOPUP"); + FhMenu = GetSubMenu(hMenuLoad,0); + + mii.cbSize = sizeof(mii); + mii.fMask = MIIM_STATE; + mii.fState = MFS_DEFAULT; + if (!pNote) + mii.fState |= MFS_GRAYED; + SetMenuItemInfo(FhMenu, ID_CONTEXTMENUNOTELISTVIEW_EDITNOTE, FALSE, &mii); + + if (!pNote) + { + EnableMenuItem(FhMenu, IDM_REMOVENOTE, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(FhMenu, ID_CONTEXTMENUNOTELISTVIEW_TOGGLEVISIBILITY, MF_GRAYED|MF_BYCOMMAND); + EnableMenuItem(FhMenu, IDM_TOGGLEONTOP, MF_GRAYED|MF_BYCOMMAND); + } + else + { + if (pNote->Visible) + CheckMenuItem(FhMenu, ID_CONTEXTMENUNOTELISTVIEW_TOGGLEVISIBILITY, MF_CHECKED|MF_BYCOMMAND); + if (pNote->OnTop) + CheckMenuItem(FhMenu, IDM_TOGGLEONTOP, MF_CHECKED|MF_BYCOMMAND); + } + + CallService(MS_LANGPACK_TRANSLATEMENU,(DWORD)FhMenu,0); + TrackPopupMenu(FhMenu,TPM_LEFTALIGN | TPM_RIGHTBUTTON,LOWORD(lParam),HIWORD(lParam),0,AhWnd,0); + DestroyMenu(hMenuLoad); + + return TRUE; +} + + +int CALLBACK DlgProcViewNotes(HWND Dialog,UINT Message,WPARAM wParam,LPARAM lParam) +{ + LV_COLUMN lvCol; + NMLISTVIEW *NM; + char *S; + int I; + + switch (Message) + { + case WM_SIZE: + { + OnListResize(Dialog); + UpdateGeomFromWnd(Dialog, g_notesListGeom, NULL, 0); + break; + } + case WM_MOVE: + UpdateGeomFromWnd(Dialog, g_notesListGeom, NULL, 0); + break; + case WM_GETMINMAXINFO: + { + MINMAXINFO *mm = (MINMAXINFO*)lParam; + mm->ptMinTrackSize.x = 394; + mm->ptMinTrackSize.y = 300; + } + return 0; + case WM_RELOAD: + { + SetDlgItemText(Dialog,IDC_REMINDERDATA,""); + InitListView(GetDlgItem(Dialog,IDC_LISTREMINDERS)); + return TRUE; + } + case WM_CONTEXTMENU: + { + HWND H; + STICKYNOTE *pNote = NULL; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + pNote = (STICKYNOTE*)TreeGetAt(g_Stickies, I); + } + } + + if (DoListContextMenu(Dialog, wParam, lParam, pNote)) + return TRUE; + } + break; + case WM_INITDIALOG: + { + HWND H; + + HICON hIcon = Skin_GetIconByHandle(hIconLibItem[13], ICON_SMALL); + SendMessage(Dialog, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon); + hIcon = Skin_GetIconByHandle(hIconLibItem[13], ICON_BIG); + SendMessage(Dialog, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); + + SetWindowText(Dialog, _T("Notes")); + + TranslateDialogDefault(Dialog); + + SetDlgItemText(Dialog,IDC_REMINDERDATA,""); + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + lvCol.mask = LVCF_TEXT | LVCF_WIDTH; + + S = Translate("Note text"); + lvCol.pszText = S; + lvCol.cchTextMax = strlen(S); + lvCol.cx = g_notesListColGeom[3]; + ListView_InsertColumn(H,0,&lvCol); + lvCol.mask = LVCF_TEXT | LVCF_WIDTH; + + S = Translate("T"); + lvCol.pszText = S; + lvCol.cchTextMax = strlen(S); + lvCol.cx = g_notesListColGeom[2]; + ListView_InsertColumn(H,0,&lvCol); + lvCol.mask = LVCF_TEXT | LVCF_WIDTH; + + S = Translate("V"); + lvCol.pszText = S; + lvCol.cchTextMax = strlen(S); + lvCol.cx = g_notesListColGeom[1]; + ListView_InsertColumn(H,0,&lvCol); + lvCol.mask = LVCF_TEXT | LVCF_WIDTH; + + S = Translate("Date/Title"); + lvCol.pszText = S; + lvCol.cchTextMax = strlen(S); + lvCol.cx = g_notesListColGeom[0]; + ListView_InsertColumn(H,0,&lvCol); + + InitListView(H); + SetWindowLong(GetDlgItem(H, 0), GWL_ID, IDC_LISTREMINDERS_HEADER); + LV = Dialog; + + if (g_notesListGeom[1] && g_notesListGeom[2]) + { + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(Dialog, &wp); + wp.rcNormalPosition.left = g_notesListGeom[0]; + wp.rcNormalPosition.top = g_notesListGeom[1]; + wp.rcNormalPosition.right = g_notesListGeom[2] + g_notesListGeom[0]; + wp.rcNormalPosition.bottom = g_notesListGeom[3] + g_notesListGeom[1]; + SetWindowPlacement(Dialog, &wp); + } + return TRUE; + } + case WM_CLOSE: + { + DestroyWindow(Dialog); + ListNotesVisible = FALSE; + return TRUE; + } + case WM_DESTROY: + ListNotesVisible = FALSE; + CallService(MS_SKIN2_RELEASEICONBIG, (WPARAM)SendMessage(Dialog, WM_SETICON, ICON_BIG, (LPARAM)NULL), 0); + CallService(MS_SKIN2_RELEASEICON, (WPARAM)SendMessage(Dialog, WM_SETICON, ICON_SMALL, (LPARAM)NULL), 0); + return TRUE; + case WM_NOTIFY: + { + if (wParam == IDC_LISTREMINDERS) + { + NM = (NMLISTVIEW *)lParam; + switch (NM->hdr.code) + { + case LVN_ITEMCHANGED: + { + S = ((STICKYNOTE*)TreeGetAt(g_Stickies,NM->iItem))->data; + SetDlgItemText(Dialog,IDC_REMINDERDATA,S); + } + break; + case NM_DBLCLK: + { + HWND H; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + EditNote((STICKYNOTE *)TreeGetAt(g_Stickies, I)); + } + } + } + break; + } + } + else if (wParam == IDC_LISTREMINDERS_HEADER) + { + NMHEADER *NM = (NMHEADER*)lParam; + switch (NM->hdr.code) + { + case HDN_ENDTRACK: + UpdateGeomFromWnd(Dialog, NULL, g_notesListColGeom, SIZEOF(g_notesListColGeom)); + break; + } + } + } + break; + case WM_COMMAND: + { + switch(LOWORD(wParam)) + { + case ID_CONTEXTMENUNOTELISTVIEW_EDITNOTE: + { + HWND H; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + EditNote((STICKYNOTE*)TreeGetAt(g_Stickies, I)); + } + } + } + return TRUE; + case ID_CONTEXTMENUNOTELISTVIEW_TOGGLEVISIBILITY: + { + HWND H; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + STICKYNOTE *SN = (STICKYNOTE*)TreeGetAt(g_Stickies, I); + SN->Visible = !SN->Visible; + ShowWindow(SN->SNHwnd,SN->Visible?SW_SHOWNA:SW_HIDE); + JustSaveNotes(); + } + } + } + return TRUE; + case IDM_TOGGLEONTOP: + { + HWND H; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + STICKYNOTE *SN = (STICKYNOTE*)TreeGetAt(g_Stickies,I); + SN->OnTop = !SN->OnTop; + SetWindowPos(SN->SNHwnd, SN->OnTop ? HWND_TOPMOST : HWND_NOTOPMOST, 0,0,0,0, SWP_SHOWWINDOW|SWP_NOMOVE|SWP_NOSIZE); + RedrawWindow(SN->SNHwnd, NULL, NULL, RDW_FRAME|RDW_INVALIDATE|RDW_UPDATENOW); + JustSaveNotes(); + } + } + } + return TRUE; + case IDC_CLOSE: + { + DestroyWindow(Dialog); + ListNotesVisible = FALSE; + return TRUE; + } + case ID_CONTEXTMENUNOTEPOPUP_NEWNOTE: + case IDC_ADDNEWREMINDER: + { + PluginMenuCommandAddNew(0,0); + return TRUE; + } + case ID_CONTEXTMENUNOTELISTVIEW_DELETEALLNOTES: + { + PluginMenuCommandDeleteAll(0,0); + return TRUE; + } + case IDM_REMOVENOTE: + { + HWND H; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + OnDeleteNote(Dialog, (STICKYNOTE*)TreeGetAt(g_Stickies, I)); + } + } + } + return TRUE; + case ID_CONTEXTMENUNOTELISTVIEW_SHOW: + { + ShowHideNotes(); + return TRUE; + } + case ID_CONTEXTMENUNOTEPOPUP_BRINGALLTOTOP: + { + BringAllNotesToFront(NULL); + return TRUE; + } + } + } + } + return FALSE; +} diff --git a/plugins/NotesAndReminders/src/options.cpp b/plugins/NotesAndReminders/src/options.cpp new file mode 100644 index 0000000000..8e15946db9 --- /dev/null +++ b/plugins/NotesAndReminders/src/options.cpp @@ -0,0 +1,637 @@ +#include "globals.h" + +#ifndef OFN_DONTADDTORECENT +#define OFN_DONTADDTORECENT 0x02000000 +#endif + + +// min allowed alpha (don't want 0 because it's a waste of resources as well as might confuse user) +#define MIN_ALPHA 30 + + +#define IDC_RESET 1007 +#define IDC_SHOWNOTES 1010 +#define IDC_ADDCONTACTMENU 1011 +#define IDC_NOTEWIDTH 1012 +#define IDC_NOTEHEIGHT 1013 +#define IDC_TRANSTRACK 1014 +#define IDC_REMINDEMAIL 1017 +#define IDC_SHOWSCROLLBAR 1018 +#define IDC_SHOWBUTTONS 1019 +#define IDC_ADDREMINDERCLOSES 1020 +#define IDC_USEMCI 1023 +#define PSM_CHANGED (WM_USER + 104) + + +extern HANDLE hkFontChange; +extern HANDLE hkColorChange; + +BOOL g_CloseAfterAddReminder, g_UseDefaultPlaySound; +HICON g_hReminderIcon = NULL; + +LOGFONT lfBody,lfCaption; +HFONT hBodyFont = NULL, hCaptionFont = NULL; +long BodyColor; +long CaptionFontColor,BodyFontColor; +BOOL g_ShowNotesAtStart,g_ShowScrollbar,g_AddContListMI,g_ShowNoteButtons; +int g_NoteTitleDate, g_NoteTitleTime; +int g_NoteWidth,g_NoteHeight; +int g_Transparency; +char *g_RemindSMS = NULL; +BOOL g_isWin2kPlus = TRUE; + +TCHAR *g_lpszAltBrowser = NULL; + +int g_reminderListGeom[4] = {0}; +int g_reminderListColGeom[2] = { 150, 205 }; +int g_notesListGeom[4] = {0}; +int g_notesListColGeom[4] = { 150, 20, 20, 165 }; + + +#define NRCDEFAULT_BODYCLR RGB(255,255,0) + + +struct DateFormat +{ + LPCSTR lpszUI; + LPCSTR lpszFmt; +} +static dateFormats[] = +{ + { "1981-12-31", "yyyy'-'MM'-'dd" }, + { "31-12-1981", "dd'-'MM'-'yyyy" }, + { "12-31-1981", "MM'-'dd'-'yyyy" }, + { "1981-dec-31", "yyyy'-'MMM'-'dd" }, + { "31-dec-1981", "dd'-'MMM'-'yyyy" }, + { "dec-31-1981", "MMM'-'dd'-'yyyy" }, + { "1981/12/31", "yyyy'/'MM'/'dd" }, + { "31/12/1981", "dd'/'MM'/'yyyy" }, + { "12/31/1981", "MM'/'dd'/'yyyy" }, + { "1981/dec/31", "yyyy'/'MMM'/'dd" }, + { "31/dec/1981", "dd'/'MMM'/'yyyy" }, + { "dec/31/1981", "MMM'/'dd'/'yyyy" }, + { "1981 dec 31", "yyyy MMM dd" }, + { "31 dec 1981", "dd MMM yyyy" }, + { "dec 31 1981", "MMM dd yyyy" } +}; + +struct TimeFormat +{ + LPCSTR lpszUI; + LPCSTR lpszFmt; +} +static timeFormats[] = +{ + { "19:30:00", "HH':'mm':'ss" }, + { "19:30", "HH':'mm'" }, + { "7:30:00 PM", "hh':'mm':'ss tt" }, + { "7:30 PM", "hh':'mm tt" }, + { "7:30:00P", "hh':'mm':'sst" }, + { "7:30P", "hh':'mmt" } +}; + + +struct FontOptionsList +{ + TCHAR *szDescr; + COLORREF defColour; + TCHAR *szDefFace; + BYTE defStyle; + char defSize; + TCHAR *szBkgName; +} +static fontOptionsList[] = +{ + {LPGENT("Sticky Note Caption"), RGB(0,0,0), _T("Small Fonts"), 0, 7, LPGENT("Sticky Note Background Color")}, + //{LPGENT("Sticky Note Caption"), RGB(0,0,0), _T("Terminal"), 0, 6, LPGENT("Sticky Note Background Color")}, + //{LPGENT("Sticky Note Caption"), RGB(0,0,0), _T("MS Serif"), 0, 7, LPGENT("Sticky Note Background Color")}, + //{LPGENT("Sticky Note Body"), RGB(0,0,0), _T("Tahoma"), 0, 8, LPGENT("Sticky Note Background Color")}, + {LPGENT("Sticky Note Body"), RGB(0,0,0), _T("System"), DBFONTF_BOLD, 10, LPGENT("Sticky Note Background Color")}, +}; + + +struct ColourOptionsList +{ + TCHAR *szName; + char *szSettingName; + COLORREF defColour; +} +static colourOptionsList[] = +{ + {LPGENT("Sticky Note Background Color"), "BodyColor", NRCDEFAULT_BODYCLR} +}; + + +LPCSTR GetDateFormatStr() +{ + return dateFormats[g_NoteTitleDate ? g_NoteTitleDate-1 : 0].lpszFmt; +} + +LPCSTR GetTimeFormatStr() +{ + return timeFormats[g_NoteTitleTime ? g_NoteTitleTime-1 : 0].lpszFmt; +} + +#if defined( _UNICODE ) +static BYTE MsgDlgGetFontDefaultCharset(const TCHAR* szFont) +{ + return DEFAULT_CHARSET; +} +#else +// get font charset according to current CP +static BYTE MsgDlgGetCPDefaultCharset() +{ + switch (GetACP()) { + case 1250: + return EASTEUROPE_CHARSET; + case 1251: + return RUSSIAN_CHARSET; + case 1252: + return ANSI_CHARSET; + case 1253: + return GREEK_CHARSET; + case 1254: + return TURKISH_CHARSET; + case 1255: + return HEBREW_CHARSET; + case 1256: + return ARABIC_CHARSET; + case 1257: + return BALTIC_CHARSET; + case 1361: + return JOHAB_CHARSET; + case 874: + return THAI_CHARSET; + case 932: + return SHIFTJIS_CHARSET; + case 936: + return GB2312_CHARSET; + case 949: + return HANGEUL_CHARSET; + case 950: + return CHINESEBIG5_CHARSET; + default: + return DEFAULT_CHARSET; + } +} + +static int CALLBACK EnumFontFamExProc(const LOGFONT *lpelfe, const TEXTMETRIC *lpntme, DWORD FontType, LPARAM lParam) +{ + *(int*)lParam = 1; + return 0; +} + +// get font charset according to current CP, if available for specified font +static BYTE MsgDlgGetFontDefaultCharset(const TCHAR* szFont) +{ + HDC hdc; + LOGFONT lf = {0}; + int found = 0; + + _tcscpy(lf.lfFaceName, szFont); + lf.lfCharSet = MsgDlgGetCPDefaultCharset(); + + // check if the font supports specified charset + hdc = GetDC(0); + EnumFontFamiliesEx(hdc, &lf, &EnumFontFamExProc, (LPARAM)&found, 0); + ReleaseDC(0, hdc); + + if (found) + return lf.lfCharSet; + else // no, give default + return DEFAULT_CHARSET; +} +#endif + + +static void InitFonts() +{ + ZeroMemory(&lfBody,sizeof(LOGFONT)); + ZeroMemory(&lfCaption,sizeof(LOGFONT)); + + LoadNRFont(NR_FONTID_CAPTION, &lfCaption, (COLORREF*)&CaptionFontColor); + LoadNRFont(NR_FONTID_BODY, &lfBody, (COLORREF*)&BodyFontColor); + + if (hBodyFont) + DeleteObject(hBodyFont); + if (hCaptionFont) + DeleteObject(hCaptionFont); + + hBodyFont = CreateFontIndirect(&lfBody); + hCaptionFont = CreateFontIndirect(&lfCaption); +} + + +static int FS_FontsChanged(WPARAM wParam, LPARAM lParam) +{ + InitFonts(); + + SaveNotes(); + LoadNotes(FALSE); + + return 0; +} + +static int FS_ColorChanged(WPARAM wParam, LPARAM lParam) +{ + LoadNRFont(NR_FONTID_CAPTION, &lfCaption, (COLORREF*)&CaptionFontColor); + LoadNRFont(NR_FONTID_BODY, &lfBody, (COLORREF*)&BodyFontColor); + + BodyColor = DBGetContactSettingDword(NULL, MODULENAME, colourOptionsList[0].szSettingName, colourOptionsList[0].defColour); + + SaveNotes(); + LoadNotes(FALSE); + + return 0; +} + +void RegisterFontServiceFonts() +{ + HDC hDC; + int nFontScale; + FontIDT fontid = {0}; + ColourIDT colorid = {0}; + char szTemp[100]; + int i; + + fontid.cbSize = sizeof(FontIDT); + + mir_sntprintf(fontid.group, SIZEOF(fontid.group), _T("%s"), LPGENT(SECTIONNAME)); + mir_sntprintf(fontid.backgroundGroup, SIZEOF(fontid.backgroundGroup), _T("%s"), LPGENT(SECTIONNAME)); + strncpy(fontid.dbSettingsGroup, MODULENAME, SIZEOF(fontid.dbSettingsGroup)); + fontid.flags = FIDF_ALLOWREREGISTER | FIDF_DEFAULTVALID | FIDF_SAVEPOINTSIZE; + + hDC = GetDC(NULL); + nFontScale = GetDeviceCaps(hDC, LOGPIXELSY); + ReleaseDC(NULL, hDC); + + for (i = 0; i < SIZEOF(fontOptionsList); i++) + { + fontid.order = i; + mir_snprintf(szTemp, SIZEOF(szTemp), "Font%d", i); + strncpy(fontid.prefix, szTemp, SIZEOF(fontid.prefix)); + _tcsncpy(fontid.name, fontOptionsList[i].szDescr, SIZEOF(fontid.name)); + fontid.deffontsettings.colour = fontOptionsList[i].defColour; + + fontid.deffontsettings.size = (char)-MulDiv(fontOptionsList[i].defSize, nFontScale, 72); + //fontid.deffontsettings.size = fontOptionsList[i].defSize; + + fontid.deffontsettings.style = fontOptionsList[i].defStyle; + fontid.deffontsettings.charset = MsgDlgGetFontDefaultCharset(fontOptionsList[i].szDefFace); + _tcsncpy(fontid.deffontsettings.szFace, fontOptionsList[i].szDefFace, SIZEOF(fontid.deffontsettings.szFace)); + _tcsncpy(fontid.backgroundName, fontOptionsList[i].szBkgName, SIZEOF(fontid.backgroundName)); + + FontRegister(&fontid); + } + + colorid.cbSize = sizeof(ColourIDT); + + mir_sntprintf(colorid.group, SIZEOF(colorid.group), _T("%s"), LPGENT(SECTIONNAME)); + strncpy(colorid.dbSettingsGroup, MODULENAME, SIZEOF(fontid.dbSettingsGroup)); + colorid.flags = 0; + + for (i = 0; i < SIZEOF(colourOptionsList); i++) + { + colorid.order = i; + _tcsncpy(colorid.name, colourOptionsList[i].szName, SIZEOF(colorid.name)); + colorid.defcolour = colourOptionsList[i].defColour; + strncpy(colorid.setting, colourOptionsList[i].szSettingName, SIZEOF(colorid.setting)); + + ColourRegister(&colorid); + } + + hkFontChange = HookEvent(ME_FONT_RELOAD, FS_FontsChanged); + hkColorChange = HookEvent(ME_COLOUR_RELOAD, FS_ColorChanged); +} + +void LoadNRFont(int i, LOGFONT *lf, COLORREF *colour) +{ + COLORREF col; + FontIDT fontid = {0}; + + fontid.cbSize = sizeof(fontid); + _tcsncpy(fontid.group, LPGENT(SECTIONNAME), SIZEOF(fontid.group)); + _tcsncpy(fontid.name, fontOptionsList[i].szDescr, SIZEOF(fontid.name)); + + col = CallService(MS_FONT_GETT, (WPARAM)&fontid, (LPARAM)lf); + + if (colour) + { + *colour = col; + } +} + + +static void TrimString(TCHAR *s) +{ + TCHAR *start; + TCHAR *end; + UINT n; + + if (!s || !*s) + { + return; + } + + start = s; + n = _tcslen(s) - 1; + + end = s + n; + + if (!_istspace(*start) && !_istspace(*end)) + { + // nothing to trim + return; + } + + // scan past leading spaces + while (*start && _istspace(*start)) start++; + + if (!*start) + { + // empty string + *s = 0; + return; + } + + // trim trailing spaces + while ( _istspace(*end) ) end--; + end[1] = 0; + + if (start > s) + { + // remove leading spaces + memmove(s, start, ((UINT)(end-start)+2)*sizeof(TCHAR)); + } +} + + +int CALLBACK DlgProcOptions(HWND hdlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + BOOL LB; + WORD SzT; + void *P; + int i; + + switch (message) + { + case WM_INITDIALOG: + { + TranslateDialogDefault(hdlg); + SendDlgItemMessage(hdlg,IDC_TRANSTRACK,TBM_SETRANGE,TRUE,MAKELONG(0,255-MIN_ALPHA)); + SendDlgItemMessage(hdlg,IDC_TRANSTRACK,TBM_SETPOS,TRUE,255-g_Transparency); + SendDlgItemMessage(hdlg,IDC_SHOWNOTES,BM_SETCHECK,(WPARAM)!g_ShowNotesAtStart,0); + SendDlgItemMessage(hdlg,IDC_SHOWBUTTONS,BM_SETCHECK,(WPARAM)g_ShowNoteButtons,0); + SendDlgItemMessage(hdlg,IDC_SHOWSCROLLBAR,BM_SETCHECK,(WPARAM)g_ShowScrollbar,0); // 4.2 + SendDlgItemMessage(hdlg,IDC_ADDCONTACTMENU,BM_SETCHECK,(WPARAM)g_AddContListMI,0); + SetDlgItemInt(hdlg,IDC_NOTEWIDTH,g_NoteWidth,FALSE); + SetDlgItemInt(hdlg,IDC_NOTEHEIGHT,g_NoteHeight,FALSE); + SendDlgItemMessage(hdlg,IDC_ADDREMINDERCLOSES,BM_SETCHECK,(WPARAM)g_CloseAfterAddReminder,0); + SendDlgItemMessage(hdlg,IDC_USEMCI,BM_SETCHECK,(WPARAM)!g_UseDefaultPlaySound,0); + + SendDlgItemMessage(hdlg,IDC_COMBODATE,CB_RESETCONTENT,0,0); + SendDlgItemMessage(hdlg,IDC_COMBOTIME,CB_RESETCONTENT,0,0); + for (i=0; icode == PSN_APPLY) + { + g_ShowNotesAtStart = !(BOOL)SendDlgItemMessage(hdlg,IDC_SHOWNOTES,BM_GETCHECK,0,0); + g_ShowNoteButtons = (BOOL)SendDlgItemMessage(hdlg,IDC_SHOWBUTTONS,BM_GETCHECK,0,0); + g_ShowScrollbar = (BOOL)SendDlgItemMessage(hdlg,IDC_SHOWSCROLLBAR,BM_GETCHECK,0,0); // 4.2 + g_AddContListMI = (BOOL)SendDlgItemMessage(hdlg,IDC_ADDCONTACTMENU,BM_GETCHECK,0,0); + g_NoteWidth = GetDlgItemInt(hdlg,IDC_NOTEWIDTH,&LB,FALSE); + g_NoteHeight = GetDlgItemInt(hdlg,IDC_NOTEHEIGHT,&LB,FALSE); + g_Transparency = 255-SendDlgItemMessage(hdlg,IDC_TRANSTRACK,TBM_GETPOS,0,0); + g_CloseAfterAddReminder = (BOOL)SendDlgItemMessage(hdlg,IDC_ADDREMINDERCLOSES,BM_GETCHECK,0,0); + g_UseDefaultPlaySound = !(BOOL)SendDlgItemMessage(hdlg,IDC_USEMCI,BM_GETCHECK,0,0); + g_NoteTitleDate = (SendDlgItemMessage(hdlg,IDC_COMBODATE,CB_GETCURSEL,0,0) + 1) % SendDlgItemMessage(hdlg,IDC_COMBODATE,CB_GETCOUNT,0,0); + g_NoteTitleTime = (SendDlgItemMessage(hdlg,IDC_COMBOTIME,CB_GETCURSEL,0,0) + 1) % SendDlgItemMessage(hdlg,IDC_COMBOTIME,CB_GETCOUNT,0,0); + if (g_NoteWidth < 179) + { + g_NoteWidth = 179; + SetDlgItemInt(hdlg,IDC_NOTEWIDTH,g_NoteWidth,FALSE); + } + if (g_NoteHeight < 35) + { + g_NoteHeight = 35; + SetDlgItemInt(hdlg,IDC_NOTEHEIGHT,g_NoteHeight,FALSE); + } + SzT = (WORD)SendDlgItemMessage(hdlg,IDC_REMINDEMAIL,WM_GETTEXTLENGTH,0,0); + if (SzT != 0) + { + g_RemindSMS = (char*)realloc(g_RemindSMS,SzT+1); + SendDlgItemMessage(hdlg,IDC_REMINDEMAIL,WM_GETTEXT,SzT+1,(LPARAM)g_RemindSMS); + } + P = g_RemindSMS; + WriteSettingBlob(0,MODULENAME,"RemindEmail",SzT,P); + + SzT = (WORD)SendDlgItemMessage(hdlg,IDC_EDIT_ALTBROWSER,WM_GETTEXTLENGTH,0,0); + if (SzT != 0) + { + g_lpszAltBrowser = (TCHAR*)mir_realloc(g_lpszAltBrowser,SzT+1); + SendDlgItemMessage(hdlg,IDC_EDIT_ALTBROWSER,WM_GETTEXT,SzT+1,(LPARAM)g_lpszAltBrowser); + TrimString(g_lpszAltBrowser); + if (!*g_lpszAltBrowser) + { + mir_free(g_lpszAltBrowser); + g_lpszAltBrowser = NULL; + } + } + else if (g_lpszAltBrowser) + { + mir_free(g_lpszAltBrowser); + g_lpszAltBrowser = NULL; + } + SetDlgItemText(hdlg,IDC_EDIT_ALTBROWSER,g_lpszAltBrowser ? g_lpszAltBrowser : _T("")); + if (g_lpszAltBrowser) + DBWriteContactSettingString(0,MODULENAME,"AltBrowser",g_lpszAltBrowser); + else + DBDeleteContactSetting(0,MODULENAME,"AltBrowser"); + + WriteSettingInt(0,MODULENAME,"ShowNotesAtStart",g_ShowNotesAtStart); + WriteSettingInt(0,MODULENAME,"ShowNoteButtons",g_ShowNoteButtons); + WriteSettingInt(0,MODULENAME,"ShowScrollbar",g_ShowScrollbar); // 4.2 + WriteSettingInt(0,MODULENAME,"AddContactMenuItems",g_AddContListMI); + WriteSettingInt(0,MODULENAME,"NoteWidth",g_NoteWidth); + WriteSettingInt(0,MODULENAME,"NoteHeight",g_NoteHeight); + WriteSettingInt(0,MODULENAME,"Transparency",g_Transparency); + WriteSettingInt(0,MODULENAME,"NoteTitleDate",g_NoteTitleDate); + WriteSettingInt(0,MODULENAME,"NoteTitleTime",g_NoteTitleTime); + WriteSettingInt(0,MODULENAME,"CloseAfterAddReminder",g_CloseAfterAddReminder); + WriteSettingInt(0,MODULENAME,"UseMCI",!g_UseDefaultPlaySound); + SaveNotes(); + LoadNotes(FALSE); + return TRUE; + } + break; + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_BTN_BROWSEBROWSER: + { + TCHAR s[MAX_PATH]; + + OPENFILENAME ofn = {0}; +#if defined(WINVER) && _WIN32_WINNT >= 0x0500 + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; +#else + ofn.lStructSize = sizeof(ofn); +#endif + ofn.hwndOwner = hdlg; + ofn.lpstrFilter = "Executable Files\0*.exe\0All Files\0*.*\0\0"; + ofn.lpstrFile = s; + ofn.nMaxFile = SIZEOF(s); + ofn.lpstrTitle = Translate("Select Executable"); + ofn.lpstrInitialDir = "."; + ofn.Flags = OFN_FILEMUSTEXIST|OFN_LONGNAMES; + if ( IsWinVer98Plus() ) + { + ofn.Flags |= OFN_ENABLESIZING; + if (g_isWin2kPlus) + ofn.Flags |= OFN_DONTADDTORECENT; + } + + SendDlgItemMessage(hdlg,IDC_EDIT_ALTBROWSER,WM_GETTEXT,(WPARAM)ofn.nMaxFile,(LPARAM)s); + + if ( GetOpenFileName(&ofn) ) + { + SetDlgItemText(hdlg,IDC_EDIT_ALTBROWSER,s); + } + } + break; + case IDC_RESET: + { + SAFE_FREE((void**)&g_RemindSMS); + SetDlgItemText(hdlg,IDC_REMINDEMAIL,""); + if (g_lpszAltBrowser) + { + mir_free(g_lpszAltBrowser); + g_lpszAltBrowser = NULL; + } + SetDlgItemText(hdlg,IDC_EDIT_ALTBROWSER,""); + g_ShowNotesAtStart = TRUE; + g_AddContListMI = TRUE; + g_ShowScrollbar = TRUE; // 4.2 + g_ShowNoteButtons = TRUE; + g_NoteTitleDate = 1; + g_NoteTitleTime = 1; + g_CloseAfterAddReminder = TRUE; + g_UseDefaultPlaySound = FALSE; + SendDlgItemMessage(hdlg,IDC_SHOWNOTES,BM_SETCHECK,!g_ShowNotesAtStart,0); + SendDlgItemMessage(hdlg,IDC_ADDCONTACTMENU,BM_SETCHECK,g_AddContListMI,0); + SendDlgItemMessage(hdlg,IDC_SHOWSCROLLBAR,BM_SETCHECK,g_ShowScrollbar,0); + SendDlgItemMessage(hdlg,IDC_SHOWBUTTONS,BM_SETCHECK,(WPARAM)g_ShowNoteButtons,0); + SendDlgItemMessage(hdlg,IDC_ADDREMINDERCLOSES,BM_SETCHECK,g_CloseAfterAddReminder,0); + SendDlgItemMessage(hdlg,IDC_USEMCI,BM_SETCHECK,!g_UseDefaultPlaySound,0); + SendDlgItemMessage(hdlg,IDC_COMBODATE,CB_SETCURSEL,(WPARAM)(g_NoteTitleDate-1),0); + SendDlgItemMessage(hdlg,IDC_COMBOTIME,CB_SETCURSEL,(WPARAM)(g_NoteTitleTime-1),0); + g_NoteWidth = 179; // 4.2 + g_NoteHeight = 35; + SetDlgItemInt(hdlg,IDC_NOTEWIDTH,g_NoteWidth,FALSE); + SetDlgItemInt(hdlg,IDC_NOTEHEIGHT,g_NoteHeight,FALSE); + g_Transparency = 255; + SendDlgItemMessage(hdlg,IDC_TRANSTRACK,TBM_SETPOS,TRUE,0); + SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); // JK optim + return TRUE; + } + default: + SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + return TRUE; + } + } + return FALSE; +} + +void InitSettings(void) +{ + void *P = NULL; + short Sz1; + + Sz1 = MAX_PATH; P = NULL; + ReadSettingBlob(0, MODULENAME, "RemindEmail", (WORD*)&Sz1, &P); + if (!(Sz1 && P)) + g_RemindSMS = NULL; + else + { + g_RemindSMS = (char*)malloc(Sz1+1); + ZeroMemory(g_RemindSMS,Sz1+1); + memcpy(g_RemindSMS,P,Sz1); + FreeSettingBlob(Sz1,P); + } + + g_lpszAltBrowser = DBGetString(0,MODULENAME,"AltBrowser"); + + g_ShowNotesAtStart = (BOOL)ReadSettingInt(0,MODULENAME,"ShowNotesAtStart",1); + g_ShowNoteButtons = (BOOL)ReadSettingInt(0,MODULENAME,"ShowNoteButtons",1); + g_ShowScrollbar = (BOOL)ReadSettingInt(0,MODULENAME,"ShowScrollbar",1); + g_AddContListMI = (BOOL)ReadSettingInt(0,MODULENAME,"AddContactMenuItems",1); + g_NoteWidth = ReadSettingInt(0,MODULENAME,"NoteWidth",179); + g_NoteHeight = ReadSettingInt(0,MODULENAME,"NoteHeight",50); + g_Transparency = ReadSettingInt(0,MODULENAME,"Transparency",255); + g_NoteTitleDate = ReadSettingInt(0,MODULENAME,"NoteTitleDate",1); + g_NoteTitleTime = ReadSettingInt(0,MODULENAME,"NoteTitleTime",1); + g_CloseAfterAddReminder = (BOOL)ReadSettingInt(0,MODULENAME,"CloseAfterAddReminder",1); + g_UseDefaultPlaySound = !(BOOL)ReadSettingInt(0,MODULENAME,"UseMCI",1); + + ReadSettingIntArray(0,MODULENAME,"ReminderListGeom",g_reminderListGeom,SIZEOF(g_reminderListGeom)); + ReadSettingIntArray(0,MODULENAME,"ReminderListColGeom",g_reminderListColGeom,SIZEOF(g_reminderListColGeom)); + ReadSettingIntArray(0,MODULENAME,"NotesListGeom",g_notesListGeom,SIZEOF(g_notesListGeom)); + ReadSettingIntArray(0,MODULENAME,"NotesListColGeom",g_notesListColGeom,SIZEOF(g_notesListColGeom)); + + BodyColor = DBGetContactSettingDword(NULL, MODULENAME, colourOptionsList[0].szSettingName, colourOptionsList[0].defColour); + + InitFonts(); + + g_hReminderIcon = Skin_GetIconByHandle(hIconLibItem[10]); + + if (g_Transparency < MIN_ALPHA) + g_Transparency = MIN_ALPHA; + else if (g_Transparency > 255) + g_Transparency = 255; +} + +void TermSettings(void) +{ + if (g_reminderListGeom[2] > 0 && g_reminderListGeom[3] > 0) + { + WriteSettingIntArray(0,MODULENAME,"ReminderListGeom",g_reminderListGeom,SIZEOF(g_reminderListGeom)); + WriteSettingIntArray(0,MODULENAME,"ReminderListColGeom",g_reminderListColGeom,SIZEOF(g_reminderListColGeom)); + } + if (g_notesListGeom[2] > 0 && g_notesListGeom[3] > 0) + { + WriteSettingIntArray(0,MODULENAME,"NotesListGeom",g_notesListGeom,SIZEOF(g_notesListGeom)); + WriteSettingIntArray(0,MODULENAME,"NotesListColGeom",g_notesListColGeom,SIZEOF(g_notesListColGeom)); + } + + if (g_lpszAltBrowser) + { + mir_free(g_lpszAltBrowser); + g_lpszAltBrowser = NULL; + } +} diff --git a/plugins/NotesAndReminders/src/reminders.cpp b/plugins/NotesAndReminders/src/reminders.cpp new file mode 100644 index 0000000000..0d545056f9 --- /dev/null +++ b/plugins/NotesAndReminders/src/reminders.cpp @@ -0,0 +1,2890 @@ +#include "globals.h" + +#define FILETIME_TICKS_PER_SEC ((ULONGLONG)10000000) + +#define MAX_REMINDER_LEN 16384 + + +// RemindersData DB data params +#define DATATAG_TEXT 1 // %s +#define DATATAG_SNDREPEAT 2 // %u (specifies seconds to wait between sound repeats, 0 if repeat is disabled) +#define DATATAG_SNDSEL 3 // %d (which sound to use, default, alt1, alt2, -1 means no sound at all) + + +#define IDC_DATE 1000 +#define IDC_TIME IDC_COMBOREMINDERTIME +#define IDC_ADDREMINDER 1002 +#define IDC_CLOSE 1003 +#define IDC_REMINDER 1004 +#define IDC_LISTREMINDERS 1000 +#define IDC_LISTREMINDERS_HEADER 2000 +#define IDC_REMINDERDATA 1001 +#define IDC_ADDNEWREMINDER 1002 +#define IDC_REMDATA 1000 +#define IDC_DISMISS 1001 +#define IDC_REMINDAGAIN 1002 +#define IDC_REMINDAGAININ 1003 +#define IDC_AFTER 1004 +#define IDC_ONDATE 1005 +#define IDC_DATEAGAIN 1006 +#define IDC_TIMEAGAIN 1007 +#define IDC_VIEWREMINDERS 1007 +#define IDC_NONE 1008 +#define IDC_DAILY 1009 +#define IDC_WEEKLY 1010 +#define IDC_MONTHLY 1011 +#define IDM_NEWREMINDER 40001 +#define IDM_DELETEREMINDER 40002 +#define IDM_DELETEALLREMINDERS 40003 +#define WM_RELOAD (WM_USER + 100) + +#define NOTIFY_LIST() if (ListReminderVisible) PostMessage(LV,WM_RELOAD,0,0) + + +TREEELEMENT *RemindersList = NULL; +static UINT QueuedReminderCount = 0; +static BOOL ListReminderVisible = FALSE; +static BOOL NewReminderVisible = FALSE; +static REMINDERDATA *pEditReminder = NULL; +static HWND LV; +static SOCKET S; + +int WS_Send(SOCKET s,char *data,int datalen); +unsigned long WS_ResolveName(char *name,WORD *port,int defaultPort); + +int CALLBACK DlgProcNotifyReminder(HWND Dialog,UINT Message, + WPARAM wParam,LPARAM lParam); +int CALLBACK DlgProcNewReminder(HWND Dialog,UINT Message,WPARAM wParam, + LPARAM lParam); +int CALLBACK DlgProcViewReminders(HWND Dialog,UINT Message,WPARAM wParam, + LPARAM lParam); + +void Send(char *user, char *host, char *Msg, char* server); +char* GetPreviewString(const char *lpsz); + + +static int ReminderSortCb(TREEELEMENT *v1, TREEELEMENT *v2) +{ + return (((REMINDERDATA*)v1->ptrdata)->When.QuadPart < ((REMINDERDATA*)v2->ptrdata)->When.QuadPart) ? -1 : 1; +} + + +#ifndef WINXP_MINIMUM +// TzSpecificLocalTimeToSystemTime/SystemTimeToTzSpecificLocalTime (re-)implemented to work on win2k and older + +static const int DaysInMonth[2][12] = +{ + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, // normal year + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } // leap year +}; + +static __inline BOOL IsLeapYear(UINT Y) +{ + return !(Y & 3) && ((Y % 100) || !(Y % 400)); +} + +static int TZDateComp(const SYSTEMTIME *lpDate, const SYSTEMTIME *lpDateRef) +{ + int boundaryDay, day; + + if (lpDate->wMonth < lpDateRef->wMonth) + return -1; + if (lpDate->wMonth > lpDateRef->wMonth) + return 1; + + if (!lpDateRef->wYear) + { + const int week = (int)lpDateRef->wDay; + const WORD wFirst = (6 + lpDateRef->wDayOfWeek - lpDate->wDayOfWeek + lpDate->wDay) % 7 + 1; + boundaryDay = (int)wFirst + 7 * (week - 1); + if (boundaryDay > DaysInMonth[ IsLeapYear(lpDate->wYear) ][lpDate->wMonth-1]) + boundaryDay -= 7; + } + else + boundaryDay = (int)lpDateRef->wDay; + + boundaryDay = ((boundaryDay * 24 + (int)lpDateRef->wHour) * 60 + (int)lpDateRef->wMinute) * 60; + day = (((int)lpDate->wDay * 24 + (int)lpDate->wHour) * 60 + (int)lpDate->wMinute) * 60 + (int)lpDate->wSecond; + + return (day < boundaryDay) ? -1 : (day > boundaryDay); +} + +static UINT TZGetType(LPTIME_ZONE_INFORMATION lpTZI, ULARGE_INTEGER *lpFT, BOOL bLocal) +{ + if (lpTZI->DaylightDate.wMonth) + { + ULARGE_INTEGER ft = *lpFT; + SYSTEMTIME tm; + BOOL BeforeStandardDate, AfterDaylightDate; + UINT id; + + if (!lpTZI->StandardDate.wMonth || (!lpTZI->StandardDate.wYear + && (!lpTZI->StandardDate.wDay || lpTZI->StandardDate.wDay > 5 + || !lpTZI->DaylightDate.wDay || lpTZI->DaylightDate.wDay > 5))) + return TIME_ZONE_ID_INVALID; + + if (!bLocal) + ft.QuadPart -= (LONGLONG)(lpTZI->Bias + lpTZI->DaylightBias) * (LONGLONG)600000000; + + FileTimeToSystemTime((FILETIME*)&ft, &tm); + + BeforeStandardDate = (TZDateComp(&tm, &lpTZI->StandardDate) < 0); + + if (!bLocal) + { + ft.QuadPart -= (LONGLONG)(lpTZI->StandardBias - lpTZI->DaylightBias) * (LONGLONG)600000000; + FileTimeToSystemTime((FILETIME*)&ft, &tm); + } + + AfterDaylightDate = (TZDateComp(&tm, &lpTZI->DaylightDate) >= 0); + + id = TIME_ZONE_ID_STANDARD; + if (lpTZI->DaylightDate.wMonth < lpTZI->StandardDate.wMonth) + { + if (BeforeStandardDate && AfterDaylightDate) + id = TIME_ZONE_ID_DAYLIGHT; + } + else + { + if (BeforeStandardDate || AfterDaylightDate) + id = TIME_ZONE_ID_DAYLIGHT; + } + + return id; + } + + return TIME_ZONE_ID_UNKNOWN; +} + +static BOOL TZGetBias(LPTIME_ZONE_INFORMATION lpTZI, ULARGE_INTEGER *lpFT, BOOL bLocal, LONG *pBias) +{ + LONG Bias = lpTZI->Bias; + + switch ( TZGetType(lpTZI, lpFT, bLocal) ) + { + case TIME_ZONE_ID_INVALID: return FALSE; + case TIME_ZONE_ID_DAYLIGHT: Bias += lpTZI->DaylightBias; break; + case TIME_ZONE_ID_STANDARD: Bias += lpTZI->StandardBias; break; + } + + *pBias = Bias; + + return TRUE; +} + +#define TzSpecificLocalTimeToSystemTime _TzSpecificLocalTimeToSystemTime +static BOOL _TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTZI, LPSYSTEMTIME lpLocal, LPSYSTEMTIME lpUtc) +{ + TIME_ZONE_INFORMATION tzi; + ULARGE_INTEGER ft; + LONG Bias; + + // if possible use the real function (shouldn't be necessary, be feels more comfortable) + if (MyTzSpecificLocalTimeToSystemTime) + return MyTzSpecificLocalTimeToSystemTime(lpTZI, lpLocal, lpUtc); + + if (!lpTZI) + { + if (GetTimeZoneInformation(&tzi) == TIME_ZONE_ID_INVALID) + return FALSE; + lpTZI = &tzi; + } + + if (!SystemTimeToFileTime(lpLocal, (FILETIME*)&ft) + || !TZGetBias(lpTZI, &ft, TRUE, &Bias)) + return FALSE; + + ft.QuadPart += (LONGLONG)Bias * (LONGLONG)600000000; + + return FileTimeToSystemTime((FILETIME*)&ft, lpUtc); +} + +#define SystemTimeToTzSpecificLocalTime _SystemTimeToTzSpecificLocalTime +static BOOL _SystemTimeToTzSpecificLocalTime(LPTIME_ZONE_INFORMATION lpTZI, LPSYSTEMTIME lpUtc, LPSYSTEMTIME lpLocal) +{ + TIME_ZONE_INFORMATION tzi; + ULARGE_INTEGER ft; + LONG Bias; + + // if possible use the real function (shouldn't be necessary, be feels more comfortable) + if (MySystemTimeToTzSpecificLocalTime) + return MySystemTimeToTzSpecificLocalTime(lpTZI, lpUtc, lpLocal); + + if (!lpTZI) + { + if (GetTimeZoneInformation(&tzi) == TIME_ZONE_ID_INVALID) + return FALSE; + lpTZI = &tzi; + } + + if (!SystemTimeToFileTime(lpUtc, (FILETIME*)&ft) + || !TZGetBias(lpTZI, &ft, FALSE, &Bias)) + return FALSE; + + ft.QuadPart -= (LONGLONG)Bias * (LONGLONG)600000000; + + return FileTimeToSystemTime((FILETIME*)&ft, lpLocal); +} + +#endif + + +// time convertsion routines that take local time-zone specific daylight saving configuration into account +// (unlike the standard FileTimeToLocalFileTime functions) + +void UtcToTzLocalFT(const FILETIME *lpUtc, FILETIME *lpLocal) +{ + SYSTEMTIME tm, tmLocal; + FILETIMEtoSYSTEMTIME(lpUtc, &tm); + SystemTimeToTzSpecificLocalTime(NULL, &tm, &tmLocal); + SYSTEMTIMEtoFILETIME(&tmLocal, lpLocal); +} + +void TzLocalToUtcFT(const FILETIME *lpLocal, FILETIME *lpUtc) +{ + SYSTEMTIME tm, tmUtc; + FILETIMEtoSYSTEMTIME(lpLocal, &tm); + TzSpecificLocalTimeToSystemTime(NULL, &tm, &tmUtc); + SYSTEMTIMEtoFILETIME(&tmUtc, lpUtc); +} + +void FileTimeToTzLocalST(const FILETIME *lpUtc, SYSTEMTIME *tmLocal) +{ + SYSTEMTIME tm; + FILETIMEtoSYSTEMTIME(lpUtc, &tm); + SystemTimeToTzSpecificLocalTime(NULL, &tm, tmLocal); +} + +void TzLocalSTToFileTime(const SYSTEMTIME *tmLocal, FILETIME *lpUtc) +{ + SYSTEMTIME tm; + TzSpecificLocalTimeToSystemTime(NULL, (SYSTEMTIME*)tmLocal, &tm); + SYSTEMTIMEtoFILETIME(&tm, lpUtc); +} + +/*void AddToTzLocalFT(FILETIME *lpLocal, UINT nSeconds) +{ + ULARGE_INTEGER utc; + TzLocalToUtcFT(lpLocal, (FILETIME*)&utc); + utc.QuadPart += (ULONGLONG)nSeconds * FILETIME_TICKS_PER_SEC; + UtcToTzLocalFT((FILETIME*)&utc, lpLocal); +}*/ + +/*static void AddToSystemTime(SYSTEMTIME *tm, UINT nSeconds) +{ + ULARGE_INTEGER li; + FILETIME ft; + + SYSTEMTIMEtoFILETIME(tm, &ft); + + li.HighPart = ft.dwHighDateTime; + li.LowPart = ft.dwLowDateTime; + li.QuadPart += (ULONGLONG)nSeconds * FILETIME_TICKS_PER_SEC; + + FILETIMEtoSYSTEMTIME((FILETIME*)&li, tm); +}*/ + + +static DWORD CreateUid() +{ + DWORD uid; + TREEELEMENT *TTE; + + if (!RemindersList) + return 1; + + for (uid = 1; ; uid++) + { + // check existing reminders if uid is in use + TTE = RemindersList; + while (TTE) + { + if (((REMINDERDATA*)TTE->ptrdata)->uid == uid) + // uid in use + goto try_next; + + TTE = (TREEELEMENT*)TTE->next; + } + + return uid; + +try_next:; + } + + // should never get here (unless someone has 4294967295 reminders) + return 0; +} + +static REMINDERDATA* FindReminder(DWORD uid) +{ + TREEELEMENT *TTE; + + if (!RemindersList) + return NULL; + + TTE = RemindersList; + while (TTE) + { + REMINDERDATA *pReminder = (REMINDERDATA*)TTE->ptrdata; + + if (pReminder->uid == uid) + { + return pReminder; + } + + TTE = (TREEELEMENT*)TTE->next; + } + + return NULL; +} + + +static void RemoveReminderSystemEvent(REMINDERDATA *p) +{ + if (p->SystemEventQueued) + { + int i; + + for (i=0; ; i++) + { + CLISTEVENT *pev; + + pev = (CLISTEVENT*) CallService(MS_CLIST_GETEVENT,(WPARAM)INVALID_HANDLE_VALUE,i); + if (!pev) + break; + + if ((ULONG)pev->lParam == p->uid && !pev->hContact + && pev->pszService && !strcmp(pev->pszService, MODULENAME"/OpenTriggeredReminder")) + { + if ( !CallService(MS_CLIST_REMOVEEVENT,(WPARAM)pev->hContact,(LPARAM)pev->hDbEvent) ) + { + p->SystemEventQueued = FALSE; + if (QueuedReminderCount) + QueuedReminderCount--; + } + break; + } + } + } +} + +void PurgeReminders(void) +{ + int ReminderCount,I; + char ValueName[32]; + + ReminderCount = ReadSettingInt(0,MODULENAME,"RemindersData",0); + for(I = 0;I < ReminderCount;I++) + { + sprintf(ValueName, "RemindersData%d", I); + DeleteSetting(0,MODULENAME,ValueName); + } +} + +void JustSaveReminders(void) +{ + TREEELEMENT *TTE; + int I, n, l; + char *tmpReminder = NULL,*Value; + char ValueName[32]; + int ReminderCount; + REMINDERDATA *pReminder; + + const int OldReminderCount = ReadSettingInt(0, MODULENAME, "RemindersData", 0); + + ReminderCount = TreeGetCount(RemindersList); + + WriteSettingInt(0,MODULENAME, "RemindersData", ReminderCount); + + for (TTE = RemindersList, I = 0; TTE; TTE = (TREEELEMENT*)TTE->next, I++) + { + pReminder = (REMINDERDATA*)TTE->ptrdata; + if (pReminder->Reminder && strlen(pReminder->Reminder)) + tmpReminder = pReminder->Reminder; + else + tmpReminder = NULL; + + if (!tmpReminder) + tmpReminder = ""; + + Value = (char*)malloc(strlen(tmpReminder) + 512); + + if (!Value) + continue; + + n = 0; + + // data header (save 'When' with 1-second resolution, it's just a waste to have 100-nanosecond resolution + // which results in larger DB strings with no use) + l = sprintf(Value, "X%u:%I64x", pReminder->uid, pReminder->When.QuadPart/FILETIME_TICKS_PER_SEC); + if (l > 0) n += l; + + // sound repeat + if (pReminder->RepeatSound) + { + l = sprintf(Value+n, "\033""%u:%u", DATATAG_SNDREPEAT, pReminder->RepeatSound); + if (l > 0) n += l; + } + + // sound + if (pReminder->SoundSel) + { + l = sprintf(Value+n, "\033""%u:%d", DATATAG_SNDSEL, pReminder->SoundSel); + if (l > 0) n += l; + } + + // reminder text/note (ALWAYS PUT THIS PARAM LAST) + if (tmpReminder && *tmpReminder) + { + l = sprintf(Value+n, "\033""%u:%s", DATATAG_TEXT, tmpReminder); + if (l > 0) n += l; + } + + // clamp data size to WORD (including null terminator) + if (n >= 0xffff) + { + // huston, we have a problem, strip some reminder text + n = 0xfffe; + ValueName[0xffff] = 0; + } + + sprintf(ValueName, "RemindersData%d", ReminderCount - I - 1); // do not want to reverse in DB + + WriteSettingBlob(0, MODULENAME, ValueName, (WORD)(n+1), Value); + + SAFE_FREE((void**)&Value); + } + + // delete any left over DB reminder entries + for(; I < OldReminderCount; I++) + { + sprintf(ValueName, "RemindersData%d", I); + DBDeleteContactSetting(0,MODULENAME,ValueName); + } +} + +void LoadReminders(void) +{ + int I,RemindersCount; + char *Value; + WORD Size; + char ValueName[32]; + BOOL GenerateUids = FALSE; + + RemindersList = NULL; + RemindersCount = ReadSettingInt(0, MODULENAME, "RemindersData", 0); + + for (I = 0; I < RemindersCount; I++) + { + Size = 65535; + Value = NULL; + sprintf(ValueName, "RemindersData%d", I); + + ReadSettingBlob(0, MODULENAME, ValueName, &Size, (void**)&Value); + + if (Size && Value) // was the blob found + { + REMINDERDATA rem = {0}; + char *TVal; + REMINDERDATA *TempRem; + char *DelPos = strchr(Value, 0x1B); + + // ensure that read data is null-terminated + Value[(UINT)Size-1] = 0; + + if (Value[0] == 'X') + { + // new eXtended/fleXible data format + + if (DelPos) + *DelPos = 0; + + // uid:when + + TVal = strchr(Value+1, ':'); + if (!TVal || (DelPos && TVal > DelPos)) + continue; + *TVal++ = 0; + + rem.uid = strtoul(Value+1, NULL, 10); + rem.When.QuadPart = _strtoui64(TVal, NULL, 16) * FILETIME_TICKS_PER_SEC; + + // optional \033 separated params + while (DelPos) + { + char *sep; + UINT tag; + + TVal = DelPos + 1; + // find param end and make sure it's null-terminated (if end of data then it's already null-terminated) + DelPos = strchr(TVal, 0x1B); + if (DelPos) + *DelPos = 0; + + // tag: + + sep = strchr(TVal, ':'); + if (!sep || (DelPos && sep > DelPos)) + goto skip; + + tag = strtoul(TVal, NULL, 10); + TVal = sep + 1; + + switch (tag) + { + case DATATAG_TEXT: + rem.Reminder = _strdup(TVal); + break; + + case DATATAG_SNDREPEAT: + rem.RepeatSound = strtoul(TVal, NULL, 10); + break; + + case DATATAG_SNDSEL: + rem.SoundSel = strtol(TVal, NULL, 10); + if (rem.SoundSel > 2) rem.SoundSel = 2; + break; + } + } + + if (rem.SoundSel < 0) + rem.RepeatSound = 0; + if (!rem.Reminder) + rem.Reminder = _strdup(""); + } + else + { + // old format (for DB backward compatibility) + + if (!DelPos) + continue; + + DelPos[0] = 0; + // convert time_t to (local) FILETIME + { + SYSTEMTIME tm; + struct tm *stm; + time_t tt; + + tt = (time_t)strtoul(Value, NULL, 10); + stm = localtime(&tt); + tm.wDayOfWeek = 0; + tm.wSecond = 0; + tm.wMilliseconds = 0; + tm.wHour = stm->tm_hour; + tm.wMinute = stm->tm_min; + tm.wSecond = stm->tm_sec; + tm.wYear = stm->tm_year + 1900; + tm.wMonth = stm->tm_mon + 1; + tm.wDay = stm->tm_mday; + SYSTEMTIMEtoFILETIME(&tm, (FILETIME*)&rem.When); + } + TVal = DelPos + 1; + rem.Reminder = _strdup(TVal); + } + + // queue uid generation if invalid uid is present + if (!rem.uid) + GenerateUids = TRUE; + + TempRem = (REMINDERDATA*)malloc(sizeof(REMINDERDATA)); + if (TempRem) + { + *TempRem = rem; + TreeAddSorted(&RemindersList, TempRem, ReminderSortCb); + } + else if (rem.Reminder) + { + free(rem.Reminder); + } +skip:; + } + + FreeSettingBlob(Size, Value); + } + + // generate UIDs if there are any items with an invalid UID + if (GenerateUids && RemindersList) + { + TREEELEMENT *TTE; + + TTE = RemindersList; + while (TTE) + { + REMINDERDATA *pReminder = (REMINDERDATA*)TTE->ptrdata; + + if (!pReminder->uid) + pReminder->uid = CreateUid(); + + TTE = (TREEELEMENT*)TTE->next; + } + + JustSaveReminders(); + } +} + + +/*void EscapeString(LPCSTR lpszSrc, char *s, int maxLen) +{ + maxLen -= 3; + + *s++ = '"'; + + while (*lpszSrc && maxLen > 1) + { + switch (*lpszSrc) + { + case '\r': *s++ = '\\'; *s++ = 'r'; break; + case '\n': *s++ = '\\'; *s++ = 'n'; break; + case '"': *s++ = '\\'; *s++ = '"'; break; + case '\t': *s++ = '\\'; *s++ = 't'; break; + case '\\': *s++ = '\\'; *s++ = '\\'; break; + default: + *s++ = *lpszSrc; + } + + lpszSrc++; + maxLen--; + } + + *s++ = '"'; + *s = 0; +} + +void ExportReminders() +{ + LPCSTR lpsz; + TREEELEMENT *TTE; + char s[MAX_REMINDER_LEN+512]; + + if (!RemindersList) + return NULL; + + // CSV header + lpsz = "TimeUTC,SoundSel,SoundRepeat,Description"; + WriteFile(hFile, lpsz, strlen(lpsz), NULL, NULL); + + TTE = RemindersList; + while (TTE) + { + REMINDERDATA *pReminder = (REMINDERDATA*)TTE->ptrdata; + + sprintf(s, "%I64u,%d,%d,", (pReminder->When.QuadPart-(ULONGLONG)116444736000000000)/FILETIME_TICKS_PER_SEC, pReminder->SoundSel, pReminder->RepeatSound); + WriteFile(hFile, s, strlen(s), NULL, NULL); + + if (pReminder->Reminder) + { + EscapeString(pReminder->Reminder, s, sizeof(s)); + WriteFile(hFile, s, strlen(s), NULL, NULL); + } + + WriteFile(hFile, (LPCVOID)"\r\n", 2, NULL, NULL); + + TTE = TTE->next; + } + + return NULL; +}*/ + + +void NewReminder(void) +{ + if (!NewReminderVisible) + { + NewReminderVisible = TRUE; + CreateDialog(hinstance, MAKEINTRESOURCE(IDD_ADDREMINDER), 0, DlgProcNewReminder); + } +} + +void EditReminder(REMINDERDATA *p) +{ + if (!p) + return; + + if (!NewReminderVisible && !p->SystemEventQueued) + { + if (!p->RemVisible) + { + p->RemVisible = TRUE; + NewReminderVisible = 2; + pEditReminder = p; + CreateDialog(hinstance, MAKEINTRESOURCE(IDD_ADDREMINDER), 0, DlgProcNewReminder); + } + else + { + BringWindowToTop(p->handle); + } + } +} + +static void DeleteReminder(REMINDERDATA *p) +{ + if (!p) + return; + + if (p->SystemEventQueued) + { + // remove pending system event + RemoveReminderSystemEvent(p); + } + + TreeDelete(&RemindersList, p); + SAFE_FREE((void**)&p->Reminder); + SAFE_FREE((void**)&p); +} + +void CloseReminderList() +{ + if (ListReminderVisible) + { + DestroyWindow(LV); + ListReminderVisible = FALSE; + } +} + +static void PurgeReminderTree() +{ + REMINDERDATA *pt; + + while (RemindersList) // empty whole tree + { + pt = (REMINDERDATA*)RemindersList->ptrdata; + if (pt->handle) DestroyWindow(pt->handle); + DeleteReminder(pt); + } + RemindersList = NULL; +} + +void SaveReminders(void) +{ + JustSaveReminders(); + PurgeReminderTree(); +} + +void DeleteReminders(void) +{ + PurgeReminders(); + WriteSettingInt(0,MODULENAME,"RemindersData",0); + PurgeReminderTree(); +} + +void ListReminders(void) +{ + if (!ListReminderVisible) + { + CreateDialog(hinstance, MAKEINTRESOURCE(IDD_LISTREMINDERS), 0, DlgProcViewReminders); + ListReminderVisible = TRUE; + } + else + { + BringWindowToTop(LV); + } +} + + +void GetTriggerTimeString(const ULARGE_INTEGER *When, char *s, UINT strSize, BOOL bUtc) +{ + SYSTEMTIME tm; + LCID lc = GetUserDefaultLCID(); + + *s = 0; + + memset(&tm, 0, sizeof(tm)); + if (bUtc) + FileTimeToTzLocalST((const FILETIME*)When, &tm); + else + FILETIMEtoSYSTEMTIME((FILETIME*)When, &tm); + + if ( GetDateFormat(lc, DATE_LONGDATE, &tm, NULL, s, strSize) ) + { + // append time + { + int n = strlen(s); + s[n++] = ' '; + s[n] = 0; + + if ( !GetTimeFormat(lc, LOCALE_NOUSEROVERRIDE|TIME_NOSECONDS, &tm, NULL, s+n, strSize-n) ) + { + mir_snprintf(s+n, strSize-n, "%02d:%02d", tm.wHour, tm.wMinute); + } + } + } + else + { + mir_snprintf(s, strSize, "%d-%02d-%02d %02d:%02d", tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute); + } +} + + +int OpenTriggeredReminder(WPARAM w, LPARAM l) +{ + REMINDERDATA *pReminder; + + if (!l) + return 0; + + l = ((CLISTEVENT*)l)->lParam; + + pReminder = (REMINDERDATA*)FindReminder((DWORD)l); + if (!pReminder || !pReminder->SystemEventQueued) + return 0; + + pReminder->SystemEventQueued = FALSE; + if (QueuedReminderCount) + QueuedReminderCount--; + + { + char S[MAX_PATH]; + char S1[128]; + HWND H; + GetTriggerTimeString(&pReminder->When, S1, sizeof(S1), TRUE); + + pReminder->RemVisible = TRUE; + + pReminder->handle = H = CreateDialog(hinstance, MAKEINTRESOURCE(IDD_NOTIFYREMINDER), 0, DlgProcNotifyReminder); + + sprintf(S, "%s! - %s", Translate("Reminder"), S1); + SetWindowText(H, S); + + if (pReminder->Reminder) + SetDlgItemText(H, IDC_REMDATA, pReminder->Reminder); + + BringWindowToTop(H); + } + + return 0; +} + +static void SkinPlaySoundPoly(LPCSTR pszSoundName) +{ + if (g_UseDefaultPlaySound) + { + SkinPlaySound(pszSoundName); + return; + } + + if (DBGetContactSettingByte(NULL, "SkinSoundsOff", pszSoundName, 0)==0) { + DBVARIANT dbv; + + if (DBGetContactSettingString(NULL, "SkinSounds", pszSoundName, &dbv)==0) { + char szFull[MAX_PATH]; + + CallService(MS_UTILS_PATHTOABSOLUTE, (WPARAM)dbv.pszVal, (LPARAM)szFull); + + //NotifyEventHooks(hPlayEvent, 0, (LPARAM)szFull); + { + // use MCI device which allows multiple sounds playing at once + // NOTE: mciSendString does not like long paths names, must convert to short + char szShort[MAX_PATH]; + char s[512]; + GetShortPathNameA(szFull, szShort, sizeof(szShort)); + mir_snprintf(s, sizeof(s), "play \"%s\"", szShort); + mciSendStringA(s, NULL, 0, NULL); + } + + DBFreeVariant(&dbv); + } + } +} + +static void UpdateReminderEvent(REMINDERDATA *pReminder, UINT nElapsedSeconds, BOOL *pHasPlayedSound) +{ + DWORD dwSoundMask; + + if (pReminder->RepeatSound) + { + if (nElapsedSeconds >= pReminder->RepeatSoundTTL) + { + pReminder->RepeatSoundTTL = pReminder->RepeatSound; + + dwSoundMask = 1 << pReminder->SoundSel; + + if ( !(*pHasPlayedSound & dwSoundMask) ) + { + switch (pReminder->SoundSel) + { + case 1: SkinPlaySoundPoly("AlertReminder2"); break; + case 2: SkinPlaySoundPoly("AlertReminder3"); break; + default: + SkinPlaySoundPoly("AlertReminder"); + } + + *pHasPlayedSound |= dwSoundMask; + } + } + else + { + pReminder->RepeatSoundTTL -= nElapsedSeconds; + } + } +} + +static void FireReminder(REMINDERDATA *pReminder, BOOL *pHasPlayedSound) +{ + DWORD dwSoundMask; + + if (pReminder->SystemEventQueued) + return; + + // add a system event + { + CLISTEVENT ev = { 0 }; + + ev.cbSize = sizeof(ev); + ev.hIcon = g_hReminderIcon; + ev.flags = CLEF_URGENT; + ev.lParam = (LPARAM)pReminder->uid; + ev.pszService = MODULENAME"/OpenTriggeredReminder"; + ev.pszTooltip = Translate("Reminder"); + + CallService(MS_CLIST_ADDEVENT,0,(LPARAM)&ev); + } + + pReminder->SystemEventQueued = TRUE; + QueuedReminderCount++; + + if (pReminder->SoundSel < 0) + { + // sound disabled + return; + } + + dwSoundMask = 1 << pReminder->SoundSel; + + pReminder->RepeatSoundTTL = pReminder->RepeatSound; + + if ( !(*pHasPlayedSound & dwSoundMask) ) + { + switch (pReminder->SoundSel) + { + case 1: SkinPlaySoundPoly("AlertReminder2"); break; + case 2: SkinPlaySoundPoly("AlertReminder3"); break; + default: + SkinPlaySoundPoly("AlertReminder"); + } + + *pHasPlayedSound |= dwSoundMask; + } +} + + +BOOL CheckRemindersAndStart(void) +{ + // returns TRUE if there are any triggered reminder with SystemEventQueued, this will shorten the update interval + // allowing sound repeats with shorter intervals + + TREEELEMENT *TTE; + ULARGE_INTEGER curT; + BOOL bHasPlayedSound; + BOOL bResult; + BOOL bHasQueuedReminders; + + if (!RemindersList) + return FALSE; + + { + SYSTEMTIME tm; + GetSystemTime(&tm); + SYSTEMTIMEtoFILETIME(&tm, (FILETIME*)&curT); + } + + // NOTE: reminder list is sorted by trigger time, so we can early out on the first reminder > cur time + + // quick check for normal case with no reminder ready to be triggered and no queued triggered reminders + // (happens 99.99999999999% of the time) + if (curT.QuadPart < ((REMINDERDATA*)RemindersList->ptrdata)->When.QuadPart && !QueuedReminderCount) + { + return FALSE; + } + + bResult = FALSE; + + // var used to avoid playing multiple alarm sounds during a single update + bHasPlayedSound = FALSE; + + // if there are queued (triggered) reminders then iterate through entire list, becaue of WM_TIMECHANGE events + // and for example daylight saving changes it's possible for an already triggered event to end up with When>curT + bHasQueuedReminders = (QueuedReminderCount != 0); + + // allthough count should always be correct, it's fool proof to just count them again in the loop below + QueuedReminderCount = 0; + + TTE = RemindersList; + while (TTE && (bHasQueuedReminders || ((REMINDERDATA*)TTE->ptrdata)->When.QuadPart <= curT.QuadPart)) + { + REMINDERDATA *pReminder = (REMINDERDATA*)TTE->ptrdata; + + if (!pReminder->RemVisible) + { + if (pReminder->SystemEventQueued) + { + UpdateReminderEvent(pReminder, REMINDER_UPDATE_INTERVAL_SHORT/1000, &bHasPlayedSound); + + QueuedReminderCount++; + bResult = TRUE; + } + else if (((REMINDERDATA*)TTE->ptrdata)->When.QuadPart <= curT.QuadPart) + { + if (!g_RemindSMS) + { + FireReminder(pReminder, &bHasPlayedSound); + + if (pReminder->SystemEventQueued) + bResult = TRUE; + } + else + { + char* S2 = strchr(g_RemindSMS, '@'); + char* S1 = (char*)malloc(S2 - g_RemindSMS); + + strncpy(S1, g_RemindSMS, S2 - g_RemindSMS); + S1[S2 - g_RemindSMS]= 0x0; + S2++; + Send(S1, S2, pReminder->Reminder ? pReminder->Reminder : "", NULL); + SAFE_FREE((void**)&S1); + DeleteReminder(pReminder); + JustSaveReminders(); + NOTIFY_LIST(); + } + } + } + + TTE = (TREEELEMENT*)TTE->next; + } + + return bResult; +} + + +static LRESULT CALLBACK DatePickerWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + case WM_RBUTTONDBLCLK: + case WM_KEYDOWN: + case WM_KEYUP: + case WM_CHAR: + case WM_INITMENUPOPUP: + case WM_PASTE: + return TRUE; + case WM_SYSKEYUP: + case WM_SYSKEYDOWN: + case WM_SYSCHAR: + return FALSE; + } + + return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("OldWndProc")), hWnd, message, wParam, lParam); +} + +static void InitDatePicker(HWND Dialog, UINT nIDDate) +{ + // subclass date picker to prevent user editing (should only use the dropdown calender to ensure valid dates) + WNDPROC pOldWndProc; + HWND hCtrl = GetDlgItem(Dialog, nIDDate); + + // tweak style of picker + if ( IsWinVerVistaPlus() ) + { + DWORD dw = SendDlgItemMessage(Dialog,nIDDate,DTM_GETMCSTYLE,0,0); + dw |= MCS_WEEKNUMBERS | MCS_NOSELCHANGEONNAV; + SendDlgItemMessage(Dialog,nIDDate,DTM_SETMCSTYLE,0,dw); + } + +#ifdef _WIN64 + pOldWndProc = (WNDPROC)SetWindowLongPtr(hCtrl, GWLP_WNDPROC, (LONG_PTR)DatePickerWndProc); +#else + pOldWndProc = (WNDPROC)SetWindowLong(hCtrl, GWL_WNDPROC, (LONG)DatePickerWndProc); +#endif + + SetProp(hCtrl, TEXT("OldWndProc"), pOldWndProc); +} + +static BOOL ParseTime(LPCSTR s, int *hout, int *mout, BOOL bTimeOffset, BOOL bAllowOffsetOverride) +{ + // validate format: [][':'[]][p | P].* + + // if bTimeOffset is FALSE the user may still enter a time offset by using + as the first char, the + // delta time will be returned in minutes (even > 60) and hout will always be -1 + + // if bTimeOffset is TRUE time is always interpreted as an offset (ignores PM indicator and defaults to minutes) + + int h, m; + BOOL bOffset = bTimeOffset; + + // read hour + + while ( iswspace(*s) ) s++; + + if (*s == '+') + { + if (!bTimeOffset) + { + if (!bAllowOffsetOverride) + return FALSE; + + // treat value as an offset anyway + bOffset = TRUE; + } + + s++; + while ( iswspace(*s) ) s++; + } + + if ( !isdigit(*s) ) + return FALSE; + h = (int)(*s-'0'); + s++; + + if (!bOffset) + { + if ( isdigit(*s) ) + { + h = h * 10 + (int)(*s-'0'); + s++; + } + + if ( isdigit(*s) ) + return FALSE; + } + else + { + // allow more than 2-digit numbers for offset + while ( isdigit(*s) ) + { + h = h * 10 + (int)(*s-'0'); + s++; + } + } + + // find : separator + + while ( iswspace(*s) ) s++; + + if (*s == ':') + { + s++; + + // read minutes + + while ( iswspace(*s) ) s++; + + if ( !isdigit(*s) ) + return FALSE; + m = (int)(*s-'0'); + s++; + + if ( isdigit(*s) ) + { + m = m * 10 + (int)(*s-'0'); + s++; + } + } + else + { + if (bOffset) + { + // no : separator found, interpret the entered number as minutes and allow > 60 + + if (h < 0) + return FALSE; + + if (bTimeOffset) + { + *hout = h / 60; + *mout = h % 60; + } + else + { + *mout = h; + *hout = -1; + } + + return TRUE; + } + else + { + m = 0; + } + } + + // validate time + if (bOffset) + { + if (h < 0) + return FALSE; + if (m < 0 || m > 59) + return FALSE; + } + else + { + if (h == 24) + h = 0; + else if (h < 0 || h > 23) + return FALSE; + if (m < 0 || m > 59) + return FALSE; + } + + if (!bOffset) + { + // check for PM indicator (not strict, only checks for P char) + + while ( iswspace(*s) ) s++; + + if (*s == 'p' || *s == 'P') + { + if (h < 13) + h += 12; + else if (h == 12) + h = 0; + } + } + else if (!bTimeOffset) + { + // entered time is an offset + + *mout = h * 60 + m; + *hout = -1; + + return TRUE; + } + + *hout = h; + *mout = m; + + return TRUE; +} + +// returns TRUE if combo box list displays time offsets ("23:34 (5 Minutes)" etc.) +__inline static BOOL IsRelativeCombo(HWND Dialog, UINT nIDTime) +{ + return (int)SendDlgItemMessage(Dialog,nIDTime,CB_GETITEMDATA,0,0) >= 0; +} + +static void PopulateTimeCombo(HWND Dialog, UINT nIDTime, BOOL bRelative, const SYSTEMTIME *tmUtc) +{ + // NOTE: may seem like a bit excessive time converstion and handling, but this is done in order + // to gracefully handle crossing daylight saving boundaries + + SYSTEMTIME tm2; + ULARGE_INTEGER li; + ULONGLONG ref; + int i, n; + char s[64]; + const ULONGLONG MinutesToFileTime = (ULONGLONG)60 * FILETIME_TICKS_PER_SEC; + LPCSTR lpszMinutes; + LPCSTR lpszHours; + WORD wCurHour, wCurMinute; + + if (!bRelative) + { + SendDlgItemMessage(Dialog,nIDTime,CB_RESETCONTENT,0,0); + + // ensure that we start on midnight local time + SystemTimeToTzSpecificLocalTime(NULL, (SYSTEMTIME*)tmUtc, &tm2); + tm2.wHour = 0; + tm2.wMinute = 0; + tm2.wSecond = 0; + tm2.wMilliseconds = 0; + TzSpecificLocalTimeToSystemTime(NULL, &tm2, &tm2); + SYSTEMTIMEtoFILETIME(&tm2, (FILETIME*)&li); + + // from 00:00 to 23:30 in 30 minute steps + for (i=0; i<50; i++) + { + const int h = i>>1; + const int m = (i&1) ? 30 : 0; + + FileTimeToTzLocalST((FILETIME*)&li, &tm2); + sprintf(s, "%02d:%02d", (UINT)tm2.wHour, (UINT)tm2.wMinute); + n = SendDlgItemMessage(Dialog,nIDTime,CB_ADDSTRING,0,(LPARAM)s); + // item data contains time offset from midnight in seconds (bit 31 is set to flag that + // combo box items are absolute times and not relative times like below + SendDlgItemMessage(Dialog,nIDTime,CB_SETITEMDATA,(WPARAM)n,(LPARAM)((ULONG)((h*60+m)*60) | 0x80000000)); + + li.QuadPart += (ULONGLONG)30 * MinutesToFileTime; + + if (tm2.wHour == 23 && tm2.wMinute >= 30) + break; + } + + return; + } + + // + + SendDlgItemMessage(Dialog, nIDTime, CB_RESETCONTENT, 0, 0); + + lpszMinutes = Translate("Minutes"); + lpszHours = Translate("Hours"); + + SYSTEMTIMEtoFILETIME(tmUtc, (FILETIME*)&li); + ref = li.QuadPart; + + // NOTE: item data contains offset from reference time (tmUtc) in seconds + + // cur time + FileTimeToTzLocalST((FILETIME*)&li, &tm2); + wCurHour = tm2.wHour; + wCurMinute = tm2.wMinute; + mir_snprintf(s, sizeof(s), "%02d:%02d", (UINT)tm2.wHour, (UINT)tm2.wMinute); + n = SendDlgItemMessage(Dialog,nIDTime,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDTime,CB_SETITEMDATA,(WPARAM)n,(LPARAM)((li.QuadPart-ref)/FILETIME_TICKS_PER_SEC)); + + // 5 minutes + li.QuadPart += (ULONGLONG)5 * MinutesToFileTime; + FileTimeToTzLocalST((FILETIME*)&li, &tm2); + mir_snprintf(s, sizeof(s), "%02d:%02d (5 %s)", (UINT)tm2.wHour, (UINT)tm2.wMinute, lpszMinutes); + n = SendDlgItemMessage(Dialog,nIDTime,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDTime,CB_SETITEMDATA,(WPARAM)n,(LPARAM)((li.QuadPart-ref)/FILETIME_TICKS_PER_SEC)); + + // 10 minutes + li.QuadPart += (ULONGLONG)5 * MinutesToFileTime; + FileTimeToTzLocalST((FILETIME*)&li, &tm2); + mir_snprintf(s, sizeof(s), "%02d:%02d (10 %s)", (UINT)tm2.wHour, (UINT)tm2.wMinute, lpszMinutes); + n = SendDlgItemMessage(Dialog,nIDTime,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDTime,CB_SETITEMDATA,(WPARAM)n,(LPARAM)((li.QuadPart-ref)/FILETIME_TICKS_PER_SEC)); + + // 15 minutes + li.QuadPart += (ULONGLONG)5 * MinutesToFileTime; + FileTimeToTzLocalST((FILETIME*)&li, &tm2); + mir_snprintf(s, sizeof(s), "%02d:%02d (15 %s)", (UINT)tm2.wHour, (UINT)tm2.wMinute, lpszMinutes); + n = SendDlgItemMessage(Dialog,nIDTime,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDTime,CB_SETITEMDATA,(WPARAM)n,(LPARAM)((li.QuadPart-ref)/FILETIME_TICKS_PER_SEC)); + + // 30 minutes + li.QuadPart += (ULONGLONG)15 * MinutesToFileTime; + FileTimeToTzLocalST((FILETIME*)&li, &tm2); + mir_snprintf(s, sizeof(s), "%02d:%02d (30 %s)", (UINT)tm2.wHour, (UINT)tm2.wMinute, lpszMinutes); + n = SendDlgItemMessage(Dialog,nIDTime,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDTime,CB_SETITEMDATA,(WPARAM)n,(LPARAM)((li.QuadPart-ref)/FILETIME_TICKS_PER_SEC)); + + // round +1h time to nearest even or half hour + li.QuadPart += (ULONGLONG)30 * MinutesToFileTime; + li.QuadPart = (li.QuadPart / (30 * MinutesToFileTime)) * (30 * MinutesToFileTime); + + // add from +1 to +23.5 (in half hour steps) if crossing daylight saving boundary it may be 22.5 or 24.5 hours + for (i=0; i<50; i++) + { + UINT dt; + + FileTimeToTzLocalST((FILETIME*)&li, &tm2); + + if (i > 40) + { + UINT nLastEntry = ((UINT)wCurHour * 60 + (UINT)wCurMinute) / 30; + if (nLastEntry) + nLastEntry *= 30; + else + nLastEntry = 23*60 + 30; + + if (((UINT)tm2.wHour * 60 + (UINT)tm2.wMinute) == nLastEntry) + break; + } + + // icq-style display 1.0, 1.5 etc. hours even though that isn't accurate due to rounding + //mir_snprintf(s, sizeof(s), "%02d:%02d (%d.%d %s)", (UINT)tm2.wHour, (UINT)tm2.wMinute, 1+(i>>1), (i&1) ? 5 : 0, lpszHours); + // display delta time more accurately to match reformatting (that icq doesn't do) + dt = (UINT)((li.QuadPart/MinutesToFileTime) - (ref/MinutesToFileTime)); + if (dt < 60) + mir_snprintf(s, sizeof(s), "%02d:%02d (%d %s)", (UINT)tm2.wHour, (UINT)tm2.wMinute, dt, lpszMinutes); + else + mir_snprintf(s, sizeof(s), "%02d:%02d (%d.%d %s)", (UINT)tm2.wHour, (UINT)tm2.wMinute, dt/60, ((dt%60)*10)/60, lpszHours); + n = SendDlgItemMessage(Dialog,nIDTime,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDTime,CB_SETITEMDATA,(WPARAM)n,(LPARAM)(dt*60)); + + li.QuadPart += (ULONGLONG)30 * MinutesToFileTime; + } +} + +static void PopulateTimeOffsetCombo(HWND Dialog, UINT nIDCombo) +{ + int i, n; + LPCSTR lpszMinutes; + LPCSTR lpszHour; + LPCSTR lpszHours; + LPCSTR lpszDay; + LPCSTR lpszDays; + LPCSTR lpszWeek; + char s[MAX_PATH]; + + SendDlgItemMessage(Dialog,nIDCombo,CB_RESETCONTENT,0,0); + + lpszMinutes = Translate("Minutes"); + lpszHour = Translate("Hour"); + lpszHours = Translate("Hours"); + lpszDay = Translate("Day"); + lpszDays = Translate("Days"); + lpszWeek = Translate("Week"); + + // 5 - 55 minutes (in 5 minute steps) + for (i = 1; i < 12; i++) + { + mir_snprintf(s, sizeof(s), "%d %s", i*5, lpszMinutes); + n = SendDlgItemMessage(Dialog,nIDCombo,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDCombo,CB_SETITEMDATA,(WPARAM)n,(LPARAM)(i*5)); + } + + // 1 hour + mir_snprintf(s, sizeof(s), "1 %s", lpszHour); + n = SendDlgItemMessage(Dialog,nIDCombo,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDCombo,CB_SETITEMDATA,(WPARAM)n,(LPARAM)60); + + // 2, 4, 8 hours + for (i = 2; i <= 8; i+=2) + { + mir_snprintf(s, sizeof(s), "%d %s", i, lpszHours); + n = SendDlgItemMessage(Dialog,nIDCombo,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDCombo,CB_SETITEMDATA,(WPARAM)n,(LPARAM)(i*60)); + } + + // 1 day + mir_snprintf(s, sizeof(s), "1 %s", lpszDay); + n = SendDlgItemMessage(Dialog,nIDCombo,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDCombo,CB_SETITEMDATA,(WPARAM)n,(LPARAM)(24*60)); + + // 2-4 days + for (i = 2; i <= 4; i++) + { + mir_snprintf(s, sizeof(s), "%d %s", i, lpszDays); + n = SendDlgItemMessage(Dialog,nIDCombo,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDCombo,CB_SETITEMDATA,(WPARAM)n,(LPARAM)(i*24*60)); + } + + // 1 week + mir_snprintf(s, sizeof(s), "1 %s", lpszWeek); + n = SendDlgItemMessage(Dialog,nIDCombo,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,nIDCombo,CB_SETITEMDATA,(WPARAM)n,(LPARAM)(7*24*60)); +} + +// returns non-zero if specified time was inside "missing" hour of daylight saving +// IMPORTANT: triggerRelUtcOut is only initialized if IsRelativeCombo() is TRUE and return value is 0 +static int ReformatTimeInputEx(HWND Dialog, UINT nIDTime, UINT nIDRefTime, int h, int m, const SYSTEMTIME *pDateLocal, ULARGE_INTEGER *triggerRelUtcOut) +{ + int n; + UINT dt; + char buf[64]; + const ULONGLONG MinutesToFileTime = (ULONGLONG)60 * FILETIME_TICKS_PER_SEC; + + if (h < 0) + { + // time value is an offset ('m' holds the offset in minutes) + + if ( IsRelativeCombo(Dialog, nIDTime) ) + { + ULONGLONG ref; + ULARGE_INTEGER li; + SYSTEMTIME tm; + + // get reference time (UTC) from hidden control + { + GetDlgItemText(Dialog, nIDRefTime, buf, 30); + li.QuadPart = ref = _strtoui64(buf, NULL, 16); + } + + // clamp delta time to 23.5 hours (coule be issues otherwise as relative combo only handles <24) + if (m > (23*60+30)) + m = 23*60+30; + + li.QuadPart += (ULONGLONG)(m * 60) * FILETIME_TICKS_PER_SEC; + + FileTimeToTzLocalST((FILETIME*)&li, &tm); + h = (int)tm.wHour; + m = (int)tm.wMinute; + + if (triggerRelUtcOut) + *triggerRelUtcOut = li; + + dt = (UINT)((li.QuadPart/MinutesToFileTime) - (ref/MinutesToFileTime)); + + if (dt < 60) + mir_snprintf(buf, sizeof(buf), "%02d:%02d (%d %s)", h, m, dt, Translate("Minutes")); + else + mir_snprintf(buf, sizeof(buf), "%02d:%02d (%d.%d %s)", h, m, dt/60, ((dt%60)*10)/60, Translate("Hours")); + + // search for preset + n = SendDlgItemMessage(Dialog, nIDTime, CB_FINDSTRING, (WPARAM)-1, (LPARAM)buf); + if (n != CB_ERR) + { + SendDlgItemMessage(Dialog, nIDTime, CB_SETCURSEL, (WPARAM)n, 0); + return 0; + } + + SetDlgItemText(Dialog, nIDTime, buf); + } + else + { + // should never happen + SendDlgItemMessage(Dialog, nIDTime, CB_SETCURSEL, 0, 0); + } + + return 0; + } + + // + + sprintf(buf, "%02d:%02d", h, m); + + // search for preset first + n = SendDlgItemMessage(Dialog, nIDTime, CB_FINDSTRING, (WPARAM)-1, (LPARAM)buf); + if (n != CB_ERR) + { + SendDlgItemMessage(Dialog, nIDTime, CB_SETCURSEL, (WPARAM)n, 0); + return 0; + } + + if ( IsRelativeCombo(Dialog, nIDTime) ) + { + // date format is a time offset ("24:43 (5 Minutes)" etc.) + + ULONGLONG ref; + SYSTEMTIME tmRefLocal; + SYSTEMTIME tmTriggerLocal, tmTriggerLocal2; + + // get reference time (UTC) from hidden control + { + GetDlgItemText(Dialog, nIDRefTime, buf, 30); + ref = _strtoui64(buf, NULL, 16); + } + + FileTimeToTzLocalST((FILETIME*)&ref, &tmRefLocal); + + { + ULARGE_INTEGER li; + const UINT nRefT = (UINT)tmRefLocal.wHour * 60 + (UINT)tmRefLocal.wMinute; + const UINT nT = h * 60 + m; + + tmTriggerLocal = tmRefLocal; + tmTriggerLocal.wHour = (WORD)h; + tmTriggerLocal.wMinute = (WORD)m; + tmTriggerLocal.wSecond = 0; + tmTriggerLocal.wMilliseconds = 0; + + if (nT < nRefT) + { + // (this special case only works correctly if time can be returned in triggerRelUtcOut) + if (tmRefLocal.wHour == tmTriggerLocal.wHour && triggerRelUtcOut) + { + // check for special case if daylight saving ends in this hour, then interpret as within the next hour + TzLocalSTToFileTime(&tmTriggerLocal, (FILETIME*)&li); + li.QuadPart += (ULONGLONG)3600*FILETIME_TICKS_PER_SEC; + FileTimeToTzLocalST((FILETIME*)&li, &tmTriggerLocal2); + if ((tmTriggerLocal2.wHour*60+tmTriggerLocal2.wMinute) == (tmTriggerLocal.wHour*60+tmTriggerLocal.wMinute)) + // special case detected + goto output_result; + } + + // tomorrow (add 24h to local time) + SYSTEMTIMEtoFILETIME(&tmTriggerLocal, (FILETIME*)&li); + li.QuadPart += (ULONGLONG)(24*3600)*FILETIME_TICKS_PER_SEC; + FILETIMEtoSYSTEMTIME((FILETIME*)&li, &tmTriggerLocal); + } + + // clean up value for potential daylight saving boundary + TzLocalSTToFileTime(&tmTriggerLocal, (FILETIME*)&li); + FileTimeToTzLocalST((FILETIME*)&li, &tmTriggerLocal2); + + // NOTE: win32 time functions will round hour downward if supplied hour does not exist due to daylight saving + // for example if supplied time is 02:30 on the night the clock is turned forward at 02:00 to 03:00, thus + // there never is a 02:30, the time functions will convert it to 01:30 (and not 03:30 as one might think) + // (02:00 would return 01:00) + + // check for special case when the current time and requested time is inside the "missing" hour + // standard->daylight switch, so that the cleaned up time ends up being earlier than the current + // time even though it originally wasn't (see note above) + if ((tmTriggerLocal2.wHour*60+tmTriggerLocal2.wMinute) < (tmTriggerLocal.wHour*60+tmTriggerLocal.wMinute)) + { + // special case detected, fall back to current time so at least the reminder won't be missed + // due to ending up at an undesired time (this way the user immediately notices something was wrong) + SendDlgItemMessage(Dialog, nIDTime, CB_SETCURSEL, 0, 0); +invalid_dst: + MessageBox(Dialog, Translate("The specified time is invalid due to begin of daylight saving (summer time)."), SECTIONNAME, MB_OK|MB_ICONWARNING); + return 1; + } + +output_result: + if (triggerRelUtcOut) + *triggerRelUtcOut = li; + + dt = (UINT)((li.QuadPart/MinutesToFileTime) - (ref/MinutesToFileTime)); + + if (dt < 60) + mir_snprintf(buf, sizeof(buf), "%02d:%02d (%d %s)", h, m, dt, Translate("Minutes")); + else + mir_snprintf(buf, sizeof(buf), "%02d:%02d (%d.%d %s)", h, m, dt/60, ((dt%60)*10)/60, Translate("Hours")); + } + } + else + { + // absolute time (00:00 to 23:59), clean up time to make sure it's not inside "missing" hour (will be rounded downard) + + FILETIME ft; + SYSTEMTIME Date = *pDateLocal; + Date.wHour = h; + Date.wMinute = m; + Date.wSecond = 0; + Date.wMilliseconds = 0; + + TzLocalSTToFileTime(&Date, &ft); + FileTimeToTzLocalST(&ft, &Date); + + if ((int)Date.wHour != h || (int)Date.wMinute != m) + { + sprintf(buf, "%02d:%02d", (UINT)Date.wHour, (UINT)Date.wMinute); + + // search for preset again + n = SendDlgItemMessage(Dialog, nIDTime, CB_FINDSTRING, (WPARAM)-1, (LPARAM)buf); + if (n != CB_ERR) + { + SendDlgItemMessage(Dialog, nIDTime, CB_SETCURSEL, (WPARAM)n, 0); + goto invalid_dst; + } + + SetDlgItemText(Dialog, nIDTime, buf); + + goto invalid_dst; + } + } + + SetDlgItemText(Dialog, nIDTime, buf); + + return 0; +} + +static __inline int ReformatTimeInput(HWND Dialog, UINT nIDTime, UINT nIDRefTime, int h, int m, const SYSTEMTIME *pDateLocal) +{ + return ReformatTimeInputEx(Dialog, nIDTime, nIDRefTime, h, m, pDateLocal, NULL); +} + +// in: pDate contains the desired trigger date in LOCAL time +// out: pDate contains the resulting trigger time and date in UTC +static BOOL GetTriggerTime(HWND Dialog, UINT nIDTime, UINT nIDRefTime, SYSTEMTIME *pDate) +{ + ULARGE_INTEGER li; + char buf[32]; + int n; + int h, m; + + // get reference (UTC) time from hidden control + { + GetDlgItemText(Dialog, nIDRefTime, buf, 30); + li.QuadPart = _strtoui64(buf, NULL, 16); + } + + if ((n = SendDlgItemMessage(Dialog, nIDTime, CB_GETCURSEL, 0, 0)) != CB_ERR) + { + // use preset value +preset_value:; + if ( IsRelativeCombo(Dialog, nIDTime) ) + { + // time offset from ref time ("24:43 (5 Minutes)" etc.) + + UINT nDeltaSeconds = (UINT)SendDlgItemMessage(Dialog, nIDTime, CB_GETITEMDATA, (WPARAM)n, 0); + li.QuadPart += (ULONGLONG)nDeltaSeconds * FILETIME_TICKS_PER_SEC; + + FILETIMEtoSYSTEMTIME((FILETIME*)&li, pDate); + + // if specified time is a small offset (< 10 Minutes) then retain current second count for better accuracy + // otherwise try to match specified time (which never contains seconds only even minutes) as close as possible + if (nDeltaSeconds >= 10*60) + { + pDate->wSecond = 0; + pDate->wMilliseconds = 0; + } + } + else + { + // absolute time (offset from midnight on pDate) + + UINT nDeltaSeconds = (UINT)((ULONG)SendDlgItemMessage(Dialog, nIDTime, CB_GETITEMDATA, (WPARAM)n, 0) & ~0x80000000); + pDate->wHour = 0; + pDate->wMinute = 0; + pDate->wSecond = 0; + pDate->wMilliseconds = 0; + TzLocalSTToFileTime(pDate, (FILETIME*)&li); + li.QuadPart += (ULONGLONG)nDeltaSeconds * FILETIME_TICKS_PER_SEC; + + FILETIMEtoSYSTEMTIME((FILETIME*)&li, pDate); + } + + return TRUE; + } + + // user entered a custom value + + GetDlgItemText(Dialog, nIDTime, buf, 30); + + if ( !ParseTime(buf, &h, &m, FALSE, IsRelativeCombo(Dialog, nIDTime)) ) + { + MessageBox(Dialog, Translate("The specified time is invalid."), SECTIONNAME, MB_OK|MB_ICONWARNING); + return FALSE; + } + + if ( IsRelativeCombo(Dialog, nIDTime) ) + { + // date has not been changed, the specified time is a time between reftime and reftime+24h + + ULARGE_INTEGER li2; + + if ( ReformatTimeInputEx(Dialog, nIDTime, nIDRefTime, h, m, pDate, &li2) ) + return FALSE; + + // check if reformatted value is a preset + if ((n = SendDlgItemMessage(Dialog, nIDTime, CB_GETCURSEL, 0, 0)) != CB_ERR) + goto preset_value; + + FILETIMEtoSYSTEMTIME((FILETIME*)&li2, pDate); + + return TRUE; + } + else + { + if ( ReformatTimeInputEx(Dialog, nIDTime, nIDRefTime, h, m, pDate, NULL) ) + return FALSE; + + // check if reformatted value is a preset + if ((n = SendDlgItemMessage(Dialog, nIDTime, CB_GETCURSEL, 0, 0)) != CB_ERR) + goto preset_value; + } + + // absolute time (on pDate) + + pDate->wHour = h; + pDate->wMinute = m; + pDate->wSecond = 0; + pDate->wMilliseconds = 0; + + TzLocalSTToFileTime(pDate, (FILETIME*)&li); + FILETIMEtoSYSTEMTIME((FILETIME*)&li, pDate); + + return TRUE; +} + +static void OnDateChanged(HWND Dialog, UINT nDateID, UINT nTimeID, UINT nRefTimeID) +{ + // repopulate time combo list with regular times (not offsets like "23:32 (5 minutes)" etc.) + + SYSTEMTIME Date, DateUtc; + int h = -1, m; + char s[32]; + + GetDlgItemText(Dialog, nTimeID, s, 30); + + ParseTime(s, &h, &m, FALSE, FALSE); + + SendDlgItemMessage(Dialog, nDateID, DTM_GETSYSTEMTIME, 0, (LPARAM)&Date); + + TzSpecificLocalTimeToSystemTime(NULL, &Date, &DateUtc); + PopulateTimeCombo(Dialog, nTimeID, FALSE, &DateUtc); + + if (h < 0) + { + // parsing failed, default to current time + SYSTEMTIME tm; + GetLocalTime(&tm); + h = (UINT)tm.wHour; + m = (UINT)tm.wMinute; + } + + ReformatTimeInput(Dialog, nTimeID, nRefTimeID, h, m, &Date); +} + + +int CALLBACK DlgProcNotifyReminder(HWND Dialog,UINT Message,WPARAM wParam,LPARAM lParam) +{ + int I; + + switch (Message) + { + case WM_INITDIALOG: + { + SYSTEMTIME tm; + ULARGE_INTEGER li; + + GetSystemTime(&tm); + SYSTEMTIMEtoFILETIME(&tm, (FILETIME*)&li); + + TranslateDialogDefault(Dialog); + + // save reference time in hidden control, is needed when adding reminder to properly detect if speicifed + // time wrapped around to tomorrow or not (dialog could in theory be open for a longer period of time + // which could potentially mess up things otherwise) + { + char s[32]; + sprintf(s, "%I64x", li.QuadPart); + SetDlgItemText(Dialog, IDC_REFTIME, s); + } + + BringWindowToTop(Dialog); + + PopulateTimeOffsetCombo(Dialog, IDC_REMINDAGAININ); + + ShowWindow(GetDlgItem(Dialog,IDC_REMINDAGAININ),SW_SHOW); + ShowWindow(GetDlgItem(Dialog,IDC_DATEAGAIN),SW_HIDE); + ShowWindow(GetDlgItem(Dialog,IDC_TIMEAGAIN),SW_HIDE); + ShowWindow(GetDlgItem(Dialog,IDC_STATIC_DATE),SW_HIDE); + ShowWindow(GetDlgItem(Dialog,IDC_STATIC_TIME),SW_HIDE); + SendDlgItemMessage(Dialog,IDC_AFTER,BM_SETCHECK,1,0); + SendDlgItemMessage(Dialog,IDC_ONDATE,BM_SETCHECK,0,0); + SendDlgItemMessage(Dialog,IDC_REMINDAGAININ,CB_SETCURSEL,0,0); + + SendDlgItemMessage(Dialog, IDC_REMDATA, EM_LIMITTEXT, MAX_REMINDER_LEN, 0); + + PopulateTimeCombo(Dialog, IDC_TIMEAGAIN, TRUE, &tm); + + FileTimeToTzLocalST((FILETIME*)&li, &tm); + + // make sure date picker uses reference time + SendDlgItemMessage(Dialog,IDC_DATEAGAIN,DTM_SETSYSTEMTIME,0,(LPARAM)&tm); + InitDatePicker(Dialog, IDC_DATEAGAIN); + + SendDlgItemMessage(Dialog,IDC_TIMEAGAIN,CB_SETCURSEL,0,0); + + return TRUE; + } + case WM_NCDESTROY: + RemoveProp(GetDlgItem(Dialog, IDC_DATEAGAIN), TEXT("OldWndProc")); + return TRUE; + case WM_NOTIFY: + { + if (wParam == IDC_DATEAGAIN) + { + NMLISTVIEW *NM = (NMLISTVIEW*)lParam; + + switch (NM->hdr.code) + { + case DTN_DATETIMECHANGE: + OnDateChanged(Dialog, IDC_DATEAGAIN, IDC_TIMEAGAIN, IDC_REFTIME); + break; + } + } + } + break; + case WM_CLOSE: + { + int ReminderCount = TreeGetCount(RemindersList); + for (I = 0; I < ReminderCount; I++) + { + REMINDERDATA *pReminder = (REMINDERDATA*)TreeGetAt(RemindersList, I); + + if (pReminder->handle == Dialog) + { + DeleteReminder(pReminder); + JustSaveReminders(); + break; + } + } + NOTIFY_LIST(); + } + DestroyWindow(Dialog); + return TRUE; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_TIMEAGAIN: + switch (HIWORD(wParam)) + { + case CBN_KILLFOCUS: + // reformat displayed value + if (SendDlgItemMessage(Dialog, IDC_TIMEAGAIN, CB_GETCURSEL, 0, 0) == CB_ERR) + { + char buf[64]; + int h, m; + GetDlgItemText(Dialog, IDC_TIMEAGAIN, buf, 30); + + if ( ParseTime(buf, &h, &m, FALSE, IsRelativeCombo(Dialog, IDC_TIMEAGAIN)) ) + { + SYSTEMTIME Date; + SendDlgItemMessage(Dialog,IDC_DATEAGAIN,DTM_GETSYSTEMTIME,0,(LPARAM)&Date); + + ReformatTimeInput(Dialog, IDC_TIMEAGAIN, IDC_REFTIME, h, m, &Date); + } + else + { + SendDlgItemMessage(Dialog, IDC_TIMEAGAIN, CB_SETCURSEL, 0, 0); + } + } + break; + } + break; + + case IDC_REMINDAGAININ: + switch (HIWORD(wParam)) + { + case CBN_KILLFOCUS: + // reformat displayed value if it has been edited + if (SendDlgItemMessage(Dialog,IDC_REMINDAGAININ,CB_GETCURSEL,0,0) == CB_ERR) + { + char buf[64]; + int h, m; + GetDlgItemText(Dialog, IDC_REMINDAGAININ, buf, 30); + + if (ParseTime(buf, &h, &m, TRUE, TRUE) && h+m) + { + if (h) + { + LPCSTR lpszHours = Translate("Hours"); + sprintf(buf, "%d:%02d %s", h, m, lpszHours); + } + else + { + LPCSTR lpszMinutes = Translate("Minutes"); + sprintf(buf, "%d %s", m, lpszMinutes); + } + SetDlgItemText(Dialog, IDC_REMINDAGAININ, buf); + } + else + { + SendDlgItemMessage(Dialog,IDC_REMINDAGAININ,CB_SETCURSEL,0,0); + } + } + break; + } + break; + + case IDC_AFTER: + { + ShowWindow(GetDlgItem(Dialog,IDC_REMINDAGAININ),SW_SHOW); + ShowWindow(GetDlgItem(Dialog,IDC_DATEAGAIN),SW_HIDE); + ShowWindow(GetDlgItem(Dialog,IDC_TIMEAGAIN),SW_HIDE); + ShowWindow(GetDlgItem(Dialog,IDC_STATIC_DATE),SW_HIDE); + ShowWindow(GetDlgItem(Dialog,IDC_STATIC_TIME),SW_HIDE); + return TRUE; + } + case IDC_ONDATE: + { + ShowWindow(GetDlgItem(Dialog,IDC_DATEAGAIN),SW_SHOW); + ShowWindow(GetDlgItem(Dialog,IDC_TIMEAGAIN),SW_SHOW); + ShowWindow(GetDlgItem(Dialog,IDC_STATIC_DATE),SW_SHOW); + ShowWindow(GetDlgItem(Dialog,IDC_STATIC_TIME),SW_SHOW); + ShowWindow(GetDlgItem(Dialog,IDC_REMINDAGAININ),SW_HIDE); + return TRUE; + } + case IDC_DISMISS: + { + int ReminderCount = TreeGetCount(RemindersList); + for (I = 0; I < ReminderCount; I++) + { + REMINDERDATA *pReminder = (REMINDERDATA*)TreeGetAt(RemindersList, I); + + if (pReminder->handle == Dialog) + { + DeleteReminder(pReminder); + JustSaveReminders(); + break; + } + } + NOTIFY_LIST(); + DestroyWindow(Dialog); + return TRUE; + } + case IDC_REMINDAGAIN: + { + int ReminderCount = TreeGetCount(RemindersList); + for (I = 0; I < ReminderCount; I++) + { + REMINDERDATA *pReminder = (REMINDERDATA*)TreeGetAt(RemindersList, I); + + if (pReminder->handle == Dialog) + { + if (SendDlgItemMessage(Dialog,IDC_AFTER,BM_GETCHECK,0,0) == BST_CHECKED) + { + // delta time + + ULONGLONG TT; + SYSTEMTIME tm; + ULARGE_INTEGER li; + int n; + + GetSystemTime(&tm); + SYSTEMTIMEtoFILETIME(&tm, (FILETIME*)&li); + + n = SendDlgItemMessage(Dialog,IDC_REMINDAGAININ,CB_GETCURSEL,0,0); + if (n != CB_ERR) + { + TT = SendDlgItemMessage(Dialog,IDC_REMINDAGAININ,CB_GETITEMDATA,(WPARAM)n,0) * 60; + + if (TT >= 24*3600) + { + // selection is 1 day or more, take daylight saving boundaries into consideration + // (ie. 24 hour might actually be 23 or 25, in order for reminder to pop up at the + // same time tomorrow) + SYSTEMTIME tm2, tm3; + ULARGE_INTEGER li2 = li; + FileTimeToTzLocalST((FILETIME*)&li2, &tm2); + li2.QuadPart += (TT * FILETIME_TICKS_PER_SEC); + FileTimeToTzLocalST((FILETIME*)&li2, &tm3); + if (tm2.wHour != tm3.wHour || tm2.wMinute != tm3.wMinute) + { + // boundary crossed + + // do a quick and dirty sanity check that times not more than 2 hours apart + if (abs((int)(tm3.wHour*60+tm3.wMinute)-(int)(tm2.wHour*60+tm2.wMinute)) <= 120) + { + // adjust TT so that same HH:MM is set + tm3.wHour = tm2.wHour; + tm3.wMinute = tm2.wMinute; + TzLocalSTToFileTime(&tm3, (FILETIME*)&li2); + TT = (li2.QuadPart - li.QuadPart) / FILETIME_TICKS_PER_SEC; + } + } + } + } + else + { + // parse user input + char s[32]; + int h = 0, m = 0; + GetDlgItemText(Dialog, IDC_REMINDAGAININ, s, 30); + ParseTime(s, &h, &m, TRUE, TRUE); + m += h * 60; + if (!m) + { + MessageBox(Dialog, Translate("The specified time offset is invalid."), SECTIONNAME, MB_OK|MB_ICONWARNING); + return TRUE; + } + + TT = m * 60; + } + + pReminder->When = li; + pReminder->When.QuadPart += (TT * FILETIME_TICKS_PER_SEC); + } + else if (SendDlgItemMessage(Dialog,IDC_ONDATE,BM_GETCHECK,0,0) == BST_CHECKED) + { + SYSTEMTIME Date; + + SendDlgItemMessage(Dialog,IDC_DATEAGAIN,DTM_GETSYSTEMTIME,0,(LPARAM)&Date); + + if ( !GetTriggerTime(Dialog, IDC_TIMEAGAIN, IDC_REFTIME, &Date) ) + break; + + SYSTEMTIMEtoFILETIME(&Date, (FILETIME*)&pReminder->When); + } + + // update reminder text + { + char *ReminderText = NULL; + int SzT = SendDlgItemMessage(Dialog,IDC_REMDATA,WM_GETTEXTLENGTH,0,0); + if (SzT) + { + if (SzT > MAX_REMINDER_LEN) SzT = MAX_REMINDER_LEN; + ReminderText = (char*)malloc(SzT+1); + SendDlgItemMessage(Dialog,IDC_REMDATA,WM_GETTEXT,SzT+1,(LPARAM)ReminderText); + } + if (pReminder->Reminder) + free(pReminder->Reminder); + pReminder->Reminder = ReminderText; + } + + pReminder->RemVisible = FALSE; + pReminder->handle = NULL; + + // re-insert tree item sorted + TreeDelete(&RemindersList,pReminder); + TreeAddSorted(&RemindersList,pReminder,ReminderSortCb); + JustSaveReminders(); + break; + } + } + NOTIFY_LIST(); + DestroyWindow(Dialog); + return TRUE; + } + case IDC_NONE: + {// create note from remainder + int ReminderCount = TreeGetCount(RemindersList); + for (I = 0; I < ReminderCount; I++) + { + REMINDERDATA *pReminder = (REMINDERDATA*)TreeGetAt(RemindersList, I); + + if (pReminder->handle == Dialog) + { + // get up-to-date reminder text + char *ReminderText = NULL; + int SzT = SendDlgItemMessage(Dialog,IDC_REMDATA,WM_GETTEXTLENGTH,0,0); + if (SzT) + { + if (SzT > MAX_REMINDER_LEN) SzT = MAX_REMINDER_LEN; + ReminderText = (char*)malloc(SzT+1); + SendDlgItemMessage(Dialog,IDC_REMDATA,WM_GETTEXT,SzT+1,(LPARAM)ReminderText); + } + + SetFocus(NewNote(0, 0, -1, -1, ReminderText, 0, TRUE, TRUE, 0)->REHwnd); + break; + } + } + return TRUE; + } + } + } + } + return FALSE; +} + +int CALLBACK DlgProcNewReminder(HWND Dialog,UINT Message,WPARAM wParam,LPARAM lParam) +{ + HICON hIcon = NULL; + switch (Message) + { + case WM_INITDIALOG: + { + ULARGE_INTEGER li; + SYSTEMTIME tm; + + if (NewReminderVisible == 2) + { + // opening the edit reminder dialog (uses same dialog resource as add reminder) + SetWindowText(Dialog, _T("Reminder")); + SetDlgItemText(Dialog, IDC_ADDREMINDER, _T("&Update Reminder")); + ShowWindow(GetDlgItem(Dialog, IDC_VIEWREMINDERS), SW_HIDE); + + li = pEditReminder->When; + FILETIMEtoSYSTEMTIME((FILETIME*)&li, &tm); + } + else + { + GetSystemTime(&tm); + SYSTEMTIMEtoFILETIME(&tm, (FILETIME*)&li); + } + + TranslateDialogDefault(Dialog); + + // save reference time in hidden control, is needed when adding reminder to properly detect if speicifed + // time wrapped around to tomorrow or not (dialog could in theory be open for a longer period of time + // which could potentially mess up things otherwise) + { + char s[32]; + sprintf(s, "%I64x", li.QuadPart); + SetDlgItemText(Dialog, IDC_REFTIME, s); + } + + /*SendDlgItemMessage(Dialog,IDC_NONE,BM_SETCHECK,1,0); + SendDlgItemMessage(Dialog,IDC_DAILY,BM_SETCHECK,0,0); + SendDlgItemMessage(Dialog,IDC_WEEKLY,BM_SETCHECK,0,0); + SendDlgItemMessage(Dialog,IDC_MONTHLY,BM_SETCHECK,0,0);*/ + + PopulateTimeCombo(Dialog, IDC_TIME, NewReminderVisible != 2, &tm); + + FileTimeToTzLocalST((FILETIME*)&li, &tm); + + // make sure date picker uses reference time + SendDlgItemMessage(Dialog,IDC_DATE,DTM_SETSYSTEMTIME,0,(LPARAM)&tm); + InitDatePicker(Dialog, IDC_DATE); + + SendDlgItemMessage(Dialog, IDC_REMINDER, EM_LIMITTEXT, MAX_REMINDER_LEN, 0); + + if (NewReminderVisible == 2) + { + int n; + char s[32]; + mir_snprintf(s, sizeof(s), "%02d:%02d", (UINT)tm.wHour, (UINT)tm.wMinute); + + // search for preset first + n = SendDlgItemMessage(Dialog, IDC_TIME, CB_FINDSTRING, (WPARAM)-1, (LPARAM)s); + if (n != CB_ERR) + SendDlgItemMessage(Dialog, IDC_TIME, CB_SETCURSEL, (WPARAM)n, 0); + else + SetDlgItemText(Dialog, IDC_TIME, s); + + SetDlgItemText(Dialog, IDC_REMINDER, pEditReminder->Reminder); + } + else + { + SendDlgItemMessage(Dialog,IDC_TIME,CB_SETCURSEL,0,0); + } + + // populate sound repeat combo + { + char s[64]; + int n; + LPCSTR lpszEvery = Translate("Every"); + LPCSTR lpszSeconds = Translate("Seconds"); + + // NOTE: use multiples of REMINDER_UPDATE_INTERVAL_SHORT (currently 5 seconds) + + n = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_ADDSTRING,0,(LPARAM)Translate("Never")); + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)0); + + mir_snprintf(s, sizeof(s), "%s 5 %s", lpszEvery, lpszSeconds); + n = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)5); + + mir_snprintf(s, sizeof(s), "%s 10 %s", lpszEvery, lpszSeconds); + n = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)10); + + mir_snprintf(s, sizeof(s), "%s 15 %s", lpszEvery, lpszSeconds); + n = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)15); + + mir_snprintf(s, sizeof(s), "%s 20 %s", lpszEvery, lpszSeconds); + n = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)20); + + mir_snprintf(s, sizeof(s), "%s 30 %s", lpszEvery, lpszSeconds); + n = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)30); + + mir_snprintf(s, sizeof(s), "%s 60 %s", lpszEvery, lpszSeconds); + n = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_ADDSTRING,0,(LPARAM)s); + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)60); + + if (NewReminderVisible == 2 && pEditReminder->RepeatSound) + { + mir_snprintf(s, sizeof(s), "%s %d %s", lpszEvery, pEditReminder->RepeatSound, lpszSeconds); + SetDlgItemText(Dialog, IDC_COMBO_REPEATSND, s); + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETCURSEL,SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_FINDSTRINGEXACT,(WPARAM)-1,(LPARAM)s),0); + } + else + { + SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_SETCURSEL,0,0); + } + } + + // populate sound selection combo + { + int n = SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_ADDSTRING,0,(LPARAM)Translate("Default")); + SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)0); + n = SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_ADDSTRING,0,(LPARAM)Translate("Alternative 1")); + SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)1); + n = SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_ADDSTRING,0,(LPARAM)Translate("Alternative 2")); + SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)2); + n = SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_ADDSTRING,0,(LPARAM)Translate("None")); + SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_SETITEMDATA,(WPARAM)n,(LPARAM)-1); + + if (NewReminderVisible == 2 && pEditReminder->SoundSel) + { + const UINT n = pEditReminder->SoundSel<0 ? 3 : pEditReminder->SoundSel; + SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_SETCURSEL,n,0); + } + else + { + SendDlgItemMessage(Dialog,IDC_COMBO_SOUND,CB_SETCURSEL,0,0); + } + } + + hIcon = Skin_GetIconByHandle(hIconLibItem[12]); + SendDlgItemMessage(Dialog,IDC_BTN_PLAYSOUND,BM_SETIMAGE,(WPARAM)IMAGE_ICON,(LPARAM)hIcon); + + if (NewReminderVisible == 2 && pEditReminder->SoundSel) + { + EnableWindow(GetDlgItem(Dialog, IDC_BTN_PLAYSOUND), pEditReminder->SoundSel>=0); + EnableWindow(GetDlgItem(Dialog, IDC_COMBO_REPEATSND), pEditReminder->SoundSel>=0); + } + + if (NewReminderVisible == 2) + SetFocus( GetDlgItem(Dialog, IDC_ADDREMINDER) ); + else + SetFocus( GetDlgItem(Dialog, IDC_REMINDER) ); + + return FALSE; + } + case WM_NCDESTROY: + RemoveProp(GetDlgItem(Dialog, IDC_DATE), TEXT("OldWndProc")); + return TRUE; + case WM_CLOSE: + { + if (NewReminderVisible == 2) + { + pEditReminder->RemVisible = FALSE; + } + DestroyWindow(Dialog); + NewReminderVisible = FALSE; + pEditReminder = NULL; + return TRUE; + } + case WM_NOTIFY: + { + if (wParam == IDC_DATE) + { + NMLISTVIEW *NM = (NMLISTVIEW*)lParam; + + switch (NM->hdr.code) + { + case DTN_DATETIMECHANGE: + OnDateChanged(Dialog, IDC_DATE, IDC_TIME, IDC_REFTIME); + break; + } + } + } + break; + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_TIME: + switch (HIWORD(wParam)) + { + case CBN_KILLFOCUS: + // reformat displayed value + if (SendDlgItemMessage(Dialog, IDC_TIME, CB_GETCURSEL, 0, 0) == CB_ERR) + { + char buf[64]; + int h, m; + GetDlgItemText(Dialog, IDC_TIME, buf, 30); + + if ( ParseTime(buf, &h, &m, FALSE, IsRelativeCombo(Dialog, IDC_TIME)) ) + { + SYSTEMTIME Date; + SendDlgItemMessage(Dialog,IDC_DATE,DTM_GETSYSTEMTIME,0,(LPARAM)&Date); + ReformatTimeInput(Dialog, IDC_TIME, IDC_REFTIME, h, m, &Date); + } + else + { + SendDlgItemMessage(Dialog, IDC_TIME, CB_SETCURSEL, 0, 0); + } + } + break; + } + break; + case IDC_COMBO_SOUND: + switch (HIWORD(wParam)) + { + case CBN_SELENDOK: + { + int n = SendDlgItemMessage(Dialog, IDC_COMBO_SOUND, CB_GETCURSEL, 0, 0); + n = (int)SendDlgItemMessage(Dialog, IDC_COMBO_SOUND, CB_GETITEMDATA, (WPARAM)n, 0); + + EnableWindow(GetDlgItem(Dialog, IDC_BTN_PLAYSOUND), n>=0); + EnableWindow(GetDlgItem(Dialog, IDC_COMBO_REPEATSND), n>=0); + } + break; + } + break; + + case IDC_BTN_PLAYSOUND: + { + int n = SendDlgItemMessage(Dialog, IDC_COMBO_SOUND, CB_GETCURSEL, 0, 0); + n = (int)SendDlgItemMessage(Dialog, IDC_COMBO_SOUND, CB_GETITEMDATA, (WPARAM)n, 0); + switch (n) + { + case 0: SkinPlaySound("AlertReminder"); break; + case 1: SkinPlaySound("AlertReminder2"); break; + case 2: SkinPlaySound("AlertReminder3"); break; + } + } + return TRUE; + case IDC_CLOSE: + { + if (NewReminderVisible == 2) + { + pEditReminder->RemVisible = FALSE; + } + DestroyWindow(Dialog); + NewReminderVisible = FALSE; + pEditReminder = NULL; + return TRUE; + } + case IDC_VIEWREMINDERS: + { + ListReminders(); + return TRUE; + } + case IDC_ADDREMINDER: + { + char *ReminderText = NULL; + int SzT; + SYSTEMTIME Date; + REMINDERDATA* TempRem; + int RepeatSound; + + SendDlgItemMessage(Dialog,IDC_DATE,DTM_GETSYSTEMTIME,0,(LPARAM)&Date); + + if ( !GetTriggerTime(Dialog, IDC_TIME, IDC_REFTIME, &Date) ) + break; + + RepeatSound = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_GETCURSEL,0,0); + if (RepeatSound != CB_ERR) + RepeatSound = SendDlgItemMessage(Dialog,IDC_COMBO_REPEATSND,CB_GETITEMDATA,(WPARAM)RepeatSound,0); + else + RepeatSound = 0; + + SzT = SendDlgItemMessage(Dialog,IDC_REMINDER,WM_GETTEXTLENGTH,0,0); + if (SzT) + { + if (SzT > MAX_REMINDER_LEN) SzT = MAX_REMINDER_LEN; + ReminderText = (char*)malloc(SzT+1); + SendDlgItemMessage(Dialog,IDC_REMINDER,WM_GETTEXT,SzT+1,(LPARAM)ReminderText); + } + + if (NewReminderVisible != 2) + { + // new reminder + TempRem = (REMINDERDATA*)malloc(sizeof(REMINDERDATA)); + TempRem->uid = CreateUid(); + SYSTEMTIMEtoFILETIME(&Date, (FILETIME*)&TempRem->When); + TempRem->Reminder = ReminderText; + TempRem->RemVisible = FALSE; + TempRem->SystemEventQueued = 0; + TempRem->RepeatSoundTTL = 0; + TempRem->SoundSel = (int)SendDlgItemMessage(Dialog, IDC_COMBO_SOUND, CB_GETITEMDATA, (WPARAM)SendDlgItemMessage(Dialog, IDC_COMBO_SOUND, CB_GETCURSEL, 0, 0), 0); + TempRem->RepeatSound = TempRem->SoundSel<0 ? 0 : (UINT)RepeatSound; + TreeAddSorted(&RemindersList,TempRem,ReminderSortCb); + } + else + { + // update existing reminder + + SYSTEMTIMEtoFILETIME(&Date, (FILETIME*)&pEditReminder->When); + if (pEditReminder->Reminder) + free(pEditReminder->Reminder); + pEditReminder->Reminder = ReminderText; + pEditReminder->SoundSel = (int)SendDlgItemMessage(Dialog, IDC_COMBO_SOUND, CB_GETITEMDATA, (WPARAM)SendDlgItemMessage(Dialog, IDC_COMBO_SOUND, CB_GETCURSEL, 0, 0), 0); + pEditReminder->RepeatSound = pEditReminder->SoundSel<0 ? 0 : (UINT)RepeatSound; + + // re-insert tree item sorted + TreeDelete(&RemindersList,pEditReminder); + TreeAddSorted(&RemindersList,pEditReminder,ReminderSortCb); + + pEditReminder->RemVisible = FALSE; + } + SetDlgItemText(Dialog,IDC_REMINDER,""); + JustSaveReminders(); + NOTIFY_LIST(); + + if (g_CloseAfterAddReminder || NewReminderVisible == 2) + { + DestroyWindow(Dialog); + NewReminderVisible = FALSE; + pEditReminder = NULL; + } + } + } + } + case WM_DESTROY: + { + Skin_ReleaseIcon(hIcon); + break; + } + } + return FALSE; +} + +static void InitListView(HWND AHLV) +{ + LV_ITEM lvTIt; + int I; + char *S; + char S1[128]; + REMINDERDATA *pReminder; + TREEELEMENT *TTE; + + ListView_SetHoverTime(AHLV,700); + ListView_SetExtendedListViewStyle(AHLV,LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_TRACKSELECT); + ListView_DeleteAllItems(AHLV); + + I = 0; + TTE = RemindersList; + while (TTE) + { + pReminder = (REMINDERDATA*)TTE->ptrdata; + + lvTIt.mask = LVIF_TEXT; + + GetTriggerTimeString(&pReminder->When, S1, sizeof(S1), TRUE); + + lvTIt.iItem = I; + lvTIt.iSubItem = 0; + lvTIt.pszText = S1; + lvTIt.cchTextMax = strlen(S1); + ListView_InsertItem(AHLV,&lvTIt); + lvTIt.mask = LVIF_TEXT; + S = GetPreviewString(pReminder->Reminder); + lvTIt.iItem = I; + lvTIt.iSubItem = 1; + lvTIt.pszText = S; + lvTIt.cchTextMax = strlen(S); + ListView_SetItem(AHLV,&lvTIt); + + I++; + TTE = (TREEELEMENT*)TTE->next; + } + + ListView_SetItemState(AHLV,0,LVIS_SELECTED,LVIS_SELECTED); +} + +void OnListResize(HWND Dialog) +{ + HWND hList, hText, hBtnNew, hBtnClose; + RECT lr, cr, tr, clsr; + int th, btnh, btnw, MARGIN; + POINT org = {0}; + + hList = GetDlgItem(Dialog, IDC_LISTREMINDERS); + hText = GetDlgItem(Dialog, IDC_REMINDERDATA); + hBtnNew = GetDlgItem(Dialog, IDC_ADDNEWREMINDER); + hBtnClose = GetDlgItem(Dialog, IDC_CLOSE); + + ClientToScreen(Dialog, &org); + GetClientRect(Dialog, &cr); + + GetWindowRect(hList, &lr); OffsetRect(&lr, -org.x, -org.y); + GetWindowRect(hText, &tr); OffsetRect(&tr, -org.x, -org.y); + GetWindowRect(hBtnClose, &clsr); OffsetRect(&clsr, -org.x, -org.y); + + MARGIN = lr.left; + + th = tr.bottom - tr.top; + btnh = clsr.bottom - clsr.top; + btnw = clsr.right - clsr.left; + + clsr.left = cr.right - MARGIN - btnw; + clsr.top = cr.bottom - MARGIN - btnh; + + tr.left = MARGIN; + tr.right = cr.right - MARGIN; + tr.bottom = clsr.top - MARGIN - 2; + tr.top = tr.bottom - th; + + lr.right = cr.right - MARGIN; + lr.bottom = tr.top - 5; + + MoveWindow(hList, lr.left, lr.top, lr.right-lr.left, lr.bottom-lr.top, FALSE); + MoveWindow(hText, tr.left, tr.top, tr.right-tr.left, tr.bottom-tr.top, FALSE); + + MoveWindow(hBtnClose, clsr.left, clsr.top, btnw, btnh, FALSE); + clsr.left -= btnw + 2; + MoveWindow(hBtnNew, clsr.left, clsr.top, btnw, btnh, FALSE); + + RedrawWindow(Dialog, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE); + //UpdateWindow(Dialog); +} + +void UpdateGeomFromWnd(HWND Dialog, int *geom, int *colgeom, int nCols) +{ + if (geom) + { + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + + GetWindowPlacement(Dialog, &wp); + + geom[0] = wp.rcNormalPosition.left; + geom[1] = wp.rcNormalPosition.top; + geom[2] = wp.rcNormalPosition.right - wp.rcNormalPosition.left; + geom[3] = wp.rcNormalPosition.bottom - wp.rcNormalPosition.top; + } + + if (colgeom) + { + int i; + HWND H = GetDlgItem(Dialog, IDC_LISTREMINDERS); + + for (i=0; iSystemEventQueued) + mii.fState |= MFS_GRAYED; + SetMenuItemInfo(FhMenu, ID_CONTEXTMENUREMINDERLISTVIEW_EDIT, FALSE, &mii); + + if (!pReminder) + EnableMenuItem(FhMenu, IDM_DELETEREMINDER, MF_GRAYED|MF_BYCOMMAND); + + CallService(MS_LANGPACK_TRANSLATEMENU,(DWORD)FhMenu,0); + TrackPopupMenu(FhMenu,TPM_LEFTALIGN | TPM_RIGHTBUTTON,LOWORD(lParam),HIWORD(lParam),0,AhWnd,0); + DestroyMenu(hMenuLoad); + return TRUE; +} + +int CALLBACK DlgProcViewReminders(HWND Dialog,UINT Message,WPARAM wParam,LPARAM lParam) +{ + LV_COLUMN lvCol; + NMLISTVIEW *NM; + char *S; + int I; + + switch (Message) + { + case WM_SIZE: + { + OnListResize(Dialog); + UpdateGeomFromWnd(Dialog, g_reminderListGeom, NULL, 0); + break; + } + case WM_MOVE: + UpdateGeomFromWnd(Dialog, g_reminderListGeom, NULL, 0); + break; + case WM_GETMINMAXINFO: + { + MINMAXINFO *mm = (MINMAXINFO*)lParam; + mm->ptMinTrackSize.x = 394; + mm->ptMinTrackSize.y = 300; + } + return 0; + case WM_RELOAD: + { + SetDlgItemText(Dialog,IDC_REMINDERDATA,""); + InitListView(GetDlgItem(Dialog,IDC_LISTREMINDERS)); + return TRUE; + } + case WM_CONTEXTMENU: + { + HWND H; + REMINDERDATA *pReminder = NULL; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + pReminder = (REMINDERDATA*)TreeGetAt(RemindersList,I); + } + } + + if ( DoListContextMenu(Dialog, wParam, lParam, pReminder) ) + return TRUE; + } + break; + case WM_INITDIALOG: + { + HWND H; + + HICON hIcon = Skin_GetIconByHandle(hIconLibItem[6], ICON_SMALL); + SendMessage(Dialog, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon); + hIcon = Skin_GetIconByHandle(hIconLibItem[6], ICON_BIG); + SendMessage(Dialog, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); + + TranslateDialogDefault(Dialog); + SetDlgItemText(Dialog,IDC_REMINDERDATA,""); + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + lvCol.mask = LVCF_TEXT | LVCF_WIDTH; + S = Translate("Reminder text"); + lvCol.pszText = S; + lvCol.cchTextMax = strlen(S); + lvCol.cx = g_reminderListColGeom[1]; + ListView_InsertColumn(H,0,&lvCol); + lvCol.mask = LVCF_TEXT | LVCF_WIDTH; + S = Translate("Date of activation"); + lvCol.pszText = S; + lvCol.cchTextMax = strlen(S); + lvCol.cx = g_reminderListColGeom[0]; + ListView_InsertColumn(H,0,&lvCol); + InitListView(H); + SetWindowLong(GetDlgItem(H, 0), GWL_ID, IDC_LISTREMINDERS_HEADER); + LV = Dialog; + + if (g_reminderListGeom[1] && g_reminderListGeom[2]) + { + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(Dialog, &wp); + wp.rcNormalPosition.left = g_reminderListGeom[0]; + wp.rcNormalPosition.top = g_reminderListGeom[1]; + wp.rcNormalPosition.right = g_reminderListGeom[2] + g_reminderListGeom[0]; + wp.rcNormalPosition.bottom = g_reminderListGeom[3] + g_reminderListGeom[1]; + SetWindowPlacement(Dialog, &wp); + } + return TRUE; + } + case WM_CLOSE: + { + DestroyWindow(Dialog); + ListReminderVisible = FALSE; + return TRUE; + } + case WM_NOTIFY: + { + if (wParam == IDC_LISTREMINDERS) + { + NM = (NMLISTVIEW *)lParam; + switch (NM->hdr.code) + { + case LVN_ITEMCHANGED: + { + S = ((REMINDERDATA*)TreeGetAt(RemindersList,NM->iItem))->Reminder; + SetDlgItemText(Dialog,IDC_REMINDERDATA,S); + } + break; + case NM_DBLCLK: + { + HWND H; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + EditReminder((REMINDERDATA*)TreeGetAt(RemindersList, I)); + } + } + } + break; + } + } + else if (wParam == IDC_LISTREMINDERS_HEADER) + { + NMHEADER *NM = (NMHEADER*)lParam; + switch (NM->hdr.code) + { + case HDN_ENDTRACK: + UpdateGeomFromWnd(Dialog, NULL, g_reminderListColGeom, SIZEOF(g_reminderListColGeom)); + break; + } + } + } + break; + case WM_COMMAND: + { + switch(LOWORD(wParam)) + { + case ID_CONTEXTMENUREMINDERLISTVIEW_EDIT: + { + HWND H; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1) + { + EditReminder((REMINDERDATA*)TreeGetAt(RemindersList, I)); + } + } + } + return TRUE; + case IDC_CLOSE: + { + DestroyWindow(Dialog); + ListReminderVisible = FALSE; + return TRUE; + } + case IDM_NEWREMINDER: + case IDC_ADDNEWREMINDER: + { + NewReminder(); + return TRUE; + } + case IDM_DELETEALLREMINDERS: + if (RemindersList && MessageBox(Dialog, Translate("Are you sure you want to delete all reminders?"), SECTIONNAME, MB_OKCANCEL) == IDOK) + { + SetDlgItemText(Dialog,IDC_REMINDERDATA,""); + DeleteReminders(); + InitListView(GetDlgItem(Dialog,IDC_LISTREMINDERS)); + } + return TRUE; + case IDM_DELETEREMINDER: + { + HWND H; + + H = GetDlgItem(Dialog,IDC_LISTREMINDERS); + if ( ListView_GetSelectedCount(H) ) + { + I = ListView_GetSelectionMark(H); + if (I != -1 + && MessageBox(Dialog, Translate("Are you sure you want to delete this reminder?"), SECTIONNAME, MB_OKCANCEL) == IDOK) + { + SetDlgItemText(Dialog,IDC_REMINDERDATA,""); + DeleteReminder((REMINDERDATA*)TreeGetAt(RemindersList, I)); + JustSaveReminders(); + InitListView(H); + } + } + return TRUE; + } + } + } + case WM_DESTROY: + { + CallService(MS_SKIN2_RELEASEICONBIG, (WPARAM)SendMessage(Dialog, WM_SETICON, ICON_BIG, (LPARAM)NULL), 0); + CallService(MS_SKIN2_RELEASEICON, (WPARAM)SendMessage(Dialog, WM_SETICON, ICON_SMALL, (LPARAM)NULL), 0); + break; + } + } + return FALSE; +} + + +///////////////////////////////////////////////////////////////////// +// Email/SMS and WinSock functions + +BOOL WS_Init() +{ + WSADATA wsd; + if (WSAStartup(MAKEWORD(2,2),&wsd)!=0) return FALSE; + return TRUE; +} + +void WS_CleanUp() +{ + WSACleanup(); +} + +int WS_Send(SOCKET s,char *data,int datalen) +{ + int rlen; + if ((rlen = send(s,data,datalen,0)) == SOCKET_ERROR) return FALSE; + return TRUE; +} + +unsigned long WS_ResolveName(char *name,WORD *port,int defaultPort) +{ + HOSTENT *lk; + char *pcolon,*nameCopy; + DWORD ret; + nameCopy=_strdup(name); + if(port != NULL) *port = defaultPort; + pcolon = strchr(nameCopy,':'); + if(pcolon != NULL) + { + if(port != NULL) *port = atoi(pcolon+1); + *pcolon = 0; + } + if (inet_addr(nameCopy) == INADDR_NONE) + { + lk = gethostbyname(nameCopy); + if(lk == 0) return SOCKET_ERROR; + else {free(nameCopy); return *(u_long*)lk->h_addr_list[0];} + } + ret=inet_addr(nameCopy); + free(nameCopy); + return ret; +} + +void Send(char *user, char *host, char *Msg, char *server) +{ + SOCKADDR_IN sockaddr; + WORD port; + char *ch = NULL; + S = socket(AF_INET,SOCK_STREAM,0); + if (!server) server = host; + if ((sockaddr.sin_addr.S_un.S_addr = WS_ResolveName(server, + &port,25))==SOCKET_ERROR) return; + sockaddr.sin_port = htons(port); + sockaddr.sin_family = AF_INET; + if(connect(S,(SOCKADDR*)&sockaddr,sizeof(sockaddr)) == SOCKET_ERROR) return; + ch = (char*)malloc(strlen(user) + strlen(host) + 16); + ch = (char*)realloc(ch,sprintf(ch,"rcpt to:%s@%s\r\n",user,host)); + WS_Send(S,"mail from: \r\n",13); + WS_Send(S,ch,strlen(ch)); + WS_Send(S,"data\r\n",6); + WS_Send(S,"From:\r\n\r\n",14); + WS_Send(S,Msg,strlen(Msg)); + WS_Send(S,"\r\n.\r\n",5); + WS_Send(S,"quit\r\n",6); + SAFE_FREE((void**)&ch); + closesocket(S); +} diff --git a/plugins/NotesAndReminders/src/resource.h b/plugins/NotesAndReminders/src/resource.h new file mode 100644 index 0000000000..0ddf8b4031 --- /dev/null +++ b/plugins/NotesAndReminders/src/resource.h @@ -0,0 +1,65 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by resource.rc +// +#define IDD_STNOTEOPTIONS 101 +#define IDD_ADDREMINDER 102 +#define IDD_NOTIFYREMINDER 103 +#define IDD_LISTREMINDERS 104 +#define IDI_ADDREMINDER 110 +#define IDI_DELETEICON 111 +#define IDI_NOTEICON 112 +#define IDI_SHOWHIDE 113 +#define IDI_CAPTIONICON 114 +#define IDI_DELETEREMINDER 115 +#define IDI_VIEWREMINDERS 116 +#define IDI_CAPTIONICONNOTTOP 117 +#define IDI_HIDENOTE 118 +#define IDI_REMOVENOTE 119 +#define IDI_REMINDER 120 +#define IDI_BRINGFRONT 121 +#define IDI_PLAYSOUND 122 +#define IDI_VIEWNOTES 123 + +#define IDC_COMBODATE 1021 +#define IDC_COMBOTIME 1022 +#define IDC_COMBOREMINDERTIME 1023 +#define IDC_REFTIME 1024 +#define IDC_STATIC_DATE 1025 +#define IDC_STATIC_TIME 1026 +#define IDC_COMBO_REPEATSND 1028 +#define IDC_COMBO_SOUND 1029 +#define IDC_BTN_PLAYSOUND 1030 +#define IDC_EDIT_ALTBROWSER 1031 +#define IDC_BTN_BROWSEBROWSER 1032 +#define ID_CONTEXTMENUNOTEPOPUP_NEWNOTE 40009 +#define ID_CONTEXTMENUNOTEPOPUP_BRINGALLTOTOP 40010 +#define ID_CONTEXTMENUNOTEPOPUP_APPEARANCE 40011 +#define ID_APPEARANCE_BACKGROUNDCOLOR 40013 +#define ID_APPEARANCE_TEXTCOLOR 40014 +#define ID_APPEARANCE_CUSTOMTEXT 40015 +#define ID_APPEARANCE_CUSTOMBG 40016 +#define ID_BACKGROUNDCOLOR_RESET 40017 +#define ID_TEXTCOLOR_RESET 40018 +#define ID_CONTEXTMENUNOTEPOPUP_PASTETITLE 40019 +#define ID_CONTEXTMENUNOTEPOPUP_RESETTITLE 40020 +#define ID_CONTEXTMENUREMINDERLISTVIEW_EDIT 40021 +#define ID_CONTEXTMENUNOTELISTVIEW_EDITNOTE 40022 +#define ID_CONTEXTMENUNOTELISTVIEW_SHOW 40023 +#define ID_CONTEXTMENUNOTELISTVIEW_DELETEALLNOTES 40024 +#define ID_CONTEXTMENUNOTEPOPUP_VIEWNOTES 40025 +#define ID_CONTEXTMENUNOTELISTVIEW_TOGGLEVISIBILITY 40026 +#define ID_APPEARANCE_FONT 40027 +#define ID_APPEARANCE_CUSTOMFONT 40028 +#define ID_FONT_RESET 40029 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 106 +#define _APS_NEXT_COMMAND_VALUE 40030 +#define _APS_NEXT_CONTROL_VALUE 1033 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif -- cgit v1.2.3