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 <File.h> 23 #include <GroupLayout.h> 24 #include <GroupLayoutBuilder.h> 25 #include <Layout.h> 26 #include <NodeMonitor.h> 27 #include <Path.h> 28 #include <PropertyInfo.h> 29 #include <private/interface/WindowPrivate.h> 30 31 #include "AppGroupView.h" 32 #include "AppUsage.h" 33 34 #undef B_TRANSLATE_CONTEXT 35 #define B_TRANSLATE_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 = 8; 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 _LoadAppFilters(true); 69 70 // Start the message loop 71 Hide(); 72 Show(); 73 } 74 75 76 NotificationWindow::~NotificationWindow() 77 { 78 appfilter_t::iterator aIt; 79 for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++) 80 delete aIt->second; 81 } 82 83 84 bool 85 NotificationWindow::QuitRequested() 86 { 87 appview_t::iterator aIt; 88 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 89 aIt->second->RemoveSelf(); 90 delete aIt->second; 91 } 92 93 BMessenger(be_app).SendMessage(B_QUIT_REQUESTED); 94 return BWindow::QuitRequested(); 95 } 96 97 98 void 99 NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active) 100 { 101 // Ensure window is in the correct position 102 if (active) 103 _ResizeAll(); 104 } 105 106 107 void 108 NotificationWindow::MessageReceived(BMessage* message) 109 { 110 switch (message->what) { 111 case B_NODE_MONITOR: 112 { 113 _LoadSettings(); 114 _LoadAppFilters(); 115 break; 116 } 117 case B_COUNT_PROPERTIES: 118 { 119 BMessage reply(B_REPLY); 120 BMessage specifier; 121 const char* property = NULL; 122 bool messageOkay = true; 123 124 if (message->FindMessage("specifiers", 0, &specifier) != B_OK) 125 messageOkay = false; 126 if (specifier.FindString("property", &property) != B_OK) 127 messageOkay = false; 128 if (strcmp(property, "message") != 0) 129 messageOkay = false; 130 131 if (messageOkay) 132 reply.AddInt32("result", fViews.size()); 133 else { 134 reply.what = B_MESSAGE_NOT_UNDERSTOOD; 135 reply.AddInt32("error", B_ERROR); 136 } 137 138 message->SendReply(&reply); 139 break; 140 } 141 case B_CREATE_PROPERTY: 142 case kNotificationMessage: 143 { 144 BMessage reply(B_REPLY); 145 BNotification* notification = new BNotification(message); 146 147 if (notification->InitCheck() == B_OK) { 148 bigtime_t timeout; 149 if (message->FindInt64("timeout", &timeout) != B_OK) 150 timeout = -1; 151 BMessenger messenger = message->ReturnAddress(); 152 app_info info; 153 154 if (messenger.IsValid()) 155 be_roster->GetRunningAppInfo(messenger.Team(), &info); 156 else 157 be_roster->GetAppInfo("application/x-vnd.Be-SHEL", &info); 158 159 NotificationView* view = new NotificationView(this, 160 notification, timeout); 161 162 bool allow = false; 163 appfilter_t::iterator it = fAppFilters.find(info.signature); 164 165 if (it == fAppFilters.end()) { 166 AppUsage* appUsage = new AppUsage(notification->Group(), 167 true); 168 169 appUsage->Allowed(notification->Title(), 170 notification->Type()); 171 fAppFilters[info.signature] = appUsage; 172 allow = true; 173 } else { 174 allow = it->second->Allowed(notification->Title(), 175 notification->Type()); 176 } 177 178 if (allow) { 179 BString groupName(notification->Group()); 180 appview_t::iterator aIt = fAppViews.find(groupName); 181 AppGroupView* group = NULL; 182 if (aIt == fAppViews.end()) { 183 group = new AppGroupView(this, 184 groupName == "" ? NULL : groupName.String()); 185 fAppViews[groupName] = group; 186 GetLayout()->AddView(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_NOT_ALLOWED); 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 NotificationView* view = NULL; 208 if (message->FindPointer("view", (void**)&view) != B_OK) 209 return; 210 211 views_t::iterator it = find(fViews.begin(), fViews.end(), view); 212 213 if (it != fViews.end()) 214 fViews.erase(it); 215 216 _ResizeAll(); 217 break; 218 } 219 default: 220 BWindow::MessageReceived(message); 221 } 222 } 223 224 225 BHandler* 226 NotificationWindow::ResolveSpecifier(BMessage* msg, int32 index, 227 BMessage* spec, int32 form, const char* prop) 228 { 229 BPropertyInfo prop_info(main_prop_list); 230 BHandler* handler = NULL; 231 232 if (strcmp(prop,"message") == 0) { 233 switch (msg->what) { 234 case B_CREATE_PROPERTY: 235 { 236 msg->PopSpecifier(); 237 handler = this; 238 break; 239 } 240 case B_SET_PROPERTY: 241 case B_GET_PROPERTY: 242 { 243 int32 i; 244 245 if (spec->FindInt32("index", &i) != B_OK) 246 i = -1; 247 248 if (i >= 0 && i < (int32)fViews.size()) { 249 msg->PopSpecifier(); 250 handler = fViews[i]; 251 } else 252 handler = NULL; 253 break; 254 } 255 case B_COUNT_PROPERTIES: 256 msg->PopSpecifier(); 257 handler = this; 258 break; 259 default: 260 break; 261 } 262 } 263 264 if (!handler) 265 handler = BWindow::ResolveSpecifier(msg, index, spec, form, prop); 266 267 return handler; 268 } 269 270 271 icon_size 272 NotificationWindow::IconSize() 273 { 274 return fIconSize; 275 } 276 277 278 int32 279 NotificationWindow::Timeout() 280 { 281 return fTimeout; 282 } 283 284 285 float 286 NotificationWindow::Width() 287 { 288 return fWidth; 289 } 290 291 292 void 293 NotificationWindow::_ResizeAll() 294 { 295 appview_t::iterator aIt; 296 bool shouldHide = true; 297 298 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 299 AppGroupView* app = aIt->second; 300 if (app->HasChildren()) { 301 shouldHide = false; 302 break; 303 } 304 } 305 306 if (shouldHide) { 307 if (!IsHidden()) 308 Hide(); 309 return; 310 } 311 312 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 313 AppGroupView* view = aIt->second; 314 315 if (!view->HasChildren()) { 316 if (!view->IsHidden()) 317 view->Hide(); 318 } else { 319 if (view->IsHidden()) 320 view->Show(); 321 } 322 } 323 324 SetPosition(); 325 326 if (IsHidden()) 327 Show(); 328 } 329 330 331 void 332 NotificationWindow::SetPosition() 333 { 334 Layout(true); 335 336 BRect bounds = DecoratorFrame(); 337 float width = Bounds().Width() + 1; 338 float height = Bounds().Height() + 1; 339 340 float leftOffset = Frame().left - bounds.left; 341 float topOffset = Frame().top - bounds.top + 1; 342 float rightOffset = bounds.right - Frame().right; 343 float bottomOffset = bounds.bottom - Frame().bottom; 344 // Size of the borders around the window 345 346 float x = Frame().left, y = Frame().top; 347 // If we can't guess, don't move... 348 349 BDeskbar deskbar; 350 BRect frame = deskbar.Frame(); 351 352 switch (deskbar.Location()) { 353 case B_DESKBAR_TOP: 354 // Put it just under, top right corner 355 y = frame.bottom + topOffset; 356 x = frame.right - width + rightOffset; 357 break; 358 case B_DESKBAR_BOTTOM: 359 // Put it just above, lower left corner 360 y = frame.top - height - bottomOffset; 361 x = frame.right - width + rightOffset; 362 break; 363 case B_DESKBAR_RIGHT_TOP: 364 x = frame.left - width - rightOffset; 365 y = frame.top - topOffset; 366 break; 367 case B_DESKBAR_LEFT_TOP: 368 x = frame.right + leftOffset; 369 y = frame.top - topOffset; 370 break; 371 case B_DESKBAR_RIGHT_BOTTOM: 372 y = frame.bottom - height + bottomOffset; 373 x = frame.left - width - rightOffset; 374 break; 375 case B_DESKBAR_LEFT_BOTTOM: 376 y = frame.bottom - height + bottomOffset; 377 x = frame.right + leftOffset; 378 break; 379 default: 380 break; 381 } 382 383 MoveTo(x, y); 384 } 385 386 387 void 388 NotificationWindow::_LoadSettings(bool startMonitor) 389 { 390 _LoadGeneralSettings(startMonitor); 391 _LoadDisplaySettings(startMonitor); 392 } 393 394 395 void 396 NotificationWindow::_LoadAppFilters(bool startMonitor) 397 { 398 BPath path; 399 400 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 401 return; 402 403 path.Append(kSettingsDirectory); 404 405 if (create_directory(path.Path(), 0755) != B_OK) 406 return; 407 408 path.Append(kFiltersSettings); 409 410 BFile file(path.Path(), B_READ_ONLY); 411 BMessage settings; 412 if (settings.Unflatten(&file) != B_OK) 413 return; 414 415 type_code type; 416 int32 count = 0; 417 418 if (settings.GetInfo("app_usage", &type, &count) != B_OK) 419 return; 420 421 for (int32 i = 0; i < count; i++) { 422 AppUsage* app = new AppUsage(); 423 settings.FindFlat("app_usage", i, app); 424 fAppFilters[app->Name()] = app; 425 } 426 427 if (startMonitor) { 428 node_ref nref; 429 BEntry entry(path.Path()); 430 entry.GetNodeRef(&nref); 431 432 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 433 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 434 B_TRANSLATE("Couldn't start filter monitor." 435 " Live filter changes disabled."), B_TRANSLATE("Darn.")); 436 alert->Go(); 437 } 438 } 439 } 440 441 442 void 443 NotificationWindow::_SaveAppFilters() 444 { 445 BPath path; 446 447 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 448 return; 449 450 path.Append(kSettingsDirectory); 451 path.Append(kFiltersSettings); 452 453 BMessage settings; 454 BFile file(path.Path(), B_WRITE_ONLY); 455 456 appfilter_t::iterator fIt; 457 for (fIt = fAppFilters.begin(); fIt != fAppFilters.end(); fIt++) 458 settings.AddFlat("app_usage", fIt->second); 459 460 settings.Flatten(&file); 461 } 462 463 464 void 465 NotificationWindow::_LoadGeneralSettings(bool startMonitor) 466 { 467 BPath path; 468 BMessage settings; 469 470 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 471 return; 472 473 path.Append(kSettingsDirectory); 474 if (create_directory(path.Path(), 0755) == B_OK) { 475 path.Append(kGeneralSettings); 476 477 BFile file(path.Path(), B_READ_ONLY); 478 settings.Unflatten(&file); 479 } 480 481 if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK) 482 fTimeout = kDefaultTimeout; 483 484 // Notify the view about the change 485 views_t::iterator it; 486 for (it = fViews.begin(); it != fViews.end(); ++it) { 487 NotificationView* view = (*it); 488 view->Invalidate(); 489 } 490 491 if (startMonitor) { 492 node_ref nref; 493 BEntry entry(path.Path()); 494 entry.GetNodeRef(&nref); 495 496 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 497 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 498 B_TRANSLATE("Couldn't start general settings monitor.\n" 499 "Live filter changes disabled."), B_TRANSLATE("OK")); 500 alert->Go(); 501 } 502 } 503 } 504 505 506 void 507 NotificationWindow::_LoadDisplaySettings(bool startMonitor) 508 { 509 BPath path; 510 BMessage settings; 511 512 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 513 return; 514 515 path.Append(kSettingsDirectory); 516 if (create_directory(path.Path(), 0755) == B_OK) { 517 path.Append(kDisplaySettings); 518 519 BFile file(path.Path(), B_READ_ONLY); 520 settings.Unflatten(&file); 521 } 522 523 int32 setting; 524 525 if (settings.FindFloat(kWidthName, &fWidth) != B_OK) 526 fWidth = kDefaultWidth; 527 528 if (settings.FindInt32(kIconSizeName, &setting) != B_OK) 529 fIconSize = kDefaultIconSize; 530 else 531 fIconSize = (icon_size)setting; 532 533 // Notify the view about the change 534 views_t::iterator it; 535 for (it = fViews.begin(); it != fViews.end(); ++it) { 536 NotificationView* view = (*it); 537 view->Invalidate(); 538 } 539 540 if (startMonitor) { 541 node_ref nref; 542 BEntry entry(path.Path()); 543 entry.GetNodeRef(&nref); 544 545 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 546 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 547 B_TRANSLATE("Couldn't start display settings monitor.\n" 548 "Live filter changes disabled."), B_TRANSLATE("OK")); 549 alert->Go(); 550 } 551 } 552 } 553