#include "HeaderView.h"
#include <algorithm>
#include <Alert.h>
#include <Application.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Locale.h>
#include <PopUpMenu.h>
#include <ScrollView.h>
#include <Volume.h>
#include <VolumeRoster.h>
#include <Window.h>
#include "Commands.h"
#include "FSUtils.h"
#include "GeneralInfoView.h"
#include "IconMenuItem.h"
#include "Model.h"
#include "NavMenu.h"
#include "PoseView.h"
#include "Shortcuts.h"
#include "Tracker.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "InfoWindow"
const float kDragSlop = 3.0f;
HeaderView::HeaderView(Model* model)
:
BView("header", B_WILL_DRAW),
fModel(model),
fIconModel(model),
fTitleEditView(NULL),
fTrackingState(no_track),
fIsDropTarget(false),
fDoubleClick(false),
fDragging(false)
{
const float labelSpacing = be_control_look->DefaultLabelSpacing();
fIconRect = BRect(BPoint(labelSpacing * 3.0f, labelSpacing),
be_control_look->ComposeIconSize(B_LARGE_ICON));
SetExplicitSize(BSize(B_SIZE_UNSET, fIconRect.Width() + 2 * fIconRect.top));
BFont currentFont;
font_height fontMetrics;
GetFont(¤tFont);
currentFont.GetHeight(&fontMetrics);
fTitleRect.left = fIconRect.right + labelSpacing;
fTitleRect.top = 0;
fTitleRect.bottom = fontMetrics.ascent + 1;
fTitleRect.right = std::min(
fTitleRect.left + currentFont.StringWidth(fModel->Name()),
Bounds().Width() - labelSpacing);
fTitleRect.OffsetBy(0,
fIconRect.top + ((fIconRect.Height() - fTitleRect.Height()) / 2));
fTitleRect.InsetBy(-1, -2);
if (fModel->IsSymLink()) {
Model* resolvedModel = new Model(model->EntryRef(), true, true);
if (resolvedModel->InitCheck() == B_OK)
fIconModel = resolvedModel;
else
delete resolvedModel;
}
}
HeaderView::~HeaderView()
{
if (fIconModel != fModel)
delete fIconModel;
}
void
HeaderView::ModelChanged(Model* model, BMessage* message)
{
if (fIconModel != fModel) {
delete fIconModel;
fIconModel = NULL;
}
fModel = model;
if (fModel->IsSymLink()) {
Model* resolvedModel = new Model(model->EntryRef(), true, true);
if (resolvedModel->InitCheck() == B_OK) {
if (fIconModel != fModel)
delete fIconModel;
fIconModel = resolvedModel;
} else {
fIconModel = model;
delete resolvedModel;
}
}
Invalidate();
}
void
HeaderView::ReLinkTargetModel(Model* model)
{
fModel = model;
if (fModel->IsSymLink()) {
Model* resolvedModel = new Model(model->EntryRef(), true, true);
if (resolvedModel->InitCheck() == B_OK) {
if (fIconModel != fModel)
delete fIconModel;
fIconModel = resolvedModel;
} else {
fIconModel = fModel;
delete resolvedModel;
}
}
Invalidate();
}
void
HeaderView::BeginEditingTitle()
{
if (fTitleEditView != NULL)
return;
BFont font(be_plain_font);
font.SetSize(font.Size() + 2);
BRect textFrame(fTitleRect);
textFrame.right = Bounds().Width() - 5;
BRect textRect(textFrame);
textRect.OffsetTo(0, 0);
textRect.InsetBy(1, 1);
textRect.right = 2000;
fTitleEditView = new BTextView(textFrame, "text_editor",
textRect, &font, 0, B_FOLLOW_ALL, B_WILL_DRAW);
fTitleEditView->SetText(fModel->Name());
DisallowFilenameKeys(fTitleEditView);
textRect = fTitleEditView->TextRect();
textRect.right = fTitleEditView->LineWidth() + 20;
fTitleEditView->SetTextRect(textRect);
fTitleEditView->SetWordWrap(false);
fTitleEditView->AddFilter(
new BMessageFilter(B_KEY_DOWN, HeaderView::TextViewFilter));
BScrollView* scrollView = new BScrollView("BorderView", fTitleEditView,
0, 0, false, false, B_PLAIN_BORDER);
AddChild(scrollView);
fTitleEditView->SelectAll();
fTitleEditView->MakeFocus();
Window()->UpdateIfNeeded();
}
void
HeaderView::FinishEditingTitle(bool commit)
{
if (fTitleEditView == NULL || !commit)
return;
const char* name = fTitleEditView->Text();
size_t length = (size_t)fTitleEditView->TextLength();
status_t result = EditModelName(fModel, name, length);
bool reopen = (result == B_NAME_TOO_LONG || result == B_NAME_IN_USE);
if (result == B_OK) {
BFont currentFont(be_plain_font);
currentFont.SetSize(currentFont.Size() + 2);
float stringWidth = currentFont.StringWidth(fTitleEditView->Text());
fTitleRect.right = std::min(fTitleRect.left + stringWidth,
Bounds().Width() - 5);
}
BView* scrollView = fTitleEditView->Parent();
if (scrollView != NULL) {
RemoveChild(scrollView);
delete scrollView;
fTitleEditView = NULL;
}
if (reopen)
BeginEditingTitle();
}
void
HeaderView::Draw(BRect)
{
SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
FillRect(Bounds());
rgb_color labelColor = ui_color(B_PANEL_TEXT_COLOR);
SetDrawingMode(B_OP_OVER);
IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(),
kNormalIcon, fIconRect.Size(), true);
SetDrawingMode(B_OP_COPY);
font_height fontMetrics;
BFont currentFont;
float lineBase = 0;
if (fTitleEditView == NULL) {
SetFont(be_bold_font);
SetFontSize(be_bold_font->Size());
GetFont(¤tFont);
currentFont.GetHeight(&fontMetrics);
lineBase = fTitleRect.bottom - fontMetrics.descent;
SetHighColor(labelColor);
MovePenTo(BPoint(fIconRect.right + 6, lineBase));
fTitleRect.right = std::min(fTitleRect.left
+ currentFont.StringWidth(fModel->Name()),
Bounds().Width() - 5);
if (StringWidth(fModel->Name()) > fTitleRect.Width()) {
BString nameString(fModel->Name());
TruncateString(&nameString, B_TRUNCATE_END,
fTitleRect.Width() - 2);
DrawString(nameString.String());
} else
DrawString(fModel->Name());
}
}
void
HeaderView::MakeFocus(bool focus)
{
if (!focus && fTitleEditView != NULL)
FinishEditingTitle(true);
}
void
HeaderView::WindowActivated(bool active)
{
if (active)
return;
if (fTitleEditView != NULL)
FinishEditingTitle(true);
}
void
HeaderView::MouseDown(BPoint where)
{
fDoubleClick = false;
if (fTitleRect.Contains(where) && fTitleEditView == NULL)
BeginEditingTitle();
else if (fTitleEditView != NULL)
FinishEditingTitle(true);
else if (fIconRect.Contains(where)) {
uint32 buttons;
Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
if (SecondaryMouseButtonDown(modifiers(), buttons)) {
BPopUpMenu* contextMenu = new BPopUpMenu("PoseContext", false, false);
if (contextMenu != NULL) {
BuildContextMenu(contextMenu);
contextMenu->SetAsyncAutoDestruct(true);
contextMenu->Go(ConvertToScreen(where), true, true,
ConvertToScreen(fIconRect));
}
} else {
BPoint offsetPoint;
offsetPoint.x = where.x - fIconRect.left;
offsetPoint.y = where.y - fIconRect.top;
if (IconCache::sIconCache->IconHitTest(offsetPoint, fIconModel, kNormalIcon,
fIconRect.Size())) {
fTrackingState = fModel->IsTrash()
? open_only_track : icon_track;
if (abs((int32)(fClickPoint.x - where.x)) < kDragSlop
&& abs((int32)(fClickPoint.y - where.y)) < kDragSlop) {
int32 clickCount;
Window()->CurrentMessage()->FindInt32("clicks",
&clickCount);
if (clickCount == 2) {
offsetPoint.x = fClickPoint.x - fIconRect.left;
offsetPoint.y = fClickPoint.y - fIconRect.top;
fDoubleClick
= IconCache::sIconCache->IconHitTest(offsetPoint,
fIconModel, kNormalIcon, fIconRect.Size());
}
}
}
}
}
fClickPoint = where;
SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
}
void
HeaderView::MouseMoved(BPoint where, uint32, const BMessage* dragMessage)
{
if (dragMessage != NULL && dragMessage->ReturnAddress() != BMessenger(this)
&& dragMessage->what == B_SIMPLE_DATA
&& BPoseView::CanHandleDragSelection(fModel, dragMessage,
(modifiers() & B_CONTROL_KEY) != 0)) {
bool overTarget = fIconRect.Contains(where);
SetDrawingMode(B_OP_OVER);
if (overTarget != fIsDropTarget) {
IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(),
overTarget ? kSelectedIcon : kNormalIcon, fIconRect.Size(), true);
fIsDropTarget = overTarget;
}
}
switch (fTrackingState) {
case icon_track:
{
if (fDragging)
break;
uint32 buttons = Window()->CurrentMessage()->GetInt32("buttons", 0);
if (buttons == 0)
break;
if (abs((int32)(where.x - fClickPoint.x)) <= kDragSlop
&& abs((int32)(where.y - fClickPoint.y)) <= kDragSlop) {
break;
}
BFont font;
GetFont(&font);
float width = std::min(fIconRect.Width() + font.StringWidth(fModel->Name()) + 4,
fIconRect.Width() * 3);
float height = CurrentFontHeight() + fIconRect.Height() + 8;
BRect rect(0, 0, width, height);
BBitmap* dragBitmap = new BBitmap(rect, B_RGBA32, true);
dragBitmap->Lock();
BView* view = new BView(dragBitmap->Bounds(), "", B_FOLLOW_NONE, 0);
dragBitmap->AddChild(view);
view->SetOrigin(0, 0);
BRect clipRect(view->Bounds());
BRegion newClip;
newClip.Set(clipRect);
view->ConstrainClippingRegion(&newClip);
view->SetHighColor(0, 0, 0, 0);
view->FillRect(view->Bounds());
view->SetDrawingMode(B_OP_ALPHA);
rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
textColor.alpha = 128;
view->SetHighColor(textColor);
view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
float hIconOffset = (rect.Width() - fIconRect.Width()) / 2;
IconCache::sIconCache->Draw(fIconModel, view, BPoint(hIconOffset, 0), kNormalIcon,
fIconRect.Size(), true);
BString nameString(fModel->Name());
if (view->StringWidth(fModel->Name()) > rect.Width())
view->TruncateString(&nameString, B_TRUNCATE_END, rect.Width() - 5);
font_height fontHeight;
font.GetHeight(&fontHeight);
float leftText
= roundf((view->StringWidth(nameString.String()) - fIconRect.Width()) / 2);
float x = hIconOffset - leftText + 2;
float y = fIconRect.Height() + fontHeight.ascent + 2;
view->MovePenTo(BPoint(x, y));
view->DrawString(nameString.String());
view->Sync();
dragBitmap->Unlock();
BMessage dragMessage(B_REFS_RECEIVED);
dragMessage.AddPoint("click_pt", fClickPoint);
BPoint tmpLoc;
uint32 button;
GetMouse(&tmpLoc, &button);
if (button)
dragMessage.AddInt32("buttons", (int32)button);
dragMessage.AddInt32("be:actions",
(modifiers() & B_OPTION_KEY) != 0 ? B_COPY_TARGET : B_MOVE_TARGET);
dragMessage.AddRef("refs", fModel->EntryRef());
x = fClickPoint.x - fIconRect.left + hIconOffset;
y = fClickPoint.y - fIconRect.top;
DragMessage(&dragMessage, dragBitmap, B_OP_ALPHA, BPoint(x, y), this);
fDragging = true;
break;
}
case open_only_track :
break;
case no_track:
break;
}
}
void
HeaderView::MouseUp(BPoint where)
{
if ((fTrackingState == icon_track || fTrackingState == open_only_track)
&& fIconRect.Contains(where)) {
if (fDoubleClick) {
BMessage message(B_REFS_RECEIVED);
message.AddRef("refs", fModel->EntryRef());
message.AddMessenger("TrackerViewToken", BMessenger(this));
be_app->PostMessage(&message);
fDoubleClick = false;
}
}
fDragging = false;
fTrackingState = no_track;
}
void
HeaderView::MessageReceived(BMessage* message)
{
if (message->WasDropped()
&& message->what == B_SIMPLE_DATA
&& message->ReturnAddress() != BMessenger(this)
&& fIconRect.Contains(ConvertFromScreen(message->DropPoint()))
&& BPoseView::CanHandleDragSelection(fModel, message,
(modifiers() & B_CONTROL_KEY) != 0)) {
BPoseView::HandleDropCommon(message, fModel, 0, this,
message->DropPoint());
Invalidate(fIconRect);
return;
}
BView::MessageReceived(message);
}
status_t
HeaderView::BuildContextMenu(BMenu* parent)
{
if (parent == NULL)
return B_BAD_VALUE;
BEntry entry(fModel->EntryRef());
entry_ref ref;
entry.GetRef(&ref);
Model model(&entry);
bool navigate = false;
if (model.InitCheck() == B_OK) {
if (model.IsSymLink()) {
if (entry.SetTo(model.EntryRef(), true) == B_OK) {
navigate = entry.IsDirectory();
entry.GetRef(&ref);
}
} else if (model.IsDirectory() || model.IsVolume())
navigate = true;
}
ModelMenuItem* navigationItem = NULL;
if (navigate) {
navigationItem = new ModelMenuItem(new Model(model),
new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, Window()));
BNavMenu* navMenu = dynamic_cast<BNavMenu*>(navigationItem->Submenu());
if (navMenu != NULL)
navMenu->SetNavDir(&ref);
navigationItem->SetLabel(model.Name());
navigationItem->SetEntry(&entry);
parent->AddItem(navigationItem, 0);
parent->AddItem(new BSeparatorItem(), 1);
BMessage* message = new BMessage(B_REFS_RECEIVED);
message->AddRef("refs", &ref);
navigationItem->SetMessage(message);
navigationItem->SetTarget(be_app);
}
parent->AddItem(TShortcuts().OpenItem());
if (!model.IsDesktop() && !model.IsRoot() && !model.IsTrash()) {
parent->AddItem(TShortcuts().EditNameItem());
parent->AddSeparatorItem();
if (fModel->IsVolume()) {
BMenuItem* item = TShortcuts().UnmountItem();
parent->AddItem(item);
BVolume boot;
BVolumeRoster().GetBootVolume(&boot);
BVolume volume;
volume.SetTo(fModel->NodeRef()->device);
if (volume == boot)
item->SetEnabled(false);
}
}
if (!model.IsRoot() && !model.IsVolume() && !model.IsTrash())
parent->AddItem(TShortcuts().IdentifyItem());
if (model.IsTrash())
parent->AddItem(TShortcuts().EmptyTrashItem());
BMenuItem* sizeItem = NULL;
if (model.IsDirectory() && !model.IsVolume() && !model.IsRoot()) {
parent->AddItem(sizeItem
= new BMenuItem(B_TRANSLATE("Recalculate folder size"),
new BMessage(kRecalculateSize)));
}
if (model.IsSymLink()) {
parent->AddItem(sizeItem
= new BMenuItem(B_TRANSLATE("Set new link target"),
new BMessage(kSetLinkTarget)));
}
parent->AddItem(new BSeparatorItem());
parent->AddItem(new BMenuItem(B_TRANSLATE("Permissions"),
new BMessage(kPermissionsSelected), 'P'));
parent->SetFont(be_plain_font);
parent->SetTargetForItems(this);
if (navigate)
navigationItem->SetTarget(be_app);
if (sizeItem)
sizeItem->SetTarget(Window());
return B_OK;
}
filter_result
HeaderView::TextViewFilter(BMessage* message, BHandler**,
BMessageFilter* filter)
{
uchar key;
HeaderView* attribView = static_cast<HeaderView*>(
static_cast<BWindow*>(filter->Looper())->FindView("header"));
BRect nuRect(attribView->TextView()->TextRect());
nuRect.right = attribView->TextView()->LineWidth() + 20;
attribView->TextView()->SetTextRect(nuRect);
attribView->TextView()->ScrollToSelection();
if (message->FindInt8("byte", (int8*)&key) != B_OK)
return B_DISPATCH_MESSAGE;
if (key == B_RETURN || key == B_ESCAPE) {
attribView->FinishEditingTitle(key == B_RETURN);
return B_SKIP_MESSAGE;
}
return B_DISPATCH_MESSAGE;
}
float
HeaderView::CurrentFontHeight()
{
BFont font;
GetFont(&font);
font_height fontHeight;
font.GetHeight(&fontHeight);
return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2;
}