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