Files
haiku-beta6/src/kits/interface/MenuBar.cpp
T
Stephan Aßmus 2f86ba4557 Implemented a new look for the Haiku interface controls. It was
overheard that they looked too ninety-ish.
TODO: The code behind this is work in progress. The basic idea
is to extract all drawing code into a new class BControlLook,
of which there is a global instance be_control_look, instantiated
in InterfaceDefs.cpp. At the moment, all the old drawing code is
still there, and the usage of be_control_look is inside if-bodies
checking the instance against NULL. In another words, by not
instanitating be_control_look, you can revert back to the old look.
BControlLook's job is to provide reusable methods for drawing
certain types of frames, backgrounds and labels, so that application
developers can make controls that re-use the same drawing code
as built-in controls and adopt to changes made there. I have added
the notion of "borders". Each of the frame drawing methods can be
made to draw certain borders only, which is supposed to help when
controls shall visually attach. This feature is not fully explored
at all ATM.
TODO: Update BColumnListView header view and BStringItem text
spacing. Update other apps where it makes sense to use BControlLook.
For the moment, only Tracker and LaunchBox are updated. More...
NOTE: The new look is not very radically different, so that existing
apps do not immediately look too ugly or out of place.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@29221 a95241bf-73f2-0310-859d-f6bbb57e9c96
2009-02-15 18:23:19 +00:00

711 lines
14 KiB
C++

/*
* Copyright 2001-2009, Haiku, Inc.
* Distributed under the terms of the MIT License.
*
* Authors:
* Marc Flerackers ([email protected])
* Stefano Ceccherini ([email protected])
* Stephan Aßmus <[email protected]>
*/
#include <MenuBar.h>
#include <math.h>
#include <Application.h>
#include <Autolock.h>
#include <ControlLook.h>
#include <LayoutUtils.h>
#include <MenuItem.h>
#include <Window.h>
#include <AppMisc.h>
#include <binary_compatibility/Interface.h>
#include <MenuPrivate.h>
#include <TokenSpace.h>
#include "BMCPrivate.h"
using BPrivate::gDefaultTokens;
struct menubar_data {
BMenuBar *menuBar;
int32 menuIndex;
bool sticky;
bool showMenu;
bool useRect;
BRect rect;
};
BMenuBar::BMenuBar(BRect frame, const char *title, uint32 resizeMask,
menu_layout layout, bool resizeToFit)
: BMenu(frame, title, resizeMask, B_WILL_DRAW | B_FRAME_EVENTS, layout,
resizeToFit),
fBorder(B_BORDER_FRAME),
fTrackingPID(-1),
fPrevFocusToken(-1),
fMenuSem(-1),
fLastBounds(NULL),
fTracking(false)
{
_InitData(layout);
}
BMenuBar::BMenuBar(const char *title, menu_layout layout, uint32 flags)
: BMenu(BRect(), title, B_FOLLOW_NONE,
flags | B_WILL_DRAW | B_FRAME_EVENTS | B_SUPPORTS_LAYOUT,
layout, false),
fBorder(B_BORDER_FRAME),
fTrackingPID(-1),
fPrevFocusToken(-1),
fMenuSem(-1),
fLastBounds(NULL),
fTracking(false)
{
_InitData(layout);
}
BMenuBar::BMenuBar(BMessage *data)
: BMenu(data),
fBorder(B_BORDER_FRAME),
fTrackingPID(-1),
fPrevFocusToken(-1),
fMenuSem(-1),
fLastBounds(NULL),
fTracking(false)
{
int32 border;
if (data->FindInt32("_border", &border) == B_OK)
SetBorder((menu_bar_border)border);
menu_layout layout = B_ITEMS_IN_COLUMN;
data->FindInt32("_layout", (int32 *)&layout);
_InitData(layout);
}
BMenuBar::~BMenuBar()
{
if (fTracking) {
status_t dummy;
wait_for_thread(fTrackingPID, &dummy);
}
delete fLastBounds;
}
BArchivable *
BMenuBar::Instantiate(BMessage *data)
{
if (validate_instantiation(data, "BMenuBar"))
return new BMenuBar(data);
return NULL;
}
status_t
BMenuBar::Archive(BMessage *data, bool deep) const
{
status_t err = BMenu::Archive(data, deep);
if (err < B_OK)
return err;
if (Border() != B_BORDER_FRAME)
err = data->AddInt32("_border", Border());
return err;
}
void
BMenuBar::SetBorder(menu_bar_border border)
{
fBorder = border;
}
menu_bar_border
BMenuBar::Border() const
{
return fBorder;
}
void
BMenuBar::Draw(BRect updateRect)
{
if (_RelayoutIfNeeded()) {
Invalidate();
return;
}
if (be_control_look != NULL) {
BRect rect(Bounds());
rgb_color base = LowColor();
uint32 flags = 0;
be_control_look->DrawBorder(this, rect, updateRect, base,
B_PLAIN_BORDER, flags, BControlLook::B_BOTTOM_BORDER);
be_control_look->DrawMenuBarBackground(this, rect, updateRect, base);
_DrawItems(updateRect);
return;
}
// TODO: implement additional border styles
rgb_color color = HighColor();
BRect bounds(Bounds());
// Restore the background of the previously selected menuitem
DrawBackground(bounds & updateRect);
rgb_color noTint = LowColor();
SetHighColor(tint_color(noTint, B_LIGHTEN_2_TINT));
StrokeLine(BPoint(0.0f, bounds.bottom - 2.0f), BPoint(0.0f, 0.0f));
StrokeLine(BPoint(bounds.right, 0.0f));
SetHighColor(tint_color(noTint, B_DARKEN_1_TINT));
StrokeLine(BPoint(1.0f, bounds.bottom - 1.0f),
BPoint(bounds.right, bounds.bottom - 1.0f));
SetHighColor(tint_color(noTint, B_DARKEN_2_TINT));
StrokeLine(BPoint(0.0f, bounds.bottom), BPoint(bounds.right, bounds.bottom));
StrokeLine(BPoint(bounds.right, 0.0f), BPoint(bounds.right, bounds.bottom));
SetHighColor(color);
// revert to previous used color (cheap PushState()/PopState())
_DrawItems(updateRect);
}
void
BMenuBar::AttachedToWindow()
{
_Install(Window());
Window()->SetKeyMenuBar(this);
BMenu::AttachedToWindow();
*fLastBounds = Bounds();
}
void
BMenuBar::DetachedFromWindow()
{
BMenu::DetachedFromWindow();
}
void
BMenuBar::MessageReceived(BMessage *msg)
{
BMenu::MessageReceived(msg);
}
void
BMenuBar::MouseDown(BPoint where)
{
if (fTracking)
return;
BWindow *window = Window();
if (!window->IsActive() || !window->IsFront()) {
window->Activate();
window->UpdateIfNeeded();
}
StartMenuBar(-1, false, false);
}
void
BMenuBar::WindowActivated(bool state)
{
BView::WindowActivated(state);
}
void
BMenuBar::MouseUp(BPoint where)
{
BView::MouseUp(where);
}
void
BMenuBar::FrameMoved(BPoint newPosition)
{
BMenu::FrameMoved(newPosition);
}
void
BMenuBar::FrameResized(float newWidth, float newHeight)
{
// invalidate right border
if (newWidth != fLastBounds->Width()) {
BRect rect(min_c(fLastBounds->right, newWidth), 0,
max_c(fLastBounds->right, newWidth), newHeight);
Invalidate(rect);
}
// invalidate bottom border
if (newHeight != fLastBounds->Height()) {
BRect rect(0, min_c(fLastBounds->bottom, newHeight) - 1,
newWidth, max_c(fLastBounds->bottom, newHeight));
Invalidate(rect);
}
fLastBounds->Set(0, 0, newWidth, newHeight);
BMenu::FrameResized(newWidth, newHeight);
}
void
BMenuBar::Show()
{
BView::Show();
}
void
BMenuBar::Hide()
{
BView::Hide();
}
BHandler *
BMenuBar::ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, int32 form, const char *property)
{
return BMenu::ResolveSpecifier(msg, index, specifier, form, property);
}
status_t
BMenuBar::GetSupportedSuites(BMessage *data)
{
return BMenu::GetSupportedSuites(data);
}
void
BMenuBar::ResizeToPreferred()
{
BMenu::ResizeToPreferred();
}
void
BMenuBar::GetPreferredSize(float *width, float *height)
{
BMenu::GetPreferredSize(width, height);
}
void
BMenuBar::MakeFocus(bool state)
{
BMenu::MakeFocus(state);
}
void
BMenuBar::AllAttached()
{
BMenu::AllAttached();
}
void
BMenuBar::AllDetached()
{
BMenu::AllDetached();
}
status_t
BMenuBar::Perform(perform_code code, void* _data)
{
switch (code) {
case PERFORM_CODE_MIN_SIZE:
((perform_data_min_size*)_data)->return_value
= BMenuBar::MinSize();
return B_OK;
case PERFORM_CODE_MAX_SIZE:
((perform_data_max_size*)_data)->return_value
= BMenuBar::MaxSize();
return B_OK;
case PERFORM_CODE_PREFERRED_SIZE:
((perform_data_preferred_size*)_data)->return_value
= BMenuBar::PreferredSize();
return B_OK;
case PERFORM_CODE_LAYOUT_ALIGNMENT:
((perform_data_layout_alignment*)_data)->return_value
= BMenuBar::LayoutAlignment();
return B_OK;
case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
((perform_data_has_height_for_width*)_data)->return_value
= BMenuBar::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;
BMenuBar::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;
BMenuBar::SetLayout(data->layout);
return B_OK;
}
case PERFORM_CODE_INVALIDATE_LAYOUT:
{
perform_data_invalidate_layout* data
= (perform_data_invalidate_layout*)_data;
BMenuBar::InvalidateLayout(data->descendants);
return B_OK;
}
case PERFORM_CODE_DO_LAYOUT:
{
BMenuBar::DoLayout();
return B_OK;
}
}
return BMenu::Perform(code, _data);
}
BSize
BMenuBar::MinSize()
{
return BMenu::MinSize();
}
BSize
BMenuBar::MaxSize()
{
BSize size = BMenu::MaxSize();
return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
BSize(B_SIZE_UNLIMITED, size.height));
}
BSize
BMenuBar::PreferredSize()
{
return BMenu::PreferredSize();
}
// #pragma mark -
void BMenuBar::_ReservedMenuBar1() {}
void BMenuBar::_ReservedMenuBar2() {}
void BMenuBar::_ReservedMenuBar3() {}
void BMenuBar::_ReservedMenuBar4() {}
BMenuBar &
BMenuBar::operator=(const BMenuBar &)
{
return *this;
}
void
BMenuBar::StartMenuBar(int32 menuIndex, bool sticky, bool showMenu, BRect *specialRect)
{
if (fTracking)
return;
BWindow *window = Window();
if (window == NULL)
debugger("MenuBar must be added to a window before it can be used.");
BAutolock lock(window);
if (!lock.IsLocked())
return;
fPrevFocusToken = -1;
fTracking = true;
// We are called from the window's thread,
// so let's call MenusBeginning() directly
window->MenusBeginning();
fMenuSem = create_sem(0, "window close sem");
_set_menu_sem_(window, fMenuSem);
fTrackingPID = spawn_thread(_TrackTask, "menu_tracking", B_DISPLAY_PRIORITY, NULL);
if (fTrackingPID >= 0) {
menubar_data data;
data.menuBar = this;
data.menuIndex = menuIndex;
data.sticky = sticky;
data.showMenu = showMenu;
data.useRect = specialRect != NULL;
if (data.useRect)
data.rect = *specialRect;
resume_thread(fTrackingPID);
send_data(fTrackingPID, 0, &data, sizeof(data));
} else {
fTracking = false;
_set_menu_sem_(window, B_NO_MORE_SEMS);
delete_sem(fMenuSem);
}
}
/* static */
int32
BMenuBar::_TrackTask(void *arg)
{
menubar_data data;
thread_id id;
receive_data(&id, &data, sizeof(data));
BMenuBar *menuBar = data.menuBar;
if (data.useRect)
menuBar->fExtraRect = &data.rect;
menuBar->_SetStickyMode(data.sticky);
int32 action;
menuBar->_Track(&action, data.menuIndex, data.showMenu);
menuBar->fTracking = false;
menuBar->fExtraRect = NULL;
// We aren't the BWindow thread, so don't call MenusEnded() directly
BWindow *window = menuBar->Window();
window->PostMessage(_MENUS_DONE_);
_set_menu_sem_(window, B_BAD_SEM_ID);
delete_sem(menuBar->fMenuSem);
menuBar->fMenuSem = B_BAD_SEM_ID;
return 0;
}
BMenuItem *
BMenuBar::_Track(int32 *action, int32 startIndex, bool showMenu)
{
// TODO: Cleanup, merge some "if" blocks if possible
fChosenItem = NULL;
BWindow *window = Window();
fState = MENU_STATE_TRACKING;
BPoint where;
uint32 buttons;
if (window->Lock()) {
if (startIndex != -1) {
be_app->ObscureCursor();
_SelectItem(ItemAt(startIndex), true, true);
}
GetMouse(&where, &buttons);
window->Unlock();
}
while (fState != MENU_STATE_CLOSED) {
bigtime_t snoozeAmount = 40000;
if (Window() == NULL || !window->Lock())
break;
BMenuItem* menuItem = NULL;
if (dynamic_cast<_BMCMenuBar_*>(this))
menuItem = ItemAt(0);
else
menuItem = _HitTestItems(where, B_ORIGIN);
if (_OverSubmenu(fSelected, ConvertToScreen(where))) {
// call _Track() from the selected sub-menu when the mouse cursor
// is over its window
BMenu *menu = fSelected->Submenu();
window->Unlock();
snoozeAmount = 30000;
bool wasSticky = _IsStickyMode();
menu->_SetStickyMode(wasSticky);
int localAction;
fChosenItem = menu->_Track(&localAction);
// The mouse could have meen moved since the last time we
// checked its position, or buttons might have been pressed.
// Unfortunately our child menus don't tell
// us the new position.
// TODO: Maybe have a shared struct between all menus
// where to store the current mouse position ?
// (Or just use the BView mouse hooks)
BPoint newWhere;
if (window->Lock()) {
GetMouse(&newWhere, &buttons);
window->Unlock();
}
// This code is needed to make menus
// that are children of BMenuFields "sticky" (see ticket #953)
if (localAction == MENU_STATE_CLOSED) {
if (fExtraRect != NULL && fExtraRect->Contains(where)
// 9 = 3 pixels ^ 2 (since point_distance() returns the square of the distance)
&& point_distance(newWhere, where) < 9) {
_SetStickyMode(true);
fExtraRect = NULL;
} else
fState = MENU_STATE_CLOSED;
}
if (!window->Lock())
break;
} else if (menuItem != NULL) {
if (menuItem->Submenu() != NULL && menuItem != fSelected) {
if (menuItem->Submenu()->Window() == NULL) {
// open the menu if it's not opened yet
_SelectItem(menuItem);
} else {
// Menu was already opened, close it and bail
_SelectItem(NULL);
fState = MENU_STATE_CLOSED;
fChosenItem = NULL;
}
} else {
// No submenu, just select the item
_SelectItem(menuItem);
}
} else if (menuItem == NULL && fSelected != NULL
&& !_IsStickyMode() && Bounds().Contains(where)) {
_SelectItem(NULL);
fState = MENU_STATE_TRACKING;
}
window->Unlock();
if (fState != MENU_STATE_CLOSED) {
// if user doesn't move the mouse, loop here,
// so we don't interfer with keyboard menu navigation
BPoint newLocation;
uint32 newButtons;
do {
snooze(snoozeAmount);
if (!LockLooper())
break;
GetMouse(&newLocation, &newButtons, true);
UnlockLooper();
} while (newLocation == where
&& newButtons == buttons);
where = newLocation;
buttons = newButtons;
if (buttons != 0 && _IsStickyMode()) {
if (menuItem == NULL
|| (menuItem->Submenu() && menuItem->Submenu()->Window())) {
// clicked outside menu bar or on item with already
// open sub menu
fState = MENU_STATE_CLOSED;
} else
_SetStickyMode(false);
} else if (buttons == 0 && !_IsStickyMode()) {
if ((fSelected != NULL && fSelected->Submenu() == NULL)
|| menuItem == NULL) {
fChosenItem = fSelected;
fState = MENU_STATE_CLOSED;
} else
_SetStickyMode(true);
}
}
}
if (window->Lock()) {
if (fSelected != NULL)
_SelectItem(NULL);
if (fChosenItem != NULL)
fChosenItem->Invoke();
_RestoreFocus();
window->Unlock();
}
if (_IsStickyMode())
_SetStickyMode(false);
_DeleteMenuWindow();
if (action != NULL)
*action = fState;
return fChosenItem;
}
void
BMenuBar::_StealFocus()
{
// We already stole the focus, don't do anything
if (fPrevFocusToken != -1)
return;
BWindow *window = Window();
if (window != NULL && window->Lock()) {
BView *focus = window->CurrentFocus();
if (focus != NULL && focus != this)
fPrevFocusToken = _get_object_token_(focus);
MakeFocus();
window->Unlock();
}
}
void
BMenuBar::_RestoreFocus()
{
BWindow *window = Window();
if (window != NULL && window->Lock()) {
BHandler *handler = NULL;
if (fPrevFocusToken != -1
&& gDefaultTokens.GetToken(fPrevFocusToken, B_HANDLER_TOKEN, (void **)&handler) == B_OK) {
BView *view = dynamic_cast<BView *>(handler);
if (view != NULL && view->Window() == window)
view->MakeFocus();
} else if (IsFocus())
MakeFocus(false);
fPrevFocusToken = -1;
window->Unlock();
}
}
void
BMenuBar::_InitData(menu_layout layout)
{
fLastBounds = new BRect(Bounds());
SetItemMargins(8, 2, 8, 2);
_SetIgnoreHidden(true);
}