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 if (info != NULL && info->color >= 0 && level == 0) 492 colorIdx = info->color; 493 else if (info != NULL) 494 info->color = colorIdx; 495 496 VolumeSnapshot* snapshot = fScanners[fCurrentVolume]->Snapshot(); 497 498 float cx = floorf(b.left + b.Width() / 2.0 + 0.5); 499 float cy = floorf(b.top + b.Height() / 2.0 + 0.5); 500 501 float mySpan; 502 503 if (level == 0) { 504 // Make room for mouse over info. 505 fMouseOverInfo.clear(); 506 fMouseOverInfo[0] = SegmentList(); 507 508 // Draw the center circle. 509 const char* displayName; 510 if (info == NULL) { 511 // NULL represents the entire volume. Show used and free space in 512 // the center circle, with the used segment representing the 513 // volume's root directory. 514 off_t volCapacity = snapshot->capacity; 515 mySpan = 360.0 * (volCapacity - snapshot->freeBytes) / volCapacity; 516 517 SetHighColor(kEmptySpcColor); 518 FillEllipse(BPoint(cx, cy), kPieCenterSize, kPieCenterSize); 519 520 SetHighColor(kBasePieColor[0]); 521 FillArc(BPoint(cx, cy), kPieCenterSize, kPieCenterSize, 0.0, mySpan); 522 523 // Show total volume capacity. 524 char label[B_PATH_NAME_LENGTH]; 525 size_to_string(volCapacity, label); 526 SetHighColor(kPieBGColor); 527 SetDrawingMode(B_OP_OVER); 528 DrawString(label, BPoint(cx - StringWidth(label) / 2.0, 529 cy + fFontHeight + kSmallVMargin)); 530 SetDrawingMode(B_OP_COPY); 531 532 displayName = snapshot->name.c_str(); 533 534 // Record in-use space and free space for use during MouseMoved(). 535 info = snapshot->rootDir; 536 info->color = colorIdx; 537 fMouseOverInfo[0].push_back(Segment(0.0, mySpan, info)); 538 if (mySpan < 360.0 - kMinSegmentSpan) { 539 fMouseOverInfo[0].push_back(Segment(mySpan, 360.0, 540 snapshot->freeSpace)); 541 } 542 } else { 543 // Show a normal directory. 544 SetHighColor(kBasePieColor[colorIdx]); 545 FillEllipse(BRect(cx - kPieCenterSize, cy - kPieCenterSize, 546 cx + kPieCenterSize + 0.5, cy + kPieCenterSize + 0.5)); 547 displayName = info->ref.name; 548 mySpan = 360.0; 549 550 // Record the segment for use during MouseMoved(). 551 fMouseOverInfo[0].push_back(Segment(0.0, mySpan, info)); 552 } 553 554 SetPenSize(1.0); 555 SetHighColor(kOutlineColor); 556 StrokeEllipse(BPoint(cx, cy), kPieCenterSize + 0.5, kPieCenterSize + 0.5); 557 558 // Show the name of the volume or directory. 559 BString label(displayName); 560 BFont font; 561 GetFont(&font); 562 font.TruncateString(&label, B_TRUNCATE_END, 563 2.0 * (kPieCenterSize - kSmallHMargin)); 564 float labelWidth = font.StringWidth(label.String()); 565 566 SetHighColor(kPieBGColor); 567 SetDrawingMode(B_OP_OVER); 568 DrawString(label.String(), BPoint(cx - labelWidth / 2.0, cy)); 569 SetDrawingMode(B_OP_COPY); 570 beginAngle = 0.0; 571 } else { 572 // Draw an exterior segment. 573 float parentSize; 574 if (info->parent == NULL) 575 parentSize = (float)snapshot->capacity; 576 else 577 parentSize = (float)info->parent->size; 578 579 mySpan = parentSpan * (float)info->size / parentSize; 580 if (mySpan >= kMinSegmentSpan) { 581 rgb_color color = kBasePieColor[colorIdx]; 582 color.red += kLightenFactor * level; 583 color.green += kLightenFactor * level; 584 color.blue += kLightenFactor * level; 585 SetHighColor(color); 586 SetPenSize(kPieRingSize); 587 float radius = kPieCenterSize + level * kPieRingSize 588 - kPieRingSize / 2.0; 589 StrokeArc(BPoint(cx, cy), radius, radius, beginAngle, mySpan); 590 591 SetHighColor(kOutlineColor); 592 SetPenSize(0.0); 593 float segBeginRadius = kPieCenterSize + (level - 1) 594 * kPieRingSize + 0.5; 595 float segLength = kPieRingSize - 0.5; 596 float rad = deg2rad(beginAngle); 597 float bx = cx + segBeginRadius * cos(rad); 598 float by = cy - segBeginRadius * sin(rad); 599 float ex = bx + segLength * cos(rad); 600 float ey = by - segLength * sin(rad); 601 StrokeLine(BPoint(bx, by), BPoint(ex, ey)); 602 603 rad = deg2rad(beginAngle + mySpan); 604 bx = cx + segBeginRadius * cos(rad); 605 by = cy - segBeginRadius * sin(rad); 606 ex = bx + segLength * cos(rad); 607 ey = by - segLength * sin(rad); 608 StrokeLine(BPoint(bx, by), BPoint(ex, ey)); 609 610 SetPenSize(0.0); 611 SetHighColor(kOutlineColor); 612 radius += kPieRingSize / 2.0; 613 StrokeArc(BPoint(cx, cy), radius, radius, beginAngle, mySpan); 614 615 // Record the segment for use during MouseMoved(). 616 if (fMouseOverInfo.find(level) == fMouseOverInfo.end()) 617 fMouseOverInfo[level] = SegmentList(); 618 619 fMouseOverInfo[level].push_back( 620 Segment(beginAngle, beginAngle + mySpan, info)); 621 } 622 } 623 624 // Draw children. 625 vector<FileInfo*>::iterator i = info->children.begin(); 626 while (i != info->children.end()) { 627 float childSpan 628 = _DrawDirectory(b, *i, mySpan, beginAngle, colorIdx, level + 1); 629 if (childSpan >= kMinSegmentSpan) { 630 beginAngle += childSpan; 631 colorIdx = (colorIdx + 1) % kBasePieColorCount; 632 } 633 i++; 634 } 635 636 return mySpan; 637 } 638 639 640 FileInfo* 641 PieView::_FileAt(const BPoint& where) 642 { 643 BRect b = Bounds(); 644 float cx = b.left + b.Width() / 2.0; 645 float cy = b.top + b.Height() / 2.0; 646 float dx = where.x - cx; 647 float dy = where.y - cy; 648 float dist = sqrt(dx*dx + dy*dy); 649 650 int level; 651 if (dist < kPieCenterSize) 652 level = 0; 653 else 654 level = 1 + (int)((dist - kPieCenterSize) / kPieRingSize); 655 656 float angle = rad2deg(atan(dy / dx)); 657 angle = ((dx < 0.0) ? 180.0 : (dy < 0.0) ? 0.0 : 360.0) - angle; 658 659 if (fMouseOverInfo.find(level) == fMouseOverInfo.end()) { 660 // No files in this level (ring) of the pie. 661 return NULL; 662 } 663 664 SegmentList s = fMouseOverInfo[level]; 665 SegmentList::iterator i = s.begin(); 666 while (i != s.end() && (angle < (*i).begin || (*i).end < angle)) 667 i++; 668 if (i == s.end()) { 669 // Nothing at this angle. 670 return NULL; 671 } 672 673 return (*i).info; 674 } 675 676 677 void 678 PieView::_AddAppToList(vector<AppMenuItem*>& list, const char* appSig, 679 int category) 680 { 681 // skip self. 682 if (strcmp(appSig, kAppSignature) == 0) 683 return; 684 685 AppMenuItem* item = new AppMenuItem(appSig, category); 686 if (item->IsValid()) { 687 vector<AppMenuItem*>::iterator i = list.begin(); 688 while (i != list.end()) { 689 if (*item->AppRef() == *(*i)->AppRef()) { 690 // Skip duplicates. 691 delete item; 692 return; 693 } 694 i++; 695 } 696 list.push_back(item); 697 } else { 698 // Skip items that weren't constructed successfully. 699 delete item; 700 } 701 } 702 703 704 BMenu* 705 PieView::_BuildOpenWithMenu(FileInfo* info) 706 { 707 vector<AppMenuItem*> appList; 708 709 // Get preferred app. 710 BMimeType* type = info->Type(); 711 char appSignature[B_MIME_TYPE_LENGTH]; 712 if (type->GetPreferredApp(appSignature) == B_OK) 713 _AddAppToList(appList, appSignature, 1); 714 715 // Get apps that handle this subtype and supertype. 716 BMessage msg; 717 if (type->GetSupportingApps(&msg) == B_OK) { 718 int32 subs, supers, i; 719 msg.FindInt32("be:sub", &subs); 720 msg.FindInt32("be:super", &supers); 721 722 const char* appSig; 723 for (i = 0; i < subs; i++) { 724 msg.FindString("applications", i, &appSig); 725 _AddAppToList(appList, appSig, 2); 726 } 727 int hold = i; 728 for (i = 0; i < supers; i++) { 729 msg.FindString("applications", i + hold, &appSig); 730 _AddAppToList(appList, appSig, 3); 731 } 732 } 733 734 // Get apps that handle any type. 735 if (BMimeType::GetWildcardApps(&msg) == B_OK) { 736 const char* appSig; 737 for (int32 i = 0; true; i++) { 738 if (msg.FindString("applications", i, &appSig) == B_OK) 739 _AddAppToList(appList, appSig, 4); 740 else 741 break; 742 } 743 } 744 745 delete type; 746 747 BMenu* openWith = new BMenu(kMenuOpenWith); 748 749 if (appList.size() == 0) { 750 BMenuItem* item = new BMenuItem(kMenuNoApps, NULL); 751 item->SetEnabled(false); 752 openWith->AddItem(item); 753 } else { 754 vector<AppMenuItem*>::iterator i = appList.begin(); 755 int category = (*i)->Category(); 756 while (i != appList.end()) { 757 if (category != (*i)->Category()) { 758 openWith->AddSeparatorItem(); 759 category = (*i)->Category(); 760 } 761 openWith->AddItem(*i); 762 i++; 763 } 764 } 765 766 return openWith; 767 } 768 769 770 void 771 PieView::_ShowContextMenu(FileInfo* info, BPoint p) 772 { 773 BRect openRect(p.x - 5.0, p.y - 5.0, p.x + 5.0, p.y + 5.0); 774 775 // Display the open-with menu only if the file is still available. 776 BNode node(&info->ref); 777 if (node.InitCheck() == B_OK) { 778 // Add "Open With" submenu. 779 BMenu* openWith = _BuildOpenWithMenu(info); 780 fMouseOverMenu->AddItem(openWith, kIdxOpenWith); 781 782 // Add a "Rescan" option for folders. 783 BMenuItem* rescan = NULL; 784 if (info->children.size() > 0) { 785 rescan = new BMenuItem(kStrRescan, NULL); 786 fMouseOverMenu->AddItem(rescan, kIdxRescan); 787 } 788 789 BMenuItem* item = fMouseOverMenu->Go(p, false, true, openRect); 790 if (item != NULL) { 791 switch (fMouseOverMenu->IndexOf(item)) { 792 case kIdxGetInfo: 793 _OpenInfo(info, p); 794 break; 795 case kIdxOpen: 796 _Launch(info); 797 break; 798 case kIdxRescan: 799 fScanners[fCurrentVolume]->Refresh(info); 800 Invalidate(); 801 break; 802 default: // must be "Open With" submenu 803 _Launch(info, ((AppMenuItem*)item)->AppRef()); 804 break; 805 } 806 } 807 808 if (rescan != NULL) { 809 fMouseOverMenu->RemoveItem(rescan); 810 delete rescan; 811 } 812 813 fMouseOverMenu->RemoveItem(openWith); 814 delete openWith; 815 } 816 else { 817 // The file is no longer available. 818 fFileUnavailableMenu->Go(p, false, true, openRect); 819 } 820 } 821 822 823 void 824 PieView::_Launch(FileInfo* info, const entry_ref* appRef) 825 { 826 BMessage msg(B_REFS_RECEIVED); 827 msg.AddRef("refs", &info->ref); 828 829 if (appRef == NULL) { 830 // Let the registrar pick an app based on the file's MIME type. 831 BMimeType* type = info->Type(); 832 be_roster->Launch(type->Type(), &msg); 833 delete type; 834 } else { 835 // Launch a designated app to handle this file. 836 be_roster->Launch(appRef, &msg); 837 } 838 } 839 840 void 841 PieView::_OpenInfo(FileInfo* info, BPoint p) 842 { 843 BMessenger tracker(kTrackerSignature); 844 if (!tracker.IsValid()) { 845 new InfoWin(p, info, Window()); 846 } else { 847 BMessage message(kGetInfo); 848 message.AddRef("refs", &info->ref); 849 tracker.SendMessage(&message); 850 } 851 } 852 853 854