diff options
Diffstat (limited to 'chrome/views/controls/button')
18 files changed, 2621 insertions, 0 deletions
diff --git a/chrome/views/controls/button/button.cc b/chrome/views/controls/button/button.cc new file mode 100644 index 0000000..5c28138 --- /dev/null +++ b/chrome/views/controls/button/button.cc @@ -0,0 +1,71 @@ +// 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/controls/button/button.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// Button, public: + +Button::~Button() { +} + +void Button::SetTooltipText(const std::wstring& tooltip_text) { + tooltip_text_ = tooltip_text; + TooltipTextChanged(); +} + +//////////////////////////////////////////////////////////////////////////////// +// Button, View overrides: + +bool Button::GetTooltipText(int x, int y, std::wstring* tooltip) { + if (!tooltip_text_.empty()) { + *tooltip = tooltip_text_; + return true; + } + return false; +} + +bool Button::GetAccessibleKeyboardShortcut(std::wstring* shortcut) { + if (!accessible_shortcut_.empty()) { + *shortcut = accessible_shortcut_; + return true; + } + return false; +} + +bool Button::GetAccessibleName(std::wstring* name) { + if (!accessible_name_.empty()) { + *name = accessible_name_; + return true; + } + return false; +} + +void Button::SetAccessibleKeyboardShortcut(const std::wstring& shortcut) { + accessible_shortcut_.assign(shortcut); +} + +void Button::SetAccessibleName(const std::wstring& name) { + accessible_name_.assign(name); +} + +//////////////////////////////////////////////////////////////////////////////// +// Button, protected: + +Button::Button(ButtonListener* listener) + : listener_(listener), + tag_(-1), + mouse_event_flags_(0) { +} + +void Button::NotifyClick(int mouse_event_flags) { + mouse_event_flags_ = mouse_event_flags; + listener_->ButtonPressed(this); + // NOTE: don't attempt to reset mouse_event_flags_ as the listener may have + // deleted us. +} + +} // namespace views diff --git a/chrome/views/controls/button/button.h b/chrome/views/controls/button/button.h new file mode 100644 index 0000000..1f6514b --- /dev/null +++ b/chrome/views/controls/button/button.h @@ -0,0 +1,74 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_BUTTON_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_BUTTON_H_ + +#include "chrome/views/view.h" + +namespace views { + +class Button; + +// An interface implemented by an object to let it know that a button was +// pressed. +class ButtonListener { + public: + virtual void ButtonPressed(Button* sender) = 0; +}; + +// A View representing a button. Depending on the specific type, the button +// could be implemented by a native control or custom rendered. +class Button : public View { + public: + virtual ~Button(); + + void SetTooltipText(const std::wstring& tooltip_text); + + int tag() const { return tag_; } + void set_tag(int tag) { tag_ = tag; } + + int mouse_event_flags() const { return mouse_event_flags_; } + + // Overridden from View: + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + virtual bool GetAccessibleKeyboardShortcut(std::wstring* shortcut); + virtual bool GetAccessibleName(std::wstring* name); + virtual void SetAccessibleKeyboardShortcut(const std::wstring& shortcut); + virtual void SetAccessibleName(const std::wstring& name); + + protected: + // Construct the Button with a Listener. The listener can be NULL, as long as + // the specific button implementation makes sure to not call NotifyClick. This + // can be true of buttons that don't have a listener - e.g. menubuttons where + // there's no default action. + explicit Button(ButtonListener* listener); + + // Cause the button to notify the listener that a click occurred. + virtual void NotifyClick(int mouse_event_flags); + + private: + // The text shown in a tooltip. + std::wstring tooltip_text_; + + // Accessibility data. + std::wstring accessible_shortcut_; + std::wstring accessible_name_; + + // The button's listener. Notified when clicked. + ButtonListener* listener_; + + // The id tag associated with this button. Used to disambiguate buttons in + // the ButtonListener implementation. + int tag_; + + // Event flags present when the button was clicked. + int mouse_event_flags_; + + DISALLOW_COPY_AND_ASSIGN(Button); +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_BUTTON_H_ diff --git a/chrome/views/controls/button/button_dropdown.cc b/chrome/views/controls/button/button_dropdown.cc new file mode 100644 index 0000000..c2ea7cc --- /dev/null +++ b/chrome/views/controls/button/button_dropdown.cc @@ -0,0 +1,193 @@ +// 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/controls/button/button_dropdown.h" + +#include "base/message_loop.h" +#include "chrome/common/l10n_util.h" +#include "chrome/views/controls/menu/view_menu_delegate.h" +#include "chrome/views/widget/widget.h" +#include "grit/generated_resources.h" + +namespace views { + +// How long to wait before showing the menu +static const int kMenuTimerDelay = 500; + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown - constructors, destructors, initialization, cleanup +// +//////////////////////////////////////////////////////////////////////////////// + +ButtonDropDown::ButtonDropDown(ButtonListener* listener, + Menu::Delegate* menu_delegate) + : ImageButton(listener), + menu_delegate_(menu_delegate), + y_position_on_lbuttondown_(0), + show_menu_factory_(this) { +} + +ButtonDropDown::~ButtonDropDown() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown - Events +// +//////////////////////////////////////////////////////////////////////////////// + +bool ButtonDropDown::OnMousePressed(const MouseEvent& e) { + if (IsEnabled() && e.IsLeftMouseButton() && HitTest(e.location())) { + // Store the y pos of the mouse coordinates so we can use them later to + // determine if the user dragged the mouse down (which should pop up the + // drag down menu immediately, instead of waiting for the timer) + y_position_on_lbuttondown_ = e.y(); + + // Schedule a task that will show the menu. + MessageLoop::current()->PostDelayedTask(FROM_HERE, + show_menu_factory_.NewRunnableMethod(&ButtonDropDown::ShowDropDownMenu, + GetWidget()->GetNativeView()), + kMenuTimerDelay); + } + + return ImageButton::OnMousePressed(e); +} + +void ButtonDropDown::OnMouseReleased(const MouseEvent& e, bool canceled) { + ImageButton::OnMouseReleased(e, canceled); + + if (canceled) + return; + + if (e.IsLeftMouseButton()) + show_menu_factory_.RevokeAll(); + + if (IsEnabled() && e.IsRightMouseButton() && HitTest(e.location())) { + show_menu_factory_.RevokeAll(); + // Make the button look depressed while the menu is open. + // NOTE: SetState() schedules a paint, but it won't occur until after the + // context menu message loop has terminated, so we PaintNow() to + // update the appearance synchronously. + SetState(BS_PUSHED); + PaintNow(); + ShowDropDownMenu(GetWidget()->GetNativeView()); + } +} + +bool ButtonDropDown::OnMouseDragged(const MouseEvent& e) { + bool result = ImageButton::OnMouseDragged(e); + + if (!show_menu_factory_.empty()) { + // SM_CYDRAG is a pixel value for minimum dragging distance before operation + // counts as a drag, and not just as a click and accidental move of a mouse. + // See http://msdn2.microsoft.com/en-us/library/ms724385.aspx for details. + int dragging_threshold = GetSystemMetrics(SM_CYDRAG); + + // If the mouse is dragged to a y position lower than where it was when + // clicked then we should not wait for the menu to appear but show + // it immediately. + if (e.y() > y_position_on_lbuttondown_ + dragging_threshold) { + show_menu_factory_.RevokeAll(); + ShowDropDownMenu(GetWidget()->GetNativeView()); + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown - Menu functions +// +//////////////////////////////////////////////////////////////////////////////// + +void ButtonDropDown::ShowContextMenu(int x, int y, bool is_mouse_gesture) { + show_menu_factory_.RevokeAll(); + // Make the button look depressed while the menu is open. + // NOTE: SetState() schedules a paint, but it won't occur until after the + // context menu message loop has terminated, so we PaintNow() to + // update the appearance synchronously. + SetState(BS_PUSHED); + PaintNow(); + ShowDropDownMenu(GetWidget()->GetNativeView()); + SetState(BS_HOT); +} + +void ButtonDropDown::ShowDropDownMenu(HWND window) { + if (menu_delegate_) { + gfx::Rect lb = GetLocalBounds(true); + + // Both the menu position and the menu anchor type change if the UI layout + // is right-to-left. + gfx::Point menu_position(lb.origin()); + menu_position.Offset(0, lb.height() - 1); + if (UILayoutIsRightToLeft()) + menu_position.Offset(lb.width() - 1, 0); + + Menu::AnchorPoint anchor = Menu::TOPLEFT; + if (UILayoutIsRightToLeft()) + anchor = Menu::TOPRIGHT; + + View::ConvertPointToScreen(this, &menu_position); + + int left_bound = GetSystemMetrics(SM_XVIRTUALSCREEN); + if (menu_position.x() < left_bound) + menu_position.set_x(left_bound); + + Menu menu(menu_delegate_, anchor, window); + + // ID's for AppendMenu is 1-based because RunMenu will ignore the user + // selection if id=0 is selected (0 = NO-OP) so we add 1 here and subtract 1 + // in the handlers above to get the actual index + int item_count = menu_delegate_->GetItemCount(); + for (int i = 0; i < item_count; i++) { + if (menu_delegate_->IsItemSeparator(i + 1)) { + menu.AppendSeparator(); + } else { + if (menu_delegate_->HasIcon(i + 1)) + menu.AppendMenuItemWithIcon(i + 1, L"", SkBitmap()); + else + menu.AppendMenuItem(i+1, L"", Menu::NORMAL); + } + } + + menu.RunMenuAt(menu_position.x(), menu_position.y()); + + // Need to explicitly clear mouse handler so that events get sent + // properly after the menu finishes running. If we don't do this, then + // the first click to other parts of the UI is eaten. + SetMouseHandler(NULL); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown - Accessibility +// +//////////////////////////////////////////////////////////////////////////////// + +bool ButtonDropDown::GetAccessibleDefaultAction(std::wstring* action) { + DCHECK(action); + + action->assign(l10n_util::GetString(IDS_ACCACTION_PRESS)); + return true; +} + +bool ButtonDropDown::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_BUTTONDROPDOWN; + return true; +} + +bool ButtonDropDown::GetAccessibleState(VARIANT* state) { + DCHECK(state); + + state->lVal |= STATE_SYSTEM_HASPOPUP; + return true; +} + +} // namespace views diff --git a/chrome/views/controls/button/button_dropdown.h b/chrome/views/controls/button/button_dropdown.h new file mode 100644 index 0000000..b46158b --- /dev/null +++ b/chrome/views/controls/button/button_dropdown.h @@ -0,0 +1,72 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_ + +#include "base/task.h" +#include "chrome/views/controls/button/image_button.h" +#include "chrome/views/controls/menu/menu.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// +// ButtonDropDown +// +// A button class that when pressed (and held) or pressed (and drag down) will +// display a menu +// +//////////////////////////////////////////////////////////////////////////////// +class ButtonDropDown : public ImageButton { + public: + ButtonDropDown(ButtonListener* listener, Menu::Delegate* menu_delegate); + virtual ~ButtonDropDown(); + + // Returns the MSAA default action of the current view. The string returned + // describes the default action that will occur when executing + // IAccessible::DoDefaultAction. + bool GetAccessibleDefaultAction(std::wstring* action); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns the MSAA state of the current view. Sets the input VARIANT + // appropriately, and returns true if a change was performed successfully. + // Overriden from View. + virtual bool GetAccessibleState(VARIANT* state); + + private: + // Overridden from Button + virtual bool OnMousePressed(const MouseEvent& e); + virtual void OnMouseReleased(const MouseEvent& e, bool canceled); + virtual bool OnMouseDragged(const MouseEvent& e); + + // Overridden from View. Used to display the right-click menu, as triggered + // by the keyboard, for instance. Using the member function ShowDropDownMenu + // for the actual display. + virtual void ShowContextMenu(int x, + int y, + bool is_mouse_gesture); + + // Internal function to show the dropdown menu + void ShowDropDownMenu(HWND window); + + // Specifies who to delegate populating the menu + Menu::Delegate* menu_delegate_; + + // Y position of mouse when left mouse button is pressed + int y_position_on_lbuttondown_; + + // A factory for tasks that show the dropdown context menu for the button. + ScopedRunnableMethodFactory<ButtonDropDown> show_menu_factory_; + + DISALLOW_COPY_AND_ASSIGN(ButtonDropDown); +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_ diff --git a/chrome/views/controls/button/checkbox.cc b/chrome/views/controls/button/checkbox.cc new file mode 100644 index 0000000..c5463de --- /dev/null +++ b/chrome/views/controls/button/checkbox.cc @@ -0,0 +1,181 @@ +// 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/controls/button/checkbox.h" + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/views/controls/button/checkbox.h" +#include "chrome/views/controls/hwnd_view.h" +#include "chrome/views/controls/label.h" + +// FIXME(ACW) there got be a better way to find out the check box sizes +static int kCheckBoxWidth = 13; +static int kCheckBoxHeight = 13; +static int kCheckBoxToLabel = 4; + +namespace views { + +// Horizontal focus padding. +const int CheckBox::kFocusPaddingHorizontal = 2; +const int CheckBox::kFocusPaddingVertical = 1; + +const char CheckBox::kViewClassName[] = "chrome/views/CheckBox"; + +CheckBox::CheckBox(const std::wstring& label) + : NativeButton(label), + is_selected_(false) { + // Note: we paint the label as a floating view + SetMinSizeFromDLUs(gfx::Size(0, 0)); + label_ = new Label(label); + label_->SetHorizontalAlignment(Label::ALIGN_LEFT); +} + +CheckBox::~CheckBox() { + delete label_; +} + +void CheckBox::SetMultiLine(bool multi_line) { + label_->SetMultiLine(multi_line); +} + +// static +int CheckBox::GetTextIndent() { + return kCheckBoxWidth + kCheckBoxToLabel + kFocusPaddingHorizontal; +} + +void CheckBox::SetIsSelected(bool f) { + if (f != is_selected_) { + is_selected_ = f; + UpdateNativeButton(); + } +} + +bool CheckBox::IsSelected() const { + return is_selected_; +} + +std::string CheckBox::GetClassName() const { + return kViewClassName; +} + +void CheckBox::Layout() { + int label_x = GetTextIndent(); + label_->SetBounds(label_x, 0, std::max(0, width() - label_x), height()); + if (hwnd_view_) { + int first_line_height = label_->GetFont().height(); + hwnd_view_->SetBounds(0, ((first_line_height - kCheckBoxHeight) / 2) + 1, + kCheckBoxWidth, kCheckBoxHeight); + hwnd_view_->UpdateHWNDBounds(); + } +} + +void CheckBox::ComputeTextRect(gfx::Rect* out) { + gfx::Size s = label_->GetPreferredSize(); + out->set_x(GetTextIndent()); + out->set_y(kFocusPaddingVertical); + int new_width = std::min(width() - (kCheckBoxWidth + kCheckBoxToLabel), + s.width()); + out->set_width(std::max(0, new_width)); + out->set_height(s.height()); +} + +void CheckBox::Paint(ChromeCanvas* canvas) { + gfx::Rect r; + ComputeTextRect(&r); + // Paint the focus border if any. + if (HasFocus()) { + // Mirror left point for rectangle to draw focus for RTL text. + canvas->DrawFocusRect(MirroredLeftPointForRect(r) - kFocusPaddingHorizontal, + r.y() - kFocusPaddingVertical, + r.width() + kFocusPaddingHorizontal * 2, + r.height() + kFocusPaddingVertical * 2); + } + PaintFloatingView(canvas, label_, r.x(), r.y(), r.width(), r.height()); +} + +void CheckBox::SetEnabled(bool enabled) { + if (enabled_ == enabled) + return; + NativeButton::SetEnabled(enabled); + label_->SetEnabled(enabled); +} + +HWND CheckBox::CreateNativeControl(HWND parent_container) { + HWND r = ::CreateWindowEx(WS_EX_TRANSPARENT | GetAdditionalExStyle(), + L"BUTTON", + L"", + WS_CHILD | BS_CHECKBOX | WS_VISIBLE, + 0, 0, width(), height(), + parent_container, NULL, NULL, NULL); + ConfigureNativeButton(r); + return r; +} + +void CheckBox::ConfigureNativeButton(HWND hwnd) { + ::SendMessage(hwnd, + static_cast<UINT>(BM_SETCHECK), + static_cast<WPARAM>(is_selected_ ? BST_CHECKED : BST_UNCHECKED), + 0); + label_->SetText(GetLabel()); +} + +gfx::Size CheckBox::GetPreferredSize() { + gfx::Size prefsize = label_->GetPreferredSize(); + prefsize.set_height(std::max(prefsize.height() + kFocusPaddingVertical * 2, + kCheckBoxHeight)); + prefsize.Enlarge(GetTextIndent() * 2, 0); + return prefsize; +} + +LRESULT CheckBox::OnCommand(UINT code, int id, HWND source) { + if (code == BN_CLICKED) + SetIsSelected(!is_selected_); + + return NativeButton::OnCommand(code, id, source); +} + +void CheckBox::HighlightButton(bool f) { + ::SendMessage(GetNativeControlHWND(), + static_cast<UINT>(BM_SETSTATE), + static_cast<WPARAM>(f), + 0); +} + +bool CheckBox::LabelHitTest(const MouseEvent& event) { + CPoint p(event.x(), event.y()); + gfx::Rect r; + ComputeTextRect(&r); + return r.Contains(event.x(), event.y()); +} + +void CheckBox::OnMouseEntered(const MouseEvent& event) { + HighlightButton(LabelHitTest(event)); +} + +void CheckBox::OnMouseMoved(const MouseEvent& event) { + HighlightButton(LabelHitTest(event)); +} + +void CheckBox::OnMouseExited(const MouseEvent& event) { + HighlightButton(false); +} + +bool CheckBox::OnMousePressed(const MouseEvent& event) { + HighlightButton(LabelHitTest(event)); + return true; +} + +bool CheckBox::OnMouseDragged(const MouseEvent& event) { + HighlightButton(LabelHitTest(event)); + return true; +} + +void CheckBox::OnMouseReleased(const MouseEvent& event, + bool canceled) { + HighlightButton(false); + if (!canceled && LabelHitTest(event)) + OnCommand(BN_CLICKED, 0, GetNativeControlHWND()); +} + +} // namespace views diff --git a/chrome/views/controls/button/checkbox.h b/chrome/views/controls/button/checkbox.h new file mode 100644 index 0000000..78c006b --- /dev/null +++ b/chrome/views/controls/button/checkbox.h @@ -0,0 +1,85 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_CHECKBOX_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_CHECKBOX_H_ + +#include "base/gfx/rect.h" +#include "chrome/views/controls/button/native_button.h" + +namespace views { + +class Label; + +//////////////////////////////////////////////////////////////////////////////// +// +// CheckBox implements a check box button. It uses the standard windows control +// for the check item but not for the label. We have to do this because windows +// always wants to draw a background under the label. I tried to intercept +// WM_CTLCOLORSTATIC and return a NULL_BRUSH and setting the BkMode to +// transparent as well as other things. The background was always drawn as solid +// black. +// +// The label is implemented with a views::Label +// +//////////////////////////////////////////////////////////////////////////////// +class CheckBox : public NativeButton { + public: + static const char kViewClassName[]; + static const int kFocusPaddingHorizontal; + static const int kFocusPaddingVertical; + + explicit CheckBox(const std::wstring& label); + virtual ~CheckBox(); + + // Allows the label to wrap across multiple lines if |multi_line| is true. + // If false, the text is cropped. + void SetMultiLine(bool multi_line); + + // Returns the x position of the text. This can also be used to indent + // subsequent dependent controls. + static int GetTextIndent(); + + virtual void SetIsSelected(bool f); + bool IsSelected() const; + + virtual std::string GetClassName() const; + + virtual gfx::Size GetPreferredSize(); + virtual void Layout(); + + virtual bool OnMousePressed(const MouseEvent& event); + virtual bool OnMouseDragged(const MouseEvent& event); + virtual void OnMouseReleased(const MouseEvent& event, bool canceled); + virtual void OnMouseEntered(const MouseEvent& event); + virtual void OnMouseMoved(const MouseEvent& event); + virtual void OnMouseExited(const MouseEvent& event); + + virtual void Paint(ChromeCanvas* canvas); + + // Overriden to forward to the label too. + virtual void SetEnabled(bool enabled); + + protected: + + virtual HWND CreateNativeControl(HWND parent_container); + virtual void ConfigureNativeButton(HWND hwnd); + virtual LRESULT OnCommand(UINT code, int id, HWND source); + + Label* label_; + private: + + void HighlightButton(bool f); + bool LabelHitTest(const MouseEvent& event); + void ComputeTextRect(gfx::Rect* out); + + bool is_selected_; + + + DISALLOW_EVIL_CONSTRUCTORS(CheckBox); +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_CHECKBOX_H_ diff --git a/chrome/views/controls/button/custom_button.cc b/chrome/views/controls/button/custom_button.cc new file mode 100644 index 0000000..04e0a50 --- /dev/null +++ b/chrome/views/controls/button/custom_button.cc @@ -0,0 +1,239 @@ +// 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/controls/button/custom_button.h" + +#include "base/base_drag_source.h" +#include "chrome/browser/drag_utils.h" +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/os_exchange_data.h" +#include "chrome/common/throb_animation.h" + +namespace views { + +// How long the hover animation takes if uninterrupted. +static const int kHoverFadeDurationMs = 150; + +//////////////////////////////////////////////////////////////////////////////// +// CustomButton, public: + +CustomButton::~CustomButton() { +} + +void CustomButton::SetState(ButtonState state) { + if (state != state_) { + if (animate_on_state_change_ || !hover_animation_->IsAnimating()) { + animate_on_state_change_ = true; + if (state_ == BS_NORMAL && state == BS_HOT) { + // Button is hovered from a normal state, start hover animation. + hover_animation_->Show(); + } else if (state_ == BS_HOT && state == BS_NORMAL) { + // Button is returning to a normal state from hover, start hover + // fade animation. + hover_animation_->Hide(); + } else { + hover_animation_->Stop(); + } + } + + state_ = state; + SchedulePaint(); + } +} + +void CustomButton::StartThrobbing(int cycles_til_stop) { + animate_on_state_change_ = false; + hover_animation_->StartThrobbing(cycles_til_stop); +} + +void CustomButton::SetAnimationDuration(int duration) { + hover_animation_->SetSlideDuration(duration); +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomButton, View overrides: + +void CustomButton::SetEnabled(bool enabled) { + if (enabled && state_ == BS_DISABLED) { + SetState(BS_NORMAL); + } else if (!enabled && state_ != BS_DISABLED) { + SetState(BS_DISABLED); + } +} + +bool CustomButton::IsEnabled() const { + return state_ != BS_DISABLED; +} + +bool CustomButton::IsFocusable() const { + return (state_ != BS_DISABLED) && View::IsFocusable(); +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomButton, protected: + +CustomButton::CustomButton(ButtonListener* listener) + : Button(listener), + state_(BS_NORMAL), + animate_on_state_change_(true) { + hover_animation_.reset(new ThrobAnimation(this)); + hover_animation_->SetSlideDuration(kHoverFadeDurationMs); +} + +bool CustomButton::IsTriggerableEvent(const MouseEvent& e) { + return e.IsLeftMouseButton(); +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomButton, View overrides (protected): + +bool CustomButton::AcceleratorPressed(const Accelerator& accelerator) { + if (enabled_) { + SetState(BS_NORMAL); + NotifyClick(0); + return true; + } + return false; +} + +bool CustomButton::OnMousePressed(const MouseEvent& e) { + if (state_ != BS_DISABLED) { + if (IsTriggerableEvent(e) && HitTest(e.location())) + SetState(BS_PUSHED); + RequestFocus(); + } + return true; +} + +bool CustomButton::OnMouseDragged(const MouseEvent& e) { + if (state_ != BS_DISABLED) { + if (!HitTest(e.location())) + SetState(BS_NORMAL); + else if (IsTriggerableEvent(e)) + SetState(BS_PUSHED); + else + SetState(BS_HOT); + } + return true; +} + +void CustomButton::OnMouseReleased(const MouseEvent& e, bool canceled) { + if (InDrag()) { + // Starting a drag results in a MouseReleased, we need to ignore it. + return; + } + + if (state_ != BS_DISABLED) { + if (canceled || !HitTest(e.location())) { + SetState(BS_NORMAL); + } else { + SetState(BS_HOT); + if (IsTriggerableEvent(e)) { + NotifyClick(e.GetFlags()); + // We may be deleted at this point (by the listener's notification + // handler) so no more doing anything, just return. + return; + } + } + } +} + +void CustomButton::OnMouseEntered(const MouseEvent& e) { + if (state_ != BS_DISABLED) + SetState(BS_HOT); +} + +void CustomButton::OnMouseMoved(const MouseEvent& e) { + if (state_ != BS_DISABLED) { + if (HitTest(e.location())) { + SetState(BS_HOT); + } else { + SetState(BS_NORMAL); + } + } +} + +void CustomButton::OnMouseExited(const MouseEvent& e) { + // Starting a drag results in a MouseExited, we need to ignore it. + if (state_ != BS_DISABLED && !InDrag()) + SetState(BS_NORMAL); +} + +bool CustomButton::OnKeyPressed(const KeyEvent& e) { + if (state_ != BS_DISABLED) { + // Space sets button state to pushed. Enter clicks the button. This matches + // the Windows native behavior of buttons, where Space clicks the button + // on KeyRelease and Enter clicks the button on KeyPressed. + if (e.GetCharacter() == VK_SPACE) { + SetState(BS_PUSHED); + return true; + } else if (e.GetCharacter() == VK_RETURN) { + SetState(BS_NORMAL); + NotifyClick(0); + return true; + } + } + return false; +} + +bool CustomButton::OnKeyReleased(const KeyEvent& e) { + if (state_ != BS_DISABLED) { + if (e.GetCharacter() == VK_SPACE) { + SetState(BS_NORMAL); + NotifyClick(0); + return true; + } + } + return false; +} + +void CustomButton::OnDragDone() { + SetState(BS_NORMAL); +} + +void CustomButton::ShowContextMenu(int x, int y, bool is_mouse_gesture) { + if (GetContextMenuController()) { + // We're about to show the context menu. Showing the context menu likely + // means we won't get a mouse exited and reset state. Reset it now to be + // sure. + if (state_ != BS_DISABLED) + SetState(BS_NORMAL); + View::ShowContextMenu(x, y, is_mouse_gesture); + } +} + +void CustomButton::ViewHierarchyChanged(bool is_add, View *parent, + View *child) { + if (!is_add && state_ != BS_DISABLED) + SetState(BS_NORMAL); +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomButton, AnimationDelegate implementation: + +void CustomButton::AnimationProgressed(const Animation* animation) { + SchedulePaint(); +} + +//////////////////////////////////////////////////////////////////////////////// +// CustomButton, private: + +void CustomButton::SetHighlighted(bool highlighted) { + if (highlighted && state_ != BS_DISABLED) { + SetState(BS_HOT); + } else if (!highlighted && state_ != BS_DISABLED) { + SetState(BS_NORMAL); + } +} + +bool CustomButton::IsHighlighted() const { + return state_ == BS_HOT; +} + +bool CustomButton::IsPushed() const { + return state_ == BS_PUSHED; +} + +} // namespace views diff --git a/chrome/views/controls/button/custom_button.h b/chrome/views/controls/button/custom_button.h new file mode 100644 index 0000000..cb4db5d --- /dev/null +++ b/chrome/views/controls/button/custom_button.h @@ -0,0 +1,94 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_ + +#include "chrome/common/animation.h" +#include "chrome/views/controls/button/button.h" + +class ThrobAnimation; + +namespace views { + +// A button with custom rendering. The common base class of IconButton and +// TextButton. +class CustomButton : public Button, + public AnimationDelegate { + public: + virtual ~CustomButton(); + + // Possible states + enum ButtonState { + BS_NORMAL = 0, + BS_HOT, + BS_PUSHED, + BS_DISABLED, + BS_COUNT + }; + + // Get/sets the current display state of the button. + ButtonState state() const { return state_; } + void SetState(ButtonState state); + + // Starts throbbing. See HoverAnimation for a description of cycles_til_stop. + void StartThrobbing(int cycles_til_stop); + + // Set how long the hover animation will last for. + void SetAnimationDuration(int duration); + + // Overridden from View: + virtual void SetEnabled(bool enabled); + virtual bool IsEnabled() const; + virtual bool IsFocusable() const; + + protected: + // Construct the Button with a Listener. See comment for Button's ctor. + explicit CustomButton(ButtonListener* listener); + + // Returns true if the event is one that can trigger notifying the listener. + // This implementation returns true if the left mouse button is down. + virtual bool IsTriggerableEvent(const MouseEvent& e); + + // Overridden from View: + virtual bool AcceleratorPressed(const Accelerator& accelerator); + virtual bool OnMousePressed(const MouseEvent& e); + virtual bool OnMouseDragged(const MouseEvent& e); + virtual void OnMouseReleased(const MouseEvent& e, bool canceled); + virtual void OnMouseEntered(const MouseEvent& e); + virtual void OnMouseMoved(const MouseEvent& e); + virtual void OnMouseExited(const MouseEvent& e); + virtual bool OnKeyPressed(const KeyEvent& e); + virtual bool OnKeyReleased(const KeyEvent& e); + virtual void OnDragDone(); + virtual void ShowContextMenu(int x, int y, bool is_mouse_gesture); + virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child); + + // Overridden from AnimationDelegate: + virtual void AnimationProgressed(const Animation* animation); + + // The button state (defined in implementation) + ButtonState state_; + + // Hover animation. + scoped_ptr<ThrobAnimation> hover_animation_; + + private: + // Set / test whether the button is highlighted (in the hover state). + void SetHighlighted(bool highlighted); + bool IsHighlighted() const; + + // Returns whether the button is pushed. + bool IsPushed() const; + + // Should we animate when the state changes? Defaults to true, but false while + // throbbing. + bool animate_on_state_change_; + + DISALLOW_COPY_AND_ASSIGN(CustomButton); +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_ diff --git a/chrome/views/controls/button/image_button.cc b/chrome/views/controls/button/image_button.cc new file mode 100644 index 0000000..235cc83 --- /dev/null +++ b/chrome/views/controls/button/image_button.cc @@ -0,0 +1,157 @@ +// 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/controls/button/image_button.h" + +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/throb_animation.h" +#include "grit/generated_resources.h" +#include "skia/ext/image_operations.h" + +namespace views { + +static const int kDefaultWidth = 16; // Default button width if no theme. +static const int kDefaultHeight = 14; // Default button height if no theme. + +//////////////////////////////////////////////////////////////////////////////// +// ImageButton, public: + +ImageButton::ImageButton(ButtonListener* listener) + : CustomButton(listener), + h_alignment_(ALIGN_LEFT), + v_alignment_(ALIGN_TOP) { + // By default, we request that the ChromeCanvas passed to our View::Paint() + // implementation is flipped horizontally so that the button's bitmaps are + // mirrored when the UI directionality is right-to-left. + EnableCanvasFlippingForRTLUI(true); +} + +ImageButton::~ImageButton() { +} + +void ImageButton::SetImage(ButtonState aState, SkBitmap* anImage) { + images_[aState] = anImage ? *anImage : SkBitmap(); +} + +void ImageButton::SetImageAlignment(HorizontalAlignment h_align, + VerticalAlignment v_align) { + h_alignment_ = h_align; + v_alignment_ = v_align; + SchedulePaint(); +} + +//////////////////////////////////////////////////////////////////////////////// +// ImageButton, View overrides: + +gfx::Size ImageButton::GetPreferredSize() { + if (!images_[BS_NORMAL].isNull()) + return gfx::Size(images_[BS_NORMAL].width(), images_[BS_NORMAL].height()); + return gfx::Size(kDefaultWidth, kDefaultHeight); +} + +void ImageButton::Paint(ChromeCanvas* canvas) { + // Call the base class first to paint any background/borders. + View::Paint(canvas); + + SkBitmap img = GetImageToPaint(); + + if (!img.isNull()) { + int x = 0, y = 0; + + if (h_alignment_ == ALIGN_CENTER) + x = (width() - img.width()) / 2; + else if (h_alignment_ == ALIGN_RIGHT) + x = width() - img.width(); + + if (v_alignment_ == ALIGN_MIDDLE) + y = (height() - img.height()) / 2; + else if (v_alignment_ == ALIGN_BOTTOM) + y = height() - img.height(); + + canvas->DrawBitmapInt(img, x, y); + } + PaintFocusBorder(canvas); +} + +//////////////////////////////////////////////////////////////////////////////// +// ImageButton, protected: + +SkBitmap ImageButton::GetImageToPaint() { + SkBitmap img; + + if (!images_[BS_HOT].isNull() && hover_animation_->IsAnimating()) { + img = skia::ImageOperations::CreateBlendedBitmap(images_[BS_NORMAL], + images_[BS_HOT], hover_animation_->GetCurrentValue()); + } else { + img = images_[state_]; + } + + return !img.isNull() ? img : images_[BS_NORMAL]; +} + +//////////////////////////////////////////////////////////////////////////////// +// ToggleImageButton, public: + +ToggleImageButton::ToggleImageButton(ButtonListener* listener) + : ImageButton(listener), + toggled_(false) { +} + +ToggleImageButton::~ToggleImageButton() { +} + +void ToggleImageButton::SetToggled(bool toggled) { + if (toggled == toggled_) + return; + + for (int i = 0; i < BS_COUNT; ++i) { + SkBitmap temp = images_[i]; + images_[i] = alternate_images_[i]; + alternate_images_[i] = temp; + } + toggled_ = toggled; + SchedulePaint(); +} + +void ToggleImageButton::SetToggledImage(ButtonState state, SkBitmap* image) { + if (toggled_) { + images_[state] = image ? *image : SkBitmap(); + if (state_ == state) + SchedulePaint(); + } else { + alternate_images_[state] = image ? *image : SkBitmap(); + } +} + +void ToggleImageButton::SetToggledTooltipText(const std::wstring& tooltip) { + toggled_tooltip_text_.assign(tooltip); +} + +//////////////////////////////////////////////////////////////////////////////// +// ToggleImageButton, ImageButton overrides: + +void ToggleImageButton::SetImage(ButtonState state, SkBitmap* image) { + if (toggled_) { + alternate_images_[state] = image ? *image : SkBitmap(); + } else { + images_[state] = image ? *image : SkBitmap(); + if (state_ == state) + SchedulePaint(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// ToggleImageButton, View overrides: + +bool ToggleImageButton::GetTooltipText(int x, int y, std::wstring* tooltip) { + if (!toggled_ || toggled_tooltip_text_.empty()) + return Button::GetTooltipText(x, y, tooltip); + + *tooltip = toggled_tooltip_text_; + return true; +} + +} // namespace views diff --git a/chrome/views/controls/button/image_button.h b/chrome/views/controls/button/image_button.h new file mode 100644 index 0000000..6d304ba --- /dev/null +++ b/chrome/views/controls/button/image_button.h @@ -0,0 +1,101 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_ + +#include "chrome/views/controls/button/custom_button.h" +#include "skia/include/SkBitmap.h" + +namespace views { + +// An image button. +class ImageButton : public CustomButton { + public: + explicit ImageButton(ButtonListener* listener); + virtual ~ImageButton(); + + // Set the image the button should use for the provided state. + virtual void SetImage(ButtonState aState, SkBitmap* anImage); + + enum HorizontalAlignment { ALIGN_LEFT = 0, + ALIGN_CENTER, + ALIGN_RIGHT, }; + + enum VerticalAlignment { ALIGN_TOP = 0, + ALIGN_MIDDLE, + ALIGN_BOTTOM }; + + // Sets how the image is laid out within the button's bounds. + void SetImageAlignment(HorizontalAlignment h_align, + VerticalAlignment v_align); + + // Overridden from View: + virtual gfx::Size GetPreferredSize(); + virtual void Paint(ChromeCanvas* canvas); + + protected: + // Returns the image to paint. This is invoked from paint and returns a value + // from images. + virtual SkBitmap GetImageToPaint(); + + // The images used to render the different states of this button. + SkBitmap images_[BS_COUNT]; + + private: + // Image alignment. + HorizontalAlignment h_alignment_; + VerticalAlignment v_alignment_; + + DISALLOW_COPY_AND_ASSIGN(ImageButton); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +// ToggleImageButton +// +// A toggle-able ImageButton. It swaps out its graphics when toggled. +// +//////////////////////////////////////////////////////////////////////////////// +class ToggleImageButton : public ImageButton { + public: + ToggleImageButton(ButtonListener* listener); + virtual ~ToggleImageButton(); + + // Change the toggled state. + void SetToggled(bool toggled); + + // Like Button::SetImage(), but to set the graphics used for the + // "has been toggled" state. Must be called for each button state + // before the button is toggled. + void SetToggledImage(ButtonState state, SkBitmap* image); + + // Set the tooltip text displayed when the button is toggled. + void SetToggledTooltipText(const std::wstring& tooltip); + + // Overridden from ImageButton: + virtual void SetImage(ButtonState aState, SkBitmap* anImage); + + // Overridden from View: + virtual bool GetTooltipText(int x, int y, std::wstring* tooltip); + + private: + // The parent class's images_ member is used for the current images, + // and this array is used to hold the alternative images. + // We swap between the two when toggling. + SkBitmap alternate_images_[BS_COUNT]; + + // True if the button is currently toggled. + bool toggled_; + + // The parent class's tooltip_text_ is displayed when not toggled, and + // this one is shown when toggled. + std::wstring toggled_tooltip_text_; + + DISALLOW_EVIL_CONSTRUCTORS(ToggleImageButton); +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_ diff --git a/chrome/views/controls/button/menu_button.cc b/chrome/views/controls/button/menu_button.cc new file mode 100644 index 0000000..0bcd2ae --- /dev/null +++ b/chrome/views/controls/button/menu_button.cc @@ -0,0 +1,254 @@ +// 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/controls/button/menu_button.h" + +#include "chrome/common/drag_drop_types.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "chrome/views/controls/button/button.h" +#include "chrome/views/controls/menu/view_menu_delegate.h" +#include "chrome/views/event.h" +#include "chrome/views/widget/root_view.h" +#include "chrome/views/widget/widget.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" + +using base::Time; +using base::TimeDelta; + +namespace views { + +// The amount of time, in milliseconds, we wait before allowing another mouse +// pressed event to show the menu. +static const int64 kMinimumTimeBetweenButtonClicks = 100; + +// The down arrow used to differentiate the menu button from normal +// text buttons. +static const SkBitmap* kMenuMarker = NULL; + +// How much padding to put on the left and right of the menu marker. +static const int kMenuMarkerPaddingLeft = 3; +static const int kMenuMarkerPaddingRight = -1; + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton - constructors, destructors, initialization +// +//////////////////////////////////////////////////////////////////////////////// + +MenuButton::MenuButton(ButtonListener* listener, + const std::wstring& text, + ViewMenuDelegate* menu_delegate, + bool show_menu_marker) + : TextButton(listener, text), + menu_visible_(false), + menu_closed_time_(), + menu_delegate_(menu_delegate), + show_menu_marker_(show_menu_marker) { + if (kMenuMarker == NULL) { + kMenuMarker = ResourceBundle::GetSharedInstance() + .GetBitmapNamed(IDR_MENU_DROPARROW); + } + set_alignment(TextButton::ALIGN_LEFT); +} + +MenuButton::~MenuButton() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton - Public APIs +// +//////////////////////////////////////////////////////////////////////////////// + +gfx::Size MenuButton::GetPreferredSize() { + gfx::Size prefsize = TextButton::GetPreferredSize(); + if (show_menu_marker_) { + prefsize.Enlarge(kMenuMarker->width() + kMenuMarkerPaddingLeft + + kMenuMarkerPaddingRight, + 0); + } + return prefsize; +} + +void MenuButton::Paint(ChromeCanvas* canvas, bool for_drag) { + TextButton::Paint(canvas, for_drag); + + if (show_menu_marker_) { + gfx::Insets insets = GetInsets(); + + // We can not use the views' mirroring infrastructure for mirroring a + // MenuButton control (see TextButton::Paint() for a detailed explanation + // regarding why we can not flip the canvas). Therefore, we need to + // manually mirror the position of the down arrow. + gfx::Rect arrow_bounds(width() - insets.right() - + kMenuMarker->width() - kMenuMarkerPaddingRight, + height() / 2 - kMenuMarker->height() / 2, + kMenuMarker->width(), + kMenuMarker->height()); + arrow_bounds.set_x(MirroredLeftPointForRect(arrow_bounds)); + canvas->DrawBitmapInt(*kMenuMarker, arrow_bounds.x(), arrow_bounds.y()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton - Events +// +//////////////////////////////////////////////////////////////////////////////// + +int MenuButton::GetMaximumScreenXCoordinate() { + Widget* widget = GetWidget(); + + if (!widget) { + NOTREACHED(); + return 0; + } + + HWND hwnd = widget->GetNativeView(); + CRect t; + ::GetWindowRect(hwnd, &t); + + gfx::Rect r(t); + gfx::Rect monitor_rect = win_util::GetMonitorBoundsForRect(r); + return monitor_rect.x() + monitor_rect.width() - 1; +} + +bool MenuButton::Activate() { + SetState(BS_PUSHED); + // We need to synchronously paint here because subsequently we enter a + // menu modal loop which will stop this window from updating and + // receiving the paint message that should be spawned by SetState until + // after the menu closes. + PaintNow(); + if (menu_delegate_) { + gfx::Rect lb = GetLocalBounds(true); + + // The position of the menu depends on whether or not the locale is + // right-to-left. + gfx::Point menu_position(lb.right(), lb.bottom()); + if (UILayoutIsRightToLeft()) + menu_position.set_x(lb.x()); + + View::ConvertPointToScreen(this, &menu_position); + if (UILayoutIsRightToLeft()) + menu_position.Offset(2, -4); + else + menu_position.Offset(-2, -4); + + int max_x_coordinate = GetMaximumScreenXCoordinate(); + if (max_x_coordinate && max_x_coordinate <= menu_position.x()) + menu_position.set_x(max_x_coordinate - 1); + + // We're about to show the menu from a mouse press. By showing from the + // mouse press event we block RootView in mouse dispatching. This also + // appears to cause RootView to get a mouse pressed BEFORE the mouse + // release is seen, which means RootView sends us another mouse press no + // matter where the user pressed. To force RootView to recalculate the + // mouse target during the mouse press we explicitly set the mouse handler + // to NULL. + GetRootView()->SetMouseHandler(NULL); + + menu_visible_ = true; + menu_delegate_->RunMenu(this, menu_position.ToPOINT(), + GetWidget()->GetNativeView()); + menu_visible_ = false; + menu_closed_time_ = Time::Now(); + + // Now that the menu has closed, we need to manually reset state to + // "normal" since the menu modal loop will have prevented normal + // mouse move messages from getting to this View. We set "normal" + // and not "hot" because the likelihood is that the mouse is now + // somewhere else (user clicked elsewhere on screen to close the menu + // or selected an item) and we will inevitably refresh the hot state + // in the event the mouse _is_ over the view. + SetState(BS_NORMAL); + + // We must return false here so that the RootView does not get stuck + // sending all mouse pressed events to us instead of the appropriate + // target. + return false; + } + return true; +} + +bool MenuButton::OnMousePressed(const MouseEvent& e) { + RequestFocus(); + if (state() != BS_DISABLED) { + // If we're draggable (GetDragOperations returns a non-zero value), then + // don't pop on press, instead wait for release. + if (e.IsOnlyLeftMouseButton() && HitTest(e.location()) && + GetDragOperations(e.x(), e.y()) == DragDropTypes::DRAG_NONE) { + TimeDelta delta = Time::Now() - menu_closed_time_; + int64 delta_in_milliseconds = delta.InMilliseconds(); + if (delta_in_milliseconds > kMinimumTimeBetweenButtonClicks) { + return Activate(); + } + } + } + return true; +} + +void MenuButton::OnMouseReleased(const MouseEvent& e, + bool canceled) { + if (GetDragOperations(e.x(), e.y()) != DragDropTypes::DRAG_NONE && + state() != BS_DISABLED && !canceled && !InDrag() && + e.IsOnlyLeftMouseButton() && HitTest(e.location())) { + Activate(); + } else { + TextButton::OnMouseReleased(e, canceled); + } +} + +// When the space bar or the enter key is pressed we need to show the menu. +bool MenuButton::OnKeyReleased(const KeyEvent& e) { + if ((e.GetCharacter() == VK_SPACE) || (e.GetCharacter() == VK_RETURN)) { + return Activate(); + } + return true; +} + +// The reason we override View::OnMouseExited is because we get this event when +// we display the menu. If we don't override this method then +// BaseButton::OnMouseExited will get the event and will set the button's state +// to BS_NORMAL instead of keeping the state BM_PUSHED. This, in turn, will +// cause the button to appear depressed while the menu is displayed. +void MenuButton::OnMouseExited(const MouseEvent& event) { + if ((state_ != BS_DISABLED) && (!menu_visible_) && (!InDrag())) { + SetState(BS_NORMAL); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton - accessibility +// +//////////////////////////////////////////////////////////////////////////////// + +bool MenuButton::GetAccessibleDefaultAction(std::wstring* action) { + DCHECK(action); + + action->assign(l10n_util::GetString(IDS_ACCACTION_PRESS)); + return true; +} + +bool MenuButton::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_BUTTONDROPDOWN; + return true; +} + +bool MenuButton::GetAccessibleState(VARIANT* state) { + DCHECK(state); + + state->lVal |= STATE_SYSTEM_HASPOPUP; + return true; +} + +} // namespace views diff --git a/chrome/views/controls/button/menu_button.h b/chrome/views/controls/button/menu_button.h new file mode 100644 index 0000000..458853e --- /dev/null +++ b/chrome/views/controls/button/menu_button.h @@ -0,0 +1,98 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_ + +#include <windows.h> + +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/background.h" +#include "chrome/views/controls/button/text_button.h" +#include "base/time.h" + +namespace views { + +class MouseEvent; +class ViewMenuDelegate; + + +//////////////////////////////////////////////////////////////////////////////// +// +// MenuButton +// +// A button that shows a menu when the left mouse button is pushed +// +//////////////////////////////////////////////////////////////////////////////// +class MenuButton : public TextButton { + public: + // + // Create a Button + MenuButton(ButtonListener* listener, + const std::wstring& text, + ViewMenuDelegate* menu_delegate, + bool show_menu_marker); + virtual ~MenuButton(); + + // Activate the button (called when the button is pressed). + virtual bool Activate(); + + // Overridden to take into account the potential use of a drop marker. + virtual gfx::Size GetPreferredSize(); + virtual void Paint(ChromeCanvas* canvas, bool for_drag); + + // These methods are overriden to implement a simple push button + // behavior + virtual bool OnMousePressed(const MouseEvent& e); + void OnMouseReleased(const MouseEvent& e, bool canceled); + virtual bool OnKeyReleased(const KeyEvent& e); + virtual void OnMouseExited(const MouseEvent& event); + + // Returns the MSAA default action of the current view. The string returned + // describes the default action that will occur when executing + // IAccessible::DoDefaultAction. + bool GetAccessibleDefaultAction(std::wstring* action); + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns the MSAA state of the current view. Sets the input VARIANT + // appropriately, and returns true if a change was performed successfully. + // Overriden from View. + virtual bool GetAccessibleState(VARIANT* state); + + protected: + // true if the menu is currently visible. + bool menu_visible_; + + private: + + // Compute the maximum X coordinate for the current screen. MenuButtons + // use this to make sure a menu is never shown off screen. + int GetMaximumScreenXCoordinate(); + + DISALLOW_EVIL_CONSTRUCTORS(MenuButton); + + // We use a time object in order to keep track of when the menu was closed. + // The time is used for simulating menu behavior for the menu button; that + // is, if the menu is shown and the button is pressed, we need to close the + // menu. There is no clean way to get the second click event because the + // menu is displayed using a modal loop and, unlike regular menus in Windows, + // the button is not part of the displayed menu. + base::Time menu_closed_time_; + + // The associated menu's resource identifier. + ViewMenuDelegate* menu_delegate_; + + // Whether or not we're showing a drop marker. + bool show_menu_marker_; + + friend class TextButtonBackground; +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_ diff --git a/chrome/views/controls/button/native_button.cc b/chrome/views/controls/button/native_button.cc new file mode 100644 index 0000000..4da39407 --- /dev/null +++ b/chrome/views/controls/button/native_button.cc @@ -0,0 +1,212 @@ +// 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/controls/button/native_button.h" + +#include "base/logging.h" +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/views/background.h" + +namespace views { + +const char NativeButton::kViewClassName[] = "chrome/views/NativeButton"; + +NativeButton::NativeButton(const std::wstring& label) + : enforce_dlu_min_size_(true) { + Init(label, false); +} + +NativeButton::NativeButton(const std::wstring& label, bool is_default) + : enforce_dlu_min_size_(true) { + Init(label, is_default); +} + +NativeButton::~NativeButton() { +} + +std::string NativeButton::GetClassName() const { + return kViewClassName; +} + +void NativeButton::SetListener(Listener *l) { + listener_ = l; +} + +void NativeButton::SetPadding(CSize size) { + padding_ = size; +} + +gfx::Size NativeButton::GetPreferredSize() { + HWND hwnd = GetNativeControlHWND(); + if (hwnd) { + SIZE sz = {0, 0}; + ::SendMessage(hwnd, + BCM_GETIDEALSIZE, + 0, + reinterpret_cast<LPARAM>(&sz)); + sz.cx += 2 * padding_.cx; + sz.cy += 2 * padding_.cy; + + if (enforce_dlu_min_size_) { + if (min_dlu_size_.width()) { + sz.cx = + std::max(static_cast<int>(sz.cx), + font_.horizontal_dlus_to_pixels(min_dlu_size_.width())); + } + if (min_dlu_size_.height()) + sz.cy = std::max(static_cast<int>(sz.cy), + font_.vertical_dlus_to_pixels(min_dlu_size_.height())); + } + return gfx::Size(sz.cx, sz.cy); + } + return gfx::Size(); +} + +void NativeButton::SetLabel(const std::wstring& l) { + // Even though we create a flipped HWND for a native button when the locale + // is right-to-left, Windows does not render text for the button using a + // right-to-left context (perhaps because the parent HWND is not flipped). + // The result is that RTL strings containing punctuation marks are not + // displayed properly. For example, the string "...ABC" (where A, B and C are + // Hebrew characters) is displayed as "ABC..." which is incorrect. + // + // In order to overcome this problem, we mark the localized Hebrew strings as + // RTL strings explicitly (using the appropriate Unicode formatting) so that + // Windows displays the text correctly regardless of the HWND hierarchy. + std::wstring localized_label; + if (l10n_util::AdjustStringForLocaleDirection(l, &localized_label)) + label_.assign(localized_label); + else + label_.assign(l); + + SetAccessibleName(l); + UpdateNativeButton(); +} + +const std::wstring NativeButton::GetLabel() const { + return label_; +} + +HWND NativeButton::CreateNativeControl(HWND parent_container) { + DWORD flags = WS_CHILD | BS_PUSHBUTTON; + if (is_default_) + flags |= BS_DEFPUSHBUTTON; + HWND r = ::CreateWindowEx(GetAdditionalExStyle(), L"BUTTON", L"", flags, 0, 0, + width(), height(), parent_container, NULL, + NULL, NULL); + SendMessage(r, WM_SETFONT, reinterpret_cast<WPARAM>(font_.hfont()), FALSE); + ConfigureNativeButton(r); + return r; +} + +LRESULT NativeButton::OnNotify(int w_param, LPNMHDR l_param) { + return 0; +} + +LRESULT NativeButton::OnCommand(UINT code, int id, HWND source) { + if (code == BN_CLICKED) + Clicked(); + return 0; +} + +void NativeButton::UpdateNativeButton() { + HWND hwnd = GetNativeControlHWND(); + if (hwnd) + ConfigureNativeButton(hwnd); +} + +void NativeButton::ConfigureNativeButton(HWND hwnd) { + ::SetWindowText(hwnd, label_.c_str()); +} + +void NativeButton::SetDefaultButton(bool is_default_button) { + if (is_default_button == is_default_) + return; + is_default_ = is_default_button; + if (is_default_button) + AddAccelerator(Accelerator(VK_RETURN, false, false, false)); + else + RemoveAccelerator(Accelerator(VK_RETURN, false, false, false)); + SendMessage(GetNativeControlHWND(), BM_SETSTYLE, + is_default_button ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON, true); +} + +bool NativeButton::AcceleratorPressed(const Accelerator& accelerator) { + if (enabled_) { + Clicked(); + return true; + } + return false; +} + +bool NativeButton::GetAccessibleRole(VARIANT* role) { + DCHECK(role); + + role->vt = VT_I4; + role->lVal = ROLE_SYSTEM_PUSHBUTTON; + return true; +} + +bool NativeButton::GetAccessibleName(std::wstring* name) { + if (!accessible_name_.empty()) { + *name = accessible_name_; + return true; + } + return false; +} + +void NativeButton::SetAccessibleName(const std::wstring& name) { + accessible_name_.assign(name); +} + +void NativeButton::Init(const std::wstring& label, bool is_default) { + // Marking the string as an RTL string if the locale is RTL. Refer to + // the comments in NativeButton::SetLabel for more details. + std::wstring localized_label; + if (l10n_util::AdjustStringForLocaleDirection(label, &localized_label)) + label_.assign(localized_label); + else + label_.assign(label); + + l10n_util::AdjustStringForLocaleDirection(label, &label_); + listener_ = NULL; + SetAccessibleName(label); + // The padding of 8 is a bit arbitrary, there appears to be no way to + // get a recommended padding, and this value varies greatly among windows + // dialogs. + // + // The min size in DLUs comes from + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwue/html/ch14e.asp + padding_ = CSize(8, 0); + is_default_ = is_default; + min_dlu_size_.SetSize(50, 14); + SetFocusable(true); + if (is_default) + AddAccelerator(Accelerator(VK_RETURN, false, false, false)); +} + +void NativeButton::Clicked() { + DCHECK(enabled_); + // Give the focus to the button. + RequestFocus(); + + if (listener_) + listener_->ButtonPressed(this); +} + +bool NativeButton::NotifyOnKeyDown() const { + return true; +} + +bool NativeButton::OnKeyDown(int virtual_key_code) { + if (virtual_key_code == VK_RETURN) { + Clicked(); + return true; + } + return false; +} + +} // namespace views diff --git a/chrome/views/controls/button/native_button.h b/chrome/views/controls/button/native_button.h new file mode 100644 index 0000000..07d7340 --- /dev/null +++ b/chrome/views/controls/button/native_button.h @@ -0,0 +1,146 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_ + +#include <string> + +#include "base/gfx/size.h" +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/controls/native_control.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// +// NativeButton is a wrapper for a windows native button +// TODO(beng): (Cleanup) this should derive also from some button-like base to +// get all the listenery-stuff for free and to work with +// AddManagedButton. +// +//////////////////////////////////////////////////////////////////////////////// +class NativeButton : public NativeControl { + public: + explicit NativeButton(const std::wstring& label); + NativeButton(const std::wstring& label, bool is_default); + virtual ~NativeButton(); + + virtual gfx::Size GetPreferredSize(); + + void SetLabel(const std::wstring& l); + const std::wstring GetLabel() const; + + std::string GetClassName() const; + + class Listener { + public: + // + // This is invoked once the button is released. + virtual void ButtonPressed(NativeButton* sender) = 0; + }; + + // + // The the listener, the object that receives a notification when this + // button is pressed. + void SetListener(Listener* listener); + + // Set the font used by this button. + void SetFont(const ChromeFont& font) { font_ = font; } + + // Adds some internal padding to the button. The |size| specified is applied + // on both sides of the button for each directions + void SetPadding(CSize size); + + // Sets/unsets this button as the default button. The default button is the + // one triggered when enter is pressed. It is displayed with a blue border. + void SetDefaultButton(bool is_default_button); + bool IsDefaultButton() const { return is_default_; } + + virtual LRESULT OnNotify(int w_param, LPNMHDR l_param); + virtual LRESULT OnCommand(UINT code, int id, HWND source); + + // Invoked when the accelerator associated with the button is pressed. + virtual bool AcceleratorPressed(const Accelerator& accelerator); + + // Sets the minimum size of the button from the specified size (in dialog + // units). If the width/height is non-zero, the preferred size of the button + // is max(preferred size of the content + padding, dlus converted to pixels). + // + // The default is 50, 14. + void SetMinSizeFromDLUs(const gfx::Size& dlu_size) { + min_dlu_size_ = dlu_size; + } + + // Returns the MSAA role of the current view. The role is what assistive + // technologies (ATs) use to determine what behavior to expect from a given + // control. + bool GetAccessibleRole(VARIANT* role); + + // Returns a brief, identifying string, containing a unique, readable name. + bool GetAccessibleName(std::wstring* name); + + // Assigns an accessible string name. + void SetAccessibleName(const std::wstring& name); + + // Sets whether the min size of this button should follow the Windows + // guidelines. Default is true. Set this to false if you want slim buttons. + void set_enforce_dlu_min_size(bool value) { enforce_dlu_min_size_ = value; } + + // The view class name. + static const char kViewClassName[]; + + protected: + + virtual HWND CreateNativeControl(HWND parent_container); + + // Sub-classes can call this method to cause the native button to reflect + // the current state + virtual void UpdateNativeButton(); + + // Sub-classes must override this method to properly configure the native + // button given the current state + virtual void ConfigureNativeButton(HWND hwnd); + + // Overridden from NativeControl so we can activate the button when Enter is + // pressed. + virtual bool NotifyOnKeyDown() const; + virtual bool OnKeyDown(int virtual_key_code); + + private: + NativeButton() {} + + // Initializes the button. If |is_default| is true, this appears like the + // "default" button, and will register Enter as its keyboard accelerator. + void Init(const std::wstring& label, bool is_default); + + void Clicked(); + + // Whether the button preferred size should follow the Microsoft layout + // guidelines. Default is true. + bool enforce_dlu_min_size_; + + std::wstring label_; + ChromeFont font_; + Listener* listener_; + + CSize padding_; + + // True if the button should be rendered to appear like the "default" button + // in the containing dialog box. Default buttons register Enter as their + // accelerator. + bool is_default_; + + // Minimum size, in dlus (see SetMinSizeFromDLUs). + gfx::Size min_dlu_size_; + + // Storage of strings needed for accessibility. + std::wstring accessible_name_; + + DISALLOW_COPY_AND_ASSIGN(NativeButton); +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_ diff --git a/chrome/views/controls/button/radio_button.cc b/chrome/views/controls/button/radio_button.cc new file mode 100644 index 0000000..758c747 --- /dev/null +++ b/chrome/views/controls/button/radio_button.cc @@ -0,0 +1,123 @@ +// 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/controls/button/radio_button.h" + +#include "chrome/views/controls/label.h" +#include "chrome/views/controls/hwnd_view.h" +#include "chrome/views/widget/root_view.h" + +namespace views { + +// FIXME(ACW) there got be a better way to find out the check box sizes +static int kRadioWidth = 13; +static int kRadioHeight = 13; +static int kRadioToLabel = 4; + +const char RadioButton::kViewClassName[] = "chrome/views/RadioButton"; + +RadioButton::RadioButton(const std::wstring& label, int group_id) + : CheckBox(label) { + SetGroup(group_id); +} + +RadioButton::~RadioButton() { +} + +HWND RadioButton::CreateNativeControl(HWND parent_container) { + HWND r = ::CreateWindowEx(GetAdditionalExStyle(), + L"BUTTON", + L"", + WS_CHILD | BS_RADIOBUTTON , + 0, 0, width(), height(), + parent_container, NULL, NULL, NULL); + ConfigureNativeButton(r); + return r; +} + +LRESULT RadioButton::OnCommand(UINT code, int id, HWND source) { + // Radio buttons can't be toggled off once selected except by clicking on + // another radio button within the same group, so we override this from + // CheckBox to prevent this from happening. + if (code == BN_CLICKED) { + RequestFocus(); + if (!IsSelected()) { + SetIsSelected(true); + return NativeButton::OnCommand(code, id, source); + } + } + return 0; +} + +// static +int RadioButton::GetTextIndent() { + return kRadioWidth + kRadioToLabel + kFocusPaddingHorizontal; +} + + +std::string RadioButton::GetClassName() const { + return kViewClassName; +} + +gfx::Size RadioButton::GetPreferredSize() { + gfx::Size prefsize = label_->GetPreferredSize(); + prefsize.set_height(std::max(prefsize.height() + kFocusPaddingVertical * 2, + kRadioHeight)); + prefsize.Enlarge(kRadioToLabel + kRadioWidth + kFocusPaddingHorizontal * 2, + 0); + return prefsize; +} + +void RadioButton::Layout() { + int label_x = GetTextIndent(); + label_->SetBounds(label_x, 0, width() - label_x, height()); + if (hwnd_view_) { + int first_line_height = label_->GetFont().height(); + hwnd_view_->SetBounds(0, ((first_line_height - kRadioHeight) / 2) + 1, + kRadioWidth, kRadioHeight); + hwnd_view_->UpdateHWNDBounds(); + } +} + +void RadioButton::SetIsSelected(bool f) { + if (f != IsSelected()) { + if (f) { + // We can't just get the root view here because sometimes the radio + // button isn't attached to a root view (e.g., if it's part of a tab page + // that is currently not active). + View* container = GetParent(); + while (container && container->GetParent()) + container = container->GetParent(); + if (container) { + std::vector<View*> other; + container->GetViewsWithGroup(GetGroup(), &other); + std::vector<View*>::iterator i; + for (i = other.begin(); i != other.end(); ++i) { + if (*i != this) { + RadioButton* peer = static_cast<RadioButton*>(*i); + peer->SetIsSelected(false); + } + } + } + } + CheckBox::SetIsSelected(f); + } +} + +View* RadioButton::GetSelectedViewForGroup(int group_id) { + std::vector<View*> views; + GetRootView()->GetViewsWithGroup(group_id, &views); + if (views.empty()) + return NULL; + + for (std::vector<View*>::const_iterator iter = views.begin(); + iter != views.end(); ++iter) { + RadioButton* radio_button = static_cast<RadioButton*>(*iter); + if (radio_button->IsSelected()) + return radio_button; + } + return NULL; +} + +} // namespace views diff --git a/chrome/views/controls/button/radio_button.h b/chrome/views/controls/button/radio_button.h new file mode 100644 index 0000000..dbae232 --- /dev/null +++ b/chrome/views/controls/button/radio_button.h @@ -0,0 +1,60 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_ + +#include "chrome/views/controls/button/checkbox.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// +// A wrapper for windows's native radio button. Radio buttons can be mutually +// exclusive with other radio buttons. +// +//////////////////////////////////////////////////////////////////////////////// +class RadioButton : public CheckBox { + public: + // The view class name. + static const char kViewClassName[]; + + // Create a radio button with the provided label and group id. + // The group id is used to identify all the other radio buttons which are in + // mutual exclusion with this radio button. Note: RadioButton assumes that + // all views with that group id are RadioButton. It is an error to give + // that group id to another view subclass which is not a radio button or + // a radio button subclass. + RadioButton(const std::wstring& label, int group_id); + virtual ~RadioButton(); + + virtual gfx::Size GetPreferredSize(); + virtual void Layout(); + + virtual std::string GetClassName() const; + + // Overridden to properly perform mutual exclusion. + virtual void SetIsSelected(bool f); + + virtual View* GetSelectedViewForGroup(int group_id); + + // When focusing a RadioButton with Tab/Shift-Tab, only the selected button + // from the group should be accessible. + virtual bool IsGroupFocusTraversable() const { return false; } + + protected: + virtual HWND CreateNativeControl(HWND parent_container); + virtual LRESULT OnCommand(UINT code, int id, HWND source); + + private: + // Get the horizontal distance of the start of the text from the left of the + // control. + static int GetTextIndent(); + + DISALLOW_EVIL_CONSTRUCTORS(RadioButton); +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_ diff --git a/chrome/views/controls/button/text_button.cc b/chrome/views/controls/button/text_button.cc new file mode 100644 index 0000000..c5f1ae6 --- /dev/null +++ b/chrome/views/controls/button/text_button.cc @@ -0,0 +1,323 @@ +// 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/controls/button/text_button.h" + +#include "chrome/common/gfx/chrome_canvas.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/resource_bundle.h" +#include "chrome/common/throb_animation.h" +#include "chrome/common/win_util.h" +#include "chrome/views/controls/button/button.h" +#include "chrome/views/event.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" + +namespace views { + +// Padding between the icon and text. +static const int kIconTextPadding = 5; + +// Preferred padding between text and edge +static const int kPreferredPaddingHorizontal = 6; +static const int kPreferredPaddingVertical = 5; + +static const SkColor kEnabledColor = SkColorSetRGB(6, 45, 117); +static const SkColor kHighlightColor = SkColorSetARGB(200, 255, 255, 255); +static const SkColor kDisabledColor = SkColorSetRGB(161, 161, 146); + +// How long the hover fade animation should last. +static const int kHoverAnimationDurationMs = 170; + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButtonBorder - constructors, destructors, initialization +// +//////////////////////////////////////////////////////////////////////////////// + +TextButtonBorder::TextButtonBorder() { + ResourceBundle& rb = ResourceBundle::GetSharedInstance(); + + hot_set_.top_left = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_LEFT_H); + hot_set_.top = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_H); + hot_set_.top_right = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_RIGHT_H); + hot_set_.left = rb.GetBitmapNamed(IDR_TEXTBUTTON_LEFT_H); + hot_set_.center = rb.GetBitmapNamed(IDR_TEXTBUTTON_CENTER_H); + hot_set_.right = rb.GetBitmapNamed(IDR_TEXTBUTTON_RIGHT_H); + hot_set_.bottom_left = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_LEFT_H); + hot_set_.bottom = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_H); + hot_set_.bottom_right = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_RIGHT_H); + + pushed_set_.top_left = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_LEFT_P); + pushed_set_.top = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_P); + pushed_set_.top_right = rb.GetBitmapNamed(IDR_TEXTBUTTON_TOP_RIGHT_P); + pushed_set_.left = rb.GetBitmapNamed(IDR_TEXTBUTTON_LEFT_P); + pushed_set_.center = rb.GetBitmapNamed(IDR_TEXTBUTTON_CENTER_P); + pushed_set_.right = rb.GetBitmapNamed(IDR_TEXTBUTTON_RIGHT_P); + pushed_set_.bottom_left = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_LEFT_P); + pushed_set_.bottom = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_P); + pushed_set_.bottom_right = rb.GetBitmapNamed(IDR_TEXTBUTTON_BOTTOM_RIGHT_P); +} + +TextButtonBorder::~TextButtonBorder() { +} + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButtonBackground - painting +// +//////////////////////////////////////////////////////////////////////////////// + +void TextButtonBorder::Paint(const View& view, ChromeCanvas* canvas) const { + const TextButton* mb = static_cast<const TextButton*>(&view); + int state = mb->state(); + + // TextButton takes care of deciding when to call Paint. + const MBBImageSet* set = &hot_set_; + if (state == TextButton::BS_PUSHED) + set = &pushed_set_; + + if (set) { + gfx::Rect bounds = view.bounds(); + + // Draw the top left image + canvas->DrawBitmapInt(*set->top_left, 0, 0); + + // Tile the top image + canvas->TileImageInt( + *set->top, + set->top_left->width(), + 0, + bounds.width() - set->top_right->width() - set->top_left->width(), + set->top->height()); + + // Draw the top right image + canvas->DrawBitmapInt(*set->top_right, + bounds.width() - set->top_right->width(), 0); + + // Tile the left image + canvas->TileImageInt( + *set->left, + 0, + set->top_left->height(), + set->top_left->width(), + bounds.height() - set->top->height() - set->bottom_left->height()); + + // Tile the center image + canvas->TileImageInt( + *set->center, + set->left->width(), + set->top->height(), + bounds.width() - set->right->width() - set->left->width(), + bounds.height() - set->bottom->height() - set->top->height()); + + // Tile the right image + canvas->TileImageInt( + *set->right, + bounds.width() - set->right->width(), + set->top_right->height(), + bounds.width(), + bounds.height() - set->bottom_right->height() - + set->top_right->height()); + + // Draw the bottom left image + canvas->DrawBitmapInt(*set->bottom_left, + 0, + bounds.height() - set->bottom_left->height()); + + // Tile the bottom image + canvas->TileImageInt( + *set->bottom, + set->bottom_left->width(), + bounds.height() - set->bottom->height(), + bounds.width() - set->bottom_right->width() - + set->bottom_left->width(), + set->bottom->height()); + + // Draw the bottom right image + canvas->DrawBitmapInt(*set->bottom_right, + bounds.width() - set->bottom_right->width(), + bounds.height() - set->bottom_right->height()); + } else { + // Do nothing + } +} + +void TextButtonBorder::GetInsets(gfx::Insets* insets) const { + insets->Set(kPreferredPaddingVertical, kPreferredPaddingHorizontal, + kPreferredPaddingVertical, kPreferredPaddingHorizontal); +} + +//////////////////////////////////////////////////////////////////////////////// +// TextButton, public: + +TextButton::TextButton(ButtonListener* listener, const std::wstring& text) + : CustomButton(listener), + font_(ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont)), + color_(kEnabledColor), + max_width_(0), + alignment_(ALIGN_LEFT) { + SetText(text); + set_border(new TextButtonBorder); + SetAnimationDuration(kHoverAnimationDurationMs); +} + +TextButton::~TextButton() { +} + +void TextButton::SetText(const std::wstring& text) { + text_ = text; + // Update our new current and max text size + text_size_.SetSize(font_.GetStringWidth(text_), font_.height()); + max_text_size_.SetSize(std::max(max_text_size_.width(), text_size_.width()), + std::max(max_text_size_.height(), + text_size_.height())); +} + +void TextButton::SetIcon(const SkBitmap& icon) { + icon_ = icon; +} + +void TextButton::ClearMaxTextSize() { + max_text_size_ = text_size_; +} + +void TextButton::Paint(ChromeCanvas* canvas, bool for_drag) { + if (!for_drag) { + PaintBackground(canvas); + + if (hover_animation_->IsAnimating()) { + // Draw the hover bitmap into an offscreen buffer, then blend it + // back into the current canvas. + canvas->saveLayerAlpha(NULL, + static_cast<int>(hover_animation_->GetCurrentValue() * 255), + SkCanvas::kARGB_NoClipLayer_SaveFlag); + canvas->drawARGB(0, 255, 255, 255, SkPorterDuff::kClear_Mode); + PaintBorder(canvas); + canvas->restore(); + } else if (state_ == BS_HOT || state_ == BS_PUSHED) { + PaintBorder(canvas); + } + + PaintFocusBorder(canvas); + } + + gfx::Insets insets = GetInsets(); + int available_width = width() - insets.width(); + int available_height = height() - insets.height(); + // Use the actual text (not max) size to properly center the text. + int content_width = text_size_.width(); + if (icon_.width() > 0) { + content_width += icon_.width(); + if (!text_.empty()) + content_width += kIconTextPadding; + } + // Place the icon along the left edge. + int icon_x; + if (alignment_ == ALIGN_LEFT) { + icon_x = insets.left(); + } else if (alignment_ == ALIGN_RIGHT) { + icon_x = available_width - content_width; + } else { + icon_x = + std::max(0, (available_width - content_width) / 2) + insets.left(); + } + int text_x = icon_x; + if (icon_.width() > 0) + text_x += icon_.width() + kIconTextPadding; + const int text_width = std::min(text_size_.width(), + width() - insets.right() - text_x); + int text_y = (available_height - text_size_.height()) / 2 + insets.top(); + + if (text_width > 0) { + // Because the text button can (at times) draw multiple elements on the + // canvas, we can not mirror the button by simply flipping the canvas as + // doing this will mirror the text itself. Flipping the canvas will also + // make the icons look wrong because icons are almost always represented as + // direction insentisive bitmaps and such bitmaps should never be flipped + // horizontally. + // + // Due to the above, we must perform the flipping manually for RTL UIs. + gfx::Rect text_bounds(text_x, text_y, text_width, text_size_.height()); + text_bounds.set_x(MirroredLeftPointForRect(text_bounds)); + + if (for_drag) { + canvas->DrawStringWithHalo(text_, font_, color_, kHighlightColor, + text_bounds.x(), + text_bounds.y(), + text_bounds.width(), + text_bounds.height(), + l10n_util::DefaultCanvasTextAlignment()); + } else { + // Draw bevel highlight + canvas->DrawStringInt(text_, + font_, + kHighlightColor, + text_bounds.x() + 1, + text_bounds.y() + 1, + text_bounds.width(), + text_bounds.height()); + + canvas->DrawStringInt(text_, + font_, + color_, + text_bounds.x(), + text_bounds.y(), + text_bounds.width(), + text_bounds.height()); + } + } + + if (icon_.width() > 0) { + int icon_y = (available_height - icon_.height()) / 2 + insets.top(); + + // Mirroring the icon position if necessary. + gfx::Rect icon_bounds(icon_x, icon_y, icon_.width(), icon_.height()); + icon_bounds.set_x(MirroredLeftPointForRect(icon_bounds)); + canvas->DrawBitmapInt(icon_, icon_bounds.x(), icon_bounds.y()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// TextButton, View overrides: + +gfx::Size TextButton::GetPreferredSize() { + gfx::Insets insets = GetInsets(); + + // Use the max size to set the button boundaries. + gfx::Size prefsize(max_text_size_.width() + icon_.width() + insets.width(), + std::max(max_text_size_.height(), icon_.height()) + + insets.height()); + + if (icon_.width() > 0 && !text_.empty()) + prefsize.Enlarge(kIconTextPadding, 0); + + if (max_width_ > 0) + prefsize.set_width(std::min(max_width_, prefsize.width())); + + return prefsize; +} + +gfx::Size TextButton::GetMinimumSize() { + return max_text_size_; +} + +void TextButton::SetEnabled(bool enabled) { + if (enabled == IsEnabled()) + return; + CustomButton::SetEnabled(enabled); + color_ = enabled ? kEnabledColor : kDisabledColor; + SchedulePaint(); +} + +bool TextButton::OnMousePressed(const MouseEvent& e) { + return true; +} + +void TextButton::Paint(ChromeCanvas* canvas) { + Paint(canvas, false); +} + +} // namespace views diff --git a/chrome/views/controls/button/text_button.h b/chrome/views/controls/button/text_button.h new file mode 100644 index 0000000..6ef4b52 --- /dev/null +++ b/chrome/views/controls/button/text_button.h @@ -0,0 +1,138 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_ +#define CHROME_VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_ + +#include "chrome/common/gfx/chrome_font.h" +#include "chrome/views/border.h" +#include "chrome/views/controls/button/custom_button.h" +#include "skia/include/SkBitmap.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButtonBorder +// +// A Border subclass that paints a TextButton's background layer - +// basically the button frame in the hot/pushed states. +// +//////////////////////////////////////////////////////////////////////////////// +class TextButtonBorder : public Border { + public: + TextButtonBorder(); + virtual ~TextButtonBorder(); + + // Render the background for the provided view + virtual void Paint(const View& view, ChromeCanvas* canvas) const; + + // Returns the insets for the border. + virtual void GetInsets(gfx::Insets* insets) const; + + private: + DISALLOW_EVIL_CONSTRUCTORS(TextButtonBorder); + + // Images + struct MBBImageSet { + SkBitmap* top_left; + SkBitmap* top; + SkBitmap* top_right; + SkBitmap* left; + SkBitmap* center; + SkBitmap* right; + SkBitmap* bottom_left; + SkBitmap* bottom; + SkBitmap* bottom_right; + }; + MBBImageSet hot_set_; + MBBImageSet pushed_set_; +}; + + +//////////////////////////////////////////////////////////////////////////////// +// +// TextButton +// +// A button which displays text and/or and icon that can be changed in +// response to actions. TextButton reserves space for the largest string +// passed to SetText. To reset the cached max size invoke ClearMaxTextSize. +// +//////////////////////////////////////////////////////////////////////////////// +class TextButton : public CustomButton { + public: + TextButton(ButtonListener* listener, const std::wstring& text); + virtual ~TextButton(); + + // Call SetText once per string in your set of possible values at button + // creation time, so that it can contain the largest of them and avoid + // resizing the button when the text changes. + virtual void SetText(const std::wstring& text); + std::wstring text() const { return text_; } + + typedef enum TextAlignment { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT + }; + + void set_alignment(TextAlignment alignment) { alignment_ = alignment; } + + // Sets the icon. + void SetIcon(const SkBitmap& icon); + SkBitmap icon() const { return icon_; } + + // TextButton remembers the maximum display size of the text passed to + // SetText. This method resets the cached maximum display size to the + // current size. + void ClearMaxTextSize(); + + void set_max_width(int max_width) { max_width_ = max_width; } + + // Paint the button into the specified canvas. If |for_drag| is true, the + // function paints a drag image representation into the canvas. + virtual void Paint(ChromeCanvas* canvas, bool for_drag); + + // Overridden from View: + virtual gfx::Size GetPreferredSize(); + virtual gfx::Size GetMinimumSize(); + virtual void SetEnabled(bool enabled); + + protected: + virtual bool OnMousePressed(const MouseEvent& e); + virtual void Paint(ChromeCanvas* canvas); + + private: + // The text string that is displayed in the button. + std::wstring text_; + + // The size of the text string. + gfx::Size text_size_; + + // Track the size of the largest text string seen so far, so that + // changing text_ will not resize the button boundary. + gfx::Size max_text_size_; + + // The alignment of the text string within the button. + TextAlignment alignment_; + + // The font used to paint the text. + ChromeFont font_; + + // Text color. + SkColor color_; + + // An icon displayed with the text. + SkBitmap icon_; + + // The width of the button will never be larger than this value. A value <= 0 + // indicates the width is not constrained. + int max_width_; + + DISALLOW_EVIL_CONSTRUCTORS(TextButton); +}; + +} // namespace views + +#endif // CHROME_VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_ |