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
43static const int32 kIdxGetInfo = 0;
44static const int32 kIdxOpen = 1;
45static const int32 kIdxOpenWith = 2;
46static const int32 kIdxRescan = 3;
47
48
49class AppMenuItem : public BMenuItem {
50public:
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
64private:
65			int					fCategory;
66			BBitmap*			fIcon;
67			entry_ref			fAppRef;
68			bool				fIsValid;
69};
70
71
72AppMenuItem::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
94AppMenuItem::~AppMenuItem()
95{
96	delete fIcon;
97}
98
99
100void
101AppMenuItem::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
111void
112AppMenuItem::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
128PieView::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
162void
163PieView::AttachedToWindow()
164{
165	fWindow = (MainWindow*)Window();
166	if (Parent()) {
167		SetViewColor(Parent()->ViewColor());
168		SetLowColor(Parent()->ViewColor());
169	}
170}
171
172
173PieView::~PieView()
174{
175	delete fMouseOverMenu;
176	delete fFileUnavailableMenu;
177	if (fScanner != NULL)
178		fScanner->RequestQuit();
179}
180
181
182void
183PieView::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
215void
216PieView::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
240void
241PieView::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
278void
279PieView::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
313void
314PieView::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
332void
333PieView::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
349void
350PieView::_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
364void
365PieView::_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(ui_color(B_PANEL_TEXT_COLOR));
390	DrawString(task, BPoint(bx, by));
391}
392
393
394void
395PieView::_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
425float
426PieView::_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
567FileInfo*
568PieView::_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
604void
605PieView::_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
631BMenu*
632PieView::_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
698void
699PieView::_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
750void
751PieView::_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
768void
769PieView::_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