#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() + view->FontHeight()) / 2.f);
rect.top = rect.bottom - view->FontHeight();
return rect;
}
BRect
BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn* column,
const BPoseView* view, float textWidth)
{
BRect rect;
float viewWidth;
poseLoc.x = roundf(poseLoc.x);
poseLoc.y = roundf(poseLoc.y);
if (view->ViewMode() == kListMode) {
viewWidth = roundf(std::min(column->Width(), textWidth));
poseLoc.x += column->Offset();
switch (fAlignment) {
case B_ALIGN_LEFT:
rect.left = poseLoc.x;
rect.right = rect.left + viewWidth - 1;
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 - 1;
break;
case B_ALIGN_RIGHT:
rect.right = poseLoc.x + column->Width();
rect.left = rect.right - viewWidth + 1;
if (rect.left < 0)
rect.left = 0;
break;
default:
TRESPASS();
break;
}
rect.bottom = poseLoc.y + roundf((view->ListElemHeight() + view->FontHeight()) / 2.f);
rect.top = rect.bottom - view->FontHeight() + 1;
} else {
float iconSize = (float)view->IconSizeInt();
if (view->ViewMode() == kIconMode) {
viewWidth = roundf(std::min(view->StringWidth("M") * 30, textWidth));
rect.left = poseLoc.x + roundf((iconSize - viewWidth) / 2.f);
rect.bottom = poseLoc.y + ceilf(view->IconPoseHeight());
} else {
viewWidth = roundf(textWidth);
rect.left = poseLoc.x + iconSize + kMiniIconSeparator;
rect.bottom = poseLoc.y + roundf((iconSize + view->FontHeight()) / 2.f);
}
rect.top = rect.bottom - view->FontHeight() + 1;
rect.right = rect.left + viewWidth - 1;
}
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.OffsetTo(roundf(rect.left), roundf(rect.top));
BTextView* textView = new BTextView(rect.InsetByCopy(-2, -2), "WidgetTextView",
rect.OffsetToCopy(2, 2), 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() - 1;
rect.bottom = rect.top + textView->LineHeight() - 1;
if (view->ViewMode() == kListMode) {
BColumn* column = view->ColumnFor(fAttrHash);
ASSERT(column != NULL);
fMaxWidth = column->Width();
} else {
fMaxWidth = textView->StringWidth("M") * 30;
if (view->ViewMode() == kIconMode && rect.Width() > bounds.Width()) {
float newWidth = std::min(fMaxWidth, rect.Width());
rect.OffsetBy(roundf((bounds.Width() - newWidth) / 2), 0);
textView->MoveTo(rect.left - 2, rect.top - 2);
}
}
textView->ResizeTo(std::min(fMaxWidth, rect.Width()) + 4, rect.Height() + 4);
textView->SetTextRect(rect.OffsetToCopy(2, 2));
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;
bool dragging = false;
if (!direct && view->Window() != NULL && view->Window()->CurrentMessage() != NULL)
dragging = view->Window()->CurrentMessage()->what == kMsgMouseDragged;
bool drawOutlines = view->WidgetTextOutline() && !selected && (direct || dragging);
bool drawCut = clipboardMode == kMoveSelectionTo;
if (selected) {
if (dragging) {
drawView->SetDrawingMode(B_OP_ALPHA);
drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
} else {
drawView->SetDrawingMode(B_OP_COPY);
}
drawView->FillRect(textRect, B_SOLID_LOW);
drawView->SetHighColor(InvertColorSmart(drawView->LowColor()));
} else {
if (view->IsDesktopView())
drawView->SetHighColor(view->HighColor());
else
drawView->SetHighUIColor(view->HighUIColor());
}
if (drawOutlines || dragging || (direct && drawCut)) {
drawView->SetDrawingMode(B_OP_ALPHA);
drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
} else {
drawView->SetDrawingMode(B_OP_OVER);
}
BPoint location(textRect.left, roundf(textRect.top) + view->FontInfo().ascent + 1);
const char* fittingText = fText->FittingText(view);
if (drawOutlines) {
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));
}
drawView->SetHighColor(textColor);
}
drawView->DrawString(fittingText, location);
if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) {
BRect lineRect(textRect);
lineRect.OffsetTo(location.x, std::min(location.y + 2, textRect.bottom));
lineRect.InsetBy(roundf(textRect.Width() - fText->Width(view)), 0);
drawView->StrokeLine(lineRect.LeftTop(), lineRect.RightTop(), B_MIXED_COLORS);
}
drawView->ConstrainClippingRegion(NULL);
}