| // Copyright (c) 2012 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/try_chrome_dialog_win/try_chrome_dialog.h" |
| |
| #include <shellapi.h> |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/run_loop.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string16.h" |
| #include "base/time/time.h" |
| #include "cc/paint/paint_flags.h" |
| #include "chrome/app/vector_icons/vector_icons.h" |
| #include "chrome/browser/ui/views/chrome_typography.h" |
| #include "chrome/browser/ui/views/try_chrome_dialog_win/arrow_border.h" |
| #include "chrome/browser/ui/views/try_chrome_dialog_win/button_layout.h" |
| #include "chrome/browser/win/taskbar_icon_finder.h" |
| #include "chrome/grit/chromium_strings.h" |
| #include "chrome/grit/generated_resources.h" |
| #include "chrome/grit/theme_resources.h" |
| #include "chrome/installer/util/experiment.h" |
| #include "chrome/installer/util/experiment_storage.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/compositor/dip_util.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/display/win/screen_win.h" |
| #include "ui/events/event.h" |
| #include "ui/events/event_constants.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/geometry/point_conversions.h" |
| #include "ui/gfx/geometry/point_f.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/rect_f.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/geometry/size_conversions.h" |
| #include "ui/gfx/geometry/size_f.h" |
| #include "ui/gfx/geometry/vector2d.h" |
| #include "ui/gfx/geometry/vector2d_conversions.h" |
| #include "ui/gfx/geometry/vector2d_f.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/paint_vector_icon.h" |
| #include "ui/gfx/text_constants.h" |
| #include "ui/gfx/win/singleton_hwnd_observer.h" |
| #include "ui/resources/grit/ui_resources.h" |
| #include "ui/views/background.h" |
| #include "ui/views/border.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/controls/button/label_button.h" |
| #include "ui/views/controls/image_view.h" |
| #include "ui/views/controls/label.h" |
| #include "ui/views/layout/grid_layout.h" |
| #include "ui/views/view.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_removals_observer.h" |
| |
| namespace { |
| |
| constexpr unsigned int kToastWidth = 360; |
| constexpr int kHoverAboveNotificationHeight = 24; |
| |
| // The thickness of the border drawn around the inner edge of the popup. |
| constexpr int kTryChromeBorderThickness = 1; |
| |
| // The space between the taskbar and the popup when it is positioned over an |
| // icon in the taskbar. |
| constexpr int kOffsetFromTaskbar = 1; |
| |
| // The dimensions of the arrow image. |
| constexpr int kArrowWidth = 24; |
| constexpr int kArrowHeight = 14; |
| |
| // The number of points the arrow icon is inset from the outer edge of the |
| // popup's border. |
| constexpr int kArrowInset = 3; |
| |
| const SkColor kTryChromeBackgroundColor = SkColorSetRGB(0x1F, 0x1F, 0x1F); |
| const SkColor kHeaderColor = SkColorSetRGB(0xFF, 0xFF, 0xFF); |
| const SkColor kBodyColor = SkColorSetARGB(0xAD, 0xFF, 0xFF, 0xFF); |
| const SkColor kBorderColor = SkColorSetARGB(0x80, 0x80, 0x80, 0x80); |
| const SkColor kButtonTextColor = SkColorSetRGB(0xFF, 0xFF, 0xFF); |
| const SkColor kButtonAcceptColor = SkColorSetRGB(0x00, 0x78, 0xDA); |
| const SkColor kButtonNoThanksColor = SkColorSetARGB(0x33, 0xFF, 0xFF, 0xFF); |
| |
| enum class ButtonTag { CLOSE_BUTTON, OK_BUTTON, NO_THANKS_BUTTON }; |
| |
| // Experiment specification information needed for layout. |
| struct ExperimentVariations { |
| enum class CloseStyle { |
| kNoThanksButton, |
| kCloseX, |
| kNoThanksButtonAndCloseX, |
| }; |
| |
| // Resource ID for header message string. |
| int heading_id; |
| // Resource ID for body message string, or 0 for no body text. |
| int body_id; |
| // Set of dismissal controls. |
| CloseStyle close_style; |
| // Which action to take on acceptance of the dialog. |
| TryChromeDialog::Result result; |
| }; |
| |
| constexpr ExperimentVariations kExperiments[] = { |
| {IDS_WIN10_TOAST_RECOMMENDATION, 0, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_RECOMMENDATION, 0, |
| ExperimentVariations::CloseStyle::kNoThanksButtonAndCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_RECOMMENDATION, 0, |
| ExperimentVariations::CloseStyle::kNoThanksButton, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_RECOMMENDATION, 0, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_WELCOME_WIN10}, |
| {IDS_WIN10_TOAST_RECOMMENDATION, 0, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_WELCOME}, |
| {IDS_WIN10_TOAST_RECOMMENDATION, IDS_WIN10_TOAST_SWITCH_FAST, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_RECOMMENDATION, IDS_WIN10_TOAST_SWITCH_SECURE, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_RECOMMENDATION, IDS_WIN10_TOAST_SWITCH_SMART, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_SWITCH_FAST, IDS_WIN10_TOAST_RECOMMENDATION, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_SWITCH_SECURE, IDS_WIN10_TOAST_RECOMMENDATION, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_SWITCH_SMART, IDS_WIN10_TOAST_RECOMMENDATION, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_BROWSE_FAST, 0, ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_BROWSE_SAFELY, 0, |
| ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_BROWSE_SMART, 0, ExperimentVariations::CloseStyle::kCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_SWITCH_SMART_AND_SECURE, IDS_WIN10_TOAST_RECOMMENDATION, |
| ExperimentVariations::CloseStyle::kNoThanksButtonAndCloseX, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}, |
| {IDS_WIN10_TOAST_SWITCH_SMART_AND_SECURE, IDS_WIN10_TOAST_RECOMMENDATION, |
| ExperimentVariations::CloseStyle::kNoThanksButton, |
| TryChromeDialog::OPEN_CHROME_DEFAULT}}; |
| |
| // Whether a button is an accept or cancel-style button. |
| enum class TryChromeButtonType { OPEN_CHROME, NO_THANKS }; |
| |
| // Builds a Win10-styled rectangular button, for this toast displayed outside of |
| // the browser. |
| std::unique_ptr<views::LabelButton> CreateWin10StyleButton( |
| views::ButtonListener* listener, |
| const base::string16& text, |
| TryChromeButtonType button_type) { |
| auto button = std::make_unique<views::LabelButton>(listener, text, |
| CONTEXT_WINDOWS10_NATIVE); |
| button->SetHorizontalAlignment(gfx::ALIGN_CENTER); |
| |
| button->SetBackground(views::CreateSolidBackground( |
| button_type == TryChromeButtonType::OPEN_CHROME ? kButtonAcceptColor |
| : kButtonNoThanksColor)); |
| button->SetEnabledTextColors(kButtonTextColor); |
| // Request specific 32pt height, 166+pt width. |
| button->SetMinSize(gfx::Size(166, 32)); |
| button->SetMaxSize(gfx::Size(0, 32)); |
| // Make button focusable for keyboard navigation. |
| button->SetFocusBehavior(views::View::FocusBehavior::ALWAYS); |
| return button; |
| } |
| |
| // A View that unconditionally reports that it handles mouse presses. This |
| // results in the widget capturing the mouse so that it receives a |
| // ET_MOUSE_CAPTURE_CHANGED event upon button release following a drag out of |
| // the background of the widget. |
| class ClickableView : public views::View { |
| public: |
| ClickableView() = default; |
| |
| // views::View: |
| bool OnMousePressed(const ui::MouseEvent& event) override; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ClickableView); |
| }; |
| |
| bool ClickableView::OnMousePressed(const ui::MouseEvent& event) { |
| return true; |
| } |
| |
| } // namespace |
| |
| // A helper class that determines properties of the desktop on which the popup |
| // will be shown, finds Chrome's taskbar icon, and handles calculations to |
| // position and draw the popup accordingly. |
| class TryChromeDialog::Context { |
| public: |
| Context(); |
| |
| // Begins asynchronous initialization of the context (i.e., runs a search for |
| // the taskbar icon), running |closure| when done. |
| void Initialize(base::OnceClosure closure); |
| |
| // Adds a border to |contents_view|, intended for use with |popup|. The |
| // border's insets cover the area drawn by the border itself, including space |
| // for the popup's arrow in case the popup is presented over a taskbar icon. |
| void AddBorderToContents(views::Widget* popup, views::View* contents_view); |
| |
| // Computes the bouding rectangle of |popup| with the given |size| and applies |
| // a shape to the popup's window as needed. |
| gfx::Rect ComputePopupBounds(views::Widget* popup, const gfx::Size& size); |
| |
| // Returns the location where the popup will be presented. |
| installer::ExperimentMetrics::ToastLocation GetToastLocation() const; |
| |
| // Returns the work area of the primary display (the one on which the popup |
| // will be presented). |
| const gfx::Rect& display_work_area() const { |
| return primary_display_.work_area(); |
| } |
| |
| // Returns the bounding rectangle of the taskbar icon over which the popup is |
| // to be presented. |
| const gfx::Rect& taskbar_icon_rect() const { return taskbar_icon_rect_; } |
| |
| private: |
| // An interface to a calclulator capable of drawing a border around a popup |
| // and positioning it. Concrete subclasses of this handle positioning the |
| // popup over the notification area or "over" the taskbar in any orientation. |
| class DialogCalculator { |
| public: |
| virtual ~DialogCalculator() {} |
| |
| // Returns the ToastLocation metric to be reported when this calculator is |
| // used. |
| installer::ExperimentMetrics::ToastLocation toast_location() const { |
| return toast_location_; |
| } |
| |
| // Adds a border to |contents_view|, intended for use with |popup|. The |
| // border's insets cover the area drawn by the border itself, including |
| // space for the popup's arrow in case the popup is presented over a taskbar |
| // icon. |
| virtual void AddBorderToContents(views::Widget* popup, |
| views::View* contents_view) = 0; |
| |
| // Returns the bounding rectangle of |popup| given the desired |size|, |
| // shaping the window for |popup| as needed. |
| virtual gfx::Rect ComputeBounds(const Context& context, |
| views::Widget* popup, |
| const gfx::Size& size) = 0; |
| |
| protected: |
| explicit DialogCalculator( |
| installer::ExperimentMetrics::ToastLocation toast_location) |
| : toast_location_(toast_location) {} |
| |
| private: |
| // The ToastLocation metric to be reported when this calculator is used. |
| const installer::ExperimentMetrics::ToastLocation toast_location_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DialogCalculator); |
| }; |
| |
| // A calculator for positioning the popup over the notification area. |
| class NotificationAreaCalculator : public DialogCalculator { |
| public: |
| NotificationAreaCalculator() |
| : DialogCalculator( |
| installer::ExperimentMetrics::kOverNotificationArea) {} |
| |
| // DialogCalculator: |
| void AddBorderToContents(views::Widget* popup, |
| views::View* contents_view) override; |
| gfx::Rect ComputeBounds(const Context& context, |
| views::Widget* popup, |
| const gfx::Size& size) override; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(NotificationAreaCalculator); |
| }; |
| |
| // A calculator for positioning the popup "over" Chrome's icon in the taskbar, |
| // handling the four possible orientations of the taskbar. This includes |
| // drawing the border and shaping the window for the arrow. |
| class TaskbarCalculator : public DialogCalculator, |
| public views::WidgetObserver, |
| public views::WidgetRemovalsObserver { |
| public: |
| enum class Location { kTop, kLeft, kBottom, kRight }; |
| |
| static std::unique_ptr<TaskbarCalculator> Create(Location location); |
| |
| // DialogCalculator: |
| void AddBorderToContents(views::Widget* popup, |
| views::View* contents_view) override; |
| gfx::Rect ComputeBounds(const Context& context, |
| views::Widget* popup, |
| const gfx::Size& size) override; |
| |
| private: |
| // A pointer to a function that populates |polygon| with the seven points |
| // that outline a popup at |dialog_bounds| within a window of |window_size| |
| // containing an arrow at |arrow_bounds| (which defines the bounding |
| // rectangle outside of the content region of the popup). |
| // |arrow_border_insets| defines border-thickness insets into the arrow that |
| // are used to properly define the region. |
| using PopupRegionCreatorFn = |
| void (*)(const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon); |
| |
| // Properties for an orientation-speciifc popup and its border. |
| struct PopupProperties { |
| // An inset that, when applied to the bounding rectangle of the arrow, |
| // subtracts off the amount by which the arrow is set into the body of the |
| // popup. |
| const gfx::Insets arrow_inset; |
| |
| // The size of the arrow, taking into account any rotation needed (i.e., a |
| // 24x14 arrow will be 14x24 after a 90 degree or 270 degree rotation). |
| const gfx::Size arrow_size; |
| |
| // Indicates the direction to translate the arrow from the center of the |
| // popup so that it is moved to the appripriate side of the popup (one of |
| // (0,-1), (-1,0), (0,1), or (1,0)). |
| const gfx::Vector2dF offset_scale; |
| |
| // The function that creates the proper region around the popup. |
| PopupRegionCreatorFn region_creator; |
| |
| // Properties for the border around the popup and its arrow. |
| const ArrowBorder::Properties border_properties; |
| }; |
| |
| // Creates a DialogCalculator for positioning the popup over a taskbar icon. |
| explicit TaskbarCalculator(const PopupProperties* properties) |
| : DialogCalculator(installer::ExperimentMetrics::kOverTaskbarPin), |
| properties_(properties), |
| contents_view_(nullptr), |
| border_(nullptr) {} |
| |
| // views::WidgetObserver: |
| // Updates the region defining the window shape of |popup|. |
| void OnWidgetBoundsChanged(views::Widget* popup, |
| const gfx::Rect& new_bounds) override; |
| |
| // views::WidgetRemovalsObserver: |
| void OnWillRemoveView(views::Widget* popup, views::View* view) override; |
| |
| // PopupRegionCreatorFn functions for the possible orientations. |
| static void CreateTopArrowRegion(const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon); |
| static void CreateLeftArrowRegion(const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon); |
| static void CreateBottomArrowRegion(const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon); |
| static void CreateRightArrowRegion(const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon); |
| |
| // Popup properties for the possible orientations. |
| static const PopupProperties kTopTaskbarProperties_; |
| static const PopupProperties kLeftTaskbarProperties_; |
| static const PopupProperties kBottomTaskbarProperties_; |
| static const PopupProperties kRightTaskbarProperties_; |
| |
| const PopupProperties* const properties_; |
| |
| // A horizontal (for top/bottom taskbars) or vertical (for left/right) |
| // displacement, in DIP, for the arrow to keep it centered with respect to |
| // the taskbar icon. |
| gfx::Vector2d arrow_adjustment_; |
| |
| views::View* contents_view_; |
| ArrowBorder* border_; |
| |
| // The last size, in pixels, for the popup's window for which its region was |
| // calculated. |
| gfx::Size window_size_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TaskbarCalculator); |
| }; |
| |
| enum class TaskbarLocation { kUnknown, kTop, kLeft, kBottom, kRight }; |
| |
| // Returns the window of the taskbar on the primary display. |
| static HWND FindTaskbarWindow(); |
| |
| // Returns the bounding rectangle of |taskbar_window| or an empty rect if |
| // |taskbar_window| is invalid or its bounds cannot be determined. |
| static gfx::Rect GetTaskbarRect(HWND taskbar_window); |
| |
| // Returns the location of the taskbar on |primary_display| given |
| // |taskbar_rect| as its bounding rectangle. Returns TaskbarLocation::kUnknown |
| // if |taskbar_rect| is empty, the taskbar is hidden, or its location cannot |
| // be determined for any other reason. |
| static TaskbarLocation FindTaskbarLocation( |
| const display::Display& primary_display, |
| const gfx::Rect& taskbar_rect); |
| |
| // Receives the bounding recangle of Chrome's taskbar icon, configures the |
| // instance accordingly, and continues processing by running |closure| (as |
| // provided to Initialize). |
| void OnTaskbarIconRect(base::OnceClosure closure, |
| const gfx::Rect& taskbar_icon_rect); |
| |
| // Returns a Calculator instance for presentation of the popup based on the |
| // presence and position of the taskbar icon. |
| std::unique_ptr<DialogCalculator> MakeCalculator() const; |
| |
| // The primary display. |
| const display::Display primary_display_; |
| |
| // The window of the taskbar on the primary display, or null. |
| const HWND taskbar_window_; |
| |
| // The bounding rectangle of the taskbar on the primary display, or an empty |
| // rect. |
| const gfx::Rect taskbar_rect_; |
| |
| // The location of the taskbar on the primary display, or kUnknown. |
| const TaskbarLocation taskbar_location_; |
| |
| // The bounding rectangle of Chrome's icon in the primary taskbar. |
| gfx::Rect taskbar_icon_rect_; |
| |
| // A dialog calculator to position and draw the popup based on the presence |
| // and location of the taskbar icon. |
| std::unique_ptr<DialogCalculator> calculator_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Context); |
| }; |
| |
| // TryChromeDialog::Context::Context ------------------------------------------- |
| |
| TryChromeDialog::Context::Context() |
| : primary_display_(display::Screen::GetScreen()->GetPrimaryDisplay()), |
| taskbar_window_(FindTaskbarWindow()), |
| taskbar_rect_(GetTaskbarRect(taskbar_window_)), |
| taskbar_location_(FindTaskbarLocation(primary_display_, taskbar_rect_)) {} |
| |
| void TryChromeDialog::Context::Initialize(base::OnceClosure closure) { |
| // Get the bounding rectangle of Chrome's taskbar icon on the primary monitor. |
| FindTaskbarIcon(base::BindOnce(&TryChromeDialog::Context::OnTaskbarIconRect, |
| base::Unretained(this), std::move(closure))); |
| } |
| |
| void TryChromeDialog::Context::AddBorderToContents(views::Widget* popup, |
| views::View* contents_view) { |
| calculator_->AddBorderToContents(popup, contents_view); |
| } |
| |
| gfx::Rect TryChromeDialog::Context::ComputePopupBounds(views::Widget* popup, |
| const gfx::Size& size) { |
| return calculator_->ComputeBounds(*this, popup, size); |
| } |
| |
| installer::ExperimentMetrics::ToastLocation |
| TryChromeDialog::Context::GetToastLocation() const { |
| return calculator_->toast_location(); |
| } |
| |
| // static |
| HWND TryChromeDialog::Context::FindTaskbarWindow() { |
| return ::FindWindow(L"Shell_TrayWnd", nullptr); |
| } |
| |
| // static |
| gfx::Rect TryChromeDialog::Context::GetTaskbarRect(HWND taskbar_window) { |
| RECT temp_rect = {}; |
| if (!taskbar_window || !::GetWindowRect(taskbar_window, &temp_rect)) |
| return gfx::Rect(); |
| return display::win::ScreenWin::ScreenToDIPRect(taskbar_window, |
| gfx::Rect(temp_rect)); |
| } |
| |
| // static |
| TryChromeDialog::Context::TaskbarLocation |
| TryChromeDialog::Context::FindTaskbarLocation( |
| const display::Display& primary_display, |
| const gfx::Rect& taskbar_rect) { |
| if (taskbar_rect.IsEmpty()) |
| return TaskbarLocation::kUnknown; |
| |
| // The taskbar is always on the primary display. |
| const gfx::Rect& monitor_rect = primary_display.bounds(); |
| |
| // Is the taskbar not on the primary display (e.g., is it hidden)? |
| if (!monitor_rect.Contains(taskbar_rect)) |
| return TaskbarLocation::kUnknown; |
| |
| // Where is the taskbar? Assume that it's "wider" than it is "tall". |
| if (taskbar_rect.width() > taskbar_rect.height()) { |
| // Horizonal. |
| if (taskbar_rect.y() >= monitor_rect.y() + monitor_rect.height() / 2) |
| return TaskbarLocation::kBottom; |
| return TaskbarLocation::kTop; |
| } |
| // Vertical. |
| if (taskbar_rect.x() < monitor_rect.x() + monitor_rect.width() / 2) |
| return TaskbarLocation::kLeft; |
| return TaskbarLocation::kRight; |
| } |
| |
| void TryChromeDialog::Context::OnTaskbarIconRect( |
| base::OnceClosure closure, |
| const gfx::Rect& taskbar_icon_rect) { |
| taskbar_icon_rect_ = taskbar_icon_rect; |
| calculator_ = MakeCalculator(); |
| std::move(closure).Run(); |
| } |
| |
| std::unique_ptr<TryChromeDialog::Context::DialogCalculator> |
| TryChromeDialog::Context::MakeCalculator() const { |
| TaskbarLocation location = taskbar_location_; |
| |
| // Present the popup over the notification area if the taskbar couldn't be |
| // found, the icon couldn't be found, or the taskbar doesn't contain the icon |
| // (e.g., the icon is scrolled out of view). |
| if (taskbar_icon_rect_.IsEmpty() || |
| (location != TaskbarLocation::kUnknown && |
| !taskbar_rect_.Contains(taskbar_icon_rect_))) { |
| location = TaskbarLocation::kUnknown; |
| } |
| |
| switch (location) { |
| case TaskbarLocation::kUnknown: |
| break; |
| case TaskbarLocation::kTop: |
| return TaskbarCalculator::Create(TaskbarCalculator::Location::kTop); |
| case TaskbarLocation::kLeft: |
| return TaskbarCalculator::Create(TaskbarCalculator::Location::kLeft); |
| case TaskbarLocation::kBottom: |
| return TaskbarCalculator::Create(TaskbarCalculator::Location::kBottom); |
| case TaskbarLocation::kRight: |
| return TaskbarCalculator::Create(TaskbarCalculator::Location::kRight); |
| } |
| return std::make_unique<NotificationAreaCalculator>(); |
| } |
| |
| // TryChromeDialog::Context::NotificationAreaCalculator ------------------------ |
| |
| void TryChromeDialog::Context::NotificationAreaCalculator::AddBorderToContents( |
| views::Widget* popup, |
| views::View* contents_view) { |
| contents_view->SetBorder( |
| views::CreateSolidBorder(kTryChromeBorderThickness, kBorderColor)); |
| } |
| |
| gfx::Rect TryChromeDialog::Context::NotificationAreaCalculator::ComputeBounds( |
| const Context& context, |
| views::Widget* popup, |
| const gfx::Size& size) { |
| const bool is_RTL = base::i18n::IsRTL(); |
| const gfx::Rect work_area = popup->GetWorkAreaBoundsInScreen(); |
| return gfx::Rect( |
| is_RTL ? work_area.x() : work_area.right() - size.width(), |
| work_area.bottom() - size.height() - kHoverAboveNotificationHeight, |
| size.width(), size.height()); |
| } |
| |
| // TryChromeDialog::Context::TaskbarCalculator --------------------------------- |
| |
| std::unique_ptr<TryChromeDialog::Context::TaskbarCalculator> |
| TryChromeDialog::Context::TaskbarCalculator::Create(Location location) { |
| switch (location) { |
| case Location::kTop: |
| return base::WrapUnique(new TaskbarCalculator(&kTopTaskbarProperties_)); |
| case Location::kLeft: |
| return base::WrapUnique(new TaskbarCalculator(&kLeftTaskbarProperties_)); |
| case Location::kBottom: |
| break; |
| case Location::kRight: |
| return base::WrapUnique(new TaskbarCalculator(&kRightTaskbarProperties_)); |
| } |
| return base::WrapUnique(new TaskbarCalculator(&kBottomTaskbarProperties_)); |
| } |
| |
| void TryChromeDialog::Context::TaskbarCalculator::AddBorderToContents( |
| views::Widget* popup, |
| views::View* contents_view) { |
| // Hold a pointer to the border so that it can be given the exact position of |
| // the arrow after the popup is sized and placed at the proper screen |
| // location. Also hold a pointer to the view to which the border is attached |
| // and observe the popup so that these pointers can be appropriately cleared. |
| contents_view_ = contents_view; |
| auto border = std::make_unique<ArrowBorder>( |
| kTryChromeBorderThickness, kBorderColor, kTryChromeBackgroundColor, |
| kInactiveToastArrowIcon, &properties_->border_properties); |
| border_ = border.get(); |
| contents_view->SetBorder(std::move(border)); |
| popup->AddObserver(this); |
| popup->AddRemovalsObserver(this); |
| } |
| |
| gfx::Rect TryChromeDialog::Context::TaskbarCalculator::ComputeBounds( |
| const Context& context, |
| views::Widget* popup, |
| const gfx::Size& size) { |
| // Center the popup over the icon. |
| const gfx::RectF taskbar_icon_rect(context.taskbar_icon_rect()); |
| gfx::PointF popup_origin(taskbar_icon_rect.CenterPoint()); |
| popup_origin.Offset(size.width() / -2.0f, size.height() / -2.0f); |
| |
| // Move the popup away from the center of the taskbar icon by the |
| // orientation-specific offset. This will move it along one of the axes to |
| // push the popup up (for a bottm-of-screen taskbar), to the right right (for |
| // a left-of-screen taskbar), etc. |
| gfx::Vector2dF translation( |
| (taskbar_icon_rect.width() + size.width()) / 2.0f + kOffsetFromTaskbar, |
| (taskbar_icon_rect.height() + size.height()) / 2.0f + kOffsetFromTaskbar); |
| |
| // offset_scale clears out one axis and makes the other positive or negative, |
| // as appropriate. |
| translation.Scale(properties_->offset_scale.x(), |
| properties_->offset_scale.y()); |
| popup_origin += translation; |
| |
| const gfx::Rect desired_bounds(gfx::ToRoundedPoint(popup_origin), size); |
| |
| // Adjust the popup to fit in the work area to handle the case where the icon |
| // is close to the edge. |
| gfx::Rect result = desired_bounds; |
| result.AdjustToFit(context.display_work_area()); |
| |
| // Remember the amount of offset for proper arrow placement. |
| arrow_adjustment_ = result.origin() - desired_bounds.origin(); |
| if (properties_->offset_scale.x()) // Popup is next to the icon. |
| arrow_adjustment_.set_x(0); |
| else // Popup is over/under the icon. |
| arrow_adjustment_.set_y(0); |
| |
| return result; |
| } |
| |
| void TryChromeDialog::Context::TaskbarCalculator::OnWidgetBoundsChanged( |
| views::Widget* popup, |
| const gfx::Rect& new_bounds) { |
| // Compute and apply the region to shape the dialog around the arrow. The |
| // region must be in screen rather than DIP coordinates relative to the |
| // client area of the window. |
| |
| aura::WindowTreeHost* const host = popup->GetNativeView()->GetHost(); |
| // Nothing to do if there's not yet a backing window. |
| if (!host) |
| return; |
| |
| // Nothing to do if the contents have yet to be added. |
| if (!popup->GetContentsView()) |
| return; |
| |
| // Get the new window size. |
| const gfx::Size window_size = host->GetBoundsInPixels().size(); |
| |
| // Nothing to do if the size of the window hasn't changed since a previous |
| // region calculation. |
| if (window_size == window_size_) |
| return; |
| |
| // Remember this size for the future and to protect from a chain of |
| // recursive calls when ::SetWindowRgn is called below. |
| window_size_ = window_size; |
| |
| const HWND hwnd = host->GetAcceleratedWidget(); |
| const float dsf = display::win::ScreenWin::GetScaleFactorForHWND(hwnd); |
| |
| // Compute the pixel position of the arrow relative to the window's client |
| // area. |
| |
| // Center the arrow over the window. |
| gfx::SizeF arrow_size(properties_->arrow_size); |
| arrow_size.Scale(dsf); |
| gfx::PointF arrow_origin(window_size.width() / 2.0f, |
| window_size.height() / 2.0f); |
| arrow_origin.Offset(arrow_size.width() / -2.0f, arrow_size.height() / -2.0f); |
| |
| // Push it out to its proper location. |
| gfx::Vector2dF translation( |
| (arrow_size.width() + window_size.width()) / 2.0f, |
| (arrow_size.height() + window_size.height()) / 2.0f); |
| translation.Scale(properties_->offset_scale.x(), |
| properties_->offset_scale.y()); |
| arrow_origin -= translation; |
| |
| // Select the bounding rectangle by putting the origin on the nearest point |
| // and rouding the size. |
| gfx::Rect arrow_bounds(gfx::ToRoundedPoint(arrow_origin), |
| gfx::ToRoundedSize(arrow_size)); |
| |
| // Move it inward so that it is flush with the outer edge of the window. |
| arrow_bounds.AdjustToFit(gfx::Rect(window_size)); |
| |
| // Offset by the adjustment from ComputeBounds to keep it centered with |
| // respect to the taskbar icon. |
| arrow_bounds -= gfx::ToRoundedVector2d( |
| gfx::ScaleVector2d(gfx::Vector2dF(arrow_adjustment_), dsf)); |
| |
| // Tell the border about the new location for the arrow so that it is painted |
| // in the correct place. |
| border_->set_arrow_bounds(arrow_bounds); |
| |
| // Compute the bounding rectangle of the dialog (the visible rect including |
| // the border without the arrow). |
| gfx::Insets scaled_insets = |
| popup->GetContentsView()->border()->GetInsets().Scale(dsf); |
| scaled_insets -= gfx::Insets(kTryChromeBorderThickness).Scale(dsf); |
| gfx::Rect dialog_bounds(window_size); |
| dialog_bounds.Inset(scaled_insets); |
| |
| // Clip the arrow to the bounds of the dialog. |
| arrow_bounds.Subtract(dialog_bounds); |
| |
| // Scale the insets into the arrow's bounding rectangle that the border |
| // extends into it. |
| gfx::Insets arrow_border_insets( |
| properties_->border_properties.arrow_border_insets.Scale(dsf)); |
| |
| POINT polygon[7]; |
| properties_->region_creator(window_size, dialog_bounds, arrow_bounds, |
| arrow_border_insets, &polygon[0]); |
| HRGN region = ::CreatePolygonRgn(&polygon[0], base::size(polygon), WINDING); |
| ::SetWindowRgn(hwnd, region, FALSE); |
| } |
| |
| void TryChromeDialog::Context::TaskbarCalculator::OnWillRemoveView( |
| views::Widget* popup, |
| views::View* view) { |
| if (view == contents_view_) { |
| // The view to which the border was added is being removed. Clear out the |
| // weak pointers to the view and border (which is owned by the view) and |
| // stop observing the widget. |
| contents_view_ = nullptr; |
| border_ = nullptr; |
| popup->RemoveRemovalsObserver(this); |
| popup->RemoveObserver(this); |
| } |
| } |
| |
| // static |
| void TryChromeDialog::Context::TaskbarCalculator::CreateTopArrowRegion( |
| const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon) { |
| polygon[0] = {dialog_bounds.x(), dialog_bounds.y()}; |
| polygon[1] = {arrow_bounds.x() + arrow_border_insets.left(), |
| dialog_bounds.y()}; |
| polygon[2] = {arrow_bounds.x() + arrow_bounds.width() / 2, 0}; |
| polygon[3] = {arrow_bounds.right() - arrow_border_insets.right(), |
| dialog_bounds.y()}; |
| polygon[4] = {dialog_bounds.right(), dialog_bounds.y()}; |
| polygon[5] = {dialog_bounds.right(), dialog_bounds.bottom()}; |
| polygon[6] = {dialog_bounds.x(), dialog_bounds.bottom()}; |
| } |
| |
| // static |
| void TryChromeDialog::Context::TaskbarCalculator::CreateLeftArrowRegion( |
| const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon) { |
| polygon[0] = {dialog_bounds.x(), dialog_bounds.y()}; |
| polygon[1] = {dialog_bounds.right(), dialog_bounds.y()}; |
| polygon[2] = {dialog_bounds.right(), dialog_bounds.bottom()}; |
| polygon[3] = {dialog_bounds.x(), dialog_bounds.bottom()}; |
| polygon[4] = {dialog_bounds.x(), |
| arrow_bounds.bottom() - arrow_border_insets.bottom()}; |
| polygon[5] = {0, arrow_bounds.y() + arrow_bounds.height() / 2}; |
| polygon[6] = {dialog_bounds.x(), |
| arrow_bounds.y() + arrow_border_insets.top()}; |
| } |
| |
| // static |
| void TryChromeDialog::Context::TaskbarCalculator::CreateBottomArrowRegion( |
| const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon) { |
| polygon[0] = {dialog_bounds.x(), dialog_bounds.y()}; |
| polygon[1] = {dialog_bounds.right(), dialog_bounds.y()}; |
| polygon[2] = {dialog_bounds.right(), dialog_bounds.bottom()}; |
| polygon[3] = {arrow_bounds.right() - arrow_border_insets.right(), |
| dialog_bounds.bottom()}; |
| polygon[4] = {arrow_bounds.x() + arrow_bounds.width() / 2, |
| window_size.height()}; |
| polygon[5] = {arrow_bounds.x() + arrow_border_insets.left(), |
| dialog_bounds.bottom()}; |
| polygon[6] = {dialog_bounds.x(), dialog_bounds.bottom()}; |
| } |
| |
| // static |
| void TryChromeDialog::Context::TaskbarCalculator::CreateRightArrowRegion( |
| const gfx::Size& window_size, |
| const gfx::Rect& dialog_bounds, |
| const gfx::Rect& arrow_bounds, |
| const gfx::Insets& arrow_border_insets, |
| POINT* polygon) { |
| polygon[0] = {dialog_bounds.x(), dialog_bounds.y()}; |
| polygon[1] = {dialog_bounds.right(), dialog_bounds.y()}; |
| polygon[2] = {dialog_bounds.right(), |
| arrow_bounds.y() + arrow_border_insets.top()}; |
| polygon[3] = {window_size.width(), |
| arrow_bounds.y() + arrow_bounds.height() / 2}; |
| polygon[4] = {dialog_bounds.right(), |
| arrow_bounds.bottom() - arrow_border_insets.bottom()}; |
| polygon[5] = {dialog_bounds.right(), dialog_bounds.bottom()}; |
| polygon[6] = {dialog_bounds.x(), dialog_bounds.bottom()}; |
| } |
| |
| // static |
| constexpr TryChromeDialog::Context::TaskbarCalculator::PopupProperties |
| TryChromeDialog::Context::TaskbarCalculator::kTopTaskbarProperties_ = { |
| {0, 0, kArrowInset, 0}, |
| {kArrowWidth, kArrowHeight}, |
| {0.0f, 1.0f} /* Translate down */, |
| &CreateTopArrowRegion, |
| {// ArrowBorder::Properties |
| gfx::Insets(kArrowHeight - kArrowInset, 0, 0, 0), |
| gfx::Insets(0, |
| kTryChromeBorderThickness, |
| 0, |
| kTryChromeBorderThickness), |
| ArrowBorder::ArrowRotation::k180Degrees}}; |
| |
| // static |
| constexpr TryChromeDialog::Context::TaskbarCalculator::PopupProperties |
| TryChromeDialog::Context::TaskbarCalculator::kLeftTaskbarProperties_{ |
| {0, 0, 0, kArrowInset}, |
| {kArrowHeight, kArrowWidth}, |
| {1.0f, 0.0f} /* Translate right */, |
| &CreateLeftArrowRegion, |
| {// ArrowBorder::Properties |
| gfx::Insets(0, kArrowHeight - kArrowInset, 0, 0), |
| gfx::Insets(kTryChromeBorderThickness, |
| 0, |
| kTryChromeBorderThickness, |
| 0), |
| ArrowBorder::ArrowRotation::k90Degrees}}; |
| |
| // static |
| constexpr TryChromeDialog::Context::TaskbarCalculator::PopupProperties |
| TryChromeDialog::Context::TaskbarCalculator::kBottomTaskbarProperties_{ |
| {kArrowInset, 0, 0, 0}, |
| {kArrowWidth, kArrowHeight}, |
| {0.0f, -1.0f} /* Translate up */, |
| &CreateBottomArrowRegion, |
| {// ArrowBorder::Properties |
| gfx::Insets(0, 0, kArrowHeight - kArrowInset, 0), |
| gfx::Insets(0, |
| kTryChromeBorderThickness, |
| 0, |
| kTryChromeBorderThickness), |
| ArrowBorder::ArrowRotation::kNone}}; |
| |
| // static |
| constexpr TryChromeDialog::Context::TaskbarCalculator::PopupProperties |
| TryChromeDialog::Context::TaskbarCalculator::kRightTaskbarProperties_{ |
| {0, kArrowInset, 0, 0}, |
| {kArrowHeight, kArrowWidth}, |
| {-1.0f, 0.0f} /* Translate left */, |
| &CreateRightArrowRegion, |
| {// ArrowBorder::Properties |
| gfx::Insets(0, 0, 0, kArrowHeight - kArrowInset), |
| gfx::Insets(kTryChromeBorderThickness, |
| 0, |
| kTryChromeBorderThickness, |
| 0), |
| ArrowBorder::ArrowRotation::k270Degrees}}; |
| |
| // TryChromeDialog::ModalShowDelegate ------------------------------------------ |
| |
| // A delegate for use by the modal Show() function to update the experiment |
| // state in the Windows registry and break out of the modal run loop upon |
| // completion. |
| class TryChromeDialog::ModalShowDelegate : public TryChromeDialog::Delegate { |
| public: |
| // Constructs the updater with a closure to be run after the dialog is closed |
| // to break out of the modal run loop. |
| explicit ModalShowDelegate(base::Closure quit_closure) |
| : quit_closure_(std::move(quit_closure)) {} |
| ~ModalShowDelegate() override = default; |
| |
| protected: |
| // TryChromeDialog::Delegate: |
| void SetToastLocation( |
| installer::ExperimentMetrics::ToastLocation toast_location) override; |
| void SetExperimentState(installer::ExperimentMetrics::State state) override; |
| void InteractionComplete() override; |
| |
| private: |
| base::Closure quit_closure_; |
| installer::ExperimentStorage storage_; |
| |
| // The time at which the toast was shown; used for computing the action delay. |
| base::TimeTicks time_shown_; |
| |
| DISALLOW_COPY_AND_ASSIGN(ModalShowDelegate); |
| }; |
| |
| void TryChromeDialog::ModalShowDelegate::SetToastLocation( |
| installer::ExperimentMetrics::ToastLocation toast_location) { |
| time_shown_ = base::TimeTicks::Now(); |
| |
| installer::Experiment experiment; |
| auto lock = storage_.AcquireLock(); |
| if (lock->LoadExperiment(&experiment)) { |
| experiment.SetDisplayTime(base::Time::Now()); |
| experiment.SetToastCount(experiment.toast_count() + 1); |
| experiment.SetToastLocation(toast_location); |
| // TODO(skare): SetUserSessionUptime |
| lock->StoreExperiment(experiment); |
| } |
| } |
| |
| void TryChromeDialog::ModalShowDelegate::SetExperimentState( |
| installer::ExperimentMetrics::State state) { |
| installer::Experiment experiment; |
| auto lock = storage_.AcquireLock(); |
| if (lock->LoadExperiment(&experiment)) { |
| if (!time_shown_.is_null()) |
| experiment.SetActionDelay(base::TimeTicks::Now() - time_shown_); |
| experiment.SetState(state); |
| lock->StoreExperiment(experiment); |
| } |
| } |
| |
| void TryChromeDialog::ModalShowDelegate::InteractionComplete() { |
| quit_closure_.Run(); |
| } |
| |
| // TryChromeDialog ------------------------------------------------------------- |
| |
| // static |
| TryChromeDialog::Result TryChromeDialog::Show( |
| size_t group, |
| ActiveModalDialogListener listener) { |
| if (group >= base::size(kExperiments)) { |
| // Exit immediately given bogus values; see TryChromeDialogBrowserTest test. |
| return NOT_NOW; |
| } |
| |
| base::RunLoop run_loop; |
| ModalShowDelegate delegate(run_loop.QuitWhenIdleClosure()); |
| TryChromeDialog dialog(group, &delegate); |
| |
| dialog.ShowDialogAsync(); |
| |
| if (listener) { |
| listener.Run(base::Bind(&TryChromeDialog::OnProcessNotification, |
| base::Unretained(&dialog))); |
| } |
| run_loop.Run(); |
| if (listener) |
| listener.Run(base::Closure()); |
| |
| return dialog.result(); |
| } |
| |
| TryChromeDialog::TryChromeDialog(size_t group, Delegate* delegate) |
| : group_(group), |
| delegate_(delegate), |
| context_(std::make_unique<Context>()) { |
| DCHECK_LT(group, base::size(kExperiments)); |
| DCHECK(delegate); |
| } |
| |
| TryChromeDialog::~TryChromeDialog() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| } |
| |
| void TryChromeDialog::ShowDialogAsync() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| |
| endsession_observer_ = std::make_unique<gfx::SingletonHwndObserver>( |
| base::Bind(&TryChromeDialog::OnWindowMessage, base::Unretained(this))); |
| |
| context_->Initialize(base::BindOnce(&TryChromeDialog::OnContextInitialized, |
| base::Unretained(this))); |
| } |
| |
| void TryChromeDialog::OnContextInitialized() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| |
| // It's possible that a rendezvous from another browser process arrived while |
| // searching for the taskbar icon (see OnProcessNotification). In this case, |
| // report the DEFER result and bail out immediately. |
| if (result_ == OPEN_CHROME_DEFER) { |
| DCHECK_EQ(state_, installer::ExperimentMetrics::kOtherLaunch); |
| CompleteInteraction(); |
| return; |
| } |
| |
| // It's also possible that a WM_ENDSESSION arrived while searching (see |
| // OnWindowMessage). In this case, continue processing since it's possible |
| // that the logoff was cancelled. The toast may as well be shown. |
| |
| // Create the popup. |
| auto logo = std::make_unique<views::ImageView>(); |
| logo->SetImage(gfx::CreateVectorIcon(kInactiveToastLogoIcon, kHeaderColor)); |
| const gfx::Size logo_size = logo->GetPreferredSize(); |
| |
| views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); |
| params.activatable = views::Widget::InitParams::ACTIVATABLE_YES; |
| // An approximate window size. Layout() can adjust. |
| params.bounds = gfx::Rect(kToastWidth, 120); |
| popup_ = new views::Widget; |
| popup_->AddObserver(this); |
| popup_->Init(params); |
| |
| auto contents_view = std::make_unique<ClickableView>(); |
| contents_view->SetBackground( |
| views::CreateSolidBackground(kTryChromeBackgroundColor)); |
| views::GridLayout* layout = contents_view->SetLayoutManager( |
| std::make_unique<views::GridLayout>(contents_view.get())); |
| layout->set_minimum_size(gfx::Size(kToastWidth, 0)); |
| views::ColumnSet* columns; |
| |
| context_->AddBorderToContents(popup_, contents_view.get()); |
| |
| // Padding around the left, top, and right of the logo. |
| static constexpr int kLogoPadding = 10; |
| static constexpr int kCloseButtonWidth = 24; |
| static constexpr int kCloseButtonTopPadding = 6; |
| static constexpr int kCloseButtonRightPadding = 5; |
| static constexpr int kSpacingAfterHeadingHorizontal = 40; |
| static constexpr int kSpacingHeadingToClose = kSpacingAfterHeadingHorizontal - |
| kCloseButtonWidth - |
| kCloseButtonRightPadding; |
| |
| // Padding around all sides of the text buttons (but not between them). |
| static constexpr int kTextButtonPadding = 12; |
| |
| // First two rows: [pad][logo][pad][text][pad][close button]. |
| // Only the close button is in the first row, spanning both. The logo and main |
| // header are in the second row. |
| const int kLabelWidth = kToastWidth - kLogoPadding - logo_size.width() - |
| kLogoPadding - kSpacingAfterHeadingHorizontal; |
| columns = layout->AddColumnSet(0); |
| columns->AddPaddingColumn(views::GridLayout::kFixedSize, |
| kLogoPadding - kTryChromeBorderThickness); |
| columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
| views::GridLayout::kFixedSize, views::GridLayout::FIXED, |
| logo_size.width(), logo_size.height()); |
| columns->AddPaddingColumn(views::GridLayout::kFixedSize, kLogoPadding); |
| columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1.0, |
| views::GridLayout::FIXED, kLabelWidth, 0); |
| columns->AddPaddingColumn(views::GridLayout::kFixedSize, |
| kSpacingHeadingToClose); |
| columns->AddColumn(views::GridLayout::LEADING, views::GridLayout::LEADING, |
| views::GridLayout::kFixedSize, views::GridLayout::USE_PREF, |
| 0, 0); |
| columns->AddPaddingColumn( |
| views::GridLayout::kFixedSize, |
| kCloseButtonRightPadding - kTryChromeBorderThickness); |
| |
| // Optional third row: [pad][text]. |
| const int logo_padding = logo_size.width() + kLogoPadding; |
| columns = layout->AddColumnSet(1); |
| columns->AddPaddingColumn( |
| views::GridLayout::kFixedSize, |
| kLogoPadding - kTryChromeBorderThickness + logo_padding); |
| columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1.0, |
| views::GridLayout::FIXED, kLabelWidth, 0); |
| |
| // Fourth row: [pad][buttons][pad]. |
| columns = layout->AddColumnSet(2); |
| columns->AddPaddingColumn(views::GridLayout::kFixedSize, |
| kTextButtonPadding - kTryChromeBorderThickness); |
| columns->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, |
| views::GridLayout::kFixedSize, views::GridLayout::USE_PREF, |
| 0, 0); |
| columns->AddPaddingColumn(views::GridLayout::kFixedSize, |
| kTextButtonPadding - kTryChromeBorderThickness); |
| |
| // First row. |
| layout->AddPaddingRow(views::GridLayout::kFixedSize, |
| kCloseButtonTopPadding - kTryChromeBorderThickness); |
| layout->StartRow(views::GridLayout::kFixedSize, 0, |
| kLogoPadding - kCloseButtonTopPadding); |
| layout->SkipColumns(1); |
| layout->SkipColumns(1); |
| // Close button if included in the variant. |
| if (kExperiments[group_].close_style == |
| ExperimentVariations::CloseStyle::kCloseX || |
| kExperiments[group_].close_style == |
| ExperimentVariations::CloseStyle::kNoThanksButtonAndCloseX) { |
| auto close_button = std::make_unique<views::ImageButton>(this); |
| close_button->SetImage( |
| views::Button::STATE_NORMAL, |
| gfx::CreateVectorIcon(kInactiveToastCloseIcon, kBodyColor)); |
| close_button->set_tag(static_cast<int>(ButtonTag::CLOSE_BUTTON)); |
| close_button_ = close_button.get(); |
| DCHECK_EQ(close_button->GetPreferredSize().width(), kCloseButtonWidth); |
| layout->AddView(close_button.release(), 1, 2); |
| close_button_->SetVisible(false); |
| } else { |
| layout->SkipColumns(1); |
| } |
| |
| // Second row. |
| layout->StartRow(views::GridLayout::kFixedSize, 0); |
| layout->AddView(logo.release()); |
| // All variants have a main header. |
| auto header = std::make_unique<views::Label>( |
| l10n_util::GetStringUTF16(kExperiments[group_].heading_id), |
| CONTEXT_WINDOWS10_NATIVE); |
| header->SetBackgroundColor(kTryChromeBackgroundColor); |
| header->SetEnabledColor(kHeaderColor); |
| header->SetMultiLine(true); |
| header->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| layout->AddView(header.release()); |
| layout->SkipColumns(1); |
| |
| // Third row: May have text or may be blank. |
| layout->StartRow(views::GridLayout::kFixedSize, 1); |
| const int body_string_id = kExperiments[group_].body_id; |
| if (body_string_id) { |
| auto body_text = std::make_unique<views::Label>( |
| l10n_util::GetStringUTF16(body_string_id), CONTEXT_WINDOWS10_NATIVE); |
| body_text->SetBackgroundColor(kTryChromeBackgroundColor); |
| body_text->SetEnabledColor(kBodyColor); |
| body_text->SetMultiLine(true); |
| body_text->SetHorizontalAlignment(gfx::ALIGN_LEFT); |
| layout->AddView(body_text.release()); |
| } |
| |
| // Fourth row: one or two buttons depending on group. |
| layout->AddPaddingRow(views::GridLayout::kFixedSize, kTextButtonPadding); |
| |
| static constexpr int kButtonsViewWidth = |
| kToastWidth - kTextButtonPadding - kTextButtonPadding; |
| auto buttons = std::make_unique<views::View>(); |
| buttons->SetLayoutManager(std::make_unique<ButtonLayout>(kButtonsViewWidth)); |
| |
| layout->StartRow(views::GridLayout::kFixedSize, 2); |
| auto accept_button = CreateWin10StyleButton( |
| this, l10n_util::GetStringUTF16(IDS_WIN10_TOAST_OPEN_CHROME), |
| TryChromeButtonType::OPEN_CHROME); |
| accept_button->set_tag(static_cast<int>(ButtonTag::OK_BUTTON)); |
| buttons->AddChildView(accept_button.release()); |
| |
| if (kExperiments[group_].close_style == |
| ExperimentVariations::CloseStyle::kNoThanksButton || |
| kExperiments[group_].close_style == |
| ExperimentVariations::CloseStyle::kNoThanksButtonAndCloseX) { |
| auto no_thanks_button = CreateWin10StyleButton( |
| this, l10n_util::GetStringUTF16(IDS_WIN10_TOAST_NO_THANKS), |
| TryChromeButtonType::NO_THANKS); |
| no_thanks_button->set_tag(static_cast<int>(ButtonTag::NO_THANKS_BUTTON)); |
| buttons->AddChildView(no_thanks_button.release()); |
| } |
| |
| layout->AddView(buttons.release()); |
| |
| layout->AddPaddingRow(views::GridLayout::kFixedSize, |
| kTextButtonPadding - kTryChromeBorderThickness); |
| |
| popup_->SetContentsView(contents_view.release()); |
| |
| // Compute the preferred size after attaching the contents view to the popup, |
| // as doing such causes the theme to propagate through the view hierarchy. |
| // This propagation can cause views to change their size requirements. |
| const gfx::Size preferred = popup_->GetContentsView()->GetPreferredSize(); |
| popup_->SetBounds(context_->ComputePopupBounds(popup_, preferred)); |
| popup_->SetAlwaysOnTop(true); |
| |
| popup_->ShowInactive(); |
| delegate_->SetToastLocation(context_->GetToastLocation()); |
| } |
| |
| void TryChromeDialog::CompleteInteraction() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| endsession_observer_.reset(); |
| delegate_->SetExperimentState(state_); |
| delegate_->InteractionComplete(); |
| } |
| |
| void TryChromeDialog::OnProcessNotification() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| |
| // Another browser process is trying to rendezvous with this one, which is |
| // either waiting on FindTaskbarIcon to complete or waiting on the user to |
| // interact with the dialog. In the former case, no attempt is made to stop |
| // the search, as it is expected to complete "quickly". When it does complete |
| // (in OnContextInitialized), processing will complete tout de suite. In the |
| // latter case, the dialog is closed so that processing will continue in |
| // OnWidgetDestroyed. OPEN_CHROME_DEFER conveys to this browser process that |
| // it should ignore its own command line and instead handle that provided by |
| // the other browser process. |
| result_ = OPEN_CHROME_DEFER; |
| state_ = installer::ExperimentMetrics::kOtherLaunch; |
| if (popup_) |
| popup_->Close(); |
| } |
| |
| void TryChromeDialog::OnWindowMessage(HWND window, |
| UINT message, |
| WPARAM wparam, |
| LPARAM lparam) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| |
| // wparam == FALSE means the system is not shutting down. |
| if (message != WM_ENDSESSION || wparam == FALSE) |
| return; |
| |
| // The ship is going down. Record the endsession event, but don't bother |
| // trying to close the window or take any other steps to shut down -- the OS |
| // will tear everything down soon enough. It's always possible that the |
| // endsession is aborted, in which case the dialog may as well stay onscreen. |
| result_ = NOT_NOW; |
| state_ = installer::ExperimentMetrics::kUserLogOff; |
| delegate_->SetExperimentState(state_); |
| } |
| |
| void TryChromeDialog::GainedMouseHover() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| if (close_button_) |
| close_button_->SetVisible(true); |
| } |
| |
| void TryChromeDialog::LostMouseHover() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| if (close_button_) |
| close_button_->SetVisible(false); |
| } |
| |
| void TryChromeDialog::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| DCHECK_EQ(result_, NOT_NOW); |
| |
| // Ignore this press if another press or a rendezvous has already been |
| // registered. |
| if (state_ != installer::ExperimentMetrics::kOtherClose) |
| return; |
| |
| // Figure out what the subsequent action and experiment state should be based |
| // on which button was pressed. |
| switch (sender->tag()) { |
| case static_cast<int>(ButtonTag::CLOSE_BUTTON): |
| state_ = installer::ExperimentMetrics::kSelectedClose; |
| break; |
| case static_cast<int>(ButtonTag::OK_BUTTON): |
| result_ = kExperiments[group_].result; |
| state_ = installer::ExperimentMetrics::kSelectedOpenChromeAndNoCrash; |
| break; |
| case static_cast<int>(ButtonTag::NO_THANKS_BUTTON): |
| state_ = installer::ExperimentMetrics::kSelectedNoThanks; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| popup_->Close(); |
| } |
| |
| void TryChromeDialog::OnWidgetClosing(views::Widget* widget) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| DCHECK_EQ(widget, popup_); |
| |
| popup_->GetNativeWindow()->RemovePreTargetHandler(this); |
| } |
| |
| void TryChromeDialog::OnWidgetCreated(views::Widget* widget) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| DCHECK_EQ(widget, popup_); |
| |
| popup_->GetNativeWindow()->AddPreTargetHandler(this); |
| } |
| |
| void TryChromeDialog::OnWidgetDestroyed(views::Widget* widget) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| DCHECK_EQ(widget, popup_); |
| |
| popup_->RemoveObserver(this); |
| popup_ = nullptr; |
| close_button_ = nullptr; |
| |
| CompleteInteraction(); |
| } |
| |
| void TryChromeDialog::OnMouseEvent(ui::MouseEvent* event) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(my_sequence_checker_); |
| DCHECK(popup_); |
| |
| switch (event->type()) { |
| // A MOUSE_ENTERED event is received if the mouse is over the dialog when it |
| // opens. |
| case ui::ET_MOUSE_ENTERED: |
| case ui::ET_MOUSE_MOVED: |
| if (!has_hover_) { |
| has_hover_ = true; |
| GainedMouseHover(); |
| } |
| break; |
| case ui::ET_MOUSE_EXITED: |
| if (has_hover_) { |
| has_hover_ = false; |
| LostMouseHover(); |
| } |
| break; |
| case ui::ET_MOUSE_CAPTURE_CHANGED: |
| if (has_hover_ && !display::Screen::GetScreen()->IsWindowUnderCursor( |
| popup_->GetNativeWindow())) { |
| has_hover_ = false; |
| LostMouseHover(); |
| } |
| break; |
| default: |
| break; |
| } |
| } |