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 #include "NotificationWindow.h" 14 15 #include <algorithm> 16 17 #include <Alert.h> 18 #include <Application.h> 19 #include <Catalog.h> 20 #include <Deskbar.h> 21 #include <Directory.h> 22 #include <File.h> 23 #include <FindDirectory.h> 24 #include <GroupLayout.h> 25 #include <NodeMonitor.h> 26 #include <Notifications.h> 27 #include <Path.h> 28 #include <PropertyInfo.h> 29 30 #include "AppGroupView.h" 31 #include "AppUsage.h" 32 33 34 #undef B_TRANSLATION_CONTEXT 35 #define B_TRANSLATION_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 = 6; 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 69 // Start the message loop 70 Hide(); 71 Show(); 72 } 73 74 75 NotificationWindow::~NotificationWindow() 76 { 77 appfilter_t::iterator aIt; 78 for (aIt = fAppFilters.begin(); aIt != fAppFilters.end(); aIt++) 79 delete aIt->second; 80 } 81 82 83 bool 84 NotificationWindow::QuitRequested() 85 { 86 appview_t::iterator aIt; 87 for (aIt = fAppViews.begin(); aIt != fAppViews.end(); aIt++) { 88 aIt->second->RemoveSelf(); 89 delete aIt->second; 90 } 91 92 BMessenger(be_app).SendMessage(B_QUIT_REQUESTED); 93 return BWindow::QuitRequested(); 94 } 95 96 97 void 98 NotificationWindow::WorkspaceActivated(int32 /*workspace*/, bool active) 99 { 100 // Ensure window is in the correct position 101 if (active) 102 SetPosition(); 103 } 104 105 106 void 107 NotificationWindow::FrameResized(float width, float height) 108 { 109 SetPosition(); 110 } 111 112 113 void 114 NotificationWindow::ScreenChanged(BRect frame, color_space mode) 115 { 116 SetPosition(); 117 } 118 119 120 void 121 NotificationWindow::MessageReceived(BMessage* message) 122 { 123 switch (message->what) { 124 case B_NODE_MONITOR: 125 { 126 _LoadSettings(); 127 break; 128 } 129 case B_COUNT_PROPERTIES: 130 { 131 BMessage reply(B_REPLY); 132 BMessage specifier; 133 const char* property = NULL; 134 bool messageOkay = true; 135 136 if (message->FindMessage("specifiers", 0, &specifier) != B_OK) 137 messageOkay = false; 138 if (specifier.FindString("property", &property) != B_OK) 139 messageOkay = false; 140 if (strcmp(property, "message") != 0) 141 messageOkay = false; 142 143 if (messageOkay) 144 reply.AddInt32("result", fViews.size()); 145 else { 146 reply.what = B_MESSAGE_NOT_UNDERSTOOD; 147 reply.AddInt32("error", B_ERROR); 148 } 149 150 message->SendReply(&reply); 151 break; 152 } 153 case B_CREATE_PROPERTY: 154 case kNotificationMessage: 155 { 156 BMessage reply(B_REPLY); 157 BNotification* notification = new BNotification(message); 158 159 if (notification->InitCheck() == B_OK) { 160 bigtime_t timeout; 161 if (message->FindInt64("timeout", &timeout) != B_OK) 162 timeout = -1; 163 BMessenger messenger = message->ReturnAddress(); 164 app_info info; 165 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 NotificationView* view = new NotificationView(this, 172 notification, timeout); 173 174 bool allow = false; 175 appfilter_t::iterator it = fAppFilters.find(info.signature); 176 177 if (it == fAppFilters.end()) { 178 AppUsage* appUsage = new AppUsage(notification->Group(), 179 true); 180 181 appUsage->Allowed(notification->Title(), 182 notification->Type()); 183 fAppFilters[info.signature] = appUsage; 184 allow = true; 185 } else { 186 allow = it->second->Allowed(notification->Title(), 187 notification->Type()); 188 } 189 190 if (allow) { 191 BString groupName(notification->Group()); 192 appview_t::iterator aIt = fAppViews.find(groupName); 193 AppGroupView* group = NULL; 194 if (aIt == fAppViews.end()) { 195 group = new AppGroupView(this, 196 groupName == "" ? NULL : groupName.String()); 197 fAppViews[groupName] = group; 198 GetLayout()->AddView(group); 199 } else 200 group = aIt->second; 201 202 group->AddInfo(view); 203 204 _ShowHide(); 205 206 reply.AddInt32("error", B_OK); 207 } else 208 reply.AddInt32("error", B_NOT_ALLOWED); 209 } else { 210 reply.what = B_MESSAGE_NOT_UNDERSTOOD; 211 reply.AddInt32("error", B_ERROR); 212 } 213 214 message->SendReply(&reply); 215 break; 216 } 217 case kRemoveView: 218 { 219 NotificationView* view = NULL; 220 if (message->FindPointer("view", (void**)&view) != B_OK) 221 return; 222 223 views_t::iterator it = find(fViews.begin(), fViews.end(), view); 224 225 if (it != fViews.end()) 226 fViews.erase(it); 227 break; 228 } 229 case kRemoveGroupView: 230 { 231 AppGroupView* view = NULL; 232 if (message->FindPointer("view", (void**)&view) != B_OK) 233 return; 234 235 // It's possible that between sending this message, and us receiving 236 // it, the view has become used again, in which case we shouldn't 237 // delete it. 238 if (view->HasChildren()) 239 return; 240 241 // this shouldn't happen 242 if (fAppViews.erase(view->Group()) < 1) 243 break; 244 245 view->RemoveSelf(); 246 delete view; 247 248 _ShowHide(); 249 break; 250 } 251 default: 252 BWindow::MessageReceived(message); 253 } 254 } 255 256 257 BHandler* 258 NotificationWindow::ResolveSpecifier(BMessage* msg, int32 index, 259 BMessage* spec, int32 form, const char* prop) 260 { 261 BPropertyInfo prop_info(main_prop_list); 262 BHandler* handler = NULL; 263 264 if (strcmp(prop,"message") == 0) { 265 switch (msg->what) { 266 case B_CREATE_PROPERTY: 267 { 268 msg->PopSpecifier(); 269 handler = this; 270 break; 271 } 272 case B_SET_PROPERTY: 273 case B_GET_PROPERTY: 274 { 275 int32 i; 276 277 if (spec->FindInt32("index", &i) != B_OK) 278 i = -1; 279 280 if (i >= 0 && i < (int32)fViews.size()) { 281 msg->PopSpecifier(); 282 handler = fViews[i]; 283 } else 284 handler = NULL; 285 break; 286 } 287 case B_COUNT_PROPERTIES: 288 msg->PopSpecifier(); 289 handler = this; 290 break; 291 default: 292 break; 293 } 294 } 295 296 if (!handler) 297 handler = BWindow::ResolveSpecifier(msg, index, spec, form, prop); 298 299 return handler; 300 } 301 302 303 icon_size 304 NotificationWindow::IconSize() 305 { 306 return fIconSize; 307 } 308 309 310 int32 311 NotificationWindow::Timeout() 312 { 313 return fTimeout; 314 } 315 316 317 float 318 NotificationWindow::Width() 319 { 320 return fWidth; 321 } 322 323 324 void 325 NotificationWindow::_ShowHide() 326 { 327 if (fAppViews.empty() && !IsHidden()) { 328 Hide(); 329 return; 330 } 331 332 if (IsHidden()) { 333 SetPosition(); 334 Show(); 335 } 336 } 337 338 339 void 340 NotificationWindow::NotificationViewSwapped(NotificationView* stale, 341 NotificationView* fresh) 342 { 343 views_t::iterator it = find(fViews.begin(), fViews.end(), stale); 344 345 if (it != fViews.end()) 346 *it = fresh; 347 } 348 349 350 void 351 NotificationWindow::SetPosition() 352 { 353 Layout(true); 354 355 BRect bounds = DecoratorFrame(); 356 float width = Bounds().Width() + 1; 357 float height = Bounds().Height() + 1; 358 359 float leftOffset = Frame().left - bounds.left; 360 float topOffset = Frame().top - bounds.top + 1; 361 float rightOffset = bounds.right - Frame().right; 362 float bottomOffset = bounds.bottom - Frame().bottom; 363 // Size of the borders around the window 364 365 float x = Frame().left, y = Frame().top; 366 // If we can't guess, don't move... 367 368 BDeskbar deskbar; 369 BRect frame = deskbar.Frame(); 370 371 switch (deskbar.Location()) { 372 case B_DESKBAR_TOP: 373 // Put it just under, top right corner 374 y = frame.bottom + topOffset; 375 x = frame.right - width + rightOffset; 376 break; 377 case B_DESKBAR_BOTTOM: 378 // Put it just above, lower left corner 379 y = frame.top - height - bottomOffset; 380 x = frame.right - width + rightOffset; 381 break; 382 case B_DESKBAR_RIGHT_TOP: 383 x = frame.left - width - rightOffset; 384 y = frame.top - topOffset + 1; 385 break; 386 case B_DESKBAR_LEFT_TOP: 387 x = frame.right + leftOffset; 388 y = frame.top - topOffset + 1; 389 break; 390 case B_DESKBAR_RIGHT_BOTTOM: 391 y = frame.bottom - height + bottomOffset; 392 x = frame.left - width - rightOffset; 393 break; 394 case B_DESKBAR_LEFT_BOTTOM: 395 y = frame.bottom - height + bottomOffset; 396 x = frame.right + leftOffset; 397 break; 398 default: 399 break; 400 } 401 402 MoveTo(x, y); 403 } 404 405 406 void 407 NotificationWindow::_LoadSettings(bool startMonitor) 408 { 409 BPath path; 410 BMessage settings; 411 412 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK) 413 return; 414 415 path.Append(kSettingsFile); 416 417 BFile file(path.Path(), B_READ_ONLY); 418 settings.Unflatten(&file); 419 420 _LoadGeneralSettings(settings); 421 _LoadDisplaySettings(settings); 422 _LoadAppFilters(settings); 423 424 if (startMonitor) { 425 node_ref nref; 426 BEntry entry(path.Path()); 427 entry.GetNodeRef(&nref); 428 429 if (watch_node(&nref, B_WATCH_ALL, BMessenger(this)) != B_OK) { 430 BAlert* alert = new BAlert(B_TRANSLATE("Warning"), 431 B_TRANSLATE("Couldn't start general settings monitor.\n" 432 "Live filter changes disabled."), B_TRANSLATE("OK")); 433 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 434 alert->Go(); 435 } 436 } 437 } 438 439 440 void 441 NotificationWindow::_LoadAppFilters(BMessage& settings) 442 { 443 type_code type; 444 int32 count = 0; 445 446 if (settings.GetInfo("app_usage", &type, &count) != B_OK) 447 return; 448 449 for (int32 i = 0; i < count; i++) { 450 AppUsage* app = new AppUsage(); 451 settings.FindFlat("app_usage", i, app); 452 fAppFilters[app->Name()] = app; 453 } 454 } 455 456 457 void 458 NotificationWindow::_LoadGeneralSettings(BMessage& settings) 459 { 460 bool shouldRun; 461 if (settings.FindBool(kAutoStartName, &shouldRun) == B_OK) { 462 if (shouldRun == false) { 463 // We should not start. Quit the app! 464 be_app_messenger.SendMessage(B_QUIT_REQUESTED); 465 } 466 } 467 if (settings.FindInt32(kTimeoutName, &fTimeout) != B_OK) 468 fTimeout = kDefaultTimeout; 469 470 // Notify the view about the change 471 views_t::iterator it; 472 for (it = fViews.begin(); it != fViews.end(); ++it) { 473 NotificationView* view = (*it); 474 view->Invalidate(); 475 } 476 } 477 478 479 void 480 NotificationWindow::_LoadDisplaySettings(BMessage& settings) 481 { 482 int32 setting; 483 484 if (settings.FindFloat(kWidthName, &fWidth) != B_OK) 485 fWidth = kDefaultWidth; 486 GetLayout()->SetExplicitMaxSize(BSize(fWidth, B_SIZE_UNSET)); 487 GetLayout()->SetExplicitMinSize(BSize(fWidth, B_SIZE_UNSET)); 488 489 if (settings.FindInt32(kIconSizeName, &setting) != B_OK) 490 fIconSize = kDefaultIconSize; 491 else 492 fIconSize = (icon_size)setting; 493 494 // Notify the view about the change 495 views_t::iterator it; 496 for (it = fViews.begin(); it != fViews.end(); ++it) { 497 NotificationView* view = (*it); 498 view->Invalidate(); 499 } 500 } 501