Open Tracker License
Terms and Conditions
Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice applies to all licensees
and shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Except as contained in this notice, the name of Be Incorporated shall not be
used in advertising or otherwise to promote the sale, use or other dealings in
this Software without prior written authorization from Be Incorporated.
Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
of Be Incorporated in the United States and other countries. Other brand product
names are registered trademarks or trademarks of their respective holders.
All rights reserved.
*/
#include "HeaderView.h"
#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 "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),
fMouseDown(false),
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 = min_c(
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)
return;
bool reopen = false;
const char* text = fTitleEditView->Text();
uint32 length = strlen(text);
if (commit && strcmp(text, fModel->Name()) != 0
&& length < B_FILE_NAME_LENGTH) {
BEntry entry(fModel->EntryRef());
BDirectory parent;
if (entry.InitCheck() == B_OK
&& entry.GetParent(&parent) == B_OK) {
if (parent.Contains(text)) {
BAlert* alert = new BAlert("",
B_TRANSLATE("That name is already taken. "
"Please type another one."),
B_TRANSLATE("OK"),
0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
reopen = true;
} else {
if (fModel->IsVolume()) {
BVolume volume(fModel->NodeRef()->device);
if (volume.InitCheck() == B_OK)
volume.SetName(text);
} else
entry.Rename(text);
BFont currentFont(be_plain_font);
currentFont.SetSize(currentFont.Size() + 2);
fTitleRect.right = min_c(fTitleRect.left
+ currentFont.StringWidth(fTitleEditView->Text()),
Bounds().Width() - 5);
}
}
} else if (length >= B_FILE_NAME_LENGTH) {
BAlert* alert = new BAlert("",
B_TRANSLATE("That name is too long. Please type another one."),
B_TRANSLATE("OK"),
0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
reopen = true;
}
BView* scrollView = fTitleEditView->Parent();
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 = min_c(
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;
BEntry entry;
fModel->GetEntry(&entry);
if (fTitleRect.Contains(where)) {
if (!fModel->HasLocalizedName()
&& ConfirmChangeIfWellKnownDirectory(&entry, kRename, true)) {
BeginEditingTitle();
}
} else if (fTitleEditView) {
FinishEditingTitle(true);
} else if (fIconRect.Contains(where)) {
uint32 buttons;
Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
if (SecondaryMouseButtonDown(modifiers(), buttons)) {
BPopUpMenu* contextMenu
= new BPopUpMenu("FileContext", false, false);
if (contextMenu) {
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;
fMouseDown = true;
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 (fMouseDown && !fDragging
&& (abs((int32)(where.x - fClickPoint.x)) > kDragSlop
|| abs((int32)(where.y - fClickPoint.y)) > kDragSlop)) {
BFont font;
GetFont(&font);
float height = CurrentFontHeight()
+ fIconRect.Height() + 8;
BRect rect(0, 0, min_c(fIconRect.Width()
+ font.StringWidth(fModel->Name()) + 4,
fIconRect.Width() * 3), 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 = (view->StringWidth(nameString.String())
- fIconRect.Width()) / 2;
view->MovePenTo(BPoint(hIconOffset - leftText + 2,
fIconRect.Height() + (fontHeight.ascent + 2)));
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());
DragMessage(&dragMessage, dragBitmap, B_OP_ALPHA,
BPoint((fClickPoint.x - fIconRect.left)
+ hIconOffset, fClickPoint.y - fIconRect.top), 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;
}
}
fMouseDown = 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(new BMenuItem(B_TRANSLATE("Open"),
new BMessage(kOpenSelection), 'O'));
if (!model.IsDesktop() && !model.IsRoot() && !model.IsTrash()
&& !fModel->HasLocalizedName()) {
parent->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
new BMessage(kEditItem), 'E'));
parent->AddSeparatorItem();
if (fModel->IsVolume()) {
BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"),
new BMessage(kUnmountVolume), 'U');
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(new BMenuItem(B_TRANSLATE("Identify"),
new BMessage(kIdentifyEntry)));
if (model.IsTrash())
parent->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
new BMessage(kEmptyTrash)));
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;
}