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