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