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 "InfoWindow.h" 37 38 #include <string.h> 39 #include <stdio.h> 40 #include <stdlib.h> 41 42 #include <Alert.h> 43 #include <Catalog.h> 44 #include <Debug.h> 45 #include <Directory.h> 46 #include <File.h> 47 #include <Font.h> 48 #include <Locale.h> 49 #include <MenuField.h> 50 #include <Mime.h> 51 #include <NodeInfo.h> 52 #include <NodeMonitor.h> 53 #include <Path.h> 54 #include <PopUpMenu.h> 55 #include <Region.h> 56 #include <Roster.h> 57 #include <Screen.h> 58 #include <ScrollView.h> 59 #include <StringFormat.h> 60 #include <SymLink.h> 61 #include <TabView.h> 62 #include <TextView.h> 63 #include <Volume.h> 64 #include <VolumeRoster.h> 65 66 #include "Attributes.h" 67 #include "AttributesView.h" 68 #include "AutoLock.h" 69 #include "Commands.h" 70 #include "DialogPane.h" 71 #include "FSUtils.h" 72 #include "GeneralInfoView.h" 73 #include "IconCache.h" 74 #include "IconMenuItem.h" 75 #include "Model.h" 76 #include "NavMenu.h" 77 #include "PoseView.h" 78 #include "StringForSize.h" 79 #include "Tracker.h" 80 #include "WidgetAttributeText.h" 81 82 83 #undef B_TRANSLATION_CONTEXT 84 #define B_TRANSLATION_CONTEXT "InfoWindow" 85 86 87 const uint32 kNewTargetSelected = 'selc'; 88 89 // #pragma mark - BInfoWindow 90 91 92 BInfoWindow::BInfoWindow(Model* model, int32 group_index, 93 LockingList<BWindow>* list) 94 : 95 BWindow(BInfoWindow::InfoWindowRect(), 96 "InfoWindow", B_TITLED_WINDOW, 97 B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS, 98 B_CURRENT_WORKSPACE), 99 fModel(model), 100 fStopCalc(false), 101 fIndex(group_index), 102 fCalcThreadID(-1), 103 fWindowList(list), 104 fPermissionsView(NULL), 105 fFilePanel(NULL), 106 fFilePanelOpen(false) 107 { 108 SetPulseRate(1000000); 109 // we use pulse to check freebytes on volume 110 111 TTracker::WatchNode(model->NodeRef(), B_WATCH_ALL | B_WATCH_MOUNT, this); 112 113 // window list is Locked by Tracker around this constructor 114 if (list != NULL) 115 list->AddItem(this); 116 117 AddShortcut('E', 0, new BMessage(kEditItem)); 118 AddShortcut('O', 0, new BMessage(kOpenSelection)); 119 AddShortcut('U', 0, new BMessage(kUnmountVolume)); 120 AddShortcut('P', 0, new BMessage(kPermissionsSelected)); 121 122 BGroupLayout* layout = new BGroupLayout(B_VERTICAL, 0); 123 SetLayout(layout); 124 125 BModelOpener modelOpener(TargetModel()); 126 if (TargetModel()->InitCheck() != B_OK) 127 return; 128 129 fHeaderView = new HeaderView(TargetModel()); 130 AddChild(fHeaderView); 131 BTabView* tabView = new BTabView("tabs"); 132 tabView->SetBorder(B_NO_BORDER); 133 AddChild(tabView); 134 135 fGeneralInfoView = new GeneralInfoView(TargetModel()); 136 tabView->AddTab(fGeneralInfoView); 137 138 BRect permissionsBounds(0, 139 fGeneralInfoView->Bounds().bottom, 140 fGeneralInfoView->Bounds().right, 141 fGeneralInfoView->Bounds().bottom + 103); 142 143 fPermissionsView = new FilePermissionsView( 144 permissionsBounds, fModel); 145 tabView->AddTab(fPermissionsView); 146 147 tabView->AddTab(new AttributesView(TargetModel())); 148 149 // This window accepts messages before being shown, so let's start the 150 // looper immediately. 151 Run(); 152 } 153 154 155 BInfoWindow::~BInfoWindow() 156 { 157 // Check to make sure the file panel is destroyed 158 delete fFilePanel; 159 delete fModel; 160 } 161 162 163 BRect 164 BInfoWindow::InfoWindowRect() 165 { 166 // starting size of window 167 return BRect(70, 50, 385, 240); 168 } 169 170 171 void 172 BInfoWindow::Quit() 173 { 174 stop_watching(this); 175 176 if (fWindowList) { 177 AutoLock<LockingList<BWindow> > lock(fWindowList); 178 fWindowList->RemoveItem(this); 179 } 180 181 fStopCalc = true; 182 183 // wait until CalcSize thread has terminated before closing window 184 status_t result; 185 wait_for_thread(fCalcThreadID, &result); 186 187 _inherited::Quit(); 188 } 189 190 191 bool 192 BInfoWindow::IsShowing(const node_ref* node) const 193 { 194 return *TargetModel()->NodeRef() == *node; 195 } 196 197 198 void 199 BInfoWindow::Show() 200 { 201 if (TargetModel()->InitCheck() != B_OK) { 202 Close(); 203 return; 204 } 205 206 AutoLock<BWindow> lock(this); 207 208 // position window appropriately based on index 209 BRect windRect(InfoWindowRect()); 210 if ((fIndex + 2) % 2 == 1) { 211 windRect.OffsetBy(320, 0); 212 fIndex--; 213 } 214 215 windRect.OffsetBy(fIndex * 8, fIndex * 8); 216 217 // make sure window is visible on screen 218 BScreen screen(this); 219 if (!windRect.Intersects(screen.Frame())) 220 windRect.OffsetTo(50, 50); 221 222 MoveTo(windRect.LeftTop()); 223 224 // volume case is handled by view 225 if (!TargetModel()->IsVolume() && !TargetModel()->IsRoot()) { 226 if (TargetModel()->IsDirectory()) { 227 // if this is a folder then spawn thread to calculate size 228 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS)); 229 fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize", 230 B_NORMAL_PRIORITY, this); 231 resume_thread(fCalcThreadID); 232 } else { 233 fGeneralInfoView->SetLastSize(TargetModel()->StatBuf()->st_size); 234 235 BString sizeStr; 236 GetSizeString(sizeStr, fGeneralInfoView->LastSize(), 0); 237 SetSizeString(sizeStr.String()); 238 } 239 } 240 241 BString buffer(B_TRANSLATE_COMMENT("%name info", "InfoWindow Title")); 242 buffer.ReplaceFirst("%name", TargetModel()->Name()); 243 SetTitle(buffer.String()); 244 245 lock.Unlock(); 246 _inherited::Show(); 247 } 248 249 250 void 251 BInfoWindow::MessageReceived(BMessage* message) 252 { 253 switch (message->what) { 254 case kRestoreState: 255 Show(); 256 break; 257 258 case kOpenSelection: 259 { 260 BMessage refsMessage(B_REFS_RECEIVED); 261 refsMessage.AddRef("refs", fModel->EntryRef()); 262 263 // add a messenger to the launch message that will be used to 264 // dispatch scripting calls from apps to the PoseView 265 refsMessage.AddMessenger("TrackerViewToken", BMessenger(this)); 266 be_app->PostMessage(&refsMessage); 267 break; 268 } 269 270 case kEditItem: 271 { 272 BEntry entry(fModel->EntryRef()); 273 if (!fModel->HasLocalizedName() 274 && ConfirmChangeIfWellKnownDirectory(&entry, kRename)) { 275 fHeaderView->BeginEditingTitle(); 276 } 277 break; 278 } 279 280 case kIdentifyEntry: 281 { 282 bool force = (modifiers() & B_OPTION_KEY) != 0; 283 BEntry entry; 284 if (entry.SetTo(fModel->EntryRef(), true) == B_OK) { 285 BPath path; 286 if (entry.GetPath(&path) == B_OK) 287 update_mime_info(path.Path(), true, false, force ? 2 : 1); 288 } 289 break; 290 } 291 292 case kRecalculateSize: 293 { 294 fStopCalc = true; 295 // Wait until any current CalcSize thread has terminated before 296 // starting a new one 297 status_t result; 298 wait_for_thread(fCalcThreadID, &result); 299 300 // Start recalculating.. 301 fStopCalc = false; 302 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS)); 303 fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize", 304 B_NORMAL_PRIORITY, this); 305 resume_thread(fCalcThreadID); 306 break; 307 } 308 309 case kSetLinkTarget: 310 OpenFilePanel(fModel->EntryRef()); 311 break; 312 313 // An item was dropped into the window 314 case B_SIMPLE_DATA: 315 // If we are not a SymLink, just ignore the request 316 if (!fModel->IsSymLink()) 317 break; 318 // supposed to fall through 319 // An item was selected from the file panel 320 // fall-through 321 case kNewTargetSelected: 322 { 323 // Extract the BEntry, and set its full path to the string value 324 BEntry targetEntry; 325 entry_ref ref; 326 BPath path; 327 328 if (message->FindRef("refs", &ref) == B_OK 329 && targetEntry.SetTo(&ref, true) == B_OK 330 && targetEntry.Exists()) { 331 // We now have to re-target the broken symlink. Unfortunately, 332 // there's no way to change the target of an existing symlink. 333 // So we have to delete the old one and create a new one. 334 // First, stop watching the broken node 335 // (we don't want this window to quit when the node 336 // is removed.) 337 stop_watching(this); 338 339 // Get the parent 340 BDirectory parent; 341 BEntry tmpEntry(TargetModel()->EntryRef()); 342 if (tmpEntry.GetParent(&parent) != B_OK) 343 break; 344 345 // Preserve the name 346 BString name(TargetModel()->Name()); 347 348 // Extract path for new target 349 BEntry target(&ref); 350 BPath targetPath; 351 if (target.GetPath(&targetPath) != B_OK) 352 break; 353 354 // Preserve the original attributes 355 AttributeStreamMemoryNode memoryNode; 356 { 357 BModelOpener opener(TargetModel()); 358 AttributeStreamFileNode original(TargetModel()->Node()); 359 memoryNode << original; 360 } 361 362 // Delete the broken node. 363 BEntry oldEntry(TargetModel()->EntryRef()); 364 oldEntry.Remove(); 365 366 // Create new node 367 BSymLink link; 368 parent.CreateSymLink(name.String(), targetPath.Path(), &link); 369 370 // Update our Model() 371 BEntry symEntry(&parent, name.String()); 372 fModel->SetTo(&symEntry); 373 374 BModelWriteOpener opener(TargetModel()); 375 376 // Copy the attributes back 377 AttributeStreamFileNode newNode(TargetModel()->Node()); 378 newNode << memoryNode; 379 380 // Start watching this again 381 TTracker::WatchNode(TargetModel()->NodeRef(), 382 B_WATCH_ALL | B_WATCH_MOUNT, this); 383 384 // Tell the attribute view about this new model 385 fGeneralInfoView->ReLinkTargetModel(TargetModel()); 386 fHeaderView->ReLinkTargetModel(TargetModel()); 387 } 388 break; 389 } 390 391 case B_CANCEL: 392 // File panel window has closed 393 delete fFilePanel; 394 fFilePanel = NULL; 395 // It's no longer open 396 fFilePanelOpen = false; 397 break; 398 399 case kUnmountVolume: 400 // Sanity check that this isn't the boot volume 401 // (The unmount menu item has been disabled in this 402 // case, but the shortcut is still active) 403 if (fModel->IsVolume()) { 404 BVolume boot; 405 BVolumeRoster().GetBootVolume(&boot); 406 BVolume volume(fModel->NodeRef()->device); 407 if (volume != boot) { 408 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 409 if (tracker != NULL) 410 tracker->SaveAllPoseLocations(); 411 412 BMessage unmountMessage(kUnmountVolume); 413 unmountMessage.AddInt32("device_id", volume.Device()); 414 be_app->PostMessage(&unmountMessage); 415 } 416 } 417 break; 418 419 case kEmptyTrash: 420 FSEmptyTrash(); 421 break; 422 423 case B_NODE_MONITOR: 424 switch (message->FindInt32("opcode")) { 425 case B_ENTRY_REMOVED: 426 { 427 node_ref itemNode; 428 message->FindInt32("device", &itemNode.device); 429 message->FindInt64("node", &itemNode.node); 430 // our window itself may be deleted 431 if (*TargetModel()->NodeRef() == itemNode) 432 Close(); 433 break; 434 } 435 436 case B_ENTRY_MOVED: 437 case B_STAT_CHANGED: 438 case B_ATTR_CHANGED: 439 fGeneralInfoView->ModelChanged(TargetModel(), message); 440 // must be called before the 441 // FilePermissionView::ModelChanged() 442 // call, because it changes the model... 443 // (bad style!) 444 fHeaderView->ModelChanged(TargetModel(), message); 445 446 if (fPermissionsView != NULL) 447 fPermissionsView->ModelChanged(TargetModel()); 448 break; 449 450 case B_DEVICE_UNMOUNTED: 451 { 452 // We were watching a volume that is no longer 453 // mounted, we might as well quit 454 node_ref itemNode; 455 // Only the device information is available 456 message->FindInt32("device", &itemNode.device); 457 if (TargetModel()->NodeRef()->device == itemNode.device) 458 Close(); 459 break; 460 } 461 462 default: 463 break; 464 } 465 break; 466 467 case kPermissionsSelected: 468 { 469 BTabView* tabView = (BTabView*)FindView("tabs"); 470 tabView->Select(1); 471 break; 472 } 473 474 default: 475 _inherited::MessageReceived(message); 476 break; 477 } 478 } 479 480 481 void 482 BInfoWindow::GetSizeString(BString& result, off_t size, int32 fileCount) 483 { 484 static BStringFormat sizeFormat(B_TRANSLATE( 485 "{0, plural, one{(# byte)} other{(# bytes)}}")); 486 static BStringFormat countFormat(B_TRANSLATE( 487 "{0, plural, one{for # file} other{for # files}}")); 488 489 char sizeBuffer[128]; 490 result << string_for_size((double)size, sizeBuffer, sizeof(sizeBuffer)); 491 492 if (size >= kKBSize) { 493 result << " "; 494 495 sizeFormat.Format(result, size); 496 // "bytes" translation could come from string_for_size 497 // which could be part of the localekit itself 498 } 499 500 if (fileCount != 0) { 501 result << " "; 502 countFormat.Format(result, fileCount); 503 } 504 } 505 506 507 int32 508 BInfoWindow::CalcSize(void* castToWindow) 509 { 510 BInfoWindow* window = static_cast<BInfoWindow*>(castToWindow); 511 BDirectory dir(window->TargetModel()->EntryRef()); 512 BDirectory trashDir; 513 FSGetTrashDir(&trashDir, window->TargetModel()->EntryRef()->device); 514 if (dir.InitCheck() != B_OK) { 515 if (window->StopCalc()) 516 return B_ERROR; 517 518 AutoLock<BWindow> lock(window); 519 if (!lock) 520 return B_ERROR; 521 522 window->SetSizeString(B_TRANSLATE("Error calculating folder size.")); 523 return B_ERROR; 524 } 525 526 BEntry dirEntry, trashEntry; 527 dir.GetEntry(&dirEntry); 528 trashDir.GetEntry(&trashEntry); 529 530 BString sizeString; 531 532 // check if user has asked for trash dir info 533 if (dirEntry != trashEntry) { 534 // if not, perform normal info calculations 535 off_t size = 0; 536 int32 fileCount = 0; 537 int32 dirCount = 0; 538 CopyLoopControl loopControl; 539 FSRecursiveCalcSize(window, &loopControl, &dir, &size, &fileCount, 540 &dirCount); 541 542 // got the size value, update the size string 543 GetSizeString(sizeString, size, fileCount); 544 } else { 545 // in the trash case, iterate through and sum up 546 // size/counts for all present trash dirs 547 off_t totalSize = 0, currentSize; 548 int32 totalFileCount = 0, currentFileCount; 549 int32 totalDirCount = 0, currentDirCount; 550 BVolumeRoster volRoster; 551 volRoster.Rewind(); 552 BVolume volume; 553 while (volRoster.GetNextVolume(&volume) == B_OK) { 554 if (!volume.IsPersistent()) 555 continue; 556 557 currentSize = 0; 558 currentFileCount = 0; 559 currentDirCount = 0; 560 561 BDirectory trashDir; 562 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { 563 CopyLoopControl loopControl; 564 FSRecursiveCalcSize(window, &loopControl, &trashDir, 565 ¤tSize, ¤tFileCount, ¤tDirCount); 566 totalSize += currentSize; 567 totalFileCount += currentFileCount; 568 totalDirCount += currentDirCount; 569 } 570 } 571 GetSizeString(sizeString, totalSize, totalFileCount); 572 } 573 574 if (window->StopCalc()) { 575 // window closed, bail 576 return B_OK; 577 } 578 579 AutoLock<BWindow> lock(window); 580 if (lock.IsLocked()) 581 window->SetSizeString(sizeString.String()); 582 583 return B_OK; 584 } 585 586 587 void 588 BInfoWindow::SetSizeString(const char* sizeString) 589 { 590 fGeneralInfoView->SetSizeString(sizeString); 591 } 592 593 594 void 595 BInfoWindow::OpenFilePanel(const entry_ref* ref) 596 { 597 // Open a file dialog box to allow the user to select a new target 598 // for the sym link 599 if (fFilePanel == NULL) { 600 BMessenger runner(this); 601 BMessage message(kNewTargetSelected); 602 fFilePanel = new BFilePanel(B_OPEN_PANEL, &runner, ref, 603 B_FILE_NODE | B_SYMLINK_NODE | B_DIRECTORY_NODE, 604 false, &message); 605 606 if (fFilePanel != NULL) { 607 fFilePanel->SetButtonLabel(B_DEFAULT_BUTTON, 608 B_TRANSLATE("Select")); 609 fFilePanel->Window()->ResizeTo(500, 300); 610 BString title(B_TRANSLATE_COMMENT("Link \"%name\" to:", 611 "File dialog title for new sym link")); 612 title.ReplaceFirst("%name", fModel->Name()); 613 fFilePanel->Window()->SetTitle(title.String()); 614 fFilePanel->Show(); 615 fFilePanelOpen = true; 616 } 617 } else if (!fFilePanelOpen) { 618 fFilePanel->Show(); 619 fFilePanelOpen = true; 620 } else { 621 fFilePanelOpen = true; 622 fFilePanel->Window()->Activate(true); 623 } 624 } 625 626 627