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