xref: /haiku/src/servers/notification/NotificationWindow.cpp (revision 922e7ba1f3228e6f28db69b0ded8f86eb32dea17)
1 /*
2  * Copyright 2010, Haiku, Inc. All Rights Reserved.
3  * Copyright 2008-2009, Pier Luigi Fiorini. All Rights Reserved.
4  * Copyright 2004-2008, Michael Davidson. All Rights Reserved.
5  * Copyright 2004-2007, Mikael Eiman. All Rights Reserved.
6  * Distributed under the terms of the MIT License.
7  *
8  * Authors:
9  *		Michael Davidson, slaad@bong.com.au
10  *		Mikael Eiman, mikael@eiman.tv
11  *		Pier Luigi Fiorini, pierluigi.fiorini@gmail.com
12  */
13 
14 
15 #include "NotificationWindow.h"
16 
17 #include <algorithm>
18 
19 #include <Alert.h>
20 #include <Application.h>
21 #include <Catalog.h>
22 #include <Debug.h>
23 #include <File.h>
24 #include <NodeMonitor.h>
25 #include <PropertyInfo.h>
26 
27 #include "AppGroupView.h"
28 #include "AppUsage.h"
29 #include "BorderView.h"
30 
31 #undef B_TRANSLATE_CONTEXT
32 #define B_TRANSLATE_CONTEXT "NotificationWindow"
33 
34 
35 property_info main_prop_list[] = {
36 	{"message", {B_GET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0},
37 		"get a message"},
38 	{"message", {B_COUNT_PROPERTIES, 0}, {B_DIRECT_SPECIFIER, 0},
39 		"count messages"},
40 	{"message", {B_CREATE_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0},
41 		"create a message"},
42 	{"message", {B_SET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0},
43 		"modify a message"},
44 	{0}
45 };
46 
47 
48 const float kCloseSize				= 8;
49 const float kExpandSize				= 8;
50 const float kPenSize				= 1;
51 const float kEdgePadding			= 5;
52 const float kSmallPadding			= 2;
53 
54 NotificationWindow::NotificationWindow()
55 	:
56 	BWindow(BRect(10, 10, 30, 30), B_TRANSLATE_MARK("Notification"),
57 		B_BORDERED_WINDOW, B_AVOID_FRONT | B_AVOID_FOCUS | B_NOT_CLOSABLE
58 		| B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_NOT_RESIZABLE,
59 		B_ALL_WORKSPACES)
60 {
61 	fBorder = new BorderView(Bounds(), "Notification");
62 
63 	AddChild(fBorder);
64 
65 	Show();
66 	Hide();
67 
68 	LoadSettings(true);
69 	LoadAppFilters(true);
70 }
71 
72 
73 NotificationWindow::~NotificationWindow()
74 {
75 	appfilter_t::iterator aIt;
76 	for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++)
77 		delete aIt->second;
78 }
79 
80 
81 bool
82 NotificationWindow::QuitRequested()
83 {
84 	appview_t::iterator aIt;
85 	for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) {
86 		aIt->second->RemoveSelf();
87 		delete aIt->second;
88 	}
89 
90 	BMessenger(be_app).SendMessage(B_QUIT_REQUESTED);
91 	return BWindow::QuitRequested();
92 }
93 
94 
95 void
96 NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active)
97 {
98 	// Ensure window is in the correct position
99 	if (active)
100 		ResizeAll();
101 }
102 
103 
104 void
105 NotificationWindow::MessageReceived(BMessage* message)
106 {
107 	switch (message->what) {
108 		case B_NODE_MONITOR:
109 		{
110 			LoadSettings();
111 			LoadAppFilters();
112 			break;
113 		}
114 		case kResizeToFit:
115 			ResizeAll();
116 			break;
117 		case B_COUNT_PROPERTIES:
118 		{
119 			BMessage reply(B_REPLY);
120 			BMessage specifier;
121 			const char* property = NULL;
122 			bool messageOkay = true;
123 
124 			if (message->FindMessage("specifiers", 0, &specifier) != B_OK)
125 				messageOkay = false;
126 			if (specifier.FindString("property", &property) != B_OK)
127 				messageOkay = false;
128 			if (strcmp(property, "message") != 0)
129 				messageOkay = false;
130 
131 			if (messageOkay)
132 				reply.AddInt32("result", fViews.size());
133 			else {
134 				reply.what = B_MESSAGE_NOT_UNDERSTOOD;
135 				reply.AddInt32("error", B_ERROR);
136 			}
137 
138 			message->SendReply(&reply);
139 			break;
140 		}
141 		case B_CREATE_PROPERTY:
142 		case kNotificationMessage:
143 		{
144 			int32 type;
145 			const char* content = NULL;
146 			const char* title = NULL;
147 			const char* app = NULL;
148 			BMessage reply(B_REPLY);
149 			bool messageOkay = true;
150 
151 			if (message->FindInt32("type", &type) != B_OK)
152 				type = B_INFORMATION_NOTIFICATION;
153 			if (message->FindString("content", &content) != B_OK)
154 				messageOkay = false;
155 			if (message->FindString("title", &title) != B_OK)
156 				messageOkay = false;
157 			if (message->FindString("app", &app) != B_OK
158 				&& message->FindString("appTitle", &app) != B_OK)
159 				messageOkay = false;
160 
161 			if (messageOkay) {
162 				NotificationView* view = new NotificationView(this,
163 					(notification_type)type, app, title, content,
164 					new BMessage(*message));
165 
166 				appfilter_t::iterator fIt = fAppFilters.find(app);
167 				bool allow = false;
168 				if (fIt == fAppFilters.end()) {
169 					app_info info;
170 					BMessenger messenger = message->ReturnAddress();
171 					if (messenger.IsValid())
172 						be_roster->GetRunningAppInfo(messenger.Team(), &info);
173 					else
174 						be_roster->GetAppInfo("application/x-vnd.Be-SHEL", &info);
175 
176 					AppUsage* appUsage = new AppUsage(info.ref, app, true);
177 					fAppFilters[app] = appUsage;
178 
179 					appUsage->Allowed(title, (notification_type)type);
180 
181 					allow = true;
182 				} else
183 					allow = fIt->second->Allowed(title, (notification_type)type);
184 
185 				if (allow) {
186 					appview_t::iterator aIt = fAppViews.find(app);
187 					AppGroupView* group = NULL;
188 					if (aIt == fAppViews.end()) {
189 						group = new AppGroupView(this, app);
190 						fAppViews[app] = group;
191 						fBorder->AddChild(group);
192 					} else
193 						group = aIt->second;
194 
195 					group->AddInfo(view);
196 
197 					ResizeAll();
198 
199 					reply.AddInt32("error", B_OK);
200 				} else
201 					reply.AddInt32("error", B_NOT_ALLOWED);
202 			} else {
203 				reply.what = B_MESSAGE_NOT_UNDERSTOOD;
204 				reply.AddInt32("error", B_ERROR);
205 			}
206 
207 			message->SendReply(&reply);
208 			break;
209 		}
210 		case kRemoveView:
211 		{
212 			void* _ptr;
213 			message->FindPointer("view", &_ptr);
214 
215 			NotificationView* info
216 				= reinterpret_cast<NotificationView*>(_ptr);
217 
218 			fBorder->RemoveChild(info);
219 
220 			std::vector<NotificationView*>::iterator i
221 				= find(fViews.begin(), fViews.end(), info);
222 			if (i != fViews.end())
223 				fViews.erase(i);
224 
225 			delete info;
226 
227 			ResizeAll();
228 			break;
229 		}
230 		default:
231 			BWindow::MessageReceived(message);
232 	}
233 }
234 
235 
236 BHandler*
237 NotificationWindow::ResolveSpecifier(BMessage* msg, int32 index,
238 	BMessage* spec, int32 form, const char* prop)
239 {
240 	BPropertyInfo prop_info(main_prop_list);
241 	BHandler* handler = NULL;
242 
243 	if (strcmp(prop,"message") == 0) {
244 		switch (msg->what) {
245 			case B_CREATE_PROPERTY:
246 			{
247 				msg->PopSpecifier();
248 				handler = this;
249 				break;
250 			}
251 			case B_SET_PROPERTY:
252 			case B_GET_PROPERTY:
253 			{
254 				int32 i;
255 
256 				if (spec->FindInt32("index", &i) != B_OK)
257 					i = -1;
258 
259 				if (i >= 0 && i < (int32)fViews.size()) {
260 					msg->PopSpecifier();
261 					handler = fViews[i];
262 				} else
263 					handler = NULL;
264 				break;
265 			}
266 			case B_COUNT_PROPERTIES:
267 				msg->PopSpecifier();
268 				handler = this;
269 				break;
270 			default:
271 				break;
272 		}
273 	}
274 
275 	if (!handler)
276 		handler = BWindow::ResolveSpecifier(msg, index, spec, form, prop);
277 
278 	return handler;
279 }
280 
281 
282 icon_size
283 NotificationWindow::IconSize()
284 {
285 	return fIconSize;
286 }
287 
288 
289 int32
290 NotificationWindow::Timeout()
291 {
292 	return fTimeout;
293 }
294 
295 
296 infoview_layout
297 NotificationWindow::Layout()
298 {
299 	return fLayout;
300 }
301 
302 
303 float
304 NotificationWindow::ViewWidth()
305 {
306 	return fWidth;
307 }
308 
309 
310 void
311 NotificationWindow::ResizeAll()
312 {
313 	if (fAppViews.empty()) {
314 		if (!IsHidden())
315 			Hide();
316 		return;
317 	}
318 
319 	appview_t::iterator aIt;
320 	bool shouldHide = true;
321 
322 	for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) {
323 		AppGroupView* app = aIt->second;
324 		if (app->HasChildren()) {
325 			shouldHide = false;
326 			break;
327 		}
328 	}
329 
330 	if (shouldHide) {
331 		if (!IsHidden())
332 			Hide();
333 		return;
334 	}
335 
336 	if (IsHidden())
337 		Show();
338 
339 	float width = 0;
340 	float height = 0;
341 
342 	for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) {
343 		AppGroupView* view = aIt->second;
344 		float w = -1;
345 		float h = -1;
346 
347 		if (!view->HasChildren()) {
348 			if (!view->IsHidden())
349 				view->Hide();
350 		} else {
351 			view->GetPreferredSize(&w, &h);
352 			width = max_c(width, h);
353 
354 			view->ResizeToPreferred();
355 			view->MoveTo(0, height);
356 
357 			height += h;
358 
359 			if (view->IsHidden())
360 				view->Show();
361 		}
362 	}
363 
364 	ResizeTo(ViewWidth(), height);
365 	PopupAnimation(Bounds().Width(), Bounds().Height());
366 }
367 
368 
369 void
370 NotificationWindow::PopupAnimation(float width, float height)
371 {
372 	float x = 0, y = 0, sx, sy;
373 	float pad = 0;
374 	BDeskbar deskbar;
375 	BRect frame = deskbar.Frame();
376 
377 	switch (deskbar.Location()) {
378 		case B_DESKBAR_TOP:
379 			// Put it just under, top right corner
380 			sx = frame.right;
381 			sy = frame.bottom + pad;
382 			y = sy;
383 			x = sx - width - pad;
384 			break;
385 		case B_DESKBAR_BOTTOM:
386 			// Put it just above, lower left corner
387 			sx = frame.right;
388 			sy = frame.top - height - pad;
389 			y = sy;
390 			x = sx - width - pad;
391 			break;
392 		case B_DESKBAR_LEFT_TOP:
393 			// Put it just to the right of the deskbar
394 			sx = frame.right + pad;
395 			sy = frame.top - height;
396 			x = sx;
397 			y = frame.top + pad;
398 			break;
399 		case B_DESKBAR_RIGHT_TOP:
400 			// Put it just to the left of the deskbar
401 			sx = frame.left - width - pad;
402 			sy = frame.top - height;
403 			x = sx;
404 			y = frame.top + pad;
405 			break;
406 		case B_DESKBAR_LEFT_BOTTOM:
407 			// Put it to the right of the deskbar.
408 			sx = frame.right + pad;
409 			sy = frame.bottom;
410 			x = sx;
411 			y = sy - height - pad;
412 			break;
413 		case B_DESKBAR_RIGHT_BOTTOM:
414 			// Put it to the left of the deskbar.
415 			sx = frame.left - width - pad;
416 			sy = frame.bottom;
417 			y = sy - height - pad;
418 			x = sx;
419 			break;
420 		default:
421 			break;
422 	}
423 
424 	MoveTo(x, y);
425 
426 	if (IsHidden() && fViews.size() != 0)
427 		Show();
428 	// Activate();// it hides floaters from apps :-(
429 }
430 
431 
432 void
433 NotificationWindow::LoadSettings(bool startMonitor)
434 {
435 	_LoadGeneralSettings(startMonitor);
436 	_LoadDisplaySettings(startMonitor);
437 }
438 
439 
440 void
441 NotificationWindow::LoadAppFilters(bool startMonitor)
442 {
443 	BPath path;
444 
445 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
446 		return;
447 
448 	path.Append(kSettingsDirectory);
449 
450 	if (create_directory(path.Path(), 0755) != B_OK)
451 		return;
452 
453 	path.Append(kFiltersSettings);
454 
455 	BFile file(path.Path(), B_READ_ONLY);
456 	BMessage settings;
457 	if (settings.Unflatten(&file) != B_OK)
458 		return;
459 
460 	type_code type;
461 	int32 count = 0;
462 
463 	if (settings.GetInfo("app_usage", &type, &count) != B_OK)
464 		return;
465 
466 	for (int32 i = 0; i < count; i++) {
467 		AppUsage* app = new AppUsage();
468 		settings.FindFlat("app_usage", i, app);
469 		fAppFilters[app->Name()] = app;
470 	}
471 
472 	if (startMonitor) {
473 		node_ref nref;
474 		BEntry entry(path.Path());
475 		entry.GetNodeRef(&nref);
476 
477 		if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) {
478 			BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
479 					B_TRANSLATE("Couldn't start filter monitor."
480 						" Live filter changes disabled."), B_TRANSLATE("Darn."));
481 			alert->Go();
482 		}
483 	}
484 }
485 
486 
487 void
488 NotificationWindow::SaveAppFilters()
489 {
490 	BPath path;
491 
492 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
493 		return;
494 
495 	path.Append(kSettingsDirectory);
496 	path.Append(kFiltersSettings);
497 
498 	BMessage settings;
499 	BFile file(path.Path(), B_WRITE_ONLY);
500 
501 	appfilter_t::iterator fIt;
502 	for (fIt = fAppFilters.begin(); fIt != fAppFilters.end(); fIt++)
503 		settings.AddFlat("app_usage", fIt->second);
504 
505 	settings.Flatten(&file);
506 }
507 
508 
509 void
510 NotificationWindow::_LoadGeneralSettings(bool startMonitor)
511 {
512 	BPath path;
513 	BMessage settings;
514 
515 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
516 		return;
517 
518 	path.Append(kSettingsDirectory);
519 	if (create_directory(path.Path(), 0755) == B_OK) {
520 		path.Append(kGeneralSettings);
521 
522 		BFile file(path.Path(), B_READ_ONLY);
523 		settings.Unflatten(&file);
524 	}
525 
526 	if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK)
527 		fTimeout = kDefaultTimeout;
528 
529 	// Notify the view about the change
530 	views_t::iterator it;
531 	for (it = fViews.begin(); it != fViews.end(); ++it) {
532 		NotificationView* view = (*it);
533 		view->SetText(view->Application(), view->Title(), view->Text());
534 		view->Invalidate();
535 	}
536 
537 	if (startMonitor) {
538 		node_ref nref;
539 		BEntry entry(path.Path());
540 		entry.GetNodeRef(&nref);
541 
542 		if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) {
543 			BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
544 						B_TRANSLATE("Couldn't start general settings monitor.\n"
545 						"Live filter changes disabled."), B_TRANSLATE("OK"));
546 			alert->Go();
547 		}
548 	}
549 }
550 
551 
552 void
553 NotificationWindow::_LoadDisplaySettings(bool startMonitor)
554 {
555 	BPath path;
556 	BMessage settings;
557 
558 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
559 		return;
560 
561 	path.Append(kSettingsDirectory);
562 	if (create_directory(path.Path(), 0755) == B_OK) {
563 		path.Append(kDisplaySettings);
564 
565 		BFile file(path.Path(), B_READ_ONLY);
566 		settings.Unflatten(&file);
567 	}
568 
569 	int32 setting;
570 
571 	if (settings.FindFloat(kWidthName, &fWidth) != B_OK)
572 		fWidth = kDefaultWidth;
573 
574 	if (settings.FindInt32(kIconSizeName, &setting) != B_OK)
575 		fIconSize = kDefaultIconSize;
576 	else
577 		fIconSize = (icon_size)setting;
578 
579 	if (settings.FindInt32(kLayoutName, &setting) != B_OK)
580 		fLayout = kDefaultLayout;
581 	else {
582 		switch (setting) {
583 			case 0:
584 				fLayout = TitleAboveIcon;
585 				break;
586 			case 1:
587 				fLayout = AllTextRightOfIcon;
588 				break;
589 			default:
590 				fLayout = kDefaultLayout;
591 		}
592 	}
593 
594 	// Notify the view about the change
595 	views_t::iterator it;
596 	for (it = fViews.begin(); it != fViews.end(); ++it) {
597 		NotificationView* view = (*it);
598 		view->SetText(view->Application(), view->Title(), view->Text());
599 		view->Invalidate();
600 	}
601 
602 	if (startMonitor) {
603 		node_ref nref;
604 		BEntry entry(path.Path());
605 		entry.GetNodeRef(&nref);
606 
607 		if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) {
608 			BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
609 				B_TRANSLATE("Couldn't start display settings monitor.\n"
610 					"Live filter changes disabled."), B_TRANSLATE("OK"));
611 			alert->Go();
612 		}
613 	}
614 }
615