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