diff options
Diffstat (limited to 'views/controls/button')
21 files changed, 2896 insertions, 0 deletions
diff --git a/views/controls/button/button.cc b/views/controls/button/button.cc new file mode 100644 index 0000000..cbb42bf --- /dev/null +++ b/views/controls/button/button.cc @@ -0,0 +1,79 @@ +// 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 "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; +} + +bool Button::GetAccessibleRole(AccessibilityTypes::Role* role) { + *role = AccessibilityTypes::ROLE_PUSHBUTTON; + return true; +} + +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; + // We can be called when there is no listener, in cases like double clicks on + // menu buttons etc. + if (listener_) + listener_->ButtonPressed(this); + // NOTE: don't attempt to reset mouse_event_flags_ as the listener may have + // deleted us. +} + +} // namespace views diff --git a/views/controls/button/button.h b/views/controls/button/button.h new file mode 100644 index 0000000..514758f --- /dev/null +++ b/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 VIEWS_CONTROLS_BUTTON_BUTTON_H_ +#define VIEWS_CONTROLS_BUTTON_BUTTON_H_ + +#include "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 bool GetAccessibleRole(AccessibilityTypes::Role* role); + 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. This can be + // true of buttons that don't have a listener - e.g. menubuttons where there's + // no default action and checkboxes. + explicit Button(ButtonListener* listener); + + // Cause the button to notify the listener that a click occurred. + virtual void NotifyClick(int mouse_event_flags); + + // The button's listener. Notified when clicked. + ButtonListener* listener_; + + private: + // The text shown in a tooltip. + std::wstring tooltip_text_; + + // Accessibility data. + std::wstring accessible_shortcut_; + std::wstring accessible_name_; + + // 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 // VIEWS_CONTROLS_BUTTON_BUTTON_H_ diff --git a/views/controls/button/button_dropdown.cc b/views/controls/button/button_dropdown.cc new file mode 100644 index 0000000..270894b --- /dev/null +++ b/views/controls/button/button_dropdown.cc @@ -0,0 +1,192 @@ +// 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 "views/controls/button/button_dropdown.h" + +#include "app/l10n_util.h" +#include "base/message_loop.h" +#include "grit/generated_resources.h" +#include "views/controls/menu/view_menu_delegate.h" +#include "views/widget/widget.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(AccessibilityTypes::Role* role) { + DCHECK(role); + + *role = AccessibilityTypes::ROLE_BUTTONDROPDOWN; + return true; +} + +bool ButtonDropDown::GetAccessibleState(AccessibilityTypes::State* state) { + DCHECK(state); + + *state = AccessibilityTypes::STATE_HASPOPUP; + return true; +} + +} // namespace views diff --git a/views/controls/button/button_dropdown.h b/views/controls/button/button_dropdown.h new file mode 100644 index 0000000..feb5b5c --- /dev/null +++ b/views/controls/button/button_dropdown.h @@ -0,0 +1,62 @@ +// 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 VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_ +#define VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_ + +#include "base/task.h" +#include "views/controls/button/image_button.h" +#include "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(); + + // Accessibility accessors, overridden from View. + virtual bool GetAccessibleDefaultAction(std::wstring* action); + virtual bool GetAccessibleRole(AccessibilityTypes::Role* role); + virtual bool GetAccessibleState(AccessibilityTypes::State* 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 // VIEWS_CONTROLS_BUTTON_BUTTON_DROPDOWN_H_ diff --git a/views/controls/button/checkbox.cc b/views/controls/button/checkbox.cc new file mode 100644 index 0000000..8783bcf --- /dev/null +++ b/views/controls/button/checkbox.cc @@ -0,0 +1,172 @@ +// Copyright (c) 2009 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 "views/controls/button/checkbox.h" + +#include "app/gfx/chrome_canvas.h" +#include "views/controls/label.h" + +namespace views { + +// static +const char Checkbox::kViewClassName[] = "views/Checkbox"; + +static const int kCheckboxLabelSpacing = 4; +static const int kLabelFocusPaddingHorizontal = 2; +static const int kLabelFocusPaddingVertical = 1; + +//////////////////////////////////////////////////////////////////////////////// +// Checkbox, public: + +Checkbox::Checkbox() : NativeButton(NULL), checked_(false) { + Init(std::wstring()); +} + +Checkbox::Checkbox(const std::wstring& label) + : NativeButton(NULL, label), + checked_(false) { + Init(label); +} + +Checkbox::~Checkbox() { +} + +void Checkbox::SetMultiLine(bool multiline) { + label_->SetMultiLine(multiline); +} + +void Checkbox::SetChecked(bool checked) { + if (checked_ == checked) + return; + checked_ = checked; + if (native_wrapper_) + native_wrapper_->UpdateChecked(); +} + +// static +int Checkbox::GetTextIndent() { + return NativeButtonWrapper::GetFixedWidth() + kCheckboxLabelSpacing; +} + +//////////////////////////////////////////////////////////////////////////////// +// Checkbox, View overrides: + +gfx::Size Checkbox::GetPreferredSize() { + gfx::Size prefsize = native_wrapper_->GetView()->GetPreferredSize(); + prefsize.set_width( + prefsize.width() + kCheckboxLabelSpacing + + kLabelFocusPaddingHorizontal * 2); + gfx::Size label_prefsize = label_->GetPreferredSize(); + prefsize.set_width(prefsize.width() + label_prefsize.width()); + prefsize.set_height( + std::max(prefsize.height(), + label_prefsize.height() + kLabelFocusPaddingVertical * 2)); + return prefsize; +} + +void Checkbox::Layout() { + if (!native_wrapper_) + return; + + gfx::Size checkmark_prefsize = native_wrapper_->GetView()->GetPreferredSize(); + int label_x = checkmark_prefsize.width() + kCheckboxLabelSpacing + + kLabelFocusPaddingHorizontal; + label_->SetBounds( + label_x, 0, std::max(0, width() - label_x - kLabelFocusPaddingHorizontal), + height()); + int first_line_height = label_->GetFont().height(); + native_wrapper_->GetView()->SetBounds( + 0, ((first_line_height - checkmark_prefsize.height()) / 2), + checkmark_prefsize.width(), checkmark_prefsize.height()); + native_wrapper_->GetView()->Layout(); +} + +void Checkbox::PaintFocusBorder(ChromeCanvas* canvas) { + // Our focus border is rendered by the label, so we don't do anything here. +} + +View* Checkbox::GetViewForPoint(const gfx::Point& point) { + return GetViewForPoint(point, false); +} + +View* Checkbox::GetViewForPoint(const gfx::Point& point, + bool can_create_floating) { + return GetLocalBounds(true).Contains(point) ? this : NULL; +} + +void Checkbox::OnMouseEntered(const MouseEvent& e) { + native_wrapper_->SetPushed(HitTestLabel(e)); +} + +void Checkbox::OnMouseMoved(const MouseEvent& e) { + native_wrapper_->SetPushed(HitTestLabel(e)); +} + +void Checkbox::OnMouseExited(const MouseEvent& e) { + native_wrapper_->SetPushed(false); +} + +bool Checkbox::OnMousePressed(const MouseEvent& e) { + native_wrapper_->SetPushed(HitTestLabel(e)); + return true; +} + +void Checkbox::OnMouseReleased(const MouseEvent& e, bool canceled) { + native_wrapper_->SetPushed(false); + if (!canceled && HitTestLabel(e)) { + SetChecked(!checked()); + ButtonPressed(); + } +} + +bool Checkbox::OnMouseDragged(const MouseEvent& e) { + return false; +} + +void Checkbox::WillGainFocus() { + label_->set_paint_as_focused(true); +} + +void Checkbox::WillLoseFocus() { + label_->set_paint_as_focused(false); +} + +std::string Checkbox::GetClassName() const { + return kViewClassName; +} + +//////////////////////////////////////////////////////////////////////////////// +// Checkbox, NativeButton overrides: + +void Checkbox::CreateWrapper() { + native_wrapper_ = NativeButtonWrapper::CreateCheckboxWrapper(this); + native_wrapper_->UpdateLabel(); + native_wrapper_->UpdateChecked(); +} + +void Checkbox::InitBorder() { + // No border, so we do nothing. +} + +//////////////////////////////////////////////////////////////////////////////// +// Checkbox, protected: + +bool Checkbox::HitTestLabel(const MouseEvent& e) { + gfx::Point tmp(e.location()); + ConvertPointToView(this, label_, &tmp); + return label_->HitTest(tmp); +} + +//////////////////////////////////////////////////////////////////////////////// +// Checkbox, private: + +void Checkbox::Init(const std::wstring& label_text) { + set_minimum_size(gfx::Size(0, 0)); + label_ = new Label(label_text); + label_->set_has_focus_border(true); + label_->SetHorizontalAlignment(Label::ALIGN_LEFT); + AddChildView(label_); +} + +} // namespace views diff --git a/views/controls/button/checkbox.h b/views/controls/button/checkbox.h new file mode 100644 index 0000000..db6706b --- /dev/null +++ b/views/controls/button/checkbox.h @@ -0,0 +1,84 @@ +// Copyright (c) 2009 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 VIEWS_CONTROLS_BUTTON_CHECKBOX_H_ +#define VIEWS_CONTROLS_BUTTON_CHECKBOX_H_ + +#include "views/controls/button/native_button.h" + +namespace views { + +class Label; + +// A NativeButton subclass representing a checkbox. +class Checkbox : public NativeButton { + public: + // The button's class name. + static const char kViewClassName[]; + + Checkbox(); + Checkbox(const std::wstring& label); + virtual ~Checkbox(); + + // Sets a listener for this checkbox. Checkboxes aren't required to have them + // since their state can be read independently of them being toggled. + void set_listener(ButtonListener* listener) { listener_ = listener; } + + // Sets whether or not the checkbox label should wrap multiple lines of text. + // If true, long lines are wrapped, and this is reflected in the preferred + // size returned by GetPreferredSize. If false, text that will not fit within + // the available bounds for the label will be cropped. + void SetMultiLine(bool multiline); + + // Sets/Gets whether or not the checkbox is checked. + virtual void SetChecked(bool checked); + bool checked() const { return checked_; } + + // Returns the indentation of the text from the left edge of the view. + static int GetTextIndent(); + + // Overridden from View: + virtual gfx::Size GetPreferredSize(); + virtual void Layout(); + virtual void PaintFocusBorder(ChromeCanvas* canvas); + virtual View* GetViewForPoint(const gfx::Point& point); + virtual View* GetViewForPoint(const gfx::Point& point, + bool can_create_floating); + virtual void OnMouseEntered(const MouseEvent& e); + virtual void OnMouseMoved(const MouseEvent& e); + virtual void OnMouseExited(const MouseEvent& e); + virtual bool OnMousePressed(const MouseEvent& e); + virtual void OnMouseReleased(const MouseEvent& e, bool canceled); + virtual bool OnMouseDragged(const MouseEvent& e); + virtual void WillGainFocus(); + virtual void WillLoseFocus(); + + protected: + virtual std::string GetClassName() const; + + // Overridden from NativeButton2: + virtual void CreateWrapper(); + virtual void InitBorder(); + + // Returns true if the event (in Checkbox coordinates) is within the bounds of + // the label. + bool HitTestLabel(const MouseEvent& e); + + private: + // Called from the constructor to create and configure the checkbox label. + void Init(const std::wstring& label_text); + + // The checkbox's label. We don't use the OS version because of transparency + // and sizing issues. + Label* label_; + + // True if the checkbox is checked. + bool checked_; + + DISALLOW_COPY_AND_ASSIGN(Checkbox); +}; + +} // namespace views + +#endif // #ifndef VIEWS_CONTROLS_BUTTON_CHECKBOX_H_ diff --git a/views/controls/button/custom_button.cc b/views/controls/button/custom_button.cc new file mode 100644 index 0000000..2b5f43d --- /dev/null +++ b/views/controls/button/custom_button.cc @@ -0,0 +1,236 @@ +// 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 "views/controls/button/custom_button.h" + +#include "app/throb_animation.h" +#include "base/keyboard_codes.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), + triggerable_event_flags_(MouseEvent::EF_LEFT_BUTTON_DOWN) { + hover_animation_.reset(new ThrobAnimation(this)); + hover_animation_->SetSlideDuration(kHoverFadeDurationMs); +} + +bool CustomButton::IsTriggerableEvent(const MouseEvent& e) { + return (triggerable_event_flags_ & e.GetFlags()) != 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// 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() == base::VKEY_SPACE) { + SetState(BS_PUSHED); + return true; + } else if (e.GetCharacter() == base::VKEY_RETURN) { + SetState(BS_NORMAL); + NotifyClick(0); + return true; + } + } + return false; +} + +bool CustomButton::OnKeyReleased(const KeyEvent& e) { + if (state_ != BS_DISABLED) { + if (e.GetCharacter() == base::VKEY_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/views/controls/button/custom_button.h b/views/controls/button/custom_button.h new file mode 100644 index 0000000..32ff76b --- /dev/null +++ b/views/controls/button/custom_button.h @@ -0,0 +1,105 @@ +// 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 VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_ +#define VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_ + +#include "app/animation.h" +#include "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; + + void set_triggerable_event_flags(int triggerable_event_flags) { + triggerable_event_flags_ = triggerable_event_flags; + } + + int triggerable_event_flags() const { + return triggerable_event_flags_; + } + + 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_; + + // Mouse event flags which can trigger button actions. + int triggerable_event_flags_; + + DISALLOW_COPY_AND_ASSIGN(CustomButton); +}; + +} // namespace views + +#endif // VIEWS_CONTROLS_BUTTON_CUSTOM_BUTTON_H_ diff --git a/views/controls/button/image_button.cc b/views/controls/button/image_button.cc new file mode 100644 index 0000000..3c4c7fe --- /dev/null +++ b/views/controls/button/image_button.cc @@ -0,0 +1,154 @@ +// 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 "views/controls/button/image_button.h" + +#include "app/gfx/chrome_canvas.h" +#include "app/throb_animation.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/views/controls/button/image_button.h b/views/controls/button/image_button.h new file mode 100644 index 0000000..c687561 --- /dev/null +++ b/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 VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_ +#define VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_ + +#include "skia/include/SkBitmap.h" +#include "views/controls/button/custom_button.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 // VIEWS_CONTROLS_BUTTON_IMAGE_BUTTON_H_ diff --git a/views/controls/button/menu_button.cc b/views/controls/button/menu_button.cc new file mode 100644 index 0000000..888137b --- /dev/null +++ b/views/controls/button/menu_button.cc @@ -0,0 +1,253 @@ +// 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 "views/controls/button/menu_button.h" + +#include "app/drag_drop_types.h" +#include "app/gfx/chrome_canvas.h" +#include "app/l10n_util.h" +#include "app/resource_bundle.h" +#include "chrome/common/win_util.h" +#include "grit/generated_resources.h" +#include "grit/theme_resources.h" +#include "views/controls/button/button.h" +#include "views/controls/menu/view_menu_delegate.h" +#include "views/event.h" +#include "views/widget/root_view.h" +#include "views/widget/widget.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(AccessibilityTypes::Role* role) { + DCHECK(role); + + *role = AccessibilityTypes::ROLE_BUTTONDROPDOWN; + return true; +} + +bool MenuButton::GetAccessibleState(AccessibilityTypes::State* state) { + DCHECK(state); + + *state = AccessibilityTypes::STATE_HASPOPUP; + return true; +} + +} // namespace views diff --git a/views/controls/button/menu_button.h b/views/controls/button/menu_button.h new file mode 100644 index 0000000..cbe9ab8 --- /dev/null +++ b/views/controls/button/menu_button.h @@ -0,0 +1,92 @@ +// 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 VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_ +#define VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_ + +#include <windows.h> + +#include "app/gfx/chrome_font.h" +#include "base/time.h" +#include "views/background.h" +#include "views/controls/button/text_button.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(); + + void set_menu_delegate(ViewMenuDelegate* delegate) { + menu_delegate_ = delegate; + } + + // 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); + + // Accessibility accessors, overridden from View. + virtual bool GetAccessibleDefaultAction(std::wstring* action); + virtual bool GetAccessibleRole(AccessibilityTypes::Role* role); + virtual bool GetAccessibleState(AccessibilityTypes::State* 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 // VIEWS_CONTROLS_BUTTON_MENU_BUTTON_H_ diff --git a/views/controls/button/native_button.cc b/views/controls/button/native_button.cc new file mode 100644 index 0000000..0af6f88 --- /dev/null +++ b/views/controls/button/native_button.cc @@ -0,0 +1,178 @@ +// Copyright (c) 2009 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 "views/controls/button/native_button.h" + +#include "app/l10n_util.h" +#include "base/logging.h" + +namespace views { + +static int kButtonBorderHWidth = 8; + +// static +const char NativeButton::kViewClassName[] = "views/NativeButton"; + +//////////////////////////////////////////////////////////////////////////////// +// NativeButton, public: + +NativeButton::NativeButton(ButtonListener* listener) + : Button(listener), + native_wrapper_(NULL), + is_default_(false), + ignore_minimum_size_(false), + minimum_size_(50, 14) { + // The min size in DLUs comes from + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwue/html/ch14e.asp + InitBorder(); + SetFocusable(true); +} + +NativeButton::NativeButton(ButtonListener* listener, const std::wstring& label) + : Button(listener), + native_wrapper_(NULL), + is_default_(false), + ignore_minimum_size_(false), + minimum_size_(50, 14) { + SetLabel(label); // SetLabel takes care of label layout in RTL UI. + // The min size in DLUs comes from + // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwue/html/ch14e.asp + InitBorder(); + SetFocusable(true); +} + +NativeButton::~NativeButton() { +} + +void NativeButton::SetLabel(const std::wstring& label) { + label_ = label; + + // 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(label_, &localized_label)) + label_ = localized_label; + + if (native_wrapper_) + native_wrapper_->UpdateLabel(); +} + +void NativeButton::SetIsDefault(bool is_default) { + if (is_default == is_default_) + return; + if (is_default) + AddAccelerator(Accelerator(VK_RETURN, false, false, false)); + else + RemoveAccelerator(Accelerator(VK_RETURN, false, false, false)); + SetAppearsAsDefault(is_default); +} + +void NativeButton::SetAppearsAsDefault(bool appears_as_default) { + is_default_ = appears_as_default; + if (native_wrapper_) + native_wrapper_->UpdateDefault(); +} + +void NativeButton::ButtonPressed() { + RequestFocus(); + + // TODO(beng): obtain mouse event flags for native buttons someday. + NotifyClick(mouse_event_flags()); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeButton, View overrides: + +gfx::Size NativeButton::GetPreferredSize() { + if (!native_wrapper_) + return gfx::Size(); + + gfx::Size sz = native_wrapper_->GetView()->GetPreferredSize(); + + // Add in the border size. (Do this before clamping the minimum size in case + // that clamping causes an increase in size that would include the borders. + gfx::Insets border = GetInsets(); + sz.set_width(sz.width() + border.left() + border.right()); + sz.set_height(sz.height() + border.top() + border.bottom()); + + // Clamp the size returned to at least the minimum size. + if (!ignore_minimum_size_) { + if (minimum_size_.width()) { + int min_width = font_.horizontal_dlus_to_pixels(minimum_size_.width()); + sz.set_width(std::max(static_cast<int>(sz.width()), min_width)); + } + if (minimum_size_.height()) { + int min_height = font_.vertical_dlus_to_pixels(minimum_size_.height()); + sz.set_height(std::max(static_cast<int>(sz.height()), min_height)); + } + } + + return sz; +} + +void NativeButton::Layout() { + if (native_wrapper_) { + native_wrapper_->GetView()->SetBounds(0, 0, width(), height()); + native_wrapper_->GetView()->Layout(); + } +} + +void NativeButton::SetEnabled(bool flag) { + Button::SetEnabled(flag); + if (native_wrapper_) + native_wrapper_->UpdateEnabled(); +} + +void NativeButton::ViewHierarchyChanged(bool is_add, View* parent, + View* child) { + if (is_add && !native_wrapper_ && GetWidget()) { + CreateWrapper(); + AddChildView(native_wrapper_->GetView()); + } +} + +std::string NativeButton::GetClassName() const { + return kViewClassName; +} + +bool NativeButton::AcceleratorPressed(const Accelerator& accelerator) { + if (IsEnabled()) { + NotifyClick(mouse_event_flags()); + return true; + } + return false; +} + +void NativeButton::Focus() { + // Forward the focus to the wrapper. + if (native_wrapper_) + native_wrapper_->SetFocus(); + else + Button::Focus(); // Will focus the RootView window (so we still get + // keyboard messages). +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeButton, protected: + +void NativeButton::CreateWrapper() { + native_wrapper_ = NativeButtonWrapper::CreateNativeButtonWrapper(this); + native_wrapper_->UpdateLabel(); + native_wrapper_->UpdateEnabled(); +} + +void NativeButton::InitBorder() { + set_border(Border::CreateEmptyBorder(0, kButtonBorderHWidth, 0, + kButtonBorderHWidth)); +} + +} // namespace views diff --git a/views/controls/button/native_button.h b/views/controls/button/native_button.h new file mode 100644 index 0000000..52946d7 --- /dev/null +++ b/views/controls/button/native_button.h @@ -0,0 +1,100 @@ +// Copyright (c) 2009 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 VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_ +#define VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_ + +#include "app/gfx/chrome_font.h" +#include "views/controls/button/button.h" +#include "views/controls/button/native_button_wrapper.h" + +class ChromeFont; + +namespace views { + +class NativeButton : public Button { + public: + // The button's class name. + static const char kViewClassName[]; + + explicit NativeButton(ButtonListener* listener); + NativeButton(ButtonListener* listener, const std::wstring& label); + virtual ~NativeButton(); + + // Sets/Gets the text to be used as the button's label. + void SetLabel(const std::wstring& label); + std::wstring label() const { return label_; } + + // Sets the font to be used when displaying the button's label. + void set_font(const ChromeFont& font) { font_ = font; } + const ChromeFont& font() const { return font_; } + + // Sets/Gets whether or not the button appears and behaves as the default + // button in its current context. + void SetIsDefault(bool default_button); + bool is_default() const { return is_default_; } + + // Sets whether or not the button appears as the default button. This does + // not make it behave as the default (i.e. no enter key accelerator is + // registered, use SetIsDefault for that). + void SetAppearsAsDefault(bool default_button); + + void set_minimum_size(const gfx::Size& minimum_size) { + minimum_size_ = minimum_size; + } + void set_ignore_minimum_size(bool ignore_minimum_size) { + ignore_minimum_size_ = ignore_minimum_size; + } + + // Called by the wrapper when the actual wrapped native button was pressed. + void ButtonPressed(); + + // Overridden from View: + virtual gfx::Size GetPreferredSize(); + virtual void Layout(); + virtual void SetEnabled(bool flag); + virtual void Focus(); + + protected: + virtual void ViewHierarchyChanged(bool is_add, View* parent, View* child); + virtual std::string GetClassName() const; + virtual bool AcceleratorPressed(const Accelerator& accelerator); + + // Create the button wrapper. Can be overridden by subclass to create a + // wrapper of a particular type. See NativeButtonWrapper interface for types. + virtual void CreateWrapper(); + + // Sets a border to the button. Override to set a different border or to not + // set one (the default is 0,8,0,8 for push buttons). + virtual void InitBorder(); + + // The object that actually implements the native button. + NativeButtonWrapper* native_wrapper_; + + private: + // The button label. + std::wstring label_; + + // True if the button is the default button in its context. + bool is_default_; + + // The font used to render the button label. + ChromeFont font_; + + // True if the button should ignore the minimum size for the platform. Default + // is false. Set to true to create narrower buttons. + bool ignore_minimum_size_; + + // The minimum size of the button from the specified size in native dialog + // units. The definition of this unit may vary from platform to platform. If + // the width/height is non-zero, the preferred size of the button will not be + // less than this value when the dialog units are converted to pixels. + gfx::Size minimum_size_; + + DISALLOW_COPY_AND_ASSIGN(NativeButton); +}; + +} // namespace views + +#endif // #ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_H_ diff --git a/views/controls/button/native_button_win.cc b/views/controls/button/native_button_win.cc new file mode 100644 index 0000000..6748241 --- /dev/null +++ b/views/controls/button/native_button_win.cc @@ -0,0 +1,234 @@ +// Copyright (c) 2009 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 "views/controls/button/native_button_win.h" + +#include "base/logging.h" +#include "views/controls/button/checkbox.h" +#include "views/controls/button/native_button.h" +#include "views/controls/button/radio_button.h" +#include "views/widget/widget.h" + +namespace views { + +//////////////////////////////////////////////////////////////////////////////// +// NativeButtonWin, public: + +NativeButtonWin::NativeButtonWin(NativeButton* native_button) + : NativeControlWin(), + native_button_(native_button) { + // Associates the actual HWND with the native_button so the native_button is + // the one considered as having the focus (not the wrapper) when the HWND is + // focused directly (with a click for example). + SetAssociatedFocusView(native_button); +} + +NativeButtonWin::~NativeButtonWin() { +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeButtonWin, NativeButtonWrapper implementation: + +void NativeButtonWin::UpdateLabel() { + SetWindowText(GetHWND(), native_button_->label().c_str()); +} + +void NativeButtonWin::UpdateFont() { + SendMessage(GetHWND(), WM_SETFONT, + reinterpret_cast<WPARAM>(native_button_->font().hfont()), + FALSE); +} + +void NativeButtonWin::UpdateEnabled() { + SetEnabled(native_button_->IsEnabled()); +} + +void NativeButtonWin::UpdateDefault() { + if (!IsCheckbox()) { + SendMessage(GetHWND(), BM_SETSTYLE, + native_button_->is_default() ? BS_DEFPUSHBUTTON : BS_PUSHBUTTON, + true); + } +} + +View* NativeButtonWin::GetView() { + return this; +} + +void NativeButtonWin::SetFocus() { + // Focus the associated HWND. + Focus(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeButtonWin, View overrides: + +gfx::Size NativeButtonWin::GetPreferredSize() { + SIZE sz = {0}; + SendMessage(GetHWND(), BCM_GETIDEALSIZE, 0, reinterpret_cast<LPARAM>(&sz)); + + return gfx::Size(sz.cx, sz.cy); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeButtonWin, NativeControlWin overrides: + +bool NativeButtonWin::ProcessMessage(UINT message, WPARAM w_param, + LPARAM l_param, LRESULT* result) { + if (message == WM_COMMAND && HIWORD(w_param) == BN_CLICKED) { + native_button_->ButtonPressed(); + *result = 0; + return true; + } + return NativeControlWin::ProcessMessage(message, w_param, l_param, result); +} + +bool NativeButtonWin::OnKeyDown(int vkey) { + bool enter_pressed = vkey == VK_RETURN; + if (enter_pressed) + native_button_->ButtonPressed(); + return enter_pressed; +} + +bool NativeButtonWin::NotifyOnKeyDown() const { + return true; +} + +void NativeButtonWin::CreateNativeControl() { + DWORD flags = WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | BS_PUSHBUTTON; + if (native_button_->is_default()) + flags |= BS_DEFPUSHBUTTON; + HWND control_hwnd = CreateWindowEx(GetAdditionalExStyle(), L"BUTTON", L"", + flags, 0, 0, width(), height(), + GetWidget()->GetNativeView(), NULL, NULL, + NULL); + NativeControlCreated(control_hwnd); +} + +void NativeButtonWin::NativeControlCreated(HWND control_hwnd) { + NativeControlWin::NativeControlCreated(control_hwnd); + + UpdateFont(); + UpdateLabel(); + UpdateDefault(); +} + +// We could obtain this from the theme, but that only works if themes are +// active. +static const int kCheckboxSize = 13; // pixels + +//////////////////////////////////////////////////////////////////////////////// +// NativeCheckboxWin, public: + +NativeCheckboxWin::NativeCheckboxWin(Checkbox* checkbox) + : NativeButtonWin(checkbox), + checkbox_(checkbox) { +} + +NativeCheckboxWin::~NativeCheckboxWin() { +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeCheckboxWin, View overrides: + +gfx::Size NativeCheckboxWin::GetPreferredSize() { + return gfx::Size(kCheckboxSize, kCheckboxSize); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeCheckboxWin, NativeButtonWrapper implementation: + +void NativeCheckboxWin::UpdateChecked() { + SendMessage(GetHWND(), BM_SETCHECK, + checkbox_->checked() ? BST_CHECKED : BST_UNCHECKED, 0); +} + +void NativeCheckboxWin::SetPushed(bool pushed) { + SendMessage(GetHWND(), BM_SETSTATE, pushed, 0); +} + +bool NativeCheckboxWin::OnKeyDown(int vkey) { + // Override the NativeButtonWin behavior which triggers the button on enter + // key presses when focused. + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeCheckboxWin, NativeButtonWin overrides: + +bool NativeCheckboxWin::ProcessMessage(UINT message, WPARAM w_param, + LPARAM l_param, LRESULT* result) { + if (message == WM_COMMAND && HIWORD(w_param) == BN_CLICKED) { + if (!IsRadioButton() || !checkbox_->checked()) + checkbox_->SetChecked(!checkbox_->checked()); + // Fall through to the NativeButtonWin's handler, which will send the + // clicked notification to the listener... + } + return NativeButtonWin::ProcessMessage(message, w_param, l_param, result); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeCheckboxWin, protected: + +void NativeCheckboxWin::CreateNativeControl() { + HWND control_hwnd = CreateWindowEx( + WS_EX_TRANSPARENT | GetAdditionalExStyle(), L"BUTTON", L"", + WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | BS_CHECKBOX, + 0, 0, width(), height(), GetWidget()->GetNativeView(), NULL, NULL, NULL); + NativeControlCreated(control_hwnd); +} + +void NativeCheckboxWin::NativeControlCreated(HWND control_hwnd) { + NativeButtonWin::NativeControlCreated(control_hwnd); + UpdateChecked(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeRadioButtonWin, public: + +NativeRadioButtonWin::NativeRadioButtonWin(RadioButton* radio_button) + : NativeCheckboxWin(radio_button) { +} + +NativeRadioButtonWin::~NativeRadioButtonWin() { +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeRadioButtonWin, NativeCheckboxWin overrides: + +void NativeRadioButtonWin::CreateNativeControl() { + HWND control_hwnd = CreateWindowEx( + GetAdditionalExStyle(), L"BUTTON", + L"", WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | BS_RADIOBUTTON, + 0, 0, width(), height(), GetWidget()->GetNativeView(), NULL, NULL, NULL); + NativeControlCreated(control_hwnd); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeButtonWrapper, public: + +// static +int NativeButtonWrapper::GetFixedWidth() { + return kCheckboxSize; +} + +// static +NativeButtonWrapper* NativeButtonWrapper::CreateNativeButtonWrapper( + NativeButton* native_button) { + return new NativeButtonWin(native_button); +} + +// static +NativeButtonWrapper* NativeButtonWrapper::CreateCheckboxWrapper( + Checkbox* checkbox) { + return new NativeCheckboxWin(checkbox); +} + +// static +NativeButtonWrapper* NativeButtonWrapper::CreateRadioButtonWrapper( + RadioButton* radio_button) { + return new NativeRadioButtonWin(radio_button); +} + +} // namespace views diff --git a/views/controls/button/native_button_win.h b/views/controls/button/native_button_win.h new file mode 100644 index 0000000..3f5141a --- /dev/null +++ b/views/controls/button/native_button_win.h @@ -0,0 +1,105 @@ +// Copyright (c) 2009 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 VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WIN_H_ +#define VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WIN_H_ + +#include "views/controls/native_control_win.h" +#include "views/controls/button/native_button_wrapper.h" + +namespace views { + +// A View that hosts a native Windows button. +class NativeButtonWin : public NativeControlWin, + public NativeButtonWrapper { + public: + explicit NativeButtonWin(NativeButton* native_button); + virtual ~NativeButtonWin(); + + // Overridden from NativeButtonWrapper: + virtual void UpdateLabel(); + virtual void UpdateFont(); + virtual void UpdateEnabled(); + virtual void UpdateDefault(); + virtual View* GetView(); + virtual void SetFocus(); + + // Overridden from View: + virtual gfx::Size GetPreferredSize(); + + // Overridden from NativeControlWin: + virtual bool ProcessMessage(UINT message, + WPARAM w_param, + LPARAM l_param, + LRESULT* result); + virtual bool OnKeyDown(int vkey); + + protected: + virtual bool NotifyOnKeyDown() const; + virtual void CreateNativeControl(); + virtual void NativeControlCreated(HWND control_hwnd); + + // Returns true if this button is actually a checkbox or radio button. + virtual bool IsCheckbox() const { return false; } + + private: + // The NativeButton we are bound to. + NativeButton* native_button_; + + DISALLOW_COPY_AND_ASSIGN(NativeButtonWin); +}; + +// A View that hosts a native Windows checkbox. +class NativeCheckboxWin : public NativeButtonWin { + public: + explicit NativeCheckboxWin(Checkbox* native_button); + virtual ~NativeCheckboxWin(); + + // Overridden from View: + virtual gfx::Size GetPreferredSize(); + + // Overridden from NativeButtonWrapper: + virtual void UpdateChecked(); + virtual void SetPushed(bool pushed); + virtual bool OnKeyDown(int vkey); + + // Overridden from NativeControlWin: + virtual bool ProcessMessage(UINT message, + WPARAM w_param, + LPARAM l_param, + LRESULT* result); + + protected: + virtual void CreateNativeControl(); + virtual void NativeControlCreated(HWND control_hwnd); + virtual bool IsCheckbox() const { return true; } + + // Returns true if this button is actually a radio button. + virtual bool IsRadioButton() const { return false; } + + private: + // The Checkbox we are bound to. + Checkbox* checkbox_; + + DISALLOW_COPY_AND_ASSIGN(NativeCheckboxWin); +}; + +// A View that hosts a native Windows radio button. +class NativeRadioButtonWin : public NativeCheckboxWin { + public: + explicit NativeRadioButtonWin(RadioButton* radio_button); + virtual ~NativeRadioButtonWin(); + + protected: + // Overridden from NativeCheckboxWin: + virtual void CreateNativeControl(); + virtual bool IsRadioButton() const { return true; } + + private: + DISALLOW_COPY_AND_ASSIGN(NativeRadioButtonWin); +}; + +} // namespace views + +#endif // #ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WIN_H_ diff --git a/views/controls/button/native_button_wrapper.h b/views/controls/button/native_button_wrapper.h new file mode 100644 index 0000000..5535ed4 --- /dev/null +++ b/views/controls/button/native_button_wrapper.h @@ -0,0 +1,62 @@ +// Copyright (c) 2009 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 VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WRAPPER_H_ +#define VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WRAPPER_H_ + +class ChromeFont; + +namespace views { + +class Checkbox; +class NativeButton; +class RadioButton; + +// A specialization of NativeControlWrapper that hosts a platform-native button. +class NativeButtonWrapper { + public: + // Updates the native button's label from the state stored in its associated + // NativeButton. + virtual void UpdateLabel() = 0; + + // Updates the native button's label font from the state stored in its + // associated NativeButton. + virtual void UpdateFont() = 0; + + // Updates the native button's enabled state from the state stored in its + // associated NativeButton. + virtual void UpdateEnabled() = 0; + + // Updates the native button's default state from the state stored in its + // associated NativeButton. + virtual void UpdateDefault() = 0; + + // Updates the native button's checked state from the state stored in its + // associated NativeCheckbox. Valid only for checkboxes and radio buttons. + virtual void UpdateChecked() {} + + // Shows the pushed state for the button if |pushed| is true. + virtual void SetPushed(bool pushed) {}; + + // Retrieves the views::View that hosts the native control. + virtual View* GetView() = 0; + + // Sets the focus to the button. + virtual void SetFocus() = 0; + + // Return the width of the button. Used for fixed size buttons (checkboxes and + // radio buttons) only. + static int GetFixedWidth(); + + // Creates an appropriate NativeButtonWrapper for the platform. + static NativeButtonWrapper* CreateNativeButtonWrapper(NativeButton* button); + static NativeButtonWrapper* CreateCheckboxWrapper(Checkbox* checkbox); + static NativeButtonWrapper* CreateRadioButtonWrapper( + RadioButton* radio_button); + +}; + +} // namespace views + +#endif // #ifndef VIEWS_CONTROLS_BUTTON_NATIVE_BUTTON_WRAPPER_H_ diff --git a/views/controls/button/radio_button.cc b/views/controls/button/radio_button.cc new file mode 100644 index 0000000..3f4820d --- /dev/null +++ b/views/controls/button/radio_button.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2009 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 "views/controls/button/radio_button.h" + +#include "views/widget/root_view.h" + +namespace views { + +// static +const char RadioButton::kViewClassName[] = "views/RadioButton"; + +//////////////////////////////////////////////////////////////////////////////// +// RadioButton, public: + +RadioButton::RadioButton() : Checkbox() { +} + +RadioButton::RadioButton(const std::wstring& label) : Checkbox(label) { +} + +RadioButton::RadioButton(const std::wstring& label, int group_id) + : Checkbox(label) { + SetGroup(group_id); +} + +RadioButton::~RadioButton() { +} + +//////////////////////////////////////////////////////////////////////////////// +// RadioButton, Checkbox overrides: + +void RadioButton::SetChecked(bool checked) { + if (checked == RadioButton::checked()) + return; + if (checked) { + // 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->SetChecked(false); + } + } + } + } + Checkbox::SetChecked(checked); + +} + +//////////////////////////////////////////////////////////////////////////////// +// RadioButton, View overrides: + +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->checked()) + return radio_button; + } + return NULL; +} + +bool RadioButton::IsGroupFocusTraversable() const { + // When focusing a radio button with tab/shift+tab, only the selected button + // from the group should be focused. + return false; +} + +void RadioButton::OnMouseReleased(const MouseEvent& event, bool canceled) { + native_wrapper_->SetPushed(false); + // Set the checked state to true only if we are unchecked, since we can't + // be toggled on and off like a checkbox. + if (!checked() && !canceled && HitTestLabel(event)) + SetChecked(true); + + ButtonPressed(); +} + +std::string RadioButton::GetClassName() const { + return kViewClassName; +} + +//////////////////////////////////////////////////////////////////////////////// +// RadioButton, NativeButton overrides: + +void RadioButton::CreateWrapper() { + native_wrapper_ = NativeButtonWrapper::CreateRadioButtonWrapper(this); + native_wrapper_->UpdateLabel(); + native_wrapper_->UpdateChecked(); +} + +} // namespace views diff --git a/views/controls/button/radio_button.h b/views/controls/button/radio_button.h new file mode 100644 index 0000000..ab7fbe2 --- /dev/null +++ b/views/controls/button/radio_button.h @@ -0,0 +1,43 @@ +// Copyright (c) 2009 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 VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_ +#define VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_ + +#include "views/controls/button/checkbox.h" + +namespace views { + +// A Checkbox subclass representing a radio button. +class RadioButton : public Checkbox { + public: + // The button's class name. + static const char kViewClassName[]; + + RadioButton(); + RadioButton(const std::wstring& label); + RadioButton(const std::wstring& label, int group_id); + virtual ~RadioButton(); + + // Overridden from Checkbox: + virtual void SetChecked(bool checked); + + // Overridden from View: + virtual View* GetSelectedViewForGroup(int group_id); + virtual bool IsGroupFocusTraversable() const; + virtual void OnMouseReleased(const MouseEvent& event, bool canceled); + + protected: + virtual std::string GetClassName() const; + + // Overridden from NativeButton: + virtual void CreateWrapper(); + + private: + DISALLOW_COPY_AND_ASSIGN(RadioButton); +}; + +} // namespace views + +#endif // #ifndef VIEWS_CONTROLS_BUTTON_RADIO_BUTTON_H_ diff --git a/views/controls/button/text_button.cc b/views/controls/button/text_button.cc new file mode 100644 index 0000000..4f68b90 --- /dev/null +++ b/views/controls/button/text_button.cc @@ -0,0 +1,325 @@ +// 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 "views/controls/button/text_button.h" + +#include "app/gfx/chrome_canvas.h" +#include "app/l10n_util.h" +#include "app/throb_animation.h" +#include "app/resource_bundle.h" +#include "views/controls/button/button.h" +#include "views/event.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), + alignment_(ALIGN_LEFT), + font_(ResourceBundle::GetSharedInstance().GetFont( + ResourceBundle::BaseFont)), + color_(kEnabledColor), + max_width_(0) { + 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) { +#if defined(OS_WIN) + // TODO(erg): Either port DrawStringWithHalo to linux or find an + // alternative here. + canvas->DrawStringWithHalo(text_, font_, color_, kHighlightColor, + text_bounds.x(), + text_bounds.y(), + text_bounds.width(), + text_bounds.height(), + l10n_util::DefaultCanvasTextAlignment()); +#endif + } 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/views/controls/button/text_button.h b/views/controls/button/text_button.h new file mode 100644 index 0000000..16bac57 --- /dev/null +++ b/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 VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_ +#define VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_ + +#include "app/gfx/chrome_font.h" +#include "skia/include/SkBitmap.h" +#include "views/border.h" +#include "views/controls/button/custom_button.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_; } + + 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 // VIEWS_CONTROLS_BUTTON_TEXT_BUTTON_H_ |