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