#include "TextWidget.h"
#include <string.h>
#include <stdlib.h>
#include <Alert.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <Debug.h>
#include <Directory.h>
#include <MessageFilter.h>
#include <ScrollView.h>
#include <TextView.h>
#include <Volume.h>
#include <Window.h>
#include "Attributes.h"
#include "ContainerWindow.h"
#include "Commands.h"
#include "FSUtils.h"
#include "PoseView.h"
#include "Utilities.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TextWidget"
const float kWidthMargin = 20;
BTextWidget::BTextWidget(Model* model, BColumn* column, BPoseView* view)
:
fText(WidgetAttributeText::NewWidgetText(model, column, view)),
fAttrHash(column->AttrHash()),
fAlignment(column->Alignment()),
fEditable(column->Editable()),
fVisible(true),
fActive(false),
fSymLink(model->IsSymLink()),
fMaxWidth(0),
fLastClickedTime(0)
{
}
BTextWidget::~BTextWidget()
{
if (fLastClickedTime != 0)
fParams.poseView->SetTextWidgetToCheck(NULL, this);
delete fText;
}
int
BTextWidget::Compare(const BTextWidget& with, BPoseView* view) const
{
return fText->Compare(*with.fText, view);
}
const char*
BTextWidget::Text(const BPoseView* view) const
{
StringAttributeText* textAttribute = dynamic_cast<StringAttributeText*>(fText);
if (textAttribute == NULL)
return NULL;
return textAttribute->ValueAsText(view);
}
float
BTextWidget::TextWidth(const BPoseView* pose) const
{
return fText->Width(pose);
}
float
BTextWidget::PreferredWidth(const BPoseView* pose) const
{
return fText->PreferredWidth(pose);
}
BRect
BTextWidget::ColumnRect(BPoint poseLoc, const BColumn* column,
const BPoseView* view)
{
if (view->ViewMode() != kListMode) {
return CalcRect(poseLoc, column, view);
}
BRect rect;
rect.left = column->Offset() + poseLoc.x;
rect.right = rect.left + column->Width();
rect.bottom = poseLoc.y + roundf((view->ListElemHeight() + ActualFontHeight(view)) / 2.f);
rect.top = rect.bottom - floorf(ActualFontHeight(view));
return rect;
}
BRect
BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn* column,
const BPoseView* view, float textWidth)
{
BRect rect;
float viewWidth;
if (view->ViewMode() == kListMode) {
viewWidth = ceilf(std::min(column->Width(), textWidth));
poseLoc.x += column->Offset();
switch (fAlignment) {
case B_ALIGN_LEFT:
rect.left = poseLoc.x;
rect.right = rect.left + viewWidth;
break;
case B_ALIGN_CENTER:
rect.left = poseLoc.x + roundf((column->Width() - viewWidth) / 2.f);
if (rect.left < 0)
rect.left = 0;
rect.right = rect.left + viewWidth;
break;
case B_ALIGN_RIGHT:
rect.right = poseLoc.x + column->Width();
rect.left = rect.right - viewWidth;
if (rect.left < 0)
rect.left = 0;
break;
default:
TRESPASS();
break;
}
rect.bottom = poseLoc.y + roundf((view->ListElemHeight() + ActualFontHeight(view)) / 2.f);
rect.top = rect.bottom - floorf(ActualFontHeight(view));
} else {
float iconSize = (float)view->IconSizeInt();
textWidth = floorf(textWidth);
if (view->ViewMode() == kIconMode) {
viewWidth = ceilf(std::min(view->StringWidth("M") * 30, textWidth));
rect.left = poseLoc.x + roundf((iconSize - viewWidth) / 2.f);
rect.bottom = poseLoc.y + ceilf(view->IconPoseHeight());
rect.top = rect.bottom - floorf(ActualFontHeight(view));
} else {
viewWidth = ceilf(textWidth);
rect.left = poseLoc.x + iconSize + kMiniIconSeparator;
rect.bottom = poseLoc.y + roundf((iconSize + ActualFontHeight(view)) / 2.f);
rect.top = poseLoc.y;
}
rect.right = rect.left + viewWidth;
}
return rect;
}
BRect
BTextWidget::CalcRect(BPoint poseLoc, const BColumn* column, const BPoseView* view)
{
return CalcRectCommon(poseLoc, column, view, fText->Width(view));
}
BRect
BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn* column, const BPoseView* view)
{
return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth());
}
BRect
BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn* column, const BPoseView* view)
{
BRect rect = CalcRect(poseLoc, column, view);
if (rect.Width() < kWidthMargin) {
if (column != NULL && column->Width() < kWidthMargin)
rect.right = rect.left + column->Width();
else
rect.right = rect.left + kWidthMargin;
}
return rect;
}
void
BTextWidget::CheckExpiration()
{
if (IsEditable() && fParams.pose->IsSelected() && fLastClickedTime) {
bigtime_t doubleClickSpeed;
get_click_speed(&doubleClickSpeed);
bigtime_t delta = system_time() - fLastClickedTime;
if (delta > doubleClickSpeed) {
fLastClickedTime = 0;
StartEdit(fParams.bounds, fParams.poseView, fParams.pose);
}
} else {
fLastClickedTime = 0;
fParams.poseView->SetTextWidgetToCheck(NULL);
}
}
void
BTextWidget::CancelWait()
{
fLastClickedTime = 0;
fParams.poseView->SetTextWidgetToCheck(NULL);
}
void
BTextWidget::MouseUp(BRect bounds, BPoseView* view, BPose* pose, BPoint)
{
view->SetTextWidgetToCheck(NULL);
if (IsEditable() && pose->IsSelected()) {
bigtime_t doubleClickSpeed;
get_click_speed(&doubleClickSpeed);
if (fLastClickedTime == 0) {
fLastClickedTime = system_time();
if (fLastClickedTime - doubleClickSpeed < pose->SelectionTime())
fLastClickedTime = 0;
} else
fLastClickedTime = 0;
if (fLastClickedTime == 0)
return;
view->SetTextWidgetToCheck(this);
fParams.pose = pose;
fParams.bounds = bounds;
fParams.poseView = view;
} else
fLastClickedTime = 0;
}
static filter_result
TextViewKeyDownFilter(BMessage* message, BHandler**, BMessageFilter* filter)
{
uchar key;
if (message->FindInt8("byte", (int8*)&key) != B_OK)
return B_DISPATCH_MESSAGE;
ThrowOnAssert(filter != NULL);
BContainerWindow* window = dynamic_cast<BContainerWindow*>(
filter->Looper());
ThrowOnAssert(window != NULL);
BPoseView* view = window->PoseView();
ThrowOnAssert(view != NULL);
if (key == B_RETURN || key == B_ESCAPE) {
view->CommitActivePose(key == B_RETURN);
return B_SKIP_MESSAGE;
}
if (key == B_TAB) {
if (view->ActivePose()) {
if (message->FindInt32("modifiers") & B_SHIFT_KEY)
view->ActivePose()->EditPreviousWidget(view);
else
view->ActivePose()->EditNextWidget(view);
}
return B_SKIP_MESSAGE;
}
BView* scrollView = view->FindView("BorderView");
if (scrollView != NULL) {
BTextView* textView = dynamic_cast<BTextView*>(
scrollView->FindView("WidgetTextView"));
if (textView != NULL) {
ASSERT(view->ActiveTextWidget() != NULL);
float maxWidth = view->ActiveTextWidget()->MaxWidth();
bool tooWide = textView->TextRect().Width() > maxWidth;
textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
}
}
return B_DISPATCH_MESSAGE;
}
static filter_result
TextViewPasteFilter(BMessage* message, BHandler**, BMessageFilter* filter)
{
ThrowOnAssert(filter != NULL);
BContainerWindow* window = dynamic_cast<BContainerWindow*>(
filter->Looper());
ThrowOnAssert(window != NULL);
BPoseView* view = window->PoseView();
ThrowOnAssert(view != NULL);
BView* scrollView = view->FindView("BorderView");
if (scrollView != NULL) {
BTextView* textView = dynamic_cast<BTextView*>(
scrollView->FindView("WidgetTextView"));
if (textView != NULL) {
float textWidth = textView->TextRect().Width();
int32 start, finish;
textView->GetSelection(&start, &finish);
if (start != finish) {
BRegion selectedRegion;
textView->GetTextRegion(start, finish, &selectedRegion);
textWidth -= selectedRegion.Frame().Width();
}
if (be_clipboard->Lock()) {
BMessage* clip = be_clipboard->Data();
if (clip != NULL) {
const char* text = NULL;
ssize_t length = 0;
if (clip->FindData("text/plain", B_MIME_TYPE,
(const void**)&text, &length) == B_OK) {
textWidth += textView->StringWidth(text);
}
}
be_clipboard->Unlock();
}
ASSERT(view->ActiveTextWidget() != NULL);
float maxWidth = view->ActiveTextWidget()->MaxWidth();
bool tooWide = textWidth > maxWidth;
if (tooWide) {
float oldWidth = textView->Bounds().Width();
float newWidth = maxWidth;
float right = oldWidth - newWidth;
if (textView->Alignment() == B_ALIGN_CENTER)
scrollView->MoveBy(roundf(right / 2), 0);
else if (textView->Alignment() == B_ALIGN_RIGHT)
scrollView->MoveBy(right, 0);
float grow = newWidth - oldWidth;
scrollView->ResizeBy(grow, 0);
}
textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
}
}
return B_DISPATCH_MESSAGE;
}
void
BTextWidget::StartEdit(BRect bounds, BPoseView* view, BPose* pose)
{
ASSERT(view != NULL);
ASSERT(view->Window() != NULL);
view->SetTextWidgetToCheck(NULL, this);
if (!IsEditable() || IsActive())
return;
if (view->IsDragging())
return;
view->SetActiveTextWidget(this);
rgb_color initialTextColor;
if (view->IsDesktopView())
initialTextColor = InvertColor(view->HighColor());
else
initialTextColor = view->HighColor();
BRect rect(bounds);
rect.OffsetBy(view->ViewMode() == kListMode ? -2 : 0, -2);
BTextView* textView = new BTextView(rect, "WidgetTextView", rect, be_plain_font,
&initialTextColor, B_FOLLOW_ALL, B_WILL_DRAW);
textView->SetWordWrap(false);
textView->SetInsets(2, 2, 2, 2);
DisallowMetaKeys(textView);
fText->SetupEditing(textView);
if (view->IsDesktopView()) {
rgb_color backColor = view->HighColor();
rgb_color textColor = InvertColor(backColor);
backColor = tint_color(backColor,
view->SelectedVolumeIsReadOnly() ? ReadOnlyTint(backColor) : B_NO_TINT);
textView->SetViewColor(backColor);
textView->SetLowColor(backColor);
textView->SetHighColor(textColor);
} else {
textView->SetViewUIColor(view->ViewUIColor());
textView->SetLowUIColor(view->LowUIColor());
textView->SetHighUIColor(view->HighUIColor());
}
if (view->SelectedVolumeIsReadOnly()) {
textView->MakeEditable(false);
textView->MakeSelectable(true);
}
textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewKeyDownFilter));
if (!view->SelectedVolumeIsReadOnly())
textView->AddFilter(new BMessageFilter(B_PASTE, TextViewPasteFilter));
rect.right = rect.left + textView->LineWidth();
rect.bottom = rect.top + textView->LineHeight() - 1 + 4;
if (view->ViewMode() == kListMode) {
BColumn* column = view->ColumnFor(fAttrHash);
ASSERT(column != NULL);
fMaxWidth = column->Width();
} else {
fMaxWidth = textView->StringWidth("M") * 30;
if (textView->LineWidth() > fMaxWidth
|| view->ViewMode() == kMiniIconMode) {
rect.OffsetBy(-2, 0);
}
}
textView->MoveTo(rect.LeftTop());
textView->ResizeTo(std::min(fMaxWidth, rect.Width()), rect.Height());
textView->SetTextRect(rect);
switch (view->ViewMode()) {
case kIconMode:
textView->SetAlignment(B_ALIGN_CENTER);
break;
case kMiniIconMode:
textView->SetAlignment(B_ALIGN_LEFT);
break;
case kListMode:
textView->SetAlignment(fAlignment);
break;
}
BScrollView* scrollView
= new BScrollView("BorderView", textView, 0, 0, false, false, B_PLAIN_BORDER);
view->AddChild(scrollView);
bool tooWide = textView->TextRect().Width() > fMaxWidth;
textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView);
view->SetActivePose(pose);
SetActive(true);
textView->SelectAll();
textView->ScrollToSelection();
textView->MakeFocus();
SetVisible(false);
view->Window()->UpdateIfNeeded();
}
void
BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView* view,
BPose* pose, int32 poseIndex)
{
view->SetActiveTextWidget(NULL);
BView* scrollView = view->FindView("BorderView");
ASSERT(scrollView != NULL);
if (scrollView == NULL)
return;
BTextView* textView = dynamic_cast<BTextView*>(scrollView->FindView("WidgetTextView"));
ASSERT(textView != NULL);
if (textView == NULL)
return;
BColumn* column = view->ColumnFor(fAttrHash);
ASSERT(column != NULL);
if (column == NULL)
return;
if (saveChanges && fText->CommitEditedText(textView)) {
view->CheckPoseSortOrder(pose, poseIndex);
}
SetVisible(true);
view->Invalidate(ColumnRect(poseLoc, column, view));
scrollView->RemoveSelf();
delete scrollView;
ASSERT(view->Window() != NULL);
view->Window()->UpdateIfNeeded();
view->MakeFocus();
SetActive(false);
}
void
BTextWidget::CheckAndUpdate(BPoint loc, const BColumn* column, BPoseView* view, bool visible)
{
BRect oldRect;
if (view->ViewMode() != kListMode)
oldRect = CalcOldRect(loc, column, view);
if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view) && visible) {
BRect invalidRect(ColumnRect(loc, column, view));
if (view->ViewMode() != kListMode)
invalidRect = invalidRect | oldRect;
view->Invalidate(invalidRect);
}
}
void
BTextWidget::SelectAll(BPoseView* view)
{
BTextView* text = dynamic_cast<BTextView*>(view->FindView("WidgetTextView"));
if (text != NULL)
text->SelectAll();
}
void
BTextWidget::Draw(BRect eraseRect, BRect textRect, BPoseView* view, BView* drawView,
bool selected, uint32 clipboardMode, BPoint offset)
{
ASSERT(view != NULL);
ASSERT(view->Window() != NULL);
ASSERT(drawView != NULL);
textRect.OffsetBy(offset);
BRegion textRegion(textRect);
drawView->ConstrainClippingRegion(&textRegion);
bool direct = drawView == view;
if (selected) {
drawView->SetDrawingMode(B_OP_COPY);
BRect invertRect(textRect);
invertRect.left = ceilf(invertRect.left);
invertRect.top = ceilf(invertRect.top);
invertRect.right = floorf(invertRect.right);
invertRect.bottom = floorf(invertRect.bottom);
drawView->FillRect(invertRect, B_SOLID_LOW);
drawView->SetHighColor(InvertColorSmart(drawView->LowColor()));
} else if (clipboardMode == kMoveSelectionTo) {
drawView->SetDrawingMode(B_OP_ALPHA);
drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
uint8 alpha = 128;
if (drawView->LowColor().IsLight())
drawView->SetHighColor(0, 0, 0, alpha);
else
drawView->SetHighColor(255, 255, 255, alpha);
} else {
drawView->SetDrawingMode(B_OP_OVER);
if (view->IsDesktopView())
drawView->SetHighColor(view->HighColor());
else
drawView->SetHighUIColor(view->HighUIColor());
}
float decenderHeight = roundf(view->FontInfo().descent);
BPoint location(textRect.left, textRect.bottom - decenderHeight);
const char* fittingText = fText->FittingText(view);
if (!selected && direct && view->WidgetTextOutline()) {
if (direct && clipboardMode != kMoveSelectionTo) {
drawView->SetDrawingMode(B_OP_ALPHA);
drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
}
BFont font;
drawView->GetFont(&font);
rgb_color textColor = drawView->HighColor();
if (textColor.IsDark()) {
rgb_color glowColor = ui_color(B_SHINE_COLOR);
font.SetFalseBoldWidth(2.0);
drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
glowColor.alpha = 30;
drawView->SetHighColor(glowColor);
drawView->DrawString(fittingText, location);
font.SetFalseBoldWidth(1.0);
drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
glowColor.alpha = 65;
drawView->SetHighColor(glowColor);
drawView->DrawString(fittingText, location);
font.SetFalseBoldWidth(0.0);
drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
} else {
rgb_color outlineColor = kBlack;
font.SetFalseBoldWidth(1.0);
drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
outlineColor.alpha = 30;
drawView->SetHighColor(outlineColor);
drawView->DrawString(fittingText, location);
font.SetFalseBoldWidth(0.0);
drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH);
outlineColor.alpha = 200;
drawView->SetHighColor(outlineColor);
drawView->DrawString(fittingText, location + BPoint(1, 1));
}
if (direct && clipboardMode != kMoveSelectionTo)
drawView->SetDrawingMode(B_OP_OVER);
drawView->SetHighColor(textColor);
}
drawView->DrawString(fittingText, location);
if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) {
if (direct && clipboardMode != kMoveSelectionTo) {
rgb_color underlineColor = drawView->HighColor();
underlineColor.alpha = 180;
drawView->SetDrawingMode(B_OP_ALPHA);
drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
drawView->SetHighColor(underlineColor);
}
BRect lineRect(textRect.OffsetByCopy(0, decenderHeight > 2 ? -(decenderHeight - 2) : 0));
lineRect.InsetBy(roundf(textRect.Width() - fText->Width(view)), 0);
drawView->StrokeLine(lineRect.LeftBottom(), lineRect.RightBottom(), B_MIXED_COLORS);
if (direct && clipboardMode != kMoveSelectionTo)
drawView->SetDrawingMode(B_OP_OVER);
}
drawView->ConstrainClippingRegion(NULL);
}