diff options
author | George Hazan <ghazan@miranda.im> | 2019-03-02 12:32:44 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2019-03-02 12:32:55 +0300 |
commit | 931a7dc1ac0dbc7e6c1083583ced915e572f5b47 (patch) | |
tree | 9fe9a6448d44030e26aa7107ce16044ed413e0d0 /protocols/Weather/src | |
parent | dd7d9954042254e66e3bbbec7195c6be8b1a0663 (diff) |
all protocols (even virtual ones) moved to the Protocols folder
Diffstat (limited to 'protocols/Weather/src')
-rw-r--r-- | protocols/Weather/src/resource.h | 175 | ||||
-rw-r--r-- | protocols/Weather/src/stdafx.cxx | 18 | ||||
-rw-r--r-- | protocols/Weather/src/stdafx.h | 547 | ||||
-rw-r--r-- | protocols/Weather/src/version.h | 13 | ||||
-rw-r--r-- | protocols/Weather/src/weather.cpp | 231 | ||||
-rw-r--r-- | protocols/Weather/src/weather_addstn.cpp | 432 | ||||
-rw-r--r-- | protocols/Weather/src/weather_contacts.cpp | 480 | ||||
-rw-r--r-- | protocols/Weather/src/weather_conv.cpp | 637 | ||||
-rw-r--r-- | protocols/Weather/src/weather_data.cpp | 439 | ||||
-rw-r--r-- | protocols/Weather/src/weather_http.cpp | 157 | ||||
-rw-r--r-- | protocols/Weather/src/weather_icons.cpp | 64 | ||||
-rw-r--r-- | protocols/Weather/src/weather_info.cpp | 240 | ||||
-rw-r--r-- | protocols/Weather/src/weather_ini.cpp | 592 | ||||
-rw-r--r-- | protocols/Weather/src/weather_mwin.cpp | 364 | ||||
-rw-r--r-- | protocols/Weather/src/weather_opt.cpp | 601 | ||||
-rw-r--r-- | protocols/Weather/src/weather_popup.cpp | 428 | ||||
-rw-r--r-- | protocols/Weather/src/weather_svcs.cpp | 375 | ||||
-rw-r--r-- | protocols/Weather/src/weather_update.cpp | 607 | ||||
-rw-r--r-- | protocols/Weather/src/weather_userinfo.cpp | 358 |
19 files changed, 6758 insertions, 0 deletions
diff --git a/protocols/Weather/src/resource.h b/protocols/Weather/src/resource.h new file mode 100644 index 0000000000..eecba58268 --- /dev/null +++ b/protocols/Weather/src/resource.h @@ -0,0 +1,175 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ..\res\resource.rc +// +#define IDI_ICON 101 +#define IDD_USERINFO 201 +#define IDD_EDIT 202 +#define IDI_DISABLED 203 +#define IDD_POPUP 204 +#define IDD_OPTIONS 205 +#define IDI_LOG 206 +#define IDI_UPDATE2 208 +#define IDI_READ 209 +#define IDI_UPDATE 210 +#define IDI_S 211 +#define IDI_MAP 212 +#define IDR_PMENU 213 +#define IDI_POPUP 214 +#define IDI_NOPOPUP 215 +#define IDD_TEXTOPT 216 +#define IDD_BRIEF 217 +#define IDD_SETUP 218 +#define IDR_TMENU 219 +#define IDR_TMMENU 220 +#define IDI_EDIT 222 +#define IDD_INFO 224 +#define IDD_SEARCHCITY 225 +#define IDC_NAME 2000 +#define IDC_ID 2001 +#define IDC_LOG 2003 +#define IDC_UPDATETIME 2005 +#define IDC_CTEXT 2006 +#define IDC_AVATARSIZE 2006 +#define IDC_UPDATE 2007 +#define IDC_BTITLE 2008 +#define IDC_STARTUPUPD 2008 +#define IDC_CHANGE 2009 +#define IDC_BTITLE2 2009 +#define IDC_USEWINCOLORS 2010 +#define IDC_BTEXT 2011 +#define IDC_CH 2013 +#define IDC_NTEXT 2015 +#define IDC_DEGREE 2016 +#define IDC_E 2017 +#define IDC_W 2018 +#define IDC_POP1 2019 +#define IDC_XTEXT 2020 +#define IDC_POP2 2020 +#define IDC_PText 2021 +#define IDC_PTitle 2023 +#define IDC_Internal 2024 +#define IDC_ETEXT 2025 +#define IDC_DISCONDICON 2025 +#define IDC_External 2026 +#define IDC_DONOTAPPUNITS 2026 +#define IDC_DEFA 2027 +#define IDC_NOFRAC 2027 +#define IDC_HTEXT 2028 +#define IDC_DPop 2029 +#define IDC_DAutoUpdate 2030 +#define IDC_IURL 2032 +#define IDC_MURL 2033 +#define IDC_PROTOCOND 2034 +#define IDC_Overwrite 2035 +#define IDC_UPDCONDCHG 2036 +#define IDC_REMOVEOLD 2037 +#define IDC_MAKEI 2039 +#define IDC_BGCOLOUR 2040 +#define IDC_TEXTCOLOUR 2041 +#define IDC_LeftClick 2042 +#define IDC_PREVIEW 2043 +#define IDC_VAR3 2044 +#define IDC_RightClick 2045 +#define IDC_DELAY 2046 +#define IDC_PDEF 2047 +#define IDC_T1 2048 +#define IDC_T2 2049 +#define IDC_W1 2050 +#define IDC_W2 2051 +#define IDC_W3 2052 +#define IDC_W4 2053 +#define IDC_BROWSE 2054 +#define IDC_VIEW1 2055 +#define IDC_RESET1 2056 +#define IDC_VIEW2 2057 +#define IDC_V1 2058 +#define IDC_V2 2059 +#define IDC_RESET2 2060 +#define IDC_SVCINFO 2061 +#define IDC_GETNAME 2062 +#define IDC_P1 2063 +#define IDC_P2 2064 +#define IDC_P3 2065 +#define IDC_P4 2066 +#define IDC_RESET 2067 +#define IDC_D1 2067 +#define IDC_D2 2068 +#define IDC_D3 2069 +#define IDC_INFO1 2069 +#define IDC_INFOICON 2070 +#define IDC_INFO11 2071 +#define IDC_INFO2 2072 +#define IDC_INFO3 2073 +#define IDC_VARLIST 2074 +#define IDC_INFO4 2075 +#define IDC_INFO5 2076 +#define IDC_PD1 2077 +#define IDC_PD2 2078 +#define IDC_PD3 2079 +#define IDC_INFO6 2079 +#define IDC_TM1 2080 +#define IDC_TM2 2081 +#define IDC_TM3 2082 +#define IDC_TM4 2083 +#define IDC_TM5 2084 +#define IDC_TM6 2085 +#define IDC_TM7 2086 +#define IDC_TM8 2087 +#define IDC_INFO7 2087 +#define IDC_TM9 2088 +#define IDC_INFO8 2089 +#define IDC_INFO9 2090 +#define IDC_INFO10 2091 +#define IDC_INFO12 2092 +#define IDC_INFO13 2093 +#define IDC_MORE 2094 +#define IDC_MOREDETAIL 2095 +#define IDC_DATALIST 2096 +#define IDC_MUPDATE 2097 +#define IDC_MFRAME 2099 +#define IDC_MTOGGLE 2101 +#define IDC_MWEBPAGE 2102 +#define IDC_MTEXT 2103 +#define IDC_STEP1 2107 +#define IDC_STEP2 2108 +#define IDC_STEP3 2109 +#define IDC_STEP4 2110 +#define IDC_INFOLIST 2117 +#define IDC_RELOADINI 2118 +#define IDC_MEMUSED 2119 +#define IDC_INICOUNT 2120 +#define IDC_AVATARSPIN 2124 +#define IDC_SEARCHCITY 2125 +#define IDC_HEADERBAR 2126 +#define IDC_E1 2128 +#define IDC_E2 2129 +#define OIC_HAND 32513 +#define OIC_QUES 32514 +#define OIC_BANG 32515 +#define OIC_NOTE 32516 +#define IDM_M1 40002 +#define IDM_M2 40003 +#define IDM_M3 40004 +#define IDM_M4 40005 +#define IDM_M5 40006 +#define IDM_M6 40007 +#define IDM_M7 40008 +#define IDM_M8 40009 +#define ID_T1 40010 +#define ID_T2 40011 +#define ID_MPREVIEW 40020 +#define ID_MRESET 40021 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NO_MFC 1 +#define _APS_NEXT_RESOURCE_VALUE 226 +#define _APS_NEXT_COMMAND_VALUE 40030 +#define _APS_NEXT_CONTROL_VALUE 2128 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/Weather/src/stdafx.cxx b/protocols/Weather/src/stdafx.cxx new file mode 100644 index 0000000000..1b563fc866 --- /dev/null +++ b/protocols/Weather/src/stdafx.cxx @@ -0,0 +1,18 @@ +/* +Copyright (C) 2012-19 Miranda NG team (https://miranda-ng.org) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h"
\ No newline at end of file diff --git a/protocols/Weather/src/stdafx.h b/protocols/Weather/src/stdafx.h new file mode 100644 index 0000000000..eee562a9ed --- /dev/null +++ b/protocols/Weather/src/stdafx.h @@ -0,0 +1,547 @@ +/* +Weather Protocol plugin for Miranda NG +Copyright (C) 2012-19 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* This file contains the includes, weather constants/declarations, + the structs, and the primitives for some of the functions. +*/ + +#pragma once + +//============ THE INCLUDES =========== + +#include <share.h> +#include <time.h> +#include <windows.h> +#include <commctrl.h> +#include <richedit.h> +#include <malloc.h> + +#include <newpluginapi.h> +#include <m_icolib.h> +#include <m_options.h> +#include <m_langpack.h> +#include <m_skin.h> +#include <m_database.h> +#include <m_history.h> +#include <m_protosvc.h> +#include <m_userinfo.h> +#include <m_netlib.h> +#include <m_ignore.h> +#include <m_findadd.h> +#include <m_button.h> +#include <m_avatars.h> +#include <m_clc.h> +#include <m_fontservice.h> +#include <m_skin_eng.h> +#include <m_cluiframes.h> +#include <m_popup.h> +#include <win2k.h> +#include <m_acc.h> + +#include <m_weather.h> +#include <m_toptoolbar.h> + +#include "resource.h" +#include "version.h" + +//============ CONSTANTS ============ + +// name +#define MODULENAME "Weather" +#define WEATHERPROTOTEXT "Weather" +#define DEFCURRENTWEATHER "WeatherCondition" +#define WEATHERCONDITION "Current" + +// weather conditions +#define SUNNY ID_STATUS_ONLINE +#define NA ID_STATUS_OFFLINE +#define PCLOUDY ID_STATUS_AWAY +#define CLOUDY ID_STATUS_NA +#define RAIN ID_STATUS_OCCUPIED +#define RSHOWER ID_STATUS_DND +#define SNOW ID_STATUS_FREECHAT +#define LIGHT ID_STATUS_INVISIBLE +#define THUNDER ID_STATUS_INVISIBLE +#define SSHOWER ID_STATUS_ONTHEPHONE +#define FOG ID_STATUS_OUTTOLUNCH +#define UNAVAIL 40081 + +// status +#define NOSTATUSDATA 1 + +// limits +#define MAX_TEXT_SIZE 4096 +#define MAX_DATA_LEN 1024 + +// db info mangement mode +#define WDBM_REMOVE 1 +#define WDBM_DETAILDISPLAY 2 + +// more info list column width +#define LIST_COLUMN 150 + +// others +#define NODATA TranslateT("N/A") +#define UM_SETCONTACT 40000 + +// weather update error codes +#define INVALID_ID_FORMAT 10 +#define INVALID_SVC 11 +#define INVALID_ID 12 +#define SVC_NOT_FOUND 20 +#define NETLIB_ERROR 30 +#define DATA_EMPTY 40 +#define DOC_NOT_FOUND 42 +#define DOC_TOO_SHORT 43 +#define UNKNOWN_ERROR 99 + +// weather update error text +#define E10 TranslateT("Invalid ID format, missing \"/\" (10)") +#define E11 TranslateT("Invalid service (11)") +#define E12 TranslateT("Invalid station (12)") +#define E20 TranslateT("Weather service ini for this station is not found (20)") +#define E30 TranslateT("Netlib error - check your internet connection (30)") +#define E40 TranslateT("Empty data is retrieved (40)") +#define E42 TranslateT("Document not found (42)") +#define E43 TranslateT("Document too short to contain any weather data (43)") +#define E99 TranslateT("Unknown error (99)") + +// HTTP error... not all translated +// 100 Continue +// 101 Switching Protocols +// 200 OK +// 201 Created +// 202 Accepted +// 203 Non-Authoritative Information +#define E204 TranslateT("HTTP Error: No content (204)") +// 205 Reset Content +// 206 Partial Content +// 300 Multiple Choices +#define E301 TranslateT("HTTP Error: Data moved (301)") +// 302 Found +// 303 See Other +// 304 Not Modified +#define E305 TranslateT("HTTP Error: Use proxy (305)") +// 306 (Unused) +#define E307 TranslateT("HTTP Error: Temporary redirect (307)") +#define E400 TranslateT("HTTP Error: Bad request (400)") +#define E401 TranslateT("HTTP Error: Unauthorized (401)") +#define E402 TranslateT("HTTP Error: Payment required (402)") +#define E403 TranslateT("HTTP Error: Forbidden (403)") +#define E404 TranslateT("HTTP Error: Not found (404)") +#define E405 TranslateT("HTTP Error: Method not allowed (405)") +// 406 Not Acceptable +#define E407 TranslateT("HTTP Error: Proxy authentication required (407)") +// 408 Request Timeout +// 409 Conflict +#define E410 TranslateT("HTTP Error: Gone (410)") +// 411 Length Required +// 412 Precondition Failed +// 413 Request Entity Too Large +// 414 Request-URI Too Long +// 415 Unsupported Media Type +// 416 Requested Range Not Satisfiable +// 417 Expectation Failed +#define E500 TranslateT("HTTP Error: Internal server error (500)") +// 501 Not Implemented +#define E502 TranslateT("HTTP Error: Bad gateway (502)") +#define E503 TranslateT("HTTP Error: Service unavailable (503)") +#define E504 TranslateT("HTTP Error: Gateway timeout (504)") +// 505 HTTP Version Not Supported + +// defaults constants +#define C_DEFAULT L"%n [%t, %c]" +#define N_DEFAULT TranslateT("%c\\nTemperature: %t\\nFeel-Like: %f\\nPressure: %p\\nWind: %i %w\\nHumidity: %m\\nDew Point: %e\\nVisibility: %v\\n\\nSun Rise: %r\\nSun Set: %y\\n\\n5 Days Forecast:\\n%[Forecast Day 1]\\n%[Forecast Day 2]\\n%[Forecast Day 3]\\n%[Forecast Day 4]\\n%[Forecast Day 5]") +#define B_DEFAULT TranslateT("Feel-Like: %f\\nPressure: %p\\nWind: %i %w\\nHumidity: %m\\nDew Point: %e\\nVisibility: %v\\n\\nSun Rise: %r\\nSun Set: %y\\n\\n5 Days Forecast:\\n%[Forecast Day 1]\\n%[Forecast Day 2]\\n%[Forecast Day 3]\\n%[Forecast Day 4]\\n%[Forecast Day 5]") +#define b_DEFAULT TranslateT("Weather Condition for %n as of %u") +#define X_DEFAULT N_DEFAULT +#define H_DEFAULT TranslateT("%c, %t (feel-like %f) Wind: %i %w Humidity: %m") +#define E_DEFAULT TranslateT("%n at %u: %c, %t (feel-like %f) Wind: %i %w Humidity: %m") +#define P_DEFAULT TranslateT("%n (%u)") +#define p_DEFAULT TranslateT("%c, %t\\nToday: High %h, Low %l") +#define s_DEFAULT TranslateT("Temperature: %[Temperature]") +#define VAR_LIST_POPUP TranslateT("%c\tcurrent condition\n%d\tcurrent date\n%e\tdewpoint\n%f\tfeel-like temperature\n%h\ttoday's high\n%i\twind direction\n%l\ttoday's low\n%m\thumidity\n%n\tstation name\n%p\tpressure\n%r\tsunrise time\n%s\tstation ID\n%t\ttemperature\n%u\tupdate time\n%v\tvisibility\n%w\twind speed\n%y\tsun set") +#define VAR_LIST_OPT TranslateT("%c\tcurrent condition\n%d\tcurrent date\n%e\tdewpoint\n%f\tfeel-like temp\n%h\ttoday's high\n%i\twind direction\n%l\ttoday's low\n%m\thumidity\n%n\tstation name\n%p\tpressure\n%r\tsunrise time\n%s\tstation ID\n%t\ttemperature\n%u\tupdate time\n%v\tvisibility\n%w\twind speed\n%y\tsun set\n----------\n\\n\tnew line") +#define WEATHER_NO_INFO TranslateT("No information available.\r\nPlease update weather condition first.") +#define CUSTOM_VARS TranslateT("%[..]\tcustom variables") +#define VARS_LIST TranslateT("Here is a list of custom variables that are currently available") +#define NO_FORECAST_URL TranslateT("The URL for complete forecast has not been set. You can set it from the Edit Settings dialog.") +#define NO_MAP_URL TranslateT("The URL for weather map has not been set. You can set it from the Edit Settings dialog.") + +//============ OPTION STRUCT ============ + +// option struct +struct MYOPTIONS +{ + // main options + BYTE AutoUpdate; + BYTE CAutoUpdate; + BYTE StartupUpdate; + BYTE NoProtoCondition; + BYTE UpdateOnlyConditionChanged; + BYTE RemoveOldData; + BYTE MakeItalic; + + WORD UpdateTime; + WORD AvatarSize; + + // units + WORD tUnit; + WORD wUnit; + WORD vUnit; + WORD pUnit; + WORD dUnit; + WORD eUnit; + wchar_t DegreeSign[4]; + BYTE DoNotAppendUnit; + BYTE NoFrac; + + // texts + wchar_t *cText; + wchar_t *bTitle; + wchar_t *bText; + wchar_t *nText; + wchar_t *eText; + wchar_t *hText; + wchar_t *xText; + wchar_t *sText; + + // advanced + BYTE DisCondIcon; + + // popup options + BYTE UsePopup; + BYTE UpdatePopup; + BYTE AlertPopup; + BYTE PopupOnChange; + BYTE ShowWarnings; + + // popup colors + BYTE UseWinColors; + COLORREF BGColour; + COLORREF TextColour; + + // popup actions + DWORD LeftClickAction; + DWORD RightClickAction; + + // popup delay + DWORD pDelay; + + // popup texts + wchar_t *pTitle; + wchar_t *pText; + + // other misc stuff + wchar_t Default[64]; + MCONTACT DefStn; +}; + +void DestroyOptions(void); + +//============ STRUCT USED TO MAKE AN UPDATE LIST ============ +struct WCONTACTLIST { + MCONTACT hContact; + struct WCONTACTLIST *next; +}; + +typedef struct WCONTACTLIST UPDATELIST; + +extern UPDATELIST *UpdateListHead, *UpdateListTail; + +void DestroyUpdateList(void); + +//============ DATA FORMAT STRUCT ============ + +#define WID_NORMAL 0 +#define WID_SET 1 +#define WID_BREAK 2 + +typedef struct { + wchar_t *Name; + wchar_t *Start; + wchar_t *End; + wchar_t *Unit; + char *Url; + wchar_t *Break; + int Type; +} WIDATAITEM; + +struct WITEMLIST { + WIDATAITEM Item; + struct WITEMLIST *Next; +}; + +typedef struct WITEMLIST WIDATAITEMLIST; + +typedef struct { + BOOL Available; + char *SearchURL; + wchar_t *NotFoundStr; + WIDATAITEM Name; +} WIIDSEARCH; + +typedef struct { + BOOL Available; + wchar_t *First; + WIDATAITEM Name; + WIDATAITEM ID; +} WINAMESEARCHTYPE; + +typedef struct { + char *SearchURL; + wchar_t *NotFoundStr; + wchar_t *SingleStr; + WINAMESEARCHTYPE Single; + WINAMESEARCHTYPE Multiple; +} WINAMESEARCH; + +struct STRLIST { + wchar_t *Item; + struct STRLIST *Next; +}; + +typedef struct STRLIST WICONDITEM; + +typedef struct { + WICONDITEM *Head; + WICONDITEM *Tail; +} WICONDLIST; + +typedef struct { + wchar_t *FileName; + wchar_t *ShortFileName; + BOOL Enabled; + + // header + wchar_t *DisplayName; + wchar_t *InternalName; + wchar_t *Description; + wchar_t *Author; + wchar_t *Version; + int InternalVer; + size_t MemUsed; + + // default + char *DefaultURL; + wchar_t *DefaultMap; + char *UpdateURL; + char *UpdateURL2; + char *UpdateURL3; + char *UpdateURL4; + char *Cookie; + char *UserAgent; +// items + int UpdateDataCount; + WIDATAITEMLIST *UpdateData; + WIDATAITEMLIST *UpdateDataTail; + WIIDSEARCH IDSearch; + WINAMESEARCH NameSearch; + WICONDLIST CondList[10]; +} WIDATA; + +//============ DATA LIST (LINKED LIST) ============ + +struct DATALIST { + WIDATA Data; + struct DATALIST *next; +}; + +typedef struct DATALIST WIDATALIST; + +//============ GLOBAL VARIABLES ============ + +extern WIDATALIST *WIHead, *WITail; + +extern HWND hPopupWindow, hWndSetup; + +extern MYOPTIONS opt; + +extern unsigned status, old_status; + +extern MWindowList hDataWindowList, hWindowList; + +extern HNETLIBUSER hNetlibUser; +extern HANDLE hHookWeatherUpdated, hHookWeatherError, hTBButton, hUpdateMutex; +extern UINT_PTR timerId; + +extern HGENMENU hMwinMenu; + +// check if weather is currently updating +extern BOOL ThreadRunning; + +//============ FUNCTION PRIMITIVES ============ + +// functions in weather_addstn.c +INT_PTR WeatherAddToList(WPARAM wParam,LPARAM lParam); +BOOL CheckSearch(); + +int IDSearch(wchar_t *id, const int searchId); +int NameSearch(wchar_t *name, const int searchId); + +INT_PTR WeatherBasicSearch(WPARAM wParam,LPARAM lParam); +INT_PTR WeatherCreateAdvancedSearchUI(WPARAM wParam, LPARAM lParam); +INT_PTR WeatherAdvancedSearch(WPARAM wParam, LPARAM lParam); + +int WeatherAdd(WPARAM wParam, LPARAM lParam); + +// functions used in weather_contacts.c +INT_PTR ViewLog(WPARAM wParam,LPARAM lParam); +INT_PTR LoadForecast(WPARAM wParam,LPARAM lParam); +INT_PTR WeatherMap(WPARAM wParam,LPARAM lParam); +INT_PTR EditSettings(WPARAM wParam,LPARAM lParam); + +int ContactDeleted(WPARAM wParam,LPARAM lParam); + +BOOL IsMyContact(MCONTACT hContact); + +// functions in weather_conv.c +void GetTemp(wchar_t *tempchar, wchar_t *unit, wchar_t *str); +void GetSpeed(wchar_t *tempchar, wchar_t *unit, wchar_t *str); +void GetPressure(wchar_t *tempchar, wchar_t *unit, wchar_t *str); +void GetDist(wchar_t *tempchar, wchar_t *unit, wchar_t *str); +void GetElev(wchar_t *tempchar, wchar_t *unit, wchar_t *str); + +WORD GetIcon(const wchar_t* cond, WIDATA *Data); +void CaseConv(wchar_t *str); +void TrimString(char *str); +void TrimString(WCHAR *str); +void ConvertBackslashes(char *str); +char *GetSearchStr(char *dis); + +wchar_t *GetDisplay(WEATHERINFO *w, const wchar_t *dis, wchar_t* str); +INT_PTR GetDisplaySvcFunc(WPARAM wParam, LPARAM lParam); + +void GetSvc(wchar_t *pszID); +void GetID(wchar_t *pszID); + +wchar_t *GetError(int code); + +// functions in weather_data.c +void GetStationID(MCONTACT hContact, wchar_t* id, int idlen); +WEATHERINFO LoadWeatherInfo(MCONTACT Change); +int DBGetData(MCONTACT hContact, char *setting, DBVARIANT *dbv); + +void EraseAllInfo(void); + +void GetDataValue(WIDATAITEM *UpdateData, wchar_t *Data, wchar_t** szInfo); +void ConvertDataValue(WIDATAITEM *UpdateData, wchar_t *Data); +void wSetData(char **Data, const char *Value); +void wSetData(WCHAR **Data, const char *Value); +void wSetData(WCHAR **Data, const WCHAR *Value); +void wfree(char **Data); +void wfree(WCHAR **Data); + +void DBDataManage(MCONTACT hContact, WORD Mode, WPARAM wParam, LPARAM lParam); + +// functions in weather_http.c +int InternetDownloadFile (char *szUrl, char *cookie, char *userAgent, wchar_t** szData); +void NetlibInit(); + +// functions in weather_ini.c +WIDATA* GetWIData(wchar_t *pszServ); + +bool IsContainedInCondList(const wchar_t *pszStr, WICONDLIST *List); + +void DestroyWIList(); +bool LoadWIData(bool dial); + +INT_PTR CALLBACK DlgPopupOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +// functions in weather_info.c +void GetINIInfo(wchar_t *pszSvc); + +void MoreVarList(); + +// functions in weather_opt.c +void SetTextDefault(const char* in); +void LoadOptions(); +void SaveOptions(); + +int OptInit(WPARAM wParam,LPARAM lParam); + + +// functions in weather_popup.c +int WeatherPopup(WPARAM wParam, LPARAM lParam); +int WeatherError(WPARAM wParam, LPARAM lParam); +int WPShowMessage(wchar_t* lpzText, WORD kind); + +LRESULT CALLBACK PopupWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + +// functions in weather_svcs.c +void InitServices(void); + +INT_PTR WeatherSetStatus(WPARAM new_status, LPARAM lParam); +INT_PTR WeatherGetCaps(WPARAM wParam, LPARAM lParam); +INT_PTR WeatherGetName(WPARAM wParam, LPARAM lParam); +INT_PTR WeatherGetStatus(WPARAM wParam, LPARAM lParam); +INT_PTR WeatherLoadIcon(WPARAM wParam, LPARAM lParam); + +void UpdateMenu(BOOL State); +void UpdatePopupMenu(BOOL State); +void AddMenuItems(); +void AvatarDownloaded(MCONTACT hContact); + +// functions in weather_update.c +int UpdateWeather(MCONTACT hContact); + +void UpdateAll(BOOL AutoUpdate, BOOL RemoveOld); +INT_PTR UpdateSingleStation(WPARAM wParam,LPARAM lParam); +INT_PTR UpdateAllInfo(WPARAM wParam,LPARAM lParam); +INT_PTR UpdateSingleRemove(WPARAM wParam,LPARAM lParam); +INT_PTR UpdateAllRemove(WPARAM wParam,LPARAM lParam); + +int GetWeatherData(MCONTACT hContact); + +void CALLBACK timerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); +void CALLBACK timerProc2(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); + +// function from multiwin module +void InitMwin(void); +void DestroyMwin(void); +INT_PTR Mwin_MenuClicked(WPARAM wParam, LPARAM lParam); +int BuildContactMenu(WPARAM wparam, LPARAM lparam); +void UpdateMwinData(MCONTACT hContact); +void removeWindow(MCONTACT hContact); + +// functions in weather_userinfo.c +int UserInfoInit(WPARAM wParam, LPARAM lParam); +INT_PTR CALLBACK DlgProcINIPage(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +#define WM_UPDATEDATA WM_USER + 2687 + +int BriefInfo(WPARAM wParam, LPARAM lParam); +INT_PTR BriefInfoSvc(WPARAM wParam, LPARAM lParam); + +void InitIcons(void); +HICON LoadIconEx(const char* name, bool big); +HANDLE GetIconHandle(const char* name); +void ReleaseIconEx(HICON hIcon); + +//============ Plugin Class ============ + +struct CMPlugin : public PLUGIN<CMPlugin> +{ + CMPlugin(); + + int Load() override; + int Unload() override; +}; + diff --git a/protocols/Weather/src/version.h b/protocols/Weather/src/version.h new file mode 100644 index 0000000000..d777fe40f9 --- /dev/null +++ b/protocols/Weather/src/version.h @@ -0,0 +1,13 @@ +#define __MAJOR_VERSION 0 +#define __MINOR_VERSION 4 +#define __RELEASE_NUM 0 +#define __BUILD_NUM 4 + +#include <stdver.h> + +#define __PLUGIN_NAME "Weather" +#define __FILENAME "Weather.dll" +#define __DESCRIPTION "Retrieves weather information and displays it in your contact list." +#define __AUTHOR "Miranda NG team" +#define __AUTHORWEB "https://miranda-ng.org/p/Weather/" +#define __COPYRIGHT "© 2002-2005 NoName, 2005-2010 Boris Krasnovskiy, 2012-19 Miranda NG team" diff --git a/protocols/Weather/src/weather.cpp b/protocols/Weather/src/weather.cpp new file mode 100644 index 0000000000..742f860967 --- /dev/null +++ b/protocols/Weather/src/weather.cpp @@ -0,0 +1,231 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* +Main file for the Weather Protocol, includes loading, unloading, +upgrading, support for plugin uninsaller, and anything that doesn't +belong to any other file. +*/ + +#include "stdafx.h" + +//============ GLOBAL VARIABLES ============ + +WIDATALIST *WIHead; +WIDATALIST *WITail; + +HWND hPopupWindow; + +HANDLE hHookWeatherUpdated; +HANDLE hHookWeatherError; + +MWindowList hDataWindowList, hWindowList; + +HANDLE hUpdateMutex; + +unsigned status; +unsigned old_status; + +UINT_PTR timerId; + +CMPlugin g_plugin; + +MYOPTIONS opt; + +// check if weather is currently updating +BOOL ThreadRunning; + +// variable to determine if module loaded +BOOL ModuleLoaded; + +HANDLE hTBButton = nullptr; + +///////////////////////////////////////////////////////////////////////////////////////// +// plugin info + +static const PLUGININFOEX pluginInfoEx = +{ + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {6B612A34-DCF2-4E32-85CF-B6FD006B745E} + {0x6b612a34, 0xdcf2, 0x4e32, {0x85, 0xcf, 0xb6, 0xfd, 0x0, 0x6b, 0x74, 0x5e}} +}; + +CMPlugin::CMPlugin() : + PLUGIN<CMPlugin>(MODULENAME, pluginInfoEx) +{ + opt.NoProtoCondition = g_plugin.getByte("NoStatus", true); + RegisterProtocol((opt.NoProtoCondition) ? PROTOTYPE_VIRTUAL : PROTOTYPE_PROTOCOL); + SetUniqueId("ID"); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST }; + +///////////////////////////////////////////////////////////////////////////////////////// + +int WeatherShutdown(WPARAM, LPARAM) +{ + KillTimer(nullptr, timerId); // kill update timer + + SaveOptions(); // save options once more + status = ID_STATUS_OFFLINE; // set status to offline + + WindowList_Broadcast(hWindowList, WM_CLOSE, 0, 0); + WindowList_Broadcast(hDataWindowList, WM_CLOSE, 0, 0); + SendMessage(hWndSetup, WM_CLOSE, 0, 0); + + return 0; +} + +int OnToolbarLoaded(WPARAM, LPARAM) +{ + TTBButton ttb = {}; + ttb.name = LPGEN("Enable/disable auto update"); + ttb.pszService = MS_WEATHER_ENABLED; + ttb.pszTooltipUp = LPGEN("Auto Update Enabled"); + ttb.pszTooltipDn = LPGEN("Auto Update Disabled"); + ttb.hIconHandleUp = GetIconHandle("main"); + ttb.hIconHandleDn = GetIconHandle("disabled"); + ttb.dwFlags = (g_plugin.getByte("AutoUpdate", 1) ? 0 : TTBBF_PUSHED) | TTBBF_ASPUSHBUTTON | TTBBF_VISIBLE; + hTBButton = g_plugin.addTTB(&ttb); + return 0; +} + +// weather protocol initialization function +// run after the event ME_SYSTEM_MODULESLOADED occurs +int WeatherInit(WPARAM, LPARAM) +{ + // initialize netlib + NetlibInit(); + + InitMwin(); + + // load weather menu items + AddMenuItems(); + + // timer for the first update + timerId = SetTimer(nullptr, 0, 5000, timerProc2); // first update is 5 sec after load + + // weather user detail + HookEvent(ME_USERINFO_INITIALISE, UserInfoInit); + HookEvent(ME_TTB_MODULELOADED, OnToolbarLoaded); + return 0; +} + +//============ MISC FUNCTIONS ============ + +// initialize the global variables at startup +void InitVar() +{ + // setup the linklist for weather update list + UpdateListTail = nullptr; + UpdateListHead = nullptr; + + // other settings + timerId = 0; + opt.DefStn = NULL; + ModuleLoaded = FALSE; +} + +int CMPlugin::Load() +{ + // initialize global variables + InitVar(); + + // load options and set defaults + LoadOptions(); + + InitIcons(); + + // reset the weather data at startup for individual contacts + EraseAllInfo(); + + // load weather update data + LoadWIData(true); + + // set status to online if "Do not display weather condition as protocol status" is enabled + old_status = status = ID_STATUS_OFFLINE; + + // add an event on weather update and error + hHookWeatherUpdated = CreateHookableEvent(ME_WEATHER_UPDATED); + hHookWeatherError = CreateHookableEvent(ME_WEATHER_ERROR); + + // initialize options and network + HookEvent(ME_OPT_INITIALISE, OptInit); + HookEvent(ME_SYSTEM_MODULESLOADED, WeatherInit); + HookEvent(ME_DB_CONTACT_DELETED, ContactDeleted); + HookEvent(ME_CLIST_DOUBLECLICKED, BriefInfo); + HookEvent(ME_WEATHER_UPDATED, WeatherPopup); + HookEvent(ME_WEATHER_ERROR, WeatherError); + HookEvent(ME_SYSTEM_PRESHUTDOWN, WeatherShutdown); + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, BuildContactMenu); + + hDataWindowList = WindowList_Create(); + hWindowList = WindowList_Create(); + + hUpdateMutex = CreateMutex(nullptr, FALSE, nullptr); + + // initialize weather protocol services + InitServices(); + + // add sound event + g_plugin.addSound("weatherupdated", _A2W(MODULENAME), LPGENW("Condition Changed")); + g_plugin.addSound("weatheralert", _A2W(MODULENAME), LPGENW("Alert Issued")); + + // window needed for popup commands + wchar_t SvcFunc[100]; + mir_snwprintf(SvcFunc, L"%s__PopupWindow", _A2W(MODULENAME)); + hPopupWindow = CreateWindowEx(WS_EX_TOOLWINDOW, L"static", SvcFunc, 0, CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, nullptr, g_plugin.getInst(), nullptr); + SetWindowLongPtr(hPopupWindow, GWLP_WNDPROC, (LONG_PTR)PopupWndProc); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// unload function + +int CMPlugin::Unload() +{ + DestroyMwin(); + DestroyWindow(hPopupWindow); + + DestroyHookableEvent(hHookWeatherUpdated); + DestroyHookableEvent(hHookWeatherError); + + Netlib_CloseHandle(hNetlibUser); + + DestroyUpdateList(); + DestroyOptions(); + DestroyWIList(); // unload all ini data from memory + + WindowList_Destroy(hDataWindowList); + WindowList_Destroy(hWindowList); + + CloseHandle(hUpdateMutex); + return 0; +} diff --git a/protocols/Weather/src/weather_addstn.cpp b/protocols/Weather/src/weather_addstn.cpp new file mode 100644 index 0000000000..ca7a8ae6e3 --- /dev/null +++ b/protocols/Weather/src/weather_addstn.cpp @@ -0,0 +1,432 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* This file contain the source related to search and add a weather station +to the contact list. Contain code for both name and ID search. +*/ + +#include "stdafx.h" + +// variables used for weather_addstn.c +static int sttSearchId = -1; +static wchar_t name1[256]; + +// ============ ADDING NEW STATION ============ + +// protocol service function for adding a new contact onto contact list +// lParam = PROTOSEARCHRESULT +INT_PTR WeatherAddToList(WPARAM, LPARAM lParam) +{ + PROTOSEARCHRESULT *psr = (PROTOSEARCHRESULT*)lParam; + if (!psr || !psr->email.w) + return 0; + + // search for existing contact + for (auto &hContact : Contacts()) { + // check if it is a weather contact + if (IsMyContact(hContact)) { + DBVARIANT dbv; + // check ID to see if the contact already exist in the database + if (!g_plugin.getWString(hContact, "ID", &dbv)) { + if (!mir_wstrcmpi(psr->email.w, dbv.pwszVal)) { + // remove the flag for not on list and hidden, thus make the contact visible + // and add them on the list + if (db_get_b(hContact, "CList", "NotOnList", 1)) { + db_unset(hContact, "CList", "NotOnList"); + db_unset(hContact, "CList", "Hidden"); + } + db_free(&dbv); + // contact is added, function quitting + return (INT_PTR)hContact; + } + db_free(&dbv); + } + } + } + + // if contact with the same ID was not found, add it + if (psr->cbSize < sizeof(PROTOSEARCHRESULT)) return 0; + MCONTACT hContact = db_add_contact(); + Proto_AddToContact(hContact, MODULENAME); + // suppress online notification for the new contact + CallService(MS_IGNORE_IGNORE, hContact, IGNOREEVENT_USERONLINE); + + // set contact info and settings + wchar_t svc[256]; + wcsncpy(svc, psr->email.w, _countof(svc)); svc[_countof(svc) - 1] = 0; + GetSvc(svc); + // set settings by obtaining the default for the service + if (psr->lastName.w[0] != 0) { + WIDATA *sData = GetWIData(svc); + g_plugin.setWString(hContact, "MapURL", sData->DefaultMap); + g_plugin.setString(hContact, "InfoURL", sData->DefaultURL); + } + else { // if no valid service is found, create empty strings for MapURL and InfoURL + g_plugin.setString(hContact, "MapURL", ""); + g_plugin.setString(hContact, "InfoURL", ""); + } + // write the other info and settings to the database + g_plugin.setWString(hContact, "ID", psr->email.w); + g_plugin.setWString(hContact, "Nick", psr->nick.w); + g_plugin.setWord(hContact, "Status", ID_STATUS_OFFLINE); + + AvatarDownloaded(hContact); + + wchar_t str[256]; + mir_snwprintf(str, TranslateT("Current weather information for %s."), psr->nick.w); + g_plugin.setWString(hContact, "About", str); + + // make the last update tags to something invalid + g_plugin.setString(hContact, "LastLog", "never"); + g_plugin.setString(hContact, "LastCondition", "None"); + g_plugin.setString(hContact, "LastTemperature", "None"); + + // ignore status change + db_set_dw(hContact, "Ignore", "Mask", 8); + + // if no default station is found, set the new contact as default station + if (opt.Default[0] == 0) { + DBVARIANT dbv; + GetStationID(hContact, opt.Default, _countof(opt.Default)); + + opt.DefStn = hContact; + if (!g_plugin.getWString(hContact, "Nick", &dbv)) { + // notification message box + mir_snwprintf(str, TranslateT("%s is now the default weather station"), dbv.pwszVal); + db_free(&dbv); + MessageBox(nullptr, str, TranslateT("Weather Protocol"), MB_OK | MB_ICONINFORMATION); + } + g_plugin.setWString("Default", opt.Default); + } + // display the Edit Settings dialog box + EditSettings(hContact, 0); + return (INT_PTR)hContact; +} + +// ============ WARNING DIALOG ============ + +// show a message box and cancel search if update is in process +BOOL CheckSearch() +{ + if (UpdateListHead != nullptr) { + MessageBox(nullptr, TranslateT("Please try again after weather update is completed."), TranslateT("Weather Protocol"), MB_OK | MB_ICONERROR); + return FALSE; + } + return TRUE; +} + +// ============ BASIC ID SEARCH ============ + +static wchar_t sttSID[32]; + +// A timer process for the ID search (threaded) +static void __cdecl BasicSearchTimerProc(LPVOID) +{ + int result; + // search only when it's not current updating weather. + if (CheckSearch()) + result = IDSearch(sttSID, sttSearchId); + + // broadcast the search result + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)sttSearchId); + + // exit the search + sttSearchId = -1; +} + +// the service function for ID search +// lParam = ID search string +INT_PTR WeatherBasicSearch(WPARAM, LPARAM lParam) +{ + if (sttSearchId != -1) return 0; //only one search at a time + wcsncpy(sttSID, (wchar_t*)lParam, _countof(sttSID)); + sttSID[_countof(sttSID) - 1] = 0; + sttSearchId = 1; + // create a thread for the ID search + mir_forkthread(BasicSearchTimerProc); + return sttSearchId; +} + +// ============ NAME SEARCH ============ +// +// name search timer process (threaded) +static void __cdecl NameSearchTimerProc(LPVOID) +{ + // search only when it's not current updating weather. + if (CheckSearch()) + if (name1[0] != 0) + NameSearch(name1, sttSearchId); // search nickname field + + // broadcast the result + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)sttSearchId); + + // exit the search + sttSearchId = -1; +} + +static INT_PTR CALLBACK WeatherSearchAdvancedDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SetFocus(GetDlgItem(hwndDlg, IDC_SEARCHCITY)); + return TRUE; + + case WM_COMMAND: + if (HIWORD(wParam) == EN_SETFOCUS) + PostMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(0, EN_SETFOCUS), (LPARAM)hwndDlg); + } + return FALSE; +} + +INT_PTR WeatherCreateAdvancedSearchUI(WPARAM, LPARAM lParam) +{ + HWND parent = (HWND)lParam; + if (parent) + return (INT_PTR)CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_SEARCHCITY), parent, WeatherSearchAdvancedDlgProc, 0); + + return 0; +} + +// service function for name search +INT_PTR WeatherAdvancedSearch(WPARAM, LPARAM lParam) +{ + if (sttSearchId != -1) return 0; //only one search at a time + + sttSearchId = 1; + GetDlgItemText((HWND)lParam, IDC_SEARCHCITY, name1, _countof(name1)); + + // search for the weather station using a thread + mir_forkthread(NameSearchTimerProc); + return sttSearchId; +} + +// ============ SEARCH FOR A WEATHER STATION USING ID ============ + +// Seaching station ID from a single weather service (Threaded) +// sID = search string for the station ID +// searchId = -1 +// sData = the ID search data for that particular weather service +// svcname = the name of the weather service that is currently searching (ie. Yahoo Weather) +int IDSearchProc(wchar_t *sID, const int searchId, WIIDSEARCH *sData, wchar_t *svc, wchar_t *svcname) +{ + wchar_t str[MAX_DATA_LEN], newID[MAX_DATA_LEN]; + + if (sData->Available) { + char loc[255]; + wchar_t *szData = nullptr; + + // load the page + mir_snprintf(loc, sData->SearchURL, sID); + BOOL bFound = (InternetDownloadFile(loc, nullptr, nullptr, &szData) == 0); + if (bFound) { + wchar_t* szInfo = szData; + + // not found + if (wcsstr(szInfo, sData->NotFoundStr) == nullptr) + GetDataValue(&sData->Name, str, &szInfo); + } + + mir_free(szData); + // Station not found exit + if (!bFound) return 1; + } + + // give no station name but only ID if the search is unavailable + else wcsncpy(str, TranslateT("<Enter station name here>"), MAX_DATA_LEN - 1); + mir_snwprintf(newID, L"%s/%s", svc, sID); + + // set the search result and broadcast it + PROTOSEARCHRESULT psr = { sizeof(psr) }; + psr.flags = PSR_UNICODE; + psr.nick.w = str; + psr.firstName.w = L" "; + psr.lastName.w = svcname; + psr.email.w = newID; + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)searchId, (LPARAM)&psr); + + return 0; +} + +// ID search (Threaded) +// sID: the ID to search for +// searchId: don't change +// return 0 if no error +int IDSearch(wchar_t *sID, const int searchId) +{ + // for a normal ID search (ID != #) + if (mir_wstrcmp(sID, L"#")) { + WIDATALIST *Item = WIHead; + + // search every weather service using the search station ID + while (Item != nullptr) { + IDSearchProc(sID, searchId, &Item->Data.IDSearch, Item->Data.InternalName, Item->Data.DisplayName); + Item = Item->next; + } + } + // if the station ID is #, return a dummy result and quit the funciton + else { + // return an empty contact on "#" + PROTOSEARCHRESULT psr = { sizeof(psr) }; + psr.flags = PSR_UNICODE; + psr.nick.w = TranslateT("<Enter station name here>"); // to be entered + psr.firstName.w = L" "; + psr.lastName.w = L""; + psr.email.w = TranslateT("<Enter station ID here>"); // to be entered + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)searchId, (LPARAM)&psr); + } + + return 0; +} + +// ============ SEARCH FOR A WEATHER STATION BY NAME ============ + +// Seaching station name from a single weather service (Threaded) +// name = the name of the weather station to be searched +// searchId = -1 +// sData = the name search data for that particular weather service +// svcname = the name of the weather service that is currently searching (ie. Yahoo Weather) +int NameSearchProc(wchar_t *name, const int searchId, WINAMESEARCH *sData, wchar_t *svc, wchar_t *svcname) +{ + wchar_t Name[MAX_DATA_LEN], str[MAX_DATA_LEN], sID[MAX_DATA_LEN], *szData = nullptr, *search; + + // replace spaces with %20 + char loc[256]; + T2Utf szSearchName(name); + mir_snprintf(loc, sData->SearchURL, mir_urlEncode(szSearchName).c_str()); + if (InternetDownloadFile(loc, nullptr, nullptr, &szData) == 0) { + wchar_t* szInfo = szData; + search = wcsstr(szInfo, sData->NotFoundStr); // determine if data is available + if (search == nullptr) { // if data is found + // test if it is single result + if (sData->Single.Available && sData->Multiple.Available) + search = wcsstr(szInfo, sData->SingleStr); + // for single result + if (sData->Single.Available && (search != nullptr || !sData->Multiple.Available)) { // single result + // if station ID appears first in the downloaded data + if (!mir_wstrcmpi(sData->Single.First, L"ID")) { + GetDataValue(&sData->Single.ID, str, &szInfo); + mir_snwprintf(sID, L"%s/%s", svc, str); + GetDataValue(&sData->Single.Name, Name, &szInfo); + } + // if station name appears first in the downloaded data + else if (!mir_wstrcmpi(sData->Single.First, L"NAME")) { + GetDataValue(&sData->Single.Name, Name, &szInfo); + GetDataValue(&sData->Single.ID, str, &szInfo); + mir_snwprintf(sID, L"%s/%s", svc, str); + } + else + str[0] = 0; + + // if no station ID is obtained, quit the search + if (str[0] == 0) { + mir_free(szData); + return 1; + } + + // if can't get the name, use the search string as name + if (Name[0] == 0) + wcsncpy(Name, name, _countof(Name)); + + // set the data and broadcast it + PROTOSEARCHRESULT psr = { sizeof(psr) }; + psr.flags = PSR_UNICODE; + psr.nick.w = Name; + psr.firstName.w = L" "; + psr.lastName.w = svcname; + psr.email.w = sID; + psr.id.w = sID; + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)searchId, (LPARAM)&psr); + mir_free(szData); + return 0; + } + // for multiple result + else if (sData->Multiple.Available) { // multiple results + // search for the next occurrence of the string + while (true) { + // if station ID appears first in the downloaded data + if (!mir_wstrcmpi(sData->Multiple.First, L"ID")) { + GetDataValue(&sData->Multiple.ID, str, &szInfo); + mir_snwprintf(sID, L"%s/%s", svc, str); + GetDataValue(&sData->Multiple.Name, Name, &szInfo); + } + // if station name appears first in the downloaded data + else if (!mir_wstrcmpi(sData->Multiple.First, L"NAME")) { + GetDataValue(&sData->Multiple.Name, Name, &szInfo); + GetDataValue(&sData->Multiple.ID, str, &szInfo); + mir_snwprintf(sID, L"%s/%s", svc, str); + } + else + break; + + // if no station ID is obtained, search completed and quit the search + if (str[0] == 0) + break; + + // if can't get the name, use the search string as name + if (Name[0] == 0) + wcsncpy(Name, name, _countof(Name)); + + PROTOSEARCHRESULT psr = { sizeof(psr) }; + psr.flags = PSR_UNICODE; + psr.nick.w = Name; + psr.firstName.w = L""; + psr.lastName.w = svcname; + psr.email.w = sID; + psr.id.w = sID; + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)searchId, (LPARAM)&psr); + } + } + } + + mir_free(szData); + return 0; + } + + mir_free(szData); + return 1; +} + +// name search (Threaded) +// name: the station name to search for +// searchId: don't change +// return 0 if no error +int NameSearch(wchar_t *name, const int searchId) +{ + // search every weather service using the search station name + WIDATALIST *Item = WIHead; + while (Item != nullptr) { + if (Item->Data.NameSearch.Single.Available || Item->Data.NameSearch.Multiple.Available) + NameSearchProc(name, searchId, &Item->Data.NameSearch, Item->Data.InternalName, Item->Data.DisplayName); + Item = Item->next; + } + + return 0; +} + +// ======================MENU ITEM FUNCTION ============ + +// add a new weather station via find/add dialog +int WeatherAdd(WPARAM, LPARAM) +{ + db_set_s(0, "FindAdd", "LastSearched", "Weather"); + CallService(MS_FINDADD_FINDADD, 0, 0); + return 0; +} diff --git a/protocols/Weather/src/weather_contacts.cpp b/protocols/Weather/src/weather_contacts.cpp new file mode 100644 index 0000000000..f572b29564 --- /dev/null +++ b/protocols/Weather/src/weather_contacts.cpp @@ -0,0 +1,480 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* This file contain the source that is related to weather contacts, +include the links, edit settings, and loading weather information for +the contact. +*/ + +#include "stdafx.h" + +static void OpenUrl(wchar_t* format, wchar_t* id) +{ + wchar_t loc[512]; + GetID(id); + mir_snwprintf(loc, format, id); + Utils_OpenUrlW(loc); +} + +//============ BASIC CONTACTS FUNCTIONS AND LINKS ============ + +// view weather log for the contact +// wParam = current contact +INT_PTR ViewLog(WPARAM wParam, LPARAM lParam) +{ + // see if the log path is set + DBVARIANT dbv; + if (!g_plugin.getWString(wParam, "Log", &dbv)) { + if (dbv.pszVal[0] != 0) + ShellExecute((HWND)lParam, L"open", dbv.pwszVal, L"", L"", SW_SHOW); + db_free(&dbv); + } + else // display warning dialog if no path is specified + MessageBox(nullptr, TranslateT("Weather condition was not logged."), + TranslateT("Weather Protocol"), MB_OK | MB_ICONINFORMATION); + return 0; +} + +// read complete forecast +// wParam = current contact +INT_PTR LoadForecast(WPARAM wParam, LPARAM) +{ + wchar_t id[256], loc2[256]; + GetStationID(wParam, id, _countof(id)); + if (id[0] != 0) { + // check if the complte forecast URL is set. If it is not, display warning and quit + if (db_get_wstatic(wParam, MODULENAME, "InfoURL", loc2, _countof(loc2)) || loc2[0] == 0) { + MessageBox(nullptr, NO_FORECAST_URL, TranslateT("Weather Protocol"), MB_ICONINFORMATION); + return 1; + } + // set the url and open the webpage + OpenUrl(loc2, id); + } + return 0; +} + +// load weather map +// wParam = current contact +INT_PTR WeatherMap(WPARAM wParam, LPARAM) +{ + wchar_t id[256], loc2[256]; + GetStationID(wParam, id, _countof(id)); + if (id[0] != 0) { + // check if the weather map URL is set. If it is not, display warning and quit + if (db_get_wstatic(wParam, MODULENAME, "MapURL", loc2, _countof(loc2)) || loc2[0] == 0) { + MessageBox(nullptr, NO_MAP_URL, TranslateT("Weather Protocol"), MB_ICONINFORMATION); + return 1; + } + + // set the url and open the webpage + OpenUrl(loc2, id); + } + + return 0; +} + +//============ EDIT SETTINGS ============ + +typedef struct +{ + MCONTACT hContact; + HICON hRename; + HICON hUserDetail; + HICON hFile; + HICON hSrchAll; +} CntSetWndDataType; + +// edit weather settings +// lParam = current contact +static INT_PTR CALLBACK DlgProcChange(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + DBVARIANT dbv; + wchar_t str[MAX_DATA_LEN], str2[256], city[256], filter[256], *pfilter, *chop; + char loc[512]; + OPENFILENAME ofn; // common dialog box structure + MCONTACT hContact; + WIDATA *sData; + CntSetWndDataType *wndData = nullptr; + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + wndData = (CntSetWndDataType*)mir_alloc(sizeof(CntSetWndDataType)); + wndData->hContact = hContact = lParam; + wndData->hRename = Skin_LoadIcon(SKINICON_OTHER_RENAME); + wndData->hUserDetail = Skin_LoadIcon(SKINICON_OTHER_USERDETAILS); + wndData->hFile = Skin_LoadIcon(SKINICON_EVENT_FILE); + wndData->hSrchAll = Skin_LoadIcon(SKINICON_OTHER_SEARCHALL); + + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)wndData); + + // set button images + SendDlgItemMessage(hwndDlg, IDC_GETNAME, BM_SETIMAGE, IMAGE_ICON, (LPARAM)wndData->hRename); + SendDlgItemMessage(hwndDlg, IDC_SVCINFO, BM_SETIMAGE, IMAGE_ICON, (LPARAM)wndData->hUserDetail); + SendDlgItemMessage(hwndDlg, IDC_BROWSE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)wndData->hFile); + SendDlgItemMessage(hwndDlg, IDC_VIEW1, BM_SETIMAGE, IMAGE_ICON, (LPARAM)wndData->hSrchAll); + SendDlgItemMessage(hwndDlg, IDC_RESET1, BM_SETIMAGE, IMAGE_ICON, (LPARAM)wndData->hRename); + SendDlgItemMessage(hwndDlg, IDC_VIEW2, BM_SETIMAGE, IMAGE_ICON, (LPARAM)wndData->hSrchAll); + SendDlgItemMessage(hwndDlg, IDC_RESET2, BM_SETIMAGE, IMAGE_ICON, (LPARAM)wndData->hRename); + + // make all buttons flat + SendDlgItemMessage(hwndDlg, IDC_GETNAME, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_SVCINFO, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_BROWSE, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_VIEW1, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_RESET1, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_VIEW2, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_RESET2, BUTTONSETASFLATBTN, TRUE, 0); + + // set tooltip for the buttons + SendDlgItemMessage(hwndDlg, IDC_GETNAME, BUTTONADDTOOLTIP, (WPARAM)LPGENW("Get city name from ID"), BATF_UNICODE); + SendDlgItemMessage(hwndDlg, IDC_SVCINFO, BUTTONADDTOOLTIP, (WPARAM)LPGENW("Weather INI information"), BATF_UNICODE); + SendDlgItemMessage(hwndDlg, IDC_BROWSE, BUTTONADDTOOLTIP, (WPARAM)LPGENW("Browse"), BATF_UNICODE); + SendDlgItemMessage(hwndDlg, IDC_VIEW1, BUTTONADDTOOLTIP, (WPARAM)LPGENW("View webpage"), BATF_UNICODE); + SendDlgItemMessage(hwndDlg, IDC_RESET1, BUTTONADDTOOLTIP, (WPARAM)LPGENW("Reset to default"), BATF_UNICODE); + SendDlgItemMessage(hwndDlg, IDC_VIEW2, BUTTONADDTOOLTIP, (WPARAM)LPGENW("View webpage"), BATF_UNICODE); + SendDlgItemMessage(hwndDlg, IDC_RESET2, BUTTONADDTOOLTIP, (WPARAM)LPGENW("Reset to default"), BATF_UNICODE); + + // save the handle for the contact + WindowList_Add(hWindowList, hwndDlg, hContact); + + // start to get the settings + // if the setting not exist, leave the dialog box blank + if (!g_plugin.getWString(hContact, "ID", &dbv)) { + SetDlgItemText(hwndDlg, IDC_ID, dbv.pwszVal); + // check if the station is a default station + CheckDlgButton(hwndDlg, IDC_DEFA, mir_wstrcmp(dbv.pwszVal, opt.Default) != 0 ? BST_CHECKED : BST_UNCHECKED); + db_free(&dbv); + } + if (!g_plugin.getWString(hContact, "Nick", &dbv)) { + SetDlgItemText(hwndDlg, IDC_NAME, dbv.pwszVal); + db_free(&dbv); + } + if (!g_plugin.getWString(hContact, "Log", &dbv)) { + SetDlgItemText(hwndDlg, IDC_LOG, dbv.pwszVal); + // if the log path is not empty, check the checkbox for external log + if (dbv.pwszVal[0]) CheckDlgButton(hwndDlg, IDC_External, BST_CHECKED); + db_free(&dbv); + } + // enable/disable the browse button depending on the value of external log checkbox + EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE), (BYTE)IsDlgButtonChecked(hwndDlg, IDC_External)); + + // other checkbox options + CheckDlgButton(hwndDlg, IDC_DPop, g_plugin.getByte(hContact, "DPopUp", FALSE) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_DAutoUpdate, g_plugin.getByte(hContact, "DAutoUpdate", FALSE) ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hwndDlg, IDC_Internal, g_plugin.getByte(hContact, "History", 0) ? BST_CHECKED : BST_UNCHECKED); + + if (!g_plugin.getWString(hContact, "InfoURL", &dbv)) { + SetDlgItemText(hwndDlg, IDC_IURL, dbv.pwszVal); + db_free(&dbv); + } + if (!g_plugin.getWString(hContact, "MapURL", &dbv)) { + SetDlgItemText(hwndDlg, IDC_MURL, dbv.pwszVal); + db_free(&dbv); + } + + // display the dialog box and free memory + Utils_RestoreWindowPositionNoMove(hwndDlg, NULL, MODULENAME, "EditSetting_"); + ShowWindow(hwndDlg, SW_SHOW); + break; + + case WM_COMMAND: + wndData = (CntSetWndDataType*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + hContact = wndData ? wndData->hContact : NULL; + + switch (LOWORD(wParam)) { + case IDC_ID: + // check if there are 2 parts in the ID (svc/id) seperated by "/" + // if not, don't let user change the setting + GetDlgItemText(hwndDlg, IDC_ID, str, _countof(str)); + chop = wcsstr(str, L"/"); + if (chop == nullptr) + EnableWindow(GetDlgItem(hwndDlg, IDC_CHANGE), FALSE); + else + EnableWindow(GetDlgItem(hwndDlg, IDC_CHANGE), TRUE); + break; + + case IDC_NAME: + // check if station name is entered + // if not, don't let user change the setting + GetDlgItemText(hwndDlg, IDC_NAME, str, _countof(str)); + EnableWindow(GetDlgItem(hwndDlg, IDC_CHANGE), str[0] != 0); + break; + + case IDC_GETNAME: + // the button for getting station name from the internet + // this function uses the ID search for add/find weather station + if (!CheckSearch()) + return TRUE; // don't download if update is in progress + + // get the weather update data using the string in the ID field + GetDlgItemText(hwndDlg, IDC_ID, str, _countof(str)); + GetSvc(str); + sData = GetWIData(str); + GetDlgItemText(hwndDlg, IDC_ID, str, _countof(str)); + GetID(str); + // if ID search is available, do it + if (sData->IDSearch.Available) { + // load the page + mir_snprintf(loc, sData->IDSearch.SearchURL, str); + str[0] = 0; + wchar_t *pData = nullptr; + if (InternetDownloadFile(loc, nullptr, sData->UserAgent, &pData) == 0) { + wchar_t *szInfo = pData; + wchar_t* search = wcsstr(szInfo, sData->IDSearch.NotFoundStr); + + // if the page is found (ie. valid ID), get the name of the city + if (search == nullptr) + GetDataValue(&sData->IDSearch.Name, str, &szInfo); + } + // free memory + mir_free(pData); + } + + // give no station name but only ID if the search is unavailable + if (str[0] != 0) + SetDlgItemText(hwndDlg, IDC_NAME, str); + break; + + case IDC_External: + // enable/disable the borwse button depending if the external log is enabled + EnableWindow(GetDlgItem(hwndDlg, IDC_BROWSE), (BYTE)IsDlgButtonChecked(hwndDlg, IDC_External)); + if (!(BYTE)IsDlgButtonChecked(hwndDlg, IDC_External)) + return TRUE; + __fallthrough; + + case IDC_BROWSE: + // browse for the external log file + GetDlgItemText(hwndDlg, IDC_LOG, str, _countof(str)); + // Initialize OPENFILENAME + memset(&ofn, 0, sizeof(OPENFILENAME)); + ofn.lStructSize = sizeof(OPENFILENAME); + ofn.hwndOwner = hwndDlg; + ofn.lpstrFile = str; + ofn.nMaxFile = _countof(str); + // set filters + wcsncpy(filter, TranslateT("Text Files"), _countof(filter) - 1); + mir_wstrncat(filter, L" (*.txt)", _countof(filter) - mir_wstrlen(filter)); + pfilter = filter + mir_wstrlen(filter) + 1; + wcsncpy(pfilter, L"*.txt", _countof(filter) - 1); + pfilter = pfilter + mir_wstrlen(pfilter) + 1; + wcsncpy(pfilter, TranslateT("All Files"), _countof(filter) - 1); + mir_wstrncat(pfilter, L" (*.*)", _countof(filter) - mir_wstrlen(filter)); + pfilter = pfilter + mir_wstrlen(pfilter) + 1; + wcsncpy(pfilter, L"*.*", _countof(filter) - 1); + pfilter = pfilter + mir_wstrlen(pfilter) + 1; + *pfilter = '\0'; + ofn.lpstrFilter = filter; + ofn.nFilterIndex = 1; + ofn.lpstrFileTitle = nullptr; + ofn.nMaxFileTitle = 0; + ofn.lpstrInitialDir = nullptr; + ofn.Flags = OFN_PATHMUSTEXIST; + + // Display a Open dialog box and put the file name on the dialog + if (GetOpenFileName(&ofn)) + SetDlgItemText(hwndDlg, IDC_LOG, ofn.lpstrFile); + // if there is no log file specified, disable external logging + EnableWindow(GetDlgItem(hwndDlg, IDC_CHANGE), ofn.lpstrFile[0] != 0); + break; + + case IDC_VIEW1: + // view the page for more info + GetDlgItemText(hwndDlg, IDC_IURL, str, _countof(str)); + if (str[0] == 0) + return TRUE; + GetDlgItemText(hwndDlg, IDC_ID, str2, _countof(str2)); + OpenUrl(str, str2); + break; + + case IDC_VIEW2: + // view the page for weather map + GetDlgItemText(hwndDlg, IDC_MURL, str, _countof(str)); + if (str[0] == 0) + return TRUE; + GetDlgItemText(hwndDlg, IDC_ID, str2, _countof(str2)); + OpenUrl(str, str2); + break; + + case IDC_RESET1: + // reset the more info url to service default + GetDlgItemText(hwndDlg, IDC_ID, str, _countof(str)); + GetSvc(str); + sData = GetWIData(str); + SetDlgItemTextA(hwndDlg, IDC_IURL, sData->DefaultURL); + break; + + case IDC_RESET2: + // reset the weathe map url to service default + GetDlgItemText(hwndDlg, IDC_ID, str, _countof(str)); + GetSvc(str); + sData = GetWIData(str); + SetDlgItemText(hwndDlg, IDC_MURL, sData->DefaultMap); + break; + + case IDC_SVCINFO: + // display the information of the ini file used by the weather station + GetDlgItemText(hwndDlg, IDC_ID, str, _countof(str)); + GetSvc(str); + GetINIInfo(str); + break; + + case IDC_CHANGE: + // temporary disable the protocol while applying the change + // start writing the new settings to database + GetDlgItemText(hwndDlg, IDC_ID, str, _countof(str)); + g_plugin.setWString(hContact, "ID", str); + if ((BYTE)IsDlgButtonChecked(hwndDlg, IDC_DEFA)) { // if default station is set + mir_wstrcpy(opt.Default, str); + opt.DefStn = hContact; + g_plugin.setWString("Default", opt.Default); + } + GetDlgItemText(hwndDlg, IDC_NAME, city, _countof(city)); + g_plugin.setWString(hContact, "Nick", city); + mir_snwprintf(str2, TranslateT("Current weather information for %s."), city); + if ((BYTE)IsDlgButtonChecked(hwndDlg, IDC_External)) { + GetDlgItemText(hwndDlg, IDC_LOG, str, _countof(str)); + g_plugin.setWString(hContact, "Log", str); + } + else g_plugin.delSetting(hContact, "Log"); + + GetDlgItemText(hwndDlg, IDC_IURL, str, _countof(str)); + g_plugin.setWString(hContact, "InfoURL", str); + + GetDlgItemText(hwndDlg, IDC_MURL, str, _countof(str)); + g_plugin.setWString(hContact, "MapURL", str); + g_plugin.setWord(hContact, "Status", ID_STATUS_OFFLINE); + g_plugin.setWord(hContact, "StatusIcon", ID_STATUS_OFFLINE); + AvatarDownloaded(hContact); + g_plugin.setWString(hContact, "About", str2); + g_plugin.setByte(hContact, "History", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_Internal)); + g_plugin.setByte(hContact, "Overwrite", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_Overwrite)); + g_plugin.setByte(hContact, "File", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_External)); + g_plugin.setByte(hContact, "DPopUp", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_DPop)); + g_plugin.setByte(hContact, "DAutoUpdate", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_DAutoUpdate)); + + // re-enable the protocol and update the data for the station + g_plugin.setString(hContact, "LastCondition", "None"); + UpdateSingleStation(hContact, 0); + __fallthrough; + + case IDCANCEL: + // remove the dialog from window list and close it + DestroyWindow(hwndDlg); + break; + } + break; + + case WM_CLOSE: + // remove the dialog from window list and close it + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + wndData = (CntSetWndDataType*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + IcoLib_ReleaseIcon(wndData->hFile); + IcoLib_ReleaseIcon(wndData->hRename); + IcoLib_ReleaseIcon(wndData->hSrchAll); + IcoLib_ReleaseIcon(wndData->hUserDetail); + mir_free(wndData); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + + WindowList_Remove(hWindowList, hwndDlg); + Utils_SaveWindowPosition(hwndDlg, NULL, MODULENAME, "EditSetting_"); + break; + } + return FALSE; +} + +// show edit settings dialog +// wParam = current contact +INT_PTR EditSettings(WPARAM wParam, LPARAM) +{ + HWND hEditDlg = WindowList_Find(hWindowList, wParam); + + // search the dialog list to prevent multiple instance of dialog for the same contact + if (hEditDlg != nullptr) { + // if the dialog box already opened, bring it to the front + SetForegroundWindow(hEditDlg); + SetFocus(hEditDlg); + } + else { + // if the dialog box is not opened, open a new one + if (IsMyContact(wParam)) + CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_EDIT), nullptr, DlgProcChange, (LPARAM)wParam); + } + + return 0; +} + +//============ CONTACT DELETION ============ +// +// when a contact is deleted, make sure some other contact take over the default station +// wParam = deleted contact +int ContactDeleted(WPARAM wParam, LPARAM) +{ + if (!IsMyContact(wParam)) + return 0; + + removeWindow(wParam); + + // exit this function if it is not default station + ptrW tszID(g_plugin.getWStringA(wParam, "ID")); + if (tszID != NULL) + if (mir_wstrcmp(tszID, opt.Default)) + return 0; + + // now the default station is deleted, try to get a new one + + // start looking for other weather stations + for (auto &hContact : Contacts(MODULENAME)) { + tszID = g_plugin.getWStringA(hContact, "ID"); + if (tszID == NULL) + continue; + + // if the station is not a default station, set it as the new default station + // this is the first weather station encountered from the search + if (mir_wstrcmp(opt.Default, tszID)) { + wcsncpy_s(opt.Default, tszID, _TRUNCATE); + opt.DefStn = hContact; + ptrW tszNick(g_plugin.getWStringA(hContact, "Nick")); + if (tszNick != NULL) { + wchar_t str[255]; + mir_snwprintf(str, TranslateT("%s is now the default weather station"), (wchar_t*)tszNick); + MessageBox(nullptr, str, TranslateT("Weather Protocol"), MB_OK | MB_ICONINFORMATION); + } + g_plugin.setWString("Default", opt.Default); + return 0; // exit this function quickly + } + } + + // got here if no more weather station left + opt.Default[0] = 0; // no default station + opt.DefStn = NULL; + g_plugin.setWString("Default", opt.Default); + return 0; +} + +BOOL IsMyContact(MCONTACT hContact) +{ + const char *szProto = GetContactProto(hContact); + return szProto != nullptr && mir_strcmp(MODULENAME, szProto) == 0; +} diff --git a/protocols/Weather/src/weather_conv.cpp b/protocols/Weather/src/weather_conv.cpp new file mode 100644 index 0000000000..c46b079531 --- /dev/null +++ b/protocols/Weather/src/weather_conv.cpp @@ -0,0 +1,637 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* +This file contain the source related unit conversion, icon assignment, +string conversions, display text parsing, etc +*/ + +#include "stdafx.h" + +//============ SOME HELPER FUNCTIONS ============ + +// see if a string is a number +// s = the string to be determined +// return value = true if the string is a number, false if it isn't +BOOL is_number(wchar_t *s) +{ + BOOL tag = FALSE; + // looking character by character + // for a number: numerous spaces in front, then optional +/-, then the number + // don't care anything that comes after it + while (*s != '\0') { + if (*s >= '0' && *s <= '9') return TRUE; + else if (*s == ' '); + else if (*s != '+' && *s != '-') return FALSE; + else if ((*s == '+' || *s == '-') && !tag) tag = TRUE; + else return FALSE; + s++; + } + return FALSE; +} + +static void numToStr(double num, wchar_t *str, size_t strSize) +{ + int i = (int)(num * (opt.NoFrac ? 10 : 100)); + int u = abs(i); + + int r = u % 10; + int w = u / 10 + (r >= 5); + + if (opt.NoFrac) + r = 0; + else { + r = w % 10; + w /= 10; + } + + if (i < 0 && (w || r)) w = -w; + if (r) + mir_snwprintf(str, strSize, L"%i.%i", w, r); + else + mir_snwprintf(str, strSize, L"%i", w); +} + +//============ UNIT CONVERSIONS ============ + +// temperature conversion +// tempchar = the string containing the temperature value +// unit = the unit for temperature +// return value = the converted temperature with degree sign and unit; if fails, return N/A +void GetTemp(wchar_t *tempchar, wchar_t *unit, wchar_t* str) +{ + // unit can be C, F + double temp; + wchar_t tstr[20]; + + TrimString(tempchar); + if (tempchar[0] == '-' && tempchar[1] == ' ') + memmove(&tempchar[1], &tempchar[2], sizeof(wchar_t)*(mir_wstrlen(&tempchar[2]) + 1)); + + // quit if the value obtained is N/A or not a number + if (!mir_wstrcmp(tempchar, NODATA) || !mir_wstrcmp(tempchar, L"N/A")) { + mir_wstrcpy(str, tempchar); + return; + } + if (!is_number(tempchar)) { + mir_wstrcpy(str, NODATA); + return; + } + + // convert the string to an integer + temp = _wtof(tempchar); + + // convert all to F first + if (!mir_wstrcmpi(unit, L"C")) temp = (temp * 9 / 5) + 32; + else if (!mir_wstrcmpi(unit, L"K")) temp = ((temp - 273.15) * 9 / 5) + 32; + + // convert to apporiate unit + switch (opt.tUnit) { + case 1: + // rounding + numToStr((temp - 32) / 9 * 5, tstr, _countof(tstr)); + if (opt.DoNotAppendUnit) + wcsncpy_s(str, MAX_DATA_LEN, tstr, _TRUNCATE); + else + mir_snwprintf(str, MAX_DATA_LEN, L"%s%sC", tstr, opt.DegreeSign); + break; + + case 2: + numToStr(temp, tstr, _countof(tstr)); + if (opt.DoNotAppendUnit) + wcsncpy_s(str, MAX_DATA_LEN, tstr, _TRUNCATE); + else + mir_snwprintf(str, MAX_DATA_LEN, L"%s%sF", tstr, opt.DegreeSign); + break; + } +} + +// temperature conversion +// tempchar = the string containing the pressure value +// unit = the unit for pressure +// return value = the converted pressure with unit; if fail, return the original string +void GetPressure(wchar_t *tempchar, wchar_t *unit, wchar_t* str) +{ + // unit can be kPa, hPa, mb, in, mm, torr + double tempunit = 0, output; + int intunit; + + // convert the string to a floating point number (always positive) + // if it end up with 0, then it's not a number, return the original string and quit + output = _wtof(tempchar); + if (output == 0) { + mir_wstrcpy(str, tempchar); + return; + } + + // convert all to mb first + if (!mir_wstrcmpi(unit, L"KPA")) + tempunit = (double)output * 10; + else if (!mir_wstrcmpi(unit, L"HPA")) + tempunit = (double)output; + else if (!mir_wstrcmpi(unit, L"MB")) + tempunit = (double)output; + else if (!mir_wstrcmpi(unit, L"IN")) + tempunit = (double)output * 33.86388; + else if (!mir_wstrcmpi(unit, L"MM")) + tempunit = (double)output * 1.33322; + else if (!mir_wstrcmpi(unit, L"TORR")) + tempunit = (double)output * 1.33322; + + // convert to apporiate unit + switch (opt.pUnit) { + case 1: + intunit = (int)(tempunit + 0.5); + mir_snwprintf(str, MAX_DATA_LEN, L"%i.%i %s", intunit / 10, intunit % 10, opt.DoNotAppendUnit ? L"" : TranslateT("kPa")); + break; + case 2: + intunit = (int)(tempunit + 0.5); + mir_snwprintf(str, MAX_DATA_LEN, L"%i %s", intunit, opt.DoNotAppendUnit ? L"" : TranslateT("mb")); + break; + case 3: + intunit = (int)((tempunit * 10 / 33.86388) + 0.5); + mir_snwprintf(str, MAX_DATA_LEN, L"%i.%i %s", intunit / 10, intunit % 10, opt.DoNotAppendUnit ? L"" : TranslateT("in")); + break; + case 4: + intunit = (int)((tempunit * 10 / 1.33322) + 0.5); + mir_snwprintf(str, MAX_DATA_LEN, L"%i.%i %s", intunit / 10, intunit % 10, opt.DoNotAppendUnit ? L"" : TranslateT("mm")); + break; + default: + mir_wstrcpy(str, tempchar); + break; + + } +} + +// speed conversion +// tempchar = the string containing the speed value +// unit = the unit for speed +// return value = the converted speed with unit; if fail, return _T("" +void GetSpeed(wchar_t *tempchar, wchar_t *unit, wchar_t *str) +{ + // unit can be km/h, mph, m/s, knots + double tempunit; + wchar_t tstr[20]; + + str[0] = 0; + + // convert the string into an integer (always positive) + // if the result is 0, then the string is not a number, return _T("" + tempunit = _wtof(tempchar); + if (tempunit == 0 && tempchar[0] != '0') + return; + + // convert all to m/s first + if (!mir_wstrcmpi(unit, L"KM/H")) + tempunit /= 3.6; + // else if ( !mir_wstrcmpi(unit, L"M/S") + // tempunit = tempunit; + else if (!mir_wstrcmpi(unit, L"MPH")) + tempunit *= 0.44704; + else if (!mir_wstrcmpi(unit, L"KNOTS")) + tempunit *= 0.514444; + + // convert to apporiate unit + switch (opt.wUnit) { + case 1: + numToStr(tempunit * 3.6, tstr, _countof(tstr)); + mir_snwprintf(str, MAX_DATA_LEN, L"%s %s", tstr, opt.DoNotAppendUnit ? L"" : TranslateT("km/h")); + break; + case 2: + numToStr(tempunit, tstr, _countof(tstr)); + mir_snwprintf(str, MAX_DATA_LEN, L"%s %s", tstr, opt.DoNotAppendUnit ? L"" : TranslateT("m/s")); + break; + case 3: + numToStr(tempunit / 0.44704, tstr, _countof(tstr)); + mir_snwprintf(str, MAX_DATA_LEN, L"%s %s", tstr, opt.DoNotAppendUnit ? L"" : TranslateT("mph")); + break; + case 4: + numToStr(tempunit / 0.514444, tstr, _countof(tstr)); + mir_snwprintf(str, MAX_DATA_LEN, L"%s %s", tstr, opt.DoNotAppendUnit ? L"" : TranslateT("knots")); + break; + } +} + +// distance conversion +// tempchar = the string containing the distance value +// unit = the unit for distance +// return value = the converted distance with unit; if fail, return original string +void GetDist(wchar_t *tempchar, wchar_t *unit, wchar_t *str) +{ + // unit can be km, miles + double tempunit = 0, output; + int intunit; + + // convert the string to a floating point number (always positive) + // if it end up with 0, then it's not a number, return the original string and quit + output = _wtof(tempchar); + if (output == 0) { + mir_wstrcpy(str, tempchar); + return; + } + + // convert all to km first + if (!mir_wstrcmpi(unit, L"KM")) + tempunit = (double)output; + else if (!mir_wstrcmpi(unit, L"MILES")) + tempunit = (double)output * 1.609; + + // convert to apporiate unit + switch (opt.vUnit) { + case 1: + intunit = (int)((tempunit * 10) + 0.5); + mir_snwprintf(str, MAX_DATA_LEN, L"%i.%i %s", intunit / 10, intunit % 10, opt.DoNotAppendUnit ? L"" : TranslateT("km")); + break; + case 2: + intunit = (int)((tempunit * 10 / 1.609) + 0.5); + mir_snwprintf(str, MAX_DATA_LEN, L"%i.%i %s", intunit / 10, intunit % 10, opt.DoNotAppendUnit ? L"" : TranslateT("miles")); + break; + default: + mir_wstrcpy(str, tempchar); + break; + } +} + +// elevation conversion +// tempchar = the string containing the elevation value +// unit = the unit for elevation +// return value = the converted elevation with unit; if fail, return original string +void GetElev(wchar_t *tempchar, wchar_t *unit, wchar_t *str) +{ + // unit can be ft, m + double tempunit = 0, output; + int intunit; + + // convert the string to a floating point number (always positive) + // if it end up with 0, then it's not a number, return the original string and quit + output = _wtof(tempchar); + if (output == 0) { + mir_wstrcpy(str, tempchar); + return; + } + + // convert all to m first + if (!mir_wstrcmpi(unit, L"M")) + tempunit = (double)output; + else if (!mir_wstrcmpi(unit, L"FT")) + tempunit = (double)output / 3.28; + + // convert to apporiate unit + switch (opt.eUnit) { + case 1: + intunit = (int)((tempunit * 10 * 3.28) + 0.5); + mir_snwprintf(str, MAX_DATA_LEN, L"%i.%i %s", intunit / 10, intunit % 10, opt.DoNotAppendUnit ? L"" : TranslateT("ft")); + break; + case 2: + intunit = (int)((tempunit * 10) + 0.5); + mir_snwprintf(str, MAX_DATA_LEN, L"%i.%i %s", intunit / 10, intunit % 10, opt.DoNotAppendUnit ? L"" : TranslateT("m")); + break; + default: + mir_wstrcpy(str, tempchar); + break; + } +} + +//============ CONDITION ICON ASSIGNMENT ============ + +// assign the contact icon (status) from the condition string +// the description may be different between different sources +// cond = the string for weather condition +// return value = status for the icon (ONLINE, OFFLINE, etc) + +static const wchar_t *statusStr[10] = { L"Lightning", L"Fog", L"Snow Shower", L"Snow", L"Rain Shower", L"Rain", L"Partly Cloudy", L"Cloudy", L"Sunny", L"N/A" }; +static const WORD statusValue[10] = { LIGHT, FOG, SSHOWER, SNOW, RSHOWER, RAIN, PCLOUDY, CLOUDY, SUNNY, NA }; + +WORD GetIcon(const wchar_t* cond, WIDATA *Data) +{ + // set the icon using ini + for (int i = 0; i < 10; i++) + if (IsContainedInCondList(cond, &Data->CondList[i])) + return statusValue[i]; + + // internal detection + if ( + wcsstr(cond, L"mainy sunny") != nullptr || + wcsstr(cond, L"mainy clear") != nullptr || + wcsstr(cond, L"partly cloudy") != nullptr || + wcsstr(cond, L"mostly") != nullptr || + wcsstr(cond, L"clouds") != nullptr) { + return PCLOUDY; + } + else if ( + wcsstr(cond, L"sunny") != nullptr || + wcsstr(cond, L"clear") != nullptr || + wcsstr(cond, L"fair") != nullptr) { + return SUNNY; + } + else if ( + wcsstr(cond, L"thunder") != nullptr || + wcsstr(cond, L"t-storm") != nullptr) { + return LIGHT; + } + else if ( + wcsstr(cond, L"cloud") != nullptr || + wcsstr(cond, L"overcast") != nullptr) { + return CLOUDY; + } + else if ( + wcsstr(cond, L"fog") != nullptr || + wcsstr(cond, L"mist") != nullptr || + wcsstr(cond, L"smoke") != nullptr || + wcsstr(cond, L"sand") != nullptr || + wcsstr(cond, L"dust") != nullptr || + wcsstr(cond, L"haze") != nullptr) { + return FOG; + } + else if ( + (wcsstr(cond, L"shower") != nullptr && wcsstr(cond, L"snow") != nullptr) || + wcsstr(cond, L"flurries") != nullptr) { + return SSHOWER; + } + else if ( + wcsstr(cond, L"rain shower") != nullptr || + wcsstr(cond, L"shower") != nullptr) { + return RSHOWER; + } + else if ( + wcsstr(cond, L"snow") != nullptr || + wcsstr(cond, L"ice") != nullptr || + wcsstr(cond, L"freezing") != nullptr || + wcsstr(cond, L"wintry") != nullptr) { + return SNOW; + } + else if ( + wcsstr(cond, L"drizzle") != nullptr || + wcsstr(cond, L"rain") != nullptr) { + return RAIN; + } + + // set the icon using langpack + for (int i = 0; i < 9; i++) { + wchar_t LangPackStr[64], LangPackStr1[128]; + int j = 0; + do { + j++; + // using the format _T("# Weather <condition name> <counter> #" + mir_snwprintf(LangPackStr, L"# Weather %s %i #", statusStr[i], j); + wcsncpy_s(LangPackStr1, TranslateW(LangPackStr), _TRUNCATE); + CharLowerBuff(LangPackStr1, (DWORD)mir_wstrlen(LangPackStr1)); + if (wcsstr(cond, LangPackStr1) != nullptr) + return statusValue[i]; + // loop until the translation string exists (ie, the translated string is differ from original) + } while (mir_wstrcmp(TranslateW(LangPackStr), LangPackStr)); + } + + return NA; +} + +//============ STRING CONVERSIONS ============ +// +// this function convert the string to the format with 1 upper case followed by lower case char +void CaseConv(wchar_t *str) +{ + bool nextUp = true; + + CharLowerBuffW(str, (DWORD)mir_wstrlen(str)); + for (wchar_t *pstr = str; *pstr; pstr++) { + if (*pstr == ' ' || *pstr == '-') + nextUp = true; + else if (nextUp) { + CharUpperBuffW(pstr, 1); + nextUp = false; + } + } +} + +// the next 2 functions are copied from miranda source +// str = the string to modify +// +void TrimString(char *str) +{ + size_t len, start; + + len = mir_strlen(str); + while (len && (unsigned char)str[len - 1] <= ' ') str[--len] = 0; + for (start = 0; (unsigned char)str[start] <= ' ' && str[start]; start++); + memmove(str, str + start, len - start + 1); +} + +void TrimString(WCHAR *str) +{ + size_t len, start; + + len = mir_wstrlen(str); + while (len && (unsigned char)str[len - 1] <= ' ') str[--len] = 0; + for (start = 0; (unsigned char)str[start] <= ' ' && str[start]; start++); + memmove(str, str + start, (len - start + 1)*sizeof(WCHAR)); +} + +// convert \t to tab and \n to linefeed +void ConvertBackslashes(char *str) +{ + for (char *pstr = str; *pstr; pstr = CharNextA(pstr)) { + if (*pstr == '\\') { + switch (pstr[1]) { + case 'n': *pstr = '\n'; break; + case 't': *pstr = '\t'; break; + default: *pstr = pstr[1]; break; + } + memmove(pstr + 1, pstr + 2, mir_strlen(pstr + 2) + 1); + } + } +} + +// replace spaces with _T("%20" +// dis = original string +// return value = the modified string with space -> _T("%20" +char *GetSearchStr(char *dis) +{ + char *pstr = dis; + size_t len = mir_strlen(dis); + while (*pstr != 0) { + if (*pstr == ' ') { + memmove(pstr + 3, pstr + 1, len); + memcpy(pstr, L"%20", 3); + pstr += 2; + } + pstr++; + len--; + } + return dis; +} + +//============ ICON ASSIGNMENT ============ +// +// make display and history strings +// w = WEATHERINFO data to be parsed +// dis = the string to parse +// return value = the parsed string +wchar_t* GetDisplay(WEATHERINFO *w, const wchar_t *dis, wchar_t* str) +{ + wchar_t lpzDate[32], chr; + char name[256], temp[2]; + DBVARIANT dbv; + size_t i; + + // Clear the string + str[0] = 0; + + // looking character by character + for (i = 0; i < mir_wstrlen(dis); i++) { + // for the escape characters + if (dis[i] == '\\') { + i++; + chr = dis[i]; + switch (chr) { + case '%': mir_wstrcat(str, L"%"); break; + case 't': mir_wstrcat(str, L"\t"); break; + case 'n': mir_wstrcat(str, L"\r\n"); break; + case '\\': mir_wstrcat(str, L"\\"); break; + } + } + + // for the % varaibles + else if (dis[i] == '%') { + i++; + chr = dis[i]; + // turn capitalized characters to small case + if (chr < 'a' && chr != '[' && chr != '%') chr = (char)((int)chr + 32); + switch (chr) { + case 'c': mir_wstrcat(str, w->cond); break; + case 'd': // get the current date + GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, nullptr, nullptr, lpzDate, _countof(lpzDate)); + mir_wstrcat(str, lpzDate); break; + case 'e': mir_wstrcat(str, w->dewpoint); break; + case 'f': mir_wstrcat(str, w->feel); break; + case 'h': mir_wstrcat(str, w->high); break; + case 'i': mir_wstrcat(str, w->winddir); break; + case 'l': mir_wstrcat(str, w->low); break; + case 'm': mir_wstrcat(str, w->humid); break; + case 'n': mir_wstrcat(str, w->city); break; + case 'p': mir_wstrcat(str, w->pressure); break; + case 'r': mir_wstrcat(str, w->sunrise); break; + case 's': mir_wstrcat(str, w->id); break; + case 't': mir_wstrcat(str, w->temp); break; + case 'u': + if (mir_wstrcmp(w->update, NODATA)) mir_wstrcat(str, w->update); + else mir_wstrcat(str, TranslateT("<unknown time>")); + break; + case 'v': mir_wstrcat(str, w->vis); break; + case 'w': mir_wstrcat(str, w->wind); break; + case 'y': mir_wstrcat(str, w->sunset); break; + case '%': mir_wstrcat(str, L"%"); break; + case '[': // custom variables + i++; + name[0] = 0; + // read the entire variable name + while (dis[i] != ']' && i < mir_wstrlen(dis)) { + mir_snprintf(temp, "%c", dis[i++]); + mir_strcat(name, temp); + } + // access the database to get its value + if (!db_get_ws(w->hContact, WEATHERCONDITION, name, &dbv)) { + if (dbv.pwszVal != TranslateW(NODATA) && dbv.pwszVal != TranslateT("<Error>")) + mir_wstrcat(str, dbv.pwszVal); + db_free(&dbv); + } + break; + } + } + // if the character is not a variable, write the original character to the new string + else { + mir_snwprintf(lpzDate, L"%c", dis[i]); + mir_wstrcat(str, lpzDate); + } + } + + return str; +} + +wchar_t svcReturnText[MAX_TEXT_SIZE]; +INT_PTR GetDisplaySvcFunc(WPARAM wParam, LPARAM lParam) +{ + WEATHERINFO winfo = LoadWeatherInfo(wParam); + return (INT_PTR)GetDisplay(&winfo, (wchar_t*)lParam, svcReturnText); +} + +//============ ID MANAGEMENT ============ +// +// get service data module internal name +// mod/id <- the mod part +// pszID = original 2-part id, return the service internal name +void GetSvc(wchar_t *pszID) +{ + wchar_t *chop = wcsstr(pszID, L"/"); + if (chop != nullptr) *chop = '\0'; + else pszID[0] = 0; +} + +// get the id use for update without the service internal name +// mod/id <- the id part +// pszID = original 2-part id, return the single part id +void GetID(wchar_t *pszID) +{ + wchar_t *chop = wcsstr(pszID, L"/"); + if (chop != nullptr) mir_wstrcpy(pszID, chop + 1); + else pszID[0] = 0; +} + +//============ WEATHER ERROR CODE ============ +// +// Get the text when an error code is specified +// code = the error code obtained when updating weather +// str = the string for the error +// +wchar_t *GetError(int code) +{ + wchar_t *str, str2[100]; + switch (code) { + case 10: str = E10; break; + case 11: str = E11; break; + case 12: str = E12; break; + case 20: str = E20; break; + case 30: str = E30; break; + case 40: str = E40; break; + case 42: str = E42; break; + case 43: str = E43; break; + case 99: str = E99; break; + case 204: str = E204; break; + case 301: str = E301; break; + case 305: str = E305; break; + case 307: str = E307; break; + case 400: str = E400; break; + case 401: str = E401; break; + case 402: str = E402; break; + case 403: str = E403; break; + case 404: str = E404; break; + case 405: str = E405; break; + case 407: str = E407; break; + case 410: str = E410; break; + case 500: str = E500; break; + case 502: str = E502; break; + case 503: str = E503; break; + case 504: str = E504; break; + default: + mir_snwprintf(str2, TranslateT("HTTP Error %i"), code); + str = str2; + break; + } + return mir_wstrdup(str); +} diff --git a/protocols/Weather/src/weather_data.cpp b/protocols/Weather/src/weather_data.cpp new file mode 100644 index 0000000000..e36ae9dc5b --- /dev/null +++ b/protocols/Weather/src/weather_data.cpp @@ -0,0 +1,439 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* +This file contain the source related loading, obtaining, and +saving individual weather data for a weather contact. +*/ + +#include "stdafx.h" + +//============ LOAD WEATHER INFO FROM A CONTACT ============ +// get station ID from DB +// hContact = the current contact handle +// return value = the string for station ID +// +void GetStationID(MCONTACT hContact, wchar_t* id, int idlen) +{ + // accessing the database + if (db_get_wstatic(hContact, MODULENAME, "ID", id, idlen)) + id[0] = 0; +} + +// initialize weather info by loading values from database +// hContact = current contact handle +// return value = the current weather information in WEATHERINFO struct +WEATHERINFO LoadWeatherInfo(MCONTACT hContact) +{ + // obtaining values from the DB + // assuming station ID must exist at all time, but others does not have to + // if the string is not found in database, a value of "N/A" is stored in the field + WEATHERINFO winfo; + winfo.hContact = hContact; + GetStationID(hContact, winfo.id, _countof(winfo.id)); + + if (db_get_wstatic(hContact, MODULENAME, "Nick", winfo.city, _countof(winfo.city))) + wcsncpy(winfo.city, NODATA, _countof(winfo.city) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Update", winfo.update, _countof(winfo.update))) + wcsncpy(winfo.update, NODATA, _countof(winfo.update) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Condition", winfo.cond, _countof(winfo.cond))) + wcsncpy(winfo.cond, NODATA, _countof(winfo.cond) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Temperature", winfo.temp, _countof(winfo.temp))) + wcsncpy(winfo.temp, NODATA, _countof(winfo.temp) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "High", winfo.high, _countof(winfo.high))) + wcsncpy(winfo.high, NODATA, _countof(winfo.high) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Low", winfo.low, _countof(winfo.low))) + wcsncpy(winfo.low, NODATA, _countof(winfo.low) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Sunset", winfo.sunset, _countof(winfo.sunset))) + wcsncpy(winfo.sunset, NODATA, _countof(winfo.sunset) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Sunrise", winfo.sunrise, _countof(winfo.sunrise))) + wcsncpy(winfo.sunrise, NODATA, _countof(winfo.sunrise) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Wind Speed", winfo.wind, _countof(winfo.wind))) + wcsncpy(winfo.wind, NODATA, _countof(winfo.wind) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Wind Direction", winfo.winddir, _countof(winfo.winddir))) + wcsncpy(winfo.winddir, NODATA, _countof(winfo.winddir) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Dewpoint", winfo.dewpoint, _countof(winfo.dewpoint))) + wcsncpy(winfo.dewpoint, NODATA, _countof(winfo.dewpoint) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Pressure", winfo.pressure, _countof(winfo.pressure))) + wcsncpy(winfo.pressure, NODATA, _countof(winfo.pressure) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Visibility", winfo.vis, _countof(winfo.vis))) + wcsncpy(winfo.vis, NODATA, _countof(winfo.vis) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Humidity", winfo.humid, _countof(winfo.humid))) + wcsncpy(winfo.humid, NODATA, _countof(winfo.humid) - 1); + if (db_get_wstatic(hContact, WEATHERCONDITION, "Feel", winfo.feel, _countof(winfo.feel))) + wcsncpy(winfo.feel, NODATA, _countof(winfo.feel) - 1); + + winfo.status = g_plugin.getWord(hContact, "StatusIcon", ID_STATUS_OFFLINE); + return winfo; +} + +// getting weather setting from database +// return 0 on success +int DBGetData(MCONTACT hContact, char *setting, DBVARIANT *dbv) +{ + if (db_get_ws(hContact, WEATHERCONDITION, setting, dbv)) { + size_t len = mir_strlen(setting) + 1; + char *set = (char*)alloca(len + 1); + *set = '#'; + memcpy(set + 1, setting, len); + + if (db_get_ws(hContact, WEATHERCONDITION, set, dbv)) + return 1; + } + return 0; +} + + +//============ ERASE OLD SETTINGS ============ +// +// erase all current weather information from database +// lastver = the last used version number in dword (using PLUGIN_MAKE_VERSION) +void EraseAllInfo() +{ + wchar_t str[255]; + int ContactCount = 0; + MCONTACT LastContact = NULL; + DBVARIANT dbv; + // loop through all contacts + for (auto &hContact : Contacts(MODULENAME)) { + g_plugin.setWord(hContact, "Status", ID_STATUS_OFFLINE); + g_plugin.setWord(hContact, "StatusIcon", ID_STATUS_OFFLINE); + db_unset(hContact, "CList", "MyHandle"); + // clear all data + if (g_plugin.getWString(hContact, "Nick", &dbv)) { + g_plugin.setWString(hContact, "Nick", TranslateT("<Enter city name here>")); + g_plugin.setString(hContact, "LastLog", "never"); + g_plugin.setString(hContact, "LastCondition", "None"); + g_plugin.setString(hContact, "LastTemperature", "None"); + } + else db_free(&dbv); + + DBDataManage(hContact, WDBM_REMOVE, 0, 0); + db_set_s(hContact, "UserInfo", "MyNotes", ""); + // reset update tag + g_plugin.setByte(hContact, "IsUpdated", FALSE); + // reset logging settings + if (!g_plugin.getWString(hContact, "Log", &dbv)) { + g_plugin.setByte(hContact, "File", (BYTE)(dbv.pwszVal[0] != 0)); + db_free(&dbv); + } + else g_plugin.setByte(hContact, "File", FALSE); + + // if no default station find, assign a new one + if (opt.Default[0] == 0) { + GetStationID(hContact, opt.Default, _countof(opt.Default)); + + opt.DefStn = hContact; + if (!g_plugin.getWString(hContact, "Nick", &dbv)) { + mir_snwprintf(str, TranslateT("%s is now the default weather station"), dbv.pwszVal); + db_free(&dbv); + MessageBox(nullptr, str, TranslateT("Weather Protocol"), MB_OK | MB_ICONINFORMATION); + } + } + // get the handle of the default station + if (opt.DefStn == NULL) { + if (!g_plugin.getWString(hContact, "ID", &dbv)) { + if (!mir_wstrcmp(dbv.pwszVal, opt.Default)) + opt.DefStn = hContact; + db_free(&dbv); + } + } + ContactCount++; // increment counter + LastContact = hContact; + } + + // if weather contact exists, set the status to online so it is ready for update + // if (ContactCount != 0) status = ONLINE; + // in case where the default station is missing + if (opt.DefStn == NULL && ContactCount != 0) { + if (!g_plugin.getWString(LastContact, "ID", &dbv)) { + wcsncpy(opt.Default, dbv.pwszVal, _countof(opt.Default) - 1); + db_free(&dbv); + } + opt.DefStn = LastContact; + if (!g_plugin.getWString(LastContact, "Nick", &dbv)) { + mir_snwprintf(str, TranslateT("%s is now the default weather station"), dbv.pwszVal); + db_free(&dbv); + MessageBox(nullptr, str, TranslateT("Weather Protocol"), MB_OK | MB_ICONINFORMATION); + } + } + // save option in case of default station changed + g_plugin.setWString("Default", opt.Default); +} + +void ConvertDataValue(WIDATAITEM *UpdateData, wchar_t *Data) +{ + wchar_t str[MAX_DATA_LEN]; + + // convert the unit + if (mir_wstrcmp(Data, TranslateT("<Error>")) && mir_wstrcmp(Data, NODATA) && mir_wstrcmp(Data, TranslateW(NODATA))) { + // temperature + if (!mir_wstrcmp(UpdateData->Name, L"Temperature") || !mir_wstrcmp(UpdateData->Name, L"High") || + !mir_wstrcmp(UpdateData->Name, L"Low") || !mir_wstrcmp(UpdateData->Name, L"Feel") || + !mir_wstrcmp(UpdateData->Name, L"Dewpoint") || + !mir_wstrcmpi(UpdateData->Unit, L"C") || !mir_wstrcmpi(UpdateData->Unit, L"F") || + !mir_wstrcmpi(UpdateData->Unit, L"K")) { + GetTemp(Data, UpdateData->Unit, str); + mir_wstrcpy(Data, str); + } + // pressure + else if (!mir_wstrcmp(UpdateData->Name, L"Pressure") || !mir_wstrcmpi(UpdateData->Unit, L"HPA") || + !mir_wstrcmpi(UpdateData->Unit, L"KPA") || !mir_wstrcmpi(UpdateData->Unit, L"MB") || + !mir_wstrcmpi(UpdateData->Unit, L"TORR") || !mir_wstrcmpi(UpdateData->Unit, L"IN") || + !mir_wstrcmpi(UpdateData->Unit, L"MM")) { + GetPressure(Data, UpdateData->Unit, str); + mir_wstrcpy(Data, str); + } + // speed + else if (!mir_wstrcmp(UpdateData->Name, L"Wind Speed") || !mir_wstrcmpi(UpdateData->Unit, L"KM/H") || + !mir_wstrcmpi(UpdateData->Unit, L"M/S") || !mir_wstrcmpi(UpdateData->Unit, L"MPH") || + !mir_wstrcmpi(UpdateData->Unit, L"KNOTS")) { + GetSpeed(Data, UpdateData->Unit, str); + mir_wstrcpy(Data, str); + } + // visibility + else if (!mir_wstrcmp(UpdateData->Name, L"Visibility") || !mir_wstrcmpi(UpdateData->Unit, L"KM") || + !mir_wstrcmpi(UpdateData->Unit, L"MILES")) { + GetDist(Data, UpdateData->Unit, str); + mir_wstrcpy(Data, str); + } + // elevation + else if (!mir_wstrcmp(UpdateData->Name, L"Elevation") || !mir_wstrcmpi(UpdateData->Unit, L"FT") || + !mir_wstrcmpi(UpdateData->Unit, L"M")) { + GetElev(Data, UpdateData->Unit, str); + mir_wstrcpy(Data, str); + } + // converting case for condition to the upper+lower format + else if (!mir_wstrcmpi(UpdateData->Unit, L"COND")) + CaseConv(Data); + // degree sign + else if (!mir_wstrcmpi(UpdateData->Unit, L"DEG")) { + if (!opt.DoNotAppendUnit) mir_wstrcat(Data, opt.DegreeSign); + } + // percent sign + else if (!mir_wstrcmpi(UpdateData->Unit, L"%")) { + if (!opt.DoNotAppendUnit) mir_wstrcat(Data, L"%"); + } + // truncating strings for day/month to 2 or 3 characters + else if (!mir_wstrcmpi(UpdateData->Unit, L"DAY") || !mir_wstrcmpi(UpdateData->Unit, L"MONTH")) + if (opt.dUnit > 1 && mir_wstrlen(Data) > opt.dUnit) + Data[opt.dUnit] = '\0'; + } +} + +//============ GET THE VALUE OF A DATAITEM ============ +// +// get the value of the data using the start, end strings +// UpdateData = the WIDATAITEM struct containing start, end, unit +// Data = the string containing weather data obtained from UpdateData +// global var. used: szInfo = the downloaded string +// +void GetDataValue(WIDATAITEM *UpdateData, wchar_t *Data, wchar_t** szData) +{ + wchar_t last = 0, current, *start, *end; + unsigned startloc = 0, endloc = 0, respos = 0; + BOOL tag = FALSE, symb = FALSE; + wchar_t *szInfo = *szData; + + Data[0] = 0; + // parse the data if available + if (UpdateData->Start[0] == 0 && UpdateData->End[0] == 0) return; + start = szInfo; + // the start string must be found + if (UpdateData->Start[0] != 0) { + start = wcsstr(szInfo, UpdateData->Start); + if (start != nullptr) { + // set the starting location for getting data + start += mir_wstrlen(UpdateData->Start); + szInfo = start; + } + } + + // the end string must be found too + if (UpdateData->End[0] != 0) + end = wcsstr(szInfo, UpdateData->End); + else + end = wcsstr(szInfo, L" "); + + if (end != nullptr) { + // set the ending location + startloc = 0; + endloc = end - szInfo; + end += mir_wstrlen(UpdateData->End); + last = '\n'; + } + + // ignore if not both of the string found - this prevent crashes + if (start != nullptr && end != nullptr) { + // begin reading the data from start location to end location + // remove all HTML tag in between, as well as leading space, ending space, + // multiple spaces, tabs, and return key + while (startloc < endloc) { + if (szInfo[startloc] == '<') tag = TRUE; + else if (szInfo[startloc] == '&' && + (szInfo[startloc + 1] == ';' || szInfo[startloc + 2] == ';' || szInfo[startloc + 3] == ';' || + szInfo[startloc + 4] == ';' || szInfo[startloc + 5] == ';' || szInfo[startloc + 6] == ';')) { + // ...but do NOT strip − + if ((endloc - startloc) > 7 && wcsncmp(szInfo + startloc, L"−", 7) == 0) { + Data[respos++] = '-'; + startloc += 7; + continue; + } + symb = TRUE; + } + else if (szInfo[startloc] == '>') tag = FALSE; + else if (szInfo[startloc] == ';') symb = FALSE; + else { + if (!tag && !symb) { + current = szInfo[startloc]; + if (current == '\n' || current == '\t' || current == ' ' || current == '\r') + current = ' '; + if (current != ' ' || last != ' ') { + if (last != '\n' && (respos != 0 || (respos == 0 && last != ' '))) + Data[respos++] = last; + last = current; + } + } + } + ++startloc; + // prevent crashes if the string go over maximun length -> generate an error + if (respos >= MAX_DATA_LEN) { + if (opt.ShowWarnings && UpdateData->Name[0] != 0 && mir_wstrcmp(UpdateData->Name, L"Ignore")) { + mir_snwprintf(Data, MAX_DATA_LEN, TranslateT("Error when obtaining data: %s"), UpdateData->Name); + WPShowMessage(Data, SM_WARNING); + } + wcsncpy(Data, TranslateT("<Error>"), MAX_DATA_LEN); + last = ' '; + respos = MAX_DATA_LEN - 1; + break; + } + } + + // get the last character + if (last != ' ') + Data[respos++] = last; + + // null terminate the string + Data[respos] = 0; + + // convert the unit + ConvertDataValue(UpdateData, Data); + + // remove the string before the data from szInfo + szInfo = end; + } + *szData = szInfo; +} + +//============ ALLOCATE SPACE AND COPY STRING ============ +// +// copy a string into a new memory location +// Data = the field the data is copied to +// Value = the original string, the string where data is copied from +void wSetData(char **Data, const char *Value) +{ + if (Value[0] != 0) + *Data = mir_strdup(Value); + else + *Data = ""; +} + +void wSetData(WCHAR **Data, const char *Value) +{ + if (Value[0] != 0) + *Data = mir_a2u(Value); + else + *Data = L""; +} + +void wSetData(WCHAR **Data, const WCHAR *Value) +{ + if (Value[0] != 0) + *Data = mir_wstrdup(Value); + else + *Data = L""; +} + +// A safer free function that free memory for a string +// Data = the string occuping the data to be freed +void wfree(char **Data) +{ + if (*Data && mir_strlen(*Data) > 0) + mir_free(*Data); + *Data = nullptr; +} + +void wfree(WCHAR **Data) +{ + if (*Data && mir_wstrlen(*Data) > 0) + mir_free(*Data); + *Data = nullptr; +} + +//============ MANAGE THE ITEMS STORED IN DB ============ +// get single setting that is found +// szSetting = the setting name +// lparam = the counter +int GetWeatherDataFromDB(const char *szSetting, void *lparam) +{ + LIST<char> *pList = (LIST<char>*)lparam; + pList->insert(mir_strdup(szSetting)); + return 0; +} + +// remove or display the weather information for a contact +// hContact - the contact in which the info is going to be removed +// +void DBDataManage(MCONTACT hContact, WORD Mode, WPARAM wParam, LPARAM) +{ + // get all the settings and store them in a temporary list + LIST<char> arSettings(10); + db_enum_settings(hContact, GetWeatherDataFromDB, WEATHERCONDITION, &arSettings); + + // begin deleting settings + auto T = arSettings.rev_iter(); + for (auto &str : T) { + ptrW wszText(db_get_wsa(hContact, WEATHERCONDITION, str)); + if (wszText == nullptr) + continue; + + switch (Mode) { + case WDBM_REMOVE: + db_unset(hContact, WEATHERCONDITION, str); + break; + + case WDBM_DETAILDISPLAY: + // skip the "WeatherInfo" variable + if (!mir_strcmp(str, "WeatherInfo") || !mir_strcmp(str, "Ignore") || str[0] == '#') + continue; + + HWND hList = GetDlgItem((HWND)wParam, IDC_DATALIST); + LV_ITEM lvi = { 0 }; + lvi.mask = LVIF_TEXT | LVIF_PARAM; + lvi.lParam = T.indexOf(&str); + lvi.pszText = TranslateW(_A2T(str)); + lvi.iItem = ListView_InsertItem(hList, &lvi); + lvi.pszText = wszText; + ListView_SetItemText(hList, lvi.iItem, 1, wszText); + break; + } + mir_free(str); + } +} diff --git a/protocols/Weather/src/weather_http.cpp b/protocols/Weather/src/weather_http.cpp new file mode 100644 index 0000000000..319663bfe7 --- /dev/null +++ b/protocols/Weather/src/weather_http.cpp @@ -0,0 +1,157 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* +This file contain the source related to downloading weather info +from the web using netlib +*/ + +#include "stdafx.h" + +HNETLIBUSER hNetlibUser; + +static int findHeader(const NETLIBHTTPREQUEST *nlhrReply, const char *hdr) +{ + for (int i = 0; i < nlhrReply->headersCount; i++) { + if (_stricmp(nlhrReply->headers[i].szName, hdr) == 0) { + return i; + } + } + return -1; +} + +//============ DOWNLOAD NEW WEATHER ============ +// +// function to download webpage from the internet +// szUrl = URL of the webpage to be retrieved +// return value = 0 for success, 1 or HTTP error code for failure +// global var used: szData, szInfo = containing the retrieved data +// +int InternetDownloadFile(char *szUrl, char *cookie, char *userAgent, wchar_t **szData) +{ + if (userAgent == nullptr || userAgent[0] == 0) + userAgent = NETLIB_USER_AGENT; + + NETLIBHTTPHEADER headers[5]; + headers[0].szName = "User-Agent"; + headers[0].szValue = userAgent; + headers[1].szName = "Cache-Control"; + headers[1].szValue = "no-cache"; + headers[2].szName = "Pragma"; + headers[2].szValue = "no-cache"; + headers[3].szName = "Connection"; + headers[3].szValue = "close"; + headers[4].szName = "Cookie"; + headers[4].szValue = cookie; + + // initialize the netlib request + NETLIBHTTPREQUEST nlhr = { sizeof(nlhr) }; + nlhr.requestType = REQUEST_GET; + nlhr.flags = NLHRF_DUMPASTEXT | NLHRF_HTTP11 | NLHRF_REDIRECT; + nlhr.szUrl = szUrl; + nlhr.headers = headers; + nlhr.headersCount = _countof(headers); + + if (cookie == nullptr || cookie[0] == 0) + --nlhr.headersCount; + + // download the page + NETLIBHTTPREQUEST *nlhrReply = Netlib_HttpTransaction(hNetlibUser, &nlhr); + if (nlhrReply == nullptr) { + // if the data does not downloaded successfully (ie. disconnected), then return 1000 as error code + *szData = (wchar_t*)mir_alloc(512); + // store the error code in szData + mir_wstrcpy(*szData, L"NetLib error occurred!!"); + return NLHRF_REDIRECT; + } + + // if the recieved code is 200 OK + int result; + if (nlhrReply->resultCode == 200) { + if (nlhrReply->dataLength) { + bool bIsUtf = false; + result = 0; + + // allocate memory and save the retrieved data + int i = findHeader(nlhrReply, "Content-Type"); + // look for Content-Type=utf-8 in header + if (i != -1 && strstr(_strlwr(nlhrReply->headers[i].szValue), "utf-8")) + bIsUtf = true; + else { + char *end = nlhrReply->pData; + while (end) { + // look for + // <meta http-equiv="Content-Type" content="utf-8" /> + char* beg = strstr(end, "<meta"); + if (beg) + { + end = strchr(beg, '>'); + if (end) + { + char tmp = *end; + *end = 0; + + char *method = strstr(beg, "http-equiv=\""); + if (method && _strnicmp(method + 12, "Content-Type", 12) == 0 && strstr(method, "utf-8")) { + bIsUtf = true; + *end = tmp; + break; + } + else *end = tmp; + } + } + else + break; + } + } + + wchar_t *retVal = nullptr; + if (bIsUtf) + retVal = mir_utf8decodeW(nlhrReply->pData); + if (retVal == nullptr) + retVal = mir_a2u(nlhrReply->pData); + *szData = retVal; + } + else result = DATA_EMPTY; + } + // return error code if the recieved code is neither 200 OK nor 302 Moved + else { + // store the error code in szData + CMStringW wszError(FORMAT, L"Error occured! HTTP Error: %i\n", nlhrReply->resultCode); + *szData = wszError.Detach(); + result = nlhrReply->resultCode; + } + + // make a copy of the retrieved data, then free the memory of the http reply + Netlib_FreeHttpRequest(nlhrReply); + return result; +} + +//============ NETLIB INITIALIZATION ============ +// +// initialize netlib support for weather protocol +void NetlibInit(void) +{ + NETLIBUSER nlu = {}; + nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_NOHTTPSOPTION | NUF_UNICODE; + nlu.szSettingsModule = MODULENAME; + nlu.szDescriptiveName.w = TranslateT("Weather HTTP connections"); + hNetlibUser = Netlib_RegisterUser(&nlu); +} diff --git a/protocols/Weather/src/weather_icons.cpp b/protocols/Weather/src/weather_icons.cpp new file mode 100644 index 0000000000..3e043d9280 --- /dev/null +++ b/protocols/Weather/src/weather_icons.cpp @@ -0,0 +1,64 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +#include "stdafx.h" + +HANDLE hIcoLibIconsChanged = nullptr; + +static IconItem iconList[] = +{ + { LPGEN("Protocol icon"), "main", IDI_ICON }, + { LPGEN("Update Disabled"), "disabled", IDI_DISABLED }, + { LPGEN("View Log"), "log", IDI_LOG }, + { LPGEN("Update with Clear"), "update2", IDI_UPDATE2 }, + { LPGEN("View Brief"), "brief", IDI_S }, + { LPGEN("View Complete"), "read", IDI_READ }, + { LPGEN("Weather Update"), "update", IDI_UPDATE }, + { LPGEN("Weather Map"), "map", IDI_MAP }, + { LPGEN("Popup"), "popup", IDI_POPUP }, + { LPGEN("No Popup"), "nopopup", IDI_NOPOPUP }, + { LPGEN("Edit Settings"), "edit", IDI_EDIT }, +}; + +void InitIcons(void) +{ + g_plugin.registerIcon(MODULENAME, iconList, MODULENAME); +} + +HICON LoadIconEx(const char* name, bool big) +{ + char szSettingName[100]; + mir_snprintf(szSettingName, "%s_%s", MODULENAME, name); + return IcoLib_GetIcon(szSettingName, big); +} + +HANDLE GetIconHandle(const char* name) +{ + for (auto &it : iconList) + if (mir_strcmp(it.szName, name) == 0) + return it.hIcolib; + + return nullptr; +} + +void ReleaseIconEx(HICON hIcon) +{ + IcoLib_ReleaseIcon(hIcon); +} diff --git a/protocols/Weather/src/weather_info.cpp b/protocols/Weather/src/weather_info.cpp new file mode 100644 index 0000000000..a2395d9f69 --- /dev/null +++ b/protocols/Weather/src/weather_info.cpp @@ -0,0 +1,240 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +/* +This file contain the source for displaying information for the +ini files, as well as function that are used for debug purpose +regrading the loading of ini contents +*/ + +#include "stdafx.h" + +//============ INI INFORMATION ============ + +// List INI Information for all loaded INI files +static void INIInfo(HWND hwndDlg) +{ + wchar_t str[16]; + size_t memused = 0; + + + HWND hIniList = GetDlgItem(hwndDlg, IDC_INFOLIST); + + ListView_DeleteAllItems(hIniList); + + LVITEM lvi = { 0 }; + lvi.mask = LVIF_TEXT; + lvi.iItem = 0; + for (WIDATALIST *Item = WIHead; Item != nullptr; Item = Item->next) { + // get the data for the ini file + lvi.iSubItem = 0; + lvi.pszText = Item->Data.InternalName; + ListView_InsertItem(hIniList, &lvi); + lvi.iSubItem = 1; + lvi.pszText = Item->Data.Author; + ListView_SetItem(hIniList, &lvi); + lvi.iSubItem = 2; + lvi.pszText = Item->Data.Version; + ListView_SetItem(hIniList, &lvi); + lvi.iSubItem = 3; + switch (Item->Data.InternalVer) { + case 1: lvi.pszText = L"1.0"; break; + case 2: lvi.pszText = L"1.1"; break; + case 3: lvi.pszText = L"1.1a"; break; + case 4: lvi.pszText = L"1.2"; break; + case 5: lvi.pszText = L"1.3"; break; + case 6: lvi.pszText = L"1.4"; break; + case 7: lvi.pszText = L"1.5"; break; + default: lvi.pszText = L""; break; + } + ListView_SetItem(hIniList, &lvi); + lvi.iSubItem = 4; + lvi.pszText = _ltow(Item->Data.UpdateDataCount, str, 10); + ListView_SetItem(hIniList, &lvi); + lvi.iSubItem = 5; + lvi.pszText = Item->Data.DisplayName; + ListView_SetItem(hIniList, &lvi); + lvi.iSubItem = 6; + lvi.pszText = Item->Data.ShortFileName; + ListView_SetItem(hIniList, &lvi); + + memused += Item->Data.MemUsed; + + ++lvi.iItem; + } + SetDlgItemText(hwndDlg, IDC_INICOUNT, _itow(lvi.iItem, str, 10)); + SetDlgItemText(hwndDlg, IDC_MEMUSED, _ltow((long)memused, str, 10)); +} + +static const struct tag_Columns +{ + const wchar_t *name; + unsigned size; +} +columns[] = +{ + { LPGENW("Name"), 70 }, + { LPGENW("Author"), 100 }, + { LPGENW("File Version"), 70 }, + { LPGENW("INI Version"), 70 }, + { LPGENW("Items"), 40 }, + { LPGENW("Display Name"), 200 }, + { LPGENW("File Name"), 150 }, +}; + + +INT_PTR CALLBACK DlgProcINIPage(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + { + HWND hIniList = GetDlgItem(hwndDlg, IDC_INFOLIST); + LVCOLUMN lvc = { 0 }; + + lvc.mask = LVCF_FMT | LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH; + lvc.fmt = LVCFMT_LEFT; + for (int i = 0; i < 7; ++i) { + lvc.iSubItem = i; + lvc.pszText = TranslateW(columns[i].name); + lvc.cx = columns[i].size; + ListView_InsertColumn(hIniList, i, &lvc); + } + INIInfo(hwndDlg); + } + + break; + + case WM_DESTROY: + break; + + case WM_COMMAND: + if (HIWORD(wParam) == BN_CLICKED && + LOWORD(wParam) == IDC_RELOADINI) { + DestroyWIList(); + LoadWIData(true); + INIInfo(hwndDlg); + } + break; + } + return 0; +} + + +// get the info of individual ini file +// pszSvc = the internal name of the service to get the data +void GetINIInfo(wchar_t *pszSvc) +{ + wchar_t str2[2048]; + WIDATA *sData = GetWIData(pszSvc); + // if the service does not exist among the loaded INI's + if (sData == nullptr) { + mir_snwprintf(str2, TranslateT("The corresponding INI file for \"%s\" is not found."), pszSvc); + MessageBox(nullptr, str2, TranslateT("Weather INI information"), MB_OK | MB_ICONINFORMATION); + } + // if exist, get the information + else { + mir_snwprintf(str2, TranslateT("Weather INI information for \"%s\":"), pszSvc); + mir_wstrncat(str2, L"\n\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, TranslateT("Name:"), _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\t\t", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, sData->DisplayName, _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, TranslateT("Internal Name:"), _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\t", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, sData->InternalName, _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, TranslateT("Author:"), _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\t\t", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, sData->Author, _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, TranslateT("Version:"), _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\t\t", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, sData->Version, _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, TranslateT("INI Version:"), _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\t", _countof(str2) - mir_wstrlen(str2)); + switch (sData->InternalVer) { + case 1: mir_wstrncat(str2, L"1.0", _countof(str2) - mir_wstrlen(str2)); break; + case 2: mir_wstrncat(str2, L"1.1", _countof(str2) - mir_wstrlen(str2)); break; + case 3: mir_wstrncat(str2, L"1.1a", _countof(str2) - mir_wstrlen(str2)); break; + case 4: mir_wstrncat(str2, L"1.2", _countof(str2) - mir_wstrlen(str2)); break; + case 5: mir_wstrncat(str2, L"1.3", _countof(str2) - mir_wstrlen(str2)); break; + case 6: mir_wstrncat(str2, L"1.4", _countof(str2) - mir_wstrlen(str2)); break; + case 7: mir_wstrncat(str2, L"1.5", _countof(str2) - mir_wstrlen(str2)); break; + } + mir_wstrncat(str2, L"\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, TranslateT("File Name:"), _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\t", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, sData->ShortFileName, _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, TranslateT("Item Count:"), _countof(str2) - mir_wstrlen(str2)); + mir_snwprintf(str2, L"%s\t%i\n", str2, sData->UpdateDataCount); + mir_wstrncat(str2, TranslateT("Memory Used:"), _countof(str2) - mir_wstrlen(str2)); + mir_snwprintf(str2, L"%s\t%i ", str2, sData->MemUsed); + mir_wstrncat(str2, TranslateT("bytes"), _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\n\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, TranslateT("Description:"), _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, L"\n", _countof(str2) - mir_wstrlen(str2)); + mir_wstrncat(str2, sData->Description, _countof(str2) - mir_wstrlen(str2)); + + // display the message box and quit + MessageBox(nullptr, str2, TranslateT("Weather INI information"), MB_OK | MB_ICONINFORMATION); + } +} + +//============ DISPLAY A LIST FOR CUSTOM VARIABLES ============ +// +// a message box for displaying the list of custom variables +// can be found when click on "More" in text option dialog +void MoreVarList(void) +{ + wchar_t str[10240], tempstr[1024]; + + // heading + wcsncpy(str, VARS_LIST, _countof(str) - 1); + mir_wstrncat(str, L"\n\n", _countof(str) - mir_wstrlen(str)); + // loop through all weather services to find custom variables + for (WIDATALIST *Item = WIHead; Item != nullptr; Item = Item->next) { + // loop through all update items in a service + for (WIDATAITEMLIST *WItem = Item->Data.UpdateData; WItem != nullptr; WItem = WItem->Next) { + // the custom variable is defined as "%[<variable name>]" + // ignore the "hi" item and hidden items + if (mir_wstrcmp(WItem->Item.Name, L"Ignore") && WItem->Item.Name[0] != '#') { + mir_snwprintf(tempstr, L"%c[%s]", '%', WItem->Item.Name); + wchar_t *find = wcsstr(str, tempstr); + // if the custom variable does not exist in the list, add it to the list + if (find == nullptr) { + mir_wstrncat(str, tempstr, _countof(str) - mir_wstrlen(str)); + mir_wstrncat(str, L", ", _countof(str) - mir_wstrlen(str)); + } + } + } + } + // remove the last comma in the list + wchar_t* find = wcsrchr(str, ','); + if (find != nullptr) + *find = '\0'; + + // display the list in a message box + MessageBox(nullptr, str, TranslateT("More Variables"), MB_OK | MB_ICONINFORMATION | MB_TOPMOST); +} + diff --git a/protocols/Weather/src/weather_ini.cpp b/protocols/Weather/src/weather_ini.cpp new file mode 100644 index 0000000000..000eab1bc9 --- /dev/null +++ b/protocols/Weather/src/weather_ini.cpp @@ -0,0 +1,592 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +/* +This file contain the source related to loading the reading the +weather ini files and store them into memory. Also containing +code for unloading and getting weather data from the ini settings. +*/ + +#include "stdafx.h" + +HWND hWndSetup; + +//============ DATA LIST (LINKED LIST) ============ +// +// add an item into weather service data list +// Data = the service data to be added to the list +static void WIListAdd(WIDATA Data) +{ + // create a new datalist item and point to the data + WIDATALIST *newItem = (WIDATALIST*)mir_alloc(sizeof(WIDATALIST)); + newItem->Data = Data; + // add to the linked list + newItem->next = nullptr; + if (WITail == nullptr) WIHead = newItem; + else WITail->next = newItem; + WITail = newItem; +} + +// get the service data (from loaded ini file) by internal name +// pszServ = internal name for the service +// return value = the matching WIDATA struct for pszServ, NULL if no match found +WIDATA* GetWIData(wchar_t *pszServ) +{ + // loop through the list to find matching internal name + for (WIDATALIST *Item = WIHead; Item != nullptr; Item = Item->next) + // if internal name found, return the data + if (mir_wstrcmp(Item->Data.InternalName, pszServ) == 0) + return &Item->Data; + + // return NULL when no match found + return nullptr; +} + +//============ DATA ITEM LIST (LINKED LIST) ============ +// +// add a new update item into the current list +void WIItemListAdd(WIDATAITEM *DataItem, WIDATA *Data) +{ + WIDATAITEMLIST *newItem = (WIDATAITEMLIST*)mir_alloc(sizeof(WIDATAITEMLIST)); + newItem->Item = *DataItem; + newItem->Next = nullptr; + if (Data->UpdateData == nullptr) Data->UpdateData = newItem; + else Data->UpdateDataTail->Next = newItem; + Data->UpdateDataTail = newItem; +} + +// reset the data item by using empty string +// Item = the item to set +// name = the string to store in the "name" field +void ResetDataItem(WIDATAITEM *Item, const wchar_t *name) +{ + Item->Name = mir_wstrdup(name); + Item->Start = L""; + Item->End = L""; + Item->Unit = L""; + Item->Url = ""; + Item->Break = L""; + Item->Type = 0; +} + +// free the data item by using empty string +// Item = the item to free +void FreeDataItem(WIDATAITEM *Item) +{ + wfree(&Item->Name); + wfree(&Item->Start); + wfree(&Item->End); + wfree(&Item->Unit); + wfree(&Item->Url); + wfree(&Item->Break); +} + +//============ Condition Icon List ============ +// +// initiate icon assignmet list +void WICondListInit(WICONDLIST *List) +{ + List->Tail = nullptr; + List->Head = nullptr; +} + +// add a new update item into the current list +void WICondListAdd(char *str, WICONDLIST *List) +{ + WICONDITEM *newItem = (WICONDITEM*)mir_alloc(sizeof(WICONDITEM)); + wSetData(&newItem->Item, str); + CharLowerBuff(newItem->Item, (DWORD)mir_wstrlen(newItem->Item)); + newItem->Next = nullptr; + if (List->Tail == nullptr) List->Head = newItem; + else List->Tail->Next = newItem; + List->Tail = newItem; +} + +// check if the condition string matched for the assignment +bool IsContainedInCondList(const wchar_t *pszStr, WICONDLIST *List) +{ + // loop through the list to find matching internal name + for (WICONDITEM *Item = List->Head; Item != nullptr; Item = Item->Next) { + // if internal name found, return true indicating that the data is found + if (wcsstr(pszStr, Item->Item)) + return true; + + } + // return false when no match found + return false; +} + +// free the memory for icon assignment list +void DestroyCondList(WICONDLIST *List) +{ + // free the list one by one + for (WICONDITEM *temp = List->Head; temp != nullptr; temp = List->Head) { + List->Head = temp->Next; + wfree(&temp->Item); // free the data struct + mir_free(temp); + } + // make sure the entire list is clear + List->Tail = nullptr; +} + + +//============ WEATHER INI SETUP DIALOG ============ +// +static INT_PTR CALLBACK DlgProcSetup(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + // make the buttons flat + SendDlgItemMessage(hwndDlg, IDC_STEP1, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_STEP2, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_STEP3, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_STEP4, BUTTONSETASFLATBTN, TRUE, 0); + + // set icons + Window_SetIcon_IcoLib(hwndDlg, GetIconHandle("main")); + + WindowList_Add(hWindowList, hwndDlg); + ShowWindow(hwndDlg, SW_SHOW); + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_STEP1: + // update current data + Utils_OpenUrl("https://miranda-ng.org/"); + break; + + case IDC_STEP2: + { + wchar_t szPath[1024]; + GetModuleFileName(GetModuleHandle(nullptr), szPath, _countof(szPath)); + wchar_t *chop = wcsrchr(szPath, '\\'); + if (chop) { + *chop = '\0'; + mir_wstrncat(szPath, L"\\Plugins\\weather\\", _countof(szPath) - mir_wstrlen(szPath)); + if (_wmkdir(szPath) == 0) + ShellExecute((HWND)lParam, L"open", szPath, L"", L"", SW_SHOW); + } + break; + } + + case IDC_STEP3: + if (LoadWIData(false)) + MessageBox(nullptr, + TranslateT("All update data has been reloaded."), + TranslateT("Weather Protocol"), MB_OK | MB_ICONINFORMATION); + break; + + case IDC_STEP4: + WeatherAdd(0, 0); + __fallthrough; + + case IDCANCEL: + // close the info window + DestroyWindow(hwndDlg); + break; + } + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hwndDlg); + break; + } + return FALSE; +} + +// load the station data from a file +// pszFile = the file name + path for the ini file to be loaded +// pszShortFile = the file name of the ini file, but not including the path +// Data = the struct to load the ini content to, and return to previous function +static void LoadStationData(wchar_t *pszFile, wchar_t *pszShortFile, WIDATA *Data) +{ + WIDATAITEM DataItem; + char *Group, *Temp; + char *ValName, *Value; + + static const char *statusStr[10] = + { + "LIGHTNING", + "FOG", + "SNOW SHOWER", + "SNOW", + "RAIN SHOWER", + "RAIN", + "PARTLY CLOUDY", + "CLOUDY", + "SUNNY", + "N/A" + }; + + // clean up old stuff + memset(Data, 0, sizeof(*Data)); + Data->Enabled = FALSE; + + // open the ini file + FILE *pfile = _wfsopen(pszFile, L"rt", _SH_DENYWR); + if (pfile != nullptr) { + char Line[4096]; + fgets(Line, _countof(Line), pfile); + TrimString(Line); + + // make sure it is a valid weather protocol ini file + if (!mir_strcmp(Line, "[Weather 0.3.x Update Data]")) + Data->InternalVer = 1; + else if (!mir_strcmp(Line, "[Weather 0.3.x Update Data 1.1]")) + Data->InternalVer = 2; + else if (!mir_strcmp(Line, "[Weather 0.3.x Update Data 1.1a]")) + Data->InternalVer = 3; + else if (!mir_strcmp(Line, "[Weather 0.3.x Update Data 1.2]")) + Data->InternalVer = 4; + else if (!mir_strcmp(Line, "[Weather 0.3.x Update Data 1.3]")) + Data->InternalVer = 5; + else if (!mir_strcmp(Line, "[Weather 0.3.x Update Data 1.4]")) + Data->InternalVer = 6; + else if (!mir_strcmp(Line, "[Weather 0.3.x Update Data 1.5]")) + Data->InternalVer = 7; + else { + wchar_t str[4096]; + mir_snwprintf(str, TranslateT("Invalid ini format for: %s"), pszFile); + MessageBox(nullptr, str, TranslateT("Weather Protocol"), MB_OK | MB_ICONERROR); + fclose(pfile); + return; + } + + // initialize all data fields + Group = ""; + + Data->DisplayName = L""; + Data->InternalName = L""; + Data->Description = L""; + Data->Author = L""; + Data->Version = L""; + Data->DefaultURL = ""; + Data->DefaultMap = L""; + Data->UpdateURL = ""; + Data->UpdateURL2 = ""; + Data->UpdateURL3 = ""; + Data->UpdateURL4 = ""; + Data->Cookie = ""; + Data->UserAgent = ""; + Data->IDSearch.SearchURL = ""; + Data->IDSearch.NotFoundStr = L""; + Data->NameSearch.SearchURL = ""; + Data->NameSearch.NotFoundStr = L""; + Data->NameSearch.SingleStr = L""; + Data->NameSearch.Single.First = L""; + Data->NameSearch.Multiple.First = L""; + Data->IDSearch.Available = FALSE; + Data->NameSearch.Single.Available = FALSE; + Data->NameSearch.Multiple.Available = FALSE; + wSetData(&Data->FileName, pszFile); + wSetData(&Data->ShortFileName, pszShortFile); + + ResetDataItem(&Data->IDSearch.Name, L"ID Search - Station Name"); + ResetDataItem(&Data->NameSearch.Single.Name, L"Name Search Single Result - Station Name"); + ResetDataItem(&Data->NameSearch.Single.ID, L"Name Search Single Result - Station ID"); + ResetDataItem(&Data->NameSearch.Multiple.Name, L"Name Search Multiple Result - Station Name"); + ResetDataItem(&Data->NameSearch.Multiple.ID, L"Name Search Multiple Result - Station ID"); + + DataItem.Name = L""; + DataItem.Start = L""; + DataItem.End = L""; + DataItem.Unit = L""; + DataItem.Url = ""; + DataItem.Break = L""; + DataItem.Type = 0; + + Temp = ""; + + // initialize the linked list for update items + Data->UpdateDataCount = 0; + Data->MemUsed = sizeof(WIDATA) + sizeof(WIDATALIST) + (mir_wstrlen(pszShortFile) + mir_wstrlen(pszFile) + 20)*sizeof(wchar_t); + Data->UpdateData = nullptr; + Data->UpdateDataTail = nullptr; + + // initialize the icon assignment list + for (int i = 0; i < 10; i++) + WICondListInit(&Data->CondList[i]); + + while (!feof(pfile)) { + // determine current tag + + if (fgets(Line, _countof(Line), pfile) == nullptr) + break; + TrimString(Line); + + // if the line is a group header/footer + if (Line[0] == '[') { + char *chop = strchr(Line + 1, ']'); + if (chop == nullptr) + continue; + + if (Line[1] != '/') { // if it is not a footer (for old ini) + // save the group name + Temp = (char *)mir_alloc(mir_strlen(Line) + 10); + strncpy(Temp, Line + 1, chop - Line - 1); + Temp[chop - Line - 1] = 0; + wfree(&Group); + wSetData(&Group, Temp); + // see if it is a update item, if it is, add a new item to the linked list + // if (_stricmp(Group, "HEADER") && _stricmp(Group, "DEFAULT") && _stricmp(Group, "ID SEARCH") && + // strcmpi(Group, "NAME SEARCH")) + // wSetData(&DataItem.Name, Group); + if (_stricmp(Group, "HEADER") && _stricmp(Group, "DEFAULT") && _stricmp(Group, "ID SEARCH") && + _stricmp(Group, "NAME SEARCH") && _stricmp(Group, "ICONS")) { + wSetData(&DataItem.Name, Temp); + DataItem.Type = WID_NORMAL; + WIItemListAdd(&DataItem, Data); + Data->UpdateDataCount++; + } + mir_free(Temp); + } + else { + wfree(&Group); + wSetData(&Group, ""); + } + } + // ignore comments and all lines without an '=' + Value = strstr(Line, "="); + if (Value == nullptr) continue; + + // get the string before '=' (ValName) and after '=' (Value) + ValName = (char *)mir_alloc(mir_strlen(Line) + 1); + strncpy(ValName, Line, Value - Line); + ValName[Value - Line] = 0; + Value++; + ConvertBackslashes(Value); + // store the value for each string + if (!_stricmp(Group, "HEADER")) { + if (!_stricmp(ValName, "NAME")) wSetData(&Data->DisplayName, Value); + else if (!_stricmp(ValName, "INTERNAL NAME")) wSetData(&Data->InternalName, Value); + else if (!_stricmp(ValName, "DESCRIPTION")) wSetData(&Data->Description, Value); + else if (!_stricmp(ValName, "AUTHOR")) wSetData(&Data->Author, Value); + else if (!_stricmp(ValName, "VERSION")) wSetData(&Data->Version, Value); + } + else if (!_stricmp(Group, "DEFAULT")) { + if (!_stricmp(ValName, "DEFAULT URL")) wSetData(&Data->DefaultURL, Value); + else if (!_stricmp(ValName, "DEFAULT MAP")) wSetData(&Data->DefaultMap, Value); + else if (!_stricmp(ValName, "UPDATE URL")) wSetData(&Data->UpdateURL, Value); + else if (!_stricmp(ValName, "UPDATE URL2")) wSetData(&Data->UpdateURL2, Value); + else if (!_stricmp(ValName, "UPDATE URL3")) wSetData(&Data->UpdateURL3, Value); + else if (!_stricmp(ValName, "UPDATE URL4")) wSetData(&Data->UpdateURL4, Value); + else if (!_stricmp(ValName, "COOKIE")) wSetData(&Data->Cookie, Value); + else if (!_stricmp(ValName, "USERAGENT")) wSetData(&Data->UserAgent, Value); + } + else if (!_stricmp(Group, "ID SEARCH")) { + if (!_stricmp(ValName, "AVAILABLE")) { + if (!_stricmp(Value, "TRUE")) Data->IDSearch.Available = TRUE; + else Data->IDSearch.Available = FALSE; + } + else if (!_stricmp(ValName, "SEARCH URL")) wSetData(&Data->IDSearch.SearchURL, Value); + else if (!_stricmp(ValName, "NOT FOUND STR")) wSetData(&Data->IDSearch.NotFoundStr, Value); + else if (!_stricmp(ValName, "NAME START")) wSetData(&Data->IDSearch.Name.Start, Value); + else if (!_stricmp(ValName, "NAME END")) wSetData(&Data->IDSearch.Name.End, Value); + } + else if (!_stricmp(Group, "NAME SEARCH")) { + if (!_stricmp(ValName, "SINGLE RESULT")) { + if (!_stricmp(Value, "TRUE")) Data->NameSearch.Single.Available = TRUE; + else Data->NameSearch.Single.Available = FALSE; + } + else if (!_stricmp(ValName, "MULTIPLE RESULT")) { + if (!_stricmp(Value, "TRUE")) Data->NameSearch.Multiple.Available = TRUE; + else Data->NameSearch.Multiple.Available = FALSE; + } + else if (!_stricmp(ValName, "SEARCH URL")) wSetData(&Data->NameSearch.SearchURL, Value); + else if (!_stricmp(ValName, "NOT FOUND STR")) wSetData(&Data->NameSearch.NotFoundStr, Value); + else if (!_stricmp(ValName, "SINGLE RESULT STR")) wSetData(&Data->NameSearch.SingleStr, Value); + else if (!_stricmp(ValName, "SINGLE FIRST")) wSetData(&Data->NameSearch.Single.First, Value); + else if (!_stricmp(ValName, "SINGLE NAME START"))wSetData(&Data->NameSearch.Single.Name.Start, Value); + else if (!_stricmp(ValName, "SINGLE NAME END")) wSetData(&Data->NameSearch.Single.Name.End, Value); + else if (!_stricmp(ValName, "SINGLE ID START")) wSetData(&Data->NameSearch.Single.ID.Start, Value); + else if (!_stricmp(ValName, "SINGLE ID END")) wSetData(&Data->NameSearch.Single.ID.End, Value); + else if (!_stricmp(ValName, "MULT FIRST")) wSetData(&Data->NameSearch.Multiple.First, Value); + else if (!_stricmp(ValName, "MULT NAME START")) wSetData(&Data->NameSearch.Multiple.Name.Start, Value); + else if (!_stricmp(ValName, "MULT NAME END")) wSetData(&Data->NameSearch.Multiple.Name.End, Value); + else if (!_stricmp(ValName, "MULT ID START")) wSetData(&Data->NameSearch.Multiple.ID.Start, Value); + else if (!_stricmp(ValName, "MULT ID END")) wSetData(&Data->NameSearch.Multiple.ID.End, Value); + } + else if (!_stricmp(Group, "ICONS")) { + for (int i = 0; i < 10; i++) { + if (!_stricmp(ValName, statusStr[i])) { + WICondListAdd(Value, &Data->CondList[i]); + break; + } + } + } + else if (Data->UpdateDataCount != 0) { + if (!_stricmp(ValName, "START")) wSetData(&Data->UpdateDataTail->Item.Start, Value); + else if (!_stricmp(ValName, "SOURCE")) wSetData(&Data->UpdateDataTail->Item.Start, Value); + else if (!_stricmp(ValName, "END")) wSetData(&Data->UpdateDataTail->Item.End, Value); + else if (!_stricmp(ValName, "UNIT")) wSetData(&Data->UpdateDataTail->Item.Unit, Value); + else if (!_stricmp(ValName, "URL")) wSetData(&Data->UpdateDataTail->Item.Url, Value); + else if (!_stricmp(ValName, "HIDDEN")) { + if (!_stricmp(Value, "TRUE")) { + wchar_t *nm = Data->UpdateDataTail->Item.Name; + size_t len = mir_wstrlen(nm) + 1; + + Data->UpdateDataTail->Item.Name = nm = (wchar_t*)mir_realloc(nm, sizeof(wchar_t)*(len + 3)); + memmove(nm + 1, nm, len*sizeof(wchar_t)); + *nm = '#'; + } + } + else if (!_stricmp(ValName, "SET DATA")) { + Data->UpdateDataTail->Item.Type = WID_SET; + wSetData(&Data->UpdateDataTail->Item.End, Value); + } + else if (!_stricmp(ValName, "BREAK DATA")) { + Data->UpdateDataTail->Item.Type = WID_BREAK; + wSetData(&Data->UpdateDataTail->Item.Break, Value); + } + } + // recalculate memory used + Data->MemUsed += (mir_strlen(Value) + 10); + wfree(&ValName); + } + // calcualate memory used for the ini and close the file + Data->MemUsed += sizeof(WIDATAITEMLIST)*Data->UpdateDataCount; + Data->Enabled = TRUE; // enable the service + fclose(pfile); + wfree(&Group); + } +} + +//============ LOADING INI FILES ============ +// +// load the weather update data form INI files +bool LoadWIData(bool dial) +{ + // make sure that the current service data list is empty + WITail = nullptr; + WIHead = WITail; + + // find all *.ini file in the plugin\weather directory + wchar_t szSearchPath[MAX_PATH], FileName[MAX_PATH]; + GetModuleFileName(GetModuleHandle(nullptr), szSearchPath, _countof(szSearchPath)); + wchar_t *chop = wcsrchr(szSearchPath, '\\'); + if (chop == nullptr) + return false; + *chop = '\0'; + mir_wstrncat(szSearchPath, L"\\Plugins\\Weather\\*.ini", _countof(szSearchPath) - mir_wstrlen(szSearchPath)); + wcsncpy(FileName, szSearchPath, MAX_PATH - 1); + + WIN32_FIND_DATA fd; + HANDLE hFind = FindFirstFile(szSearchPath, &fd); + + + // load the content of the ini file into memory + if (hFind != INVALID_HANDLE_VALUE) { + do { + chop = wcsrchr(FileName, '\\'); + chop[1] = '\0'; + mir_wstrncat(FileName, fd.cFileName, _countof(FileName) - mir_wstrlen(FileName)); + if (mir_wstrcmpi(fd.cFileName, L"SAMPLE_INI.INI")) { + WIDATA Data; + LoadStationData(FileName, fd.cFileName, &Data); + if (Data.Enabled) + WIListAdd(Data); + } + // look through the entire "plugins\weather" directory + } while (FindNextFile(hFind, &fd)); + FindClose(hFind); + } + + if (WIHead == nullptr) { + // no ini found, display an error message box. + if (dial) + hWndSetup = CreateDialog(g_plugin.getInst(), MAKEINTRESOURCE(IDD_SETUP), nullptr, DlgProcSetup); + else + MessageBox(nullptr, + TranslateT("No update data file is found. Please check your Plugins\\Weather directory."), + TranslateT("Weather Protocol"), MB_OK | MB_ICONERROR); + return false; + } + return true; +} + +//============ FREE WIDATA ITEM FROM MEMORY ============ +// +// free the WIDATA struct from memory +// Data = the struct to be freed +static void FreeWIData(WIDATA *Data) +{ + // free update items linked list first + WIDATAITEMLIST *WItem = Data->UpdateData; + while (WItem != nullptr) { + Data->UpdateData = WItem->Next; + FreeDataItem(&WItem->Item); + mir_free(WItem); + WItem = Data->UpdateData; + } + + // free the strings in the rest of the struct + wfree(&Data->DisplayName); + wfree(&Data->InternalName); + wfree(&Data->Description); + wfree(&Data->Author); + wfree(&Data->Version); + wfree(&Data->DefaultURL); + wfree(&Data->DefaultMap); + wfree(&Data->UpdateURL); + wfree(&Data->UpdateURL2); + wfree(&Data->UpdateURL3); + wfree(&Data->UpdateURL4); + wfree(&Data->Cookie); + wfree(&Data->UserAgent); + wfree(&Data->IDSearch.SearchURL); + wfree(&Data->IDSearch.NotFoundStr); + FreeDataItem(&Data->IDSearch.Name); + wfree(&Data->NameSearch.SearchURL); + wfree(&Data->NameSearch.NotFoundStr); + wfree(&Data->NameSearch.SingleStr); + wfree(&Data->NameSearch.Single.First); + FreeDataItem(&Data->NameSearch.Single.Name); + FreeDataItem(&Data->NameSearch.Single.ID); + wfree(&Data->NameSearch.Multiple.First); + FreeDataItem(&Data->NameSearch.Multiple.Name); + FreeDataItem(&Data->NameSearch.Multiple.ID); + wfree(&Data->ShortFileName); + wfree(&Data->FileName); + for (int i = 0; i < 10; i++) + DestroyCondList(&Data->CondList[i]); +} + +// remove all service data from memory +void DestroyWIList(void) +{ + // free the list one by one + while (WIHead != nullptr) { + WIDATALIST *wi = WIHead; + WIHead = wi->next; + FreeWIData(&wi->Data); // free the data struct + mir_free(wi); + } + + // make sure the entire list is clear + WITail = nullptr; +} diff --git a/protocols/Weather/src/weather_mwin.cpp b/protocols/Weather/src/weather_mwin.cpp new file mode 100644 index 0000000000..a240b01421 --- /dev/null +++ b/protocols/Weather/src/weather_mwin.cpp @@ -0,0 +1,364 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2006-2009 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2006 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +#define MS_TOOLTIP_SHOWTIP "mToolTip/ShowTip" +#define MS_TOOLTIP_HIDETIP "mToolTip/HideTip" + +static MWindowList hMwinWindowList; +static HANDLE hFontHook; + +HGENMENU hMwinMenu; + +typedef struct +{ + MCONTACT hContact; + HWND hAvt; + BOOL haveAvatar; +} MWinDataType; + +#define WM_REDRAWWIN (WM_USER + 17369) + +static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + MWinDataType *data = (MWinDataType*)GetWindowLongPtr(hwnd, GWLP_USERDATA); + + switch (msg) { + case WM_CREATE: + data = (MWinDataType*)mir_calloc(sizeof(MWinDataType)); + SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)data); + + data->hContact = (DWORD_PTR)((LPCREATESTRUCT)lParam)->lpCreateParams; + data->hAvt = CreateWindow(AVATAR_CONTROL_CLASS, TEXT(""), WS_CHILD, + 0, 0, opt.AvatarSize, opt.AvatarSize, hwnd, nullptr, g_plugin.getInst(), nullptr); + if (data->hAvt) SendMessage(data->hAvt, AVATAR_SETCONTACT, 0, (LPARAM)data->hContact); + break; + + case WM_DESTROY: + mir_free(data); + break; + + case WM_CONTEXTMENU: + { + POINT pt; + GetCursorPos(&pt); + HMENU hMenu = Menu_BuildContactMenu(data->hContact); + TrackPopupMenu(hMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hwnd, nullptr); + DestroyMenu(hMenu); + } + break; + + case WM_MOUSEMOVE: + { + TRACKMOUSEEVENT tme = { 0 }; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.hwndTrack = hwnd; + tme.dwFlags = TME_QUERY; + TrackMouseEvent(&tme); + + if (tme.dwFlags == 0) { + tme.dwFlags = TME_HOVER | TME_LEAVE; + tme.hwndTrack = hwnd; + tme.dwHoverTime = CallService(MS_CLC_GETINFOTIPHOVERTIME, 0, 0); + TrackMouseEvent(&tme); + } + } + break; + + case WM_MOUSEHOVER: + { + POINT pt; + CLCINFOTIP ti = { 0 }; + + GetCursorPos(&pt); + GetWindowRect(hwnd, &ti.rcItem); + + ti.cbSize = sizeof(ti); + ti.hItem = (HANDLE)data->hContact; + ti.ptCursor = pt; + ti.isTreeFocused = 1; + CallService(MS_TOOLTIP_SHOWTIP, 0, (LPARAM)&ti); + } + break; + + case WM_LBUTTONDBLCLK: + BriefInfo(data->hContact, 0); + break; + + case WM_COMMAND: //Needed by the contact's context menu + if (Clist_MenuProcessCommand(LOWORD(wParam), MPCF_CONTACTMENU, data->hContact)) + break; + return FALSE; + + case WM_MEASUREITEM: //Needed by the contact's context menu + return Menu_MeasureItem(lParam); + + case WM_DRAWITEM: //Needed by the contact's context menu + return Menu_DrawItem(lParam); + + case WM_NOTIFY: + if (((LPNMHDR)lParam)->code == NM_AVATAR_CHANGED) { + BOOL newava = CallService(MS_AV_GETAVATARBITMAP, data->hContact, 0) != 0; + if (newava != data->haveAvatar) { + LONG_PTR style = GetWindowLongPtr(data->hAvt, GWL_STYLE); + data->haveAvatar = newava; + SetWindowLongPtr(data->hAvt, GWL_STYLE, newava ? (style | WS_VISIBLE) : (style & ~WS_VISIBLE)); + RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE); + } + } + break; + + case WM_REDRAWWIN: + if (data->hAvt != nullptr) MoveWindow(data->hAvt, 0, 0, opt.AvatarSize, opt.AvatarSize, TRUE); + RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); + break; + + case WM_PAINT: + { + RECT r, rc; + + if (GetUpdateRect(hwnd, &r, FALSE)) { + int picSize = opt.AvatarSize; + HICON hIcon = nullptr; + + if (!data->haveAvatar) { + int statusIcon = g_plugin.getWord(data->hContact, "Status"); + + picSize = GetSystemMetrics(SM_CXICON); + hIcon = Skin_LoadProtoIcon(MODULENAME, statusIcon, true); + if ((INT_PTR)hIcon == CALLSERVICE_NOTFOUND) { + picSize = GetSystemMetrics(SM_CXSMICON); + hIcon = Skin_LoadProtoIcon(MODULENAME, statusIcon); + } + } + + LOGFONT lfnt, lfnt1; + COLORREF clr = g_plugin.getDword("ColorMwinFrame", GetSysColor(COLOR_3DFACE)); + COLORREF fntc = Font_GetW(_A2W(MODULENAME), LPGENW("Frame Font"), &lfnt); + COLORREF fntc1 = Font_GetW(_A2W(MODULENAME), LPGENW("Frame Title Font"), &lfnt1); + + ptrW tszInfo(db_get_wsa(data->hContact, WEATHERCONDITION, "WeatherInfo")); + + GetClientRect(hwnd, &rc); + + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hwnd, &ps); + + if (ServiceExists(MS_SKIN_DRAWGLYPH)) { + SKINDRAWREQUEST rq; + memset(&rq, 0, sizeof(rq)); + rq.hDC = hdc; + rq.rcDestRect = rc; + rq.rcClipRect = rc; + + mir_strcpy(rq.szObjectID, "Main,ID=WeatherFrame"); + CallService(MS_SKIN_DRAWGLYPH, (WPARAM)&rq, 0); + } + + if (clr != 0xFFFFFFFF) { + HBRUSH hBkgBrush = CreateSolidBrush(clr); + FillRect(hdc, &rc, hBkgBrush); + DeleteObject(hBkgBrush); + } + + if (!data->haveAvatar) + DrawIconEx(hdc, 1, 1, hIcon, 0, 0, 0, nullptr, DI_NORMAL); + + SetBkMode(hdc, TRANSPARENT); + + HFONT hfnt = CreateFontIndirect(&lfnt1); + HFONT hfntold = (HFONT)SelectObject(hdc, hfnt); + + wchar_t *nick = Clist_GetContactDisplayName(data->hContact); + + SIZE fontSize; + GetTextExtentPoint32(hdc, L"|", 1, &fontSize); + + rc.top += 1; + rc.left += picSize + fontSize.cx; + + SetTextColor(hdc, fntc1); + DrawText(hdc, nick, -1, &rc, DT_LEFT | DT_EXPANDTABS); + + rc.top += fontSize.cy; + + SelectObject(hdc, hfntold); + DeleteObject(hfnt); + + if (tszInfo) { + HFONT hFont = CreateFontIndirect(&lfnt); + HFONT hFontOld = (HFONT)SelectObject(hdc, hFont); + + SetTextColor(hdc, fntc); + DrawText(hdc, tszInfo, -1, &rc, DT_LEFT | DT_EXPANDTABS); + + SelectObject(hdc, hFontOld); + DeleteObject(hFont); + } + EndPaint(hwnd, &ps); + IcoLib_ReleaseIcon(hIcon); + } + break; + } + + default: + return DefWindowProc(hwnd, msg, wParam, lParam); + } + return(TRUE); +} + +static void addWindow(MCONTACT hContact) +{ + DBVARIANT dbv; + if (g_plugin.getWString(hContact, "Nick", &dbv)) + return; + + wchar_t winname[512]; + mir_snwprintf(winname, L"Weather: %s", dbv.pwszVal); + db_free(&dbv); + + HWND hWnd = CreateWindow(L"WeatherFrame", L"", WS_CHILD | WS_VISIBLE, + 0, 0, 10, 10, g_clistApi.hwndContactList, nullptr, g_plugin.getInst(), (void*)hContact); + WindowList_Add(hMwinWindowList, hWnd, hContact); + + CLISTFrame Frame = { 0 }; + Frame.szName.w = winname; + Frame.hIcon = LoadIconEx("main", FALSE); + Frame.cbSize = sizeof(Frame); + Frame.hWnd = hWnd; + Frame.align = alBottom; + Frame.Flags = F_VISIBLE | F_NOBORDER | F_UNICODE; + Frame.height = 32; + int frameID = g_plugin.addFrame(&Frame); + + g_plugin.setDword(hContact, "mwin", frameID); + db_set_b(hContact, "CList", "Hidden", TRUE); +} + +void removeWindow(MCONTACT hContact) +{ + DWORD frameId = g_plugin.getDword(hContact, "mwin"); + + WindowList_Remove(hMwinWindowList, WindowList_Find(hMwinWindowList, hContact)); + CallService(MS_CLIST_FRAMES_REMOVEFRAME, frameId, 0); + + g_plugin.setDword(hContact, "mwin", 0); + db_unset(hContact, "CList", "Hidden"); +} + +void UpdateMwinData(MCONTACT hContact) +{ + HWND hwnd = WindowList_Find(hMwinWindowList, hContact); + if (hwnd != nullptr) + RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); +} + +INT_PTR Mwin_MenuClicked(WPARAM wParam, LPARAM) +{ + BOOL addwnd = WindowList_Find(hMwinWindowList, wParam) == nullptr; + if (addwnd) + addWindow(wParam); + else + removeWindow(wParam); + return 0; +} + +int BuildContactMenu(WPARAM wparam, LPARAM) +{ + int flags = g_plugin.getDword(wparam, "mwin") ? CMIF_CHECKED : 0; + Menu_ModifyItem(hMwinMenu, nullptr, INVALID_HANDLE_VALUE, flags); + return 0; +} + +int RedrawFrame(WPARAM, LPARAM) +{ + WindowList_Broadcast(hMwinWindowList, WM_REDRAWWIN, 0, 0); + return 0; +} + +void InitMwin(void) +{ + if (!ServiceExists(MS_CLIST_FRAMES_ADDFRAME)) + return; + + hMwinWindowList = WindowList_Create(); + + WNDCLASS wndclass; + wndclass.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; + wndclass.lpfnWndProc = wndProc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = g_plugin.getInst(); + wndclass.hIcon = nullptr; + wndclass.hCursor = LoadCursor(nullptr, IDC_ARROW); + wndclass.hbrBackground = nullptr; //(HBRUSH)(COLOR_3DFACE+1); + wndclass.lpszMenuName = nullptr; + wndclass.lpszClassName = L"WeatherFrame"; + RegisterClass(&wndclass); + + ColourIDW colourid = {}; + mir_strcpy(colourid.dbSettingsGroup, MODULENAME); + mir_strcpy(colourid.setting, "ColorMwinFrame"); + mir_wstrcpy(colourid.name, LPGENW("Frame Background")); + mir_wstrcpy(colourid.group, _A2W(MODULENAME)); + colourid.defcolour = GetSysColor(COLOR_3DFACE); + g_plugin.addColor(&colourid); + + FontIDW fontid = {}; + fontid.flags = FIDF_ALLOWREREGISTER | FIDF_DEFAULTVALID; + mir_strcpy(fontid.dbSettingsGroup, MODULENAME); + mir_wstrcpy(fontid.group, _A2W(MODULENAME)); + mir_wstrcpy(fontid.name, LPGENW("Frame Font")); + mir_strcpy(fontid.setting, "fnt0"); + + HDC hdc = GetDC(nullptr); + fontid.deffontsettings.size = -13; + ReleaseDC(nullptr, hdc); + + fontid.deffontsettings.charset = DEFAULT_CHARSET; + mir_wstrcpy(fontid.deffontsettings.szFace, L"Verdana"); + mir_wstrcpy(fontid.backgroundGroup, _A2W(MODULENAME)); + mir_wstrcpy(fontid.backgroundName, LPGENW("Frame Background")); + g_plugin.addFont(&fontid); + + fontid.deffontsettings.style = DBFONTF_BOLD; + mir_wstrcpy(fontid.name, LPGENW("Frame Title Font")); + mir_strcpy(fontid.setting, "fnt1"); + g_plugin.addFont(&fontid); + + for (auto &hContact : Contacts(MODULENAME)) + if (g_plugin.getDword(hContact, "mwin")) + addWindow(hContact); + + hFontHook = HookEvent(ME_FONT_RELOAD, RedrawFrame); +} + +void DestroyMwin(void) +{ + for (auto &hContact : Contacts(MODULENAME)) { + DWORD frameId = g_plugin.getDword(hContact, "mwin"); + if (frameId) + CallService(MS_CLIST_FRAMES_REMOVEFRAME, frameId, 0); + } + UnregisterClass(L"WeatherFrame", g_plugin.getInst()); + WindowList_Destroy(hMwinWindowList); + UnhookEvent(hFontHook); +} diff --git a/protocols/Weather/src/weather_opt.cpp b/protocols/Weather/src/weather_opt.cpp new file mode 100644 index 0000000000..daf84d36c7 --- /dev/null +++ b/protocols/Weather/src/weather_opt.cpp @@ -0,0 +1,601 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* This file contain the source related to weather option pages. It also +contain code for saving/loading options from the database. +*/ + +#include "stdafx.h" + +static BOOL opt_startup; +int RedrawFrame(WPARAM wParam, LPARAM lParam); + +//============ LOADING AND SAVING OPTIONS =========== +// +// set a string to default +// in = string to determine which field to set default "CBNEHXPp" +void SetTextDefault(const char* in) +{ + wchar_t str[MAX_TEXT_SIZE]; + + if (strchr(in, 'C') != nullptr) { + wcsncpy(str, C_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.cText, str); + } + if (strchr(in, 'b') != nullptr) { + wcsncpy(str, b_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.bTitle, str); + } + if (strchr(in, 'B') != nullptr) { + wcsncpy(str, B_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.bText, str); + } + if (strchr(in, 'N') != nullptr) { + wcsncpy(str, N_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.nText, str); + } + if (strchr(in, 'E') != nullptr) { + wcsncpy(str, E_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.eText, str); + } + if (strchr(in, 'H') != nullptr) { + wcsncpy(str, H_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.hText, str); + } + if (strchr(in, 'X') != nullptr) { + wcsncpy(str, X_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.xText, str); + } + if (strchr(in, 'P') != nullptr) { + wcsncpy(str, P_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.pTitle, str); + } + if (strchr(in, 'p') != nullptr) { + wcsncpy(str, p_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.pText, str); + } + if (strchr(in, 'S') != nullptr) { + wcsncpy(str, s_DEFAULT, MAX_TEXT_SIZE - 1); + wSetData(&opt.sText, str); + } +} + +void DestroyOptions(void) +{ + wfree(&opt.cText); + wfree(&opt.bTitle); + wfree(&opt.bText); + wfree(&opt.nText); + wfree(&opt.eText); + wfree(&opt.hText); + wfree(&opt.xText); + wfree(&opt.pTitle); + wfree(&opt.pText); + wfree(&opt.sText); +} + +// load options from database + set default if the setting does not exist +void LoadOptions(void) +{ + memset(&opt, 0, sizeof(opt)); + + // main options + opt.StartupUpdate = g_plugin.getByte("StartupUpdate", true); + opt.AutoUpdate = g_plugin.getByte("AutoUpdate", true); + opt.UpdateTime = g_plugin.getWord("UpdateTime", 30); + opt.NoProtoCondition = g_plugin.getByte("NoStatus", true); + opt.UpdateOnlyConditionChanged = g_plugin.getByte("CondChangeAsUpdate", true); + opt.RemoveOldData = g_plugin.getByte("RemoveOld", false); + opt.MakeItalic = g_plugin.getByte("MakeItalic", true); + opt.AvatarSize = g_plugin.getByte("AvatarSize", 128); + + // units + opt.tUnit = g_plugin.getWord("tUnit", 1); + opt.wUnit = g_plugin.getWord("wUnit", 2); + opt.vUnit = g_plugin.getWord("vUnit", 1); + opt.pUnit = g_plugin.getWord("pUnit", 4); + opt.dUnit = g_plugin.getWord("dUnit", 1); + opt.eUnit = g_plugin.getWord("eUnit", 2); + + ptrW szValue(g_plugin.getWStringA("DegreeSign")); + wcsncpy_s(opt.DegreeSign, (szValue == NULL) ? L"" : szValue, _TRUNCATE); + + opt.DoNotAppendUnit = g_plugin.getByte("DoNotAppendUnit", 0); + opt.NoFrac = g_plugin.getByte("NoFractions", 0); + + // texts + if (szValue = g_plugin.getWStringA("DisplayText")) + wSetData(&opt.cText, TranslateW(szValue)); + else + SetTextDefault("C"); + + if (szValue = g_plugin.getWStringA("BriefTextTitle")) + wSetData(&opt.bTitle, TranslateW(szValue)); + else + SetTextDefault("b"); + + if (szValue = g_plugin.getWStringA("BriefText")) + wSetData(&opt.bText, TranslateW(szValue)); + else + SetTextDefault("B"); + + if (szValue = g_plugin.getWStringA("NoteText")) + wSetData(&opt.nText, TranslateW(szValue)); + else + SetTextDefault("N"); + + if (szValue = g_plugin.getWStringA("ExtText")) + wSetData(&opt.eText, TranslateW(szValue)); + else + SetTextDefault("E"); + + if (szValue = g_plugin.getWStringA("HistoryText")) + wSetData(&opt.hText, TranslateW(szValue)); + else + SetTextDefault("H"); + + if (szValue = g_plugin.getWStringA("ExtraText")) + wSetData(&opt.xText, TranslateW(szValue)); + else + SetTextDefault("X"); + + if (szValue = g_plugin.getWStringA("StatusText")) + wSetData(&opt.sText, TranslateW(szValue)); + else + SetTextDefault("S"); + + // advanced + opt.DisCondIcon = g_plugin.getByte("DisableConditionIcon", false); + // popup options + opt.UsePopup = g_plugin.getByte("UsePopUp", true); + opt.UpdatePopup = g_plugin.getByte("UpdatePopup", true); + opt.AlertPopup = g_plugin.getByte("AlertPopup", true); + opt.PopupOnChange = g_plugin.getByte("PopUpOnChange", true); + opt.ShowWarnings = g_plugin.getByte("ShowWarnings", true); + // popup colors + opt.BGColour = g_plugin.getDword("BackgroundColour", GetSysColor(COLOR_BTNFACE)); + opt.TextColour = g_plugin.getDword("TextColour", GetSysColor(COLOR_WINDOWTEXT)); + opt.UseWinColors = g_plugin.getByte("UseWinColors", false); + // popup actions + opt.LeftClickAction = g_plugin.getDword("LeftClickAction", IDM_M2); + opt.RightClickAction = g_plugin.getDword("RightClickAction", IDM_M1); + // popup delay + opt.pDelay = g_plugin.getDword("PopupDelay", 0); + // popup texts + if (szValue = g_plugin.getWStringA("PopupTitle")) + wSetData(&opt.pTitle, szValue); + else + SetTextDefault("P"); + + if (szValue = g_plugin.getWStringA("PopupText")) + wSetData(&opt.pText, szValue); + else + SetTextDefault("p"); + + // misc + if (szValue = g_plugin.getWStringA("Default")) + wcsncpy_s(opt.Default, szValue, _TRUNCATE); + else + opt.Default[0] = 0; +} + +// save the options to database +void SaveOptions(void) +{ + // main options + g_plugin.setByte("StartupUpdate", (BYTE)opt.StartupUpdate); + g_plugin.setByte("AutoUpdate", (BYTE)opt.AutoUpdate); + g_plugin.setWord("UpdateTime", opt.UpdateTime); + g_plugin.setByte("NoStatus", (BYTE)opt.NoProtoCondition); + g_plugin.setByte("CondChangeAsUpdate", (BYTE)opt.UpdateOnlyConditionChanged); + g_plugin.setByte("RemoveOld", (BYTE)opt.RemoveOldData); + g_plugin.setByte("MakeItalic", (BYTE)opt.MakeItalic); + g_plugin.setByte("AvatarSize", (BYTE)opt.AvatarSize); + // units + g_plugin.setWord("tUnit", opt.tUnit); + g_plugin.setWord("wUnit", opt.wUnit); + g_plugin.setWord("vUnit", opt.vUnit); + g_plugin.setWord("pUnit", opt.pUnit); + g_plugin.setWord("dUnit", opt.dUnit); + g_plugin.setWord("eUnit", opt.eUnit); + g_plugin.setWString("DegreeSign", opt.DegreeSign); + g_plugin.setByte("DoNotAppendUnit", (BYTE)opt.DoNotAppendUnit); + g_plugin.setByte("NoFractions", (BYTE)opt.NoFrac); + // texts + g_plugin.setWString("DisplayText", opt.cText); + g_plugin.setWString("BriefTextTitle", opt.bTitle); + g_plugin.setWString("BriefText", opt.bText); + g_plugin.setWString("NoteText", opt.nText); + g_plugin.setWString("ExtText", opt.eText); + g_plugin.setWString("HistoryText", opt.hText); + g_plugin.setWString("ExtraText", opt.xText); + g_plugin.setWString("StatusText", opt.sText); + // advanced + g_plugin.setByte("DisableConditionIcon", (BYTE)opt.DisCondIcon); + // popup options + g_plugin.setByte("UsePopUp", (BYTE)opt.UsePopup); + g_plugin.setByte("UpdatePopup", (BYTE)opt.UpdatePopup); + g_plugin.setByte("AlertPopup", (BYTE)opt.AlertPopup); + g_plugin.setByte("PopUpOnChange", (BYTE)opt.PopupOnChange); + g_plugin.setByte("ShowWarnings", (BYTE)opt.ShowWarnings); + // popup colors + g_plugin.setDword("BackgroundColour", opt.BGColour); + g_plugin.setDword("TextColour", opt.TextColour); + g_plugin.setByte("UseWinColors", (BYTE)opt.UseWinColors); + // popup actions + g_plugin.setDword("LeftClickAction", opt.LeftClickAction); + g_plugin.setDword("RightClickAction", opt.RightClickAction); + // popup delay + g_plugin.setDword("PopupDelay", opt.pDelay); + // popup texts + g_plugin.setWString("PopupTitle", opt.pTitle); + g_plugin.setWString("PopupText", opt.pText); + // misc stuff + g_plugin.setWString("Default", opt.Default); +} +//============ MAIN OPTIONS ============ + +// weather options +static INT_PTR CALLBACK OptionsProc(HWND hdlg, UINT msg, WPARAM wparam, LPARAM lparam) +{ + wchar_t str[512]; + + switch (msg) { + case WM_INITDIALOG: + opt_startup = TRUE; + TranslateDialogDefault(hdlg); + // load settings + _ltow(opt.UpdateTime, str, 10); + SetDlgItemText(hdlg, IDC_UPDATETIME, str); + SetDlgItemText(hdlg, IDC_DEGREE, opt.DegreeSign); + + SendDlgItemMessage(hdlg, IDC_AVATARSPIN, UDM_SETRANGE32, 0, 999); + SendDlgItemMessage(hdlg, IDC_AVATARSPIN, UDM_SETPOS, 0, opt.AvatarSize); + SendDlgItemMessage(hdlg, IDC_AVATARSIZE, EM_LIMITTEXT, 3, 0); + + CheckDlgButton(hdlg, IDC_STARTUPUPD, opt.StartupUpdate ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_UPDATE, opt.AutoUpdate ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_PROTOCOND, !opt.NoProtoCondition ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_UPDCONDCHG, opt.UpdateOnlyConditionChanged ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_REMOVEOLD, opt.RemoveOldData ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_MAKEI, opt.MakeItalic ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_DISCONDICON, opt.DisCondIcon ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_DONOTAPPUNITS, opt.DoNotAppendUnit ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_NOFRAC, opt.NoFrac ? BST_CHECKED : BST_UNCHECKED); + + // load units + switch (opt.tUnit) { // temperature + case 1: CheckRadioButton(hdlg, IDC_T1, IDC_T2, IDC_T1); break; + case 2: CheckRadioButton(hdlg, IDC_T1, IDC_T2, IDC_T2); break; + } + switch (opt.wUnit) { // wind + case 1: CheckRadioButton(hdlg, IDC_W1, IDC_W4, IDC_W1); break; + case 2: CheckRadioButton(hdlg, IDC_W1, IDC_W4, IDC_W2); break; + case 3: CheckRadioButton(hdlg, IDC_W1, IDC_W4, IDC_W3); break; + case 4: CheckRadioButton(hdlg, IDC_W1, IDC_W4, IDC_W4); break; + } + switch (opt.vUnit) { // visibility + case 1: CheckRadioButton(hdlg, IDC_V1, IDC_V2, IDC_V1); break; + case 2: CheckRadioButton(hdlg, IDC_V1, IDC_V2, IDC_V2); break; + } + switch (opt.pUnit) { // pressure + case 1: CheckRadioButton(hdlg, IDC_P1, IDC_P4, IDC_P1); break; + case 2: CheckRadioButton(hdlg, IDC_P1, IDC_P4, IDC_P2); break; + case 3: CheckRadioButton(hdlg, IDC_P1, IDC_P4, IDC_P3); break; + case 4: CheckRadioButton(hdlg, IDC_P1, IDC_P4, IDC_P4); break; + } + switch (opt.dUnit) { // pressure + case 1: CheckRadioButton(hdlg, IDC_D1, IDC_D3, IDC_D1); break; + case 2: CheckRadioButton(hdlg, IDC_D1, IDC_D3, IDC_D2); break; + case 3: CheckRadioButton(hdlg, IDC_D1, IDC_D3, IDC_D3); break; + } + + switch (opt.eUnit) { // elev + case 1: CheckRadioButton(hdlg, IDC_E1, IDC_E2, IDC_E1); break; + case 2: CheckRadioButton(hdlg, IDC_E1, IDC_E2, IDC_E2); break; + } + + opt_startup = FALSE; + return 0; + + case WM_COMMAND: + if (HIWORD(wparam) == BN_CLICKED && GetFocus() == (HWND)lparam) + if (!opt_startup) SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + if (!((LOWORD(wparam) == IDC_UPDATE || LOWORD(wparam) == IDC_DEGREE) && + (HIWORD(wparam) != EN_CHANGE || (HWND)lparam != GetFocus()))) + if (!opt_startup) SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + return 0; + + case WM_NOTIFY: + switch (((LPNMHDR)lparam)->code) { + case PSN_APPLY: + // change the status for weather protocol + if (IsDlgButtonChecked(hdlg, IDC_PROTOCOND) && opt.DefStn != NULL) { + old_status = status; + status = g_plugin.getWord(opt.DefStn, "StatusIcon", NOSTATUSDATA); + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, status); + } + + // get update time and remove the old timer + GetDlgItemText(hdlg, IDC_UPDATETIME, str, _countof(str)); + opt.UpdateTime = (WORD)_wtoi(str); + if (opt.UpdateTime < 1) opt.UpdateTime = 1; + KillTimer(nullptr, timerId); + timerId = SetTimer(nullptr, 0, opt.UpdateTime * 60000, timerProc); + + // other general options + GetDlgItemText(hdlg, IDC_DEGREE, opt.DegreeSign, _countof(opt.DegreeSign)); + opt.StartupUpdate = IsDlgButtonChecked(hdlg, IDC_STARTUPUPD); + opt.AutoUpdate = IsDlgButtonChecked(hdlg, IDC_UPDATE); + opt.NoProtoCondition = BST_UNCHECKED == IsDlgButtonChecked(hdlg, IDC_PROTOCOND); + opt.DisCondIcon = IsDlgButtonChecked(hdlg, IDC_DISCONDICON); + opt.UpdateOnlyConditionChanged = (BYTE)IsDlgButtonChecked(hdlg, IDC_UPDCONDCHG); + opt.RemoveOldData = IsDlgButtonChecked(hdlg, IDC_REMOVEOLD); + opt.MakeItalic = IsDlgButtonChecked(hdlg, IDC_MAKEI); + opt.AvatarSize = GetDlgItemInt(hdlg, IDC_AVATARSIZE, nullptr, FALSE); + opt.DoNotAppendUnit = IsDlgButtonChecked(hdlg, IDC_DONOTAPPUNITS); + opt.NoFrac = IsDlgButtonChecked(hdlg, IDC_NOFRAC); + UpdateMenu(opt.AutoUpdate); + + // save the units + if (IsDlgButtonChecked(hdlg, IDC_T1)) opt.tUnit = 1; + if (IsDlgButtonChecked(hdlg, IDC_T2)) opt.tUnit = 2; + if (IsDlgButtonChecked(hdlg, IDC_W1)) opt.wUnit = 1; + if (IsDlgButtonChecked(hdlg, IDC_W2)) opt.wUnit = 2; + if (IsDlgButtonChecked(hdlg, IDC_W3)) opt.wUnit = 3; + if (IsDlgButtonChecked(hdlg, IDC_W4)) opt.wUnit = 4; + if (IsDlgButtonChecked(hdlg, IDC_V1)) opt.vUnit = 1; + if (IsDlgButtonChecked(hdlg, IDC_V2)) opt.vUnit = 2; + if (IsDlgButtonChecked(hdlg, IDC_P1)) opt.pUnit = 1; + if (IsDlgButtonChecked(hdlg, IDC_P2)) opt.pUnit = 2; + if (IsDlgButtonChecked(hdlg, IDC_P3)) opt.pUnit = 3; + if (IsDlgButtonChecked(hdlg, IDC_P4)) opt.pUnit = 4; + if (IsDlgButtonChecked(hdlg, IDC_D1)) opt.dUnit = 1; + if (IsDlgButtonChecked(hdlg, IDC_D2)) opt.dUnit = 2; + if (IsDlgButtonChecked(hdlg, IDC_D3)) opt.dUnit = 3; + if (IsDlgButtonChecked(hdlg, IDC_E1)) opt.eUnit = 1; + if (IsDlgButtonChecked(hdlg, IDC_E2)) opt.eUnit = 2; + + // save the new weather options + SaveOptions(); + + RedrawFrame(0, 0); + + return 1; + } + break; + } + return 0; +} + +//============ TEXT OPTION DIALOG ============ + +static void LoadTextSettings(HWND hdlg) +{ + // load text option settings from memory + SetDlgItemText(hdlg, IDC_CTEXT, opt.cText); + SetDlgItemText(hdlg, IDC_BTITLE, opt.bTitle); + SetDlgItemText(hdlg, IDC_BTEXT, opt.bText); + SetDlgItemText(hdlg, IDC_ETEXT, opt.eText); + SetDlgItemText(hdlg, IDC_NTEXT, opt.nText); + SetDlgItemText(hdlg, IDC_HTEXT, opt.hText); + SetDlgItemText(hdlg, IDC_XTEXT, opt.xText); + SetDlgItemText(hdlg, IDC_BTITLE2, opt.sText); +} + +// free the display text settings from memory +static void FreeTextVar(void) +{ + wfree(&opt.cText); + wfree(&opt.bText); + wfree(&opt.bTitle); + wfree(&opt.eText); + wfree(&opt.nText); + wfree(&opt.hText); + wfree(&opt.xText); + wfree(&opt.sText); +} + +// text option dialog + +static const char *varname[8] = { "C", "b", "B", "N", "X", "E", "H", "S" }; +static const int cname[8] = { IDC_CTEXT, IDC_BTITLE, IDC_BTEXT, IDC_NTEXT, IDC_XTEXT, IDC_ETEXT, IDC_HTEXT, IDC_BTITLE2 }; +static wchar_t* const *var[8] = { &opt.cText, &opt.bTitle, &opt.bText, &opt.nText, &opt.xText, &opt.eText, &opt.hText, &opt.sText }; + +static INT_PTR CALLBACK DlgProcText(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + RECT rc, pos; + HWND button; + HMENU hMenu, hMenu1; + wchar_t str[4096]; + switch (msg) { + case WM_INITDIALOG: + opt_startup = TRUE; + // set windows position, make it top-most + GetWindowRect(hdlg, &rc); + SetWindowPos(hdlg, HWND_TOPMOST, rc.left, rc.top, 0, 0, SWP_NOSIZE); + TranslateDialogDefault(hdlg); + // generate the display text for variable list + wcsncpy(str, VAR_LIST_OPT, _countof(str) - 1); + SetDlgItemText(hdlg, IDC_VARLIST, str); + + // make the more variable and other buttons flat + SendDlgItemMessage(hdlg, IDC_MORE, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_TM1, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_TM2, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_TM3, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_TM4, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_TM5, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_TM6, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_TM7, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_TM8, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_RESET, BUTTONSETASFLATBTN, TRUE, 0); + // load the settings + LoadTextSettings(hdlg); + opt_startup = FALSE; + return TRUE; + + case WM_COMMAND: + if (opt_startup) return TRUE; + switch (LOWORD(wParam)) { + case IDC_CTEXT: + case IDC_BTITLE: + case IDC_BTEXT: + case IDC_NTEXT: + case IDC_XTEXT: + case IDC_ETEXT: + case IDC_HTEXT: + case IDC_BTITLE2: + if (HIWORD(wParam) == EN_CHANGE) + SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + break; + + case IDC_MORE: + // display custom variables list + MoreVarList(); + break; + + case IDC_TM1: + case IDC_TM2: + case IDC_TM3: + case IDC_TM4: + case IDC_TM5: + case IDC_TM6: + case IDC_TM7: + case IDC_TM8: + // display the menu + button = GetDlgItem(hdlg, LOWORD(wParam)); + GetWindowRect(button, &pos); + hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_TMMENU)); + hMenu1 = GetSubMenu(hMenu, 0); + TranslateMenu(hMenu1); + switch (TrackPopupMenu(hMenu1, TPM_LEFTBUTTON | TPM_RETURNCMD, pos.left, pos.bottom, 0, hdlg, nullptr)) { + case ID_MPREVIEW: + { + // show the preview in a message box, using the weather data from the default station + WEATHERINFO winfo = LoadWeatherInfo(opt.DefStn); + GetDisplay(&winfo, *var[LOWORD(wParam) - IDC_TM1], str); + MessageBox(nullptr, str, TranslateT("Weather Protocol Text Preview"), MB_OK | MB_TOPMOST); + break; + } + case ID_MRESET: + unsigned varo = LOWORD(wParam) - IDC_TM1; + // remove the old setting from db and free memory + wchar_t* vartmp = *var[varo]; + wfree(&vartmp); + SetTextDefault(varname[varo]); + SetDlgItemText(hdlg, cname[varo], *var[varo]); + break; + } + DestroyMenu(hMenu); + break; + + case IDC_RESET: + // left click action selection menu + button = GetDlgItem(hdlg, IDC_RESET); + GetWindowRect(button, &pos); + hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_TMENU)); + hMenu1 = GetSubMenu(hMenu, 0); + TranslateMenu(hMenu1); + switch (TrackPopupMenu(hMenu1, TPM_LEFTBUTTON | TPM_RETURNCMD, pos.left, pos.bottom, 0, hdlg, nullptr)) { + case ID_T1: + // reset to the strings in memory, discard all changes + LoadTextSettings(hdlg); + break; + + case ID_T2: + // reset to the default setting + FreeTextVar(); + SetTextDefault("CbBENHX"); + LoadTextSettings(hdlg); + break; + } + DestroyMenu(hMenu); + break; + } + return TRUE; + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->code) { + case PSN_APPLY: + // save the option + wchar_t textstr[MAX_TEXT_SIZE]; + // free memory for old settings + FreeTextVar(); + // save new settings to memory + GetDlgItemText(hdlg, IDC_CTEXT, textstr, _countof(textstr)); + wSetData(&opt.cText, textstr); + GetDlgItemText(hdlg, IDC_BTEXT, textstr, _countof(textstr)); + wSetData(&opt.bText, textstr); + GetDlgItemText(hdlg, IDC_BTITLE, textstr, _countof(textstr)); + wSetData(&opt.bTitle, textstr); + GetDlgItemText(hdlg, IDC_ETEXT, textstr, _countof(textstr)); + wSetData(&opt.eText, textstr); + GetDlgItemText(hdlg, IDC_NTEXT, textstr, _countof(textstr)); + wSetData(&opt.nText, textstr); + GetDlgItemText(hdlg, IDC_HTEXT, textstr, _countof(textstr)); + wSetData(&opt.hText, textstr); + GetDlgItemText(hdlg, IDC_XTEXT, textstr, _countof(textstr)); + wSetData(&opt.xText, textstr); + GetDlgItemText(hdlg, IDC_BTITLE2, textstr, _countof(textstr)); + wSetData(&opt.sText, textstr); + SaveOptions(); + UpdateAllInfo(0, 0); + break; + } + break; + } + return FALSE; +} + + +//============ OPTION INITIALIZATION ============ + +// register the weather option pages +int OptInit(WPARAM wParam, LPARAM) +{ + // plugin options + OPTIONSDIALOGPAGE odp = {}; + odp.position = 95600; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS); + odp.pfnDlgProc = OptionsProc; + odp.szGroup.a = LPGEN("Network"); + odp.szTitle.a = WEATHERPROTOTEXT; + odp.szTab.a = LPGEN("General"); + odp.flags = ODPF_BOLDGROUPS; + g_plugin.addOptions(wParam, &odp); + + // text options + odp.pszTemplate = MAKEINTRESOURCEA(IDD_TEXTOPT); + odp.pfnDlgProc = DlgProcText; + odp.szTab.a = LPGEN("Display"); + g_plugin.addOptions(wParam, &odp); + + // if popup service exists, load the weather popup options + if ((ServiceExists(MS_POPUP_ADDPOPUPW))) { + odp.position = 100000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_POPUP); + odp.szGroup.a = LPGEN("Popups"); + odp.szTab.a = nullptr; + odp.pfnDlgProc = DlgPopupOpts; + g_plugin.addOptions(wParam, &odp); + } + + return 0; +} diff --git a/protocols/Weather/src/weather_popup.cpp b/protocols/Weather/src/weather_popup.cpp new file mode 100644 index 0000000000..6e72d5595e --- /dev/null +++ b/protocols/Weather/src/weather_popup.cpp @@ -0,0 +1,428 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2009 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* This file contain the source related to weather popups, including popup + options, popup display, and the code for popup process. +*/ + +#include "stdafx.h" + +// variables for weather_popup.c +static MCONTACT hPopupContact; + +//============ SHOW WEATHER POPUPS ============ + +//============ WEATHER ERROR POPUPS ============ + +// display weather error or notices (not threaded) +// wParam = error text +// lParam = display type +// Type can either be SM_WARNING, SM_NOTIFY, or SM_WEATHERALERT +int WeatherError(WPARAM wParam, LPARAM lParam) +{ + if (!opt.UsePopup) + return 0; + + wchar_t* tszMsg = (wchar_t*)wParam; + + if ((DWORD)lParam == SM_WARNING) + PUShowMessageW(tszMsg, SM_WARNING); + else if ((DWORD)lParam == SM_NOTIFY) + PUShowMessageW(tszMsg, SM_NOTIFY); + else if ((DWORD)lParam == SM_WEATHERALERT) { + POPUPDATAW ppd = { 0 }; + wchar_t str1[512], str2[512]; + + // get the 2 strings + wcsncpy(str1, tszMsg, _countof(str1) - 1); + wcsncpy(str2, tszMsg, _countof(str2) - 1); + wchar_t *chop = wcschr(str1, 255); + if (chop != nullptr) + *chop = '\0'; + else + str1[0] = 0; + chop = wcschr(str2, 255); + if (chop != nullptr) + wcsncpy(str2, chop + 1, _countof(str2) - 1); + else + str2[0] = 0; + + // setup the popup + ppd.lchIcon = (HICON)LoadImage(nullptr, MAKEINTRESOURCE(OIC_BANG), IMAGE_ICON, + GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED); + mir_wstrcpy(ppd.lpwzContactName, str1); + mir_wstrcpy(ppd.lpwzText, str2); + ppd.colorBack = (opt.UseWinColors) ? GetSysColor(COLOR_BTNFACE) : opt.BGColour; + ppd.colorText = (opt.UseWinColors) ? GetSysColor(COLOR_WINDOWTEXT) : opt.TextColour; + ppd.iSeconds = opt.pDelay; + PUAddPopupW(&ppd); + } + return 0; +} + +// wrapper function for displaying weather warning popup by triggering an event +// (threaded) +// lpzText = error text +// kind = display type (see m_popup.h) +int WPShowMessage(wchar_t* lpzText, WORD kind) +{ + NotifyEventHooks(hHookWeatherError, (WPARAM)lpzText, (LPARAM)kind); + return 0; +} + +//============ WEATHER POPUP PROCESSES ============ + +// popup dialog pocess +// for selecting actions when click on the popup window +// use for displaying contact menu +static LRESULT CALLBACK PopupDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + DWORD ID = 0; + MCONTACT hContact; + hContact = PUGetContact(hWnd); + + switch (message) { + case WM_COMMAND: + ID = opt.LeftClickAction; + if (ID != IDM_M7) PUDeletePopup(hWnd); + SendMessage(hPopupWindow, ID, hContact, 0); + return TRUE; + + case WM_CONTEXTMENU: + ID = opt.RightClickAction; + if (ID != IDM_M7) PUDeletePopup(hWnd); + SendMessage(hPopupWindow, ID, hContact, 0); + return TRUE; + + case UM_FREEPLUGINDATA: + IcoLib_ReleaseIcon((HICON)PUGetPluginData(hWnd)); + return FALSE; + } + + return DefWindowProc(hWnd, message, wParam, lParam); +} + +// display weather popups +// wParam = the contact to display popup +// lParam = whether the weather data is changed or not +int WeatherPopup(WPARAM hContact, LPARAM lParam) +{ + // determine if the popup should display or not + if (opt.UsePopup && opt.UpdatePopup && (!opt.PopupOnChange || (BOOL)lParam) && !g_plugin.getByte(hContact, "DPopUp")) { + WEATHERINFO winfo = LoadWeatherInfo(hContact); + + // setup the popup + POPUPDATAW ppd = { 0 }; + ppd.lchContact = hContact; + ppd.PluginData = ppd.lchIcon = Skin_LoadProtoIcon(MODULENAME, winfo.status); + GetDisplay(&winfo, opt.pTitle, ppd.lpwzContactName); + GetDisplay(&winfo, opt.pText, ppd.lpwzText); + ppd.PluginWindowProc = PopupDlgProc; + ppd.colorBack = (opt.UseWinColors) ? GetSysColor(COLOR_BTNFACE) : opt.BGColour; + ppd.colorText = (opt.UseWinColors) ? GetSysColor(COLOR_WINDOWTEXT) : opt.TextColour; + ppd.iSeconds = opt.pDelay; + PUAddPopupW(&ppd); + } + return 0; +} + + +// process for the popup window +// containing the code for popup actions +LRESULT CALLBACK PopupWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + POINT pt; + HMENU hMenu; + switch (uMsg) { + case IDM_M2: // brief info + BriefInfo(wParam, 0); + break; + + case IDM_M3: // read complete forecast + LoadForecast(wParam, 0); + break; + + case IDM_M4: // display weather map + WeatherMap(wParam, 0); + break; + + case IDM_M5: // open history window + CallService(MS_HISTORY_SHOWCONTACTHISTORY, wParam, 0); + break; + + case IDM_M6: // open external log + ViewLog(wParam, 0); + break; + + case IDM_M7: // display contact menu + hMenu = Menu_BuildContactMenu(wParam); + GetCursorPos(&pt); + hPopupContact = wParam; + TrackPopupMenu(hMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, nullptr); + DestroyMenu(hMenu); + break; + + case IDM_M8: // display contact detail + CallService(MS_USERINFO_SHOWDIALOG, wParam, 0); + + case WM_COMMAND: //Needed by the contact's context menu + if (Clist_MenuProcessCommand(LOWORD(wParam), MPCF_CONTACTMENU, hPopupContact)) + break; + return FALSE; + + case WM_MEASUREITEM: //Needed by the contact's context menu + return Menu_MeasureItem(lParam); + + case WM_DRAWITEM: //Needed by the contact's context menu + return Menu_DrawItem(lParam); + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam);//FALSE; +} + +//============ POPUP OPTIONS ============ + +// used to select the menu item for popup action menu +static void SelectMenuItem(HMENU hMenu, int Check) +{ + for (int i = 0; i <= GetMenuItemCount(hMenu) - 1; i++) + CheckMenuItem(hMenu, i, MF_BYPOSITION | ((int)GetMenuItemID(hMenu, i) == Check) * 8); +} + +// temporary read the current option to memory +// but does not write to the database +void ReadPopupOpt(HWND hdlg) +{ + wchar_t text[MAX_TEXT_SIZE]; + wchar_t str[512]; + + // popup colour + opt.TextColour = SendDlgItemMessage(hdlg, IDC_TEXTCOLOUR, CPM_GETCOLOUR, 0, 0); + opt.BGColour = SendDlgItemMessage(hdlg, IDC_BGCOLOUR, CPM_GETCOLOUR, 0, 0); + + // get delay time + GetDlgItemText(hdlg, IDC_DELAY, str, _countof(str)); + int num = _wtoi(str); + opt.pDelay = num; + + // other options + opt.UseWinColors = (BYTE)IsDlgButtonChecked(hdlg, IDC_USEWINCOLORS); + opt.UsePopup = (BYTE)IsDlgButtonChecked(hdlg, IDC_E); + opt.UpdatePopup = (BYTE)IsDlgButtonChecked(hdlg, IDC_POP1); + opt.AlertPopup = (BYTE)IsDlgButtonChecked(hdlg, IDC_POP2); + opt.PopupOnChange = (BYTE)IsDlgButtonChecked(hdlg, IDC_CH); + opt.ShowWarnings = (BYTE)IsDlgButtonChecked(hdlg, IDC_W); + + // popup texts + wfree(&opt.pText); + wfree(&opt.pTitle); + GetDlgItemText(hdlg, IDC_PText, text, _countof(text)); + wSetData(&opt.pText, text); + GetDlgItemText(hdlg, IDC_PTitle, text, _countof(text)); + wSetData(&opt.pTitle, text); +} + +// copied and modified from NewStatusNotify +INT_PTR CALLBACK DlgPopupOpts(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + int ID; + wchar_t str[512]; + HMENU hMenu, hMenu1; + RECT pos; + HWND button; + MCONTACT hContact; + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hdlg); + SaveOptions(); + + // click actions + hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_PMENU)); + hMenu1 = GetSubMenu(hMenu, 0); + GetMenuString(hMenu1, opt.LeftClickAction, str, _countof(str), MF_BYCOMMAND); + SetDlgItemText(hdlg, IDC_LeftClick, TranslateW(str)); + GetMenuString(hMenu1, opt.RightClickAction, str, _countof(str), MF_BYCOMMAND); + SetDlgItemText(hdlg, IDC_RightClick, TranslateW(str)); + DestroyMenu(hMenu); + + // other options + CheckDlgButton(hdlg, IDC_E, opt.UsePopup ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_POP2, opt.AlertPopup ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_POP1, opt.UpdatePopup ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_CH, opt.PopupOnChange ? BST_CHECKED : BST_UNCHECKED); + CheckDlgButton(hdlg, IDC_W, opt.ShowWarnings ? BST_CHECKED : BST_UNCHECKED); + SetDlgItemText(hdlg, IDC_PText, opt.pText); + SetDlgItemText(hdlg, IDC_PTitle, opt.pTitle); + // setting popup delay option + _ltow(opt.pDelay, str, 10); + SetDlgItemText(hdlg, IDC_DELAY, str); + if (opt.pDelay == -1) + CheckRadioButton(hdlg, IDC_PD1, IDC_PD3, IDC_PD2); + else if (opt.pDelay == 0) + CheckRadioButton(hdlg, IDC_PD1, IDC_PD3, IDC_PD1); + else + CheckRadioButton(hdlg, IDC_PD1, IDC_PD3, IDC_PD3); + //Colours. First step is configuring the colours. + SendDlgItemMessage(hdlg, IDC_BGCOLOUR, CPM_SETCOLOUR, 0, opt.BGColour); + SendDlgItemMessage(hdlg, IDC_TEXTCOLOUR, CPM_SETCOLOUR, 0, opt.TextColour); + //Second step is disabling them if we want to use default Windows ones. + CheckDlgButton(hdlg, IDC_USEWINCOLORS, opt.UseWinColors ? BST_CHECKED : BST_UNCHECKED); + EnableWindow(GetDlgItem(hdlg, IDC_BGCOLOUR), !opt.UseWinColors); + EnableWindow(GetDlgItem(hdlg, IDC_TEXTCOLOUR), !opt.UseWinColors); + + // buttons + SendDlgItemMessage(hdlg, IDC_PREVIEW, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_PDEF, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_LeftClick, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_RightClick, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hdlg, IDC_VAR3, BUTTONSETASFLATBTN, TRUE, 0); + return TRUE; + + case WM_COMMAND: + // enable the "apply" button + if (HIWORD(wParam) == BN_CLICKED && GetFocus() == (HWND)lParam) + SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + if (!((LOWORD(wParam) == IDC_UPDATE || LOWORD(wParam) == IDC_DEGREE) && + (HIWORD(wParam) != EN_CHANGE || (HWND)lParam != GetFocus()))) + SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + //These are simple clicks: we don't save, but we tell the Options Page to enable the "Apply" button. + switch (LOWORD(wParam)) { + case IDC_BGCOLOUR: //Fall through + case IDC_TEXTCOLOUR: + // select new colors + if (HIWORD(wParam) == CPN_COLOURCHANGED) + SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + break; + + case IDC_USEWINCOLORS: + // use window color - enable/disable color selection controls + EnableWindow(GetDlgItem(hdlg, IDC_BGCOLOUR), !(opt.UseWinColors)); + EnableWindow(GetDlgItem(hdlg, IDC_TEXTCOLOUR), !(opt.UseWinColors)); + SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + break; + + case IDC_E: + case IDC_CH: + SendMessage(GetParent(hdlg), PSM_CHANGED, 0, 0); + break; + + case IDC_RightClick: + // right click action selection menu + button = GetDlgItem(hdlg, IDC_RightClick); + GetWindowRect(button, &pos); + + hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_PMENU)); + hMenu1 = GetSubMenu(hMenu, 0); + TranslateMenu(hMenu1); + SelectMenuItem(hMenu1, opt.RightClickAction); + ID = TrackPopupMenu(hMenu1, TPM_LEFTBUTTON | TPM_RETURNCMD, pos.left, pos.bottom, 0, hdlg, nullptr); + if (ID) + opt.RightClickAction = ID; + DestroyMenu(hMenu); + + hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_PMENU)); + hMenu1 = GetSubMenu(hMenu, 0); + GetMenuString(hMenu1, opt.RightClickAction, str, _countof(str), MF_BYCOMMAND); + SetDlgItemText(hdlg, IDC_RightClick, TranslateW(str)); + DestroyMenu(hMenu); + break; + + case IDC_LeftClick: + // left click action selection menu + button = GetDlgItem(hdlg, IDC_LeftClick); + GetWindowRect(button, &pos); + + hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_PMENU)); + hMenu1 = GetSubMenu(hMenu, 0); + TranslateMenu(hMenu1); + SelectMenuItem(hMenu1, opt.LeftClickAction); + ID = TrackPopupMenu(hMenu1, TPM_LEFTBUTTON | TPM_RETURNCMD, pos.left, pos.bottom, 0, hdlg, nullptr); + if (ID) opt.LeftClickAction = ID; + DestroyMenu(hMenu); + + hMenu = LoadMenu(g_plugin.getInst(), MAKEINTRESOURCE(IDR_PMENU)); + hMenu1 = GetSubMenu(hMenu, 0); + GetMenuString(hMenu1, opt.LeftClickAction, str, _countof(str), MF_BYCOMMAND); + SetDlgItemText(hdlg, IDC_LeftClick, TranslateW(str)); + DestroyMenu(hMenu); + break; + + case IDC_PD1: + // Popup delay setting from Popup plugin + SetDlgItemText(hdlg, IDC_DELAY, L"0"); + CheckRadioButton(hdlg, IDC_PD1, IDC_PD3, IDC_PD1); + break; + + case IDC_PD2: + // Popup delay = permanent + SetDlgItemText(hdlg, IDC_DELAY, L"-1"); + CheckRadioButton(hdlg, IDC_PD1, IDC_PD3, IDC_PD2); + break; + + case IDC_DELAY: + // if text is edited + CheckRadioButton(hdlg, IDC_PD1, IDC_PD3, IDC_PD3); + break; + + case IDC_PDEF: + // set the default value for popup texts + SetTextDefault("Pp"); + SetDlgItemText(hdlg, IDC_PText, opt.pText); + SetDlgItemText(hdlg, IDC_PTitle, opt.pTitle); + wfree(&opt.pText); + wfree(&opt.pTitle); + break; + + case IDC_VAR3: + // display variable list + wcsncpy(str, L" \n", _countof(str) - 1); // to make the message box wider + mir_wstrncat(str, VAR_LIST_POPUP, _countof(str) - mir_wstrlen(str)); + mir_wstrncat(str, L"\n", _countof(str) - mir_wstrlen(str)); + mir_wstrncat(str, CUSTOM_VARS, _countof(str) - mir_wstrlen(str)); + MessageBox(nullptr, str, TranslateT("Variable List"), MB_OK | MB_ICONASTERISK | MB_TOPMOST); + break; + + case IDC_PREVIEW: + // popup preview + hContact = opt.DefStn; + ReadPopupOpt(hdlg); // read new options to memory + WeatherPopup((WPARAM)opt.DefStn, (BOOL)TRUE); // display popup using new opt + DestroyOptions(); + LoadOptions(); // restore old option in memory + opt.DefStn = hContact; + break; + } + break; + + case WM_NOTIFY: //Here we have pressed either the OK or the APPLY button. + switch (((LPNMHDR)lParam)->code) { + case PSN_APPLY: + ReadPopupOpt(hdlg); + + // save the options, and update main menu + SaveOptions(); + UpdatePopupMenu(opt.UsePopup); + return TRUE; + } + break; + } + return FALSE; +} diff --git a/protocols/Weather/src/weather_svcs.cpp b/protocols/Weather/src/weather_svcs.cpp new file mode 100644 index 0000000000..7c3d2611aa --- /dev/null +++ b/protocols/Weather/src/weather_svcs.cpp @@ -0,0 +1,375 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* +This file contain the source related to weather protocol services +as required for a Miranda protocol. Also, it contains functions for +building/changing the weather menu items. +*/ + +#include "stdafx.h" + +static HGENMENU hEnableDisablePopupMenu; +static HGENMENU hEnableDisableMenu; + +//============ MIRANDA PROTOCOL SERVICES ============ + +// protocol service function for setting weather protocol status +INT_PTR WeatherSetStatus(WPARAM new_status, LPARAM) +{ + new_status = new_status != ID_STATUS_OFFLINE ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE; + + // if we don't want to show status for default station + if (!opt.NoProtoCondition && status != new_status) { + old_status = status; + status = new_status != ID_STATUS_OFFLINE ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE; + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, status); + + UpdateMenu(new_status != ID_STATUS_OFFLINE); + if (new_status != ID_STATUS_OFFLINE) + UpdateAll(FALSE, FALSE); + } + + return 0; +} + +// get capabilities protocol service function +INT_PTR WeatherGetCaps(WPARAM wParam, LPARAM) +{ + INT_PTR ret = 0; + + switch (wParam) { + case PFLAGNUM_1: + // support search and visible list + ret = PF1_BASICSEARCH | PF1_ADDSEARCHRES | PF1_EXTSEARCH | PF1_VISLIST | PF1_MODEMSGRECV; + break; + + case PFLAGNUM_2: + ret = PF2_ONLINE | PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | + PF2_HEAVYDND | PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE; + break; + + case PFLAGNUM_4: + ret = PF4_AVATARS | PF4_NOCUSTOMAUTH | PF4_NOAUTHDENYREASON | PF4_FORCEAUTH; + break; + + case PFLAGNUM_5: /* this is PFLAGNUM_5 change when alpha SDK is released */ + ret = PF2_INVISIBLE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | + PF2_FREECHAT | PF2_OUTTOLUNCH | PF2_ONTHEPHONE; + break; + + case PFLAG_UNIQUEIDTEXT: + ret = (INT_PTR)Translate("Station ID"); + break; + } + return ret; +} + +// protocol service function to get weather protocol name +INT_PTR WeatherGetName(WPARAM wParam, LPARAM lParam) +{ + strncpy((char*)lParam, WEATHERPROTOTEXT, wParam - 1); + *((char*)lParam + wParam - 1) = 0; + return 0; +} + +// protocol service function to get the current status of the protocol +INT_PTR WeatherGetStatus(WPARAM, LPARAM) +{ + return status; +} + +// protocol service function to get the icon of the protocol +INT_PTR WeatherLoadIcon(WPARAM wParam, LPARAM) +{ + return (LOWORD(wParam) == PLI_PROTOCOL) ? (INT_PTR)CopyIcon(LoadIconEx("main", FALSE)) : 0; +} + +static void __cdecl AckThreadProc(HANDLE param) +{ + Sleep(100); + ProtoBroadcastAck(MODULENAME, (DWORD_PTR)param, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1); +} + +// nothing to do here because weather proto do not need to retrieve contact info form network +// so just return a 0 +INT_PTR WeatherGetInfo(WPARAM, LPARAM lParam) +{ + CCSDATA *ccs = (CCSDATA *)lParam; + mir_forkthread(AckThreadProc, (void*)ccs->hContact); + return 0; +} + +// avatars +static const wchar_t *statusStr[] = { L"Light", L"Fog", L"SShower", L"Snow", L"RShower", L"Rain", L"PCloudy", L"Cloudy", L"Sunny", L"NA" }; +static const WORD statusValue[] = { LIGHT, FOG, SSHOWER, SNOW, RSHOWER, RAIN, PCLOUDY, CLOUDY, SUNNY, NA }; + +INT_PTR WeatherGetAvatarInfo(WPARAM, LPARAM lParam) +{ + wchar_t szSearchPath[MAX_PATH], *chop; + unsigned i; + PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION*)lParam; + + GetModuleFileName(GetModuleHandle(nullptr), szSearchPath, _countof(szSearchPath)); + chop = wcsrchr(szSearchPath, '\\'); + + if (chop) *chop = '\0'; + else szSearchPath[0] = 0; + + int iStatus = g_plugin.getWord(pai->hContact, "StatusIcon"); + for (i = 0; i < 10; i++) + if (statusValue[i] == iStatus) + break; + + if (i >= 10) + return GAIR_NOAVATAR; + + pai->format = PA_FORMAT_PNG; + mir_snwprintf(pai->filename, L"%s\\Plugins\\Weather\\%s.png", szSearchPath, statusStr[i]); + if (_waccess(pai->filename, 4) == 0) + return GAIR_SUCCESS; + + pai->format = PA_FORMAT_GIF; + mir_snwprintf(pai->filename, L"%s\\Plugins\\Weather\\%s.gif", szSearchPath, statusStr[i]); + if (_waccess(pai->filename, 4) == 0) + return GAIR_SUCCESS; + + pai->format = PA_FORMAT_UNKNOWN; + pai->filename[0] = 0; + return GAIR_NOAVATAR; +} + +void AvatarDownloaded(MCONTACT hContact) +{ + PROTO_AVATAR_INFORMATION ai = { 0 }; + ai.hContact = hContact; + + if (WeatherGetAvatarInfo(GAIF_FORCE, (LPARAM)&ai) == GAIR_SUCCESS) + ProtoBroadcastAck(MODULENAME, hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &ai); + else + ProtoBroadcastAck(MODULENAME, hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, nullptr); +} + +static void __cdecl WeatherGetAwayMsgThread(void *arg) +{ + Sleep(100); + + MCONTACT hContact = (DWORD_PTR)arg; + DBVARIANT dbv; + if (!db_get_ws(hContact, "CList", "StatusMsg", &dbv)) { + ProtoBroadcastAck(MODULENAME, hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1, (LPARAM)dbv.pwszVal); + db_free(&dbv); + } + else ProtoBroadcastAck(MODULENAME, hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)1); +} + +static INT_PTR WeatherGetAwayMsg(WPARAM, LPARAM lParam) +{ + CCSDATA* ccs = (CCSDATA*)lParam; + if (ccs == nullptr) + return 0; + + mir_forkthread(WeatherGetAwayMsgThread, (void*)ccs->hContact); + return 1; +} + +//============ PROTOCOL INITIALIZATION ============ +// protocol services +void InitServices(void) +{ + CreateProtoServiceFunction(MODULENAME, PS_GETCAPS, WeatherGetCaps); + CreateProtoServiceFunction(MODULENAME, PS_GETNAME, WeatherGetName); + CreateProtoServiceFunction(MODULENAME, PS_LOADICON, WeatherLoadIcon); + CreateProtoServiceFunction(MODULENAME, PS_SETSTATUS, WeatherSetStatus); + CreateProtoServiceFunction(MODULENAME, PS_GETSTATUS, WeatherGetStatus); + CreateProtoServiceFunction(MODULENAME, PS_BASICSEARCH, WeatherBasicSearch); + CreateProtoServiceFunction(MODULENAME, PS_SEARCHBYEMAIL, WeatherBasicSearch); + CreateProtoServiceFunction(MODULENAME, PS_ADDTOLIST, WeatherAddToList); + CreateProtoServiceFunction(MODULENAME, PSS_GETINFO, WeatherGetInfo); + CreateProtoServiceFunction(MODULENAME, PS_GETAVATARINFO, WeatherGetAvatarInfo); + CreateProtoServiceFunction(MODULENAME, PSS_GETAWAYMSG, WeatherGetAwayMsg); + CreateProtoServiceFunction(MODULENAME, PS_CREATEADVSEARCHUI, WeatherCreateAdvancedSearchUI); + CreateProtoServiceFunction(MODULENAME, PS_SEARCHBYADVANCED, WeatherAdvancedSearch); + + CreateProtoServiceFunction(MODULENAME, MS_WEATHER_GETDISPLAY, GetDisplaySvcFunc); +} + +//============ MENU INITIALIZATION ============ + +void UpdateMenu(BOOL State) +{ + // update option setting + opt.CAutoUpdate = State; + g_plugin.setByte("AutoUpdate", (BYTE)State); + + if (State) { // to enable auto-update + Menu_ModifyItem(hEnableDisableMenu, LPGENW("Auto Update Enabled"), GetIconHandle("main")); + opt.AutoUpdate = 1; + } + else { // to disable auto-update + Menu_ModifyItem(hEnableDisableMenu, LPGENW("Auto Update Disabled"), GetIconHandle("disabled")); + opt.AutoUpdate = 0; + } + + CallService(MS_TTB_SETBUTTONSTATE, (WPARAM)hTBButton, !State ? TTBST_PUSHED : 0); +} + +void UpdatePopupMenu(BOOL State) +{ + // update option setting + opt.UsePopup = State; + g_plugin.setByte("UsePopup", (BYTE)opt.UsePopup); + + if (State) // to enable popup + Menu_ModifyItem(hEnableDisablePopupMenu, LPGENW("Disable &weather notification"), GetIconHandle("popup")); + else // to disable popup + Menu_ModifyItem(hEnableDisablePopupMenu, LPGENW("Enable &weather notification"), GetIconHandle("nopopup")); +} + +// update the weather auto-update menu item when click on it +INT_PTR EnableDisableCmd(WPARAM wParam, LPARAM lParam) +{ + UpdateMenu(wParam == TRUE ? (BOOL)lParam : !opt.CAutoUpdate); + return 0; +} + +// update the weather popup menu item when click on it +INT_PTR MenuitemNotifyCmd(WPARAM, LPARAM) +{ + UpdatePopupMenu(!opt.UsePopup); + return 0; +} + +// adding weather contact menus +// copied and modified form "modified MSN Protocol" +void AddMenuItems(void) +{ + CMenuItem mi(&g_plugin); + + // contact menu + SET_UID(mi, 0x266ef52b, 0x869a, 0x4cac, 0xa9, 0xf8, 0xea, 0x5b, 0xb8, 0xab, 0xe0, 0x24); + CreateServiceFunction(MS_WEATHER_UPDATE, UpdateSingleStation); + mi.position = -0x7FFFFFFA; + mi.hIcolibItem = GetIconHandle("update"); + mi.name.a = LPGEN("Update Weather"); + mi.pszService = MS_WEATHER_UPDATE; + Menu_AddContactMenuItem(&mi, MODULENAME); + + SET_UID(mi, 0x45361b4, 0x8de, 0x44b4, 0x8f, 0x11, 0x9b, 0xe9, 0x6e, 0xa8, 0x83, 0x54); + CreateServiceFunction(MS_WEATHER_REFRESH, UpdateSingleRemove); + mi.position = -0x7FFFFFF9; + mi.hIcolibItem = GetIconHandle("update2"); + mi.name.a = LPGEN("Remove Old Data then Update"); + mi.pszService = MS_WEATHER_REFRESH; + Menu_AddContactMenuItem(&mi, MODULENAME); + + SET_UID(mi, 0x4232975e, 0xb181, 0x46a5, 0xb7, 0x6e, 0xd2, 0x5f, 0xef, 0xb8, 0xc4, 0x4d); + CreateServiceFunction(MS_WEATHER_BRIEF, BriefInfoSvc); + mi.position = -0x7FFFFFF8; + mi.hIcolibItem = GetIconHandle("brief"); + mi.name.a = LPGEN("Brief Information"); + mi.pszService = MS_WEATHER_BRIEF; + Menu_AddContactMenuItem(&mi, MODULENAME); + + SET_UID(mi, 0x3d6ed729, 0xd49a, 0x4ae9, 0x8e, 0x2, 0x9f, 0xe0, 0xf0, 0x2c, 0xcc, 0xb1); + CreateServiceFunction(MS_WEATHER_COMPLETE, LoadForecast); + mi.position = -0x7FFFFFF7; + mi.hIcolibItem = GetIconHandle("read"); + mi.name.a = LPGEN("Read Complete Forecast"); + mi.pszService = MS_WEATHER_COMPLETE; + Menu_AddContactMenuItem(&mi, MODULENAME); + + SET_UID(mi, 0xc4b6c5e0, 0x13c3, 0x4e02, 0x8a, 0xeb, 0xeb, 0x8a, 0xe2, 0x66, 0x40, 0xd4); + CreateServiceFunction(MS_WEATHER_MAP, WeatherMap); + mi.position = -0x7FFFFFF6; + mi.hIcolibItem = GetIconHandle("map"); + mi.name.a = LPGEN("Weather Map"); + mi.pszService = MS_WEATHER_MAP; + Menu_AddContactMenuItem(&mi, MODULENAME); + + SET_UID(mi, 0xee3ad7f4, 0x3377, 0x4e4c, 0x8f, 0x3c, 0x3b, 0xf5, 0xd4, 0x86, 0x28, 0x25); + CreateServiceFunction(MS_WEATHER_LOG, ViewLog); + mi.position = -0x7FFFFFF5; + mi.hIcolibItem = GetIconHandle("log"); + mi.name.a = LPGEN("View Log"); + mi.pszService = MS_WEATHER_LOG; + Menu_AddContactMenuItem(&mi, MODULENAME); + + SET_UID(mi, 0x1b01cd6a, 0xe5ee, 0x42b4, 0xa1, 0x6d, 0x43, 0xb9, 0x4, 0x58, 0x43, 0x2e); + CreateServiceFunction(MS_WEATHER_EDIT, EditSettings); + mi.position = -0x7FFFFFF4; + mi.hIcolibItem = GetIconHandle("edit"); + mi.name.a = LPGEN("Edit Settings"); + mi.pszService = MS_WEATHER_EDIT; + Menu_AddContactMenuItem(&mi, MODULENAME); + + // adding main menu items + mi.root = g_plugin.addRootMenu(MO_MAIN, LPGENW("Weather"), 500099000); + Menu_ConfigureItem(mi.root, MCI_OPT_UID, "82809D2F-2CF0-4E15-9350-D257A7748552"); + + SET_UID(mi, 0x5ad16188, 0xe0a0, 0x4c31, 0x85, 0xc3, 0xe4, 0x85, 0x79, 0x7e, 0x4b, 0x9c); + CreateServiceFunction(MS_WEATHER_ENABLED, EnableDisableCmd); + mi.name.a = LPGEN("Enable/Disable Weather Update"); + mi.hIcolibItem = GetIconHandle("main"); + mi.position = 10100001; + mi.pszService = MS_WEATHER_ENABLED; + hEnableDisableMenu = Menu_AddMainMenuItem(&mi); + UpdateMenu(opt.AutoUpdate); + + SET_UID(mi, 0x2b1c2054, 0x2991, 0x4025, 0x87, 0x73, 0xb6, 0xf7, 0x85, 0xac, 0xc7, 0x37); + CreateServiceFunction(MS_WEATHER_UPDATEALL, UpdateAllInfo); + mi.position = 20100001; + mi.hIcolibItem = GetIconHandle("update"); + mi.name.a = LPGEN("Update All Weather"); + mi.pszService = MS_WEATHER_UPDATEALL; + Menu_AddMainMenuItem(&mi); + + SET_UID(mi, 0x8234c00e, 0x788e, 0x424f, 0xbc, 0xc4, 0x2, 0xfd, 0x67, 0x58, 0x2d, 0x19); + CreateServiceFunction(MS_WEATHER_REFRESHALL, UpdateAllRemove); + mi.position = 20100002; + mi.hIcolibItem = GetIconHandle("update2"); + mi.name.a = LPGEN("Remove Old Data then Update All"); + mi.pszService = MS_WEATHER_REFRESHALL; + Menu_AddMainMenuItem(&mi); + + // only run if popup service exists + if (ServiceExists(MS_POPUP_ADDPOPUPW)) { + SET_UID(mi, 0xdc5411cb, 0xb7c7, 0x443b, 0x88, 0x5a, 0x90, 0x24, 0x43, 0xde, 0x54, 0x3e); + CreateServiceFunction(MODULENAME "/PopupMenu", MenuitemNotifyCmd); + mi.name.a = LPGEN("Weather Notification"); + mi.hIcolibItem = GetIconHandle("popup"); + mi.position = 0; + mi.root = g_plugin.addRootMenu(MO_MAIN, LPGENW("Popups"), 0); + mi.pszService = MODULENAME "/PopupMenu"; + hEnableDisablePopupMenu = Menu_AddMainMenuItem(&mi); + UpdatePopupMenu(opt.UsePopup); + } + + if (ServiceExists(MS_CLIST_FRAMES_ADDFRAME)) { + SET_UID(mi, 0xe193fe9b, 0xf6ad, 0x41ac, 0x95, 0x29, 0x45, 0x4, 0x44, 0xb1, 0xeb, 0x5d); + mi.pszService = "Weather/mwin_menu"; + CreateServiceFunction(mi.pszService, Mwin_MenuClicked); + mi.position = -0x7FFFFFF0; + mi.hIcolibItem = nullptr; + mi.root = nullptr; + mi.name.a = LPGEN("Display in a frame"); + hMwinMenu = Menu_AddContactMenuItem(&mi, MODULENAME); + } +} diff --git a/protocols/Weather/src/weather_update.cpp b/protocols/Weather/src/weather_update.cpp new file mode 100644 index 0000000000..12736b35be --- /dev/null +++ b/protocols/Weather/src/weather_update.cpp @@ -0,0 +1,607 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +/* +This file contain the source related to updating new weather +information, both automatic (by timer) and manually (by selecting +menu items). +*/ + +#include "stdafx.h" + +UPDATELIST *UpdateListHead, *UpdateListTail; + +//============ RETRIEVE NEW WEATHER ============ +// +// retrieve weather info and display / log them +// hContact = current contact +int UpdateWeather(MCONTACT hContact) +{ + wchar_t str[256], str2[MAX_TEXT_SIZE]; + DBVARIANT dbv; + BOOL Ch = FALSE; + + if (hContact == NULL) // some error prevention + return 1; + + dbv.pszVal = ""; + + // log to netlib log for debug purpose + Netlib_LogfW(hNetlibUser, L"************************************************************************"); + int dbres = g_plugin.getWString(hContact, "Nick", &dbv); + + Netlib_LogfW(hNetlibUser, L"<-- Start update for station -->"); + + // download the info and parse it + // result are stored in database + int code = GetWeatherData(hContact); + if (code != 0) { + // error occurs if the return value is not equals to 0 + if (opt.ShowWarnings) { + // show warnings by popup + mir_snwprintf(str, _countof(str) - 105, + TranslateT("Unable to retrieve weather information for %s"), dbv.pwszVal); + mir_wstrncat(str, L"\n", _countof(str) - mir_wstrlen(str)); + wchar_t *tszError = GetError(code); + mir_wstrncat(str, tszError, _countof(str) - mir_wstrlen(str)); + WPShowMessage(str, SM_WARNING); + mir_free(tszError); + } + // log to netlib + Netlib_LogfW(hNetlibUser, L"Error! Update cannot continue... Start to free memory"); + Netlib_LogfW(hNetlibUser, L"<-- Error occurs while updating station: %s -->", dbv.pwszVal); + if (!dbres) db_free(&dbv); + return 1; + } + if (!dbres) db_free(&dbv); + + // initialize, load new weather Data + WEATHERINFO winfo = LoadWeatherInfo(hContact); + + // translate weather condition + mir_wstrcpy(winfo.cond, TranslateW(winfo.cond)); + + // compare the old condition and determine if the weather had changed + if (opt.UpdateOnlyConditionChanged) { // consider condition change + if (!g_plugin.getWString(hContact, "LastCondition", &dbv)) { + if (mir_wstrcmpi(winfo.cond, dbv.pwszVal)) Ch = TRUE; // the weather condition is changed + db_free(&dbv); + } + else Ch = TRUE; + if (!g_plugin.getWString(hContact, "LastTemperature", &dbv)) { + if (mir_wstrcmpi(winfo.temp, dbv.pwszVal)) Ch = TRUE; // the temperature is changed + db_free(&dbv); + } + else Ch = TRUE; + } + else { // consider update time change + if (!g_plugin.getWString(hContact, "LastUpdate", &dbv)) { + if (mir_wstrcmpi(winfo.update, dbv.pwszVal)) Ch = TRUE; // the update time is changed + db_free(&dbv); + } + else Ch = TRUE; + } + + // have weather alert issued? + dbres = db_get_ws(hContact, WEATHERCONDITION, "Alert", &dbv); + if (!dbres && dbv.pwszVal[0] != 0) { + if (opt.AlertPopup && !g_plugin.getByte(hContact, "DPopUp") && Ch) { + // display alert popup + mir_snwprintf(str, L"Alert for %s%c%s", winfo.city, 255, dbv.pwszVal); + WPShowMessage(str, SM_WEATHERALERT); + } + // alert issued, set display to italic + if (opt.MakeItalic) + g_plugin.setWord(hContact, "ApparentMode", ID_STATUS_OFFLINE); + Skin_PlaySound("weatheralert"); + } + // alert dropped, set the display back to normal + else g_plugin.delSetting(hContact, "ApparentMode"); + if (!dbres) db_free(&dbv); + + // backup current condition for checking if the weather is changed or not + g_plugin.setWString(hContact, "LastLog", winfo.update); + g_plugin.setWString(hContact, "LastCondition", winfo.cond); + g_plugin.setWString(hContact, "LastTemperature", winfo.temp); + g_plugin.setWString(hContact, "LastUpdate", winfo.update); + + // display condition on contact list + if (opt.DisCondIcon && winfo.status != ID_STATUS_OFFLINE) + g_plugin.setWord(hContact, "Status", ID_STATUS_ONLINE); + else + g_plugin.setWord(hContact, "Status", winfo.status); + AvatarDownloaded(hContact); + + GetDisplay(&winfo, opt.cText, str2); + db_set_ws(hContact, "CList", "MyHandle", str2); + + GetDisplay(&winfo, opt.sText, str2); + if (str2[0]) + db_set_ws(hContact, "CList", "StatusMsg", str2); + else + db_unset(hContact, "CList", "StatusMsg"); + + ProtoBroadcastAck(MODULENAME, hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, nullptr, (LPARAM)(str2[0] ? str2 : nullptr)); + + // save descriptions in MyNotes + GetDisplay(&winfo, opt.nText, str2); + db_set_ws(hContact, "UserInfo", "MyNotes", str2); + GetDisplay(&winfo, opt.xText, str2); + db_set_ws(hContact, WEATHERCONDITION, "WeatherInfo", str2); + + // set the update tag + g_plugin.setByte(hContact, "IsUpdated", TRUE); + + // save info for default weather condition + if (!mir_wstrcmp(winfo.id, opt.Default) && !opt.NoProtoCondition) { + // save current condition for default station to be displayed after the update + old_status = status; + status = winfo.status; + // a workaround for a default station that currently have an n/a icon assigned + if (status == ID_STATUS_OFFLINE) status = NOSTATUSDATA; + ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, status); + } + + // logging + if (Ch) { + // play the sound event + Skin_PlaySound("weatherupdated"); + + if (g_plugin.getByte(hContact, "File")) { + // external log + if (!g_plugin.getWString(hContact, "Log", &dbv)) { + // for the option for overwriting the file, delete old file first + if (g_plugin.getByte(hContact, "Overwrite")) + DeleteFile(dbv.pwszVal); + + // open the file and set point to the end of file + FILE *file = _wfopen(dbv.pwszVal, L"a"); + db_free(&dbv); + if (file != nullptr) { + // write data to the file and close + GetDisplay(&winfo, opt.eText, str2); + fputws(str2, file); + fclose(file); + } + } + } + + if (g_plugin.getByte(hContact, "History")) { + // internal log using history + GetDisplay(&winfo, opt.hText, str2); + + T2Utf szMessage(str2); + + DBEVENTINFO dbei = {}; + dbei.szModule = MODULENAME; + dbei.timestamp = (DWORD)time(0); + dbei.flags = DBEF_READ | DBEF_UTF; + dbei.eventType = EVENTTYPE_MESSAGE; + dbei.pBlob = szMessage; + dbei.cbBlob = (DWORD)mir_strlen(szMessage) + 1; + db_event_add(hContact, &dbei); + } + + // show the popup + NotifyEventHooks(hHookWeatherUpdated, hContact, (LPARAM)Ch); + } + + Netlib_LogfW(hNetlibUser, L"Update Completed - Start to free memory"); + Netlib_LogfW(hNetlibUser, L"<-- Update successful for station -->"); + + // Update frame data + UpdateMwinData(hContact); + + // update brief info if its opened + HWND hMoreDataDlg = WindowList_Find(hDataWindowList, hContact); + if (hMoreDataDlg != nullptr) + PostMessage(hMoreDataDlg, WM_UPDATEDATA, 0, 0); + return 0; +} + +//============ UPDATE LIST ============ +// +// a linked list queue for updating weather station +// this function add a weather contact to the end of queue for update +// hContact = current contact +void UpdateListAdd(MCONTACT hContact) +{ + UPDATELIST *newItem = (UPDATELIST*)mir_alloc(sizeof(UPDATELIST)); + newItem->hContact = hContact; + newItem->next = nullptr; + + WaitForSingleObject(hUpdateMutex, INFINITE); + + if (UpdateListTail == nullptr) UpdateListHead = newItem; + else UpdateListTail->next = newItem; + UpdateListTail = newItem; + + ReleaseMutex(hUpdateMutex); +} + +// get the first item from the update queue and remove it from the queue +// return value = the contact for next update +MCONTACT UpdateGetFirst() +{ + MCONTACT hContact = NULL; + + WaitForSingleObject(hUpdateMutex, INFINITE); + + if (UpdateListHead != nullptr) { + UPDATELIST* Item = UpdateListHead; + + hContact = Item->hContact; + UpdateListHead = Item->next; + mir_free(Item); + + if (UpdateListHead == nullptr) + UpdateListTail = nullptr; + } + + ReleaseMutex(hUpdateMutex); + + return hContact; +} + +void DestroyUpdateList(void) +{ + WaitForSingleObject(hUpdateMutex, INFINITE); + + // free the list one by one + UPDATELIST *temp = UpdateListHead; + while (temp != nullptr) { + UpdateListHead = temp->next; + mir_free(temp); + temp = UpdateListHead; + } + // make sure the entire list is clear + UpdateListTail = nullptr; + + ReleaseMutex(hUpdateMutex); +} + +// update all weather thread +// this thread update each weather station from the queue +static void UpdateThreadProc(void *) +{ + WaitForSingleObject(hUpdateMutex, INFINITE); + if (ThreadRunning) { + ReleaseMutex(hUpdateMutex); + return; + } + ThreadRunning = TRUE; // prevent 2 instance of this thread running + ReleaseMutex(hUpdateMutex); + + // update weather by getting the first station from the queue until the queue is empty + while (UpdateListHead != nullptr && !Miranda_IsTerminated()) + UpdateWeather(UpdateGetFirst()); + + // exit the update thread + ThreadRunning = FALSE; +} + +//============ UPDATE WEATHER ============ +// +// update all weather station +// AutoUpdate = true if it is from automatic update using timer +// false if it is from update by clicking the main menu +void UpdateAll(BOOL AutoUpdate, BOOL RemoveData) +{ + // add all weather contact to the update queue list + for (auto &hContact : Contacts(MODULENAME)) + if (!g_plugin.getByte(hContact, "AutoUpdate") || !AutoUpdate) { + if (RemoveData) + DBDataManage(hContact, WDBM_REMOVE, 0, 0); + UpdateListAdd(hContact); + } + + // if it is not updating, then start the update thread process + // if it is updating, the stations just added to the queue will get updated by the already-running process + if (!ThreadRunning) + mir_forkthread(UpdateThreadProc); +} + +// update a single station +// wParam = handle for the weather station that is going to be updated +INT_PTR UpdateSingleStation(WPARAM wParam, LPARAM) +{ + if (IsMyContact(wParam)) { + // add the station to the end of the update queue + UpdateListAdd(wParam); + + // if it is not updating, then start the update thread process + // if it is updating, the stations just added to the queue will get + // updated by the already-running process + if (!ThreadRunning) + mir_forkthread(UpdateThreadProc); + } + + return 0; +} + +// update a single station with removing the old data +// wParam = handle for the weather station that is going to be updated +INT_PTR UpdateSingleRemove(WPARAM wParam, LPARAM) +{ + if (IsMyContact(wParam)) { + // add the station to the end of the update queue, and also remove old data + DBDataManage(wParam, WDBM_REMOVE, 0, 0); + UpdateListAdd(wParam); + + // if it is not updating, then start the update thread process + // if it is updating, the stations just added to the queue will get updated by the already-running process + if (!ThreadRunning) + mir_forkthread(UpdateThreadProc); + } + + return 0; +} + +// the "Update All" menu item in main menu +INT_PTR UpdateAllInfo(WPARAM, LPARAM) +{ + if (!ThreadRunning) + UpdateAll(FALSE, FALSE); + return 0; +} + +// the "Update All" menu item in main menu and remove the old data +INT_PTR UpdateAllRemove(WPARAM, LPARAM) +{ + if (!ThreadRunning) + UpdateAll(FALSE, TRUE); + return 0; +} + +//============ GETTING WEATHER DATA ============ +// +// getting weather data and save them into the database +// hContact = the contact to get the data +int GetWeatherData(MCONTACT hContact) +{ + // get each part of the id's + wchar_t id[256]; + GetStationID(hContact, id, _countof(id)); + + // test ID format + wchar_t* szInfo = wcschr(id, '/'); + if (szInfo == nullptr) + return INVALID_ID_FORMAT; + + GetID(id); + + wchar_t Svc[256]; + GetStationID(hContact, Svc, _countof(Svc)); + GetSvc(Svc); + + // check for invalid station + if (id[0] == 0) return INVALID_ID; + if (Svc[0] == 0) return INVALID_SVC; + + // get the update strings (loaded to memory from ini files) + WIDATA *Data = GetWIData(Svc); + if (Data == nullptr) + return SVC_NOT_FOUND; // the ini for the station cannot be found + + WORD cond = NA; + char loc[256]; + for (int i = 0; i < 4; ++i) { + // generate update URL + switch (i) { + case 0: + mir_snprintf(loc, Data->UpdateURL, _T2A(id)); + break; + + case 1: + mir_snprintf(loc, Data->UpdateURL2, _T2A(id)); + break; + + case 2: + mir_snprintf(loc, Data->UpdateURL3, _T2A(id)); + break; + + case 3: + mir_snprintf(loc, Data->UpdateURL4, _T2A(id)); + break; + + default: + continue; + } + + if (loc[0] == 0) + continue; + + // download the html file from the internet + wchar_t* szData = nullptr; + int retval = InternetDownloadFile(loc, Data->Cookie, Data->UserAgent, &szData); + if (retval != 0) { + mir_free(szData); + return retval; + } + if (wcsstr(szData, L"Document Not Found") != nullptr) { + mir_free(szData); + return DOC_NOT_FOUND; + } + + szInfo = szData; + WIDATAITEMLIST *Item = Data->UpdateData; + + // begin parsing item by item + while (Item != nullptr) { + if (Item->Item.Url[0] != 0 && Item->Item.Url[0] != (i + '1')) { + Item = Item->Next; + continue; + } + + wchar_t DataValue[MAX_DATA_LEN]; + switch (Item->Item.Type) { + case WID_NORMAL: + // if it is a normal item with start= and end=, then parse through the downloaded string + // to get a data value. + GetDataValue(&Item->Item, DataValue, &szInfo); + if (mir_wstrcmp(Item->Item.Name, L"Condition") && mir_wstrcmpi(Item->Item.Unit, L"Cond")) + wcsncpy(DataValue, TranslateW(DataValue), MAX_DATA_LEN - 1); + break; + + case WID_SET: + { + // for the "Set Data=" operation + DBVARIANT dbv; + wchar_t *chop, *str, str2[MAX_DATA_LEN]; + BOOL hasvar = FALSE; + size_t stl; + + // get the set data operation string + str = Item->Item.End; + DataValue[0] = 0; + // go through each part of the operation string seperated by the & operator + do { + // the end of the string, last item + chop = wcsstr(str, L" & "); + if (chop == nullptr) + chop = wcschr(str, '\0'); + + stl = min(sizeof(str2) - 1, (unsigned)(chop - str - 2)); + wcsncpy(str2, str + 1, stl); + str2[stl] = 0; + + switch (str[0]) { + case '[': // variable, add the value to the result string + hasvar = TRUE; + if (!DBGetData(hContact, _T2A(str2), &dbv)) { + mir_wstrncat(DataValue, dbv.pwszVal, _countof(DataValue) - mir_wstrlen(DataValue)); + DataValue[_countof(DataValue) - 1] = 0; + db_free(&dbv); + } + break; + + case'\"': // constant, add it to the result string + mir_wstrncat(DataValue, TranslateW(str2), _countof(DataValue) - mir_wstrlen(DataValue)); + DataValue[_countof(DataValue) - 1] = 0; + break; + } + + // remove the front part of the string that is done and continue parsing + str = chop + 3; + } while (chop[0] && str[0]); + + if (!hasvar) ConvertDataValue(&Item->Item, DataValue); + break; + } + case WID_BREAK: + { + // for the "Break Data=" operation + DBVARIANT dbv; + if (!DBGetData(hContact, _T2A(Item->Item.Start), &dbv)) { + wcsncpy(DataValue, dbv.pwszVal, _countof(DataValue)); + DataValue[_countof(DataValue) - 1] = 0; + db_free(&dbv); + } + else { + DataValue[0] = 0; + break; // do not continue if the source is invalid + } + + // generate the strings + wchar_t* end = wcsstr(DataValue, Item->Item.Break); + if (end == nullptr) { + DataValue[0] = 0; + break; // exit if break string is not found + } + *end = '\0'; + end += mir_wstrlen(Item->Item.Break); + while (end[0] == ' ') + end++; // remove extra space + + ConvertDataValue(&Item->Item, DataValue); + + // write the 2 strings created from the break operation + if (Item->Item.End[0]) + db_set_ws(hContact, WEATHERCONDITION, _T2A(Item->Item.End), end); + break; + } + } + + // don't store data if it is not available + if ((DataValue[0] != 0 && mir_wstrcmp(DataValue, NODATA) && + mir_wstrcmp(DataValue, TranslateW(NODATA)) && mir_wstrcmp(Item->Item.Name, L"Ignore")) || + (!mir_wstrcmp(Item->Item.Name, L"Alert") && i == 0)) { + // temporary workaround for mToolTip to show feel-like temperature + if (!mir_wstrcmp(Item->Item.Name, L"Feel")) + db_set_ws(hContact, WEATHERCONDITION, "Heat Index", DataValue); + GetStationID(hContact, Svc, _countof(Svc)); + if (!mir_wstrcmp(Svc, opt.Default)) + db_set_ws(0, DEFCURRENTWEATHER, _T2A(Item->Item.Name), DataValue); + if (!mir_wstrcmp(Item->Item.Name, L"Condition")) { + wchar_t buf[128], *cbuf; + mir_snwprintf(buf, L"#%s Weather", DataValue); + cbuf = TranslateW(buf); + if (cbuf[0] == '#') + cbuf = TranslateW(DataValue); + db_set_ws(hContact, WEATHERCONDITION, _T2A(Item->Item.Name), cbuf); + CharLowerBuff(DataValue, (DWORD)mir_wstrlen(DataValue)); + cond = GetIcon(DataValue, Data); + } + else if (mir_wstrcmpi(Item->Item.Unit, L"Cond") == 0) { + wchar_t buf[128], *cbuf; + mir_snwprintf(buf, L"#%s Weather", DataValue); + cbuf = TranslateW(buf); + if (cbuf[0] == '#') + cbuf = TranslateW(DataValue); + db_set_ws(hContact, WEATHERCONDITION, _T2A(Item->Item.Name), cbuf); + } + else db_set_ws(hContact, WEATHERCONDITION, _T2A(Item->Item.Name), DataValue); + } + Item = Item->Next; + } + mir_free(szData); + } + + // assign condition icon + g_plugin.setWord(hContact, "StatusIcon", cond); + g_plugin.setWString(hContact, "MirVer", Data->DisplayName); + return 0; +} + +//============ UPDATE TIMERS ============ +// +// main auto-update timer +void CALLBACK timerProc(HWND, UINT, UINT_PTR, DWORD) +{ + // only run if it is not current updating and the auto update option is enabled + if (!ThreadRunning && opt.CAutoUpdate && !Miranda_IsTerminated() && (opt.NoProtoCondition || status == ID_STATUS_ONLINE)) + UpdateAll(TRUE, FALSE); +} + + +// temporary timer for first run +// when this is run, it kill the old startup timer and create the permenant one above +void CALLBACK timerProc2(HWND, UINT, UINT_PTR, DWORD) +{ + KillTimer(nullptr, timerId); + ThreadRunning = FALSE; + + if (Miranda_IsTerminated()) + return; + + if (opt.StartupUpdate && opt.NoProtoCondition) + UpdateAll(FALSE, FALSE); + timerId = SetTimer(nullptr, 0, ((int)opt.UpdateTime) * 60000, timerProc); +} diff --git a/protocols/Weather/src/weather_userinfo.cpp b/protocols/Weather/src/weather_userinfo.cpp new file mode 100644 index 0000000000..3ce9460c1b --- /dev/null +++ b/protocols/Weather/src/weather_userinfo.cpp @@ -0,0 +1,358 @@ +/* +Weather Protocol plugin for Miranda IM +Copyright (c) 2012 Miranda NG team +Copyright (c) 2005-2011 Boris Krasnovskiy All Rights Reserved +Copyright (c) 2002-2005 Calvin Che + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + + +/* +This file contain the source that is related to display contact +information, including the one shows in user detail and the brief +information +*/ + +#include "stdafx.h" + +//============ BRIEF INFORMATION ============ +// +static int BriefDlgResizer(HWND, LPARAM, UTILRESIZECONTROL *urc) +{ + switch (urc->wId) { + case IDC_HEADERBAR: + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORX_WIDTH; + + case IDC_MTEXT: + case IDC_DATALIST: + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT; + + case IDC_MUPDATE: + return RD_ANCHORX_LEFT | RD_ANCHORY_BOTTOM; + + case IDC_MTOGGLE: + case IDC_MWEBPAGE: + case IDCANCEL: + return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM; + } + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; +} + +// set the title of the dialog and on the which rectangle +// also load brief info into message box +static void LoadBriefInfoText(HWND hwndDlg, MCONTACT hContact) +{ + WEATHERINFO winfo; + wchar_t str[4096]; + + // load weather information from the contact into the WEATHERINFO struct + winfo = LoadWeatherInfo(hContact); + // check if data exist. If not, display error message box + if (!g_plugin.getByte(hContact, "IsUpdated")) + wcsncpy(str, WEATHER_NO_INFO, _countof(str) - 1); + else + // set the display text and show the message box + GetDisplay(&winfo, opt.bText, str); + SetDlgItemText(hwndDlg, IDC_MTEXT, str); + + GetDisplay(&winfo, L"%c, %t", str); + SetWindowText(hwndDlg, winfo.city); + SetDlgItemText(hwndDlg, IDC_HEADERBAR, str); +} + +// dialog process for more data in the user info window +// lParam = contact handle +static INT_PTR CALLBACK DlgProcMoreData(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static const unsigned tabstops = 48; + MCONTACT hContact = (MCONTACT)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + // save the contact handle for later use + hContact = lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)hContact); + + SendDlgItemMessage(hwndDlg, IDC_MTEXT, EM_AUTOURLDETECT, (WPARAM)TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_MTEXT, EM_SETEVENTMASK, 0, ENM_LINK); + SendDlgItemMessage(hwndDlg, IDC_MTEXT, EM_SETMARGINS, EC_LEFTMARGIN, 5); + SendDlgItemMessage(hwndDlg, IDC_MTEXT, EM_SETTABSTOPS, 1, (LPARAM)&tabstops); + + // get the list to display + { + LV_COLUMN lvc = { 0 }; + HWND hList = GetDlgItem(hwndDlg, IDC_DATALIST); + RECT aRect = { 0 }; + GetClientRect(hList, &aRect); + + // managing styles + lvc.mask = LVCF_WIDTH | LVCF_TEXT; + ListView_SetExtendedListViewStyleEx(hList, + LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP, + LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP); + + // inserting columns + lvc.cx = LIST_COLUMN; + lvc.pszText = TranslateT("Variable"); + ListView_InsertColumn(hList, 0, &lvc); + + lvc.cx = aRect.right - LIST_COLUMN - GetSystemMetrics(SM_CXVSCROLL) - 3; + lvc.pszText = TranslateT("Information"); + ListView_InsertColumn(hList, 1, &lvc); + + // inserting data + SendMessage(hwndDlg, WM_UPDATEDATA, 0, 0); + } + TranslateDialogDefault(hwndDlg); + + // prevent dups of the window + WindowList_Add(hDataWindowList, hwndDlg, hContact); + + // restore window position + Utils_RestoreWindowPositionNoMove(hwndDlg, NULL, MODULENAME, "BriefInfo_"); + return TRUE; + + case WM_UPDATEDATA: + ListView_DeleteAllItems(GetDlgItem(hwndDlg, IDC_DATALIST)); + LoadBriefInfoText(hwndDlg, hContact); + DBDataManage(hContact, WDBM_DETAILDISPLAY, (WPARAM)hwndDlg, 0); + + // set icons + Window_FreeIcon_IcoLib(hwndDlg); + Window_SetProtoIcon_IcoLib(hwndDlg, MODULENAME, g_plugin.getWord(hContact, "StatusIcon", 0)); + + RedrawWindow(GetDlgItem(hwndDlg, IDC_HEADERBAR), nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); + break; + + case WM_SIZE: + { + RECT rc; + HWND hList = GetDlgItem(hwndDlg, IDC_DATALIST); + GetWindowRect(hList, &rc); + ListView_SetColumnWidth(hList, 1, ListView_GetColumnWidth(hList, 1) + (int)LOWORD(lParam) - (rc.right - rc.left)); + + Utils_ResizeDialog(hwndDlg, g_plugin.getInst(), MAKEINTRESOURCEA(IDD_BRIEF), BriefDlgResizer); + } + break; + + case WM_GETMINMAXINFO: + { + LPMINMAXINFO mmi = (LPMINMAXINFO)lParam; + + // The minimum width in points + mmi->ptMinTrackSize.x = 350; + // The minimum height in points + mmi->ptMinTrackSize.y = 300; + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDCANCEL: + // close the info window + DestroyWindow(hwndDlg); + break; + + case IDC_MUPDATE: + { + HWND hList = GetDlgItem(hwndDlg, IDC_DATALIST); + + // update current data + // set the text to "updating" + SetDlgItemText(hwndDlg, IDC_MTEXT, TranslateT("Retrieving new data, please wait...")); + ListView_DeleteAllItems(hList); + + LV_ITEM lvi = { 0 }; + lvi.mask = LVIF_TEXT | LVIF_PARAM; + lvi.lParam = 1; + lvi.pszText = L""; + lvi.iItem = ListView_InsertItem(hList, &lvi); + lvi.pszText = TranslateT("Retrieving new data, please wait..."); + ListView_SetItemText(hList, lvi.iItem, 1, lvi.pszText); + UpdateSingleStation(hContact, 0); + break; + } + + case IDC_MWEBPAGE: + LoadForecast(hContact, 0); // read complete forecast + break; + + case IDC_MTOGGLE: + if (IsWindowVisible(GetDlgItem(hwndDlg, IDC_DATALIST))) + SetDlgItemText(hwndDlg, IDC_MTOGGLE, TranslateT("More Info")); + else + SetDlgItemText(hwndDlg, IDC_MTOGGLE, TranslateT("Brief Info")); + ShowWindow(GetDlgItem(hwndDlg, IDC_DATALIST), (int)!IsWindowVisible( + GetDlgItem(hwndDlg, IDC_DATALIST))); + ShowWindow(GetDlgItem(hwndDlg, IDC_MTEXT), (int)!IsWindowVisible(GetDlgItem(hwndDlg, IDC_MTEXT))); + break; + } + break; + + case WM_NOTIFY: + { + LPNMHDR pNmhdr = (LPNMHDR)lParam; + if (pNmhdr->idFrom == IDC_MTEXT && pNmhdr->code == EN_LINK) { + ENLINK *enlink = (ENLINK *)lParam; + switch (enlink->msg) { + case WM_LBUTTONUP: + TEXTRANGE tr; + tr.chrg = enlink->chrg; + tr.lpstrText = (wchar_t*)mir_alloc(sizeof(wchar_t)*(tr.chrg.cpMax - tr.chrg.cpMin + 8)); + SendMessage(pNmhdr->hwndFrom, EM_GETTEXTRANGE, 0, (LPARAM)&tr); + Utils_OpenUrlW(tr.lpstrText); + mir_free(tr.lpstrText); + break; + } + } + } + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + Window_FreeIcon_IcoLib(hwndDlg); + Utils_SaveWindowPosition(hwndDlg, NULL, MODULENAME, "BriefInfo_"); + WindowList_Remove(hDataWindowList, hwndDlg); + break; + } + + return FALSE; +} + +// dialog process for the weather tab under user info +// lParam = current contact +static INT_PTR CALLBACK DlgProcUIPage(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + WEATHERINFO w; + wchar_t str[MAX_TEXT_SIZE]; + + MCONTACT hContact = (MCONTACT)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SendDlgItemMessage(hwndDlg, IDC_MOREDETAIL, BUTTONSETASFLATBTN, TRUE, 0); + // save the contact handle for later use + hContact = lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)hContact); + // load weather info for the contact + w = LoadWeatherInfo(lParam); + SetDlgItemText(hwndDlg, IDC_INFO1, GetDisplay(&w, TranslateT("Current condition for %n"), str)); + + SendDlgItemMessage(hwndDlg, IDC_INFOICON, STM_SETICON, (WPARAM)Skin_LoadProtoIcon(MODULENAME, g_plugin.getWord(hContact, "StatusIcon")), 0); + { + // bold and enlarge the current condition + LOGFONT lf; + HFONT hNormalFont = (HFONT)SendDlgItemMessage(hwndDlg, IDC_INFO2, WM_GETFONT, 0, 0); + GetObject(hNormalFont, sizeof(lf), &lf); + lf.lfWeight = FW_BOLD; + lf.lfWidth = 7; + lf.lfHeight = 15; + SendDlgItemMessage(hwndDlg, IDC_INFO2, WM_SETFONT, (WPARAM)CreateFontIndirect(&lf), 0); + } + // set the text for displaying other current weather conditions data + GetDisplay(&w, L"%c %t", str); + SetDlgItemText(hwndDlg, IDC_INFO2, str); + SetDlgItemText(hwndDlg, IDC_INFO3, w.feel); + SetDlgItemText(hwndDlg, IDC_INFO4, w.pressure); + GetDisplay(&w, L"%i %w", str); + SetDlgItemText(hwndDlg, IDC_INFO5, str); + SetDlgItemText(hwndDlg, IDC_INFO6, w.dewpoint); + SetDlgItemText(hwndDlg, IDC_INFO7, w.sunrise); + SetDlgItemText(hwndDlg, IDC_INFO8, w.sunset); + SetDlgItemText(hwndDlg, IDC_INFO9, w.high); + SetDlgItemText(hwndDlg, IDC_INFO10, w.low); + GetDisplay(&w, TranslateT("Last update on: %u"), str); + SetDlgItemText(hwndDlg, IDC_INFO11, str); + SetDlgItemText(hwndDlg, IDC_INFO12, w.humid); + SetDlgItemText(hwndDlg, IDC_INFO13, w.vis); + break; + + case WM_DESTROY: + IcoLib_ReleaseIcon((HICON)SendDlgItemMessage(hwndDlg, IDC_INFOICON, STM_SETICON, 0, 0)); + DeleteObject((HFONT)SendDlgItemMessage(hwndDlg, IDC_INFO2, WM_GETFONT, 0, 0)); + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_MOREDETAIL: + HWND hMoreDataDlg = WindowList_Find(hDataWindowList, hContact); + if (hMoreDataDlg == nullptr) + hMoreDataDlg = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_BRIEF), nullptr, DlgProcMoreData, hContact); + else { + SetForegroundWindow(hMoreDataDlg); + SetFocus(hMoreDataDlg); + } + ShowWindow(GetDlgItem(hMoreDataDlg, IDC_MTEXT), 0); + ShowWindow(GetDlgItem(hMoreDataDlg, IDC_DATALIST), 1); + } + break; + } + return 0; +} + +//============ CONTACT INFORMATION ============ +// +// initialize user info +// lParam = current contact +int UserInfoInit(WPARAM wParam, LPARAM hContact) +{ + OPTIONSDIALOGPAGE odp = {}; + odp.position = 100000000; + odp.szTitle.a = MODULENAME; + + if (hContact == 0) { + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO); + odp.pfnDlgProc = DlgProcINIPage; + g_plugin.addUserInfo(wParam, &odp); + } + else if (IsMyContact(hContact)) { // check if it is a weather contact + // register the contact info page + odp.pszTemplate = MAKEINTRESOURCEA(IDD_USERINFO); + odp.pfnDlgProc = DlgProcUIPage; + odp.flags = ODPF_BOLDGROUPS; + g_plugin.addUserInfo(wParam, &odp); + } + return 0; +} + + +// show brief information dialog +// wParam = current contact +int BriefInfo(WPARAM wParam, LPARAM) +{ + // make sure that the contact is actually a weather one + if (IsMyContact(wParam)) { + HWND hMoreDataDlg = WindowList_Find(hDataWindowList, wParam); + if (hMoreDataDlg != nullptr) { + SetForegroundWindow(hMoreDataDlg); + SetFocus(hMoreDataDlg); + } + else hMoreDataDlg = CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_BRIEF), nullptr, DlgProcMoreData, (LPARAM)wParam); + + ShowWindow(GetDlgItem(hMoreDataDlg, IDC_DATALIST), 0); + ShowWindow(GetDlgItem(hMoreDataDlg, IDC_MTEXT), 1); + SetDlgItemText(hMoreDataDlg, IDC_MTOGGLE, TranslateT("More Info")); + return 1; + } + return 0; +} + +INT_PTR BriefInfoSvc(WPARAM wParam, LPARAM lParam) +{ + return BriefInfo(wParam, lParam); +} |