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