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