xref: /haiku/src/apps/diskusage/PieView.cpp (revision 62f5ba006a08b0df30631375878effaf67ae5dbc)
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 	if (info != NULL && info->color >= 0 && level == 0)
492 		colorIdx = info->color;
493 	else if (info != NULL)
494 		info->color = colorIdx;
495 
496 	VolumeSnapshot* snapshot = fScanners[fCurrentVolume]->Snapshot();
497 
498 	float cx = floorf(b.left + b.Width() / 2.0 + 0.5);
499 	float cy = floorf(b.top + b.Height() / 2.0 + 0.5);
500 
501 	float mySpan;
502 
503 	if (level == 0) {
504 		// Make room for mouse over info.
505 		fMouseOverInfo.clear();
506 		fMouseOverInfo[0] = SegmentList();
507 
508 		// Draw the center circle.
509 		const char* displayName;
510 		if (info == NULL) {
511 			// NULL represents the entire volume.  Show used and free space in
512 			// the center circle, with the used segment representing the
513 			// volume's root directory.
514 			off_t volCapacity = snapshot->capacity;
515 			mySpan = 360.0 * (volCapacity - snapshot->freeBytes) / volCapacity;
516 
517 			SetHighColor(kEmptySpcColor);
518 			FillEllipse(BPoint(cx, cy), kPieCenterSize, kPieCenterSize);
519 
520 			SetHighColor(kBasePieColor[0]);
521 			FillArc(BPoint(cx, cy), kPieCenterSize, kPieCenterSize, 0.0, mySpan);
522 
523 			// Show total volume capacity.
524 			char label[B_PATH_NAME_LENGTH];
525 			size_to_string(volCapacity, label);
526 			SetHighColor(kPieBGColor);
527 			SetDrawingMode(B_OP_OVER);
528 			DrawString(label, BPoint(cx - StringWidth(label) / 2.0,
529 				cy + fFontHeight + kSmallVMargin));
530 			SetDrawingMode(B_OP_COPY);
531 
532 			displayName = snapshot->name.c_str();
533 
534 			// Record in-use space and free space for use during MouseMoved().
535 			info = snapshot->rootDir;
536 			info->color = colorIdx;
537 			fMouseOverInfo[0].push_back(Segment(0.0, mySpan, info));
538 			if (mySpan < 360.0 - kMinSegmentSpan) {
539 				fMouseOverInfo[0].push_back(Segment(mySpan, 360.0,
540 					snapshot->freeSpace));
541 			}
542 		} else {
543 			// Show a normal directory.
544 			SetHighColor(kBasePieColor[colorIdx]);
545 			FillEllipse(BRect(cx - kPieCenterSize, cy - kPieCenterSize,
546 				cx + kPieCenterSize + 0.5, cy + kPieCenterSize + 0.5));
547 			displayName = info->ref.name;
548 			mySpan = 360.0;
549 
550 			// Record the segment for use during MouseMoved().
551 			fMouseOverInfo[0].push_back(Segment(0.0, mySpan, info));
552 		}
553 
554 		SetPenSize(1.0);
555 		SetHighColor(kOutlineColor);
556 		StrokeEllipse(BPoint(cx, cy), kPieCenterSize + 0.5, kPieCenterSize + 0.5);
557 
558 		// Show the name of the volume or directory.
559 		BString label(displayName);
560 		BFont font;
561 		GetFont(&font);
562 		font.TruncateString(&label, B_TRUNCATE_END,
563 			2.0 * (kPieCenterSize - kSmallHMargin));
564 		float labelWidth = font.StringWidth(label.String());
565 
566 		SetHighColor(kPieBGColor);
567 		SetDrawingMode(B_OP_OVER);
568 		DrawString(label.String(), BPoint(cx - labelWidth / 2.0, cy));
569 		SetDrawingMode(B_OP_COPY);
570 		beginAngle = 0.0;
571 	} else {
572 		// Draw an exterior segment.
573 		float parentSize;
574 		if (info->parent == NULL)
575 			parentSize = (float)snapshot->capacity;
576 		else
577 			parentSize = (float)info->parent->size;
578 
579 		mySpan = parentSpan * (float)info->size / parentSize;
580 		if (mySpan >= kMinSegmentSpan) {
581 			rgb_color color = kBasePieColor[colorIdx];
582 			color.red += kLightenFactor * level;
583 			color.green += kLightenFactor * level;
584 			color.blue += kLightenFactor * level;
585 			SetHighColor(color);
586 			SetPenSize(kPieRingSize);
587 			float radius = kPieCenterSize + level * kPieRingSize
588 				- kPieRingSize / 2.0;
589 			StrokeArc(BPoint(cx, cy), radius, radius, beginAngle, mySpan);
590 
591 			SetHighColor(kOutlineColor);
592 			SetPenSize(0.0);
593 			float segBeginRadius = kPieCenterSize + (level - 1)
594 				* kPieRingSize + 0.5;
595 			float segLength = kPieRingSize - 0.5;
596 			float rad = deg2rad(beginAngle);
597 			float bx = cx + segBeginRadius * cos(rad);
598 			float by = cy - segBeginRadius * sin(rad);
599 			float ex = bx + segLength * cos(rad);
600 			float ey = by - segLength * sin(rad);
601 			StrokeLine(BPoint(bx, by), BPoint(ex, ey));
602 
603 			rad = deg2rad(beginAngle + mySpan);
604 			bx = cx + segBeginRadius * cos(rad);
605 			by = cy - segBeginRadius * sin(rad);
606 			ex = bx + segLength * cos(rad);
607 			ey = by - segLength * sin(rad);
608 			StrokeLine(BPoint(bx, by), BPoint(ex, ey));
609 
610 			SetPenSize(0.0);
611 			SetHighColor(kOutlineColor);
612 			radius += kPieRingSize / 2.0;
613 			StrokeArc(BPoint(cx, cy), radius, radius, beginAngle, mySpan);
614 
615 			// Record the segment for use during MouseMoved().
616 			if (fMouseOverInfo.find(level) == fMouseOverInfo.end())
617 				fMouseOverInfo[level] = SegmentList();
618 
619 			fMouseOverInfo[level].push_back(
620 				Segment(beginAngle, beginAngle + mySpan, info));
621 		}
622 	}
623 
624 	// Draw children.
625 	vector<FileInfo*>::iterator i = info->children.begin();
626 	while (i != info->children.end()) {
627 		float childSpan
628 			= _DrawDirectory(b, *i, mySpan, beginAngle, colorIdx, level + 1);
629 		if (childSpan >= kMinSegmentSpan) {
630 			beginAngle += childSpan;
631 			colorIdx = (colorIdx + 1) % kBasePieColorCount;
632 		}
633 		i++;
634 	}
635 
636 	return mySpan;
637 }
638 
639 
640 FileInfo*
641 PieView::_FileAt(const BPoint& where)
642 {
643 	BRect b = Bounds();
644 	float cx = b.left + b.Width() / 2.0;
645 	float cy = b.top + b.Height() / 2.0;
646 	float dx = where.x - cx;
647 	float dy = where.y - cy;
648 	float dist = sqrt(dx*dx + dy*dy);
649 
650 	int level;
651 	if (dist < kPieCenterSize)
652 		level = 0;
653 	else
654 		level = 1 + (int)((dist - kPieCenterSize) / kPieRingSize);
655 
656 	float angle = rad2deg(atan(dy / dx));
657 	angle = ((dx < 0.0) ? 180.0 : (dy < 0.0) ? 0.0 : 360.0) - angle;
658 
659 	if (fMouseOverInfo.find(level) == fMouseOverInfo.end()) {
660 		// No files in this level (ring) of the pie.
661 		return NULL;
662 	}
663 
664 	SegmentList s = fMouseOverInfo[level];
665 	SegmentList::iterator i = s.begin();
666 	while (i != s.end() && (angle < (*i).begin || (*i).end < angle))
667 		i++;
668 	if (i == s.end()) {
669 		// Nothing at this angle.
670 		return NULL;
671 	}
672 
673 	return (*i).info;
674 }
675 
676 
677 void
678 PieView::_AddAppToList(vector<AppMenuItem*>& list, const char* appSig,
679 	int category)
680 {
681 	// skip self.
682 	if (strcmp(appSig, kAppSignature) == 0)
683 		return;
684 
685 	AppMenuItem* item = new AppMenuItem(appSig, category);
686 	if (item->IsValid()) {
687 		vector<AppMenuItem*>::iterator i = list.begin();
688 		while (i != list.end()) {
689 			if (*item->AppRef() == *(*i)->AppRef()) {
690 				// Skip duplicates.
691 				delete item;
692 				return;
693 			}
694 			i++;
695 		}
696 		list.push_back(item);
697 	} else {
698 		// Skip items that weren't constructed successfully.
699 		delete item;
700 	}
701 }
702 
703 
704 BMenu*
705 PieView::_BuildOpenWithMenu(FileInfo* info)
706 {
707 	vector<AppMenuItem*> appList;
708 
709 	// Get preferred app.
710 	BMimeType* type = info->Type();
711 	char appSignature[B_MIME_TYPE_LENGTH];
712 	if (type->GetPreferredApp(appSignature) == B_OK)
713 		_AddAppToList(appList, appSignature, 1);
714 
715 	// Get apps that handle this subtype and supertype.
716 	BMessage msg;
717 	if (type->GetSupportingApps(&msg) == B_OK) {
718 		int32 subs, supers, i;
719 		msg.FindInt32("be:sub", &subs);
720 		msg.FindInt32("be:super", &supers);
721 
722 		const char* appSig;
723 		for (i = 0; i < subs; i++) {
724 			msg.FindString("applications", i, &appSig);
725 			_AddAppToList(appList, appSig, 2);
726 		}
727 		int hold = i;
728 		for (i = 0; i < supers; i++) {
729 			msg.FindString("applications", i + hold, &appSig);
730 			_AddAppToList(appList, appSig, 3);
731 		}
732 	}
733 
734 	// Get apps that handle any type.
735 	if (BMimeType::GetWildcardApps(&msg) == B_OK) {
736 		const char* appSig;
737 		for (int32 i = 0; true; i++) {
738 			if (msg.FindString("applications", i, &appSig) == B_OK)
739 				_AddAppToList(appList, appSig, 4);
740 			else
741 				break;
742 		}
743 	}
744 
745 	delete type;
746 
747 	BMenu* openWith = new BMenu(kMenuOpenWith);
748 
749 	if (appList.size() == 0) {
750 		BMenuItem* item = new BMenuItem(kMenuNoApps, NULL);
751 		item->SetEnabled(false);
752 		openWith->AddItem(item);
753 	} else {
754 		vector<AppMenuItem*>::iterator i = appList.begin();
755 		int category = (*i)->Category();
756 		while (i != appList.end()) {
757 			if (category != (*i)->Category()) {
758 				openWith->AddSeparatorItem();
759 				category = (*i)->Category();
760 			}
761 			openWith->AddItem(*i);
762 			i++;
763 		}
764 	}
765 
766 	return openWith;
767 }
768 
769 
770 void
771 PieView::_ShowContextMenu(FileInfo* info, BPoint p)
772 {
773 	BRect openRect(p.x - 5.0, p.y - 5.0, p.x + 5.0, p.y + 5.0);
774 
775 	// Display the open-with menu only if the file is still available.
776 	BNode node(&info->ref);
777 	if (node.InitCheck() == B_OK) {
778 		// Add "Open With" submenu.
779 		BMenu* openWith = _BuildOpenWithMenu(info);
780 		fMouseOverMenu->AddItem(openWith, kIdxOpenWith);
781 
782 		// Add a "Rescan" option for folders.
783 		BMenuItem* rescan = NULL;
784 		if (info->children.size() > 0) {
785 			rescan = new BMenuItem(kStrRescan, NULL);
786 			fMouseOverMenu->AddItem(rescan, kIdxRescan);
787 		}
788 
789 		BMenuItem* item = fMouseOverMenu->Go(p, false, true, openRect);
790 		if (item != NULL) {
791 			switch (fMouseOverMenu->IndexOf(item)) {
792 				case kIdxGetInfo:
793 					_OpenInfo(info, p);
794 					break;
795 				case kIdxOpen:
796 					_Launch(info);
797 					break;
798 				case kIdxRescan:
799 					fScanners[fCurrentVolume]->Refresh(info);
800 					Invalidate();
801 					break;
802 				default: // must be "Open With" submenu
803 					_Launch(info, ((AppMenuItem*)item)->AppRef());
804 					break;
805 			}
806 		}
807 
808 		if (rescan != NULL) {
809 			fMouseOverMenu->RemoveItem(rescan);
810 			delete rescan;
811 		}
812 
813 		fMouseOverMenu->RemoveItem(openWith);
814 		delete openWith;
815 	}
816 	else {
817 		// The file is no longer available.
818 		fFileUnavailableMenu->Go(p, false, true, openRect);
819 	}
820 }
821 
822 
823 void
824 PieView::_Launch(FileInfo* info, const entry_ref* appRef)
825 {
826 	BMessage msg(B_REFS_RECEIVED);
827 	msg.AddRef("refs", &info->ref);
828 
829 	if (appRef == NULL) {
830 		// Let the registrar pick an app based on the file's MIME type.
831 		BMimeType* type = info->Type();
832 		be_roster->Launch(type->Type(), &msg);
833 		delete type;
834 	} else {
835 		// Launch a designated app to handle this file.
836 		be_roster->Launch(appRef, &msg);
837 	}
838 }
839 
840 void
841 PieView::_OpenInfo(FileInfo* info, BPoint p)
842 {
843 	BMessenger tracker(kTrackerSignature);
844 	if (!tracker.IsValid()) {
845 		new InfoWin(p, info, Window());
846  	} else {
847 		BMessage message(kGetInfo);
848 		message.AddRef("refs", &info->ref);
849 		tracker.SendMessage(&message);
850 	}
851 }
852 
853 
854