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