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