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 fHeaderView->BeginEditingTitle(); 273 break; 274 } 275 276 case kIdentifyEntry: 277 { 278 bool force = (modifiers() & B_OPTION_KEY) != 0; 279 BEntry entry; 280 if (entry.SetTo(fModel->EntryRef(), true) == B_OK) { 281 BPath path; 282 if (entry.GetPath(&path) == B_OK) 283 update_mime_info(path.Path(), true, false, force ? 2 : 1); 284 } 285 break; 286 } 287 288 case kRecalculateSize: 289 { 290 fStopCalc = true; 291 // Wait until any current CalcSize thread has terminated before 292 // starting a new one 293 status_t result; 294 wait_for_thread(fCalcThreadID, &result); 295 296 // Start recalculating.. 297 fStopCalc = false; 298 SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS)); 299 fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize", 300 B_NORMAL_PRIORITY, this); 301 resume_thread(fCalcThreadID); 302 break; 303 } 304 305 case kSetLinkTarget: 306 OpenFilePanel(fModel->EntryRef()); 307 break; 308 309 // An item was dropped into the window 310 case B_SIMPLE_DATA: 311 // If we are not a SymLink, just ignore the request 312 if (!fModel->IsSymLink()) 313 break; 314 // supposed to fall through 315 // An item was selected from the file panel 316 // fall-through 317 case kNewTargetSelected: 318 { 319 // Extract the BEntry, and set its full path to the string value 320 BEntry targetEntry; 321 entry_ref ref; 322 BPath path; 323 324 if (message->FindRef("refs", &ref) == B_OK 325 && targetEntry.SetTo(&ref, true) == B_OK 326 && targetEntry.Exists()) { 327 // We now have to re-target the broken symlink. Unfortunately, 328 // there's no way to change the target of an existing symlink. 329 // So we have to delete the old one and create a new one. 330 // First, stop watching the broken node 331 // (we don't want this window to quit when the node 332 // is removed.) 333 stop_watching(this); 334 335 // Get the parent 336 BDirectory parent; 337 BEntry tmpEntry(TargetModel()->EntryRef()); 338 if (tmpEntry.GetParent(&parent) != B_OK) 339 break; 340 341 // Preserve the name 342 BString name(TargetModel()->Name()); 343 344 // Extract path for new target 345 BEntry target(&ref); 346 BPath targetPath; 347 if (target.GetPath(&targetPath) != B_OK) 348 break; 349 350 // Preserve the original attributes 351 AttributeStreamMemoryNode memoryNode; 352 { 353 BModelOpener opener(TargetModel()); 354 AttributeStreamFileNode original(TargetModel()->Node()); 355 memoryNode << original; 356 } 357 358 // Delete the broken node. 359 BEntry oldEntry(TargetModel()->EntryRef()); 360 oldEntry.Remove(); 361 362 // Create new node 363 BSymLink link; 364 parent.CreateSymLink(name.String(), targetPath.Path(), &link); 365 366 // Update our Model() 367 BEntry symEntry(&parent, name.String()); 368 fModel->SetTo(&symEntry); 369 370 BModelWriteOpener opener(TargetModel()); 371 372 // Copy the attributes back 373 AttributeStreamFileNode newNode(TargetModel()->Node()); 374 newNode << memoryNode; 375 376 // Start watching this again 377 TTracker::WatchNode(TargetModel()->NodeRef(), 378 B_WATCH_ALL | B_WATCH_MOUNT, this); 379 380 // Tell the attribute view about this new model 381 fGeneralInfoView->ReLinkTargetModel(TargetModel()); 382 fHeaderView->ReLinkTargetModel(TargetModel()); 383 } 384 break; 385 } 386 387 case B_CANCEL: 388 // File panel window has closed 389 delete fFilePanel; 390 fFilePanel = NULL; 391 // It's no longer open 392 fFilePanelOpen = false; 393 break; 394 395 case kUnmountVolume: 396 // Sanity check that this isn't the boot volume 397 // (The unmount menu item has been disabled in this 398 // case, but the shortcut is still active) 399 if (fModel->IsVolume()) { 400 BVolume boot; 401 BVolumeRoster().GetBootVolume(&boot); 402 BVolume volume(fModel->NodeRef()->device); 403 if (volume != boot) { 404 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 405 if (tracker != NULL) 406 tracker->SaveAllPoseLocations(); 407 408 BMessage unmountMessage(kUnmountVolume); 409 unmountMessage.AddInt32("device_id", volume.Device()); 410 be_app->PostMessage(&unmountMessage); 411 } 412 } 413 break; 414 415 case kEmptyTrash: 416 FSEmptyTrash(); 417 break; 418 419 case B_NODE_MONITOR: 420 switch (message->FindInt32("opcode")) { 421 case B_ENTRY_REMOVED: 422 { 423 node_ref itemNode; 424 message->FindInt32("device", &itemNode.device); 425 message->FindInt64("node", &itemNode.node); 426 // our window itself may be deleted 427 if (*TargetModel()->NodeRef() == itemNode) 428 Close(); 429 break; 430 } 431 432 case B_ENTRY_MOVED: 433 case B_STAT_CHANGED: 434 case B_ATTR_CHANGED: 435 fGeneralInfoView->ModelChanged(TargetModel(), message); 436 // must be called before the 437 // FilePermissionView::ModelChanged() 438 // call, because it changes the model... 439 // (bad style!) 440 fHeaderView->ModelChanged(TargetModel(), message); 441 442 if (fPermissionsView != NULL) 443 fPermissionsView->ModelChanged(TargetModel()); 444 break; 445 446 case B_DEVICE_UNMOUNTED: 447 { 448 // We were watching a volume that is no longer 449 // mounted, we might as well quit 450 node_ref itemNode; 451 // Only the device information is available 452 message->FindInt32("device", &itemNode.device); 453 if (TargetModel()->NodeRef()->device == itemNode.device) 454 Close(); 455 break; 456 } 457 458 default: 459 break; 460 } 461 break; 462 463 case kPermissionsSelected: 464 { 465 BTabView* tabView = (BTabView*)FindView("tabs"); 466 tabView->Select(1); 467 break; 468 } 469 470 default: 471 _inherited::MessageReceived(message); 472 break; 473 } 474 } 475 476 477 void 478 BInfoWindow::GetSizeString(BString& result, off_t size, int32 fileCount) 479 { 480 static BStringFormat sizeFormat(B_TRANSLATE( 481 "{0, plural, one{(# byte)} other{(# bytes)}}")); 482 static BStringFormat countFormat(B_TRANSLATE( 483 "{0, plural, one{for # file} other{for # files}}")); 484 485 char sizeBuffer[128]; 486 result << string_for_size((double)size, sizeBuffer, sizeof(sizeBuffer)); 487 488 if (size >= kKBSize) { 489 result << " "; 490 491 sizeFormat.Format(result, size); 492 // "bytes" translation could come from string_for_size 493 // which could be part of the localekit itself 494 } 495 496 if (fileCount != 0) { 497 result << " "; 498 countFormat.Format(result, fileCount); 499 } 500 } 501 502 503 int32 504 BInfoWindow::CalcSize(void* castToWindow) 505 { 506 BInfoWindow* window = static_cast<BInfoWindow*>(castToWindow); 507 BDirectory dir(window->TargetModel()->EntryRef()); 508 BDirectory trashDir; 509 FSGetTrashDir(&trashDir, window->TargetModel()->EntryRef()->device); 510 if (dir.InitCheck() != B_OK) { 511 if (window->StopCalc()) 512 return B_ERROR; 513 514 AutoLock<BWindow> lock(window); 515 if (!lock) 516 return B_ERROR; 517 518 window->SetSizeString(B_TRANSLATE("Error calculating folder size.")); 519 return B_ERROR; 520 } 521 522 BEntry dirEntry, trashEntry; 523 dir.GetEntry(&dirEntry); 524 trashDir.GetEntry(&trashEntry); 525 526 BString sizeString; 527 528 // check if user has asked for trash dir info 529 if (dirEntry != trashEntry) { 530 // if not, perform normal info calculations 531 off_t size = 0; 532 int32 fileCount = 0; 533 int32 dirCount = 0; 534 CopyLoopControl loopControl; 535 FSRecursiveCalcSize(window, &loopControl, &dir, &size, &fileCount, 536 &dirCount); 537 538 // got the size value, update the size string 539 GetSizeString(sizeString, size, fileCount); 540 } else { 541 // in the trash case, iterate through and sum up 542 // size/counts for all present trash dirs 543 off_t totalSize = 0, currentSize; 544 int32 totalFileCount = 0, currentFileCount; 545 int32 totalDirCount = 0, currentDirCount; 546 BVolumeRoster volRoster; 547 volRoster.Rewind(); 548 BVolume volume; 549 while (volRoster.GetNextVolume(&volume) == B_OK) { 550 if (!volume.IsPersistent()) 551 continue; 552 553 currentSize = 0; 554 currentFileCount = 0; 555 currentDirCount = 0; 556 557 BDirectory trashDir; 558 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { 559 CopyLoopControl loopControl; 560 FSRecursiveCalcSize(window, &loopControl, &trashDir, 561 ¤tSize, ¤tFileCount, ¤tDirCount); 562 totalSize += currentSize; 563 totalFileCount += currentFileCount; 564 totalDirCount += currentDirCount; 565 } 566 } 567 GetSizeString(sizeString, totalSize, totalFileCount); 568 } 569 570 if (window->StopCalc()) { 571 // window closed, bail 572 return B_OK; 573 } 574 575 AutoLock<BWindow> lock(window); 576 if (lock.IsLocked()) 577 window->SetSizeString(sizeString.String()); 578 579 return B_OK; 580 } 581 582 583 void 584 BInfoWindow::SetSizeString(const char* sizeString) 585 { 586 fGeneralInfoView->SetSizeString(sizeString); 587 } 588 589 590 void 591 BInfoWindow::OpenFilePanel(const entry_ref* ref) 592 { 593 // Open a file dialog box to allow the user to select a new target 594 // for the sym link 595 if (fFilePanel == NULL) { 596 BMessenger runner(this); 597 BMessage message(kNewTargetSelected); 598 fFilePanel = new BFilePanel(B_OPEN_PANEL, &runner, ref, 599 B_FILE_NODE | B_SYMLINK_NODE | B_DIRECTORY_NODE, 600 false, &message); 601 602 if (fFilePanel != NULL) { 603 fFilePanel->SetButtonLabel(B_DEFAULT_BUTTON, 604 B_TRANSLATE("Select")); 605 fFilePanel->Window()->ResizeTo(500, 300); 606 BString title(B_TRANSLATE_COMMENT("Link \"%name\" to:", 607 "File dialog title for new sym link")); 608 title.ReplaceFirst("%name", fModel->Name()); 609 fFilePanel->Window()->SetTitle(title.String()); 610 fFilePanel->Show(); 611 fFilePanelOpen = true; 612 } 613 } else if (!fFilePanelOpen) { 614 fFilePanel->Show(); 615 fFilePanelOpen = true; 616 } else { 617 fFilePanelOpen = true; 618 fFilePanel->Window()->Activate(true); 619 } 620 } 621 622 623