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