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