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 SetPosition(); 194 195 reply.AddInt32("error", B_OK); 196 } else 197 reply.AddInt32("error", B_NOT_ALLOWED); 198 } else { 199 reply.what = B_MESSAGE_NOT_UNDERSTOOD; 200 reply.AddInt32("error", B_ERROR); 201 } 202 203 message->SendReply(&reply); 204 break; 205 } 206 case kRemoveView: 207 { 208 NotificationView* view = NULL; 209 if (message->FindPointer("view", (void**)&view) != B_OK) 210 return; 211 212 views_t::iterator it = find(fViews.begin(), fViews.end(), view); 213 214 if (it != fViews.end()) 215 fViews.erase(it); 216 217 _ResizeAll(); 218 SetPosition(); 219 break; 220 } 221 default: 222 BWindow::MessageReceived(message); 223 } 224 } 225 226 227 BHandler* 228 NotificationWindow::ResolveSpecifier(BMessage* msg, int32 index, 229 BMessage* spec, int32 form, const char* prop) 230 { 231 BPropertyInfo prop_info(main_prop_list); 232 BHandler* handler = NULL; 233 234 if (strcmp(prop,"message") == 0) { 235 switch (msg->what) { 236 case B_CREATE_PROPERTY: 237 { 238 msg->PopSpecifier(); 239 handler = this; 240 break; 241 } 242 case B_SET_PROPERTY: 243 case B_GET_PROPERTY: 244 { 245 int32 i; 246 247 if (spec->FindInt32("index", &i) != B_OK) 248 i = -1; 249 250 if (i >= 0 && i < (int32)fViews.size()) { 251 msg->PopSpecifier(); 252 handler = fViews[i]; 253 } else 254 handler = NULL; 255 break; 256 } 257 case B_COUNT_PROPERTIES: 258 msg->PopSpecifier(); 259 handler = this; 260 break; 261 default: 262 break; 263 } 264 } 265 266 if (!handler) 267 handler = BWindow::ResolveSpecifier(msg, index, spec, form, prop); 268 269 return handler; 270 } 271 272 273 icon_size 274 NotificationWindow::IconSize() 275 { 276 return fIconSize; 277 } 278 279 280 int32 281 NotificationWindow::Timeout() 282 { 283 return fTimeout; 284 } 285 286 287 float 288 NotificationWindow::Width() 289 { 290 return fWidth; 291 } 292 293 294 void 295 NotificationWindow::_ResizeAll() 296 { 297 if (fAppViews.empty()) { 298 if (!IsHidden()) 299 Hide(); 300 return; 301 } 302 303 appview_t::iterator aIt; 304 bool shouldHide = true; 305 306 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 307 AppGroupView* app = aIt->second; 308 if (app->HasChildren()) { 309 shouldHide = false; 310 break; 311 } 312 } 313 314 if (shouldHide) { 315 if (!IsHidden()) 316 Hide(); 317 return; 318 } 319 320 if (IsHidden()) 321 Show(); 322 323 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 324 AppGroupView* view = aIt->second; 325 326 if (!view->HasChildren()) { 327 if (!view->IsHidden()) 328 view->Hide(); 329 } else { 330 if (view->IsHidden()) 331 view->Show(); 332 } 333 } 334 } 335 336 337 void 338 NotificationWindow::SetPosition() 339 { 340 Layout(true); 341 342 BRect bounds = DecoratorFrame(); 343 float width = Bounds().Width() + 1; 344 float height = Bounds().Height() + 1; 345 346 float leftOffset = Frame().left - bounds.left; 347 float topOffset = Frame().top - bounds.top + 1; 348 float rightOffset = bounds.right - Frame().right; 349 float bottomOffset = bounds.bottom - Frame().bottom; 350 // Size of the borders around the window 351 352 float x = Frame().left, y = Frame().top; 353 // If we can't guess, don't move... 354 355 BDeskbar deskbar; 356 BRect frame = deskbar.Frame(); 357 358 switch (deskbar.Location()) { 359 case B_DESKBAR_TOP: 360 // Put it just under, top right corner 361 y = frame.bottom + topOffset; 362 x = frame.right - width + rightOffset; 363 break; 364 case B_DESKBAR_BOTTOM: 365 // Put it just above, lower left corner 366 y = frame.top - height - bottomOffset; 367 x = frame.right - width + rightOffset; 368 break; 369 case B_DESKBAR_RIGHT_TOP: 370 x = frame.left - width - rightOffset; 371 y = frame.top - topOffset; 372 break; 373 case B_DESKBAR_LEFT_TOP: 374 x = frame.right + leftOffset; 375 y = frame.top - topOffset; 376 break; 377 case B_DESKBAR_RIGHT_BOTTOM: 378 y = frame.bottom - height + bottomOffset; 379 x = frame.left - width - rightOffset; 380 break; 381 case B_DESKBAR_LEFT_BOTTOM: 382 y = frame.bottom - height + bottomOffset; 383 x = frame.right + leftOffset; 384 break; 385 default: 386 break; 387 } 388 389 MoveTo(x, y); 390 } 391 392 393 void 394 NotificationWindow::_LoadSettings(bool startMonitor) 395 { 396 _LoadGeneralSettings(startMonitor); 397 _LoadDisplaySettings(startMonitor); 398 } 399 400 401 void 402 NotificationWindow::_LoadAppFilters(bool startMonitor) 403 { 404 BPath path; 405 406 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 407 return; 408 409 path.Append(kSettingsDirectory); 410 411 if (create_directory(path.Path(), 0755) != B_OK) 412 return; 413 414 path.Append(kFiltersSettings); 415 416 BFile file(path.Path(), B_READ_ONLY); 417 BMessage settings; 418 if (settings.Unflatten(&file) != B_OK) 419 return; 420 421 type_code type; 422 int32 count = 0; 423 424 if (settings.GetInfo("app_usage", &type, &count) != B_OK) 425 return; 426 427 for (int32 i = 0; i < count; i++) { 428 AppUsage* app = new AppUsage(); 429 settings.FindFlat("app_usage", i, app); 430 fAppFilters[app->Name()] = app; 431 } 432 433 if (startMonitor) { 434 node_ref nref; 435 BEntry entry(path.Path()); 436 entry.GetNodeRef(&nref); 437 438 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 439 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 440 B_TRANSLATE("Couldn't start filter monitor." 441 " Live filter changes disabled."), B_TRANSLATE("Darn.")); 442 alert->Go(); 443 } 444 } 445 } 446 447 448 void 449 NotificationWindow::_SaveAppFilters() 450 { 451 BPath path; 452 453 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 454 return; 455 456 path.Append(kSettingsDirectory); 457 path.Append(kFiltersSettings); 458 459 BMessage settings; 460 BFile file(path.Path(), B_WRITE_ONLY); 461 462 appfilter_t::iterator fIt; 463 for (fIt = fAppFilters.begin(); fIt != fAppFilters.end(); fIt++) 464 settings.AddFlat("app_usage", fIt->second); 465 466 settings.Flatten(&file); 467 } 468 469 470 void NotificationWindow::Show() 471 { 472 BWindow::Show(); 473 SetPosition(); 474 } 475 476 477 void 478 NotificationWindow::_LoadGeneralSettings(bool startMonitor) 479 { 480 BPath path; 481 BMessage settings; 482 483 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 484 return; 485 486 path.Append(kSettingsDirectory); 487 if (create_directory(path.Path(), 0755) == B_OK) { 488 path.Append(kGeneralSettings); 489 490 BFile file(path.Path(), B_READ_ONLY); 491 settings.Unflatten(&file); 492 } 493 494 if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK) 495 fTimeout = kDefaultTimeout; 496 497 // Notify the view about the change 498 views_t::iterator it; 499 for (it = fViews.begin(); it != fViews.end(); ++it) { 500 NotificationView* view = (*it); 501 view->Invalidate(); 502 } 503 504 if (startMonitor) { 505 node_ref nref; 506 BEntry entry(path.Path()); 507 entry.GetNodeRef(&nref); 508 509 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 510 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 511 B_TRANSLATE("Couldn't start general settings monitor.\n" 512 "Live filter changes disabled."), B_TRANSLATE("OK")); 513 alert->Go(); 514 } 515 } 516 } 517 518 519 void 520 NotificationWindow::_LoadDisplaySettings(bool startMonitor) 521 { 522 BPath path; 523 BMessage settings; 524 525 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 526 return; 527 528 path.Append(kSettingsDirectory); 529 if (create_directory(path.Path(), 0755) == B_OK) { 530 path.Append(kDisplaySettings); 531 532 BFile file(path.Path(), B_READ_ONLY); 533 settings.Unflatten(&file); 534 } 535 536 int32 setting; 537 538 if (settings.FindFloat(kWidthName, &fWidth) != B_OK) 539 fWidth = kDefaultWidth; 540 541 if (settings.FindInt32(kIconSizeName, &setting) != B_OK) 542 fIconSize = kDefaultIconSize; 543 else 544 fIconSize = (icon_size)setting; 545 546 // Notify the view about the change 547 views_t::iterator it; 548 for (it = fViews.begin(); it != fViews.end(); ++it) { 549 NotificationView* view = (*it); 550 view->Invalidate(); 551 } 552 553 if (startMonitor) { 554 node_ref nref; 555 BEntry entry(path.Path()); 556 entry.GetNodeRef(&nref); 557 558 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 559 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 560 B_TRANSLATE("Couldn't start display settings monitor.\n" 561 "Live filter changes disabled."), B_TRANSLATE("OK")); 562 alert->Go(); 563 } 564 } 565 } 566