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 "GeneralInfoView.h" 37 38 #include <algorithm> 39 40 #include <Application.h> 41 #include <Catalog.h> 42 #include <Locale.h> 43 #include <NodeMonitor.h> 44 #include <PopUpMenu.h> 45 #include <Region.h> 46 #include <Roster.h> 47 #include <Screen.h> 48 #include <StringFormat.h> 49 #include <SymLink.h> 50 #include <Volume.h> 51 #include <VolumeRoster.h> 52 #include <Window.h> 53 54 #include "Attributes.h" 55 #include "Commands.h" 56 #include "FSUtils.h" 57 #include "IconMenuItem.h" 58 #include "InfoWindow.h" 59 #include "Model.h" 60 #include "NavMenu.h" 61 #include "PoseView.h" 62 #include "StringForSize.h" 63 #include "Tracker.h" 64 #include "WidgetAttributeText.h" 65 66 67 #undef B_TRANSLATION_CONTEXT 68 #define B_TRANSLATION_CONTEXT "InfoWindow" 69 70 71 namespace BPrivate { 72 73 class TrackingView : public BControl { 74 public: 75 TrackingView(BRect, const char* str, BMessage* message); 76 77 virtual void MouseDown(BPoint); 78 virtual void MouseMoved(BPoint where, uint32 transit, const BMessage*); 79 virtual void MouseUp(BPoint); 80 virtual void Draw(BRect); 81 82 private: 83 bool fMouseDown; 84 bool fMouseInView; 85 }; 86 87 } // namespace BPrivate 88 89 90 const float kBorderMargin = 15.0f; 91 const float kDrawMargin = 3.0f; 92 93 94 const uint32 kSetPreferredApp = 'setp'; 95 const uint32 kSelectNewSymTarget = 'snew'; 96 const uint32 kOpenLinkSource = 'opls'; 97 const uint32 kOpenLinkTarget = 'oplt'; 98 99 100 static void 101 OpenParentAndSelectOriginal(const entry_ref* ref) 102 { 103 BEntry entry(ref); 104 node_ref node; 105 entry.GetNodeRef(&node); 106 107 BEntry parent; 108 entry.GetParent(&parent); 109 entry_ref parentRef; 110 parent.GetRef(&parentRef); 111 112 BMessage message(B_REFS_RECEIVED); 113 message.AddRef("refs", &parentRef); 114 message.AddData("nodeRefToSelect", B_RAW_TYPE, &node, sizeof(node_ref)); 115 116 be_app->PostMessage(&message); 117 } 118 119 120 static BWindow* 121 OpenToolTipWindow(BScreen& screen, BRect rect, const char* name, 122 const char* string, BMessenger target, BMessage* message) 123 { 124 font_height fontHeight; 125 be_plain_font->GetHeight(&fontHeight); 126 float height = ceilf(fontHeight.ascent + fontHeight.descent); 127 rect.top = floorf(rect.top + (rect.Height() - height) / 2.0f); 128 rect.bottom = rect.top + height; 129 130 rect.right = rect.left + ceilf(be_plain_font->StringWidth(string)) + 4; 131 if (rect.left < 0) 132 rect.OffsetBy(-rect.left, 0); 133 else if (rect.right > screen.Frame().right) 134 rect.OffsetBy(screen.Frame().right - rect.right, 0); 135 136 BWindow* window = new BWindow(rect, name, B_BORDERED_WINDOW_LOOK, 137 B_FLOATING_ALL_WINDOW_FEEL, 138 B_NOT_MOVABLE | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE 139 | B_NOT_RESIZABLE | B_AVOID_FOCUS | B_NO_WORKSPACE_ACTIVATION 140 | B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS); 141 142 TrackingView* trackingView = new TrackingView(window->Bounds(), 143 string, message); 144 trackingView->SetTarget(target); 145 window->AddChild(trackingView); 146 147 window->Sync(); 148 window->Show(); 149 150 return window; 151 } 152 153 154 GeneralInfoView::GeneralInfoView(Model* model) 155 : 156 BGroupView(B_VERTICAL), 157 fDivider(0), 158 fPreferredAppMenu(NULL), 159 fModel(model), 160 fMouseDown(false), 161 fTrackingState(no_track), 162 fPathWindow(NULL), 163 fLinkWindow(NULL), 164 fDescWindow(NULL), 165 fCurrentLinkColorWhich(B_LINK_TEXT_COLOR), 166 fCurrentPathColorWhich(fCurrentLinkColorWhich) 167 { 168 const char* fieldNames[] = { 169 B_TRANSLATE("Description:"), 170 B_TRANSLATE("Location:"), 171 B_TRANSLATE("Opens with:"), 172 B_TRANSLATE("Capacity:"), 173 B_TRANSLATE("Size:"), 174 B_TRANSLATE("Created:"), 175 B_TRANSLATE("Modified:"), 176 B_TRANSLATE("Kind:"), 177 B_TRANSLATE("Link to:"), 178 B_TRANSLATE("Version:"), 179 B_TRANSLATE("Filesystem:"), 180 NULL 181 }; 182 183 184 SetFlags(Flags() | B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS); 185 SetName(B_TRANSLATE("Information")); 186 // Set view color to standard background grey 187 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 188 SetFont(be_plain_font); 189 fFreeBytes = -1; 190 fSizeString = ""; 191 fSizeRect.Set(0, 0, 0, 0); 192 193 // Find offset for attributes, might be overiden below if there 194 // is a prefered handle menu displayed 195 BFont currentFont; 196 GetFont(¤tFont); 197 currentFont.SetSize(currentFont.Size() - 2); 198 199 // The widest string depends on the locale. We should check them all, this 200 // is only an approximation that works for English and French. 201 float width = 0; 202 for (int i = 0; fieldNames[i] != 0; i++) 203 width = std::max(width, StringWidth(fieldNames[i])); 204 fDivider = width + kBorderMargin + 1; 205 206 // Keep some free space for the stuff we print ourselves 207 float lineHeight = CurrentFontHeight(); 208 int lineCount = 7; 209 if (model->IsSymLink()) 210 lineCount += 1; // Add space for "Link to" line 211 if (model->IsExecutable()) 212 lineCount += 2; // Add space for "Version" and "Description" lines 213 GroupLayout()->SetInsets(kBorderMargin, lineHeight * lineCount, 214 B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING); 215 216 // Add a preferred handler pop-up menu if this item 217 // is a file...This goes in place of the Link To: 218 // string... 219 if (model->IsFile()) { 220 BMimeType mime(fModel->MimeType()); 221 BNodeInfo nodeInfo(fModel->Node()); 222 223 // But don't add the menu if the file is executable 224 if (!fModel->IsExecutable()) { 225 fPreferredAppMenu = new BMenuField("", "", new BPopUpMenu("")); 226 currentFont.SetSize(currentFont.Size() + 2); 227 fDivider = currentFont.StringWidth(B_TRANSLATE("Opens with:")) 228 + 5; 229 fPreferredAppMenu->SetDivider(fDivider); 230 fDivider += (kBorderMargin - 2); 231 fPreferredAppMenu->SetFont(¤tFont); 232 fPreferredAppMenu->SetHighUIColor(B_PANEL_TEXT_COLOR); 233 fPreferredAppMenu->SetLabel(B_TRANSLATE("Opens with:")); 234 235 char prefSignature[B_MIME_TYPE_LENGTH]; 236 nodeInfo.GetPreferredApp(prefSignature); 237 238 BMessage supportingAppList; 239 mime.GetSupportingApps(&supportingAppList); 240 241 // Add the default menu item and set it to marked 242 BMenuItem* result; 243 result = new BMenuItem(B_TRANSLATE("Default application"), 244 new BMessage(kSetPreferredApp)); 245 result->SetTarget(this); 246 fPreferredAppMenu->Menu()->AddItem(result); 247 result->SetMarked(true); 248 249 for (int32 index = 0; ; index++) { 250 const char* signature; 251 if (supportingAppList.FindString("applications", index, 252 &signature) != B_OK) { 253 break; 254 } 255 256 // Only add separator item if there are more items 257 if (index == 0) 258 fPreferredAppMenu->Menu()->AddSeparatorItem(); 259 260 BMessage* itemMessage = new BMessage(kSetPreferredApp); 261 itemMessage->AddString("signature", signature); 262 263 status_t err = B_ERROR; 264 entry_ref entry; 265 266 if (signature && signature[0]) 267 err = be_roster->FindApp(signature, &entry); 268 269 if (err != B_OK) 270 result = new BMenuItem(signature, itemMessage); 271 else 272 result = new BMenuItem(entry.name, itemMessage); 273 274 result->SetTarget(this); 275 fPreferredAppMenu->Menu()->AddItem(result); 276 if (strcmp(signature, prefSignature) == 0) 277 result->SetMarked(true); 278 } 279 280 AddChild(fPreferredAppMenu); 281 } 282 } 283 } 284 285 286 GeneralInfoView::~GeneralInfoView() 287 { 288 if (fPathWindow->Lock()) 289 fPathWindow->Quit(); 290 291 if (fLinkWindow->Lock()) 292 fLinkWindow->Quit(); 293 294 if (fDescWindow->Lock()) 295 fDescWindow->Quit(); 296 } 297 298 299 void 300 GeneralInfoView::InitStrings(const Model* model) 301 { 302 BMimeType mime; 303 char kind[B_MIME_TYPE_LENGTH]; 304 305 ASSERT(model->IsNodeOpen()); 306 307 BRect drawBounds(Bounds()); 308 drawBounds.left = fDivider; 309 310 // We'll do our own truncation later on in Draw() 311 WidgetAttributeText::AttrAsString(model, &fCreatedStr, kAttrStatCreated, 312 B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this); 313 WidgetAttributeText::AttrAsString(model, &fModifiedStr, kAttrStatModified, 314 B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this); 315 WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath, 316 B_STRING_TYPE, 0, this); 317 318 // Use the same method as used to resolve fIconModel, which handles 319 // both absolute and relative symlinks. if the link is broken, try to 320 // get a little more information. 321 if (model->IsSymLink()) { 322 bool linked = false; 323 324 Model resolvedModel(model->EntryRef(), true, true); 325 if (resolvedModel.InitCheck() == B_OK) { 326 // Get the path of the link 327 BPath traversedPath; 328 resolvedModel.GetPath(&traversedPath); 329 330 // If the BPath is initialized, then check the file for existence 331 if (traversedPath.InitCheck() == B_OK) { 332 BEntry entry(traversedPath.Path(), false); 333 // look at the target itself 334 if (entry.InitCheck() == B_OK && entry.Exists()) 335 linked = true; 336 } 337 } 338 339 // always show the target as it is: absolute or relative! 340 BSymLink symLink(model->EntryRef()); 341 char linkToPath[B_PATH_NAME_LENGTH]; 342 symLink.ReadLink(linkToPath, B_PATH_NAME_LENGTH); 343 fLinkToStr = linkToPath; 344 if (!linked) { 345 // link points to missing object 346 fLinkToStr += B_TRANSLATE(" (broken)"); 347 } 348 } else if (model->IsExecutable()) { 349 if (((Model*)model)->GetLongVersionString(fDescStr, 350 B_APP_VERSION_KIND) == B_OK) { 351 // we want a flat string, so replace all newlines and tabs 352 // with spaces 353 fDescStr.ReplaceAll('\n', ' '); 354 fDescStr.ReplaceAll('\t', ' '); 355 } else 356 fDescStr = "-"; 357 } else if (model->IsVolume()) { 358 const node_ref* modelNodeRef = fModel->NodeRef(); 359 fs_info modelInfo; 360 if (fs_stat_dev(modelNodeRef->device, &modelInfo) == B_OK) 361 { 362 fFileSystemStr = modelInfo.fsh_name; 363 fFileSystemStr << B_TRANSLATE(" (block size: ") 364 << modelInfo.block_size; 365 if ((modelInfo.flags & B_FS_HAS_QUERY) != 0) 366 fFileSystemStr += B_TRANSLATE(", indexed"); 367 fFileSystemStr += ")"; 368 } else 369 fFileSystemStr = B_TRANSLATE("(unknown)"); 370 } 371 372 if (mime.SetType(model->MimeType()) == B_OK 373 && mime.GetShortDescription(kind) == B_OK) 374 fKindStr = kind; 375 376 if (fKindStr.Length() == 0) 377 fKindStr = model->MimeType(); 378 } 379 380 381 void 382 GeneralInfoView::AttachedToWindow() 383 { 384 BFont font(be_plain_font); 385 386 font.SetSpacing(B_BITMAP_SPACING); 387 SetFont(&font); 388 389 CheckAndSetSize(); 390 if (fPreferredAppMenu) 391 fPreferredAppMenu->Menu()->SetTargetForItems(this); 392 393 _inherited::AttachedToWindow(); 394 } 395 396 397 void 398 GeneralInfoView::Pulse() 399 { 400 CheckAndSetSize(); 401 _inherited::Pulse(); 402 } 403 404 405 void 406 GeneralInfoView::ModelChanged(Model* model, BMessage* message) 407 { 408 BRect drawBounds(Bounds()); 409 drawBounds.left = fDivider; 410 411 switch (message->FindInt32("opcode")) { 412 case B_ENTRY_MOVED: 413 { 414 node_ref dirNode; 415 node_ref itemNode; 416 dirNode.device = itemNode.device = message->FindInt32("device"); 417 message->FindInt64("to directory", &dirNode.node); 418 message->FindInt64("node", &itemNode.node); 419 420 const char* name; 421 if (message->FindString("name", &name) != B_OK) 422 return; 423 424 // ensure notification is for us 425 if (*model->NodeRef() == itemNode 426 // For volumes, the device ID is obviously not handled in a 427 // consistent way; the node monitor sends us the ID of the 428 // parent device, while the model is set to the device of the 429 // volume directly - this hack works for volumes that are 430 // mounted in the root directory 431 || (model->IsVolume() 432 && itemNode.device == 1 433 && itemNode.node == model->NodeRef()->node)) { 434 model->UpdateEntryRef(&dirNode, name); 435 BString title; 436 title.SetToFormat(B_TRANSLATE_COMMENT("%s info", 437 "window title"), name); 438 Window()->SetTitle(title.String()); 439 WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath, 440 B_STRING_TYPE, 0, this); 441 Invalidate(); 442 } 443 break; 444 } 445 446 case B_STAT_CHANGED: 447 if (model->OpenNode() == B_OK) { 448 WidgetAttributeText::AttrAsString(model, &fCreatedStr, 449 kAttrStatCreated, B_TIME_TYPE, drawBounds.Width() 450 - kBorderMargin, this); 451 WidgetAttributeText::AttrAsString(model, &fModifiedStr, 452 kAttrStatModified, B_TIME_TYPE, drawBounds.Width() 453 - kBorderMargin, this); 454 455 // don't change the size if it's a directory 456 if (!model->IsDirectory()) { 457 fLastSize = model->StatBuf()->st_size; 458 fSizeString = ""; 459 BInfoWindow::GetSizeString(fSizeString, fLastSize, 0); 460 } 461 model->CloseNode(); 462 } 463 break; 464 465 case B_ATTR_CHANGED: 466 { 467 // watch for icon updates 468 const char* attrName; 469 if (message->FindString("attr", &attrName) == B_OK) { 470 if (strcmp(attrName, kAttrLargeIcon) == 0 471 || strcmp(attrName, kAttrIcon) == 0) { 472 IconCache::sIconCache->IconChanged(model->ResolveIfLink()); 473 Invalidate(); 474 } else if (strcmp(attrName, kAttrMIMEType) == 0) { 475 if (model->OpenNode() == B_OK) { 476 model->AttrChanged(attrName); 477 InitStrings(model); 478 model->CloseNode(); 479 } 480 Invalidate(); 481 } 482 } 483 break; 484 } 485 486 default: 487 break; 488 } 489 490 fModel = model; 491 if (fModel->IsSymLink()) { 492 InitStrings(model); 493 Invalidate(); 494 } 495 496 drawBounds.left = fDivider; 497 Invalidate(drawBounds); 498 } 499 500 501 // This only applies to symlinks. If the target of the symlink 502 // was changed, then we have to update the entire model. 503 // (Since in order to re-target a symlink, we had to delete 504 // the old model and create a new one; BSymLink::SetTarget(), 505 // would be nice) 506 507 void 508 GeneralInfoView::ReLinkTargetModel(Model* model) 509 { 510 fModel = model; 511 InitStrings(model); 512 Invalidate(Bounds()); 513 } 514 515 516 void 517 GeneralInfoView::MouseDown(BPoint where) 518 { 519 // Start tracking the mouse if we are in any of the hotspots 520 if (fLinkRect.Contains(where)) { 521 InvertRect(fLinkRect); 522 fTrackingState = link_track; 523 } else if (fPathRect.Contains(where)) { 524 InvertRect(fPathRect); 525 fTrackingState = path_track; 526 } else if (fSizeRect.Contains(where)) { 527 if (fModel->IsDirectory() && !fModel->IsVolume() 528 && !fModel->IsRoot()) { 529 InvertRect(fSizeRect); 530 fTrackingState = size_track; 531 } else 532 fTrackingState = no_track; 533 } 534 535 fMouseDown = true; 536 SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); 537 } 538 539 540 void 541 GeneralInfoView::MouseMoved(BPoint where, uint32, const BMessage* dragMessage) 542 { 543 fCurrentLinkColorWhich = B_LINK_TEXT_COLOR; 544 fCurrentPathColorWhich = fCurrentLinkColorWhich; 545 546 switch (fTrackingState) { 547 case link_track: 548 if (fLinkRect.Contains(where) != fMouseDown) { 549 fMouseDown = !fMouseDown; 550 InvertRect(fLinkRect); 551 } 552 break; 553 554 case path_track: 555 if (fPathRect.Contains(where) != fMouseDown) { 556 fMouseDown = !fMouseDown; 557 InvertRect(fPathRect); 558 } 559 break; 560 561 case size_track: 562 if (fSizeRect.Contains(where) != fMouseDown) { 563 fMouseDown = !fMouseDown; 564 InvertRect(fSizeRect); 565 } 566 break; 567 568 default: 569 { 570 // Only consider this if the window is the active window. 571 // We have to manually get the mouse here in the event that the 572 // mouse is over a pop-up window 573 uint32 buttons; 574 BPoint point; 575 GetMouse(&point, &buttons); 576 if (Window()->IsActive() && !buttons) { 577 // If we are down here, then that means that we're tracking 578 // the mouse but not from a mouse down. In this case, we're 579 // just interested in knowing whether or not we need to 580 // display the "pop-up" version of the path or link text. 581 BScreen screen(Window()); 582 BFont font; 583 GetFont(&font); 584 float maxWidth = (Bounds().Width() 585 - (fDivider + kBorderMargin)); 586 587 if (fPathRect.Contains(point)) { 588 if (fCurrentPathColorWhich != B_LINK_HOVER_COLOR) 589 fCurrentPathColorWhich = B_LINK_HOVER_COLOR; 590 591 if (font.StringWidth(fPathStr.String()) > maxWidth) { 592 fTrackingState = no_track; 593 BRect rect = ConvertToScreen(fPathRect); 594 595 if (fPathWindow == NULL 596 || BMessenger(fPathWindow).IsValid() == false) { 597 fPathWindow = OpenToolTipWindow(screen, rect, 598 "fPathWindow", fPathStr.String(), 599 BMessenger(this), 600 new BMessage(kOpenLinkSource)); 601 } 602 } 603 } else if (fLinkRect.Contains(point)) { 604 605 if (fCurrentLinkColorWhich != B_LINK_HOVER_COLOR) 606 fCurrentLinkColorWhich = B_LINK_HOVER_COLOR; 607 608 if (font.StringWidth(fLinkToStr.String()) > maxWidth) { 609 fTrackingState = no_track; 610 BRect rect = ConvertToScreen(fLinkRect); 611 612 if (!fLinkWindow 613 || BMessenger(fLinkWindow).IsValid() == false) { 614 fLinkWindow = OpenToolTipWindow(screen, rect, 615 "fLinkWindow", fLinkToStr.String(), 616 BMessenger(this), 617 new BMessage(kOpenLinkTarget)); 618 } 619 } 620 } else if (fDescRect.Contains(point) 621 && font.StringWidth(fDescStr.String()) > maxWidth) { 622 fTrackingState = no_track; 623 BRect rect = ConvertToScreen(fDescRect); 624 625 if (!fDescWindow 626 || BMessenger(fDescWindow).IsValid() == false) { 627 fDescWindow = OpenToolTipWindow(screen, rect, 628 "fDescWindow", fDescStr.String(), 629 BMessenger(this), NULL); 630 } 631 } 632 } 633 break; 634 } 635 } 636 637 DelayedInvalidate(16666, fPathRect); 638 DelayedInvalidate(16666, fLinkRect); 639 } 640 641 642 void 643 GeneralInfoView::OpenLinkSource() 644 { 645 OpenParentAndSelectOriginal(fModel->EntryRef()); 646 } 647 648 649 void 650 GeneralInfoView::OpenLinkTarget() 651 { 652 Model resolvedModel(fModel->EntryRef(), true, true); 653 BEntry entry; 654 if (resolvedModel.InitCheck() == B_OK) { 655 // Get the path of the link 656 BPath traversedPath; 657 resolvedModel.GetPath(&traversedPath); 658 659 // If the BPath is initialized, then check the file for existence 660 if (traversedPath.InitCheck() == B_OK) 661 entry.SetTo(traversedPath.Path()); 662 } 663 if (entry.InitCheck() != B_OK || !entry.Exists()) { 664 // Open a file dialog panel to allow the user to relink. 665 BInfoWindow* window = dynamic_cast<BInfoWindow*>(Window()); 666 if (window != NULL) 667 window->OpenFilePanel(fModel->EntryRef()); 668 } else { 669 entry_ref ref; 670 entry.GetRef(&ref); 671 BPath path(&ref); 672 printf("Opening link target: %s\n", path.Path()); 673 OpenParentAndSelectOriginal(&ref); 674 } 675 } 676 677 678 void 679 GeneralInfoView::MouseUp(BPoint where) 680 { 681 // Are we in the link rect? 682 if (fTrackingState == link_track && fLinkRect.Contains(where)) { 683 InvertRect(fLinkRect); 684 OpenLinkTarget(); 685 } else if (fTrackingState == path_track && fPathRect.Contains(where)) { 686 InvertRect(fPathRect); 687 OpenLinkSource(); 688 } else if (fTrackingState == size_track && fSizeRect.Contains(where)) { 689 // Recalculate size 690 Window()->PostMessage(kRecalculateSize); 691 } 692 693 // End mouse tracking 694 fMouseDown = false; 695 fTrackingState = no_track; 696 } 697 698 699 void 700 GeneralInfoView::CheckAndSetSize() 701 { 702 if (fModel->IsVolume() || fModel->IsRoot()) { 703 off_t freeBytes = 0; 704 off_t capacity = 0; 705 706 if (fModel->IsVolume()) { 707 BVolume volume(fModel->NodeRef()->device); 708 freeBytes = volume.FreeBytes(); 709 capacity = volume.Capacity(); 710 } else { 711 // iterate over all volumes 712 BVolumeRoster volumeRoster; 713 BVolume volume; 714 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 715 freeBytes += volume.FreeBytes(); 716 capacity += volume.Capacity(); 717 } 718 } 719 720 if (fFreeBytes == freeBytes) 721 return; 722 723 fFreeBytes = freeBytes; 724 725 fSizeString.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)")); 726 727 char sizeStr[128]; 728 string_for_size(capacity, sizeStr, sizeof(sizeStr)); 729 fSizeString.ReplaceFirst("%capacity", sizeStr); 730 string_for_size(capacity - fFreeBytes, sizeStr, sizeof(sizeStr)); 731 fSizeString.ReplaceFirst("%used", sizeStr); 732 string_for_size(fFreeBytes, sizeStr, sizeof(sizeStr)); 733 fSizeString.ReplaceFirst("%free", sizeStr); 734 735 } else if (fModel->IsFile()) { 736 // poll for size changes because they do not get node monitored 737 // until a file gets closed (with the old BFS) 738 StatStruct statBuf; 739 BModelOpener opener(fModel); 740 741 if (fModel->InitCheck() != B_OK 742 || fModel->Node()->GetStat(&statBuf) != B_OK) { 743 return; 744 } 745 746 if (fLastSize == statBuf.st_size) 747 return; 748 749 fLastSize = statBuf.st_size; 750 fSizeString = ""; 751 BInfoWindow::GetSizeString(fSizeString, fLastSize, 0); 752 } else 753 return; 754 755 SetSizeString(fSizeString); 756 } 757 758 759 void 760 GeneralInfoView::MessageReceived(BMessage* message) 761 { 762 switch (message->what) { 763 case kSetPreferredApp: 764 { 765 BNode node(fModel->EntryRef()); 766 BNodeInfo nodeInfo(&node); 767 768 const char* newSignature; 769 if (message->FindString("signature", &newSignature) != B_OK) 770 newSignature = NULL; 771 772 fModel->SetPreferredAppSignature(newSignature); 773 nodeInfo.SetPreferredApp(newSignature); 774 break; 775 } 776 777 case kOpenLinkSource: 778 OpenLinkSource(); 779 break; 780 781 case kOpenLinkTarget: 782 OpenLinkTarget(); 783 break; 784 785 default: 786 _inherited::MessageReceived(message); 787 break; 788 } 789 } 790 791 792 void 793 GeneralInfoView::FrameResized(float, float) 794 { 795 BModelOpener opener(fModel); 796 797 // Truncate the strings according to the new width 798 InitStrings(fModel); 799 } 800 801 802 void 803 GeneralInfoView::Draw(BRect) 804 { 805 // Set the low color for anti-aliasing 806 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 807 808 // Clear the old contents 809 SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 810 FillRect(Bounds()); 811 812 rgb_color labelColor = ui_color(B_PANEL_TEXT_COLOR); 813 rgb_color attributeColor = mix_color(HighColor(), labelColor, 192); 814 815 // Font information 816 font_height fontMetrics; 817 float lineHeight = 0; 818 float lineBase = 0; 819 // Draw the attribute font stuff 820 SetFont(be_plain_font); 821 GetFontHeight(&fontMetrics); 822 lineHeight = CurrentFontHeight() + 5; 823 824 // Starting base line for the first string 825 lineBase = lineHeight; 826 827 // Capacity/size 828 SetHighColor(labelColor); 829 if (fModel->IsVolume() || fModel->IsRoot()) { 830 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Capacity:"))), 831 lineBase)); 832 DrawString(B_TRANSLATE("Capacity:")); 833 } else { 834 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Size:"))), 835 lineBase)); 836 fSizeRect.left = fDivider + 2; 837 fSizeRect.top = lineBase - fontMetrics.ascent; 838 fSizeRect.bottom = lineBase + fontMetrics.descent; 839 DrawString(B_TRANSLATE("Size:")); 840 } 841 842 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 843 SetHighColor(attributeColor); 844 // Check for possible need of truncation 845 if (StringWidth(fSizeString.String()) 846 > (Bounds().Width() - (fDivider + kBorderMargin))) { 847 BString tmpString(fSizeString.String()); 848 TruncateString(&tmpString, B_TRUNCATE_MIDDLE, 849 Bounds().Width() - (fDivider + kBorderMargin)); 850 DrawString(tmpString.String()); 851 fSizeRect.right = fSizeRect.left + StringWidth(tmpString.String()) 852 + 3; 853 } else { 854 DrawString(fSizeString.String()); 855 fSizeRect.right = fSizeRect.left + StringWidth(fSizeString.String()) + 3; 856 } 857 lineBase += lineHeight; 858 859 // Created 860 SetHighColor(labelColor); 861 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Created:"))), 862 lineBase)); 863 DrawString(B_TRANSLATE("Created:")); 864 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 865 SetHighColor(attributeColor); 866 DrawString(fCreatedStr.String()); 867 lineBase += lineHeight; 868 869 // Modified 870 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Modified:"))), 871 lineBase)); 872 SetHighColor(labelColor); 873 DrawString(B_TRANSLATE("Modified:")); 874 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 875 SetHighColor(attributeColor); 876 DrawString(fModifiedStr.String()); 877 lineBase += lineHeight; 878 879 // Kind 880 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Kind:"))), 881 lineBase)); 882 SetHighColor(labelColor); 883 DrawString(B_TRANSLATE("Kind:")); 884 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 885 SetHighColor(attributeColor); 886 DrawString(fKindStr.String()); 887 lineBase += lineHeight; 888 889 BFont normalFont; 890 GetFont(&normalFont); 891 892 // Path 893 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Location:"))), 894 lineBase)); 895 SetHighColor(labelColor); 896 DrawString(B_TRANSLATE("Location:")); 897 898 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 899 SetHighUIColor(fCurrentPathColorWhich); 900 901 // Check for truncation 902 if (StringWidth(fPathStr.String()) > (Bounds().Width() 903 - (fDivider + kBorderMargin))) { 904 BString nameString(fPathStr.String()); 905 TruncateString(&nameString, B_TRUNCATE_MIDDLE, 906 Bounds().Width() - (fDivider + kBorderMargin)); 907 DrawString(nameString.String()); 908 } else 909 DrawString(fPathStr.String()); 910 911 // Cache the position of the path 912 fPathRect.top = lineBase - fontMetrics.ascent; 913 fPathRect.bottom = lineBase + fontMetrics.descent; 914 fPathRect.left = fDivider + 2; 915 fPathRect.right = fPathRect.left + StringWidth(fPathStr.String()) + 3; 916 917 lineBase += lineHeight; 918 919 // Link to/version 920 if (fModel->IsSymLink()) { 921 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Link to:"))), 922 lineBase)); 923 SetHighColor(labelColor); 924 DrawString(B_TRANSLATE("Link to:")); 925 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 926 SetHighUIColor(fCurrentLinkColorWhich); 927 928 // Check for truncation 929 if (StringWidth(fLinkToStr.String()) > (Bounds().Width() 930 - (fDivider + kBorderMargin))) { 931 BString nameString(fLinkToStr.String()); 932 TruncateString(&nameString, B_TRUNCATE_MIDDLE, 933 Bounds().Width() - (fDivider + kBorderMargin)); 934 DrawString(nameString.String()); 935 } else 936 DrawString(fLinkToStr.String()); 937 938 // Cache the position of the link field 939 fLinkRect.top = lineBase - fontMetrics.ascent; 940 fLinkRect.bottom = lineBase + fontMetrics.descent; 941 fLinkRect.left = fDivider + 2; 942 fLinkRect.right = fLinkRect.left + StringWidth(fLinkToStr.String()) 943 + 3; 944 945 // No description field 946 fDescRect = BRect(-1, -1, -1, -1); 947 } else if (fModel->IsExecutable()) { 948 //Version 949 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Version:"))), 950 lineBase)); 951 SetHighColor(labelColor); 952 DrawString(B_TRANSLATE("Version:")); 953 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 954 SetHighColor(attributeColor); 955 BString nameString; 956 if (fModel->GetVersionString(nameString, B_APP_VERSION_KIND) == B_OK) 957 DrawString(nameString.String()); 958 else 959 DrawString("-"); 960 lineBase += lineHeight; 961 962 // Description 963 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Description:"))), 964 lineBase)); 965 SetHighColor(labelColor); 966 DrawString(B_TRANSLATE("Description:")); 967 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 968 SetHighColor(attributeColor); 969 // Check for truncation 970 if (StringWidth(fDescStr.String()) > (Bounds().Width() 971 - (fDivider + kBorderMargin))) { 972 BString nameString(fDescStr.String()); 973 TruncateString(&nameString, B_TRUNCATE_MIDDLE, 974 Bounds().Width() - (fDivider + kBorderMargin)); 975 DrawString(nameString.String()); 976 } else 977 DrawString(fDescStr.String()); 978 979 // Cache the position of the description field 980 fDescRect.top = lineBase - fontMetrics.ascent; 981 fDescRect.bottom = lineBase + fontMetrics.descent; 982 fDescRect.left = fDivider + 2; 983 fDescRect.right = fDescRect.left + StringWidth(fDescStr.String()) + 3; 984 985 // No link field 986 fLinkRect = BRect(-1, -1, -1, -1); 987 } else if (fModel->IsVolume()) { 988 //Filesystem 989 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Filesystem:"))), 990 lineBase)); 991 SetHighColor(labelColor); 992 DrawString(B_TRANSLATE("Filesystem:")); 993 MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); 994 SetHighColor(attributeColor); 995 // Check for truncation 996 if (StringWidth(fFileSystemStr.String()) > (Bounds().Width() 997 - (fDivider + kBorderMargin))) { 998 BString nameString(fFileSystemStr.String()); 999 TruncateString(&nameString, B_TRUNCATE_MIDDLE, 1000 Bounds().Width() - (fDivider + kBorderMargin)); 1001 DrawString(nameString.String()); 1002 } else 1003 DrawString(fFileSystemStr.String()); 1004 1005 // No description field or link field 1006 fDescRect = BRect(-1, -1, -1, -1); 1007 fLinkRect = BRect(-1, -1, -1, -1); 1008 } 1009 } 1010 1011 1012 void 1013 GeneralInfoView::WindowActivated(bool active) 1014 { 1015 if (active) 1016 return; 1017 1018 if (fPathWindow->Lock()) { 1019 fPathWindow->Quit(); 1020 fPathWindow = NULL; 1021 } 1022 1023 if (fLinkWindow->Lock()) { 1024 fLinkWindow->Quit(); 1025 fLinkWindow = NULL; 1026 } 1027 1028 if (fDescWindow->Lock()) { 1029 fDescWindow->Quit(); 1030 fDescWindow = NULL; 1031 } 1032 } 1033 1034 1035 float 1036 GeneralInfoView::CurrentFontHeight() 1037 { 1038 BFont font; 1039 GetFont(&font); 1040 font_height fontHeight; 1041 font.GetHeight(&fontHeight); 1042 1043 return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2; 1044 } 1045 1046 1047 off_t 1048 GeneralInfoView::LastSize() const 1049 { 1050 return fLastSize; 1051 } 1052 1053 1054 void 1055 GeneralInfoView::SetLastSize(off_t lastSize) 1056 { 1057 fLastSize = lastSize; 1058 } 1059 1060 1061 void 1062 GeneralInfoView::SetSizeString(const char* sizeString) 1063 { 1064 fSizeString = sizeString; 1065 1066 float lineHeight = CurrentFontHeight() + 6; 1067 BRect bounds(fDivider, 0, Bounds().right, lineHeight); 1068 Invalidate(bounds); 1069 } 1070 1071 1072 // #pragma mark - 1073 1074 1075 TrackingView::TrackingView(BRect frame, const char* str, BMessage* message) 1076 : BControl(frame, "trackingView", str, message, B_FOLLOW_ALL, 1077 B_WILL_DRAW), 1078 fMouseDown(false), 1079 fMouseInView(false) 1080 { 1081 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1082 SetEventMask(B_POINTER_EVENTS, 0); 1083 } 1084 1085 1086 void 1087 TrackingView::MouseDown(BPoint) 1088 { 1089 if (Message() != NULL) { 1090 fMouseDown = true; 1091 fMouseInView = true; 1092 InvertRect(Bounds()); 1093 } 1094 } 1095 1096 1097 void 1098 TrackingView::MouseMoved(BPoint, uint32 transit, const BMessage*) 1099 { 1100 if ((transit == B_ENTERED_VIEW || transit == B_EXITED_VIEW) && fMouseDown) 1101 InvertRect(Bounds()); 1102 1103 fMouseInView = (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW); 1104 DelayedInvalidate(16666, Bounds()); 1105 if (!fMouseInView && !fMouseDown) 1106 Window()->Close(); 1107 } 1108 1109 1110 void 1111 TrackingView::MouseUp(BPoint) 1112 { 1113 if (Message() != NULL) { 1114 if (fMouseInView) 1115 Invoke(); 1116 1117 fMouseDown = false; 1118 Window()->Close(); 1119 } 1120 } 1121 1122 1123 void 1124 TrackingView::Draw(BRect) 1125 { 1126 if (Message() != NULL) 1127 SetHighUIColor(fMouseInView ? B_LINK_HOVER_COLOR 1128 : B_LINK_TEXT_COLOR); 1129 else 1130 SetHighUIColor(B_PANEL_TEXT_COLOR); 1131 SetLowColor(ViewColor()); 1132 1133 font_height fontHeight; 1134 GetFontHeight(&fontHeight); 1135 1136 DrawString(Label(), BPoint(3, Bounds().Height() - fontHeight.descent)); 1137 } 1138