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