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