| // Copyright 2013 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/browser/ui/views/toolbar/toolbar_action_view.h" |
| |
| #include <string> |
| |
| #include "base/auto_reset.h" |
| #include "base/bind.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/themes/theme_properties.h" |
| #include "chrome/browser/ui/layout_constants.h" |
| #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h" |
| #include "chrome/browser/ui/view_ids.h" |
| #include "chrome/browser/ui/views/chrome_layout_provider.h" |
| #include "chrome/browser/ui/views/extensions/extension_context_menu_controller.h" |
| #include "chrome/browser/ui/views/toolbar/toolbar_button.h" |
| #include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/notification_source.h" |
| #include "ui/accessibility/ax_node_data.h" |
| #include "ui/base/metadata/metadata_impl_macros.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/theme_provider.h" |
| #include "ui/compositor/paint_recorder.h" |
| #include "ui/events/event.h" |
| #include "ui/gfx/color_palette.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/image/image_skia_operations.h" |
| #include "ui/gfx/image/image_skia_source.h" |
| #include "ui/native_theme/native_theme.h" |
| #include "ui/views/animation/ink_drop_impl.h" |
| #include "ui/views/controls/button/button.h" |
| #include "ui/views/controls/button/label_button_border.h" |
| #include "ui/views/controls/menu/menu_controller.h" |
| #include "ui/views/controls/menu/menu_model_adapter.h" |
| #include "ui/views/controls/menu/menu_runner.h" |
| #include "ui/views/mouse_constants.h" |
| |
| using views::LabelButtonBorder; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ToolbarActionView::Delegate |
| |
| bool ToolbarActionView::Delegate::CanShowIconInToolbar() const { |
| return true; |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ToolbarActionView |
| |
| ToolbarActionView::ToolbarActionView( |
| ToolbarActionViewController* view_controller, |
| ToolbarActionView::Delegate* delegate) |
| : MenuButton(base::BindRepeating(&ToolbarActionView::ButtonPressed, |
| base::Unretained(this))), |
| view_controller_(view_controller), |
| delegate_(delegate) { |
| ConfigureInkDropForToolbar(this); |
| SetHideInkDropWhenShowingContextMenu(false); |
| SetShowInkDropWhenHotTracked(true); |
| SetID(VIEW_ID_BROWSER_ACTION); |
| view_controller_->SetDelegate(this); |
| SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| set_drag_controller(delegate_); |
| // Normally, the notify action is determined by whether a view is draggable |
| // (and is set to press for non-draggable and release for draggable views). |
| // However, ToolbarActionViews may be draggable or non-draggable depending on |
| // whether they are shown in an incognito window. We want to preserve the same |
| // trigger event to keep the UX (more) consistent. Set all ToolbarActionViews |
| // to trigger on mouse release. |
| button_controller()->set_notify_action( |
| views::ButtonController::NotifyAction::kOnRelease); |
| |
| context_menu_controller_ = |
| std::make_unique<ExtensionContextMenuController>(view_controller); |
| set_context_menu_controller(context_menu_controller_.get()); |
| |
| UpdateState(); |
| } |
| |
| ToolbarActionView::~ToolbarActionView() { |
| view_controller_->SetDelegate(nullptr); |
| } |
| |
| gfx::Rect ToolbarActionView::GetAnchorBoundsInScreen() const { |
| gfx::Rect bounds = GetBoundsInScreen(); |
| bounds.Inset(GetToolbarInkDropInsets(this)); |
| return bounds; |
| } |
| |
| std::unique_ptr<LabelButtonBorder> ToolbarActionView::CreateDefaultBorder() |
| const { |
| std::unique_ptr<LabelButtonBorder> border = |
| LabelButton::CreateDefaultBorder(); |
| // Toolbar action buttons have no insets because the badges are drawn right at |
| // the edge of the view's area. Other padding (such as centering the icon) is |
| // handled directly by the Image. |
| border->set_insets(gfx::Insets()); |
| return border; |
| } |
| |
| bool ToolbarActionView::IsTriggerableEvent(const ui::Event& event) { |
| // By default MenuButton checks the time since the menu closure, but that |
| // prevents left clicks from showing the extension popup when the context menu |
| // is showing. The time check is to prevent reshowing on the same click that |
| // closed the menu, when this class handles via |suppress_next_release_|, so |
| // it's not necessary. Bypass it by calling IsTriggerableEventType() instead |
| // of IsTriggerableEvent(). |
| return button_controller()->IsTriggerableEventType(event); |
| } |
| |
| bool ToolbarActionView::OnKeyPressed(const ui::KeyEvent& event) { |
| if (event.key_code() == ui::VKEY_DOWN) { |
| context_menu_controller()->ShowContextMenuForView(this, gfx::Point(), |
| ui::MENU_SOURCE_KEYBOARD); |
| return true; |
| } |
| return MenuButton::OnKeyPressed(event); |
| } |
| |
| content::WebContents* ToolbarActionView::GetCurrentWebContents() const { |
| return delegate_->GetCurrentWebContents(); |
| } |
| |
| void ToolbarActionView::UpdateState() { |
| content::WebContents* web_contents = GetCurrentWebContents(); |
| SetAccessibleName(view_controller_->GetAccessibleName(web_contents)); |
| if (!sessions::SessionTabHelper::IdForTab(web_contents).is_valid()) |
| return; |
| |
| gfx::ImageSkia icon( |
| view_controller_->GetIcon(web_contents, GetPreferredSize()) |
| .AsImageSkia()); |
| |
| if (!icon.isNull()) |
| SetImageModel(views::Button::STATE_NORMAL, |
| ui::ImageModel::FromImageSkia(icon)); |
| |
| SetTooltipText(view_controller_->GetTooltip(web_contents)); |
| |
| Layout(); // We need to layout since we may have added an icon as a result. |
| SchedulePaint(); |
| } |
| |
| gfx::ImageSkia ToolbarActionView::GetIconForTest() { |
| return GetImage(views::Button::STATE_NORMAL); |
| } |
| |
| int ToolbarActionView::GetDragOperationsForTest(const gfx::Point& point) { |
| return views::View::GetDragOperations(point); |
| } |
| |
| gfx::Size ToolbarActionView::CalculatePreferredSize() const { |
| return delegate_->GetToolbarActionSize(); |
| } |
| |
| bool ToolbarActionView::OnMousePressed(const ui::MouseEvent& event) { |
| if (event.IsOnlyLeftMouseButton()) { |
| if (view_controller()->IsShowingPopup()) { |
| // Left-clicking the button should always hide the popup. In most cases, |
| // this would have happened automatically anyway due to the popup losing |
| // activation, but if the popup is currently being inspected, the |
| // activation loss will not automatically close it, so force-hide here. |
| view_controller_->HidePopup(); |
| |
| // Since we just hid the popup, don't allow the mouse release for this |
| // click to re-show it. |
| suppress_next_release_ = true; |
| } else { |
| // This event is likely to trigger the MenuButton action. |
| // TODO(bruthig): The ACTION_PENDING triggering logic should be in |
| // MenuButton::OnPressed() however there is a bug with the pressed state |
| // logic in MenuButton. See http://crbug.com/567252. |
| ink_drop()->AnimateToState(views::InkDropState::ACTION_PENDING, &event); |
| } |
| } |
| |
| return MenuButton::OnMousePressed(event); |
| } |
| |
| void ToolbarActionView::OnMouseReleased(const ui::MouseEvent& event) { |
| // MenuButton::OnMouseReleased() may synchronously delete |this|, so writing |
| // member variables after that point is unsafe. Instead, copy the old value |
| // of |suppress_next_release_| so it can be updated now. |
| const bool suppress_next_release = suppress_next_release_; |
| suppress_next_release_ = false; |
| if (!suppress_next_release) |
| MenuButton::OnMouseReleased(event); |
| } |
| |
| void ToolbarActionView::OnGestureEvent(ui::GestureEvent* event) { |
| // While the dropdown menu is showing, the button should not handle gestures. |
| if (context_menu_controller_->IsMenuRunning()) |
| event->StopPropagation(); |
| else |
| MenuButton::OnGestureEvent(event); |
| } |
| |
| void ToolbarActionView::OnDragDone() { |
| views::MenuButton::OnDragDone(); |
| |
| // The mouse release that ends a drag does not generate a mouse release event, |
| // so OnMouseReleased() doesn't get called. Thus if the click that started |
| // the drag set |suppress_next_release_|, it must be reset here or the next |
| // mouse release after the drag will be erroneously discarded. |
| suppress_next_release_ = false; |
| } |
| |
| void ToolbarActionView::AddedToWidget() { |
| MenuButton::AddedToWidget(); |
| |
| // This cannot happen until there's a focus controller, which lives on the |
| // widget. |
| view_controller_->RegisterCommand(); |
| } |
| |
| void ToolbarActionView::RemovedFromWidget() { |
| // This must happen before the focus controller, which lives on the widget, |
| // becomes unreachable. |
| view_controller_->UnregisterCommand(); |
| |
| MenuButton::RemovedFromWidget(); |
| } |
| |
| views::View* ToolbarActionView::GetAsView() { |
| return this; |
| } |
| |
| views::FocusManager* ToolbarActionView::GetFocusManagerForAccelerator() { |
| return GetFocusManager(); |
| } |
| |
| views::Button* ToolbarActionView::GetReferenceButtonForPopup() { |
| // Browser actions in the overflow menu can still show popups, so we may need |
| // a reference view other than this button's parent. If so, use the overflow |
| // view which is a BrowserAppMenuButton. |
| return GetVisible() ? this : delegate_->GetOverflowReferenceView(); |
| } |
| |
| void ToolbarActionView::ShowContextMenuAsFallback() { |
| context_menu_controller()->ShowContextMenuForView( |
| this, GetKeyboardContextMenuLocation(), ui::MENU_SOURCE_NONE); |
| } |
| |
| bool ToolbarActionView::CanShowIconInToolbar() const { |
| return delegate_->CanShowIconInToolbar(); |
| } |
| |
| void ToolbarActionView::OnPopupShown(bool by_user) { |
| // If this was through direct user action, we press the menu button. |
| if (by_user) { |
| // GetReferenceButtonForPopup returns either |this| or |
| // delegate_->GetOverflowReferenceView() which is a BrowserAppMenuButton. |
| // This cast is safe because both will have a MenuButtonController. |
| views::MenuButtonController* reference_view_controller = |
| static_cast<views::MenuButtonController*>( |
| GetReferenceButtonForPopup()->button_controller()); |
| pressed_lock_ = reference_view_controller->TakeLock(); |
| } |
| } |
| |
| void ToolbarActionView::OnPopupClosed() { |
| pressed_lock_.reset(); // Unpress the menu button if it was pressed. |
| } |
| |
| void ToolbarActionView::ButtonPressed() { |
| if (view_controller_->IsEnabled(GetCurrentWebContents())) { |
| base::RecordAction(base::UserMetricsAction( |
| "Extensions.Toolbar.ExtensionActivatedFromToolbar")); |
| view_controller_->ExecuteAction( |
| true, ToolbarActionViewController::InvocationSource::kToolbarButton); |
| } else { |
| // If the action isn't enabled, show the context menu as a fallback. |
| context_menu_controller()->ShowContextMenuForView(this, GetMenuPosition(), |
| ui::MENU_SOURCE_NONE); |
| } |
| } |
| |
| BEGIN_METADATA(ToolbarActionView, views::MenuButton) |
| END_METADATA |