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.
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.
17 #include <AppFileInfo.h>
20 #include <ControlLook.h>
24 #include <Messenger.h>
26 #include <PopUpMenu.h>
29 #include <StringForSize.h>
32 #include <tracker_private.h>
35 #include "DiskUsage.h"
36 #include "InfoWindow.h"
37 #include "MainWindow.h"
40 #undef B_TRANSLATION_CONTEXT
41 #define B_TRANSLATION_CONTEXT "Pie View"
43 static const int32 kIdxGetInfo
= 0;
44 static const int32 kIdxOpen
= 1;
45 static const int32 kIdxOpenWith
= 2;
46 static const int32 kIdxRescan
= 3;
49 class AppMenuItem
: public BMenuItem
{
51 AppMenuItem(const char* appSig
, int category
);
52 virtual ~AppMenuItem();
54 virtual void GetContentSize(float* _width
, float* _height
);
55 virtual void DrawContent();
59 const entry_ref
* AppRef() const
72 AppMenuItem::AppMenuItem(const char* appSig
, int category
)
74 BMenuItem(kEmptyStr
, NULL
),
79 if (be_roster
->FindApp(appSig
, &fAppRef
) == B_NO_ERROR
) {
80 fIcon
= new BBitmap(BRect(0.0, 0.0, 15.0, 15.0), B_RGBA32
);
81 if (BNodeInfo::GetTrackerIcon(&fAppRef
, fIcon
, B_MINI_ICON
) == B_OK
) {
82 BEntry
appEntry(&fAppRef
);
83 if (appEntry
.InitCheck() == B_OK
) {
84 char name
[B_FILE_NAME_LENGTH
];
85 appEntry
.GetName(name
);
94 AppMenuItem::~AppMenuItem()
101 AppMenuItem::GetContentSize(float* _width
, float* _height
)
103 BMenuItem::GetContentSize(_width
, _height
);
105 *_width
+= fIcon
->Bounds().Width();
107 *_height
= max_c(*_height
, fIcon
->Bounds().Height());
112 AppMenuItem::DrawContent()
114 float yOffset
, height
;
115 GetContentSize(NULL
, &height
);
116 yOffset
= (height
- fIcon
->Bounds().Height()) / 2;
117 Menu()->SetDrawingMode(B_OP_OVER
);
118 Menu()->MovePenBy(0.0, yOffset
);
119 Menu()->DrawBitmap(fIcon
);
120 Menu()->MovePenBy(fIcon
->Bounds().Width() + kSmallHMargin
, -yOffset
);
121 BMenuItem::DrawContent();
125 // #pragma mark - PieView
128 PieView::PieView(BVolume
* volume
)
130 BView(NULL
, B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
| B_SUBPIXEL_PRECISE
),
139 fMouseOverMenu
= new BPopUpMenu(kEmptyStr
, false, false);
140 fMouseOverMenu
->AddItem(new BMenuItem(B_TRANSLATE("Get info"), NULL
),
142 fMouseOverMenu
->AddItem(new BMenuItem(B_TRANSLATE("Open"), NULL
),
145 fFileUnavailableMenu
= new BPopUpMenu(kEmptyStr
, false, false);
146 BMenuItem
* item
= new BMenuItem(B_TRANSLATE("file unavailable"), NULL
);
147 item
->SetEnabled(false);
148 fFileUnavailableMenu
->AddItem(item
);
152 font
.SetSize(ceilf(font
.Size() * 1.33));
153 font
.SetFace(B_BOLD_FACE
);
156 struct font_height fh
;
158 fFontHeight
= ceilf(fh
.ascent
) + ceilf(fh
.descent
) + ceilf(fh
.leading
);
163 PieView::AttachedToWindow()
165 fWindow
= (MainWindow
*)Window();
167 SetViewColor(Parent()->ViewColor());
168 SetLowColor(Parent()->ViewColor());
175 delete fMouseOverMenu
;
176 delete fFileUnavailableMenu
;
177 if (fScanner
!= NULL
)
178 fScanner
->RequestQuit();
183 PieView::MessageReceived(BMessage
* message
)
185 switch (message
->what
) {
187 if (fScanner
!= NULL
)
192 if (fVolume
!= NULL
) {
193 if (fScanner
!= NULL
)
196 _ShowVolume(fVolume
);
197 fWindow
->EnableCancel();
203 fWindow
->EnableRescan();
210 BView::MessageReceived(message
);
216 PieView::MouseDown(BPoint where
)
218 BMessage
* current
= Window()->CurrentMessage();
221 if (current
->FindInt32("buttons", (int32
*)&buttons
) != B_OK
)
222 buttons
= B_PRIMARY_MOUSE_BUTTON
;
224 FileInfo
* info
= _FileAt(where
);
225 if (info
== NULL
|| info
->pseudo
)
228 if ((buttons
& B_PRIMARY_MOUSE_BUTTON
) != 0) {
232 SetMouseEventMask(B_POINTER_EVENTS
);
233 } else if (buttons
& B_SECONDARY_MOUSE_BUTTON
) {
234 where
= ConvertToScreen(where
);
235 _ShowContextMenu(info
, where
);
241 PieView::MouseUp(BPoint where
)
243 if (fClicked
&& !fDragging
) {
244 // The primary mouse button was released and there's no dragging happening.
245 FileInfo
* info
= _FileAt(where
);
247 BMessage
* current
= Window()->CurrentMessage();
250 if (current
->FindInt32("modifiers", (int32
*)&modifiers
) != B_OK
)
253 if ((modifiers
& B_COMMAND_KEY
) != 0) {
254 // launch the app on command-click
258 if (info
== fScanner
->CurrentDir()) {
259 fScanner
->ChangeDir(info
->parent
);
261 fUpdateFileAt
= true;
263 } else if (info
->children
.size() > 0) {
264 fScanner
->ChangeDir(info
);
266 fUpdateFileAt
= true;
279 PieView::MouseMoved(BPoint where
, uint32 transit
, const BMessage
* dragMessage
)
282 // Primary mouse button is down.
285 // If the mouse has moved far enough, initiate dragging.
286 BPoint diff
= where
- fDragStart
;
287 float distance
= sqrtf(diff
.x
* diff
.x
+ diff
.y
* diff
.x
);
288 if (distance
> kDragThreshold
) {
291 BBitmap
* icon
= new BBitmap(BRect(0.0, 0.0, 31.0, 31.0), B_RGBA32
);
292 if (BNodeInfo::GetTrackerIcon(&fClickedFile
->ref
, icon
,
293 B_LARGE_ICON
) == B_OK
) {
294 BMessage
msg(B_SIMPLE_DATA
);
295 msg
.AddRef("refs", &fClickedFile
->ref
);
296 DragMessage(&msg
, icon
, B_OP_BLEND
, BPoint(15.0, 15.0));
301 // Mouse button is not down, display file info.
302 if (transit
== B_EXITED_VIEW
) {
304 fWindow
->ShowInfo(NULL
);
306 // Display file information.
307 fWindow
->ShowInfo(_FileAt(where
));
314 PieView::Draw(BRect updateRect
)
316 if (fScanner
!= NULL
) {
317 // There is a current volume.
318 if (fScanner
->IsBusy()) {
319 // Show progress of scanning.
320 _DrawProgressBar(updateRect
);
321 } else if (fScanner
->Snapshot() != NULL
) {
322 _DrawPieChart(updateRect
);
324 fWindow
->ShowInfo(_FileAt(fLastWhere
));
325 fUpdateFileAt
= false;
333 PieView::SetPath(BPath path
)
335 if (fScanner
== NULL
)
336 _ShowVolume(fVolume
);
338 if (fScanner
!= NULL
) {
339 string
desiredPath(path
.Path());
340 fScanner
->SetDesiredPath(desiredPath
);
346 // #pragma mark - private
350 PieView::_ShowVolume(BVolume
* volume
)
352 if (volume
!= NULL
) {
353 if (fScanner
== NULL
)
354 fScanner
= new Scanner(volume
, this);
356 if (fScanner
->Snapshot() == NULL
)
365 PieView::_DrawProgressBar(BRect updateRect
)
367 // Show the progress of the scanning operation.
369 fMouseOverInfo
.clear();
371 // Draw the progress bar.
373 float bx
= floorf((b
.left
+ b
.Width() - kProgBarWidth
) / 2.0);
374 float by
= floorf((b
.top
+ b
.Height() - kProgBarHeight
) / 2.0);
375 float ex
= bx
+ kProgBarWidth
;
376 float ey
= by
+ kProgBarHeight
;
377 float mx
= bx
+ floorf((kProgBarWidth
- 2.0) * fScanner
->Progress() + 0.5);
379 const rgb_color kBarColor
= {50, 150, 255, 255};
380 BRect
barFrame(bx
, by
, ex
, ey
);
381 be_control_look
->DrawStatusBar(this, barFrame
, updateRect
,
382 ui_color(B_PANEL_BACKGROUND_COLOR
), kBarColor
, mx
);
384 // Tell what we are doing.
385 const char* task
= fScanner
->Task();
386 float strWidth
= StringWidth(task
);
387 bx
= (b
.left
+ b
.Width() - strWidth
) / 2.0;
388 by
-= fFontHeight
+ 2.0 * kSmallVMargin
;
389 SetHighColor(0, 0, 0);
390 DrawString(task
, BPoint(bx
, by
));
395 PieView::_DrawPieChart(BRect updateRect
)
397 BRect pieRect
= Bounds();
398 if (!updateRect
.Intersects(pieRect
))
401 pieRect
.InsetBy(kPieOuterMargin
, kPieOuterMargin
);
403 // constraint proportions
404 if (pieRect
.Width() > pieRect
.Height()) {
405 float moveBy
= (pieRect
.Width() - pieRect
.Height()) / 2;
406 pieRect
.left
+= moveBy
;
407 pieRect
.right
-= moveBy
;
409 float moveBy
= (pieRect
.Height() - pieRect
.Width()) / 2;
410 pieRect
.top
-= moveBy
;
411 pieRect
.bottom
+= moveBy
;
414 FileInfo
* currentDir
= fScanner
->CurrentDir();
415 FileInfo
* parent
= currentDir
;
416 while (parent
!= NULL
) {
417 parent
= parent
->parent
;
420 _DrawDirectory(pieRect
, currentDir
, 0.0, 0.0,
421 colorIdx
% kBasePieColorCount
, 0);
426 PieView::_DrawDirectory(BRect b
, FileInfo
* info
, float parentSpan
,
427 float beginAngle
, int colorIdx
, int level
)
429 if (b
.Width() < 2.0 * (kPieCenterSize
+ level
* kPieRingSize
430 + kPieOuterMargin
+ kPieInnerMargin
)) {
434 if (info
!= NULL
&& info
->color
>= 0 && level
== 0)
435 colorIdx
= info
->color
% kBasePieColorCount
;
436 else if (info
!= NULL
)
437 info
->color
= colorIdx
;
439 VolumeSnapshot
* snapshot
= fScanner
->Snapshot();
441 float cx
= floorf(b
.left
+ b
.Width() / 2.0 + 0.5);
442 float cy
= floorf(b
.top
+ b
.Height() / 2.0 + 0.5);
447 // Make room for mouse over info.
448 fMouseOverInfo
.clear();
449 fMouseOverInfo
[0] = SegmentList();
451 // Draw the center circle.
452 const char* displayName
;
454 // NULL represents the entire volume. Show used and free space in
455 // the center circle, with the used segment representing the
456 // volume's root directory.
457 off_t volCapacity
= snapshot
->capacity
;
458 mySpan
= 360.0 * (volCapacity
- snapshot
->freeBytes
) / volCapacity
;
460 SetHighColor(kEmptySpcColor
);
461 FillEllipse(BPoint(cx
, cy
), kPieCenterSize
, kPieCenterSize
);
463 SetHighColor(kBasePieColor
[0]);
464 FillArc(BPoint(cx
, cy
), kPieCenterSize
, kPieCenterSize
, 0.0,
467 // Show total volume capacity.
468 char label
[B_PATH_NAME_LENGTH
];
469 string_for_size(volCapacity
, label
, sizeof(label
));
470 SetHighColor(kPieBGColor
);
471 SetDrawingMode(B_OP_OVER
);
472 DrawString(label
, BPoint(cx
- StringWidth(label
) / 2.0,
473 cy
+ fFontHeight
+ kSmallVMargin
));
474 SetDrawingMode(B_OP_COPY
);
476 displayName
= snapshot
->name
.c_str();
478 // Record in-use space and free space for use during MouseMoved().
479 info
= snapshot
->rootDir
;
480 info
->color
= colorIdx
;
481 fMouseOverInfo
[0].push_back(Segment(0.0, mySpan
, info
));
482 if (mySpan
< 360.0 - kMinSegmentSpan
) {
483 fMouseOverInfo
[0].push_back(Segment(mySpan
, 360.0,
484 snapshot
->freeSpace
));
487 // Show a normal directory.
488 SetHighColor(kBasePieColor
[colorIdx
]);
489 FillEllipse(BRect(cx
- kPieCenterSize
, cy
- kPieCenterSize
,
490 cx
+ kPieCenterSize
+ 0.5, cy
+ kPieCenterSize
+ 0.5));
491 displayName
= info
->ref
.name
;
494 // Record the segment for use during MouseMoved().
495 fMouseOverInfo
[0].push_back(Segment(0.0, mySpan
, info
));
499 SetHighColor(kOutlineColor
);
500 StrokeEllipse(BPoint(cx
, cy
), kPieCenterSize
+ 0.5,
501 kPieCenterSize
+ 0.5);
503 // Show the name of the volume or directory.
504 BString
label(displayName
);
507 font
.TruncateString(&label
, B_TRUNCATE_END
,
508 2.0 * (kPieCenterSize
- kSmallHMargin
));
509 float labelWidth
= font
.StringWidth(label
.String());
511 SetHighColor(kPieBGColor
);
512 SetDrawingMode(B_OP_OVER
);
513 DrawString(label
.String(), BPoint(cx
- labelWidth
/ 2.0, cy
));
514 SetDrawingMode(B_OP_COPY
);
517 // Draw an exterior segment.
519 if (info
->parent
== NULL
)
520 parentSize
= (float)snapshot
->capacity
;
522 parentSize
= (float)info
->parent
->size
;
524 mySpan
= parentSpan
* (float)info
->size
/ parentSize
;
525 if (mySpan
>= kMinSegmentSpan
) {
526 const float tint
= 1.4f
- level
* 0.08f
;
527 float radius
= kPieCenterSize
+ level
* kPieRingSize
528 - kPieRingSize
/ 2.0;
530 // Draw the grey border
531 SetHighColor(tint_color(kOutlineColor
, tint
));
532 SetPenSize(kPieRingSize
+ 1.5f
);
533 StrokeArc(BPoint(cx
, cy
), radius
, radius
,
534 beginAngle
- 0.001f
* radius
, mySpan
+ 0.002f
* radius
);
536 // Draw the colored area
537 rgb_color color
= tint_color(kBasePieColor
[colorIdx
], tint
);
539 SetPenSize(kPieRingSize
);
540 StrokeArc(BPoint(cx
, cy
), radius
, radius
, beginAngle
, mySpan
);
542 // Record the segment for use during MouseMoved().
543 if (fMouseOverInfo
.find(level
) == fMouseOverInfo
.end())
544 fMouseOverInfo
[level
] = SegmentList();
546 fMouseOverInfo
[level
].push_back(
547 Segment(beginAngle
, beginAngle
+ mySpan
, info
));
552 vector
<FileInfo
*>::iterator i
= info
->children
.begin();
553 while (i
!= info
->children
.end()) {
555 = _DrawDirectory(b
, *i
, mySpan
, beginAngle
, colorIdx
, level
+ 1);
556 if (childSpan
>= kMinSegmentSpan
) {
557 beginAngle
+= childSpan
;
558 colorIdx
= (colorIdx
+ 1) % kBasePieColorCount
;
568 PieView::_FileAt(const BPoint
& where
)
571 float cx
= b
.left
+ b
.Width() / 2.0;
572 float cy
= b
.top
+ b
.Height() / 2.0;
573 float dx
= where
.x
- cx
;
574 float dy
= where
.y
- cy
;
575 float dist
= sqrt(dx
* dx
+ dy
* dy
);
578 if (dist
< kPieCenterSize
)
581 level
= 1 + (int)((dist
- kPieCenterSize
) / kPieRingSize
);
583 float angle
= rad2deg(atan(dy
/ dx
));
584 angle
= ((dx
< 0.0) ? 180.0 : (dy
< 0.0) ? 0.0 : 360.0) - angle
;
586 if (fMouseOverInfo
.find(level
) == fMouseOverInfo
.end()) {
587 // No files in this level (ring) of the pie.
591 SegmentList s
= fMouseOverInfo
[level
];
592 SegmentList::iterator i
= s
.begin();
593 while (i
!= s
.end() && (angle
< (*i
).begin
|| (*i
).end
< angle
))
596 // Nothing at this angle.
605 PieView::_AddAppToList(vector
<AppMenuItem
*>& list
, const char* appSig
,
609 if (strcmp(appSig
, kAppSignature
) == 0)
612 AppMenuItem
* item
= new AppMenuItem(appSig
, category
);
613 if (item
->IsValid()) {
614 vector
<AppMenuItem
*>::iterator i
= list
.begin();
615 while (i
!= list
.end()) {
616 if (*item
->AppRef() == *(*i
)->AppRef()) {
623 list
.push_back(item
);
625 // Skip items that weren't constructed successfully.
632 PieView::_BuildOpenWithMenu(FileInfo
* info
)
634 vector
<AppMenuItem
*> appList
;
636 // Get preferred app.
637 BMimeType
* type
= info
->Type();
638 char appSignature
[B_MIME_TYPE_LENGTH
];
639 if (type
->GetPreferredApp(appSignature
) == B_OK
)
640 _AddAppToList(appList
, appSignature
, 1);
642 // Get apps that handle this subtype and supertype.
644 if (type
->GetSupportingApps(&msg
) == B_OK
) {
645 int32 subs
, supers
, i
;
646 msg
.FindInt32("be:sub", &subs
);
647 msg
.FindInt32("be:super", &supers
);
650 for (i
= 0; i
< subs
; i
++) {
651 msg
.FindString("applications", i
, &appSig
);
652 _AddAppToList(appList
, appSig
, 2);
655 for (i
= 0; i
< supers
; i
++) {
656 msg
.FindString("applications", i
+ hold
, &appSig
);
657 _AddAppToList(appList
, appSig
, 3);
661 // Get apps that handle any type.
662 if (BMimeType::GetWildcardApps(&msg
) == B_OK
) {
664 for (int32 i
= 0; true; i
++) {
665 if (msg
.FindString("applications", i
, &appSig
) == B_OK
)
666 _AddAppToList(appList
, appSig
, 4);
674 BMenu
* openWith
= new BMenu(B_TRANSLATE("Open with"));
676 if (appList
.size() == 0) {
677 BMenuItem
* item
= new BMenuItem(B_TRANSLATE("no supporting apps"),
679 item
->SetEnabled(false);
680 openWith
->AddItem(item
);
682 vector
<AppMenuItem
*>::iterator i
= appList
.begin();
683 int category
= (*i
)->Category();
684 while (i
!= appList
.end()) {
685 if (category
!= (*i
)->Category()) {
686 openWith
->AddSeparatorItem();
687 category
= (*i
)->Category();
689 openWith
->AddItem(*i
);
699 PieView::_ShowContextMenu(FileInfo
* info
, BPoint p
)
701 BRect
openRect(p
.x
- 5.0, p
.y
- 5.0, p
.x
+ 5.0, p
.y
+ 5.0);
703 // Display the open-with menu only if the file is still available.
704 BNode
node(&info
->ref
);
705 if (node
.InitCheck() == B_OK
) {
706 // Add "Open With" submenu.
707 BMenu
* openWith
= _BuildOpenWithMenu(info
);
708 fMouseOverMenu
->AddItem(openWith
, kIdxOpenWith
);
710 // Add a "Rescan" option for folders.
711 BMenuItem
* rescan
= NULL
;
712 if (info
->children
.size() > 0) {
713 rescan
= new BMenuItem(B_TRANSLATE("Rescan"), NULL
);
714 fMouseOverMenu
->AddItem(rescan
, kIdxRescan
);
717 BMenuItem
* item
= fMouseOverMenu
->Go(p
, false, true, openRect
);
719 switch (fMouseOverMenu
->IndexOf(item
)) {
727 fScanner
->Refresh(info
);
728 fWindow
->EnableCancel();
731 default: // must be "Open With" submenu
732 _Launch(info
, ((AppMenuItem
*)item
)->AppRef());
737 if (rescan
!= NULL
) {
738 fMouseOverMenu
->RemoveItem(rescan
);
742 fMouseOverMenu
->RemoveItem(openWith
);
745 // The file is no longer available.
746 fFileUnavailableMenu
->Go(p
, false, true, openRect
);
751 PieView::_Launch(FileInfo
* info
, const entry_ref
* appRef
)
753 BMessage
msg(B_REFS_RECEIVED
);
754 msg
.AddRef("refs", &info
->ref
);
756 if (appRef
== NULL
) {
757 // Let the registrar pick an app based on the file's MIME type.
758 BMimeType
* type
= info
->Type();
759 be_roster
->Launch(type
->Type(), &msg
);
762 // Launch a designated app to handle this file.
763 be_roster
->Launch(appRef
, &msg
);
769 PieView::_OpenInfo(FileInfo
* info
, BPoint p
)
771 BMessenger
tracker(kTrackerSignature
);
772 if (!tracker
.IsValid()) {
773 new InfoWin(p
, info
, Window());
775 BMessage
message(kGetInfo
);
776 message
.AddRef("refs", &info
->ref
);
777 tracker
.SendMessage(&message
);