Develop and Download Open Source Software

Browse Subversion Repository

Contents of /trunk/src/mascot.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 132 - (show annotations) (download) (as text)
Tue Sep 13 17:40:57 2016 UTC (7 years, 6 months ago) by z0rac
File MIME type: text/x-c++src
File size: 16101 byte(s)


1 /*
2 * Copyright (C) 2009-2016 TSUBAKIMOTO Hiroya <z0rac@users.sourceforge.jp>
3 *
4 * This software comes with ABSOLUTELY NO WARRANTY; for details of
5 * the license terms, see the LICENSE.txt file included with the program.
6 */
7 #include "define.h"
8 #include "mailbox.h"
9 #include "setting.h"
10 #include "icon.h"
11 #include "window.h"
12 #include <cassert>
13
14 #ifdef _DEBUG
15 #include <iostream>
16 #define DBG(s) s
17 #define LOG(s) (cout << s)
18 #else
19 #define DBG(s)
20 #define LOG(s)
21 #endif
22
23 #ifndef NIN_SELECT
24 #define NIN_SELECT (WM_USER+0)
25 #define NIN_KEYSELECT (WM_USER+1)
26 #endif
27
28 #define ICON_BKGC RGB(255, 0, 255)
29
30 /** tooltips - tooltips controller
31 */
32 namespace {
33 class tooltips : public window, window::timer {
34 static commctrl use;
35 struct info : public TOOLINFO {
36 info(const window& tips);
37 };
38 class status : public window {
39 LRESULT notify(WPARAM w, LPARAM l);
40 void tracking(bool tracking);
41 public:
42 status(const window& owner);
43 void operator()(const string& text);
44 void reset(bool await = true);
45 void disable();
46 };
47 status _status;
48 void wakeup(window&) { clearballoon(); }
49 LRESULT notify(WPARAM w, LPARAM l);
50 public:
51 tooltips(const window& owner);
52 void tip(const string& text) { _status(text); }
53 void reset(bool await = true) { _status.reset(await); }
54 void balloon(LPCWSTR text, unsigned sec = 0,
55 const string& title = string(), int icon = 0);
56 void clearballoon();
57 void topmost(bool owner);
58 public:
59 class ellipsis {
60 HDC hDC;
61 HGDIOBJ hFont;
62 public:
63 ellipsis();
64 ~ellipsis();
65 win32::wstr operator()(LPCWSTR ws) const;
66 };
67 };
68 window::commctrl tooltips::use(ICC_BAR_CLASSES);
69 }
70
71 tooltips::info::info(const window& tips)
72 {
73 ZeroMemory(this, sizeof(TOOLINFO));
74 cbSize = sizeof(TOOLINFO);
75 hwnd = GetParent(tips.hwnd());
76 }
77
78 tooltips::status::status(const window& owner)
79 : window(TOOLTIPS_CLASS, NULL, owner.hwnd())
80 {
81 style(WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX);
82 info ti(*this);
83 ti.uFlags = TTF_SUBCLASS;
84 GetClientRect(ti.hwnd, &ti.rect);
85 SendMessage(hwnd(), TTM_ADDTOOL, 0, LPARAM(&ti));
86 }
87
88 void
89 tooltips::status::operator()(const string& text)
90 {
91 info ti(*this);
92 ti.lpszText = LPSTR(text.c_str());
93 SendMessage(hwnd(), TTM_UPDATETIPTEXT, 0, LPARAM(&ti));
94 }
95
96 void
97 tooltips::status::reset(bool await)
98 {
99 if (await) {
100 tracking(true);
101 } else if (!visible()) {
102 for (int i = 0; i < 2; ++i) SendMessage(hwnd(), TTM_ACTIVATE, i, 0);
103 }
104 }
105
106 void
107 tooltips::status::disable()
108 {
109 SendMessage(hwnd(), TTM_ACTIVATE, FALSE, 0);
110 tracking(false);
111 }
112
113 void
114 tooltips::status::tracking(bool tracking)
115 {
116 TRACKMOUSEEVENT tme = {
117 sizeof(TRACKMOUSEEVENT),
118 tracking ? TME_LEAVE : TME_LEAVE | TME_CANCEL,
119 GetParent(hwnd())
120 };
121 TrackMouseEvent(&tme);
122 }
123
124 LRESULT
125 tooltips::status::notify(WPARAM w, LPARAM l)
126 {
127 if (LPNMHDR(l)->code == TTN_POP) reset();
128 return window::notify(w, l);
129 }
130
131 tooltips::tooltips(const window& owner)
132 : window(TOOLTIPS_CLASS, NULL, owner.hwnd()), _status(owner)
133 {
134 style(WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE);
135 info ti(*this);
136 ti.uFlags = TTF_TRACK;
137 SendMessage(hwnd(), TTM_ADDTOOL, 0, LPARAM(&ti));
138 SendMessage(hwnd(), TTM_SETMAXTIPWIDTH, 0, 300);
139 }
140
141 void
142 tooltips::balloon(LPCWSTR text, unsigned sec, const string& title, int icon)
143 {
144 TOOLINFOW ti = { sizeof(TOOLINFOW) };
145 ti.hwnd = GetParent(hwnd());
146 SendMessage(hwnd(), TTM_TRACKACTIVATE, FALSE, LPARAM(&ti));
147 _status.disable();
148 RECT r;
149 GetClientRect(ti.hwnd, &r);
150 r.left = r.right / 2, r.top = r.bottom * 9 / 16;
151 ClientToScreen(ti.hwnd, LPPOINT(&r));
152 SendMessage(hwnd(), TTM_TRACKPOSITION, 0, MAKELPARAM(r.left, r.top));
153 ti.lpszText = LPWSTR(text);
154 SendMessage(hwnd(), TTM_UPDATETIPTEXTW, 0, LPARAM(&ti));
155 SendMessage(hwnd(), TTM_SETTITLEA, WPARAM(icon), LPARAM(title.c_str()));
156 SendMessage(hwnd(), TTM_TRACKACTIVATE, TRUE, LPARAM(&ti));
157 settimer(*this, sec * 1000);
158 }
159
160 void
161 tooltips::clearballoon()
162 {
163 info ti(*this);
164 SendMessage(hwnd(), TTM_TRACKACTIVATE, FALSE, LPARAM(&ti));
165 }
166
167 void
168 tooltips::topmost(bool owner)
169 {
170 if (window::topmost() != owner) window::topmost(owner);
171 if (_status.topmost() != owner) _status.topmost(owner);
172 }
173
174 LRESULT
175 tooltips::notify(WPARAM w, LPARAM l)
176 {
177 if (LPNMHDR(l)->code == TTN_POP) _status.reset();
178 return window::notify(w, l);
179 }
180
181 tooltips::ellipsis::ellipsis()
182 : hDC(CreateCompatibleDC(NULL))
183 {
184 NONCLIENTMETRICS ncm = { sizeof(NONCLIENTMETRICS) };
185 SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
186 hFont = SelectObject(hDC, CreateFontIndirect(&ncm.lfStatusFont));
187 }
188
189 tooltips::ellipsis::~ellipsis()
190 {
191 DeleteObject(SelectObject(hDC, hFont));
192 DeleteDC(hDC);
193 }
194
195 win32::wstr
196 tooltips::ellipsis::operator()(LPCWSTR ws) const
197 {
198 struct buf {
199 LPWSTR data;
200 buf(LPCWSTR ws) : data(lstrcpyW(new WCHAR[lstrlenW(ws) + 5], ws)) {}
201 ~buf() { delete [] data; }
202 } buf(ws);
203 for (LPWSTR p = buf.data; *++p;) {
204 if (*p == '\t') *p = ' ';
205 }
206 static RECT r = { 0, 0, 300, 300 };
207 DrawTextW(hDC, buf.data, -1, &r,
208 DT_SINGLELINE | DT_NOPREFIX | DT_END_ELLIPSIS | DT_MODIFYSTRING);
209 return buf.data;
210 }
211
212 /** iconwindow - icon window with system tray
213 */
214 namespace {
215 class iconwindow : public appwindow, window::timer {
216 icon _icon;
217 tooltips _tips;
218 string _status;
219 UINT _tbcmsg;
220 bool _trayicon(bool tray);
221 void _update();
222 void _updatetips();
223 protected:
224 LRESULT dispatch(UINT m, WPARAM w, LPARAM l);
225 void release() { _trayicon(false); }
226 void draw(HDC hDC);
227 void raised(bool topmost) { _tips.topmost(topmost); }
228 bool popup(const menu& menu, LPARAM pt);
229 void wakeup(window& source);
230 void reset(int type);
231 void status(const string& text);
232 void balloon(LPCWSTR text, unsigned sec,
233 const string& title = string(), int icon = 0);
234 int size() const { return _icon.size(); }
235 public:
236 iconwindow(const icon& icon);
237 ~iconwindow() { if (hwnd()) _trayicon(false); }
238 void trayicon(bool tray);
239 bool intray() const { return !visible(); }
240 void transparent(int alpha) { appwindow::transparent(alpha, ICON_BKGC); }
241 };
242 }
243
244 bool
245 iconwindow::_trayicon(bool tray)
246 {
247 NOTIFYICONDATA ni = { sizeof(NOTIFYICONDATA), hwnd() };
248 if (tray) {
249 ni.uFlags = NIF_MESSAGE | NIF_ICON;
250 ni.uCallbackMessage = WM_USER;
251 ni.hIcon = _icon;
252 while (!Shell_NotifyIcon(NIM_ADD, &ni)) {
253 if (GetLastError() != ERROR_TIMEOUT) return false;
254 if (Shell_NotifyIcon(NIM_MODIFY, &ni)) break;
255 Sleep(1000);
256 }
257 ni.uVersion = NOTIFYICON_VERSION;
258 Shell_NotifyIcon(NIM_SETVERSION, &ni);
259 } else {
260 Shell_NotifyIcon(NIM_DELETE, &ni);
261 }
262 return true;
263 }
264
265 void
266 iconwindow::_update()
267 {
268 if (intray()) {
269 NOTIFYICONDATA ni = { sizeof(NOTIFYICONDATA), hwnd() };
270 ni.uFlags = NIF_ICON;
271 ni.hIcon = _icon;
272 Shell_NotifyIcon(NIM_MODIFY, &ni);
273 } else {
274 invalidate();
275 }
276 }
277
278 void
279 iconwindow::_updatetips()
280 {
281 if (intray()) {
282 NOTIFYICONDATA ni = { sizeof(NOTIFYICONDATA), hwnd() };
283 ni.uFlags = NIF_TIP;
284 lstrcpyn(ni.szTip, _status.c_str(), sizeof(ni.szTip));
285 Shell_NotifyIcon(NIM_MODIFY, &ni);
286 } else {
287 _tips.tip(_status);
288 }
289 }
290
291 LRESULT
292 iconwindow::dispatch(UINT m, WPARAM w, LPARAM l)
293 {
294 switch (m) {
295 case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN:
296 case WM_SHOWWINDOW:
297 _tips.clearballoon();
298 // fall down
299 case WM_MOUSELEAVE:
300 _tips.reset(hascursor());
301 break;
302 case WM_USER: // from tray icon
303 switch (UINT(l)) {
304 case WM_CONTEXTMENU: m = WM_CONTEXTMENU; break;
305 case NIN_SELECT: m = WM_LBUTTONDOWN; break;
306 case NIN_KEYSELECT: m = WM_LBUTTONDBLCLK; break;
307 default: return 0;
308 }
309 POINT pt;
310 GetCursorPos(&pt);
311 foreground();
312 PostMessage(hwnd(), m, 0, MAKELPARAM(pt.x, pt.y));
313 return 0;
314 default:
315 if (m == _tbcmsg && intray()) _trayicon(true);
316 break;
317 }
318 return appwindow::dispatch(m, w, l);
319 }
320
321 void
322 iconwindow::draw(HDC hDC)
323 {
324 RECT r;
325 GetClientRect(hwnd(), &r);
326 HBRUSH br = CreateSolidBrush(ICON_BKGC);
327 DrawIconEx(hDC, r.left, r.top, _icon, r.right, r.bottom, 0, br, DI_NORMAL);
328 DeleteObject(HGDIOBJ(br));
329 }
330
331 bool
332 iconwindow::popup(const menu& menu, LPARAM pt)
333 {
334 bool t = appwindow::popup(menu, pt);
335 if (!t && intray()) {
336 NOTIFYICONDATA ni = { sizeof(NOTIFYICONDATA), hwnd() };
337 Shell_NotifyIcon(NIM_SETFOCUS, &ni);
338 }
339 return t;
340 }
341
342 void
343 iconwindow::wakeup(window& source)
344 {
345 source.settimer(*this, _icon.next().delay());
346 _update();
347 }
348
349 void
350 iconwindow::reset(int type)
351 {
352 settimer(*this, _icon.reset(type).delay());
353 _update();
354 }
355
356 void
357 iconwindow::status(const string& text)
358 {
359 _status = text;
360 _tips.clearballoon();
361 _updatetips();
362 }
363
364 void
365 iconwindow::balloon(LPCWSTR text, unsigned sec,
366 const string& title, int icon)
367 {
368 if (intray()) {
369 NOTIFYICONDATAW ni = { sizeof(NOTIFYICONDATAW), hwnd() };
370 ni.uFlags = NIF_INFO;
371 int n = lstrlenW(text);
372 if (n >= int(sizeof(ni.szInfo) / sizeof(ni.szInfo[0]))) {
373 n = sizeof(ni.szInfo) / sizeof(ni.szInfo[0]);
374 while (n-- && text[n] != '\n') continue;
375 if (n < 0) n = sizeof(ni.szInfo) / sizeof(ni.szInfo[0]) - 1;
376 }
377 lstrcpynW(ni.szInfo, text, n + 1);
378 ni.uTimeout = sec * 1000;
379 lstrcpynW(ni.szInfoTitle, win32::wstr(title),
380 sizeof(ni.szInfoTitle) / sizeof(ni.szInfoTitle[0]));
381 ni.dwInfoFlags = icon & 3;
382 Shell_NotifyIconW(NIM_MODIFY, &ni);
383 } else {
384 _tips.balloon(text, sec, title, icon);
385 }
386 }
387
388 iconwindow::iconwindow(const icon& icon)
389 : _icon(icon), _tips(self()), _tbcmsg(RegisterWindowMessage("TaskbarCreated"))
390 {
391 style(WS_POPUP, WS_EX_TOOLWINDOW | WS_EX_LAYERED);
392 _icon.reset();
393 }
394
395 void
396 iconwindow::trayicon(bool tray)
397 {
398 _icon.resize(tray ? GetSystemMetrics(SM_CXSMICON) : _icon.size());
399 if (_trayicon(tray)) {
400 show(!tray);
401 _updatetips();
402 } else {
403 _icon.resize(_icon.size());
404 }
405 }
406
407 /** mascotwindow - mascot window
408 */
409 namespace {
410 class mascotwindow : public iconwindow {
411 menu _menu;
412 int _balloon;
413 int _subjects;
414 void _release();
415 static icon _icon();
416 protected:
417 LRESULT dispatch(UINT m, WPARAM w, LPARAM l);
418 void release();
419 void update(int recent, int unseen, list<mailbox*>* mboxes);
420 public:
421 mascotwindow();
422 ~mascotwindow() { if (hwnd()) _release(); }
423 using iconwindow::balloon;
424 };
425 }
426
427 void
428 mascotwindow::_release()
429 {
430 try {
431 MONITORINFO info = { sizeof(info) };
432 GetMonitorInfo(MonitorFromWindow(hwnd(), MONITOR_DEFAULTTONEAREST), &info);
433 RECT dt = info.rcMonitor;
434 RECT r = bounds();
435 setting::preferences("mascot")
436 ("position", setting::tuple
437 (r.left)(r.top)(dt.right - dt.left)(dt.bottom - dt.top)(topmost()))
438 ("tray", intray());
439 } catch (...) {}
440 }
441
442 icon
443 mascotwindow::_icon()
444 {
445 try {
446 int id;
447 string fn;
448 setting::preferences()["icon"]()()(id = 1).sep(0)(fn);
449 return icon(id, fn);
450 } catch (...) {
451 return icon(1);
452 }
453 }
454
455 LRESULT
456 mascotwindow::dispatch(UINT m, WPARAM w, LPARAM l)
457 {
458 switch (m) {
459 case WM_ENDSESSION:
460 if (w) {
461 _release();
462 execute(ID_EVENT_LOGOFF);
463 }
464 break;
465 case WM_LBUTTONDOWN:
466 if (!intray()) {
467 ReleaseCapture();
468 PostMessage(hwnd(), WM_SYSCOMMAND, SC_MOVE | HTCAPTION, l);
469 break;
470 }
471 // fall down
472 case WM_LBUTTONDBLCLK:
473 execute(_menu);
474 return 0;
475 case WM_CONTEXTMENU:
476 if (l == ~LPARAM(0) && !intray()) {
477 POINT pt = extent();
478 pt.x >>= 1, pt.y >>= 1;
479 ClientToScreen(hwnd(), &pt);
480 l = MAKELPARAM(pt.x, pt.y);
481 }
482 popup(_menu, l);
483 return 0;
484 case WM_APP: // broadcast
485 update(LOWORD(w), HIWORD(w), reinterpret_cast<list<mailbox*>*>(l));
486 return 0;
487 }
488 return iconwindow::dispatch(m, w, l);
489 }
490
491 void
492 mascotwindow::release()
493 {
494 iconwindow::release();
495 _release();
496 PostQuitMessage(0);
497 }
498
499 void
500 mascotwindow::update(int recent, int unseen, list<mailbox*>* mboxes)
501 {
502 if (mboxes) {
503 LOG("Update: " << recent << ", " << unseen << endl);
504 win32::wstr info;
505 bool newer = false;
506 tooltips::ellipsis ellips;
507 list<mailbox*>::const_iterator p = mboxes->begin();
508 for (; p != mboxes->end(); ++p) {
509 int n = (*p)->recent();
510 if (n > 0 && _balloon) {
511 const list<mail>& mails = (*p)->mails();
512 info += win32::wstr('\n' + win32::exe.textf(ID_TEXT_FETCHED_MAIL,
513 n, mails.size()) +
514 " @ " + (*p)->name());
515 list<mail>::const_iterator it = mails.end();
516 for (int i = min(n, _subjects); i-- > 0;) {
517 --it, info += ellips(win32::wstr("\n- " + it->subject(), CP_UTF8));
518 }
519 } else if (n < 0) {
520 info += win32::wstr('\n' + win32::exe.text(ID_TEXT_FETCH_ERROR) +
521 " @ " + (*p)->name());
522 }
523 newer = newer || n > 0;
524 }
525 ReplyMessage(0);
526 status(win32::exe.textf(ID_TEXT_FETCHED_MAIL, recent, unseen));
527 if (info) {
528 balloon(info + 1, _balloon ? _balloon : 10,
529 win32::exe.text(newer ? ID_TEXT_BALLOON_TITLE :
530 ID_TEXT_BALLOON_ERROR),
531 newer ? NIIF_INFO : NIIF_ERROR);
532 }
533 reset((unseen == 0) + 1);
534 } else {
535 status(win32::exe.text(ID_TEXT_FETCHING));
536 reset(0);
537 }
538 }
539
540 mascotwindow::mascotwindow()
541 : iconwindow(_icon()), _menu(MAKEINTRESOURCE(1))
542 {
543 setting prefs = setting::preferences();
544 int icon, transparency;
545 prefs["icon"](icon = size())(transparency = 0);
546 if (!icon) icon = GetSystemMetrics(SM_CXICON);
547 prefs["balloon"](_balloon = 10)(_subjects = 0);
548
549 prefs = setting::preferences("mascot");
550 RECT dt;
551 GetWindowRect(GetDesktopWindow(), &dt);
552 dt.right -= dt.left, dt.bottom -= dt.top;
553 RECT r = { dt.right - icon, dt.top, dt.right, dt.bottom };
554 int raise, tray;
555 prefs["position"](r.left)(r.top)(r.right)(r.bottom)(raise = 0);
556 prefs["tray"](tray = 0);
557 RECT rt = { r.left, r.top, r.left + icon, r.top + icon };
558 MONITORINFO info = { sizeof(info) };
559 GetMonitorInfo(MonitorFromRect(&rt, MONITOR_DEFAULTTONEAREST), &info);
560 dt = info.rcMonitor;
561 dt.right -= dt.left, dt.bottom -= dt.top;
562 r.left = dt.left + MulDiv(r.left - dt.left, dt.right, r.right);
563 r.top = dt.top + MulDiv(r.top - dt.top, dt.bottom, r.bottom);
564 r.right = r.left + icon;
565 r.bottom = r.top + icon;
566 move(adjust(r, info.rcMonitor, icon / 4));
567 transparent(255 - 255 * transparency / 100);
568 topmost(raise != 0);
569 trayicon(tray != 0);
570 }
571
572 namespace cmd {
573 struct trayicon : public window::command {
574 void execute(window& source)
575 { ((mascotwindow&)source).trayicon(!((mascotwindow&)source).intray()); }
576 UINT state(window& source)
577 { return ((mascotwindow&)source).intray() ? MFS_CHECKED : 0; }
578 };
579
580 struct alwaysontop : public window::command {
581 void execute(window& source) { source.topmost(!source.topmost()); }
582 UINT state(window& source)
583 {
584 return ((mascotwindow&)source).intray() ?
585 MFS_DISABLED : source.topmost() ? MFS_CHECKED : 0;
586 }
587 };
588
589 struct about : public window::command {
590 about() : window::command(-1001) {}
591 void execute(window& source)
592 {
593 ((mascotwindow&)source).balloon(win32::wstr(win32::exe.text(ID_TEXT_ABOUT)), 10,
594 win32::exe.text(ID_TEXT_VERSION));
595 }
596 };
597 }
598
599 window*
600 mascot()
601 {
602 unique_ptr<mascotwindow> w(new mascotwindow);
603 w->addcmd(ID_MENU_TRAYICON, new cmd::trayicon);
604 w->addcmd(ID_MENU_ALWAYSONTOP, new cmd::alwaysontop);
605 w->addcmd(ID_MENU_ABOUT, new cmd::about);
606 if (string(setting::preferences("mascot")["tray"]).empty()) {
607 w->execute(ID_MENU_ABOUT);
608 }
609 return w.release();
610 }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26