/*
* Copyright 2006-2016 Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus, superstippi@gmx.de
* Marc Flerackers, mflerackers@androme.be
* John Scipione, jscipione@gmail.com
* Ingo Weinhold, bonefish@cs.tu-berlin.de
*/
#include <MenuField.h>
#include <algorithm>
#include <stdio.h>
// for printf in TRACE
#include <stdlib.h>
#include <string.h>
#include <AbstractLayoutItem.h>
#include <Archivable.h>
#include <BMCPrivate.h>
#include <ControlLook.h>
#include <LayoutUtils.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <MenuItemPrivate.h>
#include <MenuPrivate.h>
#include <Message.h>
#include <MessageFilter.h>
#include <Thread.h>
#include <Window.h>
#include <binary_compatibility/Interface.h>
#include <binary_compatibility/Support.h>
#ifdef CALLED
# undef CALLED
#endif
#ifdef TRACE
# undef TRACE
#endif
//#define TRACE_MENU_FIELD
#ifdef TRACE_MENU_FIELD
# include <FunctionTracer.h>
static int32 sFunctionDepth = -1;
# define CALLED(x...) FunctionTracer _ft("BMenuField", __FUNCTION__, \
sFunctionDepth)
# define TRACE(x...) { BString _to; \
_to.Append(' ', (sFunctionDepth + 1) * 2); \
printf("%s", _to.String()); printf(x); }
#else
# define CALLED(x...)
# define TRACE(x...)
#endif
static const float kMinMenuBarWidth = 20.0f;
// found by experimenting on BeOS R5
namespace {
const char* const kFrameField = "BMenuField:layoutItem:frame";
const char* const kMenuBarItemField = "BMenuField:barItem";
const char* const kLabelItemField = "BMenuField:labelItem";
}
// #pragma mark - LabelLayoutItem
class BMenuField::LabelLayoutItem : public BAbstractLayoutItem {
public:
LabelLayoutItem(BMenuField* parent);
LabelLayoutItem(BMessage* archive);
BRect FrameInParent() const;
virtual bool IsVisible();
virtual void SetVisible(bool visible);
virtual BRect Frame();
virtual void SetFrame(BRect frame);
void SetParent(BMenuField* parent);
virtual BView* View();
virtual BSize BaseMinSize();
virtual BSize BaseMaxSize();
virtual BSize BasePreferredSize();
virtual BAlignment BaseAlignment();
virtual status_t Archive(BMessage* into, bool deep = true) const;
static BArchivable* Instantiate(BMessage* from);
private:
BMenuField* fParent;
BRect fFrame;
};
// #pragma mark - MenuBarLayoutItem
class BMenuField::MenuBarLayoutItem : public BAbstractLayoutItem {
public:
MenuBarLayoutItem(BMenuField* parent);
MenuBarLayoutItem(BMessage* from);
BRect FrameInParent() const;
virtual bool IsVisible();
virtual void SetVisible(bool visible);
virtual BRect Frame();
virtual void SetFrame(BRect frame);
void SetParent(BMenuField* parent);
virtual BView* View();
virtual BSize BaseMinSize();
virtual BSize BaseMaxSize();
virtual BSize BasePreferredSize();
virtual BAlignment BaseAlignment();
virtual status_t Archive(BMessage* into, bool deep = true) const;
static BArchivable* Instantiate(BMessage* from);
private:
BMenuField* fParent;
BRect fFrame;
};
// #pragma mark - LayoutData
struct BMenuField::LayoutData {
LayoutData()
:
label_layout_item(NULL),
menu_bar_layout_item(NULL),
previous_height(-1),
valid(false)
{
}
LabelLayoutItem* label_layout_item;
MenuBarLayoutItem* menu_bar_layout_item;
float previous_height; // used in FrameResized() for
// invalidation
font_height font_info;
float label_width;
float label_height;
BSize min;
BSize menu_bar_min;
bool valid;
};
// #pragma mark - MouseDownFilter
class MouseDownFilter : public BMessageFilter
{
public:
MouseDownFilter();
virtual ~MouseDownFilter();
virtual filter_result Filter(BMessage* message, BHandler** target);
};
MouseDownFilter::MouseDownFilter()
:
BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE)
{
}
MouseDownFilter::~MouseDownFilter()
{
}
filter_result
MouseDownFilter::Filter(BMessage* message, BHandler** target)
{
return message->what == B_MOUSE_DOWN ? B_SKIP_MESSAGE : B_DISPATCH_MESSAGE;
}
// #pragma mark - BMenuField
BMenuField::BMenuField(BRect frame, const char* name, const char* label,
BMenu* menu, uint32 resizingMode, uint32 flags)
:
BView(frame, name, resizingMode, flags)
{
CALLED();
TRACE("frame.width: %.2f, height: %.2f\n", frame.Width(), frame.Height());
InitObject(label);
frame.OffsetTo(B_ORIGIN);
_InitMenuBar(menu, frame, false);
InitObject2();
}
BMenuField::BMenuField(BRect frame, const char* name, const char* label,
BMenu* menu, bool fixedSize, uint32 resizingMode, uint32 flags)
:
BView(frame, name, resizingMode, flags)
{
InitObject(label);
fFixedSizeMB = fixedSize;
frame.OffsetTo(B_ORIGIN);
_InitMenuBar(menu, frame, fixedSize);
InitObject2();
}
BMenuField::BMenuField(const char* name, const char* label, BMenu* menu,
uint32 flags)
:
BView(name, flags | B_FRAME_EVENTS)
{
InitObject(label);
_InitMenuBar(menu, BRect(0, 0, 100, 15), true);
InitObject2();
}
BMenuField::BMenuField(const char* label, BMenu* menu, uint32 flags)
:
BView(NULL, flags | B_FRAME_EVENTS)
{
InitObject(label);
_InitMenuBar(menu, BRect(0, 0, 100, 15), true);
InitObject2();
}
//! Copy&Paste error, should be removed at some point (already private)
BMenuField::BMenuField(const char* name, const char* label, BMenu* menu,
BMessage* message, uint32 flags)
:
BView(name, flags | B_FRAME_EVENTS)
{
InitObject(label);
_InitMenuBar(menu, BRect(0, 0, 100, 15), true);
InitObject2();
}
//! Copy&Paste error, should be removed at some point (already private)
BMenuField::BMenuField(const char* label, BMenu* menu, BMessage* message)
:
BView(NULL, B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS)
{
InitObject(label);
_InitMenuBar(menu, BRect(0, 0, 100, 15), true);
InitObject2();
}
BMenuField::BMenuField(BMessage* data)
:
BView(BUnarchiver::PrepareArchive(data))
{
BUnarchiver unarchiver(data);
const char* label = NULL;
data->FindString("_label", &label);
InitObject(label);
data->FindFloat("_divide", &fDivider);
int32 align;
if (data->FindInt32("_align", &align) == B_OK)
SetAlignment((alignment)align);
if (!BUnarchiver::IsArchiveManaged(data))
_InitMenuBar(data);
unarchiver.Finish();
}
BMenuField::~BMenuField()
{
free(fLabel);
status_t dummy;
if (fMenuTaskID >= 0)
wait_for_thread(fMenuTaskID, &dummy);
delete fLayoutData;
delete fMouseDownFilter;
}
BArchivable*
BMenuField::Instantiate(BMessage* data)
{
if (validate_instantiation(data, "BMenuField"))
return new BMenuField(data);
return NULL;
}
status_t
BMenuField::Archive(BMessage* data, bool deep) const
{
BArchiver archiver(data);
status_t ret = BView::Archive(data, deep);
if (ret == B_OK && Label())
ret = data->AddString("_label", Label());
if (ret == B_OK && !IsEnabled())
ret = data->AddBool("_disable", true);
if (ret == B_OK)
ret = data->AddInt32("_align", Alignment());
if (ret == B_OK)
ret = data->AddFloat("_divide", Divider());
if (ret == B_OK && fFixedSizeMB)
ret = data->AddBool("be:fixeds", true);
bool dmark = false;
if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar))
dmark = menuBar->IsPopUpMarkerShown();
data->AddBool("be:dmark", dmark);
return archiver.Finish(ret);
}
status_t
BMenuField::AllArchived(BMessage* into) const
{
status_t err;
if ((err = BView::AllArchived(into)) != B_OK)
return err;
BArchiver archiver(into);
BArchivable* menuBarItem = fLayoutData->menu_bar_layout_item;
if (archiver.IsArchived(menuBarItem))
err = archiver.AddArchivable(kMenuBarItemField, menuBarItem);
if (err != B_OK)
return err;
BArchivable* labelBarItem = fLayoutData->label_layout_item;
if (archiver.IsArchived(labelBarItem))
err = archiver.AddArchivable(kLabelItemField, labelBarItem);
return err;
}
status_t
BMenuField::AllUnarchived(const BMessage* from)
{
BUnarchiver unarchiver(from);
status_t err = B_OK;
if ((err = BView::AllUnarchived(from)) != B_OK)
return err;
_InitMenuBar(from);
if (unarchiver.IsInstantiated(kMenuBarItemField)) {
MenuBarLayoutItem*& menuItem = fLayoutData->menu_bar_layout_item;
err = unarchiver.FindObject(kMenuBarItemField,
BUnarchiver::B_DONT_ASSUME_OWNERSHIP, menuItem);
if (err == B_OK)
menuItem->SetParent(this);
else
return err;
}
if (unarchiver.IsInstantiated(kLabelItemField)) {
LabelLayoutItem*& labelItem = fLayoutData->label_layout_item;
err = unarchiver.FindObject(kLabelItemField,
BUnarchiver::B_DONT_ASSUME_OWNERSHIP, labelItem);
if (err == B_OK)
labelItem->SetParent(this);
}
return err;
}
void
BMenuField::Draw(BRect updateRect)
{
_DrawLabel(updateRect);
_DrawMenuBar(updateRect);
}
void
BMenuField::AttachedToWindow()
{
CALLED();
// Our low color must match the parent's view color.
if (Parent() != NULL) {
AdoptParentColors();
float tint = B_NO_TINT;
color_which which = ViewUIColor(&tint);
if (which == B_NO_COLOR)
SetLowColor(ViewColor());
else
SetLowUIColor(which, tint);
} else
AdoptSystemColors();
}
void
BMenuField::AllAttached()
{
CALLED();
TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
float width = Bounds().Width();
if (!fFixedSizeMB && _MenuBarWidth() < kMinMenuBarWidth) {
// The menu bar is too narrow, resize it to fit the menu items
BMenuItem* item = fMenuBar->ItemAt(0);
if (item != NULL) {
float right;
fMenuBar->GetItemMargins(NULL, NULL, &right, NULL);
width = item->Frame().Width() + kVMargin + _MenuBarOffset() + right;
}
}
ResizeTo(width, fMenuBar->Bounds().Height() + kVMargin * 2);
TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height());
}
void
BMenuField::MouseDown(BPoint where)
{
BRect bounds = fMenuBar->ConvertFromParent(Bounds());
fMenuBar->StartMenuBar(-1, false, true, &bounds);
fMenuTaskID = spawn_thread((thread_func)_thread_entry,
"_m_task_", B_NORMAL_PRIORITY, this);
if (fMenuTaskID >= 0 && resume_thread(fMenuTaskID) == B_OK) {
if (fMouseDownFilter->Looper() == NULL)
Window()->AddCommonFilter(fMouseDownFilter);
MouseDownThread<BMenuField>::TrackMouse(this, &BMenuField::_DoneTracking,
&BMenuField::_Track);
}
}
void
BMenuField::KeyDown(const char* bytes, int32 numBytes)
{
switch (bytes[0]) {
case B_SPACE:
case B_RIGHT_ARROW:
case B_DOWN_ARROW:
{
if (!IsEnabled())
break;
BRect bounds = fMenuBar->ConvertFromParent(Bounds());
fMenuBar->StartMenuBar(0, true, true, &bounds);
bounds = Bounds();
bounds.right = fDivider;
Invalidate(bounds);
}
default:
BView::KeyDown(bytes, numBytes);
}
}
void
BMenuField::MakeFocus(bool focused)
{
if (IsFocus() == focused)
return;
BView::MakeFocus(focused);
if (Window() != NULL)
Invalidate(); // TODO: use fLayoutData->label_width
}
void
BMenuField::MessageReceived(BMessage* message)
{
BView::MessageReceived(message);
}
void
BMenuField::WindowActivated(bool active)
{
BView::WindowActivated(active);
if (IsFocus())
Invalidate();
}
void
BMenuField::MouseMoved(BPoint point, uint32 code, const BMessage* message)
{
BView::MouseMoved(point, code, message);
}
void
BMenuField::MouseUp(BPoint where)
{
BView::MouseUp(where);
}
void
BMenuField::DetachedFromWindow()
{
BView::DetachedFromWindow();
}
void
BMenuField::AllDetached()
{
BView::AllDetached();
}
void
BMenuField::FrameMoved(BPoint newPosition)
{
BView::FrameMoved(newPosition);
}
void
BMenuField::FrameResized(float newWidth, float newHeight)
{
BView::FrameResized(newWidth, newHeight);
if (fFixedSizeMB) {
// we have let the menubar resize itself, but
// in fixed size mode, the menubar is supposed to
// be at the right end of the view always. Since
// the menu bar is in follow left/right mode then,
// resizing ourselfs might have caused the menubar
// to be outside now
fMenuBar->ResizeTo(_MenuBarWidth(), fMenuBar->Frame().Height());
}
if (newHeight != fLayoutData->previous_height && Label()) {
// The height changed, which means the label has to move and we
// probably also invalidate a part of the borders around the menu bar.
// So don't be shy and invalidate the whole thing.
Invalidate();
}
fLayoutData->previous_height = newHeight;
}
BMenu*
BMenuField::Menu() const
{
return fMenu;
}
BMenuBar*
BMenuField::MenuBar() const
{
return fMenuBar;
}
BMenuItem*
BMenuField::MenuItem() const
{
return fMenuBar->ItemAt(0);
}
void
BMenuField::SetLabel(const char* label)
{
if (fLabel) {
if (label && strcmp(fLabel, label) == 0)
return;
free(fLabel);
}
fLabel = strdup(label);
if (Window())
Invalidate();
InvalidateLayout();
}
const char*
BMenuField::Label() const
{
return fLabel;
}
void
BMenuField::SetEnabled(bool on)
{
if (fEnabled == on)
return;
fEnabled = on;
fMenuBar->SetEnabled(on);
if (Window()) {
fMenuBar->Invalidate(fMenuBar->Bounds());
Invalidate(Bounds());
}
}
bool
BMenuField::IsEnabled() const
{
return fEnabled;
}
void
BMenuField::SetAlignment(alignment label)
{
fAlign = label;
}
alignment
BMenuField::Alignment() const
{
return fAlign;
}
void
BMenuField::SetDivider(float position)
{
position = roundf(position);
float delta = fDivider - position;
if (delta == 0.0f)
return;
fDivider = position;
if ((Flags() & B_SUPPORTS_LAYOUT) != 0) {
// We should never get here, since layout support means, we also
// layout the divider, and don't use this method at all.
Relayout();
} else {
BRect dirty(fMenuBar->Frame());
fMenuBar->MoveTo(_MenuBarOffset(), kVMargin);
if (fFixedSizeMB)
fMenuBar->ResizeTo(_MenuBarWidth(), dirty.Height());
dirty = dirty | fMenuBar->Frame();
dirty.InsetBy(-kVMargin, -kVMargin);
Invalidate(dirty);
}
}
float
BMenuField::Divider() const
{
return fDivider;
}
void
BMenuField::ShowPopUpMarker()
{
if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) {
menuBar->TogglePopUpMarker(true);
menuBar->Invalidate();
}
}
void
BMenuField::HidePopUpMarker()
{
if (_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar)) {
menuBar->TogglePopUpMarker(false);
menuBar->Invalidate();
}
}
BHandler*
BMenuField::ResolveSpecifier(BMessage* message, int32 index,
BMessage* specifier, int32 form, const char* property)
{
return BView::ResolveSpecifier(message, index, specifier, form, property);
}
status_t
BMenuField::GetSupportedSuites(BMessage* data)
{
return BView::GetSupportedSuites(data);
}
void
BMenuField::ResizeToPreferred()
{
CALLED();
TRACE("fMenuBar->Frame().width: %.2f, height: %.2f\n",
fMenuBar->Frame().Width(), fMenuBar->Frame().Height());
fMenuBar->ResizeToPreferred();
TRACE("fMenuBar->Frame().width: %.2f, height: %.2f\n",
fMenuBar->Frame().Width(), fMenuBar->Frame().Height());
BView::ResizeToPreferred();
Invalidate();
}
void
BMenuField::GetPreferredSize(float* _width, float* _height)
{
CALLED();
_ValidateLayoutData();
if (_width)
*_width = fLayoutData->min.width;
if (_height)
*_height = fLayoutData->min.height;
}
BSize
BMenuField::MinSize()
{
CALLED();
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min);
}
BSize
BMenuField::MaxSize()
{
CALLED();
_ValidateLayoutData();
BSize max = fLayoutData->min;
max.width = B_SIZE_UNLIMITED;
return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
}
BSize
BMenuField::PreferredSize()
{
CALLED();
_ValidateLayoutData();
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min);
}
BLayoutItem*
BMenuField::CreateLabelLayoutItem()
{
if (fLayoutData->label_layout_item == NULL)
fLayoutData->label_layout_item = new LabelLayoutItem(this);
return fLayoutData->label_layout_item;
}
BLayoutItem*
BMenuField::CreateMenuBarLayoutItem()
{
if (fLayoutData->menu_bar_layout_item == NULL) {
// align the menu bar in the full available space
fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
B_ALIGN_VERTICAL_UNSET));
fLayoutData->menu_bar_layout_item = new MenuBarLayoutItem(this);
}
return fLayoutData->menu_bar_layout_item;
}
status_t
BMenuField::Perform(perform_code code, void* _data)
{
switch (code) {
case PERFORM_CODE_MIN_SIZE:
((perform_data_min_size*)_data)->return_value
= BMenuField::MinSize();
return B_OK;
case PERFORM_CODE_MAX_SIZE:
((perform_data_max_size*)_data)->return_value
= BMenuField::MaxSize();
return B_OK;
case PERFORM_CODE_PREFERRED_SIZE:
((perform_data_preferred_size*)_data)->return_value
= BMenuField::PreferredSize();
return B_OK;
case PERFORM_CODE_LAYOUT_ALIGNMENT:
((perform_data_layout_alignment*)_data)->return_value
= BMenuField::LayoutAlignment();
return B_OK;
case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
((perform_data_has_height_for_width*)_data)->return_value
= BMenuField::HasHeightForWidth();
return B_OK;
case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
{
perform_data_get_height_for_width* data
= (perform_data_get_height_for_width*)_data;
BMenuField::GetHeightForWidth(data->width, &data->min, &data->max,
&data->preferred);
return B_OK;
}
case PERFORM_CODE_SET_LAYOUT:
{
perform_data_set_layout* data = (perform_data_set_layout*)_data;
BMenuField::SetLayout(data->layout);
return B_OK;
}
case PERFORM_CODE_LAYOUT_INVALIDATED:
{
perform_data_layout_invalidated* data
= (perform_data_layout_invalidated*)_data;
BMenuField::LayoutInvalidated(data->descendants);
return B_OK;
}
case PERFORM_CODE_DO_LAYOUT:
{
BMenuField::DoLayout();
return B_OK;
}
case PERFORM_CODE_ALL_UNARCHIVED:
{
perform_data_all_unarchived* data
= (perform_data_all_unarchived*)_data;
data->return_value = BMenuField::AllUnarchived(data->archive);
return B_OK;
}
case PERFORM_CODE_ALL_ARCHIVED:
{
perform_data_all_archived* data
= (perform_data_all_archived*)_data;
data->return_value = BMenuField::AllArchived(data->archive);
return B_OK;
}
}
return BView::Perform(code, _data);
}
void
BMenuField::LayoutInvalidated(bool descendants)
{
CALLED();
fLayoutData->valid = false;
}
void
BMenuField::DoLayout()
{
// Bail out, if we shan't do layout.
if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
return;
CALLED();
// If the user set a layout, we let the base class version call its
// hook.
if (GetLayout() != NULL) {
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 != NULL
&& fLayoutData->menu_bar_layout_item != NULL
&& fLayoutData->label_layout_item->Frame().IsValid()
&& fLayoutData->menu_bar_layout_item->Frame().IsValid()) {
// We have valid layout items, they define the divider location.
divider = fabs(fLayoutData->menu_bar_layout_item->Frame().left
- fLayoutData->label_layout_item->Frame().left);
} else if (fLayoutData->label_width > 0) {
divider = fLayoutData->label_width
+ be_control_look->DefaultLabelSpacing();
}
// menu bar
BRect dirty(fMenuBar->Frame());
BRect menuBarFrame(divider + kVMargin, kVMargin, size.width - kVMargin,
size.height - kVMargin);
// place the menu bar and set the divider
BLayoutUtils::AlignInFrame(fMenuBar, menuBarFrame);
fDivider = divider;
// invalidate dirty region
dirty = dirty | fMenuBar->Frame();
dirty.InsetBy(-kVMargin, -kVMargin);
Invalidate(dirty);
}
void BMenuField::_ReservedMenuField1() {}
void BMenuField::_ReservedMenuField2() {}
void BMenuField::_ReservedMenuField3() {}
void
BMenuField::InitObject(const char* label)
{
CALLED();
fLabel = NULL;
fMenu = NULL;
fMenuBar = NULL;
fAlign = B_ALIGN_LEFT;
fEnabled = true;
fFixedSizeMB = false;
fMenuTaskID = -1;
fLayoutData = new LayoutData;
fMouseDownFilter = new MouseDownFilter();
SetLabel(label);
if (label)
fDivider = floorf(Frame().Width() / 2.0f);
else
fDivider = 0;
}
void
BMenuField::InitObject2()
{
CALLED();
if (!fFixedSizeMB) {
float height;
fMenuBar->GetPreferredSize(NULL, &height);
fMenuBar->ResizeTo(_MenuBarWidth(), height);
}
TRACE("frame(%.1f, %.1f, %.1f, %.1f) (%.2f, %.2f)\n",
fMenuBar->Frame().left, fMenuBar->Frame().top,
fMenuBar->Frame().right, fMenuBar->Frame().bottom,
fMenuBar->Frame().Width(), fMenuBar->Frame().Height());
fMenuBar->AddFilter(new _BMCFilter_(this, B_MOUSE_DOWN));
}
void
BMenuField::_DrawLabel(BRect updateRect)
{
CALLED();
_ValidateLayoutData();
const char* label = Label();
if (label == NULL)
return;
BRect rect;
if (fLayoutData->label_layout_item != NULL)
rect = fLayoutData->label_layout_item->FrameInParent();
else {
rect = Bounds();
rect.right = fDivider;
}
if (!rect.IsValid() || !rect.Intersects(updateRect))
return;
uint32 flags = 0;
if (!IsEnabled())
flags |= BControlLook::B_DISABLED;
// save the current low color
PushState();
rgb_color textColor;
BPrivate::MenuPrivate menuPrivate(fMenuBar);
if (menuPrivate.State() != MENU_STATE_CLOSED) {
// highlight the background of the label grey (like BeOS R5)
SetLowColor(ui_color(B_MENU_SELECTED_BACKGROUND_COLOR));
BRect fillRect(rect.InsetByCopy(0, kVMargin));
FillRect(fillRect, B_SOLID_LOW);
textColor = ui_color(B_MENU_SELECTED_ITEM_TEXT_COLOR);
} else
textColor = ui_color(B_PANEL_TEXT_COLOR);
be_control_look->DrawLabel(this, label, rect, updateRect, LowColor(), flags,
BAlignment(fAlign, B_ALIGN_MIDDLE), &textColor);
// restore the previous low color
PopState();
}
void
BMenuField::_DrawMenuBar(BRect updateRect)
{
CALLED();
BRect rect(fMenuBar->Frame().InsetByCopy(-kVMargin, -kVMargin));
if (!rect.IsValid() || !rect.Intersects(updateRect))
return;
uint32 flags = 0;
if (!IsEnabled())
flags |= BControlLook::B_DISABLED;
if (IsFocus() && Window()->IsActive())
flags |= BControlLook::B_FOCUSED;
be_control_look->DrawMenuFieldFrame(this, rect, updateRect,
fMenuBar->LowColor(), LowColor(), flags);
}
void
BMenuField::InitMenu(BMenu* menu)
{
menu->SetFont(be_plain_font);
int32 index = 0;
BMenu* subMenu;
while ((subMenu = menu->SubmenuAt(index++)) != NULL)
InitMenu(subMenu);
}
/*static*/ int32
BMenuField::_thread_entry(void* arg)
{
return static_cast<BMenuField*>(arg)->_MenuTask();
}
int32
BMenuField::_MenuTask()
{
if (!LockLooper())
return 0;
Invalidate();
UnlockLooper();
bool tracking;
do {
snooze(20000);
if (!LockLooper())
return 0;
tracking = fMenuBar->fTracking;
UnlockLooper();
} while (tracking);
if (LockLooper()) {
Invalidate();
UnlockLooper();
}
return 0;
}
void
BMenuField::_UpdateFrame()
{
CALLED();
if (fLayoutData->label_layout_item == NULL
|| fLayoutData->menu_bar_layout_item == NULL) {
return;
}
BRect labelFrame = fLayoutData->label_layout_item->Frame();
BRect menuFrame = fLayoutData->menu_bar_layout_item->Frame();
if (!labelFrame.IsValid() || !menuFrame.IsValid())
return;
// update divider
fDivider = menuFrame.left - labelFrame.left;
// update our frame
MoveTo(labelFrame.left, labelFrame.top);
BSize oldSize = Bounds().Size();
ResizeTo(menuFrame.left + menuFrame.Width() - labelFrame.left,
menuFrame.top + menuFrame.Height() - labelFrame.top);
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
BMenuField::_InitMenuBar(BMenu* menu, BRect frame, bool fixedSize)
{
CALLED();
if ((Flags() & B_SUPPORTS_LAYOUT) != 0) {
fMenuBar = new _BMCMenuBar_(this);
} else {
frame.left = _MenuBarOffset();
frame.top = kVMargin;
frame.right -= kVMargin;
frame.bottom -= kVMargin;
TRACE("frame(%.1f, %.1f, %.1f, %.1f) (%.2f, %.2f)\n",
frame.left, frame.top, frame.right, frame.bottom,
frame.Width(), frame.Height());
fMenuBar = new _BMCMenuBar_(frame, fixedSize, this);
}
if (fixedSize) {
// align the menu bar in the full available space
fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
B_ALIGN_VERTICAL_UNSET));
} else {
// align the menu bar left in the available space
fMenuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT,
B_ALIGN_VERTICAL_UNSET));
}
AddChild(fMenuBar);
_AddMenu(menu);
fMenuBar->SetFont(be_plain_font);
}
void
BMenuField::_InitMenuBar(const BMessage* archive)
{
bool fixed;
if (archive->FindBool("be:fixeds", &fixed) == B_OK)
fFixedSizeMB = fixed;
fMenuBar = (BMenuBar*)FindView("_mc_mb_");
if (fMenuBar == NULL) {
_InitMenuBar(new BMenu(""), BRect(0, 0, 100, 15), fFixedSizeMB);
InitObject2();
} else {
fMenuBar->AddFilter(new _BMCFilter_(this, B_MOUSE_DOWN));
// this is normally done in InitObject2()
}
_AddMenu(fMenuBar->SubmenuAt(0));
bool disable;
if (archive->FindBool("_disable", &disable) == B_OK)
SetEnabled(!disable);
bool dmark = false;
archive->FindBool("be:dmark", &dmark);
_BMCMenuBar_* menuBar = dynamic_cast<_BMCMenuBar_*>(fMenuBar);
if (menuBar != NULL)
menuBar->TogglePopUpMarker(dmark);
}
void
BMenuField::_AddMenu(BMenu* menu)
{
if (menu == NULL || fMenuBar == NULL)
return;
fMenu = menu;
InitMenu(menu);
BMenuItem* item = NULL;
if (!menu->IsRadioMode() || (item = menu->FindMarked()) == NULL) {
// find the first enabled non-seperator item
int32 itemCount = menu->CountItems();
for (int32 i = 0; i < itemCount; i++) {
item = menu->ItemAt((int32)i);
if (item == NULL || !item->IsEnabled()
|| dynamic_cast<BSeparatorItem*>(item) != NULL) {
item = NULL;
continue;
}
break;
}
}
if (item == NULL) {
fMenuBar->AddItem(menu);
return;
}
// build an empty copy of item
BMessage data;
status_t result = item->Archive(&data, false);
if (result != B_OK) {
fMenuBar->AddItem(menu);
return;
}
BArchivable* object = instantiate_object(&data);
if (object == NULL) {
fMenuBar->AddItem(menu);
return;
}
BMenuItem* newItem = static_cast<BMenuItem*>(object);
// unset parameters
BPrivate::MenuItemPrivate newMenuItemPrivate(newItem);
newMenuItemPrivate.Uninstall();
// set the menu
newMenuItemPrivate.SetSubmenu(menu);
fMenuBar->AddItem(newItem);
}
void
BMenuField::_ValidateLayoutData()
{
CALLED();
if (fLayoutData->valid)
return;
// cache font height
font_height& fh = fLayoutData->font_info;
GetFontHeight(&fh);
const char* label = Label();
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
+ be_control_look->DefaultLabelSpacing();
}
// If we shan't do real layout, we let the current divider take influence.
if ((Flags() & B_SUPPORTS_LAYOUT) == 0)
divider = std::max(divider, fDivider);
// get the minimal (== preferred) menu bar size
// TODO: BMenu::MinSize() is using the ResizeMode() to decide the
// minimum width. If the mode is B_FOLLOW_LEFT_RIGHT, it will use the
// parent's frame width or window's frame width. So at least the returned
// size is wrong, but apparantly it doesn't have much bad effect.
fLayoutData->menu_bar_min = fMenuBar->MinSize();
TRACE("menu bar min width: %.2f\n", fLayoutData->menu_bar_min.width);
// compute our minimal (== preferred) size
BSize min(fLayoutData->menu_bar_min);
min.width += 2 * kVMargin;
min.height += 2 * kVMargin;
if (divider > 0)
min.width += divider;
if (fLayoutData->label_height > min.height)
min.height = fLayoutData->label_height;
fLayoutData->min = min;
fLayoutData->valid = true;
ResetLayoutInvalidation();
TRACE("width: %.2f, height: %.2f\n", min.width, min.height);
}
float
BMenuField::_MenuBarOffset() const
{
return std::max(fDivider + kVMargin, kVMargin);
}
float
BMenuField::_MenuBarWidth() const
{
return Bounds().Width() - (_MenuBarOffset() + kVMargin);
}
void
BMenuField::_DoneTracking(BPoint point)
{
Window()->RemoveCommonFilter(fMouseDownFilter);
}
void
BMenuField::_Track(BPoint point, uint32)
{
}
// #pragma mark - BMenuField::LabelLayoutItem
BMenuField::LabelLayoutItem::LabelLayoutItem(BMenuField* parent)
:
fParent(parent),
fFrame()
{
}
BMenuField::LabelLayoutItem::LabelLayoutItem(BMessage* from)
:
BAbstractLayoutItem(from),
fParent(NULL),
fFrame()
{
from->FindRect(kFrameField, &fFrame);
}
BRect
BMenuField::LabelLayoutItem::FrameInParent() const
{
return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
}
bool
BMenuField::LabelLayoutItem::IsVisible()
{
return !fParent->IsHidden(fParent);
}
void
BMenuField::LabelLayoutItem::SetVisible(bool visible)
{
// not allowed
}
BRect
BMenuField::LabelLayoutItem::Frame()
{
return fFrame;
}
void
BMenuField::LabelLayoutItem::SetFrame(BRect frame)
{
fFrame = frame;
fParent->_UpdateFrame();
}
void
BMenuField::LabelLayoutItem::SetParent(BMenuField* parent)
{
fParent = parent;
}
BView*
BMenuField::LabelLayoutItem::View()
{
return fParent;
}
BSize
BMenuField::LabelLayoutItem::BaseMinSize()
{
fParent->_ValidateLayoutData();
if (fParent->Label() == NULL)
return BSize(-1, -1);
return BSize(fParent->fLayoutData->label_width
+ be_control_look->DefaultLabelSpacing(),
fParent->fLayoutData->label_height);
}
BSize
BMenuField::LabelLayoutItem::BaseMaxSize()
{
return BaseMinSize();
}
BSize
BMenuField::LabelLayoutItem::BasePreferredSize()
{
return BaseMinSize();
}
BAlignment
BMenuField::LabelLayoutItem::BaseAlignment()
{
return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
}
status_t
BMenuField::LabelLayoutItem::Archive(BMessage* into, bool deep) const
{
BArchiver archiver(into);
status_t err = BAbstractLayoutItem::Archive(into, deep);
if (err == B_OK)
err = into->AddRect(kFrameField, fFrame);
return archiver.Finish(err);
}
BArchivable*
BMenuField::LabelLayoutItem::Instantiate(BMessage* from)
{
if (validate_instantiation(from, "BMenuField::LabelLayoutItem"))
return new LabelLayoutItem(from);
return NULL;
}
// #pragma mark - BMenuField::MenuBarLayoutItem
BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMenuField* 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));
}
BMenuField::MenuBarLayoutItem::MenuBarLayoutItem(BMessage* from)
:
BAbstractLayoutItem(from),
fParent(NULL),
fFrame()
{
from->FindRect(kFrameField, &fFrame);
}
BRect
BMenuField::MenuBarLayoutItem::FrameInParent() const
{
return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top);
}
bool
BMenuField::MenuBarLayoutItem::IsVisible()
{
return !fParent->IsHidden(fParent);
}
void
BMenuField::MenuBarLayoutItem::SetVisible(bool visible)
{
// not allowed
}
BRect
BMenuField::MenuBarLayoutItem::Frame()
{
return fFrame;
}
void
BMenuField::MenuBarLayoutItem::SetFrame(BRect frame)
{
fFrame = frame;
fParent->_UpdateFrame();
}
void
BMenuField::MenuBarLayoutItem::SetParent(BMenuField* parent)
{
fParent = parent;
}
BView*
BMenuField::MenuBarLayoutItem::View()
{
return fParent;
}
BSize
BMenuField::MenuBarLayoutItem::BaseMinSize()
{
fParent->_ValidateLayoutData();
BSize size = fParent->fLayoutData->menu_bar_min;
size.width += 2 * kVMargin;
size.height += 2 * kVMargin;
return size;
}
BSize
BMenuField::MenuBarLayoutItem::BaseMaxSize()
{
BSize size(BaseMinSize());
size.width = B_SIZE_UNLIMITED;
return size;
}
BSize
BMenuField::MenuBarLayoutItem::BasePreferredSize()
{
return BaseMinSize();
}
BAlignment
BMenuField::MenuBarLayoutItem::BaseAlignment()
{
return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
}
status_t
BMenuField::MenuBarLayoutItem::Archive(BMessage* into, bool deep) const
{
BArchiver archiver(into);
status_t err = BAbstractLayoutItem::Archive(into, deep);
if (err == B_OK)
err = into->AddRect(kFrameField, fFrame);
return archiver.Finish(err);
}
BArchivable*
BMenuField::MenuBarLayoutItem::Instantiate(BMessage* from)
{
if (validate_instantiation(from, "BMenuField::MenuBarLayoutItem"))
return new MenuBarLayoutItem(from);
return NULL;
}
extern "C" void
B_IF_GCC_2(InvalidateLayout__10BMenuFieldb, _ZN10BMenuField16InvalidateLayoutEb)(
BMenuField* field, bool descendants)
{
perform_data_layout_invalidated data;
data.descendants = descendants;
field->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: font_info, label_width, label_height.