| // 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 "chrome/views/chrome_menu.h" |
| |
| #include <windows.h> |
| #include <uxtheme.h> |
| #include <Vssym32.h> |
| |
| #include "base/base_drag_source.h" |
| #include "base/gfx/native_theme.h" |
| #include "base/gfx/skia_utils.h" |
| #include "base/message_loop.h" |
| #include "base/task.h" |
| #include "base/timer.h" |
| #include "base/win_util.h" |
| #include "chrome/browser/drag_utils.h" |
| #include "chrome/common/gfx/chrome_canvas.h" |
| #include "chrome/common/gfx/color_utils.h" |
| #include "chrome/common/l10n_util.h" |
| #include "chrome/common/os_exchange_data.h" |
| #include "chrome/views/border.h" |
| #include "chrome/views/hwnd_view_container.h" |
| #include "chrome/views/root_view.h" |
| #include "generated_resources.h" |
| |
| // 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 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 LONG 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; |
| |
| using gfx::NativeTheme; |
| |
| namespace ChromeViews { |
| |
| // Calculates all sizes that we can from the OS. |
| // |
| // This is invoked prior to Running a menu. |
| void UpdateMenuPartSizes() { |
| 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; |
| |
| label_start = kItemLeftMargin + check_width + kIconToLabelPadding; |
| if (render_gutter) |
| label_start += gutter_width + kGutterToLabel; |
| |
| ReleaseDC(NULL, dc); |
| |
| CSize pref; |
| MenuItemView menu_item(NULL); |
| menu_item.SetTitle(L"blah"); // Text doesn't matter here. |
| menu_item.GetPreferredSize(&pref); |
| pref_menu_height = pref.cy; |
| } |
| |
| namespace { |
| |
| // Convenience for scrolling the view such that the origin is visible. |
| static void ScrollToVisible(View* view) { |
| view->ScrollRectToVisible(0, 0, view->width(), view->height()); |
| } |
| |
| // 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 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_EVIL_CONSTRUCTORS(MenuScrollTask); |
| }; |
| |
| // 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 void GetPreferredSize(CSize* out) { |
| out->cx = kScrollArrowHeight * 2 - 1; |
| out->cy = 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(ChromeCanvas* 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_EVIL_CONSTRUCTORS(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(); |
| CSize pref; |
| child->GetPreferredSize(&pref); |
| // Constrain y to make sure we don't show past the bottom of the view. |
| y = std::max(0, std::min(static_cast<int>(pref.cy) - this->height(), y)); |
| child->SetY(-y); |
| } |
| |
| // Returns the contents, which is the SubmenuView. |
| View* GetContents() { |
| return GetChildViewAt(0); |
| } |
| |
| private: |
| DISALLOW_EVIL_CONSTRUCTORS(MenuScrollView); |
| }; |
| |
| // 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_); |
| |
| SetBorder( |
| Border::CreateEmptyBorder(kSubmenuBorderSize, kSubmenuBorderSize, |
| kSubmenuBorderSize, kSubmenuBorderSize)); |
| } |
| |
| virtual void Paint(ChromeCanvas* 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; |
| } |
| |
| CSize pref; |
| scroll_up_button_->GetPreferredSize(&pref); |
| scroll_up_button_->SetBounds(x, y, width, pref.cy); |
| content_height -= pref.cy; |
| |
| const int scroll_view_y = y + pref.cy; |
| |
| scroll_down_button_->GetPreferredSize(&pref); |
| scroll_down_button_->SetBounds(x, height() - pref.cy - insets.top(), |
| width, pref.cy); |
| content_height -= pref.cy; |
| |
| scroll_view_->SetBounds(x, scroll_view_y, width, content_height); |
| scroll_view_->Layout(); |
| } |
| |
| virtual void DidChangeBounds(const CRect& previous, const CRect& current) { |
| CSize content_pref; |
| scroll_view_->GetContents()->GetPreferredSize(&content_pref); |
| scroll_up_button_->SetVisible(content_pref.cy > height()); |
| scroll_down_button_->SetVisible(content_pref.cy > height()); |
| } |
| |
| virtual void GetPreferredSize(CSize* out) { |
| scroll_view_->GetContents()->GetPreferredSize(out); |
| gfx::Insets insets = GetInsets(); |
| out->cx += insets.width(); |
| out->cy += insets.height(); |
| } |
| |
| private: |
| // The scroll buttons. |
| View* scroll_up_button_; |
| View* scroll_down_button_; |
| |
| // The scroll view. |
| MenuScrollView* scroll_view_; |
| |
| DISALLOW_EVIL_CONSTRUCTORS(MenuScrollViewContainer); |
| }; |
| |
| // MenuSeparator --------------------------------------------------------------- |
| |
| // Renders a separator. |
| |
| class MenuSeparator : public View { |
| public: |
| MenuSeparator() { |
| } |
| |
| void Paint(ChromeCanvas* 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(); |
| } |
| |
| void GetPreferredSize(CSize* out) { |
| out->cx = 10; // Just in case we're the only item in a menu. |
| out->cy = separator_height; |
| } |
| |
| private: |
| DISALLOW_EVIL_CONSTRUCTORS(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(ViewContainer* container, |
| SubmenuView* submenu) |
| : RootView(container), |
| 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_) { |
| if (canceled) { |
| GetMenuController()->Cancel(true); |
| } else { |
| GetMenuController()->OnMouseReleased(submenu_, event); |
| } |
| forward_drag_to_menu_controller_ = false; |
| } |
| } |
| |
| 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_EVIL_CONSTRUCTORS(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 HWNDViewContainer { |
| public: |
| 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) { |
| HWNDViewContainer::Init(parent, bounds, true); |
| 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(); |
| HWNDViewContainer::Hide(); |
| } |
| |
| virtual void OnCaptureChanged(HWND hwnd) { |
| HWNDViewContainer::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_EVIL_CONSTRUCTORS(MenuHost); |
| }; |
| |
| // 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; |
| |
| EmptyMenuMenuItem(MenuItemView* parent) : MenuItemView(parent, 0, NORMAL) { |
| SetTitle(l10n_util::GetString(IDS_MENU_EMPTY_SUBMENU)); |
| // Set this so that we're not identified as a normal menu item. |
| SetID(kEmptyMenuItemViewID); |
| SetEnabled(false); |
| } |
| |
| private: |
| DISALLOW_EVIL_CONSTRUCTORS(EmptyMenuMenuItem); |
| }; |
| |
| // static |
| const int EmptyMenuMenuItem::kEmptyMenuItemViewID = |
| MenuItemView::kMenuItemViewID + 1; |
| |
| } // namespace |
| |
| // SubmenuView --------------------------------------------------------------- |
| |
| SubmenuView::SubmenuView(MenuItemView* parent) |
| : parent_menu_item_(parent), |
| host_(NULL), |
| drop_item_(NULL), |
| scroll_view_container_(NULL) { |
| DCHECK(parent); |
| // We'll delete ourselves, otherwise the ScrollView would delete us on close. |
| SetParentOwned(false); |
| } |
| |
| SubmenuView::~SubmenuView() { |
| DCHECK(!host_); |
| 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; |
| CSize pref; |
| GetPreferredSize(&pref); |
| SetBounds(x(), y(), parent->width(), pref.cy); |
| |
| 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); |
| CSize child_pref_size; |
| child->GetPreferredSize(&child_pref_size); |
| child->SetBounds(x, y, menu_item_width, child_pref_size.cy); |
| y += child_pref_size.cy; |
| } |
| } |
| |
| void SubmenuView::GetPreferredSize(CSize* out) { |
| if (GetChildViewCount() == 0) { |
| out->SetSize(0, 0); |
| return; |
| } |
| |
| int max_width = 0; |
| int height = 0; |
| for (int i = 0; i < GetChildViewCount(); ++i) { |
| View* child = GetChildViewAt(i); |
| CSize child_pref_size; |
| child->GetPreferredSize(&child_pref_size); |
| max_width = std::max(max_width, static_cast<int>(child_pref_size.cx)); |
| height += child_pref_size.cy; |
| } |
| gfx::Insets insets = GetInsets(); |
| out->SetSize(max_width + insets.width(), height + insets.height()); |
| } |
| |
| void SubmenuView::DidChangeBounds(const CRect& previous, const CRect& current) { |
| SchedulePaint(); |
| } |
| |
| void SubmenuView::PaintChildren(ChromeCanvas* canvas) { |
| View::PaintChildren(canvas); |
| |
| if (drop_item_ && drop_position_ != MenuDelegate::DROP_ON) |
| PaintDropIndicator(canvas, drop_item_, drop_position_); |
| } |
| |
| 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; |
| } |
| |
| void SubmenuView::ShowAt(HWND parent, |
| const gfx::Rect& bounds, |
| bool do_capture) { |
| DCHECK(!host_); |
| 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(bool destroy_host) { |
| if (host_) { |
| if (destroy_host) { |
| host_->Close(); |
| host_ = NULL; |
| } else { |
| host_->Hide(); |
| } |
| } |
| } |
| |
| void SubmenuView::ReleaseCapture() { |
| host_->ReleaseCapture(); |
| } |
| |
| 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(ChromeCanvas* 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); |
| CRect item_bounds_c; |
| item->GetBounds(&item_bounds_c); |
| gfx::Rect item_bounds(item_bounds_c); |
| 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 |
| const int MenuItemView::kDropBetweenPixels = 5; |
| |
| // static |
| bool MenuItemView::allow_task_nesting_during_run_ = false; |
| |
| MenuItemView::MenuItemView(MenuDelegate* delegate) { |
| DCHECK(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 show_mnemonics) { |
| PrepareForRun(show_mnemonics); |
| |
| int mouse_event_flags; |
| |
| MenuController* controller = MenuController::GetActiveInstance(); |
| 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(ChromeCanvas* canvas) { |
| Paint(canvas, false); |
| } |
| |
| void MenuItemView::GetPreferredSize(CSize* out) { |
| out->cx = font_.GetStringWidth(title_) + label_start + item_right_margin; |
| out->cy = font_.height() + kItemBottomMargin + kItemTopMargin; |
| } |
| |
| 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() { |
| 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); |
| |
| 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 show_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; |
| |
| show_mnemonics_ = show_mnemonics; |
| |
| AddEmptyMenus(); |
| |
| UpdateMenuPartSizes(); |
| } |
| |
| int MenuItemView::GetDrawStringFlags() { |
| int flags = 0; |
| if (UILayoutIsRightToLeft()) |
| flags |= ChromeCanvas::TEXT_ALIGN_RIGHT; |
| else |
| flags |= ChromeCanvas::TEXT_ALIGN_LEFT; |
| |
| return flags | |
| (show_mnemonics_ ? ChromeCanvas::SHOW_PREFIX : ChromeCanvas::HIDE_PREFIX); |
| } |
| |
| 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(RECT* rect) const { |
| gfx::Rect mirrored_rect(*rect); |
| mirrored_rect.set_x(MirroredLeftPointForRect(mirrored_rect)); |
| *rect = mirrored_rect.ToRECT(); |
| } |
| |
| void MenuItemView::Paint(ChromeCanvas* 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) { |
| RECT gutter_bounds = { label_start - kGutterToLabel - gutter_width, 0, 0, |
| height() }; |
| gutter_bounds.right = gutter_bounds.left + gutter_width; |
| AdjustBoundsForRTLUI(&gutter_bounds); |
| NativeTheme::instance()->PaintMenuGutter(dc, MENU_POPUPGUTTER, MPI_NORMAL, &gutter_bounds); |
| } |
| |
| // Render the background. |
| if (!for_drag) { |
| RECT item_bounds = { 0, 0, width(), height() }; |
| AdjustBoundsForRTLUI(&item_bounds); |
| NativeTheme::instance()->PaintMenuItemBackground( |
| NativeTheme::MENU, dc, MENU_POPUPITEM, state, render_selection, |
| &item_bounds); |
| } |
| |
| int icon_x = kItemLeftMargin; |
| int icon_y = kItemTopMargin + (height() - kItemTopMargin - |
| kItemBottomMargin - check_height) / 2; |
| int icon_height = check_height; |
| int icon_width = check_width; |
| |
| if (type_ == CHECKBOX && GetDelegate()->IsItemChecked(GetCommand())) { |
| // Draw the check background. |
| RECT check_bg_bounds = { 0, 0, icon_x + icon_width, height() }; |
| const int bg_state = IsEnabled() ? MCB_NORMAL : MCB_DISABLED; |
| AdjustBoundsForRTLUI(&check_bg_bounds); |
| NativeTheme::instance()->PaintMenuCheckBackground( |
| NativeTheme::MENU, dc, MENU_POPUPCHECKBACKGROUND, bg_state, |
| &check_bg_bounds); |
| |
| // And the check. |
| RECT check_bounds = { icon_x, icon_y, icon_x + icon_width, |
| icon_y + icon_height }; |
| const int check_state = IsEnabled() ? MC_CHECKMARKNORMAL : |
| MC_CHECKMARKDISABLED; |
| AdjustBoundsForRTLUI(&check_bounds); |
| NativeTheme::instance()->PaintMenuCheck( |
| NativeTheme::MENU, dc, MENU_POPUPCHECK, check_state, &check_bounds, |
| 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::Rect text_bounds(label_start, kItemTopMargin, width, font_.height()); |
| text_bounds.set_x(MirroredLeftPointForRect(text_bounds)); |
| 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, |
| kItemTopMargin + (height() - kItemTopMargin - |
| kItemBottomMargin - 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; |
| RECT arrow_bounds = { this->width() - item_right_margin + kLabelToArrowPadding, |
| 0, 0, height() }; |
| arrow_bounds.right = arrow_bounds.left + arrow_width; |
| 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; |
| |
| NativeTheme::instance()->PaintMenuArrow( |
| NativeTheme::MENU, dc, MENU_POPUPSUBMENU, state_id, &arrow_bounds, |
| arrow_direction, render_selection); |
| } |
| canvas->endPlatformPaint(); |
| } |
| |
| void MenuItemView::DestroyAllMenuHosts() { |
| if (!HasSubmenu()) |
| return; |
| |
| submenu_->Close(true); |
| for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count; |
| ++i) { |
| submenu_->GetMenuItemAt(i)->DestroyAllMenuHosts(); |
| } |
| } |
| |
| // 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); |
| } |
| |
| did_capture_ = false; |
| |
| any_menu_contains_mouse_ = false; |
| |
| // 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; |
| } |
| |
| 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; |
| } |
| |
| any_menu_contains_mouse_ = true; |
| |
| bool open_submenu = false; |
| if (!part.menu) { |
| part.menu = source->GetMenuItem(); |
| 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 (ChromeViews::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. |
| CPoint press_loc(press_x_, press_y_); |
| View::ConvertPointToScreen(source->GetScrollViewContainer(), &press_loc); |
| View::ConvertPointToView(NULL, item, &press_loc); |
| CPoint drag_loc(event.x(), event.y()); |
| View::ConvertPointToScreen(source->GetScrollViewContainer(), &drag_loc); |
| View::ConvertPointToView(NULL, item, &drag_loc); |
| in_drag_ = true; |
| ChromeCanvas canvas(item->width(), item->height(), false); |
| item->Paint(&canvas, true); |
| |
| scoped_refptr<OSExchangeData> data(new OSExchangeData); |
| item->GetDelegate()->WriteDragData(item, data.get()); |
| 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(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); |
| } |
| any_menu_contains_mouse_ = (part.type == MenuPart::MENU_ITEM); |
| } |
| |
| 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()); |
| any_menu_contains_mouse_ = (part.type == MenuPart::MENU_ITEM); |
| 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); |
| CPoint loc(event.x(), event.y()); |
| 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; |
| |
| any_menu_contains_mouse_ = (part.type == MenuPart::MENU_ITEM); |
| if (part.type == MenuPart::MENU_ITEM && part.menu) { |
| SetSelection(part.menu, true, false); |
| } else if (!part.is_scroll() && any_menu_contains_mouse_ && |
| 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); |
| any_menu_contains_mouse_ = 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(); |
| |
| CPoint screen_loc(event.x(), event.y()); |
| 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) { |
| CPoint menu_item_loc(event.x(), event.y()); |
| 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 > MenuItemView::kDropBetweenPixels && |
| menu_item_loc.y < (menu_item_height - |
| MenuItemView::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_) |
| return false; |
| |
| // NOTE: we don't get WM_ACTIVATE or anything else interesting in here. |
| switch (msg.message) { |
| // 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_CANCELMODE: |
| case WM_SYSKEYDOWN: |
| case WM_SYSKEYUP: |
| // 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; |
| |
| 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), |
| in_drag_(false), |
| any_menu_contains_mouse_(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(CPoint(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(CPoint(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(CPoint(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; |
| |
| CPoint 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 CPoint& screen_loc, |
| MenuPart* part) { |
| // Is the mouse over the scroll buttons? |
| CPoint 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)) { |
| CPoint 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; |
| 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 CPoint& screen_loc) { |
| CPoint 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()) { |
| // See description of in_drag_ as to why close is conditionalized like |
| // this. |
| current_path[i]->GetSubmenu()->Close(!in_drag_); |
| } |
| } |
| |
| // 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()) { |
| // The submenu is showing, but it shouldn't be, close it. |
| // See description of in_drag_ as to why close is conditionalized like |
| // this. |
| state_.item->GetSubmenu()->Close(!in_drag_); |
| } |
| |
| 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; |
| // See description of in_drag_ as to why close is conditionalized like this. |
| item->GetSubmenu()->Close(!in_drag_); |
| } |
| |
| 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); |
| |
| CSize pref; |
| submenu->GetScrollViewContainer()->GetPreferredSize(&pref); |
| |
| // Don't let the menu go to wide. This is some where between what IE and FF |
| // do. |
| pref.cx = std::min(pref.cx, kMaxMenuWidth); |
| if (!state_.monitor_bounds.IsEmpty()) |
| pref.cx = std::min(pref.cx, |
| static_cast<LONG>(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.cx; |
| if (!state_.monitor_bounds.IsEmpty() && |
| y + pref.cy > 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.cy = std::min(pref.cy, |
| static_cast<LONG>(state_.monitor_bounds.bottom() - y)); |
| } else { |
| pref.cy = std::min(pref.cy, static_cast<LONG>( |
| state_.initial_bounds.y() - state_.monitor_bounds.y())); |
| y = state_.initial_bounds.y() - pref.cy; |
| } |
| } |
| } else { |
| // Not the first menu; position it relative to the bounds of the menu |
| // item. |
| CPoint item_loc(0, 0); |
| 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.cx > state_.monitor_bounds.right()) { |
| if (layout_is_rtl) |
| *is_leading = true; |
| else |
| *is_leading = false; |
| x = item_loc.x - pref.cx + kSubmenuHorizontalInset; |
| } |
| } else { |
| x = item_loc.x - pref.cx + 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.cy = std::min(pref.cy, |
| static_cast<LONG>(state_.monitor_bounds.height())); |
| if (y + pref.cy > state_.monitor_bounds.bottom()) |
| y = state_.monitor_bounds.bottom() - pref.cy; |
| if (y < state_.monitor_bounds.y()) |
| y = state_.monitor_bounds.y(); |
| } |
| } |
| |
| if (state_.monitor_bounds.width() != 0) { |
| if (x + pref.cx > state_.monitor_bounds.right()) |
| x = state_.monitor_bounds.right() - pref.cx; |
| if (x < state_.monitor_bounds.x()) |
| x = state_.monitor_bounds.x(); |
| } |
| return gfx::Rect(x, y, pref.cx, pref.cy); |
| } |
| |
| // 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()->GetViewContainer()->GetHWND() == 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) { |
| CPoint screen_loc(event.x(), event.y()); |
| View::ConvertPointToScreen(source->GetScrollViewContainer(), &screen_loc); |
| HWND window = WindowFromPoint(screen_loc); |
| 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()->GetHWND() && |
| GetWindowThreadProcessId(submenu->host()->GetHWND(), 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, WM_NCLBUTTONDOWN, nc_hit_result, |
| MAKELPARAM(window_x, window_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 ChromeViews |
| |