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