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