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 709 if (fModel->IsVolume()) { 710 BVolume volume(fModel->NodeRef()->device); 711 freeBytes = volume.FreeBytes(); 712 capacity = volume.Capacity(); 713 } else { 714 // iterate over all volumes 715 BVolumeRoster volumeRoster; 716 BVolume volume; 717 while (volumeRoster.GetNextVolume(&volume) == B_OK) { 718 freeBytes += volume.FreeBytes(); 719 capacity += volume.Capacity(); 720 } 721 } 722 723 if (fFreeBytes == freeBytes) 724 return; 725 726 fFreeBytes = freeBytes; 727 728 fSizeString.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)")); 729 730 char sizeStr[128]; 731 string_for_size(capacity, sizeStr, sizeof(sizeStr)); 732 fSizeString.ReplaceFirst("%capacity", sizeStr); 733 string_for_size(capacity - fFreeBytes, sizeStr, sizeof(sizeStr)); 734 fSizeString.ReplaceFirst("%used", sizeStr); 735 string_for_size(fFreeBytes, sizeStr, sizeof(sizeStr)); 736 fSizeString.ReplaceFirst("%free", sizeStr); 737 738 } else if (fModel->IsFile()) { 739 // poll for size changes because they do not get node monitored 740 // until a file gets closed (with the old BFS) 741 StatStruct statBuf; 742 BModelOpener opener(fModel); 743 744 if (fModel->InitCheck() != B_OK 745 || fModel->Node()->GetStat(&statBuf) != B_OK) { 746 return; 747 } 748 749 if (fLastSize == statBuf.st_size) 750 return; 751 752 fLastSize = statBuf.st_size; 753 fSizeString = ""; 754 BInfoWindow::GetSizeString(fSizeString, fLastSize, 0); 755 } else 756 return; 757 758 SetSizeString(fSizeString); 759 } 760 761 762 void 763 GeneralInfoView::MessageReceived(BMessage* message) 764 { 765 switch (message->what) { 766 case kSetPreferredApp: 767 { 768 BNode node(fModel->EntryRef()); 769 BNodeInfo nodeInfo(&node); 770 771 const char* newSignature; 772 if (message->FindString("signature", &newSignature) != B_OK) 773 newSignature = NULL; 774 775 fModel->SetPreferredAppSignature(newSignature); 776 nodeInfo.SetPreferredApp(newSignature); 777 break; 778 } 779 780 case kOpenLinkSource: 781 OpenLinkSource(); 782 break; 783 784 case kOpenLinkTarget: 785 OpenLinkTarget(); 786 break; 787 788 default: 789 _inherited::MessageReceived(message); 790 break; 791 } 792 } 793 794 795 void 796 GeneralInfoView::FrameResized(float, float) 797 { 798 BModelOpener opener(fModel); 799 800 // Truncate the strings according to the new width 801 InitStrings(fModel); 802 } 803 804 805 void 806 GeneralInfoView::Draw(BRect) 807 { 808 // Set the low color for anti-aliasing 809 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 810 811 // Clear the old contents 812 SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 813 FillRect(Bounds()); 814 815 rgb_color labelColor = ui_color(B_PANEL_TEXT_COLOR); 816 rgb_color attributeColor = mix_color(HighColor(), labelColor, 192); 817 818 // Font information 819 font_height fontMetrics; 820 float lineHeight = 0; 821 float lineBase = 0; 822 // Draw the attribute font stuff 823 SetFont(be_plain_font); 824 GetFontHeight(&fontMetrics); 825 lineHeight = CurrentFontHeight() + 5; 826 827 // Starting base line for the first string 828 lineBase = lineHeight; 829 830 // Capacity/size 831 SetHighColor(labelColor); 832 if (fModel->IsVolume() || fModel->IsRoot()) { 833 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Capacity:"))), 834 lineBase)); 835 DrawString(B_TRANSLATE("Capacity:")); 836 } else { 837 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Size:"))), 838 lineBase)); 839 fSizeRect.left = fDivider + 2; 840 fSizeRect.top = lineBase - fontMetrics.ascent; 841 fSizeRect.bottom = lineBase + fontMetrics.descent; 842 DrawString(B_TRANSLATE("Size:")); 843 } 844 845 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 846 SetHighColor(attributeColor); 847 // Check for possible need of truncation 848 if (StringWidth(fSizeString.String()) 849 > (Bounds().Width() - (fDivider + sBorderMargin))) { 850 BString tmpString(fSizeString.String()); 851 TruncateString(&tmpString, B_TRUNCATE_MIDDLE, 852 Bounds().Width() - (fDivider + sBorderMargin)); 853 DrawString(tmpString.String()); 854 fSizeRect.right = fSizeRect.left + StringWidth(tmpString.String()) 855 + 3; 856 } else { 857 DrawString(fSizeString.String()); 858 fSizeRect.right = fSizeRect.left + StringWidth(fSizeString.String()) + 3; 859 } 860 lineBase += lineHeight; 861 862 // Created 863 SetHighColor(labelColor); 864 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Created:"))), 865 lineBase)); 866 DrawString(B_TRANSLATE("Created:")); 867 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 868 SetHighColor(attributeColor); 869 DrawString(fCreatedStr.String()); 870 lineBase += lineHeight; 871 872 // Modified 873 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Modified:"))), 874 lineBase)); 875 SetHighColor(labelColor); 876 DrawString(B_TRANSLATE("Modified:")); 877 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 878 SetHighColor(attributeColor); 879 DrawString(fModifiedStr.String()); 880 lineBase += lineHeight; 881 882 // Kind 883 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Kind:"))), 884 lineBase)); 885 SetHighColor(labelColor); 886 DrawString(B_TRANSLATE("Kind:")); 887 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 888 SetHighColor(attributeColor); 889 DrawString(fKindStr.String()); 890 lineBase += lineHeight; 891 892 BFont normalFont; 893 GetFont(&normalFont); 894 895 // Path 896 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Location:"))), 897 lineBase)); 898 SetHighColor(labelColor); 899 DrawString(B_TRANSLATE("Location:")); 900 901 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 902 SetHighUIColor(fCurrentPathColorWhich); 903 904 // Check for truncation 905 if (StringWidth(fPathStr.String()) > (Bounds().Width() 906 - (fDivider + sBorderMargin))) { 907 BString nameString(fPathStr.String()); 908 TruncateString(&nameString, B_TRUNCATE_MIDDLE, 909 Bounds().Width() - (fDivider + sBorderMargin)); 910 DrawString(nameString.String()); 911 } else 912 DrawString(fPathStr.String()); 913 914 // Cache the position of the path 915 fPathRect.top = lineBase - fontMetrics.ascent; 916 fPathRect.bottom = lineBase + fontMetrics.descent; 917 fPathRect.left = fDivider + 2; 918 fPathRect.right = fPathRect.left + StringWidth(fPathStr.String()) + 3; 919 920 lineBase += lineHeight; 921 922 // Link to/version 923 if (fModel->IsSymLink()) { 924 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Link to:"))), 925 lineBase)); 926 SetHighColor(labelColor); 927 DrawString(B_TRANSLATE("Link to:")); 928 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 929 SetHighUIColor(fCurrentLinkColorWhich); 930 931 // Check for truncation 932 if (StringWidth(fLinkToStr.String()) > (Bounds().Width() 933 - (fDivider + sBorderMargin))) { 934 BString nameString(fLinkToStr.String()); 935 TruncateString(&nameString, B_TRUNCATE_MIDDLE, 936 Bounds().Width() - (fDivider + sBorderMargin)); 937 DrawString(nameString.String()); 938 } else 939 DrawString(fLinkToStr.String()); 940 941 // Cache the position of the link field 942 fLinkRect.top = lineBase - fontMetrics.ascent; 943 fLinkRect.bottom = lineBase + fontMetrics.descent; 944 fLinkRect.left = fDivider + 2; 945 fLinkRect.right = fLinkRect.left + StringWidth(fLinkToStr.String()) 946 + 3; 947 948 // No description field 949 fDescRect = BRect(-1, -1, -1, -1); 950 } else if (fModel->IsExecutable()) { 951 //Version 952 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Version:"))), 953 lineBase)); 954 SetHighColor(labelColor); 955 DrawString(B_TRANSLATE("Version:")); 956 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 957 SetHighColor(attributeColor); 958 BString nameString; 959 if (fModel->GetVersionString(nameString, B_APP_VERSION_KIND) == B_OK) 960 DrawString(nameString.String()); 961 else 962 DrawString("-"); 963 lineBase += lineHeight; 964 965 // Description 966 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Description:"))), 967 lineBase)); 968 SetHighColor(labelColor); 969 DrawString(B_TRANSLATE("Description:")); 970 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 971 SetHighColor(attributeColor); 972 // Check for truncation 973 if (StringWidth(fDescStr.String()) > (Bounds().Width() 974 - (fDivider + sBorderMargin))) { 975 BString nameString(fDescStr.String()); 976 TruncateString(&nameString, B_TRUNCATE_MIDDLE, 977 Bounds().Width() - (fDivider + sBorderMargin)); 978 DrawString(nameString.String()); 979 } else 980 DrawString(fDescStr.String()); 981 982 // Cache the position of the description field 983 fDescRect.top = lineBase - fontMetrics.ascent; 984 fDescRect.bottom = lineBase + fontMetrics.descent; 985 fDescRect.left = fDivider + 2; 986 fDescRect.right = fDescRect.left + StringWidth(fDescStr.String()) + 3; 987 988 // No link field 989 fLinkRect = BRect(-1, -1, -1, -1); 990 } else if (fModel->IsVolume()) { 991 //Filesystem 992 MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Filesystem:"))), 993 lineBase)); 994 SetHighColor(labelColor); 995 DrawString(B_TRANSLATE("Filesystem:")); 996 MovePenTo(BPoint(fDivider + sDrawMargin, lineBase)); 997 SetHighColor(attributeColor); 998 // Check for truncation 999 if (StringWidth(fFileSystemStr.String()) > (Bounds().Width() 1000 - (fDivider + sBorderMargin))) { 1001 BString nameString(fFileSystemStr.String()); 1002 TruncateString(&nameString, B_TRUNCATE_MIDDLE, 1003 Bounds().Width() - (fDivider + sBorderMargin)); 1004 DrawString(nameString.String()); 1005 } else 1006 DrawString(fFileSystemStr.String()); 1007 1008 // No description field or link field 1009 fDescRect = BRect(-1, -1, -1, -1); 1010 fLinkRect = BRect(-1, -1, -1, -1); 1011 } 1012 } 1013 1014 1015 void 1016 GeneralInfoView::WindowActivated(bool active) 1017 { 1018 if (active) 1019 return; 1020 1021 if (fPathWindow->Lock()) { 1022 fPathWindow->Quit(); 1023 fPathWindow = NULL; 1024 } 1025 1026 if (fLinkWindow->Lock()) { 1027 fLinkWindow->Quit(); 1028 fLinkWindow = NULL; 1029 } 1030 1031 if (fDescWindow->Lock()) { 1032 fDescWindow->Quit(); 1033 fDescWindow = NULL; 1034 } 1035 } 1036 1037 1038 float 1039 GeneralInfoView::CurrentFontHeight() 1040 { 1041 BFont font; 1042 GetFont(&font); 1043 font_height fontHeight; 1044 font.GetHeight(&fontHeight); 1045 1046 return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2; 1047 } 1048 1049 1050 off_t 1051 GeneralInfoView::LastSize() const 1052 { 1053 return fLastSize; 1054 } 1055 1056 1057 void 1058 GeneralInfoView::SetLastSize(off_t lastSize) 1059 { 1060 fLastSize = lastSize; 1061 } 1062 1063 1064 void 1065 GeneralInfoView::SetSizeString(const char* sizeString) 1066 { 1067 fSizeString = sizeString; 1068 1069 float lineHeight = CurrentFontHeight() + 6; 1070 BRect bounds(fDivider, 0, Bounds().right, lineHeight); 1071 Invalidate(bounds); 1072 } 1073 1074 1075 // #pragma mark - 1076 1077 1078 TrackingView::TrackingView(BRect frame, const char* str, BMessage* message) 1079 : BControl(frame, "trackingView", str, message, B_FOLLOW_ALL, 1080 B_WILL_DRAW), 1081 fMouseDown(false), 1082 fMouseInView(false) 1083 { 1084 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1085 SetEventMask(B_POINTER_EVENTS, 0); 1086 } 1087 1088 1089 void 1090 TrackingView::MouseDown(BPoint) 1091 { 1092 if (Message() != NULL) { 1093 fMouseDown = true; 1094 fMouseInView = true; 1095 InvertRect(Bounds()); 1096 } 1097 } 1098 1099 1100 void 1101 TrackingView::MouseMoved(BPoint, uint32 transit, const BMessage*) 1102 { 1103 if ((transit == B_ENTERED_VIEW || transit == B_EXITED_VIEW) && fMouseDown) 1104 InvertRect(Bounds()); 1105 1106 fMouseInView = (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW); 1107 DelayedInvalidate(16666, Bounds()); 1108 if (!fMouseInView && !fMouseDown) 1109 Window()->Close(); 1110 } 1111 1112 1113 void 1114 TrackingView::MouseUp(BPoint) 1115 { 1116 if (Message() != NULL) { 1117 if (fMouseInView) 1118 Invoke(); 1119 1120 fMouseDown = false; 1121 Window()->Close(); 1122 } 1123 } 1124 1125 1126 void 1127 TrackingView::Draw(BRect) 1128 { 1129 if (Message() != NULL) 1130 SetHighUIColor(fMouseInView ? B_LINK_HOVER_COLOR 1131 : B_LINK_TEXT_COLOR); 1132 else 1133 SetHighUIColor(B_PANEL_TEXT_COLOR); 1134 SetLowColor(ViewColor()); 1135 1136 font_height fontHeight; 1137 GetFontHeight(&fontHeight); 1138 1139 DrawString(Label(), BPoint(3, Bounds().Height() - fontHeight.descent)); 1140 } 1141