* 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

View File

@ -80,16 +80,26 @@ public:
virtual status_t GetSupportedSuites(BMessage* data);
virtual void SetFlags(uint32 flags);
virtual BSize MinSize();
virtual BSize MaxSize();
virtual BSize PreferredSize();
virtual void InvalidateLayout(bool descendants = false);
BLayoutItem* CreateLabelLayoutItem();
BLayoutItem* CreateTextViewLayoutItem();
private:
class LabelLayoutItem;
class TextViewLayoutItem;
protected:
virtual void DoLayout();
friend class _BTextInput_;
friend class LabelLayoutItem;
friend class TextViewLayoutItem;
private:
class LabelLayoutItem;
class TextViewLayoutItem;
struct LayoutData;
friend class _BTextInput_;
friend class LabelLayoutItem;
friend class TextViewLayoutItem;
virtual status_t Perform(perform_code d, void* arg);
@ -108,18 +118,17 @@ private:
void _LayoutTextView();
void _UpdateFrame();
void _ValidateLayoutData();
private:
BPrivate::_BTextInput_* fText;
char* fLabel;
BMessage* fModificationMessage;
alignment fLabelAlign;
float fDivider;
float fPreviousWidth;
float fPreviousHeight;
BLayoutItem* fLabelLayoutItem;
BLayoutItem* fTextViewLayoutItem;
uint32 _reserved[5];
LayoutData* fLayoutData;
uint32 _reserved[9];
};
#endif // _TEXT_CONTROL_H

View File

@ -27,7 +27,7 @@
#ifdef TRACE_TEXT_CONTROL
# include <FunctionTracer.h>
static int32 sFunctionDepth = -1;
# define CALLED(x...) FunctionTracer _ft("BMenuField", __FUNCTION__, \
# define CALLED(x...) FunctionTracer _ft("BTextControl", __FUNCTION__, \
sFunctionDepth)
# define TRACE(x...) { BString _to; \
_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 -
static const int32 kFrameMargin = 2;
static const int32 kLabelInputSpacing = 3;
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)
@ -347,8 +374,8 @@ BTextControl::Draw(BRect updateRect)
// label
if (Label()) {
font_height fontHeight;
GetFontHeight(&fontHeight);
_ValidateLayoutData();
font_height& fontHeight = fLayoutData->font_info;
float y = Bounds().top + (Bounds().Height() + 1 - fontHeight.ascent
- fontHeight.descent) / 2 + fontHeight.ascent;
@ -357,7 +384,7 @@ BTextControl::Draw(BRect updateRect)
float labelWidth = StringWidth(Label());
switch (fLabelAlign) {
case B_ALIGN_RIGHT:
x = fDivider - labelWidth - 3.0;
x = fDivider - labelWidth - kLabelInputSpacing;
break;
case B_ALIGN_CENTER:
@ -371,7 +398,7 @@ BTextControl::Draw(BRect updateRect)
BRect labelArea(x, Bounds().top, x + labelWidth, Bounds().bottom);
if (x < fDivider && updateRect.Intersects(labelArea)) {
labelArea.right = fText->Frame().left - 3;
labelArea.right = fText->Frame().left - kLabelInputSpacing;
BRegion clipRegion(labelArea);
ConstrainClippingRegion(&clipRegion);
@ -436,25 +463,15 @@ BTextControl::SetEnabled(bool 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;
CALLED();
*_height = max_c(labelHeight, textHeight);
}
_ValidateLayoutData();
if (_width) {
*_width = Bounds().Width();
const char* label = Label();
if (label) {
float width = ceilf(StringWidth(label));
*_width = (width * 1.3) + width + 4.0;
}
}
if (_width)
*_width = fLayoutData->min.width;
if (_height)
*_height = fLayoutData->min.height;
}
@ -583,41 +600,50 @@ BTextControl::FrameResized(float width, float height)
BControl::FrameResized(width, height);
// TODO: this causes flickering still...
// changes in width
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
BRect rect = bounds;
rect.left += fPreviousWidth - border;
rect.left += fLayoutData->previous_width - kFrameMargin;
rect.right--;
Invalidate(rect);
} else if (bounds.Width() < fPreviousWidth) {
} else if (bounds.Width() < fLayoutData->previous_width) {
// invalidate the region of the new right border
BRect rect = bounds;
rect.left = rect.right - border;
rect.left = rect.right - kFrameMargin;
Invalidate(rect);
}
// changes in height
if (bounds.Height() > fPreviousHeight) {
if (bounds.Height() > fLayoutData->previous_height) {
// invalidate the region between the old and the new bottom border
BRect rect = bounds;
rect.top += fPreviousHeight - border;
rect.top += fLayoutData->previous_height - kFrameMargin;
rect.bottom--;
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
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);
}
fPreviousWidth = uint16(bounds.Width());
fPreviousHeight = uint16(bounds.Height());
fLayoutData->previous_width = bounds.Width();
fLayoutData->previous_height = bounds.Height();
TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height());
}
@ -639,28 +665,130 @@ BTextControl::WindowActivated(bool active)
}
status_t
BTextControl::Perform(perform_code d, void *arg)
BSize
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*
BTextControl::CreateLabelLayoutItem()
{
if (!fLabelLayoutItem)
fLabelLayoutItem = new LabelLayoutItem(this);
return fLabelLayoutItem;
if (!fLayoutData->label_layout_item)
fLayoutData->label_layout_item = new LabelLayoutItem(this);
return fLayoutData->label_layout_item;
}
BLayoutItem*
BTextControl::CreateTextViewLayoutItem()
{
if (!fTextViewLayoutItem)
fTextViewLayoutItem = new TextViewLayoutItem(this);
return fTextViewLayoutItem;
if (!fLayoutData->text_view_layout_item)
fLayoutData->text_view_layout_item = new TextViewLayoutItem(this);
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;
fLabelAlign = B_ALIGN_LEFT;
fDivider = 0.0f;
fPreviousWidth = bounds.Width();
fPreviousHeight = bounds.Height();
fLabelLayoutItem = NULL;
fTextViewLayoutItem = NULL;
fLayoutData = new LayoutData(bounds.Width(), bounds.Height());
int32 flags = 0;
@ -755,7 +880,7 @@ BTextControl::_InitData(const char* label, const char* initialText,
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);
frame.InsetBy(kFrameMargin, kFrameMargin);
BRect textRect(frame.OffsetToCopy(B_ORIGIN));
fText = new BPrivate::_BTextInput_(frame, textRect,
@ -774,14 +899,11 @@ BTextControl::_ValidateLayout()
{
CALLED();
float height;
BTextControl::GetPreferredSize(NULL, &height);
_ValidateLayoutData();
ResizeTo(Bounds().Width(), height);
ResizeTo(Bounds().Width(), fLayoutData->min.height);
_LayoutTextView();
fPreviousHeight = Bounds().Height();
}
@ -809,17 +931,81 @@ BTextControl::_LayoutTextView()
void
BTextControl::_UpdateFrame()
{
if (fLabelLayoutItem && fTextViewLayoutItem) {
BRect labelFrame = fLabelLayoutItem->Frame();
BRect textFrame = fTextViewLayoutItem->Frame();
CALLED();
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);
BSize oldSize = Bounds().Size();
ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left,
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 -
@ -869,17 +1055,13 @@ BTextControl::LabelLayoutItem::View()
BSize
BTextControl::LabelLayoutItem::BaseMinSize()
{
// TODO: Cache the info. Might be too expensive for this call.
const char* label = fParent->Label();
if (!label)
fParent->_ValidateLayoutData();
if (!fParent->Label())
return BSize(-1, -1);
BSize size;
fParent->GetPreferredSize(NULL, &size.height);
size.width = fParent->StringWidth(label) + 2 * 3 - 1;
return size;
return BSize(fParent->fLayoutData->label_width + 5,
fParent->fLayoutData->label_height);
}
@ -911,6 +1093,9 @@ BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent)
: fParent(parent),
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
BTextControl::TextViewLayoutItem::BaseMinSize()
{
// TODO: Cache the info. Might be too expensive for this call.
BSize size;
fParent->GetPreferredSize(NULL, &size.height);
fParent->_ValidateLayoutData();
// mmh, some arbitrary minimal width
size.width = 30;
BSize size = fParent->fLayoutData->text_view_min;
size.width += 2 * kFrameMargin;
size.height += 2 * kFrameMargin;
return size;
}

View File

@ -15,6 +15,7 @@
#include <string.h>
#include <InterfaceDefs.h>
#include <LayoutUtils.h>
#include <Message.h>
#include <TextControl.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
_BTextInput_::AlignTextRect()
{

View File

@ -34,6 +34,8 @@ virtual void FrameResized(float width, float height);
virtual void KeyDown(const char *bytes, int32 numBytes);
virtual void MakeFocus(bool focusState = true);
virtual BSize MinSize();
void AlignTextRect();
void SetInitialText();