summaryrefslogtreecommitdiff
path: root/protocols/CurrencyRates/src
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2019-03-02 12:32:44 +0300
committerGeorge Hazan <ghazan@miranda.im>2019-03-02 12:32:55 +0300
commit931a7dc1ac0dbc7e6c1083583ced915e572f5b47 (patch)
tree9fe9a6448d44030e26aa7107ce16044ed413e0d0 /protocols/CurrencyRates/src
parentdd7d9954042254e66e3bbbec7195c6be8b1a0663 (diff)
all protocols (even virtual ones) moved to the Protocols folder
Diffstat (limited to 'protocols/CurrencyRates/src')
-rw-r--r--protocols/CurrencyRates/src/Chart.h280
-rw-r--r--protocols/CurrencyRates/src/ComHelper.cpp28
-rw-r--r--protocols/CurrencyRates/src/ComHelper.h7
-rw-r--r--protocols/CurrencyRates/src/CommonOptionDlg.cpp221
-rw-r--r--protocols/CurrencyRates/src/CommonOptionDlg.h17
-rw-r--r--protocols/CurrencyRates/src/CreateFilePath.cpp17
-rw-r--r--protocols/CurrencyRates/src/CreateFilePath.h6
-rw-r--r--protocols/CurrencyRates/src/CurrencyConverter.cpp260
-rw-r--r--protocols/CurrencyRates/src/CurrencyConverter.h6
-rw-r--r--protocols/CurrencyRates/src/CurrencyRateChart.cpp101
-rw-r--r--protocols/CurrencyRates/src/CurrencyRateChart.h12
-rw-r--r--protocols/CurrencyRates/src/CurrencyRateInfoDlg.cpp257
-rw-r--r--protocols/CurrencyRates/src/CurrencyRateInfoDlg.h11
-rw-r--r--protocols/CurrencyRates/src/CurrencyRatesProviderBase.cpp935
-rw-r--r--protocols/CurrencyRates/src/CurrencyRatesProviderBase.h122
-rw-r--r--protocols/CurrencyRates/src/CurrencyRatesProviderCurrencyConverter.cpp438
-rw-r--r--protocols/CurrencyRates/src/CurrencyRatesProviderCurrencyConverter.h32
-rw-r--r--protocols/CurrencyRates/src/CurrencyRatesProviders.cpp83
-rw-r--r--protocols/CurrencyRates/src/DBUtils.cpp43
-rw-r--r--protocols/CurrencyRates/src/DBUtils.h53
-rw-r--r--protocols/CurrencyRates/src/EconomicRateInfo.h59
-rw-r--r--protocols/CurrencyRates/src/ExtraImages.cpp30
-rw-r--r--protocols/CurrencyRates/src/ExtraImages.h16
-rw-r--r--protocols/CurrencyRates/src/Forex.cpp325
-rw-r--r--protocols/CurrencyRates/src/HTMLParserMS.cpp254
-rw-r--r--protocols/CurrencyRates/src/HTMLParserMS.h32
-rw-r--r--protocols/CurrencyRates/src/HTTPSession.cpp91
-rw-r--r--protocols/CurrencyRates/src/HTTPSession.h20
-rw-r--r--protocols/CurrencyRates/src/ICurrencyRatesProvider.h62
-rw-r--r--protocols/CurrencyRates/src/IHTMLEngine.h18
-rw-r--r--protocols/CurrencyRates/src/IHTMLParser.h41
-rw-r--r--protocols/CurrencyRates/src/IconLib.cpp40
-rw-r--r--protocols/CurrencyRates/src/IconLib.h11
-rw-r--r--protocols/CurrencyRates/src/ImportExport.cpp507
-rw-r--r--protocols/CurrencyRates/src/ImportExport.h10
-rw-r--r--protocols/CurrencyRates/src/IsWithinAccuracy.h15
-rw-r--r--protocols/CurrencyRates/src/Locale.cpp59
-rw-r--r--protocols/CurrencyRates/src/Locale.h9
-rw-r--r--protocols/CurrencyRates/src/Log.cpp41
-rw-r--r--protocols/CurrencyRates/src/Log.h13
-rw-r--r--protocols/CurrencyRates/src/ModuleInfo.cpp64
-rw-r--r--protocols/CurrencyRates/src/ModuleInfo.h23
-rw-r--r--protocols/CurrencyRates/src/SettingsDlg.cpp971
-rw-r--r--protocols/CurrencyRates/src/SettingsDlg.h116
-rw-r--r--protocols/CurrencyRates/src/WinCtrlHelper.cpp31
-rw-r--r--protocols/CurrencyRates/src/WinCtrlHelper.h37
-rw-r--r--protocols/CurrencyRates/src/resource.h102
-rw-r--r--protocols/CurrencyRates/src/stdafx.cxx18
-rw-r--r--protocols/CurrencyRates/src/stdafx.h113
-rw-r--r--protocols/CurrencyRates/src/version.h13
50 files changed, 6070 insertions, 0 deletions
diff --git a/protocols/CurrencyRates/src/Chart.h b/protocols/CurrencyRates/src/Chart.h
new file mode 100644
index 0000000000..1247ead065
--- /dev/null
+++ b/protocols/CurrencyRates/src/Chart.h
@@ -0,0 +1,280 @@
+#ifndef __FAE7F26E_61ED_4951_BE87_5E022CDF21DF_Chart_h__
+#define __FAE7F26E_61ED_4951_BE87_5E022CDF21DF_Chart_h__
+
+#pragma once
+
+namespace detail
+{
+ template<class T> struct CConverter
+ {
+ static double Convert(const T& v)
+ {
+ return boost::numeric_cast<double>(v);
+ }
+
+ static tstring ToString(const T& v)
+ {
+ return boost::lexical_cast<tstring>(v);
+ }
+ };
+
+ template<> struct CConverter < double >
+ {
+ static double Convert(double v)
+ {
+ return v;
+ }
+
+ static tstring ToString(double v)
+ {
+ tostringstream s;
+ s.imbue(std::locale(""));
+ s << std::fixed << v;
+ return s.str();
+ }
+ };
+}
+
+template<class TXValue, class TYValue, class TXConverter = detail::CConverter<TXValue>, class TYConverter = detail::CConverter<TYValue> >
+class CChart
+{
+private:
+ typedef std::pair<TXValue, TYValue> TValue;
+ typedef std::vector<TValue> TValues;
+
+public:
+ CChart() : m_MaxY(), m_MinY()
+ {
+ memset(&m_rect, 0, sizeof(m_rect));
+ }
+
+ ~CChart()
+ {
+ }
+
+ void AddValue(const TXValue& x, const TYValue& y)
+ {
+ if (m_aValues.empty())
+ {
+ m_MaxY = m_MinY = y;
+ }
+ else
+ {
+ m_MaxY = __max(y, m_MaxY);
+ m_MinY = __min(y, m_MinY);
+ }
+ m_aValues.push_back(std::make_pair(x, y));
+ }
+
+ void SetRect(int x, int y, int cx, int cy)
+ {
+ m_rect.left = x;
+ m_rect.right = x + cx;
+ m_rect.top = y;
+ m_rect.bottom = y + cy;
+ }
+
+ void Draw(HDC hdc) const
+ {
+ RECT rc = m_rect;
+ DrawBackground(hdc, rc);
+ if (false == m_aValues.empty())
+ {
+ ::InflateRect(&rc, -10, -10);
+ DrawGrid(hdc, rc);
+ DrawAxis(hdc, rc);
+ DrawPoints(hdc, rc);
+ }
+ else
+ {
+ HFONT hFont = static_cast<HFONT>(::GetStockObject(DEFAULT_GUI_FONT));
+ HFONT hOldFont = static_cast<HFONT>(::SelectObject(hdc, hFont));
+
+ LPCTSTR pszText = TranslateT("There is nothing to show");
+ int nDrawTextResult = ::DrawText(hdc, pszText, -1, &rc, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
+ assert(0 != nDrawTextResult);
+
+ ::SelectObject(hdc, hOldFont);
+ BOOL bResult = ::DeleteObject(hFont);
+ assert(TRUE == bResult);
+ }
+ }
+
+private:
+ void DrawBackground(HDC hdc, RECT& rc) const
+ {
+ // HBRUSH hBrush = ::CreateSolidBrush(RGB(255,0,0));//user preferable background color here!
+ // ::FillRect(hdc,&m_rect,hBrush);
+ // ::DeleteBrush(hBrush);
+ }
+
+ void DrawGrid(HDC hdc, RECT& rc) const
+ {
+ enum{ number_of_lines = 5 };
+ HPEN hPen = ::CreatePen(PS_SOLID, 1, RGB(125, 125, 125));
+ HPEN hPenOld = static_cast<HPEN>(::SelectObject(hdc, hPen));
+ HFONT hFont = static_cast<HFONT>(::GetStockObject(DEFAULT_GUI_FONT));
+ HFONT hOldFont = static_cast<HFONT>(::SelectObject(hdc, hFont));
+
+ //vertical grid
+ int step = (rc.bottom - rc.top) / number_of_lines;
+ TYValue y_val = m_MinY + ((m_MaxY - m_MinY) / number_of_lines);
+ int nXIndent = 0;
+ for (int y = rc.bottom - step; y > rc.top; y -= step, y_val += ((m_MaxY - m_MinY) / number_of_lines))
+ {
+ tstring sY = TYConverter::ToString(y_val);
+ SIZE sizeText = { 0, 0 };
+ BOOL bResult = ::GetTextExtentPoint32(hdc, sY.c_str(), (int)sY.size(), &sizeText);
+ assert(TRUE == bResult);
+ nXIndent = __max(nXIndent, sizeText.cx);
+ }
+
+ y_val = m_MinY + ((m_MaxY - m_MinY) / number_of_lines);
+ nXIndent += 2;
+ rc.left += nXIndent;
+ for (int y = rc.bottom - step; y > rc.top; y -= step, y_val += ((m_MaxY - m_MinY) / number_of_lines))
+ {
+ tstring sY = TYConverter::ToString(y_val);
+ SIZE sizeText = { 0, 0 };
+ BOOL bResult = ::GetTextExtentPoint32(hdc, sY.c_str(), (int)sY.size(), &sizeText);
+ assert(TRUE == bResult);
+
+ RECT rcText = { rc.left - nXIndent, y - (sizeText.cy / 2), rc.left - 1, y + (sizeText.cy / 2) };
+ int nDrawTextResult = ::DrawText(hdc, sY.c_str(), -1, &rcText, DT_SINGLELINE | DT_VCENTER | DT_RIGHT);
+ assert(0 != nDrawTextResult);
+
+ bResult = ::MoveToEx(hdc, rc.left, y, NULL);
+ assert(TRUE == bResult);
+
+ bResult = ::LineTo(hdc, rc.right, y);
+ assert(TRUE == bResult);
+ }
+
+ // horizontal grid
+ HRGN rgnAllLables = ::CreateRectRgn(0, 0, 0, 0);
+ HRGN rgnTemporary = ::CreateRectRgn(0, 0, 0, 0);
+ bool bFixedRect = false;
+ step = (rc.right - rc.left) / number_of_lines;
+ TXValue x_val = m_aValues[0].first + ((m_aValues[m_aValues.size() - 1].first - m_aValues[0].first) / number_of_lines);
+ for (int x = rc.left + step; x < rc.right; x += step, x_val += ((m_aValues[m_aValues.size() - 1].first - m_aValues[0].first) / number_of_lines))
+ {
+ tstring sX = TXConverter::ToString(x_val);
+ SIZE sizeText = { 0, 0 };
+ BOOL bResult = ::GetTextExtentPoint32(hdc, sX.c_str(), (int)sX.size(), &sizeText);
+ assert(TRUE == bResult);
+
+ if (false == bFixedRect)
+ {
+ rc.bottom -= sizeText.cy + 2;
+ bFixedRect = true;
+ }
+
+ RECT rcText = { x - (sizeText.cx / 2), rc.bottom, x + (sizeText.cx / 2), rc.bottom + sizeText.cy - 1 };
+ // Draw a label if it doesn't overlap with previous ones
+ HRGN rgnCurrentLable = ::CreateRectRgnIndirect(&rcText);
+ if (NULLREGION == ::CombineRgn(rgnTemporary, rgnCurrentLable, rgnAllLables, RGN_AND))
+ {
+ int nDrawTextResult = ::DrawText(hdc, sX.c_str(), (int)sX.size(), &rcText, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
+ assert(0 != nDrawTextResult);
+ int nCombineRgnResult = ::CombineRgn(rgnTemporary, rgnCurrentLable, rgnAllLables, RGN_OR);
+ assert(ERROR != nCombineRgnResult);
+ nCombineRgnResult = ::CombineRgn(rgnAllLables, rgnTemporary, NULL, RGN_COPY);
+ assert(ERROR != nCombineRgnResult);
+ }
+ bResult = ::DeleteObject(rgnCurrentLable);
+ assert(TRUE == bResult);
+
+ bResult = ::MoveToEx(hdc, x, rc.bottom, NULL);
+ assert(TRUE == bResult);
+
+ bResult = ::LineTo(hdc, x, rc.top);
+ assert(TRUE == bResult);
+ }
+
+ BOOL bResult = ::DeleteObject(rgnAllLables);
+ assert(TRUE == bResult);
+ bResult = ::DeleteObject(rgnTemporary);
+ assert(TRUE == bResult);
+
+ ::SelectObject(hdc, hOldFont);
+ ::SelectObject(hdc, hPenOld);
+ bResult = ::DeleteObject(hFont);
+ assert(TRUE == bResult);
+ bResult = ::DeleteObject(hPen);
+ assert(TRUE == bResult);
+ }
+
+ void DrawAxis(HDC hdc, RECT& rc) const
+ {
+ HPEN hPen = ::CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
+ HPEN hPenOld = static_cast<HPEN>(::SelectObject(hdc, hPen));
+
+ // draw Y-axes
+ BOOL bResult = ::MoveToEx(hdc, rc.left + 1, rc.bottom - 1, NULL);
+ assert(TRUE == bResult);
+ bResult = ::LineTo(hdc, rc.left + 1, rc.top + 1);
+ assert(TRUE == bResult);
+
+ // draw X-axes
+ bResult = ::MoveToEx(hdc, rc.left + 1, rc.bottom - 1, NULL);
+ assert(TRUE == bResult);
+ bResult = ::LineTo(hdc, rc.right - 1, rc.bottom - 1);
+ assert(TRUE == bResult);
+
+ ::SelectObject(hdc, hPenOld);
+ bResult = ::DeleteObject(hPen);
+ assert(TRUE == bResult);
+ }
+
+ void DrawPoints(HDC hdc, RECT& rc) const
+ {
+ TXValue xMin(m_aValues[0].first);
+ double dx = TXConverter::Convert(m_aValues[m_aValues.size() - 1].first - xMin);
+ double dY = TYConverter::Convert(m_MaxY - m_MinY);
+
+ HPEN hPen = ::CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
+ HGDIOBJ hPenOld = ::SelectObject(hdc, hPen);
+
+ HBRUSH hBrush = ::CreateSolidBrush(RGB(255, 0, 0));
+ HGDIOBJ hBrushOld = ::SelectObject(hdc, hBrush);
+
+ bool bPrevValid = false;
+ int xPrex, yPrev;
+
+ BOOST_FOREACH(const TValue& v, m_aValues)
+ {
+ double k = TXConverter::Convert(v.first - xMin);
+
+ int x = rc.left + boost::numeric_cast<int>((rc.right - rc.left)*(k / dx));
+ k = TYConverter::Convert(v.second - m_MinY);
+ int y = rc.bottom - boost::numeric_cast<int>((rc.bottom - rc.top)*(k / dY));
+ ::Ellipse(hdc, x - 5, y - 5, x + 5, y + 5);
+ if (bPrevValid)
+ {
+ BOOL bResult = ::MoveToEx(hdc, xPrex, yPrev, NULL);
+ assert(TRUE == bResult);
+ bResult = ::LineTo(hdc, x, y);
+ assert(TRUE == bResult);
+ }
+
+ xPrex = x, yPrev = y;
+ bPrevValid = true;
+ }
+
+ ::SelectObject(hdc, hPenOld);
+ BOOL bResult = ::DeleteObject(hPen);
+ assert(TRUE == bResult);
+
+ ::SelectObject(hdc, hBrushOld);
+ bResult = ::DeleteObject(hBrush);
+ assert(TRUE == bResult);
+ }
+
+private:
+ TValues m_aValues;
+ RECT m_rect;
+ TYValue m_MaxY;
+ TYValue m_MinY;
+};
+
+#endif // __FAE7F26E_61ED_4951_BE87_5E022CDF21DF_Chart_h__
diff --git a/protocols/CurrencyRates/src/ComHelper.cpp b/protocols/CurrencyRates/src/ComHelper.cpp
new file mode 100644
index 0000000000..c4715892b5
--- /dev/null
+++ b/protocols/CurrencyRates/src/ComHelper.cpp
@@ -0,0 +1,28 @@
+#include "StdAfx.h"
+
+tstring ComException2Msg(_com_error& e, const tstring& rsAdditionalInfo)
+{
+ HRESULT hError = e.Error();
+ tostringstream o;
+ if (false == rsAdditionalInfo.empty())
+ o << rsAdditionalInfo << "\n";
+
+ o << e.ErrorMessage() << L" (" << std::hex << hError << L")";
+
+ IErrorInfo* p = e.ErrorInfo();
+ CComPtr<IErrorInfo> pErrorInfo(p);
+ if (nullptr != p)
+ p->Release();
+
+ if (pErrorInfo)
+ o << L"\n" << e.Description();
+
+ return o.str();
+}
+
+void ShowComError(_com_error& e, const tstring& rsAdditionalInfo)
+{
+ tstring sErrorMsg = ComException2Msg(e, rsAdditionalInfo);
+ LogIt(sErrorMsg);
+ CurrencyRates_MessageBox(nullptr, sErrorMsg.c_str(), MB_OK | MB_ICONERROR);
+}
diff --git a/protocols/CurrencyRates/src/ComHelper.h b/protocols/CurrencyRates/src/ComHelper.h
new file mode 100644
index 0000000000..343d436785
--- /dev/null
+++ b/protocols/CurrencyRates/src/ComHelper.h
@@ -0,0 +1,7 @@
+#ifndef __37ae28ab_c414_4aba_bbef_d23dd68643a5_ComHelper_h__
+#define __37ae28ab_c414_4aba_bbef_d23dd68643a5_ComHelper_h__
+
+void ShowComError(_com_error& e, const tstring& rsAdditionalInfo);
+tstring ComException2Msg(_com_error& e, const tstring& rsAdditionalInfo);
+
+#endif//__37ae28ab_c414_4aba_bbef_d23dd68643a5_ComHelper_h__
diff --git a/protocols/CurrencyRates/src/CommonOptionDlg.cpp b/protocols/CurrencyRates/src/CommonOptionDlg.cpp
new file mode 100644
index 0000000000..335457ed89
--- /dev/null
+++ b/protocols/CurrencyRates/src/CommonOptionDlg.cpp
@@ -0,0 +1,221 @@
+#include "StdAfx.h"
+
+typedef boost::shared_ptr<CAdvProviderSettings> TAdvSettingsPtr;
+typedef std::map<const ICurrencyRatesProvider*, TAdvSettingsPtr> TAdvSettings;
+
+TAdvSettings g_aAdvSettings;
+
+CAdvProviderSettings* get_adv_settings(const ICurrencyRatesProvider *pProvider, bool bCreateIfNonExist)
+{
+ TAdvSettings::iterator i = g_aAdvSettings.find(pProvider);
+ if (i != g_aAdvSettings.end())
+ return i->second.get();
+
+ if (true == bCreateIfNonExist) {
+ TAdvSettingsPtr pAdvSet(new CAdvProviderSettings(pProvider));
+ g_aAdvSettings.insert(std::make_pair(pProvider, pAdvSet));
+ return pAdvSet.get();
+ }
+
+ return nullptr;
+}
+
+void remove_adv_settings(const ICurrencyRatesProvider *pProvider)
+{
+ TAdvSettings::iterator i = g_aAdvSettings.find(pProvider);
+ if (i != g_aAdvSettings.end())
+ g_aAdvSettings.erase(i);
+}
+
+void CommonOptionDlgProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp, CCommonDlgProcData& rData)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ assert(rData.m_pCurrencyRatesProvider);
+
+ // set contact list display format
+ tstring sDspNameFrmt = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_DisplayNameFormat, DB_DEF_DisplayNameFormat);
+ ::SetDlgItemText(hWnd, IDC_EDIT_CONTACT_LIST_FORMAT, sDspNameFrmt.c_str());
+
+ // set status message display format
+ tstring sStatusMsgFrmt = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_StatusMsgFormat, DB_DEF_StatusMsgFormat);
+ ::SetDlgItemText(hWnd, IDC_EDIT_STATUS_MESSAGE_FORMAT, sStatusMsgFrmt.c_str());
+
+ // set tendency format
+ tstring sTendencyFrmt = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_TendencyFormat, DB_DEF_TendencyFormat);
+ ::SetDlgItemText(hWnd, IDC_EDIT_TENDENCY_FORMAT, sTendencyFrmt.c_str());
+
+ // set api key
+ tstring sApiKey = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_ApiKey, L"");
+ ::SetDlgItemText(hWnd, IDC_EDIT_PERSONAL_KEY, sApiKey.c_str());
+
+ // refresh rate
+ HWND hwndCombo = ::GetDlgItem(hWnd, IDC_COMBO_REFRESH_RATE);
+ LPCTSTR pszRefreshRateTypes[] = { TranslateT("Seconds"), TranslateT("Minutes"), TranslateT("Hours") };
+ for (int i = 0; i < _countof(pszRefreshRateTypes); ++i)
+ ::SendMessage(hwndCombo, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(pszRefreshRateTypes[i]));
+
+ int nRefreshRateType = db_get_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_RefreshRateType, RRT_MINUTES);
+ if (nRefreshRateType < RRT_SECONDS || nRefreshRateType > RRT_HOURS)
+ nRefreshRateType = RRT_MINUTES;
+
+ UINT nRate = db_get_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_RefreshRateValue, 1);
+ switch (nRefreshRateType) {
+ default:
+ case RRT_SECONDS:
+ case RRT_MINUTES:
+ if (nRate < 1 || nRate > 60)
+ nRate = 1;
+
+ spin_set_range(::GetDlgItem(hWnd, IDC_SPIN_REFRESH_RATE), 1, 60);
+ break;
+ case RRT_HOURS:
+ if (nRate < 1 || nRate > 24)
+ nRate = 1;
+
+ spin_set_range(::GetDlgItem(hWnd, IDC_SPIN_REFRESH_RATE), 1, 24);
+ break;
+ }
+
+ ::SendMessage(hwndCombo, CB_SETCURSEL, nRefreshRateType, 0);
+ ::SetDlgItemInt(hWnd, IDC_EDIT_REFRESH_RATE, nRate, FALSE);
+
+ PropSheet_UnChanged(::GetParent(hWnd), hWnd);
+ }
+ break;
+
+ case WM_COMMAND:
+ switch (HIWORD(wp)) {
+ case CBN_SELCHANGE:
+ if (IDC_COMBO_REFRESH_RATE == LOWORD(wp)) {
+ ERefreshRateType nType = static_cast<ERefreshRateType>(::SendMessage(reinterpret_cast<HWND>(lp), CB_GETCURSEL, 0, 0));
+ switch (nType) {
+ default:
+ case RRT_SECONDS:
+ case RRT_MINUTES:
+ spin_set_range(::GetDlgItem(hWnd, IDC_SPIN_REFRESH_RATE), 1, 60);
+ break;
+ case RRT_HOURS:
+ spin_set_range(::GetDlgItem(hWnd, IDC_SPIN_REFRESH_RATE), 1, 24);
+ BOOL bOk = FALSE;
+ UINT nRefreshRate = ::GetDlgItemInt(hWnd, IDC_EDIT_REFRESH_RATE, &bOk, FALSE);
+ if (TRUE == bOk && nRefreshRate > 24)
+ ::SetDlgItemInt(hWnd, IDC_EDIT_REFRESH_RATE, 24, FALSE);
+ break;
+ }
+
+ PropSheet_Changed(::GetParent(hWnd), hWnd);
+ }
+ break;
+
+ case EN_CHANGE:
+ switch (LOWORD(wp)) {
+ case IDC_EDIT_REFRESH_RATE:
+ case IDC_EDIT_CONTACT_LIST_FORMAT:
+ case IDC_EDIT_STATUS_MESSAGE_FORMAT:
+ case IDC_EDIT_TENDENCY_FORMAT:
+ case IDC_EDIT_PERSONAL_KEY:
+ if (reinterpret_cast<HWND>(lp) == ::GetFocus())
+ PropSheet_Changed(::GetParent(hWnd), hWnd);
+ break;
+ }
+ break;
+
+ case BN_CLICKED:
+ switch (LOWORD(wp)) {
+ case IDC_BUTTON_DESCRIPTION:
+ show_variable_list(hWnd, rData.m_pCurrencyRatesProvider);
+ break;
+ case IDC_BUTTON_ADVANCED_SETTINGS:
+ CAdvProviderSettings* pAdvSet = get_adv_settings(rData.m_pCurrencyRatesProvider, true);
+ assert(pAdvSet);
+ if (true == ShowSettingsDlg(hWnd, pAdvSet))
+ PropSheet_Changed(::GetParent(hWnd), hWnd);
+ break;
+ }
+ break;
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ LPNMHDR pNMHDR = reinterpret_cast<LPNMHDR>(lp);
+ switch (pNMHDR->code) {
+ case PSN_KILLACTIVE:
+ {
+ BOOL bOk = FALSE;
+ UINT nRefreshRate = ::GetDlgItemInt(hWnd, IDC_EDIT_REFRESH_RATE, &bOk, FALSE);
+ ERefreshRateType nType = static_cast<ERefreshRateType>(::SendDlgItemMessage(hWnd, IDC_COMBO_REFRESH_RATE, CB_GETCURSEL, 0, 0));
+ switch (nType) {
+ default:
+ case RRT_MINUTES:
+ case RRT_SECONDS:
+ if (FALSE == bOk || nRefreshRate < 1 || nRefreshRate > 60) {
+ prepare_edit_ctrl_for_error(::GetDlgItem(hWnd, IDC_EDIT_REFRESH_RATE));
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter integer value between 1 and 60."), MB_OK | MB_ICONERROR);
+ bOk = FALSE;
+ }
+ break;
+ case RRT_HOURS:
+ if (FALSE == bOk || nRefreshRate < 1 || nRefreshRate > 24) {
+ prepare_edit_ctrl_for_error(::GetDlgItem(hWnd, IDC_EDIT_REFRESH_RATE));
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter integer value between 1 and 24."), MB_OK | MB_ICONERROR);
+ bOk = FALSE;
+ }
+ break;
+ }
+
+ if (TRUE == bOk) {
+ HWND hEdit = ::GetDlgItem(hWnd, IDC_EDIT_CONTACT_LIST_FORMAT);
+ assert(IsWindow(hEdit));
+
+ tstring s = get_window_text(hEdit);
+ if (true == s.empty()) {
+ prepare_edit_ctrl_for_error(hEdit);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter text to display in contact list."), MB_OK | MB_ICONERROR);
+ bOk = FALSE;
+ }
+ }
+
+ ::SetWindowLongPtr(hWnd, DWLP_MSGRESULT, (TRUE == bOk) ? FALSE : TRUE);
+ }
+ break;
+
+ case PSN_APPLY:
+ BOOL bOk = FALSE;
+ UINT nRefreshRate = ::GetDlgItemInt(hWnd, IDC_EDIT_REFRESH_RATE, &bOk, FALSE);
+ assert(TRUE == bOk);
+ ERefreshRateType nType = static_cast<ERefreshRateType>(::SendDlgItemMessage(hWnd, IDC_COMBO_REFRESH_RATE, CB_GETCURSEL, 0, 0));
+
+ assert(rData.m_pCurrencyRatesProvider);
+
+ rData.m_bFireSetingsChangedEvent = true;
+ db_set_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_RefreshRateType, nType);
+ db_set_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_RefreshRateValue, nRefreshRate);
+
+ tstring s = get_window_text(::GetDlgItem(hWnd, IDC_EDIT_CONTACT_LIST_FORMAT));
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_KEY_DisplayNameFormat, s.c_str());
+
+ s = get_window_text(::GetDlgItem(hWnd, IDC_EDIT_STATUS_MESSAGE_FORMAT));
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_KEY_StatusMsgFormat, s.c_str());
+
+ s = get_window_text(::GetDlgItem(hWnd, IDC_EDIT_TENDENCY_FORMAT));
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_KEY_TendencyFormat, s.c_str());
+
+ s = get_window_text(::GetDlgItem(hWnd, IDC_EDIT_PERSONAL_KEY));
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_KEY_ApiKey, s.c_str());
+
+ CAdvProviderSettings* pAdvSet = get_adv_settings(rData.m_pCurrencyRatesProvider, false);
+ if (pAdvSet)
+ pAdvSet->SaveToDb();
+ break;
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ remove_adv_settings(rData.m_pCurrencyRatesProvider);
+ break;
+ }
+}
diff --git a/protocols/CurrencyRates/src/CommonOptionDlg.h b/protocols/CurrencyRates/src/CommonOptionDlg.h
new file mode 100644
index 0000000000..bc4ca8c456
--- /dev/null
+++ b/protocols/CurrencyRates/src/CommonOptionDlg.h
@@ -0,0 +1,17 @@
+#ifndef __c85fe710_f71b_4a58_9d44_3e39f6209c5f_CommonOptionDlg_h__
+#define __c85fe710_f71b_4a58_9d44_3e39f6209c5f_CommonOptionDlg_h__
+
+class CCurrencyRatesProviderBase;
+
+struct CCommonDlgProcData
+{
+ CCommonDlgProcData(const CCurrencyRatesProviderBase* pCurrencyRatesProvider)
+ : m_pCurrencyRatesProvider(pCurrencyRatesProvider), m_bFireSetingsChangedEvent(false){}
+
+ const CCurrencyRatesProviderBase* m_pCurrencyRatesProvider;
+ bool m_bFireSetingsChangedEvent;
+};
+
+void CommonOptionDlgProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp, CCommonDlgProcData& rData);
+
+#endif//__c85fe710_f71b_4a58_9d44_3e39f6209c5f_CommonOptionDlg_h__
diff --git a/protocols/CurrencyRates/src/CreateFilePath.cpp b/protocols/CurrencyRates/src/CreateFilePath.cpp
new file mode 100644
index 0000000000..71490432a9
--- /dev/null
+++ b/protocols/CurrencyRates/src/CreateFilePath.cpp
@@ -0,0 +1,17 @@
+#include "StdAfx.h"
+
+tstring CreateFilePath(const tstring &rsName)
+{
+ wchar_t szPath[_MAX_PATH];
+ ::GetModuleFileName(g_plugin.getInst(), szPath, _MAX_PATH);
+
+ wchar_t* p = wcsrchr(szPath, '\\');
+ if (p)
+ *p = 0;
+
+ tstring s(rsName);
+ FixInvalidChars(s);
+ tostringstream o;
+ o << szPath << L"\\CurrencyRates\\" << s;
+ return o.str();
+} \ No newline at end of file
diff --git a/protocols/CurrencyRates/src/CreateFilePath.h b/protocols/CurrencyRates/src/CreateFilePath.h
new file mode 100644
index 0000000000..e4a88494fb
--- /dev/null
+++ b/protocols/CurrencyRates/src/CreateFilePath.h
@@ -0,0 +1,6 @@
+#ifndef _aaf3bee6_cee7_4023_8848_5911ad7a9660_CreateFilePath_h__
+#define _aaf3bee6_cee7_4023_8848_5911ad7a9660_CreateFilePath_h__
+
+tstring CreateFilePath(const tstring& rsName);
+
+#endif //_aaf3bee6_cee7_4023_8848_5911ad7a9660_CreateFilePath_h__
diff --git a/protocols/CurrencyRates/src/CurrencyConverter.cpp b/protocols/CurrencyRates/src/CurrencyConverter.cpp
new file mode 100644
index 0000000000..5a7f4d1487
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyConverter.cpp
@@ -0,0 +1,260 @@
+#include "StdAfx.h"
+#include "CurrencyRatesProviderCurrencyConverter.h"
+
+#define WINDOW_PREFIX "CurrenyConverter_"
+
+#define DB_STR_CC_CURRENCYRATE_FROM_ID "CurrencyConverter_FromID"
+#define DB_STR_CC_CURRENCYRATE_TO_ID "CurrencyConverter_ToID"
+#define DB_STR_CC_AMOUNT "CurrencyConverter_Amount"
+
+static CCurrencyRatesProviderCurrencyConverter *get_currency_converter_provider()
+{
+ for (auto &it : g_apProviders)
+ if (auto p = dynamic_cast<CCurrencyRatesProviderCurrencyConverter*>(it))
+ return p;
+
+ assert(!"We should never get here!");
+ return nullptr;
+}
+
+CCurrencyRateSection get_currencyrates(const CCurrencyRatesProviderCurrencyConverter* pProvider = nullptr)
+{
+ if (nullptr == pProvider)
+ pProvider = get_currency_converter_provider();
+
+ if (pProvider) {
+ const auto& rCurrencyRates = pProvider->GetCurrencyRates();
+ if (rCurrencyRates.GetSectionCount() > 0)
+ return rCurrencyRates.GetSection(0);
+ }
+
+ return CCurrencyRateSection();
+}
+
+inline tstring make_currencyrate_name(const CCurrencyRate &rCurrencyRate)
+{
+ const tstring &rsDesc = rCurrencyRate.GetName();
+ return((false == rsDesc.empty()) ? rsDesc : rCurrencyRate.GetSymbol());
+}
+
+inline void update_convert_button(HWND hDlg)
+{
+ int nFrom = static_cast<int>(::SendDlgItemMessage(hDlg, IDC_COMBO_CONVERT_FROM, CB_GETCURSEL, 0, 0));
+ int nTo = static_cast<int>(::SendDlgItemMessage(hDlg, IDC_COMBO_CONVERT_INTO, CB_GETCURSEL, 0, 0));
+ bool bEnableButton = ((CB_ERR != nFrom)
+ && (CB_ERR != nTo)
+ && (nFrom != nTo)
+ && (GetWindowTextLength(GetDlgItem(hDlg, IDC_EDIT_VALUE)) > 0));
+ EnableWindow(GetDlgItem(hDlg, IDC_BUTTON_CONVERT), bEnableButton);
+}
+
+inline void update_swap_button(HWND hDlg)
+{
+ int nFrom = static_cast<int>(::SendDlgItemMessage(hDlg, IDC_COMBO_CONVERT_FROM, CB_GETCURSEL, 0, 0));
+ int nTo = static_cast<int>(::SendDlgItemMessage(hDlg, IDC_COMBO_CONVERT_INTO, CB_GETCURSEL, 0, 0));
+ bool bEnableButton = ((CB_ERR != nFrom)
+ && (CB_ERR != nTo)
+ && (nFrom != nTo));
+ EnableWindow(GetDlgItem(hDlg, IDC_BUTTON_SWAP), bEnableButton);
+}
+
+inline tstring double2str(double dValue)
+{
+ tostringstream output;
+ output.imbue(GetSystemLocale());
+ output << std::fixed << std::setprecision(2) << dValue;
+ return output.str();
+}
+
+inline bool str2double(const tstring& s, double& d)
+{
+ tistringstream input(s);
+ input.imbue(GetSystemLocale());
+ input >> d;
+ return ((false == input.bad()) && (false == input.fail()));
+}
+
+
+INT_PTR CALLBACK CurrencyConverterDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hDlg);
+ {
+ MWindowList hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX, false);
+ assert(hWL);
+ WindowList_Add(hWL, hDlg);
+
+ Window_SetIcon_IcoLib(hDlg, CurrencyRates_GetIconHandle(IDI_ICON_CURRENCY_CONVERTER));
+
+ HWND hcbxFrom = ::GetDlgItem(hDlg, IDC_COMBO_CONVERT_FROM);
+ HWND hcbxTo = ::GetDlgItem(hDlg, IDC_COMBO_CONVERT_INTO);
+
+ tstring sFromCurrencyRateID = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_STR_CC_CURRENCYRATE_FROM_ID);
+ tstring sToCurrencyRateID = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_STR_CC_CURRENCYRATE_TO_ID);
+
+ const auto pProvider = get_currency_converter_provider();
+ const auto& rSection = get_currencyrates(pProvider);
+ auto cCurrencyRates = rSection.GetCurrencyRateCount();
+ for (auto i = 0u; i < cCurrencyRates; ++i) {
+ const auto& rCurrencyRate = rSection.GetCurrencyRate(i);
+ tstring sName = make_currencyrate_name(rCurrencyRate);
+ LPCTSTR pszName = sName.c_str();
+ LRESULT nFrom = ::SendMessage(hcbxFrom, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(pszName));
+ LRESULT nTo = ::SendMessage(hcbxTo, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(pszName));
+
+ if (0 == mir_wstrcmpi(rCurrencyRate.GetID().c_str(), sFromCurrencyRateID.c_str())) {
+ ::SendMessage(hcbxFrom, CB_SETCURSEL, nFrom, 0);
+ }
+
+ if (0 == mir_wstrcmpi(rCurrencyRate.GetID().c_str(), sToCurrencyRateID.c_str())) {
+ ::SendMessage(hcbxTo, CB_SETCURSEL, nTo, 0);
+ }
+ }
+
+ double dAmount = 1.0;
+ CurrencyRates_DBReadDouble(NULL, CURRENCYRATES_MODULE_NAME, DB_STR_CC_AMOUNT, dAmount);
+ ::SetDlgItemText(hDlg, IDC_EDIT_VALUE, double2str(dAmount).c_str());
+
+ const ICurrencyRatesProvider::CProviderInfo& pi = pProvider->GetInfo();
+ tostringstream o;
+ o << TranslateT("Info provided by") << L" <a href=\"" << pi.m_sURL << L"\">" << pi.m_sName << L"</a>";
+
+ ::SetDlgItemText(hDlg, IDC_SYSLINK_PROVIDER, o.str().c_str());
+
+ ::SendDlgItemMessage(hDlg, IDC_BUTTON_SWAP, BM_SETIMAGE, IMAGE_ICON, LPARAM(CurrencyRates_LoadIconEx(IDI_ICON_SWAP)));
+
+ update_convert_button(hDlg);
+ update_swap_button(hDlg);
+
+ Utils_RestoreWindowPositionNoSize(hDlg, NULL, CURRENCYRATES_MODULE_NAME, WINDOW_PREFIX);
+ ::ShowWindow(hDlg, SW_SHOW);
+ }
+ return TRUE;
+
+ case WM_CLOSE:
+ {
+ MWindowList hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX, false);
+ assert(hWL);
+ WindowList_Remove(hWL, hDlg);
+ Utils_SaveWindowPosition(hDlg, NULL, CURRENCYRATES_MODULE_NAME, WINDOW_PREFIX);
+ EndDialog(hDlg, 0);
+ }
+ return TRUE;
+
+ case WM_DESTROY:
+ Window_FreeIcon_IcoLib(hDlg);
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wp)) {
+ case IDC_COMBO_CONVERT_FROM:
+ case IDC_COMBO_CONVERT_INTO:
+ if (CBN_SELCHANGE == HIWORD(wp)) {
+ update_convert_button(hDlg);
+ update_swap_button(hDlg);
+ }
+ return TRUE;
+
+ case IDC_EDIT_VALUE:
+ if (EN_CHANGE == HIWORD(wp))
+ update_convert_button(hDlg);
+ return TRUE;
+
+ case IDCANCEL:
+ SendMessage(hDlg, WM_CLOSE, 0, 0);
+ return TRUE;
+
+ case IDC_BUTTON_SWAP:
+ {
+ HWND wndFrom = ::GetDlgItem(hDlg, IDC_COMBO_CONVERT_FROM);
+ HWND wndTo = ::GetDlgItem(hDlg, IDC_COMBO_CONVERT_INTO);
+ WPARAM nFrom = ::SendMessage(wndFrom, CB_GETCURSEL, 0, 0);
+ WPARAM nTo = ::SendMessage(wndTo, CB_GETCURSEL, 0, 0);
+
+ ::SendMessage(wndFrom, CB_SETCURSEL, nTo, 0);
+ ::SendMessage(wndTo, CB_SETCURSEL, nFrom, 0);
+ }
+ return TRUE;
+
+ case IDC_BUTTON_CONVERT:
+ {
+ HWND hwndAmount = GetDlgItem(hDlg, IDC_EDIT_VALUE);
+ tstring sText = get_window_text(hwndAmount);
+
+ double dAmount = 1.0;
+ if ((true == str2double(sText, dAmount)) && (dAmount > 0.0)) {
+ CurrencyRates_DBWriteDouble(NULL, CURRENCYRATES_MODULE_NAME, DB_STR_CC_AMOUNT, dAmount);
+
+ size_t nFrom = static_cast<size_t>(::SendDlgItemMessage(hDlg, IDC_COMBO_CONVERT_FROM, CB_GETCURSEL, 0, 0));
+ size_t nTo = static_cast<size_t>(::SendDlgItemMessage(hDlg, IDC_COMBO_CONVERT_INTO, CB_GETCURSEL, 0, 0));
+ if ((CB_ERR != nFrom) && (CB_ERR != nTo) && (nFrom != nTo)) {
+ const auto& rSection = get_currencyrates();
+ size_t cCurrencyRates = rSection.GetCurrencyRateCount();
+ if ((nFrom < cCurrencyRates) && (nTo < cCurrencyRates)) {
+ auto from = rSection.GetCurrencyRate(nFrom);
+ auto to = rSection.GetCurrencyRate(nTo);
+
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_STR_CC_CURRENCYRATE_FROM_ID, from.GetID().c_str());
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_STR_CC_CURRENCYRATE_TO_ID, to.GetID().c_str());
+
+ const auto pProvider = get_currency_converter_provider();
+ assert(pProvider);
+ if (pProvider) {
+ tstring sResult;
+ std::string sError;
+ try {
+ double dResult = pProvider->Convert(dAmount, from, to);
+ tostringstream ss;
+ ss.imbue(GetSystemLocale());
+ ss << std::fixed << std::setprecision(2) << dAmount << " " << from.GetName() << " = " << dResult << " " << to.GetName();
+ sResult = ss.str();
+ }
+ catch (std::exception& e) {
+ sError = e.what();
+ }
+
+ if (false == sError.empty())
+ sResult = currencyrates_a2t(sError.c_str());//A2T(sError.c_str());
+
+ SetDlgItemText(hDlg, IDC_EDIT_RESULT, sResult.c_str());
+ }
+ }
+ }
+ }
+ else {
+ CurrencyRates_MessageBox(hDlg, TranslateT("Enter positive number."), MB_OK | MB_ICONERROR);
+ prepare_edit_ctrl_for_error(GetDlgItem(hDlg, IDC_EDIT_VALUE));
+ }
+ }
+ return TRUE;
+ }
+ break;
+
+ case WM_NOTIFY:
+ LPNMHDR pNMHDR = reinterpret_cast<LPNMHDR>(lp);
+ switch (pNMHDR->code) {
+ case NM_CLICK:
+ if (IDC_SYSLINK_PROVIDER == wp) {
+ PNMLINK pNMLink = reinterpret_cast<PNMLINK>(pNMHDR);
+ ::ShellExecute(hDlg, L"open", pNMLink->item.szUrl, nullptr, nullptr, SW_SHOWNORMAL);
+ }
+ break;
+ }
+ break;
+ }
+ return (FALSE);
+}
+
+INT_PTR CurrencyRatesMenu_CurrencyConverter(WPARAM, LPARAM)
+{
+ MWindowList hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX, true);
+ HWND hWnd = WindowList_Find(hWL, NULL);
+ if (nullptr != hWnd) {
+ SetForegroundWindow(hWnd);
+ SetFocus(hWnd);
+ }
+ else CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_CURRENCY_CONVERTER), nullptr, CurrencyConverterDlgProc, 0);
+
+ return 0;
+}
diff --git a/protocols/CurrencyRates/src/CurrencyConverter.h b/protocols/CurrencyRates/src/CurrencyConverter.h
new file mode 100644
index 0000000000..eca03ec40e
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyConverter.h
@@ -0,0 +1,6 @@
+#ifndef __4FB6320B_2D02_408b_BAF5_426C185AAA11_CurrencyConverter_h__
+#define __4FB6320B_2D02_408b_BAF5_426C185AAA11_CurrencyConverter_h__
+
+INT_PTR CurrencyRatesMenu_CurrencyConverter(WPARAM wp, LPARAM lp);
+
+#endif //__4FB6320B_2D02_408b_BAF5_426C185AAA11_CurrencyConverter_h__
diff --git a/protocols/CurrencyRates/src/CurrencyRateChart.cpp b/protocols/CurrencyRates/src/CurrencyRateChart.cpp
new file mode 100644
index 0000000000..a3208811a4
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRateChart.cpp
@@ -0,0 +1,101 @@
+#include "StdAfx.h"
+
+#ifdef CHART_IMPLEMENT
+
+namespace
+{
+ class CMyJob : private boost::noncopyable
+ {
+ private:
+ CMyJob(LPCTSTR pszName = nullptr): m_hJob(::CreateJobObject(nullptr,pszName))
+ {
+ if(m_hJob)
+ {
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = { 0 };
+ jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+ if(0 == ::SetInformationJobObject(m_hJob,JobObjectExtendedLimitInformation,&jeli,sizeof(jeli)))
+ {
+#ifdef OUTPUT_TO_DEBUG_VIEWER
+ ::OutputDebugString(_T("Error occurred during the job initialization\n"));
+#endif
+ }
+ }
+
+ }
+ ~CMyJob()
+ {
+ if(m_hJob)
+ {
+ ::CloseHandle(m_hJob);
+ }
+ }
+
+ public:
+ static CMyJob& GetInstance()
+ {
+ static CMyJob g_job(_T("MirandaJob_E12D5E9C_00E7_4FFA_9831_F35E45C6EBDA"));
+ return g_job;
+ }
+
+ bool AssignProcess(HANDLE hProcess)
+ {
+ if(m_hJob && hProcess)
+ {
+ auto b = (TRUE == ::AssignProcessToJobObject(m_hJob,hProcess));
+ return b;
+ }
+
+ return false;
+ }
+
+ private:
+ HANDLE m_hJob;
+ };
+
+}
+
+INT_PTR CurrencyRatesMenu_Chart(WPARAM wp, LPARAM /*lp*/)
+{
+#ifdef _UNICODE
+ MCONTACT hContact = static_cast<MCONTACT>(wp);
+ if (NULL == hContact)
+ return 0;
+
+ auto sLogFileName = GetContactLogFileName(hContact);
+
+ if(auto hWnd = ::FindWindow(nullptr,_T("Miranda CurrencyRates Chart")))
+ {
+ COPYDATASTRUCT copydata_struct;
+ copydata_struct.cbData = static_cast<DWORD>(sLogFileName.size()*sizeof(TCHAR));
+ copydata_struct.lpData = const_cast<void*>(static_cast<const void*>(sLogFileName.c_str()));
+ copydata_struct.dwData = 0x1945;
+
+ SendMessage(hWnd,WM_COPYDATA,0,reinterpret_cast<LPARAM>(&copydata_struct));
+ }
+ else
+ {
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWNORMAL;
+ ZeroMemory(&pi, sizeof(pi));
+
+ auto sCmdLine = CreateFilePath(_T("CurrencyRatesChart.exe"));
+ sCmdLine += _T(" \"");
+ sCmdLine += sLogFileName;
+ sCmdLine += _T("\"");
+ if(::CreateProcess(nullptr,const_cast<LPTSTR>(sCmdLine.c_str()),nullptr,nullptr,FALSE,0,nullptr,nullptr,&si,&pi))
+ {
+ CMyJob::GetInstance().AssignProcess(pi.hProcess);
+
+ ::CloseHandle(pi.hThread);
+ ::CloseHandle(pi.hProcess);
+ }
+ }
+#endif
+ return 0;
+}
+
+#endif //CHART_IMPLEMENT
diff --git a/protocols/CurrencyRates/src/CurrencyRateChart.h b/protocols/CurrencyRates/src/CurrencyRateChart.h
new file mode 100644
index 0000000000..0869c1fa1c
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRateChart.h
@@ -0,0 +1,12 @@
+#ifndef __39BE8775_A837_494f_925C_0ABF7910F238_CurrencyRateChart_h__
+#define __39BE8775_A837_494f_925C_0ABF7910F238_CurrencyRateChart_h__
+
+#pragma once
+
+#ifdef CHART_IMPLEMENT
+
+INT_PTR CurrencyRatesMenu_Chart(WPARAM wp, LPARAM lp);
+
+#endif
+
+#endif //__39BE8775_A837_494f_925C_0ABF7910F238_CurrencyRateChart_h__
diff --git a/protocols/CurrencyRates/src/CurrencyRateInfoDlg.cpp b/protocols/CurrencyRates/src/CurrencyRateInfoDlg.cpp
new file mode 100644
index 0000000000..c05520686e
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRateInfoDlg.cpp
@@ -0,0 +1,257 @@
+#include "StdAfx.h"
+
+// extern HANDLE g_hWindowListEditSettings;
+extern HGENMENU g_hMenuEditSettings;
+extern HGENMENU g_hMenuOpenLogFile;
+#ifdef CHART_IMPLEMENT
+extern HGENMENU g_hMenuChart;
+#endif
+extern HGENMENU g_hMenuRefresh, g_hMenuRoot;
+
+#define WINDOW_PREFIX_INFO "Currency Rate Info"
+
+MCONTACT g_hContact;
+
+inline bool IsMyContact(MCONTACT hContact)
+{
+ return nullptr != GetContactProviderPtr(hContact);
+}
+
+inline MCONTACT get_contact(HWND hWnd)
+{
+ return MCONTACT(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+}
+
+static bool get_fetch_time(time_t& rTime, MCONTACT hContact)
+{
+ rTime = db_get_dw(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FETCH_TIME, -1);
+ return (rTime != -1);
+}
+
+INT_PTR CALLBACK CurrencyRateInfoDlgProcImpl(MCONTACT hContact, HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ assert(hContact);
+
+ TranslateDialogDefault(hdlg);
+ {
+ tstring sDescription = GetContactName(hContact);
+ ::SetDlgItemText(hdlg, IDC_STATIC_CURRENCYRATE_NAME, sDescription.c_str());
+
+ double dRate = 0.0;
+ if (true == CurrencyRates_DBReadDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_PREV_VALUE, dRate)) {
+ tostringstream o;
+ o.imbue(GetSystemLocale());
+ o << dRate;
+
+ ::SetDlgItemText(hdlg, IDC_EDIT_PREVIOUS_RATE, o.str().c_str());
+ }
+
+ dRate = 0.0;
+ if (true == CurrencyRates_DBReadDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_CURR_VALUE, dRate)) {
+ tostringstream o;
+ o.imbue(GetSystemLocale());
+ o << dRate;
+
+ ::SetDlgItemText(hdlg, IDC_EDIT_RATE, o.str().c_str());
+ }
+
+ time_t nFetchTime;
+ if (true == get_fetch_time(nFetchTime, hContact)) {
+ wchar_t szTime[50] = { 0 };
+ if (0 == _tctime_s(szTime, 50, &nFetchTime)) {
+ ::SetDlgItemText(hdlg, IDC_EDIT_RATE_FETCH_TIME, szTime);
+ }
+ }
+
+ const ICurrencyRatesProvider::CProviderInfo& pi = GetContactProviderPtr(hContact)->GetInfo();
+ tostringstream o;
+ o << TranslateT("Info provided by") << L" <a href=\"" << pi.m_sURL << L"\">" << pi.m_sName << L"</a>";
+
+ ::SetDlgItemText(hdlg, IDC_SYSLINK_PROVIDER, o.str().c_str());
+ }
+ return TRUE;
+
+ case WM_NOTIFY:
+ LPNMHDR pNMHDR = reinterpret_cast<LPNMHDR>(lParam);
+ switch (pNMHDR->code) {
+ case NM_CLICK:
+ if (IDC_SYSLINK_PROVIDER == wParam) {
+ PNMLINK pNMLink = reinterpret_cast<PNMLINK>(pNMHDR);
+ ::ShellExecute(hdlg, L"open", pNMLink->item.szUrl, nullptr, nullptr, SW_SHOWNORMAL);
+ }
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+INT_PTR CALLBACK CurrencyRateInfoDlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ return CurrencyRateInfoDlgProcImpl(g_hContact, hdlg, msg, wParam, lParam);
+}
+
+int CurrencyRatesEventFunc_OnUserInfoInit(WPARAM wp, LPARAM hContact)
+{
+ if (NULL == hContact)
+ return 0;
+
+ if (false == IsMyContact(hContact))
+ return 0;
+
+ g_hContact = hContact;
+
+ OPTIONSDIALOGPAGE odp = {};
+ odp.pfnDlgProc = CurrencyRateInfoDlgProc;
+ odp.position = -2000000000;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_DIALOG_CURRENCYRATE_INFO);
+ odp.szTitle.a = LPGEN("Currency Rate");
+ g_plugin.addUserInfo(wp, &odp);
+ return 0;
+}
+
+
+INT_PTR CurrencyRatesMenu_EditSettings(WPARAM wp, LPARAM)
+{
+ MCONTACT hContact = MCONTACT(wp);
+ if (NULL != hContact)
+ ShowSettingsDlg(hContact);
+ return 0;
+}
+
+namespace
+{
+ bool get_log_file(MCONTACT hContact, tstring& rsLogfile)
+ {
+ rsLogfile = GetContactLogFileName(hContact);
+ return ((rsLogfile.empty()) ? false : true);
+ }
+}
+
+INT_PTR CurrencyRatesMenu_OpenLogFile(WPARAM wp, LPARAM)
+{
+ MCONTACT hContact = MCONTACT(wp);
+ if (NULL == hContact)
+ return 0;
+
+ tstring sLogFileName;
+ if ((true == get_log_file(hContact, sLogFileName)) && (false == sLogFileName.empty()))
+ ::ShellExecute(nullptr, L"open", sLogFileName.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
+
+ return 0;
+}
+
+INT_PTR CurrencyRatesMenu_RefreshContact(WPARAM wp, LPARAM)
+{
+ MCONTACT hContact = MCONTACT(wp);
+ if (NULL == hContact)
+ return 0;
+
+ ICurrencyRatesProvider *pProvider = GetContactProviderPtr(hContact);
+ if (pProvider)
+ pProvider->RefreshContact(hContact);
+ return 0;
+}
+
+static INT_PTR CALLBACK CurrencyRateInfoDlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ MCONTACT hContact = NULL;
+ MWindowList hWL;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ hContact = MCONTACT(lParam);
+ hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX_INFO, false);
+ assert(hWL);
+ WindowList_Add(hWL, hdlg, hContact);
+
+ ::SetWindowLongPtr(hdlg, GWLP_USERDATA, hContact);
+ Utils_RestoreWindowPositionNoSize(hdlg, hContact, CURRENCYRATES_MODULE_NAME, WINDOW_PREFIX_INFO);
+ ::ShowWindow(hdlg, SW_SHOW);
+ break;
+
+ case WM_CLOSE:
+ DestroyWindow(hdlg);
+ return FALSE;
+
+ case WM_DESTROY:
+ hContact = get_contact(hdlg);
+ if (hContact) {
+ SetWindowLongPtr(hdlg, GWLP_USERDATA, 0);
+
+ hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX_INFO, false);
+ assert(hWL);
+ WindowList_Remove(hWL, hdlg);
+ Utils_SaveWindowPosition(hdlg, hContact, CURRENCYRATES_MODULE_NAME, WINDOW_PREFIX_INFO);
+ }
+ return FALSE;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDOK) {
+ ::DestroyWindow(hdlg);
+ return FALSE;
+ }
+
+ default:
+ hContact = get_contact(hdlg);
+ break;
+ }
+
+ return CurrencyRateInfoDlgProcImpl(hContact, hdlg, msg, wParam, lParam);
+}
+
+int CurrencyRates_OnContactDoubleClick(WPARAM wp, LPARAM/* lp*/)
+{
+ MCONTACT hContact = MCONTACT(wp);
+ if (GetContactProviderPtr(hContact)) {
+ MWindowList hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX_INFO, true);
+ assert(hWL);
+ HWND hWnd = WindowList_Find(hWL, hContact);
+ if (nullptr != hWnd) {
+ SetForegroundWindow(hWnd);
+ SetFocus(hWnd);
+ }
+ else if (true == IsMyContact(hContact))
+ CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_DIALOG_CURRENCYRATE_INFO_1), nullptr, CurrencyRateInfoDlgProc1, LPARAM(hContact));
+
+ return 1;
+ }
+
+ return 0;
+}
+
+int CurrencyRates_PrebuildContactMenu(WPARAM wp, LPARAM)
+{
+ Menu_EnableItem(g_hMenuEditSettings, false);
+ Menu_EnableItem(g_hMenuOpenLogFile, false);
+ #ifdef CHART_IMPLEMENT
+ Menu_EnableItem(g_hMenuChart, false);
+ #endif
+ Menu_EnableItem(g_hMenuRefresh, false);
+
+ MCONTACT hContact = MCONTACT(wp);
+ char *szProto = GetContactProto(hContact);
+ if (mir_strcmp(szProto, CURRENCYRATES_PROTOCOL_NAME)) {
+ Menu_ShowItem(g_hMenuRoot, false);
+ return 0;
+ }
+
+ Menu_ShowItem(g_hMenuRoot, true);
+ Menu_EnableItem(g_hMenuEditSettings, true);
+
+ Menu_EnableItem(g_hMenuRefresh, true);
+
+ tstring sLogFileName;
+ bool bThereIsLogFile = (true == get_log_file(hContact, sLogFileName))
+ && (false == sLogFileName.empty()) && (0 == _waccess(sLogFileName.c_str(), 04));
+ if (true == bThereIsLogFile) {
+ #ifdef CHART_IMPLEMENT
+ Menu_EnableItem(g_hMenuChart, true);
+ #endif
+ Menu_EnableItem(g_hMenuOpenLogFile, true);
+ }
+
+ return 0;
+}
diff --git a/protocols/CurrencyRates/src/CurrencyRateInfoDlg.h b/protocols/CurrencyRates/src/CurrencyRateInfoDlg.h
new file mode 100644
index 0000000000..d0df6e99c5
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRateInfoDlg.h
@@ -0,0 +1,11 @@
+#ifndef __aa849fa0_ff3f_49e9_b47a_e7dd34783dc2_CurrencyRateInfoDlg_h__
+#define __aa849fa0_ff3f_49e9_b47a_e7dd34783dc2_CurrencyRateInfoDlg_h__
+
+int CurrencyRatesEventFunc_OnUserInfoInit(WPARAM wp, LPARAM lp);
+INT_PTR CurrencyRatesMenu_EditSettings(WPARAM wp, LPARAM lp);
+INT_PTR CurrencyRatesMenu_OpenLogFile(WPARAM wp, LPARAM lp);
+INT_PTR CurrencyRatesMenu_RefreshContact(WPARAM wp, LPARAM lp);
+int CurrencyRates_PrebuildContactMenu(WPARAM wp, LPARAM lp);
+int CurrencyRates_OnContactDoubleClick(WPARAM wp, LPARAM lp);
+
+#endif //__aa849fa0_ff3f_49e9_b47a_e7dd34783dc2_CurrencyRateInfoDlg_h__
diff --git a/protocols/CurrencyRates/src/CurrencyRatesProviderBase.cpp b/protocols/CurrencyRates/src/CurrencyRatesProviderBase.cpp
new file mode 100644
index 0000000000..592fd551ba
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRatesProviderBase.cpp
@@ -0,0 +1,935 @@
+#include "StdAfx.h"
+
+extern bool g_bAutoUpdate;
+extern HANDLE g_hEventWorkThreadStop;
+
+struct CXMLFileInfo
+{
+ CXMLFileInfo() : m_qs(L"Unknown") {}
+ ICurrencyRatesProvider::CProviderInfo m_pi;
+ CCurrencyRateSection m_qs;
+ tstring m_sURL;
+};
+
+inline tstring get_ini_file_name(LPCTSTR pszFileName)
+{
+ return CreateFilePath(pszFileName);
+}
+
+bool parse_currencyrate(const TiXmlNode *pTop, CCurrencyRate &q)
+{
+ tstring sSymbol, sDescription, sID;
+
+ for (auto *pNode : TiXmlEnum(pTop)) {
+ const char *sName = pNode->Value();
+ if (!mir_strcmpi(sName, "symbol")) {
+ sSymbol = GetNodeText(pNode);
+ if (sSymbol.empty())
+ return false;
+ }
+ else if (!mir_strcmpi(sName, "description")) {
+ sDescription = GetNodeText(pNode);
+ }
+ else if (!mir_strcmpi(sName, "id")) {
+ sID = GetNodeText(pNode);
+ if (sID.empty())
+ return false;
+ }
+ }
+
+ q = CCurrencyRate(sID, TranslateW(sSymbol.c_str()), TranslateW(sDescription.c_str()));
+ return true;
+}
+
+bool parse_section(const TiXmlNode *pTop, CCurrencyRateSection &qs)
+{
+ CCurrencyRateSection::TSections aSections;
+ CCurrencyRateSection::TCurrencyRates aCurrencyRates;
+ tstring sSectionName;
+
+ for (auto *pNode : TiXmlEnum(pTop)) {
+ const char *sName = pNode->Value();
+ if (!mir_strcmpi(sName, "section")) {
+ CCurrencyRateSection qs1;
+ if (true == parse_section(pNode, qs1))
+ aSections.push_back(qs1);
+ }
+ else if (!mir_strcmpi(sName, "currencyrate")) {
+ CCurrencyRate q;
+ if (true == parse_currencyrate(pNode, q))
+ aCurrencyRates.push_back(q);
+ }
+ else if (!mir_strcmpi(sName, "name")) {
+ sSectionName = GetNodeText(pNode);
+ if (sSectionName.empty())
+ return false;
+ }
+ }
+
+ qs = CCurrencyRateSection(TranslateW(sSectionName.c_str()), aSections, aCurrencyRates);
+ return true;
+}
+
+const TiXmlNode* find_provider(const TiXmlNode *pRoot)
+{
+ for (auto *pNode : TiXmlEnum(pRoot)) {
+ const char *sName = pNode->Value();
+ if (!mir_strcmpi(sName, "Provider"))
+ return pNode;
+
+ if (auto *pProvider = find_provider(pNode))
+ return pProvider;
+ }
+
+ return nullptr;
+}
+
+CXMLFileInfo parse_ini_file(const tstring &rsXMLFile, bool &rbSucceded)
+{
+ CXMLFileInfo res;
+ CCurrencyRateSection::TSections aSections;
+
+ TiXmlDocument doc;
+ if (doc.LoadFile(_T2A(rsXMLFile.c_str())) == tinyxml2::XML_SUCCESS) {
+ const TiXmlNode *pProvider = find_provider(&doc);
+ if (pProvider) {
+ rbSucceded = true;
+ for (auto *pNode : TiXmlEnum(pProvider)) {
+ const char *sName = pNode->Value();
+ if (!mir_strcmpi(sName, "section")) {
+ CCurrencyRateSection qs;
+ if (parse_section(pNode, qs))
+ aSections.push_back(qs);
+ }
+ else if (!mir_strcmpi(sName, "Name"))
+ res.m_pi.m_sName = GetNodeText(pNode);
+ else if (!mir_strcmpi(sName, "ref"))
+ res.m_pi.m_sURL = GetNodeText(pNode);
+ else if (!mir_strcmpi(sName, "url"))
+ res.m_sURL = GetNodeText(pNode);
+ }
+ }
+ }
+
+ res.m_qs = CCurrencyRateSection(res.m_pi.m_sName, aSections);
+ return res;
+}
+
+CXMLFileInfo init_xml_info(LPCTSTR pszFileName, bool& rbSucceded)
+{
+ rbSucceded = false;
+ tstring sIniFile = get_ini_file_name(pszFileName);
+ return parse_ini_file(sIniFile, rbSucceded);
+}
+
+CCurrencyRatesProviderBase::CCurrencyRatesProviderBase() :
+ m_hEventSettingsChanged(::CreateEvent(nullptr, FALSE, FALSE, nullptr)),
+ m_hEventRefreshContact(::CreateEvent(nullptr, FALSE, FALSE, nullptr)),
+ m_bRefreshInProgress(false)
+{
+}
+
+CCurrencyRatesProviderBase::~CCurrencyRatesProviderBase()
+{
+ delete m_pXMLInfo;
+
+ ::CloseHandle(m_hEventSettingsChanged);
+ ::CloseHandle(m_hEventRefreshContact);
+}
+
+bool CCurrencyRatesProviderBase::Init()
+{
+ bool bSucceded = (m_pXMLInfo == nullptr);
+ if (!m_pXMLInfo)
+ m_pXMLInfo = new CXMLFileInfo(init_xml_info(DB_DEF_IniFileName, bSucceded));
+
+ return bSucceded;
+}
+
+const CCurrencyRatesProviderBase::CProviderInfo& CCurrencyRatesProviderBase::GetInfo() const
+{
+ return m_pXMLInfo->m_pi;
+}
+
+const CCurrencyRateSection& CCurrencyRatesProviderBase::GetCurrencyRates() const
+{
+ return m_pXMLInfo->m_qs;
+}
+
+const tstring& CCurrencyRatesProviderBase::GetURL() const
+{
+ return m_pXMLInfo->m_sURL;
+}
+
+bool CCurrencyRatesProviderBase::IsOnline()
+{
+ return /*g_bAutoUpdate*/true;
+}
+
+void CCurrencyRatesProviderBase::AddContact(MCONTACT hContact)
+{
+ // CCritSection cs(m_cs);
+ assert(m_aContacts.end() == std::find(m_aContacts.begin(), m_aContacts.end(), hContact));
+
+ m_aContacts.push_back(hContact);
+}
+
+void CCurrencyRatesProviderBase::DeleteContact(MCONTACT hContact)
+{
+ mir_cslock lck(m_cs);
+
+ TContacts::iterator i = std::find(m_aContacts.begin(), m_aContacts.end(), hContact);
+ if (i != m_aContacts.end())
+ m_aContacts.erase(i);
+}
+
+void CCurrencyRatesProviderBase::SetContactStatus(MCONTACT hContact, int nNewStatus)
+{
+ int nStatus = db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_STATUS, ID_STATUS_OFFLINE);
+ if (nNewStatus != nStatus) {
+ db_set_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_STATUS, nNewStatus);
+
+ if (ID_STATUS_ONLINE != nNewStatus) {
+ db_unset(hContact, LIST_MODULE_NAME, STATUS_MSG_NAME);
+ tstring sSymbol = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL);
+ if (false == sSymbol.empty())
+ db_set_ws(hContact, LIST_MODULE_NAME, CONTACT_LIST_NAME, sSymbol.c_str());
+
+ SetContactExtraImage(hContact, eiEmpty);
+ }
+ }
+}
+
+class CTendency
+{
+ enum { NumValues = 2 };
+ enum EComparison
+ {
+ NonValid,
+ Greater,
+ Less,
+ Equal,
+ GreaterOrEqual,
+ LessOrEqual
+ };
+
+public:
+ enum EResult
+ {
+ NotChanged,
+ Up,
+ Down
+ };
+
+public:
+ CTendency() : m_nComparison(NonValid) {}
+
+ bool Parse(CCurrencyRatesProviderBase *pProvider, const tstring& rsFrmt, MCONTACT hContact)
+ {
+ m_abValueFlags[0] = false;
+ m_abValueFlags[1] = false;
+ m_nComparison = NonValid;
+ bool bValid = true;
+ int nCurValue = 0;
+ for (tstring::const_iterator i = rsFrmt.begin(); i != rsFrmt.end() && bValid && nCurValue < NumValues;) {
+ wchar_t chr = *i;
+ switch (chr) {
+ default:
+ if (false == std::isspace(chr))
+ bValid = false;
+ else
+ ++i;
+ break;
+
+ case '%':
+ ++i;
+ if (i != rsFrmt.end()) {
+ wchar_t t = *i;
+ ++i;
+
+ double d;
+ bValid = pProvider->ParseSymbol(hContact, t, d);
+ if (bValid) {
+ m_adValues[nCurValue] = d;
+ m_abValueFlags[nCurValue] = true;
+ ++nCurValue;
+ }
+ }
+ else bValid = false;
+ break;
+ case '>':
+ m_nComparison = Greater;
+ ++i;
+ break;
+ case '<':
+ m_nComparison = Less;
+ ++i;
+ break;
+ case '=':
+ switch (m_nComparison) {
+ default:
+ bValid = false;
+ break;
+ case NonValid:
+ m_nComparison = Equal;
+ break;
+ case Greater:
+ m_nComparison = GreaterOrEqual;
+ break;
+ case Less:
+ m_nComparison = LessOrEqual;
+ break;
+ }
+ ++i;
+ break;
+ }
+ }
+
+ return (bValid && IsValid());
+ }
+
+ bool IsValid() const { return (m_abValueFlags[0] && m_abValueFlags[1] && (m_nComparison != NonValid)); }
+
+ EResult Compare() const
+ {
+ switch (m_nComparison) {
+ case Greater:
+ if (true == IsWithinAccuracy(m_adValues[0], m_adValues[1]))
+ return NotChanged;
+
+ if (m_adValues[0] > m_adValues[1])
+ return Up;
+ return Down;
+
+ case GreaterOrEqual:
+ if ((true == IsWithinAccuracy(m_adValues[0], m_adValues[1])) || (m_adValues[0] > m_adValues[1]))
+ return Up;
+ return Down;
+
+ case Less:
+ if (true == IsWithinAccuracy(m_adValues[0], m_adValues[1]))
+ return NotChanged;
+
+ if (m_adValues[0] < m_adValues[1])
+ return Up;
+ return Down;
+
+ case LessOrEqual:
+ if ((true == IsWithinAccuracy(m_adValues[0], m_adValues[1])) || (m_adValues[0] < m_adValues[1]))
+ return Up;
+ return Down;
+
+ case Equal:
+ if (true == IsWithinAccuracy(m_adValues[0], m_adValues[1]))
+ return Up;
+ return Down;
+ }
+ return NotChanged;
+ }
+
+private:
+ double m_adValues[NumValues];
+ bool m_abValueFlags[NumValues];
+ EComparison m_nComparison;
+};
+
+tstring format_rate(const ICurrencyRatesProvider *pProvider, MCONTACT hContact, const tstring &rsFrmt)
+{
+ tstring sResult;
+
+ for (tstring::const_iterator i = rsFrmt.begin(); i != rsFrmt.end();) {
+ wchar_t chr = *i;
+ switch (chr) {
+ default:
+ sResult += chr;
+ ++i;
+ break;
+
+ case '\\':
+ ++i;
+ if (i != rsFrmt.end()) {
+ wchar_t t = *i;
+ switch (t) {
+ case '%': sResult += L"%"; break;
+ case 't': sResult += L"\t"; break;
+ case 'n': sResult += L"\n"; break;
+ case '\\': sResult += L"\\"; break;
+ default: sResult += chr; sResult += t; break;
+ }
+ ++i;
+ }
+ else sResult += chr;
+ break;
+
+ case '%':
+ ++i;
+ if (i != rsFrmt.end()) {
+ chr = *i;
+
+ byte nWidth = 0;
+ if (::isdigit(chr)) {
+ nWidth = chr - 0x30;
+ ++i;
+ if (i == rsFrmt.end()) {
+ sResult += chr;
+ break;
+ }
+ else chr = *i;
+ }
+
+ sResult += pProvider->FormatSymbol(hContact, chr, nWidth);
+ ++i;
+ }
+ else sResult += chr;
+ break;
+ }
+ }
+
+ return sResult;
+}
+
+void log_to_file(const ICurrencyRatesProvider *pProvider,
+ MCONTACT hContact,
+ const tstring& rsLogFileName,
+ const tstring& rsFormat)
+{
+ CreatePathToFileW(rsLogFileName.c_str());
+
+ tofstream file(rsLogFileName.c_str(), std::ios::app | std::ios::out);
+ file.imbue(GetSystemLocale());
+ if (file.good()) {
+ tstring s = format_rate(pProvider, hContact, rsFormat);
+ file << s;
+ }
+}
+
+void log_to_history(const ICurrencyRatesProvider *pProvider,
+ MCONTACT hContact,
+ time_t nTime,
+ const tstring& rsFormat)
+{
+ tstring s = format_rate(pProvider, hContact, rsFormat);
+ T2Utf psz(s.c_str());
+
+ DBEVENTINFO dbei = {};
+ dbei.szModule = CURRENCYRATES_MODULE_NAME;
+ dbei.timestamp = static_cast<DWORD>(nTime);
+ dbei.flags = DBEF_READ | DBEF_UTF;
+ dbei.eventType = EVENTTYPE_MESSAGE;
+ dbei.cbBlob = (int)::mir_strlen(psz) + 1;
+ dbei.pBlob = (PBYTE)(char*)psz;
+ db_event_add(hContact, &dbei);
+}
+
+bool do_set_contact_extra_icon(MCONTACT hContact, const CTendency& tendency)
+{
+ CTendency::EResult nComparison = tendency.Compare();
+
+ if (CTendency::NotChanged == nComparison)
+ return SetContactExtraImage(hContact, eiNotChanged);
+
+ if (CTendency::Up == nComparison)
+ return SetContactExtraImage(hContact, eiUp);
+
+ if (CTendency::Down == nComparison)
+ return SetContactExtraImage(hContact, eiDown);
+
+ return false;
+}
+
+bool show_popup(const ICurrencyRatesProvider *pProvider,
+ MCONTACT hContact,
+ const CTendency& tendency,
+ const tstring& rsFormat,
+ const CPopupSettings& ps)
+{
+ if (!ServiceExists(MS_POPUP_ADDPOPUPW))
+ return false;
+
+ POPUPDATAW ppd;
+ memset(&ppd, 0, sizeof(ppd));
+ ppd.lchContact = hContact;
+
+ if (tendency.IsValid()) {
+ CTendency::EResult nComparison = tendency.Compare();
+ if (CTendency::NotChanged == nComparison)
+ ppd.lchIcon = CurrencyRates_LoadIconEx(IDI_ICON_NOTCHANGED);
+ else if (CTendency::Up == nComparison)
+ ppd.lchIcon = CurrencyRates_LoadIconEx(IDI_ICON_UP);
+ else if (CTendency::Down == nComparison)
+ ppd.lchIcon = CurrencyRates_LoadIconEx(IDI_ICON_DOWN);
+ }
+
+ mir_wstrncpy(ppd.lpwzContactName, pProvider->FormatSymbol(hContact, 's').c_str(), MAX_CONTACTNAME);
+ {
+ ptrW ss(variables_parsedup((wchar_t*)rsFormat.c_str(), nullptr, hContact));
+ tstring sText = format_rate(pProvider, hContact, tstring(ss));
+ mir_wstrncpy(ppd.lpwzText, sText.c_str(), MAX_SECONDLINE);
+ }
+
+ if (CPopupSettings::colourDefault == ps.GetColourMode()) {
+ ppd.colorText = CPopupSettings::GetDefColourText();
+ ppd.colorBack = CPopupSettings::GetDefColourBk();
+ }
+ else {
+ ppd.colorText = ps.GetColourText();
+ ppd.colorBack = ps.GetColourBk();
+ }
+
+ switch (ps.GetDelayMode()) {
+ default:
+ assert(!"Unknown popup delay mode");
+ case CPopupSettings::delayFromPopup:
+ ppd.iSeconds = 0;
+ break;
+ case CPopupSettings::delayPermanent:
+ ppd.iSeconds = -1;
+ break;
+ case CPopupSettings::delayCustom:
+ ppd.iSeconds = ps.GetDelayTimeout();
+ break;
+ }
+
+ LPARAM lp = 0;
+ if (false == ps.GetHistoryFlag())
+ lp |= 0x08;
+
+ return (0 == CallService(MS_POPUP_ADDPOPUPW, reinterpret_cast<WPARAM>(&ppd), lp));
+}
+
+void CCurrencyRatesProviderBase::WriteContactRate(MCONTACT hContact, double dRate, const tstring& rsSymbol/* = ""*/)
+{
+ time_t nTime = ::time(0);
+
+ if (false == rsSymbol.empty())
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL, rsSymbol.c_str());
+
+ double dPrev = 0.0;
+ bool bValidPrev = CurrencyRates_DBReadDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_CURR_VALUE, dPrev);
+ if (true == bValidPrev)
+ CurrencyRates_DBWriteDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_PREV_VALUE, dPrev);
+
+ CurrencyRates_DBWriteDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_CURR_VALUE, dRate);
+ db_set_dw(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FETCH_TIME, nTime);
+
+ tstring sSymbol = rsSymbol;
+
+ tostringstream oNick;
+ oNick.imbue(GetSystemLocale());
+ if (false == m_sContactListFormat.empty()) {
+ tstring s = format_rate(this, hContact, m_sContactListFormat);
+ oNick << s;
+ }
+ else {
+ if (true == sSymbol.empty())
+ sSymbol = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL);
+
+ oNick << std::setfill(L' ') << std::setw(10) << std::left << sSymbol << std::setw(6) << std::right << dRate;
+ }
+ CTendency tendency;
+
+ if (true == tendency.Parse(this, m_sTendencyFormat, hContact))
+ do_set_contact_extra_icon(hContact, tendency);
+
+ db_set_ws(hContact, LIST_MODULE_NAME, CONTACT_LIST_NAME, oNick.str().c_str());
+
+ tstring sStatusMsg = format_rate(this, hContact, m_sStatusMsgFormat);
+ if (false == sStatusMsg.empty())
+ db_set_ws(hContact, LIST_MODULE_NAME, STATUS_MSG_NAME, sStatusMsg.c_str());
+ else
+ db_unset(hContact, LIST_MODULE_NAME, STATUS_MSG_NAME);
+
+ bool bUseContactSpecific = (db_get_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CONTACT_SPEC_SETTINGS, 0) > 0);
+
+ CAdvProviderSettings global_settings(this);
+
+ WORD dwMode = (bUseContactSpecific)
+ ? db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG, static_cast<WORD>(lmDisabled))
+ : global_settings.GetLogMode();
+ if (dwMode&lmExternalFile) {
+ bool bAdd = true;
+ bool bOnlyIfChanged = (bUseContactSpecific)
+ ? (db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG_FILE_CONDITION, 1) > 0)
+ : global_settings.GetLogOnlyChangedFlag();
+ if (true == bOnlyIfChanged) {
+ bAdd = ((false == bValidPrev) || (false == IsWithinAccuracy(dRate, dPrev)));
+ }
+ if (true == bAdd) {
+ tstring sLogFileName = (bUseContactSpecific)
+ ? CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG_FILE, global_settings.GetLogFileName().c_str())
+ : global_settings.GetLogFileName();
+
+ if (true == sSymbol.empty()) {
+ sSymbol = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL);
+ }
+
+ sLogFileName = GenerateLogFileName(sLogFileName, sSymbol);
+
+ tstring sFormat = global_settings.GetLogFormat();
+ if (bUseContactSpecific)
+ sFormat = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_LOG_FILE, DB_DEF_LogFormat);
+
+ log_to_file(this, hContact, sLogFileName, sFormat);
+ }
+ }
+ if (dwMode&lmInternalHistory) {
+ bool bAdd = true;
+ bool bOnlyIfChanged = (bUseContactSpecific)
+ ? (db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_HISTORY_CONDITION, 1) > 0)
+ : global_settings.GetHistoryOnlyChangedFlag();
+
+ if (true == bOnlyIfChanged) {
+ bAdd = ((false == bValidPrev) || (false == IsWithinAccuracy(dRate, dPrev)));
+ }
+ if (true == bAdd) {
+ tstring sFormat = (bUseContactSpecific)
+ ? CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_HISTORY, global_settings.GetHistoryFormat().c_str())
+ : global_settings.GetHistoryFormat();
+
+ log_to_history(this, hContact, nTime, sFormat);
+ }
+ }
+
+ if (dwMode&lmPopup) {
+ bool bOnlyIfChanged = (bUseContactSpecific)
+ ? (1 == db_get_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_CONDITION, 1) > 0)
+ : global_settings.GetShowPopupIfValueChangedFlag();
+ if ((false == bOnlyIfChanged)
+ || ((true == bOnlyIfChanged) && (true == bValidPrev) && (false == IsWithinAccuracy(dRate, dPrev)))) {
+ tstring sFormat = (bUseContactSpecific)
+ ? CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_POPUP, global_settings.GetPopupFormat().c_str())
+ : global_settings.GetPopupFormat();
+
+ CPopupSettings ps = *(global_settings.GetPopupSettingsPtr());
+ ps.InitForContact(hContact);
+ show_popup(this, hContact, tendency, sFormat, ps);
+ }
+ }
+
+ SetContactStatus(hContact, ID_STATUS_ONLINE);
+}
+
+MCONTACT CCurrencyRatesProviderBase::CreateNewContact(const tstring& rsName)
+{
+ MCONTACT hContact = db_add_contact();
+ if (hContact) {
+ if (0 == Proto_AddToContact(hContact, CURRENCYRATES_PROTOCOL_NAME)) {
+ tstring sProvName = GetInfo().m_sName;
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_PROVIDER, sProvName.c_str());
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL, rsName.c_str());
+ db_set_ws(hContact, LIST_MODULE_NAME, CONTACT_LIST_NAME, rsName.c_str());
+
+ mir_cslock lck(m_cs);
+ m_aContacts.push_back(hContact);
+ }
+ else {
+ db_delete_contact(hContact);
+ hContact = NULL;
+ }
+ }
+
+ return hContact;
+}
+
+DWORD get_refresh_timeout_miliseconds()
+{
+ if (!g_bAutoUpdate)
+ return INFINITE;
+
+ int nRefreshRateType = db_get_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_RefreshRateType, RRT_MINUTES);
+ if (nRefreshRateType < RRT_SECONDS || nRefreshRateType > RRT_HOURS)
+ nRefreshRateType = RRT_MINUTES;
+
+ DWORD nTimeout = db_get_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_RefreshRateValue, 1);
+ switch (nRefreshRateType) {
+ default:
+ case RRT_SECONDS:
+ if (nTimeout < 1 || nTimeout > 60)
+ nTimeout = 1;
+
+ nTimeout *= 1000;
+ break;
+ case RRT_MINUTES:
+ if (nTimeout < 1 || nTimeout > 60)
+ nTimeout = 1;
+
+ nTimeout *= 1000 * 60;
+ break;
+ case RRT_HOURS:
+ if (nTimeout < 1 || nTimeout > 24)
+ nTimeout = 1;
+
+ nTimeout *= 1000 * 60 * 60;
+ break;
+ }
+
+ return nTimeout;
+}
+
+class CBoolGuard
+{
+public:
+ CBoolGuard(bool& rb) : m_b(rb) { m_b = true; }
+ ~CBoolGuard() { m_b = false; }
+
+private:
+ bool m_b;
+};
+
+void CCurrencyRatesProviderBase::Run()
+{
+ DWORD nTimeout = get_refresh_timeout_miliseconds();
+ m_sContactListFormat = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_DisplayNameFormat, DB_DEF_DisplayNameFormat);
+ m_sStatusMsgFormat = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_StatusMsgFormat, DB_DEF_StatusMsgFormat);
+ m_sTendencyFormat = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_TendencyFormat, DB_DEF_TendencyFormat);
+
+ enum
+ {
+ STOP_THREAD = 0,
+ SETTINGS_CHANGED = 1,
+ REFRESH_CONTACT = 2,
+ COUNT_SYNC_OBJECTS = 3
+ };
+
+ HANDLE anEvents[COUNT_SYNC_OBJECTS];
+ anEvents[STOP_THREAD] = g_hEventWorkThreadStop;
+ anEvents[SETTINGS_CHANGED] = m_hEventSettingsChanged;
+ anEvents[REFRESH_CONTACT] = m_hEventRefreshContact;
+
+ TContacts anContacts;
+ {
+ mir_cslock lck(m_cs);
+ anContacts = m_aContacts;
+ }
+
+ bool bGoToBed = false;
+
+ if (g_bAutoUpdate) {
+ CBoolGuard bg(m_bRefreshInProgress);
+ RefreshCurrencyRates(anContacts);
+ }
+
+ while (false == bGoToBed) {
+ anContacts.clear();
+
+ DWORD dwBegin = ::GetTickCount();
+ DWORD dwResult = ::WaitForMultipleObjects(COUNT_SYNC_OBJECTS, anEvents, FALSE, nTimeout);
+ switch (dwResult) {
+ case WAIT_FAILED:
+ assert(!"WaitForMultipleObjects failed");
+ bGoToBed = true;
+ break;
+
+ case WAIT_ABANDONED_0 + STOP_THREAD:
+ case WAIT_ABANDONED_0 + SETTINGS_CHANGED:
+ case WAIT_ABANDONED_0 + REFRESH_CONTACT:
+ assert(!"WaitForMultipleObjects abandoned");
+
+ case WAIT_OBJECT_0 + STOP_THREAD:
+ bGoToBed = true;
+ break;
+
+ case WAIT_OBJECT_0 + SETTINGS_CHANGED:
+ nTimeout = get_refresh_timeout_miliseconds();
+ m_sContactListFormat = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_DisplayNameFormat, DB_DEF_DisplayNameFormat);
+ m_sStatusMsgFormat = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_StatusMsgFormat, DB_DEF_StatusMsgFormat);
+ m_sTendencyFormat = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_TendencyFormat, DB_DEF_TendencyFormat);
+ {
+ mir_cslock lck(m_cs);
+ anContacts = m_aContacts;
+ }
+ break;
+
+ case WAIT_OBJECT_0 + REFRESH_CONTACT:
+ {
+ DWORD dwTimeRest = ::GetTickCount() - dwBegin;
+ if (INFINITE != nTimeout && dwTimeRest < nTimeout)
+ nTimeout -= dwTimeRest;
+
+ {
+ mir_cslock lck(m_cs);
+ anContacts = m_aRefreshingContacts;
+ m_aRefreshingContacts.clear();
+ }
+
+ CBoolGuard bg(m_bRefreshInProgress);
+ RefreshCurrencyRates(anContacts);
+ }
+ break;
+
+ case WAIT_TIMEOUT:
+ nTimeout = get_refresh_timeout_miliseconds();
+ {
+ mir_cslock lck(m_cs);
+ anContacts = m_aContacts;
+ }
+ {
+ CBoolGuard bg(m_bRefreshInProgress);
+ RefreshCurrencyRates(anContacts);
+ }
+ break;
+
+ default:
+ assert(!"What is the hell?");
+ }
+ }
+
+ OnEndRun();
+}
+
+void CCurrencyRatesProviderBase::OnEndRun()
+{
+ TContacts anContacts;
+ {
+ mir_cslock lck(m_cs);
+ anContacts = m_aContacts;
+ m_aRefreshingContacts.clear();
+ }
+
+ CBoolGuard bg(m_bRefreshInProgress);
+ for (auto &it : anContacts)
+ SetContactStatus(it, ID_STATUS_OFFLINE);
+}
+
+void CCurrencyRatesProviderBase::RefreshSettings()
+{
+ ::SetEvent(m_hEventSettingsChanged);
+}
+
+void CCurrencyRatesProviderBase::RefreshAllContacts()
+{
+ { mir_cslock lck(m_cs);
+ m_aRefreshingContacts.clear();
+ for (auto &hContact : m_aContacts)
+ m_aRefreshingContacts.push_back(hContact);
+ }
+
+ ::SetEvent(m_hEventRefreshContact);
+}
+
+void CCurrencyRatesProviderBase::RefreshContact(MCONTACT hContact)
+{
+ { mir_cslock lck(m_cs);
+ m_aRefreshingContacts.push_back(hContact);
+ }
+
+ ::SetEvent(m_hEventRefreshContact);
+}
+
+void CCurrencyRatesProviderBase::FillFormat(TFormatSpecificators &array) const
+{
+ array.push_back(CFormatSpecificator(L"%S", TranslateT("Source of Information")));
+ array.push_back(CFormatSpecificator(L"%r", TranslateT("Rate Value")));
+ array.push_back(CFormatSpecificator(L"%p", TranslateT("Previous Rate Value")));
+ array.push_back(CFormatSpecificator(L"%X", TranslateT("Fetch Time")));
+ array.push_back(CFormatSpecificator(L"%x", TranslateT("Fetch Date")));
+ array.push_back(CFormatSpecificator(L"%t", TranslateT("Fetch Time and Date")));
+ array.push_back(CFormatSpecificator(L"\\%", TranslateT("Percentage Character (%)")));
+ array.push_back(CFormatSpecificator(L"\\t", TranslateT("Tabulation")));
+ array.push_back(CFormatSpecificator(L"\\\\", TranslateT("Left slash (\\)")));
+}
+
+bool CCurrencyRatesProviderBase::ParseSymbol(MCONTACT hContact, wchar_t c, double &d)
+{
+ switch (c) {
+ case 'r':
+ case 'R':
+ return CurrencyRates_DBReadDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_CURR_VALUE, d);
+
+ case 'p':
+ case 'P':
+ return CurrencyRates_DBReadDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_PREV_VALUE, d);
+ }
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool get_fetch_time(MCONTACT hContact, time_t &rTime)
+{
+ DBVARIANT dbv;
+ if (db_get(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FETCH_TIME, &dbv) || (DBVT_DWORD != dbv.type))
+ return false;
+
+ rTime = dbv.dVal;
+ return true;
+}
+
+static tstring format_fetch_time(MCONTACT hContact, const tstring &rsFormat)
+{
+ time_t nTime;
+ if (true == get_fetch_time(hContact, nTime)) {
+ boost::posix_time::ptime time = boost::date_time::c_local_adjustor<boost::posix_time::ptime>::utc_to_local(boost::posix_time::from_time_t(nTime));
+ tostringstream k;
+ k.imbue(std::locale(GetSystemLocale(), new ttime_facet(rsFormat.c_str())));
+ k << time;
+ return k.str();
+ }
+
+ return tstring();
+}
+
+static tstring format_double(double dValue, int nWidth)
+{
+ tostringstream o;
+ o.imbue(GetSystemLocale());
+
+ if (nWidth > 0 && nWidth <= 9)
+ o << std::setprecision(nWidth) << std::showpoint << std::fixed;
+
+ o << dValue;
+
+ return o.str();
+}
+
+tstring CCurrencyRatesProviderBase::FormatSymbol(MCONTACT hContact, wchar_t c, int nWidth) const
+{
+ tstring ret;
+ double d = 0.0;
+
+ switch (c) {
+ case '%':
+ case '\t':
+ case '\\':
+ ret = c;
+ break;
+ case 'S':
+ ret = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_PROVIDER);
+ break;
+ case 's':
+ ret = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL);
+ break;
+ case 'X':
+ ret = format_fetch_time(hContact, CurrencyRates_GetTimeFormat(true));
+ break;
+ case 'x':
+ ret = format_fetch_time(hContact, CurrencyRates_GetDateFormat(true));
+ break;
+ case 't':
+ {
+ tstring sFrmt = CurrencyRates_GetDateFormat(true);
+ sFrmt += L" ";
+ sFrmt += CurrencyRates_GetTimeFormat(true);
+ ret = format_fetch_time(hContact, sFrmt);
+ }
+ break;
+ case 'r':
+ case 'R':
+ if (true == CurrencyRates_DBReadDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_CURR_VALUE, d))
+ ret = format_double(d, nWidth);
+ else
+ ret = L"-";
+ break;
+
+ case 'p':
+ case 'P':
+ if (true == CurrencyRates_DBReadDouble(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_PREV_VALUE, d))
+ ret = format_double(d, nWidth);
+ else
+ ret = L"-";
+ break;
+ }
+
+ return ret;
+}
diff --git a/protocols/CurrencyRates/src/CurrencyRatesProviderBase.h b/protocols/CurrencyRates/src/CurrencyRatesProviderBase.h
new file mode 100644
index 0000000000..d407a9a709
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRatesProviderBase.h
@@ -0,0 +1,122 @@
+#ifndef __3e6cb4ec_fc47_468f_a2c8_a77941176bc9_CurrencyRatesProviderBase_h__
+#define __3e6cb4ec_fc47_468f_a2c8_a77941176bc9_CurrencyRatesProviderBase_h__
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// CCurrencyRate - one currency
+
+class CCurrencyRate
+{
+public:
+ CCurrencyRate(const tstring& rsID = L"", const tstring& rsSymbol = L"", const tstring& rsName = L"")
+ : m_sSymbol(rsSymbol), m_sName(rsName), m_sID(rsID){}
+
+ const tstring& GetSymbol() const{ return m_sSymbol; }
+ const tstring& GetName() const{ return m_sName; }
+ const tstring& GetID() const{ return m_sID; }
+
+private:
+ tstring m_sSymbol;
+ tstring m_sName;
+ tstring m_sID;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// CCurrencyRateSection - block of currency settings
+
+class CCurrencyRateSection
+{
+public:
+ typedef std::vector<CCurrencyRateSection> TSections;
+ typedef std::vector<CCurrencyRate> TCurrencyRates;
+
+public:
+ CCurrencyRateSection(const tstring& rsName = L"", const TSections& raSections = TSections(), const TCurrencyRates& raCurrencyRates = TCurrencyRates())
+ : m_sName(rsName), m_aSections(raSections), m_aCurrencyRates(raCurrencyRates){}
+
+ const tstring& GetName() const
+ {
+ return m_sName;
+ }
+
+ size_t GetSectionCount() const
+ {
+ return m_aSections.size();
+ }
+
+ CCurrencyRateSection GetSection(size_t nIndex) const
+ {
+ return ((nIndex < m_aSections.size()) ? m_aSections[nIndex] : CCurrencyRateSection());
+ }
+
+ size_t GetCurrencyRateCount() const
+ {
+ return m_aCurrencyRates.size();
+ }
+
+ CCurrencyRate GetCurrencyRate(size_t nIndex) const
+ {
+ return ((nIndex < m_aCurrencyRates.size()) ? m_aCurrencyRates[nIndex] : CCurrencyRate());
+ }
+
+private:
+ tstring m_sName;
+ TSections m_aSections;
+ TCurrencyRates m_aCurrencyRates;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// CCurrencyRatesProviderBase - basic set of methods for retrieving currencies
+
+typedef std::vector<MCONTACT> TContacts;
+
+class CCurrencyRatesProviderBase : public ICurrencyRatesProvider
+{
+ void OnEndRun();
+
+ struct CXMLFileInfo *m_pXMLInfo = nullptr;
+
+ HANDLE m_hEventSettingsChanged;
+ HANDLE m_hEventRefreshContact;
+ tstring m_sContactListFormat;
+ tstring m_sStatusMsgFormat;
+ tstring m_sTendencyFormat;
+ TContacts m_aRefreshingContacts;
+ bool m_bRefreshInProgress;
+
+public:
+ CCurrencyRatesProviderBase();
+ ~CCurrencyRatesProviderBase();
+
+ const CCurrencyRateSection& GetCurrencyRates() const;
+
+ bool Init() override;
+ const CProviderInfo& GetInfo() const override;
+
+ void AddContact(MCONTACT hContact) override;
+ void DeleteContact(MCONTACT hContact) override;
+
+ void Run() override;
+
+ void RefreshAllContacts() override;
+ void RefreshSettings() override;
+ void RefreshContact(MCONTACT hContact) override;
+
+ void FillFormat(TFormatSpecificators&) const override;
+ bool ParseSymbol(MCONTACT hContact, wchar_t c, double &d) override;
+ tstring FormatSymbol(MCONTACT hContact, wchar_t c, int nWidth = 0) const override;
+
+protected:
+ const tstring& GetURL() const;
+ MCONTACT CreateNewContact(const tstring& rsName);
+ static bool IsOnline();
+ static void SetContactStatus(MCONTACT hContact, int nNewStatus);
+ void WriteContactRate(MCONTACT hContact, double dRate, const tstring& rsSymbol = L"");
+
+ virtual void RefreshCurrencyRates(TContacts &anContacts) = 0;
+
+protected:
+ TContacts m_aContacts;
+ mutable mir_cs m_cs;
+};
+
+#endif //__3e6cb4ec_fc47_468f_a2c8_a77941176bc9_CurrencyRatesProviderBase_h__
diff --git a/protocols/CurrencyRates/src/CurrencyRatesProviderCurrencyConverter.cpp b/protocols/CurrencyRates/src/CurrencyRatesProviderCurrencyConverter.cpp
new file mode 100644
index 0000000000..36693d52b1
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRatesProviderCurrencyConverter.cpp
@@ -0,0 +1,438 @@
+#include "stdafx.h"
+#include "CurrencyRatesProviderCurrencyConverter.h"
+#include <boost\property_tree\ptree.hpp>
+#include <boost\property_tree\json_parser.hpp>
+
+tstring build_url(const tstring &rsURL, const tstring &from, const tstring &to)
+{
+ tostringstream o;
+ o << rsURL << L"?q=" << from << L"_" << to << "&compact=ultra";
+ ptrA szApiKey(g_plugin.getStringA(DB_KEY_ApiKey));
+ if (szApiKey != nullptr)
+ o << "&apiKey=" << szApiKey.get();
+ return o.str();
+}
+
+tstring build_url(MCONTACT hContact, const tstring &rsURL)
+{
+ tstring sFrom = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_ID);
+ tstring sTo = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_ID);
+ return build_url(rsURL, sFrom, sTo);
+}
+
+bool parse_responce(const tstring &rsJSON, double &dRate)
+{
+ try {
+ boost::property_tree::ptree pt;
+ std::istringstream i_stream(currencyrates_t2a(rsJSON.c_str()));
+
+ boost::property_tree::read_json(i_stream, pt);
+ if (!pt.empty()) {
+ auto pt_nested = pt.begin()->second;
+ dRate = pt_nested.get_value<double>();
+ }
+ else {
+ dRate = pt.get_value<double>();
+ }
+
+ return true;
+ }
+ catch (boost::property_tree::ptree_error&) {
+ }
+ return false;
+}
+
+using TWatchedRates = std::vector<CCurrencyRatesProviderCurrencyConverter::TRateInfo>;
+TWatchedRates g_aWatchedRates;
+
+INT_PTR CALLBACK OptDlgProc(HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ auto get_provider = []()->CCurrencyRatesProviderCurrencyConverter*
+ {
+ for (auto &pProvider : g_apProviders)
+ if (auto p = dynamic_cast<CCurrencyRatesProviderCurrencyConverter*>(pProvider))
+ return p;
+
+ assert(!"We should never get here!");
+ return nullptr;
+ };
+
+ auto make_currencyrate_name = [](const CCurrencyRate &rCurrencyRate)->tstring
+ {
+ const tstring& rsDesc = rCurrencyRate.GetName();
+ return((false == rsDesc.empty()) ? rsDesc : rCurrencyRate.GetSymbol());
+ };
+
+ auto make_contact_name = [](const tstring &rsSymbolFrom, const tstring &rsSymbolTo)->tstring
+ {
+ tostringstream o;
+ o << rsSymbolFrom << L"/" << rsSymbolTo;
+ return o.str();
+ };
+
+
+ auto make_rate_name = [make_contact_name](const CCurrencyRatesProviderCurrencyConverter::TRateInfo &ri)->tstring
+ {
+ if ((false == ri.first.GetName().empty()) && (false == ri.second.GetName().empty()))
+ return make_contact_name(ri.first.GetName(), ri.second.GetName());
+
+ return make_contact_name(ri.first.GetSymbol(), ri.second.GetSymbol());
+ };
+
+
+ auto pProvider = get_provider();
+
+ CCommonDlgProcData d(pProvider);
+ CommonOptionDlgProc(hdlg, message, wParam, lParam, d);
+
+ switch (message) {
+ case WM_NOTIFY:
+ {
+ LPNMHDR pNMHDR = reinterpret_cast<LPNMHDR>(lParam);
+ switch (pNMHDR->code) {
+ case PSN_APPLY:
+ {
+ if (pProvider) {
+ TWatchedRates aTemp(g_aWatchedRates);
+ TWatchedRates aRemove;
+ size_t cWatchedRates = pProvider->GetWatchedRateCount();
+ for (size_t i = 0; i < cWatchedRates; ++i) {
+ CCurrencyRatesProviderCurrencyConverter::TRateInfo ri;
+ if (true == pProvider->GetWatchedRateInfo(i, ri)) {
+ auto it = std::find_if(aTemp.begin(), aTemp.end(), [&ri](const auto& other)->bool
+ {
+ return ((0 == mir_wstrcmpi(ri.first.GetID().c_str(), other.first.GetID().c_str()))
+ && ((0 == mir_wstrcmpi(ri.second.GetID().c_str(), other.second.GetID().c_str()))));
+ });
+ if (it == aTemp.end()) {
+ aRemove.push_back(ri);
+ }
+ else {
+ aTemp.erase(it);
+ }
+ }
+ }
+
+ for (auto &it : aRemove) pProvider->WatchForRate(it, false);
+ for (auto &it : aTemp) pProvider->WatchForRate(it, true);
+ pProvider->RefreshSettings();
+ }
+ }
+ break;
+ }
+ }
+ break;
+
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hdlg);
+ {
+ g_aWatchedRates.clear();
+
+ HWND hcbxFrom = ::GetDlgItem(hdlg, IDC_COMBO_CONVERT_FROM);
+ HWND hcbxTo = ::GetDlgItem(hdlg, IDC_COMBO_CONVERT_INTO);
+
+ CCurrencyRateSection rSection;
+ const auto& rCurrencyRates = pProvider->GetCurrencyRates();
+ if (rCurrencyRates.GetSectionCount() > 0) {
+ rSection = rCurrencyRates.GetSection(0);
+ }
+
+ auto cCurrencyRates = rSection.GetCurrencyRateCount();
+ for (auto i = 0u; i < cCurrencyRates; ++i) {
+ const auto& rCurrencyRate = rSection.GetCurrencyRate(i);
+ tstring sName = make_currencyrate_name(rCurrencyRate);
+ LPCTSTR pszName = sName.c_str();
+ ::SendMessage(hcbxFrom, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(pszName));
+ ::SendMessage(hcbxTo, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(pszName));
+ }
+
+ auto cWatchedRates = pProvider->GetWatchedRateCount();
+ for (auto i = 0u; i < cWatchedRates; ++i) {
+ CCurrencyRatesProviderCurrencyConverter::TRateInfo ri;
+ if (true == pProvider->GetWatchedRateInfo(i, ri)) {
+ g_aWatchedRates.push_back(ri);
+ tstring sRate = make_rate_name(ri);
+ LPCTSTR pszRateName = sRate.c_str();
+ ::SendDlgItemMessage(hdlg, IDC_LIST_RATES, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(pszRateName));
+ }
+ }
+
+ ::EnableWindow(::GetDlgItem(hdlg, IDC_BUTTON_ADD), FALSE);
+ ::EnableWindow(::GetDlgItem(hdlg, IDC_BUTTON_REMOVE), FALSE);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (HIWORD(wParam)) {
+ case CBN_SELCHANGE:
+ switch (LOWORD(wParam)) {
+ case IDC_COMBO_REFRESH_RATE:
+ break;
+ case IDC_COMBO_CONVERT_FROM:
+ case IDC_COMBO_CONVERT_INTO:
+ {
+ int nFrom = static_cast<int>(::SendDlgItemMessage(hdlg, IDC_COMBO_CONVERT_FROM, CB_GETCURSEL, 0, 0));
+ int nTo = static_cast<int>(::SendDlgItemMessage(hdlg, IDC_COMBO_CONVERT_INTO, CB_GETCURSEL, 0, 0));
+ bool bEnableAddButton = ((CB_ERR != nFrom) && (CB_ERR != nTo) && (nFrom != nTo));
+ EnableWindow(GetDlgItem(hdlg, IDC_BUTTON_ADD), bEnableAddButton);
+ }
+ break;
+ case IDC_LIST_RATES:
+ {
+ int nSel = ::SendDlgItemMessage(hdlg, IDC_LIST_RATES, LB_GETCURSEL, 0, 0);
+ ::EnableWindow(::GetDlgItem(hdlg, IDC_BUTTON_REMOVE), (LB_ERR != nSel));
+ }
+ break;
+ }
+ break;
+
+ case BN_CLICKED:
+ switch (LOWORD(wParam)) {
+ case IDC_BUTTON_ADD:
+ {
+ size_t nFrom = static_cast<size_t>(::SendDlgItemMessage(hdlg, IDC_COMBO_CONVERT_FROM, CB_GETCURSEL, 0, 0));
+ size_t nTo = static_cast<size_t>(::SendDlgItemMessage(hdlg, IDC_COMBO_CONVERT_INTO, CB_GETCURSEL, 0, 0));
+ if ((CB_ERR != nFrom) && (CB_ERR != nTo) && (nFrom != nTo)) {
+ CCurrencyRateSection rSection;
+ const auto& rCurrencyRates = pProvider->GetCurrencyRates();
+ if (rCurrencyRates.GetSectionCount() > 0) {
+ rSection = rCurrencyRates.GetSection(0);
+ }
+
+ auto cCurrencyRates = rSection.GetCurrencyRateCount();
+ if ((nFrom < cCurrencyRates) && (nTo < cCurrencyRates)) {
+ CCurrencyRatesProviderCurrencyConverter::TRateInfo ri;
+ ri.first = rSection.GetCurrencyRate(nFrom);
+ ri.second = rSection.GetCurrencyRate(nTo);
+
+ g_aWatchedRates.push_back(ri);
+
+ tstring sRate = make_rate_name(ri);
+ LPCTSTR pszRateName = sRate.c_str();
+ ::SendDlgItemMessage(hdlg, IDC_LIST_RATES, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(pszRateName));
+ PropSheet_Changed(::GetParent(hdlg), hdlg);
+ }
+ }
+ }
+ break;
+
+ case IDC_BUTTON_REMOVE:
+ HWND hWnd = ::GetDlgItem(hdlg, IDC_LIST_RATES);
+ int nSel = ::SendMessage(hWnd, LB_GETCURSEL, 0, 0);
+ if (LB_ERR != nSel) {
+ if ((LB_ERR != ::SendMessage(hWnd, LB_DELETESTRING, nSel, 0))
+ && (nSel < static_cast<int>(g_aWatchedRates.size()))) {
+
+ TWatchedRates::iterator i = g_aWatchedRates.begin();
+ std::advance(i, nSel);
+ g_aWatchedRates.erase(i);
+ PropSheet_Changed(::GetParent(hdlg), hdlg);
+ }
+ }
+
+ nSel = ::SendMessage(hWnd, LB_GETCURSEL, 0, 0);
+ ::EnableWindow(::GetDlgItem(hdlg, IDC_BUTTON_REMOVE), (LB_ERR != nSel));
+ break;
+ }
+ break;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+CCurrencyRatesProviderCurrencyConverter::CCurrencyRatesProviderCurrencyConverter()
+{
+}
+
+CCurrencyRatesProviderCurrencyConverter::~CCurrencyRatesProviderCurrencyConverter()
+{
+}
+
+void CCurrencyRatesProviderCurrencyConverter::ShowPropertyPage(WPARAM wp, OPTIONSDIALOGPAGE &odp)
+{
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_DIALOG_OPT_GOOGLE);
+ odp.pfnDlgProc = OptDlgProc;
+ odp.szTab.w = const_cast<LPTSTR>(GetInfo().m_sName.c_str());
+ g_plugin.addOptions(wp, &odp);
+}
+
+void CCurrencyRatesProviderCurrencyConverter::RefreshCurrencyRates(TContacts &anContacts)
+{
+ CHTTPSession http;
+ tstring sURL = GetURL();
+
+ for (TContacts::const_iterator i = anContacts.begin(); i != anContacts.end() && IsOnline(); ++i) {
+ MCONTACT hContact = *i;
+
+ tstring sFullURL = build_url(hContact, sURL);
+ if ((true == http.OpenURL(sFullURL)) && (true == IsOnline())) {
+ tstring sHTML;
+ if ((true == http.ReadResponce(sHTML)) && (true == IsOnline())) {
+ double dRate = 0.0;
+ if ((true == parse_responce(sHTML, dRate)) && (true == IsOnline())) {
+ WriteContactRate(hContact, dRate);
+ continue;
+ }
+ }
+ }
+
+ SetContactStatus(hContact, ID_STATUS_NA);
+ }
+}
+
+double CCurrencyRatesProviderCurrencyConverter::Convert(double dAmount, const CCurrencyRate &from, const CCurrencyRate &to) const
+{
+ tstring sFullURL = build_url(GetURL(), from.GetID(), to.GetID());
+
+ CHTTPSession http;
+ if ((true == http.OpenURL(sFullURL))) {
+ tstring sHTML;
+ if ((true == http.ReadResponce(sHTML))) {
+ double dResult = 0.0;
+ if ((true == parse_responce(sHTML, dResult)))
+ return dResult * dAmount;
+
+ throw std::runtime_error(Translate("Error occurred during HTML parsing."));
+ }
+ else throw std::runtime_error(Translate("Error occurred during site access."));
+ }
+ else throw std::runtime_error(Translate("Error occurred during site access."));
+
+ return 0.0;
+}
+
+size_t CCurrencyRatesProviderCurrencyConverter::GetWatchedRateCount() const
+{
+ return m_aContacts.size();
+}
+
+bool CCurrencyRatesProviderCurrencyConverter::GetWatchedRateInfo(size_t nIndex, TRateInfo &rRateInfo)
+{
+ if (nIndex >= m_aContacts.size())
+ return false;
+
+ MCONTACT hContact = m_aContacts[nIndex];
+ tstring sSymbolFrom = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_ID);
+ tstring sSymbolTo = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_ID);
+ tstring sDescFrom = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_DESCRIPTION);
+ tstring sDescTo = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_DESCRIPTION);
+
+ rRateInfo.first = CCurrencyRate(sSymbolFrom, sSymbolFrom, sDescFrom);
+ rRateInfo.second = CCurrencyRate(sSymbolTo, sSymbolTo, sDescTo);
+ return true;
+}
+
+bool CCurrencyRatesProviderCurrencyConverter::WatchForRate(const TRateInfo &ri, bool bWatch)
+{
+ auto i = std::find_if(m_aContacts.begin(), m_aContacts.end(), [&ri](auto hContact)->bool
+ {
+ tstring sFrom = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_ID);
+ tstring sTo = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_ID);
+ return ((0 == mir_wstrcmpi(ri.first.GetID().c_str(), sFrom.c_str()))
+ && (0 == mir_wstrcmpi(ri.second.GetID().c_str(), sTo.c_str())));
+ });
+
+ auto make_contact_name = [](const tstring &rsSymbolFrom, const tstring &rsSymbolTo)->tstring
+ {
+ tostringstream o;
+ o << rsSymbolFrom << L"/" << rsSymbolTo;
+ return o.str();
+ };
+
+
+ if ((true == bWatch) && (i == m_aContacts.end())) {
+ tstring sName = make_contact_name(ri.first.GetSymbol(), ri.second.GetSymbol());
+ MCONTACT hContact = CreateNewContact(sName);
+ if (hContact) {
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_ID, ri.first.GetID().c_str());
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_ID, ri.second.GetID().c_str());
+ if (false == ri.first.GetName().empty()) {
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_DESCRIPTION, ri.first.GetName().c_str());
+ }
+ if (false == ri.second.GetName().empty()) {
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_DESCRIPTION, ri.second.GetName().c_str());
+ }
+
+ return true;
+ }
+ }
+ else if ((false == bWatch) && (i != m_aContacts.end())) {
+ MCONTACT hContact = *i;
+ {// for CCritSection
+ mir_cslock lck(m_cs);
+ m_aContacts.erase(i);
+ }
+
+ db_delete_contact(hContact);
+ return true;
+ }
+
+ return false;
+}
+
+MCONTACT CCurrencyRatesProviderCurrencyConverter::GetContactByID(const tstring& rsFromID, const tstring& rsToID) const
+{
+ mir_cslock lck(m_cs);
+
+ auto i = std::find_if(m_aContacts.begin(), m_aContacts.end(), [rsFromID, rsToID](MCONTACT hContact)->bool
+ {
+ tstring sFrom = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_ID);
+ tstring sTo = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_ID);
+ return ((0 == mir_wstrcmpi(rsFromID.c_str(), sFrom.c_str())) && (0 == mir_wstrcmpi(rsToID.c_str(), sTo.c_str())));
+ });
+
+ if (i != m_aContacts.end())
+ return *i;
+
+ return NULL;
+}
+
+void CCurrencyRatesProviderCurrencyConverter::FillFormat(TFormatSpecificators &array) const
+{
+ CSuper::FillFormat(array);
+
+ array.push_back(CFormatSpecificator(L"%F", TranslateT("From Currency Full Name")));
+ array.push_back(CFormatSpecificator(L"%f", TranslateT("From Currency Short Name")));
+ array.push_back(CFormatSpecificator(L"%I", TranslateT("Into Currency Full Name")));
+ array.push_back(CFormatSpecificator(L"%i", TranslateT("Into Currency Short Name")));
+ array.push_back(CFormatSpecificator(L"%s", TranslateT("Short notation for \"%f/%i\"")));
+}
+
+tstring CCurrencyRatesProviderCurrencyConverter::FormatSymbol(MCONTACT hContact, wchar_t c, int nWidth) const
+{
+ switch (c) {
+ case 'F':
+ return CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_DESCRIPTION);
+
+ case 'f':
+ return CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_FROM_ID);
+
+ case 'I':
+ return CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_DESCRIPTION);
+
+ case 'i':
+ return CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_TO_ID);
+ }
+
+ return CSuper::FormatSymbol(hContact, c, nWidth);
+}
+
+MCONTACT CCurrencyRatesProviderCurrencyConverter::ImportContact(const TiXmlNode *pRoot)
+{
+ const char *sFromID = nullptr, *sToID = nullptr;
+
+ for (auto *pNode : TiXmlFilter(pRoot, "Setting")) {
+ TNameValue Item = parse_setting_node(pNode);
+ if (!mir_strcmpi(Item.first, DB_STR_FROM_ID))
+ sFromID = Item.second;
+ else if (!mir_strcmpi(Item.first, DB_STR_TO_ID))
+ sToID = Item.second;
+ }
+
+ if (sFromID && sToID)
+ return GetContactByID(Utf2T(sFromID).get(), Utf2T(sToID).get());
+
+ return 0;
+}
diff --git a/protocols/CurrencyRates/src/CurrencyRatesProviderCurrencyConverter.h b/protocols/CurrencyRates/src/CurrencyRatesProviderCurrencyConverter.h
new file mode 100644
index 0000000000..bb65f08737
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRatesProviderCurrencyConverter.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#define DB_STR_FROM_ID "FromID"
+#define DB_STR_TO_ID "ToID"
+#define DB_STR_FROM_DESCRIPTION "FromDesc"
+#define DB_STR_TO_DESCRIPTION "ToDesc"
+
+
+class CCurrencyRatesProviderCurrencyConverter : public CCurrencyRatesProviderBase
+{
+public:
+ typedef CCurrencyRatesProviderBase CSuper;
+ using TRateInfo = std::pair<CCurrencyRate, CCurrencyRate>;
+
+public:
+ CCurrencyRatesProviderCurrencyConverter();
+ ~CCurrencyRatesProviderCurrencyConverter();
+
+ double Convert(double dAmount, const CCurrencyRate &from, const CCurrencyRate &to) const;
+ size_t GetWatchedRateCount() const;
+ bool GetWatchedRateInfo(size_t nIndex, TRateInfo &rRateInfo);
+ bool WatchForRate(const TRateInfo &ri, bool bWatch);
+ MCONTACT GetContactByID(const tstring &rsFromID, const tstring &rsToID) const;
+
+private:
+ void FillFormat(TFormatSpecificators &) const override;
+ void RefreshCurrencyRates(TContacts &anContacts) override;
+ void ShowPropertyPage(WPARAM wp, OPTIONSDIALOGPAGE &odp) override;
+
+ MCONTACT ImportContact(const TiXmlNode*) override;
+ tstring FormatSymbol(MCONTACT hContact, wchar_t c, int nWidth) const override;
+};
diff --git a/protocols/CurrencyRates/src/CurrencyRatesProviders.cpp b/protocols/CurrencyRates/src/CurrencyRatesProviders.cpp
new file mode 100644
index 0000000000..31dd4c771a
--- /dev/null
+++ b/protocols/CurrencyRates/src/CurrencyRatesProviders.cpp
@@ -0,0 +1,83 @@
+#include "StdAfx.h"
+#include "CurrencyRatesProviderCurrencyConverter.h"
+
+#define LAST_RUN_VERSION "LastRunVersion"
+
+TCurrencyRatesProviders g_apProviders;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+template<class T>void create_provider(TCurrencyRatesProviders& g_apProviders)
+{
+ ICurrencyRatesProvider *pProvider = new T;
+ if (pProvider->Init())
+ g_apProviders.push_back(pProvider);
+};
+
+void CreateProviders()
+{
+ create_provider<CCurrencyRatesProviderCurrencyConverter>(g_apProviders);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void ClearProviders()
+{
+ g_apProviders.clear();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void convert_contact_settings(MCONTACT hContact)
+{
+ WORD dwLogMode = db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG, static_cast<WORD>(lmDisabled));
+ if ((dwLogMode&lmInternalHistory) || (dwLogMode&lmExternalFile))
+ db_set_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CONTACT_SPEC_SETTINGS, 1);
+}
+
+void InitProviders()
+{
+ CreateProviders();
+
+ const WORD nCurrentVersion = 17;
+ WORD nVersion = db_get_w(0, CURRENCYRATES_MODULE_NAME, LAST_RUN_VERSION, 1);
+
+ for (auto &hContact : Contacts(CURRENCYRATES_MODULE_NAME)) {
+ ICurrencyRatesProvider *pProvider = GetContactProviderPtr(hContact);
+ if (pProvider) {
+ pProvider->AddContact(hContact);
+ if (nVersion < nCurrentVersion)
+ convert_contact_settings(hContact);
+ }
+ }
+
+ db_set_w(0, CURRENCYRATES_MODULE_NAME, LAST_RUN_VERSION, nCurrentVersion);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+ICurrencyRatesProvider* GetContactProviderPtr(MCONTACT hContact)
+{
+ char* szProto = GetContactProto(hContact);
+ if (nullptr == szProto || 0 != ::_stricmp(szProto, CURRENCYRATES_PROTOCOL_NAME))
+ return nullptr;
+
+ tstring sProvider = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_PROVIDER);
+ if (true == sProvider.empty())
+ return nullptr;
+
+ return FindProvider(sProvider);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+ICurrencyRatesProvider* FindProvider(const tstring& rsName)
+{
+ for (auto &pProvider : g_apProviders) {
+ const ICurrencyRatesProvider::CProviderInfo& rInfo = pProvider->GetInfo();
+ if (0 == ::mir_wstrcmpi(rsName.c_str(), rInfo.m_sName.c_str()))
+ return pProvider;
+ }
+
+ return nullptr;
+}
diff --git a/protocols/CurrencyRates/src/DBUtils.cpp b/protocols/CurrencyRates/src/DBUtils.cpp
new file mode 100644
index 0000000000..20189c0f3f
--- /dev/null
+++ b/protocols/CurrencyRates/src/DBUtils.cpp
@@ -0,0 +1,43 @@
+#include "StdAfx.h"
+
+std::wstring GetNodeText(const TiXmlElement *pNode)
+{
+ auto *pszText = pNode->GetText();
+ if (pszText)
+ return Utf2T(pszText).get();
+
+ return std::wstring();
+}
+
+std::wstring CurrencyRates_DBGetStringW(MCONTACT hContact, const char *szModule, const char *szSetting, const wchar_t *pszDefValue)
+{
+ if (pszDefValue == nullptr)
+ pszDefValue = L"";
+
+ return std::wstring(ptrW(db_get_wsa(hContact, szModule, szSetting, pszDefValue)));
+}
+
+bool CurrencyRates_DBWriteDouble(MCONTACT hContact, const char *szModule, const char *szSetting, double dValue)
+{
+ return 0 == db_set_blob(hContact, szModule, szSetting, &dValue, sizeof(dValue));
+}
+
+bool CurrencyRates_DBReadDouble(MCONTACT hContact, const char *szModule, const char *szSetting, double& rdValue)
+{
+ DBVARIANT dbv = {};
+ dbv.type = DBVT_BLOB;
+
+ bool bResult = ((0 == db_get(hContact, szModule, szSetting, &dbv)) && (DBVT_BLOB == dbv.type));
+ if (bResult)
+ rdValue = *reinterpret_cast<double*>(dbv.pbVal);
+
+ db_free(&dbv);
+ return bResult;
+}
+
+void FixInvalidChars(tstring &s)
+{
+ for (auto &c : s)
+ if (wcschr(L"\\/:*?\"<>|", c))
+ c = '_';
+}
diff --git a/protocols/CurrencyRates/src/DBUtils.h b/protocols/CurrencyRates/src/DBUtils.h
new file mode 100644
index 0000000000..f6c7f83b71
--- /dev/null
+++ b/protocols/CurrencyRates/src/DBUtils.h
@@ -0,0 +1,53 @@
+#ifndef __54294385_3fdd_4f0c_98c3_c583a96e7fb4_DBUtils_h__
+#define __54294385_3fdd_4f0c_98c3_c583a96e7fb4_DBUtils_h__
+
+#define DB_KEY_RefreshRateType "CC_RefreshRateType"
+#define DB_KEY_RefreshRateValue "CC_RefreshRateValue"
+
+#define DB_KEY_StatusMsgFormat "CC_StatusMessageFormat"
+#define DB_DEF_StatusMsgFormat L""
+
+#define DB_DEF_IniFileName L"CC.xml"
+
+#define DB_KEY_ApiKey "CC_ApiKey"
+
+#define DB_KEY_DisplayNameFormat "CC_DspNameFrmt"
+#define DB_DEF_DisplayNameFormat L"1 %f = %r %i"
+
+#define DB_KEY_HistoryFormat "CC_HistoryFormat"
+#define DB_DEF_HistoryFormat L"%s %r"
+
+#define DB_KEY_HistoryCondition "CC_AddToHistoryOnlyIfValueIsChanged"
+
+#define DB_KEY_LogMode "CC_LogMode"
+#define DB_KEY_LogFile "CC_LogFile"
+#define DB_KEY_LogCondition "CC_AddToLogOnlyIfValueIsChanged"
+
+#define DB_KEY_LogFormat "CC_LogFileFormat"
+#define DB_DEF_LogFormat L"%s\\t%t\\t%r\\n"
+
+#define DB_KEY_PopupFormat "CC_PopupFormat"
+#define DB_DEF_PopupFormat L"\\nCurrent = %r\\nPrevious = %p"
+
+#define DB_KEY_PopupCondition "CC_ShowPopupOnlyIfValueChanged"
+
+#define DB_KEY_PopupColourMode "CC_PopupColourMode"
+#define DB_KEY_PopupBkColour "CC_PopupColourBk"
+#define DB_KEY_PopupTextColour "CC_PopupColourText"
+#define DB_KEY_PopupDelayMode "CC_PopupDelayMode"
+#define DB_KEY_PopupDelayTimeout "CC_PopupDelayTimeout"
+#define DB_KEY_PopupHistoryFlag "CC_PopupHistoryFlag"
+
+#define DB_KEY_TendencyFormat "CC_TendencyFormat"
+#define DB_DEF_TendencyFormat L"%r>%p"
+
+void FixInvalidChars(tstring &s);
+
+std::wstring GetNodeText(const TiXmlElement*);
+
+std::wstring CurrencyRates_DBGetStringW(MCONTACT hContact, const char *szModule, const char *szSetting, const wchar_t* pszDefValue = nullptr);
+
+bool CurrencyRates_DBWriteDouble(MCONTACT hContact, const char *szModule, const char *szSetting, double dValue);
+bool CurrencyRates_DBReadDouble(MCONTACT hContact, const char *szModule, const char *szSetting, double& rdValue);
+
+#endif //__54294385_3fdd_4f0c_98c3_c583a96e7fb4_DBUtils_h__
diff --git a/protocols/CurrencyRates/src/EconomicRateInfo.h b/protocols/CurrencyRates/src/EconomicRateInfo.h
new file mode 100644
index 0000000000..664649a47a
--- /dev/null
+++ b/protocols/CurrencyRates/src/EconomicRateInfo.h
@@ -0,0 +1,59 @@
+#ifndef __87d726e0_26c6_485d_8016_1fba819b037d_EconomicRateInfo__
+#define __87d726e0_26c6_485d_8016_1fba819b037d_EconomicRateInfo__
+
+#define CURRENCYRATES_PROTOCOL_NAME "CurrencyRates"// protocol name
+
+#define CURRENCYRATES_MODULE_NAME "CurrencyRates" // db settings module path
+
+enum ERefreshRateType
+{
+ RRT_SECONDS = 0,
+ RRT_MINUTES = 1,
+ RRT_HOURS = 2
+};
+
+#define DB_STR_ENABLE_LOG "EnableLog"
+#define DB_STR_CURRENCYRATE_PROVIDER "CurrencyRateProvider"
+#define DB_STR_CURRENCYRATE_ID "CurrencyRateID"
+#define DB_STR_CURRENCYRATE_SYMBOL "CurrencyRateSymbol"
+#define DB_STR_CURRENCYRATE_DESCRIPTION "CurrencyRateDescription"
+#define DB_STR_CURRENCYRATE_PREV_VALUE "PreviousCurrencyRateValue"
+#define DB_STR_CURRENCYRATE_CURR_VALUE "CurrentCurrencyRateValue"
+#define DB_STR_CURRENCYRATE_FETCH_TIME "FetchTime"
+
+
+enum ELogMode
+{
+ lmDisabled = 0x0000,
+ lmInternalHistory = 0x0001,
+ lmExternalFile = 0x0002,
+ lmPopup = 0x0004,
+};
+
+#define DB_STR_CONTACT_SPEC_SETTINGS "ContactSpecSettings"
+#define DB_STR_CURRENCYRATE_LOG "Log"
+#define DB_STR_CURRENCYRATE_LOG_FILE "LogFile"
+#define DB_STR_CURRENCYRATE_FORMAT_LOG_FILE "LogFileFormat"
+#define DB_STR_CURRENCYRATE_FORMAT_HISTORY "HistoryFormat"
+#define DB_STR_CURRENCYRATE_LOG_FILE_CONDITION "AddToLogOnlyIfValueIsChanged"
+#define DB_STR_CURRENCYRATE_HISTORY_CONDITION "AddToHistoryOnlyIfValueIsChanged"
+#define DB_STR_CURRENCYRATE_EXTRA_IMAGE_SLOT "ExtraImageSlot"
+#define DB_STR_CURRENCYRATE_FORMAT_POPUP "PopupFormat"
+#define DB_STR_CURRENCYRATE_POPUP_CONDITION "ShowPopupOnlyIfValueIsChanged"
+
+#define DB_STR_CURRENCYRATE_POPUP_COLOUR_MODE "PopupColourMode"
+#define DB_STR_CURRENCYRATE_POPUP_COLOUR_BK "PopupColourBk"
+#define DB_STR_CURRENCYRATE_POPUP_COLOUR_TEXT "PopupColourText"
+#define DB_STR_CURRENCYRATE_POPUP_DELAY_MODE "PopupDelayMode"
+#define DB_STR_CURRENCYRATE_POPUP_DELAY_TIMEOUT "PopupDelayTimeout"
+#define DB_STR_CURRENCYRATE_POPUP_HISTORY_FLAG "PopupHistoryFlag"
+
+
+// #define DB_STR_NICK "Nick"
+#define DB_STR_STATUS "Status"
+
+#define LIST_MODULE_NAME "CList"
+#define CONTACT_LIST_NAME "MyHandle"
+#define STATUS_MSG_NAME "StatusMsg"
+
+#endif //__87d726e0_26c6_485d_8016_1fba819b037d_EconomicRateInfo__
diff --git a/protocols/CurrencyRates/src/ExtraImages.cpp b/protocols/CurrencyRates/src/ExtraImages.cpp
new file mode 100644
index 0000000000..686f4e34e7
--- /dev/null
+++ b/protocols/CurrencyRates/src/ExtraImages.cpp
@@ -0,0 +1,30 @@
+#include "StdAfx.h"
+
+static HANDLE hExtraIcon;
+
+void CurrencyRates_InitExtraIcons()
+{
+ hExtraIcon = ExtraIcon_RegisterIcolib(ICON_STR_CURRENCYRATE, CURRENCYRATES_PROTOCOL_NAME, CURRENCYRATES_PROTOCOL_NAME "_" ICON_STR_MAIN);
+}
+
+bool SetContactExtraImage(MCONTACT hContact, EImageIndex nIndex)
+{
+ if (!hExtraIcon)
+ return false;
+
+ HANDLE hIcolib;
+ switch (nIndex) {
+ case eiUp:
+ hIcolib = CurrencyRates_GetIconHandle(IDI_ICON_UP);
+ break;
+ case eiDown:
+ hIcolib = CurrencyRates_GetIconHandle(IDI_ICON_DOWN);
+ break;
+ case eiNotChanged:
+ hIcolib = CurrencyRates_GetIconHandle(IDI_ICON_NOTCHANGED);
+ break;
+ default:
+ hIcolib = nullptr;
+ }
+ return ExtraIcon_SetIcon(hExtraIcon, hContact, hIcolib) == 0;
+}
diff --git a/protocols/CurrencyRates/src/ExtraImages.h b/protocols/CurrencyRates/src/ExtraImages.h
new file mode 100644
index 0000000000..b3ef9deaa6
--- /dev/null
+++ b/protocols/CurrencyRates/src/ExtraImages.h
@@ -0,0 +1,16 @@
+#ifndef __9d0dac0c_12e4_46ce_809a_db6dc7d6f269_ExtraImages_h__
+#define __9d0dac0c_12e4_46ce_809a_db6dc7d6f269_ExtraImages_h__
+
+enum EImageIndex
+{
+ eiUp = 0,
+ eiDown = 1,
+ eiNotChanged = 2,
+ eiEmpty = 3
+};
+
+bool SetContactExtraImage(MCONTACT hContact, EImageIndex nIndex);
+
+void CurrencyRates_InitExtraIcons(void);
+
+#endif //__9d0dac0c_12e4_46ce_809a_db6dc7d6f269_ExtraImages_h__
diff --git a/protocols/CurrencyRates/src/Forex.cpp b/protocols/CurrencyRates/src/Forex.cpp
new file mode 100644
index 0000000000..b3b2424d48
--- /dev/null
+++ b/protocols/CurrencyRates/src/Forex.cpp
@@ -0,0 +1,325 @@
+// Forex.cpp : Defines the exported functions for the DLL application.
+//
+
+#include "stdafx.h"
+
+CMPlugin g_plugin;
+
+HANDLE g_hEventWorkThreadStop;
+//int g_nStatus = ID_STATUS_OFFLINE;
+bool g_bAutoUpdate = true;
+HGENMENU g_hMenuEditSettings = nullptr;
+HGENMENU g_hMenuOpenLogFile = nullptr;
+#ifdef CHART_IMPLEMENT
+HGENMENU g_hMenuChart = nullptr;
+#endif
+HGENMENU g_hMenuRefresh = nullptr, g_hMenuRoot = nullptr;
+
+#define DB_STR_AUTO_UPDATE "AutoUpdate"
+
+typedef std::vector<HANDLE> THandles;
+THandles g_ahThreads;
+HGENMENU g_hEnableDisableMenu;
+HANDLE g_hTBButton;
+
+LPSTR g_pszAutoUpdateCmd = "CurrencyRates/Enable-Disable Auto Update";
+LPSTR g_pszCurrencyConverter = "CurrencyRates/CurrencyConverter";
+
+void UpdateMenu(bool bAutoUpdate)
+{
+ if (bAutoUpdate) // to enable auto-update
+ Menu_ModifyItem(g_hEnableDisableMenu, LPGENW("Auto Update Enabled"), CurrencyRates_GetIconHandle(IDI_ICON_MAIN));
+ else // to disable auto-update
+ Menu_ModifyItem(g_hEnableDisableMenu, LPGENW("Auto Update Disabled"), CurrencyRates_GetIconHandle(IDI_ICON_DISABLED));
+
+ CallService(MS_TTB_SETBUTTONSTATE, reinterpret_cast<WPARAM>(g_hTBButton), !bAutoUpdate ? TTBST_PUSHED : 0);
+}
+
+INT_PTR CurrencyRatesMenu_RefreshAll(WPARAM, LPARAM)
+{
+ for (auto &pProvider : g_apProviders)
+ pProvider->RefreshAllContacts();
+ return 0;
+}
+
+INT_PTR CurrencyRatesMenu_EnableDisable(WPARAM, LPARAM)
+{
+ g_bAutoUpdate = (g_bAutoUpdate) ? false : true;
+ db_set_b(0, CURRENCYRATES_MODULE_NAME, DB_STR_AUTO_UPDATE, g_bAutoUpdate);
+
+ for (auto &pProvider : g_apProviders) {
+ pProvider->RefreshSettings();
+ if (g_bAutoUpdate)
+ pProvider->RefreshAllContacts();
+ }
+
+ UpdateMenu(g_bAutoUpdate);
+ return 0;
+}
+
+void InitMenu()
+{
+ CMenuItem mi(&g_plugin);
+ mi.flags = CMIF_UNICODE;
+ mi.root = g_plugin.addRootMenu(MO_MAIN, LPGENW("Currency Rates"), 0, CurrencyRates_GetIconHandle(IDI_ICON_MAIN));
+ Menu_ConfigureItem(mi.root, MCI_OPT_UID, "B474F556-22B6-42A1-A91E-22FE4F671388");
+
+ SET_UID(mi, 0x9de6716, 0x3591, 0x48c4, 0x9f, 0x64, 0x1b, 0xfd, 0xc6, 0xd1, 0x34, 0x97);
+ mi.name.w = LPGENW("Enable/Disable Auto Update");
+ mi.position = 10100001;
+ mi.hIcolibItem = CurrencyRates_GetIconHandle(IDI_ICON_MAIN);
+ mi.pszService = g_pszAutoUpdateCmd;
+ g_hEnableDisableMenu = Menu_AddMainMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, CurrencyRatesMenu_EnableDisable);
+ UpdateMenu(g_bAutoUpdate);
+
+ SET_UID(mi, 0x91cbabf6, 0x5073, 0x4a78, 0x84, 0x8, 0x34, 0x61, 0xc1, 0x8a, 0x34, 0xd9);
+ mi.name.w = LPGENW("Refresh All Rates");
+ mi.position = 20100001;
+ mi.hIcolibItem = CurrencyRates_GetIconHandle(IDI_ICON_MAIN);
+ mi.pszService = "CurrencyRates/RefreshAll";
+ Menu_AddMainMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, CurrencyRatesMenu_RefreshAll);
+
+ SET_UID(mi, 0x3663409c, 0xbd36, 0x473b, 0x9b, 0x4f, 0xff, 0x80, 0xf6, 0x2c, 0xdf, 0x9b);
+ mi.name.w = LPGENW("Currency Converter...");
+ mi.position = 20100002;
+ mi.hIcolibItem = CurrencyRates_GetIconHandle(IDI_ICON_CURRENCY_CONVERTER);
+ mi.pszService = g_pszCurrencyConverter;
+ Menu_AddMainMenuItem(&mi);
+ CreateServiceFunction(mi.pszService, CurrencyRatesMenu_CurrencyConverter);
+
+ SET_UID(mi, 0x7cca4fd9, 0x903f, 0x4b7d, 0x93, 0x7a, 0x18, 0x63, 0x23, 0xd4, 0xa9, 0xa9);
+ mi.name.w = LPGENW("Export All Currency Rates");
+ mi.hIcolibItem = CurrencyRates_GetIconHandle(IDI_ICON_EXPORT);
+ mi.pszService = MS_CURRENCYRATES_EXPORT;
+ mi.position = 20100003;
+ Menu_AddMainMenuItem(&mi);
+
+ SET_UID(mi, 0xa994d3b, 0x77c2, 0x4612, 0x8d, 0x5, 0x6a, 0xae, 0x8c, 0x21, 0xbd, 0xc9);
+ mi.name.w = LPGENW("Import All Currency Rates");
+ mi.hIcolibItem = CurrencyRates_GetIconHandle(IDI_ICON_IMPORT);
+ mi.pszService = MS_CURRENCYRATES_IMPORT;
+ mi.position = 20100004;
+ Menu_AddMainMenuItem(&mi);
+
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, CurrencyRates_PrebuildContactMenu);
+
+ g_hMenuRoot = mi.root = g_plugin.addRootMenu(MO_CONTACT, LPGENW("Currency Rates"), 0, CurrencyRates_GetIconHandle(IDI_ICON_MAIN));
+ Menu_ConfigureItem(mi.root, MCI_OPT_UID, "C259BE01-642C-461E-997D-0E756B2A3AD6");
+
+ SET_UID(mi, 0xb9812194, 0x3235, 0x4e76, 0xa3, 0xa4, 0x73, 0x32, 0x96, 0x1c, 0x1c, 0xf4);
+ mi.name.w = LPGENW("Refresh");
+ mi.hIcolibItem = CurrencyRates_GetIconHandle(IDI_ICON_REFRESH);
+ mi.pszService = "CurrencyRates/RefreshContact";
+ g_hMenuRefresh = Menu_AddContactMenuItem(&mi, CURRENCYRATES_PROTOCOL_NAME);
+ Menu_ConfigureItem(g_hMenuRefresh, MCI_OPT_EXECPARAM, INT_PTR(0));
+ CreateServiceFunction(mi.pszService, CurrencyRatesMenu_RefreshContact);
+
+ SET_UID(mi, 0x19a16fa2, 0xf370, 0x4201, 0x92, 0x9, 0x25, 0xde, 0x4e, 0x55, 0xf9, 0x1a);
+ mi.name.w = LPGENW("Open Log File...");
+ mi.hIcolibItem = nullptr;
+ mi.pszService = "CurrencyRates/OpenLogFile";
+ g_hMenuOpenLogFile = Menu_AddContactMenuItem(&mi, CURRENCYRATES_PROTOCOL_NAME);
+ Menu_ConfigureItem(g_hMenuOpenLogFile, MCI_OPT_EXECPARAM, 1);
+ CreateServiceFunction(mi.pszService, CurrencyRatesMenu_OpenLogFile);
+
+ #ifdef CHART_IMPLEMENT
+ SET_UID(mi, 0x65da7256, 0x43a2, 0x4857, 0xac, 0x52, 0x1c, 0xb7, 0xff, 0xd7, 0x96, 0xfa);
+ mi.name.w = LPGENW("Chart...");
+ mi.hIcolibItem = nullptr;
+ mi.pszService = "CurrencyRates/Chart";
+ g_hMenuChart = Menu_AddContactMenuItem(&mi, CURRENCYRATES_PROTOCOL_NAME);
+ CreateServiceFunction(mi.pszService, CurrencyRatesMenu_Chart);
+ #endif
+
+ SET_UID(mi, 0xac5fc17, 0x5640, 0x4f81, 0xa3, 0x44, 0x8c, 0xb6, 0x9a, 0x5c, 0x98, 0xf);
+ mi.name.w = LPGENW("Edit Settings...");
+ mi.hIcolibItem = nullptr;
+ mi.pszService = "CurrencyRates/EditSettings";
+ g_hMenuEditSettings = Menu_AddContactMenuItem(&mi, CURRENCYRATES_PROTOCOL_NAME);
+ #ifdef CHART_IMPLEMENT
+ Menu_ConfigureItem(g_hMenuEditSettings, MCI_OPT_EXECPARAM, 3);
+ #else
+ Menu_ConfigureItem(g_hMenuEditSettings, MCI_OPT_EXECPARAM, 2);
+ #endif
+ CreateServiceFunction(mi.pszService, CurrencyRatesMenu_EditSettings);
+}
+
+int CurrencyRates_OnToolbarLoaded(WPARAM, LPARAM)
+{
+ TTBButton ttb = {};
+ ttb.name = LPGEN("Enable/Disable Currency Rates Auto Update");
+ ttb.pszService = g_pszAutoUpdateCmd;
+ ttb.pszTooltipUp = LPGEN("Currency Rates Auto Update Enabled");
+ ttb.pszTooltipDn = LPGEN("Currency Rates Auto Update Disabled");
+ ttb.hIconHandleUp = CurrencyRates_GetIconHandle(IDI_ICON_MAIN);
+ ttb.hIconHandleDn = CurrencyRates_GetIconHandle(IDI_ICON_DISABLED);
+ ttb.dwFlags = ((g_bAutoUpdate) ? 0 : TTBBF_PUSHED) | TTBBF_ASPUSHBUTTON | TTBBF_VISIBLE;
+ g_hTBButton = g_plugin.addTTB(&ttb);
+
+ ttb.name = LPGEN("Currency Converter");
+ ttb.pszService = g_pszCurrencyConverter;
+ ttb.pszTooltipUp = LPGEN("Currency Converter");
+ ttb.pszTooltipDn = LPGEN("Currency Converter");
+ ttb.hIconHandleUp = CurrencyRates_GetIconHandle(IDI_ICON_CURRENCY_CONVERTER);
+ ttb.hIconHandleDn = CurrencyRates_GetIconHandle(IDI_ICON_CURRENCY_CONVERTER);
+ ttb.dwFlags = TTBBF_VISIBLE;
+ g_plugin.addTTB(&ttb);
+
+ return 0;
+}
+
+static void WorkingThread(void *pParam)
+{
+ ICurrencyRatesProvider *pProvider = reinterpret_cast<ICurrencyRatesProvider*>(pParam);
+ assert(pProvider);
+
+ if (pProvider)
+ pProvider->Run();
+}
+
+int CurrencyRatesEventFunc_OnModulesLoaded(WPARAM, LPARAM)
+{
+ CHTTPSession::Init();
+
+ g_hEventWorkThreadStop = ::CreateEvent(nullptr, TRUE, FALSE, nullptr);
+ HookEvent(ME_USERINFO_INITIALISE, CurrencyRatesEventFunc_OnUserInfoInit);
+
+ HookEvent(ME_CLIST_DOUBLECLICKED, CurrencyRates_OnContactDoubleClick);
+
+ HookEvent(ME_TTB_MODULELOADED, CurrencyRates_OnToolbarLoaded);
+
+ g_bAutoUpdate = 1 == db_get_b(0, CURRENCYRATES_MODULE_NAME, DB_STR_AUTO_UPDATE, 1);
+
+ InitMenu();
+
+ ::ResetEvent(g_hEventWorkThreadStop);
+
+ for (auto &pProvider : g_apProviders)
+ g_ahThreads.push_back(mir_forkthread(WorkingThread, pProvider));
+ return 0;
+}
+
+int CurrencyRatesEventFunc_OnContactDeleted(WPARAM hContact, LPARAM)
+{
+ auto pProvider = GetContactProviderPtr(hContact);
+ if (pProvider)
+ pProvider->DeleteContact(hContact);
+ return 0;
+}
+
+INT_PTR CurrencyRateProtoFunc_GetCaps(WPARAM wParam, LPARAM)
+{
+ switch (wParam) {
+ case PFLAG_UNIQUEIDTEXT:
+ return (INT_PTR)Translate("Currency Symbol");
+ }
+
+ return 0;
+}
+
+INT_PTR CurrencyRateProtoFunc_GetStatus(WPARAM, LPARAM)
+{
+ return g_bAutoUpdate ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE;
+}
+
+void WaitForWorkingThreads()
+{
+ size_t cThreads = g_ahThreads.size();
+ if (cThreads > 0) {
+ HANDLE* paHandles = &*(g_ahThreads.begin());
+ ::WaitForMultipleObjects((DWORD)cThreads, paHandles, TRUE, INFINITE);
+ }
+}
+
+
+int CurrencyRatesEventFunc_PreShutdown(WPARAM, LPARAM)
+{
+ ::SetEvent(g_hEventWorkThreadStop);
+
+ CModuleInfo::OnMirandaShutdown();
+ return 0;
+}
+
+int CurrencyRatesEventFunc_OptInitialise(WPARAM wp, LPARAM/* lp*/)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.position = 910000000;
+ odp.szTitle.w = LPGENW("Currency Rates");
+ odp.szGroup.w = LPGENW("Network");
+ odp.flags = ODPF_USERINFOTAB | ODPF_UNICODE;
+
+ for (auto &it : g_apProviders)
+ it->ShowPropertyPage(wp, odp);
+ return 0;
+}
+
+inline int CurrencyRates_UnhookEvent(HANDLE h)
+{
+ return UnhookEvent(h);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+EXTERN_C __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+PLUGININFOEX pluginInfoEx =
+{
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE,
+ // {E882056D-0D1D-4131-9A98-404CBAEA6A9C}
+ { 0xe882056d, 0xd1d, 0x4131, { 0x9a, 0x98, 0x40, 0x4c, 0xba, 0xea, 0x6a, 0x9c } }
+};
+
+CMPlugin::CMPlugin() :
+ PLUGIN<CMPlugin>(CURRENCYRATES_PROTOCOL_NAME, pluginInfoEx)
+{
+ RegisterProtocol(PROTOTYPE_VIRTUAL);
+ SetUniqueId(DB_STR_CURRENCYRATE_SYMBOL);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPlugin::Load(void)
+{
+ if (!CModuleInfo::Verify())
+ return 1;
+
+ CurrencyRates_IconsInit();
+ CurrencyRates_InitExtraIcons();
+
+ InitProviders();
+
+ CreateProtoServiceFunction(CURRENCYRATES_PROTOCOL_NAME, PS_GETCAPS, CurrencyRateProtoFunc_GetCaps);
+ CreateProtoServiceFunction(CURRENCYRATES_PROTOCOL_NAME, PS_GETSTATUS, CurrencyRateProtoFunc_GetStatus);
+
+ HookEvent(ME_SYSTEM_MODULESLOADED, CurrencyRatesEventFunc_OnModulesLoaded);
+ HookEvent(ME_DB_CONTACT_DELETED, CurrencyRatesEventFunc_OnContactDeleted);
+ HookEvent(ME_SYSTEM_PRESHUTDOWN, CurrencyRatesEventFunc_PreShutdown);
+ HookEvent(ME_OPT_INITIALISE, CurrencyRatesEventFunc_OptInitialise);
+
+ CreateServiceFunction(MS_CURRENCYRATES_EXPORT, CurrencyRates_Export);
+ CreateServiceFunction(MS_CURRENCYRATES_IMPORT, CurrencyRates_Import);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPlugin::Unload(void)
+{
+ WaitForWorkingThreads();
+
+ ClearProviders();
+ ::CloseHandle(g_hEventWorkThreadStop);
+ return 0;
+}
diff --git a/protocols/CurrencyRates/src/HTMLParserMS.cpp b/protocols/CurrencyRates/src/HTMLParserMS.cpp
new file mode 100644
index 0000000000..a712d7589f
--- /dev/null
+++ b/protocols/CurrencyRates/src/HTMLParserMS.cpp
@@ -0,0 +1,254 @@
+#include "StdAfx.h"
+
+using _com_util::CheckError;
+
+class CHTMLNode : public IHTMLNode
+{
+public:
+ typedef CComPtr<IDispatch> TComPtr;
+ typedef CComPtr<IHTMLDocument3> TDocumentPtr;
+
+protected:
+ typedef CComPtr<IHTMLElementCollection> TElementCollectionPtr;
+
+public:
+ CHTMLNode(const TComPtr& pElement, const TDocumentPtr& pDocument)
+ : m_pElement(pElement), m_pDocument(pDocument)
+ {
+ }
+
+ virtual THTMLNodePtr GetElementByID(const tstring& rsID) const
+ {
+ if (m_pDocument) {
+ CComPtr<IHTMLElement> pElement;
+ if (SUCCEEDED(m_pDocument->getElementById(bstr_t(rsID.c_str()), &pElement))
+ && pElement) {
+ TComPtr p(pElement);
+ return THTMLNodePtr(new CHTMLNode(p, m_pDocument));
+ }
+ }
+
+ return THTMLNodePtr();
+ }
+
+ virtual size_t GetChildCount() const
+ {
+ TElementCollectionPtr pColl = GetElementCollectionPtr();
+ if (pColl) {
+ LONG celem = 0;
+ HRESULT hr = pColl->get_length(&celem);
+ if (S_OK == hr)
+ return celem;
+ }
+
+ return 0;
+ }
+
+ virtual THTMLNodePtr GetChildPtr(size_t nIndex)
+ {
+ TElementCollectionPtr pColl = GetElementCollectionPtr();
+ if (pColl) {
+ VARIANT varIndex;
+ varIndex.vt = VT_UINT;
+ varIndex.lVal = (LONG)nIndex;
+ VARIANT var2;
+ VariantInit(&var2);
+ TComPtr pDisp;
+ HRESULT hr = pColl->item(varIndex, var2, &pDisp);
+ if (S_OK == hr && pDisp)
+ return THTMLNodePtr(new CHTMLNode(pDisp, m_pDocument));
+ }
+
+ return THTMLNodePtr();
+ }
+
+ virtual bool Is(EType nType) const
+ {
+ switch (nType) {
+ case Table:
+ {
+ CComPtr<IHTMLTable> pTable;
+ return (SUCCEEDED(m_pElement->QueryInterface(IID_IHTMLTable, reinterpret_cast<void**>(&pTable))) && (pTable));
+ }
+ case TableRow:
+ {
+ CComPtr<IHTMLTableRow> pRow;
+ return (SUCCEEDED(m_pElement->QueryInterface(IID_IHTMLTableRow, reinterpret_cast<void**>(&pRow))) && (pRow));
+ }
+ case TableColumn:
+ {
+ CComPtr<IHTMLTableCol> pCol;
+ return (SUCCEEDED(m_pElement->QueryInterface(IID_IHTMLTableCol, reinterpret_cast<void**>(&pCol))) && (pCol));
+ }
+ }
+
+ return false;
+ }
+
+ virtual tstring GetAttribute(const tstring& rsAttrName) const
+ {
+ tstring sAttr;
+ CComPtr<IHTMLElement> pElement;
+ if (SUCCEEDED(m_pElement->QueryInterface(IID_IHTMLElement, reinterpret_cast<void**>(&pElement))) && pElement) {
+ _variant_t vAttribute;
+ BSTR pbstrAttrName = ::SysAllocString(rsAttrName.c_str());
+ if (SUCCEEDED(pElement->getAttribute(pbstrAttrName, 1, &vAttribute)) && VT_NULL != vAttribute.vt && VT_EMPTY != vAttribute.vt) {
+ try {
+ _bstr_t b(vAttribute);
+ LPCTSTR psz = b;
+ if (psz)
+ sAttr = psz;
+ }
+ catch (_com_error&) {
+ }
+ }
+ ::SysFreeString(pbstrAttrName);
+ }
+
+ return sAttr;
+ }
+
+ virtual tstring GetText() const
+ {
+ tstring sText;
+ CComPtr<IHTMLElement> pElement;
+ if (SUCCEEDED(m_pElement->QueryInterface(IID_IHTMLElement, reinterpret_cast<void**>(&pElement))) && pElement) {
+ BSTR bstrText;
+ if (SUCCEEDED(pElement->get_innerText(&bstrText)) && bstrText) {
+ try {
+ sText = _bstr_t(bstrText);
+ }
+ catch (_com_error&) {
+ }
+
+ ::SysFreeString(bstrText);
+ }
+ }
+
+ return sText;
+ }
+
+protected:
+ virtual TElementCollectionPtr GetElementCollectionPtr() const
+ {
+ TElementCollectionPtr pColl;
+ HRESULT hr = m_pElement->QueryInterface(IID_IHTMLElementCollection, reinterpret_cast<void**>(&pColl));
+ if (FAILED(hr)) {
+ CComPtr<IHTMLElement> pElement;
+ if (SUCCEEDED(m_pElement->QueryInterface(IID_IHTMLElement, reinterpret_cast<void**>(&pElement))) && pElement) {
+ CComPtr<IDispatch> pDisp;
+ if (SUCCEEDED(pElement->get_children(&pDisp)) && pDisp)
+ pDisp->QueryInterface(IID_IHTMLElementCollection, reinterpret_cast<void**>(&pColl));
+ }
+ }
+
+ return pColl;
+ }
+
+private:
+ TComPtr m_pElement;
+ TDocumentPtr m_pDocument;
+};
+
+CHTMLParserMS::CHTMLParserMS() : m_bCallUninit(false)
+{
+ try {
+ CheckError(::CoInitialize(nullptr));
+
+ m_bCallUninit = true;
+
+ _com_util::CheckError(
+ ::CoCreateInstance(CLSID_HTMLDocument,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IHTMLDocument2,
+ (LPVOID*)&m_pDoc)
+ );
+
+ CComPtr<IPersistStreamInit> pPersist;
+ _com_util::CheckError(m_pDoc->QueryInterface(IID_IPersistStreamInit,
+ (LPVOID*)&pPersist));
+
+ _com_util::CheckError(pPersist->InitNew());
+
+ _com_util::CheckError(m_pDoc->QueryInterface(IID_IMarkupServices,
+ (LPVOID*)&m_pMS));
+
+ if (m_pMS) {
+ _com_util::CheckError(m_pMS->CreateMarkupPointer(&m_pMkStart));
+ _com_util::CheckError(m_pMS->CreateMarkupPointer(&m_pMkFinish));
+ }
+ }
+ catch (_com_error&/* e*/) {
+ // show_com_error_msg(e);
+ }
+}
+
+CHTMLParserMS::~CHTMLParserMS()
+{
+ if (m_bCallUninit)
+ ::CoUninitialize();
+}
+
+CHTMLParserMS::THTMLNodePtr CHTMLParserMS::ParseString(const tstring& rsHTML)
+{
+ mir_cslock lck(m_cs);
+
+ CComPtr<IMarkupContainer> pMC;
+ HRESULT hr = m_pMS->ParseString((OLECHAR*)rsHTML.c_str(), 0, &pMC, m_pMkStart, m_pMkFinish);
+ if (SUCCEEDED(hr) && pMC) {
+ CComPtr<IHTMLDocument2> pNewDoc;
+ hr = pMC->QueryInterface(IID_IHTMLDocument, (LPVOID*)&pNewDoc);
+ if (SUCCEEDED(hr) && pNewDoc) {
+ CComPtr<IHTMLElementCollection> pColl;
+ pNewDoc->get_all(&pColl);
+
+ CHTMLNode::TDocumentPtr pDoc;
+ pMC->QueryInterface(IID_IHTMLDocument3, (LPVOID*)&pDoc);
+ return THTMLNodePtr(new CHTMLNode(CHTMLNode::TComPtr(pColl), pDoc));
+ }
+ }
+
+ return THTMLNodePtr();
+}
+
+bool CHTMLParserMS::IsInstalled()
+{
+ bool bResult = true;
+ bool bCallUninit = false;
+ try {
+ CheckError(::CoInitialize(nullptr));
+
+ bCallUninit = true;
+
+ CComPtr<IHTMLDocument2> pDoc;
+ _com_util::CheckError(
+ ::CoCreateInstance(CLSID_HTMLDocument,
+ nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_IHTMLDocument2,
+ reinterpret_cast<LPVOID*>(&pDoc))
+ );
+ }
+ catch (_com_error&/* e*/) {
+ bResult = false;
+ }
+
+ if (bCallUninit)
+ ::CoUninitialize();
+
+ return bResult;
+}
+
+CHTMLEngineMS::CHTMLEngineMS()
+{
+}
+
+CHTMLEngineMS::~CHTMLEngineMS()
+{
+}
+
+CHTMLEngineMS::THTMLParserPtr CHTMLEngineMS::GetParserPtr() const
+{
+ return THTMLParserPtr(new CHTMLParserMS);
+}
diff --git a/protocols/CurrencyRates/src/HTMLParserMS.h b/protocols/CurrencyRates/src/HTMLParserMS.h
new file mode 100644
index 0000000000..6b2ceb26d8
--- /dev/null
+++ b/protocols/CurrencyRates/src/HTMLParserMS.h
@@ -0,0 +1,32 @@
+#ifndef __3c99e3f7_ecd9_4d9b_8f86_fe293c5fc8e6_HTMLParserMS_h__
+#define __3c99e3f7_ecd9_4d9b_8f86_fe293c5fc8e6_HTMLParserMS_h__
+
+class CHTMLParserMS : public IHTMLParser
+{
+public:
+ CHTMLParserMS();
+ ~CHTMLParserMS();
+
+ virtual THTMLNodePtr ParseString(const tstring& rsHTML);
+
+ static bool IsInstalled();
+
+private:
+ bool m_bCallUninit;
+ CComPtr<IHTMLDocument2> m_pDoc;
+ CComPtr<IMarkupServices> m_pMS;
+ CComPtr<IMarkupPointer> m_pMkStart;
+ CComPtr<IMarkupPointer> m_pMkFinish;
+ mutable mir_cs m_cs;
+};
+
+class CHTMLEngineMS : public IHTMLEngine
+{
+public:
+ CHTMLEngineMS();
+ ~CHTMLEngineMS();
+
+ virtual THTMLParserPtr GetParserPtr() const;
+};
+
+#endif //__3c99e3f7_ecd9_4d9b_8f86_fe293c5fc8e6_HTMLParserMS_h__
diff --git a/protocols/CurrencyRates/src/HTTPSession.cpp b/protocols/CurrencyRates/src/HTTPSession.cpp
new file mode 100644
index 0000000000..22c34867c5
--- /dev/null
+++ b/protocols/CurrencyRates/src/HTTPSession.cpp
@@ -0,0 +1,91 @@
+#include "StdAfx.h"
+
+HNETLIBUSER CHTTPSession::g_hNetLib = nullptr;
+
+#define ERROR_MSG LPGENW("This plugin requires a personal key. Press Yes to obtain it at the site and then enter the result in the Options dialog, otherwise this plugin will fail.")
+
+void CALLBACK waitStub()
+{
+ if (IDYES == MessageBox(0, TranslateW(ERROR_MSG), _A2W(CURRENCYRATES_MODULE_NAME), MB_YESNOCANCEL))
+ Utils_OpenUrl("https://free.currencyconverterapi.com/free-api-key");
+}
+
+static int find_header(const NETLIBHTTPREQUEST* pRequest, const char* hdr)
+{
+ for (int i = 0; i < pRequest->headersCount; ++i)
+ if (0 == _stricmp(pRequest->headers[i].szName, hdr))
+ return i;
+
+ return -1;
+}
+
+bool CHTTPSession::OpenURL(const tstring &rsURL)
+{
+ std::string s = currencyrates_t2a(rsURL.c_str());
+ m_szUrl = s.c_str();
+ return true;
+}
+
+bool CHTTPSession::ReadResponce(tstring& rsResponce)
+{
+ if (m_szUrl.IsEmpty())
+ return false;
+
+ NETLIBHTTPHEADER headers[] =
+ {
+ { "User-Agent", NETLIB_USER_AGENT },
+ { "Connection", "close" },
+ { "Cache-Control", "no-cache" },
+ { "Pragma", "no-cache" }
+ };
+
+ NETLIBHTTPREQUEST nlhr = {};
+ nlhr.cbSize = sizeof(nlhr);
+ nlhr.requestType = REQUEST_GET;
+ nlhr.flags = NLHRF_DUMPASTEXT | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ nlhr.szUrl = m_szUrl.GetBuffer();
+ nlhr.headersCount = _countof(headers);
+ nlhr.headers = headers;
+
+ bool bResult = false;
+ NETLIBHTTPREQUEST *pReply = nullptr;
+ {
+ mir_cslock lck(m_mx);
+ pReply = Netlib_HttpTransaction(g_hNetLib, &nlhr);
+ }
+
+ if (pReply) {
+ if ((200 == pReply->resultCode) && (pReply->dataLength > 0)) {
+ CMStringA buf(pReply->pData, pReply->dataLength);
+ int nIndex = find_header(pReply, "Content-Type");
+ if ((-1 != nIndex) && (nullptr != strstr(_strlwr(pReply->headers[nIndex].szValue), "utf-8")))
+ rsResponce = ptrW(mir_utf8decodeW(buf));
+ else
+ rsResponce = currencyrates_a2t(buf);
+
+ bResult = true;
+ }
+
+ Netlib_FreeHttpRequest(pReply);
+ }
+ return bResult;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// module initialization
+
+bool CHTTPSession::Init()
+{
+ assert(nullptr == g_hNetLib);
+
+ ptrA szApiKey(g_plugin.getStringA(DB_KEY_ApiKey));
+ if (szApiKey == nullptr)
+ Miranda_WaitOnHandle(waitStub);
+
+ NETLIBUSER nlu = {};
+ nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_NOHTTPSOPTION | NUF_UNICODE;
+ nlu.szSettingsModule = CURRENCYRATES_PROTOCOL_NAME;
+ nlu.szDescriptiveName.w = TranslateT("CurrencyRates HTTP connections");
+ g_hNetLib = Netlib_RegisterUser(&nlu);
+ return (nullptr != g_hNetLib);
+}
diff --git a/protocols/CurrencyRates/src/HTTPSession.h b/protocols/CurrencyRates/src/HTTPSession.h
new file mode 100644
index 0000000000..9928c58304
--- /dev/null
+++ b/protocols/CurrencyRates/src/HTTPSession.h
@@ -0,0 +1,20 @@
+#ifndef __8C9706FF_6B05_4d0d_85B8_5724E5DC0BA4_HTTPSession_h__
+#define __8C9706FF_6B05_4d0d_85B8_5724E5DC0BA4_HTTPSession_h__
+
+class CHTTPSession
+{
+ static HNETLIBUSER g_hNetLib;
+ CMStringA m_szUrl;
+ mir_cs m_mx;
+
+public:
+ CHTTPSession() {}
+ ~CHTTPSession() {}
+
+ static bool Init();
+
+ bool OpenURL(const tstring &rsURL);
+ bool ReadResponce(tstring &rsResponce);
+};
+
+#endif //__8C9706FF_6B05_4d0d_85B8_5724E5DC0BA4_HTTPSession_h__
diff --git a/protocols/CurrencyRates/src/ICurrencyRatesProvider.h b/protocols/CurrencyRates/src/ICurrencyRatesProvider.h
new file mode 100644
index 0000000000..a80cae3088
--- /dev/null
+++ b/protocols/CurrencyRates/src/ICurrencyRatesProvider.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#ifndef __ac71e133_786c_41a7_ab07_625b76ff2a8c_CurrencyRatesProvider_h__
+#define __ac71e133_786c_41a7_ab07_625b76ff2a8c_CurrencyRatesProvider_h__
+
+class CCurrencyRatesProviderVisitor;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// CFormatSpecificator - array of variables to replace
+
+using CFormatSpecificator = std::pair<tstring, tstring>;
+typedef std::vector<CFormatSpecificator> TFormatSpecificators;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// ICurrencyRatesProvider - abstract interface
+
+class ICurrencyRatesProvider : private boost::noncopyable
+{
+public:
+ struct CProviderInfo
+ {
+ tstring m_sName;
+ tstring m_sURL;
+ };
+
+public:
+ ICurrencyRatesProvider() {}
+ virtual ~ICurrencyRatesProvider() {}
+
+ virtual bool Init() = 0;
+ virtual const CProviderInfo& GetInfo() const = 0;
+
+ virtual void AddContact(MCONTACT hContact) = 0;
+ virtual void DeleteContact(MCONTACT hContact) = 0;
+ virtual MCONTACT ImportContact(const TiXmlNode*) = 0;
+
+ virtual void ShowPropertyPage(WPARAM wp, OPTIONSDIALOGPAGE& odp) = 0;
+
+ virtual void RefreshAllContacts() = 0;
+ virtual void RefreshSettings() = 0;
+ virtual void RefreshContact(MCONTACT hContact) = 0;
+
+ virtual void FillFormat(TFormatSpecificators&) const = 0;
+ virtual bool ParseSymbol(MCONTACT hContact, wchar_t c, double &d) = 0;
+ virtual tstring FormatSymbol(MCONTACT hContact, wchar_t c, int nWidth = 0) const = 0;
+
+ virtual void Run() = 0;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+typedef std::vector<ICurrencyRatesProvider*> TCurrencyRatesProviders;
+extern TCurrencyRatesProviders g_apProviders;
+
+ICurrencyRatesProvider* FindProvider(const tstring& rsName);
+ICurrencyRatesProvider* GetContactProviderPtr(MCONTACT hContact);
+
+void InitProviders();
+void CreateProviders();
+void ClearProviders();
+
+#endif //__ac71e133_786c_41a7_ab07_625b76ff2a8c_CurrencyRatesProvider_h__
diff --git a/protocols/CurrencyRates/src/IHTMLEngine.h b/protocols/CurrencyRates/src/IHTMLEngine.h
new file mode 100644
index 0000000000..6cc9defce7
--- /dev/null
+++ b/protocols/CurrencyRates/src/IHTMLEngine.h
@@ -0,0 +1,18 @@
+#ifndef __85dbfa97_919b_4776_919c_7410a1c3d787_HTMLEngine_h__
+#define __85dbfa97_919b_4776_919c_7410a1c3d787_HTMLEngine_h__
+
+class IHTMLParser;
+
+class IHTMLEngine
+{
+public:
+ typedef boost::shared_ptr<IHTMLParser> THTMLParserPtr;
+
+public:
+ IHTMLEngine(void){}
+ virtual ~IHTMLEngine() {}
+
+ virtual THTMLParserPtr GetParserPtr() const = 0;
+};
+
+#endif //__85dbfa97_919b_4776_919c_7410a1c3d787_HTMLEngine_h__
diff --git a/protocols/CurrencyRates/src/IHTMLParser.h b/protocols/CurrencyRates/src/IHTMLParser.h
new file mode 100644
index 0000000000..c0b97a7277
--- /dev/null
+++ b/protocols/CurrencyRates/src/IHTMLParser.h
@@ -0,0 +1,41 @@
+#ifndef __98ad6d6d_2a27_43fd_bf3e_c18416a45e54_IHTMLParser_h__
+#define __98ad6d6d_2a27_43fd_bf3e_c18416a45e54_IHTMLParser_h__
+
+class IHTMLNode
+{
+public:
+ typedef boost::shared_ptr<IHTMLNode> THTMLNodePtr;
+
+ enum EType
+ {
+ Table = 1,
+ TableRow,
+ TableColumn
+ };
+
+public:
+ IHTMLNode() {}
+ virtual ~IHTMLNode() {}
+
+ virtual size_t GetChildCount() const = 0;
+ virtual THTMLNodePtr GetChildPtr(size_t nIndex) = 0;
+ virtual bool Is(EType nType) const = 0;
+
+ virtual THTMLNodePtr GetElementByID(const tstring& rsID) const = 0;
+
+ virtual tstring GetAttribute(const tstring& rsAttrName) const = 0;
+ virtual tstring GetText() const = 0;
+};
+
+class IHTMLParser
+{
+public:
+ typedef IHTMLNode::THTMLNodePtr THTMLNodePtr;
+public:
+ IHTMLParser() {}
+ virtual ~IHTMLParser() {}
+
+ virtual THTMLNodePtr ParseString(const tstring& rsHTML) = 0;
+};
+
+#endif //__98ad6d6d_2a27_43fd_bf3e_c18416a45e54_IHTMLParser_h__
diff --git a/protocols/CurrencyRates/src/IconLib.cpp b/protocols/CurrencyRates/src/IconLib.cpp
new file mode 100644
index 0000000000..da67d1310f
--- /dev/null
+++ b/protocols/CurrencyRates/src/IconLib.cpp
@@ -0,0 +1,40 @@
+#include "StdAfx.h"
+
+static IconItem iconList[] =
+{
+ { LPGEN("Protocol icon"), ICON_STR_MAIN, IDI_ICON_MAIN },
+ { LPGEN("Auto Update Disabled"), "auto_update_disabled", IDI_ICON_DISABLED },
+ { LPGEN("Currency Rate up"), "currencyrate_up", IDI_ICON_UP },
+ { LPGEN("Currency Rate down"), "currencyrate_down", IDI_ICON_DOWN },
+ { LPGEN("Currency Rate not changed"), "currencyrate_not_changed", IDI_ICON_NOTCHANGED },
+ { LPGEN("Currency Rate Section"), "currencyrate_section", IDI_ICON_SECTION },
+ { LPGEN("Currency Rate"), ICON_STR_CURRENCYRATE, IDI_ICON_CURRENCYRATE },
+ { LPGEN("Currency Converter"), "currency_converter", IDI_ICON_CURRENCY_CONVERTER },
+ { LPGEN("Refresh"), "refresh", IDI_ICON_REFRESH },
+ { LPGEN("Export"), "export", IDI_ICON_EXPORT },
+ { LPGEN("Swap button"), "swap", IDI_ICON_SWAP },
+ { LPGEN("Import"), "import", IDI_ICON_IMPORT }
+};
+
+void CurrencyRates_IconsInit()
+{
+ ::g_plugin.registerIcon(CURRENCYRATES_PROTOCOL_NAME, iconList, CURRENCYRATES_PROTOCOL_NAME);
+}
+
+HICON CurrencyRates_LoadIconEx(int iconId, bool bBig /*= false*/)
+{
+ for (int i = 0; i < _countof(iconList); i++)
+ if (iconList[i].defIconID == iconId)
+ return IcoLib_GetIconByHandle(iconList[i].hIcolib, bBig);
+
+ return nullptr;
+}
+
+HANDLE CurrencyRates_GetIconHandle(int iconId)
+{
+ for (int i = 0; i < _countof(iconList); i++)
+ if (iconList[i].defIconID == iconId)
+ return iconList[i].hIcolib;
+
+ return nullptr;
+}
diff --git a/protocols/CurrencyRates/src/IconLib.h b/protocols/CurrencyRates/src/IconLib.h
new file mode 100644
index 0000000000..4e0146fba8
--- /dev/null
+++ b/protocols/CurrencyRates/src/IconLib.h
@@ -0,0 +1,11 @@
+#ifndef __8821d334_afac_439e_9a81_76318e1ac4ef_IconLib_h__
+#define __8821d334_afac_439e_9a81_76318e1ac4ef_IconLib_h__
+
+#define ICON_STR_MAIN "main"
+#define ICON_STR_CURRENCYRATE "currencyrate"
+
+void CurrencyRates_IconsInit();
+HICON CurrencyRates_LoadIconEx(int iconId, bool bBig = false);
+HANDLE CurrencyRates_GetIconHandle(int iconId);
+
+#endif //__8821d334_afac_439e_9a81_76318e1ac4ef_IconLib_h__
diff --git a/protocols/CurrencyRates/src/ImportExport.cpp b/protocols/CurrencyRates/src/ImportExport.cpp
new file mode 100644
index 0000000000..95fb9d95f7
--- /dev/null
+++ b/protocols/CurrencyRates/src/ImportExport.cpp
@@ -0,0 +1,507 @@
+#include "StdAfx.h"
+
+const char g_szXmlValue[] = "Value";
+const char g_szXmlName[] = "Name";
+const char g_szXmlSetting[] = "Setting";
+const char g_szXmlModule[] = "Module";
+const char g_szXmlContact[] = "Contact";
+const char g_szXmlContacts[] = "Contacts";
+const char g_szXmlType[] = "type";
+const char g_szXmlTypeByte[] = "byte";
+const char g_szXmlTypeWord[] = "word";
+const char g_szXmlTypeDword[] = "dword";
+const char g_szXmlTypeAsciiz[] = "asciiz";
+const char g_szXmlTypeWchar[] = "wchar";
+const char g_szXmlTypeUtf8[] = "utf8";
+const char g_szXmlTypeBlob[] = "blob";
+
+struct CEnumContext
+{
+ CEnumContext(TiXmlDocument &doc) :
+ m_xmlDoc(doc)
+ {}
+
+ TiXmlDocument &m_xmlDoc;
+ TiXmlNode *m_pNode;
+ MCONTACT m_hContact;
+ LPCSTR m_pszModule;
+};
+
+struct mir_safety_dbvar
+{
+ mir_safety_dbvar(DBVARIANT* p) : m_p(p) {}
+ ~mir_safety_dbvar() { db_free(m_p); }
+ DBVARIANT* m_p;
+};
+
+static int enum_contact_settings(const char *szSetting, void *lp)
+{
+ CEnumContext *ctx = reinterpret_cast<CEnumContext*>(lp);
+
+ DBVARIANT dbv;
+ if (0 == db_get(ctx->m_hContact, ctx->m_pszModule, szSetting, &dbv)) {
+ mir_safety_dbvar sdbvar(&dbv);
+
+ std::string sType;
+ std::wostringstream sValue;
+ sValue.imbue(GetSystemLocale());
+
+ switch (dbv.type) {
+ case DBVT_BYTE:
+ sValue << dbv.bVal;
+ sType = g_szXmlTypeByte;
+ break;
+ case DBVT_WORD:
+ sValue << dbv.wVal;
+ sType = g_szXmlTypeWord;
+ break;
+ case DBVT_DWORD:
+ sValue << dbv.dVal;
+ sType = g_szXmlTypeDword;
+ break;
+ case DBVT_ASCIIZ:
+ sType = g_szXmlTypeAsciiz;
+ if (dbv.pszVal)
+ sValue << dbv.pszVal;
+ break;
+ case DBVT_WCHAR:
+ sType = g_szXmlTypeWchar;
+ if (dbv.pwszVal)
+ sValue << dbv.pwszVal;
+ break;
+ case DBVT_UTF8:
+ sType = g_szXmlTypeUtf8;
+ if (dbv.pszVal)
+ sValue << dbv.pszVal;
+ break;
+ case DBVT_BLOB:
+ sType = g_szXmlTypeBlob;
+ if (dbv.pbVal) {
+ ptrA buf(mir_base64_encode(dbv.pbVal, dbv.cpbVal));
+ if (buf)
+ sValue << buf;
+ }
+ break;
+ }
+
+ auto *pXmlName = ctx->m_xmlDoc.NewElement(g_szXmlName);
+ pXmlName->SetText(szSetting);
+
+ auto *pXmlValue = ctx->m_xmlDoc.NewElement(g_szXmlValue);
+ pXmlValue->SetText(T2Utf(sValue.str().c_str()).get());
+ pXmlValue->SetAttribute(g_szXmlType, sType.c_str());
+
+ auto *pXmlSet = ctx->m_xmlDoc.NewElement(g_szXmlSetting);
+ pXmlSet->InsertEndChild(pXmlName);
+ pXmlSet->InsertEndChild(pXmlValue);
+ ctx->m_pNode->InsertEndChild(pXmlSet);
+ }
+
+ return 0;
+}
+
+int EnumDbModules(const char *szModuleName, void *lp)
+{
+ CEnumContext *ctx = (CEnumContext*)lp;
+ auto *pXml = ctx->m_pNode;
+ auto *pModule = ctx->m_xmlDoc.NewElement(g_szXmlModule);
+ pModule->SetText(szModuleName);
+
+ ctx->m_pszModule = szModuleName;
+ ctx->m_pNode = pModule;
+ db_enum_settings(ctx->m_hContact, &enum_contact_settings, szModuleName, ctx);
+
+ if (pModule->FirstChildElement(g_szXmlSetting))
+ pXml->InsertEndChild(pModule);
+
+ ctx->m_pNode = pXml;
+ return 0;
+}
+
+TiXmlNode* export_contact(MCONTACT hContact, TiXmlDocument &pDoc)
+{
+ CEnumContext ctx(pDoc);
+ ctx.m_pNode = pDoc.NewElement(g_szXmlContact);
+ ctx.m_hContact = hContact;
+ db_enum_modules(EnumDbModules, &ctx);
+
+ return ctx.m_pNode;
+}
+
+LPCTSTR prepare_filter(LPTSTR pszBuffer, size_t cBuffer)
+{
+ LPTSTR p = pszBuffer;
+ LPCTSTR pszXml = TranslateT("XML File (*.xml)");
+ mir_wstrncpy(p, pszXml, (int)cBuffer);
+ size_t nLen = mir_wstrlen(pszXml) + 1;
+ p += nLen;
+ if (nLen < cBuffer) {
+ mir_wstrncpy(p, L"*.xml", (int)(cBuffer - nLen));
+ p += 6;
+ nLen += 6;
+ }
+
+ if (nLen < cBuffer) {
+ LPCTSTR pszAll = TranslateT("All files (*.*)");
+ mir_wstrncpy(p, pszAll, (int)(cBuffer - nLen));
+ size_t n = mir_wstrlen(pszAll) + 1;
+ nLen += n;
+ p += n;
+ }
+
+ if (nLen < cBuffer) {
+ mir_wstrncpy(p, L"*.*", (int)(cBuffer - nLen));
+ p += 4;
+ nLen += 4;
+ }
+
+ if (nLen < cBuffer)
+ *p = '\0';
+
+ return pszBuffer;
+}
+
+bool show_open_file_dialog(bool bOpen, tstring& rsFile)
+{
+ wchar_t szBuffer[MAX_PATH];
+ wchar_t szFilter[MAX_PATH];
+ OPENFILENAME ofn;
+ memset(&ofn, 0, sizeof(ofn));
+
+ ofn.lStructSize = sizeof(OPENFILENAME);
+
+ ofn.hwndOwner = nullptr;
+ ofn.lpstrFilter = prepare_filter(szFilter, MAX_PATH);
+ ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER;
+ ofn.lpstrDefExt = L"xml";
+ if (bOpen)
+ ofn.Flags |= OFN_FILEMUSTEXIST;
+ else
+ ofn.Flags |= OFN_OVERWRITEPROMPT;
+
+ ofn.nMaxFile = MAX_PATH;
+ ofn.lpstrFile = szBuffer;
+ ofn.lpstrFile[0] = '\0';
+
+ if (bOpen) {
+ if (FALSE == GetOpenFileName(&ofn))
+ return false;
+ }
+ else {
+ if (FALSE == GetSaveFileName(&ofn))
+ return false;
+ }
+
+ rsFile = szBuffer;
+ return true;
+}
+
+INT_PTR CurrencyRates_Export(WPARAM wp, LPARAM lp)
+{
+ tstring sFileName;
+ const char* pszFile = reinterpret_cast<const char*>(lp);
+ if (nullptr == pszFile) {
+ if (false == show_open_file_dialog(false, sFileName))
+ return -1;
+ }
+ else sFileName = currencyrates_a2t(pszFile);
+
+ TiXmlDocument doc;
+ auto *pRoot = doc.NewElement(g_szXmlContacts);
+ doc.InsertFirstChild(pRoot);
+
+ MCONTACT hContact = MCONTACT(wp);
+ if (hContact) {
+ auto pProvider = GetContactProviderPtr(hContact);
+ if (pProvider) {
+ auto *pNode = export_contact(hContact, doc);
+ if (pNode)
+ pRoot->InsertEndChild(pNode);
+ }
+ }
+ else {
+ for (auto &cc : Contacts(CURRENCYRATES_MODULE_NAME)) {
+ auto pProvider = GetContactProviderPtr(cc);
+ if (pProvider) {
+ auto *pNode = export_contact(cc, doc);
+ if (pNode)
+ pRoot->InsertEndChild(pNode);
+ }
+ }
+ }
+
+ return doc.SaveFile(_T2A(sFileName.c_str()), true);
+}
+
+bool set_contact_settings(MCONTACT hContact, DBCONTACTWRITESETTING& dbs)
+{
+ assert(DBVT_DELETED != dbs.value.type);
+ return (0 == db_set(hContact, dbs.szModule, dbs.szSetting, &dbs.value));
+}
+
+bool handle_module(MCONTACT hContact, const TiXmlElement *pXmlModule)
+{
+ const char *szModuleName = pXmlModule->GetText();
+ if (szModuleName == nullptr)
+ return false;
+
+ size_t cCreatedRecords = 0;
+ bool bCListModule = !mir_strcmpi(szModuleName, "CList");
+
+ DBCONTACTWRITESETTING dbs;
+ dbs.szModule = szModuleName;
+ for (auto *pSetting : TiXmlFilter(pXmlModule, g_szXmlSetting)) {
+ auto *pNode = pSetting->FirstChildElement(g_szXmlName);
+ if (pNode == nullptr)
+ continue;
+ const char *sName = pNode->GetText();
+
+ pNode = pSetting->FirstChildElement(g_szXmlValue);
+ if (pNode == nullptr)
+ continue;
+ const char *sValue = pNode->GetText();
+ const char *sType = pNode->Attribute(g_szXmlType);
+
+ if (sName == nullptr || sType == nullptr || sValue == nullptr)
+ continue;
+
+ dbs.szSetting = sName;
+ if (!mir_strcmpi(g_szXmlTypeByte, sType)) {
+ std::istringstream in(sValue);
+ in.imbue(GetSystemLocale());
+ dbs.value.cVal = in.get();
+ if (in.good() && in.eof()) {
+ dbs.value.type = DBVT_BYTE;
+ if (set_contact_settings(hContact, dbs))
+ ++cCreatedRecords;
+ }
+ }
+ else if (!mir_strcmpi(g_szXmlTypeWord, sType)) {
+ std::istringstream in(sValue);
+ in.imbue(GetSystemLocale());
+ in >> dbs.value.wVal;
+ if (in.good() || in.eof()) {
+ dbs.value.type = DBVT_WORD;
+ if (set_contact_settings(hContact, dbs))
+ ++cCreatedRecords;
+ }
+ }
+ else if (!mir_strcmpi(g_szXmlTypeDword, sType)) {
+ std::istringstream in(sValue);
+ in.imbue(GetSystemLocale());
+ in >> dbs.value.dVal;
+ if (in.good() || in.eof()) {
+ dbs.value.type = DBVT_DWORD;
+ if (set_contact_settings(hContact, dbs))
+ ++cCreatedRecords;
+ }
+ }
+ else if (!mir_strcmpi(g_szXmlTypeAsciiz, sType)) {
+ dbs.value.pszVal = (char*)sValue;
+ dbs.value.type = DBVT_ASCIIZ;
+ if (set_contact_settings(hContact, dbs))
+ ++cCreatedRecords;
+ }
+ else if (!mir_strcmpi(g_szXmlTypeUtf8, sType)) {
+ dbs.value.pszVal = (char*)sValue;
+ dbs.value.type = DBVT_UTF8;
+ if (set_contact_settings(hContact, dbs))
+ ++cCreatedRecords;
+ }
+ else if (!mir_strcmpi(g_szXmlTypeWchar, sType)) {
+ Utf2T val(sValue);
+ dbs.value.pwszVal = val;
+ dbs.value.type = DBVT_WCHAR;
+ if (set_contact_settings(hContact, dbs))
+ ++cCreatedRecords;
+ }
+ else if (!mir_strcmpi(g_szXmlTypeBlob, sType)) {
+ size_t bufLen;
+ mir_ptr<BYTE> buf((PBYTE)mir_base64_decode(sValue, &bufLen));
+ if (buf) {
+ dbs.value.pbVal = buf;
+ dbs.value.cpbVal = (WORD)bufLen;
+ dbs.value.type = DBVT_BLOB;
+
+ if (set_contact_settings(hContact, dbs))
+ ++cCreatedRecords;
+ }
+ }
+
+ if (bCListModule && !mir_strcmpi(sName, "Group"))
+ Clist_GroupCreate(NULL, Utf2T(sValue));
+ }
+
+ return true;
+}
+
+size_t count_contacts(const TiXmlNode *pXmlRoot, bool bInContactsGroup)
+{
+ size_t cContacts = 0;
+
+ for (auto *pNode : TiXmlEnum(pXmlRoot)) {
+ const char *sName = pNode->Name();
+ if (false == bInContactsGroup) {
+ if (!mir_strcmpi(g_szXmlContacts, sName))
+ cContacts += count_contacts(pNode, true);
+ else
+ cContacts += count_contacts(pNode, false);
+ }
+ else {
+ if (!mir_strcmpi(g_szXmlContact, sName))
+ ++cContacts;
+ }
+ }
+
+ return cContacts;
+}
+
+struct CImportContext
+{
+ CImportContext(size_t cTotalContacts) : m_cTotalContacts(cTotalContacts), m_cHandledContacts(0), m_nFlags(0) {}
+
+ size_t m_cTotalContacts;
+ size_t m_cHandledContacts;
+ UINT m_nFlags;
+};
+
+struct CContactState
+{
+ CContactState() : m_hContact(NULL), m_bNewContact(false) {}
+ MCONTACT m_hContact;
+ ICurrencyRatesProvider *m_pProvider;
+ bool m_bNewContact;
+};
+
+const TiXmlNode* find_currencyrates_module(const TiXmlNode *pXmlContact)
+{
+ for (auto *pNode : TiXmlEnum(pXmlContact))
+ if ((!mir_strcmpi(g_szXmlModule, pNode->Name())) && (!mir_strcmpi(CURRENCYRATES_MODULE_NAME, pNode->GetText())))
+ return pNode;
+
+ return nullptr;
+}
+
+TNameValue parse_setting_node(const TiXmlNode *pXmlSetting)
+{
+ assert(pXmlSetting);
+
+ const char *sName, *sValue;
+ for (auto *pNode : TiXmlEnum(pXmlSetting)) {
+ if (!mir_strcmpi(g_szXmlName, pNode->Name()))
+ sName = pNode->GetText();
+ else if (!mir_strcmpi(g_szXmlValue, pNode->Name()))
+ sValue = pNode->GetText();
+ }
+
+ return std::make_pair(sName, sValue);
+}
+
+ICurrencyRatesProvider* find_provider(const TiXmlNode *pXmlCurrencyRatesModule)
+{
+ for (auto *pNode : TiXmlFilter(pXmlCurrencyRatesModule, g_szXmlSetting)) {
+ TNameValue Item = parse_setting_node(pNode);
+ if ((!mir_strcmpi(DB_STR_CURRENCYRATE_PROVIDER, Item.first)) && Item.second)
+ return FindProvider(Utf2T(Item.second).get());
+ }
+
+ return nullptr;
+}
+
+bool get_contact_state(const TiXmlNode *pXmlContact, CContactState& cst)
+{
+ auto *pXmlCurrencyRates = find_currencyrates_module(pXmlContact);
+ if (!pXmlCurrencyRates)
+ return false;
+
+ cst.m_pProvider = find_provider(pXmlCurrencyRates);
+ if (!cst.m_pProvider)
+ return false;
+
+ cst.m_hContact = cst.m_pProvider->ImportContact(pXmlCurrencyRates);
+ return true;
+}
+
+bool import_contact(const TiXmlNode *pXmlContact, CImportContext &impctx)
+{
+ ++impctx.m_cHandledContacts;
+
+ CContactState cst;
+ if (!get_contact_state(pXmlContact, cst))
+ return false;
+
+ if (NULL == cst.m_hContact) {
+ cst.m_hContact = db_add_contact();
+ cst.m_bNewContact = true;
+ }
+ else if (impctx.m_nFlags & CURRENCYRATES_IMPORT_SKIP_EXISTING_CONTACTS)
+ return true;
+
+ if (!cst.m_hContact)
+ return false;
+
+ for (auto *pNode : TiXmlFilter(pXmlContact, g_szXmlModule))
+ if (!handle_module(cst.m_hContact, pNode))
+ return false;
+
+ if (cst.m_bNewContact) {
+ cst.m_pProvider->AddContact(cst.m_hContact);
+ cst.m_pProvider->RefreshContact(cst.m_hContact);
+ }
+ return true;
+}
+
+size_t import_contacts(const TiXmlNode *pXmlContacts, CImportContext &impctx)
+{
+ size_t cContacts = 0;
+ for (auto *pNode : TiXmlFilter(pXmlContacts, g_szXmlContact))
+ if (import_contact(pNode, impctx))
+ ++cContacts;
+
+ return cContacts;
+}
+
+size_t handle_contacts_node(const TiXmlNode *pXmlRoot, CImportContext& impctx)
+{
+ size_t cContacts = 0;
+ for (auto *pNode : TiXmlEnum(pXmlRoot)) {
+ if (!mir_strcmpi(g_szXmlContacts, pNode->Name()))
+ cContacts += import_contacts(pNode, impctx);
+ else
+ cContacts += handle_contacts_node(pNode, impctx);
+ }
+
+ return cContacts;
+}
+
+bool do_import(const TiXmlNode *pXmlRoot, UINT nFlags)
+{
+ CImportContext imctx(count_contacts(pXmlRoot, false));
+ imctx.m_cHandledContacts = 0;
+ imctx.m_nFlags = nFlags;
+
+ return (handle_contacts_node(pXmlRoot, imctx) > 0);
+}
+
+INT_PTR CurrencyRates_Import(WPARAM wp, LPARAM lp)
+{
+ tstring sFileName;
+ const char* pszFile = reinterpret_cast<const char*>(lp);
+ if (nullptr == pszFile) {
+ if (false == show_open_file_dialog(true, sFileName))
+ return -1;
+ }
+ else sFileName = currencyrates_a2t(pszFile);
+
+ FILE *in = _wfopen(sFileName.c_str(), L"rb");
+ if (in == nullptr)
+ return 1;
+
+ TiXmlDocument doc;
+ int res = doc.LoadFile(in);
+ fclose(in);
+ if (res)
+ return 1;
+
+ return (do_import(&doc, wp) ? 0 : 1);
+}
diff --git a/protocols/CurrencyRates/src/ImportExport.h b/protocols/CurrencyRates/src/ImportExport.h
new file mode 100644
index 0000000000..b07bc4c4c3
--- /dev/null
+++ b/protocols/CurrencyRates/src/ImportExport.h
@@ -0,0 +1,10 @@
+#ifndef __F86374E6_713C_4600_85FB_903A5CDF7251_IMPORT_EXPORT_H__
+#define __F86374E6_713C_4600_85FB_903A5CDF7251_IMPORT_EXPORT_H__
+
+INT_PTR CurrencyRates_Export(WPARAM wp, LPARAM lp);
+INT_PTR CurrencyRates_Import(WPARAM wp, LPARAM lp);
+
+using TNameValue = std::pair<const char*, const char*> ; // first is name,second is value
+TNameValue parse_setting_node(const TiXmlNode *pXmlSetting);
+
+#endif //__F86374E6_713C_4600_85FB_903A5CDF7251_IMPORT_EXPORT_H__
diff --git a/protocols/CurrencyRates/src/IsWithinAccuracy.h b/protocols/CurrencyRates/src/IsWithinAccuracy.h
new file mode 100644
index 0000000000..558dbb87d3
--- /dev/null
+++ b/protocols/CurrencyRates/src/IsWithinAccuracy.h
@@ -0,0 +1,15 @@
+#ifndef __C8C6FB80_D66B_4382_8FAB_E92C83F77BB8_IsWithinAcuracy_h__
+#define __C8C6FB80_D66B_4382_8FAB_E92C83F77BB8_IsWithinAcuracy_h__
+
+inline bool IsWithinAccuracy(double dValue1, double dValue2, double dAccuracy = 1e-4)
+{
+ double dDifference = dValue1 - dValue2;
+
+ if ((-dAccuracy <= dDifference) && (dDifference <= dAccuracy))
+ return true;
+ else
+ return false;
+}
+
+
+#endif //__C8C6FB80_D66B_4382_8FAB_E92C83F77BB8_IsWithinAcuracy_h__
diff --git a/protocols/CurrencyRates/src/Locale.cpp b/protocols/CurrencyRates/src/Locale.cpp
new file mode 100644
index 0000000000..501015eb21
--- /dev/null
+++ b/protocols/CurrencyRates/src/Locale.cpp
@@ -0,0 +1,59 @@
+#include "StdAfx.h"
+
+const std::locale GetSystemLocale()
+{
+ return std::locale("");
+}
+
+tstring get_int_registry_value(LPCTSTR pszValueName)
+{
+ tstring sResult;
+ HKEY hKey = nullptr;
+ LONG lResult = ::RegOpenKeyEx(HKEY_CURRENT_USER,
+ L"Control Panel\\International", 0, KEY_QUERY_VALUE, &hKey);
+ if ((ERROR_SUCCESS == lResult) && (nullptr != hKey)) {
+ DWORD dwType = 0;
+ DWORD dwSize = 0;
+ lResult = ::RegQueryValueEx(hKey, pszValueName, nullptr, &dwType, nullptr, &dwSize);
+ if ((ERROR_SUCCESS == lResult) && ((REG_SZ == dwType) || (REG_EXPAND_SZ == dwType))) {
+ std::vector<wchar_t> aBuffer(dwSize);
+ lResult = ::RegQueryValueEx(hKey, pszValueName, nullptr, nullptr, reinterpret_cast<LPBYTE>(&*aBuffer.begin()), &dwSize);
+ if (ERROR_SUCCESS == lResult)
+ std::copy(aBuffer.begin(), aBuffer.end(), std::back_inserter(sResult));
+ }
+ }
+
+ if (nullptr != hKey) {
+ lResult = ::RegCloseKey(hKey);
+ assert(ERROR_SUCCESS == lResult);
+ }
+
+ return sResult;
+}
+
+LPCTSTR date_win_2_boost(const tstring& sFrmt)
+{
+ if (sFrmt == L"dd/MM/yy")
+ return L"%d/%m/%y";
+ if (sFrmt == L"yyyy-MM-dd")
+ return L"%y-%m-%d";
+ return L"%d.%m.%y";
+}
+
+LPCTSTR time_win_2_boost(const tstring& sFrmt)
+{
+ if (sFrmt == L"H:mm" || sFrmt == L"HH:mm")
+ return L"%H:%M";
+
+ return L"%H:%M:%S";
+}
+
+LPCTSTR CurrencyRates_GetDateFormat(bool bShort)
+{
+ return date_win_2_boost(get_int_registry_value(bShort ? L"sShortDate" : L"sLongDate"));
+}
+
+LPCTSTR CurrencyRates_GetTimeFormat(bool bShort)
+{
+ return time_win_2_boost(get_int_registry_value(bShort ? L"sShortTime" : L"sTimeFormat"));
+}
diff --git a/protocols/CurrencyRates/src/Locale.h b/protocols/CurrencyRates/src/Locale.h
new file mode 100644
index 0000000000..2ef5e320cb
--- /dev/null
+++ b/protocols/CurrencyRates/src/Locale.h
@@ -0,0 +1,9 @@
+#ifndef __11f7afd0_5a66_4029_8bf3_e3c66346b349_Locale_h_
+#define __11f7afd0_5a66_4029_8bf3_e3c66346b349_Locale_h_
+
+// std::string GetLocaleInfoString(LCTYPE LCType,LCID Locale = LOCALE_USER_DEFAULT);
+const std::locale GetSystemLocale();
+LPCTSTR CurrencyRates_GetDateFormat(bool bShort);
+LPCTSTR CurrencyRates_GetTimeFormat(bool bShort);
+
+#endif //__11f7afd0_5a66_4029_8bf3_e3c66346b349_Locale_h_
diff --git a/protocols/CurrencyRates/src/Log.cpp b/protocols/CurrencyRates/src/Log.cpp
new file mode 100644
index 0000000000..c769f1a98d
--- /dev/null
+++ b/protocols/CurrencyRates/src/Log.cpp
@@ -0,0 +1,41 @@
+#include "StdAfx.h"
+
+namespace
+{
+ mir_cs g_Mutex;
+
+ tstring get_log_file_name()
+ {
+ return CreateFilePath(L"CurrencyRates.log");
+ }
+
+ bool is_log_enabled()
+ {
+#ifdef _DEBUG
+ return true;
+#else
+ return (1 == db_get_b(0, CURRENCYRATES_MODULE_NAME, DB_STR_ENABLE_LOG, false));
+#endif
+ }
+
+ void do_log(const tstring& rsFileName, const tstring& rsMsg)
+ {
+ mir_cslock lck(g_Mutex);
+ tofstream file(rsFileName.c_str(), std::ios::ate | std::ios::app);
+ if (file.good())
+ {
+ wchar_t szTime[20];
+ _tstrtime_s(szTime);
+ file << szTime << L" ================================>\n" << rsMsg << L"\n\n";
+ }
+ }
+}
+
+void LogIt(const tstring& rsMsg)
+{
+ if (is_log_enabled())
+ {
+ tstring sFileName = get_log_file_name();
+ do_log(sFileName, rsMsg);
+ }
+}
diff --git a/protocols/CurrencyRates/src/Log.h b/protocols/CurrencyRates/src/Log.h
new file mode 100644
index 0000000000..b62ae9ac52
--- /dev/null
+++ b/protocols/CurrencyRates/src/Log.h
@@ -0,0 +1,13 @@
+#ifndef __653719be_16d6_4058_8555_8aa7d5404214_OutputDlg_h__
+#define __653719be_16d6_4058_8555_8aa7d5404214_OutputDlg_h__
+
+enum ESeverity
+{
+ Info,
+ Warning,
+ Error
+};
+
+void LogIt(const tstring& rsMsg);
+
+#endif //__653719be_16d6_4058_8555_8aa7d5404214_OutputDlg_h__
diff --git a/protocols/CurrencyRates/src/ModuleInfo.cpp b/protocols/CurrencyRates/src/ModuleInfo.cpp
new file mode 100644
index 0000000000..172f2ba884
--- /dev/null
+++ b/protocols/CurrencyRates/src/ModuleInfo.cpp
@@ -0,0 +1,64 @@
+#include "StdAfx.h"
+
+static CModuleInfo mi;
+static CModuleInfo::THTMLEnginePtr g_pHTMLEngine;
+static mir_cs g_lmParsers;
+
+typedef std::map<std::string, MWindowList> THandles;
+static THandles g_ahWindowLists;
+
+MWindowList CModuleInfo::GetWindowList(const std::string& rsKey, bool bAllocateIfNonExist /*= true*/)
+{
+ MWindowList hResult = nullptr;
+ THandles::const_iterator i = g_ahWindowLists.find(rsKey);
+ if (i != g_ahWindowLists.end()) {
+ hResult = i->second;
+ }
+ else if (bAllocateIfNonExist) {
+ hResult = WindowList_Create();
+ if (hResult)
+ g_ahWindowLists.insert(std::make_pair(rsKey, hResult));
+ }
+
+ return hResult;
+}
+
+void CModuleInfo::OnMirandaShutdown()
+{
+ for (auto &p : g_ahWindowLists)
+ WindowList_Broadcast(p.second, WM_CLOSE, 0, 0);
+}
+
+CModuleInfo::THTMLEnginePtr CModuleInfo::GetHTMLEngine()
+{
+ if (!g_pHTMLEngine) {
+ mir_cslock lck(g_lmParsers);
+ if (!g_pHTMLEngine)
+ g_pHTMLEngine = THTMLEnginePtr(new CHTMLEngineMS);
+ }
+
+ return g_pHTMLEngine;
+}
+
+void CModuleInfo::SetHTMLEngine(THTMLEnginePtr pEngine)
+{
+ g_pHTMLEngine = pEngine;
+}
+
+bool CModuleInfo::Verify()
+{
+ INITCOMMONCONTROLSEX icc = { 0 };
+ icc.dwSize = sizeof(icc);
+ icc.dwICC = ICC_WIN95_CLASSES | ICC_LINK_CLASS;
+ if (FALSE == ::InitCommonControlsEx(&icc))
+ return false;
+
+ if (!g_pHTMLEngine && (false == CHTMLParserMS::IsInstalled())) {
+ CurrencyRates_MessageBox(nullptr,
+ TranslateT("Miranda could not load CurrencyRates plugin. Microsoft HTML parser is missing."),
+ MB_YESNO | MB_ICONQUESTION);
+ return false;
+ }
+
+ return true;
+}
diff --git a/protocols/CurrencyRates/src/ModuleInfo.h b/protocols/CurrencyRates/src/ModuleInfo.h
new file mode 100644
index 0000000000..39399f5c43
--- /dev/null
+++ b/protocols/CurrencyRates/src/ModuleInfo.h
@@ -0,0 +1,23 @@
+#ifndef __d0f22b66_3135_4bbe_bee5_a31ea631ce58_ModuleInfo__
+#define __d0f22b66_3135_4bbe_bee5_a31ea631ce58_ModuleInfo__
+
+class CCurrencyRatesProviders;
+class IHTMLEngine;
+
+class CModuleInfo
+{
+public:
+ typedef boost::shared_ptr<CCurrencyRatesProviders> TCurrencyRatesProvidersPtr;
+ typedef boost::shared_ptr<IHTMLEngine> THTMLEnginePtr;
+
+public:
+ static void OnMirandaShutdown(void);
+ static MWindowList GetWindowList(const std::string& rsKey, bool bAllocateIfNonExist = true);
+
+ static bool Verify();
+
+ static THTMLEnginePtr GetHTMLEngine();
+ static void SetHTMLEngine(THTMLEnginePtr pEngine);
+};
+
+#endif //__d0f22b66_3135_4bbe_bee5_a31ea631ce58_ModuleInfo__
diff --git a/protocols/CurrencyRates/src/SettingsDlg.cpp b/protocols/CurrencyRates/src/SettingsDlg.cpp
new file mode 100644
index 0000000000..0d919d76a0
--- /dev/null
+++ b/protocols/CurrencyRates/src/SettingsDlg.cpp
@@ -0,0 +1,971 @@
+#include "StdAfx.h"
+
+#define WINDOW_PREFIX_SETTINGS "Edit Settings_"
+
+const wchar_t g_pszVariableCurrencyRateName[] = L"%currencyratename%";
+const wchar_t g_pszVariableUserProfile[] = L"%miranda_userdata%";
+
+void update_file_controls(HWND hDlg)
+{
+ bool bEnable = (1 == ::IsDlgButtonChecked(hDlg, IDC_CHECK_EXTERNAL_FILE));
+
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_FILE_NAME), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_SELECT_FILE), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_BROWSE), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_LOG_FILE_FORMAT), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_LOG_FILE_FORMAT), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_LOG_FILE_DESCRIPTION), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_LOG_FILE_CONDITION), bEnable);
+}
+
+void update_history_controls(HWND hDlg)
+{
+ bool bEnable = (1 == ::IsDlgButtonChecked(hDlg, IDC_CHECK_INTERNAL_HISTORY));
+
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_HISTORY_FORMAT), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_HISTORY_FORMAT), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_HISTORY_DESCRIPTION), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_HISTORY_CONDITION), bEnable);
+}
+
+void update_popup_controls(HWND hDlg)
+{
+ bool bEnable = (1 == ::IsDlgButtonChecked(hDlg, IDC_CHECK_SHOW_POPUP));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_POPUP_FORMAT), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_SHOW_POPUP_ONLY_VALUE_CHANGED), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_POPUP_FORMAT), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_POPUP_FORMAT_DESCRIPTION), bEnable);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_POPUP_SETTINGS), bEnable);
+}
+
+bool enable_popup_controls(HWND hDlg)
+{
+ bool bIsPopupServiceEnabled = 1 == ServiceExists(MS_POPUP_ADDPOPUPW);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_SHOW_POPUP), bIsPopupServiceEnabled);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_POPUP_FORMAT), bIsPopupServiceEnabled);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_SHOW_POPUP_ONLY_VALUE_CHANGED), bIsPopupServiceEnabled);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_POPUP_FORMAT), bIsPopupServiceEnabled);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_POPUP_FORMAT_DESCRIPTION), bIsPopupServiceEnabled);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_POPUP_SETTINGS), bIsPopupServiceEnabled);
+
+ return bIsPopupServiceEnabled;
+}
+
+void update_all_controls(HWND hDlg)
+{
+ bool bIsCheckedContactSpec = (1 == ::IsDlgButtonChecked(hDlg, IDC_CHECK_CONTACT_SPECIFIC));
+ bool bIsCheckedExternal = (1 == ::IsDlgButtonChecked(hDlg, IDC_CHECK_EXTERNAL_FILE));
+
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_EXTERNAL_FILE), bIsCheckedContactSpec);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_FILE_NAME), (bIsCheckedContactSpec&&bIsCheckedExternal));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_SELECT_FILE), (bIsCheckedContactSpec&&bIsCheckedExternal));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_BROWSE), (bIsCheckedContactSpec&&bIsCheckedExternal));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_LOG_FILE_FORMAT), (bIsCheckedContactSpec&&bIsCheckedExternal));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_LOG_FILE_FORMAT), (bIsCheckedContactSpec&&bIsCheckedExternal));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_LOG_FILE_DESCRIPTION), (bIsCheckedContactSpec&&bIsCheckedExternal));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_LOG_FILE_CONDITION), (bIsCheckedContactSpec&&bIsCheckedExternal));
+
+ bool bIsCheckedHistory = (1 == ::IsDlgButtonChecked(hDlg, IDC_CHECK_INTERNAL_HISTORY));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_INTERNAL_HISTORY), bIsCheckedContactSpec);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_HISTORY_FORMAT), (bIsCheckedContactSpec&&bIsCheckedHistory));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_HISTORY_FORMAT), (bIsCheckedContactSpec&&bIsCheckedHistory));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_HISTORY_DESCRIPTION), (bIsCheckedContactSpec&&bIsCheckedHistory));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_HISTORY_CONDITION), (bIsCheckedContactSpec&&bIsCheckedHistory));
+
+ bool bIsPopupServiceEnabled = 1 == ServiceExists(MS_POPUP_ADDPOPUPW);
+ bool bIsCheckedShowPopup = (1 == ::IsDlgButtonChecked(hDlg, IDC_CHECK_SHOW_POPUP));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_SHOW_POPUP), (bIsCheckedContactSpec&&bIsPopupServiceEnabled));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_EDIT_POPUP_FORMAT), (bIsCheckedContactSpec&&bIsPopupServiceEnabled&&bIsCheckedShowPopup));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_CHECK_SHOW_POPUP_ONLY_VALUE_CHANGED), (bIsCheckedContactSpec&&bIsPopupServiceEnabled&&bIsCheckedShowPopup));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_STATIC_POPUP_FORMAT), (bIsCheckedContactSpec&&bIsPopupServiceEnabled&&bIsCheckedShowPopup));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_POPUP_FORMAT_DESCRIPTION), (bIsCheckedContactSpec&&bIsPopupServiceEnabled&&bIsCheckedShowPopup));
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BUTTON_POPUP_SETTINGS), (bIsCheckedContactSpec&&bIsPopupServiceEnabled));
+}
+
+std::vector<wchar_t> get_filter()
+{
+ std::vector<wchar_t> aFilter;
+ LPCTSTR pszFilterParts[] = { LPGENW("Log Files (*.txt,*.log)"), L"*.txt;*.log", LPGENW("All files (*.*)"), L"*.*" };
+ for (int i = 0; i < sizeof(pszFilterParts) / sizeof(pszFilterParts[0]); ++i) {
+ tstring sPart = TranslateW(pszFilterParts[i]);
+ std::copy(sPart.begin(), sPart.end(), std::back_inserter(aFilter));
+ aFilter.push_back('\0');
+
+ }
+ aFilter.push_back('\0');
+ return aFilter;
+}
+void select_log_file(HWND hDlg)
+{
+ std::vector<wchar_t> aFileBuffer(_MAX_PATH * 2, '\0');
+ LPTSTR pszFile = &*aFileBuffer.begin();
+
+ std::vector<wchar_t> aFilterBuffer = get_filter();
+ LPCTSTR pszFilter = &*aFilterBuffer.begin();
+
+ OPENFILENAME ofn = { 0 };
+ ofn.lStructSize = sizeof(OPENFILENAME);
+ ofn.hwndOwner = hDlg;
+ ofn.lpstrFile = pszFile;
+ ofn.nMaxFile = (DWORD)aFileBuffer.size();
+ ofn.lpstrFilter = pszFilter;
+ ofn.nFilterIndex = 1;
+ ofn.hInstance = g_plugin.getInst();
+ ofn.lpstrDefExt = L"log";
+ ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_EXPLORER;
+
+ BOOL b = GetOpenFileName(&ofn);
+ if (TRUE == b) {
+ SetDlgItemText(hDlg, IDC_EDIT_FILE_NAME, ofn.lpstrFile);
+ }
+}
+
+struct CSettingWindowParam
+{
+ CSettingWindowParam(MCONTACT hContact) : m_hContact(hContact), m_pPopupSettings(nullptr) {}
+ ~CSettingWindowParam() { delete m_pPopupSettings; }
+
+ MCONTACT m_hContact;
+ CPopupSettings* m_pPopupSettings;
+};
+
+inline CSettingWindowParam* get_param(HWND hWnd)
+{
+ return reinterpret_cast<CSettingWindowParam*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+}
+
+void update_popup_controls_settings(HWND hDlg)
+{
+ bool bIsColoursEnabled = 1 == IsDlgButtonChecked(hDlg, IDC_RADIO_USER_DEFINED_COLOURS);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_BGCOLOR), bIsColoursEnabled);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_TEXTCOLOR), bIsColoursEnabled);
+
+ bool bIsDelayEnabled = 1 == IsDlgButtonChecked(hDlg, IDC_DELAYCUSTOM);
+ ::EnableWindow(::GetDlgItem(hDlg, IDC_DELAY), bIsDelayEnabled);
+
+}
+
+INT_PTR CALLBACK EditPopupSettingsDlgProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ CPopupSettings* pSettings = reinterpret_cast<CPopupSettings*>(lp);
+ TranslateDialogDefault(hWnd);
+ ::SendDlgItemMessage(hWnd, IDC_BGCOLOR, CPM_SETCOLOUR, 0, pSettings->GetColourBk());
+ ::SendDlgItemMessage(hWnd, IDC_TEXTCOLOR, CPM_SETCOLOUR, 0, pSettings->GetColourText());
+
+ ::CheckDlgButton(hWnd, IDC_CHECK_DONT_USE_POPUPHISTORY, pSettings->GetHistoryFlag() ? BST_CHECKED : BST_UNCHECKED);
+
+ ::CheckRadioButton(hWnd, IDC_RADIO_DEFAULT_COLOURS, IDC_RADIO_USER_DEFINED_COLOURS, (CPopupSettings::colourDefault == pSettings->GetColourMode()) ? IDC_RADIO_DEFAULT_COLOURS : IDC_RADIO_USER_DEFINED_COLOURS);
+ UINT n;
+ switch (pSettings->GetDelayMode()) {
+ default:
+ assert(!"Unknown delay mode. Please, fix it");
+ case CPopupSettings::delayFromPopup:
+ n = IDC_DELAYFROMPU;
+ break;
+ case CPopupSettings::delayCustom:
+ n = IDC_DELAYCUSTOM;
+ break;
+ case CPopupSettings::delayPermanent:
+ n = IDC_DELAYPERMANENT;
+ break;
+ }
+ ::CheckRadioButton(hWnd, IDC_DELAYFROMPU, IDC_DELAYPERMANENT, n);
+
+ ::SetDlgItemInt(hWnd, IDC_DELAY, pSettings->GetDelayTimeout(), FALSE);
+
+ update_popup_controls_settings(hWnd);
+
+ ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pSettings));
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wp)) {
+ case IDC_RADIO_DEFAULT_COLOURS:
+ case IDC_RADIO_USER_DEFINED_COLOURS:
+ case IDC_DELAYFROMPU:
+ case IDC_DELAYCUSTOM:
+ case IDC_DELAYPERMANENT:
+ update_popup_controls_settings(hWnd);
+ break;
+
+ case IDCANCEL:
+ ::EndDialog(hWnd, IDCANCEL);
+ break;
+
+ case IDOK:
+ {
+ CPopupSettings* pSettings = reinterpret_cast<CPopupSettings*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+
+ bool bError = false;
+ BOOL bOk = FALSE;
+ UINT nDelay = ::GetDlgItemInt(hWnd, IDC_DELAY, &bOk, FALSE);
+ CPopupSettings::EDelayMode nModeDelay = pSettings->GetDelayMode();
+ if (1 == ::IsDlgButtonChecked(hWnd, IDC_DELAYFROMPU))
+ nModeDelay = CPopupSettings::delayFromPopup;
+ else if (1 == ::IsDlgButtonChecked(hWnd, IDC_DELAYCUSTOM)) {
+ if (TRUE == bOk)
+ nModeDelay = CPopupSettings::delayCustom;
+ else {
+ prepare_edit_ctrl_for_error(::GetDlgItem(hWnd, IDC_DELAY));
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter integer value"), MB_OK | MB_ICONERROR);
+ bError = true;
+ }
+ }
+ else if (1 == ::IsDlgButtonChecked(hWnd, IDC_DELAYPERMANENT))
+ nModeDelay = CPopupSettings::delayPermanent;
+
+ if (false == bError) {
+ pSettings->SetDelayMode(nModeDelay);
+ if (TRUE == bOk)
+ pSettings->SetDelayTimeout(nDelay);
+
+ pSettings->SetHistoryFlag((1 == IsDlgButtonChecked(hWnd, IDC_CHECK_DONT_USE_POPUPHISTORY)));
+
+ if (1 == ::IsDlgButtonChecked(hWnd, IDC_RADIO_DEFAULT_COLOURS))
+ pSettings->SetColourMode(CPopupSettings::colourDefault);
+ else if (1 == ::IsDlgButtonChecked(hWnd, IDC_RADIO_USER_DEFINED_COLOURS))
+ pSettings->SetColourMode(CPopupSettings::colourUserDefined);
+
+ pSettings->SetColourBk(static_cast<COLORREF>(::SendDlgItemMessage(hWnd, IDC_BGCOLOR, CPM_GETCOLOUR, 0, 0)));
+ pSettings->SetColourText(static_cast<COLORREF>(::SendDlgItemMessage(hWnd, IDC_TEXTCOLOR, CPM_GETCOLOUR, 0, 0)));
+
+ ::EndDialog(hWnd, IDOK);
+ }
+ }
+ break;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+INT_PTR CALLBACK EditSettingsPerContactDlgProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hWnd);
+ {
+ MCONTACT hContact = MCONTACT(lp);
+
+ MWindowList hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX_SETTINGS, false);
+ assert(hWL);
+ WindowList_Add(hWL, hWnd, hContact);
+
+ tstring sName = GetContactName(hContact);
+ ::SetDlgItemText(hWnd, IDC_EDIT_NAME, sName.c_str());
+
+ BYTE bUseContactSpecific = db_get_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CONTACT_SPEC_SETTINGS, 0);
+ ::CheckDlgButton(hWnd, IDC_CHECK_CONTACT_SPECIFIC, bUseContactSpecific ? BST_CHECKED : BST_UNCHECKED);
+
+ auto pProvider = GetContactProviderPtr(hContact);
+ CAdvProviderSettings setGlobal(pProvider);
+ // log to history
+ WORD dwLogMode = db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG, setGlobal.GetLogMode());
+ UINT nCheck = (dwLogMode&lmInternalHistory) ? 1 : 0;
+ ::CheckDlgButton(hWnd, IDC_CHECK_INTERNAL_HISTORY, nCheck ? BST_CHECKED : BST_UNCHECKED);
+
+ tstring sHistoryFrmt = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_HISTORY, setGlobal.GetHistoryFormat().c_str());
+ ::SetDlgItemText(hWnd, IDC_EDIT_HISTORY_FORMAT, sHistoryFrmt.c_str());
+
+ WORD wOnlyIfChanged = db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_HISTORY_CONDITION, setGlobal.GetHistoryOnlyChangedFlag());
+ ::CheckDlgButton(hWnd, IDC_CHECK_HISTORY_CONDITION, (1 == wOnlyIfChanged) ? BST_CHECKED : BST_UNCHECKED);
+
+ // log to file
+ nCheck = (dwLogMode&lmExternalFile) ? 1 : 0;
+ ::CheckDlgButton(hWnd, IDC_CHECK_EXTERNAL_FILE, nCheck ? BST_CHECKED : BST_UNCHECKED);
+
+ tstring sLogFileName = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG_FILE);
+ if (true == sLogFileName.empty()) {
+ sLogFileName = GenerateLogFileName(setGlobal.GetLogFileName(), CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL), glfnResolveCurrencyRateName);
+ }
+ ::SetDlgItemText(hWnd, IDC_EDIT_FILE_NAME, sLogFileName.c_str());
+
+ tstring sLogFileFrmt = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_LOG_FILE, setGlobal.GetLogFormat().c_str());
+ ::SetDlgItemText(hWnd, IDC_EDIT_LOG_FILE_FORMAT, sLogFileFrmt.c_str());
+
+ wOnlyIfChanged = db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG_FILE_CONDITION, setGlobal.GetLogOnlyChangedFlag());
+ ::CheckDlgButton(hWnd, IDC_CHECK_LOG_FILE_CONDITION, (1 == wOnlyIfChanged) ? BST_CHECKED : BST_UNCHECKED);
+
+ // popup
+ nCheck = (dwLogMode&lmPopup) ? 1 : 0;
+ ::CheckDlgButton(hWnd, IDC_CHECK_SHOW_POPUP, nCheck ? BST_CHECKED : BST_UNCHECKED);
+ tstring sPopupFrmt = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_POPUP, setGlobal.GetPopupFormat().c_str());
+ ::SetDlgItemText(hWnd, IDC_EDIT_POPUP_FORMAT, sPopupFrmt.c_str());
+ bool bOnlyIfChanged = 1 == db_get_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_CONDITION, setGlobal.GetShowPopupIfValueChangedFlag());
+ ::CheckDlgButton(hWnd, IDC_CHECK_SHOW_POPUP_ONLY_VALUE_CHANGED, (bOnlyIfChanged) ? BST_CHECKED : BST_UNCHECKED);
+
+ update_all_controls(hWnd);
+
+ CSettingWindowParam* pParam = new CSettingWindowParam(hContact);
+ ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pParam));
+ Utils_RestoreWindowPositionNoSize(hWnd, hContact, CURRENCYRATES_MODULE_NAME, WINDOW_PREFIX_SETTINGS);
+ ::ShowWindow(hWnd, SW_SHOW);
+ }
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wp)) {
+ case IDC_BUTTON_HISTORY_DESCRIPTION:
+ case IDC_BUTTON_LOG_FILE_DESCRIPTION:
+ case IDC_BUTTON_POPUP_FORMAT_DESCRIPTION:
+ if (BN_CLICKED == HIWORD(wp))
+ show_variable_list(hWnd, GetContactProviderPtr(get_param(hWnd)->m_hContact));
+ break;
+
+ case IDC_CHECK_CONTACT_SPECIFIC:
+ if (BN_CLICKED == HIWORD(wp))
+ update_all_controls(hWnd);
+ break;
+ case IDC_CHECK_EXTERNAL_FILE:
+ if (BN_CLICKED == HIWORD(wp))
+ update_file_controls(hWnd);
+ break;
+ case IDC_CHECK_INTERNAL_HISTORY:
+ if (BN_CLICKED == HIWORD(wp))
+ update_history_controls(hWnd);
+ break;
+ case IDC_CHECK_SHOW_POPUP:
+ if (BN_CLICKED == HIWORD(wp))
+ update_popup_controls(hWnd);
+ break;
+ case IDC_BUTTON_BROWSE:
+ if (BN_CLICKED == HIWORD(wp))
+ select_log_file(hWnd);
+ break;
+
+ case IDC_BUTTON_POPUP_SETTINGS:
+ if (BN_CLICKED == HIWORD(wp)) {
+ CSettingWindowParam* pParam = get_param(hWnd);
+ if (!pParam->m_pPopupSettings) {
+ pParam->m_pPopupSettings = new CPopupSettings();
+ pParam->m_pPopupSettings->InitForContact(pParam->m_hContact);
+ }
+
+ DialogBoxParam(g_plugin.getInst(),
+ MAKEINTRESOURCE(IDD_DIALOG_POPUP),
+ hWnd,
+ EditPopupSettingsDlgProc, reinterpret_cast<LPARAM>(pParam->m_pPopupSettings));
+ }
+ break;
+
+ case IDOK:
+ {
+ CSettingWindowParam* pParam = get_param(hWnd);
+ MCONTACT hContact = pParam->m_hContact;
+
+ bool bUseContactSpec = 1 == ::IsDlgButtonChecked(hWnd, IDC_CHECK_CONTACT_SPECIFIC);
+
+ WORD nLogMode = lmDisabled;
+ UINT nCheck = ::IsDlgButtonChecked(hWnd, IDC_CHECK_EXTERNAL_FILE);
+ if (1 == nCheck)
+ nLogMode |= lmExternalFile;
+
+ nCheck = ::IsDlgButtonChecked(hWnd, IDC_CHECK_INTERNAL_HISTORY);
+ if (1 == nCheck)
+ nLogMode |= lmInternalHistory;
+
+ nCheck = ::IsDlgButtonChecked(hWnd, IDC_CHECK_SHOW_POPUP);
+ if (1 == nCheck)
+ nLogMode |= lmPopup;
+
+ bool bOk = true;
+ HWND hwndLogFile = ::GetDlgItem(hWnd, IDC_EDIT_FILE_NAME);
+ HWND hwndLogFileFrmt = ::GetDlgItem(hWnd, IDC_EDIT_LOG_FILE_FORMAT);
+ HWND hwndHistoryFrmt = ::GetDlgItem(hWnd, IDC_EDIT_HISTORY_FORMAT);
+ tstring sLogFile = get_window_text(hwndLogFile);
+ tstring sLogFileFormat = get_window_text(hwndLogFileFrmt);
+ tstring sHistoryFormat = get_window_text(hwndHistoryFrmt);
+ if ((nLogMode&lmExternalFile)) {
+ if (true == sLogFile.empty()) {
+ prepare_edit_ctrl_for_error(hwndLogFile);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter log file name."), MB_OK | MB_ICONERROR);
+ bOk = false;
+ }
+ else if (true == sLogFileFormat.empty()) {
+ prepare_edit_ctrl_for_error(hwndLogFileFrmt);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter log file format."), MB_OK | MB_ICONERROR);
+ bOk = false;
+ }
+ }
+
+ if ((true == bOk) && (nLogMode&lmInternalHistory) && (true == sHistoryFormat.empty())) {
+ prepare_edit_ctrl_for_error(hwndHistoryFrmt);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter history format."), MB_OK | MB_ICONERROR);
+ bOk = false;
+ }
+
+ HWND hwndPopupFrmt = ::GetDlgItem(hWnd, IDC_EDIT_POPUP_FORMAT);
+ tstring sPopupFormat = get_window_text(hwndPopupFrmt);
+ if ((true == bOk) && (nLogMode&lmPopup) && (true == sPopupFormat.empty())) {
+ prepare_edit_ctrl_for_error(hwndPopupFrmt);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter popup window format."), MB_OK | MB_ICONERROR);
+ bOk = false;
+ }
+
+ if (true == bOk) {
+ UINT nIfChangedHistory = IsDlgButtonChecked(hWnd, IDC_CHECK_HISTORY_CONDITION);
+ UINT nIfChangedFile = IsDlgButtonChecked(hWnd, IDC_CHECK_LOG_FILE_CONDITION);
+ bool bIfChangedPopup = (1 == IsDlgButtonChecked(hWnd, IDC_CHECK_SHOW_POPUP_ONLY_VALUE_CHANGED));
+
+ db_set_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CONTACT_SPEC_SETTINGS, bUseContactSpec);
+ db_set_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG, nLogMode);
+ db_set_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG_FILE_CONDITION, nIfChangedFile);
+ db_set_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_HISTORY_CONDITION, nIfChangedHistory);
+ db_set_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_CONDITION, bIfChangedPopup);
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG_FILE, sLogFile.c_str());
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_LOG_FILE, sLogFileFormat.c_str());
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_HISTORY, sHistoryFormat.c_str());
+ db_set_ws(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_FORMAT_POPUP, sPopupFormat.c_str());
+
+ if (pParam->m_pPopupSettings) {
+ pParam->m_pPopupSettings->SaveForContact(hContact);
+ }
+
+ ::DestroyWindow(hWnd);
+ }
+ }
+ break;
+
+ case IDCANCEL:
+ DestroyWindow(hWnd);
+ break;
+ }
+ break;
+
+ case WM_CLOSE:
+ DestroyWindow(hWnd);
+ break;
+
+ case WM_DESTROY:
+ CSettingWindowParam* pParam = get_param(hWnd);
+ SetWindowLongPtr(hWnd, GWLP_USERDATA, 0);
+
+ MWindowList hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX_SETTINGS, false);
+ assert(hWL);
+ WindowList_Remove(hWL, hWnd);
+ Utils_SaveWindowPosition(hWnd, pParam->m_hContact, CURRENCYRATES_MODULE_NAME, WINDOW_PREFIX_SETTINGS);
+ delete pParam;
+ break;
+ }
+
+ return FALSE;
+}
+
+void ShowSettingsDlg(MCONTACT hContact)
+{
+ MWindowList hWL = CModuleInfo::GetWindowList(WINDOW_PREFIX_SETTINGS, true);
+ assert(hWL);
+ HWND hWnd = WindowList_Find(hWL, hContact);
+ if (nullptr != hWnd) {
+ SetForegroundWindow(hWnd);
+ SetFocus(hWnd);
+ }
+ else CreateDialogParam(g_plugin.getInst(), MAKEINTRESOURCE(IDD_CONTACT_SETTINGS), nullptr, EditSettingsPerContactDlgProc, LPARAM(hContact));
+}
+
+//////////////////////////////////////////////////////////////////////////
+
+INT_PTR CALLBACK EditSettingsPerProviderDlgProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ {
+ TranslateDialogDefault(hWnd);
+ CAdvProviderSettings* pAdvSettings = reinterpret_cast<CAdvProviderSettings*>(lp);
+
+ ::SetDlgItemText(hWnd, IDC_EDIT_NAME, pAdvSettings->GetProviderPtr()->GetInfo().m_sName.c_str());
+
+ // log to history
+ WORD dwLogMode = pAdvSettings->GetLogMode();
+ UINT nCheck = (dwLogMode&lmInternalHistory) ? 1 : 0;
+ ::CheckDlgButton(hWnd, IDC_CHECK_INTERNAL_HISTORY, nCheck ? BST_CHECKED : BST_UNCHECKED);
+ ::SetDlgItemText(hWnd, IDC_EDIT_HISTORY_FORMAT, pAdvSettings->GetHistoryFormat().c_str());
+ ::CheckDlgButton(hWnd, IDC_CHECK_HISTORY_CONDITION, (pAdvSettings->GetHistoryOnlyChangedFlag()) ? BST_CHECKED : BST_UNCHECKED);
+
+ // log to file
+ nCheck = (dwLogMode&lmExternalFile) ? 1 : 0;
+ ::CheckDlgButton(hWnd, IDC_CHECK_EXTERNAL_FILE, nCheck ? BST_CHECKED : BST_UNCHECKED);
+ ::SetDlgItemText(hWnd, IDC_EDIT_FILE_NAME, pAdvSettings->GetLogFileName().c_str());
+ ::SetDlgItemText(hWnd, IDC_EDIT_LOG_FILE_FORMAT, pAdvSettings->GetLogFormat().c_str());
+ ::CheckDlgButton(hWnd, IDC_CHECK_LOG_FILE_CONDITION, (pAdvSettings->GetLogOnlyChangedFlag()) ? BST_CHECKED : BST_UNCHECKED);
+
+ update_file_controls(hWnd);
+ update_history_controls(hWnd);
+
+ // popup
+ nCheck = (dwLogMode&lmPopup) ? 1 : 0;
+ ::CheckDlgButton(hWnd, IDC_CHECK_SHOW_POPUP, nCheck ? BST_CHECKED : BST_UNCHECKED);
+ ::SetDlgItemText(hWnd, IDC_EDIT_POPUP_FORMAT, pAdvSettings->GetPopupFormat().c_str());
+ ::CheckDlgButton(hWnd, IDC_CHECK_SHOW_POPUP_ONLY_VALUE_CHANGED, (pAdvSettings->GetShowPopupIfValueChangedFlag()) ? BST_CHECKED : BST_UNCHECKED);
+
+ if (true == enable_popup_controls(hWnd))
+ update_popup_controls(hWnd);
+
+ ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pAdvSettings));
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wp)) {
+ case IDOK:
+ {
+ WORD nLogMode = lmDisabled;
+ UINT nCheck = ::IsDlgButtonChecked(hWnd, IDC_CHECK_EXTERNAL_FILE);
+ if (1 == nCheck) {
+ nLogMode |= lmExternalFile;
+ }
+
+ nCheck = ::IsDlgButtonChecked(hWnd, IDC_CHECK_INTERNAL_HISTORY);
+ if (1 == nCheck) {
+ nLogMode |= lmInternalHistory;
+ }
+
+ nCheck = ::IsDlgButtonChecked(hWnd, IDC_CHECK_SHOW_POPUP);
+ if (1 == nCheck) {
+ nLogMode |= lmPopup;
+ }
+
+ bool bOk = true;
+ HWND hwndLogFile = ::GetDlgItem(hWnd, IDC_EDIT_FILE_NAME);
+ HWND hwndLogFileFrmt = ::GetDlgItem(hWnd, IDC_EDIT_LOG_FILE_FORMAT);
+
+ tstring sLogFile = get_window_text(hwndLogFile);
+ tstring sLogFileFormat = get_window_text(hwndLogFileFrmt);
+
+ if ((nLogMode&lmExternalFile)) {
+ if (true == sLogFile.empty()) {
+ prepare_edit_ctrl_for_error(hwndLogFile);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter log file name."), MB_OK | MB_ICONERROR);
+ bOk = false;
+ }
+ else if (true == sLogFileFormat.empty()) {
+ prepare_edit_ctrl_for_error(hwndLogFileFrmt);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter log file format."), MB_OK | MB_ICONERROR);
+ bOk = false;
+ }
+ }
+
+ HWND hwndHistoryFrmt = ::GetDlgItem(hWnd, IDC_EDIT_HISTORY_FORMAT);
+ tstring sHistoryFormat = get_window_text(hwndHistoryFrmt);
+ if ((true == bOk) && (nLogMode&lmInternalHistory) && (true == sHistoryFormat.empty())) {
+ prepare_edit_ctrl_for_error(hwndHistoryFrmt);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter history format."), MB_OK | MB_ICONERROR);
+ bOk = false;
+ }
+
+ HWND hwndPopupFrmt = ::GetDlgItem(hWnd, IDC_EDIT_POPUP_FORMAT);
+ tstring sPopupFormat = get_window_text(hwndPopupFrmt);
+ if ((true == bOk) && (nLogMode&lmPopup) && (true == sPopupFormat.empty())) {
+ prepare_edit_ctrl_for_error(hwndPopupFrmt);
+ CurrencyRates_MessageBox(hWnd, TranslateT("Enter popup window format."), MB_OK | MB_ICONERROR);
+ bOk = false;
+ }
+
+ if (true == bOk) {
+ CAdvProviderSettings* pAdvSettings = reinterpret_cast<CAdvProviderSettings*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+
+ pAdvSettings->SetLogMode(nLogMode);
+ pAdvSettings->SetHistoryOnlyChangedFlag(1 == IsDlgButtonChecked(hWnd, IDC_CHECK_HISTORY_CONDITION));
+ pAdvSettings->SetLogOnlyChangedFlag(1 == IsDlgButtonChecked(hWnd, IDC_CHECK_LOG_FILE_CONDITION));
+ pAdvSettings->SetShowPopupIfValueChangedFlag(1 == IsDlgButtonChecked(hWnd, IDC_CHECK_SHOW_POPUP_ONLY_VALUE_CHANGED));
+ pAdvSettings->SetLogFileName(sLogFile);
+ pAdvSettings->SetLogFormat(sLogFileFormat);
+ pAdvSettings->SetHistoryFormat(sHistoryFormat);
+ pAdvSettings->SetPopupFormat(sPopupFormat);
+
+ ::EndDialog(hWnd, IDOK);
+ }
+ }
+ break;
+
+ case IDCANCEL:
+ ::EndDialog(hWnd, IDCANCEL);
+ break;
+
+ case IDC_BUTTON_HISTORY_DESCRIPTION:
+ case IDC_BUTTON_LOG_FILE_DESCRIPTION:
+ case IDC_BUTTON_POPUP_FORMAT_DESCRIPTION:
+ if (BN_CLICKED == HIWORD(wp)) {
+ const CAdvProviderSettings* pAdvSettings = reinterpret_cast<CAdvProviderSettings*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+ show_variable_list(hWnd, pAdvSettings->GetProviderPtr());
+ }
+ break;
+
+ case IDC_CHECK_EXTERNAL_FILE:
+ if (BN_CLICKED == HIWORD(wp))
+ update_file_controls(hWnd);
+ break;
+
+ case IDC_CHECK_INTERNAL_HISTORY:
+ if (BN_CLICKED == HIWORD(wp))
+ update_history_controls(hWnd);
+ break;
+
+ case IDC_CHECK_SHOW_POPUP:
+ if (BN_CLICKED == HIWORD(wp))
+ update_popup_controls(hWnd);
+ break;
+
+ case IDC_BUTTON_BROWSE:
+ if (BN_CLICKED == HIWORD(wp))
+ select_log_file(hWnd);
+ break;
+
+ case IDC_BUTTON_POPUP_SETTINGS:
+ const CAdvProviderSettings* pAdvSettings = reinterpret_cast<CAdvProviderSettings*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
+ DialogBoxParam(g_plugin.getInst(),
+ MAKEINTRESOURCE(IDD_DIALOG_POPUP),
+ hWnd,
+ EditPopupSettingsDlgProc, reinterpret_cast<LPARAM>(pAdvSettings->GetPopupSettingsPtr()));
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+CAdvProviderSettings::CAdvProviderSettings(const ICurrencyRatesProvider *pCurrencyRatesProvider)
+ : m_pCurrencyRatesProvider(pCurrencyRatesProvider),
+ m_wLogMode(lmDisabled),
+ m_bIsOnlyChangedHistory(false),
+ m_bIsOnlyChangedLogFile(false),
+ m_bShowPopupIfValueChanged(false),
+ m_pPopupSettings(nullptr)
+{
+ assert(m_pCurrencyRatesProvider);
+
+ m_wLogMode = db_get_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_LogMode, static_cast<WORD>(lmDisabled));
+ m_sFormatHistory = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_HistoryFormat, DB_DEF_HistoryFormat);
+ m_bIsOnlyChangedHistory = 1 == db_get_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_HistoryCondition, 0);
+
+ m_sLogFileName = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_LogFile);
+ if (true == m_sLogFileName.empty()) {
+ m_sLogFileName = g_pszVariableUserProfile;
+ m_sLogFileName += L"\\CurrencyRates\\";
+ m_sLogFileName += g_pszVariableCurrencyRateName;
+ m_sLogFileName += L".log";
+ }
+
+ m_sFormatLogFile = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_LogFormat, DB_DEF_LogFormat);
+ m_bIsOnlyChangedLogFile = (1 == db_get_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_LogCondition, 0));
+
+ m_sPopupFormat = CurrencyRates_DBGetStringW(NULL, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupFormat, DB_DEF_PopupFormat);
+ m_bShowPopupIfValueChanged = (1 == db_get_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupCondition, 0));
+}
+
+CAdvProviderSettings::~CAdvProviderSettings()
+{
+ delete m_pPopupSettings;
+}
+
+const ICurrencyRatesProvider* CAdvProviderSettings::GetProviderPtr() const
+{
+ return m_pCurrencyRatesProvider;
+}
+
+void CAdvProviderSettings::SaveToDb() const
+{
+ db_set_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_LogMode, m_wLogMode);
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_KEY_HistoryFormat, m_sFormatHistory.c_str());
+ db_set_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_HistoryCondition, m_bIsOnlyChangedHistory);
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_KEY_LogFile, m_sLogFileName.c_str());
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_KEY_LogFormat, m_sFormatLogFile.c_str());
+ db_set_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_LogCondition, m_bIsOnlyChangedLogFile);
+ db_set_ws(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupFormat, m_sPopupFormat.c_str());
+ db_set_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupCondition, m_bShowPopupIfValueChanged);
+
+ if (nullptr != m_pPopupSettings) {
+ db_set_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupColourMode, static_cast<BYTE>(m_pPopupSettings->GetColourMode()));
+ db_set_dw(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupBkColour, m_pPopupSettings->GetColourBk());
+ db_set_dw(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupTextColour, m_pPopupSettings->GetColourText());
+ db_set_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupDelayMode, static_cast<BYTE>(m_pPopupSettings->GetDelayMode()));
+ db_set_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupDelayTimeout, m_pPopupSettings->GetDelayTimeout());
+ db_set_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupHistoryFlag, m_pPopupSettings->GetHistoryFlag());
+ }
+}
+
+WORD CAdvProviderSettings::GetLogMode() const
+{
+ return m_wLogMode;
+}
+
+void CAdvProviderSettings::SetLogMode(WORD wMode)
+{
+ m_wLogMode = wMode;
+}
+
+tstring CAdvProviderSettings::GetHistoryFormat() const
+{
+ return m_sFormatHistory;
+}
+
+void CAdvProviderSettings::SetHistoryFormat(const tstring& rsFormat)
+{
+ m_sFormatHistory = rsFormat;
+}
+
+bool CAdvProviderSettings::GetHistoryOnlyChangedFlag() const
+{
+ return m_bIsOnlyChangedHistory;
+}
+
+void CAdvProviderSettings::SetHistoryOnlyChangedFlag(bool bMode)
+{
+ m_bIsOnlyChangedHistory = bMode;
+}
+
+tstring CAdvProviderSettings::GetLogFileName() const
+{
+ return m_sLogFileName;
+}
+
+void CAdvProviderSettings::SetLogFileName(const tstring& rsFile)
+{
+ m_sLogFileName = rsFile;
+}
+
+tstring CAdvProviderSettings::GetLogFormat() const
+{
+ return m_sFormatLogFile;
+}
+
+void CAdvProviderSettings::SetLogFormat(const tstring& rsFormat)
+{
+ m_sFormatLogFile = rsFormat;
+}
+
+bool CAdvProviderSettings::GetLogOnlyChangedFlag() const
+{
+ return m_bIsOnlyChangedLogFile;
+}
+
+void CAdvProviderSettings::SetLogOnlyChangedFlag(bool bMode)
+{
+ m_bIsOnlyChangedLogFile = bMode;
+}
+
+const tstring& CAdvProviderSettings::GetPopupFormat() const
+{
+ return m_sPopupFormat;
+}
+
+void CAdvProviderSettings::SetPopupFormat(const tstring& val)
+{
+ m_sPopupFormat = val;
+}
+
+bool CAdvProviderSettings::GetShowPopupIfValueChangedFlag() const
+{
+ return m_bShowPopupIfValueChanged;
+}
+
+void CAdvProviderSettings::SetShowPopupIfValueChangedFlag(bool val)
+{
+ m_bShowPopupIfValueChanged = val;
+}
+
+CPopupSettings* CAdvProviderSettings::GetPopupSettingsPtr() const
+{
+ if (nullptr == m_pPopupSettings)
+ m_pPopupSettings = new CPopupSettings();
+
+ return m_pPopupSettings;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// class CPopupSettings
+
+CPopupSettings::CPopupSettings() :
+ m_modeColour(colourDefault),
+ m_modeDelay(delayFromPopup),
+ m_rgbBkg(GetDefColourBk()),
+ m_rgbText(GetDefColourText()),
+ m_wDelay(3),
+ m_bUseHistory(false)
+
+{
+ BYTE m = db_get_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupColourMode, static_cast<BYTE>(m_modeColour));
+ if (m >= colourDefault && m <= colourUserDefined)
+ m_modeColour = static_cast<EColourMode>(m);
+
+ m_rgbBkg = db_get_dw(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupBkColour, m_rgbBkg);
+ m_rgbText = db_get_dw(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupTextColour, m_rgbText);
+
+ m = db_get_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupDelayMode, static_cast<BYTE>(m_modeDelay));
+ if (m >= delayFromPopup && m <= delayPermanent) {
+ m_modeDelay = static_cast<EDelayMode>(m);
+ }
+ m_wDelay = db_get_w(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupDelayTimeout, m_wDelay);
+ m_bUseHistory = (1 == db_get_b(0, CURRENCYRATES_MODULE_NAME, DB_KEY_PopupHistoryFlag, m_bUseHistory));
+}
+
+/*static */
+COLORREF CPopupSettings::GetDefColourBk()
+{
+ return ::GetSysColor(COLOR_BTNFACE);
+}
+
+/*static */
+COLORREF CPopupSettings::GetDefColourText()
+{
+ return ::GetSysColor(COLOR_BTNTEXT);
+}
+
+void CPopupSettings::InitForContact(MCONTACT hContact)
+{
+ BYTE m = db_get_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_COLOUR_MODE, static_cast<BYTE>(m_modeColour));
+ if (m >= CPopupSettings::colourDefault && m <= CPopupSettings::colourUserDefined) {
+ m_modeColour = static_cast<CPopupSettings::EColourMode>(m);
+ }
+
+ m_rgbBkg = db_get_dw(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_COLOUR_BK, m_rgbBkg);
+ m_rgbText = db_get_dw(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_COLOUR_TEXT, m_rgbText);
+
+ m = db_get_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_DELAY_MODE, static_cast<BYTE>(m_modeDelay));
+ if (m >= CPopupSettings::delayFromPopup && m <= CPopupSettings::delayPermanent) {
+ m_modeDelay = static_cast<CPopupSettings::EDelayMode>(m);
+ }
+ m_wDelay = db_get_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_DELAY_TIMEOUT, m_wDelay);
+ m_bUseHistory = 1 == db_get_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_HISTORY_FLAG, m_bUseHistory);
+}
+
+void CPopupSettings::SaveForContact(MCONTACT hContact) const
+{
+ db_set_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_COLOUR_MODE, static_cast<BYTE>(m_modeColour));
+ db_set_dw(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_COLOUR_BK, m_rgbBkg);
+ db_set_dw(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_COLOUR_TEXT, m_rgbText);
+ db_set_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_DELAY_MODE, static_cast<BYTE>(m_modeDelay));
+ db_set_w(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_DELAY_TIMEOUT, m_wDelay);
+ db_set_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_POPUP_HISTORY_FLAG, m_bUseHistory);
+}
+
+CPopupSettings::EColourMode CPopupSettings::GetColourMode() const
+{
+ return m_modeColour;
+}
+
+void CPopupSettings::SetColourMode(EColourMode nMode)
+{
+ m_modeColour = nMode;
+}
+
+COLORREF CPopupSettings::GetColourBk() const
+{
+ return m_rgbBkg;
+}
+
+void CPopupSettings::SetColourBk(COLORREF rgb)
+{
+ m_rgbBkg = rgb;
+}
+
+COLORREF CPopupSettings::GetColourText() const
+{
+ return m_rgbText;
+}
+
+void CPopupSettings::SetColourText(COLORREF rgb)
+{
+ m_rgbText = rgb;
+}
+
+CPopupSettings::EDelayMode CPopupSettings::GetDelayMode() const
+{
+ return m_modeDelay;
+}
+
+void CPopupSettings::SetDelayMode(EDelayMode nMode)
+{
+ m_modeDelay = nMode;
+}
+
+WORD CPopupSettings::GetDelayTimeout() const
+{
+ return m_wDelay;
+}
+
+void CPopupSettings::SetDelayTimeout(WORD delay)
+{
+ m_wDelay = delay;
+}
+
+bool CPopupSettings::GetHistoryFlag() const
+{
+ return m_bUseHistory;
+}
+
+void CPopupSettings::SetHistoryFlag(bool flag)
+{
+ m_bUseHistory = flag;
+}
+
+bool ShowSettingsDlg(HWND hWndParent, CAdvProviderSettings* pAdvSettings)
+{
+ assert(pAdvSettings);
+
+ return (IDOK == DialogBoxParam(g_plugin.getInst(),
+ MAKEINTRESOURCE(IDD_PROVIDER_ADV_SETTINGS),
+ hWndParent,
+ EditSettingsPerProviderDlgProc,
+ reinterpret_cast<LPARAM>(pAdvSettings)));
+}
+
+tstring GenerateLogFileName(const tstring &rsLogFilePattern, const tstring &rsCurrencyRateSymbol, int nFlags)
+{
+ tstring sPath = rsLogFilePattern;
+ if (nFlags&glfnResolveCurrencyRateName) {
+ assert(false == rsCurrencyRateSymbol.empty());
+
+ tstring::size_type n = sPath.find(g_pszVariableCurrencyRateName);
+ if (tstring::npos != n) {
+ tstring s = rsCurrencyRateSymbol;
+ FixInvalidChars(s);
+ sPath.replace(n, _countof(g_pszVariableCurrencyRateName)-1, s.c_str());
+ }
+ }
+
+ if (nFlags & glfnResolveUserProfile) {
+ wchar_t *ptszParsedName = Utils_ReplaceVarsW(sPath.c_str());
+ if (ptszParsedName) {
+ sPath = ptszParsedName;
+ mir_free(ptszParsedName);
+ }
+ }
+
+ return sPath;
+}
+
+tstring GetContactLogFileName(MCONTACT hContact)
+{
+ tstring result;
+
+ auto pProvider = GetContactProviderPtr(hContact);
+ if (pProvider) {
+ tstring sPattern;
+ bool bUseContactSpecific = (db_get_b(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CONTACT_SPEC_SETTINGS, 0) > 0);
+ if (bUseContactSpecific)
+ sPattern = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_LOG_FILE);
+ else {
+ CAdvProviderSettings global_settings(pProvider);
+ sPattern = global_settings.GetLogFileName();
+ }
+
+ result = GenerateLogFileName(sPattern, CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL));
+ }
+
+ return result;
+}
+
+tstring GetContactName(MCONTACT hContact)
+{
+ tstring sDescription = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_DESCRIPTION);
+ if (sDescription.empty())
+ sDescription = CurrencyRates_DBGetStringW(hContact, CURRENCYRATES_MODULE_NAME, DB_STR_CURRENCYRATE_SYMBOL);
+
+ return sDescription;
+}
diff --git a/protocols/CurrencyRates/src/SettingsDlg.h b/protocols/CurrencyRates/src/SettingsDlg.h
new file mode 100644
index 0000000000..b6b49b97bd
--- /dev/null
+++ b/protocols/CurrencyRates/src/SettingsDlg.h
@@ -0,0 +1,116 @@
+#ifndef __E211E4D9_383C_43BE_A787_7EF1D585B90D_SettingsDlg_h__
+#define __E211E4D9_383C_43BE_A787_7EF1D585B90D_SettingsDlg_h__
+
+class CPopupSettings
+{
+public:
+ enum EColourMode
+ {
+ colourDefault,
+ colourUserDefined,
+ };
+
+ enum EDelayMode
+ {
+ delayFromPopup,
+ delayCustom,
+ delayPermanent
+ };
+
+public:
+ CPopupSettings();
+
+ static COLORREF GetDefColourBk();
+ static COLORREF GetDefColourText();
+
+ void InitForContact(MCONTACT hContact);
+ void SaveForContact(MCONTACT hContact) const;
+
+ EColourMode GetColourMode() const;
+ void SetColourMode(EColourMode nMode);
+
+ COLORREF GetColourBk() const;
+ void SetColourBk(COLORREF rgb);
+
+ COLORREF GetColourText() const;
+ void SetColourText(COLORREF rgb);
+
+ EDelayMode GetDelayMode() const;
+ void SetDelayMode(EDelayMode nMode);
+
+ WORD GetDelayTimeout() const;
+ void SetDelayTimeout(WORD delay);
+
+ bool GetHistoryFlag() const;
+ void SetHistoryFlag(bool flag);
+
+private:
+ EColourMode m_modeColour;
+ EDelayMode m_modeDelay;
+ COLORREF m_rgbBkg;
+ COLORREF m_rgbText;
+ WORD m_wDelay;
+ bool m_bUseHistory;
+};
+
+
+class CAdvProviderSettings
+{
+public:
+ CAdvProviderSettings(const ICurrencyRatesProvider *pCurrencyRatesProvider);
+ ~CAdvProviderSettings();
+
+ void SaveToDb() const;
+
+ const ICurrencyRatesProvider* GetProviderPtr() const;
+
+ WORD GetLogMode() const;
+ void SetLogMode(WORD wMode);
+ tstring GetHistoryFormat() const;
+ void SetHistoryFormat(const tstring& rsFormat);
+ bool GetHistoryOnlyChangedFlag() const;
+ void SetHistoryOnlyChangedFlag(bool bMode);
+
+ tstring GetLogFileName() const;
+ void SetLogFileName(const tstring& rsFile);
+ tstring GetLogFormat() const;
+ void SetLogFormat(const tstring& rsFormat);
+ bool GetLogOnlyChangedFlag() const;
+ void SetLogOnlyChangedFlag(bool bMode);
+
+ const tstring& GetPopupFormat() const;
+ void SetPopupFormat(const tstring& val);
+
+ bool GetShowPopupIfValueChangedFlag() const;
+ void SetShowPopupIfValueChangedFlag(bool val);
+
+ CPopupSettings* GetPopupSettingsPtr() const;
+
+private:
+ const ICurrencyRatesProvider *m_pCurrencyRatesProvider;
+ WORD m_wLogMode;
+ tstring m_sFormatHistory;
+ bool m_bIsOnlyChangedHistory;
+ tstring m_sLogFileName;
+ tstring m_sFormatLogFile;
+ bool m_bIsOnlyChangedLogFile;
+ tstring m_sPopupFormat;
+ bool m_bShowPopupIfValueChanged;
+ mutable CPopupSettings* m_pPopupSettings;
+};
+
+void ShowSettingsDlg(MCONTACT hContact);
+bool ShowSettingsDlg(HWND hWndParent, CAdvProviderSettings* pAdvSettings);
+
+enum
+{
+ glfnResolveCurrencyRateName = 0x0001,
+ glfnResolveUserProfile = 0x0002,
+ glfnResolveAll = glfnResolveCurrencyRateName | glfnResolveUserProfile,
+};
+tstring GenerateLogFileName(const tstring& rsLogFilePattern, const tstring& rsCurrencyRateSymbol, int nFlags = glfnResolveAll);
+tstring GetContactLogFileName(MCONTACT hContact);
+tstring GetContactName(MCONTACT hContact);
+
+#endif //__E211E4D9_383C_43BE_A787_7EF1D585B90D_SettingsDlg_h__
+
diff --git a/protocols/CurrencyRates/src/WinCtrlHelper.cpp b/protocols/CurrencyRates/src/WinCtrlHelper.cpp
new file mode 100644
index 0000000000..f45c4db536
--- /dev/null
+++ b/protocols/CurrencyRates/src/WinCtrlHelper.cpp
@@ -0,0 +1,31 @@
+#include "stdafx.h"
+
+class CVariableListDlg : public CDlgBase
+{
+ const ICurrencyRatesProvider *m_pProvider;
+
+public:
+ CVariableListDlg(HWND hwndParent, const ICurrencyRatesProvider *pProvider) :
+ CDlgBase(g_plugin, IDD_DIALOG_VARIABLE_LIST),
+ m_pProvider(pProvider)
+ {
+ SetParent(hwndParent);
+ }
+
+ bool OnInitDialog() override
+ {
+ TFormatSpecificators aSpecificators;
+ m_pProvider->FillFormat(aSpecificators);
+
+ tostringstream o;
+ for (auto &spec : aSpecificators)
+ o << spec.first << '\t' << spec.second << L"\r\n";
+ ::SetDlgItemText(m_hwnd, IDC_EDIT_VARIABLE, o.str().c_str());
+ return true;
+ }
+};
+
+void show_variable_list(HWND hwndParent, const ICurrencyRatesProvider *pProvider)
+{
+ CVariableListDlg(hwndParent, pProvider).DoModal();
+}
diff --git a/protocols/CurrencyRates/src/WinCtrlHelper.h b/protocols/CurrencyRates/src/WinCtrlHelper.h
new file mode 100644
index 0000000000..a770072bd6
--- /dev/null
+++ b/protocols/CurrencyRates/src/WinCtrlHelper.h
@@ -0,0 +1,37 @@
+#ifndef __a05d6852_4497_4f28_85e1_48a15a170738_WinCtrlHelper_h__
+#define __a05d6852_4497_4f28_85e1_48a15a170738_WinCtrlHelper_h__
+
+class ICurrencyRatesProvider;
+
+inline tstring get_window_text(HWND hWnd)
+{
+ int cBytes = ::GetWindowTextLength(hWnd);
+
+ std::vector<wchar_t> aBuf(cBytes + 1);
+ LPTSTR pBuffer = &*(aBuf.begin());
+ ::GetWindowText(hWnd, pBuffer, cBytes + 1);
+
+ return tstring(pBuffer);
+}
+
+inline void prepare_edit_ctrl_for_error(HWND hwndEdit)
+{
+ ::SetFocus(hwndEdit);
+ ::SendMessage(hwndEdit, EM_SETSEL, 0, -1);
+ ::SendMessage(hwndEdit, EM_SCROLLCARET, 0, 0);
+}
+
+void show_variable_list(HWND hwndParent, const ICurrencyRatesProvider *pProvider);
+
+inline int CurrencyRates_MessageBox(HWND hWnd, LPCTSTR pszText, UINT nType = MB_OK)
+{
+ return ::MessageBox(hWnd, pszText, currencyrates_a2t(MIRANDANAME).c_str(), nType);
+}
+
+inline void spin_set_range(HWND hwndSpin, short nLower, short nUpper)
+{
+ ::SendMessage(hwndSpin, UDM_SETRANGE, 0, MAKELPARAM(nUpper, nLower));
+}
+
+
+#endif //__a05d6852_4497_4f28_85e1_48a15a170738_WinCtrlHelper_h__
diff --git a/protocols/CurrencyRates/src/resource.h b/protocols/CurrencyRates/src/resource.h
new file mode 100644
index 0000000000..e1216a897c
--- /dev/null
+++ b/protocols/CurrencyRates/src/resource.h
@@ -0,0 +1,102 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by w:\miranda-ng\plugins\CurrencyRates\res\Forex.rc
+//
+#define IDI_ICON_MAIN 102
+#define IDD_DIALOG_CURRENCYRATE_INFO 102
+#define IDD_DIALOG_OPT_GOOGLE 103
+#define IDI_ICON_SECTION 110
+#define IDI_ICON_CURRENCYRATE 111
+#define IDI_ICON_UP 113
+#define IDI_ICON_DOWN 114
+#define IDD_CONTACT_SETTINGS 115
+#define IDI_ICON_NOTCHANGED 116
+#define IDD_CURRENCY_CONVERTER 116
+#define IDI_ICON_CURRENCY_CONVERTER 117
+#define IDD_DIALOG_CURRENCYRATE_INFO_1 118
+#define IDI_ICON_REFRESH 118
+#define IDI_ICON_IMPORT 119
+#define IDI_ICON_EXPORT 120
+#define IDD_PROVIDER_ADV_SETTINGS 120
+#define IDI_ICON_SWAP 121
+#define IDD_DIALOG_POPUP 121
+#define IDI_ICON_MAIN1 122
+#define IDI_ICON_DISABLED 122
+#define IDD_DIALOG_VARIABLE_LIST 123
+#define IDC_EDIT_REFRESH_RATE 1002
+#define IDC_SPIN_REFRESH_RATE 1003
+#define IDC_COMBO_REFRESH_RATE 1004
+#define IDC_STATIC_CURRENCYRATE_NAME 1008
+#define IDC_SYSLINK_PROVIDER 1009
+#define IDC_STATIC_CHART 1010
+#define IDC_STATIC_CURRENCYRATE_CHART 1010
+#define IDC_COMBO_CONVERT_FROM 1011
+#define IDC_COMBO_CONVERT_INTO 1012
+#define IDC_BUTTON_ADD 1013
+#define IDC_LIST_RATES 1014
+#define IDC_BUTTON_REMOVE 1015
+#define IDC_EDIT_RATE 1016
+#define IDC_EDIT_RATE_FETCH_TIME 1017
+#define IDC_EDIT_CONTACT_LIST_FORMAT 1018
+#define IDC_EDIT_PREVIOUS_RATE 1018
+#define IDC_BUTTON_DESCRIPTION 1019
+#define IDC_CHECK_INTERNAL_HISTORY 1020
+#define IDC_EDIT_STATUS_MESSAGE_FORMAT 1020
+#define IDC_CHECK_EXTERNAL_FILE 1021
+#define IDC_EDIT_FILE_NAME 1022
+#define IDC_EDIT_TENDENCY_FORMAT 1022
+#define IDC_BUTTON_BROWSE 1023
+#define IDC_EDIT_TENDENCY_FORMAT2 1023
+#define IDC_EDIT_PERSONAL_KEY 1023
+#define IDC_STATIC_SELECT_FILE 1024
+#define IDC_EDIT_NAME 1025
+#define IDC_EDIT_HISTORY_FORMAT 1026
+#define IDC_EDIT_LOG_FILE_FORMAT 1027
+#define IDC_BUTTON_DESCRIPTION2 1028
+#define IDC_BUTTON_LOG_FILE_DESCRIPTION 1028
+#define IDC_STATIC_HISTORY_FORMAT 1029
+#define IDC_BUTTON_HISTORY_DESCRIPTION 1030
+#define IDC_STATIC_LOG_FILE_FORMAT 1031
+#define IDC_CHECK_HISTORY_CONDITION 1032
+#define IDC_CHECK_LOG_CONDITION2 1033
+#define IDC_CHECK_LOG_FILE_CONDITION 1033
+#define IDC_EDIT_VALUE 1033
+#define IDC_BUTTON_CONVERT 1034
+#define IDC_CHECK_SHOW_POPUP_ONLY_VALUE_CHANGED 1034
+#define IDC_STATIC_POPUP_FORMAT 1035
+#define IDC_EDIT_POPUP_FORMAT 1036
+#define IDC_BUTTON_LOG_FILE_DESCRIPTION2 1037
+#define IDC_BUTTON_POPUP_FORMAT_DESCRIPTION 1037
+#define IDC_EDIT_RESULT 1039
+#define IDC_BUTTON_SWAP 1060
+#define IDC_BUTTON_ADVANCED_SETTINGS 1061
+#define IDC_BUTTON_POPUP_SETTINGS 1061
+#define IDC_CHECK_CONTACT_SPECIFIC 1062
+#define IDC_RADIO_DEFAULT_COLOURS 1063
+#define IDC_CHECK_SHOW_POPUP 1064
+#define IDC_RADIO_USER_DEFINED_COLOURS 1064
+#define IDC_MFCCOLORBUTTON1 1066
+#define IDC_CHECK1 1067
+#define IDC_CHECK_DONT_USE_POPUPHISTORY 1067
+#define IDC_EDIT_FROM2 1071
+#define IDC_STATIC_PROVIDER_NAME 1071
+#define IDC_DELAY 1072
+#define IDC_EDIT1 1072
+#define IDC_EDIT_VARIABLE 1072
+#define IDC_BGCOLOR 1074
+#define IDC_TEXTCOLOR 1075
+#define IDC_PREV 1076
+#define IDC_DELAYFROMPU 1093
+#define IDC_DELAYCUSTOM 1094
+#define IDC_DELAYPERMANENT 1095
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 126
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1073
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/protocols/CurrencyRates/src/stdafx.cxx b/protocols/CurrencyRates/src/stdafx.cxx
new file mode 100644
index 0000000000..66afad80f1
--- /dev/null
+++ b/protocols/CurrencyRates/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"
diff --git a/protocols/CurrencyRates/src/stdafx.h b/protocols/CurrencyRates/src/stdafx.h
new file mode 100644
index 0000000000..d2bc83fd18
--- /dev/null
+++ b/protocols/CurrencyRates/src/stdafx.h
@@ -0,0 +1,113 @@
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+
+#undef _HAS_EXCEPTIONS
+#define _HAS_EXCEPTIONS 1
+
+#include <windows.h>
+#include <mshtml.h>
+#include <comdef.h>
+#include <commctrl.h>
+#include <ShellAPI.h>
+#include <sys/stat.h>
+#include <CommDlg.h>
+#include <fstream>
+#include <msapi/comptr.h>
+
+#include <newpluginapi.h>
+#include <m_database.h>
+#include <win2k.h>
+#include <m_xml.h>
+#include <m_clist.h>
+#include <m_langpack.h>
+#include <m_options.h>
+#include <m_protosvc.h>
+#include <m_extraicons.h>
+#include <m_icolib.h>
+#include <m_genmenu.h>
+#include <m_netlib.h>
+#include <m_popup.h>
+#include <m_userinfo.h>
+#include <m_gui.h>
+
+#include <m_variables.h>
+#include <m_CurrencyRates.h>
+#include <m_toptoolbar.h>
+
+#include <boost\date_time\posix_time\posix_time.hpp>
+#include <boost\date_time\c_local_time_adjustor.hpp>
+
+typedef std::wstring tstring;
+typedef std::wostringstream tostringstream;
+typedef std::wistringstream tistringstream;
+typedef std::wofstream tofstream;
+typedef std::wifstream tifstream;
+typedef std::wostream tostream;
+typedef std::wistream tistream;
+typedef boost::posix_time::wtime_input_facet ttime_input_facet;
+typedef boost::posix_time::wtime_facet ttime_facet;
+
+inline std::string currencyrates_t2a(const wchar_t* t)
+{
+ std::string s;
+ char* p = mir_u2a(t);
+ if (p) {
+ s = p;
+ mir_free(p);
+ }
+ return s;
+}
+
+inline tstring currencyrates_a2t(const char* s)
+{
+ tstring t;
+ wchar_t* p = mir_a2u(s);
+ if (p) {
+ t = p;
+ mir_free(p);
+ }
+ return t;
+}
+
+#include "resource.h"
+#include "version.h"
+#include "IconLib.h"
+#include "CurrencyRateInfoDlg.h"
+#include "ModuleInfo.h"
+#include "DBUtils.h"
+#include "HTTPSession.h"
+#include "CurrencyConverter.h"
+#include "WinCtrlHelper.h"
+#include "ImportExport.h"
+#include "ComHelper.h"
+#include "Log.h"
+#include "CommonOptionDlg.h"
+#include "EconomicRateInfo.h"
+#include "SettingsDlg.h"
+#include "CreateFilePath.h"
+#include "Locale.h"
+#include "ExtraImages.h"
+#include "IsWithinAccuracy.h"
+#include "ICurrencyRatesProvider.h"
+#include "CurrencyRatesProviderBase.h"
+
+#define CHART_IMPLEMENT
+#ifdef CHART_IMPLEMENT
+#include "CurrencyRateChart.h"
+#include "Chart.h"
+#endif
+#include "IHTMLParser.h"
+#include "IHTMLEngine.h"
+#include "HTMLParserMS.h"
+
+struct CMPlugin : public PLUGIN<CMPlugin>
+{
+ CMPlugin();
+
+ int Load() override;
+ int Unload() override;
+};
diff --git a/protocols/CurrencyRates/src/version.h b/protocols/CurrencyRates/src/version.h
new file mode 100644
index 0000000000..cd73fb3f68
--- /dev/null
+++ b/protocols/CurrencyRates/src/version.h
@@ -0,0 +1,13 @@
+#define __MAJOR_VERSION 0
+#define __MINOR_VERSION 2
+#define __RELEASE_NUM 0
+#define __BUILD_NUM 3
+
+#include <stdver.h>
+
+#define __PLUGIN_NAME "Currency Rates"
+#define __FILENAME "CurrencyRates.dll"
+#define __DESCRIPTION "Shows currency rates."
+#define __AUTHOR "Dioksin"
+#define __AUTHORWEB "https://miranda-ng.org/p/CurrencyRates/"
+#define __COPYRIGHT ""