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