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