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