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