Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: src/engine/NotificationWindow.cpp

Issue 6505394822184960: Issue 1109 - Support notifications (Closed)
Patch Set: rebase and address comments Created July 23, 2015, 2:13 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #include <cassert>
2 #include "NotificationWindow.h"
3 #include "../shared/Utils.h"
4 #include <algorithm>
5 #include <fstream>
6 #include "../shared/MsHTMLUtils.h"
7
8 // it is taken from src/plugin/Resource.h
9 #define IDI_ICON_ENABLED 301
10
11 namespace {
12 // DIP = device independant pixels, as DPI is 96
13 const uint32_t kWindowWidth = 400 /*DIP*/;
14 const uint32_t kWindowHeight = 120 /*DIP*/;
15 const uint32_t kIconSize = 64 /*DIP*/;
16 const uint32_t kIconPadding = 10 /*DIP*/;
17
18 // offsets from boundaries of working area of screen
19 const uint32_t kWindowMarginRight = 50 /*DIP*/;
20 const uint32_t kWindowMarginBottom = 20 /*DIP*/;
21
22 std::vector<uint16_t> iconSizes = []()->std::vector<uint16_t>
23 {
24 std::vector<uint16_t> iconSizes;
25 iconSizes.emplace_back(16);
26 iconSizes.emplace_back(19);
27 iconSizes.emplace_back(48);
28 iconSizes.emplace_back(128);
29 iconSizes.emplace_back(256);
30 return iconSizes;
31 }();
32
33 class DCHandle
34 {
35 public:
36 explicit DCHandle(HDC hDC = nullptr) : m_hDC(hDC)
37 {
38 }
39
40 ~DCHandle()
41 {
42 if(m_hDC != nullptr)
43 {
44 ::DeleteDC(m_hDC);
45 m_hDC = nullptr;
46 }
47 }
48
49 operator HDC()
50 {
51 return m_hDC;
52 }
53
54 int GetDeviceCaps(int nIndex) const
55 {
56 ATLASSERT(m_hDC != nullptr);
57 return ::GetDeviceCaps(m_hDC, nIndex);
58 }
59
60 HBITMAP SelectBitmap(HBITMAP value)
61 {
62 return static_cast<HBITMAP>(::SelectObject(m_hDC, value));
63 }
64 private:
65 DCHandle(const DCHandle&);
66 DCHandle& operator=(const DCHandle&);
67 private:
68 HDC m_hDC;
69 };
70
71 /// Case insensitive pattern replacing function.
72 /// ReplaceMulti("Some TeXt <PlaceHolder>Some link</A> and text", "<placeholde r>", ->"<a>")->
73 /// "Some TeXt <a>Some link</A> and text".
74 std::wstring ReplaceMulti(const std::wstring& src, std::wstring placeholder, c onst std::function<std::wstring()>& replacementGenerator)
75 {
76 std::transform(placeholder.begin(), placeholder.end(), placeholder.begin(), ::towlower);
77 std::wstring srcLowerCase = src;
78 std::transform(srcLowerCase.begin(), srcLowerCase.end(), srcLowerCase.begin( ), ::towlower);
79 std::wstring retValue;
80 std::wstring::size_type placeHolderOffset = 0;
81 std::wstring::size_type nextStringChunkOffset = 0;
82 while ((placeHolderOffset = srcLowerCase.find(placeholder, nextStringChunkOf fset)) != std::wstring::npos)
83 {
84 retValue.append(src.substr(nextStringChunkOffset, placeHolderOffset - next StringChunkOffset));
85 retValue.append(replacementGenerator());
86 nextStringChunkOffset = placeHolderOffset + placeholder.length();
87 }
88 retValue.append(src.substr(nextStringChunkOffset));
89 return retValue;
90 }
91 std::wstring ReplaceMulti(const std::wstring& workingString, const std::wstrin g& templ, const std::wstring& replacement)
92 {
93 return ReplaceMulti(workingString, templ, [&replacement]()->std::wstring
94 {
95 return replacement;
96 });
97 }
98 }
99
100 NotificationWindow::NotificationWindow(const AdblockPlus::Notification& notifica tion, const std::wstring& htmlFileDir)
101 {
102 const std::wstring filePath = htmlFileDir + L"NotificationWindow.html";
103 std::wifstream ifs(filePath);
104 assert(ifs.good() && "Cannot open NotificationWindow.html file");
105 if (!ifs.good())
106 {
107 throw std::runtime_error("Cannot read NotificationWindow.html");
108 }
109 m_htmlPage.assign((std::istreambuf_iterator<wchar_t>(ifs)), std::istreambuf_it erator<wchar_t>());
110
111 m_links = ToUtf16Strings(notification.GetLinks());
112 auto body = ToUtf16String(notification.GetMessageString());
113 uint32_t linkIDCounter = 0;
114 body = ReplaceMulti(body, L"<a>", [this, &linkIDCounter]()->std::wstring
115 {
116 return L"<a href=\"#\" data-linkID=\"" + std::to_wstring(linkIDCounter++) + L"\">";
117 });
118 assert(linkIDCounter == m_links.size() && "The amount of links in the text is different from the amount of provided links");
119 m_htmlPage = ReplaceMulti(m_htmlPage, L"<!--Title-->", ToUtf16String(notificat ion.GetTitle()));
120 m_htmlPage = ReplaceMulti(m_htmlPage, L"<!--Body-->", body);
121 }
122
123 NotificationWindow::~NotificationWindow()
124 {
125 }
126
127 LRESULT NotificationWindow::OnCreate(const CREATESTRUCT* /*createStruct*/) {
128 m_bgColor.CreateSolidBrush(RGB(255, 255, 255));
129
130 CRect iconRect(CPoint(0, 0), CSize(kIconSize + 2 * kIconPadding, kIconSize + 2 * kIconPadding));
131 m_icon.Create(m_hWnd, DPIAware(iconRect), nullptr, WS_CHILD | WS_VISIBLE | SS_ BITMAP | SS_CENTERIMAGE);
132 LoadABPIcon();
133
134 m_axIE.Create(m_hWnd, DPIAware(CRect(CPoint(iconRect.right, 0), CSize(kWindowW idth - iconRect.right, kWindowHeight))),
135 L"", WS_CHILD | WS_VISIBLE, 0, kHTMLDocumentCtrlID);
136 m_axIE.CreateControl((L"mshtml:" + m_htmlPage).c_str());
137 ATL::CComPtr<IAxWinAmbientDispatch> axWinAmbient;
138 if (SUCCEEDED(m_axIE.QueryHost(&axWinAmbient))) {
139 // disable web browser context menu
140 axWinAmbient->put_AllowContextMenu(VARIANT_FALSE);
141 // make web browser DPI aware, so the browser itself sets zoom level and
142 // cares about rendering (not zooming) in the proper size.
143 DWORD docFlags;
144 axWinAmbient->get_DocHostFlags(&docFlags);
145 docFlags |= DOCHOSTUIFLAG_DPI_AWARE;
146 // remove DOCHOSTUIFLAG_SCROLL_NO, so it's scrollable
147 docFlags &= ~DOCHOSTUIFLAG_SCROLL_NO;
148 axWinAmbient->put_DocHostFlags(docFlags);
149 }
150 // kHTMLDocumentCtrlID works here
151 AtlAdviseSinkMap(this, true);
152
153 SetMsgHandled(false);
154 return 0;
155 }
156
157 LRESULT NotificationWindow::OnCtlColor(UINT /*msg*/, WPARAM /*wParam*/, LPARAM l Param, BOOL& handled)
158 {
159 if (reinterpret_cast<HWND>(lParam) != m_icon)
160 {
161 handled = FALSE;
162 }
163 return reinterpret_cast<LRESULT>(static_cast<HBRUSH>(m_bgColor));
164 }
165
166 LRESULT NotificationWindow::OnClick(UINT /*msg*/, WPARAM /*wParam*/, LPARAM /*lP aram*/, BOOL& /*handled*/)
167 {
168 if(m_onClickCallback)
169 m_onClickCallback();
170 return 0;
171 }
172
173 void NotificationWindow::OnSize(uint32_t wParam, CSize size)
174 {
175 if (m_icon)
176 {
177 CRect rect(CPoint(0, 0), DPIAware(CSize(kIconSize + 2 * kIconPadding, kIconS ize + 2 * kIconPadding)));
178 m_icon.SetWindowPos(0, &rect, SWP_NOMOVE);
179 }
180 if (m_axIE)
181 {
182 size.cx -= DPIAware(kIconSize + 2 * kIconPadding);
183 CRect rect(CPoint(0, 0), size);
184 m_axIE.SetWindowPos(0, &rect, SWP_NOMOVE);
185 }
186 SetMsgHandled(false);
187 }
188
189 void NotificationWindow::OnDestroy()
190 {
191 AtlAdviseSinkMap(this, false);
192 // and proceed as usual
193 SetMsgHandled(false);
194 }
195
196 void __stdcall NotificationWindow::OnHTMLDocumentClick(IHTMLEventObj* eventObjec t)
197 {
198 // stop propagating the event since it's handled by us and should not cause an y other actions.
199 if (!eventObject)
200 return;
201 eventObject->put_cancelBubble(VARIANT_TRUE);
202 eventObject->put_returnValue(ATL::CComVariant(false));
203 ATL::CComPtr<IHTMLElement> htmlElement;
204 if (FAILED(eventObject->get_srcElement(&htmlElement)) || !htmlElement) {
205 return;
206 }
207 ATL::CComBSTR tag;
208 htmlElement->get_tagName(&tag);
209 const wchar_t expectedTag[] = { L"a" };
210 if (_wcsnicmp(tag, expectedTag, min(sizeof(expectedTag), tag.Length())) != 0) {
211 return;
212 }
213 auto classAttr = GetHtmlElementAttribute(*htmlElement, ATL::CComBSTR(L"class") );
214 if (classAttr.attributeValue == L"closeButton")
215 {
216 if (m_onCloseCallback)
217 m_onCloseCallback();
218 return;
219 }
220 if (!m_onLinkClickedCallback)
221 {
222 return;
223 }
224 auto linkIDAttr = GetHtmlElementAttribute(*htmlElement, ATL::CComBSTR(L"data-l inkID"));
225 uint32_t linkID = 0;
226 if (!linkIDAttr.attributeValue.empty() && (linkID = std::stoi(linkIDAttr.attri buteValue)) < m_links.size())
227 {
228 m_onLinkClickedCallback(m_links[linkID]);
229 if (m_onCloseCallback)
230 m_onCloseCallback();
231 }
232 }
233
234 void __stdcall NotificationWindow::OnHTMLDocumentSelectStart(IHTMLEventObj* even tObject)
235 {
236 if (!eventObject)
237 return;
238 // disable selecting
239 eventObject->put_cancelBubble(VARIANT_TRUE);
240 eventObject->put_returnValue(ATL::CComVariant(false));
241 }
242
243 void NotificationWindow::LoadABPIcon()
244 {
245 ScopedModule m_adblockPlusDLL;
246 if (!(m_adblockPlusDLL.Open(L"AdblockPlus32.dll", LOAD_LIBRARY_AS_DATAFILE) ||
247 m_adblockPlusDLL.Open(L"AdblockPlus64.dll", LOAD_LIBRARY_AS_DATAFILE) ||
248 // for debug
249 m_adblockPlusDLL.Open(L"AdblockPlus.dll", LOAD_LIBRARY_AS_DATAFILE)))
250 {
251 return;
252 }
253 auto iconSizeIterator = lower_bound(iconSizes.begin(), iconSizes.end(), DPIAwa re(kIconSize));
254 if (iconSizeIterator == iconSizes.end())
255 {
256 iconSizeIterator = iconSizes.rbegin().base();
257 }
258
259 auto hIcon = static_cast<HICON>(::LoadImageW(m_adblockPlusDLL, (L"#" + std::to _wstring(IDI_ICON_ENABLED)).c_str(),
260 IMAGE_ICON, *iconSizeIterator, *iconSizeIterator, LR_SHARED));
261 if (hIcon == nullptr)
262 {
263 return;
264 }
265
266 HDC screenDC = m_icon.GetDC();
267 DCHandle tmpDC(::CreateCompatibleDC(screenDC));
268 ScopedObjectHandle<HBITMAP> bitmap;
269 bitmap = CreateCompatibleBitmap(screenDC, DPIAware(kIconSize), DPIAware(kIconS ize));
270 HBITMAP prevBitmap = tmpDC.SelectBitmap(bitmap);
271 CRect tmpRect(CPoint(0, 0), DPIAware(CSize(kIconSize, kIconSize)));
272 FillRect(tmpDC, &tmpRect, m_bgColor);
273 ::DrawIconEx(tmpDC, 0, 0, hIcon, tmpRect.Width(), tmpRect.Height(), 0, nullptr , DI_NORMAL);
274 m_iconImg.Attach(bitmap.Detach());
275 tmpDC.SelectBitmap(prevBitmap);
276 m_icon.SetBitmap(m_iconImg);
277 }
278
279 POINT NotificationBorderWindow::GetWindowCoordinates() {
280 HMONITOR primaryMonitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTOPRIMARY);
281 {
282 DCHandle hdc(GetDC());
283 m_dpi = hdc.GetDeviceCaps(LOGPIXELSX);
284 }
285 MONITORINFO monitorInfo = {};
286 monitorInfo.cbSize = sizeof(monitorInfo);
287 GetMonitorInfo(primaryMonitor, &monitorInfo);
288 int windowX = monitorInfo.rcWork.right - DPIAware(kWindowWidth + kWindowMargin Right);
289 int windowY = monitorInfo.rcWork.bottom - DPIAware(kWindowHeight + kWindowMarg inBottom);
290 POINT coords = {windowX, windowY};
291 return coords;
292 }
293
294 NotificationBorderWindow::NotificationBorderWindow(const AdblockPlus::Notificati on& notification, const std::wstring& htmlFileDir)
295 : m_content(notification, htmlFileDir)
296 {
297 m_content.SetOnClick([this]
298 {
299 PostMessage(WM_CLOSE);
300 });
301 m_content.SetOnClose([this]
302 {
303 PostMessage(WM_CLOSE);
304 });
305 }
306
307 LRESULT NotificationBorderWindow::OnCreate(const CREATESTRUCT* createStruct)
308 {
309 auto windowCoords = GetWindowCoordinates();
310 MoveWindow(windowCoords.x, windowCoords.y, DPIAware(kWindowWidth), DPIAware(kW indowHeight));
311
312 RECT clientRect;
313 GetClientRect(&clientRect);
314 // make one pixel border
315 clientRect.top += 1;
316 clientRect.left += 1;
317 clientRect.bottom -= 1;
318 clientRect.right -= 1;
319 m_content.Create(m_hWnd, clientRect, nullptr, WS_CHILD | WS_VISIBLE);
320 auto err = GetLastError();
321 SetMsgHandled(false);
322 return 0;
323 }
324
325 void NotificationBorderWindow::OnSize(uint32_t wParam, CSize size)
326 {
327 if (m_content.IsWindow())
328 {
329 RECT clientRect;
330 GetClientRect(&clientRect);
331 clientRect.top += 1;
332 clientRect.left += 1;
333 clientRect.bottom -= 1;
334 clientRect.right -= 1;
335 m_content.MoveWindow(&clientRect);
336 }
337 SetMsgHandled(false);
338 }
339
340 LRESULT NotificationBorderWindow::OnClick(UINT /*msg*/, WPARAM /*wParam*/, LPARA M /*lParam*/, BOOL& /*handled*/)
341 {
342 DestroyWindow();
343 return 0;
344 }
345
346 void NotificationBorderWindow::OnFinalMessage(HWND) {
347 if (!!m_onDestroyedCallback) {
348 m_onDestroyedCallback();
349 }
350 }
OLDNEW

Powered by Google App Engine
This is Rietveld