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

Powered by Google App Engine
This is Rietveld