* Implemented a lot of layout related methods and resolved a few TODOs by

doing a lot of these things the same way as BMenuField are already doing.
  Perhaps a private helper class could be refactored from these two controls
  to avoid duplicating a lot of this code, although there are a few subtle
  differences here and there.

These changes make a BTextControl behave properly in the layout management
frame work, in case CreateLabelLayoutItem() and CreateTextViewLayoutItem()
are _not_ used to layout the BTextControl.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@27584 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus
2008-09-16 10:50:31 +00:00
parent 08edea662b
commit 349c911ee9
4 changed files with 288 additions and 82 deletions
+21 -12
View File
@@ -80,16 +80,26 @@ public:
virtual status_t GetSupportedSuites(BMessage* data); virtual status_t GetSupportedSuites(BMessage* data);
virtual void SetFlags(uint32 flags); virtual void SetFlags(uint32 flags);
virtual BSize MinSize();
virtual BSize MaxSize();
virtual BSize PreferredSize();
virtual void InvalidateLayout(bool descendants = false);
BLayoutItem* CreateLabelLayoutItem(); BLayoutItem* CreateLabelLayoutItem();
BLayoutItem* CreateTextViewLayoutItem(); BLayoutItem* CreateTextViewLayoutItem();
private: protected:
class LabelLayoutItem; virtual void DoLayout();
class TextViewLayoutItem;
friend class _BTextInput_; private:
friend class LabelLayoutItem; class LabelLayoutItem;
friend class TextViewLayoutItem; class TextViewLayoutItem;
struct LayoutData;
friend class _BTextInput_;
friend class LabelLayoutItem;
friend class TextViewLayoutItem;
virtual status_t Perform(perform_code d, void* arg); virtual status_t Perform(perform_code d, void* arg);
@@ -108,18 +118,17 @@ private:
void _LayoutTextView(); void _LayoutTextView();
void _UpdateFrame(); void _UpdateFrame();
void _ValidateLayoutData();
private: private:
BPrivate::_BTextInput_* fText; BPrivate::_BTextInput_* fText;
char* fLabel;
BMessage* fModificationMessage; BMessage* fModificationMessage;
alignment fLabelAlign; alignment fLabelAlign;
float fDivider; float fDivider;
float fPreviousWidth;
float fPreviousHeight;
BLayoutItem* fLabelLayoutItem;
BLayoutItem* fTextViewLayoutItem;
uint32 _reserved[5]; LayoutData* fLayoutData;
uint32 _reserved[9];
}; };
#endif // _TEXT_CONTROL_H #endif // _TEXT_CONTROL_H
+254 -70
View File
@@ -27,7 +27,7 @@
#ifdef TRACE_TEXT_CONTROL #ifdef TRACE_TEXT_CONTROL
# include <FunctionTracer.h> # include <FunctionTracer.h>
static int32 sFunctionDepth = -1; static int32 sFunctionDepth = -1;
# define CALLED(x...) FunctionTracer _ft("BMenuField", __FUNCTION__, \ # define CALLED(x...) FunctionTracer _ft("BTextControl", __FUNCTION__, \
sFunctionDepth) sFunctionDepth)
# define TRACE(x...) { BString _to; \ # define TRACE(x...) { BString _to; \
_to.Append(' ', (sFunctionDepth + 1) * 2); \ _to.Append(' ', (sFunctionDepth + 1) * 2); \
@@ -84,9 +84,36 @@ private:
}; };
struct BTextControl::LayoutData {
LayoutData(float width, float height)
: label_layout_item(NULL),
text_view_layout_item(NULL),
previous_width(width),
previous_height(height),
valid(false)
{
}
LabelLayoutItem* label_layout_item;
TextViewLayoutItem* text_view_layout_item;
float previous_width; // used in FrameResized() for
float previous_height; // invalidation
font_height font_info;
float label_width;
float label_height;
BSize min;
BSize text_view_min;
bool valid;
};
// #pragma mark - // #pragma mark -
static const int32 kFrameMargin = 2;
static const int32 kLabelInputSpacing = 3;
BTextControl::BTextControl(BRect frame, const char* name, const char* label, BTextControl::BTextControl(BRect frame, const char* name, const char* label,
const char* text, BMessage* message, uint32 mask, uint32 flags) const char* text, BMessage* message, uint32 mask, uint32 flags)
: BControl(frame, name, label, message, mask, flags | B_FRAME_EVENTS) : BControl(frame, name, label, message, mask, flags | B_FRAME_EVENTS)
@@ -347,8 +374,8 @@ BTextControl::Draw(BRect updateRect)
// label // label
if (Label()) { if (Label()) {
font_height fontHeight; _ValidateLayoutData();
GetFontHeight(&fontHeight); font_height& fontHeight = fLayoutData->font_info;
float y = Bounds().top + (Bounds().Height() + 1 - fontHeight.ascent float y = Bounds().top + (Bounds().Height() + 1 - fontHeight.ascent
- fontHeight.descent) / 2 + fontHeight.ascent; - fontHeight.descent) / 2 + fontHeight.ascent;
@@ -357,7 +384,7 @@ BTextControl::Draw(BRect updateRect)
float labelWidth = StringWidth(Label()); float labelWidth = StringWidth(Label());
switch (fLabelAlign) { switch (fLabelAlign) {
case B_ALIGN_RIGHT: case B_ALIGN_RIGHT:
x = fDivider - labelWidth - 3.0; x = fDivider - labelWidth - kLabelInputSpacing;
break; break;
case B_ALIGN_CENTER: case B_ALIGN_CENTER:
@@ -371,7 +398,7 @@ BTextControl::Draw(BRect updateRect)
BRect labelArea(x, Bounds().top, x + labelWidth, Bounds().bottom); BRect labelArea(x, Bounds().top, x + labelWidth, Bounds().bottom);
if (x < fDivider && updateRect.Intersects(labelArea)) { if (x < fDivider && updateRect.Intersects(labelArea)) {
labelArea.right = fText->Frame().left - 3; labelArea.right = fText->Frame().left - kLabelInputSpacing;
BRegion clipRegion(labelArea); BRegion clipRegion(labelArea);
ConstrainClippingRegion(&clipRegion); ConstrainClippingRegion(&clipRegion);
@@ -436,25 +463,15 @@ BTextControl::SetEnabled(bool enabled)
void void
BTextControl::GetPreferredSize(float *_width, float *_height) BTextControl::GetPreferredSize(float *_width, float *_height)
{ {
if (_height) { CALLED();
// 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); _ValidateLayoutData();
}
if (_width) { if (_width)
*_width = Bounds().Width(); *_width = fLayoutData->min.width;
const char* label = Label();
if (label) { if (_height)
float width = ceilf(StringWidth(label)); *_height = fLayoutData->min.height;
*_width = (width * 1.3) + width + 4.0;
}
}
} }
@@ -583,41 +600,50 @@ BTextControl::FrameResized(float width, float height)
BControl::FrameResized(width, height); BControl::FrameResized(width, height);
// TODO: this causes flickering still...
// changes in width // changes in width
BRect bounds = Bounds(); BRect bounds = Bounds();
const float border = 2.0f;
if (bounds.Width() > fPreviousWidth) { if (bounds.Width() > fLayoutData->previous_width) {
// invalidate the region between the old and the new right border // invalidate the region between the old and the new right border
BRect rect = bounds; BRect rect = bounds;
rect.left += fPreviousWidth - border; rect.left += fLayoutData->previous_width - kFrameMargin;
rect.right--; rect.right--;
Invalidate(rect); Invalidate(rect);
} else if (bounds.Width() < fPreviousWidth) { } else if (bounds.Width() < fLayoutData->previous_width) {
// invalidate the region of the new right border // invalidate the region of the new right border
BRect rect = bounds; BRect rect = bounds;
rect.left = rect.right - border; rect.left = rect.right - kFrameMargin;
Invalidate(rect); Invalidate(rect);
} }
// changes in height // changes in height
if (bounds.Height() > fPreviousHeight) { if (bounds.Height() > fLayoutData->previous_height) {
// invalidate the region between the old and the new bottom border // invalidate the region between the old and the new bottom border
BRect rect = bounds; BRect rect = bounds;
rect.top += fPreviousHeight - border; rect.top += fLayoutData->previous_height - kFrameMargin;
rect.bottom--; rect.bottom--;
Invalidate(rect); Invalidate(rect);
} else if (bounds.Height() < fPreviousHeight) { // invalidate label area
rect = bounds;
rect.right = fDivider;
Invalidate(rect);
} else if (bounds.Height() < fLayoutData->previous_height) {
// invalidate the region of the new bottom border // invalidate the region of the new bottom border
BRect rect = bounds; BRect rect = bounds;
rect.top = rect.bottom - border; rect.top = rect.bottom - kFrameMargin;
Invalidate(rect);
// invalidate label area
rect = bounds;
rect.right = fDivider;
Invalidate(rect); Invalidate(rect);
} }
fPreviousWidth = uint16(bounds.Width()); fLayoutData->previous_width = bounds.Width();
fPreviousHeight = uint16(bounds.Height()); fLayoutData->previous_height = bounds.Height();
TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height()); TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height());
} }
@@ -639,28 +665,130 @@ BTextControl::WindowActivated(bool active)
} }
status_t BSize
BTextControl::Perform(perform_code d, void *arg) BTextControl::MinSize()
{ {
return BControl::Perform(d, arg); CALLED();
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
}
BSize
BTextControl::MaxSize()
{
CALLED();
_ValidateLayoutData();
BSize max = fLayoutData->min;
max.width = B_SIZE_UNLIMITED;
return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
}
BSize
BTextControl::PreferredSize()
{
CALLED();
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min);
}
void
BTextControl::InvalidateLayout(bool descendants)
{
CALLED();
fLayoutData->valid = false;
BView::InvalidateLayout(descendants);
} }
BLayoutItem* BLayoutItem*
BTextControl::CreateLabelLayoutItem() BTextControl::CreateLabelLayoutItem()
{ {
if (!fLabelLayoutItem) if (!fLayoutData->label_layout_item)
fLabelLayoutItem = new LabelLayoutItem(this); fLayoutData->label_layout_item = new LabelLayoutItem(this);
return fLabelLayoutItem; return fLayoutData->label_layout_item;
} }
BLayoutItem* BLayoutItem*
BTextControl::CreateTextViewLayoutItem() BTextControl::CreateTextViewLayoutItem()
{ {
if (!fTextViewLayoutItem) if (!fLayoutData->text_view_layout_item)
fTextViewLayoutItem = new TextViewLayoutItem(this); fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
return fTextViewLayoutItem; return fLayoutData->text_view_layout_item;
}
void
BTextControl::DoLayout()
{
// Bail out, if we shan't do layout.
if (!(Flags() & B_SUPPORTS_LAYOUT))
return;
CALLED();
// If the user set a layout, we let the base class version call its
// hook.
if (GetLayout()) {
BView::DoLayout();
return;
}
_ValidateLayoutData();
// validate current size
BSize size(Bounds().Size());
if (size.width < fLayoutData->min.width)
size.width = fLayoutData->min.width;
if (size.height < fLayoutData->min.height)
size.height = fLayoutData->min.height;
// divider
float divider = 0;
if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) {
// We have layout items. They define the divider location.
divider = fLayoutData->text_view_layout_item->Frame().left
- fLayoutData->label_layout_item->Frame().left;
} else {
if (fLayoutData->label_width > 0)
divider = fLayoutData->label_width + 5;
}
// text view
BRect dirty(fText->Frame());
BRect textFrame(divider + 1, kFrameMargin, size.width - 2,
size.height - kFrameMargin);
// place the text view and set the divider
BLayoutUtils::AlignInFrame(fText, textFrame);
fDivider = divider;
// invalidate dirty region
dirty = dirty | fText->Frame();
dirty.InsetBy(-kFrameMargin, -kFrameMargin);
Invalidate(dirty);
}
// #pragma mark -
status_t
BTextControl::Perform(perform_code d, void *arg)
{
return BControl::Perform(d, arg);
} }
@@ -723,10 +851,7 @@ BTextControl::_InitData(const char* label, const char* initialText,
fModificationMessage = NULL; fModificationMessage = NULL;
fLabelAlign = B_ALIGN_LEFT; fLabelAlign = B_ALIGN_LEFT;
fDivider = 0.0f; fDivider = 0.0f;
fPreviousWidth = bounds.Width(); fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
fPreviousHeight = bounds.Height();
fLabelLayoutItem = NULL;
fTextViewLayoutItem = NULL;
int32 flags = 0; int32 flags = 0;
@@ -755,7 +880,7 @@ BTextControl::_InitData(const char* label, const char* initialText,
BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom); BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom);
// we are stroking the frame around the text view, which // we are stroking the frame around the text view, which
// is 2 pixels wide // is 2 pixels wide
frame.InsetBy(2.0, 3.0); frame.InsetBy(kFrameMargin, kFrameMargin);
BRect textRect(frame.OffsetToCopy(B_ORIGIN)); BRect textRect(frame.OffsetToCopy(B_ORIGIN));
fText = new BPrivate::_BTextInput_(frame, textRect, fText = new BPrivate::_BTextInput_(frame, textRect,
@@ -774,14 +899,11 @@ BTextControl::_ValidateLayout()
{ {
CALLED(); CALLED();
float height; _ValidateLayoutData();
BTextControl::GetPreferredSize(NULL, &height);
ResizeTo(Bounds().Width(), height); ResizeTo(Bounds().Width(), fLayoutData->min.height);
_LayoutTextView(); _LayoutTextView();
fPreviousHeight = Bounds().Height();
} }
@@ -809,17 +931,81 @@ BTextControl::_LayoutTextView()
void void
BTextControl::_UpdateFrame() BTextControl::_UpdateFrame()
{ {
if (fLabelLayoutItem && fTextViewLayoutItem) { CALLED();
BRect labelFrame = fLabelLayoutItem->Frame();
BRect textFrame = fTextViewLayoutItem->Frame(); if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) {
BRect labelFrame = fLayoutData->label_layout_item->Frame();
BRect textFrame = fLayoutData->text_view_layout_item->Frame();
// update divider
fDivider = textFrame.left - labelFrame.left;
MoveTo(labelFrame.left, labelFrame.top); MoveTo(labelFrame.left, labelFrame.top);
BSize oldSize = Bounds().Size();
ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left, ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left,
textFrame.top + textFrame.Height() - labelFrame.top); textFrame.top + textFrame.Height() - labelFrame.top);
SetDivider(textFrame.left - labelFrame.left); BSize newSize = Bounds().Size();
// If the size changes, ResizeTo() will trigger a relayout, otherwise
// we need to do that explicitly.
if (newSize != oldSize)
Relayout();
} }
} }
void
BTextControl::_ValidateLayoutData()
{
CALLED();
if (fLayoutData->valid)
return;
// cache font height
font_height& fh = fLayoutData->font_info;
GetFontHeight(&fh);
if (Label() != NULL) {
fLayoutData->label_width = ceilf(StringWidth(Label()));
fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent);
} else {
fLayoutData->label_width = 0;
fLayoutData->label_height = 0;
}
// compute the minimal divider
float divider = 0;
if (fLayoutData->label_width > 0)
divider = fLayoutData->label_width + 5;
// If we shan't do real layout, we let the current divider take influence.
if (!(Flags() & B_SUPPORTS_LAYOUT))
divider = max_c(divider, fDivider);
// get the minimal (== preferred) text view size
fLayoutData->text_view_min = fText->MinSize();
TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width);
// compute our minimal (== preferred) size
BSize min(fLayoutData->text_view_min);
min.width += 2 * kFrameMargin;
min.height += 2 * kFrameMargin;
if (divider > 0)
min.width += divider;
if (fLayoutData->label_height > min.height)
min.height = fLayoutData->label_height;
fLayoutData->min = min;
fLayoutData->valid = true;
TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
}
// #pragma mark - // #pragma mark -
@@ -869,17 +1055,13 @@ BTextControl::LabelLayoutItem::View()
BSize BSize
BTextControl::LabelLayoutItem::BaseMinSize() BTextControl::LabelLayoutItem::BaseMinSize()
{ {
// TODO: Cache the info. Might be too expensive for this call. fParent->_ValidateLayoutData();
const char* label = fParent->Label();
if (!label) if (!fParent->Label())
return BSize(-1, -1); return BSize(-1, -1);
BSize size; return BSize(fParent->fLayoutData->label_width + 5,
fParent->GetPreferredSize(NULL, &size.height); fParent->fLayoutData->label_height);
size.width = fParent->StringWidth(label) + 2 * 3 - 1;
return size;
} }
@@ -911,6 +1093,9 @@ BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
: fParent(parent), : fParent(parent),
fFrame() fFrame()
{ {
// by default the part right of the divider shall have an unlimited maximum
// width
SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
} }
@@ -953,12 +1138,11 @@ BTextControl::TextViewLayoutItem::View()
BSize BSize
BTextControl::TextViewLayoutItem::BaseMinSize() BTextControl::TextViewLayoutItem::BaseMinSize()
{ {
// TODO: Cache the info. Might be too expensive for this call. fParent->_ValidateLayoutData();
BSize size;
fParent->GetPreferredSize(NULL, &size.height);
// mmh, some arbitrary minimal width BSize size = fParent->fLayoutData->text_view_min;
size.width = 30; size.width += 2 * kFrameMargin;
size.height += 2 * kFrameMargin;
return size; return size;
} }
+11
View File
@@ -15,6 +15,7 @@
#include <string.h> #include <string.h>
#include <InterfaceDefs.h> #include <InterfaceDefs.h>
#include <LayoutUtils.h>
#include <Message.h> #include <Message.h>
#include <TextControl.h> #include <TextControl.h>
#include <TextView.h> #include <TextView.h>
@@ -147,6 +148,16 @@ _BTextInput_::MakeFocus(bool state)
} }
BSize
_BTextInput_::MinSize()
{
BSize min;
min.height = ceilf(LineHeight(0) + 1.0);
min.width = min.height * 3;
return BLayoutUtils::ComposeSize(ExplicitMinSize(), min);
}
void void
_BTextInput_::AlignTextRect() _BTextInput_::AlignTextRect()
{ {
+2
View File
@@ -34,6 +34,8 @@ virtual void FrameResized(float width, float height);
virtual void KeyDown(const char *bytes, int32 numBytes); virtual void KeyDown(const char *bytes, int32 numBytes);
virtual void MakeFocus(bool focusState = true); virtual void MakeFocus(bool focusState = true);
virtual BSize MinSize();
void AlignTextRect(); void AlignTextRect();
void SetInitialText(); void SetInitialText();