diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-24 15:46:45 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-08-24 15:46:45 +0000 |
commit | a87ecb3c7170caa40810def524fbae1aee4d3533 (patch) | |
tree | 947732a745f124feda7ace8ac466cf75c746db62 /views/controls/menu | |
parent | de1b764f404e5fb6975e7cc9de7120d46e28dcfd (diff) | |
download | chromium_src-a87ecb3c7170caa40810def524fbae1aee4d3533.zip chromium_src-a87ecb3c7170caa40810def524fbae1aee4d3533.tar.gz chromium_src-a87ecb3c7170caa40810def524fbae1aee4d3533.tar.bz2 |
Further refactoring of menus for GTK. I've now separated out
everything and menus some what work on GTK. I need to get painting
working and I'm sure there will be fine tuning, but I want to check in
all this splitting of files before someone else needs change one of
these files.
Thankfully I wrote an interactive ui test that exercises much of this code, and it still passes:)
BUG=none
TEST=thoroughly test bookmark menus on windows to make sure I didn't
break them.
Review URL: http://codereview.chromium.org/174274
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24098 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'views/controls/menu')
23 files changed, 1086 insertions, 4403 deletions
diff --git a/views/controls/menu/chrome_menu.cc b/views/controls/menu/chrome_menu.cc deleted file mode 100644 index 351e082..0000000 --- a/views/controls/menu/chrome_menu.cc +++ /dev/null @@ -1,2833 +0,0 @@ -// 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 "views/controls/menu/chrome_menu.h" - -#include <windows.h> -#include <uxtheme.h> -#include <Vssym32.h> - -#include "app/gfx/canvas.h" -#include "app/gfx/color_utils.h" -#include "app/l10n_util.h" -#include "app/l10n_util_win.h" -#include "app/os_exchange_data.h" -#include "app/os_exchange_data_provider_win.h" -#include "base/base_drag_source.h" -#include "base/gfx/native_theme.h" -#include "base/message_loop.h" -#include "base/task.h" -#include "base/timer.h" -#include "base/win_util.h" -#include "grit/app_strings.h" -#include "skia/ext/skia_utils_win.h" -#include "views/border.h" -#include "views/drag_utils.h" -#include "views/focus/focus_manager.h" -#include "views/view_constants.h" -#include "views/widget/root_view.h" -#include "views/widget/widget_win.h" - -#undef min -#undef max - -using base::Time; -using base::TimeDelta; - -// Margins between the top of the item and the label. -static const int kItemTopMargin = 3; - -// Margins between the bottom of the item and the label. -static const int kItemBottomMargin = 4; - -// Margins used if the menu doesn't have icons. -static const int kItemNoIconTopMargin = 1; -static const int kItemNoIconBottomMargin = 3; - -// Margins between the left of the item and the icon. -static const int kItemLeftMargin = 4; - -// Padding between the label and submenu arrow. -static const int kLabelToArrowPadding = 10; - -// Padding between the arrow and the edge. -static const int kArrowToEdgePadding = 5; - -// Padding between the icon and label. -static const int kIconToLabelPadding = 8; - -// Padding between the gutter and label. -static const int kGutterToLabel = 5; - -// Height of the scroll arrow. -// This goes up to 4 with large fonts, but this is close enough for now. -static const int kScrollArrowHeight = 3; - -// Size of the check. This comes from the OS. -static int check_width; -static int check_height; - -// Size of the submenu arrow. This comes from the OS. -static int arrow_width; -static int arrow_height; - -// Width of the gutter. Only used if render_gutter is true. -static int gutter_width; - -// Margins between the right of the item and the label. -static int item_right_margin; - -// X-coordinate of where the label starts. -static int label_start; - -// Height of the separator. -static int separator_height; - -// Padding around the edges of the submenu. -static const int kSubmenuBorderSize = 3; - -// Amount to inset submenus. -static const int kSubmenuHorizontalInset = 3; - -// Delay, in ms, between when menus are selected are moused over and the menu -// appears. -static const int kShowDelay = 400; - -// Amount of time from when the drop exits the menu and the menu is hidden. -static const int kCloseOnExitTime = 1200; - -// Height of the drop indicator. This should be an event number. -static const int kDropIndicatorHeight = 2; - -// Color of the drop indicator. -static const SkColor kDropIndicatorColor = SK_ColorBLACK; - -// Whether or not the gutter should be rendered. The gutter is specific to -// Vista. -static bool render_gutter = false; - -// Max width of a menu. There does not appear to be an OS value for this, yet -// both IE and FF restrict the max width of a menu. -static const int kMaxMenuWidth = 400; - -// Period of the scroll timer (in milliseconds). -static const int kScrollTimerMS = 30; - -// Preferred height of menu items. Reset every time a menu is run. -static int pref_menu_height; - -// Are mnemonics shown? This is updated before the menus are shown. -static bool show_mnemonics; - -using gfx::NativeTheme; - -namespace views { - -namespace { - -// Returns the font menus are to use. -gfx::Font GetMenuFont() { - NONCLIENTMETRICS metrics; - win_util::GetNonClientMetrics(&metrics); - - l10n_util::AdjustUIFont(&(metrics.lfMenuFont)); - HFONT font = CreateFontIndirect(&metrics.lfMenuFont); - DLOG_ASSERT(font); - return gfx::Font::CreateFont(font); -} - -// Calculates all sizes that we can from the OS. -// -// This is invoked prior to Running a menu. -void UpdateMenuPartSizes(bool has_icons) { - HDC dc = GetDC(NULL); - RECT bounds = { 0, 0, 200, 200 }; - SIZE check_size; - if (NativeTheme::instance()->GetThemePartSize( - NativeTheme::MENU, dc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, &bounds, - TS_TRUE, &check_size) == S_OK) { - check_width = check_size.cx; - check_height = check_size.cy; - } else { - check_width = GetSystemMetrics(SM_CXMENUCHECK); - check_height = GetSystemMetrics(SM_CYMENUCHECK); - } - - SIZE arrow_size; - if (NativeTheme::instance()->GetThemePartSize( - NativeTheme::MENU, dc, MENU_POPUPSUBMENU, MSM_NORMAL, &bounds, - TS_TRUE, &arrow_size) == S_OK) { - arrow_width = arrow_size.cx; - arrow_height = arrow_size.cy; - } else { - // Sadly I didn't see a specify metrics for this. - arrow_width = GetSystemMetrics(SM_CXMENUCHECK); - arrow_height = GetSystemMetrics(SM_CYMENUCHECK); - } - - SIZE gutter_size; - if (NativeTheme::instance()->GetThemePartSize( - NativeTheme::MENU, dc, MENU_POPUPGUTTER, MSM_NORMAL, &bounds, - TS_TRUE, &gutter_size) == S_OK) { - gutter_width = gutter_size.cx; - render_gutter = true; - } else { - gutter_width = 0; - render_gutter = false; - } - - SIZE separator_size; - if (NativeTheme::instance()->GetThemePartSize( - NativeTheme::MENU, dc, MENU_POPUPSEPARATOR, MSM_NORMAL, &bounds, - TS_TRUE, &separator_size) == S_OK) { - separator_height = separator_size.cy; - } else { - separator_height = GetSystemMetrics(SM_CYMENU) / 2; - } - - item_right_margin = kLabelToArrowPadding + arrow_width + kArrowToEdgePadding; - - if (has_icons) { - label_start = kItemLeftMargin + check_width + kIconToLabelPadding; - } else { - // If there are no icons don't pad by the icon to label padding. This - // makes us look close to system menus. - label_start = kItemLeftMargin + check_width; - } - if (render_gutter) - label_start += gutter_width + kGutterToLabel; - - ReleaseDC(NULL, dc); - - MenuItemView menu_item(NULL); - menu_item.SetTitle(L"blah"); // Text doesn't matter here. - pref_menu_height = menu_item.GetPreferredSize().height(); -} - -// Convenience for scrolling the view such that the origin is visible. -static void ScrollToVisible(View* view) { - view->ScrollRectToVisible(0, 0, view->width(), view->height()); -} - -} // namespace - -// MenuScrollTask -------------------------------------------------------------- - -// MenuScrollTask is used when the SubmenuView does not all fit on screen and -// the mouse is over the scroll up/down buttons. MenuScrollTask schedules -// itself with a RepeatingTimer. When Run is invoked MenuScrollTask scrolls -// appropriately. - -class MenuController::MenuScrollTask { - public: - MenuScrollTask() : submenu_(NULL) { - pixels_per_second_ = pref_menu_height * 20; - } - - void Update(const MenuController::MenuPart& part) { - if (!part.is_scroll()) { - StopScrolling(); - return; - } - DCHECK(part.submenu); - SubmenuView* new_menu = part.submenu; - bool new_is_up = (part.type == MenuController::MenuPart::SCROLL_UP); - if (new_menu == submenu_ && is_scrolling_up_ == new_is_up) - return; - - start_scroll_time_ = Time::Now(); - start_y_ = part.submenu->GetVisibleBounds().y(); - submenu_ = new_menu; - is_scrolling_up_ = new_is_up; - - if (!scrolling_timer_.IsRunning()) { - scrolling_timer_.Start(TimeDelta::FromMilliseconds(kScrollTimerMS), this, - &MenuScrollTask::Run); - } - } - - void StopScrolling() { - if (scrolling_timer_.IsRunning()) { - scrolling_timer_.Stop(); - submenu_ = NULL; - } - } - - // The menu being scrolled. Returns null if not scrolling. - SubmenuView* submenu() const { return submenu_; } - - private: - void Run() { - DCHECK(submenu_); - gfx::Rect vis_rect = submenu_->GetVisibleBounds(); - const int delta_y = static_cast<int>( - (Time::Now() - start_scroll_time_).InMilliseconds() * - pixels_per_second_ / 1000); - int target_y = start_y_; - if (is_scrolling_up_) - target_y = std::max(0, target_y - delta_y); - else - target_y = std::min(submenu_->height() - vis_rect.height(), - target_y + delta_y); - submenu_->ScrollRectToVisible(vis_rect.x(), target_y, vis_rect.width(), - vis_rect.height()); - } - - // SubmenuView being scrolled. - SubmenuView* submenu_; - - // Direction scrolling. - bool is_scrolling_up_; - - // Timer to periodically scroll. - base::RepeatingTimer<MenuScrollTask> scrolling_timer_; - - // Time we started scrolling at. - Time start_scroll_time_; - - // How many pixels to scroll per second. - int pixels_per_second_; - - // Y-coordinate of submenu_view_ when scrolling started. - int start_y_; - - DISALLOW_COPY_AND_ASSIGN(MenuScrollTask); -}; - -namespace { - -// MenuScrollButton ------------------------------------------------------------ - -// MenuScrollButton is used for the scroll buttons when not all menu items fit -// on screen. MenuScrollButton forwards appropriate events to the -// MenuController. - -class MenuScrollButton : public View { - public: - explicit MenuScrollButton(SubmenuView* host, bool is_up) - : host_(host), - is_up_(is_up), - // Make our height the same as that of other MenuItemViews. - pref_height_(pref_menu_height) { - } - - virtual gfx::Size GetPreferredSize() { - return gfx::Size(kScrollArrowHeight * 2 - 1, pref_height_); - } - - virtual bool CanDrop(const OSExchangeData& data) { - DCHECK(host_->GetMenuItem()->GetMenuController()); - return true; // Always return true so that drop events are targeted to us. - } - - virtual void OnDragEntered(const DropTargetEvent& event) { - DCHECK(host_->GetMenuItem()->GetMenuController()); - host_->GetMenuItem()->GetMenuController()->OnDragEnteredScrollButton( - host_, is_up_); - } - - virtual int OnDragUpdated(const DropTargetEvent& event) { - return DragDropTypes::DRAG_NONE; - } - - virtual void OnDragExited() { - DCHECK(host_->GetMenuItem()->GetMenuController()); - host_->GetMenuItem()->GetMenuController()->OnDragExitedScrollButton(host_); - } - - virtual int OnPerformDrop(const DropTargetEvent& event) { - return DragDropTypes::DRAG_NONE; - } - - virtual void Paint(gfx::Canvas* canvas) { - HDC dc = canvas->beginPlatformPaint(); - - // The background. - RECT item_bounds = { 0, 0, width(), height() }; - NativeTheme::instance()->PaintMenuItemBackground( - NativeTheme::MENU, dc, MENU_POPUPITEM, MPI_NORMAL, false, - &item_bounds); - - // Then the arrow. - int x = width() / 2; - int y = (height() - kScrollArrowHeight) / 2; - int delta_y = 1; - if (!is_up_) { - delta_y = -1; - y += kScrollArrowHeight; - } - SkColor arrow_color = color_utils::GetSysSkColor(COLOR_MENUTEXT); - for (int i = 0; i < kScrollArrowHeight; ++i, --x, y += delta_y) - canvas->FillRectInt(arrow_color, x, y, (i * 2) + 1, 1); - - canvas->endPlatformPaint(); - } - - private: - // SubmenuView we were created for. - SubmenuView* host_; - - // Direction of the button. - bool is_up_; - - // Preferred height. - int pref_height_; - - DISALLOW_COPY_AND_ASSIGN(MenuScrollButton); -}; - -// MenuScrollView -------------------------------------------------------------- - -// MenuScrollView is a viewport for the SubmenuView. It's reason to exist is so -// that ScrollRectToVisible works. -// -// NOTE: It is possible to use ScrollView directly (after making it deal with -// null scrollbars), but clicking on a child of ScrollView forces the window to -// become active, which we don't want. As we really only need a fraction of -// what ScrollView does, we use a one off variant. - -class MenuScrollView : public View { - public: - explicit MenuScrollView(View* child) { - AddChildView(child); - } - - virtual void ScrollRectToVisible(int x, int y, int width, int height) { - // NOTE: this assumes we only want to scroll in the y direction. - - View* child = GetContents(); - // Convert y to view's coordinates. - y -= child->y(); - gfx::Size pref = child->GetPreferredSize(); - // Constrain y to make sure we don't show past the bottom of the view. - y = std::max(0, std::min(pref.height() - this->height(), y)); - child->SetY(-y); - } - - // Returns the contents, which is the SubmenuView. - View* GetContents() { - return GetChildViewAt(0); - } - - private: - DISALLOW_COPY_AND_ASSIGN(MenuScrollView); -}; - -} // namespace - -// MenuScrollViewContainer ----------------------------------------------------- - -// MenuScrollViewContainer contains the SubmenuView (through a MenuScrollView) -// and two scroll buttons. The scroll buttons are only visible and enabled if -// the preferred height of the SubmenuView is bigger than our bounds. -class MenuScrollViewContainer : public View { - public: - explicit MenuScrollViewContainer(SubmenuView* content_view) { - scroll_up_button_ = new MenuScrollButton(content_view, true); - scroll_down_button_ = new MenuScrollButton(content_view, false); - AddChildView(scroll_up_button_); - AddChildView(scroll_down_button_); - - scroll_view_ = new MenuScrollView(content_view); - AddChildView(scroll_view_); - - set_border(Border::CreateEmptyBorder( - kSubmenuBorderSize, kSubmenuBorderSize, - kSubmenuBorderSize, kSubmenuBorderSize)); - } - - virtual void Paint(gfx::Canvas* canvas) { - HDC dc = canvas->beginPlatformPaint(); - CRect bounds(0, 0, width(), height()); - NativeTheme::instance()->PaintMenuBackground( - NativeTheme::MENU, dc, MENU_POPUPBACKGROUND, 0, &bounds); - canvas->endPlatformPaint(); - } - - View* scroll_down_button() { return scroll_down_button_; } - - View* scroll_up_button() { return scroll_up_button_; } - - virtual void Layout() { - gfx::Insets insets = GetInsets(); - int x = insets.left(); - int y = insets.top(); - int width = View::width() - insets.width(); - int content_height = height() - insets.height(); - if (!scroll_up_button_->IsVisible()) { - scroll_view_->SetBounds(x, y, width, content_height); - scroll_view_->Layout(); - return; - } - - gfx::Size pref = scroll_up_button_->GetPreferredSize(); - scroll_up_button_->SetBounds(x, y, width, pref.height()); - content_height -= pref.height(); - - const int scroll_view_y = y + pref.height(); - - pref = scroll_down_button_->GetPreferredSize(); - scroll_down_button_->SetBounds(x, height() - pref.height() - insets.top(), - width, pref.height()); - content_height -= pref.height(); - - scroll_view_->SetBounds(x, scroll_view_y, width, content_height); - scroll_view_->Layout(); - } - - virtual void DidChangeBounds(const gfx::Rect& previous, - const gfx::Rect& current) { - gfx::Size content_pref = scroll_view_->GetContents()->GetPreferredSize(); - scroll_up_button_->SetVisible(content_pref.height() > height()); - scroll_down_button_->SetVisible(content_pref.height() > height()); - } - - virtual gfx::Size GetPreferredSize() { - gfx::Size prefsize = scroll_view_->GetContents()->GetPreferredSize(); - gfx::Insets insets = GetInsets(); - prefsize.Enlarge(insets.width(), insets.height()); - return prefsize; - } - - private: - // The scroll buttons. - View* scroll_up_button_; - View* scroll_down_button_; - - // The scroll view. - MenuScrollView* scroll_view_; - - DISALLOW_COPY_AND_ASSIGN(MenuScrollViewContainer); -}; - -namespace { - -// MenuSeparator --------------------------------------------------------------- - -// Renders a separator. - -class MenuSeparator : public View { - public: - MenuSeparator() { - } - - void Paint(gfx::Canvas* canvas) { - // The gutter is rendered before the background. - int start_x = 0; - int start_y = height() / 3; - HDC dc = canvas->beginPlatformPaint(); - if (render_gutter) { - // If render_gutter is true, we're on Vista and need to render the - // gutter, then indent the separator from the gutter. - RECT gutter_bounds = { label_start - kGutterToLabel - gutter_width, 0, 0, - height() }; - gutter_bounds.right = gutter_bounds.left + gutter_width; - NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL, - &gutter_bounds); - start_x = gutter_bounds.left + gutter_width; - start_y = 0; - } - RECT separator_bounds = { start_x, start_y, width(), height() }; - NativeTheme::instance()->PaintMenuSeparator(dc, MENU_POPUPSEPARATOR, - MPI_NORMAL, &separator_bounds); - canvas->endPlatformPaint(); - } - - gfx::Size GetPreferredSize() { - return gfx::Size(10, // Just in case we're the only item in a menu. - separator_height); - } - - private: - DISALLOW_COPY_AND_ASSIGN(MenuSeparator); -}; - -// MenuHostRootView ---------------------------------------------------------- - -// MenuHostRootView is the RootView of the window showing the menu. -// SubmenuView's scroll view is added as a child of MenuHostRootView. -// MenuHostRootView forwards relevant events to the MenuController. -// -// As all the menu items are owned by the root menu item, care must be taken -// such that when MenuHostRootView is deleted it doesn't delete the menu items. - -class MenuHostRootView : public RootView { - public: - explicit MenuHostRootView(Widget* widget, - SubmenuView* submenu) - : RootView(widget), - submenu_(submenu), - forward_drag_to_menu_controller_(true), - suspend_events_(false) { -#ifdef DEBUG_MENU - DLOG(INFO) << " new MenuHostRootView " << this; -#endif - } - - virtual bool OnMousePressed(const MouseEvent& event) { - if (suspend_events_) - return true; - - forward_drag_to_menu_controller_ = - ((event.x() < 0 || event.y() < 0 || event.x() >= width() || - event.y() >= height()) || - !RootView::OnMousePressed(event)); - if (forward_drag_to_menu_controller_) - GetMenuController()->OnMousePressed(submenu_, event); - return true; - } - - virtual bool OnMouseDragged(const MouseEvent& event) { - if (suspend_events_) - return true; - - if (forward_drag_to_menu_controller_) { -#ifdef DEBUG_MENU - DLOG(INFO) << " MenuHostRootView::OnMouseDragged source=" << submenu_; -#endif - GetMenuController()->OnMouseDragged(submenu_, event); - return true; - } - return RootView::OnMouseDragged(event); - } - - virtual void OnMouseReleased(const MouseEvent& event, bool canceled) { - if (suspend_events_) - return; - - RootView::OnMouseReleased(event, canceled); - if (forward_drag_to_menu_controller_) { - forward_drag_to_menu_controller_ = false; - if (canceled) { - GetMenuController()->Cancel(true); - } else { - GetMenuController()->OnMouseReleased(submenu_, event); - } - } - } - - virtual void OnMouseMoved(const MouseEvent& event) { - if (suspend_events_) - return; - - RootView::OnMouseMoved(event); - GetMenuController()->OnMouseMoved(submenu_, event); - } - - virtual void ProcessOnMouseExited() { - if (suspend_events_) - return; - - RootView::ProcessOnMouseExited(); - } - - virtual bool ProcessMouseWheelEvent(const MouseWheelEvent& e) { - // RootView::ProcessMouseWheelEvent forwards to the focused view. We don't - // have a focused view, so we need to override this then forward to - // the menu. - return submenu_->OnMouseWheel(e); - } - - void SuspendEvents() { - suspend_events_ = true; - } - - private: - MenuController* GetMenuController() { - return submenu_->GetMenuItem()->GetMenuController(); - } - - // The SubmenuView we contain. - SubmenuView* submenu_; - - // Whether mouse dragged/released should be forwarded to the MenuController. - bool forward_drag_to_menu_controller_; - - // Whether events are suspended. If true, no events are forwarded to the - // MenuController. - bool suspend_events_; - - DISALLOW_COPY_AND_ASSIGN(MenuHostRootView); -}; - -} // namespace - -// MenuHost ------------------------------------------------------------------ - -// MenuHost is the window responsible for showing a single menu. -// -// Similar to MenuHostRootView, care must be taken such that when MenuHost is -// deleted, it doesn't delete the menu items. MenuHost is closed via a -// DelayedClosed, which avoids timing issues with deleting the window while -// capture or events are directed at it. - -class MenuHost : public WidgetWin { - public: - explicit MenuHost(SubmenuView* submenu) - : closed_(false), - submenu_(submenu), - owns_capture_(false) { - set_window_style(WS_POPUP); - set_initial_class_style( - (win_util::GetWinVersion() < win_util::WINVERSION_XP) ? - 0 : CS_DROPSHADOW); - is_mouse_down_ = - ((GetKeyState(VK_LBUTTON) & 0x80) || - (GetKeyState(VK_RBUTTON) & 0x80) || - (GetKeyState(VK_MBUTTON) & 0x80) || - (GetKeyState(VK_XBUTTON1) & 0x80) || - (GetKeyState(VK_XBUTTON2) & 0x80)); - // Mouse clicks shouldn't give us focus. - set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE); - } - - void Init(HWND parent, - const gfx::Rect& bounds, - View* contents_view, - bool do_capture) { - WidgetWin::Init(parent, bounds); - SetContentsView(contents_view); - // We don't want to take focus away from the hosting window. - ShowWindow(SW_SHOWNA); - owns_capture_ = do_capture; - if (do_capture) { - SetCapture(); - has_capture_ = true; -#ifdef DEBUG_MENU - DLOG(INFO) << "Doing capture"; -#endif - } - } - - virtual void Hide() { - if (closed_) { - // We're already closed, nothing to do. - // This is invoked twice if the first time just hid us, and the second - // time deleted Closed (deleted) us. - return; - } - // The menus are freed separately, and possibly before the window is closed, - // remove them so that View doesn't try to access deleted objects. - static_cast<MenuHostRootView*>(GetRootView())->SuspendEvents(); - GetRootView()->RemoveAllChildViews(false); - closed_ = true; - ReleaseCapture(); - WidgetWin::Hide(); - } - - virtual void HideWindow() { - // Make sure we release capture before hiding. - ReleaseCapture(); - WidgetWin::Hide(); - } - - virtual void OnCaptureChanged(HWND hwnd) { - WidgetWin::OnCaptureChanged(hwnd); - owns_capture_ = false; -#ifdef DEBUG_MENU - DLOG(INFO) << "Capture changed"; -#endif - } - - void ReleaseCapture() { - if (owns_capture_) { -#ifdef DEBUG_MENU - DLOG(INFO) << "released capture"; -#endif - owns_capture_ = false; - ::ReleaseCapture(); - } - } - - protected: - // Overriden to create MenuHostRootView. - virtual RootView* CreateRootView() { - return new MenuHostRootView(this, submenu_); - } - - virtual void OnCancelMode() { - if (!closed_) { -#ifdef DEBUG_MENU - DLOG(INFO) << "OnCanceMode, closing menu"; -#endif - submenu_->GetMenuItem()->GetMenuController()->Cancel(true); - } - } - - // Overriden to return false, we do NOT want to release capture on mouse - // release. - virtual bool ReleaseCaptureOnMouseReleased() { - return false; - } - - private: - // If true, we've been closed. - bool closed_; - - // If true, we own the capture and need to release it. - bool owns_capture_; - - // The view we contain. - SubmenuView* submenu_; - - DISALLOW_COPY_AND_ASSIGN(MenuHost); -}; - -namespace { - -// EmptyMenuMenuItem --------------------------------------------------------- - -// EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem -// is itself a MenuItemView, but it uses a different ID so that it isn't -// identified as a MenuItemView. - -class EmptyMenuMenuItem : public MenuItemView { - public: - // ID used for EmptyMenuMenuItem. - static const int kEmptyMenuItemViewID; - - explicit EmptyMenuMenuItem(MenuItemView* parent) : - MenuItemView(parent, 0, NORMAL) { - SetTitle(l10n_util::GetString(IDS_APP_MENU_EMPTY_SUBMENU)); - // Set this so that we're not identified as a normal menu item. - SetID(kEmptyMenuItemViewID); - SetEnabled(false); - } - - private: - DISALLOW_COPY_AND_ASSIGN(EmptyMenuMenuItem); -}; - -// static -const int EmptyMenuMenuItem::kEmptyMenuItemViewID = - MenuItemView::kMenuItemViewID + 1; - -} // namespace - -// SubmenuView --------------------------------------------------------------- - -SubmenuView::SubmenuView(MenuItemView* parent) - : parent_menu_item_(parent), - host_(NULL), - drop_item_(NULL), - drop_position_(MenuDelegate::DROP_NONE), - scroll_view_container_(NULL) { - DCHECK(parent); - // We'll delete ourselves, otherwise the ScrollView would delete us on close. - SetParentOwned(false); -} - -SubmenuView::~SubmenuView() { - // The menu may not have been closed yet (it will be hidden, but not - // necessarily closed). - Close(); - - delete scroll_view_container_; -} - -int SubmenuView::GetMenuItemCount() { - int count = 0; - for (int i = 0; i < GetChildViewCount(); ++i) { - if (GetChildViewAt(i)->GetID() == MenuItemView::kMenuItemViewID) - count++; - } - return count; -} - -MenuItemView* SubmenuView::GetMenuItemAt(int index) { - for (int i = 0, count = 0; i < GetChildViewCount(); ++i) { - if (GetChildViewAt(i)->GetID() == MenuItemView::kMenuItemViewID && - count++ == index) { - return static_cast<MenuItemView*>(GetChildViewAt(i)); - } - } - NOTREACHED(); - return NULL; -} - -void SubmenuView::Layout() { - // We're in a ScrollView, and need to set our width/height ourselves. - View* parent = GetParent(); - if (!parent) - return; - SetBounds(x(), y(), parent->width(), GetPreferredSize().height()); - - gfx::Insets insets = GetInsets(); - int x = insets.left(); - int y = insets.top(); - int menu_item_width = width() - insets.width(); - for (int i = 0; i < GetChildViewCount(); ++i) { - View* child = GetChildViewAt(i); - gfx::Size child_pref_size = child->GetPreferredSize(); - child->SetBounds(x, y, menu_item_width, child_pref_size.height()); - y += child_pref_size.height(); - } -} - -gfx::Size SubmenuView::GetPreferredSize() { - if (GetChildViewCount() == 0) - return gfx::Size(); - - int max_width = 0; - int height = 0; - for (int i = 0; i < GetChildViewCount(); ++i) { - View* child = GetChildViewAt(i); - gfx::Size child_pref_size = child->GetPreferredSize(); - max_width = std::max(max_width, child_pref_size.width()); - height += child_pref_size.height(); - } - gfx::Insets insets = GetInsets(); - return gfx::Size(max_width + insets.width(), height + insets.height()); -} - -void SubmenuView::DidChangeBounds(const gfx::Rect& previous, - const gfx::Rect& current) { - SchedulePaint(); -} - -void SubmenuView::PaintChildren(gfx::Canvas* canvas) { - View::PaintChildren(canvas); - - if (drop_item_ && drop_position_ != MenuDelegate::DROP_ON) - PaintDropIndicator(canvas, drop_item_, drop_position_); -} - -// TODO(sky): need to add support for new dnd methods for Linux. - -bool SubmenuView::CanDrop(const OSExchangeData& data) { - DCHECK(GetMenuItem()->GetMenuController()); - return GetMenuItem()->GetMenuController()->CanDrop(this, data); -} - -void SubmenuView::OnDragEntered(const DropTargetEvent& event) { - DCHECK(GetMenuItem()->GetMenuController()); - GetMenuItem()->GetMenuController()->OnDragEntered(this, event); -} - -int SubmenuView::OnDragUpdated(const DropTargetEvent& event) { - DCHECK(GetMenuItem()->GetMenuController()); - return GetMenuItem()->GetMenuController()->OnDragUpdated(this, event); -} - -void SubmenuView::OnDragExited() { - DCHECK(GetMenuItem()->GetMenuController()); - GetMenuItem()->GetMenuController()->OnDragExited(this); -} - -int SubmenuView::OnPerformDrop(const DropTargetEvent& event) { - DCHECK(GetMenuItem()->GetMenuController()); - return GetMenuItem()->GetMenuController()->OnPerformDrop(this, event); -} - -bool SubmenuView::OnMouseWheel(const MouseWheelEvent& e) { - gfx::Rect vis_bounds = GetVisibleBounds(); - int menu_item_count = GetMenuItemCount(); - if (vis_bounds.height() == height() || !menu_item_count) { - // All menu items are visible, nothing to scroll. - return true; - } - - // Find the index of the first menu item whose y-coordinate is >= visible - // y-coordinate. - int first_vis_index = -1; - for (int i = 0; i < menu_item_count; ++i) { - MenuItemView* menu_item = GetMenuItemAt(i); - if (menu_item->y() == vis_bounds.y()) { - first_vis_index = i; - break; - } else if (menu_item->y() > vis_bounds.y()) { - first_vis_index = std::max(0, i - 1); - break; - } - } - if (first_vis_index == -1) - return true; - - // If the first item isn't entirely visible, make it visible, otherwise make - // the next/previous one entirely visible. - int delta = abs(e.GetOffset() / WHEEL_DELTA); - bool scroll_up = (e.GetOffset() > 0); - while (delta-- > 0) { - int scroll_amount = 0; - if (scroll_up) { - if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y()) { - if (first_vis_index != 0) { - scroll_amount = GetMenuItemAt(first_vis_index - 1)->y() - - vis_bounds.y(); - first_vis_index--; - } else { - break; - } - } else { - scroll_amount = GetMenuItemAt(first_vis_index)->y() - vis_bounds.y(); - } - } else { - if (first_vis_index + 1 == GetMenuItemCount()) - break; - scroll_amount = GetMenuItemAt(first_vis_index + 1)->y() - - vis_bounds.y(); - if (GetMenuItemAt(first_vis_index)->y() == vis_bounds.y()) - first_vis_index++; - } - ScrollRectToVisible(0, vis_bounds.y() + scroll_amount, vis_bounds.width(), - vis_bounds.height()); - vis_bounds = GetVisibleBounds(); - } - - return true; -} - -bool SubmenuView::IsShowing() { - return host_ && host_->IsVisible(); -} - -void SubmenuView::ShowAt(HWND parent, - const gfx::Rect& bounds, - bool do_capture) { - if (host_) { - host_->ShowWindow(SW_SHOWNA); - return; - } - - host_ = new MenuHost(this); - // Force construction of the scroll view container. - GetScrollViewContainer(); - // Make sure the first row is visible. - ScrollRectToVisible(0, 0, 1, 1); - host_->Init(parent, bounds, scroll_view_container_, do_capture); -} - -void SubmenuView::Close() { - if (host_) { - host_->Close(); - host_ = NULL; - } -} - -void SubmenuView::Hide() { - if (host_) - host_->HideWindow(); -} - -void SubmenuView::ReleaseCapture() { - host_->ReleaseCapture(); -} - -bool SubmenuView::SkipDefaultKeyEventProcessing(const views::KeyEvent& e) { - return views::FocusManager::IsTabTraversalKeyEvent(e); -} - -void SubmenuView::SetDropMenuItem(MenuItemView* item, - MenuDelegate::DropPosition position) { - if (drop_item_ == item && drop_position_ == position) - return; - SchedulePaintForDropIndicator(drop_item_, drop_position_); - drop_item_ = item; - drop_position_ = position; - SchedulePaintForDropIndicator(drop_item_, drop_position_); -} - -bool SubmenuView::GetShowSelection(MenuItemView* item) { - if (drop_item_ == NULL) - return true; - // Something is being dropped on one of this menus items. Show the - // selection if the drop is on the passed in item and the drop position is - // ON. - return (drop_item_ == item && drop_position_ == MenuDelegate::DROP_ON); -} - -MenuScrollViewContainer* SubmenuView::GetScrollViewContainer() { - if (!scroll_view_container_) { - scroll_view_container_ = new MenuScrollViewContainer(this); - // Otherwise MenuHost would delete us. - scroll_view_container_->SetParentOwned(false); - } - return scroll_view_container_; -} - -void SubmenuView::PaintDropIndicator(gfx::Canvas* canvas, - MenuItemView* item, - MenuDelegate::DropPosition position) { - if (position == MenuDelegate::DROP_NONE) - return; - - gfx::Rect bounds = CalculateDropIndicatorBounds(item, position); - canvas->FillRectInt(kDropIndicatorColor, bounds.x(), bounds.y(), - bounds.width(), bounds.height()); -} - -void SubmenuView::SchedulePaintForDropIndicator( - MenuItemView* item, - MenuDelegate::DropPosition position) { - if (item == NULL) - return; - - if (position == MenuDelegate::DROP_ON) { - item->SchedulePaint(); - } else if (position != MenuDelegate::DROP_NONE) { - gfx::Rect bounds = CalculateDropIndicatorBounds(item, position); - SchedulePaint(bounds.x(), bounds.y(), bounds.width(), bounds.height()); - } -} - -gfx::Rect SubmenuView::CalculateDropIndicatorBounds( - MenuItemView* item, - MenuDelegate::DropPosition position) { - DCHECK(position != MenuDelegate::DROP_NONE); - gfx::Rect item_bounds = item->bounds(); - switch (position) { - case MenuDelegate::DROP_BEFORE: - item_bounds.Offset(0, -kDropIndicatorHeight / 2); - item_bounds.set_height(kDropIndicatorHeight); - return item_bounds; - - case MenuDelegate::DROP_AFTER: - item_bounds.Offset(0, item_bounds.height() - kDropIndicatorHeight / 2); - item_bounds.set_height(kDropIndicatorHeight); - return item_bounds; - - default: - // Don't render anything for on. - return gfx::Rect(); - } -} - -// MenuItemView --------------------------------------------------------------- - -// static -const int MenuItemView::kMenuItemViewID = 1001; - -// static -bool MenuItemView::allow_task_nesting_during_run_ = false; - -MenuItemView::MenuItemView(MenuDelegate* delegate) { - // NOTE: don't check the delegate for NULL, UpdateMenuPartSizes supplies a - // NULL delegate. - Init(NULL, 0, SUBMENU, delegate); -} - -MenuItemView::~MenuItemView() { - if (controller_) { - // We're currently showing. - - // We can't delete ourselves while we're blocking. - DCHECK(!controller_->IsBlockingRun()); - - // Invoking Cancel is going to call us back and notify the delegate. - // Notifying the delegate from the destructor can be problematic. To avoid - // this the delegate is set to NULL. - delegate_ = NULL; - - controller_->Cancel(true); - } - delete submenu_; -} - -void MenuItemView::RunMenuAt(HWND parent, - const gfx::Rect& bounds, - AnchorPosition anchor, - bool has_mnemonics) { - PrepareForRun(has_mnemonics); - - int mouse_event_flags; - - MenuController* controller = MenuController::GetActiveInstance(); - if (controller && !controller->IsBlockingRun()) { - // A menu is already showing, but it isn't a blocking menu. Cancel it. - // We can get here during drag and drop if the user right clicks on the - // menu quickly after the drop. - controller->Cancel(true); - controller = NULL; - } - bool owns_controller = false; - if (!controller) { - // No menus are showing, show one. - controller = new MenuController(true); - MenuController::SetActiveInstance(controller); - owns_controller = true; - } else { - // A menu is already showing, use the same controller. - - // Don't support blocking from within non-blocking. - DCHECK(controller->IsBlockingRun()); - } - - controller_ = controller; - - // Run the loop. - MenuItemView* result = - controller->Run(parent, this, bounds, anchor, &mouse_event_flags); - - RemoveEmptyMenus(); - - controller_ = NULL; - - if (owns_controller) { - // We created the controller and need to delete it. - if (MenuController::GetActiveInstance() == controller) - MenuController::SetActiveInstance(NULL); - delete controller; - } - // Make sure all the windows we created to show the menus have been - // destroyed. - DestroyAllMenuHosts(); - if (result && delegate_) - delegate_->ExecuteCommand(result->GetCommand(), mouse_event_flags); -} - -void MenuItemView::RunMenuForDropAt(HWND parent, - const gfx::Rect& bounds, - AnchorPosition anchor) { - PrepareForRun(false); - - // If there is a menu, hide it so that only one menu is shown during dnd. - MenuController* current_controller = MenuController::GetActiveInstance(); - if (current_controller) { - current_controller->Cancel(true); - } - - // Always create a new controller for non-blocking. - controller_ = new MenuController(false); - - // Set the instance, that way it can be canceled by another menu. - MenuController::SetActiveInstance(controller_); - - controller_->Run(parent, this, bounds, anchor, NULL); -} - -void MenuItemView::Cancel() { - if (controller_ && !canceled_) { - canceled_ = true; - controller_->Cancel(true); - } -} - -SubmenuView* MenuItemView::CreateSubmenu() { - if (!submenu_) - submenu_ = new SubmenuView(this); - return submenu_; -} - -void MenuItemView::SetSelected(bool selected) { - selected_ = selected; - SchedulePaint(); -} - -void MenuItemView::SetIcon(const SkBitmap& icon, int item_id) { - MenuItemView* item = GetDescendantByID(item_id); - DCHECK(item); - item->SetIcon(icon); -} - -void MenuItemView::SetIcon(const SkBitmap& icon) { - icon_ = icon; - SchedulePaint(); -} - -void MenuItemView::Paint(gfx::Canvas* canvas) { - Paint(canvas, false); -} - -gfx::Size MenuItemView::GetPreferredSize() { - gfx::Font& font = GetRootMenuItem()->font_; - return gfx::Size( - font.GetStringWidth(title_) + label_start + item_right_margin, - font.height() + GetBottomMargin() + GetTopMargin()); -} - -MenuController* MenuItemView::GetMenuController() { - return GetRootMenuItem()->controller_; -} - -MenuDelegate* MenuItemView::GetDelegate() { - return GetRootMenuItem()->delegate_; -} - -MenuItemView* MenuItemView::GetRootMenuItem() { - MenuItemView* item = this; - while (item) { - MenuItemView* parent = item->GetParentMenuItem(); - if (!parent) - return item; - item = parent; - } - NOTREACHED(); - return NULL; -} - -wchar_t MenuItemView::GetMnemonic() { - if (!has_mnemonics_) - return 0; - - const std::wstring& title = GetTitle(); - size_t index = 0; - do { - index = title.find('&', index); - if (index != std::wstring::npos) { - if (index + 1 != title.size() && title[index + 1] != '&') - return title[index + 1]; - index++; - } - } while (index != std::wstring::npos); - return 0; -} - -MenuItemView::MenuItemView(MenuItemView* parent, - int command, - MenuItemView::Type type) { - Init(parent, command, type, NULL); -} - -void MenuItemView::Init(MenuItemView* parent, - int command, - MenuItemView::Type type, - MenuDelegate* delegate) { - delegate_ = delegate; - controller_ = NULL; - canceled_ = false; - parent_menu_item_ = parent; - type_ = type; - selected_ = false; - command_ = command; - submenu_ = NULL; - // Assign our ID, this allows SubmenuItemView to find MenuItemViews. - SetID(kMenuItemViewID); - has_icons_ = false; - - MenuDelegate* root_delegate = GetDelegate(); - if (root_delegate) - SetEnabled(root_delegate->IsCommandEnabled(command)); -} - -MenuItemView* MenuItemView::AppendMenuItemInternal(int item_id, - const std::wstring& label, - const SkBitmap& icon, - Type type) { - if (!submenu_) - CreateSubmenu(); - if (type == SEPARATOR) { - submenu_->AddChildView(new MenuSeparator()); - return NULL; - } - MenuItemView* item = new MenuItemView(this, item_id, type); - if (label.empty() && GetDelegate()) - item->SetTitle(GetDelegate()->GetLabel(item_id)); - else - item->SetTitle(label); - item->SetIcon(icon); - if (type == SUBMENU) - item->CreateSubmenu(); - submenu_->AddChildView(item); - return item; -} - -MenuItemView* MenuItemView::GetDescendantByID(int id) { - if (GetCommand() == id) - return this; - if (!HasSubmenu()) - return NULL; - for (int i = 0; i < GetSubmenu()->GetChildViewCount(); ++i) { - View* child = GetSubmenu()->GetChildViewAt(i); - if (child->GetID() == MenuItemView::kMenuItemViewID) { - MenuItemView* result = static_cast<MenuItemView*>(child)-> - GetDescendantByID(id); - if (result) - return result; - } - } - return NULL; -} - -void MenuItemView::DropMenuClosed(bool notify_delegate) { - DCHECK(controller_); - DCHECK(!controller_->IsBlockingRun()); - if (MenuController::GetActiveInstance() == controller_) - MenuController::SetActiveInstance(NULL); - delete controller_; - controller_ = NULL; - - RemoveEmptyMenus(); - - if (notify_delegate && delegate_) { - // Our delegate is null when invoked from the destructor. - delegate_->DropMenuClosed(this); - } - // WARNING: its possible the delegate deleted us at this point. -} - -void MenuItemView::PrepareForRun(bool has_mnemonics) { - // Currently we only support showing the root. - DCHECK(!parent_menu_item_); - - // Don't invoke run from within run on the same menu. - DCHECK(!controller_); - - // Force us to have a submenu. - CreateSubmenu(); - - canceled_ = false; - - has_mnemonics_ = has_mnemonics; - - AddEmptyMenus(); - - if (!MenuController::GetActiveInstance()) { - // Only update the menu size if there are no menus showing, otherwise - // things may shift around. - UpdateMenuPartSizes(has_icons_); - } - - font_ = GetMenuFont(); - - BOOL show_cues; - show_mnemonics = - (SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &show_cues, 0) && - show_cues == TRUE); -} - -int MenuItemView::GetDrawStringFlags() { - int flags = 0; - if (UILayoutIsRightToLeft()) - flags |= gfx::Canvas::TEXT_ALIGN_RIGHT; - else - flags |= gfx::Canvas::TEXT_ALIGN_LEFT; - - if (has_mnemonics_) { - if (show_mnemonics) - flags |= gfx::Canvas::SHOW_PREFIX; - else - flags |= gfx::Canvas::HIDE_PREFIX; - } - return flags; -} - -void MenuItemView::AddEmptyMenus() { - DCHECK(HasSubmenu()); - if (submenu_->GetChildViewCount() == 0) { - submenu_->AddChildView(0, new EmptyMenuMenuItem(this)); - } else { - for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count; - ++i) { - MenuItemView* child = submenu_->GetMenuItemAt(i); - if (child->HasSubmenu()) - child->AddEmptyMenus(); - } - } -} - -void MenuItemView::RemoveEmptyMenus() { - DCHECK(HasSubmenu()); - // Iterate backwards as we may end up removing views, which alters the child - // view count. - for (int i = submenu_->GetChildViewCount() - 1; i >= 0; --i) { - View* child = submenu_->GetChildViewAt(i); - if (child->GetID() == MenuItemView::kMenuItemViewID) { - MenuItemView* menu_item = static_cast<MenuItemView*>(child); - if (menu_item->HasSubmenu()) - menu_item->RemoveEmptyMenus(); - } else if (child->GetID() == EmptyMenuMenuItem::kEmptyMenuItemViewID) { - submenu_->RemoveChildView(child); - } - } -} - -void MenuItemView::AdjustBoundsForRTLUI(gfx::Rect* rect) const { - rect->set_x(MirroredLeftPointForRect(*rect)); -} - -void MenuItemView::Paint(gfx::Canvas* canvas, bool for_drag) { - bool render_selection = - (!for_drag && IsSelected() && - parent_menu_item_->GetSubmenu()->GetShowSelection(this)); - int state = render_selection ? MPI_HOT : - (IsEnabled() ? MPI_NORMAL : MPI_DISABLED); - HDC dc = canvas->beginPlatformPaint(); - - // The gutter is rendered before the background. - if (render_gutter && !for_drag) { - gfx::Rect gutter_bounds(label_start - kGutterToLabel - gutter_width, 0, - gutter_width, height()); - AdjustBoundsForRTLUI(&gutter_bounds); - RECT gutter_rect = gutter_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL, - &gutter_rect); - } - - // Render the background. - if (!for_drag) { - gfx::Rect item_bounds(0, 0, width(), height()); - AdjustBoundsForRTLUI(&item_bounds); - RECT item_rect = item_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuItemBackground( - NativeTheme::MENU, dc, MENU_POPUPITEM, state, render_selection, - &item_rect); - } - - int icon_x = kItemLeftMargin; - int top_margin = GetTopMargin(); - int bottom_margin = GetBottomMargin(); - int icon_y = top_margin + (height() - kItemTopMargin - - bottom_margin - check_height) / 2; - int icon_height = check_height; - int icon_width = check_width; - - if (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand())) { - // Draw the check background. - gfx::Rect check_bg_bounds(0, 0, icon_x + icon_width, height()); - const int bg_state = IsEnabled() ? MCB_NORMAL : MCB_DISABLED; - AdjustBoundsForRTLUI(&check_bg_bounds); - RECT check_bg_rect = check_bg_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuCheckBackground( - NativeTheme::MENU, dc, MENU_POPUPCHECKBACKGROUND, bg_state, - &check_bg_rect); - - // And the check. - gfx::Rect check_bounds(icon_x, icon_y, icon_width, icon_height); - const int check_state = IsEnabled() ? MC_CHECKMARKNORMAL : - MC_CHECKMARKDISABLED; - AdjustBoundsForRTLUI(&check_bounds); - RECT check_rect = check_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuCheck( - NativeTheme::MENU, dc, MENU_POPUPCHECK, check_state, &check_rect, - render_selection); - } - - // Render the foreground. - // Menu color is specific to Vista, fallback to classic colors if can't - // get color. - int default_sys_color = render_selection ? COLOR_HIGHLIGHTTEXT : - (IsEnabled() ? COLOR_MENUTEXT : COLOR_GRAYTEXT); - SkColor fg_color = NativeTheme::instance()->GetThemeColorWithDefault( - NativeTheme::MENU, MENU_POPUPITEM, state, TMT_TEXTCOLOR, - default_sys_color); - int width = this->width() - item_right_margin - label_start; - gfx::Font& font = GetRootMenuItem()->font_; - gfx::Rect text_bounds(label_start, top_margin, width, font.height()); - text_bounds.set_x(MirroredLeftPointForRect(text_bounds)); - if (for_drag) { - // With different themes, it's difficult to tell what the correct foreground - // and background colors are for the text to draw the correct halo. Instead, - // just draw black on white, which will look good in most cases. - canvas->DrawStringWithHalo(GetTitle(), font, 0x00000000, 0xFFFFFFFF, - text_bounds.x(), text_bounds.y(), - text_bounds.width(), text_bounds.height(), - GetRootMenuItem()->GetDrawStringFlags()); - } else { - canvas->DrawStringInt(GetTitle(), font, fg_color, - text_bounds.x(), text_bounds.y(), text_bounds.width(), - text_bounds.height(), - GetRootMenuItem()->GetDrawStringFlags()); - } - - if (icon_.width() > 0) { - gfx::Rect icon_bounds(kItemLeftMargin, - top_margin + (height() - top_margin - - bottom_margin - icon_.height()) / 2, - icon_.width(), - icon_.height()); - icon_bounds.set_x(MirroredLeftPointForRect(icon_bounds)); - canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y()); - } - - if (HasSubmenu()) { - int state_id = IsEnabled() ? MSM_NORMAL : MSM_DISABLED; - gfx::Rect arrow_bounds(this->width() - item_right_margin + kLabelToArrowPadding, - 0, arrow_width, height()); - AdjustBoundsForRTLUI(&arrow_bounds); - - // If our sub menus open from right to left (which is the case when the - // locale is RTL) then we should make sure the menu arrow points to the - // right direction. - NativeTheme::MenuArrowDirection arrow_direction; - if (UILayoutIsRightToLeft()) - arrow_direction = NativeTheme::LEFT_POINTING_ARROW; - else - arrow_direction = NativeTheme::RIGHT_POINTING_ARROW; - - RECT arrow_rect = arrow_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuArrow( - NativeTheme::MENU, dc, MENU_POPUPSUBMENU, state_id, &arrow_rect, - arrow_direction, render_selection); - } - canvas->endPlatformPaint(); -} - -void MenuItemView::DestroyAllMenuHosts() { - if (!HasSubmenu()) - return; - - submenu_->Close(); - for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count; - ++i) { - submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts(); - } -} - -int MenuItemView::GetTopMargin() { - MenuItemView* root = GetRootMenuItem(); - return root && root->has_icons_ ? kItemTopMargin : kItemNoIconTopMargin; -} - -int MenuItemView::GetBottomMargin() { - MenuItemView* root = GetRootMenuItem(); - return root && root->has_icons_ ? - kItemBottomMargin : kItemNoIconBottomMargin; -} - -// MenuController ------------------------------------------------------------ - -// static -MenuController* MenuController::active_instance_ = NULL; - -// static -MenuController* MenuController::GetActiveInstance() { - return active_instance_; -} - -#ifdef DEBUG_MENU -static int instance_count = 0; -static int nested_depth = 0; -#endif - -MenuItemView* MenuController::Run(HWND parent, - MenuItemView* root, - const gfx::Rect& bounds, - MenuItemView::AnchorPosition position, - int* result_mouse_event_flags) { - exit_all_ = false; - possible_drag_ = false; - - bool nested_menu = showing_; - if (showing_) { - // Only support nesting of blocking_run menus, nesting of - // blocking/non-blocking shouldn't be needed. - DCHECK(blocking_run_); - - // We're already showing, push the current state. - menu_stack_.push_back(state_); - - // The context menu should be owned by the same parent. - DCHECK(owner_ == parent); - } else { - showing_ = true; - } - - // Reset current state. - pending_state_ = State(); - state_ = State(); - pending_state_.initial_bounds = bounds; - if (bounds.height() > 1) { - // Inset the bounds slightly, otherwise drag coordinates don't line up - // nicely and menus close prematurely. - pending_state_.initial_bounds.Inset(0, 1); - } - pending_state_.anchor = position; - owner_ = parent; - - // Calculate the bounds of the monitor we'll show menus on. Do this once to - // avoid repeated system queries for the info. - POINT initial_loc = { bounds.x(), bounds.y() }; - HMONITOR monitor = MonitorFromPoint(initial_loc, MONITOR_DEFAULTTONEAREST); - if (monitor) { - MONITORINFO mi = {0}; - mi.cbSize = sizeof(mi); - GetMonitorInfo(monitor, &mi); - // Menus appear over the taskbar. - pending_state_.monitor_bounds = gfx::Rect(mi.rcMonitor); - } - - // Set the selection, which opens the initial menu. - SetSelection(root, true, true); - - if (!blocking_run_) { - // Start the timer to hide the menu. This is needed as we get no - // notification when the drag has finished. - StartCancelAllTimer(); - return NULL; - } - -#ifdef DEBUG_MENU - nested_depth++; - DLOG(INFO) << " entering nested loop, depth=" << nested_depth; -#endif - - MessageLoopForUI* loop = MessageLoopForUI::current(); - if (MenuItemView::allow_task_nesting_during_run_) { - bool did_allow_task_nesting = loop->NestableTasksAllowed(); - loop->SetNestableTasksAllowed(true); - loop->Run(this); - loop->SetNestableTasksAllowed(did_allow_task_nesting); - } else { - loop->Run(this); - } - -#ifdef DEBUG_MENU - nested_depth--; - DLOG(INFO) << " exiting nested loop, depth=" << nested_depth; -#endif - - // Close any open menus. - SetSelection(NULL, false, true); - - if (nested_menu) { - DCHECK(!menu_stack_.empty()); - // We're running from within a menu, restore the previous state. - // The menus are already showing, so we don't have to show them. - state_ = menu_stack_.back(); - pending_state_ = menu_stack_.back(); - menu_stack_.pop_back(); - } else { - showing_ = false; - did_capture_ = false; - } - - MenuItemView* result = result_; - // In case we're nested, reset result_. - result_ = NULL; - - if (result_mouse_event_flags) - *result_mouse_event_flags = result_mouse_event_flags_; - - if (nested_menu && result) { - // We're nested and about to return a value. The caller might enter another - // blocking loop. We need to make sure all menus are hidden before that - // happens otherwise the menus will stay on screen. - CloseAllNestedMenus(); - - // Set exit_all_ to true, which makes sure all nested loops exit - // immediately. - exit_all_ = true; - } - - return result; -} - -void MenuController::SetSelection(MenuItemView* menu_item, - bool open_submenu, - bool update_immediately) { - size_t paths_differ_at = 0; - std::vector<MenuItemView*> current_path; - std::vector<MenuItemView*> new_path; - BuildPathsAndCalculateDiff(pending_state_.item, menu_item, ¤t_path, - &new_path, &paths_differ_at); - - size_t current_size = current_path.size(); - size_t new_size = new_path.size(); - - // Notify the old path it isn't selected. - for (size_t i = paths_differ_at; i < current_size; ++i) - current_path[i]->SetSelected(false); - - // Notify the new path it is selected. - for (size_t i = paths_differ_at; i < new_size; ++i) - new_path[i]->SetSelected(true); - - if (menu_item && menu_item->GetDelegate()) - menu_item->GetDelegate()->SelectionChanged(menu_item); - - pending_state_.item = menu_item; - pending_state_.submenu_open = open_submenu; - - // Stop timers. - StopShowTimer(); - StopCancelAllTimer(); - - if (update_immediately) - CommitPendingSelection(); - else - StartShowTimer(); -} - -void MenuController::Cancel(bool all) { - if (!showing_) { - // This occurs if we're in the process of notifying the delegate for a drop - // and the delegate cancels us. - return; - } - - MenuItemView* selected = state_.item; - exit_all_ = all; - - // Hide windows immediately. - SetSelection(NULL, false, true); - - if (!blocking_run_) { - // If we didn't block the caller we need to notify the menu, which - // triggers deleting us. - DCHECK(selected); - showing_ = false; - selected->GetRootMenuItem()->DropMenuClosed(true); - // WARNING: the call to MenuClosed deletes us. - return; - } -} - -void MenuController::OnMousePressed(SubmenuView* source, - const MouseEvent& event) { -#ifdef DEBUG_MENU - DLOG(INFO) << "OnMousePressed source=" << source; -#endif - if (!blocking_run_) - return; - - MenuPart part = - GetMenuPartByScreenCoordinate(source, event.x(), event.y()); - if (part.is_scroll()) - return; // Ignore presses on scroll buttons. - - if (part.type == MenuPart::NONE || - (part.type == MenuPart::MENU_ITEM && part.menu && - part.menu->GetRootMenuItem() != state_.item->GetRootMenuItem())) { - // Mouse wasn't pressed over any menu, or the active menu, cancel. - - // We're going to close and we own the mouse capture. We need to repost the - // mouse down, otherwise the window the user clicked on won't get the - // event. - RepostEvent(source, event); - - // And close. - Cancel(true); - return; - } - - bool open_submenu = false; - if (!part.menu) { - part.menu = part.parent; - open_submenu = true; - } else { - if (part.menu->GetDelegate()->CanDrag(part.menu)) { - possible_drag_ = true; - press_x_ = event.x(); - press_y_ = event.y(); - } - if (part.menu->HasSubmenu()) - open_submenu = true; - } - // On a press we immediately commit the selection, that way a submenu - // pops up immediately rather than after a delay. - SetSelection(part.menu, open_submenu, true); -} - -void MenuController::OnMouseDragged(SubmenuView* source, - const MouseEvent& event) { -#ifdef DEBUG_MENU - DLOG(INFO) << "OnMouseDragged source=" << source; -#endif - MenuPart part = - GetMenuPartByScreenCoordinate(source, event.x(), event.y()); - UpdateScrolling(part); - - if (!blocking_run_) - return; - - if (possible_drag_) { - if (View::ExceededDragThreshold(event.x() - press_x_, - event.y() - press_y_)) { - MenuItemView* item = state_.item; - DCHECK(item); - // Points are in the coordinates of the submenu, need to map to that of - // the selected item. Additionally source may not be the parent of - // the selected item, so need to map to screen first then to item. - gfx::Point press_loc(press_x_, press_y_); - View::ConvertPointToScreen(source->GetScrollViewContainer(), &press_loc); - View::ConvertPointToView(NULL, item, &press_loc); - gfx::Point drag_loc(event.location()); - View::ConvertPointToScreen(source->GetScrollViewContainer(), &drag_loc); - View::ConvertPointToView(NULL, item, &drag_loc); - gfx::Canvas canvas(item->width(), item->height(), false); - item->Paint(&canvas, true); - - OSExchangeData data; - item->GetDelegate()->WriteDragData(item, &data); - drag_utils::SetDragImageOnDataObject(canvas, item->width(), - item->height(), press_loc.x(), - press_loc.y(), &data); - - scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); - int drag_ops = item->GetDelegate()->GetDragOperations(item); - DWORD effects; - StopScrolling(); - DoDragDrop(OSExchangeDataProviderWin::GetIDataObject(data), drag_source, - DragDropTypes::DragOperationToDropEffect(drag_ops), - &effects); - if (GetActiveInstance() == this) { - if (showing_) { - // We're still showing, close all menus. - CloseAllNestedMenus(); - Cancel(true); - } // else case, drop was on us. - } // else case, someone canceled us, don't do anything - } - return; - } - if (part.type == MenuPart::MENU_ITEM) { - if (!part.menu) - part.menu = source->GetMenuItem(); - SetSelection(part.menu ? part.menu : state_.item, true, false); - } -} - -void MenuController::OnMouseReleased(SubmenuView* source, - const MouseEvent& event) { -#ifdef DEBUG_MENU - DLOG(INFO) << "OnMouseReleased source=" << source; -#endif - if (!blocking_run_) - return; - - DCHECK(state_.item); - possible_drag_ = false; - DCHECK(blocking_run_); - MenuPart part = - GetMenuPartByScreenCoordinate(source, event.x(), event.y()); - if (event.IsRightMouseButton() && (part.type == MenuPart::MENU_ITEM && - part.menu)) { - // Set the selection immediately, making sure the submenu is only open - // if it already was. - bool open_submenu = (state_.item == pending_state_.item && - state_.submenu_open); - SetSelection(pending_state_.item, open_submenu, true); - gfx::Point loc(event.location()); - View::ConvertPointToScreen(source->GetScrollViewContainer(), &loc); - - // If we open a context menu just return now - if (part.menu->GetDelegate()->ShowContextMenu( - part.menu, part.menu->GetCommand(), loc.x(), loc.y(), true)) - return; - } - - if (!part.is_scroll() && part.menu && !part.menu->HasSubmenu()) { - if (part.menu->GetDelegate()->IsTriggerableEvent(event)) { - Accept(part.menu, event.GetFlags()); - return; - } - } else if (part.type == MenuPart::MENU_ITEM) { - // User either clicked on empty space, or a menu that has children. - SetSelection(part.menu ? part.menu : state_.item, true, true); - } -} - -void MenuController::OnMouseMoved(SubmenuView* source, - const MouseEvent& event) { -#ifdef DEBUG_MENU - DLOG(INFO) << "OnMouseMoved source=" << source; -#endif - if (showing_submenu_) - return; - - MenuPart part = - GetMenuPartByScreenCoordinate(source, event.x(), event.y()); - - UpdateScrolling(part); - - if (!blocking_run_) - return; - - if (part.type == MenuPart::MENU_ITEM && part.menu) { - SetSelection(part.menu, true, false); - } else if (!part.is_scroll() && pending_state_.item && - (!pending_state_.item->HasSubmenu() || - !pending_state_.item->GetSubmenu()->IsShowing())) { - // On exit if the user hasn't selected an item with a submenu, move the - // selection back to the parent menu item. - SetSelection(pending_state_.item->GetParentMenuItem(), true, false); - } -} - -void MenuController::OnMouseEntered(SubmenuView* source, - const MouseEvent& event) { - // MouseEntered is always followed by a mouse moved, so don't need to - // do anything here. -} - -bool MenuController::CanDrop(SubmenuView* source, const OSExchangeData& data) { - return source->GetMenuItem()->GetDelegate()->CanDrop(source->GetMenuItem(), - data); -} - -void MenuController::OnDragEntered(SubmenuView* source, - const DropTargetEvent& event) { - valid_drop_coordinates_ = false; -} - -int MenuController::OnDragUpdated(SubmenuView* source, - const DropTargetEvent& event) { - StopCancelAllTimer(); - - gfx::Point screen_loc(event.location()); - View::ConvertPointToScreen(source, &screen_loc); - if (valid_drop_coordinates_ && screen_loc.x() == drop_x_ && - screen_loc.y() == drop_y_) { - return last_drop_operation_; - } - drop_x_ = screen_loc.x(); - drop_y_ = screen_loc.y(); - valid_drop_coordinates_ = true; - - MenuItemView* menu_item = GetMenuItemAt(source, event.x(), event.y()); - bool over_empty_menu = false; - if (!menu_item) { - // See if we're over an empty menu. - menu_item = GetEmptyMenuItemAt(source, event.x(), event.y()); - if (menu_item) - over_empty_menu = true; - } - MenuDelegate::DropPosition drop_position = MenuDelegate::DROP_NONE; - int drop_operation = DragDropTypes::DRAG_NONE; - if (menu_item) { - gfx::Point menu_item_loc(event.location()); - View::ConvertPointToView(source, menu_item, &menu_item_loc); - MenuItemView* query_menu_item; - if (!over_empty_menu) { - int menu_item_height = menu_item->height(); - if (menu_item->HasSubmenu() && - (menu_item_loc.y() > kDropBetweenPixels && - menu_item_loc.y() < (menu_item_height - kDropBetweenPixels))) { - drop_position = MenuDelegate::DROP_ON; - } else if (menu_item_loc.y() < menu_item_height / 2) { - drop_position = MenuDelegate::DROP_BEFORE; - } else { - drop_position = MenuDelegate::DROP_AFTER; - } - query_menu_item = menu_item; - } else { - query_menu_item = menu_item->GetParentMenuItem(); - drop_position = MenuDelegate::DROP_ON; - } - drop_operation = menu_item->GetDelegate()->GetDropOperation( - query_menu_item, event, &drop_position); - - if (menu_item->HasSubmenu()) { - // The menu has a submenu, schedule the submenu to open. - SetSelection(menu_item, true, false); - } else { - SetSelection(menu_item, false, false); - } - - if (drop_position == MenuDelegate::DROP_NONE || - drop_operation == DragDropTypes::DRAG_NONE) { - menu_item = NULL; - } - } else { - SetSelection(source->GetMenuItem(), true, false); - } - SetDropMenuItem(menu_item, drop_position); - last_drop_operation_ = drop_operation; - return drop_operation; -} - -void MenuController::OnDragExited(SubmenuView* source) { - StartCancelAllTimer(); - - if (drop_target_) { - StopShowTimer(); - SetDropMenuItem(NULL, MenuDelegate::DROP_NONE); - } -} - -int MenuController::OnPerformDrop(SubmenuView* source, - const DropTargetEvent& event) { - DCHECK(drop_target_); - // NOTE: the delegate may delete us after invoking OnPerformDrop, as such - // we don't call cancel here. - - MenuItemView* item = state_.item; - DCHECK(item); - - MenuItemView* drop_target = drop_target_; - MenuDelegate::DropPosition drop_position = drop_position_; - - // Close all menus, including any nested menus. - SetSelection(NULL, false, true); - CloseAllNestedMenus(); - - // Set state such that we exit. - showing_ = false; - exit_all_ = true; - - if (!IsBlockingRun()) - item->GetRootMenuItem()->DropMenuClosed(false); - - // WARNING: the call to MenuClosed deletes us. - - // If over an empty menu item, drop occurs on the parent. - if (drop_target->GetID() == EmptyMenuMenuItem::kEmptyMenuItemViewID) - drop_target = drop_target->GetParentMenuItem(); - - return drop_target->GetDelegate()->OnPerformDrop( - drop_target, drop_position, event); -} - -void MenuController::OnDragEnteredScrollButton(SubmenuView* source, - bool is_up) { - MenuPart part; - part.type = is_up ? MenuPart::SCROLL_UP : MenuPart::SCROLL_DOWN; - part.submenu = source; - UpdateScrolling(part); - - // Do this to force the selection to hide. - SetDropMenuItem(source->GetMenuItemAt(0), MenuDelegate::DROP_NONE); - - StopCancelAllTimer(); -} - -void MenuController::OnDragExitedScrollButton(SubmenuView* source) { - StartCancelAllTimer(); - SetDropMenuItem(NULL, MenuDelegate::DROP_NONE); - StopScrolling(); -} - -// static -void MenuController::SetActiveInstance(MenuController* controller) { - active_instance_ = controller; -} - -bool MenuController::Dispatch(const MSG& msg) { - DCHECK(blocking_run_); - - if (exit_all_) { - // We must translate/dispatch the message here, otherwise we would drop - // the message on the floor. - TranslateMessage(&msg); - DispatchMessage(&msg); - return false; - } - - // NOTE: we don't get WM_ACTIVATE or anything else interesting in here. - switch (msg.message) { - case WM_CONTEXTMENU: { - MenuItemView* item = pending_state_.item; - if (item && item->GetRootMenuItem() != item) { - gfx::Point screen_loc(0, item->height()); - View::ConvertPointToScreen(item, &screen_loc); - item->GetDelegate()->ShowContextMenu( - item, item->GetCommand(), screen_loc.x(), screen_loc.y(), false); - } - return true; - } - - // NOTE: focus wasn't changed when the menu was shown. As such, don't - // dispatch key events otherwise the focused window will get the events. - case WM_KEYDOWN: - return OnKeyDown(msg); - - case WM_CHAR: - return OnChar(msg); - - case WM_KEYUP: - return true; - - case WM_SYSKEYUP: - // We may have been shown on a system key, as such don't do anything - // here. If another system key is pushed we'll get a WM_SYSKEYDOWN and - // close the menu. - return true; - - case WM_CANCELMODE: - case WM_SYSKEYDOWN: - // Exit immediately on system keys. - Cancel(true); - return false; - - default: - break; - } - TranslateMessage(&msg); - DispatchMessage(&msg); - return !exit_all_; -} - -bool MenuController::OnKeyDown(const MSG& msg) { - DCHECK(blocking_run_); - - switch (msg.wParam) { - case VK_UP: - IncrementSelection(-1); - break; - - case VK_DOWN: - IncrementSelection(1); - break; - - // Handling of VK_RIGHT and VK_LEFT is different depending on the UI - // layout. - case VK_RIGHT: - if (l10n_util::TextDirection() == l10n_util::RIGHT_TO_LEFT) - CloseSubmenu(); - else - OpenSubmenuChangeSelectionIfCan(); - break; - - case VK_LEFT: - if (l10n_util::TextDirection() == l10n_util::RIGHT_TO_LEFT) - OpenSubmenuChangeSelectionIfCan(); - else - CloseSubmenu(); - break; - - case VK_RETURN: - if (pending_state_.item) { - if (pending_state_.item->HasSubmenu()) { - OpenSubmenuChangeSelectionIfCan(); - } else if (pending_state_.item->IsEnabled()) { - Accept(pending_state_.item, 0); - return false; - } - } - break; - - case VK_ESCAPE: - if (!state_.item->GetParentMenuItem() || - (!state_.item->GetParentMenuItem()->GetParentMenuItem() && - (!state_.item->HasSubmenu() || - !state_.item->GetSubmenu()->IsShowing()))) { - // User pressed escape and only one menu is shown, cancel it. - Cancel(false); - return false; - } else { - CloseSubmenu(); - } - break; - - case VK_APPS: - break; - - default: - TranslateMessage(&msg); - break; - } - return true; -} - -bool MenuController::OnChar(const MSG& msg) { - DCHECK(blocking_run_); - - return !SelectByChar(static_cast<wchar_t>(msg.wParam)); -} - -MenuController::MenuController(bool blocking) - : blocking_run_(blocking), - showing_(false), - exit_all_(false), - did_capture_(false), - result_(NULL), - drop_target_(NULL), - owner_(NULL), - possible_drag_(false), - valid_drop_coordinates_(false), - showing_submenu_(false), - result_mouse_event_flags_(0) { -#ifdef DEBUG_MENU - instance_count++; - DLOG(INFO) << "created MC, count=" << instance_count; -#endif -} - -MenuController::~MenuController() { - DCHECK(!showing_); - StopShowTimer(); - StopCancelAllTimer(); -#ifdef DEBUG_MENU - instance_count--; - DLOG(INFO) << "destroyed MC, count=" << instance_count; -#endif -} - -void MenuController::Accept(MenuItemView* item, int mouse_event_flags) { - DCHECK(IsBlockingRun()); - result_ = item; - exit_all_ = true; - result_mouse_event_flags_ = mouse_event_flags; -} - -void MenuController::CloseAllNestedMenus() { - for (std::list<State>::iterator i = menu_stack_.begin(); - i != menu_stack_.end(); ++i) { - MenuItemView* item = i->item; - MenuItemView* last_item = item; - while (item) { - CloseMenu(item); - last_item = item; - item = item->GetParentMenuItem(); - } - i->submenu_open = false; - i->item = last_item; - } -} - -MenuItemView* MenuController::GetMenuItemAt(View* source, int x, int y) { - View* child_under_mouse = source->GetViewForPoint(gfx::Point(x, y)); - if (child_under_mouse && child_under_mouse->IsEnabled() && - child_under_mouse->GetID() == MenuItemView::kMenuItemViewID) { - return static_cast<MenuItemView*>(child_under_mouse); - } - return NULL; -} - -MenuItemView* MenuController::GetEmptyMenuItemAt(View* source, int x, int y) { - View* child_under_mouse = source->GetViewForPoint(gfx::Point(x, y)); - if (child_under_mouse && - child_under_mouse->GetID() == EmptyMenuMenuItem::kEmptyMenuItemViewID) { - return static_cast<MenuItemView*>(child_under_mouse); - } - return NULL; -} - -bool MenuController::IsScrollButtonAt(SubmenuView* source, - int x, - int y, - MenuPart::Type* part) { - MenuScrollViewContainer* scroll_view = source->GetScrollViewContainer(); - View* child_under_mouse = scroll_view->GetViewForPoint(gfx::Point(x, y)); - if (child_under_mouse && child_under_mouse->IsEnabled()) { - if (child_under_mouse == scroll_view->scroll_up_button()) { - *part = MenuPart::SCROLL_UP; - return true; - } - if (child_under_mouse == scroll_view->scroll_down_button()) { - *part = MenuPart::SCROLL_DOWN; - return true; - } - } - return false; -} - -MenuController::MenuPart MenuController::GetMenuPartByScreenCoordinate( - SubmenuView* source, - int source_x, - int source_y) { - MenuPart part; - - gfx::Point screen_loc(source_x, source_y); - View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); - - MenuItemView* item = state_.item; - while (item) { - if (item->HasSubmenu() && item->GetSubmenu()->IsShowing() && - GetMenuPartByScreenCoordinateImpl(item->GetSubmenu(), screen_loc, - &part)) { - return part; - } - item = item->GetParentMenuItem(); - } - - return part; -} - -bool MenuController::GetMenuPartByScreenCoordinateImpl( - SubmenuView* menu, - const gfx::Point& screen_loc, - MenuPart* part) { - // Is the mouse over the scroll buttons? - gfx::Point scroll_view_loc = screen_loc; - View* scroll_view_container = menu->GetScrollViewContainer(); - View::ConvertPointToView(NULL, scroll_view_container, &scroll_view_loc); - if (scroll_view_loc.x() < 0 || - scroll_view_loc.x() >= scroll_view_container->width() || - scroll_view_loc.y() < 0 || - scroll_view_loc.y() >= scroll_view_container->height()) { - // Point isn't contained in menu. - return false; - } - if (IsScrollButtonAt(menu, scroll_view_loc.x(), scroll_view_loc.y(), - &(part->type))) { - part->submenu = menu; - return true; - } - - // Not over the scroll button. Check the actual menu. - if (DoesSubmenuContainLocation(menu, screen_loc)) { - gfx::Point menu_loc = screen_loc; - View::ConvertPointToView(NULL, menu, &menu_loc); - part->menu = GetMenuItemAt(menu, menu_loc.x(), menu_loc.y()); - part->type = MenuPart::MENU_ITEM; - if (!part->menu) - part->parent = menu->GetMenuItem(); - return true; - } - - // While the mouse isn't over a menu item or the scroll buttons of menu, it - // is contained by menu and so we return true. If we didn't return true other - // menus would be searched, even though they are likely obscured by us. - return true; -} - -bool MenuController::DoesSubmenuContainLocation(SubmenuView* submenu, - const gfx::Point& screen_loc) { - gfx::Point view_loc = screen_loc; - View::ConvertPointToView(NULL, submenu, &view_loc); - gfx::Rect vis_rect = submenu->GetVisibleBounds(); - return vis_rect.Contains(view_loc.x(), view_loc.y()); -} - -void MenuController::CommitPendingSelection() { - StopShowTimer(); - - size_t paths_differ_at = 0; - std::vector<MenuItemView*> current_path; - std::vector<MenuItemView*> new_path; - BuildPathsAndCalculateDiff(state_.item, pending_state_.item, ¤t_path, - &new_path, &paths_differ_at); - - // Hide the old menu. - for (size_t i = paths_differ_at; i < current_path.size(); ++i) { - if (current_path[i]->HasSubmenu()) { - current_path[i]->GetSubmenu()->Hide(); - } - } - - // Copy pending to state_, making sure to preserve the direction menus were - // opened. - std::list<bool> pending_open_direction; - state_.open_leading.swap(pending_open_direction); - state_ = pending_state_; - state_.open_leading.swap(pending_open_direction); - - int menu_depth = MenuDepth(state_.item); - if (menu_depth == 0) { - state_.open_leading.clear(); - } else { - int cached_size = static_cast<int>(state_.open_leading.size()); - DCHECK(menu_depth >= 0); - while (cached_size-- >= menu_depth) - state_.open_leading.pop_back(); - } - - if (!state_.item) { - // Nothing to select. - StopScrolling(); - return; - } - - // Open all the submenus preceeding the last menu item (last menu item is - // handled next). - if (new_path.size() > 1) { - for (std::vector<MenuItemView*>::iterator i = new_path.begin(); - i != new_path.end() - 1; ++i) { - OpenMenu(*i); - } - } - - if (state_.submenu_open) { - // The submenu should be open, open the submenu if the item has a submenu. - if (state_.item->HasSubmenu()) { - OpenMenu(state_.item); - } else { - state_.submenu_open = false; - } - } else if (state_.item->HasSubmenu() && - state_.item->GetSubmenu()->IsShowing()) { - state_.item->GetSubmenu()->Hide(); - } - - if (scroll_task_.get() && scroll_task_->submenu()) { - // Stop the scrolling if none of the elements of the selection contain - // the menu being scrolled. - bool found = false; - MenuItemView* item = state_.item; - while (item && !found) { - found = (item->HasSubmenu() && item->GetSubmenu()->IsShowing() && - item->GetSubmenu() == scroll_task_->submenu()); - item = item->GetParentMenuItem(); - } - if (!found) - StopScrolling(); - } -} - -void MenuController::CloseMenu(MenuItemView* item) { - DCHECK(item); - if (!item->HasSubmenu()) - return; - item->GetSubmenu()->Hide(); -} - -void MenuController::OpenMenu(MenuItemView* item) { - DCHECK(item); - if (item->GetSubmenu()->IsShowing()) { - return; - } - - bool prefer_leading = - state_.open_leading.empty() ? true : state_.open_leading.back(); - bool resulting_direction; - gfx::Rect bounds = - CalculateMenuBounds(item, prefer_leading, &resulting_direction); - state_.open_leading.push_back(resulting_direction); - bool do_capture = (!did_capture_ && blocking_run_); - showing_submenu_ = true; - item->GetSubmenu()->ShowAt(owner_, bounds, do_capture); - showing_submenu_ = false; - did_capture_ = true; -} - -void MenuController::BuildPathsAndCalculateDiff( - MenuItemView* old_item, - MenuItemView* new_item, - std::vector<MenuItemView*>* old_path, - std::vector<MenuItemView*>* new_path, - size_t* first_diff_at) { - DCHECK(old_path && new_path && first_diff_at); - BuildMenuItemPath(old_item, old_path); - BuildMenuItemPath(new_item, new_path); - - size_t common_size = std::min(old_path->size(), new_path->size()); - - // Find the first difference between the two paths, when the loop - // returns, diff_i is the first index where the two paths differ. - for (size_t i = 0; i < common_size; ++i) { - if ((*old_path)[i] != (*new_path)[i]) { - *first_diff_at = i; - return; - } - } - - *first_diff_at = common_size; -} - -void MenuController::BuildMenuItemPath(MenuItemView* item, - std::vector<MenuItemView*>* path) { - if (!item) - return; - BuildMenuItemPath(item->GetParentMenuItem(), path); - path->push_back(item); -} - -void MenuController::StartShowTimer() { - show_timer_.Start(TimeDelta::FromMilliseconds(kShowDelay), this, - &MenuController::CommitPendingSelection); -} - -void MenuController::StopShowTimer() { - show_timer_.Stop(); -} - -void MenuController::StartCancelAllTimer() { - cancel_all_timer_.Start(TimeDelta::FromMilliseconds(kCloseOnExitTime), - this, &MenuController::CancelAll); -} - -void MenuController::StopCancelAllTimer() { - cancel_all_timer_.Stop(); -} - -gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, - bool prefer_leading, - bool* is_leading) { - DCHECK(item); - - SubmenuView* submenu = item->GetSubmenu(); - DCHECK(submenu); - - gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize(); - - // Don't let the menu go to wide. This is some where between what IE and FF - // do. - pref.set_width(std::min(pref.width(), kMaxMenuWidth)); - if (!state_.monitor_bounds.IsEmpty()) - pref.set_width(std::min(pref.width(), state_.monitor_bounds.width())); - - // Assume we can honor prefer_leading. - *is_leading = prefer_leading; - - int x, y; - - if (!item->GetParentMenuItem()) { - // First item, position relative to initial location. - x = state_.initial_bounds.x(); - y = state_.initial_bounds.bottom(); - if (state_.anchor == MenuItemView::TOPRIGHT) - x = x + state_.initial_bounds.width() - pref.width(); - if (!state_.monitor_bounds.IsEmpty() && - y + pref.height() > state_.monitor_bounds.bottom()) { - // The menu doesn't fit on screen. If the first location is above the - // half way point, show from the mouse location to bottom of screen. - // Otherwise show from the top of the screen to the location of the mouse. - // While odd, this behavior matches IE. - if (y < (state_.monitor_bounds.y() + - state_.monitor_bounds.height() / 2)) { - pref.set_height(std::min(pref.height(), - state_.monitor_bounds.bottom() - y)); - } else { - pref.set_height(std::min(pref.height(), - state_.initial_bounds.y() - state_.monitor_bounds.y())); - y = state_.initial_bounds.y() - pref.height(); - } - } - } else { - // Not the first menu; position it relative to the bounds of the menu - // item. - gfx::Point item_loc; - View::ConvertPointToScreen(item, &item_loc); - - // We must make sure we take into account the UI layout. If the layout is - // RTL, then a 'leading' menu is positioned to the left of the parent menu - // item and not to the right. - bool layout_is_rtl = item->UILayoutIsRightToLeft(); - bool create_on_the_right = (prefer_leading && !layout_is_rtl) || - (!prefer_leading && layout_is_rtl); - - if (create_on_the_right) { - x = item_loc.x() + item->width() - kSubmenuHorizontalInset; - if (state_.monitor_bounds.width() != 0 && - x + pref.width() > state_.monitor_bounds.right()) { - if (layout_is_rtl) - *is_leading = true; - else - *is_leading = false; - x = item_loc.x() - pref.width() + kSubmenuHorizontalInset; - } - } else { - x = item_loc.x() - pref.width() + kSubmenuHorizontalInset; - if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) { - if (layout_is_rtl) - *is_leading = false; - else - *is_leading = true; - x = item_loc.x() + item->width() - kSubmenuHorizontalInset; - } - } - y = item_loc.y() - kSubmenuBorderSize; - if (state_.monitor_bounds.width() != 0) { - pref.set_height(std::min(pref.height(), state_.monitor_bounds.height())); - if (y + pref.height() > state_.monitor_bounds.bottom()) - y = state_.monitor_bounds.bottom() - pref.height(); - if (y < state_.monitor_bounds.y()) - y = state_.monitor_bounds.y(); - } - } - - if (state_.monitor_bounds.width() != 0) { - if (x + pref.width() > state_.monitor_bounds.right()) - x = state_.monitor_bounds.right() - pref.width(); - if (x < state_.monitor_bounds.x()) - x = state_.monitor_bounds.x(); - } - return gfx::Rect(x, y, pref.width(), pref.height()); -} - -// static -int MenuController::MenuDepth(MenuItemView* item) { - if (!item) - return 0; - return MenuDepth(item->GetParentMenuItem()) + 1; -} - -void MenuController::IncrementSelection(int delta) { - MenuItemView* item = pending_state_.item; - DCHECK(item); - if (pending_state_.submenu_open && item->HasSubmenu() && - item->GetSubmenu()->IsShowing()) { - // A menu is selected and open, but none of its children are selected, - // select the first menu item. - if (item->GetSubmenu()->GetMenuItemCount()) { - SetSelection(item->GetSubmenu()->GetMenuItemAt(0), false, false); - ScrollToVisible(item->GetSubmenu()->GetMenuItemAt(0)); - return; // return so else case can fall through. - } - } - if (item->GetParentMenuItem()) { - MenuItemView* parent = item->GetParentMenuItem(); - int parent_count = parent->GetSubmenu()->GetMenuItemCount(); - if (parent_count > 1) { - for (int i = 0; i < parent_count; ++i) { - if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { - int next_index = (i + delta + parent_count) % parent_count; - ScrollToVisible(parent->GetSubmenu()->GetMenuItemAt(next_index)); - SetSelection(parent->GetSubmenu()->GetMenuItemAt(next_index), false, - false); - break; - } - } - } - } -} - -void MenuController::OpenSubmenuChangeSelectionIfCan() { - MenuItemView* item = pending_state_.item; - if (item->HasSubmenu()) { - if (item->GetSubmenu()->GetMenuItemCount() > 0) { - SetSelection(item->GetSubmenu()->GetMenuItemAt(0), false, true); - } else { - // No menu items, just show the sub-menu. - SetSelection(item, true, true); - } - } -} - -void MenuController::CloseSubmenu() { - MenuItemView* item = state_.item; - DCHECK(item); - if (!item->GetParentMenuItem()) - return; - if (item->HasSubmenu() && item->GetSubmenu()->IsShowing()) { - SetSelection(item, false, true); - } else if (item->GetParentMenuItem()->GetParentMenuItem()) { - SetSelection(item->GetParentMenuItem(), false, true); - } -} - -bool MenuController::IsMenuWindow(MenuItemView* item, HWND window) { - if (!item) - return false; - return ((item->HasSubmenu() && item->GetSubmenu()->IsShowing() && - item->GetSubmenu()->GetWidget()->GetNativeView() == window) || - IsMenuWindow(item->GetParentMenuItem(), window)); -} - -bool MenuController::SelectByChar(wchar_t character) { - wchar_t char_array[1] = { character }; - wchar_t key = l10n_util::ToLower(char_array)[0]; - MenuItemView* item = pending_state_.item; - if (!item->HasSubmenu() || !item->GetSubmenu()->IsShowing()) - item = item->GetParentMenuItem(); - DCHECK(item); - DCHECK(item->HasSubmenu()); - SubmenuView* submenu = item->GetSubmenu(); - DCHECK(submenu); - int menu_item_count = submenu->GetMenuItemCount(); - if (!menu_item_count) - return false; - for (int i = 0; i < menu_item_count; ++i) { - MenuItemView* child = submenu->GetMenuItemAt(i); - if (child->GetMnemonic() == key && child->IsEnabled()) { - Accept(child, 0); - return true; - } - } - - // No matching mnemonic, search through items that don't have mnemonic - // based on first character of the title. - int first_match = -1; - bool has_multiple = false; - int next_match = -1; - int index_of_item = -1; - for (int i = 0; i < menu_item_count; ++i) { - MenuItemView* child = submenu->GetMenuItemAt(i); - if (!child->GetMnemonic() && child->IsEnabled()) { - std::wstring lower_title = l10n_util::ToLower(child->GetTitle()); - if (child == pending_state_.item) - index_of_item = i; - if (lower_title.length() && lower_title[0] == key) { - if (first_match == -1) - first_match = i; - else - has_multiple = true; - if (next_match == -1 && index_of_item != -1 && i > index_of_item) - next_match = i; - } - } - } - if (first_match != -1) { - if (!has_multiple) { - if (submenu->GetMenuItemAt(first_match)->HasSubmenu()) { - SetSelection(submenu->GetMenuItemAt(first_match), true, false); - } else { - Accept(submenu->GetMenuItemAt(first_match), 0); - return true; - } - } else if (index_of_item == -1 || next_match == -1) { - SetSelection(submenu->GetMenuItemAt(first_match), false, false); - } else { - SetSelection(submenu->GetMenuItemAt(next_match), false, false); - } - } - return false; -} - -void MenuController::RepostEvent(SubmenuView* source, - const MouseEvent& event) { - gfx::Point screen_loc(event.location()); - View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); - HWND window = WindowFromPoint(screen_loc.ToPOINT()); - if (window) { -#ifdef DEBUG_MENU - DLOG(INFO) << "RepostEvent on press"; -#endif - - // Release the capture. - SubmenuView* submenu = state_.item->GetRootMenuItem()->GetSubmenu(); - submenu->ReleaseCapture(); - - if (submenu->host() && submenu->host()->GetNativeView() && - GetWindowThreadProcessId(submenu->host()->GetNativeView(), NULL) != - GetWindowThreadProcessId(window, NULL)) { - // Even though we have mouse capture, windows generates a mouse event - // if the other window is in a separate thread. Don't generate an event in - // this case else the target window can get double events leading to bad - // behavior. - return; - } - - // Convert the coordinates to the target window. - RECT window_bounds; - GetWindowRect(window, &window_bounds); - int window_x = screen_loc.x() - window_bounds.left; - int window_y = screen_loc.y() - window_bounds.top; - - // Determine whether the click was in the client area or not. - // NOTE: WM_NCHITTEST coordinates are relative to the screen. - LRESULT nc_hit_result = SendMessage(window, WM_NCHITTEST, 0, - MAKELPARAM(screen_loc.x(), - screen_loc.y())); - const bool in_client_area = (nc_hit_result == HTCLIENT); - - // TODO(sky): this isn't right. The event to generate should correspond - // with the event we just got. MouseEvent only tells us what is down, - // which may differ. Need to add ability to get changed button from - // MouseEvent. - int event_type; - if (event.IsLeftMouseButton()) - event_type = in_client_area ? WM_LBUTTONDOWN : WM_NCLBUTTONDOWN; - else if (event.IsMiddleMouseButton()) - event_type = in_client_area ? WM_MBUTTONDOWN : WM_NCMBUTTONDOWN; - else if (event.IsRightMouseButton()) - event_type = in_client_area ? WM_RBUTTONDOWN : WM_NCRBUTTONDOWN; - else - event_type = 0; // Unknown mouse press. - - if (event_type) { - if (in_client_area) { - PostMessage(window, event_type, event.GetWindowsFlags(), - MAKELPARAM(window_x, window_y)); - } else { - PostMessage(window, event_type, nc_hit_result, - MAKELPARAM(screen_loc.x(), screen_loc.y())); - } - } - } -} - -void MenuController::SetDropMenuItem( - MenuItemView* new_target, - MenuDelegate::DropPosition new_position) { - if (new_target == drop_target_ && new_position == drop_position_) - return; - - if (drop_target_) { - drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem( - NULL, MenuDelegate::DROP_NONE); - } - drop_target_ = new_target; - drop_position_ = new_position; - if (drop_target_) { - drop_target_->GetParentMenuItem()->GetSubmenu()->SetDropMenuItem( - drop_target_, drop_position_); - } -} - -void MenuController::UpdateScrolling(const MenuPart& part) { - if (!part.is_scroll() && !scroll_task_.get()) - return; - - if (!scroll_task_.get()) - scroll_task_.reset(new MenuScrollTask()); - scroll_task_->Update(part); -} - -void MenuController::StopScrolling() { - scroll_task_.reset(NULL); -} - -} // namespace views diff --git a/views/controls/menu/chrome_menu.h b/views/controls/menu/chrome_menu.h deleted file mode 100644 index 89d16d41..0000000 --- a/views/controls/menu/chrome_menu.h +++ /dev/null @@ -1,962 +0,0 @@ -// 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. - -#ifndef VIEWS_CONTROLS_MENU_CHROME_MENU_H_ -#define VIEWS_CONTROLS_MENU_CHROME_MENU_H_ - -#include <list> -#include <vector> - -#include "app/drag_drop_types.h" -#include "app/gfx/font.h" -#include "base/gfx/native_widget_types.h" -#include "base/gfx/point.h" -#include "base/gfx/rect.h" -#include "base/message_loop.h" -#include "base/task.h" -#include "base/timer.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "views/controls/menu/controller.h" -#include "views/event.h" -#include "views/view.h" - -namespace views { - -class MenuController; -class MenuHost; -class MenuItemView; -class MenuScrollViewContainer; -class SubmenuView; - -namespace { -class MenuHostRootView; -} - -// MenuDelegate -------------------------------------------------------------- - -// Delegate for the menu. - -class MenuDelegate : Controller { - public: - // Used during drag and drop to indicate where the drop indicator should - // be rendered. - enum DropPosition { - // Indicates a drop is not allowed here. - DROP_NONE, - - // Indicates the drop should occur before the item. - DROP_BEFORE, - - // Indicates the drop should occur after the item. - DROP_AFTER, - - // Indicates the drop should occur on the item. - DROP_ON - }; - - // Whether or not an item should be shown as checked. - // TODO(sky): need checked support. - virtual bool IsItemChecked(int id) const { - return false; - } - - // The string shown for the menu item. This is only invoked when an item is - // added with an empty label. - virtual std::wstring GetLabel(int id) const { - return std::wstring(); - } - - // Shows the context menu with the specified id. This is invoked when the - // user does the appropriate gesture to show a context menu. The id - // identifies the id of the menu to show the context menu for. - // is_mouse_gesture is true if this is the result of a mouse gesture. - // If this is not the result of a mouse gesture x/y is the recommended - // location to display the content menu at. In either case, x/y is in - // screen coordinates. - // Returns true if a context menu was displayed, otherwise false - virtual bool ShowContextMenu(MenuItemView* source, - int id, - int x, - int y, - bool is_mouse_gesture) { - return false; - } - - // Controller - virtual bool SupportsCommand(int id) const { - return true; - } - virtual bool IsCommandEnabled(int id) const { - return true; - } - virtual bool GetContextualLabel(int id, std::wstring* out) const { - return false; - } - virtual void ExecuteCommand(int id) { - } - - // Executes the specified command. mouse_event_flags give the flags of the - // mouse event that triggered this to be invoked (views::MouseEvent - // flags). mouse_event_flags is 0 if this is triggered by a user gesture - // other than a mouse event. - virtual void ExecuteCommand(int id, int mouse_event_flags) { - ExecuteCommand(id); - } - - // Returns true if the specified mouse event is one the user can use - // to trigger, or accept, the mouse. Defaults to left or right mouse buttons. - virtual bool IsTriggerableEvent(const MouseEvent& e) { - return e.IsLeftMouseButton() || e.IsRightMouseButton(); - } - - // Invoked to determine if drops can be accepted for a submenu. This is - // ONLY invoked for menus that have submenus and indicates whether or not - // a drop can occur on any of the child items of the item. For example, - // consider the following menu structure: - // - // A - // B - // C - // - // Where A has a submenu with children B and C. This is ONLY invoked for - // A, not B and C. - // - // To restrict which children can be dropped on override GetDropOperation. - virtual bool CanDrop(MenuItemView* menu, const OSExchangeData& data) { - return false; - } - - // Returns the drop operation for the specified target menu item. This is - // only invoked if CanDrop returned true for the parent menu. position - // is set based on the location of the mouse, reset to specify a different - // position. - // - // If a drop should not be allowed, returned DragDropTypes::DRAG_NONE. - virtual int GetDropOperation(MenuItemView* item, - const DropTargetEvent& event, - DropPosition* position) { - NOTREACHED() << "If you override CanDrop, you need to override this too"; - return DragDropTypes::DRAG_NONE; - } - - // Invoked to perform the drop operation. This is ONLY invoked if - // canDrop returned true for the parent menu item, and GetDropOperation - // returned an operation other than DragDropTypes::DRAG_NONE. - // - // menu indicates the menu the drop occurred on. - virtual int OnPerformDrop(MenuItemView* menu, - DropPosition position, - const DropTargetEvent& event) { - NOTREACHED() << "If you override CanDrop, you need to override this too"; - return DragDropTypes::DRAG_NONE; - } - - // Invoked to determine if it is possible for the user to drag the specified - // menu item. - virtual bool CanDrag(MenuItemView* menu) { - return false; - } - - // Invoked to write the data for a drag operation to data. sender is the - // MenuItemView being dragged. - virtual void WriteDragData(MenuItemView* sender, OSExchangeData* data) { - NOTREACHED() << "If you override CanDrag, you must override this too."; - } - - // Invoked to determine the drag operations for a drag session of sender. - // See DragDropTypes for possible values. - virtual int GetDragOperations(MenuItemView* sender) { - NOTREACHED() << "If you override CanDrag, you must override this too."; - return 0; - } - - // Notification the menu has closed. This is only sent when running the - // menu for a drop. - virtual void DropMenuClosed(MenuItemView* menu) { - } - - // Notification that the user has highlighted the specified item. - virtual void SelectionChanged(MenuItemView* menu) { - } -}; - -// MenuItemView -------------------------------------------------------------- - -// MenuItemView represents a single menu item with a label and optional icon. -// Each MenuItemView may also contain a submenu, which in turn may contain -// any number of child MenuItemViews. -// -// To use a menu create an initial MenuItemView using the constructor that -// takes a MenuDelegate, then create any number of child menu items by way -// of the various AddXXX methods. -// -// MenuItemView is itself a View, which means you can add Views to each -// MenuItemView. This normally NOT want you want, rather add other child Views -// to the submenu of the MenuItemView. -// -// There are two ways to show a MenuItemView: -// 1. Use RunMenuAt. This blocks the caller, executing the selected command -// on success. -// 2. Use RunMenuForDropAt. This is intended for use during a drop session -// and does NOT block the caller. Instead the delegate is notified when the -// menu closes via the DropMenuClosed method. - -class MenuItemView : public View { - friend class MenuController; - - public: - // ID used to identify menu items. - static const int kMenuItemViewID; - - // If true SetNestableTasksAllowed(true) is invoked before MessageLoop::Run - // is invoked. This is only useful for testing and defaults to false. - static bool allow_task_nesting_during_run_; - - // Different types of menu items. - enum Type { - NORMAL, - SUBMENU, - CHECKBOX, - RADIO, - SEPARATOR - }; - - // Where the menu should be anchored to. - enum AnchorPosition { - TOPLEFT, - TOPRIGHT - }; - - // Constructor for use with the top level menu item. This menu is never - // shown to the user, rather its use as the parent for all menu items. - explicit MenuItemView(MenuDelegate* delegate); - - virtual ~MenuItemView(); - - // Run methods. See description above class for details. Both Run methods take - // a rectangle, which is used to position the menu. |has_mnemonics| indicates - // whether the items have mnemonics. Mnemonics are identified by way of the - // character following the '&'. - void RunMenuAt(gfx::NativeView parent, - const gfx::Rect& bounds, - AnchorPosition anchor, - bool has_mnemonics); - void RunMenuForDropAt(gfx::NativeView parent, - const gfx::Rect& bounds, - AnchorPosition anchor); - - // Hides and cancels the menu. This does nothing if the menu is not open. - void Cancel(); - - // Adds an item to this menu. - // item_id The id of the item, used to identify it in delegate callbacks - // or (if delegate is NULL) to identify the command associated - // with this item with the controller specified in the ctor. Note - // that this value should not be 0 as this has a special meaning - // ("NULL command, no item selected") - // label The text label shown. - // type The type of item. - void AppendMenuItem(int item_id, - const std::wstring& label, - Type type) { - AppendMenuItemInternal(item_id, label, SkBitmap(), type); - } - - // Append a submenu to this menu. - // The returned pointer is owned by this menu. - MenuItemView* AppendSubMenu(int item_id, - const std::wstring& label) { - return AppendMenuItemInternal(item_id, label, SkBitmap(), SUBMENU); - } - - // Append a submenu with an icon to this menu. - // The returned pointer is owned by this menu. - MenuItemView* AppendSubMenuWithIcon(int item_id, - const std::wstring& label, - const SkBitmap& icon) { - return AppendMenuItemInternal(item_id, label, icon, SUBMENU); - } - - // This is a convenience for standard text label menu items where the label - // is provided with this call. - void AppendMenuItemWithLabel(int item_id, - const std::wstring& label) { - AppendMenuItem(item_id, label, NORMAL); - } - - // This is a convenience for text label menu items where the label is - // provided by the delegate. - void AppendDelegateMenuItem(int item_id) { - AppendMenuItem(item_id, std::wstring(), NORMAL); - } - - // Adds a separator to this menu - void AppendSeparator() { - AppendMenuItemInternal(0, std::wstring(), SkBitmap(), SEPARATOR); - } - - // Appends a menu item with an icon. This is for the menu item which - // needs an icon. Calling this function forces the Menu class to draw - // the menu, instead of relying on Windows. - void AppendMenuItemWithIcon(int item_id, - const std::wstring& label, - const SkBitmap& icon) { - AppendMenuItemInternal(item_id, label, icon, NORMAL); - } - - // Returns the view that contains child menu items. If the submenu has - // not been creates, this creates it. - virtual SubmenuView* CreateSubmenu(); - - // Returns true if this menu item has a submenu. - virtual bool HasSubmenu() const { return (submenu_ != NULL); } - - // Returns the view containing child menu items. - virtual SubmenuView* GetSubmenu() const { return submenu_; } - - // Returns the parent menu item. - MenuItemView* GetParentMenuItem() const { return parent_menu_item_; } - - // Sets the font. - void SetFont(const gfx::Font& font) { font_ = font; } - - // Sets the title - void SetTitle(const std::wstring& title) { - title_ = title; - } - - // Returns the title. - const std::wstring& GetTitle() const { return title_; } - - // Sets whether this item is selected. This is invoked as the user moves - // the mouse around the menu while open. - void SetSelected(bool selected); - - // Returns true if the item is selected. - bool IsSelected() const { return selected_; } - - // Sets the icon for the descendant identified by item_id. - void SetIcon(const SkBitmap& icon, int item_id); - - // Sets the icon of this menu item. - void SetIcon(const SkBitmap& icon); - - // Returns the icon. - const SkBitmap& GetIcon() const { return icon_; } - - // Sets the command id of this menu item. - void SetCommand(int command) { command_ = command; } - - // Returns the command id of this item. - int GetCommand() const { return command_; } - - // Paints the menu item. - virtual void Paint(gfx::Canvas* canvas); - - // Returns the preferred size of this item. - virtual gfx::Size GetPreferredSize(); - - // Returns the object responsible for controlling showing the menu. - MenuController* GetMenuController(); - - // Returns the delegate. This returns the delegate of the root menu item. - MenuDelegate* GetDelegate(); - - // Returns the root parent, or this if this has no parent. - MenuItemView* GetRootMenuItem(); - - // Returns the mnemonic for this MenuItemView, or 0 if this MenuItemView - // doesn't have a mnemonic. - wchar_t GetMnemonic(); - - // Do we have icons? This only has effect on the top menu. Turning this on - // makes the menus slightly wider and taller. - void set_has_icons(bool has_icons) { - has_icons_ = has_icons; - } - - protected: - // Creates a MenuItemView. This is used by the various AddXXX methods. - MenuItemView(MenuItemView* parent, int command, Type type); - - private: - // Called by the two constructors to initialize this menu item. - void Init(MenuItemView* parent, - int command, - MenuItemView::Type type, - MenuDelegate* delegate); - - // All the AddXXX methods funnel into this. - MenuItemView* AppendMenuItemInternal(int item_id, - const std::wstring& label, - const SkBitmap& icon, - Type type); - - // Returns the descendant with the specified command. - MenuItemView* GetDescendantByID(int id); - - // Invoked by the MenuController when the menu closes as the result of - // drag and drop run. - void DropMenuClosed(bool notify_delegate); - - // The RunXXX methods call into this to set up the necessary state before - // running. - void PrepareForRun(bool has_mnemonics); - - // Returns the flags passed to DrawStringInt. - int GetDrawStringFlags(); - - // If this menu item has no children a child is added showing it has no - // children. Otherwise AddEmtpyMenuIfNecessary is recursively invoked on - // child menu items that have children. - void AddEmptyMenus(); - - // Undoes the work of AddEmptyMenus. - void RemoveEmptyMenus(); - - // Given bounds within our View, this helper routine mirrors the bounds if - // necessary. - void AdjustBoundsForRTLUI(gfx::Rect* rect) const; - - // Actual paint implementation. If for_drag is true, portions of the menu - // are not rendered. - void Paint(gfx::Canvas* canvas, bool for_drag); - - // Destroys the window used to display this menu and recursively destroys - // the windows used to display all descendants. - void DestroyAllMenuHosts(); - - // Returns the various margins. - int GetTopMargin(); - int GetBottomMargin(); - - // The delegate. This is only valid for the root menu item. You shouldn't - // use this directly, instead use GetDelegate() which walks the tree as - // as necessary. - MenuDelegate* delegate_; - - // Returns the controller for the run operation, or NULL if the menu isn't - // showing. - MenuController* controller_; - - // Used to detect when Cancel was invoked. - bool canceled_; - - // Our parent. - MenuItemView* parent_menu_item_; - - // Type of menu. NOTE: MenuItemView doesn't itself represent SEPARATOR, - // that is handled by an entirely different view class. - Type type_; - - // Whether we're selected. - bool selected_; - - // Command id. - int command_; - - // Submenu, created via CreateSubmenu. - SubmenuView* submenu_; - - // Font. - gfx::Font font_; - - // Title. - std::wstring title_; - - // Icon. - SkBitmap icon_; - - // Does the title have a mnemonic? - bool has_mnemonics_; - - bool has_icons_; - - DISALLOW_EVIL_CONSTRUCTORS(MenuItemView); -}; - -// SubmenuView ---------------------------------------------------------------- - -// SubmenuView is the parent of all menu items. -// -// SubmenuView has the following responsibilities: -// . It positions and sizes all child views (any type of View may be added, -// not just MenuItemViews). -// . Forwards the appropriate events to the MenuController. This allows the -// MenuController to update the selection as the user moves the mouse around. -// . Renders the drop indicator during a drop operation. -// . Shows and hides the window (a WidgetWin) when the menu is shown on -// screen. -// -// SubmenuView is itself contained in a MenuScrollViewContainer. -// MenuScrollViewContainer handles showing as much of the SubmenuView as the -// screen allows. If the SubmenuView is taller than the screen, scroll buttons -// are provided that allow the user to see all the menu items. -class SubmenuView : public View { - public: - // Creates a SubmenuView for the specified menu item. - explicit SubmenuView(MenuItemView* parent); - ~SubmenuView(); - - // Returns the number of child views that are MenuItemViews. - // MenuItemViews are identified by ID. - int GetMenuItemCount(); - - // Returns the MenuItemView at the specified index. - MenuItemView* GetMenuItemAt(int index); - - // Positions and sizes the child views. This tiles the views vertically, - // giving each child the available width. - virtual void Layout(); - virtual gfx::Size GetPreferredSize(); - - // View method. Overriden to schedule a paint. We do this so that when - // scrolling occurs, everything is repainted correctly. - virtual void DidChangeBounds(const gfx::Rect& previous, - const gfx::Rect& current); - - // Painting. - void PaintChildren(gfx::Canvas* canvas); - - // Drag and drop methods. These are forwarded to the MenuController. - virtual bool CanDrop(const OSExchangeData& data); - virtual void OnDragEntered(const DropTargetEvent& event); - virtual int OnDragUpdated(const DropTargetEvent& event); - virtual void OnDragExited(); - virtual int OnPerformDrop(const DropTargetEvent& event); - - // Scrolls on menu item boundaries. - virtual bool OnMouseWheel(const MouseWheelEvent& e); - - // Returns true if the menu is showing. - bool IsShowing(); - - // Shows the menu at the specified location. Coordinates are in screen - // coordinates. max_width gives the max width the view should be. - void ShowAt(gfx::NativeView parent, const gfx::Rect& bounds, bool do_capture); - - // Closes the menu, destroying the host. - void Close(); - - // Hides the hosting window. - // - // The hosting window is hidden first, then deleted (Close) when the menu is - // done running. This is done to avoid deletion ordering dependencies. In - // particular, during drag and drop (and when a modal dialog is shown as - // a result of choosing a context menu) it is possible that an event is - // being processed by the host, so that host is on the stack when we need to - // close the window. If we closed the window immediately (and deleted it), - // when control returned back to host we would crash as host was deleted. - void Hide(); - - // If mouse capture was grabbed, it is released. Does nothing if mouse was - // not captured. - void ReleaseCapture(); - - // Overriden from View to prevent tab from doing anything. - virtual bool SkipDefaultKeyEventProcessing(const views::KeyEvent& e); - - // Returns the parent menu item we're showing children for. - MenuItemView* GetMenuItem() const { return parent_menu_item_; } - - // Set the drop item and position. - void SetDropMenuItem(MenuItemView* item, - MenuDelegate::DropPosition position); - - // Returns whether the selection should be shown for the specified item. - // The selection is NOT shown during drag and drop when the drop is over - // the menu. - bool GetShowSelection(MenuItemView* item); - - // Returns the container for the SubmenuView. - MenuScrollViewContainer* GetScrollViewContainer(); - - // Returns the host of the menu. Returns NULL if not showing. - MenuHost* host() const { return host_; } - - private: - // Paints the drop indicator. This is only invoked if item is non-NULL and - // position is not DROP_NONE. - void PaintDropIndicator(gfx::Canvas* canvas, - MenuItemView* item, - MenuDelegate::DropPosition position); - - void SchedulePaintForDropIndicator(MenuItemView* item, - MenuDelegate::DropPosition position); - - // Calculates the location of th edrop indicator. - gfx::Rect CalculateDropIndicatorBounds(MenuItemView* item, - MenuDelegate::DropPosition position); - - // Parent menu item. - MenuItemView* parent_menu_item_; - - // WidgetWin subclass used to show the children. - MenuHost* host_; - - // If non-null, indicates a drop is in progress and drop_item is the item - // the drop is over. - MenuItemView* drop_item_; - - // Position of the drop. - MenuDelegate::DropPosition drop_position_; - - // Ancestor of the SubmenuView, lazily created. - MenuScrollViewContainer* scroll_view_container_; - - DISALLOW_EVIL_CONSTRUCTORS(SubmenuView); -}; - -// MenuController ------------------------------------------------------------- - -// MenuController manages showing, selecting and drag/drop for menus. -// All relevant events are forwarded to the MenuController from SubmenuView -// and MenuHost. - -class MenuController -#if defined(OS_WIN) - : public MessageLoopForUI::Dispatcher { -#else - { -#endif - public: - friend class MenuHostRootView; - friend class MenuItemView; - - // If a menu is currently active, this returns the controller for it. - static MenuController* GetActiveInstance(); - - // Runs the menu at the specified location. If the menu was configured to - // block, the selected item is returned. If the menu does not block this - // returns NULL immediately. - MenuItemView* Run(gfx::NativeView parent, - MenuItemView* root, - const gfx::Rect& bounds, - MenuItemView::AnchorPosition position, - int* mouse_event_flags); - - // Whether or not Run blocks. - bool IsBlockingRun() const { return blocking_run_; } - - // Sets the selection to menu_item, a value of NULL unselects everything. - // If open_submenu is true and menu_item has a submenu, the submenu is shown. - // If update_immediately is true, submenus are opened immediately, otherwise - // submenus are only opened after a timer fires. - // - // Internally this updates pending_state_ immediatley, and if - // update_immediately is true, CommitPendingSelection is invoked to - // show/hide submenus and update state_. - void SetSelection(MenuItemView* menu_item, - bool open_submenu, - bool update_immediately); - - // Cancels the current Run. If all is true, any nested loops are canceled - // as well. This immediately hides all menus. - void Cancel(bool all); - - // An alternative to Cancel(true) that can be used with a OneShotTimer. - void CancelAll() { return Cancel(true); } - - // Various events, forwarded from the submenu. - // - // NOTE: the coordinates of the events are in that of the - // MenuScrollViewContainer. - void OnMousePressed(SubmenuView* source, const MouseEvent& event); - void OnMouseDragged(SubmenuView* source, const MouseEvent& event); - void OnMouseReleased(SubmenuView* source, const MouseEvent& event); - void OnMouseMoved(SubmenuView* source, const MouseEvent& event); - void OnMouseEntered(SubmenuView* source, const MouseEvent& event); - bool CanDrop(SubmenuView* source, const OSExchangeData& data); - void OnDragEntered(SubmenuView* source, const DropTargetEvent& event); - int OnDragUpdated(SubmenuView* source, const DropTargetEvent& event); - void OnDragExited(SubmenuView* source); - int OnPerformDrop(SubmenuView* source, const DropTargetEvent& event); - - // Invoked from the scroll buttons of the MenuScrollViewContainer. - void OnDragEnteredScrollButton(SubmenuView* source, bool is_up); - void OnDragExitedScrollButton(SubmenuView* source); - - private: - class MenuScrollTask; - - // Tracks selection information. - struct State { - State() : item(NULL), submenu_open(false) {} - - // The selected menu item. - MenuItemView* item; - - // If item has a submenu this indicates if the submenu is showing. - bool submenu_open; - - // Bounds passed to the run menu. Used for positioning the first menu. - gfx::Rect initial_bounds; - - // Position of the initial menu. - MenuItemView::AnchorPosition anchor; - - // The direction child menus have opened in. - std::list<bool> open_leading; - - // Bounds for the monitor we're showing on. - gfx::Rect monitor_bounds; - }; - - // Used by GetMenuPartByScreenCoordinate to indicate the menu part at a - // particular location. - struct MenuPart { - // Type of part. - enum Type { - NONE, - MENU_ITEM, - SCROLL_UP, - SCROLL_DOWN - }; - - MenuPart() : type(NONE), menu(NULL), parent(NULL), submenu(NULL) {} - - // Convenience for testing type == SCROLL_DOWN or type == SCROLL_UP. - bool is_scroll() const { return type == SCROLL_DOWN || type == SCROLL_UP; } - - // Type of part. - Type type; - - // If type is MENU_ITEM, this is the menu item the mouse is over, otherwise - // this is NULL. - // NOTE: if type is MENU_ITEM and the mouse is not over a valid menu item - // but is over a menu (for example, the mouse is over a separator or - // empty menu), this is NULL and parent is the menu the mouse was - // clicked on. - MenuItemView* menu; - - // If type is MENU_ITEM but the mouse is not over a menu item this is the - // parent of the menu item the user clicked on. Otherwise this is NULL. - MenuItemView* parent; - - // If type is SCROLL_*, this is the submenu the mouse is over. - SubmenuView* submenu; - }; - - // Sets the active MenuController. - static void SetActiveInstance(MenuController* controller); - -#if defined(OS_WIN) - // Dispatcher method. This returns true if the menu was canceled, or - // if the message is such that the menu should be closed. - virtual bool Dispatch(const MSG& msg); - - // Key processing. The return value of these is returned from Dispatch. - // In other words, if these return false (which they do if escape was - // pressed, or a matching mnemonic was found) the message loop returns. - bool OnKeyDown(const MSG& msg); - bool OnChar(const MSG& msg); -#endif - - // Creates a MenuController. If blocking is true, Run blocks the caller - explicit MenuController(bool blocking); - - ~MenuController(); - - // Invoked when the user accepts the selected item. This is only used - // when blocking. This schedules the loop to quit. - void Accept(MenuItemView* item, int mouse_event_flags); - - // Closes all menus, including any menus of nested invocations of Run. - void CloseAllNestedMenus(); - - // Gets the enabled menu item at the specified location. - // If over_any_menu is non-null it is set to indicate whether the location - // is over any menu. It is possible for this to return NULL, but - // over_any_menu to be true. For example, the user clicked on a separator. - MenuItemView* GetMenuItemAt(View* menu, int x, int y); - - // If there is an empty menu item at the specified location, it is returned. - MenuItemView* GetEmptyMenuItemAt(View* source, int x, int y); - - // Returns true if the coordinate is over the scroll buttons of the - // SubmenuView's MenuScrollViewContainer. If true is returned, part is set to - // indicate which scroll button the coordinate is. - bool IsScrollButtonAt(SubmenuView* source, - int x, - int y, - MenuPart::Type* part); - - // Returns the target for the mouse event. - MenuPart GetMenuPartByScreenCoordinate(SubmenuView* source, - int source_x, - int source_y); - - // Implementation of GetMenuPartByScreenCoordinate for a single menu. Returns - // true if the supplied SubmenuView contains the location in terms of the - // screen. If it does, part is set appropriately and true is returned. - bool GetMenuPartByScreenCoordinateImpl(SubmenuView* menu, - const gfx::Point& screen_loc, - MenuPart* part); - - // Returns true if the SubmenuView contains the specified location. This does - // NOT included the scroll buttons, only the submenu view. - bool DoesSubmenuContainLocation(SubmenuView* submenu, - const gfx::Point& screen_loc); - - // Opens/Closes the necessary menus such that state_ matches that of - // pending_state_. This is invoked if submenus are not opened immediately, - // but after a delay. - void CommitPendingSelection(); - - // If item has a submenu, it is closed. This does NOT update the selection - // in anyway. - void CloseMenu(MenuItemView* item); - - // If item has a submenu, it is opened. This does NOT update the selection - // in anyway. - void OpenMenu(MenuItemView* item); - - // Builds the paths of the two menu items into the two paths, and - // sets first_diff_at to the location of the first difference between the - // two paths. - void BuildPathsAndCalculateDiff(MenuItemView* old_item, - MenuItemView* new_item, - std::vector<MenuItemView*>* old_path, - std::vector<MenuItemView*>* new_path, - size_t* first_diff_at); - - // Builds the path for the specified item. - void BuildMenuItemPath(MenuItemView* item, std::vector<MenuItemView*>* path); - - // Starts/stops the timer that commits the pending state to state - // (opens/closes submenus). - void StartShowTimer(); - void StopShowTimer(); - - // Starts/stops the timer cancel the menu. This is used during drag and - // drop when the drop enters/exits the menu. - void StartCancelAllTimer(); - void StopCancelAllTimer(); - - // Calculates the bounds of the menu to show. is_leading is set to match the - // direction the menu opened in. - gfx::Rect CalculateMenuBounds(MenuItemView* item, - bool prefer_leading, - bool* is_leading); - - // Returns the depth of the menu. - static int MenuDepth(MenuItemView* item); - - // Selects the next/previous menu item. - void IncrementSelection(int delta); - - // If the selected item has a submenu and it isn't currently open, the - // the selection is changed such that the menu opens immediately. - void OpenSubmenuChangeSelectionIfCan(); - - // If possible, closes the submenu. - void CloseSubmenu(); - - // Returns true if window is the window used to show item, or any of - // items ancestors. - bool IsMenuWindow(MenuItemView* item, gfx::NativeWindow window); - - // Selects by mnemonic, and if that doesn't work tries the first character of - // the title. Returns true if a match was selected and the menu should exit. - bool SelectByChar(wchar_t key); - - // If there is a window at the location of the event, a new mouse event is - // generated and posted to it. - void RepostEvent(SubmenuView* source, const MouseEvent& event); - - // Sets the drop target to new_item. - void SetDropMenuItem(MenuItemView* new_item, - MenuDelegate::DropPosition position); - - // Starts/stops scrolling as appropriate. part gives the part the mouse is - // over. - void UpdateScrolling(const MenuPart& part); - - // Stops scrolling. - void StopScrolling(); - - // The active instance. - static MenuController* active_instance_; - - // If true, Run blocks. If false, Run doesn't block and this is used for - // drag and drop. Note that the semantics for drag and drop are slightly - // different: cancel timer is kicked off any time the drag moves outside the - // menu, mouse events do nothing... - bool blocking_run_; - - // If true, we're showing. - bool showing_; - - // If true, all nested run loops should be exited. - bool exit_all_; - - // Whether we did a capture. We do a capture only if we're blocking and - // the mouse was down when Run. - bool did_capture_; - - // As the user drags the mouse around pending_state_ changes immediately. - // When the user stops moving/dragging the mouse (or clicks the mouse) - // pending_state_ is committed to state_, potentially resulting in - // opening or closing submenus. This gives a slight delayed effect to - // submenus as the user moves the mouse around. This is done so that as the - // user moves the mouse all submenus don't immediately pop. - State pending_state_; - State state_; - - // If the user accepted the selection, this is the result. - MenuItemView* result_; - - // The mouse event flags when the user clicked on a menu. Is 0 if the - // user did not use the mousee to select the menu. - int result_mouse_event_flags_; - - // If not empty, it means we're nested. When Run is invoked from within - // Run, the current state (state_) is pushed onto menu_stack_. This allows - // MenuController to restore the state when the nested run returns. - std::list<State> menu_stack_; - - // As the mouse moves around submenus are not opened immediately. Instead - // they open after this timer fires. - base::OneShotTimer<MenuController> show_timer_; - - // Used to invoke CancelAll(). This is used during drag and drop to hide the - // menu after the mouse moves out of the of the menu. This is necessitated by - // the lack of an ability to detect when the drag has completed from the drop - // side. - base::OneShotTimer<MenuController> cancel_all_timer_; - - // Drop target. - MenuItemView* drop_target_; - MenuDelegate::DropPosition drop_position_; - - // Owner of child windows. - gfx::NativeWindow owner_; - - // Indicates a possible drag operation. - bool possible_drag_; - - // Location the mouse was pressed at. Used to detect d&d. - int press_x_; - int press_y_; - - // We get a slew of drag updated messages as the mouse is over us. To avoid - // continually processing whether we can drop, we cache the coordinates. - bool valid_drop_coordinates_; - int drop_x_; - int drop_y_; - int last_drop_operation_; - - // If true, we're in the middle of invoking ShowAt on a submenu. - bool showing_submenu_; - - // Task for scrolling the menu. If non-null indicates a scroll is currently - // underway. - scoped_ptr<MenuScrollTask> scroll_task_; - - DISALLOW_COPY_AND_ASSIGN(MenuController); -}; - -} // namespace views - -#endif // VIEWS_CONTROLS_MENU_CHROME_MENU_H_ diff --git a/views/controls/menu/menu_config.cc b/views/controls/menu/menu_config.cc new file mode 100644 index 0000000..cfdebea --- /dev/null +++ b/views/controls/menu/menu_config.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_config.h" + +#include "build/build_config.h" + +namespace views { + +static MenuConfig* config_instance = NULL; + +void MenuConfig::Reset() { + delete config_instance; + config_instance = NULL; +} + +// static +const MenuConfig& MenuConfig::instance() { + if (!config_instance) + config_instance = Create(); + return *config_instance; +} + +} // namespace views diff --git a/views/controls/menu/menu_config.h b/views/controls/menu/menu_config.h new file mode 100644 index 0000000..a502b8a --- /dev/null +++ b/views/controls/menu/menu_config.h @@ -0,0 +1,102 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_CONTROLS_MENU_MENU_CONFIG_H_ +#define VIEWS_CONTROLS_MENU_MENU_CONFIG_H_ + +#include "app/gfx/font.h" + +namespace views { + +// Layout type information for menu items. Use the instance() method to obtain +// the MenuConfig for the current platform. +struct MenuConfig { + MenuConfig() : item_top_margin(3), + item_bottom_margin(4), + item_no_icon_top_margin(1), + item_no_icon_bottom_margin(3), + item_left_margin(4), + label_to_arrow_padding(10), + arrow_to_edge_padding(5), + icon_to_label_padding(8), + gutter_to_label(5), + check_width(16), + check_height(16), + arrow_height(9), + arrow_width(9), + gutter_width(0), + separator_height(6), + render_gutter(false), + show_mnemonics(false), + scroll_arrow_height(3) { + } + + // Resets the single shared MenuConfig instance. The next time instance() is + // invoked a new MenuConfig is created and configured. + static void Reset(); + + // Returns the single shared MenuConfig instance, creating if necessary. + static const MenuConfig& instance(); + + // Font used by menus. + gfx::Font font; + + // Margins between the top of the item and the label. + int item_top_margin; + + // Margins between the bottom of the item and the label. + int item_bottom_margin; + + // Margins used if the menu doesn't have icons. + int item_no_icon_top_margin; + int item_no_icon_bottom_margin; + + // Margins between the left of the item and the icon. + int item_left_margin; + + // Padding between the label and submenu arrow. + int label_to_arrow_padding; + + // Padding between the arrow and the edge. + int arrow_to_edge_padding; + + // Padding between the icon and label. + int icon_to_label_padding; + + // Padding between the gutter and label. + int gutter_to_label; + + // Size of the check. + int check_width; + int check_height; + + // Size of the submenu arrow. + int arrow_height; + int arrow_width; + + // Width of the gutter. Only used if render_gutter is true. + int gutter_width; + + // Height of the separator. + int separator_height; + + // Whether or not the gutter should be rendered. The gutter is specific to + // Vista. + bool render_gutter; + + // Are mnemonics shown? + bool show_mnemonics; + + // Height of the scroll arrow. + int scroll_arrow_height; + + private: + // Creates and configures a new MenuConfig as appropriate for the current + // platform. + static MenuConfig* Create(); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_MENU_MENU_CONFIG_H_ diff --git a/views/controls/menu/menu_config_gtk.cc b/views/controls/menu/menu_config_gtk.cc new file mode 100644 index 0000000..1930824 --- /dev/null +++ b/views/controls/menu/menu_config_gtk.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_config.h" + +namespace views { + +// static +MenuConfig* MenuConfig::Create() { + // TODO: decide what we want this to look like. + return new MenuConfig(); +} + +} // namespace views diff --git a/views/controls/menu/menu_config_win.cc b/views/controls/menu/menu_config_win.cc new file mode 100644 index 0000000..233eae68 --- /dev/null +++ b/views/controls/menu/menu_config_win.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_config.h" + +#include <windows.h> +#include <uxtheme.h> +#include <Vssym32.h> + +#include "base/gfx/native_theme.h" +#include "base/logging.h" +#include "app/l10n_util_win.h" +#include "base/win_util.h" + +using gfx::NativeTheme; + +namespace views { + +// static +MenuConfig* MenuConfig::Create() { + MenuConfig* config = new MenuConfig(); + NONCLIENTMETRICS metrics; + win_util::GetNonClientMetrics(&metrics); + l10n_util::AdjustUIFont(&(metrics.lfMenuFont)); + HFONT font = CreateFontIndirect(&metrics.lfMenuFont); + DLOG_ASSERT(font); + config->font = gfx::Font::CreateFont(font); + + HDC dc = GetDC(NULL); + RECT bounds = { 0, 0, 200, 200 }; + SIZE check_size; + if (NativeTheme::instance()->GetThemePartSize( + NativeTheme::MENU, dc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, &bounds, + TS_TRUE, &check_size) == S_OK) { + config->check_width = check_size.cx; + config->check_height = check_size.cy; + } else { + config->check_width = GetSystemMetrics(SM_CXMENUCHECK); + config->check_height = GetSystemMetrics(SM_CYMENUCHECK); + } + + SIZE arrow_size; + if (NativeTheme::instance()->GetThemePartSize( + NativeTheme::MENU, dc, MENU_POPUPSUBMENU, MSM_NORMAL, &bounds, + TS_TRUE, &arrow_size) == S_OK) { + config->arrow_width = arrow_size.cx; + config->arrow_height = arrow_size.cy; + } else { + // Sadly I didn't see a specify metrics for this. + config->arrow_width = GetSystemMetrics(SM_CXMENUCHECK); + config->arrow_height = GetSystemMetrics(SM_CYMENUCHECK); + } + + SIZE gutter_size; + if (NativeTheme::instance()->GetThemePartSize( + NativeTheme::MENU, dc, MENU_POPUPGUTTER, MSM_NORMAL, &bounds, + TS_TRUE, &gutter_size) == S_OK) { + config->gutter_width = gutter_size.cx; + config->render_gutter = true; + } else { + config->gutter_width = 0; + config->render_gutter = false; + } + + SIZE separator_size; + if (NativeTheme::instance()->GetThemePartSize( + NativeTheme::MENU, dc, MENU_POPUPSEPARATOR, MSM_NORMAL, &bounds, + TS_TRUE, &separator_size) == S_OK) { + config->separator_height = separator_size.cy; + } else { + config->separator_height = GetSystemMetrics(SM_CYMENU) / 2; + } + + ReleaseDC(NULL, dc); + + BOOL show_cues; + config->show_mnemonics = + (SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &show_cues, 0) && + show_cues == TRUE); + return config; +} + +} // namespace views diff --git a/views/controls/menu/menu_controller.cc b/views/controls/menu/menu_controller.cc index f8efbbc..7c54aa4 100644 --- a/views/controls/menu/menu_controller.cc +++ b/views/controls/menu/menu_controller.cc @@ -7,7 +7,6 @@ #include "app/gfx/canvas.h" #include "app/l10n_util.h" #include "app/os_exchange_data.h" -#include "base/base_drag_source.h" #include "base/time.h" #include "views/controls/menu/menu_scroll_view_container.h" #include "views/controls/menu/submenu_view.h" @@ -17,6 +16,7 @@ #if defined(OS_WIN) #include "app/os_exchange_data_provider_win.h" +#include "base/base_drag_source.h" #endif using base::Time; @@ -144,7 +144,7 @@ static int instance_count = 0; static int nested_depth = 0; #endif -MenuItemView* MenuController::Run(HWND parent, +MenuItemView* MenuController::Run(gfx::NativeView parent, MenuItemView* root, const gfx::Rect& bounds, MenuItemView::AnchorPosition position, @@ -179,8 +179,10 @@ MenuItemView* MenuController::Run(HWND parent, pending_state_.anchor = position; owner_ = parent; + // TODO: push this into Screen. // Calculate the bounds of the monitor we'll show menus on. Do this once to // avoid repeated system queries for the info. +#if defined(OS_WIN) POINT initial_loc = { bounds.x(), bounds.y() }; HMONITOR monitor = MonitorFromPoint(initial_loc, MONITOR_DEFAULTTONEAREST); if (monitor) { @@ -190,6 +192,9 @@ MenuItemView* MenuController::Run(HWND parent, // Menus appear over the taskbar. pending_state_.monitor_bounds = gfx::Rect(mi.rcMonitor); } +#else + NOTIMPLEMENTED(); +#endif // Set the selection, which opens the initial menu. SetSelection(root, true, true); @@ -338,7 +343,13 @@ void MenuController::OnMousePressed(SubmenuView* source, // We're going to close and we own the mouse capture. We need to repost the // mouse down, otherwise the window the user clicked on won't get the // event. +#if defined(OS_WIN) RepostEvent(source, event); +#else + // Do we really need the repost logic for linux? I tend to think not but I + // need to verify that + NOTIMPLEMENTED(); +#endif // And close. Cancel(true); @@ -398,13 +409,17 @@ void MenuController::OnMouseDragged(SubmenuView* source, item->height(), press_loc.x(), press_loc.y(), &data); - scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); + StopScrolling(); +#if defined(OS_WIN) int drag_ops = item->GetDelegate()->GetDragOperations(item); + scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); DWORD effects; - StopScrolling(); DoDragDrop(OSExchangeDataProviderWin::GetIDataObject(data), drag_source, DragDropTypes::DragOperationToDropEffect(drag_ops), &effects); +#else + NOTIMPLEMENTED(); +#endif if (GetActiveInstance() == this) { if (showing_) { // We're still showing, close all menus. @@ -637,6 +652,7 @@ void MenuController::SetActiveInstance(MenuController* controller) { active_instance_ = controller; } +#if defined(OS_WIN) bool MenuController::Dispatch(const MSG& msg) { DCHECK(blocking_run_); @@ -759,6 +775,16 @@ bool MenuController::OnChar(const MSG& msg) { return !SelectByChar(static_cast<wchar_t>(msg.wParam)); } +#else +bool MenuController::Dispatch(GdkEvent* event) { + gtk_main_do_event(event); + if (exit_all_) + return false; + + NOTIMPLEMENTED(); + return !exit_all_; +} +#endif MenuController::MenuController(bool blocking) : blocking_run_(blocking), @@ -766,12 +792,12 @@ MenuController::MenuController(bool blocking) exit_all_(false), did_capture_(false), result_(NULL), + result_mouse_event_flags_(0), drop_target_(NULL), owner_(NULL), possible_drag_(false), valid_drop_coordinates_(false), - showing_submenu_(false), - result_mouse_event_flags_(0) { + showing_submenu_(false) { #ifdef DEBUG_MENU instance_count++; DLOG(INFO) << "created MC, count=" << instance_count; @@ -1222,14 +1248,6 @@ void MenuController::CloseSubmenu() { } } -bool MenuController::IsMenuWindow(MenuItemView* item, HWND window) { - if (!item) - return false; - return ((item->HasSubmenu() && item->GetSubmenu()->IsShowing() && - item->GetSubmenu()->GetWidget()->GetNativeView() == window) || - IsMenuWindow(item->GetParentMenuItem(), window)); -} - bool MenuController::SelectByChar(wchar_t character) { wchar_t char_array[1] = { character }; wchar_t key = l10n_util::ToLower(char_array)[0]; @@ -1290,6 +1308,7 @@ bool MenuController::SelectByChar(wchar_t character) { return false; } +#if defined(OS_WIN) void MenuController::RepostEvent(SubmenuView* source, const MouseEvent& event) { gfx::Point screen_loc(event.location()); @@ -1352,6 +1371,7 @@ void MenuController::RepostEvent(SubmenuView* source, } } } +#endif void MenuController::SetDropMenuItem( MenuItemView* new_target, diff --git a/views/controls/menu/menu_controller.h b/views/controls/menu/menu_controller.h index 16034e1..2512ede 100644 --- a/views/controls/menu/menu_controller.h +++ b/views/controls/menu/menu_controller.h @@ -31,12 +31,7 @@ class View; // MenuController is used internally by the various menu classes to manage // showing, selecting and drag/drop for menus. All relevant events are // forwarded to the MenuController from SubmenuView and MenuHost. -class MenuController -#if defined(OS_WIN) - : public MessageLoopForUI::Dispatcher { -#else - { -#endif +class MenuController : public MessageLoopForUI::Dispatcher { public: friend class MenuHostRootView; friend class MenuItemView; @@ -168,6 +163,8 @@ class MenuController // pressed, or a matching mnemonic was found) the message loop returns. bool OnKeyDown(const MSG& msg); bool OnChar(const MSG& msg); +#else + virtual bool Dispatch(GdkEvent* event); #endif // Creates a MenuController. If blocking is true, Run blocks the caller @@ -270,17 +267,15 @@ class MenuController // If possible, closes the submenu. void CloseSubmenu(); - // Returns true if window is the window used to show item, or any of - // items ancestors. - bool IsMenuWindow(MenuItemView* item, gfx::NativeWindow window); - // Selects by mnemonic, and if that doesn't work tries the first character of // the title. Returns true if a match was selected and the menu should exit. bool SelectByChar(wchar_t key); +#if defined(OS_WIN) // If there is a window at the location of the event, a new mouse event is // generated and posted to it. void RepostEvent(SubmenuView* source, const MouseEvent& event); +#endif // Sets the drop target to new_item. void SetDropMenuItem(MenuItemView* new_item, @@ -348,7 +343,7 @@ class MenuController MenuDelegate::DropPosition drop_position_; // Owner of child windows. - gfx::NativeWindow owner_; + gfx::NativeView owner_; // Indicates a possible drag operation. bool possible_drag_; diff --git a/views/controls/menu/menu_host_gtk.cc b/views/controls/menu/menu_host_gtk.cc new file mode 100644 index 0000000..b87aa23 --- /dev/null +++ b/views/controls/menu/menu_host_gtk.cc @@ -0,0 +1,99 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_host_gtk.h" + +#include <gdk/gdk.h> + +#include "views/controls/menu/menu_controller.h" +#include "views/controls/menu/menu_host_root_view.h" +#include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/submenu_view.h" + +namespace views { + +MenuHost::MenuHost(SubmenuView* submenu) + : WidgetGtk(WidgetGtk::TYPE_POPUP), + closed_(false), + submenu_(submenu) { + // TODO(sky): make sure this is needed. + GdkModifierType current_event_mod; + if (gtk_get_current_event_state(¤t_event_mod)) { + set_mouse_down( + (current_event_mod & GDK_BUTTON1_MASK) || + (current_event_mod & GDK_BUTTON2_MASK) || + (current_event_mod & GDK_BUTTON3_MASK) || + (current_event_mod & GDK_BUTTON4_MASK) || + (current_event_mod & GDK_BUTTON5_MASK)); + } +} + +void MenuHost::Init(gfx::NativeView parent, + const gfx::Rect& bounds, + View* contents_view, + bool do_capture) { + WidgetGtk::Init(parent, bounds); + SetContentsView(contents_view); + // TODO(sky): see if there is some way to show without changing focus. + Show(); + if (do_capture) { + DoGrab(); +#ifdef DEBUG_MENU + DLOG(INFO) << "Doing capture"; +#endif + } +} + +void MenuHost::Show() { + WidgetGtk::Show(); +} + +void MenuHost::Hide() { + if (closed_) { + // We're already closed, nothing to do. + // This is invoked twice if the first time just hid us, and the second + // time deleted Closed (deleted) us. + return; + } + // The menus are freed separately, and possibly before the window is closed, + // remove them so that View doesn't try to access deleted objects. + static_cast<MenuHostRootView*>(GetRootView())->suspend_events(); + GetRootView()->RemoveAllChildViews(false); + closed_ = true; + ReleaseGrab(); + WidgetGtk::Hide(); +} + +void MenuHost::HideWindow() { + // Make sure we release capture before hiding. + ReleaseGrab(); + WidgetGtk::Hide(); +} + +void MenuHost::ReleaseCapture() { + ReleaseGrab(); +} + +RootView* MenuHost::CreateRootView() { + return new MenuHostRootView(this, submenu_); +} + +void MenuHost::OnCancelMode() { + // TODO(sky): see if there is an equivalent to this. + if (!closed_) { +#ifdef DEBUG_MENU + DLOG(INFO) << "OnCanceMode, closing menu"; +#endif + submenu_->GetMenuItem()->GetMenuController()->Cancel(true); + } +} + +// Overriden to return false, we do NOT want to release capture on mouse +// release. +bool MenuHost::ReleaseCaptureOnMouseReleased() { + return false; +} + +} // namespace views diff --git a/views/controls/menu/menu_host_gtk.h b/views/controls/menu/menu_host_gtk.h new file mode 100644 index 0000000..545acb5 --- /dev/null +++ b/views/controls/menu/menu_host_gtk.h @@ -0,0 +1,51 @@ +// Copyright (c) 2009 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. + + +#ifndef VIEWS_CONTROLS_MENU_MENU_HOST_GTK_H_ +#define VIEWS_CONTROLS_MENU_MENU_HOST_GTK_H_ + +#include "views/widget/widget_gtk.h" + +namespace views { + +class SubmenuView; + +// MenuHost implementation for Gtk. +class MenuHost : public WidgetGtk { + public: + explicit MenuHost(SubmenuView* submenu); + + void Init(gfx::NativeView parent, + const gfx::Rect& bounds, + View* contents_view, + bool do_capture); + + void Show(); + virtual void Hide(); + virtual void HideWindow(); + void ReleaseCapture(); + + protected: + virtual RootView* CreateRootView(); + + virtual void OnCancelMode(); + + // Overriden to return false, we do NOT want to release capture on mouse + // release. + virtual bool ReleaseCaptureOnMouseReleased(); + + private: + // If true, we've been closed. + bool closed_; + + // The view we contain. + SubmenuView* submenu_; + + DISALLOW_COPY_AND_ASSIGN(MenuHost); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_MENU_MENU_HOST_GTK_H_ diff --git a/views/controls/menu/menu_host_root_view.cc b/views/controls/menu/menu_host_root_view.cc new file mode 100644 index 0000000..1812801 --- /dev/null +++ b/views/controls/menu/menu_host_root_view.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_host_root_view.h" + +#include "views/controls/menu/menu_controller.h" +#include "views/controls/menu/submenu_view.h" + +namespace views { + +MenuHostRootView::MenuHostRootView(Widget* widget, + SubmenuView* submenu) + : RootView(widget), + submenu_(submenu), + forward_drag_to_menu_controller_(true), + suspend_events_(false) { +#ifdef DEBUG_MENU + DLOG(INFO) << " new MenuHostRootView " << this; +#endif +} + +bool MenuHostRootView::OnMousePressed(const MouseEvent& event) { + if (suspend_events_) + return true; + + forward_drag_to_menu_controller_ = + ((event.x() < 0 || event.y() < 0 || event.x() >= width() || + event.y() >= height()) || + !RootView::OnMousePressed(event)); + if (forward_drag_to_menu_controller_) + GetMenuController()->OnMousePressed(submenu_, event); + return true; +} + +bool MenuHostRootView::OnMouseDragged(const MouseEvent& event) { + if (suspend_events_) + return true; + + if (forward_drag_to_menu_controller_) { +#ifdef DEBUG_MENU + DLOG(INFO) << " MenuHostRootView::OnMouseDragged source=" << submenu_; +#endif + GetMenuController()->OnMouseDragged(submenu_, event); + return true; + } + return RootView::OnMouseDragged(event); +} + +void MenuHostRootView::OnMouseReleased(const MouseEvent& event, + bool canceled) { + if (suspend_events_) + return; + + RootView::OnMouseReleased(event, canceled); + if (forward_drag_to_menu_controller_) { + forward_drag_to_menu_controller_ = false; + if (canceled) { + GetMenuController()->Cancel(true); + } else { + GetMenuController()->OnMouseReleased(submenu_, event); + } + } +} + +void MenuHostRootView::OnMouseMoved(const MouseEvent& event) { + if (suspend_events_) + return; + + RootView::OnMouseMoved(event); + GetMenuController()->OnMouseMoved(submenu_, event); +} + +void MenuHostRootView::ProcessOnMouseExited() { + if (suspend_events_) + return; + + RootView::ProcessOnMouseExited(); +} + +bool MenuHostRootView::ProcessMouseWheelEvent(const MouseWheelEvent& e) { + // RootView::ProcessMouseWheelEvent forwards to the focused view. We don't + // have a focused view, so we need to override this then forward to + // the menu. + return submenu_->OnMouseWheel(e); +} + +MenuController* MenuHostRootView::GetMenuController() { + return submenu_->GetMenuItem()->GetMenuController(); +} + +} // namespace views diff --git a/views/controls/menu/menu_host_root_view.h b/views/controls/menu/menu_host_root_view.h new file mode 100644 index 0000000..2ae774e --- /dev/null +++ b/views/controls/menu/menu_host_root_view.h @@ -0,0 +1,56 @@ +// Copyright (c) 2009 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. + +#ifndef VIEWS_CONTROLS_MENU_MENU_HOST_ROOT_VIEW_H_ +#define VIEWS_CONTROLS_MENU_MENU_HOST_ROOT_VIEW_H_ + +#include "views/widget/root_view.h" + +namespace views { + +class MenuController; +class SubmenuView; + +// MenuHostRootView is the RootView of the window showing the menu. +// SubmenuView's scroll view is added as a child of MenuHostRootView. +// MenuHostRootView forwards relevant events to the MenuController. +// +// As all the menu items are owned by the root menu item, care must be taken +// such that when MenuHostRootView is deleted it doesn't delete the menu items. +class MenuHostRootView : public RootView { + public: + MenuHostRootView(Widget* widget, SubmenuView* submenu); + + // When invoked subsequent events are NOT forwarded to the MenuController. + void suspend_events() { + suspend_events_ = true; + } + + virtual bool OnMousePressed(const MouseEvent& event); + virtual bool OnMouseDragged(const MouseEvent& event); + virtual void OnMouseReleased(const MouseEvent& event, bool canceled); + virtual void OnMouseMoved(const MouseEvent& event); + virtual void ProcessOnMouseExited(); + virtual bool ProcessMouseWheelEvent(const MouseWheelEvent& e); + + private: + // Returns the MenuController for this MenuHostRootView. + MenuController* GetMenuController(); + + // The SubmenuView we contain. + SubmenuView* submenu_; + + // Whether mouse dragged/released should be forwarded to the MenuController. + bool forward_drag_to_menu_controller_; + + // Whether events are suspended. If true, no events are forwarded to the + // MenuController. + bool suspend_events_; + + DISALLOW_COPY_AND_ASSIGN(MenuHostRootView); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_MENU_MENU_HOST_ROOT_VIEW_H_ diff --git a/views/controls/menu/menu_host_win.cc b/views/controls/menu/menu_host_win.cc new file mode 100644 index 0000000..d6fd655 --- /dev/null +++ b/views/controls/menu/menu_host_win.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_host_win.h" + +#include "base/win_util.h" +#include "views/controls/menu/menu_controller.h" +#include "views/controls/menu/menu_host_root_view.h" +#include "views/controls/menu/menu_item_view.h" +#include "views/controls/menu/submenu_view.h" + +namespace views { + +MenuHost::MenuHost(SubmenuView* submenu) + : closed_(false), + submenu_(submenu), + owns_capture_(false) { + set_window_style(WS_POPUP); + set_initial_class_style( + (win_util::GetWinVersion() < win_util::WINVERSION_XP) ? + 0 : CS_DROPSHADOW); + is_mouse_down_ = + ((GetKeyState(VK_LBUTTON) & 0x80) || + (GetKeyState(VK_RBUTTON) & 0x80) || + (GetKeyState(VK_MBUTTON) & 0x80) || + (GetKeyState(VK_XBUTTON1) & 0x80) || + (GetKeyState(VK_XBUTTON2) & 0x80)); + // Mouse clicks shouldn't give us focus. + set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE); +} + +void MenuHost::Init(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + bool do_capture) { + WidgetWin::Init(parent, bounds); + SetContentsView(contents_view); + Show(); + owns_capture_ = do_capture; + if (do_capture) { + SetCapture(); + has_capture_ = true; +#ifdef DEBUG_MENU + DLOG(INFO) << "Doing capture"; +#endif + } +} + +void MenuHost::Show() { + // We don't want to take focus away from the hosting window. + ShowWindow(SW_SHOWNA); +} + +void MenuHost::Hide() { + if (closed_) { + // We're already closed, nothing to do. + // This is invoked twice if the first time just hid us, and the second + // time deleted Closed (deleted) us. + return; + } + // The menus are freed separately, and possibly before the window is closed, + // remove them so that View doesn't try to access deleted objects. + static_cast<MenuHostRootView*>(GetRootView())->suspend_events(); + GetRootView()->RemoveAllChildViews(false); + closed_ = true; + ReleaseCapture(); + WidgetWin::Hide(); +} + +void MenuHost::HideWindow() { + // Make sure we release capture before hiding. + ReleaseCapture(); + WidgetWin::Hide(); +} + +void MenuHost::OnCaptureChanged(HWND hwnd) { + WidgetWin::OnCaptureChanged(hwnd); + owns_capture_ = false; +#ifdef DEBUG_MENU + DLOG(INFO) << "Capture changed"; +#endif +} + +void MenuHost::ReleaseCapture() { + if (owns_capture_) { +#ifdef DEBUG_MENU + DLOG(INFO) << "released capture"; +#endif + owns_capture_ = false; + ::ReleaseCapture(); + } +} + +RootView* MenuHost::CreateRootView() { + return new MenuHostRootView(this, submenu_); +} + +void MenuHost::OnCancelMode() { + if (!closed_) { +#ifdef DEBUG_MENU + DLOG(INFO) << "OnCanceMode, closing menu"; +#endif + submenu_->GetMenuItem()->GetMenuController()->Cancel(true); + } +} + +// Overriden to return false, we do NOT want to release capture on mouse +// release. +bool MenuHost::ReleaseCaptureOnMouseReleased() { + return false; +} + +} // namespace views diff --git a/views/controls/menu/menu_host_win.h b/views/controls/menu/menu_host_win.h new file mode 100644 index 0000000..49cc13b --- /dev/null +++ b/views/controls/menu/menu_host_win.h @@ -0,0 +1,55 @@ +// Copyright (c) 2009 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. + + +#ifndef VIEWS_CONTROLS_MENU_MENU_HOST_WIN_H_ +#define VIEWS_CONTROLS_MENU_MENU_HOST_WIN_H_ + +#include "views/widget/widget_win.h" + +namespace views { + +class SubmenuView; + +// MenuHost implementation for windows. +class MenuHost : public WidgetWin { + public: + explicit MenuHost(SubmenuView* submenu); + + void Init(HWND parent, + const gfx::Rect& bounds, + View* contents_view, + bool do_capture); + + void Show(); + virtual void Hide(); + virtual void HideWindow(); + virtual void OnCaptureChanged(HWND hwnd); + void ReleaseCapture(); + + protected: + virtual RootView* CreateRootView(); + + virtual void OnCancelMode(); + + // Overriden to return false, we do NOT want to release capture on mouse + // release. + virtual bool ReleaseCaptureOnMouseReleased(); + + private: + // If true, we've been closed. + bool closed_; + + // If true, we own the capture and need to release it. + bool owns_capture_; + + // The view we contain. + SubmenuView* submenu_; + + DISALLOW_COPY_AND_ASSIGN(MenuHost); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_MENU_MENU_HOST_WIN_H_ diff --git a/views/controls/menu/menu_item_view.cc b/views/controls/menu/menu_item_view.cc index 69c57a2..6308b52 100644 --- a/views/controls/menu/menu_item_view.cc +++ b/views/controls/menu/menu_item_view.cc @@ -4,215 +4,18 @@ #include "views/controls/menu/menu_item_view.h" -#if defined(OS_WIN) -#include <windows.h> -#include <uxtheme.h> -#include <Vssym32.h> -#endif - #include "app/gfx/canvas.h" #include "app/l10n_util.h" #include "grit/app_strings.h" +#include "views/controls/menu/menu_config.h" #include "views/controls/menu/menu_controller.h" +#include "views/controls/menu/menu_separator.h" #include "views/controls/menu/submenu_view.h" -#if defined(OS_WIN) -#include "app/l10n_util_win.h" -#include "base/gfx/native_theme.h" -#include "base/win_util.h" -#endif - -// Margins between the top of the item and the label. -static const int kItemTopMargin = 3; - -// Margins between the bottom of the item and the label. -static const int kItemBottomMargin = 4; - -// Margins used if the menu doesn't have icons. -static const int kItemNoIconTopMargin = 1; -static const int kItemNoIconBottomMargin = 3; - -// Margins between the left of the item and the icon. -static const int kItemLeftMargin = 4; - -// Padding between the label and submenu arrow. -static const int kLabelToArrowPadding = 10; - -// Padding between the arrow and the edge. -static const int kArrowToEdgePadding = 5; - -// Padding between the icon and label. -static const int kIconToLabelPadding = 8; - -// Padding between the gutter and label. -static const int kGutterToLabel = 5; - -// Size of the check. This comes from the OS. -static int check_width; -static int check_height; - -// Size of the submenu arrow. This comes from the OS. -static int arrow_width; -static int arrow_height; - -// Width of the gutter. Only used if render_gutter is true. -static int gutter_width; - -// Margins between the right of the item and the label. -static int item_right_margin; - -// X-coordinate of where the label starts. -static int label_start; - -// Height of the separator. -static int separator_height; - -// Whether or not the gutter should be rendered. The gutter is specific to -// Vista. -static bool render_gutter = false; - -// Preferred height of menu items. Reset every time a menu is run. -static int pref_menu_h; - -// Are mnemonics shown? This is updated before the menus are shown. -static bool show_mnemonics; - -using gfx::NativeTheme; - namespace views { namespace { -// Returns the font menus are to use. -gfx::Font GetMenuFont() { -#if defined(OS_WIN) - NONCLIENTMETRICS metrics; - win_util::GetNonClientMetrics(&metrics); - - l10n_util::AdjustUIFont(&(metrics.lfMenuFont)); - HFONT font = CreateFontIndirect(&metrics.lfMenuFont); - DLOG_ASSERT(font); - return gfx::Font::CreateFont(font); -#else - return gfx::Font(); -#endif -} - -// Calculates all sizes that we can from the OS. -// -// This is invoked prior to Running a menu. -void UpdateMenuPartSizes(bool has_icons) { -#if defined(OS_WIN) - HDC dc = GetDC(NULL); - RECT bounds = { 0, 0, 200, 200 }; - SIZE check_size; - if (NativeTheme::instance()->GetThemePartSize( - NativeTheme::MENU, dc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, &bounds, - TS_TRUE, &check_size) == S_OK) { - check_width = check_size.cx; - check_height = check_size.cy; - } else { - check_width = GetSystemMetrics(SM_CXMENUCHECK); - check_height = GetSystemMetrics(SM_CYMENUCHECK); - } - - SIZE arrow_size; - if (NativeTheme::instance()->GetThemePartSize( - NativeTheme::MENU, dc, MENU_POPUPSUBMENU, MSM_NORMAL, &bounds, - TS_TRUE, &arrow_size) == S_OK) { - arrow_width = arrow_size.cx; - arrow_height = arrow_size.cy; - } else { - // Sadly I didn't see a specify metrics for this. - arrow_width = GetSystemMetrics(SM_CXMENUCHECK); - arrow_height = GetSystemMetrics(SM_CYMENUCHECK); - } - - SIZE gutter_size; - if (NativeTheme::instance()->GetThemePartSize( - NativeTheme::MENU, dc, MENU_POPUPGUTTER, MSM_NORMAL, &bounds, - TS_TRUE, &gutter_size) == S_OK) { - gutter_width = gutter_size.cx; - render_gutter = true; - } else { - gutter_width = 0; - render_gutter = false; - } - - SIZE separator_size; - if (NativeTheme::instance()->GetThemePartSize( - NativeTheme::MENU, dc, MENU_POPUPSEPARATOR, MSM_NORMAL, &bounds, - TS_TRUE, &separator_size) == S_OK) { - separator_height = separator_size.cy; - } else { - separator_height = GetSystemMetrics(SM_CYMENU) / 2; - } - - item_right_margin = kLabelToArrowPadding + arrow_width + kArrowToEdgePadding; - - if (has_icons) { - label_start = kItemLeftMargin + check_width + kIconToLabelPadding; - } else { - // If there are no icons don't pad by the icon to label padding. This - // makes us look close to system menus. - label_start = kItemLeftMargin + check_width; - } - if (render_gutter) - label_start += gutter_width + kGutterToLabel; - - ReleaseDC(NULL, dc); - - MenuItemView menu_item(NULL); - menu_item.SetTitle(L"blah"); // Text doesn't matter here. - pref_menu_h = menu_item.GetPreferredSize().height(); -#endif -} - -// Convenience for scrolling the view such that the origin is visible. -static void ScrollToVisible(View* view) { - view->ScrollRectToVisible(0, 0, view->width(), view->height()); -} - -// MenuSeparator --------------------------------------------------------------- - -// Renders a separator. - -class MenuSeparator : public View { - public: - MenuSeparator() { - } - - void Paint(gfx::Canvas* canvas) { - // The gutter is rendered before the background. - int start_x = 0; - int start_y = height() / 3; - HDC dc = canvas->beginPlatformPaint(); - if (render_gutter) { - // If render_gutter is true, we're on Vista and need to render the - // gutter, then indent the separator from the gutter. - RECT gutter_bounds = { label_start - kGutterToLabel - gutter_width, 0, 0, - height() }; - gutter_bounds.right = gutter_bounds.left + gutter_width; - NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL, - &gutter_bounds); - start_x = gutter_bounds.left + gutter_width; - start_y = 0; - } - RECT separator_bounds = { start_x, start_y, width(), height() }; - NativeTheme::instance()->PaintMenuSeparator(dc, MENU_POPUPSEPARATOR, - MPI_NORMAL, &separator_bounds); - canvas->endPlatformPaint(); - } - - gfx::Size GetPreferredSize() { - return gfx::Size(10, // Just in case we're the only item in a menu. - separator_height); - } - - private: - DISALLOW_COPY_AND_ASSIGN(MenuSeparator); -}; - // EmptyMenuMenuItem --------------------------------------------------------- // EmptyMenuMenuItem is used when a menu has no menu items. EmptyMenuMenuItem @@ -247,6 +50,17 @@ const int MenuItemView::kEmptyMenuItemViewID = // static bool MenuItemView::allow_task_nesting_during_run_ = false; +// static +int MenuItemView::label_start_; + +// Margins between the right of the item and the label. +// static +int MenuItemView::item_right_margin_; + +// Preferred height of menu items. Reset every time a menu is run. +// static +int MenuItemView::pref_menu_height_; + MenuItemView::MenuItemView(MenuDelegate* delegate) { // NOTE: don't check the delegate for NULL, UpdateMenuPartSizes supplies a // NULL delegate. @@ -270,12 +84,7 @@ MenuItemView::~MenuItemView() { delete submenu_; } -// static -int MenuItemView::pref_menu_height() { - return pref_menu_h; -} - -void MenuItemView::RunMenuAt(HWND parent, +void MenuItemView::RunMenuAt(gfx::NativeView parent, const gfx::Rect& bounds, AnchorPosition anchor, bool has_mnemonics) { @@ -327,7 +136,7 @@ void MenuItemView::RunMenuAt(HWND parent, delegate_->ExecuteCommand(result->GetCommand(), mouse_event_flags); } -void MenuItemView::RunMenuForDropAt(HWND parent, +void MenuItemView::RunMenuForDropAt(gfx::NativeView parent, const gfx::Rect& bounds, AnchorPosition anchor) { PrepareForRun(false); @@ -381,9 +190,9 @@ void MenuItemView::Paint(gfx::Canvas* canvas) { } gfx::Size MenuItemView::GetPreferredSize() { - gfx::Font& font = GetRootMenuItem()->font_; + const gfx::Font& font = MenuConfig::instance().font; return gfx::Size( - font.GetStringWidth(title_) + label_start + item_right_margin, + font.GetStringWidth(title_) + label_start_ + item_right_margin_, font.height() + GetBottomMargin() + GetTopMargin()); } @@ -430,6 +239,32 @@ MenuItemView::MenuItemView(MenuItemView* parent, Init(parent, command, type, NULL); } +// Calculates all sizes that we can from the OS. +// +// This is invoked prior to Running a menu. +void MenuItemView::UpdateMenuPartSizes(bool has_icons) { + MenuConfig::Reset(); + const MenuConfig& config = MenuConfig::instance(); + + item_right_margin_ = config.label_to_arrow_padding + config.arrow_width + + config.arrow_to_edge_padding; + + if (has_icons) { + label_start_ = config.item_left_margin + config.check_width + + config.icon_to_label_padding; + } else { + // If there are no icons don't pad by the icon to label padding. This + // makes us look close to system menus. + label_start_ = config.item_left_margin + config.check_width; + } + if (config.render_gutter) + label_start_ += config.gutter_width + config.gutter_to_label; + + MenuItemView menu_item(NULL); + menu_item.SetTitle(L"blah"); // Text doesn't matter here. + pref_menu_height_ = menu_item.GetPreferredSize().height(); +} + void MenuItemView::Init(MenuItemView* parent, int command, MenuItemView::Type type, @@ -528,13 +363,6 @@ void MenuItemView::PrepareForRun(bool has_mnemonics) { // things may shift around. UpdateMenuPartSizes(has_icons_); } - - font_ = GetMenuFont(); - - BOOL show_cues; - show_mnemonics = - (SystemParametersInfo(SPI_GETKEYBOARDCUES, 0, &show_cues, 0) && - show_cues == TRUE); } int MenuItemView::GetDrawStringFlags() { @@ -545,7 +373,7 @@ int MenuItemView::GetDrawStringFlags() { flags |= gfx::Canvas::TEXT_ALIGN_LEFT; if (has_mnemonics_) { - if (show_mnemonics) + if (MenuConfig::instance().show_mnemonics) flags |= gfx::Canvas::SHOW_PREFIX; else flags |= gfx::Canvas::HIDE_PREFIX; @@ -587,122 +415,6 @@ void MenuItemView::AdjustBoundsForRTLUI(gfx::Rect* rect) const { rect->set_x(MirroredLeftPointForRect(*rect)); } -void MenuItemView::Paint(gfx::Canvas* canvas, bool for_drag) { - bool render_selection = - (!for_drag && IsSelected() && - parent_menu_item_->GetSubmenu()->GetShowSelection(this)); - int state = render_selection ? MPI_HOT : - (IsEnabled() ? MPI_NORMAL : MPI_DISABLED); - HDC dc = canvas->beginPlatformPaint(); - - // The gutter is rendered before the background. - if (render_gutter && !for_drag) { - gfx::Rect gutter_bounds(label_start - kGutterToLabel - gutter_width, 0, - gutter_width, height()); - AdjustBoundsForRTLUI(&gutter_bounds); - RECT gutter_rect = gutter_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL, - &gutter_rect); - } - - // Render the background. - if (!for_drag) { - gfx::Rect item_bounds(0, 0, width(), height()); - AdjustBoundsForRTLUI(&item_bounds); - RECT item_rect = item_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuItemBackground( - NativeTheme::MENU, dc, MENU_POPUPITEM, state, render_selection, - &item_rect); - } - - int icon_x = kItemLeftMargin; - int top_margin = GetTopMargin(); - int bottom_margin = GetBottomMargin(); - int icon_y = top_margin + (height() - kItemTopMargin - - bottom_margin - check_height) / 2; - int icon_height = check_height; - int icon_width = check_width; - - if (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand())) { - // Draw the check background. - gfx::Rect check_bg_bounds(0, 0, icon_x + icon_width, height()); - const int bg_state = IsEnabled() ? MCB_NORMAL : MCB_DISABLED; - AdjustBoundsForRTLUI(&check_bg_bounds); - RECT check_bg_rect = check_bg_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuCheckBackground( - NativeTheme::MENU, dc, MENU_POPUPCHECKBACKGROUND, bg_state, - &check_bg_rect); - - // And the check. - gfx::Rect check_bounds(icon_x, icon_y, icon_width, icon_height); - const int check_state = IsEnabled() ? MC_CHECKMARKNORMAL : - MC_CHECKMARKDISABLED; - AdjustBoundsForRTLUI(&check_bounds); - RECT check_rect = check_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuCheck( - NativeTheme::MENU, dc, MENU_POPUPCHECK, check_state, &check_rect, - render_selection); - } - - // Render the foreground. - // Menu color is specific to Vista, fallback to classic colors if can't - // get color. - int default_sys_color = render_selection ? COLOR_HIGHLIGHTTEXT : - (IsEnabled() ? COLOR_MENUTEXT : COLOR_GRAYTEXT); - SkColor fg_color = NativeTheme::instance()->GetThemeColorWithDefault( - NativeTheme::MENU, MENU_POPUPITEM, state, TMT_TEXTCOLOR, - default_sys_color); - int width = this->width() - item_right_margin - label_start; - gfx::Font& font = GetRootMenuItem()->font_; - gfx::Rect text_bounds(label_start, top_margin, width, font.height()); - text_bounds.set_x(MirroredLeftPointForRect(text_bounds)); - if (for_drag) { - // With different themes, it's difficult to tell what the correct foreground - // and background colors are for the text to draw the correct halo. Instead, - // just draw black on white, which will look good in most cases. - canvas->DrawStringWithHalo(GetTitle(), font, 0x00000000, 0xFFFFFFFF, - text_bounds.x(), text_bounds.y(), - text_bounds.width(), text_bounds.height(), - GetRootMenuItem()->GetDrawStringFlags()); - } else { - canvas->DrawStringInt(GetTitle(), font, fg_color, - text_bounds.x(), text_bounds.y(), text_bounds.width(), - text_bounds.height(), - GetRootMenuItem()->GetDrawStringFlags()); - } - - if (icon_.width() > 0) { - gfx::Rect icon_bounds(kItemLeftMargin, - top_margin + (height() - top_margin - - bottom_margin - icon_.height()) / 2, - icon_.width(), - icon_.height()); - icon_bounds.set_x(MirroredLeftPointForRect(icon_bounds)); - canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y()); - } - - if (HasSubmenu()) { - int state_id = IsEnabled() ? MSM_NORMAL : MSM_DISABLED; - gfx::Rect arrow_bounds(this->width() - item_right_margin + kLabelToArrowPadding, - 0, arrow_width, height()); - AdjustBoundsForRTLUI(&arrow_bounds); - - // If our sub menus open from right to left (which is the case when the - // locale is RTL) then we should make sure the menu arrow points to the - // right direction. - NativeTheme::MenuArrowDirection arrow_direction; - if (UILayoutIsRightToLeft()) - arrow_direction = NativeTheme::LEFT_POINTING_ARROW; - else - arrow_direction = NativeTheme::RIGHT_POINTING_ARROW; - - RECT arrow_rect = arrow_bounds.ToRECT(); - NativeTheme::instance()->PaintMenuArrow( - NativeTheme::MENU, dc, MENU_POPUPSUBMENU, state_id, &arrow_rect, - arrow_direction, render_selection); - } - canvas->endPlatformPaint(); -} void MenuItemView::DestroyAllMenuHosts() { if (!HasSubmenu()) @@ -717,13 +429,16 @@ void MenuItemView::DestroyAllMenuHosts() { int MenuItemView::GetTopMargin() { MenuItemView* root = GetRootMenuItem(); - return root && root->has_icons_ ? kItemTopMargin : kItemNoIconTopMargin; + return root && root->has_icons_ + ? MenuConfig::instance().item_top_margin : + MenuConfig::instance().item_no_icon_top_margin; } int MenuItemView::GetBottomMargin() { MenuItemView* root = GetRootMenuItem(); - return root && root->has_icons_ ? - kItemBottomMargin : kItemNoIconBottomMargin; + return root && root->has_icons_ + ? MenuConfig::instance().item_bottom_margin : + MenuConfig::instance().item_no_icon_bottom_margin; } } // namespace views diff --git a/views/controls/menu/menu_item_view.h b/views/controls/menu/menu_item_view.h index b15bfa3..86b1bbf 100644 --- a/views/controls/menu/menu_item_view.h +++ b/views/controls/menu/menu_item_view.h @@ -5,7 +5,6 @@ #ifndef VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_ #define VIEWS_CONTROLS_MENU_MENU_ITEM_VIEW_H_ -#include "app/gfx/font.h" #include "third_party/skia/include/core/SkBitmap.h" #include "views/view.h" @@ -73,7 +72,10 @@ class MenuItemView : public View { // Returns the preferred height of menu items. This is only valid when the // menu is about to be shown. - static int pref_menu_height(); + static int pref_menu_height() { return pref_menu_height_; } + + // X-coordinate of where the label starts. + static int label_start() { return label_start_; } // Run methods. See description above class for details. Both Run methods take // a rectangle, which is used to position the menu. |has_mnemonics| indicates @@ -159,9 +161,6 @@ class MenuItemView : public View { // Returns the parent menu item. MenuItemView* GetParentMenuItem() const { return parent_menu_item_; } - // Sets the font. - void SetFont(const gfx::Font& font) { font_ = font; } - // Sets the title void SetTitle(const std::wstring& title) { title_ = title; @@ -222,6 +221,11 @@ class MenuItemView : public View { MenuItemView(MenuItemView* parent, int command, Type type); private: + // Calculates all sizes that we can from the OS. + // + // This is invoked prior to Running a menu. + static void UpdateMenuPartSizes(bool has_icons); + // Called by the two constructors to initialize this menu item. void Init(MenuItemView* parent, int command, @@ -300,9 +304,6 @@ class MenuItemView : public View { // Submenu, created via CreateSubmenu. SubmenuView* submenu_; - // Font. - gfx::Font font_; - // Title. std::wstring title_; @@ -314,6 +315,15 @@ class MenuItemView : public View { bool has_icons_; + // X-coordinate of where the label starts. + static int label_start_; + + // Margins between the right of the item and the label. + static int item_right_margin_; + + // Preferred height of menu items. Reset every time a menu is run. + static int pref_menu_height_; + DISALLOW_COPY_AND_ASSIGN(MenuItemView); }; diff --git a/views/controls/menu/menu_item_view_gtk.cc b/views/controls/menu/menu_item_view_gtk.cc new file mode 100644 index 0000000..6f5b8e4 --- /dev/null +++ b/views/controls/menu/menu_item_view_gtk.cc @@ -0,0 +1,16 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_item_view.h" + +#include "base/logging.h" +#include "views/controls/menu/menu_config.h" + +namespace views { + +void MenuItemView::Paint(gfx::Canvas* canvas, bool for_drag) { + NOTIMPLEMENTED(); +} + +} // namespace views diff --git a/views/controls/menu/menu_item_view_win.cc b/views/controls/menu/menu_item_view_win.cc new file mode 100644 index 0000000..8f3812c --- /dev/null +++ b/views/controls/menu/menu_item_view_win.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2009 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 "views/controls/menu/menu_item_view.h" + +#include <windows.h> +#include <uxtheme.h> +#include <Vssym32.h> + +#include "app/gfx/canvas.h" +#include "base/gfx/native_theme.h" +#include "app/l10n_util.h" +#include "grit/app_strings.h" +#include "views/controls/menu/menu_config.h" +#include "views/controls/menu/submenu_view.h" + +using gfx::NativeTheme; + +namespace views { + +void MenuItemView::Paint(gfx::Canvas* canvas, bool for_drag) { + const MenuConfig& config = MenuConfig::instance(); + bool render_selection = + (!for_drag && IsSelected() && + parent_menu_item_->GetSubmenu()->GetShowSelection(this)); + int state = render_selection ? MPI_HOT : + (IsEnabled() ? MPI_NORMAL : MPI_DISABLED); + HDC dc = canvas->beginPlatformPaint(); + + // The gutter is rendered before the background. + if (config.render_gutter && !for_drag) { + gfx::Rect gutter_bounds(label_start_ - config.gutter_to_label - + config.gutter_width, 0, config.gutter_width, + height()); + AdjustBoundsForRTLUI(&gutter_bounds); + RECT gutter_rect = gutter_bounds.ToRECT(); + NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL, + &gutter_rect); + } + + // Render the background. + if (!for_drag) { + gfx::Rect item_bounds(0, 0, width(), height()); + AdjustBoundsForRTLUI(&item_bounds); + RECT item_rect = item_bounds.ToRECT(); + NativeTheme::instance()->PaintMenuItemBackground( + NativeTheme::MENU, dc, MENU_POPUPITEM, state, render_selection, + &item_rect); + } + + int icon_x = config.item_left_margin; + int top_margin = GetTopMargin(); + int bottom_margin = GetBottomMargin(); + int icon_y = top_margin + (height() - config.item_top_margin - + bottom_margin - config.check_height) / 2; + int icon_height = config.check_height; + int icon_width = config.check_width; + + if (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand())) { + // Draw the check background. + gfx::Rect check_bg_bounds(0, 0, icon_x + icon_width, height()); + const int bg_state = IsEnabled() ? MCB_NORMAL : MCB_DISABLED; + AdjustBoundsForRTLUI(&check_bg_bounds); + RECT check_bg_rect = check_bg_bounds.ToRECT(); + NativeTheme::instance()->PaintMenuCheckBackground( + NativeTheme::MENU, dc, MENU_POPUPCHECKBACKGROUND, bg_state, + &check_bg_rect); + + // And the check. + gfx::Rect check_bounds(icon_x, icon_y, icon_width, icon_height); + const int check_state = IsEnabled() ? MC_CHECKMARKNORMAL : + MC_CHECKMARKDISABLED; + AdjustBoundsForRTLUI(&check_bounds); + RECT check_rect = check_bounds.ToRECT(); + NativeTheme::instance()->PaintMenuCheck( + NativeTheme::MENU, dc, MENU_POPUPCHECK, check_state, &check_rect, + render_selection); + } + + // Render the foreground. + // Menu color is specific to Vista, fallback to classic colors if can't + // get color. + int default_sys_color = render_selection ? COLOR_HIGHLIGHTTEXT : + (IsEnabled() ? COLOR_MENUTEXT : COLOR_GRAYTEXT); + SkColor fg_color = NativeTheme::instance()->GetThemeColorWithDefault( + NativeTheme::MENU, MENU_POPUPITEM, state, TMT_TEXTCOLOR, + default_sys_color); + int width = this->width() - item_right_margin_ - label_start_; + const gfx::Font& font = MenuConfig::instance().font; + gfx::Rect text_bounds(label_start_, top_margin, width, font.height()); + text_bounds.set_x(MirroredLeftPointForRect(text_bounds)); + if (for_drag) { + // With different themes, it's difficult to tell what the correct + // foreground and background colors are for the text to draw the correct + // halo. Instead, just draw black on white, which will look good in most + // cases. + canvas->DrawStringWithHalo(GetTitle(), font, 0x00000000, 0xFFFFFFFF, + text_bounds.x(), text_bounds.y(), + text_bounds.width(), text_bounds.height(), + GetRootMenuItem()->GetDrawStringFlags()); + } else { + canvas->DrawStringInt(GetTitle(), font, fg_color, + text_bounds.x(), text_bounds.y(), text_bounds.width(), + text_bounds.height(), + GetRootMenuItem()->GetDrawStringFlags()); + } + + if (icon_.width() > 0) { + gfx::Rect icon_bounds(config.item_left_margin, + top_margin + (height() - top_margin - + bottom_margin - icon_.height()) / 2, + icon_.width(), + icon_.height()); + icon_bounds.set_x(MirroredLeftPointForRect(icon_bounds)); + canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y()); + } + + if (HasSubmenu()) { + int state_id = IsEnabled() ? MSM_NORMAL : MSM_DISABLED; + gfx::Rect arrow_bounds(this->width() - item_right_margin_ + + config.label_to_arrow_padding, 0, + config.arrow_width, height()); + AdjustBoundsForRTLUI(&arrow_bounds); + + // If our sub menus open from right to left (which is the case when the + // locale is RTL) then we should make sure the menu arrow points to the + // right direction. + NativeTheme::MenuArrowDirection arrow_direction; + if (UILayoutIsRightToLeft()) + arrow_direction = NativeTheme::LEFT_POINTING_ARROW; + else + arrow_direction = NativeTheme::RIGHT_POINTING_ARROW; + + RECT arrow_rect = arrow_bounds.ToRECT(); + NativeTheme::instance()->PaintMenuArrow( + NativeTheme::MENU, dc, MENU_POPUPSUBMENU, state_id, &arrow_rect, + arrow_direction, render_selection); + } + canvas->endPlatformPaint(); +} + +} // namespace views diff --git a/views/controls/menu/menu_scroll_view_container.cc b/views/controls/menu/menu_scroll_view_container.cc index bd233d9..45a4777 100644 --- a/views/controls/menu/menu_scroll_view_container.cc +++ b/views/controls/menu/menu_scroll_view_container.cc @@ -12,17 +12,23 @@ #include "app/gfx/canvas.h" #include "app/gfx/color_utils.h" -#include "base/gfx/native_theme.h" #include "views/border.h" +#include "views/controls/menu/menu_config.h" #include "views/controls/menu/menu_controller.h" #include "views/controls/menu/menu_item_view.h" #include "views/controls/menu/submenu_view.h" +#if defined(OS_WIN) +#include "base/gfx/native_theme.h" +#endif + +#if defined(OS_WIN) using gfx::NativeTheme; +#endif // Height of the scroll arrow. // This goes up to 4 with large fonts, but this is close enough for now. -static const int kScrollArrowHeight = 3; +static const int scroll_arrow_height = 3; namespace views { @@ -44,7 +50,8 @@ class MenuScrollButton : public View { } virtual gfx::Size GetPreferredSize() { - return gfx::Size(kScrollArrowHeight * 2 - 1, pref_height_); + return gfx::Size(MenuConfig::instance().scroll_arrow_height * 2 - 1, + pref_height_); } virtual bool CanDrop(const OSExchangeData& data) { @@ -72,6 +79,8 @@ class MenuScrollButton : public View { } virtual void Paint(gfx::Canvas* canvas) { +#if defined(OS_WIN) + const MenuConfig& config = MenuConfig::instance(); HDC dc = canvas->beginPlatformPaint(); // The background. @@ -82,17 +91,20 @@ class MenuScrollButton : public View { // Then the arrow. int x = width() / 2; - int y = (height() - kScrollArrowHeight) / 2; + int y = (height() - config.scroll_arrow_height) / 2; int delta_y = 1; if (!is_up_) { delta_y = -1; - y += kScrollArrowHeight; + y += config.scroll_arrow_height; } SkColor arrow_color = color_utils::GetSysSkColor(COLOR_MENUTEXT); - for (int i = 0; i < kScrollArrowHeight; ++i, --x, y += delta_y) + for (int i = 0; i < config.scroll_arrow_height; ++i, --x, y += delta_y) canvas->FillRectInt(arrow_color, x, y, (i * 2) + 1, 1); canvas->endPlatformPaint(); +#else + NOTIMPLEMENTED(); +#endif } private: @@ -166,11 +178,15 @@ MenuScrollViewContainer::MenuScrollViewContainer(SubmenuView* content_view) { } void MenuScrollViewContainer::Paint(gfx::Canvas* canvas) { +#if defined(OS_WIN) HDC dc = canvas->beginPlatformPaint(); RECT bounds = {0, 0, width(), height()}; NativeTheme::instance()->PaintMenuBackground( NativeTheme::MENU, dc, MENU_POPUPBACKGROUND, 0, &bounds); canvas->endPlatformPaint(); +#else + NOTIMPLEMENTED(); +#endif } void MenuScrollViewContainer::Layout() { diff --git a/views/controls/menu/menu_separator.h b/views/controls/menu/menu_separator.h new file mode 100644 index 0000000..af37f8a --- /dev/null +++ b/views/controls/menu/menu_separator.h @@ -0,0 +1,27 @@ +// Copyright (c) 2009 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. + + +#ifndef VIEWS_CONTROLS_MENU_MENU_SEPARATOR_H_ +#define VIEWS_CONTROLS_MENU_MENU_SEPARATOR_H_ + +#include "views/view.h" + +namespace views { + +class MenuSeparator : public View { + public: + MenuSeparator() {} + + // View overrides. + void Paint(gfx::Canvas* canvas); + gfx::Size GetPreferredSize(); + + private: + DISALLOW_COPY_AND_ASSIGN(MenuSeparator); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_MENU_MENU_SEPARATOR_H_ diff --git a/views/controls/menu/menu_separator_gtk.cc b/views/controls/menu/menu_separator_gtk.cc new file mode 100644 index 0000000..fdb5e8f --- /dev/null +++ b/views/controls/menu/menu_separator_gtk.cc @@ -0,0 +1,21 @@ +// 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 "views/controls/menu/menu_separator.h" + +#include "base/logging.h" +#include "views/controls/menu/menu_config.h" + +namespace views { + +void MenuSeparator::Paint(gfx::Canvas* canvas) { + NOTIMPLEMENTED(); +} + +gfx::Size MenuSeparator::GetPreferredSize() { + return gfx::Size(10, // Just in case we're the only item in a menu. + MenuConfig::instance().separator_height); +} + +} // namespace views diff --git a/views/controls/menu/menu_separator_win.cc b/views/controls/menu/menu_separator_win.cc new file mode 100644 index 0000000..b2ffe5e2 --- /dev/null +++ b/views/controls/menu/menu_separator_win.cc @@ -0,0 +1,47 @@ +// 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 "views/controls/menu/menu_separator.h" + +#include <windows.h> +#include <uxtheme.h> +#include <Vssym32.h> + +#include "app/gfx/canvas.h" +#include "base/gfx/native_theme.h" +#include "views/controls/menu/menu_config.h" +#include "views/controls/menu/menu_item_view.h" + +namespace views { + +void MenuSeparator::Paint(gfx::Canvas* canvas) { + const MenuConfig& config = MenuConfig::instance(); + // The gutter is rendered before the background. + int start_x = 0; + int start_y = height() / 3; + HDC dc = canvas->beginPlatformPaint(); + if (config.render_gutter) { + // If render_gutter is true, we're on Vista and need to render the + // gutter, then indent the separator from the gutter. + RECT gutter_bounds = { MenuItemView::label_start() - + config.gutter_to_label - config.gutter_width, 0, 0, + height() }; + gutter_bounds.right = gutter_bounds.left + config.gutter_width; + gfx::NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, + MPI_NORMAL, &gutter_bounds); + start_x = gutter_bounds.left + config.gutter_width; + start_y = 0; + } + RECT separator_bounds = { start_x, start_y, width(), height() }; + gfx::NativeTheme::instance()->PaintMenuSeparator( + dc, MENU_POPUPSEPARATOR, MPI_NORMAL, &separator_bounds); + canvas->endPlatformPaint(); +} + +gfx::Size MenuSeparator::GetPreferredSize() { + return gfx::Size(10, // Just in case we're the only item in a menu. + MenuConfig::instance().separator_height); +} + +} // namespace views diff --git a/views/controls/menu/submenu_view.cc b/views/controls/menu/submenu_view.cc index ddf47c9..46580c0 100644 --- a/views/controls/menu/submenu_view.cc +++ b/views/controls/menu/submenu_view.cc @@ -10,8 +10,9 @@ #include "views/widget/root_view.h" #if defined(OS_WIN) -#include "base/win_util.h" -#include "views/widget/widget_win.h" +#include "views/controls/menu/menu_host_win.h" +#elif defined(OS_LINUX) +#include "views/controls/menu/menu_host_gtk.h" #endif // Height of the drop indicator. This should be an even number. @@ -22,236 +23,6 @@ static const SkColor kDropIndicatorColor = SK_ColorBLACK; namespace views { -// MenuHostRootView ---------------------------------------------------------- - -// MenuHostRootView is the RootView of the window showing the menu. -// SubmenuView's scroll view is added as a child of MenuHostRootView. -// MenuHostRootView forwards relevant events to the MenuController. -// -// As all the menu items are owned by the root menu item, care must be taken -// such that when MenuHostRootView is deleted it doesn't delete the menu items. -class MenuHostRootView : public RootView { - public: - explicit MenuHostRootView(Widget* widget, - SubmenuView* submenu) - : RootView(widget), - submenu_(submenu), - forward_drag_to_menu_controller_(true), - suspend_events_(false) { -#ifdef DEBUG_MENU - DLOG(INFO) << " new MenuHostRootView " << this; -#endif - } - - virtual bool OnMousePressed(const MouseEvent& event) { - if (suspend_events_) - return true; - - forward_drag_to_menu_controller_ = - ((event.x() < 0 || event.y() < 0 || event.x() >= width() || - event.y() >= height()) || - !RootView::OnMousePressed(event)); - if (forward_drag_to_menu_controller_) - GetMenuController()->OnMousePressed(submenu_, event); - return true; - } - - virtual bool OnMouseDragged(const MouseEvent& event) { - if (suspend_events_) - return true; - - if (forward_drag_to_menu_controller_) { -#ifdef DEBUG_MENU - DLOG(INFO) << " MenuHostRootView::OnMouseDragged source=" << submenu_; -#endif - GetMenuController()->OnMouseDragged(submenu_, event); - return true; - } - return RootView::OnMouseDragged(event); - } - - virtual void OnMouseReleased(const MouseEvent& event, bool canceled) { - if (suspend_events_) - return; - - RootView::OnMouseReleased(event, canceled); - if (forward_drag_to_menu_controller_) { - forward_drag_to_menu_controller_ = false; - if (canceled) { - GetMenuController()->Cancel(true); - } else { - GetMenuController()->OnMouseReleased(submenu_, event); - } - } - } - - virtual void OnMouseMoved(const MouseEvent& event) { - if (suspend_events_) - return; - - RootView::OnMouseMoved(event); - GetMenuController()->OnMouseMoved(submenu_, event); - } - - virtual void ProcessOnMouseExited() { - if (suspend_events_) - return; - - RootView::ProcessOnMouseExited(); - } - - virtual bool ProcessMouseWheelEvent(const MouseWheelEvent& e) { - // RootView::ProcessMouseWheelEvent forwards to the focused view. We don't - // have a focused view, so we need to override this then forward to - // the menu. - return submenu_->OnMouseWheel(e); - } - - void SuspendEvents() { - suspend_events_ = true; - } - - private: - MenuController* GetMenuController() { - return submenu_->GetMenuItem()->GetMenuController(); - } - - // The SubmenuView we contain. - SubmenuView* submenu_; - - // Whether mouse dragged/released should be forwarded to the MenuController. - bool forward_drag_to_menu_controller_; - - // Whether events are suspended. If true, no events are forwarded to the - // MenuController. - bool suspend_events_; - - DISALLOW_COPY_AND_ASSIGN(MenuHostRootView); -}; - -// MenuHost ------------------------------------------------------------------ - -// MenuHost is the window responsible for showing a single menu. -// -// Similar to MenuHostRootView, care must be taken such that when MenuHost is -// deleted, it doesn't delete the menu items. MenuHost is closed via a -// DelayedClosed, which avoids timing issues with deleting the window while -// capture or events are directed at it. - -class MenuHost : public WidgetWin { - public: - explicit MenuHost(SubmenuView* submenu) - : closed_(false), - submenu_(submenu), - owns_capture_(false) { - set_window_style(WS_POPUP); - set_initial_class_style( - (win_util::GetWinVersion() < win_util::WINVERSION_XP) ? - 0 : CS_DROPSHADOW); - is_mouse_down_ = - ((GetKeyState(VK_LBUTTON) & 0x80) || - (GetKeyState(VK_RBUTTON) & 0x80) || - (GetKeyState(VK_MBUTTON) & 0x80) || - (GetKeyState(VK_XBUTTON1) & 0x80) || - (GetKeyState(VK_XBUTTON2) & 0x80)); - // Mouse clicks shouldn't give us focus. - set_window_ex_style(WS_EX_TOPMOST | WS_EX_NOACTIVATE); - } - - void Init(HWND parent, - const gfx::Rect& bounds, - View* contents_view, - bool do_capture) { - WidgetWin::Init(parent, bounds); - SetContentsView(contents_view); - // We don't want to take focus away from the hosting window. - ShowWindow(SW_SHOWNA); - owns_capture_ = do_capture; - if (do_capture) { - SetCapture(); - has_capture_ = true; -#ifdef DEBUG_MENU - DLOG(INFO) << "Doing capture"; -#endif - } - } - - virtual void Hide() { - if (closed_) { - // We're already closed, nothing to do. - // This is invoked twice if the first time just hid us, and the second - // time deleted Closed (deleted) us. - return; - } - // The menus are freed separately, and possibly before the window is closed, - // remove them so that View doesn't try to access deleted objects. - static_cast<MenuHostRootView*>(GetRootView())->SuspendEvents(); - GetRootView()->RemoveAllChildViews(false); - closed_ = true; - ReleaseCapture(); - WidgetWin::Hide(); - } - - virtual void HideWindow() { - // Make sure we release capture before hiding. - ReleaseCapture(); - WidgetWin::Hide(); - } - - virtual void OnCaptureChanged(HWND hwnd) { - WidgetWin::OnCaptureChanged(hwnd); - owns_capture_ = false; -#ifdef DEBUG_MENU - DLOG(INFO) << "Capture changed"; -#endif - } - - void ReleaseCapture() { - if (owns_capture_) { -#ifdef DEBUG_MENU - DLOG(INFO) << "released capture"; -#endif - owns_capture_ = false; - ::ReleaseCapture(); - } - } - - protected: - // Overriden to create MenuHostRootView. - virtual RootView* CreateRootView() { - return new MenuHostRootView(this, submenu_); - } - - virtual void OnCancelMode() { - if (!closed_) { -#ifdef DEBUG_MENU - DLOG(INFO) << "OnCanceMode, closing menu"; -#endif - submenu_->GetMenuItem()->GetMenuController()->Cancel(true); - } - } - - // Overriden to return false, we do NOT want to release capture on mouse - // release. - virtual bool ReleaseCaptureOnMouseReleased() { - return false; - } - - private: - // If true, we've been closed. - bool closed_; - - // If true, we own the capture and need to release it. - bool owns_capture_; - - // The view we contain. - SubmenuView* submenu_; - - DISALLOW_COPY_AND_ASSIGN(MenuHost); -}; - -// SubmenuView --------------------------------------------------------------- - // static const int SubmenuView::kSubmenuBorderSize = 3; @@ -394,7 +165,11 @@ bool SubmenuView::OnMouseWheel(const MouseWheelEvent& e) { // If the first item isn't entirely visible, make it visible, otherwise make // the next/previous one entirely visible. +#if defined(OS_WIN) int delta = abs(e.GetOffset() / WHEEL_DELTA); +#elif defined(OS_LINUX) + int delta = abs(e.GetOffset()); +#endif bool scroll_up = (e.GetOffset() > 0); while (delta-- > 0) { int scroll_amount = 0; @@ -430,11 +205,11 @@ bool SubmenuView::IsShowing() { return host_ && host_->IsVisible(); } -void SubmenuView::ShowAt(HWND parent, +void SubmenuView::ShowAt(gfx::NativeView parent, const gfx::Rect& bounds, bool do_capture) { if (host_) { - host_->ShowWindow(SW_SHOWNA); + host_->Show(); return; } |