blob: 011a8b31d51ba46740ffc390e147478191c0f681 [file] [log] [blame]
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <atlbase.h>
#include <atlapp.h>
#include "chrome/views/menu_button.h"
#include "chrome/app/theme/theme_resources.h"
#include "chrome/common/drag_drop_types.h"
#include "chrome/common/gfx/chrome_canvas.h"
#include "chrome/common/l10n_util.h"
#include "chrome/common/resource_bundle.h"
#include "chrome/common/win_util.h"
#include "chrome/views/button.h"
#include "chrome/views/event.h"
#include "chrome/views/root_view.h"
#include "chrome/views/view_menu_delegate.h"
#include "chrome/views/widget.h"
#include "generated_resources.h"
using base::Time;
using base::TimeDelta;
namespace views {
// The amount of time, in milliseconds, we wait before allowing another mouse
// pressed event to show the menu.
static const int64 kMinimumTimeBetweenButtonClicks = 100;
// The down arrow used to differentiate the menu button from normal
// text buttons.
static const SkBitmap* kMenuMarker = NULL;
// How much padding to put on the left and right of the menu marker.
static const int kMenuMarkerPaddingLeft = 3;
static const int kMenuMarkerPaddingRight = -1;
////////////////////////////////////////////////////////////////////////////////
//
// MenuButton - constructors, destructors, initialization
//
////////////////////////////////////////////////////////////////////////////////
MenuButton::MenuButton(const std::wstring& text,
ViewMenuDelegate* menu_delegate,
bool show_menu_marker)
: TextButton(text),
menu_visible_(false),
menu_closed_time_(),
menu_delegate_(menu_delegate),
show_menu_marker_(show_menu_marker) {
if (kMenuMarker == NULL) {
kMenuMarker = ResourceBundle::GetSharedInstance()
.GetBitmapNamed(IDR_MENU_DROPARROW);
}
SetTextAlignment(TextButton::ALIGN_LEFT);
}
MenuButton::~MenuButton() {
}
////////////////////////////////////////////////////////////////////////////////
//
// MenuButton - Public APIs
//
////////////////////////////////////////////////////////////////////////////////
gfx::Size MenuButton::GetPreferredSize() {
gfx::Size prefsize = TextButton::GetPreferredSize();
if (show_menu_marker_) {
prefsize.Enlarge(kMenuMarker->width() + kMenuMarkerPaddingLeft +
kMenuMarkerPaddingRight,
0);
}
return prefsize;
}
void MenuButton::Paint(ChromeCanvas* canvas, bool for_drag) {
TextButton::Paint(canvas, for_drag);
if (show_menu_marker_) {
gfx::Insets insets = GetInsets();
// We can not use the views' mirroring infrastructure for mirroring a
// MenuButton control (see TextButton::Paint() for a detailed explanation
// regarding why we can not flip the canvas). Therefore, we need to
// manually mirror the position of the down arrow.
gfx::Rect arrow_bounds(width() - insets.right() -
kMenuMarker->width() - kMenuMarkerPaddingRight,
height() / 2 - kMenuMarker->height() / 2,
kMenuMarker->width(),
kMenuMarker->height());
arrow_bounds.set_x(MirroredLeftPointForRect(arrow_bounds));
canvas->DrawBitmapInt(*kMenuMarker, arrow_bounds.x(), arrow_bounds.y());
}
}
////////////////////////////////////////////////////////////////////////////////
//
// MenuButton - Events
//
////////////////////////////////////////////////////////////////////////////////
int MenuButton::GetMaximumScreenXCoordinate() {
Widget* widget = GetWidget();
if (!widget) {
NOTREACHED();
return 0;
}
HWND hwnd = widget->GetHWND();
CRect t;
::GetWindowRect(hwnd, &t);
gfx::Rect r(t);
gfx::Rect monitor_rect = win_util::GetMonitorBoundsForRect(r);
return monitor_rect.x() + monitor_rect.width() - 1;
}
bool MenuButton::Activate() {
SetState(BS_PUSHED);
// We need to synchronously paint here because subsequently we enter a
// menu modal loop which will stop this window from updating and
// receiving the paint message that should be spawned by SetState until
// after the menu closes.
PaintNow();
if (menu_delegate_) {
gfx::Rect lb = GetLocalBounds(true);
// The position of the menu depends on whether or not the locale is
// right-to-left.
gfx::Point menu_position(lb.right(), lb.bottom());
if (UILayoutIsRightToLeft())
menu_position.set_x(lb.x());
View::ConvertPointToScreen(this, &menu_position);
if (UILayoutIsRightToLeft())
menu_position.Offset(2, -4);
else
menu_position.Offset(-2, -4);
int max_x_coordinate = GetMaximumScreenXCoordinate();
if (max_x_coordinate && max_x_coordinate <= menu_position.x())
menu_position.set_x(max_x_coordinate - 1);
// We're about to show the menu from a mouse press. By showing from the
// mouse press event we block RootView in mouse dispatching. This also
// appears to cause RootView to get a mouse pressed BEFORE the mouse
// release is seen, which means RootView sends us another mouse press no
// matter where the user pressed. To force RootView to recalculate the
// mouse target during the mouse press we explicitly set the mouse handler
// to NULL.
GetRootView()->SetMouseHandler(NULL);
menu_visible_ = true;
menu_delegate_->RunMenu(this, menu_position.ToPOINT(),
GetWidget()->GetHWND());
menu_visible_ = false;
menu_closed_time_ = Time::Now();
// Now that the menu has closed, we need to manually reset state to
// "normal" since the menu modal loop will have prevented normal
// mouse move messages from getting to this View. We set "normal"
// and not "hot" because the likelihood is that the mouse is now
// somewhere else (user clicked elsewhere on screen to close the menu
// or selected an item) and we will inevitably refresh the hot state
// in the event the mouse _is_ over the view.
SetState(BS_NORMAL);
// We must return false here so that the RootView does not get stuck
// sending all mouse pressed events to us instead of the appropriate
// target.
return false;
}
return true;
}
bool MenuButton::OnMousePressed(const MouseEvent& e) {
if (IsFocusable())
RequestFocus();
if (GetState() != BS_DISABLED) {
// If we're draggable (GetDragOperations returns a non-zero value), then
// don't pop on press, instead wait for release.
if (e.IsOnlyLeftMouseButton() && HitTest(e.location()) &&
GetDragOperations(e.x(), e.y()) == DragDropTypes::DRAG_NONE) {
TimeDelta delta = Time::Now() - menu_closed_time_;
int64 delta_in_milliseconds = delta.InMilliseconds();
if (delta_in_milliseconds > kMinimumTimeBetweenButtonClicks) {
return Activate();
}
}
}
return true;
}
void MenuButton::OnMouseReleased(const MouseEvent& e,
bool canceled) {
if (GetDragOperations(e.x(), e.y()) != DragDropTypes::DRAG_NONE &&
GetState() != BS_DISABLED && !canceled && !InDrag() &&
e.IsOnlyLeftMouseButton() && HitTest(e.location())) {
Activate();
} else {
TextButton::OnMouseReleased(e, canceled);
}
}
// When the space bar or the enter key is pressed we need to show the menu.
bool MenuButton::OnKeyReleased(const KeyEvent& e) {
if ((e.GetCharacter() == VK_SPACE) || (e.GetCharacter() == VK_RETURN)) {
return Activate();
}
return true;
}
// The reason we override View::OnMouseExited is because we get this event when
// we display the menu. If we don't override this method then
// BaseButton::OnMouseExited will get the event and will set the button's state
// to BS_NORMAL instead of keeping the state BM_PUSHED. This, in turn, will
// cause the button to appear depressed while the menu is displayed.
void MenuButton::OnMouseExited(const MouseEvent& event) {
if ((state_ != BS_DISABLED) && (!menu_visible_) && (!InDrag())) {
SetState(BS_NORMAL);
}
}
////////////////////////////////////////////////////////////////////////////////
//
// MenuButton - accessibility
//
////////////////////////////////////////////////////////////////////////////////
bool MenuButton::GetAccessibleDefaultAction(std::wstring* action) {
DCHECK(action);
action->assign(l10n_util::GetString(IDS_ACCACTION_PRESS));
return true;
}
bool MenuButton::GetAccessibleRole(VARIANT* role) {
DCHECK(role);
role->vt = VT_I4;
role->lVal = ROLE_SYSTEM_BUTTONDROPDOWN;
return true;
}
bool MenuButton::GetAccessibleState(VARIANT* state) {
DCHECK(state);
state->lVal |= STATE_SYSTEM_HASPOPUP;
return true;
}
} // namespace views