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