| // 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 "ash/wm/caption_buttons/frame_caption_button_container_view.h" |
| |
| #include "ash/ash_switches.h" |
| #include "ash/shell.h" |
| #include "ash/shell_delegate.h" |
| #include "ash/wm/caption_buttons/alternate_frame_size_button.h" |
| #include "ash/wm/caption_buttons/frame_caption_button.h" |
| #include "ash/wm/caption_buttons/frame_maximize_button.h" |
| #include "grit/ash_resources.h" |
| #include "grit/ui_strings.h" // Accessibility names |
| #include "ui/base/hit_test.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/gfx/canvas.h" |
| #include "ui/gfx/insets.h" |
| #include "ui/gfx/point.h" |
| #include "ui/views/controls/button/image_button.h" |
| #include "ui/views/widget/widget.h" |
| #include "ui/views/widget/widget_delegate.h" |
| |
| namespace ash { |
| |
| namespace { |
| |
| // The distance between buttons. |
| const int kDistanceBetweenButtons = -1; |
| |
| // Converts |point| from |src| to |dst| and hittests against |dst|. |
| bool ConvertPointToViewAndHitTest(const views::View* src, |
| const views::View* dst, |
| const gfx::Point& point) { |
| gfx::Point converted(point); |
| views::View::ConvertPointToTarget(src, dst, &converted); |
| return dst->HitTestPoint(converted); |
| } |
| |
| } // namespace |
| |
| // static |
| const char FrameCaptionButtonContainerView::kViewClassName[] = |
| "FrameCaptionButtonContainerView"; |
| |
| FrameCaptionButtonContainerView::FrameCaptionButtonContainerView( |
| views::Widget* frame, |
| MinimizeAllowed minimize_allowed) |
| : frame_(frame), |
| header_style_(HEADER_STYLE_SHORT), |
| minimize_button_(NULL), |
| size_button_(NULL), |
| close_button_(NULL) { |
| bool alternate_style = switches::UseAlternateFrameCaptionButtonStyle(); |
| |
| // Insert the buttons left to right. |
| minimize_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_MINIMIZE); |
| minimize_button_->SetAccessibleName( |
| l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MINIMIZE)); |
| // Hide |minimize_button_| when using the non-alternate button style because |
| // |size_button_| is capable of minimizing in this case. |
| minimize_button_->SetVisible( |
| minimize_allowed == MINIMIZE_ALLOWED && |
| (alternate_style || !frame_->widget_delegate()->CanMaximize())); |
| AddChildView(minimize_button_); |
| |
| if (alternate_style) |
| size_button_ = new AlternateFrameSizeButton(this, frame, this); |
| else |
| size_button_ = new FrameMaximizeButton(this, frame); |
| size_button_->SetAccessibleName( |
| l10n_util::GetStringUTF16(IDS_APP_ACCNAME_MAXIMIZE)); |
| size_button_->SetVisible(frame_->widget_delegate()->CanMaximize()); |
| AddChildView(size_button_); |
| |
| close_button_ = new FrameCaptionButton(this, CAPTION_BUTTON_ICON_CLOSE); |
| close_button_->SetAccessibleName( |
| l10n_util::GetStringUTF16(IDS_APP_ACCNAME_CLOSE)); |
| AddChildView(close_button_); |
| |
| button_separator_ = ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| IDR_AURA_WINDOW_BUTTON_SEPARATOR).AsImageSkia(); |
| } |
| |
| FrameCaptionButtonContainerView::~FrameCaptionButtonContainerView() { |
| } |
| |
| FrameMaximizeButton* |
| FrameCaptionButtonContainerView::GetOldStyleSizeButton() { |
| return switches::UseAlternateFrameCaptionButtonStyle() ? |
| NULL : static_cast<FrameMaximizeButton*>(size_button_); |
| } |
| |
| void FrameCaptionButtonContainerView::ResetWindowControls() { |
| SetButtonsToNormal(ANIMATE_NO); |
| } |
| |
| int FrameCaptionButtonContainerView::NonClientHitTest( |
| const gfx::Point& point) const { |
| if (close_button_->visible() && |
| ConvertPointToViewAndHitTest(this, close_button_, point)) { |
| return HTCLOSE; |
| } else if (size_button_->visible() && |
| ConvertPointToViewAndHitTest(this, size_button_, point)) { |
| return HTMAXBUTTON; |
| } else if (minimize_button_->visible() && |
| ConvertPointToViewAndHitTest(this, minimize_button_, point)) { |
| return HTMINBUTTON; |
| } |
| return HTNOWHERE; |
| } |
| |
| gfx::Size FrameCaptionButtonContainerView::GetPreferredSize() { |
| int width = 0; |
| bool first_visible = true; |
| for (int i = 0; i < child_count(); ++i) { |
| views::View* child = child_at(i); |
| if (!child->visible()) |
| continue; |
| |
| width += child_at(i)->GetPreferredSize().width(); |
| if (!first_visible) |
| width += kDistanceBetweenButtons; |
| first_visible = false; |
| } |
| return gfx::Size(width, close_button_->GetPreferredSize().height()); |
| } |
| |
| void FrameCaptionButtonContainerView::Layout() { |
| FrameCaptionButton::Style style = FrameCaptionButton::STYLE_SHORT_RESTORED; |
| if (header_style_ == HEADER_STYLE_SHORT) { |
| if (frame_->IsMaximized() || frame_->IsFullscreen()) |
| style = FrameCaptionButton::STYLE_SHORT_MAXIMIZED_OR_FULLSCREEN; |
| // Else: FrameCaptionButton::STYLE_SHORT_RESTORED; |
| } else { |
| style = FrameCaptionButton::STYLE_TALL_RESTORED; |
| } |
| |
| minimize_button_->SetStyle(style); |
| size_button_->SetStyle(style); |
| close_button_->SetStyle(style); |
| |
| int x = 0; |
| for (int i = 0; i < child_count(); ++i) { |
| views::View* child = child_at(i); |
| if (!child->visible()) |
| continue; |
| |
| gfx::Size size = child->GetPreferredSize(); |
| child->SetBounds(x, 0, size.width(), size.height()); |
| x += size.width() + kDistanceBetweenButtons; |
| } |
| } |
| |
| const char* FrameCaptionButtonContainerView::GetClassName() const { |
| return kViewClassName; |
| } |
| |
| void FrameCaptionButtonContainerView::OnPaint(gfx::Canvas* canvas) { |
| views::View::OnPaint(canvas); |
| |
| // The alternate button style does not paint the button separator. |
| if (!switches::UseAlternateFrameCaptionButtonStyle()) { |
| // We should have at most two visible buttons. The button separator is |
| // always painted underneath the close button regardless of whether a |
| // button other than the close button is visible. |
| gfx::Rect divider(close_button_->bounds().origin(), |
| button_separator_.size()); |
| canvas->DrawImageInt(button_separator_, |
| GetMirroredXForRect(divider), |
| divider.y()); |
| } |
| } |
| |
| void FrameCaptionButtonContainerView::ButtonPressed(views::Button* sender, |
| const ui::Event& event) { |
| // When shift-clicking, slow down animations for visual debugging. |
| // We used to do this via an event filter that looked for the shift key being |
| // pressed but this interfered with several normal keyboard shortcuts. |
| scoped_ptr<ui::ScopedAnimationDurationScaleMode> slow_duration_mode; |
| if (event.IsShiftDown()) { |
| slow_duration_mode.reset(new ui::ScopedAnimationDurationScaleMode( |
| ui::ScopedAnimationDurationScaleMode::SLOW_DURATION)); |
| } |
| |
| // Abort any animations of the button icons. |
| SetButtonsToNormal(ANIMATE_NO); |
| |
| ash::UserMetricsAction action = |
| ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MINIMIZE; |
| if (sender == minimize_button_) { |
| frame_->Minimize(); |
| } else if (sender == size_button_) { |
| if (frame_->IsFullscreen()) { // Can be clicked in immersive fullscreen. |
| frame_->SetFullscreen(false); |
| action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_EXIT_FULLSCREEN; |
| } else if (frame_->IsMaximized()) { |
| frame_->Restore(); |
| action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_RESTORE; |
| } else { |
| frame_->Maximize(); |
| action = ash::UMA_WINDOW_MAXIMIZE_BUTTON_CLICK_MAXIMIZE; |
| } |
| } else if(sender == close_button_) { |
| frame_->Close(); |
| action = ash::UMA_WINDOW_CLOSE_BUTTON_CLICK; |
| } else { |
| return; |
| } |
| ash::Shell::GetInstance()->delegate()->RecordUserMetricsAction(action); |
| } |
| |
| bool FrameCaptionButtonContainerView::IsMinimizeButtonVisible() const { |
| return minimize_button_->visible(); |
| } |
| |
| void FrameCaptionButtonContainerView::SetButtonsToNormal(Animate animate) { |
| SetButtonIcons(CAPTION_BUTTON_ICON_MINIMIZE, CAPTION_BUTTON_ICON_CLOSE, |
| animate); |
| minimize_button_->SetState(views::Button::STATE_NORMAL); |
| size_button_->SetState(views::Button::STATE_NORMAL); |
| close_button_->SetState(views::Button::STATE_NORMAL); |
| } |
| |
| void FrameCaptionButtonContainerView::SetButtonIcons( |
| CaptionButtonIcon minimize_button_icon, |
| CaptionButtonIcon close_button_icon, |
| Animate animate) { |
| FrameCaptionButton::Animate fcb_animate = (animate == ANIMATE_YES) ? |
| FrameCaptionButton::ANIMATE_YES : FrameCaptionButton::ANIMATE_NO; |
| minimize_button_->SetIcon(minimize_button_icon, fcb_animate); |
| close_button_->SetIcon(close_button_icon, fcb_animate); |
| } |
| |
| const FrameCaptionButton* |
| FrameCaptionButtonContainerView::PressButtonAt( |
| const gfx::Point& position_in_screen, |
| const gfx::Insets& pressed_hittest_outer_insets) const { |
| DCHECK(switches::UseAlternateFrameCaptionButtonStyle()); |
| gfx::Point position(position_in_screen); |
| views::View::ConvertPointFromScreen(this, &position); |
| |
| FrameCaptionButton* buttons[] = { |
| close_button_, size_button_, minimize_button_ |
| }; |
| FrameCaptionButton* pressed_button = NULL; |
| for (size_t i = 0; i < arraysize(buttons); ++i) { |
| FrameCaptionButton* button = buttons[i]; |
| if (!button->visible()) |
| continue; |
| |
| if (button->state() == views::Button::STATE_PRESSED) { |
| gfx::Rect expanded_bounds = button->bounds(); |
| expanded_bounds.Inset(pressed_hittest_outer_insets); |
| if (expanded_bounds.Contains(position)) { |
| pressed_button = button; |
| // Do not break in order to give preference to buttons which are |
| // closer to |position_in_screen| than the currently pressed button. |
| // TODO(pkotwicz): Make the caption buttons not overlap. |
| } |
| } else if (ConvertPointToViewAndHitTest(this, button, position)) { |
| pressed_button = button; |
| break; |
| } |
| } |
| |
| for (size_t i = 0; i < arraysize(buttons); ++i) { |
| buttons[i]->SetState(buttons[i] == pressed_button ? |
| views::Button::STATE_PRESSED : views::Button::STATE_NORMAL); |
| } |
| return pressed_button; |
| } |
| |
| } // namespace ash |