diff --git a/docs/apps/diskusage/DiskUsage.html b/docs/apps/diskusage/DiskUsage.html new file mode 100644 index 0000000000..c275e5f67e --- /dev/null +++ b/docs/apps/diskusage/DiskUsage.html @@ -0,0 +1,142 @@ + + +DiskUsage + + + + +
+
+

 DiskUsage
+Volume Utilization Visualizer

+ +
+ + +

What is it?

+ +DiskUsage shows a graphical representation of how the space on your +volumes is utilized. It uses concentric circles to represent different levels +in the file system hierarchy. It is useful for answering the question, "Where +has all my disk space gone?" And sometimes it's fun just to look at the +pictures.

+ +Here is a picture of DiskUsage in action, so to speak.

+ +

+ +The circle in the middle represents my /boot/home directory. +Each segment of the ring immediately outside that circle represents a file or +directory within /boot/home. And so on. + + +

Usage

+ +Startup. You can launch DiskUsage from the +Tracker, either directly or by dropping a volume or folder on its +icon; or from the Terminal, optionally passing the names of one +or more files or folders as arguments. If you launch DiskUsage +without specifying a volume, it will wait until you drop one or more volumes +or folders on its window or select a volume from the menu.

+ +While DiskUsage is scanning a volume, it displays a status bar +showing its progress. Multiple volumes are scanned in parallel (each in its +own thread). You can view the progress on a volume by selecting that volume +from the menu.

+ +The main display. When DiskUsage finishes scanning +a volume, it displays a graphical representation of the volume's files and +directories. As you move the mouse over each part of the picture, information +about each file or directory appears in the area at the bottom of the window. +You can resize the window to increase or decrease the number of levels that +DiskUsage displays.

+ +Zooming. You can "zoom" in on a directory by clicking the +primary mouse button on it. The picture is redrawn with that directory in the +center circle. You can zoom out one level by clicking the center circle. At +the outermost level, the center circle represents the volume itself, showing +free and used space as pie slices.

+ +Pop-up menu. Clicking the secondary mouse button on a part +of the picture pops up a menu with Tracker-like options +applicable to that file or directory. For directories, the menu includes an +option to rescan that directory and its subdirectories (not the entire +volume). This is useful since DiskUsage does not monitor changes +to the file system.

+ +Drag-and-drop. You can drag files and directories from +DiskUsage to other applications (or to the desktop, the trash, etc.) +with the primary mouse button. You can drop volumes and folders on +DiskUsage's window to zoom directly to them.

+ +Other controls. The buttons in the top right corner of the +DiskUsage window rescan the selected volume and display the +documentation (this file).

+ +Notes: + +

+ + +

Credits, etc.

+ +Thanks to Steffen Gerlach for creating the Windows +Scanner +utility, from which I took the idea for using concentric circles to represent +disk usage.

+ +Thanks to the Vim development team for +creating the best programmer's editor on the planet, with which +DiskUsage's original code and documentation were lovingly +hand-crafted.

+ +Thanks to the folks at Be for creating such a kick-butt OS. It has made +programming fun again.

+ +Thanks to Mike Steed for doing the programming. All the code is original.

+ +DiskUsage used to be called guido by it's original +author. The name guido originated from GUI + du + -- the command-line disk usage utility. Plus, one of his favorite languages, +Python, was created by a fine programmer +named Guido. + + +

Known problems

+ + + + +

License

+ +DiskUsage is Copyright (c) 1999 Mike Steed. You are free to use +and distribute this software as long as it is accompanied by this documentation +and copyright notice. The software comes with no warranty, etc.

+ +


+ + + diff --git a/docs/apps/diskusage/home.png b/docs/apps/diskusage/home.png new file mode 100644 index 0000000000..d8632ecedf Binary files /dev/null and b/docs/apps/diskusage/home.png differ diff --git a/docs/apps/diskusage/icon.png b/docs/apps/diskusage/icon.png new file mode 100644 index 0000000000..a35ae694e7 Binary files /dev/null and b/docs/apps/diskusage/icon.png differ diff --git a/src/apps/Jamfile b/src/apps/Jamfile index 0c76bbda65..795d13bd8d 100644 --- a/src/apps/Jamfile +++ b/src/apps/Jamfile @@ -13,6 +13,7 @@ SubInclude HAIKU_TOP src apps cortex ; SubInclude HAIKU_TOP src apps deskbar ; SubInclude HAIKU_TOP src apps deskcalc ; SubInclude HAIKU_TOP src apps diskprobe ; +SubInclude HAIKU_TOP src apps diskusage ; SubInclude HAIKU_TOP src apps drivesetup ; SubInclude HAIKU_TOP src apps expander ; SubInclude HAIKU_TOP src apps fontdemo ; diff --git a/src/apps/diskusage/App.cpp b/src/apps/diskusage/App.cpp new file mode 100644 index 0000000000..5e5b03b046 --- /dev/null +++ b/src/apps/diskusage/App.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#include "App.h" + +#include + +#include +#include +#include +#include + +#include "Common.h" +#include "MainWindow.h" + + +App::App() + : BApplication(kAppSignature), + fResources(read_resources(kAppSignature)), + fMainWindow(NULL) +{ +} + + +App::~App() +{ + delete fResources; +} + + +void +App::ArgvReceived(int32 argc, char** argv) +{ + BMessage refsReceived(B_REFS_RECEIVED); + for (int32 i = 1; i < argc; i++) { + BEntry entry(argv[i], true); + entry_ref ref; + if (entry.GetRef(&ref) == B_OK) + refsReceived.AddRef("refs", &ref); + } + if (refsReceived.HasRef("refs")) + PostMessage(&refsReceived); +} + + +void +App::RefsReceived(BMessage* message) +{ + fMainWindow->PostMessage(message); +} + + +void +App::ReadyToRun() +{ + BRect frame; + + BPath path; + BFile settingsFile; + BMessage settings; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK + || path.Append("DiskUsage") != B_OK + || settingsFile.SetTo(path.Path(), B_READ_ONLY) != B_OK + || settings.Unflatten(&settingsFile) != B_OK + || settings.FindRect("window frame", &frame) != B_OK) { + // use default window frame + frame.Set(0, 0, kDefaultPieSize, kDefaultPieSize); + frame.OffsetTo(50, 50); + } + + fMainWindow = new MainWindow(frame); +} + + +bool +App::QuitRequested() +{ + // Save the settings. + BPath path; + BFile settingsFile; + BMessage settings; + if (settings.AddRect("window frame", fMainWindow->Frame()) != B_OK + || find_directory(B_USER_SETTINGS_DIRECTORY, &path) != B_OK + || path.Append("DiskUsage") != B_OK + || settingsFile.SetTo(path.Path(), B_WRITE_ONLY) != B_OK + || settings.Flatten(&settingsFile) != B_OK) { + fprintf(stderr, "Failed to write application settings.\n"); + } + + return BApplication::QuitRequested(); +} + + +// #pragma mark - + + +int +main() +{ + App app; + app.Run(); + return 0; +} + diff --git a/src/apps/diskusage/App.h b/src/apps/diskusage/App.h new file mode 100644 index 0000000000..fe39ac2179 --- /dev/null +++ b/src/apps/diskusage/App.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef APP_H +#define APP_H + +#include + + +class MainWindow; + +class App: public BApplication { +public: + App(); + virtual ~App(); + + virtual void ArgvReceived(int32 argc, char** argv); + virtual void RefsReceived(BMessage* message); + + virtual void ReadyToRun(); + virtual bool QuitRequested(); + +private: + BResources* fResources; + MainWindow* fMainWindow; +}; + +#endif // APP_H + diff --git a/src/apps/diskusage/Common.cpp b/src/apps/diskusage/Common.cpp new file mode 100644 index 0000000000..25ede95817 --- /dev/null +++ b/src/apps/diskusage/Common.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#define ASSIGN_RESOURCES +#include "Common.h" + +#include +#include + +#include +#include +#include +#include + + +BResources* +read_resources(const char* appSignature) +{ + status_t ret = be_app->GetAppInfo(&kAppInfo); + if (ret != B_OK) { + fprintf(stderr, "Failed to init application info: %s\n", + strerror(ret)); + exit(1); + } + + BFile file(&kAppInfo.ref, O_RDONLY); + ret = file.InitCheck(); + if (ret != B_OK) { + fprintf(stderr, "Failed to init application file to read resources: " + "%s\n", strerror(ret)); + exit(1); + } + + BResources* r = new BResources(&file); + +#define LoadString(n) (char*)r->LoadResource(B_STRING_TYPE, n, &ignore) +#define LoadColor(n) *(rgb_color*)r->LoadResource(B_RGB_COLOR_TYPE, n, &ignore) +#define LoadUint8(n) *(uint8*)r->LoadResource(B_UINT8_TYPE, n, &ignore) + + size_t ignore; + kVolMenuLabel = LoadString("STR_VM_LABEL"); + kOneFile = LoadString("STR_1_FILE"); + kManyFiles = LoadString("STR_N_FILES"); + kStrRescan = LoadString("STR_RESCAN"); + kStrScanningX = LoadString("STR_SCN_X"); + kStrUnavail = LoadString("STR_UNAVAIL"); + kVolMenuDefault = LoadString("STR_VM_DFLT"); + kVolPrompt = LoadString("STR_VPROMPT"); + kMenuGetInfo = LoadString("STR_M_INFO"); + kMenuOpen = LoadString("STR_M_OPEN"); + kMenuOpenWith = LoadString("STR_M_OPENW"); + kMenuNoApps = LoadString("STR_M_NAPPS"); + kInfoSize = LoadString("STR_SIZE"); + kInfoInFiles = LoadString("STR_INFILES"); + kInfoCreated = LoadString("STR_MADE"); + kInfoModified = LoadString("STR_MOD"); + kInfoTimeFmt = LoadString("STR_TIMEFMT"); + kInfoKind = LoadString("STR_KIND"); + kInfoPath = LoadString("STR_PATH"); + + kWindowColor = ui_color(B_PANEL_BACKGROUND_COLOR); + kOutlineColor = LoadColor("RGB_PIE_OL"); + kPieBGColor = LoadColor("RGB_PIE_BG"); + kEmptySpcColor = LoadColor("RGB_PIE_MT"); + + kBasePieColorCount = LoadUint8("N_PIE_COLORS"); + kBasePieColor = new rgb_color[kBasePieColorCount]; + for (int i = 0; i < kBasePieColorCount; i++) { + char colorName[16] = "RGB_PIE_n"; + colorName[8] = '1' + i; + kBasePieColor[i] = LoadColor(colorName); + } + + // Get a reference to the help file. + BPath path; + if (find_directory(B_BEOS_DOCUMENTATION_DIRECTORY, &path) == B_OK + && path.Append(kHelpFileName) == B_OK) { + printf("help file =? %s\n", path.Path()); + BEntry entry(path.Path()); + kFoundHelpFile = entry.Exists(); + entry.GetRef(&kHelpFileRef); + } else + kFoundHelpFile = false; + + + return r; +} + + +void +size_to_string(off_t byteCount, char* name) +{ + struct { + off_t limit; + float divisor; + char* format; + } scale[] = { + { 0x100000, 1024.0, "%.2f KB" }, + { 0x40000000, 1048576.0, "%.2f MB" }, + { 0x10000000000ull, 1073741824.0, "%.2f GB" }, + { 0x4000000000000ull, 1.09951162778e+12, "%.2f TB" } + }; + + if (byteCount < 1024) { + sprintf(name, "%lld bytes", byteCount); + } else { + int i = 0; + while (byteCount >= scale[i].limit) + i++; + + sprintf(name, scale[i].format, byteCount / scale[i].divisor); + } +} + diff --git a/src/apps/diskusage/Common.h b/src/apps/diskusage/Common.h new file mode 100644 index 0000000000..83ce776e42 --- /dev/null +++ b/src/apps/diskusage/Common.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef COMMON_H +#define COMMON_H + +#include +#include +#include +#include + + +#ifdef ASSIGN_RESOURCES +# define EXTERN +# define EQ(x) =x +#else +# define EXTERN extern +# define EQ(x) +#endif + +#define PUBLIC_CONST extern const + + +// Resources +EXTERN char* kStrRescan; +EXTERN char* kStrScanningX; +EXTERN char* kStrUnavail; +EXTERN char* kVolMenuLabel; +EXTERN char* kVolMenuDefault; +EXTERN char* kVolPrompt; +EXTERN char* kOneFile; +EXTERN char* kManyFiles; +EXTERN char* kMenuGetInfo; +EXTERN char* kMenuOpen; +EXTERN char* kMenuOpenWith; +EXTERN char* kMenuNoApps; +EXTERN char* kInfoSize; +EXTERN char* kInfoInFiles; +EXTERN char* kInfoCreated; +EXTERN char* kInfoModified; +EXTERN char* kInfoTimeFmt; +EXTERN char* kInfoKind; +EXTERN char* kInfoPath; + +EXTERN rgb_color kWindowColor; +EXTERN rgb_color kOutlineColor; +EXTERN rgb_color kPieBGColor; +EXTERN rgb_color kEmptySpcColor; +EXTERN int kBasePieColorCount; +EXTERN rgb_color *kBasePieColor; + +// Non-resources :) +PUBLIC_CONST char* kAppSignature EQ("application/x-vnd.haiku-diskusage"); +PUBLIC_CONST char* kHelpFileName EQ("apps/diskusage/DiskUsage.html"); +PUBLIC_CONST char* kPieRectAttrName EQ("mainrect"); + +PUBLIC_CONST char* kHelpBtnLabel EQ("?"); +PUBLIC_CONST char* kEmptyStr EQ(""); +PUBLIC_CONST char* kNameVolPtr EQ("vol"); +PUBLIC_CONST char* kNameFilePtr EQ("file"); + +PUBLIC_CONST float kSmallHMargin EQ(5.0); +PUBLIC_CONST float kSmallVMargin EQ(2.0); +PUBLIC_CONST float kButtonMargin EQ(20.0); +PUBLIC_CONST float kMinButtonWidth EQ(60.0); + +PUBLIC_CONST float kProgBarWidth EQ(150.0); +PUBLIC_CONST float kProgBarHeight EQ(16.0); +PUBLIC_CONST float kReportInterval EQ(2.5); + +PUBLIC_CONST float kDefaultPieSize EQ(400.0); +PUBLIC_CONST float kPieCenterSize EQ(80.0); +PUBLIC_CONST float kPieRingSize EQ(20.0); +PUBLIC_CONST float kPieInnerMargin EQ(10.0); +PUBLIC_CONST float kPieOuterMargin EQ(10.0); +PUBLIC_CONST float kMinSegmentSpan EQ(2.0); +PUBLIC_CONST int kLightenFactor EQ(0x12); +PUBLIC_CONST float kDragThreshold EQ(5.0); + +EXTERN app_info kAppInfo; +EXTERN entry_ref kHelpFileRef; +EXTERN bool kFoundHelpFile; + +#define kMenuSelectVol 'gMSV' +#define kBtnRescan 'gBRF' +#define kBtnHelp 'gHLP' +#define kScanRefresh 'gSRF' +#define kScanProgress 'gSPR' +#define kScanDone 'gSDN' + +// support functions +#define max(x, y) ((x) > (y) ? (x) : (y)) +#define min(x, y) ((x) < (y) ? (x) : (y)) + +#define deg2rad(x) (2.0 * M_PI * (x) / 360.0) +#define rad2deg(x) (360.0 * (x) / (2.0 * M_PI)) + +BResources* read_resources(const char* appSignature); +void size_to_string(off_t byteCount, char* name); + +#endif // COMMON_H + diff --git a/src/apps/diskusage/ControlsView.cpp b/src/apps/diskusage/ControlsView.cpp new file mode 100644 index 0000000000..7ba86f80ef --- /dev/null +++ b/src/apps/diskusage/ControlsView.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#include "ControlsView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common.h" + + +class VolumeMenuItem: public BMenuItem { +public: + VolumeMenuItem(BVolume* volume, BMessage* message); + virtual ~VolumeMenuItem(); + + virtual void GetContentSize(float* width, float* height); + virtual void DrawContent(); + + BVolume* Volume() const + { return fVolume; } + status_t Invoke() + { return BMenuItem::Invoke(); } + +private: + BBitmap* fIcon; + BVolume* fVolume; +}; + + +VolumeMenuItem::VolumeMenuItem(BVolume* volume, BMessage* message) + : BMenuItem(kEmptyStr, message), + fIcon(new BBitmap(BRect(0, 0, 15, 15), B_RGBA32)), + fVolume(volume) +{ + char name[B_PATH_NAME_LENGTH]; + fVolume->GetName(name); + SetLabel(name); + + if (fVolume->GetIcon(fIcon, B_MINI_ICON) < B_OK) { + delete fIcon; + fIcon = NULL; + } +} + + +VolumeMenuItem::~VolumeMenuItem() +{ + delete fIcon; + delete fVolume; +} + + +void +VolumeMenuItem::GetContentSize(float* width, float* height) +{ + *width = be_plain_font->StringWidth(Label()); + + struct font_height fh; + be_plain_font->GetHeight(&fh); + float fontHeight = fh.ascent + fh.descent + fh.leading; + if (fIcon) { + *height = max(fontHeight, fIcon->Bounds().Height()); + *width += fIcon->Bounds().Width() + kSmallHMargin; + } else + *height = fontHeight; +} + + +void +VolumeMenuItem::DrawContent() +{ + if (fIcon) { + Menu()->SetDrawingMode(B_OP_OVER); + Menu()->MovePenBy(0.0, -1.0); + Menu()->DrawBitmap(fIcon); + Menu()->SetDrawingMode(B_OP_COPY); + Menu()->MovePenBy(fIcon->Bounds().Width() + kSmallHMargin, 0.0); + } + BMenuItem::DrawContent(); +} + + +// #pragma mark - + + +class ControlsView::VolumePopup: public BMenuField { +public: + VolumePopup(BRect r); + virtual ~VolumePopup(); + + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage* message); + + BVolume* FindDeviceFor(dev_t device, + bool invoke = false); + +private: + void _AddVolume(dev_t device); + void _RemoveVolume(dev_t device); + + BVolumeRoster* fVolumeRoster; +}; + + +ControlsView::VolumePopup::VolumePopup(BRect r) + : BMenuField(r, NULL, kVolMenuLabel, new BPopUpMenu(kVolMenuDefault), + false, B_FOLLOW_LEFT) +{ + SetViewColor(kWindowColor); + SetLowColor(kWindowColor); + + SetDivider(kSmallHMargin + StringWidth(kVolMenuLabel)); + + // Populate the menu with the persistent volumes. + fVolumeRoster = new BVolumeRoster(); + + BVolume tempVolume; + while (fVolumeRoster->GetNextVolume(&tempVolume) == B_OK) { + if (tempVolume.IsPersistent()) { + BVolume* volume = new BVolume(tempVolume); + BMessage* message = new BMessage(kMenuSelectVol); + message->AddPointer(kNameVolPtr, volume); + VolumeMenuItem *item = new VolumeMenuItem(volume, message); + Menu()->AddItem(item); + } + } +} + + +ControlsView::VolumePopup::~VolumePopup() +{ + fVolumeRoster->StopWatching(); + delete fVolumeRoster; +} + + +void +ControlsView::VolumePopup::AttachedToWindow() +{ + // Begin watching mount and unmount events. + fVolumeRoster->StartWatching(BMessenger(this)); +} + + +void +ControlsView::VolumePopup::MessageReceived(BMessage* message) +{ + switch (message->what) { + case B_NODE_MONITOR: + switch (message->FindInt32("opcode")) { + case B_DEVICE_MOUNTED: + _AddVolume(message->FindInt32("new device")); + break; + + case B_DEVICE_UNMOUNTED: + _RemoveVolume(message->FindInt32("device")); + break; + } + break; + + default: + BMenuField::MessageReceived(message); + break; + } +} + + +BVolume* +ControlsView::VolumePopup::FindDeviceFor(dev_t device, bool invoke) +{ + BVolume* volume = NULL; + + // Iterate through items looking for a BVolume representing this device. + for (int i = 0; VolumeMenuItem* item = (VolumeMenuItem*)Menu()->ItemAt(i); i++) { + if (item->Volume()->Device() == device) { + volume = item->Volume(); + if (invoke) + item->Invoke(); + break; + } + } + + return volume; +} + + +void +ControlsView::VolumePopup::_AddVolume(dev_t device) +{ + // Make sure the volume is not already in the menu. + for (int i = 0; i < Menu()->CountItems(); i++) { + VolumeMenuItem* item = (VolumeMenuItem*)Menu()->ItemAt(i); + if (item->Volume()->Device() == device) + return; + } + + // Add the newly mounted volume to the menu. + BVolume* volume = new BVolume(device); + BMessage* message = new BMessage(kMenuSelectVol); + message->AddPointer(kNameVolPtr, volume); + VolumeMenuItem* item = new VolumeMenuItem(volume, message); + Menu()->AddItem(item); +} + + +void +ControlsView::VolumePopup::_RemoveVolume(dev_t device) +{ + for (int i = 0; i < Menu()->CountItems(); i++) { + VolumeMenuItem* item = (VolumeMenuItem*)Menu()->ItemAt(i); + if (item->Volume()->Device() == device) { + // If the volume being removed is currently selected, prompt for a + // different volume. + if (item->IsMarked()) { + // update the displayed volume label now that there is no marked + // item: + Menu()->Superitem()->SetLabel(kVolMenuDefault); + + BMessage messae(kMenuSelectVol); + messae.AddPointer(kNameVolPtr, NULL); + Window()->PostMessage(&messae); + } + + Menu()->RemoveItem(item); + return; + } + } +} + + +// #pragma mark - + + +ControlsView::ControlsView(BRect r) + : BView(r, NULL, B_FOLLOW_LEFT_RIGHT, 0) +{ + SetViewColor(kWindowColor); + + r.top += kSmallVMargin; + r.right -= kSmallHMargin; + float buttonWidth = kButtonMargin + StringWidth(kHelpBtnLabel); + r.left = r.right - buttonWidth; + BButton* helpButton = new BButton(r, NULL, kHelpBtnLabel, new BMessage(kBtnHelp), + B_FOLLOW_RIGHT); + if (!kFoundHelpFile) + helpButton->SetEnabled(false); + + r.right = r.left - kSmallHMargin; + buttonWidth = kButtonMargin + StringWidth(kStrRescan); + r.left = r.right - max(kMinButtonWidth, buttonWidth); + fRescanButton = new BButton(r, NULL, kStrRescan, new BMessage(kBtnRescan), + B_FOLLOW_RIGHT); + + fVolumePopup = new VolumePopup( + BRect(kSmallHMargin, kSmallVMargin, r.left - kSmallHMargin, kSmallVMargin)); + + float width, height; + fRescanButton->GetPreferredSize(&width, &height); + ResizeTo(Bounds().Width(), height + 6.0); + + // Horizontal divider + BRect r = Bounds(); + r.top = r.bottom - 1.0; + r.left -= 5.0; r.right += 5.0; + BBox* divider = new BBox(r, NULL, B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW); + + AddChild(fVolumePopup); + AddChild(divider); + AddChild(fRescanButton); + AddChild(helpButton); +} + + +ControlsView::~ControlsView() +{ +} + + +BVolume* +ControlsView::FindDeviceFor(dev_t device, bool invoke) +{ + return fVolumePopup->FindDeviceFor(device, invoke); +} + + diff --git a/src/apps/diskusage/ControlsView.h b/src/apps/diskusage/ControlsView.h new file mode 100644 index 0000000000..b57f0558d2 --- /dev/null +++ b/src/apps/diskusage/ControlsView.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef CONTROLS_VIEW_H +#define CONTROLS_VIEW_H + +#include +#include + + +class BVolume; + +class ControlsView: public BView { +public: + ControlsView(BRect r); + virtual ~ControlsView(); + + void SetRescanEnabled(bool enable) + { fRescanButton->SetEnabled(enable); } + + BVolume* FindDeviceFor(dev_t device, + bool invoke = false); + +private: + class VolumePopup; + + VolumePopup* fVolumePopup; + BButton* fRescanButton; +}; + + +#endif // CONTROLS_VIEW_H + diff --git a/src/apps/diskusage/DiskUsage.rdef b/src/apps/diskusage/DiskUsage.rdef new file mode 100644 index 0000000000..8c04e378dd --- /dev/null +++ b/src/apps/diskusage/DiskUsage.rdef @@ -0,0 +1,78 @@ +resource app_signature "application/x-vnd.haiku-diskusage"; + +resource app_version { + major = 1, + middle = 0, + minor = 0, + + variety = B_APPV_FINAL, + internal = 0, + + short_info = "1.0.1", + long_info = "1.0.1 (c) 1999 Mike Steed, (c) 2008 Haiku" +}; + +resource app_flags B_MULTIPLE_LAUNCH; + +resource file_types message { + "types" = "application/x-vnd.Be-volume", + "types" = "application/x-vnd.Be-directory" +}; + +resource vector_icon { + $"6E6369660F03010000020016023AF81C3830AFBB29EB3E24FE4903D742949400" + $"8EFF47020016023AFDE4B927BB3BBB443D89C84A587E467D6A007DFFB0020016" + $"023BFC1538B2EABB58DE3E788D4A21874591FF0096FFB0020006023C96323A4D" + $"3FBAFC013D5A974B57A549844D00474747FFA5A0A002001602BC592FBB29A73C" + $"0CE4BD0B7C4892C04B7966007DFFD40200060238DBB4399733BC4A333BA54248" + $"6E6649EE7B00596756FFEBB2B203A7FF0003FF0000040180020306033E800000" + $"00000000003DC00049E00049200000007F7F7EB1EAEAFF4F9DA4020306033E80" + $"000000000000003DC00049E00049200000E3B78D86F4F8C9F4888C9802030603" + $"3E80000000000000003DC00049E00049200000A9525286F5C0D3F4B05A660203" + $"06033E80000000000000003DC00049E00049200000888C9886EBEDF2F4888C98" + $"020016033D400000000000000040000048800000000000FF9BD3FFCD0F0A0622" + $"3C2249445B5A3E5A3139250A05223C2246B54AC1D7393239250A055A31392539" + $"32C7D0BE1E5A3C0A043932B4CBC2564458C921BE980A04444B445B5A3E5A310A" + $"04223C2249445B444B08022743B814C1F10802264329440A05445D495D603E5A" + $"3B5B3F02043F2EC3882EBB2B2E2C3A2CBAB82CBFFF3F46BB2B46C38846523A52" + $"BFFF52BAB80604BF3E2E3E2EBAF2B8B32C3A2CBACC2CBDF9B84CBF1AB7F7BE90" + $"B84CBF1A3E3906033E4039C6D5BCA1C6D5BCA1C651BA7FC024B89AC392B8D4C0" + $"24B89A0604FE3F3AB915C00AB915C00ABA72C14E3F46BCBE46C33846C6DDBDF1" + $"C668C04FC6DDBDF10204423942BCBD42BD2E3F3AC0023ABEB13A3C393CBD2E3C" + $"BCBD3F38BEB138C002380A04233C444B59323926110A090108000A0001001001" + $"178400040A010101000A020102000A030103000A00030A0B0C1001178402040A" + $"0A010A000A0B010B000A0C010C000A00010D1001178400040A0D010D000A0401" + $"04000A050105000A0601061001178520040A0801073024B39901178200040A07" + $"01073020B2E601178200040A0E010E1815FF0117812204" +}; + +resource(101, "RGB_WIN") rgb_color { 0xDE, 0xDB, 0xDE, 0xFF }; +resource(102, "RGB_PIE_OL") rgb_color { 0x80, 0x80, 0x80, 0xFF }; +resource(103, "RGB_PIE_BG") rgb_color { 0xFF, 0xFF, 0xFF, 0xFF }; +resource(104, "RGB_PIE_MT") rgb_color { 0xA0, 0xA0, 0xA0, 0xFF }; +resource(105, "RGB_PIE_1") rgb_color { 0x00, 0x60, 0x60, 0xFF }; +resource(106, "RGB_PIE_2") rgb_color { 0x00, 0x00, 0x68, 0xFF }; +resource(107, "RGB_PIE_3") rgb_color { 0x60, 0x00, 0x60, 0xFF }; +resource(108, "RGB_PIE_4") rgb_color { 0x68, 0x00, 0x00, 0xFF }; + +resource(101, "N_PIE_COLORS") (uint8)4; + +resource(103, "STR_N_FILES") "%d files"; +resource(102, "STR_1_FILE") "%d file"; +resource(101, "STR_VM_LABEL") "Volume:"; +resource(104, "STR_RESCAN") "Rescan"; +resource(105, "STR_SCN_X") "Scanning "; +resource(106, "STR_UNAVAIL") "file unavailable"; +resource(107, "STR_VM_DFLT") "(Select)"; +resource(108, "STR_VPROMPT") "Select a volume to scan"; +resource(109, "STR_M_INFO") "Get Info"; +resource(110, "STR_M_OPEN") "Open"; +resource(111, "STR_M_OPENW") "Open With"; +resource(112, "STR_M_NAPPS") "no supporting apps"; +resource(113, "STR_SIZE") "Size"; +resource(114, "STR_INFILES") " in %d files"; +resource(115, "STR_MADE") "Created"; +resource(116, "STR_MOD") "Modified"; +resource(117, "STR_TIMEFMT") "%a, %d %b %Y, %r"; +resource(118, "STR_KIND") "Kind"; +resource(119, "STR_PATH") "Path"; diff --git a/src/apps/diskusage/InfoWindow.cpp b/src/apps/diskusage/InfoWindow.cpp new file mode 100644 index 0000000000..70e64c3dea --- /dev/null +++ b/src/apps/diskusage/InfoWindow.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#include "InfoWindow.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include "Common.h" +#include "Snapshot.h" + + +LeftView::LeftView(BRect frame, BBitmap* icon) + : BView(frame, NULL, B_FOLLOW_NONE, B_WILL_DRAW), + fIcon(icon) +{ + SetViewColor(tint_color(kWindowColor, B_LIGHTEN_1_TINT)); +} + + +LeftView::~LeftView() +{ + delete fIcon; +} + + +void +LeftView::Draw(BRect updateRect) +{ + float right = Bounds().Width() - kSmallHMargin; + BRect iconRect(right - 31.0, kSmallVMargin, right, kSmallVMargin + 31.0); + if (updateRect.Intersects(iconRect)) { + SetDrawingMode(B_OP_OVER); + DrawBitmap(fIcon, iconRect); + } +} + + +InfoWin::InfoWin(BPoint p, FileInfo *f, BWindow* parent) + : BWindow(BRect(p, p), kEmptyStr, B_FLOATING_WINDOW_LOOK, + B_FLOATING_SUBSET_WINDOW_FEEL, + B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE) +{ + AddToSubset(parent); + + typedef pair Item; + typedef vector InfoList; + + char name[B_PATH_NAME_LENGTH]; + strcpy(name, f->ref.name); + strcat(name, " info"); + SetTitle(name); + + InfoList info; + Item item; + + // Size + size_to_string(f->size, name); + if (f->count > 0) { + // This is a directory. + char str[64]; + sprintf(str, kInfoInFiles, f->count); + strcat(name, str); + } + info.push_back(Item(kInfoSize, name)); + + // Created & modified dates + BEntry entry(&f->ref); + time_t t; + entry.GetCreationTime(&t); + strftime(name, 64, kInfoTimeFmt, localtime(&t)); + info.push_back(Item(kInfoCreated, name)); + entry.GetModificationTime(&t); + strftime(name, 64, kInfoTimeFmt, localtime(&t)); + info.push_back(Item(kInfoModified, name)); + + // Kind + BMimeType* type = f->Type(); + type->GetShortDescription(name); + info.push_back(Item(kInfoKind, name)); + delete type; + + // Path + string path; + f->GetPath(path); + info.push_back(Item(kInfoPath, path)); + + // Icon + BBitmap *icon = new BBitmap(BRect(0.0, 0.0, 31.0, 31.0), B_RGBA32); + entry_ref ref; + entry.GetRef(&ref); + BNodeInfo::GetTrackerIcon(&ref, icon, B_LARGE_ICON); + + // Compute the window size and add the views. + BFont smallFont(be_plain_font); + smallFont.SetSize(floorf(smallFont.Size() * 0.95)); + + struct font_height fh; + smallFont.GetHeight(&fh); + float fontHeight = fh.ascent + fh.descent + fh.leading; + + float leftWidth = 32.0; + float rightWidth = 0.0; + InfoList::iterator i = info.begin(); + while (i != info.end()) { + float w = smallFont.StringWidth((*i).first.c_str()) + 2.0 * kSmallHMargin; + leftWidth = max(leftWidth, w); + w = smallFont.StringWidth((*i).second.c_str()) + 2.0 * kSmallHMargin; + rightWidth = max(rightWidth, w); + i++; + } + + float winHeight = 32.0 + 4.0 * kSmallVMargin + 5.0 * (fontHeight + kSmallVMargin); + float winWidth = leftWidth + rightWidth; + ResizeTo(winWidth, winHeight); + + LeftView *leftView = new LeftView(BRect(0.0, 0.0, leftWidth, winHeight), icon); + BView *rightView = new BView( + BRect(leftWidth + 1.0, 0.0, winWidth, winHeight), NULL, + B_FOLLOW_NONE, B_WILL_DRAW); + + AddChild(leftView); + AddChild(rightView); + + BStringView *sv = new BStringView( + BRect(kSmallHMargin, kSmallVMargin, rightView->Bounds().Width(), kSmallVMargin + 30.0), + NULL, f->ref.name, B_FOLLOW_ALL); + + BFont largeFont(be_plain_font); + largeFont.SetSize(ceilf(largeFont.Size() * 1.1)); + sv->SetFont(&largeFont); + rightView->AddChild(sv); + + float y = 32.0 + 4.0 * kSmallVMargin; + i = info.begin(); + while (i != info.end()) { + sv = new BStringView( + BRect(kSmallHMargin, y, leftView->Bounds().Width(), y + fontHeight), + NULL, (*i).first.c_str()); + sv->SetFont(&smallFont); + sv->SetAlignment(B_ALIGN_RIGHT); + sv->SetHighColor(kBasePieColor[1]); // arbitrary + leftView->AddChild(sv); + + sv = new BStringView( + BRect(kSmallHMargin, y, rightView->Bounds().Width(), y + fontHeight), + NULL, (*i).second.c_str()); + sv->SetFont(&smallFont); + rightView->AddChild(sv); + + y += fontHeight + kSmallVMargin; + i++; + } + + Show(); +} + + +InfoWin::~InfoWin() +{ +} + + diff --git a/src/apps/diskusage/InfoWindow.h b/src/apps/diskusage/InfoWindow.h new file mode 100644 index 0000000000..b72902e35b --- /dev/null +++ b/src/apps/diskusage/InfoWindow.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef INFO_WINDOW_H +#define INFO_WINDOW_H + +#include + + +struct FileInfo; + +class LeftView: public BView { +public: + LeftView(BRect frame, BBitmap* icon); + virtual ~LeftView(); + + virtual void Draw(BRect updateRect); + +private: + BBitmap* fIcon; +}; + + +class InfoWin: public BWindow { +public: + InfoWin(BPoint location, FileInfo* info, + BWindow* parent); + virtual ~InfoWin(); +}; + +#endif // INFO_WINDOW_H + diff --git a/src/apps/diskusage/Jamfile b/src/apps/diskusage/Jamfile new file mode 100644 index 0000000000..11132a97df --- /dev/null +++ b/src/apps/diskusage/Jamfile @@ -0,0 +1,16 @@ +SubDir HAIKU_TOP src apps diskusage ; + +Application DiskUsage : + App.cpp + Common.cpp + ControlsView.cpp + InfoWindow.cpp + MainWindow.cpp + PieView.cpp + Scanner.cpp + Snapshot.cpp + StatusView.cpp + : be $(TARGET_LIBSTDC++) + : DiskUsage.rdef +; + diff --git a/src/apps/diskusage/MainWindow.cpp b/src/apps/diskusage/MainWindow.cpp new file mode 100644 index 0000000000..8a57e838fe --- /dev/null +++ b/src/apps/diskusage/MainWindow.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#include "MainWindow.h" + +#include +#include +#include +#include + +#include "Common.h" +#include "ControlsView.h" +#include "PieView.h" +#include "StatusView.h" + + +const float kMinWinSize = 275.0; + + +MainWindow::MainWindow(BRect pieRect) + : BWindow(pieRect, "DiskUsage", B_TITLED_WINDOW, + B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE) +{ + BRect r = Bounds(); + r.bottom = r.top + 1.0; + fControlsView = new ControlsView(r); + float cvHeight = fControlsView->Bounds().Height(); + AddChild(fControlsView); + + r = Bounds(); + r.top = r.bottom - 1.0; + fStatusView = new StatusView(r); + float svHeight = fStatusView->Bounds().Height(); + + float winHeight = pieRect.Height() + cvHeight + svHeight; + fStatusView->MoveTo(0.0, winHeight - svHeight); + ResizeTo(r.Width(), winHeight); + + AddChild(fStatusView); + + r = fControlsView->Frame(); + r.top = r.bottom + 1.0; + r.bottom = fStatusView->Frame().top - 1.0; + fPieView = new PieView(r, this); + AddChild(fPieView); + + Show(); + + // Note: The following code is semi-broken because BScreen::Frame() + // returns incorrect dimensions for the G200 in 1152x864 mode. I reported + // this bug, and Be said it's not a bug -- the Matrox driver actually uses + // a resolution of 1152x900 in that mode. Oh well. + Lock(); + float extraHeight = fControlsView->Bounds().Height() + fStatusView->Bounds().Height(); + float minHeight = kMinWinSize + extraHeight; + float maxHeight = BScreen(this).Frame().Height(); + float maxWidth = maxHeight - extraHeight; + Unlock(); + + SetSizeLimits(kMinWinSize, maxWidth, minHeight, maxHeight); +} + + +MainWindow::~MainWindow() +{ +} + + +void +MainWindow::MessageReceived(BMessage* message) +{ + switch (message->what) { + case kBtnRescan: + case kMenuSelectVol: + case B_REFS_RECEIVED: + fPieView->MessageReceived(message); + break; + + case kBtnHelp: + be_roster->Launch(&kHelpFileRef); + break; + + default: + BWindow::MessageReceived(message); + break; + } +} + + +void +MainWindow::Zoom(BPoint origin, float width, float height) +{ + width = Frame().Width(); + height = Frame().Height(); + if (_FixAspectRatio(&width, &height)) + SetZoomLimits(width, height); + + BWindow::Zoom(Frame().LeftTop(), width, height); +} + + +void +MainWindow::FrameResized(float width, float height) +{ + if (_FixAspectRatio(&width, &height)) + ResizeTo(width, height); +} + + +bool +MainWindow::QuitRequested() +{ + be_app->PostMessage(B_QUIT_REQUESTED); + return true; +} + + +// #pragma mark - + + +void +MainWindow::ShowInfo(const FileInfo* info) +{ + fStatusView->Show(info); +} + + +void +MainWindow::SetRescanEnabled(bool enabled) +{ + fControlsView->SetRescanEnabled(enabled); +} + + +BVolume* +MainWindow::FindDeviceFor(dev_t device, bool invoke) +{ + return fControlsView->FindDeviceFor(device, invoke); +} + + +// #pragma mark - + + +bool +MainWindow::_FixAspectRatio(float* width, float* height) +{ + float ctrlViewHeight = fControlsView->Bounds().Height(); + float statusViewHeight = fStatusView->Bounds().Height(); + + float newPieSize = *height - ctrlViewHeight - statusViewHeight; + if (*width < newPieSize) + newPieSize = *width; + + float newWidth = newPieSize; + float newHeight = newPieSize + ctrlViewHeight + statusViewHeight; + if (*width != newWidth || *height != newHeight) { + *width = newWidth; + *height = newHeight; + return true; + } + + return false; +} + + diff --git a/src/apps/diskusage/MainWindow.h b/src/apps/diskusage/MainWindow.h new file mode 100644 index 0000000000..0333cddb38 --- /dev/null +++ b/src/apps/diskusage/MainWindow.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef MAIN_WINDOW_H +#define MAIN_WINDOW_H + +#include + + +class BVolume; +class ControlsView; +struct FileInfo; +class PieView; +class StatusView; + +class MainWindow: public BWindow { +public: + MainWindow(BRect pieRect); + virtual ~MainWindow(); + + virtual void MessageReceived(BMessage* message); + virtual void Zoom(BPoint origin, float width, float height); + virtual void FrameResized(float width, float height); + virtual bool QuitRequested(); + + void ShowInfo(const FileInfo* info); + void SetRescanEnabled(bool enabled); + BVolume* FindDeviceFor(dev_t device, + bool invoke = false); + +private: + bool _FixAspectRatio(float* width, float* height); + + ControlsView* fControlsView; + PieView* fPieView; + StatusView* fStatusView; +}; + +#endif // MAIN_WINDOW_H diff --git a/src/apps/diskusage/PieView.cpp b/src/apps/diskusage/PieView.cpp new file mode 100644 index 0000000000..c6c06afd20 --- /dev/null +++ b/src/apps/diskusage/PieView.cpp @@ -0,0 +1,817 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#include "PieView.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Common.h" +#include "InfoWindow.h" +#include "MainWindow.h" +#include "Scanner.h" + + +static const int32 kIdxGetInfo = 0; +static const int32 kIdxOpen = 1; +static const int32 kIdxOpenWith = 2; +static const int32 kIdxRescan = 3; + +// TODO: It would be nice to make a common base class for AppMenuItem and +// VolMenuItem (menu items that include an icon). + +class AppMenuItem: public BMenuItem { +public: + AppMenuItem(const char* appSig, int category); + virtual ~AppMenuItem(); + + virtual void GetContentSize(float* _width, float* _height); + virtual void DrawContent(); + + int Category() const + { return fCategory; } + const entry_ref* AppRef() const + { return &fAppRef; } + bool IsValid() const + { return fIsValid; } + +private: + int fCategory; + BBitmap* fIcon; + entry_ref fAppRef; + bool fIsValid; +}; + + +AppMenuItem::AppMenuItem(const char* appSig, int category): + BMenuItem(kEmptyStr, NULL), fCategory(category), fIcon(NULL), fIsValid(false) +{ + if (be_roster->FindApp(appSig, &fAppRef) == B_NO_ERROR) { + fIcon = new BBitmap(BRect(0.0, 0.0, 15.0, 15.0), B_RGBA32); + if (BNodeInfo::GetTrackerIcon(&fAppRef, fIcon, B_MINI_ICON) == B_OK) { + BEntry appEntry(&fAppRef); + if (appEntry.InitCheck() == B_OK) { + char name[B_FILE_NAME_LENGTH]; + appEntry.GetName(name); + SetLabel(name); + fIsValid = true; + } + } + } +} + + +AppMenuItem::~AppMenuItem() +{ + delete fIcon; +} + + +void +AppMenuItem::GetContentSize(float* _width, float* _height) +{ + if (_width) + *_width = fIcon->Bounds().Width() + be_plain_font->StringWidth(Label()); + + if (_height) { + struct font_height fh; + be_plain_font->GetHeight(&fh); + float fontHeight = ceilf(fh.ascent) + ceilf(fh.descent) + + ceilf(fh.leading); + *_height = max(fontHeight, fIcon->Bounds().Height()); + } +} + + +void +AppMenuItem::DrawContent() +{ + Menu()->SetDrawingMode(B_OP_OVER); + Menu()->MovePenBy(0.0, -1.0); + Menu()->DrawBitmap(fIcon); + Menu()->MovePenBy(fIcon->Bounds().Width() + kSmallHMargin, 0.0); + BMenuItem::DrawContent(); +} + + +// #pragma mark - PieView + + +PieView::PieView(BRect frame, MainWindow* window) + : BView(frame, NULL, B_FOLLOW_ALL, + B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_SUBPIXEL_PRECISE), + fWindow(window), + fScanners(), + fCurrentVolume(NULL), + fMouseOverInfo(), + fClicked(false), + fDragging(false) +{ + SetViewColor(B_TRANSPARENT_COLOR); + SetLowColor(kWindowColor); + + fMouseOverMenu = new BPopUpMenu(kEmptyStr, false, false); + fMouseOverMenu->AddItem(new BMenuItem(kMenuGetInfo, NULL), kIdxGetInfo); + fMouseOverMenu->AddItem(new BMenuItem(kMenuOpen, NULL), kIdxOpen); + + fFileUnavailableMenu = new BPopUpMenu(kEmptyStr, false, false); + BMenuItem* item = new BMenuItem(kStrUnavail, NULL); + item->SetEnabled(false); + fFileUnavailableMenu->AddItem(item); + + BFont font; + GetFont(&font); + font.SetSize(ceilf(font.Size() * 1.33)); + font.SetFace(B_BOLD_FACE); + SetFont(&font); + + struct font_height fh; + font.GetHeight(&fh); + fFontHeight = ceilf(fh.ascent) + ceilf(fh.descent) + ceilf(fh.leading); +} + + +PieView::~PieView() +{ + delete fMouseOverMenu; + delete fFileUnavailableMenu; + + while (fScanners.size() != 0) { + ScannerMap::iterator i = fScanners.begin(); + (*i).second->RequestQuit(); + fScanners.erase(i); + } +} + + +void +PieView::MessageReceived(BMessage* message) +{ + switch (message->what) { + case B_SIMPLE_DATA: + case B_REFS_RECEIVED: + { + entry_ref ref; + for (int i = 0; message->FindRef("refs", i, &ref) == B_OK; i++) { + BEntry entry(&ref, true); + _HandleArg(&entry); + } + break; + } + + case kBtnRescan: + if (fCurrentVolume != NULL) { + fScanners[fCurrentVolume]->Refresh(); + Invalidate(); + } + break; + + case kMenuSelectVol: + { + BVolume* volume; + if (message->FindPointer(kNameVolPtr, (void**)&volume) == B_OK) + _ShowVolume(volume); + break; + } + + case kScanProgress: + case kScanDone: + { + BVolume* volume; + if (message->FindPointer(kNameVolPtr, (void**)&volume) == B_OK + && volume == fCurrentVolume) { + Invalidate(); + } + break; + } + + default: + BView::MessageReceived(message); + break; + } +} + + +void +PieView::MouseDown(BPoint where) +{ + uint32 buttons; + BMessage* current = Window()->CurrentMessage(); + if (current->FindInt32("buttons", (int32*)&buttons) != B_OK) + buttons = B_PRIMARY_MOUSE_BUTTON; + + FileInfo* info = _FileAt(where); + if (info == NULL || info->pseudo) + return; + + if (buttons & B_PRIMARY_MOUSE_BUTTON) { + fClicked = true; + fDragStart = where; + fClickedFile = info; + SetMouseEventMask(B_POINTER_EVENTS); + } else if (buttons & B_SECONDARY_MOUSE_BUTTON) { + where = ConvertToScreen(where); + _ShowContextMenu(info, where); + } +} + + +void +PieView::MouseUp(BPoint where) +{ + // If the primary button was released and there was no dragging happening, + // just zoom in or out. + if (fClicked && !fDragging) { + Scanner* scanner = fScanners[fCurrentVolume]; + FileInfo* info = _FileAt(where); + if (info != NULL) { + if (info == scanner->CurrentDir()) { + scanner->ChangeDir(info->parent); + Invalidate(); + } else if (info->children.size() > 0) { + scanner->ChangeDir(info); + Invalidate(); + } + } + } + + fClicked = false; + fDragging = false; +} + + +void +PieView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) +{ + if (fClicked) { + // Primary mouse button is down. + if (fDragging) + return; + // If the mouse has moved far enough, initiate dragging. + BPoint diff = where - fDragStart; + float distance = sqrtf(diff.x * diff.x + diff.y * diff.x); + if (distance > kDragThreshold) { + fDragging = true; + + BBitmap* icon = new BBitmap(BRect(0.0, 0.0, 31.0, 31.0), B_RGBA32); + if (BNodeInfo::GetTrackerIcon(&fClickedFile->ref, icon, + B_LARGE_ICON) == B_OK) { + BMessage msg(B_SIMPLE_DATA); + msg.AddRef("refs", &fClickedFile->ref); + DragMessage(&msg, icon, B_OP_BLEND, BPoint(15.0, 15.0)); + } else + delete icon; + } + } else { + // Mouse button is not down, display file info. + if (transit == B_EXITED_VIEW) { + // Clear status view + fWindow->ShowInfo(NULL); + } else { + // Display file information. + fWindow->ShowInfo(_FileAt(where)); + } + } +} + + +void +PieView::Draw(BRect updateRect) +{ + if (fScanners.find(fCurrentVolume) != fScanners.end()) { + // There is a current volume. + Scanner* scanner = fScanners[fCurrentVolume]; + if (scanner->IsBusy()) { + // Show progress of scanning. + _DrawProgressBar(updateRect); + fWindow->SetRescanEnabled(false); + } else { + _DrawPieChart(updateRect); + fWindow->SetRescanEnabled(true); + } + } else { + // No volume has been selected, so display a prompt for one. + FillRect(updateRect, B_SOLID_LOW); + BRect b = Bounds(); + float strWidth = StringWidth(kVolPrompt); + float bx = (b.left + b.Width() - strWidth) / 2.0; + float by = (b.top + b.Height()) / 2.0; + SetHighColor(0x00, 0x00, 0x00); + DrawString(kVolPrompt, BPoint(bx, by)); + fWindow->SetRescanEnabled(false); + } +} + + +// #pragma mark - private + + +void +PieView::_HandleArg(const BEntry* entry) +{ + // Going through these seemingly pointless gyrations (instead of getting + // the dev_t directly from the BEntry) allows us to process volumes + // properly. It looks like a dropped volume refers to dev_t 1 (why?), but + // getting the dev_t from the path allows us to get at the dev_t where the + // root directory actually lives. + BPath path; + entry->GetPath(&path); + dev_t device = dev_for_path(path.Path()); + + // Set the desired path in the scanner. + Scanner* scanner = NULL; + ScannerMap::iterator i = fScanners.begin(); + while (i != fScanners.end()) { + if ((*i).second->Device() == device) { + scanner = (*i).second; + break; + } + i++; + } + + if (scanner == NULL) { + BVolume* volume = fWindow->FindDeviceFor(device); + if (volume != NULL) + scanner = fScanners[volume] = new Scanner(volume, this); + } + if (scanner != NULL) { + string desiredPath(path.Path()); + scanner->SetDesiredPath(desiredPath); + + // Select the volume on the menu. + fWindow->FindDeviceFor(device, true); + } +} + + +void +PieView::_ShowVolume(BVolume* volume) +{ + if (volume != NULL) { + if (fScanners.find(volume) == fScanners.end()) + fScanners[volume] = new Scanner(volume, this); + + Scanner* scanner = fScanners[volume]; + if (scanner->Snapshot() == NULL) + scanner->Refresh(); + } + + fCurrentVolume = volume; + Invalidate(); +} + + +void +PieView::_DrawProgressBar(BRect updateRect) +{ + // Show the progress of the scanning operation. + + fMouseOverInfo.clear(); + + FillRect(updateRect, B_SOLID_LOW); + + // Draw the progress bar. + BRect b = Bounds(); + float bx = floorf((b.left + b.Width() - kProgBarWidth) / 2.0); + float by = floorf((b.top + b.Height() - kProgBarHeight) / 2.0); + float ex = bx + kProgBarWidth; + float ey = by + kProgBarHeight; + SetPenSize(1.0); + SetHighColor(tint_color(kWindowColor, B_LIGHTEN_2_TINT)); + StrokeLine(BPoint(bx, ey), BPoint(ex, ey)); + StrokeLine(BPoint(ex, by), BPoint(ex, ey)); + SetHighColor(tint_color(kWindowColor, B_DARKEN_2_TINT)); + StrokeLine(BPoint(bx, by), BPoint(ex, by)); + StrokeLine(BPoint(bx, by), BPoint(bx, ey)); + + bx += 1.0; by += 1.0; + ex -= 1.0; ey -= 1.0; + float mx = bx + floorf((kProgBarWidth - 2.0) + * fScanners[fCurrentVolume]->Progress() / 100.0 + 0.5); + SetHighColor(tint_color(kWindowColor, B_DARKEN_1_TINT)); + FillRect(BRect(mx, by, ex, ey)); + + SetHighColor(0x00, 0x20, 0x90); + StrokeLine(BPoint(bx, ey), BPoint(mx, ey)); + StrokeLine(BPoint(mx, by), BPoint(mx, ey)); + SetHighColor(0x00, 0xc0, 0xff); + StrokeLine(BPoint(bx, by), BPoint(mx, by)); + StrokeLine(BPoint(bx, by), BPoint(bx, ey)); + + bx += 1.0; by += 1.0; + mx -= 1.0; ey -= 1.0; + SetHighColor(0x00, 0x80, 0xf0); + FillRect(BRect(bx, by, mx, ey)); + + // Tell what we are doing. + const char* task = fScanners[fCurrentVolume]->Task(); + float strWidth = StringWidth(task); + bx = (b.left + b.Width() - strWidth) / 2.0; + by -= fFontHeight + 2.0 * kSmallVMargin; + SetHighColor(0, 0, 0); + DrawString(task, BPoint(bx, by)); +} + + +void +PieView::_DrawPieChart(BRect updateRect) +{ + BRect pieRect = Bounds(); + pieRect.InsetBy(kPieOuterMargin, kPieOuterMargin); + if (!updateRect.Intersects(pieRect)) + return; + + SetHighColor(kPieBGColor); + FillRect(updateRect); + + int colorIdx = 0; + FileInfo* currentDir = fScanners[fCurrentVolume]->CurrentDir(); + FileInfo* parent = currentDir; + while (parent != NULL) { + parent = parent->parent; + colorIdx++; + } + _DrawDirectory(currentDir, 0.0, 0.0, colorIdx % kBasePieColorCount, 0); + + // This is just for the case when the mouse hovers over the view + // while the scanning process is running and then does not move + // until after the results are shown. In that case the info will + // not automatically update. (TODO: fix this and put into MessageReceived() + // kScanDone - currently, fileAt() returns NULL (mouseoverinfo...)) + BPoint where; + uint32 ignore; + GetMouse(&where, &ignore, false); + fWindow->ShowInfo(_FileAt(where)); +} + + +float +PieView::_DrawDirectory(FileInfo* info, float parentSpan, float beginAngle, + int colorIdx, int level) +{ + BRect b = Bounds(); + if (b.Width() < 2.0 * (kPieCenterSize + level * kPieRingSize + + kPieOuterMargin + kPieInnerMargin)) { + return 0.0; + } + + VolumeSnapshot* snapshot = fScanners[fCurrentVolume]->Snapshot(); + + float cx = floorf(b.left + b.Width() / 2.0 + 0.5); + float cy = floorf(b.top + b.Height() / 2.0 + 0.5); + + float mySpan; + + if (level == 0) { + // Make room for mouse over info. + fMouseOverInfo.clear(); + fMouseOverInfo[0] = SegmentList(); + + // Draw the center circle. + const char* displayName; + if (info == NULL) { + // NULL represents the entire volume. Show used and free space in + // the center circle, with the used segment representing the + // volume's root directory. + off_t volCapacity = snapshot->capacity; + mySpan = 360.0 * (volCapacity - snapshot->freeBytes) / volCapacity; + + SetHighColor(kEmptySpcColor); + FillEllipse(BPoint(cx, cy), kPieCenterSize, kPieCenterSize); + + SetHighColor(kBasePieColor[0]); + FillArc(BPoint(cx, cy), kPieCenterSize, kPieCenterSize, 0.0, mySpan); + + // Show total volume capacity. + char label[B_PATH_NAME_LENGTH]; + size_to_string(volCapacity, label); + SetHighColor(kPieBGColor); + SetDrawingMode(B_OP_OVER); + DrawString(label, BPoint(cx - StringWidth(label) / 2.0, + cy + fFontHeight + kSmallVMargin)); + SetDrawingMode(B_OP_COPY); + + displayName = snapshot->name.c_str(); + + // Record in-use space and free space for use during MouseMoved(). + info = snapshot->rootDir; + fMouseOverInfo[0].push_back(Segment(0.0, mySpan, info)); + if (mySpan < 360.0 - kMinSegmentSpan) { + fMouseOverInfo[0].push_back(Segment(mySpan, 360.0, + snapshot->freeSpace)); + } + } else { + // Show a normal directory. + SetHighColor(kBasePieColor[colorIdx]); + FillEllipse(BRect(cx - kPieCenterSize, cy - kPieCenterSize, + cx + kPieCenterSize + 0.5, cy + kPieCenterSize + 0.5)); + displayName = info->ref.name; + mySpan = 360.0; + + // Record the segment for use during MouseMoved(). + fMouseOverInfo[0].push_back(Segment(0.0, mySpan, info)); + } + + SetPenSize(1.0); + SetHighColor(kOutlineColor); + StrokeEllipse(BPoint(cx, cy), kPieCenterSize + 0.5, kPieCenterSize + 0.5); + + // Show the name of the volume or directory. + BString label(displayName); + BFont font; + GetFont(&font); + font.TruncateString(&label, B_TRUNCATE_END, + 2.0 * (kPieCenterSize - kSmallHMargin)); + float labelWidth = font.StringWidth(label.String()); + + SetHighColor(kPieBGColor); + SetDrawingMode(B_OP_OVER); + DrawString(label.String(), BPoint(cx - labelWidth / 2.0, cy)); + SetDrawingMode(B_OP_COPY); + beginAngle = 0.0; + } else { + // Draw an exterior segment. + float parentSize; + if (info->parent == NULL) + parentSize = (float)snapshot->capacity; + else + parentSize = (float)info->parent->size; + + mySpan = parentSpan * (float)info->size / parentSize; + if (mySpan >= kMinSegmentSpan) { + rgb_color color = kBasePieColor[colorIdx]; + color.red += kLightenFactor * level; + color.green += kLightenFactor * level; + color.blue += kLightenFactor * level; + SetHighColor(color); + SetPenSize(kPieRingSize); + float radius = kPieCenterSize + level * kPieRingSize + - kPieRingSize / 2.0; + StrokeArc(BPoint(cx, cy), radius, radius, beginAngle, mySpan); + + SetHighColor(kOutlineColor); + SetPenSize(0.0); + float segBeginRadius = kPieCenterSize + (level - 1) + * kPieRingSize + 0.5; + float segLength = kPieRingSize - 0.5; + float rad = deg2rad(beginAngle); + float bx = cx + segBeginRadius * cos(rad); + float by = cy - segBeginRadius * sin(rad); + float ex = bx + segLength * cos(rad); + float ey = by - segLength * sin(rad); + StrokeLine(BPoint(bx, by), BPoint(ex, ey)); + + rad = deg2rad(beginAngle + mySpan); + bx = cx + segBeginRadius * cos(rad); + by = cy - segBeginRadius * sin(rad); + ex = bx + segLength * cos(rad); + ey = by - segLength * sin(rad); + StrokeLine(BPoint(bx, by), BPoint(ex, ey)); + + SetPenSize(0.0); + SetHighColor(kOutlineColor); + radius += kPieRingSize / 2.0; + StrokeArc(BPoint(cx, cy), radius, radius, beginAngle, mySpan); + + // Record the segment for use during MouseMoved(). + if (fMouseOverInfo.find(level) == fMouseOverInfo.end()) + fMouseOverInfo[level] = SegmentList(); + + fMouseOverInfo[level].push_back( + Segment(beginAngle, beginAngle + mySpan, info)); + } + } + + // Draw children. + vector::iterator i = info->children.begin(); + while (i != info->children.end()) { + float childSpan + = _DrawDirectory(*i, mySpan, beginAngle, colorIdx, level + 1); + if (childSpan >= kMinSegmentSpan) { + beginAngle += childSpan; + colorIdx = (colorIdx + 1) % kBasePieColorCount; + } + i++; + } + + return mySpan; +} + + +FileInfo* +PieView::_FileAt(const BPoint& where) +{ + BRect b = Bounds(); + float cx = b.left + b.Width() / 2.0; + float cy = b.top + b.Height() / 2.0; + float dx = where.x - cx; + float dy = where.y - cy; + float dist = sqrt(dx*dx + dy*dy); + + int level; + if (dist < kPieCenterSize) + level = 0; + else + level = 1 + (int)((dist - kPieCenterSize) / kPieRingSize); + + float angle = rad2deg(atan(dy / dx)); + angle = ((dx < 0.0) ? 180.0 : (dy < 0.0) ? 0.0 : 360.0) - angle; + + if (fMouseOverInfo.find(level) == fMouseOverInfo.end()) { + // No files in this level (ring) of the pie. + return NULL; + } + + SegmentList s = fMouseOverInfo[level]; + SegmentList::iterator i = s.begin(); + while (i != s.end() && (angle < (*i).begin || (*i).end < angle)) + i++; + if (i == s.end()) { + // Nothing at this angle. + return NULL; + } + + return (*i).info; +} + + +void +PieView::_AddAppToList(vector& list, const char* appSig, + int category) +{ + // skip self. + if (strcmp(appSig, kAppSignature) == 0) + return; + + AppMenuItem* item = new AppMenuItem(appSig, category); + if (item->IsValid()) { + vector::iterator i = list.begin(); + while (i != list.end()) { + if (*item->AppRef() == *(*i)->AppRef()) { + // Skip duplicates. + delete item; + return; + } + i++; + } + list.push_back(item); + } else { + // Skip items that weren't constructed successfully. + delete item; + } +} + + +BMenu* +PieView::_BuildOpenWithMenu(FileInfo* info) +{ + vector appList; + + // Get preferred app. + BMimeType* type = info->Type(); + char appSignature[B_MIME_TYPE_LENGTH]; + if (type->GetPreferredApp(appSignature) == B_OK) + _AddAppToList(appList, appSignature, 1); + + // Get apps that handle this subtype and supertype. + BMessage msg; + if (type->GetSupportingApps(&msg) == B_OK) { + int32 subs, supers, i; + msg.FindInt32("be:sub", &subs); + msg.FindInt32("be:super", &supers); + + const char* appSig; + for (i = 0; i < subs; i++) { + msg.FindString("applications", i, &appSig); + _AddAppToList(appList, appSig, 2); + } + int hold = i; + for (i = 0; i < supers; i++) { + msg.FindString("applications", i + hold, &appSig); + _AddAppToList(appList, appSig, 3); + } + } + + // Get apps that handle any type. + if (BMimeType::GetWildcardApps(&msg) == B_OK) { + const char* appSig; + for (int32 i = 0; true; i++) { + if (msg.FindString("applications", i, &appSig) == B_OK) + _AddAppToList(appList, appSig, 4); + else + break; + } + } + + delete type; + + BMenu* openWith = new BMenu(kMenuOpenWith); + + if (appList.size() == 0) { + BMenuItem* item = new BMenuItem(kMenuNoApps, NULL); + item->SetEnabled(false); + openWith->AddItem(item); + } else { + vector::iterator i = appList.begin(); + int category = (*i)->Category(); + while (i != appList.end()) { + if (category != (*i)->Category()) { + openWith->AddSeparatorItem(); + category = (*i)->Category(); + } + openWith->AddItem(*i); + i++; + } + } + + return openWith; +} + + +void +PieView::_ShowContextMenu(FileInfo* info, BPoint p) +{ + BRect openRect(p.x - 5.0, p.y - 5.0, p.x + 5.0, p.y + 5.0); + + // Display the open-with menu only if the file is still available. + BNode node(&info->ref); + if (node.InitCheck() == B_OK) { + // Add "Open With" submenu. + BMenu* openWith = _BuildOpenWithMenu(info); + fMouseOverMenu->AddItem(openWith, kIdxOpenWith); + + // Add a "Rescan" option for folders. + BMenuItem* rescan = NULL; + if (info->children.size() > 0) { + rescan = new BMenuItem(kStrRescan, NULL); + fMouseOverMenu->AddItem(rescan, kIdxRescan); + } + + BMenuItem* item = fMouseOverMenu->Go(p, false, true, openRect); + if (item != NULL) { + switch (fMouseOverMenu->IndexOf(item)) { + case kIdxGetInfo: + new InfoWin(p, info, Window()); + break; + case kIdxOpen: + _Launch(info); + break; + case kIdxRescan: + fScanners[fCurrentVolume]->Refresh(info); + Invalidate(); + break; + default: // must be "Open With" submenu + _Launch(info, ((AppMenuItem*)item)->AppRef()); + break; + } + } + + if (rescan != NULL) { + fMouseOverMenu->RemoveItem(rescan); + delete rescan; + } + + fMouseOverMenu->RemoveItem(openWith); + delete openWith; + } + else { + // The file is no longer available. + fFileUnavailableMenu->Go(p, false, true, openRect); + } +} + + +void +PieView::_Launch(FileInfo* info, const entry_ref* appRef) +{ + BMessage msg(B_REFS_RECEIVED); + msg.AddRef("refs", &info->ref); + + if (appRef == NULL) { + // Let the registrar pick an app based on the file's MIME type. + BMimeType* type = info->Type(); + be_roster->Launch(type->Type(), &msg); + delete type; + } else { + // Launch a designated app to handle this file. + be_roster->Launch(appRef, &msg); + } +} + + diff --git a/src/apps/diskusage/PieView.h b/src/apps/diskusage/PieView.h new file mode 100644 index 0000000000..92f5afc9a2 --- /dev/null +++ b/src/apps/diskusage/PieView.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef PIE_VIEW_H +#define PIE_VIEW_H + +#include + +#include +#include + + +class AppMenuItem; +class BEntry; +class BMenu; +class BPopUpMenu; +class BVolume; +struct entry_ref; +struct FileInfo; +class Scanner; +class MainWindow; + +class PieView: public BView { +public: + PieView(BRect frame, MainWindow* window); + virtual ~PieView(); + + virtual void MessageReceived(BMessage* message); + virtual void MouseDown(BPoint where); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint where, uint32 transit, + const BMessage* dragMessage); + virtual void Draw(BRect updateRect); + +private: + void _HandleArg(const BEntry* entry); + void _ShowVolume(BVolume* volume); + void _DrawProgressBar(BRect updateRect); + void _DrawPieChart(BRect updateRect); + float _DrawDirectory(FileInfo* info, + float parentSpan, float beginAngle, + int colorIdx, int level); + FileInfo* _FileAt(const BPoint& where); + void _AddAppToList(vector& list, + const char* appSignature, int category); + BMenu* _BuildOpenWithMenu(FileInfo* info); + void _ShowContextMenu(FileInfo* info, BPoint where); + void _Launch(FileInfo* info, + const entry_ref* ref = NULL); + +private: + struct Segment { + Segment() + : begin(0.0), end(0.0), info(NULL) { } + Segment(float b, float e, FileInfo* i) + : begin(b), end(e), info(i) { } + Segment(const Segment &s) + : begin(s.begin), end(s.end), info(s.info) { } + ~Segment() { } + + float begin; + float end; + FileInfo* info; + }; + typedef vector SegmentList; + typedef map MouseOverInfo; + + typedef map ScannerMap; + +private: + MainWindow* fWindow; + ScannerMap fScanners; + BVolume* fCurrentVolume; + FileInfo* fCurrentDir; + MouseOverInfo fMouseOverInfo; + BPopUpMenu* fMouseOverMenu; + BPopUpMenu* fFileUnavailableMenu; + float fFontHeight; + bool fClicked; + bool fDragging; + BPoint fDragStart; + FileInfo* fClickedFile; +}; + +#endif // PIE_VIEW_H + diff --git a/src/apps/diskusage/Scanner.cpp b/src/apps/diskusage/Scanner.cpp new file mode 100644 index 0000000000..01dd03c08b --- /dev/null +++ b/src/apps/diskusage/Scanner.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#include "Scanner.h" + +#include + +#include "Common.h" + + +Scanner::Scanner(BVolume *v, BHandler *handler) + : BLooper(), + fListener(handler), + fDoneMessage(kScanDone), + fProgressMessage(kScanProgress), + + fVolume(v), + fSnapshot(NULL), + fDesiredPath(), + fTask(), + fBusy(false), + fQuitRequested(false) +{ + fDoneMessage.AddPointer(kNameVolPtr, fVolume); + fProgressMessage.AddPointer(kNameVolPtr, fVolume); + + Run(); +} + + +Scanner::~Scanner() +{ + delete fSnapshot; +} + + +void +Scanner::MessageReceived(BMessage* message) +{ + switch (message->what) { + case kScanRefresh: + { + FileInfo* startInfo; + if (message->FindPointer(kNameFilePtr, (void **)&startInfo) == B_OK) + _RunScan(startInfo); + break; + } + + default: + BLooper::MessageReceived(message); + break; + } +} + + +void +Scanner::Refresh(FileInfo* startInfo) +{ + if (fBusy) + return; + + fBusy = true; + + // Remember the current directory, if any, so we can return to it after + // the scanning is done. + if (fSnapshot != NULL && fSnapshot->currentDir != NULL) + fSnapshot->currentDir->GetPath(fDesiredPath); + + fTask.assign(kEmptyStr); + + BMessage msg(kScanRefresh); + msg.AddPointer(kNameFilePtr, startInfo); + PostMessage(&msg); +} + + +void +Scanner::SetDesiredPath(string &path) +{ + fDesiredPath = path; + + if (fSnapshot == NULL) + Refresh(); + else + _ChangeToDesired(); +} + + +void +Scanner::RequestQuit() +{ + // If the looper thread is scanning, we won't succeed to grab the lock, + // since the thread is scanning from within MessageReceived(). Then by + // setting fQuitRequested, the thread will stop scanning and only after + // that happened we succeed to grab the lock. So this line of action + // should be safe. + fQuitRequested = true; + Lock(); + Quit(); +} + + +// #pragma mark - private + + +void +Scanner::_RunScan(FileInfo* startInfo) +{ + if (startInfo == NULL || startInfo == fSnapshot->rootDir) { + delete fSnapshot; + fSnapshot = new VolumeSnapshot(fVolume); + fTask = kStrScanningX + fSnapshot->name; + fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes; + fVolumeBytesScanned = 0; + fProgress = 0.0; + fLastReport = -100.0; + + BDirectory root; + fVolume->GetRootDirectory(&root); + fSnapshot->rootDir = _GetFileInfo(&root, NULL); + + FileInfo* freeSpace = new FileInfo; + freeSpace->pseudo = true; + string s = "Free on " + fSnapshot->name; + freeSpace->ref.set_name(s.c_str()); + freeSpace->size = fSnapshot->freeBytes; + fSnapshot->freeSpace = freeSpace; + + fSnapshot->currentDir = NULL; + + } else { + fSnapshot->capacity = fVolume->Capacity(); + fSnapshot->freeBytes = fVolume->FreeBytes(); + fTask = kStrScanningX + string(startInfo->ref.name); + fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes; + fVolumeBytesScanned = fVolumeBytesInUse - startInfo->size; // best guess + fProgress = 100.0 * fVolumeBytesScanned / fVolumeBytesInUse; + fLastReport = -100.0; + + BDirectory startDir(&startInfo->ref); + if (startDir.InitCheck() == B_OK) { + FileInfo *parent = startInfo->parent; + vector::iterator i = parent->children.begin(); + FileInfo* newInfo = _GetFileInfo(&startDir, parent); + while (i != parent->children.end() && *i != startInfo) + i++; + + int idx = i - parent->children.begin(); + parent->children[idx] = newInfo; + + // Fixup count and size fields in parent directory. + parent->size += newInfo->size - startInfo->size; + parent->count += newInfo->count - startInfo->count; + + delete startInfo; + } + } + + fBusy = false; + _ChangeToDesired(); + fListener.SendMessage(&fDoneMessage); +} + + +FileInfo* +Scanner::_GetFileInfo(BDirectory* dir, FileInfo* parent) +{ + FileInfo* thisDir = new FileInfo; + BEntry entry; + dir->GetEntry(&entry); + entry.GetRef(&thisDir->ref); + thisDir->parent = parent; + thisDir->count = 0; + + while (true) { + if (fQuitRequested) + break; + + if (dir->GetNextEntry(&entry) == B_ENTRY_NOT_FOUND) + break; + if (entry.IsSymLink()) + continue; + + if (entry.IsFile()) { + FileInfo *child = new FileInfo; + entry.GetRef(&child->ref); + entry.GetSize(&child->size); + child->parent = thisDir; + thisDir->children.push_back(child); + + // Send a progress report periodically. + fVolumeBytesScanned += child->size; + fProgress = 100.0 * fVolumeBytesScanned / fVolumeBytesInUse; + if (fProgress - fLastReport > kReportInterval) { + fLastReport = fProgress; + fListener.SendMessage(&fProgressMessage); + } + } + else if (entry.IsDirectory()) { + BDirectory childDir(&entry); + thisDir->children.push_back(_GetFileInfo(&childDir, thisDir)); + } + thisDir->count++; + } + + vector::iterator i = thisDir->children.begin(); + while (i != thisDir->children.end()) { + thisDir->size += (*i)->size; + thisDir->count += (*i)->count; + i++; + } + + return thisDir; +} + + +void +Scanner::_ChangeToDesired() +{ + if (fBusy || fDesiredPath.size() <= 0) + return; + + char* workPath = strdup(fDesiredPath.c_str()); + char* pathPtr = &workPath[1]; // skip leading '/' + char* endOfPath = pathPtr + strlen(pathPtr); + + // Check the name of the root directory. It is a special case because + // it has no parent data structure. + FileInfo* checkDir = fSnapshot->rootDir; + FileInfo* prefDir = NULL; + char* sep = strchr(pathPtr, '/'); + if (sep != NULL) + *sep = '\0'; + + if (strcmp(pathPtr, checkDir->ref.name) == 0) { + // Root directory matches, so follow the remainder of the path as + // far as possible. + prefDir = checkDir; + pathPtr += 1 + strlen(pathPtr); + while (pathPtr < endOfPath) { + sep = strchr(pathPtr, '/'); + if (sep != NULL) + *sep = '\0'; + + checkDir = prefDir->FindChild(pathPtr); + if (checkDir == NULL || checkDir->children.size() == 0) + break; + + pathPtr += 1 + strlen(pathPtr); + prefDir = checkDir; + } + } + + // If the desired path is the volume's root directory, default to the + // volume display. + if (prefDir == fSnapshot->rootDir) + prefDir = NULL; + + ChangeDir(prefDir); + + free(workPath); + fDesiredPath.erase(); +} + + diff --git a/src/apps/diskusage/Scanner.h b/src/apps/diskusage/Scanner.h new file mode 100644 index 0000000000..0b0a64f9c6 --- /dev/null +++ b/src/apps/diskusage/Scanner.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef SCANNER_H +#define SCANNER_H + +#include + +#include +#include +#include +#include + +#include "Snapshot.h" + + +class BDirectory; + +class Scanner: public BLooper { +public: + Scanner(BVolume* volume, BHandler* handler); + virtual ~Scanner(); + + virtual void MessageReceived(BMessage* message); + + VolumeSnapshot* Snapshot() const + { return fBusy ? NULL : fSnapshot; } + void Refresh(FileInfo* startInfo = NULL); + bool IsBusy() const + { return fBusy; } + const char* Task() const + { return fTask.c_str(); } + float Progress() const + { return min_c(100.0, fProgress); } + FileInfo* CurrentDir() const + { return fBusy ? NULL : fSnapshot->currentDir; } + void ChangeDir(FileInfo* info) + { fSnapshot->currentDir = info; } + void SetDesiredPath(string &path); + dev_t Device() const + { return fVolume->Device(); } + void RequestQuit(); + +private: + void _RunScan(FileInfo *startInfo); + FileInfo* _GetFileInfo(BDirectory* dir, FileInfo* parent); + void _ChangeToDesired(); + + BMessenger fListener; + BMessage fDoneMessage; + BMessage fProgressMessage; + + BVolume* fVolume; + off_t fVolumeBytesInUse; + off_t fVolumeBytesScanned; + float fProgress; + float fLastReport; + VolumeSnapshot* fSnapshot; + string fDesiredPath; + string fTask; + bool fBusy; + bool fQuitRequested; +}; + +#endif // SCANNER_H + diff --git a/src/apps/diskusage/Snapshot.cpp b/src/apps/diskusage/Snapshot.cpp new file mode 100644 index 0000000000..b9100bc62d --- /dev/null +++ b/src/apps/diskusage/Snapshot.cpp @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#include "Snapshot.h" + +#include + +#include +#include +#include +#include +#include + + +static const char* kFileType = B_FILE_MIME_TYPE; +static const char* kDirType = "application/x-vnd.Be-directory"; +static const char* kVolumeType = "application/x-vnd.Be-volume"; + + +FileInfo::FileInfo() + : pseudo(false), + size(0), + count(0), + parent(NULL), + children() +{ +} + + +FileInfo::~FileInfo() +{ + while (children.size() != 0) { + FileInfo* child = *children.begin(); + delete child; + children.erase(children.begin()); + } +} + + +void +FileInfo::GetPath(string& path) const +{ + if (pseudo) { + path.assign(ref.name); + } else { + BEntry entry(&ref, true); + BPath pathObj(&entry); + path.assign(pathObj.Path()); + } +} + + +FileInfo* +FileInfo::FindChild(const char* name) const +{ + vector::const_iterator i = children.begin(); + while (i != children.end()) { + if (strcmp((*i)->ref.name, name) == 0) + return *i; + i++; + } + + return NULL; +} + + +BMimeType* +FileInfo::Type() const +{ + char mimeStr[B_MIME_TYPE_LENGTH] = { '\0' }; + if (parent == NULL) { + // This is the volume's root directory; treat it as a volume type. + strcpy(mimeStr, kVolumeType); + } else { + // Get the MIME type from the registrar. + BNode node(&ref); + if (node.InitCheck() == B_OK) { + BNodeInfo nodeInfo(&node); + if (nodeInfo.InitCheck() == B_OK) { + status_t s = nodeInfo.GetType(mimeStr); + if (s != B_OK && children.size() > 0) { + if (s == B_ENTRY_NOT_FOUND) { + // This status appears to be returned only for files on + // BFS volumes (e.g., CDFS volumes return B_BAD_VALUE). + //nodeInfo.SetType(kDirType); + } + strcpy(mimeStr, kDirType); + } + } + } + } + + if (strlen(mimeStr) == 0) + strcpy(mimeStr, kFileType); + + return new BMimeType(mimeStr); +} + + +// #pragma mark - + + +VolumeSnapshot::VolumeSnapshot(const BVolume* volume) +{ + char nameBuffer[B_FILE_NAME_LENGTH]; + volume->GetName(nameBuffer); + name = nameBuffer; + + capacity = volume->Capacity(); + freeBytes = volume->FreeBytes(); + rootDir = NULL; + freeSpace = NULL; +} + + +VolumeSnapshot::~VolumeSnapshot() +{ + delete rootDir; + delete freeSpace; +} + diff --git a/src/apps/diskusage/Snapshot.h b/src/apps/diskusage/Snapshot.h new file mode 100644 index 0000000000..691ee35433 --- /dev/null +++ b/src/apps/diskusage/Snapshot.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef SNAPSHOT_H +#define SNAPSHOT_H + +#include +#include + +#include + + +class BMimeType; +class BVolume; + +struct FileInfo { + FileInfo(); + ~FileInfo(); + + void GetPath(string& path) const; + FileInfo* FindChild(const char* name) const; + BMimeType* Type() const; + + bool pseudo; + entry_ref ref; + off_t size; + int count; + FileInfo* parent; + vector children; +}; + + +struct VolumeSnapshot { + VolumeSnapshot(const BVolume* volume); + ~VolumeSnapshot(); + + string name; + off_t capacity; + off_t freeBytes; + FileInfo* rootDir; + FileInfo* freeSpace; + FileInfo* currentDir; +}; + +#endif // SNAPSHOT_H + diff --git a/src/apps/diskusage/StatusView.cpp b/src/apps/diskusage/StatusView.cpp new file mode 100644 index 0000000000..a43a9cbf37 --- /dev/null +++ b/src/apps/diskusage/StatusView.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#include "StatusView.h" + +#include +#include + +#include +#include +#include +#include + +#include "Common.h" +#include "Scanner.h" + + +StatusView::StatusView(BRect r) + : BView(r, NULL, B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM, B_WILL_DRAW), + fCurrentFileInfo(NULL) +{ + SetViewColor(kWindowColor); + + struct font_height fh; + be_plain_font->GetHeight(&fh); + float plainHeight = ceilf(fh.ascent) + ceilf(fh.descent) + + ceilf(fh.leading); + float fixedHeight = ceilf(fh.ascent) + ceilf(fh.descent) + + ceilf(fh.leading); + ResizeTo(Bounds().Width(), + 2.0 * kSmallVMargin + max(plainHeight, fixedHeight)); + + BRect r = Bounds(); + r.top += kSmallVMargin; + + // Size display + r.right -= kSmallHMargin; + r.left = r.right - StringWidth("9999.99 GB"); + fSizeView = new BStringView(r, NULL, kEmptyStr, + B_FOLLOW_RIGHT | B_FOLLOW_TOP_BOTTOM); + AddChild(fSizeView); + + // Vertical divider + r.right = r.left - kSmallHMargin; + r.left = r.right - 1.0; + AddChild(new BBox(r.InsetByCopy(0, -5), NULL, + B_FOLLOW_RIGHT | B_FOLLOW_TOP_BOTTOM)); + + // Count display + char testLabel[256]; + sprintf(testLabel, kManyFiles, 999999); + r.right = r.left - kSmallHMargin; + r.left = r.right - (StringWidth(testLabel) + kSmallHMargin); + fCountView = new BStringView(r, NULL, kEmptyStr, + B_FOLLOW_RIGHT|B_FOLLOW_TOP_BOTTOM); + AddChild(fCountView); + + // Vertical divider + r.right = r.left - kSmallHMargin; + r.left = r.right - 1.0; + AddChild(new BBox(r.InsetByCopy(0, -5), NULL, + B_FOLLOW_RIGHT | B_FOLLOW_TOP_BOTTOM)); + + // Path display + r.right = r.left - kSmallHMargin; + r.left = kSmallHMargin; + fPathView = new BStringView(r, NULL, kEmptyStr, + B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP_BOTTOM); + AddChild(fPathView); + + // Horizontal divider + r = Bounds(); + r.bottom = r.top + 1.0; + AddChild(new BBox(r.InsetByCopy(-5, 0), NULL, + B_FOLLOW_LEFT_RIGHT | B_FOLLOW_BOTTOM, B_WILL_DRAW)); +} + + +StatusView::~StatusView() +{ +} + + +void +StatusView::Show(const FileInfo* info) +{ + if (info == fCurrentFileInfo) + return; + + fCurrentFileInfo = info; + + if (info == NULL) { + fPathView->SetText(kEmptyStr); + fSizeView->SetText(kEmptyStr); + fCountView->SetText(kEmptyStr); + return; + } + + if (!info->pseudo) { + BNode node(&info->ref); + if (node.InitCheck() != B_OK) { + fPathView->SetText(kStrUnavail); + fSizeView->SetText(kEmptyStr); + fCountView->SetText(kEmptyStr); + return; + } + } + + float viewWidth = fPathView->Bounds().Width(); + string path; + info->GetPath(path); + BString pathLabel = path.c_str(); + be_plain_font->TruncateString(&pathLabel, B_TRUNCATE_BEGINNING, viewWidth); + fPathView->SetText(pathLabel.String()); + + char label[B_PATH_NAME_LENGTH]; + size_to_string(info->size, label); + fSizeView->SetText(label); + + if (info->count > 0) { + char label[256]; + sprintf(label, (info->count == 1) ? kOneFile : kManyFiles, info->count); + fCountView->SetText(label); + } else { + fCountView->SetText(kEmptyStr); + } +} + + diff --git a/src/apps/diskusage/StatusView.h b/src/apps/diskusage/StatusView.h new file mode 100644 index 0000000000..eaf409b5d0 --- /dev/null +++ b/src/apps/diskusage/StatusView.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2008 Stephan Aßmus . All rights reserved. + * Distributed under the terms of the MIT/X11 license. + * + * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software + * as long as it is accompanied by it's documentation and this copyright notice. + * The software comes with no warranty, etc. + */ +#ifndef STATUS_VIEW_H +#define STATUS_VIEW_H + +#include +#include +#include + + +struct FileInfo; + +class StatusView: public BView { +public: + StatusView(BRect frame); + virtual ~StatusView(); + + void Show(const FileInfo* info); + +private: + BStringView* fPathView; + BStringView* fSizeView; + BStringView* fCountView; + const FileInfo* fCurrentFileInfo; +}; + + +#endif // STATUS_VIEW_H + diff --git a/src/apps/diskusage/TODO b/src/apps/diskusage/TODO new file mode 100644 index 0000000000..14dfdd1974 --- /dev/null +++ b/src/apps/diskusage/TODO @@ -0,0 +1,22 @@ +---Bugs--- + Why doesn't drag-and-drop on the icon work the same as dropping on the + open view? (e.g., multiple volumes don't work; floppy isn't accepted.) + +---Notes--- + Remember to change the alpha component of RGB colors to 0xff with + DiskProbe, if you use Resourcer. They live in block 6. + +---Future/maybe/bloat--- + Display unavailable files in a distinct color (maybe gray)? + Find a way to do redrawing more efficiently (BPicture?) + Add <- and -> buttons to traverse the zoom history. + Display pie in grayscale+patterns / color? + Display an icon somewhere while volumes are being scanned. Check out the + barber shop icon in the Tracker during queries! + Handle the special case where the volume is full: the volume display is + the same as the root directory display except for the colors! + When scanning all volumes, scan different physical devices in parallel, + and multiple partitions on a device in sequence. + Add a cancel button during scanning? + A button to cycle colors? (Useless toy). +