1/* 2 * Copyright (c) 2008 Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright (c) 2009 Philippe Saint-Pierre, stpere@gmail.com 4 * All rights reserved. Distributed under the terms of the MIT license. 5 * 6 * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software 7 * as long as it is accompanied by it's documentation and this copyright notice. 8 * The software comes with no warranty, etc. 9 */ 10 11 12#include "PieView.h" 13 14#include <fs_info.h> 15#include <math.h> 16 17#include <AppFileInfo.h> 18#include <Bitmap.h> 19#include <Catalog.h> 20#include <ControlLook.h> 21#include <Entry.h> 22#include <File.h> 23#include <MenuItem.h> 24#include <Messenger.h> 25#include <Path.h> 26#include <PopUpMenu.h> 27#include <Roster.h> 28#include <String.h> 29#include <Volume.h> 30 31#include <tracker_private.h> 32 33#include "Commands.h" 34#include "DiskUsage.h" 35#include "InfoWindow.h" 36#include "MainWindow.h" 37#include "Scanner.h" 38 39#undef B_TRANSLATION_CONTEXT 40#define B_TRANSLATION_CONTEXT "Pie View" 41 42static const int32 kIdxGetInfo = 0; 43static const int32 kIdxOpen = 1; 44static const int32 kIdxOpenWith = 2; 45static const int32 kIdxRescan = 3; 46 47 48class AppMenuItem : public BMenuItem { 49public: 50 AppMenuItem(const char* appSig, int category); 51 virtual ~AppMenuItem(); 52 53 virtual void GetContentSize(float* _width, float* _height); 54 virtual void DrawContent(); 55 56 int Category() const 57 { return fCategory; } 58 const entry_ref* AppRef() const 59 { return &fAppRef; } 60 bool IsValid() const 61 { return fIsValid; } 62 63private: 64 int fCategory; 65 BBitmap* fIcon; 66 entry_ref fAppRef; 67 bool fIsValid; 68}; 69 70 71AppMenuItem::AppMenuItem(const char* appSig, int category) 72 : 73 BMenuItem(kEmptyStr, NULL), 74 fCategory(category), 75 fIcon(NULL), 76 fIsValid(false) 77{ 78 if (be_roster->FindApp(appSig, &fAppRef) == B_NO_ERROR) { 79 fIcon = new BBitmap(BRect(0.0, 0.0, 15.0, 15.0), B_RGBA32); 80 if (BNodeInfo::GetTrackerIcon(&fAppRef, fIcon, B_MINI_ICON) == B_OK) { 81 BEntry appEntry(&fAppRef); 82 if (appEntry.InitCheck() == B_OK) { 83 char name[B_FILE_NAME_LENGTH]; 84 appEntry.GetName(name); 85 SetLabel(name); 86 fIsValid = true; 87 } 88 } 89 } 90} 91 92 93AppMenuItem::~AppMenuItem() 94{ 95 delete fIcon; 96} 97 98 99void 100AppMenuItem::GetContentSize(float* _width, float* _height) 101{ 102 if (_width) 103 *_width = fIcon->Bounds().Width() + 104 be_plain_font->StringWidth(Label()); 105 106 if (_height) { 107 struct font_height fh; 108 be_plain_font->GetHeight(&fh); 109 float fontHeight = ceilf(fh.ascent) + ceilf(fh.descent) 110 + ceilf(fh.leading); 111 *_height = max_c(fontHeight, fIcon->Bounds().Height()); 112 } 113} 114 115 116void 117AppMenuItem::DrawContent() 118{ 119 Menu()->SetDrawingMode(B_OP_OVER); 120 Menu()->MovePenBy(0.0, -1.0); 121 Menu()->DrawBitmap(fIcon); 122 Menu()->MovePenBy(fIcon->Bounds().Width() + kSmallHMargin, 0.0); 123 BMenuItem::DrawContent(); 124} 125 126 127// #pragma mark - PieView 128 129 130PieView::PieView(BVolume* volume) 131 : 132 BView(NULL, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_SUBPIXEL_PRECISE), 133 fWindow(NULL), 134 fScanner(NULL), 135 fVolume(volume), 136 fMouseOverInfo(), 137 fClicked(false), 138 fDragging(false), 139 fUpdateFileAt(false) 140{ 141 fMouseOverMenu = new BPopUpMenu(kEmptyStr, false, false); 142 fMouseOverMenu->AddItem(new BMenuItem(B_TRANSLATE("Get info"), NULL), 143 kIdxGetInfo); 144 fMouseOverMenu->AddItem(new BMenuItem(B_TRANSLATE("Open"), NULL), 145 kIdxOpen); 146 147 fFileUnavailableMenu = new BPopUpMenu(kEmptyStr, false, false); 148 BMenuItem* item = new BMenuItem(B_TRANSLATE("file unavailable"), NULL); 149 item->SetEnabled(false); 150 fFileUnavailableMenu->AddItem(item); 151 152 BFont font; 153 GetFont(&font); 154 font.SetSize(ceilf(font.Size() * 1.33)); 155 font.SetFace(B_BOLD_FACE); 156 SetFont(&font); 157 158 struct font_height fh; 159 font.GetHeight(&fh); 160 fFontHeight = ceilf(fh.ascent) + ceilf(fh.descent) + ceilf(fh.leading); 161} 162 163 164void 165PieView::AttachedToWindow() 166{ 167 fWindow = (MainWindow*)Window(); 168} 169 170 171PieView::~PieView() 172{ 173 delete fMouseOverMenu; 174 delete fFileUnavailableMenu; 175 if (fScanner != NULL) 176 fScanner->RequestQuit(); 177} 178 179 180void 181PieView::MessageReceived(BMessage* message) 182{ 183 switch (message->what) { 184 case kBtnCancel: 185 if (fScanner != NULL) 186 fScanner->Cancel(); 187 break; 188 case kBtnRescan: 189 if (fVolume != NULL) { 190 if (fScanner != NULL) 191 fScanner->Refresh(); 192 else 193 _ShowVolume(fVolume); 194 fWindow->EnableCancel(); 195 Invalidate(); 196 } 197 break; 198 199 case kScanDone: 200 fWindow->EnableRescan(); 201 case kScanProgress: 202 Invalidate(); 203 break; 204 205 default: 206 BView::MessageReceived(message); 207 break; 208 } 209} 210 211 212void 213PieView::MouseDown(BPoint where) 214{ 215 uint32 buttons; 216 BMessage* current = Window()->CurrentMessage(); 217 if (current->FindInt32("buttons", (int32*)&buttons) != B_OK) 218 buttons = B_PRIMARY_MOUSE_BUTTON; 219 220 FileInfo* info = _FileAt(where); 221 if (info == NULL || info->pseudo) 222 return; 223 224 if (buttons & B_PRIMARY_MOUSE_BUTTON) { 225 fClicked = true; 226 fDragStart = where; 227 fClickedFile = info; 228 SetMouseEventMask(B_POINTER_EVENTS); 229 } else if (buttons & B_SECONDARY_MOUSE_BUTTON) { 230 where = ConvertToScreen(where); 231 _ShowContextMenu(info, where); 232 } 233} 234 235 236void 237PieView::MouseUp(BPoint where) 238{ 239 // If the primary button was released and there was no dragging happening, 240 // just zoom in or out. 241 if (fClicked && !fDragging) { 242 FileInfo* info = _FileAt(where); 243 if (info != NULL) { 244 if (info == fScanner->CurrentDir()) { 245 fScanner->ChangeDir(info->parent); 246 fLastWhere = where; 247 fUpdateFileAt = true; 248 Invalidate(); 249 } else if (info->children.size() > 0) { 250 fScanner->ChangeDir(info); 251 fLastWhere = where; 252 fUpdateFileAt = true; 253 Invalidate(); 254 } 255 } 256 } 257 258 fClicked = false; 259 fDragging = false; 260} 261 262 263void 264PieView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 265{ 266 if (fClicked) { 267 // Primary mouse button is down. 268 if (fDragging) 269 return; 270 // If the mouse has moved far enough, initiate dragging. 271 BPoint diff = where - fDragStart; 272 float distance = sqrtf(diff.x * diff.x + diff.y * diff.x); 273 if (distance > kDragThreshold) { 274 fDragging = true; 275 276 BBitmap* icon = new BBitmap(BRect(0.0, 0.0, 31.0, 31.0), B_RGBA32); 277 if (BNodeInfo::GetTrackerIcon(&fClickedFile->ref, icon, 278 B_LARGE_ICON) == B_OK) { 279 BMessage msg(B_SIMPLE_DATA); 280 msg.AddRef("refs", &fClickedFile->ref); 281 DragMessage(&msg, icon, B_OP_BLEND, BPoint(15.0, 15.0)); 282 } else 283 delete icon; 284 } 285 } else { 286 // Mouse button is not down, display file info. 287 if (transit == B_EXITED_VIEW) { 288 // Clear status view 289 fWindow->ShowInfo(NULL); 290 } else { 291 // Display file information. 292 fWindow->ShowInfo(_FileAt(where)); 293 } 294 } 295} 296 297 298void 299PieView::Draw(BRect updateRect) 300{ 301 if (fScanner != NULL) { 302 // There is a current volume. 303 if (fScanner->IsBusy()) { 304 // Show progress of scanning. 305 _DrawProgressBar(updateRect); 306 } else if (fScanner->Snapshot() != NULL) { 307 _DrawPieChart(updateRect); 308 if (fUpdateFileAt) { 309 fWindow->ShowInfo(_FileAt(fLastWhere)); 310 fUpdateFileAt = false; 311 } 312 } 313 } 314} 315 316 317void 318PieView::SetPath(BPath path) 319{ 320 if (fScanner == NULL) 321 _ShowVolume(fVolume); 322 323 if (fScanner != NULL) { 324 string desiredPath(path.Path()); 325 fScanner->SetDesiredPath(desiredPath); 326 Invalidate(); 327 } 328} 329 330 331// #pragma mark - private 332 333 334void 335PieView::_ShowVolume(BVolume* volume) 336{ 337 if (volume != NULL) { 338 if (fScanner == NULL) 339 fScanner = new Scanner(volume, this); 340 341 if (fScanner->Snapshot() == NULL) 342 fScanner->Refresh(); 343 } 344 345 Invalidate(); 346} 347 348 349void 350PieView::_DrawProgressBar(BRect updateRect) 351{ 352 // Show the progress of the scanning operation. 353 354 fMouseOverInfo.clear(); 355 356 FillRect(updateRect, B_SOLID_LOW); 357 358 // Draw the progress bar. 359 BRect b = Bounds(); 360 float bx = floorf((b.left + b.Width() - kProgBarWidth) / 2.0); 361 float by = floorf((b.top + b.Height() - kProgBarHeight) / 2.0); 362 float ex = bx + kProgBarWidth; 363 float ey = by + kProgBarHeight; 364 float mx = bx + floorf((kProgBarWidth - 2.0) * fScanner->Progress() + 0.5); 365 366 const rgb_color kBarColor = {50, 150, 255, 255}; 367 BRect barFrame(bx, by, ex, ey); 368 be_control_look->DrawStatusBar(this, barFrame, updateRect, 369 ui_color(B_PANEL_BACKGROUND_COLOR), kBarColor, mx); 370 371 // Tell what we are doing. 372 const char* task = fScanner->Task(); 373 float strWidth = StringWidth(task); 374 bx = (b.left + b.Width() - strWidth) / 2.0; 375 by -= fFontHeight + 2.0 * kSmallVMargin; 376 SetHighColor(0, 0, 0); 377 DrawString(task, BPoint(bx, by)); 378} 379 380 381void 382PieView::_DrawPieChart(BRect updateRect) 383{ 384 BRect pieRect = Bounds(); 385 if (!updateRect.Intersects(pieRect)) 386 return; 387 388 pieRect.InsetBy(kPieOuterMargin, kPieOuterMargin); 389 390 SetHighColor(kPieBGColor); 391 FillRect(updateRect); 392 393 // constraint proportions 394 if (pieRect.Width() > pieRect.Height()) { 395 float moveBy = (pieRect.Width() - pieRect.Height()) / 2; 396 pieRect.left += moveBy; 397 pieRect.right -= moveBy; 398 } else { 399 float moveBy = (pieRect.Height() - pieRect.Width()) / 2; 400 pieRect.top -= moveBy; 401 pieRect.bottom += moveBy; 402 } 403 int colorIdx = 0; 404 FileInfo* currentDir = fScanner->CurrentDir(); 405 FileInfo* parent = currentDir; 406 while (parent != NULL) { 407 parent = parent->parent; 408 colorIdx++; 409 } 410 _DrawDirectory(pieRect, currentDir, 0.0, 0.0, 411 colorIdx % kBasePieColorCount, 0); 412} 413 414 415float 416PieView::_DrawDirectory(BRect b, FileInfo* info, float parentSpan, 417 float beginAngle, int colorIdx, int level) 418{ 419 if (b.Width() < 2.0 * (kPieCenterSize + level * kPieRingSize 420 + kPieOuterMargin + kPieInnerMargin)) { 421 return 0.0; 422 } 423 424 if (info != NULL && info->color >= 0 && level == 0) 425 colorIdx = info->color % kBasePieColorCount; 426 else if (info != NULL) 427 info->color = colorIdx; 428 429 VolumeSnapshot* snapshot = fScanner->Snapshot(); 430 431 float cx = floorf(b.left + b.Width() / 2.0 + 0.5); 432 float cy = floorf(b.top + b.Height() / 2.0 + 0.5); 433 434 float mySpan; 435 436 if (level == 0) { 437 // Make room for mouse over info. 438 fMouseOverInfo.clear(); 439 fMouseOverInfo[0] = SegmentList(); 440 441 // Draw the center circle. 442 const char* displayName; 443 if (info == NULL) { 444 // NULL represents the entire volume. Show used and free space in 445 // the center circle, with the used segment representing the 446 // volume's root directory. 447 off_t volCapacity = snapshot->capacity; 448 mySpan = 360.0 * (volCapacity - snapshot->freeBytes) / volCapacity; 449 450 SetHighColor(kEmptySpcColor); 451 FillEllipse(BPoint(cx, cy), kPieCenterSize, kPieCenterSize); 452 453 SetHighColor(kBasePieColor[0]); 454 FillArc(BPoint(cx, cy), kPieCenterSize, kPieCenterSize, 0.0, 455 mySpan); 456 457 // Show total volume capacity. 458 char label[B_PATH_NAME_LENGTH]; 459 size_to_string(volCapacity, label, sizeof(label)); 460 SetHighColor(kPieBGColor); 461 SetDrawingMode(B_OP_OVER); 462 DrawString(label, BPoint(cx - StringWidth(label) / 2.0, 463 cy + fFontHeight + kSmallVMargin)); 464 SetDrawingMode(B_OP_COPY); 465 466 displayName = snapshot->name.c_str(); 467 468 // Record in-use space and free space for use during MouseMoved(). 469 info = snapshot->rootDir; 470 info->color = colorIdx; 471 fMouseOverInfo[0].push_back(Segment(0.0, mySpan, info)); 472 if (mySpan < 360.0 - kMinSegmentSpan) { 473 fMouseOverInfo[0].push_back(Segment(mySpan, 360.0, 474 snapshot->freeSpace)); 475 } 476 } else { 477 // Show a normal directory. 478 SetHighColor(kBasePieColor[colorIdx]); 479 FillEllipse(BRect(cx - kPieCenterSize, cy - kPieCenterSize, 480 cx + kPieCenterSize + 0.5, cy + kPieCenterSize + 0.5)); 481 displayName = info->ref.name; 482 mySpan = 360.0; 483 484 // Record the segment for use during MouseMoved(). 485 fMouseOverInfo[0].push_back(Segment(0.0, mySpan, info)); 486 } 487 488 SetPenSize(1.0); 489 SetHighColor(kOutlineColor); 490 StrokeEllipse(BPoint(cx, cy), kPieCenterSize + 0.5, 491 kPieCenterSize + 0.5); 492 493 // Show the name of the volume or directory. 494 BString label(displayName); 495 BFont font; 496 GetFont(&font); 497 font.TruncateString(&label, B_TRUNCATE_END, 498 2.0 * (kPieCenterSize - kSmallHMargin)); 499 float labelWidth = font.StringWidth(label.String()); 500 501 SetHighColor(kPieBGColor); 502 SetDrawingMode(B_OP_OVER); 503 DrawString(label.String(), BPoint(cx - labelWidth / 2.0, cy)); 504 SetDrawingMode(B_OP_COPY); 505 beginAngle = 0.0; 506 } else { 507 // Draw an exterior segment. 508 float parentSize; 509 if (info->parent == NULL) 510 parentSize = (float)snapshot->capacity; 511 else 512 parentSize = (float)info->parent->size; 513 514 mySpan = parentSpan * (float)info->size / parentSize; 515 if (mySpan >= kMinSegmentSpan) { 516 const float tint = 1.4f - level * 0.08f; 517 float radius = kPieCenterSize + level * kPieRingSize 518 - kPieRingSize / 2.0; 519 520 // Draw the grey border 521 SetHighColor(tint_color(kOutlineColor, tint)); 522 SetPenSize(kPieRingSize + 1.5f); 523 StrokeArc(BPoint(cx, cy), radius, radius, 524 beginAngle - 0.001f * radius, mySpan + 0.002f * radius); 525 526 // Draw the colored area 527 rgb_color color = tint_color(kBasePieColor[colorIdx], tint); 528 SetHighColor(color); 529 SetPenSize(kPieRingSize); 530 StrokeArc(BPoint(cx, cy), radius, radius, beginAngle, mySpan); 531 532 // Record the segment for use during MouseMoved(). 533 if (fMouseOverInfo.find(level) == fMouseOverInfo.end()) 534 fMouseOverInfo[level] = SegmentList(); 535 536 fMouseOverInfo[level].push_back( 537 Segment(beginAngle, beginAngle + mySpan, info)); 538 } 539 } 540 541 // Draw children. 542 vector<FileInfo*>::iterator i = info->children.begin(); 543 while (i != info->children.end()) { 544 float childSpan 545 = _DrawDirectory(b, *i, mySpan, beginAngle, colorIdx, level + 1); 546 if (childSpan >= kMinSegmentSpan) { 547 beginAngle += childSpan; 548 colorIdx = (colorIdx + 1) % kBasePieColorCount; 549 } 550 i++; 551 } 552 553 return mySpan; 554} 555 556 557FileInfo* 558PieView::_FileAt(const BPoint& where) 559{ 560 BRect b = Bounds(); 561 float cx = b.left + b.Width() / 2.0; 562 float cy = b.top + b.Height() / 2.0; 563 float dx = where.x - cx; 564 float dy = where.y - cy; 565 float dist = sqrt(dx*dx + dy*dy); 566 567 int level; 568 if (dist < kPieCenterSize) 569 level = 0; 570 else 571 level = 1 + (int)((dist - kPieCenterSize) / kPieRingSize); 572 573 float angle = rad2deg(atan(dy / dx)); 574 angle = ((dx < 0.0) ? 180.0 : (dy < 0.0) ? 0.0 : 360.0) - angle; 575 576 if (fMouseOverInfo.find(level) == fMouseOverInfo.end()) { 577 // No files in this level (ring) of the pie. 578 return NULL; 579 } 580 581 SegmentList s = fMouseOverInfo[level]; 582 SegmentList::iterator i = s.begin(); 583 while (i != s.end() && (angle < (*i).begin || (*i).end < angle)) 584 i++; 585 if (i == s.end()) { 586 // Nothing at this angle. 587 return NULL; 588 } 589 590 return (*i).info; 591} 592 593 594void 595PieView::_AddAppToList(vector<AppMenuItem*>& list, const char* appSig, 596 int category) 597{ 598 // skip self. 599 if (strcmp(appSig, kAppSignature) == 0) 600 return; 601 602 AppMenuItem* item = new AppMenuItem(appSig, category); 603 if (item->IsValid()) { 604 vector<AppMenuItem*>::iterator i = list.begin(); 605 while (i != list.end()) { 606 if (*item->AppRef() == *(*i)->AppRef()) { 607 // Skip duplicates. 608 delete item; 609 return; 610 } 611 i++; 612 } 613 list.push_back(item); 614 } else { 615 // Skip items that weren't constructed successfully. 616 delete item; 617 } 618} 619 620 621BMenu* 622PieView::_BuildOpenWithMenu(FileInfo* info) 623{ 624 vector<AppMenuItem*> appList; 625 626 // Get preferred app. 627 BMimeType* type = info->Type(); 628 char appSignature[B_MIME_TYPE_LENGTH]; 629 if (type->GetPreferredApp(appSignature) == B_OK) 630 _AddAppToList(appList, appSignature, 1); 631 632 // Get apps that handle this subtype and supertype. 633 BMessage msg; 634 if (type->GetSupportingApps(&msg) == B_OK) { 635 int32 subs, supers, i; 636 msg.FindInt32("be:sub", &subs); 637 msg.FindInt32("be:super", &supers); 638 639 const char* appSig; 640 for (i = 0; i < subs; i++) { 641 msg.FindString("applications", i, &appSig); 642 _AddAppToList(appList, appSig, 2); 643 } 644 int hold = i; 645 for (i = 0; i < supers; i++) { 646 msg.FindString("applications", i + hold, &appSig); 647 _AddAppToList(appList, appSig, 3); 648 } 649 } 650 651 // Get apps that handle any type. 652 if (BMimeType::GetWildcardApps(&msg) == B_OK) { 653 const char* appSig; 654 for (int32 i = 0; true; i++) { 655 if (msg.FindString("applications", i, &appSig) == B_OK) 656 _AddAppToList(appList, appSig, 4); 657 else 658 break; 659 } 660 } 661 662 delete type; 663 664 BMenu* openWith = new BMenu(B_TRANSLATE("Open with")); 665 666 if (appList.size() == 0) { 667 BMenuItem* item = new BMenuItem(B_TRANSLATE("no supporting apps"), 668 NULL); 669 item->SetEnabled(false); 670 openWith->AddItem(item); 671 } else { 672 vector<AppMenuItem*>::iterator i = appList.begin(); 673 int category = (*i)->Category(); 674 while (i != appList.end()) { 675 if (category != (*i)->Category()) { 676 openWith->AddSeparatorItem(); 677 category = (*i)->Category(); 678 } 679 openWith->AddItem(*i); 680 i++; 681 } 682 } 683 684 return openWith; 685} 686 687 688void 689PieView::_ShowContextMenu(FileInfo* info, BPoint p) 690{ 691 BRect openRect(p.x - 5.0, p.y - 5.0, p.x + 5.0, p.y + 5.0); 692 693 // Display the open-with menu only if the file is still available. 694 BNode node(&info->ref); 695 if (node.InitCheck() == B_OK) { 696 // Add "Open With" submenu. 697 BMenu* openWith = _BuildOpenWithMenu(info); 698 fMouseOverMenu->AddItem(openWith, kIdxOpenWith); 699 700 // Add a "Rescan" option for folders. 701 BMenuItem* rescan = NULL; 702 if (info->children.size() > 0) { 703 rescan = new BMenuItem(B_TRANSLATE("Rescan"), NULL); 704 fMouseOverMenu->AddItem(rescan, kIdxRescan); 705 } 706 707 BMenuItem* item = fMouseOverMenu->Go(p, false, true, openRect); 708 if (item != NULL) { 709 switch (fMouseOverMenu->IndexOf(item)) { 710 case kIdxGetInfo: 711 _OpenInfo(info, p); 712 break; 713 case kIdxOpen: 714 _Launch(info); 715 break; 716 case kIdxRescan: 717 fScanner->Refresh(info); 718 Invalidate(); 719 break; 720 default: // must be "Open With" submenu 721 _Launch(info, ((AppMenuItem*)item)->AppRef()); 722 break; 723 } 724 } 725 726 if (rescan != NULL) { 727 fMouseOverMenu->RemoveItem(rescan); 728 delete rescan; 729 } 730 731 fMouseOverMenu->RemoveItem(openWith); 732 delete openWith; 733 } 734 else { 735 // The file is no longer available. 736 fFileUnavailableMenu->Go(p, false, true, openRect); 737 } 738} 739 740 741void 742PieView::_Launch(FileInfo* info, const entry_ref* appRef) 743{ 744 BMessage msg(B_REFS_RECEIVED); 745 msg.AddRef("refs", &info->ref); 746 747 if (appRef == NULL) { 748 // Let the registrar pick an app based on the file's MIME type. 749 BMimeType* type = info->Type(); 750 be_roster->Launch(type->Type(), &msg); 751 delete type; 752 } else { 753 // Launch a designated app to handle this file. 754 be_roster->Launch(appRef, &msg); 755 } 756} 757 758 759void 760PieView::_OpenInfo(FileInfo* info, BPoint p) 761{ 762 BMessenger tracker(kTrackerSignature); 763 if (!tracker.IsValid()) { 764 new InfoWin(p, info, Window()); 765 } else { 766 BMessage message(kGetInfo); 767 message.AddRef("refs", &info->ref); 768 tracker.SendMessage(&message); 769 } 770} 771