1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "DeskWindow.h" 37 38 #include <Catalog.h> 39 #include <Debug.h> 40 #include <FindDirectory.h> 41 #include <Locale.h> 42 #include <Messenger.h> 43 #include <NodeMonitor.h> 44 #include <Path.h> 45 #include <PathFinder.h> 46 #include <PathMonitor.h> 47 #include <PopUpMenu.h> 48 #include <Resources.h> 49 #include <Screen.h> 50 #include <String.h> 51 #include <StringList.h> 52 #include <Volume.h> 53 #include <WindowPrivate.h> 54 55 #include <fcntl.h> 56 #include <unistd.h> 57 58 #include "Attributes.h" 59 #include "AutoLock.h" 60 #include "BackgroundImage.h" 61 #include "Commands.h" 62 #include "FSUtils.h" 63 #include "IconMenuItem.h" 64 #include "KeyInfos.h" 65 #include "MountMenu.h" 66 #include "PoseView.h" 67 #include "Tracker.h" 68 #include "TemplatesMenu.h" 69 70 71 const char* kShelfPath = "tracker_shelf"; 72 // replicant support 73 74 const char* kShortcutsSettings = "shortcuts_settings"; 75 const char* kDefaultShortcut = "BEOS:default_shortcut"; 76 const uint32 kDefaultModifiers = B_OPTION_KEY | B_COMMAND_KEY; 77 78 79 static struct AddonShortcut* 80 MatchOne(struct AddonShortcut* item, void* castToName) 81 { 82 if (strcmp(item->model->Name(), (const char*)castToName) == 0) { 83 // found match, bail out 84 return item; 85 } 86 87 return 0; 88 } 89 90 91 static void 92 AddOneShortcut(Model* model, char key, uint32 modifiers, BDeskWindow* window) 93 { 94 if (key == '\0') 95 return; 96 97 BMessage* runAddon = new BMessage(kLoadAddOn); 98 runAddon->AddRef("refs", model->EntryRef()); 99 window->AddShortcut(key, modifiers, runAddon); 100 } 101 102 103 104 static struct AddonShortcut* 105 RevertToDefault(struct AddonShortcut* item, void* castToWindow) 106 { 107 if (item->key != item->defaultKey || item->modifiers != kDefaultModifiers) { 108 BDeskWindow* window = static_cast<BDeskWindow*>(castToWindow); 109 if (window != NULL) { 110 window->RemoveShortcut(item->key, item->modifiers); 111 item->key = item->defaultKey; 112 item->modifiers = kDefaultModifiers; 113 AddOneShortcut(item->model, item->key, item->modifiers, window); 114 } 115 } 116 117 return 0; 118 } 119 120 121 static struct AddonShortcut* 122 FindElement(struct AddonShortcut* item, void* castToOther) 123 { 124 Model* other = static_cast<Model*>(castToOther); 125 if (*item->model->EntryRef() == *other->EntryRef()) 126 return item; 127 128 return 0; 129 } 130 131 132 static void 133 LoadAddOnDir(BDirectory directory, BDeskWindow* window, 134 LockingList<AddonShortcut>* list) 135 { 136 BEntry entry; 137 while (directory.GetNextEntry(&entry) == B_OK) { 138 Model* model = new Model(&entry); 139 if (model->InitCheck() == B_OK && model->IsSymLink()) { 140 // resolve symlinks 141 Model* resolved = new Model(model->EntryRef(), true, true); 142 if (resolved->InitCheck() == B_OK) 143 model->SetLinkTo(resolved); 144 else 145 delete resolved; 146 } 147 if (model->InitCheck() != B_OK 148 || !model->ResolveIfLink()->IsExecutable()) { 149 delete model; 150 continue; 151 } 152 153 char* name = strdup(model->Name()); 154 if (!list->EachElement(MatchOne, name)) { 155 struct AddonShortcut* item = new struct AddonShortcut; 156 item->model = model; 157 158 BResources resources(model->ResolveIfLink()->EntryRef()); 159 size_t size; 160 char* shortcut = (char*)resources.LoadResource(B_STRING_TYPE, 161 kDefaultShortcut, &size); 162 if (shortcut == NULL || strlen(shortcut) > 1) 163 item->key = '\0'; 164 else 165 item->key = shortcut[0]; 166 AddOneShortcut(model, item->key, kDefaultModifiers, window); 167 item->defaultKey = item->key; 168 item->modifiers = kDefaultModifiers; 169 list->AddItem(item); 170 } 171 free(name); 172 } 173 174 node_ref nodeRef; 175 directory.GetNodeRef(&nodeRef); 176 177 TTracker::WatchNode(&nodeRef, B_WATCH_DIRECTORY, window); 178 } 179 180 181 // #pragma mark - BDeskWindow 182 183 184 #undef B_TRANSLATION_CONTEXT 185 #define B_TRANSLATION_CONTEXT "DeskWindow" 186 187 188 BDeskWindow::BDeskWindow(LockingList<BWindow>* windowList) 189 : 190 BContainerWindow(windowList, 0, kDesktopWindowLook, 191 kDesktopWindowFeel, B_NOT_MOVABLE | B_WILL_ACCEPT_FIRST_CLICK 192 | B_NOT_ZOOMABLE | B_NOT_CLOSABLE | B_NOT_MINIMIZABLE 193 | B_NOT_RESIZABLE | B_ASYNCHRONOUS_CONTROLS, B_ALL_WORKSPACES, 194 false, true), 195 fDeskShelf(NULL), 196 fNodeRef(NULL), 197 fShortcutsSettings(NULL) 198 { 199 // Add icon view switching shortcuts. These are displayed in the context 200 // menu, although they obviously don't work from those menu items. 201 BMessage* message = new BMessage(kIconMode); 202 AddShortcut('1', B_COMMAND_KEY, message, PoseView()); 203 204 message = new BMessage(kMiniIconMode); 205 AddShortcut('2', B_COMMAND_KEY, message, PoseView()); 206 207 message = new BMessage(kIconMode); 208 message->AddInt32("scale", 1); 209 AddShortcut('+', B_COMMAND_KEY, message, PoseView()); 210 211 message = new BMessage(kIconMode); 212 message->AddInt32("scale", 0); 213 AddShortcut('-', B_COMMAND_KEY, message, PoseView()); 214 } 215 216 217 BDeskWindow::~BDeskWindow() 218 { 219 SaveDesktopPoseLocations(); 220 // explicit call to SavePoseLocations so that extended pose info 221 // gets committed properly 222 PoseView()->DisableSaveLocation(); 223 // prevent double-saving, this would slow down quitting 224 PoseView()->StopSettingsWatch(); 225 stop_watching(this); 226 } 227 228 229 void 230 BDeskWindow::Init(const BMessage*) 231 { 232 // Set the size of the screen before calling the container window's 233 // Init() because it will add volume poses to this window and 234 // they will be clipped otherwise 235 236 BScreen screen(this); 237 fOldFrame = screen.Frame(); 238 239 ResizeTo(fOldFrame.Width(), fOldFrame.Height()); 240 241 InitKeyIndices(); 242 InitAddonsList(false); 243 ApplyShortcutPreferences(false); 244 245 _inherited::Init(); 246 247 entry_ref ref; 248 BPath path; 249 if (!BootedInSafeMode() && FSFindTrackerSettingsDir(&path) == B_OK) { 250 path.Append(kShelfPath); 251 close(open(path.Path(), O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR 252 | S_IRGRP | S_IROTH)); 253 if (get_ref_for_path(path.Path(), &ref) == B_OK) 254 fDeskShelf = new BShelf(&ref, fPoseView); 255 256 if (fDeskShelf != NULL) 257 fDeskShelf->SetDisplaysZombies(true); 258 } 259 } 260 261 262 void 263 BDeskWindow::InitAddonsList(bool update) 264 { 265 AutoLock<LockingList<AddonShortcut> > lock(fAddonsList); 266 if (lock.IsLocked()) { 267 if (update) { 268 for (int i = fAddonsList->CountItems() - 1; i >= 0; i--) { 269 AddonShortcut* item = fAddonsList->ItemAt(i); 270 RemoveShortcut(item->key, B_OPTION_KEY | B_COMMAND_KEY); 271 } 272 fAddonsList->MakeEmpty(true); 273 } 274 275 BStringList addOnPaths; 276 BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Tracker", 277 addOnPaths); 278 int32 count = addOnPaths.CountStrings(); 279 for (int32 i = 0; i < count; i++) { 280 LoadAddOnDir(BDirectory(addOnPaths.StringAt(i)), this, 281 fAddonsList); 282 } 283 } 284 } 285 286 287 void 288 BDeskWindow::ApplyShortcutPreferences(bool update) 289 { 290 AutoLock<LockingList<AddonShortcut> > lock(fAddonsList); 291 if (lock.IsLocked()) { 292 if (!update) { 293 BPath path; 294 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { 295 BPathMonitor::StartWatching(path.Path(), 296 B_WATCH_STAT | B_WATCH_FILES_ONLY, this); 297 path.Append(kShortcutsSettings); 298 fShortcutsSettings = new char[strlen(path.Path()) + 1]; 299 strcpy(fShortcutsSettings, path.Path()); 300 } 301 } 302 303 fAddonsList->EachElement(RevertToDefault, this); 304 305 BFile shortcutSettings(fShortcutsSettings, B_READ_ONLY); 306 BMessage fileMsg; 307 if (shortcutSettings.InitCheck() != B_OK 308 || fileMsg.Unflatten(&shortcutSettings) != B_OK) { 309 fNodeRef = NULL; 310 return; 311 } 312 shortcutSettings.GetNodeRef(fNodeRef); 313 314 int32 i = 0; 315 BMessage message; 316 while (fileMsg.FindMessage("spec", i++, &message) == B_OK) { 317 int32 key; 318 if (message.FindInt32("key", &key) == B_OK) { 319 // only handle shortcuts referring add-ons 320 BString command; 321 if (message.FindString("command", &command) != B_OK) 322 continue; 323 324 bool isInAddons = false; 325 326 BStringList addOnPaths; 327 BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, 328 "Tracker/", addOnPaths); 329 for (int32 i = 0; i < addOnPaths.CountStrings(); i++) { 330 if (command.StartsWith(addOnPaths.StringAt(i))) { 331 isInAddons = true; 332 break; 333 } 334 } 335 336 if (!isInAddons) 337 continue; 338 339 BEntry entry(command); 340 if (entry.InitCheck() != B_OK) 341 continue; 342 343 const char* shortcut = GetKeyName(key); 344 if (strlen(shortcut) != 1) 345 continue; 346 347 uint32 modifiers = B_COMMAND_KEY; 348 // it's required by interface kit to at least 349 // have B_COMMAND_KEY 350 int32 value; 351 if (message.FindInt32("mcidx", 0, &value) == B_OK) 352 modifiers |= (value != 0 ? B_SHIFT_KEY : 0); 353 354 if (message.FindInt32("mcidx", 1, &value) == B_OK) 355 modifiers |= (value != 0 ? B_CONTROL_KEY : 0); 356 357 if (message.FindInt32("mcidx", 3, &value) == B_OK) 358 modifiers |= (value != 0 ? B_OPTION_KEY : 0); 359 360 Model model(&entry); 361 AddonShortcut* item = fAddonsList->EachElement(FindElement, 362 &model); 363 if (item != NULL) { 364 if (item->key != '\0') 365 RemoveShortcut(item->key, item->modifiers); 366 367 item->key = shortcut[0]; 368 item->modifiers = modifiers; 369 AddOneShortcut(&model, item->key, item->modifiers, this); 370 } 371 } 372 } 373 } 374 } 375 376 377 void 378 BDeskWindow::Quit() 379 { 380 if (fNavigationItem != NULL) { 381 // this duplicates BContainerWindow::Quit because 382 // fNavigationItem can be part of fTrashContextMenu 383 // and would get deleted with it 384 BMenu* menu = fNavigationItem->Menu(); 385 if (menu != NULL) 386 menu->RemoveItem(fNavigationItem); 387 388 delete fNavigationItem; 389 fNavigationItem = 0; 390 } 391 392 fAddonsList->MakeEmpty(true); 393 delete fAddonsList; 394 395 delete fDeskShelf; 396 _inherited::Quit(); 397 } 398 399 400 BPoseView* 401 BDeskWindow::NewPoseView(Model* model, uint32 viewMode) 402 { 403 return new DesktopPoseView(model, viewMode); 404 } 405 406 407 void 408 BDeskWindow::CreatePoseView(Model* model) 409 { 410 fPoseView = NewPoseView(model, kIconMode); 411 fPoseView->SetIconMapping(false); 412 fPoseView->SetEnsurePosesVisible(true); 413 fPoseView->SetAutoScroll(false); 414 415 BScreen screen(this); 416 rgb_color desktopColor = screen.DesktopColor(); 417 if (desktopColor.alpha != 255) { 418 desktopColor.alpha = 255; 419 #if B_BEOS_VERSION > B_BEOS_VERSION_5 420 // This call seems to have the power to cause R5 to freeze! 421 // Please report if commenting this out helped or helped not 422 // on your system 423 screen.SetDesktopColor(desktopColor); 424 #endif 425 } 426 427 fPoseView->SetViewColor(desktopColor); 428 fPoseView->SetLowColor(desktopColor); 429 430 fPoseView->SetResizingMode(B_FOLLOW_ALL); 431 fPoseView->ResizeTo(Bounds().Size()); 432 AddChild(fPoseView); 433 434 PoseView()->StartSettingsWatch(); 435 } 436 437 438 void 439 BDeskWindow::AddWindowContextMenus(BMenu* menu) 440 { 441 TemplatesMenu* tempateMenu = new TemplatesMenu(PoseView(), 442 B_TRANSLATE("New")); 443 444 menu->AddItem(tempateMenu); 445 tempateMenu->SetTargetForItems(PoseView()); 446 tempateMenu->SetFont(be_plain_font); 447 448 menu->AddSeparatorItem(); 449 450 BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view")); 451 BMenuItem* item; 452 453 static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 }; 454 BMessage* message; 455 456 for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) { 457 uint32 iconSize = kIconSizes[i]; 458 message = new BMessage(kIconMode); 459 message->AddInt32("size", iconSize); 460 BString label; 461 label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32, 462 "The '×' is the Unicode multiplication sign U+00D7"), 463 iconSize, iconSize); 464 item = new BMenuItem(label, message); 465 item->SetMarked(PoseView()->IconSizeInt() == iconSize); 466 item->SetTarget(PoseView()); 467 iconSizeMenu->AddItem(item); 468 } 469 470 iconSizeMenu->AddSeparatorItem(); 471 472 message = new BMessage(kIconMode); 473 message->AddInt32("scale", 0); 474 item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-'); 475 item->SetTarget(PoseView()); 476 iconSizeMenu->AddItem(item); 477 478 message = new BMessage(kIconMode); 479 message->AddInt32("scale", 1); 480 item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+'); 481 item->SetTarget(PoseView()); 482 iconSizeMenu->AddItem(item); 483 484 // A sub menu where the super item can be invoked. 485 menu->AddItem(iconSizeMenu); 486 iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY); 487 iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode)); 488 iconSizeMenu->Superitem()->SetTarget(PoseView()); 489 iconSizeMenu->Superitem()->SetMarked(PoseView()->ViewMode() == kIconMode); 490 491 item = new BMenuItem(B_TRANSLATE("Mini icon view"), 492 new BMessage(kMiniIconMode), '2'); 493 item->SetMarked(PoseView()->ViewMode() == kMiniIconMode); 494 menu->AddItem(item); 495 496 menu->AddSeparatorItem(); 497 498 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 499 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"), 500 new BMessage(B_PASTE), 'V'); 501 menu->AddItem(pasteItem); 502 menu->AddSeparatorItem(); 503 #endif 504 menu->AddItem(new BMenuItem(B_TRANSLATE("Clean up"), 505 new BMessage(kCleanup), 'K')); 506 menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS), 507 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY)); 508 menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"), 509 new BMessage(B_SELECT_ALL), 'A')); 510 511 menu->AddSeparatorItem(); 512 menu->AddItem(new MountMenu(B_TRANSLATE("Mount"))); 513 514 menu->AddSeparatorItem(); 515 menu->AddItem(new BMenu(B_TRANSLATE("Add-ons"))); 516 517 // target items as needed 518 menu->SetTargetForItems(PoseView()); 519 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU 520 pasteItem->SetTarget(this); 521 #endif 522 } 523 524 525 void 526 BDeskWindow::WorkspaceActivated(int32 workspace, bool state) 527 { 528 if (fBackgroundImage) 529 fBackgroundImage->WorkspaceActivated(PoseView(), workspace, state); 530 } 531 532 533 void 534 BDeskWindow::SaveDesktopPoseLocations() 535 { 536 PoseView()->SavePoseLocations(&fOldFrame); 537 } 538 539 540 void 541 BDeskWindow::ScreenChanged(BRect frame, color_space space) 542 { 543 bool frameChanged = (frame != fOldFrame); 544 545 SaveDesktopPoseLocations(); 546 fOldFrame = frame; 547 ResizeTo(frame.Width(), frame.Height()); 548 549 if (fBackgroundImage) 550 fBackgroundImage->ScreenChanged(frame, space); 551 552 PoseView()->CheckPoseVisibility(frameChanged ? &frame : 0); 553 // if frame changed, pass new frame so that icons can 554 // get rearranged based on old pose info for the frame 555 } 556 557 558 void 559 BDeskWindow::UpdateDesktopBackgroundImages() 560 { 561 WindowStateNodeOpener opener(this, false); 562 fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, 563 opener.Node(), true, PoseView()); 564 } 565 566 567 void 568 BDeskWindow::Show() 569 { 570 if (fBackgroundImage) 571 fBackgroundImage->Show(PoseView(), current_workspace()); 572 573 PoseView()->CheckPoseVisibility(); 574 575 _inherited::Show(); 576 } 577 578 579 bool 580 BDeskWindow::ShouldAddScrollBars() const 581 { 582 return false; 583 } 584 585 586 bool 587 BDeskWindow::ShouldAddMenus() const 588 { 589 return false; 590 } 591 592 593 bool 594 BDeskWindow::ShouldAddContainerView() const 595 { 596 return false; 597 } 598 599 600 void 601 BDeskWindow::MessageReceived(BMessage* message) 602 { 603 if (message->WasDropped()) { 604 const rgb_color* color; 605 ssize_t size; 606 // handle "roColour"-style color drops 607 if (message->FindData("RGBColor", 'RGBC', 608 (const void**)&color, &size) == B_OK) { 609 BScreen(this).SetDesktopColor(*color); 610 fPoseView->SetViewColor(*color); 611 fPoseView->SetLowColor(*color); 612 613 // Notify the backgrounds app that the background changed 614 status_t initStatus; 615 BMessenger messenger("application/x-vnd.Haiku-Backgrounds", -1, 616 &initStatus); 617 if (initStatus == B_OK) 618 messenger.SendMessage(message); 619 620 return; 621 } 622 } 623 624 switch (message->what) { 625 case B_PATH_MONITOR: 626 { 627 const char* path = ""; 628 if (!(message->FindString("path", &path) == B_OK 629 && strcmp(path, fShortcutsSettings) == 0)) { 630 631 dev_t device; 632 ino_t node; 633 if (fNodeRef == NULL 634 || message->FindInt32("device", &device) != B_OK 635 || message->FindInt64("node", &node) != B_OK 636 || device != fNodeRef->device 637 || node != fNodeRef->node) 638 break; 639 } 640 ApplyShortcutPreferences(true); 641 break; 642 } 643 case B_NODE_MONITOR: 644 PRINT(("will update addon shortcuts\n")); 645 InitAddonsList(true); 646 ApplyShortcutPreferences(true); 647 break; 648 649 default: 650 _inherited::MessageReceived(message); 651 break; 652 } 653 } 654