Files
haiku-beta6/src/kits/interface/TextControl.cpp
T
Karsten Heimrich 1bdc2a5203 * ResizeToPreferred und GetPreferredSize now behave more closely to R5
this fixes mostly all windows containing TextControls in ArtPaint and
  should fix also ticket #2543

  ArtPaint was calling the BTextControl ctor with an empty rect, followed
  by ResizeToPreferred, which made in the best case the TextInput visible
  but it did not take the label into account. Should help more R5 apps.

  Some more tests on R5 have shown that even if you pass an extra large
  width, a call to ResizeToPreferred will not respect the passed width.
  Also the _BTextInput_ seems to grow with the length of the passed label.



git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@27330 a95241bf-73f2-0310-859d-f6bbb57e9c96
2008-09-04 22:05:40 +00:00

970 lines
18 KiB
C++

/*
* Copyright 2001-2008, Haiku Inc.
* Distributed under the terms of the MIT License.
*
* Authors:
* Frans van Nispen ([email protected])
* Stephan Aßmus <[email protected]>
* Ingo Weinhold <[email protected]>
*/
/*! BTextControl displays text that can act like a control. */
#include <stdio.h>
#include <AbstractLayoutItem.h>
#include <LayoutUtils.h>
#include <Message.h>
#include <Region.h>
#include <TextControl.h>
#include <Window.h>
#include "TextInput.h"
class BTextControl::LabelLayoutItem : public BAbstractLayoutItem {
public:
LabelLayoutItem(BTextControl* parent);
virtual bool IsVisible();
virtual void SetVisible(bool visible);
virtual BRect Frame();
virtual void SetFrame(BRect frame);
virtual BView* View();
virtual BSize BaseMinSize();
virtual BSize BaseMaxSize();
virtual BSize BasePreferredSize();
virtual BAlignment BaseAlignment();
private:
BTextControl* fParent;
BRect fFrame;
};
class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem {
public:
TextViewLayoutItem(BTextControl* parent);
virtual bool IsVisible();
virtual void SetVisible(bool visible);
virtual BRect Frame();
virtual void SetFrame(BRect frame);
virtual BView* View();
virtual BSize BaseMinSize();
virtual BSize BaseMaxSize();
virtual BSize BasePreferredSize();
virtual BAlignment BaseAlignment();
private:
BTextControl* fParent;
BRect fFrame;
};
// #pragma mark -
BTextControl::BTextControl(BRect frame, const char* name, const char* label,
const char* text, BMessage* message, uint32 mask, uint32 flags)
: BControl(frame, name, label, message, mask, flags | B_FRAME_EVENTS)
{
_InitData(label, text);
_ValidateLayout();
}
BTextControl::BTextControl(const char* name, const char* label,
const char* text, BMessage* message, uint32 flags)
: BControl(BRect(0, 0, -1, -1), name, label, message, B_FOLLOW_NONE,
flags | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT)
{
_InitData(label, text);
_ValidateLayout();
}
BTextControl::BTextControl(const char* label, const char* text,
BMessage* message)
: BControl(BRect(0, 0, -1, -1), NULL, label, message, B_FOLLOW_NONE,
B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT)
{
_InitData(label, text);
_ValidateLayout();
}
BTextControl::~BTextControl()
{
SetModificationMessage(NULL);
}
BTextControl::BTextControl(BMessage* archive)
: BControl(archive)
{
_InitData(Label(), NULL, archive);
int32 labelAlignment = B_ALIGN_LEFT;
int32 textAlignment = B_ALIGN_LEFT;
if (archive->HasInt32("_a_label"))
archive->FindInt32("_a_label", &labelAlignment);
if (archive->HasInt32("_a_text"))
archive->FindInt32("_a_text", &textAlignment);
SetAlignment((alignment)labelAlignment, (alignment)textAlignment);
if (archive->HasFloat("_divide"))
archive->FindFloat("_divide", &fDivider);
if (archive->HasMessage("_mod_msg")) {
BMessage* message = new BMessage;
archive->FindMessage("_mod_msg", message);
SetModificationMessage(message);
}
}
BArchivable*
BTextControl::Instantiate(BMessage* archive)
{
if (validate_instantiation(archive, "BTextControl"))
return new BTextControl(archive);
return NULL;
}
status_t
BTextControl::Archive(BMessage *data, bool deep) const
{
status_t ret = BControl::Archive(data, deep);
alignment labelAlignment, textAlignment;
GetAlignment(&labelAlignment, &textAlignment);
if (ret == B_OK)
ret = data->AddInt32("_a_label", labelAlignment);
if (ret == B_OK)
ret = data->AddInt32("_a_text", textAlignment);
if (ret == B_OK)
ret = data->AddFloat("_divide", Divider());
if (ModificationMessage() && (ret == B_OK))
ret = data->AddMessage("_mod_msg", ModificationMessage());
return ret;
}
void
BTextControl::SetText(const char *text)
{
if (InvokeKind() != B_CONTROL_INVOKED)
return;
fText->SetText(text);
if (IsFocus())
fText->SetInitialText();
fText->Invalidate();
}
const char *
BTextControl::Text() const
{
return fText->Text();
}
void
BTextControl::SetValue(int32 value)
{
BControl::SetValue(value);
}
status_t
BTextControl::Invoke(BMessage *message)
{
return BControl::Invoke(message);
}
BTextView *
BTextControl::TextView() const
{
return fText;
}
void
BTextControl::SetModificationMessage(BMessage *message)
{
delete fModificationMessage;
fModificationMessage = message;
}
BMessage *
BTextControl::ModificationMessage() const
{
return fModificationMessage;
}
void
BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment)
{
fText->SetAlignment(textAlignment);
fText->AlignTextRect();
if (fLabelAlign != labelAlignment) {
fLabelAlign = labelAlignment;
Invalidate();
}
}
void
BTextControl::GetAlignment(alignment* _label, alignment* _text) const
{
if (_label)
*_label = fLabelAlign;
if (_text)
*_text = fText->Alignment();
}
void
BTextControl::SetDivider(float dividingLine)
{
fDivider = floorf(dividingLine + 0.5);
_LayoutTextView();
if (Window()) {
fText->Invalidate();
Invalidate();
}
}
float
BTextControl::Divider() const
{
return fDivider;
}
void
BTextControl::Draw(BRect updateRect)
{
rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR);
rgb_color lighten1 = tint_color(noTint, B_LIGHTEN_1_TINT);
rgb_color lighten2 = tint_color(noTint, B_LIGHTEN_2_TINT);
rgb_color lightenMax = tint_color(noTint, B_LIGHTEN_MAX_TINT);
rgb_color darken1 = tint_color(noTint, B_DARKEN_1_TINT);
rgb_color darken2 = tint_color(noTint, B_DARKEN_2_TINT);
rgb_color darken4 = tint_color(noTint, B_DARKEN_4_TINT);
rgb_color navigationColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR);
bool enabled = IsEnabled();
bool active = false;
if (fText->IsFocus() && Window()->IsActive())
active = true;
// outer bevel
BRect rect = fText->Frame();
rect.InsetBy(-2, -2);
if (enabled)
SetHighColor(darken1);
else
SetHighColor(noTint);
StrokeLine(rect.LeftBottom(), rect.LeftTop());
StrokeLine(rect.RightTop());
if (enabled)
SetHighColor(lighten2);
else
SetHighColor(lighten1);
StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
StrokeLine(BPoint(rect.right, rect.top + 1.0f), rect.RightBottom());
// inner bevel
rect.InsetBy(1.0f, 1.0f);
if (active) {
SetHighColor(navigationColor);
StrokeRect(rect);
} else {
if (enabled)
SetHighColor(darken4);
else
SetHighColor(darken2);
StrokeLine(rect.LeftTop(), rect.LeftBottom());
StrokeLine(rect.LeftTop(), rect.RightTop());
SetHighColor(noTint);
StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom());
StrokeLine(BPoint(rect.right, rect.top + 1.0f));
}
// label
if (Label()) {
font_height fontHeight;
GetFontHeight(&fontHeight);
float y = Bounds().top + (Bounds().Height() + 1 - fontHeight.ascent
- fontHeight.descent) / 2 + fontHeight.ascent;
float x;
float labelWidth = StringWidth(Label());
switch (fLabelAlign) {
case B_ALIGN_RIGHT:
x = fDivider - labelWidth - 3.0;
break;
case B_ALIGN_CENTER:
x = fDivider - labelWidth / 2.0;
break;
default:
x = 0.0;
break;
}
BRect labelArea(x, Bounds().top, x + labelWidth, Bounds().bottom);
if (x < fDivider && updateRect.Intersects(labelArea)) {
labelArea.right = fText->Frame().left - 3;
BRegion clipRegion(labelArea);
ConstrainClippingRegion(&clipRegion);
SetHighColor(IsEnabled() ? ui_color(B_CONTROL_TEXT_COLOR)
: tint_color(noTint, B_DISABLED_LABEL_TINT));
DrawString(Label(), BPoint(x, y));
}
}
}
void
BTextControl::MouseDown(BPoint where)
{
if (!fText->IsFocus()) {
fText->MakeFocus(true);
}
}
void
BTextControl::AttachedToWindow()
{
BControl::AttachedToWindow();
_UpdateTextViewColors(IsEnabled());
fText->MakeEditable(IsEnabled());
}
void
BTextControl::MakeFocus(bool state)
{
if (state != fText->IsFocus()) {
fText->MakeFocus(state);
if (state)
fText->SelectAll();
}
}
void
BTextControl::SetEnabled(bool enabled)
{
if (IsEnabled() == enabled)
return;
if (Window()) {
fText->MakeEditable(enabled);
_UpdateTextViewColors(enabled);
fText->Invalidate();
Window()->UpdateIfNeeded();
}
BControl::SetEnabled(enabled);
}
void
BTextControl::GetPreferredSize(float *_width, float *_height)
{
if (_height) {
// we need enough space for the label and the child text view
font_height fontHeight;
GetFontHeight(&fontHeight);
float labelHeight = ceil(fontHeight.ascent + fontHeight.descent
+ fontHeight.leading);
float textHeight = ceilf(fText->LineHeight(0) + 1.0) + 4.0;
*_height = max_c(labelHeight, textHeight);
}
if (_width) {
*_width = Bounds().Width();
const char* label = Label();
if (label) {
float width = ceilf(StringWidth(label));
*_width = (width * 1.3) + width + 4.0;
}
}
}
void
BTextControl::ResizeToPreferred()
{
BView::ResizeToPreferred();
fDivider = 0.0;
const char* label = Label();
if (label)
fDivider = ceil(StringWidth(label)) + 2.0;
_LayoutTextView();
}
void
BTextControl::SetFlags(uint32 flags)
{
if (!fSkipSetFlags) {
// If the textview is navigable, set it to not navigable if needed
// Else if it is not navigable, set it to navigable if needed
if (fText->Flags() & B_NAVIGABLE) {
if (!(flags & B_NAVIGABLE))
fText->SetFlags(fText->Flags() & ~B_NAVIGABLE);
} else {
if (flags & B_NAVIGABLE)
fText->SetFlags(fText->Flags() | B_NAVIGABLE);
}
// Don't make this one navigable
flags &= ~B_NAVIGABLE;
}
BView::SetFlags(flags);
}
void
BTextControl::MessageReceived(BMessage *msg)
{
switch(msg->what) {
case B_SET_PROPERTY:
case B_GET_PROPERTY:
// TODO
break;
default:
BControl::MessageReceived(msg);
break;
}
}
BHandler *
BTextControl::ResolveSpecifier(BMessage *msg, int32 index,
BMessage *specifier, int32 form,
const char *property)
{
/*
BPropertyInfo propInfo(prop_list);
BHandler *target = NULL;
if (propInfo.FindMatch(message, 0, specifier, what, property) < B_OK)
return BControl::ResolveSpecifier(message, index, specifier, what,
property);
else
return this;
*/
return BControl::ResolveSpecifier(msg, index, specifier, form, property);
}
status_t
BTextControl::GetSupportedSuites(BMessage *data)
{
return BControl::GetSupportedSuites(data);
}
void
BTextControl::MouseUp(BPoint pt)
{
BControl::MouseUp(pt);
}
void
BTextControl::MouseMoved(BPoint pt, uint32 code, const BMessage *msg)
{
BControl::MouseMoved(pt, code, msg);
}
void
BTextControl::DetachedFromWindow()
{
BControl::DetachedFromWindow();
}
void
BTextControl::AllAttached()
{
BControl::AllAttached();
}
void
BTextControl::AllDetached()
{
BControl::AllDetached();
}
void
BTextControl::FrameMoved(BPoint newPosition)
{
BControl::FrameMoved(newPosition);
}
void
BTextControl::FrameResized(float width, float height)
{
BControl::FrameResized(width, height);
// changes in width
BRect bounds = Bounds();
const float border = 2.0f;
if (bounds.Width() > fPreviousWidth) {
// invalidate the region between the old and the new right border
BRect rect = bounds;
rect.left += fPreviousWidth - border;
rect.right--;
Invalidate(rect);
} else if (bounds.Width() < fPreviousWidth) {
// invalidate the region of the new right border
BRect rect = bounds;
rect.left = rect.right - border;
Invalidate(rect);
}
// changes in height
if (bounds.Height() > fPreviousHeight) {
// invalidate the region between the old and the new bottom border
BRect rect = bounds;
rect.top += fPreviousHeight - border;
rect.bottom--;
Invalidate(rect);
} else if (bounds.Height() < fPreviousHeight) {
// invalidate the region of the new bottom border
BRect rect = bounds;
rect.top = rect.bottom - border;
Invalidate(rect);
}
fPreviousWidth = uint16(bounds.Width());
fPreviousHeight = uint16(bounds.Height());
}
void
BTextControl::WindowActivated(bool active)
{
if (fText->IsFocus()) {
// invalidate to remove/show focus indication
BRect rect = fText->Frame();
rect.InsetBy(-1, -1);
Invalidate(rect);
// help out embedded text view which doesn't
// get notified of this
fText->Invalidate();
}
}
status_t
BTextControl::Perform(perform_code d, void *arg)
{
return BControl::Perform(d, arg);
}
BLayoutItem*
BTextControl::CreateLabelLayoutItem()
{
if (!fLabelLayoutItem)
fLabelLayoutItem = new LabelLayoutItem(this);
return fLabelLayoutItem;
}
BLayoutItem*
BTextControl::CreateTextViewLayoutItem()
{
if (!fTextViewLayoutItem)
fTextViewLayoutItem = new TextViewLayoutItem(this);
return fTextViewLayoutItem;
}
void BTextControl::_ReservedTextControl1() {}
void BTextControl::_ReservedTextControl2() {}
void BTextControl::_ReservedTextControl3() {}
void BTextControl::_ReservedTextControl4() {}
BTextControl &
BTextControl::operator=(const BTextControl&)
{
return *this;
}
void
BTextControl::_UpdateTextViewColors(bool enabled)
{
rgb_color textColor;
rgb_color color;
BFont font;
fText->GetFontAndColor(0, &font);
if (enabled)
textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
else {
textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_DISABLED_LABEL_TINT);
}
fText->SetFontAndColor(&font, B_FONT_ALL, &textColor);
if (enabled) {
color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
} else {
color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
B_LIGHTEN_2_TINT);
}
fText->SetViewColor(color);
fText->SetLowColor(color);
}
void
BTextControl::_CommitValue()
{
}
void
BTextControl::_InitData(const char* label, const char* initialText,
BMessage* archive)
{
BRect bounds(Bounds());
fText = NULL;
fModificationMessage = NULL;
fLabelAlign = B_ALIGN_LEFT;
fDivider = 0.0f;
fPreviousWidth = bounds.Width();
fPreviousHeight = bounds.Height();
fLabelLayoutItem = NULL;
fTextViewLayoutItem = NULL;
fSkipSetFlags = false;
int32 flags = 0;
BFont font(be_plain_font);
if (!archive || !archive->HasString("_fname"))
flags |= B_FONT_FAMILY_AND_STYLE;
if (!archive || !archive->HasFloat("_fflt"))
flags |= B_FONT_SIZE;
if (flags != 0)
SetFont(&font, flags);
if (label)
fDivider = floorf(bounds.Width() / 2.0f);
uint32 navigableFlags = Flags() & B_NAVIGABLE;
if (navigableFlags != 0) {
fSkipSetFlags = true;
SetFlags(Flags() & ~B_NAVIGABLE);
fSkipSetFlags = false;
}
if (archive)
fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_"));
if (fText == NULL) {
BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
// we are stroking the frame around the text view, which
// is 2 pixels wide
frame.InsetBy(2.0, 3.0);
BRect textRect(frame.OffsetToCopy(B_ORIGIN));
fText = new BPrivate::_BTextInput_(frame, textRect,
B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
B_WILL_DRAW | B_FRAME_EVENTS | navigableFlags);
AddChild(fText);
SetText(initialText);
fText->SetAlignment(B_ALIGN_LEFT);
fText->AlignTextRect();
}
}
void
BTextControl::_ValidateLayout()
{
float width, height;
BTextControl::GetPreferredSize(&width, &height);
ResizeTo(width, height);
_LayoutTextView();
fPreviousHeight = Bounds().Height();
}
void
BTextControl::_LayoutTextView()
{
BRect frame = Bounds();
frame.left = fDivider;
// we are stroking the frame around the text view, which
// is 2 pixels wide
frame.InsetBy(2.0, 2.0);
fText->MoveTo(frame.left, frame.top);
fText->ResizeTo(frame.Width(), frame.Height());
fText->AlignTextRect();
}
void
BTextControl::_UpdateFrame()
{
if (fLabelLayoutItem && fTextViewLayoutItem) {
BRect labelFrame = fLabelLayoutItem->Frame();
BRect textFrame = fTextViewLayoutItem->Frame();
MoveTo(labelFrame.left, labelFrame.top);
ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left,
textFrame.top + textFrame.Height() - labelFrame.top);
SetDivider(textFrame.left - labelFrame.left);
}
}
// #pragma mark -
BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent)
: fParent(parent),
fFrame()
{
}
bool
BTextControl::LabelLayoutItem::IsVisible()
{
return !fParent->IsHidden(fParent);
}
void
BTextControl::LabelLayoutItem::SetVisible(bool visible)
{
// not allowed
}
BRect
BTextControl::LabelLayoutItem::Frame()
{
return fFrame;
}
void
BTextControl::LabelLayoutItem::SetFrame(BRect frame)
{
fFrame = frame;
fParent->_UpdateFrame();
}
BView*
BTextControl::LabelLayoutItem::View()
{
return fParent;
}
BSize
BTextControl::LabelLayoutItem::BaseMinSize()
{
// TODO: Cache the info. Might be too expensive for this call.
const char* label = fParent->Label();
if (!label)
return BSize(-1, -1);
BSize size;
fParent->GetPreferredSize(NULL, &size.height);
size.width = fParent->StringWidth(label) + 2 * 3 - 1;
return size;
}
BSize
BTextControl::LabelLayoutItem::BaseMaxSize()
{
return BaseMinSize();
}
BSize
BTextControl::LabelLayoutItem::BasePreferredSize()
{
return BaseMinSize();
}
BAlignment
BTextControl::LabelLayoutItem::BaseAlignment()
{
return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
}
// #pragma mark -
BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
: fParent(parent),
fFrame()
{
}
bool
BTextControl::TextViewLayoutItem::IsVisible()
{
return !fParent->IsHidden(fParent);
}
void
BTextControl::TextViewLayoutItem::SetVisible(bool visible)
{
// not allowed
}
BRect
BTextControl::TextViewLayoutItem::Frame()
{
return fFrame;
}
void
BTextControl::TextViewLayoutItem::SetFrame(BRect frame)
{
fFrame = frame;
fParent->_UpdateFrame();
}
BView*
BTextControl::TextViewLayoutItem::View()
{
return fParent;
}
BSize
BTextControl::TextViewLayoutItem::BaseMinSize()
{
// TODO: Cache the info. Might be too expensive for this call.
BSize size;
fParent->GetPreferredSize(NULL, &size.height);
// mmh, some arbitrary minimal width
size.width = 30;
return size;
}
BSize
BTextControl::TextViewLayoutItem::BaseMaxSize()
{
BSize size(BaseMinSize());
size.width = B_SIZE_UNLIMITED;
return size;
}
BSize
BTextControl::TextViewLayoutItem::BasePreferredSize()
{
BSize size(BaseMinSize());
// puh, no idea...
size.width = 100;
return size;
}
BAlignment
BTextControl::TextViewLayoutItem::BaseAlignment()
{
return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
}