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