xref: /haiku/src/servers/notification/NotificationWindow.cpp (revision 85892ec52f476b254d75e2bb2e6560e72faa567c)
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 #include "NotificationWindow.h"
14 
15 #include <algorithm>
16 
17 #include <Alert.h>
18 #include <Application.h>
19 #include <Catalog.h>
20 #include <Deskbar.h>
21 #include <Directory.h>
22 #include <File.h>
23 #include <FindDirectory.h>
24 #include <GroupLayout.h>
25 #include <NodeMonitor.h>
26 #include <Notifications.h>
27 #include <Path.h>
28 #include <PropertyInfo.h>
29 
30 #include "AppGroupView.h"
31 #include "AppUsage.h"
32 
33 
34 #undef B_TRANSLATION_CONTEXT
35 #define B_TRANSLATION_CONTEXT "NotificationWindow"
36 
37 
38 property_info main_prop_list[] = {
39 	{"message", {B_GET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0},
40 		"get a message"},
41 	{"message", {B_COUNT_PROPERTIES, 0}, {B_DIRECT_SPECIFIER, 0},
42 		"count messages"},
43 	{"message", {B_CREATE_PROPERTY, 0}, {B_DIRECT_SPECIFIER, 0},
44 		"create a message"},
45 	{"message", {B_SET_PROPERTY, 0}, {B_INDEX_SPECIFIER, 0},
46 		"modify a message"},
47 	{0}
48 };
49 
50 
51 const float kCloseSize				= 6;
52 const float kExpandSize				= 8;
53 const float kPenSize				= 1;
54 const float kEdgePadding			= 2;
55 const float kSmallPadding			= 2;
56 
57 NotificationWindow::NotificationWindow()
58 	:
59 	BWindow(BRect(0, 0, -1, -1), B_TRANSLATE_MARK("Notification"),
60 		B_BORDERED_WINDOW_LOOK, B_FLOATING_ALL_WINDOW_FEEL, B_AVOID_FRONT
61 		| B_AVOID_FOCUS | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE
62 		| B_NOT_RESIZABLE | B_NOT_MOVABLE | B_AUTO_UPDATE_SIZE_LIMITS,
63 		B_ALL_WORKSPACES)
64 {
65 	SetLayout(new BGroupLayout(B_VERTICAL, 0));
66 
67 	_LoadSettings(true);
68 
69 	// Start the message loop
70 	Hide();
71 	Show();
72 }
73 
74 
75 NotificationWindow::~NotificationWindow()
76 {
77 	appfilter_t::iterator aIt;
78 	for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++)
79 		delete aIt->second;
80 }
81 
82 
83 bool
84 NotificationWindow::QuitRequested()
85 {
86 	appview_t::iterator aIt;
87 	for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) {
88 		aIt->second->RemoveSelf();
89 		delete aIt->second;
90 	}
91 
92 	BMessenger(be_app).SendMessage(B_QUIT_REQUESTED);
93 	return BWindow::QuitRequested();
94 }
95 
96 
97 void
98 NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active)
99 {
100 	// Ensure window is in the correct position
101 	if (active)
102 		SetPosition();
103 }
104 
105 
106 void
107 NotificationWindow::FrameResized(float width, float height)
108 {
109 	SetPosition();
110 }
111 
112 
113 void
114 NotificationWindow::ScreenChanged(BRect frame, color_space mode)
115 {
116 	SetPosition();
117 }
118 
119 
120 void
121 NotificationWindow::MessageReceived(BMessage* message)
122 {
123 	switch (message->what) {
124 		case B_NODE_MONITOR:
125 		{
126 			_LoadSettings();
127 			break;
128 		}
129 		case B_COUNT_PROPERTIES:
130 		{
131 			BMessage reply(B_REPLY);
132 			BMessage specifier;
133 			const char* property = NULL;
134 			bool messageOkay = true;
135 
136 			if (message->FindMessage("specifiers", 0, &specifier) != B_OK)
137 				messageOkay = false;
138 			if (specifier.FindString("property", &property) != B_OK)
139 				messageOkay = false;
140 			if (strcmp(property, "message") != 0)
141 				messageOkay = false;
142 
143 			if (messageOkay)
144 				reply.AddInt32("result", fViews.size());
145 			else {
146 				reply.what = B_MESSAGE_NOT_UNDERSTOOD;
147 				reply.AddInt32("error", B_ERROR);
148 			}
149 
150 			message->SendReply(&reply);
151 			break;
152 		}
153 		case B_CREATE_PROPERTY:
154 		case kNotificationMessage:
155 		{
156 			BMessage reply(B_REPLY);
157 			BNotification* notification = new BNotification(message);
158 
159 			if (notification->InitCheck() == B_OK) {
160 				bigtime_t timeout;
161 				if (message->FindInt64("timeout", &timeout) != B_OK)
162 					timeout = -1;
163 				BMessenger messenger = message->ReturnAddress();
164 				app_info info;
165 
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 				NotificationView* view = new NotificationView(this,
172 					notification, timeout);
173 
174 				bool allow = false;
175 				appfilter_t::iterator it = fAppFilters.find(info.signature);
176 
177 				if (it == fAppFilters.end()) {
178 					AppUsage* appUsage = new AppUsage(notification->Group(),
179 						true);
180 
181 					appUsage->Allowed(notification->Title(),
182 							notification->Type());
183 					fAppFilters[info.signature] = appUsage;
184 					allow = true;
185 				} else {
186 					allow = it->second->Allowed(notification->Title(),
187 						notification->Type());
188 				}
189 
190 				if (allow) {
191 					BString groupName(notification->Group());
192 					appview_t::iterator aIt = fAppViews.find(groupName);
193 					AppGroupView* group = NULL;
194 					if (aIt == fAppViews.end()) {
195 						group = new AppGroupView(this,
196 							groupName == "" ? NULL : groupName.String());
197 						fAppViews[groupName] = group;
198 						GetLayout()->AddView(group);
199 					} else
200 						group = aIt->second;
201 
202 					group->AddInfo(view);
203 
204 					_ShowHide();
205 
206 					reply.AddInt32("error", B_OK);
207 				} else
208 					reply.AddInt32("error", B_NOT_ALLOWED);
209 			} else {
210 				reply.what = B_MESSAGE_NOT_UNDERSTOOD;
211 				reply.AddInt32("error", B_ERROR);
212 			}
213 
214 			message->SendReply(&reply);
215 			break;
216 		}
217 		case kRemoveView:
218 		{
219 			NotificationView* view = NULL;
220 			if (message->FindPointer("view", (void**)&view) != B_OK)
221 				return;
222 
223 			views_t::iterator it = find(fViews.begin(), fViews.end(), view);
224 
225 			if (it != fViews.end())
226 				fViews.erase(it);
227 			break;
228 		}
229 		case kRemoveGroupView:
230 		{
231 			AppGroupView* view = NULL;
232 			if (message->FindPointer("view", (void**)&view) != B_OK)
233 				return;
234 
235 			// It's possible that between sending this message, and us receiving
236 			// it, the view has become used again, in which case we shouldn't
237 			// delete it.
238 			if (view->HasChildren())
239 				return;
240 
241 			// this shouldn't happen
242 			if (fAppViews.erase(view->Group()) < 1)
243 				break;
244 
245 			view->RemoveSelf();
246 			delete view;
247 
248 			_ShowHide();
249 			break;
250 		}
251 		default:
252 			BWindow::MessageReceived(message);
253 	}
254 }
255 
256 
257 BHandler*
258 NotificationWindow::ResolveSpecifier(BMessage* msg, int32 index,
259 	BMessage* spec, int32 form, const char* prop)
260 {
261 	BPropertyInfo prop_info(main_prop_list);
262 	BHandler* handler = NULL;
263 
264 	if (strcmp(prop,"message") == 0) {
265 		switch (msg->what) {
266 			case B_CREATE_PROPERTY:
267 			{
268 				msg->PopSpecifier();
269 				handler = this;
270 				break;
271 			}
272 			case B_SET_PROPERTY:
273 			case B_GET_PROPERTY:
274 			{
275 				int32 i;
276 
277 				if (spec->FindInt32("index", &i) != B_OK)
278 					i = -1;
279 
280 				if (i >= 0 && i < (int32)fViews.size()) {
281 					msg->PopSpecifier();
282 					handler = fViews[i];
283 				} else
284 					handler = NULL;
285 				break;
286 			}
287 			case B_COUNT_PROPERTIES:
288 				msg->PopSpecifier();
289 				handler = this;
290 				break;
291 			default:
292 				break;
293 		}
294 	}
295 
296 	if (!handler)
297 		handler = BWindow::ResolveSpecifier(msg, index, spec, form, prop);
298 
299 	return handler;
300 }
301 
302 
303 icon_size
304 NotificationWindow::IconSize()
305 {
306 	return fIconSize;
307 }
308 
309 
310 int32
311 NotificationWindow::Timeout()
312 {
313 	return fTimeout;
314 }
315 
316 
317 float
318 NotificationWindow::Width()
319 {
320 	return fWidth;
321 }
322 
323 
324 void
325 NotificationWindow::_ShowHide()
326 {
327 	if (fAppViews.empty() && !IsHidden()) {
328 		Hide();
329 		return;
330 	}
331 
332 	if (IsHidden()) {
333 		SetPosition();
334 		Show();
335 	}
336 }
337 
338 
339 void
340 NotificationWindow::NotificationViewSwapped(NotificationView* stale,
341 	NotificationView* fresh)
342 {
343 	views_t::iterator it = find(fViews.begin(), fViews.end(), stale);
344 
345 	if (it != fViews.end())
346 		*it = fresh;
347 }
348 
349 
350 void
351 NotificationWindow::SetPosition()
352 {
353 	Layout(true);
354 
355 	BRect bounds = DecoratorFrame();
356 	float width = Bounds().Width() + 1;
357 	float height = Bounds().Height() + 1;
358 
359 	float leftOffset = Frame().left - bounds.left;
360 	float topOffset = Frame().top - bounds.top + 1;
361 	float rightOffset = bounds.right - Frame().right;
362 	float bottomOffset = bounds.bottom - Frame().bottom;
363 		// Size of the borders around the window
364 
365 	float x = Frame().left, y = Frame().top;
366 		// If we can't guess, don't move...
367 
368 	BDeskbar deskbar;
369 	BRect frame = deskbar.Frame();
370 
371 	switch (deskbar.Location()) {
372 		case B_DESKBAR_TOP:
373 			// Put it just under, top right corner
374 			y = frame.bottom + topOffset;
375 			x = frame.right - width + rightOffset;
376 			break;
377 		case B_DESKBAR_BOTTOM:
378 			// Put it just above, lower left corner
379 			y = frame.top - height - bottomOffset;
380 			x = frame.right - width + rightOffset;
381 			break;
382 		case B_DESKBAR_RIGHT_TOP:
383 			x = frame.left - width - rightOffset;
384 			y = frame.top - topOffset + 1;
385 			break;
386 		case B_DESKBAR_LEFT_TOP:
387 			x = frame.right + leftOffset;
388 			y = frame.top - topOffset + 1;
389 			break;
390 		case B_DESKBAR_RIGHT_BOTTOM:
391 			y = frame.bottom - height + bottomOffset;
392 			x = frame.left - width - rightOffset;
393 			break;
394 		case B_DESKBAR_LEFT_BOTTOM:
395 			y = frame.bottom - height + bottomOffset;
396 			x = frame.right + leftOffset;
397 			break;
398 		default:
399 			break;
400 	}
401 
402 	MoveTo(x, y);
403 }
404 
405 
406 void
407 NotificationWindow::_LoadSettings(bool startMonitor)
408 {
409 	BPath path;
410 	BMessage settings;
411 
412 	if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK)
413 		return;
414 
415 	path.Append(kSettingsFile);
416 
417 	BFile file(path.Path(), B_READ_ONLY);
418 	settings.Unflatten(&file);
419 
420 	_LoadGeneralSettings(settings);
421 	_LoadDisplaySettings(settings);
422 	_LoadAppFilters(settings);
423 
424 	if (startMonitor) {
425 		node_ref nref;
426 		BEntry entry(path.Path());
427 		entry.GetNodeRef(&nref);
428 
429 		if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) {
430 			BAlert* alert = new BAlert(B_TRANSLATE("Warning"),
431 						B_TRANSLATE("Couldn't start general settings monitor.\n"
432 						"Live filter changes disabled."), B_TRANSLATE("OK"));
433 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
434 			alert->Go();
435 		}
436 	}
437 }
438 
439 
440 void
441 NotificationWindow::_LoadAppFilters(BMessage& settings)
442 {
443 	type_code type;
444 	int32 count = 0;
445 
446 	if (settings.GetInfo("app_usage", &type, &count) != B_OK)
447 		return;
448 
449 	for (int32 i = 0; i < count; i++) {
450 		AppUsage* app = new AppUsage();
451 		settings.FindFlat("app_usage", i, app);
452 		fAppFilters[app->Name()] = app;
453 	}
454 }
455 
456 
457 void
458 NotificationWindow::_LoadGeneralSettings(BMessage& settings)
459 {
460 	bool shouldRun;
461 	if (settings.FindBool(kAutoStartName, &shouldRun) == B_OK) {
462 		if (shouldRun == false) {
463 			// We should not start. Quit the app!
464 			be_app_messenger.SendMessage(B_QUIT_REQUESTED);
465 		}
466 	}
467 	if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK)
468 		fTimeout = kDefaultTimeout;
469 
470 	// Notify the view about the change
471 	views_t::iterator it;
472 	for (it = fViews.begin(); it != fViews.end(); ++it) {
473 		NotificationView* view = (*it);
474 		view->Invalidate();
475 	}
476 }
477 
478 
479 void
480 NotificationWindow::_LoadDisplaySettings(BMessage& settings)
481 {
482 	int32 setting;
483 
484 	if (settings.FindFloat(kWidthName, &fWidth) != B_OK)
485 		fWidth = kDefaultWidth;
486 	GetLayout()->SetExplicitMaxSize(BSize(fWidth, B_SIZE_UNSET));
487 	GetLayout()->SetExplicitMinSize(BSize(fWidth, B_SIZE_UNSET));
488 
489 	if (settings.FindInt32(kIconSizeName, &setting) != B_OK)
490 		fIconSize = kDefaultIconSize;
491 	else
492 		fIconSize = (icon_size)setting;
493 
494 	// Notify the view about the change
495 	views_t::iterator it;
496 	for (it = fViews.begin(); it != fViews.end(); ++it) {
497 		NotificationView* view = (*it);
498 		view->Invalidate();
499 	}
500 }
501