#include "InfoWindow.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <Alert.h>
#include <Catalog.h>
#include <Debug.h>
#include <Directory.h>
#include <File.h>
#include <Font.h>
#include <Locale.h>
#include <MenuField.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <StringFormat.h>
#include <SymLink.h>
#include <TabView.h>
#include <TextView.h>
#include <Volume.h>
#include <VolumeRoster.h>
#include "Attributes.h"
#include "AttributesView.h"
#include "AutoLock.h"
#include "Commands.h"
#include "DialogPane.h"
#include "FSUtils.h"
#include "GeneralInfoView.h"
#include "IconCache.h"
#include "Model.h"
#include "NavMenu.h"
#include "PoseView.h"
#include "StringForSize.h"
#include "Tracker.h"
#include "WidgetAttributeText.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "InfoWindow"
const uint32 kNewTargetSelected = 'selc';
BInfoWindow::BInfoWindow(Model* model, int32 group_index,
LockingList<BWindow>* list)
:
BWindow(BInfoWindow::InfoWindowRect(),
"InfoWindow", B_TITLED_WINDOW,
B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS,
B_CURRENT_WORKSPACE),
fModel(model),
fStopCalc(false),
fIndex(group_index),
fCalcThreadID(-1),
fWindowList(list),
fPermissionsView(NULL),
fFilePanel(NULL),
fFilePanelOpen(false)
{
SetPulseRate(1000000);
TTracker::WatchNode(model->NodeRef(), B_WATCH_ALL | B_WATCH_MOUNT, this);
if (list != NULL)
list->AddItem(this);
AddShortcut('E', 0, new BMessage(kEditName));
AddShortcut('O', 0, new BMessage(kOpenSelection));
AddShortcut('U', 0, new BMessage(kUnmountVolume));
AddShortcut('P', 0, new BMessage(kPermissionsSelected));
BGroupLayout* layout = new BGroupLayout(B_VERTICAL, 0);
SetLayout(layout);
BModelOpener modelOpener(TargetModel());
if (TargetModel()->InitCheck() != B_OK)
return;
fHeaderView = new HeaderView(TargetModel());
AddChild(fHeaderView);
BTabView* tabView = new BTabView("tabs");
tabView->SetBorder(B_NO_BORDER);
AddChild(tabView);
fGeneralInfoView = new GeneralInfoView(TargetModel());
tabView->AddTab(fGeneralInfoView);
BRect permissionsBounds(0,
fGeneralInfoView->Bounds().bottom,
fGeneralInfoView->Bounds().right,
fGeneralInfoView->Bounds().bottom + 103);
fPermissionsView = new FilePermissionsView(
permissionsBounds, fModel);
tabView->AddTab(fPermissionsView);
tabView->AddTab(new AttributesView(TargetModel()));
Run();
}
BInfoWindow::~BInfoWindow()
{
delete fFilePanel;
delete fModel;
}
BRect
BInfoWindow::InfoWindowRect()
{
return BRect(70, 50, 385, 240);
}
void
BInfoWindow::Quit()
{
stop_watching(this);
if (fWindowList) {
AutoLock<LockingList<BWindow> > lock(fWindowList);
fWindowList->RemoveItem(this);
}
fStopCalc = true;
status_t result;
wait_for_thread(fCalcThreadID, &result);
_inherited::Quit();
}
bool
BInfoWindow::IsShowing(const node_ref* node) const
{
return *TargetModel()->NodeRef() == *node;
}
void
BInfoWindow::Show()
{
if (TargetModel()->InitCheck() != B_OK) {
Close();
return;
}
AutoLock<BWindow> lock(this);
BRect windRect(InfoWindowRect());
if ((fIndex + 2) % 2 == 1) {
windRect.OffsetBy(320, 0);
fIndex--;
}
windRect.OffsetBy(fIndex * 8, fIndex * 8);
BScreen screen(this);
if (!windRect.Intersects(screen.Frame()))
windRect.OffsetTo(50, 50);
MoveTo(windRect.LeftTop());
if (!TargetModel()->IsVolume() && !TargetModel()->IsRoot()) {
if (TargetModel()->IsDirectory()) {
SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS));
fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize",
B_NORMAL_PRIORITY, this);
resume_thread(fCalcThreadID);
} else {
fGeneralInfoView->SetLastSize(TargetModel()->StatBuf()->st_size);
BString sizeStr;
GetSizeString(sizeStr, fGeneralInfoView->LastSize(), 0);
SetSizeString(sizeStr.String());
}
}
BString buffer(B_TRANSLATE_COMMENT("%name info", "InfoWindow Title"));
buffer.ReplaceFirst("%name", TargetModel()->Name());
SetTitle(buffer.String());
lock.Unlock();
_inherited::Show();
}
void
BInfoWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case kRestoreState:
Show();
break;
case kOpenSelection:
{
BMessage refsMessage(B_REFS_RECEIVED);
refsMessage.AddRef("refs", fModel->EntryRef());
refsMessage.AddMessenger("TrackerViewToken", BMessenger(this));
be_app->PostMessage(&refsMessage);
break;
}
case kEditName:
{
BEntry entry(fModel->EntryRef());
fHeaderView->BeginEditingTitle();
break;
}
case kIdentifyEntry:
{
bool force = (modifiers() & B_OPTION_KEY) != 0;
BEntry entry;
if (entry.SetTo(fModel->EntryRef(), true) == B_OK) {
BPath path;
if (entry.GetPath(&path) == B_OK)
update_mime_info(path.Path(), true, false, force ? 2 : 1);
}
break;
}
case kRecalculateSize:
{
fStopCalc = true;
status_t result;
wait_for_thread(fCalcThreadID, &result);
fStopCalc = false;
SetSizeString(B_TRANSLATE("calculating" B_UTF8_ELLIPSIS));
fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize",
B_NORMAL_PRIORITY, this);
resume_thread(fCalcThreadID);
break;
}
case kSetLinkTarget:
OpenFilePanel(fModel->EntryRef());
break;
case B_SIMPLE_DATA:
if (!fModel->IsSymLink())
break;
case kNewTargetSelected:
{
BEntry targetEntry;
entry_ref ref;
BPath path;
if (message->FindRef("refs", &ref) == B_OK
&& targetEntry.SetTo(&ref, true) == B_OK
&& targetEntry.Exists()) {
stop_watching(this);
BDirectory parent;
BEntry tmpEntry(TargetModel()->EntryRef());
if (tmpEntry.GetParent(&parent) != B_OK)
break;
BString name(TargetModel()->Name());
BEntry target(&ref);
BPath targetPath;
if (target.GetPath(&targetPath) != B_OK)
break;
AttributeStreamMemoryNode memoryNode;
{
BModelOpener opener(TargetModel());
AttributeStreamFileNode original(TargetModel()->Node());
memoryNode << original;
}
BEntry oldEntry(TargetModel()->EntryRef());
oldEntry.Remove();
BSymLink link;
parent.CreateSymLink(name.String(), targetPath.Path(), &link);
BEntry symEntry(&parent, name.String());
fModel->SetTo(&symEntry);
BModelWriteOpener opener(TargetModel());
AttributeStreamFileNode newNode(TargetModel()->Node());
newNode << memoryNode;
TTracker::WatchNode(TargetModel()->NodeRef(),
B_WATCH_ALL | B_WATCH_MOUNT, this);
fGeneralInfoView->ReLinkTargetModel(TargetModel());
fHeaderView->ReLinkTargetModel(TargetModel());
}
break;
}
case B_CANCEL:
delete fFilePanel;
fFilePanel = NULL;
fFilePanelOpen = false;
break;
case kUnmountVolume:
if (fModel->IsVolume()) {
BVolume boot;
BVolumeRoster().GetBootVolume(&boot);
BVolume volume(fModel->NodeRef()->device);
if (volume != boot) {
TTracker* tracker = dynamic_cast<TTracker*>(be_app);
if (tracker != NULL)
tracker->SaveAllPoseLocations();
BMessage unmountMessage(kUnmountVolume);
unmountMessage.AddInt32("device_id", volume.Device());
be_app->PostMessage(&unmountMessage);
}
}
break;
case kEmptyTrash:
FSEmptyTrash();
break;
case B_NODE_MONITOR:
switch (message->GetInt32("opcode", 0)) {
case B_ENTRY_REMOVED:
{
node_ref itemNode;
message->FindInt32("device", &itemNode.device);
message->FindInt64("node", &itemNode.node);
if (*TargetModel()->NodeRef() == itemNode)
Close();
break;
}
case B_ENTRY_MOVED:
case B_STAT_CHANGED:
case B_ATTR_CHANGED:
fGeneralInfoView->ModelChanged(TargetModel(), message);
fHeaderView->ModelChanged(TargetModel(), message);
if (fPermissionsView != NULL)
fPermissionsView->ModelChanged(TargetModel());
break;
case B_DEVICE_UNMOUNTED:
{
node_ref itemNode;
message->FindInt32("device", &itemNode.device);
if (TargetModel()->NodeRef()->device == itemNode.device)
Close();
break;
}
default:
break;
}
break;
case kPermissionsSelected:
{
BTabView* tabView = (BTabView*)FindView("tabs");
tabView->Select(1);
break;
}
default:
_inherited::MessageReceived(message);
break;
}
}
void
BInfoWindow::GetSizeString(BString& result, off_t size, int32 fileCount)
{
static BStringFormat sizeFormat(B_TRANSLATE(
"{0, plural, one{(# byte)} other{(# bytes)}}"));
static BStringFormat countFormat(B_TRANSLATE(
"{0, plural, one{for # file} other{for # files}}"));
char sizeBuffer[128];
result << string_for_size((double)size, sizeBuffer, sizeof(sizeBuffer));
if (size >= kKBSize) {
result << " ";
sizeFormat.Format(result, size);
}
if (fileCount != 0) {
result << " ";
countFormat.Format(result, fileCount);
}
}
int32
BInfoWindow::CalcSize(void* castToWindow)
{
BInfoWindow* window = static_cast<BInfoWindow*>(castToWindow);
BDirectory dir(window->TargetModel()->EntryRef());
BDirectory trashDir;
FSGetTrashDir(&trashDir, window->TargetModel()->EntryRef()->device);
if (dir.InitCheck() != B_OK) {
if (window->StopCalc())
return B_ERROR;
AutoLock<BWindow> lock(window);
if (!lock)
return B_ERROR;
window->SetSizeString(B_TRANSLATE("Error calculating folder size."));
return B_ERROR;
}
BEntry dirEntry, trashEntry;
dir.GetEntry(&dirEntry);
trashDir.GetEntry(&trashEntry);
BString sizeString;
if (dirEntry != trashEntry) {
off_t size = 0;
int32 fileCount = 0;
int32 dirCount = 0;
CopyLoopControl loopControl;
FSRecursiveCalcSize(window, &loopControl, &dir, &size, &fileCount,
&dirCount);
GetSizeString(sizeString, size, fileCount);
} else {
off_t totalSize = 0, currentSize;
int32 totalFileCount = 0, currentFileCount;
int32 totalDirCount = 0, currentDirCount;
BVolumeRoster volRoster;
volRoster.Rewind();
BVolume volume;
while (volRoster.GetNextVolume(&volume) == B_OK) {
if (!volume.IsPersistent())
continue;
currentSize = 0;
currentFileCount = 0;
currentDirCount = 0;
BDirectory trashDir;
if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) {
CopyLoopControl loopControl;
FSRecursiveCalcSize(window, &loopControl, &trashDir,
¤tSize, ¤tFileCount, ¤tDirCount);
totalSize += currentSize;
totalFileCount += currentFileCount;
totalDirCount += currentDirCount;
}
}
GetSizeString(sizeString, totalSize, totalFileCount);
}
if (window->StopCalc()) {
return B_OK;
}
AutoLock<BWindow> lock(window);
if (lock.IsLocked())
window->SetSizeString(sizeString.String());
return B_OK;
}
void
BInfoWindow::SetSizeString(const char* sizeString)
{
fGeneralInfoView->SetSizeString(sizeString);
}
void
BInfoWindow::OpenFilePanel(const entry_ref* ref)
{
if (fFilePanel == NULL) {
BMessenger runner(this);
BMessage message(kNewTargetSelected);
fFilePanel = new BFilePanel(B_OPEN_PANEL, &runner, ref,
B_FILE_NODE | B_SYMLINK_NODE | B_DIRECTORY_NODE,
false, &message);
if (fFilePanel != NULL) {
fFilePanel->SetButtonLabel(B_DEFAULT_BUTTON,
B_TRANSLATE("Select"));
fFilePanel->Window()->ResizeTo(500, 300);
BString title(B_TRANSLATE_COMMENT("Link \"%name\" to:",
"File dialog title for new sym link"));
title.ReplaceFirst("%name", fModel->Name());
fFilePanel->Window()->SetTitle(title.String());
fFilePanel->Show();
fFilePanelOpen = true;
}
} else if (!fFilePanelOpen) {
fFilePanel->Show();
fFilePanelOpen = true;
} else {
fFilePanelOpen = true;
fFilePanel->Window()->Activate(true);
}
}