summaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
authortfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-18 21:58:23 +0000
committertfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-11-18 21:58:23 +0000
commitb1ed27851db7f86d7f71dfe4c897c796a953fc25 (patch)
treeb9d972e7a6770b4c9055507fe2e9d329b0974106 /ui
parentcee34b707ac41e83ff9d9045c0ffda49ec3f556f (diff)
downloadchromium_src-b1ed27851db7f86d7f71dfe4c897c796a953fc25.zip
chromium_src-b1ed27851db7f86d7f71dfe4c897c796a953fc25.tar.gz
chromium_src-b1ed27851db7f86d7f71dfe4c897c796a953fc25.tar.bz2
views: Move bubble, events, focus and layout to ui/views/.
Left stub files that will be removed in a follow up patch after updating the files to point to the new location. BUG=104039 R=ben@chromium.org Review URL: http://codereview.chromium.org/8588064 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110761 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r--ui/views/bubble/border_contents_view.cc185
-rw-r--r--ui/views/bubble/border_contents_view.h88
-rw-r--r--ui/views/bubble/bubble_border.cc511
-rw-r--r--ui/views/bubble/bubble_border.h183
-rw-r--r--ui/views/bubble/bubble_delegate.cc287
-rw-r--r--ui/views/bubble/bubble_delegate.h153
-rw-r--r--ui/views/bubble/bubble_delegate_unittest.cc31
-rw-r--r--ui/views/bubble/bubble_frame_view.cc78
-rw-r--r--ui/views/bubble/bubble_frame_view.h57
-rw-r--r--ui/views/bubble/bubble_frame_view_unittest.cc68
-rw-r--r--ui/views/events/event.cc200
-rw-r--r--ui/views/events/event.h415
-rw-r--r--ui/views/events/event_aura.cc62
-rw-r--r--ui/views/events/event_gtk.cc183
-rw-r--r--ui/views/events/event_unittest.cc128
-rw-r--r--ui/views/events/event_wayland.cc23
-rw-r--r--ui/views/events/event_win.cc30
-rw-r--r--ui/views/events/event_x.cc152
-rw-r--r--ui/views/focus/accelerator_handler.h61
-rw-r--r--ui/views/focus/accelerator_handler_aura.cc29
-rw-r--r--ui/views/focus/accelerator_handler_gtk.cc22
-rw-r--r--ui/views/focus/accelerator_handler_gtk_unittest.cc199
-rw-r--r--ui/views/focus/accelerator_handler_touch.cc187
-rw-r--r--ui/views/focus/accelerator_handler_wayland.cc18
-rw-r--r--ui/views/focus/accelerator_handler_win.cc60
-rw-r--r--ui/views/focus/external_focus_tracker.cc72
-rw-r--r--ui/views/focus/external_focus_tracker.h78
-rw-r--r--ui/views/focus/focus_manager.cc413
-rw-r--r--ui/views/focus/focus_manager.h283
-rw-r--r--ui/views/focus/focus_manager_factory.cc55
-rw-r--r--ui/views/focus/focus_manager_factory.h41
-rw-r--r--ui/views/focus/focus_manager_unittest.cc1763
-rw-r--r--ui/views/focus/focus_search.cc272
-rw-r--r--ui/views/focus/focus_search.h122
-rw-r--r--ui/views/focus/view_storage.cc115
-rw-r--r--ui/views/focus/view_storage.h70
-rw-r--r--ui/views/focus/widget_focus_manager.cc50
-rw-r--r--ui/views/focus/widget_focus_manager.h82
-rw-r--r--ui/views/layout/box_layout.cc76
-rw-r--r--ui/views/layout/box_layout.h62
-rw-r--r--ui/views/layout/box_layout_unittest.cc111
-rw-r--r--ui/views/layout/fill_layout.cc30
-rw-r--r--ui/views/layout/fill_layout.h36
-rw-r--r--ui/views/layout/grid_layout.cc1070
-rw-r--r--ui/views/layout/grid_layout.h371
-rw-r--r--ui/views/layout/grid_layout_unittest.cc606
-rw-r--r--ui/views/layout/layout_constants.h71
-rw-r--r--ui/views/layout/layout_manager.cc30
-rw-r--r--ui/views/layout/layout_manager.h61
49 files changed, 9350 insertions, 0 deletions
diff --git a/ui/views/bubble/border_contents_view.cc b/ui/views/bubble/border_contents_view.cc
new file mode 100644
index 0000000..67c893a
--- /dev/null
+++ b/ui/views/bubble/border_contents_view.cc
@@ -0,0 +1,185 @@
+// Copyright (c) 2011 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/bubble/border_contents_view.h"
+
+#include <algorithm>
+
+#include "ui/gfx/screen.h"
+
+static const int kTopMargin = 6;
+static const int kLeftMargin = 6;
+static const int kBottomMargin = 6;
+static const int kRightMargin = 6;
+
+namespace {
+
+// Computes how much |window_bounds| is off-screen of the monitor bounds
+// |monitor_bounds| and puts the values in |offscreen_insets|.
+// Returns false if |window_bounds| is actually contained in |monitor_bounds|,
+// in which case |offscreen_insets| is not modified.
+bool ComputeOffScreenInsets(const gfx::Rect& monitor_bounds,
+ const gfx::Rect& window_bounds,
+ gfx::Insets* offscreen_insets) {
+ if (monitor_bounds.Contains(window_bounds))
+ return false;
+
+ if (!offscreen_insets)
+ return true;
+
+ // window_bounds
+ // +-------------------------------+
+ // | top |
+ // | +----------------+ |
+ // | left | monitor_bounds | right |
+ // | +----------------+ |
+ // | bottom |
+ // +-------------------------------+
+ int top = std::max(0, monitor_bounds.y() - window_bounds.y());
+ int left = std::max(0, monitor_bounds.x() - window_bounds.x());
+ int bottom = std::max(0, window_bounds.bottom() - monitor_bounds.bottom());
+ int right = std::max(0, window_bounds.right() - monitor_bounds.right());
+
+ offscreen_insets->Set(top, left, bottom, right);
+ return true;
+}
+
+// Convenience method that returns the height of |insets| if |vertical| is
+// true, its width otherwise.
+int GetInsetsLength(const gfx::Insets& insets, bool vertical) {
+ return vertical ? insets.height() : insets.width();
+}
+
+} // namespace
+
+namespace views {
+
+BorderContentsView::BorderContentsView()
+ : bubble_border_(NULL),
+ content_margins_(kTopMargin, kLeftMargin, kBottomMargin, kRightMargin) {
+}
+
+BorderContentsView::BorderContentsView(int top_margin,
+ int left_margin,
+ int bottom_margin,
+ int right_margin)
+ : bubble_border_(NULL),
+ content_margins_(top_margin, left_margin, bottom_margin, right_margin) {
+}
+
+BorderContentsView::~BorderContentsView() {}
+
+void BorderContentsView::Init() {
+ // Default arrow location.
+ BubbleBorder::ArrowLocation arrow_location =
+ BubbleBorder::TOP_LEFT;
+ if (base::i18n::IsRTL())
+ arrow_location = BubbleBorder::horizontal_mirror(arrow_location);
+ DCHECK(!bubble_border_);
+
+ // TODO(alicet): Expose the shadow option in BorderContentsView when we make
+ // the fullscreen exit bubble use the new bubble code.
+ bubble_border_ = new BubbleBorder(arrow_location,
+ views::BubbleBorder::NO_SHADOW);
+ set_border(bubble_border_);
+ set_background(new BubbleBackground(bubble_border_));
+}
+
+void BorderContentsView::SetBackgroundColor(SkColor color) {
+ bubble_border_->set_background_color(color);
+}
+
+void BorderContentsView::SetAlignment(
+ views::BubbleBorder::BubbleAlignment alignment) {
+ bubble_border_->set_alignment(alignment);
+}
+
+void BorderContentsView::SizeAndGetBounds(
+ const gfx::Rect& position_relative_to,
+ BubbleBorder::ArrowLocation arrow_location,
+ bool allow_bubble_offscreen,
+ const gfx::Size& contents_size,
+ gfx::Rect* contents_bounds,
+ gfx::Rect* window_bounds) {
+ if (base::i18n::IsRTL())
+ arrow_location = BubbleBorder::horizontal_mirror(arrow_location);
+ bubble_border_->set_arrow_location(arrow_location);
+ // Set the border.
+ set_border(bubble_border_);
+
+ // Give the contents a margin.
+ gfx::Size local_contents_size(contents_size);
+ local_contents_size.Enlarge(content_margins_.width(),
+ content_margins_.height());
+
+ // Try putting the arrow in its initial location, and calculating the bounds.
+ *window_bounds =
+ bubble_border_->GetBounds(position_relative_to, local_contents_size);
+ if (!allow_bubble_offscreen) {
+ gfx::Rect monitor_bounds = GetMonitorBounds(position_relative_to);
+ if (!monitor_bounds.IsEmpty()) {
+ // Try to resize vertically if this does not fit on the screen.
+ MirrorArrowIfOffScreen(true, // |vertical|.
+ position_relative_to, monitor_bounds,
+ local_contents_size, &arrow_location,
+ window_bounds);
+ // Then try to resize horizontally if it still does not fit on the screen.
+ MirrorArrowIfOffScreen(false, // |vertical|.
+ position_relative_to, monitor_bounds,
+ local_contents_size, &arrow_location,
+ window_bounds);
+ }
+ }
+
+ // Calculate the bounds of the contained contents (in window coordinates) by
+ // subtracting the border dimensions and margin amounts.
+ *contents_bounds = gfx::Rect(gfx::Point(), window_bounds->size());
+ gfx::Insets insets;
+ bubble_border_->GetInsets(&insets);
+ insets += content_margins_;
+ contents_bounds->Inset(insets);
+}
+
+gfx::Rect BorderContentsView::GetMonitorBounds(const gfx::Rect& rect) {
+ return gfx::Screen::GetMonitorWorkAreaNearestPoint(rect.CenterPoint());
+}
+
+void BorderContentsView::MirrorArrowIfOffScreen(
+ bool vertical,
+ const gfx::Rect& position_relative_to,
+ const gfx::Rect& monitor_bounds,
+ const gfx::Size& local_contents_size,
+ BubbleBorder::ArrowLocation* arrow_location,
+ gfx::Rect* window_bounds) {
+ // If the bounds don't fit, move the arrow to its mirrored position to see if
+ // it improves things.
+ gfx::Insets offscreen_insets;
+ if (ComputeOffScreenInsets(monitor_bounds, *window_bounds,
+ &offscreen_insets) &&
+ GetInsetsLength(offscreen_insets, vertical) > 0) {
+ BubbleBorder::ArrowLocation original_arrow_location =
+ *arrow_location;
+ *arrow_location =
+ vertical ? BubbleBorder::vertical_mirror(*arrow_location) :
+ BubbleBorder::horizontal_mirror(*arrow_location);
+
+ // Change the arrow and get the new bounds.
+ bubble_border_->set_arrow_location(*arrow_location);
+ *window_bounds = bubble_border_->GetBounds(position_relative_to,
+ local_contents_size);
+ gfx::Insets new_offscreen_insets;
+ // If there is more of the window offscreen, we'll keep the old arrow.
+ if (ComputeOffScreenInsets(monitor_bounds, *window_bounds,
+ &new_offscreen_insets) &&
+ GetInsetsLength(new_offscreen_insets, vertical) >=
+ GetInsetsLength(offscreen_insets, vertical)) {
+ *arrow_location = original_arrow_location;
+ bubble_border_->set_arrow_location(*arrow_location);
+ *window_bounds = bubble_border_->GetBounds(position_relative_to,
+ local_contents_size);
+ }
+ }
+}
+
+} // namespace views
diff --git a/ui/views/bubble/border_contents_view.h b/ui/views/bubble/border_contents_view.h
new file mode 100644
index 0000000..696dd26
--- /dev/null
+++ b/ui/views/bubble/border_contents_view.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2011 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 UI_VIEWS_BUBBLE_BORDER_CONTENTS_VIEW_H_
+#define UI_VIEWS_BUBBLE_BORDER_CONTENTS_VIEW_H_
+#pragma once
+
+#include "views/bubble/bubble_border.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "views/view.h"
+
+namespace views {
+
+// This is used to paint the border and background of the Bubble.
+class VIEWS_EXPORT BorderContentsView : public View {
+ public:
+ BorderContentsView();
+ BorderContentsView(int top_margin,
+ int left_margin,
+ int bottom_margin,
+ int right_margin);
+
+ // Must be called before this object can be used.
+ void Init();
+
+ // Sets the background color.
+ void SetBackgroundColor(SkColor color);
+
+ // Sets the bubble alignment.
+ void SetAlignment(views::BubbleBorder::BubbleAlignment alignment);
+
+ // Given the size of the contents and the rect to point at, returns the bounds
+ // of both the border and the contents inside the bubble.
+ // |arrow_location| specifies the preferred location for the arrow
+ // anchor. If the bubble does not fit on the monitor and
+ // |allow_bubble_offscreen| is false, the arrow location may change so the
+ // bubble shows entirely.
+ virtual void SizeAndGetBounds(
+ const gfx::Rect& position_relative_to, // In screen coordinates
+ BubbleBorder::ArrowLocation arrow_location,
+ bool allow_bubble_offscreen,
+ const gfx::Size& contents_size,
+ gfx::Rect* contents_bounds, // Returned in window coordinates
+ gfx::Rect* window_bounds); // Returned in screen coordinates
+
+ // Sets content margins.
+ void set_content_margins(const gfx::Insets& margins) {
+ content_margins_ = margins;
+ }
+
+ // Accessor for |content_margins_|.
+ const gfx::Insets& content_margins() const {
+ return content_margins_;
+ }
+
+ protected:
+ virtual ~BorderContentsView();
+
+ // Returns the bounds for the monitor showing the specified |rect|.
+ virtual gfx::Rect GetMonitorBounds(const gfx::Rect& rect);
+
+ BubbleBorder* bubble_border() const { return bubble_border_; }
+
+ private:
+ // Changes |arrow_location| to its mirrored version, vertically if |vertical|
+ // is true, horizontally otherwise, if |window_bounds| don't fit in
+ // |monitor_bounds|.
+ void MirrorArrowIfOffScreen(
+ bool vertical,
+ const gfx::Rect& position_relative_to,
+ const gfx::Rect& monitor_bounds,
+ const gfx::Size& local_contents_size,
+ BubbleBorder::ArrowLocation* arrow_location,
+ gfx::Rect* window_bounds);
+
+ // The bubble border.
+ BubbleBorder* bubble_border_;
+
+ // Margins between the content and the inside of the border, in pixels.
+ gfx::Insets content_margins_;
+
+ DISALLOW_COPY_AND_ASSIGN(BorderContentsView);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_BUBBLE_BORDER_CONTENTS_VIEW_H_
diff --git a/ui/views/bubble/bubble_border.cc b/ui/views/bubble/bubble_border.cc
new file mode 100644
index 0000000..368b0be
--- /dev/null
+++ b/ui/views/bubble/bubble_border.cc
@@ -0,0 +1,511 @@
+// Copyright (c) 2011 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/bubble/bubble_border.h"
+
+#include <algorithm> // for std::max
+
+#include "base/logging.h"
+#include "grit/ui_resources.h"
+#include "grit/ui_resources_standard.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas_skia.h"
+#include "ui/gfx/path.h"
+
+namespace views {
+
+struct BubbleBorder::BorderImages {
+ BorderImages()
+ : left(NULL),
+ top_left(NULL),
+ top(NULL),
+ top_right(NULL),
+ right(NULL),
+ bottom_right(NULL),
+ bottom(NULL),
+ bottom_left(NULL),
+ left_arrow(NULL),
+ top_arrow(NULL),
+ right_arrow(NULL),
+ bottom_arrow(NULL) {
+ }
+
+ SkBitmap* left;
+ SkBitmap* top_left;
+ SkBitmap* top;
+ SkBitmap* top_right;
+ SkBitmap* right;
+ SkBitmap* bottom_right;
+ SkBitmap* bottom;
+ SkBitmap* bottom_left;
+ SkBitmap* left_arrow;
+ SkBitmap* top_arrow;
+ SkBitmap* right_arrow;
+ SkBitmap* bottom_arrow;
+};
+
+// static
+struct BubbleBorder::BorderImages* BubbleBorder::normal_images_ = NULL;
+struct BubbleBorder::BorderImages* BubbleBorder::shadow_images_ = NULL;
+
+
+// The height inside the arrow image, in pixels.
+static const int kArrowInteriorHeight = 7;
+
+BubbleBorder::BubbleBorder(ArrowLocation arrow_location, Shadow shadow)
+ : override_arrow_offset_(0),
+ arrow_location_(arrow_location),
+ alignment_(ALIGN_ARROW_TO_MID_ANCHOR),
+ background_color_(SK_ColorWHITE) {
+ images_ = GetBorderImages(shadow);
+
+ // Calculate horizontal and vertical insets for arrow by ensuring that
+ // the widest arrow and corner images will have enough room to avoid overlap
+ int offset_x =
+ (std::max(images_->top_arrow->width(),
+ images_->bottom_arrow->width()) / 2) +
+ std::max(std::max(images_->top_left->width(),
+ images_->top_right->width()),
+ std::max(images_->bottom_left->width(),
+ images_->bottom_right->width()));
+ int offset_y =
+ (std::max(images_->left_arrow->height(),
+ images_->right_arrow->height()) / 2) +
+ std::max(std::max(images_->top_left->height(),
+ images_->top_right->height()),
+ std::max(images_->bottom_left->height(),
+ images_->bottom_right->height()));
+ arrow_offset_ = std::max(offset_x, offset_y);
+}
+
+gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& position_relative_to,
+ const gfx::Size& contents_size) const {
+ // Desired size is size of contents enlarged by the size of the border images.
+ gfx::Size border_size(contents_size);
+ gfx::Insets insets;
+ GetInsets(&insets);
+ border_size.Enlarge(insets.left() + insets.right(),
+ insets.top() + insets.bottom());
+
+ // Screen position depends on the arrow location.
+ // The arrow should overlap the target by some amount since there is space
+ // for shadow between arrow tip and bitmap bounds.
+ const int kArrowOverlap = 3;
+ int x = position_relative_to.x();
+ int y = position_relative_to.y();
+ int w = position_relative_to.width();
+ int h = position_relative_to.height();
+ int arrow_offset = override_arrow_offset_ ? override_arrow_offset_ :
+ arrow_offset_;
+
+ // Calculate bubble x coordinate.
+ switch (arrow_location_) {
+ case TOP_LEFT:
+ case BOTTOM_LEFT:
+ x += alignment_ == ALIGN_ARROW_TO_MID_ANCHOR ? w / 2 - arrow_offset :
+ -kArrowOverlap;
+ break;
+
+ case TOP_RIGHT:
+ case BOTTOM_RIGHT:
+ x += alignment_ == ALIGN_ARROW_TO_MID_ANCHOR ?
+ w / 2 + arrow_offset - border_size.width() + 1 :
+ w - border_size.width() + kArrowOverlap;
+ break;
+
+ case LEFT_TOP:
+ case LEFT_BOTTOM:
+ x += w - kArrowOverlap;
+ break;
+
+ case RIGHT_TOP:
+ case RIGHT_BOTTOM:
+ x += kArrowOverlap - border_size.width();
+ break;
+
+ case NONE:
+ case FLOAT:
+ x += w / 2 - border_size.width() / 2;
+ break;
+ }
+
+ // Calculate bubble y coordinate.
+ switch (arrow_location_) {
+ case TOP_LEFT:
+ case TOP_RIGHT:
+ y += h - kArrowOverlap;
+ break;
+
+ case BOTTOM_LEFT:
+ case BOTTOM_RIGHT:
+ y += kArrowOverlap - border_size.height();
+ break;
+
+ case LEFT_TOP:
+ case RIGHT_TOP:
+ y += alignment_ == ALIGN_ARROW_TO_MID_ANCHOR ? h / 2 - arrow_offset :
+ -kArrowOverlap;
+ break;
+
+ case LEFT_BOTTOM:
+ case RIGHT_BOTTOM:
+ y += alignment_ == ALIGN_ARROW_TO_MID_ANCHOR ?
+ h / 2 + arrow_offset - border_size.height() + 1 :
+ h - border_size.height() + kArrowOverlap;
+ break;
+
+ case NONE:
+ y += h;
+ break;
+
+ case FLOAT:
+ y += h / 2 - border_size.height() / 2;
+ break;
+ }
+
+ return gfx::Rect(x, y, border_size.width(), border_size.height());
+}
+
+void BubbleBorder::GetInsets(gfx::Insets* insets) const {
+ int top = images_->top->height();
+ int bottom = images_->bottom->height();
+ int left = images_->left->width();
+ int right = images_->right->width();
+ switch (arrow_location_) {
+ case TOP_LEFT:
+ case TOP_RIGHT:
+ top = std::max(top, images_->top_arrow->height());
+ break;
+
+ case BOTTOM_LEFT:
+ case BOTTOM_RIGHT:
+ bottom = std::max(bottom, images_->bottom_arrow->height());
+ break;
+
+ case LEFT_TOP:
+ case LEFT_BOTTOM:
+ left = std::max(left, images_->left_arrow->width());
+ break;
+
+ case RIGHT_TOP:
+ case RIGHT_BOTTOM:
+ right = std::max(right, images_->right_arrow->width());
+ break;
+
+ case NONE:
+ case FLOAT:
+ // Nothing to do.
+ break;
+ }
+ insets->Set(top, left, bottom, right);
+}
+
+int BubbleBorder::SetArrowOffset(int offset, const gfx::Size& contents_size) {
+ gfx::Size border_size(contents_size);
+ gfx::Insets insets;
+ GetInsets(&insets);
+ border_size.Enlarge(insets.left() + insets.right(),
+ insets.top() + insets.bottom());
+ offset = std::max(arrow_offset_,
+ std::min(offset, (is_arrow_on_horizontal(arrow_location_) ?
+ border_size.width() : border_size.height()) - arrow_offset_));
+ override_arrow_offset_ = offset;
+ return override_arrow_offset_;
+}
+
+// static
+BubbleBorder::BorderImages* BubbleBorder::GetBorderImages(Shadow shadow) {
+ if (shadow == SHADOW && shadow_images_ == NULL) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ shadow_images_ = new BorderImages();
+ shadow_images_->left = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_L);
+ shadow_images_->top_left = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_TL);
+ shadow_images_->top = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_T);
+ shadow_images_->top_right = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_TR);
+ shadow_images_->right = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_R);
+ shadow_images_->bottom_right = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_BR);
+ shadow_images_->bottom = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_B);
+ shadow_images_->bottom_left = rb.GetBitmapNamed(IDR_BUBBLE_SHADOW_BL);
+ shadow_images_->left_arrow = new SkBitmap();
+ shadow_images_->top_arrow = new SkBitmap();
+ shadow_images_->right_arrow = new SkBitmap();
+ shadow_images_->bottom_arrow = new SkBitmap();
+ } else if (shadow == NO_SHADOW && normal_images_ == NULL) {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ normal_images_ = new BorderImages();
+ normal_images_->left = rb.GetBitmapNamed(IDR_BUBBLE_L);
+ normal_images_->top_left = rb.GetBitmapNamed(IDR_BUBBLE_TL);
+ normal_images_->top = rb.GetBitmapNamed(IDR_BUBBLE_T);
+ normal_images_->top_right = rb.GetBitmapNamed(IDR_BUBBLE_TR);
+ normal_images_->right = rb.GetBitmapNamed(IDR_BUBBLE_R);
+ normal_images_->bottom_right = rb.GetBitmapNamed(IDR_BUBBLE_BR);
+ normal_images_->bottom = rb.GetBitmapNamed(IDR_BUBBLE_B);
+ normal_images_->bottom_left = rb.GetBitmapNamed(IDR_BUBBLE_BL);
+ normal_images_->left_arrow = rb.GetBitmapNamed(IDR_BUBBLE_L_ARROW);
+ normal_images_->top_arrow = rb.GetBitmapNamed(IDR_BUBBLE_T_ARROW);
+ normal_images_->right_arrow = rb.GetBitmapNamed(IDR_BUBBLE_R_ARROW);
+ normal_images_->bottom_arrow = rb.GetBitmapNamed(IDR_BUBBLE_B_ARROW);
+ }
+ return shadow == SHADOW ? shadow_images_ : normal_images_;
+}
+
+BubbleBorder::~BubbleBorder() {}
+
+void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) const {
+ // Convenience shorthand variables.
+ const int tl_width = images_->top_left->width();
+ const int tl_height = images_->top_left->height();
+ const int t_height = images_->top->height();
+ const int tr_width = images_->top_right->width();
+ const int tr_height = images_->top_right->height();
+ const int l_width = images_->left->width();
+ const int r_width = images_->right->width();
+ const int br_width = images_->bottom_right->width();
+ const int br_height = images_->bottom_right->height();
+ const int b_height = images_->bottom->height();
+ const int bl_width = images_->bottom_left->width();
+ const int bl_height = images_->bottom_left->height();
+
+ gfx::Insets insets;
+ GetInsets(&insets);
+ const int top = insets.top() - t_height;
+ const int bottom = view.height() - insets.bottom() + b_height;
+ const int left = insets.left() - l_width;
+ const int right = view.width() - insets.right() + r_width;
+ const int height = bottom - top;
+ const int width = right - left;
+
+ // |arrow_offset| is offset of arrow from the begining of the edge.
+ int arrow_offset = arrow_offset_;
+ if (override_arrow_offset_)
+ arrow_offset = override_arrow_offset_;
+ else if (is_arrow_on_horizontal(arrow_location_) &&
+ !is_arrow_on_left(arrow_location_)) {
+ arrow_offset = view.width() - arrow_offset - 1;
+ } else if (!is_arrow_on_horizontal(arrow_location_) &&
+ !is_arrow_on_top(arrow_location_)) {
+ arrow_offset = view.height() - arrow_offset - 1;
+ }
+
+ // Left edge.
+ if (arrow_location_ == LEFT_TOP || arrow_location_ == LEFT_BOTTOM) {
+ int start_y = top + tl_height;
+ int before_arrow =
+ arrow_offset - start_y - images_->left_arrow->height() / 2;
+ int after_arrow = height - tl_height - bl_height -
+ images_->left_arrow->height() - before_arrow;
+ int tip_y = start_y + before_arrow + images_->left_arrow->height() / 2;
+ DrawArrowInterior(canvas,
+ false,
+ images_->left_arrow->width() - kArrowInteriorHeight,
+ tip_y,
+ kArrowInteriorHeight,
+ images_->left_arrow->height() / 2 - 1);
+ DrawEdgeWithArrow(canvas,
+ false,
+ images_->left,
+ images_->left_arrow,
+ left,
+ start_y,
+ before_arrow,
+ after_arrow,
+ images_->left->width() - images_->left_arrow->width());
+ } else {
+ canvas->TileImageInt(*images_->left, left, top + tl_height, l_width,
+ height - tl_height - bl_height);
+ }
+
+ // Top left corner.
+ canvas->DrawBitmapInt(*images_->top_left, left, top);
+
+ // Top edge.
+ if (arrow_location_ == TOP_LEFT || arrow_location_ == TOP_RIGHT) {
+ int start_x = left + tl_width;
+ int before_arrow = arrow_offset - start_x - images_->top_arrow->width() / 2;
+ int after_arrow = width - tl_width - tr_width -
+ images_->top_arrow->width() - before_arrow;
+ DrawArrowInterior(canvas,
+ true,
+ start_x + before_arrow + images_->top_arrow->width() / 2,
+ images_->top_arrow->height() - kArrowInteriorHeight,
+ 1 - images_->top_arrow->width() / 2,
+ kArrowInteriorHeight);
+ DrawEdgeWithArrow(canvas,
+ true,
+ images_->top,
+ images_->top_arrow,
+ start_x,
+ top,
+ before_arrow,
+ after_arrow,
+ images_->top->height() - images_->top_arrow->height());
+ } else {
+ canvas->TileImageInt(*images_->top, left + tl_width, top,
+ width - tl_width - tr_width, t_height);
+ }
+
+ // Top right corner.
+ canvas->DrawBitmapInt(*images_->top_right, right - tr_width, top);
+
+ // Right edge.
+ if (arrow_location_ == RIGHT_TOP || arrow_location_ == RIGHT_BOTTOM) {
+ int start_y = top + tr_height;
+ int before_arrow =
+ arrow_offset - start_y - images_->right_arrow->height() / 2;
+ int after_arrow = height - tl_height - bl_height -
+ images_->right_arrow->height() - before_arrow;
+ int tip_y = start_y + before_arrow + images_->right_arrow->height() / 2;
+ DrawArrowInterior(canvas,
+ false,
+ right - r_width + kArrowInteriorHeight,
+ tip_y,
+ -kArrowInteriorHeight,
+ images_->right_arrow->height() / 2 - 1);
+ DrawEdgeWithArrow(canvas,
+ false,
+ images_->right,
+ images_->right_arrow,
+ right - r_width,
+ start_y,
+ before_arrow,
+ after_arrow,
+ 0);
+ } else {
+ canvas->TileImageInt(*images_->right, right - r_width, top + tr_height,
+ r_width, height - tr_height - br_height);
+ }
+
+ // Bottom right corner.
+ canvas->DrawBitmapInt(*images_->bottom_right,
+ right - br_width,
+ bottom - br_height);
+
+ // Bottom edge.
+ if (arrow_location_ == BOTTOM_LEFT || arrow_location_ == BOTTOM_RIGHT) {
+ int start_x = left + bl_width;
+ int before_arrow =
+ arrow_offset - start_x - images_->bottom_arrow->width() / 2;
+ int after_arrow = width - bl_width - br_width -
+ images_->bottom_arrow->width() - before_arrow;
+ int tip_x = start_x + before_arrow + images_->bottom_arrow->width() / 2;
+ DrawArrowInterior(canvas,
+ true,
+ tip_x,
+ bottom - b_height + kArrowInteriorHeight,
+ 1 - images_->bottom_arrow->width() / 2,
+ -kArrowInteriorHeight);
+ DrawEdgeWithArrow(canvas,
+ true,
+ images_->bottom,
+ images_->bottom_arrow,
+ start_x,
+ bottom - b_height,
+ before_arrow,
+ after_arrow,
+ 0);
+ } else {
+ canvas->TileImageInt(*images_->bottom, left + bl_width, bottom - b_height,
+ width - bl_width - br_width, b_height);
+ }
+
+ // Bottom left corner.
+ canvas->DrawBitmapInt(*images_->bottom_left, left, bottom - bl_height);
+}
+
+void BubbleBorder::DrawEdgeWithArrow(gfx::Canvas* canvas,
+ bool is_horizontal,
+ SkBitmap* edge,
+ SkBitmap* arrow,
+ int start_x,
+ int start_y,
+ int before_arrow,
+ int after_arrow,
+ int offset) const {
+ /* Here's what the parameters mean:
+ * start_x
+ * .
+ * . ┌───┐ ┬ offset
+ * start_y..........┌────┬────────┤ ▲ ├────────┬────┐
+ * │ / │--------│∙ ∙│--------│ \ │
+ * │ / ├────────┴───┴────────┤ \ │
+ * ├───┬┘ └┬───┤
+ * └───┬────┘ └───┬────┘
+ * before_arrow ─┘ └─ after_arrow
+ */
+ if (before_arrow) {
+ canvas->TileImageInt(*edge, start_x, start_y,
+ is_horizontal ? before_arrow : edge->width(),
+ is_horizontal ? edge->height() : before_arrow);
+ }
+
+ canvas->DrawBitmapInt(*arrow,
+ start_x + (is_horizontal ? before_arrow : offset),
+ start_y + (is_horizontal ? offset : before_arrow));
+
+ if (after_arrow) {
+ start_x += (is_horizontal ? before_arrow + arrow->width() : 0);
+ start_y += (is_horizontal ? 0 : before_arrow + arrow->height());
+ canvas->TileImageInt(*edge, start_x, start_y,
+ is_horizontal ? after_arrow : edge->width(),
+ is_horizontal ? edge->height() : after_arrow);
+ }
+}
+
+void BubbleBorder::DrawArrowInterior(gfx::Canvas* canvas,
+ bool is_horizontal,
+ int tip_x,
+ int tip_y,
+ int shift_x,
+ int shift_y) const {
+ /* This function fills the interior of the arrow with background color.
+ * It draws isosceles triangle under semitransparent arrow tip.
+ *
+ * Here's what the parameters mean:
+ *
+ * ┌──────── |tip_x|
+ * ┌─────┐
+ * │ ▲ │ ──── |tip y|
+ * │∙∙∙∙∙│ ┐
+ * └─────┘ └─── |shift_x| (offset from tip to vertexes of isosceles triangle)
+ * └────────── |shift_y|
+ */
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(background_color_);
+ gfx::Path path;
+ path.incReserve(4);
+ path.moveTo(SkIntToScalar(tip_x), SkIntToScalar(tip_y));
+ path.lineTo(SkIntToScalar(tip_x + shift_x),
+ SkIntToScalar(tip_y + shift_y));
+ if (is_horizontal)
+ path.lineTo(SkIntToScalar(tip_x - shift_x), SkIntToScalar(tip_y + shift_y));
+ else
+ path.lineTo(SkIntToScalar(tip_x + shift_x), SkIntToScalar(tip_y - shift_y));
+ path.close();
+ canvas->GetSkCanvas()->drawPath(path, paint);
+}
+
+/////////////////////////
+
+void BubbleBackground::Paint(gfx::Canvas* canvas, views::View* view) const {
+ // The border of this view creates an anti-aliased round-rect region for the
+ // contents, which we need to fill with the background color.
+ // NOTE: This doesn't handle an arrow location of "NONE", which has square top
+ // corners.
+ SkPaint paint;
+ paint.setAntiAlias(true);
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setColor(border_->background_color());
+ gfx::Path path;
+ gfx::Rect bounds(view->GetContentsBounds());
+ SkRect rect;
+ rect.set(SkIntToScalar(bounds.x()), SkIntToScalar(bounds.y()),
+ SkIntToScalar(bounds.right()), SkIntToScalar(bounds.bottom()));
+ SkScalar radius = SkIntToScalar(BubbleBorder::GetCornerRadius());
+ path.addRoundRect(rect, radius, radius);
+ canvas->GetSkCanvas()->drawPath(path, paint);
+}
+
+} // namespace views
diff --git a/ui/views/bubble/bubble_border.h b/ui/views/bubble/bubble_border.h
new file mode 100644
index 0000000..09e20bb
--- /dev/null
+++ b/ui/views/bubble/bubble_border.h
@@ -0,0 +1,183 @@
+// Copyright (c) 2011 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 UI_VIEWS_BUBBLE_BUBBLE_BORDER_H_
+#define UI_VIEWS_BUBBLE_BUBBLE_BORDER_H_
+#pragma once
+
+#include "views/background.h"
+#include "views/border.h"
+
+class SkBitmap;
+
+namespace views {
+
+// Renders a border, with optional arrow, and a custom dropshadow.
+// This can be used to produce floating "bubble" objects with rounded corners.
+class VIEWS_EXPORT BubbleBorder : public views::Border {
+ public:
+ // Possible locations for the (optional) arrow.
+ // 0 bit specifies left or right.
+ // 1 bit specifies top or bottom.
+ // 2 bit specifies horizontal or vertical.
+ enum ArrowLocation {
+ TOP_LEFT = 0,
+ TOP_RIGHT = 1,
+ BOTTOM_LEFT = 2,
+ BOTTOM_RIGHT = 3,
+ LEFT_TOP = 4,
+ RIGHT_TOP = 5,
+ LEFT_BOTTOM = 6,
+ RIGHT_BOTTOM = 7,
+ NONE = 8, // No arrow. Positioned under the supplied rect.
+ FLOAT = 9 // No arrow. Centered over the supplied rect.
+ };
+
+ enum Shadow {
+ SHADOW = 0,
+ NO_SHADOW = 1
+ };
+
+ // The position of the bubble in relation to the anchor.
+ enum BubbleAlignment {
+ // The tip of the arrow points to the middle of the anchor.
+ ALIGN_ARROW_TO_MID_ANCHOR,
+ // The edge nearest to the arrow is lined up with the edge of the anchor.
+ ALIGN_EDGE_TO_ANCHOR_EDGE
+ };
+
+ BubbleBorder(ArrowLocation arrow_location, Shadow shadow);
+
+ // Returns the radius of the corner of the border.
+ static int GetCornerRadius() {
+ // We can't safely calculate a border radius by comparing the sizes of the
+ // side and corner images, because either may have been extended in various
+ // directions in order to do more subtle dropshadow fading or other effects.
+ // So we hardcode the most accurate value.
+ return 4;
+ }
+
+ // Sets the location for the arrow.
+ void set_arrow_location(ArrowLocation arrow_location) {
+ arrow_location_ = arrow_location;
+ }
+ ArrowLocation arrow_location() const { return arrow_location_; }
+
+ // Sets the alignment.
+ void set_alignment(BubbleAlignment alignment) { alignment_ = alignment; }
+ BubbleAlignment alignment() const { return alignment_; }
+
+ static ArrowLocation horizontal_mirror(ArrowLocation loc) {
+ return loc >= NONE ? loc : static_cast<ArrowLocation>(loc ^ 1);
+ }
+
+ static ArrowLocation vertical_mirror(ArrowLocation loc) {
+ return loc >= NONE ? loc : static_cast<ArrowLocation>(loc ^ 2);
+ }
+
+ static bool has_arrow(ArrowLocation loc) {
+ return loc >= NONE ? false : true;
+ }
+
+ static bool is_arrow_on_left(ArrowLocation loc) {
+ return loc >= NONE ? false : !(loc & 1);
+ }
+
+ static bool is_arrow_on_top(ArrowLocation loc) {
+ return loc >= NONE ? false : !(loc & 2);
+ }
+
+ static bool is_arrow_on_horizontal(ArrowLocation loc) {
+ return loc >= NONE ? false : !(loc & 4);
+ }
+
+ // Sets the background color for the arrow body. This is irrelevant if you do
+ // not also set the arrow location to something other than NONE.
+ void set_background_color(SkColor background_color) {
+ background_color_ = background_color;
+ }
+ SkColor background_color() const { return background_color_; }
+
+ // For borders with an arrow, gives the desired bounds (in screen coordinates)
+ // given the rect to point to and the size of the contained contents. This
+ // depends on the arrow location, so if you change that, you should call this
+ // again to find out the new coordinates.
+ gfx::Rect GetBounds(const gfx::Rect& position_relative_to,
+ const gfx::Size& contents_size) const;
+
+ // Sets a fixed offset for the arrow from the beginning of corresponding edge.
+ // The arrow will still point to the same location but the bubble will shift
+ // location to make that happen. Returns actuall arrow offset, in case of
+ // overflow it differ from desired.
+ int SetArrowOffset(int offset, const gfx::Size& contents_size);
+
+ // Overridden from views::Border:
+ virtual void GetInsets(gfx::Insets* insets) const;
+
+ private:
+ struct BorderImages;
+
+ // Loads images if necessary.
+ static BorderImages* GetBorderImages(Shadow shadow);
+
+ virtual ~BubbleBorder();
+
+ // Overridden from views::Border:
+ virtual void Paint(const views::View& view, gfx::Canvas* canvas) const;
+
+ void DrawEdgeWithArrow(gfx::Canvas* canvas,
+ bool is_horizontal,
+ SkBitmap* edge,
+ SkBitmap* arrow,
+ int start_x,
+ int start_y,
+ int before_arrow,
+ int after_arrow,
+ int offset) const;
+
+ void DrawArrowInterior(gfx::Canvas* canvas,
+ bool is_horizontal,
+ int tip_x,
+ int tip_y,
+ int shift_x,
+ int shift_y) const;
+
+ // Border graphics.
+ struct BorderImages* images_;
+
+ // Image bundles.
+ static struct BorderImages* normal_images_;
+ static struct BorderImages* shadow_images_;
+
+ // Minimal offset of the arrow from the closet edge of bounding rect.
+ int arrow_offset_;
+
+ // If specified, overrides the pre-calculated |arrow_offset_| of the arrow.
+ int override_arrow_offset_;
+
+ ArrowLocation arrow_location_;
+ BubbleAlignment alignment_;
+ SkColor background_color_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleBorder);
+};
+
+// A Background that clips itself to the specified BubbleBorder and uses
+// the background color of the BubbleBorder.
+class VIEWS_EXPORT BubbleBackground : public views::Background {
+ public:
+ explicit BubbleBackground(BubbleBorder* border) : border_(border) {}
+
+ // Background overrides.
+ virtual void Paint(gfx::Canvas* canvas, views::View* view) const;
+
+ private:
+ BubbleBorder* border_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleBackground);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_BUBBLE_BUBBLE_BORDER_H_
diff --git a/ui/views/bubble/bubble_delegate.cc b/ui/views/bubble/bubble_delegate.cc
new file mode 100644
index 0000000..b3baea4
--- /dev/null
+++ b/ui/views/bubble/bubble_delegate.cc
@@ -0,0 +1,287 @@
+// Copyright (c) 2011 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/bubble/bubble_delegate.h"
+
+#include "ui/base/animation/slide_animation.h"
+#include "views/bubble/bubble_frame_view.h"
+#include "views/widget/widget.h"
+
+// The duration of the fade animation in milliseconds.
+static const int kHideFadeDurationMS = 200;
+
+namespace views {
+
+namespace {
+
+// Create a widget to host the bubble.
+Widget* CreateBubbleWidget(BubbleDelegateView* bubble, Widget* parent) {
+ Widget* bubble_widget = new Widget();
+ Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE);
+ bubble_params.delegate = bubble;
+ bubble_params.transparent = true;
+ bubble_params.parent_widget = parent;
+#if defined(OS_WIN) && !defined(USE_AURA)
+ bubble_params.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS;
+ bubble_params.transparent = false;
+#endif
+ bubble_widget->Init(bubble_params);
+ return bubble_widget;
+}
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+// The border widget's delegate, needed for transparent Windows native controls.
+// TODO(msw): Remove this when Windows native controls are no longer needed.
+class VIEWS_EXPORT BubbleBorderDelegateView : public WidgetDelegateView {
+ public:
+ explicit BubbleBorderDelegateView(BubbleDelegateView* bubble)
+ : bubble_(bubble) {}
+ virtual ~BubbleBorderDelegateView() {}
+
+ // WidgetDelegateView overrides:
+ virtual bool CanActivate() const OVERRIDE;
+ virtual NonClientFrameView* CreateNonClientFrameView() OVERRIDE;
+
+ private:
+ BubbleDelegateView* bubble_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleBorderDelegateView);
+};
+
+bool BubbleBorderDelegateView::CanActivate() const { return false; }
+
+NonClientFrameView* BubbleBorderDelegateView::CreateNonClientFrameView() {
+ return bubble_->CreateNonClientFrameView();
+}
+
+// Create a widget to host the bubble's border.
+Widget* CreateBorderWidget(BubbleDelegateView* bubble, Widget* parent) {
+ Widget* border_widget = new Widget();
+ Widget::InitParams border_params(Widget::InitParams::TYPE_BUBBLE);
+ border_params.delegate = new BubbleBorderDelegateView(bubble);
+ border_params.transparent = true;
+ border_params.parent_widget = parent;
+ if (!border_params.parent_widget)
+ border_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ border_widget->Init(border_params);
+ return border_widget;
+}
+#endif
+
+} // namespace
+
+BubbleDelegateView::BubbleDelegateView()
+ : close_on_esc_(true),
+ close_on_deactivate_(true),
+ allow_bubble_offscreen_(false),
+ anchor_view_(NULL),
+ arrow_location_(BubbleBorder::TOP_LEFT),
+ color_(SK_ColorWHITE),
+ border_widget_(NULL),
+ use_focusless_(false) {
+ set_background(views::Background::CreateSolidBackground(color_));
+ AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, 0));
+}
+
+BubbleDelegateView::BubbleDelegateView(
+ View* anchor_view,
+ BubbleBorder::ArrowLocation arrow_location,
+ const SkColor& color)
+ : close_on_esc_(true),
+ close_on_deactivate_(true),
+ allow_bubble_offscreen_(false),
+ anchor_view_(anchor_view),
+ arrow_location_(arrow_location),
+ color_(color),
+ original_opacity_(255),
+ border_widget_(NULL),
+ use_focusless_(false) {
+ set_background(views::Background::CreateSolidBackground(color_));
+ AddAccelerator(ui::Accelerator(ui::VKEY_ESCAPE, 0));
+}
+
+BubbleDelegateView::~BubbleDelegateView() {
+ if (border_widget_)
+ border_widget_->Close();
+}
+
+// static
+Widget* BubbleDelegateView::CreateBubble(BubbleDelegateView* bubble_delegate) {
+ bubble_delegate->Init();
+ Widget* parent = bubble_delegate->anchor_view() ?
+ bubble_delegate->anchor_view()->GetWidget() : NULL;
+ Widget* bubble_widget = CreateBubbleWidget(bubble_delegate, parent);
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+ // First set the contents view to initialize view bounds for widget sizing.
+ bubble_widget->SetContentsView(bubble_delegate->GetContentsView());
+ bubble_delegate->border_widget_ = CreateBorderWidget(bubble_delegate, parent);
+#endif
+
+ bubble_delegate->SizeToContents();
+ bubble_widget->AddObserver(bubble_delegate);
+ if (parent && parent->GetTopLevelWidget())
+ parent->GetTopLevelWidget()->DisableInactiveRendering();
+ return bubble_widget;
+}
+
+View* BubbleDelegateView::GetInitiallyFocusedView() {
+ return this;
+}
+
+BubbleDelegateView* BubbleDelegateView::AsBubbleDelegate() {
+ return this;
+}
+
+View* BubbleDelegateView::GetContentsView() {
+ return this;
+}
+
+NonClientFrameView* BubbleDelegateView::CreateNonClientFrameView() {
+ return new BubbleFrameView(GetArrowLocation(),
+ GetPreferredSize(),
+ GetColor(),
+ allow_bubble_offscreen_);
+}
+
+void BubbleDelegateView::OnWidgetActivationChanged(Widget* widget,
+ bool active) {
+ if (close_on_deactivate() && widget == GetWidget() && !active) {
+ GetWidget()->RemoveObserver(this);
+ GetWidget()->Close();
+ }
+}
+
+gfx::Point BubbleDelegateView::GetAnchorPoint() {
+ if (!anchor_view())
+ return gfx::Point();
+
+ BubbleBorder::ArrowLocation location = GetArrowLocation();
+ gfx::Point anchor(anchor_view()->bounds().CenterPoint());
+ // By default, pick the middle of |anchor_view_|'s edge opposite the arrow.
+ if (BubbleBorder::is_arrow_on_horizontal(location)) {
+ anchor.SetPoint(anchor_view()->width() / 2,
+ BubbleBorder::is_arrow_on_top(location) ? anchor_view()->height() : 0);
+ } else if (BubbleBorder::has_arrow(location)) {
+ anchor.SetPoint(
+ BubbleBorder::is_arrow_on_left(location) ? anchor_view()->width() : 0,
+ anchor_view_->height() / 2);
+ }
+ View::ConvertPointToScreen(anchor_view(), &anchor);
+ return anchor;
+}
+
+BubbleBorder::ArrowLocation BubbleDelegateView::GetArrowLocation() const {
+ return arrow_location_;
+}
+
+SkColor BubbleDelegateView::GetColor() const {
+ return color_;
+}
+
+void BubbleDelegateView::Show() {
+ if (border_widget_)
+ border_widget_->Show();
+ GetWidget()->Show();
+ GetFocusManager()->SetFocusedView(GetInitiallyFocusedView());
+}
+
+void BubbleDelegateView::StartFade(bool fade_in) {
+ fade_animation_.reset(new ui::SlideAnimation(this));
+ fade_animation_->SetSlideDuration(kHideFadeDurationMS);
+ fade_animation_->Reset(fade_in ? 0.0 : 1.0);
+ if (fade_in) {
+ original_opacity_ = 0;
+ if (border_widget_)
+ border_widget_->SetOpacity(original_opacity_);
+ GetWidget()->SetOpacity(original_opacity_);
+ Show();
+ fade_animation_->Show();
+ } else {
+ original_opacity_ = 255;
+ fade_animation_->Hide();
+ }
+}
+
+void BubbleDelegateView::ResetFade() {
+ fade_animation_.reset();
+ if (border_widget_)
+ border_widget_->SetOpacity(original_opacity_);
+ GetWidget()->SetOpacity(original_opacity_);
+}
+
+bool BubbleDelegateView::AcceleratorPressed(
+ const ui::Accelerator& accelerator) {
+ if (!close_on_esc() || accelerator.key_code() != ui::VKEY_ESCAPE)
+ return false;
+ if (fade_animation_.get())
+ fade_animation_->Reset();
+ GetWidget()->Close();
+ return true;
+}
+
+void BubbleDelegateView::AnimationEnded(const ui::Animation* animation) {
+ if (animation != fade_animation_.get())
+ return;
+ bool closed = fade_animation_->GetCurrentValue() == 0;
+ fade_animation_->Reset();
+ if (closed)
+ GetWidget()->Close();
+}
+
+void BubbleDelegateView::AnimationProgressed(const ui::Animation* animation) {
+ if (animation != fade_animation_.get())
+ return;
+ DCHECK(fade_animation_->is_animating());
+ unsigned char opacity = fade_animation_->GetCurrentValue() * 255;
+#if defined(OS_WIN) && !defined(USE_AURA)
+ // Explicitly set the content Widget's layered style and set transparency via
+ // SetLayeredWindowAttributes. This is done because initializing the Widget as
+ // transparent and setting opacity via UpdateLayeredWindow doesn't support
+ // hosting child native Windows controls.
+ const HWND hwnd = GetWidget()->GetNativeView();
+ const DWORD style = GetWindowLong(hwnd, GWL_EXSTYLE);
+ if ((opacity == 255) == !!(style & WS_EX_LAYERED))
+ SetWindowLong(hwnd, GWL_EXSTYLE, style ^ WS_EX_LAYERED);
+ SetLayeredWindowAttributes(hwnd, 0, opacity, LWA_ALPHA);
+ // Update the border widget's opacity.
+ border_widget_->SetOpacity(opacity);
+ border_widget_->non_client_view()->SchedulePaint();
+#endif
+ GetWidget()->SetOpacity(opacity);
+ SchedulePaint();
+}
+
+void BubbleDelegateView::Init() {}
+
+void BubbleDelegateView::SizeToContents() {
+#if defined(OS_WIN) && !defined(USE_AURA)
+ border_widget_->SetBounds(GetBubbleBounds());
+ GetWidget()->SetBounds(GetBubbleClientBounds());
+#else
+ GetWidget()->SetBounds(GetBubbleBounds());
+#endif
+}
+
+BubbleFrameView* BubbleDelegateView::GetBubbleFrameView() const {
+ const Widget* widget = border_widget_ ? border_widget_ : GetWidget();
+ return static_cast<BubbleFrameView*>(widget->non_client_view()->frame_view());
+}
+
+gfx::Rect BubbleDelegateView::GetBubbleBounds() {
+ // The argument rect has its origin at the bubble's arrow anchor point;
+ // its size is the preferred size of the bubble's client view (this view).
+ return GetBubbleFrameView()->GetWindowBoundsForClientBounds(
+ gfx::Rect(GetAnchorPoint(), GetPreferredSize()));
+}
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+gfx::Rect BubbleDelegateView::GetBubbleClientBounds() const {
+ gfx::Rect client_bounds(GetBubbleFrameView()->GetBoundsForClientView());
+ client_bounds.Offset(border_widget_->GetWindowScreenBounds().origin());
+ return client_bounds;
+}
+#endif
+
+} // namespace views
diff --git a/ui/views/bubble/bubble_delegate.h b/ui/views/bubble/bubble_delegate.h
new file mode 100644
index 0000000..a21bfab
--- /dev/null
+++ b/ui/views/bubble/bubble_delegate.h
@@ -0,0 +1,153 @@
+// Copyright (c) 2011 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 UI_VIEWS_BUBBLE_BUBBLE_DELEGATE_H_
+#define UI_VIEWS_BUBBLE_BUBBLE_DELEGATE_H_
+#pragma once
+
+#include "base/gtest_prod_util.h"
+#include "ui/base/animation/animation_delegate.h"
+#include "views/bubble/bubble_border.h"
+#include "views/widget/widget.h"
+#include "views/widget/widget_delegate.h"
+
+namespace ui {
+class SlideAnimation;
+} // namespace ui
+
+namespace views {
+
+class BubbleFrameView;
+
+// BubbleDelegateView creates frame and client views for bubble Widgets.
+// BubbleDelegateView itself is the client's contents view.
+//
+///////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT BubbleDelegateView : public WidgetDelegateView,
+ public ui::AnimationDelegate,
+ public Widget::Observer {
+ public:
+ BubbleDelegateView();
+ BubbleDelegateView(View* anchor_view,
+ BubbleBorder::ArrowLocation arrow_location,
+ const SkColor& color);
+ virtual ~BubbleDelegateView();
+
+ // Create and initialize the bubble Widget(s) with proper bounds.
+ static Widget* CreateBubble(BubbleDelegateView* bubble_delegate);
+
+ // WidgetDelegate overrides:
+ virtual View* GetInitiallyFocusedView() OVERRIDE;
+ virtual BubbleDelegateView* AsBubbleDelegate() OVERRIDE;
+ virtual View* GetContentsView() OVERRIDE;
+ virtual NonClientFrameView* CreateNonClientFrameView() OVERRIDE;
+
+ // Widget::Observer overrides:
+ virtual void OnWidgetActivationChanged(Widget* widget, bool active) OVERRIDE;
+
+ bool close_on_esc() const { return close_on_esc_; }
+ void set_close_on_esc(bool close_on_esc) { close_on_esc_ = close_on_esc; }
+
+ bool close_on_deactivate() const { return close_on_deactivate_; }
+ void set_close_on_deactivate(bool close_on_deactivate) {
+ close_on_deactivate_ = close_on_deactivate;
+ }
+
+ bool allow_bubble_offscreen() const { return allow_bubble_offscreen_; }
+ void set_allow_bubble_offscreen(bool allow_bubble_offscreen) {
+ allow_bubble_offscreen_ = allow_bubble_offscreen;
+ }
+
+ View* anchor_view() const { return anchor_view_; }
+
+ bool use_focusless() const { return use_focusless_; }
+ void set_use_focusless(bool use_focusless) {
+ use_focusless_ = use_focusless;
+ }
+
+ // Get the arrow's anchor point in screen space.
+ virtual gfx::Point GetAnchorPoint();
+
+ // Get the arrow's location on the bubble.
+ virtual BubbleBorder::ArrowLocation GetArrowLocation() const;
+
+ // Get the color used for the background and border.
+ virtual SkColor GetColor() const;
+
+ // Show the bubble's widget (and |border_widget_| on Windows).
+ void Show();
+
+ // Fade the bubble in or out via Widget transparency.
+ // Fade in calls Widget::Show; fade out calls Widget::Close upon completion.
+ void StartFade(bool fade_in);
+
+ // Reset fade and opacity of bubble. Restore the opacity of the
+ // bubble to the setting before StartFade() was called.
+ void ResetFade();
+
+ protected:
+ // View overrides:
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE;
+
+ // ui::AnimationDelegate overrides:
+ virtual void AnimationEnded(const ui::Animation* animation) OVERRIDE;
+ virtual void AnimationProgressed(const ui::Animation* animation) OVERRIDE;
+
+ // Perform view initialization on the contents for bubble sizing.
+ virtual void Init();
+
+ // Resizes and potentially moves the Bubble to best accommodate the
+ // contents preferred size.
+ void SizeToContents();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewBasicTest, NonClientHitTest);
+ FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, CreateDelegate);
+
+ BubbleFrameView* GetBubbleFrameView() const;
+
+ // Get bubble bounds from the anchor point and client view's preferred size.
+ gfx::Rect GetBubbleBounds();
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+ // Get bounds for the Windows-only widget that hosts the bubble's contents.
+ gfx::Rect GetBubbleClientBounds() const;
+#endif
+
+ // Fade animation for bubble.
+ scoped_ptr<ui::SlideAnimation> fade_animation_;
+
+ // Flags controlling bubble closure on the escape key and deactivation.
+ bool close_on_esc_;
+ bool close_on_deactivate_;
+
+ // Whether the bubble is allowed to be displayed offscreen, or if auto
+ // re-positioning should be performed.
+ bool allow_bubble_offscreen_;
+
+ // The view hosting this bubble; the arrow is anchored to this view.
+ View* anchor_view_;
+
+ // The arrow's location on the bubble.
+ BubbleBorder::ArrowLocation arrow_location_;
+
+ // The background color of the bubble.
+ SkColor color_;
+
+ // Original opacity of the bubble.
+ int original_opacity_;
+
+ // The widget hosting the border for this bubble (non-Aura Windows only).
+ Widget* border_widget_;
+
+ // Create a popup window for focusless bubbles on Linux/ChromeOS.
+ // These bubbles are not interactive and should not gain focus.
+ bool use_focusless_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleDelegateView);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_BUBBLE_BUBBLE_DELEGATE_H_
diff --git a/ui/views/bubble/bubble_delegate_unittest.cc b/ui/views/bubble/bubble_delegate_unittest.cc
new file mode 100644
index 0000000..f6d256a
--- /dev/null
+++ b/ui/views/bubble/bubble_delegate_unittest.cc
@@ -0,0 +1,31 @@
+// Copyright (c) 2011 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 "ui/views/test/views_test_base.h"
+#include "views/bubble/bubble_delegate.h"
+#include "views/bubble/bubble_frame_view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+typedef ViewsTestBase BubbleDelegateTest;
+
+TEST_F(BubbleDelegateTest, CreateDelegate) {
+ BubbleDelegateView* bubble_delegate = new BubbleDelegateView(
+ NULL, BubbleBorder::NONE, SK_ColorGREEN);
+ Widget* bubble_widget(
+ BubbleDelegateView::CreateBubble(bubble_delegate));
+ EXPECT_EQ(bubble_delegate, bubble_widget->widget_delegate());
+ EXPECT_EQ(bubble_widget, bubble_delegate->GetWidget());
+
+ BubbleBorder* border =
+ bubble_delegate->GetBubbleFrameView()->bubble_border();
+ EXPECT_EQ(bubble_delegate->GetArrowLocation(), border->arrow_location());
+ EXPECT_EQ(bubble_delegate->GetColor(), border->background_color());
+
+ bubble_widget->CloseNow();
+ RunPendingMessages();
+}
+
+} // namespace views
diff --git a/ui/views/bubble/bubble_frame_view.cc b/ui/views/bubble/bubble_frame_view.cc
new file mode 100644
index 0000000..85e1f6e
--- /dev/null
+++ b/ui/views/bubble/bubble_frame_view.cc
@@ -0,0 +1,78 @@
+// Copyright (c) 2011 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/bubble/bubble_frame_view.h"
+
+#include <algorithm>
+
+#include "ui/views/window/client_view.h"
+#include "views/bubble/border_contents_view.h"
+#include "views/bubble/bubble_border.h"
+#include "views/layout/fill_layout.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+BubbleFrameView::BubbleFrameView(BubbleBorder::ArrowLocation location,
+ const gfx::Size& client_size,
+ SkColor color,
+ bool allow_bubble_offscreen)
+ : border_contents_(new BorderContentsView()),
+ location_(location),
+ allow_bubble_offscreen_(allow_bubble_offscreen) {
+ border_contents_->Init();
+ bubble_border()->set_arrow_location(location_);
+ bubble_border()->set_background_color(color);
+ SetLayoutManager(new views::FillLayout());
+ AddChildView(border_contents_);
+ gfx::Rect bounds(gfx::Point(), client_size);
+ gfx::Rect windows_bounds = GetWindowBoundsForClientBounds(bounds);
+ border_contents_->SetBoundsRect(
+ gfx::Rect(gfx::Point(), windows_bounds.size()));
+ SetBoundsRect(windows_bounds);
+}
+
+BubbleFrameView::~BubbleFrameView() {}
+
+gfx::Rect BubbleFrameView::GetBoundsForClientView() const {
+ gfx::Insets margin;
+ bubble_border()->GetInsets(&margin);
+ margin += border_contents_->content_margins();
+ return gfx::Rect(margin.left(),
+ margin.top(),
+ std::max(width() - margin.width(), 0),
+ std::max(height() - margin.height(), 0));
+}
+
+gfx::Rect BubbleFrameView::GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const {
+ // The |client_bounds| origin is the bubble arrow anchor point.
+ gfx::Rect position_relative_to(client_bounds.origin(), gfx::Size());
+ // The |client_bounds| size is the bubble client view size.
+ gfx::Rect content_bounds;
+ gfx::Rect window_bounds;
+ border_contents_->SizeAndGetBounds(position_relative_to,
+ location_,
+ allow_bubble_offscreen_,
+ client_bounds.size(),
+ &content_bounds,
+ &window_bounds);
+ return window_bounds;
+}
+
+int BubbleFrameView::NonClientHitTest(const gfx::Point& point) {
+ return GetWidget()->client_view()->NonClientHitTest(point);
+}
+
+gfx::Size BubbleFrameView::GetPreferredSize() {
+ Widget* widget = GetWidget();
+ gfx::Rect rect(gfx::Point(), widget->client_view()->GetPreferredSize());
+ return widget->non_client_view()->GetWindowBoundsForClientBounds(rect).size();
+}
+
+BubbleBorder* BubbleFrameView::bubble_border() const {
+ return static_cast<BubbleBorder*>(border_contents_->border());
+}
+
+} // namespace views
diff --git a/ui/views/bubble/bubble_frame_view.h b/ui/views/bubble/bubble_frame_view.h
new file mode 100644
index 0000000..f134725
--- /dev/null
+++ b/ui/views/bubble/bubble_frame_view.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011 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 UI_VIEWS_BUBBLE_BUBBLE_FRAME_VIEW_H_
+#define UI_VIEWS_BUBBLE_BUBBLE_FRAME_VIEW_H_
+#pragma once
+
+#include "base/gtest_prod_util.h"
+#include "ui/views/window/non_client_view.h"
+#include "views/bubble/bubble_border.h"
+
+namespace views {
+
+class BorderContentsView;
+
+// BubbleFrameView to render BubbleBorder.
+//
+////////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView {
+ public:
+ BubbleFrameView(BubbleBorder::ArrowLocation location,
+ const gfx::Size& client_size,
+ SkColor color,
+ bool allow_bubble_offscreen);
+ virtual ~BubbleFrameView();
+
+ // NonClientFrameView overrides:
+ virtual gfx::Rect GetBoundsForClientView() const OVERRIDE;
+ virtual gfx::Rect GetWindowBoundsForClientBounds(
+ const gfx::Rect& client_bounds) const OVERRIDE;
+ virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE;
+ virtual void GetWindowMask(const gfx::Size& size,
+ gfx::Path* window_mask) OVERRIDE {}
+ virtual void EnableClose(bool enable) OVERRIDE {}
+ virtual void ResetWindowControls() OVERRIDE {}
+ virtual void UpdateWindowIcon() OVERRIDE {}
+
+ // View overrides:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ // Accessor for bubble border inside border contents.
+ BubbleBorder* bubble_border() const;
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewBasicTest, GetBoundsForClientView);
+
+ BorderContentsView* border_contents_;
+ BubbleBorder::ArrowLocation location_;
+ bool allow_bubble_offscreen_;
+
+ DISALLOW_COPY_AND_ASSIGN(BubbleFrameView);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_BUBBLE_BUBBLE_FRAME_VIEW_H_
diff --git a/ui/views/bubble/bubble_frame_view_unittest.cc b/ui/views/bubble/bubble_frame_view_unittest.cc
new file mode 100644
index 0000000..6699bfd
--- /dev/null
+++ b/ui/views/bubble/bubble_frame_view_unittest.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2011 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 "ui/base/hit_test.h"
+#include "ui/views/test/views_test_base.h"
+#include "views/bubble/border_contents_view.h"
+#include "views/bubble/bubble_border.h"
+#include "views/bubble/bubble_delegate.h"
+#include "views/bubble/bubble_frame_view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+typedef ViewsTestBase BubbleFrameViewBasicTest;
+
+const BubbleBorder::ArrowLocation kArrow = BubbleBorder::TOP_LEFT;
+const gfx::Rect kRect(10, 10, 200, 200);
+const SkColor kBackgroundColor = SK_ColorRED;
+const bool kAllowBubbleOffscreen = true;
+
+TEST_F(BubbleFrameViewBasicTest, GetBoundsForClientView) {
+ BubbleFrameView frame(kArrow, kRect.size(), kBackgroundColor,
+ kAllowBubbleOffscreen);
+ EXPECT_EQ(frame.GetWindowBoundsForClientBounds(kRect).size(), frame.size());
+ EXPECT_EQ(kArrow, frame.bubble_border()->arrow_location());
+ EXPECT_EQ(kBackgroundColor, frame.bubble_border()->background_color());
+
+ int margin_x = frame.border_contents_->content_margins().left();
+ int margin_y = frame.border_contents_->content_margins().top();
+ gfx::Insets insets;
+ frame.bubble_border()->GetInsets(&insets);
+ EXPECT_EQ(insets.left() + margin_x, frame.GetBoundsForClientView().x());
+ EXPECT_EQ(insets.top() + margin_y, frame.GetBoundsForClientView().y());
+}
+
+namespace {
+
+class SizedBubbleDelegateView : public BubbleDelegateView {
+ public:
+ SizedBubbleDelegateView() {}
+ virtual ~SizedBubbleDelegateView() {}
+
+ // View overrides:
+ virtual gfx::Size GetPreferredSize() OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SizedBubbleDelegateView);
+};
+
+gfx::Size SizedBubbleDelegateView::GetPreferredSize() { return kRect.size(); }
+
+} // namespace
+
+TEST_F(BubbleFrameViewBasicTest, NonClientHitTest) {
+ BubbleDelegateView* delegate = new SizedBubbleDelegateView();
+ Widget* widget(BubbleDelegateView::CreateBubble(delegate));
+ delegate->Show();
+ gfx::Point kPtInBound(100, 100);
+ gfx::Point kPtOutsideBound(1000, 1000);
+ BubbleFrameView* bubble_frame_view = delegate->GetBubbleFrameView();
+ EXPECT_EQ(HTCLIENT, bubble_frame_view->NonClientHitTest(kPtInBound));
+ EXPECT_EQ(HTNOWHERE, bubble_frame_view->NonClientHitTest(kPtOutsideBound));
+ widget->CloseNow();
+ RunPendingMessages();
+}
+
+} // namespace views
diff --git a/ui/views/events/event.cc b/ui/views/events/event.cc
new file mode 100644
index 0000000..67d165e
--- /dev/null
+++ b/ui/views/events/event.cc
@@ -0,0 +1,200 @@
+// Copyright (c) 2011 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/events/event.h"
+
+#include "base/logging.h"
+#include "ui/base/keycodes/keyboard_code_conversion.h"
+#include "views/view.h"
+#include "views/widget/root_view.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// Event, protected:
+
+Event::Event(ui::EventType type, int flags)
+ : type_(type),
+ time_stamp_(base::Time::NowFromSystemTime()),
+ flags_(flags) {
+ // Safely initialize the pointer/struct to null/empty.
+ memset(&native_event_, 0, sizeof(native_event_));
+#if defined(TOOLKIT_USES_GTK)
+ gdk_event_ = NULL;
+#endif
+}
+
+Event::Event(const NativeEvent& native_event, ui::EventType type, int flags)
+ : native_event_(native_event),
+ type_(type),
+ time_stamp_(base::Time::NowFromSystemTime()),
+ flags_(flags) {
+#if defined(TOOLKIT_USES_GTK)
+ gdk_event_ = NULL;
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LocatedEvent, protected:
+
+#if !defined(USE_AURA)
+LocatedEvent::LocatedEvent(const NativeEvent& native_event)
+ : Event(native_event,
+ ui::EventTypeFromNative(native_event),
+ ui::EventFlagsFromNative(native_event)),
+ location_(ui::EventLocationFromNative(native_event)) {
+}
+#endif
+
+// TODO(msw): Kill this legacy constructor when we update uses.
+LocatedEvent::LocatedEvent(ui::EventType type,
+ const gfx::Point& location,
+ int flags)
+ : Event(type, flags),
+ location_(location) {
+}
+
+LocatedEvent::LocatedEvent(const LocatedEvent& model,
+ View* source,
+ View* target)
+ : Event(model),
+ location_(model.location_) {
+ if (target && target != source)
+ View::ConvertPointToView(source, target, &location_);
+}
+
+LocatedEvent::LocatedEvent(const LocatedEvent& model, View* root)
+ : Event(model),
+ location_(model.location_) {
+ View::ConvertPointFromWidget(root, &location_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyEvent, public:
+
+#if !defined(USE_AURA)
+KeyEvent::KeyEvent(const NativeEvent& native_event)
+ : Event(native_event,
+ ui::EventTypeFromNative(native_event),
+ ui::EventFlagsFromNative(native_event)),
+ key_code_(ui::KeyboardCodeFromNative(native_event)),
+ character_(0),
+ unmodified_character_(0) {
+}
+#endif
+
+KeyEvent::KeyEvent(ui::EventType type,
+ ui::KeyboardCode key_code,
+ int event_flags)
+ : Event(type, event_flags),
+ key_code_(key_code),
+ character_(ui::GetCharacterFromKeyCode(key_code, event_flags)),
+ unmodified_character_(0) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseEvent, public:
+
+MouseEvent::MouseEvent(const NativeEvent& native_event)
+ : LocatedEvent(native_event) {
+}
+
+MouseEvent::MouseEvent(const MouseEvent& model, View* source, View* target)
+ : LocatedEvent(model, source, target) {
+}
+
+MouseEvent::MouseEvent(const TouchEvent& touch)
+ : LocatedEvent(touch.native_event()) {
+ // The location of the event is correctly extracted from the native event. But
+ // it is necessary to update the event type.
+ ui::EventType mtype = ui::ET_UNKNOWN;
+ switch (touch.type()) {
+ case ui::ET_TOUCH_RELEASED:
+ mtype = ui::ET_MOUSE_RELEASED;
+ break;
+ case ui::ET_TOUCH_PRESSED:
+ mtype = ui::ET_MOUSE_PRESSED;
+ break;
+ case ui::ET_TOUCH_MOVED:
+ mtype = ui::ET_MOUSE_MOVED;
+ break;
+ default:
+ NOTREACHED() << "Invalid mouse event.";
+ }
+ set_type(mtype);
+
+ // It may not be possible to extract the button-information necessary for a
+ // MouseEvent from the native event for a TouchEvent, so the flags are
+ // explicitly updated as well. The button is approximated from the touchpoint
+ // identity.
+ int new_flags = flags() & ~(ui::EF_LEFT_BUTTON_DOWN |
+ ui::EF_RIGHT_BUTTON_DOWN |
+ ui::EF_MIDDLE_BUTTON_DOWN);
+ int button = ui::EF_LEFT_BUTTON_DOWN;
+ if (touch.identity() == 1)
+ button = ui::EF_RIGHT_BUTTON_DOWN;
+ else if (touch.identity() == 2)
+ button = ui::EF_MIDDLE_BUTTON_DOWN;
+ set_flags(new_flags | button);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseWheelEvent, public:
+
+#if !defined(USE_AURA)
+MouseWheelEvent::MouseWheelEvent(const NativeEvent& native_event)
+ : MouseEvent(native_event),
+ offset_(ui::GetMouseWheelOffset(native_event)) {
+}
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEvent, public:
+
+TouchEvent::TouchEvent(ui::EventType type,
+ int x,
+ int y,
+ int flags,
+ int touch_id,
+ float radius_x,
+ float radius_y,
+ float angle,
+ float force)
+ : LocatedEvent(type, gfx::Point(x, y), flags),
+ touch_id_(touch_id),
+ radius_x_(radius_x),
+ radius_y_(radius_y),
+ rotation_angle_(angle),
+ force_(force) {
+}
+
+TouchEvent::TouchEvent(const TouchEvent& model, View* source, View* target)
+ : LocatedEvent(model, source, target),
+ touch_id_(model.touch_id_),
+ radius_x_(model.radius_x_),
+ radius_y_(model.radius_y_),
+ rotation_angle_(model.rotation_angle_),
+ force_(model.force_) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEvent, private:
+
+TouchEvent::TouchEvent(const TouchEvent& model, View* root)
+ : LocatedEvent(model, root),
+ touch_id_(model.touch_id_),
+ radius_x_(model.radius_x_),
+ radius_y_(model.radius_y_),
+ rotation_angle_(model.rotation_angle_),
+ force_(model.force_) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseWheelEvent, public:
+
+// This value matches windows WHEEL_DELTA.
+// static
+const int MouseWheelEvent::kWheelDelta = 120;
+
+} // namespace views
diff --git a/ui/views/events/event.h b/ui/views/events/event.h
new file mode 100644
index 0000000..bcf57c5
--- /dev/null
+++ b/ui/views/events/event.h
@@ -0,0 +1,415 @@
+// Copyright (c) 2011 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 UI_VIEWS_EVENTS_EVENT_H_
+#define UI_VIEWS_EVENTS_EVENT_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/time.h"
+#include "ui/base/events.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/gfx/point.h"
+#include "views/views_export.h"
+
+namespace ui {
+class OSExchangeData;
+}
+
+#if defined(USE_AURA)
+namespace aura {
+class Event;
+}
+#endif
+
+// TODO(msw): Remove GTK support from views event code when possible.
+#if defined(TOOLKIT_USES_GTK)
+typedef union _GdkEvent GdkEvent;
+#endif
+
+namespace views {
+
+#if defined(USE_AURA)
+typedef aura::Event* NativeEvent;
+#else
+typedef base::NativeEvent NativeEvent;
+#endif
+
+class View;
+
+namespace internal {
+class NativeWidgetView;
+class RootView;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// Event class
+//
+// An event encapsulates an input event that can be propagated into view
+// hierarchies. An event has a type, some flags and a time stamp.
+//
+// Each major event type has a corresponding Event subclass.
+//
+// Events are immutable but support copy
+//
+////////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT Event {
+ public:
+ const NativeEvent& native_event() const { return native_event_; }
+#if defined(TOOLKIT_USES_GTK)
+ GdkEvent* gdk_event() const { return gdk_event_; }
+#endif
+ ui::EventType type() const { return type_; }
+ const base::Time& time_stamp() const { return time_stamp_; }
+ int flags() const { return flags_; }
+ void set_flags(int flags) { flags_ = flags; }
+
+ // The following methods return true if the respective keys were pressed at
+ // the time the event was created.
+ bool IsShiftDown() const { return (flags_ & ui::EF_SHIFT_DOWN) != 0; }
+ bool IsControlDown() const { return (flags_ & ui::EF_CONTROL_DOWN) != 0; }
+ bool IsCapsLockDown() const { return (flags_ & ui::EF_CAPS_LOCK_DOWN) != 0; }
+ bool IsAltDown() const { return (flags_ & ui::EF_ALT_DOWN) != 0; }
+
+ bool IsMouseEvent() const {
+ return type_ == ui::ET_MOUSE_PRESSED ||
+ type_ == ui::ET_MOUSE_DRAGGED ||
+ type_ == ui::ET_MOUSE_RELEASED ||
+ type_ == ui::ET_MOUSE_MOVED ||
+ type_ == ui::ET_MOUSE_ENTERED ||
+ type_ == ui::ET_MOUSE_EXITED ||
+ type_ == ui::ET_MOUSEWHEEL;
+ }
+
+ bool IsTouchEvent() const {
+ return type_ == ui::ET_TOUCH_RELEASED ||
+ type_ == ui::ET_TOUCH_PRESSED ||
+ type_ == ui::ET_TOUCH_MOVED ||
+ type_ == ui::ET_TOUCH_STATIONARY ||
+ type_ == ui::ET_TOUCH_CANCELLED;
+ }
+
+ protected:
+ Event(ui::EventType type, int flags);
+ Event(const NativeEvent& native_event, ui::EventType type, int flags);
+#if defined(TOOLKIT_USES_GTK)
+ Event(GdkEvent* gdk_event, ui::EventType type, int flags);
+#endif
+
+ Event(const Event& model)
+ : native_event_(model.native_event()),
+#if defined(TOOLKIT_USES_GTK)
+ gdk_event_(model.gdk_event_),
+#endif
+ type_(model.type()),
+ time_stamp_(model.time_stamp()),
+ flags_(model.flags()) {
+ }
+
+ void set_type(ui::EventType type) { type_ = type; }
+
+ private:
+ void operator=(const Event&);
+
+ NativeEvent native_event_;
+#if defined(TOOLKIT_USES_GTK)
+ GdkEvent* gdk_event_;
+#endif
+ ui::EventType type_;
+ base::Time time_stamp_;
+ int flags_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// LocatedEvent class
+//
+// A generic event that is used for any events that is located at a specific
+// position in the screen.
+//
+////////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT LocatedEvent : public Event {
+ public:
+ int x() const { return location_.x(); }
+ int y() const { return location_.y(); }
+ const gfx::Point& location() const { return location_; }
+
+ protected:
+ explicit LocatedEvent(const NativeEvent& native_event);
+#if defined(TOOLKIT_USES_GTK)
+ explicit LocatedEvent(GdkEvent* gdk_event);
+#endif
+
+ // TODO(msw): Kill this legacy constructor when we update uses.
+ // Simple initialization from cracked metadata.
+ LocatedEvent(ui::EventType type, const gfx::Point& location, int flags);
+
+ // Create a new LocatedEvent which is identical to the provided model.
+ // If source / target views are provided, the model location will be converted
+ // from |source| coordinate system to |target| coordinate system.
+ LocatedEvent(const LocatedEvent& model, View* source, View* target);
+
+ // This constructor is to allow converting the location of an event from the
+ // widget's coordinate system to the RootView's coordinate system.
+ LocatedEvent(const LocatedEvent& model, View* root);
+
+ gfx::Point location_;
+};
+
+class TouchEvent;
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MouseEvent class
+//
+// A mouse event is used for any input event related to the mouse.
+//
+////////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT MouseEvent : public LocatedEvent {
+ public:
+ explicit MouseEvent(const NativeEvent& native_event);
+#if defined(TOOLKIT_USES_GTK)
+ explicit MouseEvent(GdkEvent* gdk_event);
+#endif
+
+ // Create a new MouseEvent which is identical to the provided model.
+ // If source / target views are provided, the model location will be converted
+ // from |source| coordinate system to |target| coordinate system.
+ MouseEvent(const MouseEvent& model, View* source, View* target);
+
+ // Creates a new MouseEvent from a TouchEvent. The location of the TouchEvent
+ // is the same as the MouseEvent. Other attributes (e.g. type, flags) are
+ // mapped from the TouchEvent to appropriate MouseEvent attributes.
+ // GestureManager uses this to convert TouchEvents that are not handled by any
+ // view.
+ explicit MouseEvent(const TouchEvent& touch);
+
+ // TODO(msw): Kill this legacy constructor when we update uses.
+ // Create a new mouse event
+ MouseEvent(ui::EventType type, int x, int y, int flags)
+ : LocatedEvent(type, gfx::Point(x, y), flags) {
+ }
+
+ // Conveniences to quickly test what button is down
+ bool IsOnlyLeftMouseButton() const {
+ return (flags() & ui::EF_LEFT_BUTTON_DOWN) &&
+ !(flags() & (ui::EF_MIDDLE_BUTTON_DOWN | ui::EF_RIGHT_BUTTON_DOWN));
+ }
+
+ bool IsLeftMouseButton() const {
+ return (flags() & ui::EF_LEFT_BUTTON_DOWN) != 0;
+ }
+
+ bool IsOnlyMiddleMouseButton() const {
+ return (flags() & ui::EF_MIDDLE_BUTTON_DOWN) &&
+ !(flags() & (ui::EF_LEFT_BUTTON_DOWN | ui::EF_RIGHT_BUTTON_DOWN));
+ }
+
+ bool IsMiddleMouseButton() const {
+ return (flags() & ui::EF_MIDDLE_BUTTON_DOWN) != 0;
+ }
+
+ bool IsOnlyRightMouseButton() const {
+ return (flags() & ui::EF_RIGHT_BUTTON_DOWN) &&
+ !(flags() & (ui::EF_LEFT_BUTTON_DOWN | ui::EF_MIDDLE_BUTTON_DOWN));
+ }
+
+ bool IsRightMouseButton() const {
+ return (flags() & ui::EF_RIGHT_BUTTON_DOWN) != 0;
+ }
+
+ protected:
+ MouseEvent(const MouseEvent& model, View* root)
+ : LocatedEvent(model, root) {
+ }
+
+ private:
+ friend class internal::NativeWidgetView;
+ friend class internal::RootView;
+
+ DISALLOW_COPY_AND_ASSIGN(MouseEvent);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// TouchEvent class
+//
+// A touch event is generated by touch screen and advanced track
+// pad devices. There is a deliberate direct correspondence between
+// TouchEvent and PlatformTouchPoint.
+//
+////////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT TouchEvent : public LocatedEvent {
+ public:
+ explicit TouchEvent(const NativeEvent& native_event);
+
+ // Create a new touch event.
+ TouchEvent(ui::EventType type,
+ int x,
+ int y,
+ int flags,
+ int touch_id,
+ float radius_x,
+ float radius_y,
+ float angle,
+ float force);
+
+ // Create a new TouchEvent which is identical to the provided model.
+ // If source / target views are provided, the model location will be converted
+ // from |source| coordinate system to |target| coordinate system.
+ TouchEvent(const TouchEvent& model, View* source, View* target);
+
+ int identity() const { return touch_id_; }
+
+ float radius_x() const { return radius_x_; }
+ float radius_y() const { return radius_y_; }
+ float rotation_angle() const { return rotation_angle_; }
+ float force() const { return force_; }
+
+ private:
+ friend class internal::NativeWidgetView;
+ friend class internal::RootView;
+
+ TouchEvent(const TouchEvent& model, View* root);
+
+ // The identity (typically finger) of the touch starting at 0 and incrementing
+ // for each separable additional touch that the hardware can detect.
+ const int touch_id_;
+
+ // Radius of the X (major) axis of the touch ellipse. 1.0 if unknown.
+ const float radius_x_;
+
+ // Radius of the Y (minor) axis of the touch ellipse. 1.0 if unknown.
+ const float radius_y_;
+
+ // Angle of the major axis away from the X axis. Default 0.0.
+ const float rotation_angle_;
+
+ // Force (pressure) of the touch. Normalized to be [0, 1]. Default to be 0.0.
+ const float force_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchEvent);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyEvent class
+//
+// KeyEvent encapsulates keyboard input events - key press and release.
+//
+////////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT KeyEvent : public Event {
+ public:
+ explicit KeyEvent(const NativeEvent& native_event);
+#if defined(TOOLKIT_USES_GTK)
+ explicit KeyEvent(GdkEvent* gdk_event);
+#endif
+
+ // Creates a new KeyEvent synthetically (i.e. not in response to an input
+ // event from the host environment). This is typically only used in testing as
+ // some metadata obtainable from the underlying native event is not present.
+ // It's also used by input methods to fabricate keyboard events.
+ KeyEvent(ui::EventType type,
+ ui::KeyboardCode key_code,
+ int event_flags);
+
+ ui::KeyboardCode key_code() const { return key_code_; }
+
+ // These setters allow an I18N virtual keyboard to fabricate a keyboard event
+ // which does not have a corresponding ui::KeyboardCode (example: U+00E1 Latin
+ // small letter A with acute, U+0410 Cyrillic capital letter A.)
+ // GetCharacter() and GetUnmodifiedCharacter() return the character.
+ void set_character(uint16 character) { character_ = character; }
+ void set_unmodified_character(uint16 unmodified_character) {
+ unmodified_character_ = unmodified_character;
+ }
+
+ // Gets the character generated by this key event. It only supports Unicode
+ // BMP characters.
+ uint16 GetCharacter() const;
+
+ // Gets the character generated by this key event ignoring concurrently-held
+ // modifiers (except shift).
+ uint16 GetUnmodifiedCharacter() const;
+
+ private:
+ ui::KeyboardCode key_code_;
+
+ uint16 character_;
+ uint16 unmodified_character_;
+
+ DISALLOW_COPY_AND_ASSIGN(KeyEvent);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MouseWheelEvent class
+//
+// A MouseWheelEvent is used to propagate mouse wheel user events.
+// Note: e.GetOffset() > 0 means scroll up / left.
+//
+////////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT MouseWheelEvent : public MouseEvent {
+ public:
+ // See |offset| for details.
+ static const int kWheelDelta;
+
+ explicit MouseWheelEvent(const NativeEvent& native_event);
+#if defined(TOOLKIT_USES_GTK)
+ explicit MouseWheelEvent(GdkEvent* gdk_event);
+#endif
+
+ // The amount to scroll. This is in multiples of kWheelDelta.
+ int offset() const { return offset_; }
+
+ private:
+ friend class internal::RootView;
+ friend class internal::NativeWidgetView;
+
+ MouseWheelEvent(const MouseWheelEvent& model, View* root)
+ : MouseEvent(model, root),
+ offset_(model.offset_) {
+ }
+
+ int offset_;
+
+ DISALLOW_COPY_AND_ASSIGN(MouseWheelEvent);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// DropTargetEvent class
+//
+// A DropTargetEvent is sent to the view the mouse is over during a drag and
+// drop operation.
+//
+////////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT DropTargetEvent : public LocatedEvent {
+ public:
+ DropTargetEvent(const ui::OSExchangeData& data,
+ int x,
+ int y,
+ int source_operations)
+ : LocatedEvent(ui::ET_DROP_TARGET_EVENT, gfx::Point(x, y), 0),
+ data_(data),
+ source_operations_(source_operations) {
+ // TODO(msw): Hook up key state flags for CTRL + drag and drop, etc.
+ }
+
+ const ui::OSExchangeData& data() const { return data_; }
+ int source_operations() const { return source_operations_; }
+
+ private:
+ // Data associated with the drag/drop session.
+ const ui::OSExchangeData& data_;
+
+ // Bitmask of supported ui::DragDropTypes::DragOperation by the source.
+ int source_operations_;
+
+ DISALLOW_COPY_AND_ASSIGN(DropTargetEvent);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_EVENTS_EVENT_H_
diff --git a/ui/views/events/event_aura.cc b/ui/views/events/event_aura.cc
new file mode 100644
index 0000000..a4d3169
--- /dev/null
+++ b/ui/views/events/event_aura.cc
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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/events/event.h"
+
+#include "base/logging.h"
+#include "ui/aura/event.h"
+#include "ui/base/keycodes/keyboard_code_conversion.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// LocatedEvent, protected:
+
+LocatedEvent::LocatedEvent(const NativeEvent& native_event)
+ : Event(native_event, native_event->type(), native_event->flags()),
+ location_(static_cast<aura::LocatedEvent*>(native_event)->location()) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEvent, public:
+
+TouchEvent::TouchEvent(const NativeEvent& event)
+ : LocatedEvent(event),
+ touch_id_(static_cast<aura::TouchEvent*>(event)->touch_id()),
+ radius_x_(static_cast<aura::TouchEvent*>(event)->radius_x()),
+ radius_y_(static_cast<aura::TouchEvent*>(event)->radius_y()),
+ rotation_angle_(static_cast<aura::TouchEvent*>(event)->rotation_angle()),
+ force_(static_cast<aura::TouchEvent*>(event)->force()) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyEvent, public:
+
+KeyEvent::KeyEvent(const NativeEvent& native_event)
+ : Event(native_event, native_event->type(), native_event->flags()),
+ key_code_(static_cast<aura::KeyEvent*>(native_event)->key_code()),
+ character_(ui::GetCharacterFromKeyCode(key_code_, flags())),
+ unmodified_character_(0) {
+}
+
+uint16 KeyEvent::GetCharacter() const {
+ return character_;
+}
+
+uint16 KeyEvent::GetUnmodifiedCharacter() const {
+ if (unmodified_character_)
+ return unmodified_character_;
+
+ return ui::GetCharacterFromKeyCode(key_code_, flags() & ui::EF_SHIFT_DOWN);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseWheelEvent, public:
+
+MouseWheelEvent::MouseWheelEvent(const NativeEvent& native_event)
+ : MouseEvent(native_event),
+ offset_(ui::GetMouseWheelOffset(native_event->native_event())) {
+}
+
+} // namespace views
diff --git a/ui/views/events/event_gtk.cc b/ui/views/events/event_gtk.cc
new file mode 100644
index 0000000..fb0c88a
--- /dev/null
+++ b/ui/views/events/event_gtk.cc
@@ -0,0 +1,183 @@
+// Copyright (c) 2011 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/events/event.h"
+
+#include <gdk/gdk.h>
+
+#include "base/logging.h"
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+#include "ui/gfx/point.h"
+
+namespace {
+
+unsigned int GetGdkStateFromNative(GdkEvent* gdk_event) {
+ switch (gdk_event->type) {
+ case GDK_KEY_PRESS:
+ case GDK_KEY_RELEASE:
+ return gdk_event->key.state;
+ case GDK_BUTTON_PRESS:
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ case GDK_BUTTON_RELEASE:
+ return gdk_event->button.state;
+ case GDK_SCROLL:
+ return gdk_event->scroll.state;
+ case GDK_MOTION_NOTIFY:
+ return gdk_event->motion.state;
+ case GDK_ENTER_NOTIFY:
+ case GDK_LEAVE_NOTIFY:
+ return gdk_event->crossing.state;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return 0;
+}
+
+int GetFlagsFromGdkState(unsigned int state) {
+ int flags = 0;
+ flags |= (state & GDK_LOCK_MASK) ? ui::EF_CAPS_LOCK_DOWN : 0;
+ flags |= (state & GDK_CONTROL_MASK) ? ui::EF_CONTROL_DOWN : 0;
+ flags |= (state & GDK_SHIFT_MASK) ? ui::EF_SHIFT_DOWN : 0;
+ flags |= (state & GDK_MOD1_MASK) ? ui::EF_ALT_DOWN : 0;
+ flags |= (state & GDK_BUTTON1_MASK) ? ui::EF_LEFT_BUTTON_DOWN : 0;
+ flags |= (state & GDK_BUTTON2_MASK) ? ui::EF_MIDDLE_BUTTON_DOWN : 0;
+ flags |= (state & GDK_BUTTON3_MASK) ? ui::EF_RIGHT_BUTTON_DOWN : 0;
+ return flags;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// These functions mirror ui/base/events.h, but GTK is in the midst of removal.
+
+ui::EventType EventTypeFromNative(GdkEvent* gdk_event) {
+ // Add new event types as necessary.
+ switch (gdk_event->type) {
+ case GDK_2BUTTON_PRESS:
+ case GDK_3BUTTON_PRESS:
+ case GDK_BUTTON_PRESS:
+ return ui::ET_MOUSE_PRESSED;
+ case GDK_BUTTON_RELEASE:
+ return ui::ET_MOUSE_RELEASED;
+ case GDK_DRAG_MOTION:
+ return ui::ET_MOUSE_DRAGGED;
+ case GDK_ENTER_NOTIFY:
+ return ui::ET_MOUSE_ENTERED;
+ case GDK_KEY_PRESS:
+ return ui::ET_KEY_PRESSED;
+ case GDK_KEY_RELEASE:
+ return ui::ET_KEY_RELEASED;
+ case GDK_LEAVE_NOTIFY:
+ return ui::ET_MOUSE_EXITED;
+ case GDK_MOTION_NOTIFY:
+ if (gdk_event->motion.state & GDK_BUTTON1_MASK ||
+ gdk_event->motion.state & GDK_BUTTON2_MASK ||
+ gdk_event->motion.state & GDK_BUTTON3_MASK ||
+ gdk_event->motion.state & GDK_BUTTON4_MASK ||
+ gdk_event->motion.state & GDK_BUTTON5_MASK) {
+ return ui::ET_MOUSE_DRAGGED;
+ }
+ return ui::ET_MOUSE_MOVED;
+ case GDK_SCROLL:
+ return ui::ET_MOUSEWHEEL;
+ default:
+ NOTREACHED();
+ break;
+ }
+ return ui::ET_UNKNOWN;
+}
+
+int EventFlagsFromNative(GdkEvent* gdk_event) {
+ int flags = GetFlagsFromGdkState(GetGdkStateFromNative(gdk_event));
+ if (gdk_event->type == GDK_2BUTTON_PRESS)
+ flags |= ui::EF_IS_DOUBLE_CLICK;
+ if (gdk_event->type == GDK_BUTTON_PRESS ||
+ gdk_event->type == GDK_2BUTTON_PRESS ||
+ gdk_event->type == GDK_3BUTTON_PRESS ||
+ gdk_event->type == GDK_BUTTON_RELEASE) {
+ switch (gdk_event->button.button) {
+ case 1:
+ return flags | ui::EF_LEFT_BUTTON_DOWN;
+ case 2:
+ return flags | ui::EF_MIDDLE_BUTTON_DOWN;
+ case 3:
+ return flags | ui::EF_RIGHT_BUTTON_DOWN;
+ }
+ }
+ return flags;
+}
+
+gfx::Point EventLocationFromNative(GdkEvent* gdk_event) {
+ double x = 0, y = 0;
+ if (gdk_event_get_coords(gdk_event, &x, &y))
+ return gfx::Point(static_cast<int>(x), static_cast<int>(y));
+ return gfx::Point();
+}
+
+ui::KeyboardCode KeyboardCodeFromNative(GdkEvent* gdk_event) {
+ DCHECK(gdk_event->type == GDK_KEY_PRESS ||
+ gdk_event->type == GDK_KEY_RELEASE);
+ return ui::KeyboardCodeFromGdkEventKey(&gdk_event->key);
+}
+
+int GetMouseWheelOffset(GdkEvent* gdk_event) {
+ DCHECK(gdk_event->type == GDK_SCROLL);
+ int offset = (gdk_event->scroll.direction == GDK_SCROLL_UP ||
+ gdk_event->scroll.direction == GDK_SCROLL_LEFT) ? 1 : -1;
+ return offset;
+}
+
+} // namespace
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// Event, protected:
+
+Event::Event(GdkEvent* gdk_event, ui::EventType type, int flags)
+ : native_event_(NULL),
+ gdk_event_(gdk_event),
+ type_(type),
+ time_stamp_(base::Time::NowFromSystemTime()),
+ flags_(flags) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// LocatedEvent, protected:
+
+LocatedEvent::LocatedEvent(GdkEvent* gdk_event)
+ : Event(gdk_event,
+ EventTypeFromNative(gdk_event),
+ EventFlagsFromNative(gdk_event)),
+ location_(EventLocationFromNative(gdk_event)) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyEvent, public:
+
+KeyEvent::KeyEvent(GdkEvent* gdk_event)
+ : Event(gdk_event,
+ EventTypeFromNative(gdk_event),
+ EventFlagsFromNative(gdk_event)),
+ key_code_(KeyboardCodeFromNative(gdk_event)),
+ character_(0),
+ unmodified_character_(0) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseEvent, public:
+
+MouseEvent::MouseEvent(GdkEvent* gdk_event)
+ : LocatedEvent(gdk_event) {
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// MouseWheelEvent, public:
+
+MouseWheelEvent::MouseWheelEvent(GdkEvent* gdk_event)
+ : MouseEvent(gdk_event),
+ offset_(kWheelDelta * GetMouseWheelOffset(gdk_event)) {
+}
+
+} // namespace views
diff --git a/ui/views/events/event_unittest.cc b/ui/views/events/event_unittest.cc
new file mode 100644
index 0000000..211877d
--- /dev/null
+++ b/ui/views/events/event_unittest.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h"
+
+#include "base/basictypes.h"
+#include "views/events/event.h"
+
+// Bug 99129.
+#if defined(USE_AURA)
+#define MAYBE_KeyEvent FAILS_KeyEvent
+#define MAYBE_KeyEventDirectUnicode FAILS_KeyEventDirectUnicode
+#else
+#define MAYBE_KeyEvent KeyEvent
+#define MAYBE_KeyEventDirectUnicode KeyEventDirectUnicode
+#endif
+
+namespace views {
+
+class EventTest : public testing::Test {
+ public:
+ EventTest() {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(EventTest);
+};
+
+TEST_F(EventTest, MAYBE_KeyEvent) {
+ static const struct {
+ ui::KeyboardCode key_code;
+ int flags;
+ uint16 character;
+ uint16 unmodified_character;
+ } kTestData[] = {
+ { ui::VKEY_A, 0, 'a', 'a' },
+ { ui::VKEY_A, ui::EF_SHIFT_DOWN, 'A', 'A' },
+ { ui::VKEY_A, ui::EF_CAPS_LOCK_DOWN, 'A', 'a' },
+ { ui::VKEY_A, ui::EF_SHIFT_DOWN | ui::EF_CAPS_LOCK_DOWN, 'a', 'A' },
+ { ui::VKEY_A, ui::EF_CONTROL_DOWN, 0x01, 'a' },
+ { ui::VKEY_A, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\x01', 'A' },
+ { ui::VKEY_Z, 0, 'z', 'z' },
+ { ui::VKEY_Z, ui::EF_SHIFT_DOWN, 'Z', 'Z' },
+ { ui::VKEY_Z, ui::EF_CAPS_LOCK_DOWN, 'Z', 'z' },
+ { ui::VKEY_Z, ui::EF_SHIFT_DOWN | ui::EF_CAPS_LOCK_DOWN, 'z', 'Z' },
+ { ui::VKEY_Z, ui::EF_CONTROL_DOWN, '\x1A', 'z' },
+ { ui::VKEY_Z, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\x1A', 'Z' },
+
+ { ui::VKEY_2, ui::EF_CONTROL_DOWN, '\0', '2' },
+ { ui::VKEY_2, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\0', '@' },
+ { ui::VKEY_6, ui::EF_CONTROL_DOWN, '\0', '6' },
+ { ui::VKEY_6, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\x1E', '^' },
+ { ui::VKEY_OEM_MINUS, ui::EF_CONTROL_DOWN, '\0', '-' },
+ { ui::VKEY_OEM_MINUS, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\x1F', '_'},
+ { ui::VKEY_OEM_4, ui::EF_CONTROL_DOWN, '\x1B', '[' },
+ { ui::VKEY_OEM_4, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\0', '{' },
+ { ui::VKEY_OEM_5, ui::EF_CONTROL_DOWN, '\x1C', '\\' },
+ { ui::VKEY_OEM_5, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\0', '|' },
+ { ui::VKEY_OEM_6, ui::EF_CONTROL_DOWN, '\x1D', ']' },
+ { ui::VKEY_OEM_6, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\0', '}' },
+ { ui::VKEY_RETURN, ui::EF_CONTROL_DOWN, '\x0A', '\r' },
+
+ { ui::VKEY_0, 0, '0', '0' },
+ { ui::VKEY_0, ui::EF_SHIFT_DOWN, ')', ')' },
+ { ui::VKEY_0, ui::EF_SHIFT_DOWN | ui::EF_CAPS_LOCK_DOWN, ')', ')' },
+ { ui::VKEY_0, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\0', ')' },
+
+ { ui::VKEY_9, 0, '9', '9' },
+ { ui::VKEY_9, ui::EF_SHIFT_DOWN, '(', '(' },
+ { ui::VKEY_9, ui::EF_SHIFT_DOWN | ui::EF_CAPS_LOCK_DOWN, '(', '(' },
+ { ui::VKEY_9, ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN, '\0', '(' },
+
+ { ui::VKEY_NUMPAD0, ui::EF_CONTROL_DOWN, '\0', '0' },
+ { ui::VKEY_NUMPAD0, ui::EF_SHIFT_DOWN, '0', '0' },
+
+ { ui::VKEY_NUMPAD9, ui::EF_CONTROL_DOWN, '\0', '9' },
+ { ui::VKEY_NUMPAD9, ui::EF_SHIFT_DOWN, '9', '9' },
+
+ { ui::VKEY_TAB, ui::EF_CONTROL_DOWN, '\0', '\t' },
+ { ui::VKEY_TAB, ui::EF_SHIFT_DOWN, '\t', '\t' },
+
+ { ui::VKEY_MULTIPLY, ui::EF_CONTROL_DOWN, '\0', '*' },
+ { ui::VKEY_MULTIPLY, ui::EF_SHIFT_DOWN, '*', '*' },
+ { ui::VKEY_ADD, ui::EF_CONTROL_DOWN, '\0', '+' },
+ { ui::VKEY_ADD, ui::EF_SHIFT_DOWN, '+', '+' },
+ { ui::VKEY_SUBTRACT, ui::EF_CONTROL_DOWN, '\0', '-' },
+ { ui::VKEY_SUBTRACT, ui::EF_SHIFT_DOWN, '-', '-' },
+ { ui::VKEY_DECIMAL, ui::EF_CONTROL_DOWN, '\0', '.' },
+ { ui::VKEY_DECIMAL, ui::EF_SHIFT_DOWN, '.', '.' },
+ { ui::VKEY_DIVIDE, ui::EF_CONTROL_DOWN, '\0', '/' },
+ { ui::VKEY_DIVIDE, ui::EF_SHIFT_DOWN, '/', '/' },
+
+ { ui::VKEY_OEM_1, ui::EF_CONTROL_DOWN, '\0', ';' },
+ { ui::VKEY_OEM_1, ui::EF_SHIFT_DOWN, ':', ':' },
+ { ui::VKEY_OEM_PLUS, ui::EF_CONTROL_DOWN, '\0', '=' },
+ { ui::VKEY_OEM_PLUS, ui::EF_SHIFT_DOWN, '+', '+' },
+ { ui::VKEY_OEM_COMMA, ui::EF_CONTROL_DOWN, '\0', ',' },
+ { ui::VKEY_OEM_COMMA, ui::EF_SHIFT_DOWN, '<', '<' },
+ { ui::VKEY_OEM_PERIOD, ui::EF_CONTROL_DOWN, '\0', '.' },
+ { ui::VKEY_OEM_PERIOD, ui::EF_SHIFT_DOWN, '>', '>' },
+ { ui::VKEY_OEM_3, ui::EF_CONTROL_DOWN, '\0', '`' },
+ { ui::VKEY_OEM_3, ui::EF_SHIFT_DOWN, '~', '~' },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestData); ++i) {
+ KeyEvent key(ui::ET_KEY_PRESSED, kTestData[i].key_code, kTestData[i].flags);
+ EXPECT_EQ(kTestData[i].character, key.GetCharacter())
+ << " Index:" << i << " key_code:" << kTestData[i].key_code;
+ EXPECT_EQ(kTestData[i].unmodified_character, key.GetUnmodifiedCharacter())
+ << " Index:" << i << " key_code:" << kTestData[i].key_code;
+ }
+}
+
+TEST_F(EventTest, MAYBE_KeyEventDirectUnicode) {
+ KeyEvent key(ui::ET_KEY_PRESSED, ui::VKEY_UNKNOWN, ui::EF_SHIFT_DOWN);
+ key.set_character(0x1234U);
+ key.set_unmodified_character(0x4321U);
+ EXPECT_EQ(0x1234U, key.GetCharacter());
+ EXPECT_EQ(0x4321U, key.GetUnmodifiedCharacter());
+ KeyEvent key2(ui::ET_KEY_RELEASED, ui::VKEY_UNKNOWN, ui::EF_CONTROL_DOWN);
+ key2.set_character(0x4321U);
+ key2.set_unmodified_character(0x1234U);
+ EXPECT_EQ(0x4321U, key2.GetCharacter());
+ EXPECT_EQ(0x1234U, key2.GetUnmodifiedCharacter());
+}
+
+} // namespace views
diff --git a/ui/views/events/event_wayland.cc b/ui/views/events/event_wayland.cc
new file mode 100644
index 0000000..44998e6
--- /dev/null
+++ b/ui/views/events/event_wayland.cc
@@ -0,0 +1,23 @@
+// Copyright (c) 2011 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/events/event.h"
+
+#include "base/logging.h"
+#include "ui/base/keycodes/keyboard_code_conversion.h"
+
+namespace views {
+
+//////////////////////////////////////////////////////////////////////////////
+// KeyEvent, public:
+
+uint16 KeyEvent::GetCharacter() const {
+ return ui::GetCharacterFromKeyCode(key_code_, flags());
+}
+
+uint16 KeyEvent::GetUnmodifiedCharacter() const {
+ return ui::GetCharacterFromKeyCode(key_code_, flags() & ui::EF_SHIFT_DOWN);
+}
+
+} // namespace views
diff --git a/ui/views/events/event_win.cc b/ui/views/events/event_win.cc
new file mode 100644
index 0000000..03b855e
--- /dev/null
+++ b/ui/views/events/event_win.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 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/events/event.h"
+
+#include "base/logging.h"
+#include "ui/base/keycodes/keyboard_code_conversion.h"
+
+namespace views {
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyEvent, public:
+
+uint16 KeyEvent::GetCharacter() const {
+ if (character_)
+ return character_;
+ return (native_event().message == WM_CHAR) ? key_code_ :
+ ui::GetCharacterFromKeyCode(key_code_, flags());
+}
+
+uint16 KeyEvent::GetUnmodifiedCharacter() const {
+ if (unmodified_character_)
+ return unmodified_character_;
+ // Looks like there is no way to get unmodified character on Windows.
+ return (native_event().message == WM_CHAR) ? key_code_ :
+ ui::GetCharacterFromKeyCode(key_code_, flags() & ui::EF_SHIFT_DOWN);
+}
+
+} // namespace views
diff --git a/ui/views/events/event_x.cc b/ui/views/events/event_x.cc
new file mode 100644
index 0000000..1c9e330
--- /dev/null
+++ b/ui/views/events/event_x.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2011 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/events/event.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <X11/extensions/XInput2.h>
+#include <X11/Xlib.h>
+
+#include "base/logging.h"
+#include "base/utf_string_conversions.h"
+#include "ui/base/events.h"
+#include "ui/base/keycodes/keyboard_code_conversion.h"
+#include "ui/base/keycodes/keyboard_code_conversion_x.h"
+#include "ui/base/touch/touch_factory.h"
+#include "views/widget/root_view.h"
+
+namespace views {
+
+namespace {
+
+// The following two functions are copied from event_gtk.cc. These will be
+// removed when GTK dependency is removed.
+#if defined(TOOLKIT_USES_GTK)
+uint16 GetCharacterFromGdkKeyval(guint keyval) {
+ guint32 ch = gdk_keyval_to_unicode(keyval);
+
+ // We only support BMP characters.
+ return ch < 0xFFFE ? static_cast<uint16>(ch) : 0;
+}
+
+GdkEventKey* GetGdkEventKeyFromNative(GdkEvent* gdk_event) {
+ DCHECK(gdk_event->type == GDK_KEY_PRESS ||
+ gdk_event->type == GDK_KEY_RELEASE);
+ return &gdk_event->key;
+}
+#endif
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+// KeyEvent, public:
+
+uint16 KeyEvent::GetCharacter() const {
+ if (character_)
+ return character_;
+
+ if (!native_event()) {
+#if defined(TOOLKIT_USES_GTK)
+ // This event may have been created from a Gdk event.
+ if (!IsControlDown() && gdk_event()) {
+ uint16 ch = GetCharacterFromGdkKeyval(
+ GetGdkEventKeyFromNative(gdk_event())->keyval);
+ if (ch)
+ return ch;
+ }
+#endif
+ return ui::GetCharacterFromKeyCode(key_code_, flags());
+ }
+
+ DCHECK(native_event()->type == KeyPress ||
+ native_event()->type == KeyRelease);
+
+ uint16 ch = ui::DefaultSymbolFromXEvent(native_event());
+ return ch ? ch : ui::GetCharacterFromKeyCode(key_code_, flags());
+}
+
+uint16 KeyEvent::GetUnmodifiedCharacter() const {
+ if (unmodified_character_)
+ return unmodified_character_;
+
+ if (!native_event()) {
+#if defined(TOOLKIT_USES_GTK)
+ // This event may have been created from a Gdk event.
+ if (gdk_event()) {
+ GdkEventKey* key = GetGdkEventKeyFromNative(gdk_event());
+
+ static const guint kIgnoredModifiers =
+ GDK_CONTROL_MASK | GDK_LOCK_MASK | GDK_MOD1_MASK | GDK_MOD2_MASK |
+ GDK_MOD3_MASK | GDK_MOD4_MASK | GDK_MOD5_MASK | GDK_SUPER_MASK |
+ GDK_HYPER_MASK | GDK_META_MASK;
+
+ // We can't use things like (key->state & GDK_SHIFT_MASK), as it may mask
+ // out bits used by X11 or Gtk internally.
+ GdkModifierType modifiers =
+ static_cast<GdkModifierType>(key->state & ~kIgnoredModifiers);
+ guint keyval = 0;
+ if (gdk_keymap_translate_keyboard_state(NULL, key->hardware_keycode,
+ modifiers, key->group, &keyval, NULL, NULL, NULL)) {
+ uint16 ch = GetCharacterFromGdkKeyval(keyval);
+ if (ch)
+ return ch;
+ }
+ }
+#endif
+ return ui::GetCharacterFromKeyCode(key_code_, flags() & ui::EF_SHIFT_DOWN);
+ }
+
+ DCHECK(native_event()->type == KeyPress ||
+ native_event()->type == KeyRelease);
+
+ XKeyEvent *key = &native_event()->xkey;
+
+ static const unsigned int kIgnoredModifiers = ControlMask | LockMask |
+ Mod1Mask | Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask;
+
+ // We can't use things like (key.state & ShiftMask), as it may mask out bits
+ // used by X11 internally.
+ key->state &= ~kIgnoredModifiers;
+ uint16 ch = ui::DefaultSymbolFromXEvent(native_event());
+ return ch ? ch :
+ ui::GetCharacterFromKeyCode(key_code_, flags() & ui::EF_SHIFT_DOWN);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TouchEvent, public:
+
+TouchEvent::TouchEvent(const base::NativeEvent& native_event)
+ : LocatedEvent(native_event),
+ touch_id_(ui::GetTouchId(native_event)),
+ radius_x_(ui::GetTouchRadiusX(native_event)),
+ radius_y_(ui::GetTouchRadiusY(native_event)),
+ rotation_angle_(ui::GetTouchAngle(native_event)),
+ force_(ui::GetTouchForce(native_event)) {
+#if defined(USE_XI2_MT)
+ if (type() == ui::ET_TOUCH_RELEASED) {
+ // NOTE: The slot is allocated by TouchFactory for each XI_TouchBegin
+ // event, which carries a new tracking ID to identify a new touch
+ // sequence.
+ ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+ float tracking_id;
+ if (factory->ExtractTouchParam(*native_event,
+ ui::TouchFactory::TP_TRACKING_ID,
+ &tracking_id)) {
+ factory->ReleaseSlotForTrackingID(tracking_id);
+ }
+ }
+#else
+ if (type() == ui::ET_TOUCH_PRESSED || type() == ui::ET_TOUCH_RELEASED) {
+ ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+ float slot;
+ if (factory->ExtractTouchParam(*native_event,
+ ui::TouchFactory::TP_SLOT_ID, &slot)) {
+ factory->SetSlotUsed(slot, type() == ui::ET_TOUCH_PRESSED);
+ }
+ }
+#endif
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler.h b/ui/views/focus/accelerator_handler.h
new file mode 100644
index 0000000..c0f9591
--- /dev/null
+++ b/ui/views/focus/accelerator_handler.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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 UI_VIEWS_FOCUS_ACCELERATOR_HANDLER_H_
+#define UI_VIEWS_FOCUS_ACCELERATOR_HANDLER_H_
+#pragma once
+
+#include "build/build_config.h"
+
+#if defined(TOOLKIT_USES_GTK)
+#include <gdk/gdk.h>
+#endif
+
+#include <set>
+#include <vector>
+
+#include "base/message_loop.h"
+#include "views/views_export.h"
+
+namespace views {
+
+#if defined(TOUCH_UI) || defined(USE_AURA)
+#if defined(USE_X11) && !defined(USE_WAYLAND)
+// Dispatch an XEvent to the RootView. Return true if the event was dispatched
+// and handled, false otherwise.
+bool VIEWS_EXPORT DispatchXEvent(XEvent* xevent);
+#endif // USE_X11 && !USE_WAYLAND
+#endif // TOUCH_UI || USE_AURA
+
+// This class delegates the key messages to the associated FocusManager class
+// for the window that is receiving these messages for accelerator processing.
+class VIEWS_EXPORT AcceleratorHandler : public MessageLoop::Dispatcher {
+ public:
+ AcceleratorHandler();
+
+ // Dispatcher method. This returns true if an accelerator was processed by the
+ // focus manager
+#if defined(OS_WIN)
+ virtual bool Dispatch(const MSG& msg);
+#elif defined(USE_WAYLAND)
+ virtual base::MessagePumpDispatcher::DispatchStatus Dispatch(
+ base::wayland::WaylandEvent* ev);
+#elif defined(TOUCH_UI) || defined(USE_AURA)
+ virtual base::MessagePumpDispatcher::DispatchStatus Dispatch(XEvent* xev);
+#else
+ virtual bool Dispatch(GdkEvent* event);
+#endif
+
+ private:
+#if defined(OS_WIN)
+ // The keys currently pressed and consumed by the FocusManager.
+ std::set<WPARAM> pressed_keys_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorHandler);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_ACCELERATOR_HANDLER_H_
diff --git a/ui/views/focus/accelerator_handler_aura.cc b/ui/views/focus/accelerator_handler_aura.cc
new file mode 100644
index 0000000..ab552899
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_aura.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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/focus/accelerator_handler.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {
+}
+
+#if defined(OS_WIN)
+bool AcceleratorHandler::Dispatch(const MSG& msg) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ return true;
+}
+#else
+base::MessagePumpDispatcher::DispatchStatus AcceleratorHandler::Dispatch(
+ XEvent*) {
+ return base::MessagePumpDispatcher::EVENT_IGNORED;
+}
+
+bool DispatchXEvent(XEvent* xev) {
+ return false;
+}
+#endif // defined(OS_WIN)
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_gtk.cc b/ui/views/focus/accelerator_handler_gtk.cc
new file mode 100644
index 0000000..fab3b86
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_gtk.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 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/focus/accelerator_handler.h"
+
+#include <gtk/gtk.h>
+
+#include "views/focus/focus_manager.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {}
+
+bool AcceleratorHandler::Dispatch(GdkEvent* event) {
+ // The logic for handling keyboard accelerators has been moved into
+ // NativeWidgetGtk::OnEventKey handler (views/widget/widget_gtk.cc).
+ gtk_main_do_event(event);
+ return true;
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_gtk_unittest.cc b/ui/views/focus/accelerator_handler_gtk_unittest.cc
new file mode 100644
index 0000000..098325d
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_gtk_unittest.cc
@@ -0,0 +1,199 @@
+// Copyright (c) 2011 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 <gdk/gdkkeysyms.h>
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gfx/rect.h"
+#include "ui/base/models/accelerator.h"
+#include "views/focus/accelerator_handler.h"
+#include "views/focus/focus_manager.h"
+#include "views/view.h"
+#include "views/widget/widget.h"
+#include "views/widget/widget_delegate.h"
+
+namespace views {
+
+class AcceleratorHandlerGtkTest
+ : public testing::Test,
+ public WidgetDelegate,
+ public ui::AcceleratorTarget {
+ public:
+ AcceleratorHandlerGtkTest()
+ : kMenuAccelerator(ui::VKEY_MENU, false, false, false),
+ kHomepageAccelerator(ui::VKEY_HOME, false, false, true),
+ content_view_(NULL) {
+ }
+
+ virtual void SetUp() {
+ window_ = Widget::CreateWindowWithBounds(this, gfx::Rect(0, 0, 500, 500));
+ window_->Show();
+ FocusManager* focus_manager = window_->GetFocusManager();
+ focus_manager->RegisterAccelerator(kMenuAccelerator, this);
+ focus_manager->RegisterAccelerator(kHomepageAccelerator, this);
+ menu_pressed_ = false;
+ home_pressed_ = false;
+ }
+
+ virtual void TearDown() {
+ window_->Close();
+
+ // Flush the message loop to make application verifiers happy.
+ message_loop_.RunAllPending();
+ }
+
+ GdkEventKey CreateKeyEvent(GdkEventType type, guint keyval, guint state) {
+ GdkEventKey evt;
+ memset(&evt, 0, sizeof(evt));
+ evt.type = type;
+ evt.keyval = keyval;
+ // The keyval won't be a "correct" hardware keycode for any real hardware,
+ // but the code should never depend on exact hardware keycodes, just the
+ // fact that the code for presses and releases of the same key match.
+ evt.hardware_keycode = keyval;
+ evt.state = state;
+ GtkWidget* widget = GTK_WIDGET(window_->GetNativeWindow());
+ evt.window = widget->window;
+ return evt;
+ }
+
+ // AcceleratorTarget implementation.
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) {
+ if (accelerator == kMenuAccelerator)
+ menu_pressed_ = true;
+ else if (accelerator == kHomepageAccelerator)
+ home_pressed_ = true;
+ return true;
+ }
+
+ // WidgetDelegate Implementation.
+ virtual View* GetContentsView() {
+ if (!content_view_)
+ content_view_ = new View();
+ return content_view_;
+ }
+ virtual const views::Widget* GetWidget() const {
+ return content_view_->GetWidget();
+ }
+ virtual views::Widget* GetWidget() {
+ return content_view_->GetWidget();
+ }
+
+ virtual void InitContentView() {
+ }
+
+ protected:
+ bool menu_pressed_;
+ bool home_pressed_;
+
+ private:
+ ui::Accelerator kMenuAccelerator;
+ ui::Accelerator kHomepageAccelerator;
+ Widget* window_;
+ View* content_view_;
+ MessageLoopForUI message_loop_;
+ DISALLOW_COPY_AND_ASSIGN(AcceleratorHandlerGtkTest);
+};
+
+// Test that the homepage accelerator (Alt+Home) is activated on key down
+// and that the menu accelerator (Alt) is never activated.
+TEST_F(AcceleratorHandlerGtkTest, TestHomepageAccelerator) {
+ AcceleratorHandler handler;
+ GdkEventKey evt;
+
+ ASSERT_FALSE(menu_pressed_);
+ ASSERT_FALSE(home_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Alt_L, 0);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+ ASSERT_FALSE(home_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Home, GDK_MOD1_MASK);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+ ASSERT_TRUE(home_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Home, GDK_MOD1_MASK);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Alt_L, 0);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+ ASSERT_TRUE(home_pressed_);
+}
+
+// Test that the menu accelerator is activated on key up and not key down.
+TEST_F(AcceleratorHandlerGtkTest, TestMenuAccelerator) {
+ AcceleratorHandler handler;
+ GdkEventKey evt;
+
+ ASSERT_FALSE(menu_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Alt_L, 0);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Alt_L, 0);
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_TRUE(menu_pressed_);
+}
+
+// Test that the menu accelerator isn't confused by the interaction of the
+// Alt and Shift keys. Try the following sequence on Linux:
+// Press Alt
+// Press Shift
+// Release Alt
+// Release Shift
+// The key codes for pressing Alt and releasing Alt are different! This
+// caused a bug in a previous version of the code, which is now fixed by
+// keeping track of hardware keycodes, which are consistent.
+TEST_F(AcceleratorHandlerGtkTest, TestAltShiftInteraction) {
+ AcceleratorHandler handler;
+ GdkEventKey evt;
+
+ ASSERT_FALSE(menu_pressed_);
+
+ // Press Shift.
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Shift_L, 0);
+ evt.hardware_keycode = 0x32;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+ // Press Alt - but GDK calls this Meta when Shift is also down.
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Meta_L, 0);
+ evt.hardware_keycode = 0x40;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ // Release Shift.
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Shift_L, 0);
+ evt.hardware_keycode = 0x32;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+ // Release Alt - with Shift not down, the keyval is now Alt, but
+ // the hardware keycode is unchanged.
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Alt_L, 0);
+ evt.hardware_keycode = 0x40;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_FALSE(menu_pressed_);
+
+ // Press Alt by itself.
+ evt = CreateKeyEvent(GDK_KEY_PRESS, GDK_Alt_L, 0);
+ evt.hardware_keycode = 0x40;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ // This line fails if we don't keep track of hardware keycodes.
+ ASSERT_FALSE(menu_pressed_);
+
+ // Release Alt - now this should trigger the menu shortcut.
+ evt = CreateKeyEvent(GDK_KEY_RELEASE, GDK_Alt_L, 0);
+ evt.hardware_keycode = 0x40;
+ EXPECT_TRUE(handler.Dispatch(reinterpret_cast<GdkEvent*>(&evt)));
+
+ ASSERT_TRUE(menu_pressed_);
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_touch.cc b/ui/views/focus/accelerator_handler_touch.cc
new file mode 100644
index 0000000..1c4c9b3
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_touch.cc
@@ -0,0 +1,187 @@
+// Copyright (c) 2011 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/focus/accelerator_handler.h"
+
+#include <bitset>
+#include <gtk/gtk.h>
+#include <X11/extensions/XInput2.h>
+
+#include "ui/base/touch/touch_factory.h"
+#include "ui/views/ime/input_method.h"
+#include "views/events/event.h"
+#include "views/focus/focus_manager.h"
+#include "views/view.h"
+#include "views/widget/native_widget.h"
+
+namespace views {
+
+namespace {
+
+Widget* FindWidgetForGdkWindow(GdkWindow* gdk_window) {
+ gpointer data = NULL;
+ gdk_window_get_user_data(gdk_window, &data);
+ GtkWidget* gtk_widget = reinterpret_cast<GtkWidget*>(data);
+ if (!gtk_widget || !GTK_IS_WIDGET(gtk_widget)) {
+ DLOG(WARNING) << "no GtkWidget found for that GdkWindow";
+ return NULL;
+ }
+ Widget* widget = Widget::GetWidgetForNativeView(gtk_widget);
+
+ if (!widget) {
+ DLOG(WARNING) << "no NativeWidgetGtk found for that GtkWidget";
+ return NULL;
+ }
+ return widget;
+}
+
+} // namespace
+
+bool DispatchX2Event(Widget* widget, XEvent* xev) {
+ XGenericEventCookie* cookie = &xev->xcookie;
+ switch (cookie->evtype) {
+ case XI_KeyPress:
+ case XI_KeyRelease: {
+ // TODO(sad): We don't capture XInput2 events from keyboard yet.
+ break;
+ }
+#if defined(USE_XI2_MT)
+ case XI_TouchBegin:
+ case XI_TouchEnd:
+ case XI_TouchUpdate: {
+ // Hide the cursor when a touch event comes in.
+ ui::TouchFactory::GetInstance()->SetCursorVisible(false, false);
+
+ // If the TouchEvent is processed by |widget|, then return.
+ TouchEvent touch(xev);
+ if (widget->OnTouchEvent(touch) != ui::TOUCH_STATUS_UNKNOWN)
+ return true;
+
+ // We do not want to generate a mouse event for an unprocessed touch
+ // event here. That is already done by the gesture manager in
+ // RootView::OnTouchEvent.
+ return false;
+ }
+#endif
+ case XI_ButtonPress:
+ case XI_ButtonRelease:
+ case XI_Motion: {
+ XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(cookie->data);
+
+ // Scrolling the wheel generates press/release events with button id's 4
+ // and 5. In case of a wheelscroll, we do not want to show the cursor.
+ if (xievent->detail == 4 || xievent->detail == 5) {
+ MouseWheelEvent wheelev(xev);
+ return widget->OnMouseEvent(wheelev);
+ }
+
+ ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
+ // Is the event coming from a touch device?
+ if (factory->IsTouchDevice(xievent->sourceid)) {
+ // Hide the cursor when a touch event comes in.
+ factory->SetCursorVisible(false, false);
+
+ // With XInput 2.0, XI_ButtonPress and XI_ButtonRelease events are
+ // ignored, as XI_Motion events contain enough data to detect finger
+ // press and release. See more notes in TouchFactory::TouchParam.
+ if ((cookie->evtype == XI_ButtonPress ||
+ cookie->evtype == XI_ButtonRelease) &&
+ factory->IsRealTouchDevice(xievent->sourceid))
+ return false;
+
+ // If the TouchEvent is processed by |widget|, then return. Otherwise
+ // let it fall through so it can be used as a MouseEvent, if desired.
+ TouchEvent touch(xev);
+ if (widget->OnTouchEvent(touch) != ui::TOUCH_STATUS_UNKNOWN)
+ return true;
+
+ // We do not want to generate a mouse event for an unprocessed touch
+ // event here. That is already done by the gesture manager in
+ // RootView::OnTouchEvent.
+ return false;
+ } else {
+ MouseEvent mouseev(xev);
+
+ // Show the cursor. Start a timer to hide the cursor after a delay on
+ // move (not drag) events, or if the only button pressed is released.
+ bool start_timer = mouseev.type() == ui::ET_MOUSE_MOVED;
+ start_timer |= mouseev.type() == ui::ET_MOUSE_RELEASED &&
+ (mouseev.IsOnlyLeftMouseButton() ||
+ mouseev.IsOnlyMiddleMouseButton() ||
+ mouseev.IsOnlyRightMouseButton());
+ factory->SetCursorVisible(true, start_timer);
+
+ return widget->OnMouseEvent(mouseev);
+ }
+ }
+ }
+ return false;
+}
+
+bool DispatchXEvent(XEvent* xev) {
+ GdkDisplay* gdisp = gdk_display_get_default();
+ XID xwindow = xev->xany.window;
+
+ if (xev->type == GenericEvent) {
+ if (!ui::TouchFactory::GetInstance()->ShouldProcessXI2Event(xev))
+ return true; // Consume the event.
+
+ XGenericEventCookie* cookie = &xev->xcookie;
+ if (cookie->evtype == XI_HierarchyChanged) {
+ ui::TouchFactory::GetInstance()->UpdateDeviceList(cookie->display);
+ return true;
+ }
+
+ XIDeviceEvent* xiev = static_cast<XIDeviceEvent*>(cookie->data);
+ xwindow = xiev->event;
+ }
+
+ GdkWindow* gwind = gdk_window_lookup_for_display(gdisp, xwindow);
+ Widget* widget = FindWidgetForGdkWindow(gwind);
+ if (widget) {
+ switch (xev->type) {
+ case KeyPress:
+ case KeyRelease: {
+ KeyEvent keyev(xev);
+ InputMethod* ime = widget->GetInputMethod();
+ // Always dispatch key events to the input method first, to make sure
+ // that the input method's hotkeys work all time.
+ if (ime) {
+ ime->DispatchKeyEvent(keyev);
+ return true;
+ }
+ return widget->OnKeyEvent(keyev);
+ }
+ case ButtonPress:
+ case ButtonRelease:
+ if (xev->xbutton.button == 4 || xev->xbutton.button == 5) {
+ // Scrolling the wheel triggers button press/release events.
+ MouseWheelEvent wheelev(xev);
+ return widget->OnMouseEvent(wheelev);
+ }
+ // fallthrough
+ case MotionNotify: {
+ MouseEvent mouseev(xev);
+ return widget->OnMouseEvent(mouseev);
+ }
+
+ case GenericEvent: {
+ return DispatchX2Event(widget, xev);
+ }
+ }
+ }
+
+ return false;
+}
+
+AcceleratorHandler::AcceleratorHandler() {}
+
+base::MessagePumpDispatcher::DispatchStatus
+ AcceleratorHandler::Dispatch(XEvent* xev) {
+ return DispatchXEvent(xev) ?
+ base::MessagePumpDispatcher::EVENT_PROCESSED :
+ base::MessagePumpDispatcher::EVENT_IGNORED;
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_wayland.cc b/ui/views/focus/accelerator_handler_wayland.cc
new file mode 100644
index 0000000..a8963ee
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_wayland.cc
@@ -0,0 +1,18 @@
+// Copyright (c) 2011 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/focus/accelerator_handler.h"
+
+#include "views/focus/focus_manager.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {}
+
+base::MessagePumpDispatcher::DispatchStatus
+ AcceleratorHandler::Dispatch(base::wayland::WaylandEvent* ev) {
+ return base::MessagePumpDispatcher::EVENT_IGNORED;
+}
+
+} // namespace views
diff --git a/ui/views/focus/accelerator_handler_win.cc b/ui/views/focus/accelerator_handler_win.cc
new file mode 100644
index 0000000..8f2c8e8
--- /dev/null
+++ b/ui/views/focus/accelerator_handler_win.cc
@@ -0,0 +1,60 @@
+// Copyright (c) 2011 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/focus/accelerator_handler.h"
+
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/keycodes/keyboard_code_conversion_win.h"
+#include "views/events/event.h"
+#include "views/focus/focus_manager.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+AcceleratorHandler::AcceleratorHandler() {
+}
+
+bool AcceleratorHandler::Dispatch(const MSG& msg) {
+ bool process_message = true;
+
+ if (msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST) {
+ Widget* widget = Widget::GetTopLevelWidgetForNativeView(msg.hwnd);
+ FocusManager* focus_manager = widget ? widget->GetFocusManager() : NULL;
+ if (focus_manager) {
+ switch (msg.message) {
+ case WM_KEYDOWN:
+ case WM_SYSKEYDOWN: {
+ KeyEvent event(msg);
+ process_message = focus_manager->OnKeyEvent(event);
+ if (!process_message) {
+ // Record that this key is pressed so we can remember not to
+ // translate and dispatch the associated WM_KEYUP.
+ pressed_keys_.insert(msg.wParam);
+ }
+ break;
+ }
+ case WM_KEYUP:
+ case WM_SYSKEYUP: {
+ std::set<WPARAM>::iterator iter = pressed_keys_.find(msg.wParam);
+ if (iter != pressed_keys_.end()) {
+ // Don't translate/dispatch the KEYUP since we have eaten the
+ // associated KEYDOWN.
+ pressed_keys_.erase(iter);
+ return true;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ if (process_message) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+
+ return true;
+}
+
+} // namespace views
diff --git a/ui/views/focus/external_focus_tracker.cc b/ui/views/focus/external_focus_tracker.cc
new file mode 100644
index 0000000..193b57e
--- /dev/null
+++ b/ui/views/focus/external_focus_tracker.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2011 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/focus/external_focus_tracker.h"
+
+#include "base/logging.h"
+#include "views/view.h"
+#include "views/focus/view_storage.h"
+
+namespace views {
+
+ExternalFocusTracker::ExternalFocusTracker(View* parent_view,
+ FocusManager* focus_manager)
+ : focus_manager_(focus_manager),
+ parent_view_(parent_view) {
+ DCHECK(focus_manager);
+ DCHECK(parent_view);
+ view_storage_ = ViewStorage::GetInstance();
+ last_focused_view_storage_id_ = view_storage_->CreateStorageID();
+ // Store the view which is focused when we're created.
+ StartTracking();
+}
+
+ExternalFocusTracker::~ExternalFocusTracker() {
+ view_storage_->RemoveView(last_focused_view_storage_id_);
+ if (focus_manager_)
+ focus_manager_->RemoveFocusChangeListener(this);
+}
+
+void ExternalFocusTracker::OnWillChangeFocus(View* focused_before,
+ View* focused_now) {
+ if (focused_now && !parent_view_->Contains(focused_now) &&
+ parent_view_ != focused_now) {
+ // Store the newly focused view.
+ StoreLastFocusedView(focused_now);
+ }
+}
+
+void ExternalFocusTracker::OnDidChangeFocus(View* focused_before,
+ View* focused_now) {
+}
+
+void ExternalFocusTracker::FocusLastFocusedExternalView() {
+ View* last_focused_view =
+ view_storage_->RetrieveView(last_focused_view_storage_id_);
+ if (last_focused_view)
+ last_focused_view->RequestFocus();
+}
+
+void ExternalFocusTracker::SetFocusManager(FocusManager* focus_manager) {
+ if (focus_manager_)
+ focus_manager_->RemoveFocusChangeListener(this);
+ focus_manager_ = focus_manager;
+ if (focus_manager_)
+ StartTracking();
+}
+
+void ExternalFocusTracker::StoreLastFocusedView(View* view) {
+ view_storage_->RemoveView(last_focused_view_storage_id_);
+ // If the view is NULL, remove the last focused view from storage, but don't
+ // try to store NULL.
+ if (view != NULL)
+ view_storage_->StoreView(last_focused_view_storage_id_, view);
+}
+
+void ExternalFocusTracker::StartTracking() {
+ StoreLastFocusedView(focus_manager_->GetFocusedView());
+ focus_manager_->AddFocusChangeListener(this);
+}
+
+} // namespace views
diff --git a/ui/views/focus/external_focus_tracker.h b/ui/views/focus/external_focus_tracker.h
new file mode 100644
index 0000000..0ea1479
--- /dev/null
+++ b/ui/views/focus/external_focus_tracker.h
@@ -0,0 +1,78 @@
+// Copyright (c) 2011 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 UI_VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
+#define UI_VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
+#pragma once
+
+#include "views/focus/focus_manager.h"
+
+namespace views {
+
+class View;
+class ViewStorage;
+
+// ExternalFocusTracker tracks the last focused view which belongs to the
+// provided focus manager and is not either the provided parent view or one of
+// its descendants. This is generally used if the parent view want to return
+// focus to some other view once it is dismissed. The parent view and the focus
+// manager must exist for the duration of the tracking. If the focus manager
+// must be deleted before this object is deleted, make sure to call
+// SetFocusManager(NULL) first.
+//
+// Typical use: When a view is added to the view hierarchy, it instantiates an
+// ExternalFocusTracker and passes in itself and its focus manager. Then,
+// when that view wants to return focus to the last focused view which is not
+// itself and not a descandant of itself, (usually when it is being closed)
+// it calls FocusLastFocusedExternalView.
+class VIEWS_EXPORT ExternalFocusTracker : public FocusChangeListener {
+ public:
+ ExternalFocusTracker(View* parent_view, FocusManager* focus_manager);
+
+ virtual ~ExternalFocusTracker();
+ // FocusChangeListener implementation.
+ virtual void OnWillChangeFocus(View* focused_before, View* focused_now);
+ virtual void OnDidChangeFocus(View* focused_before, View* focused_now);
+
+ // Focuses last focused view which is not a child of parent view and is not
+ // parent view itself. Returns true if focus for a view was requested, false
+ // otherwise.
+ void FocusLastFocusedExternalView();
+
+ // Sets the focus manager whose focus we are tracking. |focus_manager| can
+ // be NULL, but no focus changes will be tracked. This is useful if the focus
+ // manager went away, but you might later want to start tracking with a new
+ // manager later, or call FocusLastFocusedExternalView to focus the previous
+ // view.
+ void SetFocusManager(FocusManager* focus_manager);
+
+ private:
+ // Store the provided view. This view will be focused when
+ // FocusLastFocusedExternalView is called.
+ void StoreLastFocusedView(View* view);
+
+ // Store the currently focused view for our view manager and register as a
+ // listener for future focus changes.
+ void StartTracking();
+
+ // Focus manager which we are a listener for.
+ FocusManager* focus_manager_;
+
+ // ID of the last focused view, which we store in view_storage_.
+ int last_focused_view_storage_id_;
+
+ // Used to store the last focused view which is not a child of
+ // ExternalFocusTracker.
+ ViewStorage* view_storage_;
+
+ // The parent view of views which we should not track focus changes to. We
+ // also do not track changes to parent_view_ itself.
+ View* parent_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExternalFocusTracker);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_EXTERNAL_FOCUS_TRACKER_H_
diff --git a/ui/views/focus/focus_manager.cc b/ui/views/focus/focus_manager.cc
new file mode 100644
index 0000000..3ee0755
--- /dev/null
+++ b/ui/views/focus/focus_manager.cc
@@ -0,0 +1,413 @@
+// Copyright (c) 2011 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/focus/focus_manager.h"
+
+#include <algorithm>
+
+#include "base/auto_reset.h"
+#include "base/logging.h"
+#include "build/build_config.h"
+#include "ui/base/accelerator_manager.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/models/accelerator.h"
+#include "views/focus/focus_search.h"
+#include "views/focus/view_storage.h"
+#include "views/focus/widget_focus_manager.h"
+#include "views/view.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+
+namespace views {
+
+FocusManager::FocusManager(Widget* widget)
+ : widget_(widget),
+ focused_view_(NULL),
+ accelerator_manager_(new ui::AcceleratorManager),
+ focus_change_reason_(kReasonDirectFocusChange),
+ is_changing_focus_(false) {
+ DCHECK(widget_);
+ stored_focused_view_storage_id_ =
+ ViewStorage::GetInstance()->CreateStorageID();
+}
+
+FocusManager::~FocusManager() {
+}
+
+bool FocusManager::OnKeyEvent(const KeyEvent& event) {
+#if defined(OS_WIN)
+ // If the focused view wants to process the key event as is, let it be.
+ // On Linux we always dispatch key events to the focused view first, so
+ // we should not do this check here. See also NativeWidgetGtk::OnKeyEvent().
+ if (focused_view_ && focused_view_->SkipDefaultKeyEventProcessing(event))
+ return true;
+#endif
+
+ // Intercept Tab related messages for focus traversal.
+ // Note that we don't do focus traversal if the root window is not part of the
+ // active window hierarchy as this would mean we have no focused view and
+ // would focus the first focusable view.
+#if defined(OS_WIN) && !defined(USE_AURA)
+ HWND top_window = widget_->GetNativeView();
+ HWND active_window = ::GetActiveWindow();
+ if ((active_window == top_window || ::IsChild(active_window, top_window)) &&
+ IsTabTraversalKeyEvent(event)) {
+ AdvanceFocus(event.IsShiftDown());
+ return false;
+ }
+#else
+ if (IsTabTraversalKeyEvent(event)) {
+ AdvanceFocus(event.IsShiftDown());
+ return false;
+ }
+#endif
+
+ // Intercept arrow key messages to switch between grouped views.
+ ui::KeyboardCode key_code = event.key_code();
+ if (focused_view_ && focused_view_->GetGroup() != -1 &&
+ (key_code == ui::VKEY_UP || key_code == ui::VKEY_DOWN ||
+ key_code == ui::VKEY_LEFT || key_code == ui::VKEY_RIGHT)) {
+ bool next = (key_code == ui::VKEY_RIGHT || key_code == ui::VKEY_DOWN);
+ View::Views views;
+ focused_view_->parent()->GetViewsInGroup(focused_view_->GetGroup(), &views);
+ View::Views::const_iterator i(
+ std::find(views.begin(), views.end(), focused_view_));
+ DCHECK(i != views.end());
+ int index = static_cast<int>(i - views.begin());
+ index += next ? 1 : -1;
+ if (index < 0) {
+ index = static_cast<int>(views.size()) - 1;
+ } else if (index >= static_cast<int>(views.size())) {
+ index = 0;
+ }
+ SetFocusedViewWithReason(views[index], kReasonFocusTraversal);
+ return false;
+ }
+
+ // Process keyboard accelerators.
+ // If the key combination matches an accelerator, the accelerator is
+ // triggered, otherwise the key event is processed as usual.
+ ui::Accelerator accelerator(event.key_code(),
+ event.IsShiftDown(),
+ event.IsControlDown(),
+ event.IsAltDown());
+ if (ProcessAccelerator(accelerator)) {
+ // If a shortcut was activated for this keydown message, do not propagate
+ // the event further.
+ return false;
+ }
+ return true;
+}
+
+void FocusManager::ValidateFocusedView() {
+ if (focused_view_) {
+ if (!ContainsView(focused_view_))
+ ClearFocus();
+ }
+}
+
+// Tests whether a view is valid, whether it still belongs to the window
+// hierarchy of the FocusManager.
+bool FocusManager::ContainsView(View* view) {
+ Widget* widget = view->GetWidget();
+ return widget ? widget->GetFocusManager() == this : false;
+}
+
+void FocusManager::AdvanceFocus(bool reverse) {
+ View* v = GetNextFocusableView(focused_view_, reverse, false);
+ // Note: Do not skip this next block when v == focused_view_. If the user
+ // tabs past the last focusable element in a webpage, we'll get here, and if
+ // the TabContentsContainerView is the only focusable view (possible in
+ // fullscreen mode), we need to run this block in order to cycle around to the
+ // first element on the page.
+ if (v) {
+ v->AboutToRequestFocusFromTabTraversal(reverse);
+ SetFocusedViewWithReason(v, kReasonFocusTraversal);
+ }
+}
+
+void FocusManager::ClearNativeFocus() {
+ // Keep the top root window focused so we get keyboard events.
+ widget_->ClearNativeFocus();
+}
+
+View* FocusManager::GetNextFocusableView(View* original_starting_view,
+ bool reverse,
+ bool dont_loop) {
+ FocusTraversable* focus_traversable = NULL;
+
+ // Let's revalidate the focused view.
+ ValidateFocusedView();
+
+ View* starting_view = NULL;
+ if (original_starting_view) {
+ // Search up the containment hierarchy to see if a view is acting as
+ // a pane, and wants to implement its own focus traversable to keep
+ // the focus trapped within that pane.
+ View* pane_search = original_starting_view;
+ while (pane_search) {
+ focus_traversable = pane_search->GetPaneFocusTraversable();
+ if (focus_traversable) {
+ starting_view = original_starting_view;
+ break;
+ }
+ pane_search = pane_search->parent();
+ }
+
+ if (!focus_traversable) {
+ if (!reverse) {
+ // If the starting view has a focus traversable, use it.
+ // This is the case with NativeWidgetWins for example.
+ focus_traversable = original_starting_view->GetFocusTraversable();
+
+ // Otherwise default to the root view.
+ if (!focus_traversable) {
+ focus_traversable =
+ original_starting_view->GetWidget()->GetFocusTraversable();
+ starting_view = original_starting_view;
+ }
+ } else {
+ // When you are going back, starting view's FocusTraversable
+ // should not be used.
+ focus_traversable =
+ original_starting_view->GetWidget()->GetFocusTraversable();
+ starting_view = original_starting_view;
+ }
+ }
+ } else {
+ focus_traversable = widget_->GetFocusTraversable();
+ }
+
+ // Traverse the FocusTraversable tree down to find the focusable view.
+ View* v = FindFocusableView(focus_traversable, starting_view, reverse);
+ if (v) {
+ return v;
+ } else {
+ // Let's go up in the FocusTraversable tree.
+ FocusTraversable* parent_focus_traversable =
+ focus_traversable->GetFocusTraversableParent();
+ starting_view = focus_traversable->GetFocusTraversableParentView();
+ while (parent_focus_traversable) {
+ FocusTraversable* new_focus_traversable = NULL;
+ View* new_starting_view = NULL;
+ // When we are going backward, the parent view might gain the next focus.
+ bool check_starting_view = reverse;
+ v = parent_focus_traversable->GetFocusSearch()->FindNextFocusableView(
+ starting_view, reverse, FocusSearch::UP,
+ check_starting_view, &new_focus_traversable, &new_starting_view);
+
+ if (new_focus_traversable) {
+ DCHECK(!v);
+
+ // There is a FocusTraversable, traverse it down.
+ v = FindFocusableView(new_focus_traversable, NULL, reverse);
+ }
+
+ if (v)
+ return v;
+
+ starting_view = focus_traversable->GetFocusTraversableParentView();
+ parent_focus_traversable =
+ parent_focus_traversable->GetFocusTraversableParent();
+ }
+
+ // If we get here, we have reached the end of the focus hierarchy, let's
+ // loop. Make sure there was at least a view to start with, to prevent
+ // infinitely looping in empty windows.
+ if (!dont_loop && original_starting_view) {
+ // Easy, just clear the selection and press tab again.
+ // By calling with NULL as the starting view, we'll start from the
+ // top_root_view.
+ return GetNextFocusableView(NULL, reverse, true);
+ }
+ }
+ return NULL;
+}
+
+void FocusManager::SetFocusedViewWithReason(
+ View* view, FocusChangeReason reason) {
+ if (focused_view_ == view)
+ return;
+
+ AutoReset<bool> auto_changing_focus(&is_changing_focus_, true);
+ // Update the reason for the focus change (since this is checked by
+ // some listeners), then notify all listeners.
+ focus_change_reason_ = reason;
+ FOR_EACH_OBSERVER(FocusChangeListener, focus_change_listeners_,
+ OnWillChangeFocus(focused_view_, view));
+
+ View* old_focused_view = focused_view_;
+ focused_view_ = view;
+ if (old_focused_view)
+ old_focused_view->Blur();
+ if (focused_view_)
+ focused_view_->Focus();
+
+ FOR_EACH_OBSERVER(FocusChangeListener, focus_change_listeners_,
+ OnDidChangeFocus(old_focused_view, focused_view_));
+}
+
+void FocusManager::ClearFocus() {
+ SetFocusedView(NULL);
+ ClearNativeFocus();
+}
+
+void FocusManager::StoreFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetInstance();
+ if (!view_storage) {
+ // This should never happen but bug 981648 seems to indicate it could.
+ NOTREACHED();
+ return;
+ }
+
+ // TODO (jcampan): when a TabContents containing a popup is closed, the focus
+ // is stored twice causing an assert. We should find a better alternative than
+ // removing the view from the storage explicitly.
+ view_storage->RemoveView(stored_focused_view_storage_id_);
+
+ if (!focused_view_)
+ return;
+
+ view_storage->StoreView(stored_focused_view_storage_id_, focused_view_);
+
+ View* v = focused_view_;
+
+ {
+ // Temporarily disable notification. ClearFocus() will set the focus to the
+ // main browser window. This extra focus bounce which happens during
+ // deactivation can confuse registered WidgetFocusListeners, as the focus
+ // is not changing due to a user-initiated event.
+ AutoNativeNotificationDisabler local_notification_disabler;
+ ClearFocus();
+ }
+
+ if (v)
+ v->SchedulePaint(); // Remove focus border.
+}
+
+void FocusManager::RestoreFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetInstance();
+ if (!view_storage) {
+ // This should never happen but bug 981648 seems to indicate it could.
+ NOTREACHED();
+ return;
+ }
+
+ View* view = view_storage->RetrieveView(stored_focused_view_storage_id_);
+ if (view) {
+ if (ContainsView(view)) {
+ if (!view->IsFocusableInRootView() &&
+ view->IsAccessibilityFocusableInRootView()) {
+ // RequestFocus would fail, but we want to restore focus to controls
+ // that had focus in accessibility mode.
+ SetFocusedViewWithReason(view, kReasonFocusRestore);
+ } else {
+ // This usually just sets the focus if this view is focusable, but
+ // let the view override RequestFocus if necessary.
+ view->RequestFocus();
+
+ // If it succeeded, the reason would be incorrect; set it to
+ // focus restore.
+ if (focused_view_ == view)
+ focus_change_reason_ = kReasonFocusRestore;
+ }
+ }
+ }
+}
+
+void FocusManager::ClearStoredFocusedView() {
+ ViewStorage* view_storage = ViewStorage::GetInstance();
+ if (!view_storage) {
+ // This should never happen but bug 981648 seems to indicate it could.
+ NOTREACHED();
+ return;
+ }
+ view_storage->RemoveView(stored_focused_view_storage_id_);
+}
+
+// Find the next (previous if reverse is true) focusable view for the specified
+// FocusTraversable, starting at the specified view, traversing down the
+// FocusTraversable hierarchy.
+View* FocusManager::FindFocusableView(FocusTraversable* focus_traversable,
+ View* starting_view,
+ bool reverse) {
+ FocusTraversable* new_focus_traversable = NULL;
+ View* new_starting_view = NULL;
+ View* v = focus_traversable->GetFocusSearch()->FindNextFocusableView(
+ starting_view,
+ reverse,
+ FocusSearch::DOWN,
+ false,
+ &new_focus_traversable,
+ &new_starting_view);
+
+ // Let's go down the FocusTraversable tree as much as we can.
+ while (new_focus_traversable) {
+ DCHECK(!v);
+ focus_traversable = new_focus_traversable;
+ starting_view = new_starting_view;
+ new_focus_traversable = NULL;
+ starting_view = NULL;
+ v = focus_traversable->GetFocusSearch()->FindNextFocusableView(
+ starting_view,
+ reverse,
+ FocusSearch::DOWN,
+ false,
+ &new_focus_traversable,
+ &new_starting_view);
+ }
+ return v;
+}
+
+void FocusManager::RegisterAccelerator(
+ const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target) {
+ accelerator_manager_->Register(accelerator, target);
+}
+
+void FocusManager::UnregisterAccelerator(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target) {
+ accelerator_manager_->Unregister(accelerator, target);
+}
+
+void FocusManager::UnregisterAccelerators(ui::AcceleratorTarget* target) {
+ accelerator_manager_->UnregisterAll(target);
+}
+
+bool FocusManager::ProcessAccelerator(const ui::Accelerator& accelerator) {
+ return accelerator_manager_->Process(accelerator);
+}
+
+ui::AcceleratorTarget* FocusManager::GetCurrentTargetForAccelerator(
+ const ui::Accelerator& accelerator) const {
+ return accelerator_manager_->GetCurrentTarget(accelerator);
+}
+
+void FocusManager::FocusNativeView(gfx::NativeView native_view) {
+ widget_->FocusNativeView(native_view);
+}
+
+// static
+bool FocusManager::IsTabTraversalKeyEvent(const KeyEvent& key_event) {
+ return key_event.key_code() == ui::VKEY_TAB && !key_event.IsControlDown();
+}
+
+void FocusManager::ViewRemoved(View* removed) {
+ // If the view being removed contains (or is) the focused view,
+ // clear the focus. However, it's not safe to call ClearFocus()
+ // (and in turn ClearNativeFocus()) here because ViewRemoved() can
+ // be called while the top level widget is being destroyed.
+ if (focused_view_ && removed && removed->Contains(focused_view_))
+ SetFocusedView(NULL);
+}
+
+void FocusManager::AddFocusChangeListener(FocusChangeListener* listener) {
+ focus_change_listeners_.AddObserver(listener);
+}
+
+void FocusManager::RemoveFocusChangeListener(FocusChangeListener* listener) {
+ focus_change_listeners_.RemoveObserver(listener);
+}
+
+} // namespace views
diff --git a/ui/views/focus/focus_manager.h b/ui/views/focus/focus_manager.h
new file mode 100644
index 0000000..0fdecc1
--- /dev/null
+++ b/ui/views/focus/focus_manager.h
@@ -0,0 +1,283 @@
+// Copyright (c) 2011 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 UI_VIEWS_FOCUS_FOCUS_MANAGER_H_
+#define UI_VIEWS_FOCUS_FOCUS_MANAGER_H_
+#pragma once
+
+#include <list>
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/observer_list.h"
+#include "ui/base/models/accelerator.h"
+#include "ui/gfx/native_widget_types.h"
+#include "views/views_export.h"
+#include "views/events/event.h"
+
+// The FocusManager class is used to handle focus traversal, store/restore
+// focused views and handle keyboard accelerators.
+//
+// There are 2 types of focus:
+// - the native focus, which is the focus that an gfx::NativeView has.
+// - the view focus, which is the focus that a views::View has.
+//
+// Each native view must register with their Focus Manager so the focus manager
+// gets notified when they are focused (and keeps track of the native focus) and
+// as well so that the tab key events can be intercepted.
+// They can provide when they register a View that is kept in synch in term of
+// focus. This is used in NativeControl for example, where a View wraps an
+// actual native window.
+// This is already done for you if you subclass the NativeControl class or if
+// you use the NativeViewHost class.
+//
+// When creating a top window (derived from views::Widget) that is not a child
+// window, it creates and owns a FocusManager to manage the focus for itself and
+// all its child windows.
+//
+// The FocusTraversable interface exposes the methods a class should implement
+// in order to be able to be focus traversed when tab key is pressed.
+// RootViews implement FocusTraversable.
+// The FocusManager contains a top FocusTraversable instance, which is the top
+// RootView.
+//
+// If you just use views, then the focus traversal is handled for you by the
+// RootView. The default traversal order is the order in which the views have
+// been added to their container. You can modify this order by using the View
+// method SetNextFocusableView().
+//
+// If you are embedding a native view containing a nested RootView (for example
+// by adding a NativeControl that contains a NativeWidgetWin as its native
+// component), then you need to:
+// - override the View::GetFocusTraversable() method in your outer component.
+// It should return the RootView of the inner component. This is used when
+// the focus traversal traverse down the focus hierarchy to enter the nested
+// RootView. In the example mentioned above, the NativeControl overrides
+// GetFocusTraversable() and returns hwnd_view_container_->GetRootView().
+// - call Widget::SetFocusTraversableParent() on the nested RootView and point
+// it to the outer RootView. This is used when the focus goes out of the
+// nested RootView. In the example:
+// hwnd_view_container_->GetWidget()->SetFocusTraversableParent(
+// native_control->GetRootView());
+// - call RootView::SetFocusTraversableParentView() on the nested RootView with
+// the parent view that directly contains the native window. This is needed
+// when traversing up from the nested RootView to know which view to start
+// with when going to the next/previous view.
+// In our example:
+// hwnd_view_container_->GetWidget()->SetFocusTraversableParent(
+// native_control);
+//
+// Note that FocusTraversable do not have to be RootViews: AccessibleToolbarView
+// is FocusTraversable.
+
+namespace ui {
+class AcceleratorTarget;
+class AcceleratorManager;
+}
+
+namespace views {
+
+class FocusSearch;
+class RootView;
+class View;
+class Widget;
+
+// The FocusTraversable interface is used by components that want to process
+// focus traversal events (due to Tab/Shift-Tab key events).
+class VIEWS_EXPORT FocusTraversable {
+ public:
+ // Return a FocusSearch object that implements the algorithm to find
+ // the next or previous focusable view.
+ virtual FocusSearch* GetFocusSearch() = 0;
+
+ // Should return the parent FocusTraversable.
+ // The top RootView which is the top FocusTraversable returns NULL.
+ virtual FocusTraversable* GetFocusTraversableParent() = 0;
+
+ // This should return the View this FocusTraversable belongs to.
+ // It is used when walking up the view hierarchy tree to find which view
+ // should be used as the starting view for finding the next/previous view.
+ virtual View* GetFocusTraversableParentView() = 0;
+
+ protected:
+ virtual ~FocusTraversable() {}
+};
+
+// This interface should be implemented by classes that want to be notified when
+// the focus is about to change. See the Add/RemoveFocusChangeListener methods.
+class VIEWS_EXPORT FocusChangeListener {
+ public:
+ // No change to focus state has occurred yet when this function is called.
+ virtual void OnWillChangeFocus(View* focused_before, View* focused_now) = 0;
+
+ // Called after focus state has changed.
+ virtual void OnDidChangeFocus(View* focused_before, View* focused_now) = 0;
+
+ protected:
+ virtual ~FocusChangeListener() {}
+};
+
+class VIEWS_EXPORT FocusManager {
+ public:
+ // The reason why the focus changed.
+ enum FocusChangeReason {
+ // The focus changed because the user traversed focusable views using
+ // keys like Tab or Shift+Tab.
+ kReasonFocusTraversal,
+
+ // The focus changed due to restoring the focus.
+ kReasonFocusRestore,
+
+ // The focus changed due to a click or a shortcut to jump directly to
+ // a particular view.
+ kReasonDirectFocusChange
+ };
+
+ explicit FocusManager(Widget* widget);
+ virtual ~FocusManager();
+
+ // Processes the passed key event for accelerators and tab traversal.
+ // Returns false if the event has been consumed and should not be processed
+ // further.
+ bool OnKeyEvent(const KeyEvent& event);
+
+ // Returns true is the specified is part of the hierarchy of the window
+ // associated with this FocusManager.
+ bool ContainsView(View* view);
+
+ // Advances the focus (backward if reverse is true).
+ void AdvanceFocus(bool reverse);
+
+ // The FocusManager keeps track of the focused view within a RootView.
+ View* GetFocusedView() { return focused_view_; }
+ const View* GetFocusedView() const { return focused_view_; }
+
+ // Low-level methods to force the focus to change (and optionally provide
+ // a reason). If the focus change should only happen if the view is
+ // currenty focusable, enabled, and visible, call view->RequestFocus().
+ void SetFocusedViewWithReason(View* view, FocusChangeReason reason);
+ void SetFocusedView(View* view) {
+ SetFocusedViewWithReason(view, kReasonDirectFocusChange);
+ }
+
+ // Get the reason why the focus most recently changed.
+ FocusChangeReason focus_change_reason() const {
+ return focus_change_reason_;
+ }
+
+ // Clears the focused view. The window associated with the top root view gets
+ // the native focus (so we still get keyboard events).
+ void ClearFocus();
+
+ // Validates the focused view, clearing it if the window it belongs too is not
+ // attached to the window hierarchy anymore.
+ void ValidateFocusedView();
+
+ // Stores and restores the focused view. Used when the window becomes
+ // active/inactive.
+ void StoreFocusedView();
+ void RestoreFocusedView();
+
+ // Clears the stored focused view.
+ void ClearStoredFocusedView();
+
+ // Returns true if in the process of changing the focused view.
+ bool is_changing_focus() const { return is_changing_focus_; }
+
+ // Register a keyboard accelerator for the specified target. If multiple
+ // targets are registered for an accelerator, a target registered later has
+ // higher priority.
+ // Note that we are currently limited to accelerators that are either:
+ // - a key combination including Ctrl or Alt
+ // - the escape key
+ // - the enter key
+ // - any F key (F1, F2, F3 ...)
+ // - any browser specific keys (as available on special keyboards)
+ void RegisterAccelerator(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target);
+
+ // Unregister the specified keyboard accelerator for the specified target.
+ void UnregisterAccelerator(const ui::Accelerator& accelerator,
+ ui::AcceleratorTarget* target);
+
+ // Unregister all keyboard accelerator for the specified target.
+ void UnregisterAccelerators(ui::AcceleratorTarget* target);
+
+ // Activate the target associated with the specified accelerator.
+ // First, AcceleratorPressed handler of the most recently registered target
+ // is called, and if that handler processes the event (i.e. returns true),
+ // this method immediately returns. If not, we do the same thing on the next
+ // target, and so on.
+ // Returns true if an accelerator was activated.
+ bool ProcessAccelerator(const ui::Accelerator& accelerator);
+
+ // Called by a RootView when a view within its hierarchy is removed
+ // from its parent. This will only be called by a RootView in a
+ // hierarchy of Widgets that this FocusManager is attached to the
+ // parent Widget of.
+ void ViewRemoved(View* removed);
+
+ // Adds/removes a listener. The FocusChangeListener is notified every time
+ // the focused view is about to change.
+ void AddFocusChangeListener(FocusChangeListener* listener);
+ void RemoveFocusChangeListener(FocusChangeListener* listener);
+
+ // Returns the AcceleratorTarget that should be activated for the specified
+ // keyboard accelerator, or NULL if no view is registered for that keyboard
+ // accelerator.
+ ui::AcceleratorTarget* GetCurrentTargetForAccelerator(
+ const ui::Accelerator& accelertor) const;
+
+ // Sets the focus to the specified native view.
+ virtual void FocusNativeView(gfx::NativeView native_view);
+
+ // Clears the native view having the focus.
+ virtual void ClearNativeFocus();
+
+ // Convenience method that returns true if the passed |key_event| should
+ // trigger tab traversal (if it is a TAB key press with or without SHIFT
+ // pressed).
+ static bool IsTabTraversalKeyEvent(const KeyEvent& key_event);
+
+ private:
+ // Returns the next focusable view.
+ View* GetNextFocusableView(View* starting_view, bool reverse, bool dont_loop);
+
+ // Returns the focusable view found in the FocusTraversable specified starting
+ // at the specified view. This traverses down along the FocusTraversable
+ // hierarchy.
+ // Returns NULL if no focusable view were found.
+ View* FindFocusableView(FocusTraversable* focus_traversable,
+ View* starting_view,
+ bool reverse);
+
+ // The top-level Widget this FocusManager is associated with.
+ Widget* widget_;
+
+ // The view that currently is focused.
+ View* focused_view_;
+
+ // The AcceleratorManager this FocusManager is associated with.
+ scoped_ptr<ui::AcceleratorManager> accelerator_manager_;
+
+ // The storage id used in the ViewStorage to store/restore the view that last
+ // had focus.
+ int stored_focused_view_storage_id_;
+
+ // The reason why the focus most recently changed.
+ FocusChangeReason focus_change_reason_;
+
+ // The list of registered FocusChange listeners.
+ ObserverList<FocusChangeListener, true> focus_change_listeners_;
+
+ // See description above getter.
+ bool is_changing_focus_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusManager);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_FOCUS_MANAGER_H_
diff --git a/ui/views/focus/focus_manager_factory.cc b/ui/views/focus/focus_manager_factory.cc
new file mode 100644
index 0000000..457f60b
--- /dev/null
+++ b/ui/views/focus/focus_manager_factory.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2011 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/focus/focus_manager_factory.h"
+
+#include "base/compiler_specific.h"
+#include "views/focus/focus_manager.h"
+
+namespace {
+
+using views::FocusManager;
+
+class DefaultFocusManagerFactory : public views::FocusManagerFactory {
+ public:
+ DefaultFocusManagerFactory() : views::FocusManagerFactory() {}
+ virtual ~DefaultFocusManagerFactory() {}
+
+ protected:
+ virtual FocusManager* CreateFocusManager(views::Widget* widget) OVERRIDE {
+ return new FocusManager(widget);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DefaultFocusManagerFactory);
+};
+
+views::FocusManagerFactory* focus_manager_factory = NULL;
+
+} // namespace
+
+namespace views {
+
+FocusManagerFactory::FocusManagerFactory() {
+}
+
+FocusManagerFactory::~FocusManagerFactory() {
+}
+
+// static
+FocusManager* FocusManagerFactory::Create(Widget* widget) {
+ if (!focus_manager_factory)
+ focus_manager_factory = new DefaultFocusManagerFactory();
+ return focus_manager_factory->CreateFocusManager(widget);
+}
+
+// static
+void FocusManagerFactory::Install(FocusManagerFactory* f) {
+ if (f == focus_manager_factory)
+ return;
+ delete focus_manager_factory;
+ focus_manager_factory = f ? f : new DefaultFocusManagerFactory();
+}
+
+} // namespace views
diff --git a/ui/views/focus/focus_manager_factory.h b/ui/views/focus/focus_manager_factory.h
new file mode 100644
index 0000000..7aae11d
--- /dev/null
+++ b/ui/views/focus/focus_manager_factory.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2011 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 UI_VIEWS_FOCUS_FOCUS_MANAGER_FACTORY_H_
+#define UI_VIEWS_FOCUS_FOCUS_MANAGER_FACTORY_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "views/views_export.h"
+
+namespace views {
+
+class FocusManager;
+class Widget;
+
+// A factory to create FocusManager. This is used in unit tests
+// to inject a custom factory.
+class VIEWS_EXPORT FocusManagerFactory {
+ public:
+ // Create a FocusManager for the given |widget| using installe Factory.
+ static FocusManager* Create(Widget* widget);
+
+ // Installs FocusManagerFactory. If |factory| is NULL, it resets
+ // to the default factory which creates plain FocusManager.
+ static void Install(FocusManagerFactory* factory);
+
+ protected:
+ FocusManagerFactory();
+ virtual ~FocusManagerFactory();
+
+ // Create a FocusManager for the given |widget|.
+ virtual FocusManager* CreateFocusManager(Widget* widget) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusManagerFactory);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_FOCUS_MANAGER_FACTORY_H_
diff --git a/ui/views/focus/focus_manager_unittest.cc b/ui/views/focus/focus_manager_unittest.cc
new file mode 100644
index 0000000..d4372f2
--- /dev/null
+++ b/ui/views/focus/focus_manager_unittest.cc
@@ -0,0 +1,1763 @@
+// Copyright (c) 2011 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 "base/logging.h"
+#include "base/string16.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/base/keycodes/keyboard_codes.h"
+#include "ui/base/models/accelerator.h"
+#include "ui/base/models/combobox_model.h"
+#include "ui/gfx/rect.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/window/non_client_view.h"
+#include "views/background.h"
+#include "views/border.h"
+#include "views/controls/button/checkbox.h"
+#include "views/controls/button/radio_button.h"
+#include "views/controls/combobox/combobox.h"
+#include "views/controls/combobox/native_combobox_wrapper.h"
+#include "views/controls/label.h"
+#include "views/controls/link.h"
+#include "views/controls/native/native_view_host.h"
+#include "views/controls/scroll_view.h"
+#include "views/controls/tabbed_pane/native_tabbed_pane_wrapper.h"
+#include "views/controls/tabbed_pane/tabbed_pane.h"
+#include "views/controls/textfield/textfield.h"
+#include "views/focus/accelerator_handler.h"
+#include "views/focus/focus_manager_factory.h"
+#include "views/widget/root_view.h"
+#include "views/widget/widget.h"
+#include "views/widget/widget_delegate.h"
+
+#if defined(OS_LINUX)
+#include "ui/base/keycodes/keyboard_code_conversion_gtk.h"
+#endif
+
+namespace {
+const int kWindowWidth = 600;
+const int kWindowHeight = 500;
+
+int count = 1;
+
+const int kTopCheckBoxID = count++; // 1
+const int kLeftContainerID = count++;
+const int kAppleLabelID = count++;
+const int kAppleTextfieldID = count++;
+const int kOrangeLabelID = count++; // 5
+const int kOrangeTextfieldID = count++;
+const int kBananaLabelID = count++;
+const int kBananaTextfieldID = count++;
+const int kKiwiLabelID = count++;
+const int kKiwiTextfieldID = count++; // 10
+const int kFruitButtonID = count++;
+const int kFruitCheckBoxID = count++;
+const int kComboboxID = count++;
+
+const int kRightContainerID = count++;
+const int kAsparagusButtonID = count++; // 15
+const int kBroccoliButtonID = count++;
+const int kCauliflowerButtonID = count++;
+
+const int kInnerContainerID = count++;
+const int kScrollViewID = count++;
+const int kRosettaLinkID = count++; // 20
+const int kStupeurEtTremblementLinkID = count++;
+const int kDinerGameLinkID = count++;
+const int kRidiculeLinkID = count++;
+const int kClosetLinkID = count++;
+const int kVisitingLinkID = count++; // 25
+const int kAmelieLinkID = count++;
+const int kJoyeuxNoelLinkID = count++;
+const int kCampingLinkID = count++;
+const int kBriceDeNiceLinkID = count++;
+const int kTaxiLinkID = count++; // 30
+const int kAsterixLinkID = count++;
+
+const int kOKButtonID = count++;
+const int kCancelButtonID = count++;
+const int kHelpButtonID = count++;
+
+const int kStyleContainerID = count++; // 35
+const int kBoldCheckBoxID = count++;
+const int kItalicCheckBoxID = count++;
+const int kUnderlinedCheckBoxID = count++;
+const int kStyleHelpLinkID = count++;
+const int kStyleTextEditID = count++; // 40
+
+const int kSearchContainerID = count++;
+const int kSearchTextfieldID = count++;
+const int kSearchButtonID = count++;
+const int kHelpLinkID = count++;
+
+const int kThumbnailContainerID = count++; // 45
+const int kThumbnailStarID = count++;
+const int kThumbnailSuperStarID = count++;
+
+} // namespace
+
+namespace views {
+
+class FocusManagerTest : public ViewsTestBase, public WidgetDelegate {
+ public:
+ FocusManagerTest()
+ : window_(NULL),
+ content_view_(NULL),
+ focus_change_listener_(NULL) {
+ }
+
+ ~FocusManagerTest() {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ ViewsTestBase::SetUp();
+ window_ = Widget::CreateWindowWithBounds(this, bounds());
+ InitContentView();
+ window_->Show();
+ }
+
+ virtual void TearDown() OVERRIDE {
+ if (focus_change_listener_)
+ GetFocusManager()->RemoveFocusChangeListener(focus_change_listener_);
+ window_->Close();
+
+ // Flush the message loop to make application verifiers happy.
+ RunPendingMessages();
+ ViewsTestBase::TearDown();
+ }
+
+ FocusManager* GetFocusManager() {
+ return window_->GetFocusManager();
+ }
+
+ void FocusNativeView(gfx::NativeView native_view) {
+#if defined(USE_AURA)
+ NOTIMPLEMENTED();
+#elif defined(OS_WIN)
+ ::SendMessage(native_view, WM_SETFOCUS, NULL, NULL);
+#else
+ gint return_val;
+ GdkEventFocus event;
+ event.type = GDK_FOCUS_CHANGE;
+ event.window =
+ gtk_widget_get_root_window(GTK_WIDGET(window_->GetNativeWindow()));
+ event.send_event = TRUE;
+ event.in = TRUE;
+ gtk_signal_emit_by_name(GTK_OBJECT(native_view), "focus-in-event",
+ &event, &return_val);
+#endif
+ }
+
+ // WidgetDelegate Implementation.
+ virtual View* GetContentsView() OVERRIDE {
+ if (!content_view_)
+ content_view_ = new View();
+ return content_view_;
+ }
+ virtual Widget* GetWidget() OVERRIDE {
+ return content_view_->GetWidget();
+ }
+ virtual const Widget* GetWidget() const OVERRIDE {
+ return content_view_->GetWidget();
+ }
+
+ virtual void InitContentView() {
+ }
+
+ protected:
+ virtual gfx::Rect bounds() {
+ return gfx::Rect(0, 0, 500, 500);
+ }
+
+ // Mocks activating/deactivating the window.
+ void SimulateActivateWindow() {
+#if defined(USE_AURA)
+ NOTIMPLEMENTED();
+#elif defined(OS_WIN)
+ ::SendMessage(window_->GetNativeWindow(), WM_ACTIVATE, WA_ACTIVE, NULL);
+#else
+ gboolean result;
+ g_signal_emit_by_name(G_OBJECT(window_->GetNativeWindow()),
+ "focus_in_event", 0, &result);
+#endif
+ }
+ void SimulateDeactivateWindow() {
+#if defined(USE_AURA)
+ NOTIMPLEMENTED();
+#elif defined(OS_WIN)
+ ::SendMessage(window_->GetNativeWindow(), WM_ACTIVATE, WA_INACTIVE, NULL);
+#else
+ gboolean result;
+ g_signal_emit_by_name(G_OBJECT(window_->GetNativeWindow()),
+ "focus_out_event", 0, & result);
+#endif
+ }
+
+ Widget* window_;
+ View* content_view_;
+
+ void AddFocusChangeListener(FocusChangeListener* listener) {
+ ASSERT_FALSE(focus_change_listener_);
+ focus_change_listener_ = listener;
+ GetFocusManager()->AddFocusChangeListener(listener);
+ }
+
+#if defined(USE_AURA)
+ void PostKeyDown(ui::KeyboardCode key_code) {
+ NOTIMPLEMENTED();
+ }
+
+ void PostKeyUp(ui::KeyboardCode key_code) {
+ NOTIMPLEMENTED();
+ }
+#elif defined(OS_WIN)
+ void PostKeyDown(ui::KeyboardCode key_code) {
+ ::PostMessage(window_->GetNativeWindow(), WM_KEYDOWN, key_code, 0);
+ }
+
+ void PostKeyUp(ui::KeyboardCode key_code) {
+ ::PostMessage(window_->GetNativeWindow(), WM_KEYUP, key_code, 0);
+ }
+#elif defined(OS_LINUX)
+ void PostKeyDown(ui::KeyboardCode key_code) {
+ PostKeyEvent(key_code, true);
+ }
+
+ void PostKeyUp(ui::KeyboardCode key_code) {
+ PostKeyEvent(key_code, false);
+ }
+
+ void PostKeyEvent(ui::KeyboardCode key_code, bool pressed) {
+ int keyval = GdkKeyCodeForWindowsKeyCode(key_code, false);
+ GdkKeymapKey* keys;
+ gint n_keys;
+ gdk_keymap_get_entries_for_keyval(
+ gdk_keymap_get_default(),
+ keyval,
+ &keys,
+ &n_keys);
+ GdkEvent* event = gdk_event_new(pressed ? GDK_KEY_PRESS : GDK_KEY_RELEASE);
+ GdkEventKey* key_event = reinterpret_cast<GdkEventKey*>(event);
+ int modifier = 0;
+ if (pressed)
+ key_event->state = modifier | GDK_KEY_PRESS_MASK;
+ else
+ key_event->state = modifier | GDK_KEY_RELEASE_MASK;
+
+ key_event->window = GTK_WIDGET(window_->GetNativeWindow())->window;
+ DCHECK(key_event->window != NULL);
+ g_object_ref(key_event->window);
+ key_event->send_event = true;
+ key_event->time = GDK_CURRENT_TIME;
+ key_event->keyval = keyval;
+ key_event->hardware_keycode = keys[0].keycode;
+ key_event->group = keys[0].group;
+
+ g_free(keys);
+
+ gdk_event_put(event);
+ gdk_event_free(event);
+ }
+#endif
+
+ private:
+ FocusChangeListener* focus_change_listener_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusManagerTest);
+};
+
+// BorderView is a view containing a native window with its own view hierarchy.
+// It is interesting to test focus traversal from a view hierarchy to an inner
+// view hierarchy.
+class BorderView : public NativeViewHost {
+ public:
+ explicit BorderView(View* child) : child_(child), widget_(NULL) {
+ DCHECK(child);
+ set_focusable(false);
+ }
+
+ virtual ~BorderView() {}
+
+ virtual internal::RootView* GetContentsRootView() {
+ return static_cast<internal::RootView*>(widget_->GetRootView());
+ }
+
+ virtual FocusTraversable* GetFocusTraversable() {
+ return static_cast<internal::RootView*>(widget_->GetRootView());
+ }
+
+ virtual void ViewHierarchyChanged(bool is_add, View *parent, View *child) {
+ NativeViewHost::ViewHierarchyChanged(is_add, parent, child);
+
+ if (child == this && is_add) {
+ if (!widget_) {
+ widget_ = new Widget;
+ Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
+#if defined(OS_WIN)
+ params.parent = parent->GetWidget()->GetNativeView();
+#elif defined(TOOLKIT_USES_GTK)
+ params.parent = native_view();
+#else
+ NOTREACHED();
+#endif
+ widget_->Init(params);
+ widget_->SetFocusTraversableParentView(this);
+ widget_->SetContentsView(child_);
+ }
+
+ // We have been added to a view hierarchy, attach the native view.
+ Attach(widget_->GetNativeView());
+ // Also update the FocusTraversable parent so the focus traversal works.
+ static_cast<internal::RootView*>(widget_->GetRootView())->
+ SetFocusTraversableParent(GetWidget()->GetFocusTraversable());
+ }
+ }
+
+ private:
+ View* child_;
+ Widget* widget_;
+
+ DISALLOW_COPY_AND_ASSIGN(BorderView);
+};
+
+class DummyComboboxModel : public ui::ComboboxModel {
+ public:
+ virtual int GetItemCount() { return 10; }
+
+ virtual string16 GetItemAt(int index) {
+ return ASCIIToUTF16("Item ") + base::IntToString16(index);
+ }
+};
+
+// A View that can act as a pane.
+class PaneView : public View, public FocusTraversable {
+ public:
+ PaneView() : focus_search_(NULL) {}
+
+ // If this method is called, this view will use GetPaneFocusTraversable to
+ // have this provided FocusSearch used instead of the default one, allowing
+ // you to trap focus within the pane.
+ void EnablePaneFocus(FocusSearch* focus_search) {
+ focus_search_ = focus_search;
+ }
+
+ // Overridden from views::View:
+ virtual FocusTraversable* GetPaneFocusTraversable() {
+ if (focus_search_)
+ return this;
+ else
+ return NULL;
+ }
+
+ // Overridden from views::FocusTraversable:
+ virtual views::FocusSearch* GetFocusSearch() {
+ return focus_search_;
+ }
+ virtual FocusTraversable* GetFocusTraversableParent() {
+ return NULL;
+ }
+ virtual View* GetFocusTraversableParentView() {
+ return NULL;
+ }
+
+ private:
+ FocusSearch* focus_search_;
+};
+
+class FocusTraversalTest : public FocusManagerTest {
+ public:
+ ~FocusTraversalTest();
+
+ virtual void InitContentView();
+
+ protected:
+ FocusTraversalTest();
+
+ virtual gfx::Rect bounds() {
+ return gfx::Rect(0, 0, 600, 460);
+ }
+
+ View* FindViewByID(int id) {
+ View* view = GetContentsView()->GetViewByID(id);
+ if (view)
+ return view;
+ if (style_tab_)
+ view = style_tab_->GetSelectedTab()->GetViewByID(id);
+ if (view)
+ return view;
+ view = search_border_view_->GetContentsRootView()->GetViewByID(id);
+ if (view)
+ return view;
+ return NULL;
+ }
+
+ protected:
+ TabbedPane* style_tab_;
+ BorderView* search_border_view_;
+ DummyComboboxModel combobox_model_;
+ PaneView* left_container_;
+ PaneView* right_container_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// FocusTraversalTest
+////////////////////////////////////////////////////////////////////////////////
+
+FocusTraversalTest::FocusTraversalTest()
+ : style_tab_(NULL),
+ search_border_view_(NULL) {
+}
+
+FocusTraversalTest::~FocusTraversalTest() {
+}
+
+void FocusTraversalTest::InitContentView() {
+ // Create a complicated view hierarchy with lots of control types for
+ // use by all of the focus traversal tests.
+ //
+ // Class name, ID, and asterisk next to focusable views:
+ //
+ // View
+ // Checkbox * kTopCheckBoxID
+ // PaneView kLeftContainerID
+ // Label kAppleLabelID
+ // Textfield * kAppleTextfieldID
+ // Label kOrangeLabelID
+ // Textfield * kOrangeTextfieldID
+ // Label kBananaLabelID
+ // Textfield * kBananaTextfieldID
+ // Label kKiwiLabelID
+ // Textfield * kKiwiTextfieldID
+ // NativeButton * kFruitButtonID
+ // Checkbox * kFruitCheckBoxID
+ // Combobox * kComboboxID
+ // PaneView kRightContainerID
+ // RadioButton * kAsparagusButtonID
+ // RadioButton * kBroccoliButtonID
+ // RadioButton * kCauliflowerButtonID
+ // View kInnerContainerID
+ // ScrollView kScrollViewID
+ // View
+ // Link * kRosettaLinkID
+ // Link * kStupeurEtTremblementLinkID
+ // Link * kDinerGameLinkID
+ // Link * kRidiculeLinkID
+ // Link * kClosetLinkID
+ // Link * kVisitingLinkID
+ // Link * kAmelieLinkID
+ // Link * kJoyeuxNoelLinkID
+ // Link * kCampingLinkID
+ // Link * kBriceDeNiceLinkID
+ // Link * kTaxiLinkID
+ // Link * kAsterixLinkID
+ // NativeButton * kOKButtonID
+ // NativeButton * kCancelButtonID
+ // NativeButton * kHelpButtonID
+ // TabbedPane * kStyleContainerID
+ // View
+ // Checkbox * kBoldCheckBoxID
+ // Checkbox * kItalicCheckBoxID
+ // Checkbox * kUnderlinedCheckBoxID
+ // Link * kStyleHelpLinkID
+ // Textfield * kStyleTextEditID
+ // Other
+ // BorderView kSearchContainerID
+ // View
+ // Textfield * kSearchTextfieldID
+ // NativeButton * kSearchButtonID
+ // Link * kHelpLinkID
+ // View * kThumbnailContainerID
+ // NativeButton * kThumbnailStarID
+ // NativeButton * kThumbnailSuperStarID
+
+ content_view_->set_background(
+ Background::CreateSolidBackground(SK_ColorWHITE));
+
+ Checkbox* cb = new Checkbox(ASCIIToUTF16("This is a checkbox"));
+ content_view_->AddChildView(cb);
+ // In this fast paced world, who really has time for non hard-coded layout?
+ cb->SetBounds(10, 10, 200, 20);
+ cb->set_id(kTopCheckBoxID);
+
+ left_container_ = new PaneView();
+ left_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
+ left_container_->set_background(
+ Background::CreateSolidBackground(240, 240, 240));
+ left_container_->set_id(kLeftContainerID);
+ content_view_->AddChildView(left_container_);
+ left_container_->SetBounds(10, 35, 250, 200);
+
+ int label_x = 5;
+ int label_width = 50;
+ int label_height = 15;
+ int text_field_width = 150;
+ int y = 10;
+ int gap_between_labels = 10;
+
+ Label* label = new Label(ASCIIToUTF16("Apple:"));
+ label->set_id(kAppleLabelID);
+ left_container_->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ Textfield* text_field = new Textfield();
+ text_field->set_id(kAppleTextfieldID);
+ left_container_->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new Label(ASCIIToUTF16("Orange:"));
+ label->set_id(kOrangeLabelID);
+ left_container_->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new Textfield();
+ text_field->set_id(kOrangeTextfieldID);
+ left_container_->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new Label(ASCIIToUTF16("Banana:"));
+ label->set_id(kBananaLabelID);
+ left_container_->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new Textfield();
+ text_field->set_id(kBananaTextfieldID);
+ left_container_->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ label = new Label(ASCIIToUTF16("Kiwi:"));
+ label->set_id(kKiwiLabelID);
+ left_container_->AddChildView(label);
+ label->SetBounds(label_x, y, label_width, label_height);
+
+ text_field = new Textfield();
+ text_field->set_id(kKiwiTextfieldID);
+ left_container_->AddChildView(text_field);
+ text_field->SetBounds(label_x + label_width + 5, y,
+ text_field_width, label_height);
+
+ y += label_height + gap_between_labels;
+
+ NativeTextButton* button = new NativeTextButton(NULL,
+ ASCIIToUTF16("Click me"));
+ button->SetBounds(label_x, y + 10, 80, 30);
+ button->set_id(kFruitButtonID);
+ left_container_->AddChildView(button);
+ y += 40;
+
+ cb = new Checkbox(ASCIIToUTF16("This is another check box"));
+ cb->SetBounds(label_x + label_width + 5, y, 180, 20);
+ cb->set_id(kFruitCheckBoxID);
+ left_container_->AddChildView(cb);
+ y += 20;
+
+ Combobox* combobox = new Combobox(&combobox_model_);
+ combobox->SetBounds(label_x + label_width + 5, y, 150, 30);
+ combobox->set_id(kComboboxID);
+ left_container_->AddChildView(combobox);
+
+ right_container_ = new PaneView();
+ right_container_->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
+ right_container_->set_background(
+ Background::CreateSolidBackground(240, 240, 240));
+ right_container_->set_id(kRightContainerID);
+ content_view_->AddChildView(right_container_);
+ right_container_->SetBounds(270, 35, 300, 200);
+
+ y = 10;
+ int radio_button_height = 18;
+ int gap_between_radio_buttons = 10;
+ RadioButton* radio_button = new RadioButton(ASCIIToUTF16("Asparagus"), 1);
+ radio_button->set_id(kAsparagusButtonID);
+ right_container_->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ y += radio_button_height + gap_between_radio_buttons;
+ radio_button = new RadioButton(ASCIIToUTF16("Broccoli"), 1);
+ radio_button->set_id(kBroccoliButtonID);
+ right_container_->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ RadioButton* radio_button_to_check = radio_button;
+ y += radio_button_height + gap_between_radio_buttons;
+ radio_button = new RadioButton(ASCIIToUTF16("Cauliflower"), 1);
+ radio_button->set_id(kCauliflowerButtonID);
+ right_container_->AddChildView(radio_button);
+ radio_button->SetBounds(5, y, 70, radio_button_height);
+ radio_button->SetGroup(1);
+ y += radio_button_height + gap_between_radio_buttons;
+
+ View* inner_container = new View();
+ inner_container->set_border(Border::CreateSolidBorder(1, SK_ColorBLACK));
+ inner_container->set_background(
+ Background::CreateSolidBackground(230, 230, 230));
+ inner_container->set_id(kInnerContainerID);
+ right_container_->AddChildView(inner_container);
+ inner_container->SetBounds(100, 10, 150, 180);
+
+ ScrollView* scroll_view = new ScrollView();
+ scroll_view->set_id(kScrollViewID);
+ inner_container->AddChildView(scroll_view);
+ scroll_view->SetBounds(1, 1, 148, 178);
+
+ View* scroll_content = new View();
+ scroll_content->SetBounds(0, 0, 200, 200);
+ scroll_content->set_background(
+ Background::CreateSolidBackground(200, 200, 200));
+ scroll_view->SetContents(scroll_content);
+
+ static const char* const kTitles[] = {
+ "Rosetta", "Stupeur et tremblement", "The diner game",
+ "Ridicule", "Le placard", "Les Visiteurs", "Amelie",
+ "Joyeux Noel", "Camping", "Brice de Nice",
+ "Taxi", "Asterix"
+ };
+
+ static const int kIDs[] = {
+ kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
+ kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID,
+ kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID
+ };
+
+ DCHECK(arraysize(kTitles) == arraysize(kIDs));
+
+ y = 5;
+ for (size_t i = 0; i < arraysize(kTitles); ++i) {
+ Link* link = new Link(ASCIIToUTF16(kTitles[i]));
+ link->SetHorizontalAlignment(Label::ALIGN_LEFT);
+ link->set_id(kIDs[i]);
+ scroll_content->AddChildView(link);
+ link->SetBounds(5, y, 300, 15);
+ y += 15;
+ }
+
+ y = 250;
+ int width = 60;
+ button = new NativeTextButton(NULL, ASCIIToUTF16("OK"));
+ button->set_id(kOKButtonID);
+ button->SetIsDefault(true);
+
+ content_view_->AddChildView(button);
+ button->SetBounds(150, y, width, 30);
+
+ button = new NativeTextButton(NULL, ASCIIToUTF16("Cancel"));
+ button->set_id(kCancelButtonID);
+ content_view_->AddChildView(button);
+ button->SetBounds(220, y, width, 30);
+
+ button = new NativeTextButton(NULL, ASCIIToUTF16("Help"));
+ button->set_id(kHelpButtonID);
+ content_view_->AddChildView(button);
+ button->SetBounds(290, y, width, 30);
+
+ y += 40;
+
+ // Left bottom box with style checkboxes.
+ View* contents = new View();
+ contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
+ cb = new Checkbox(ASCIIToUTF16("Bold"));
+ contents->AddChildView(cb);
+ cb->SetBounds(10, 10, 50, 20);
+ cb->set_id(kBoldCheckBoxID);
+
+ cb = new Checkbox(ASCIIToUTF16("Italic"));
+ contents->AddChildView(cb);
+ cb->SetBounds(70, 10, 50, 20);
+ cb->set_id(kItalicCheckBoxID);
+
+ cb = new Checkbox(ASCIIToUTF16("Underlined"));
+ contents->AddChildView(cb);
+ cb->SetBounds(130, 10, 70, 20);
+ cb->set_id(kUnderlinedCheckBoxID);
+
+ Link* link = new Link(ASCIIToUTF16("Help"));
+ contents->AddChildView(link);
+ link->SetBounds(10, 35, 70, 10);
+ link->set_id(kStyleHelpLinkID);
+
+ text_field = new Textfield();
+ contents->AddChildView(text_field);
+ text_field->SetBounds(10, 50, 100, 20);
+ text_field->set_id(kStyleTextEditID);
+
+ style_tab_ = new TabbedPane();
+ style_tab_->set_id(kStyleContainerID);
+ content_view_->AddChildView(style_tab_);
+ style_tab_->SetBounds(10, y, 210, 100);
+ style_tab_->AddTab(ASCIIToUTF16("Style"), contents);
+ style_tab_->AddTab(ASCIIToUTF16("Other"), new View());
+
+ // Right bottom box with search.
+ contents = new View();
+ contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
+ text_field = new Textfield();
+ contents->AddChildView(text_field);
+ text_field->SetBounds(10, 10, 100, 20);
+ text_field->set_id(kSearchTextfieldID);
+
+ button = new NativeTextButton(NULL, ASCIIToUTF16("Search"));
+ contents->AddChildView(button);
+ button->SetBounds(112, 5, 60, 30);
+ button->set_id(kSearchButtonID);
+
+ link = new Link(ASCIIToUTF16("Help"));
+ link->SetHorizontalAlignment(Label::ALIGN_LEFT);
+ link->set_id(kHelpLinkID);
+ contents->AddChildView(link);
+ link->SetBounds(175, 10, 30, 20);
+
+ search_border_view_ = new BorderView(contents);
+ search_border_view_->set_id(kSearchContainerID);
+
+ content_view_->AddChildView(search_border_view_);
+ search_border_view_->SetBounds(300, y, 240, 50);
+
+ y += 60;
+
+ contents = new View();
+ contents->set_focusable(true);
+ contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE));
+ contents->set_id(kThumbnailContainerID);
+ button = new NativeTextButton(NULL, ASCIIToUTF16("Star"));
+ contents->AddChildView(button);
+ button->SetBounds(5, 5, 50, 30);
+ button->set_id(kThumbnailStarID);
+ button = new NativeTextButton(NULL, ASCIIToUTF16("SuperStar"));
+ contents->AddChildView(button);
+ button->SetBounds(60, 5, 100, 30);
+ button->set_id(kThumbnailSuperStarID);
+
+ content_view_->AddChildView(contents);
+ contents->SetBounds(250, y, 200, 50);
+ // We can only call RadioButton::SetChecked() on the radio-button is part of
+ // the view hierarchy.
+ radio_button_to_check->SetChecked(true);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// The tests
+////////////////////////////////////////////////////////////////////////////////
+
+enum FocusTestEventType {
+ ON_FOCUS = 0,
+ ON_BLUR
+};
+
+struct FocusTestEvent {
+ FocusTestEvent(FocusTestEventType type, int view_id)
+ : type(type),
+ view_id(view_id) {
+ }
+
+ FocusTestEventType type;
+ int view_id;
+};
+
+class SimpleTestView : public View {
+ public:
+ SimpleTestView(std::vector<FocusTestEvent>* event_list, int view_id)
+ : event_list_(event_list) {
+ set_focusable(true);
+ set_id(view_id);
+ }
+
+ virtual void OnFocus() {
+ event_list_->push_back(FocusTestEvent(ON_FOCUS, id()));
+ }
+
+ virtual void OnBlur() {
+ event_list_->push_back(FocusTestEvent(ON_BLUR, id()));
+ }
+
+ private:
+ std::vector<FocusTestEvent>* event_list_;
+};
+
+// Tests that the appropriate Focus related methods are called when a View
+// gets/loses focus.
+TEST_F(FocusManagerTest, ViewFocusCallbacks) {
+ std::vector<FocusTestEvent> event_list;
+ const int kView1ID = 1;
+ const int kView2ID = 2;
+
+ SimpleTestView* view1 = new SimpleTestView(&event_list, kView1ID);
+ SimpleTestView* view2 = new SimpleTestView(&event_list, kView2ID);
+ content_view_->AddChildView(view1);
+ content_view_->AddChildView(view2);
+
+ view1->RequestFocus();
+ ASSERT_EQ(1, static_cast<int>(event_list.size()));
+ EXPECT_EQ(ON_FOCUS, event_list[0].type);
+ EXPECT_EQ(kView1ID, event_list[0].view_id);
+
+ event_list.clear();
+ view2->RequestFocus();
+ ASSERT_EQ(2, static_cast<int>(event_list.size()));
+ EXPECT_EQ(ON_BLUR, event_list[0].type);
+ EXPECT_EQ(kView1ID, event_list[0].view_id);
+ EXPECT_EQ(ON_FOCUS, event_list[1].type);
+ EXPECT_EQ(kView2ID, event_list[1].view_id);
+
+ event_list.clear();
+ GetFocusManager()->ClearFocus();
+ ASSERT_EQ(1, static_cast<int>(event_list.size()));
+ EXPECT_EQ(ON_BLUR, event_list[0].type);
+ EXPECT_EQ(kView2ID, event_list[0].view_id);
+}
+
+typedef std::pair<View*, View*> ViewPair;
+class TestFocusChangeListener : public FocusChangeListener {
+ public:
+ virtual void OnWillChangeFocus(View* focused_before, View* focused_now) {
+ focus_changes_.push_back(ViewPair(focused_before, focused_now));
+ }
+ virtual void OnDidChangeFocus(View* focused_before, View* focused_now) {
+ }
+
+ const std::vector<ViewPair>& focus_changes() const { return focus_changes_; }
+ void ClearFocusChanges() { focus_changes_.clear(); }
+
+ private:
+ // A vector of which views lost/gained focus.
+ std::vector<ViewPair> focus_changes_;
+};
+
+TEST_F(FocusManagerTest, FocusChangeListener) {
+ View* view1 = new View();
+ view1->set_focusable(true);
+ View* view2 = new View();
+ view2->set_focusable(true);
+ content_view_->AddChildView(view1);
+ content_view_->AddChildView(view2);
+
+ TestFocusChangeListener listener;
+ AddFocusChangeListener(&listener);
+
+ // Visual Studio 2010 has problems converting NULL to the null pointer for
+ // std::pair. See http://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair
+ // It will work if we pass nullptr.
+#if defined(_MSC_VER) && _MSC_VER >= 1600
+ views::View* null_view = nullptr;
+#else
+ views::View* null_view = NULL;
+#endif
+
+ view1->RequestFocus();
+ ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(null_view, view1));
+ listener.ClearFocusChanges();
+
+ view2->RequestFocus();
+ ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(view1, view2));
+ listener.ClearFocusChanges();
+
+ GetFocusManager()->ClearFocus();
+ ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(view2, null_view));
+}
+
+class TestNativeButton : public NativeTextButton {
+ public:
+ explicit TestNativeButton(const string16& text)
+ : NativeTextButton(NULL, text) {
+ };
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return GetWidget()->GetNativeView();
+ }
+};
+
+class TestCheckbox : public Checkbox {
+ public:
+ explicit TestCheckbox(const string16& text) : Checkbox(text) {
+ };
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return GetWidget()->GetNativeView();
+ }
+};
+
+class TestRadioButton : public RadioButton {
+ public:
+ explicit TestRadioButton(const string16& text)
+ : RadioButton(text, 1) {
+ }
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return GetWidget()->GetNativeView();
+ }
+};
+
+class TestTextfield : public Textfield {
+ public:
+ TestTextfield() { }
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return native_wrapper_->GetTestingHandle();
+ }
+};
+
+class TestCombobox : public Combobox, public ui::ComboboxModel {
+ public:
+ TestCombobox() : Combobox(this) { }
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return native_wrapper_->GetTestingHandle();
+ }
+ virtual int GetItemCount() {
+ return 10;
+ }
+ virtual string16 GetItemAt(int index) {
+ return ASCIIToUTF16("Hello combo");
+ }
+};
+
+class TestTabbedPane : public TabbedPane {
+ public:
+ TestTabbedPane() { }
+ virtual gfx::NativeView TestGetNativeControlView() {
+ return native_tabbed_pane_->GetTestingHandle();
+ }
+};
+
+#if !defined(TOUCH_UI)
+// TODO(oshima): replace TOUCH_UI with PURE_VIEWS
+
+// Tests that NativeControls do set the focus View appropriately on the
+// FocusManager.
+TEST_F(FocusManagerTest, FAILS_FocusNativeControls) {
+ TestTextfield* textfield = new TestTextfield();
+ TestTabbedPane* tabbed_pane = new TestTabbedPane();
+ TestTextfield* textfield2 = new TestTextfield();
+
+ content_view_->AddChildView(textfield);
+ content_view_->AddChildView(tabbed_pane);
+
+ tabbed_pane->AddTab(ASCIIToUTF16("Awesome textfield"), textfield2);
+
+ // Simulate the native view getting the native focus (such as by user click).
+ FocusNativeView(textfield->TestGetNativeControlView());
+ EXPECT_EQ(textfield, GetFocusManager()->GetFocusedView());
+
+ FocusNativeView(tabbed_pane->TestGetNativeControlView());
+ EXPECT_EQ(tabbed_pane, GetFocusManager()->GetFocusedView());
+
+ FocusNativeView(textfield2->TestGetNativeControlView());
+ EXPECT_EQ(textfield2, GetFocusManager()->GetFocusedView());
+}
+#endif
+
+// On linux, we don't store/restore focused view because gtk handles
+// this (and pure views will be the same).
+#if defined(OS_WIN)
+
+// Test that when activating/deactivating the top window, the focus is stored/
+// restored properly.
+TEST_F(FocusManagerTest, FocusStoreRestore) {
+ // Simulate an activate, otherwise the deactivate isn't going to do anything.
+ SimulateActivateWindow();
+
+ NativeTextButton* button = new NativeTextButton(NULL,
+ ASCIIToUTF16("Press me"));
+ View* view = new View();
+ view->set_focusable(true);
+
+ content_view_->AddChildView(button);
+ button->SetBounds(10, 10, 200, 30);
+ content_view_->AddChildView(view);
+ RunPendingMessages();
+
+ TestFocusChangeListener listener;
+ AddFocusChangeListener(&listener);
+
+ view->RequestFocus();
+ RunPendingMessages();
+ // MessageLoopForUI::current()->RunWithDispatcher(new AcceleratorHandler());
+
+ // Visual Studio 2010 has problems converting NULL to the null pointer for
+ // std::pair. See http://connect.microsoft.com/VisualStudio/feedback/details/520043/error-converting-from-null-to-a-pointer-type-in-std-pair
+ // It will work if we pass nullptr.
+#if defined(_MSC_VER) && _MSC_VER >= 1600
+ views::View* null_view = nullptr;
+#else
+ views::View* null_view = NULL;
+#endif
+
+ // Deacivate the window, it should store its focus.
+ SimulateDeactivateWindow();
+ EXPECT_EQ(NULL, GetFocusManager()->GetFocusedView());
+ ASSERT_EQ(2, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(null_view, view));
+ EXPECT_TRUE(listener.focus_changes()[1] == ViewPair(view, null_view));
+ listener.ClearFocusChanges();
+
+ // Reactivate, focus should come-back to the previously focused view.
+ SimulateActivateWindow();
+ EXPECT_EQ(view, GetFocusManager()->GetFocusedView());
+ ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(null_view, view));
+ listener.ClearFocusChanges();
+
+ // Same test with a NativeControl.
+ button->RequestFocus();
+ SimulateDeactivateWindow();
+ EXPECT_EQ(NULL, GetFocusManager()->GetFocusedView());
+ ASSERT_EQ(2, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(view, button));
+ EXPECT_TRUE(listener.focus_changes()[1] == ViewPair(button, null_view));
+ listener.ClearFocusChanges();
+
+ SimulateActivateWindow();
+ EXPECT_EQ(button, GetFocusManager()->GetFocusedView());
+ ASSERT_EQ(1, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(null_view, button));
+ listener.ClearFocusChanges();
+
+ /*
+ // Now test that while the window is inactive we can change the focused view
+ // (we do that in several places).
+ SimulateDeactivateWindow();
+ // TODO: would have to mock the window being inactive (with a TestWidgetWin
+ // that would return false on IsActive()).
+ GetFocusManager()->SetFocusedView(view);
+ ::SendMessage(window_->GetNativeWindow(), WM_ACTIVATE, WA_ACTIVE, NULL);
+
+ EXPECT_EQ(view, GetFocusManager()->GetFocusedView());
+ ASSERT_EQ(2, static_cast<int>(listener.focus_changes().size()));
+ EXPECT_TRUE(listener.focus_changes()[0] == ViewPair(button, null_view));
+ EXPECT_TRUE(listener.focus_changes()[1] == ViewPair(null_view, view));
+ */
+}
+#endif
+
+#if !defined(TOUCH_UI)
+// TODO(oshima): There is no tabbed pane in pure views. Replace it
+// with different implementation.
+
+TEST_F(FocusManagerTest, ContainsView) {
+ View* view = new View();
+ scoped_ptr<View> detached_view(new View());
+ TabbedPane* tabbed_pane = new TabbedPane();
+ TabbedPane* nested_tabbed_pane = new TabbedPane();
+ NativeTextButton* tab_button = new NativeTextButton(
+ NULL, ASCIIToUTF16("tab button"));
+
+ content_view_->AddChildView(view);
+ content_view_->AddChildView(tabbed_pane);
+ // Adding a View inside a TabbedPane to test the case of nested root view.
+
+ tabbed_pane->AddTab(ASCIIToUTF16("Awesome tab"), nested_tabbed_pane);
+ nested_tabbed_pane->AddTab(ASCIIToUTF16("Awesomer tab"), tab_button);
+
+ EXPECT_TRUE(GetFocusManager()->ContainsView(view));
+ EXPECT_TRUE(GetFocusManager()->ContainsView(tabbed_pane));
+ EXPECT_TRUE(GetFocusManager()->ContainsView(nested_tabbed_pane));
+ EXPECT_TRUE(GetFocusManager()->ContainsView(tab_button));
+ EXPECT_FALSE(GetFocusManager()->ContainsView(detached_view.get()));
+}
+#endif
+
+TEST_F(FocusTraversalTest, NormalTraversal) {
+ const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
+ kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
+ kFruitButtonID, kFruitCheckBoxID, kComboboxID, kBroccoliButtonID,
+ kRosettaLinkID, kStupeurEtTremblementLinkID,
+ kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID,
+ kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID,
+ kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID,
+ kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
+ kSearchTextfieldID, kSearchButtonID, kHelpLinkID,
+ kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
+
+ // Uncomment the following line if you want to test manually the UI of this
+ // test.
+ // MessageLoopForUI::current()->RunWithDispatcher(new AcceleratorHandler());
+
+ // Let's traverse the whole focus hierarchy (several times, to make sure it
+ // loops OK).
+ GetFocusManager()->ClearFocus();
+ for (int i = 0; i < 3; ++i) {
+ for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
+ GetFocusManager()->AdvanceFocus(false);
+ View* focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->id());
+ }
+ }
+
+ // Let's traverse in reverse order.
+ GetFocusManager()->ClearFocus();
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
+ GetFocusManager()->AdvanceFocus(true);
+ View* focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->id());
+ }
+ }
+}
+
+TEST_F(FocusTraversalTest, TraversalWithNonEnabledViews) {
+ const int kDisabledIDs[] = {
+ kBananaTextfieldID, kFruitCheckBoxID, kComboboxID, kAsparagusButtonID,
+ kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID,
+ kTaxiLinkID, kAsterixLinkID, kHelpButtonID, kBoldCheckBoxID,
+ kSearchTextfieldID, kHelpLinkID };
+
+ const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
+ kOrangeTextfieldID, kKiwiTextfieldID, kFruitButtonID, kBroccoliButtonID,
+ kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
+ kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID,
+ kOKButtonID, kCancelButtonID, kStyleContainerID, kItalicCheckBoxID,
+ kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
+ kSearchButtonID, kThumbnailContainerID, kThumbnailStarID,
+ kThumbnailSuperStarID };
+
+ // Let's disable some views.
+ for (size_t i = 0; i < arraysize(kDisabledIDs); i++) {
+ View* v = FindViewByID(kDisabledIDs[i]);
+ ASSERT_TRUE(v != NULL);
+ v->SetEnabled(false);
+ }
+
+ // Uncomment the following line if you want to test manually the UI of this
+ // test.
+ // MessageLoopForUI::current()->RunWithDispatcher(new AcceleratorHandler());
+
+ View* focused_view;
+ // Let's do one traversal (several times, to make sure it loops ok).
+ GetFocusManager()->ClearFocus();
+ for (int i = 0; i < 3; ++i) {
+ for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
+ GetFocusManager()->AdvanceFocus(false);
+ focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->id());
+ }
+ }
+
+ // Same thing in reverse.
+ GetFocusManager()->ClearFocus();
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
+ GetFocusManager()->AdvanceFocus(true);
+ focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->id());
+ }
+ }
+}
+
+TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) {
+ const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID,
+ kThumbnailContainerID };
+
+ const int kTraversalIDs[] = { kAppleTextfieldID, kOrangeTextfieldID,
+ kBananaTextfieldID, kKiwiTextfieldID, kFruitButtonID, kFruitCheckBoxID,
+ kComboboxID, kBroccoliButtonID, kRosettaLinkID,
+ kStupeurEtTremblementLinkID, kDinerGameLinkID, kRidiculeLinkID,
+ kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
+ kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID,
+ kCancelButtonID, kHelpButtonID, kStyleContainerID, kBoldCheckBoxID,
+ kItalicCheckBoxID, kUnderlinedCheckBoxID, kStyleHelpLinkID,
+ kStyleTextEditID, kSearchTextfieldID, kSearchButtonID, kHelpLinkID };
+
+
+ // Let's make some views invisible.
+ for (size_t i = 0; i < arraysize(kInvisibleIDs); i++) {
+ View* v = FindViewByID(kInvisibleIDs[i]);
+ ASSERT_TRUE(v != NULL);
+ v->SetVisible(false);
+ }
+
+ // Uncomment the following line if you want to test manually the UI of this
+ // test.
+ // MessageLoopForUI::current()->RunWithDispatcher(new AcceleratorHandler());
+
+ View* focused_view;
+ // Let's do one traversal (several times, to make sure it loops ok).
+ GetFocusManager()->ClearFocus();
+ for (int i = 0; i < 3; ++i) {
+ for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
+ GetFocusManager()->AdvanceFocus(false);
+ focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->id());
+ }
+ }
+
+ // Same thing in reverse.
+ GetFocusManager()->ClearFocus();
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
+ GetFocusManager()->AdvanceFocus(true);
+ focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kTraversalIDs[j], focused_view->id());
+ }
+ }
+}
+
+TEST_F(FocusTraversalTest, PaneTraversal) {
+ // Tests trapping the traversal within a pane - useful for full
+ // keyboard accessibility for toolbars.
+
+ // First test the left container.
+ const int kLeftTraversalIDs[] = {
+ kAppleTextfieldID,
+ kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
+ kFruitButtonID, kFruitCheckBoxID, kComboboxID };
+
+ FocusSearch focus_search_left(left_container_, true, false);
+ left_container_->EnablePaneFocus(&focus_search_left);
+ FindViewByID(kComboboxID)->RequestFocus();
+
+ // Traverse the focus hierarchy within the pane several times.
+ for (int i = 0; i < 3; ++i) {
+ for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) {
+ GetFocusManager()->AdvanceFocus(false);
+ View* focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
+ }
+ }
+
+ // Traverse in reverse order.
+ FindViewByID(kAppleTextfieldID)->RequestFocus();
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) {
+ GetFocusManager()->AdvanceFocus(true);
+ View* focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
+ }
+ }
+
+ // Now test the right container, but this time with accessibility mode.
+ // Make some links not focusable, but mark one of them as
+ // "accessibility focusable", so it should show up in the traversal.
+ const int kRightTraversalIDs[] = {
+ kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID,
+ kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
+ kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID };
+
+ FocusSearch focus_search_right(right_container_, true, true);
+ right_container_->EnablePaneFocus(&focus_search_right);
+ FindViewByID(kRosettaLinkID)->set_focusable(false);
+ FindViewByID(kStupeurEtTremblementLinkID)->set_focusable(false);
+ FindViewByID(kDinerGameLinkID)->set_accessibility_focusable(true);
+ FindViewByID(kDinerGameLinkID)->set_focusable(false);
+ FindViewByID(kAsterixLinkID)->RequestFocus();
+
+ // Traverse the focus hierarchy within the pane several times.
+ for (int i = 0; i < 3; ++i) {
+ for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) {
+ GetFocusManager()->AdvanceFocus(false);
+ View* focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
+ }
+ }
+
+ // Traverse in reverse order.
+ FindViewByID(kBroccoliButtonID)->RequestFocus();
+ for (int i = 0; i < 3; ++i) {
+ for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) {
+ GetFocusManager()->AdvanceFocus(true);
+ View* focused_view = GetFocusManager()->GetFocusedView();
+ EXPECT_TRUE(focused_view != NULL);
+ if (focused_view)
+ EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
+ }
+ }
+}
+
+// Counts accelerator calls.
+class TestAcceleratorTarget : public ui::AcceleratorTarget {
+ public:
+ explicit TestAcceleratorTarget(bool process_accelerator)
+ : accelerator_count_(0), process_accelerator_(process_accelerator) {}
+
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) {
+ ++accelerator_count_;
+ return process_accelerator_;
+ }
+
+ int accelerator_count() const { return accelerator_count_; }
+
+ private:
+ int accelerator_count_; // number of times that the accelerator is activated
+ bool process_accelerator_; // return value of AcceleratorPressed
+
+ DISALLOW_COPY_AND_ASSIGN(TestAcceleratorTarget);
+};
+
+TEST_F(FocusManagerTest, CallsNormalAcceleratorTarget) {
+ FocusManager* focus_manager = GetFocusManager();
+ ui::Accelerator return_accelerator(ui::VKEY_RETURN, false, false, false);
+ ui::Accelerator escape_accelerator(ui::VKEY_ESCAPE, false, false, false);
+
+ TestAcceleratorTarget return_target(true);
+ TestAcceleratorTarget escape_target(true);
+ EXPECT_EQ(return_target.accelerator_count(), 0);
+ EXPECT_EQ(escape_target.accelerator_count(), 0);
+ EXPECT_EQ(NULL,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+ EXPECT_EQ(NULL,
+ focus_manager->GetCurrentTargetForAccelerator(escape_accelerator));
+
+ // Register targets.
+ focus_manager->RegisterAccelerator(return_accelerator, &return_target);
+ focus_manager->RegisterAccelerator(escape_accelerator, &escape_target);
+
+ // Checks if the correct target is registered.
+ EXPECT_EQ(&return_target,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+ EXPECT_EQ(&escape_target,
+ focus_manager->GetCurrentTargetForAccelerator(escape_accelerator));
+
+ // Hitting the return key.
+ EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
+ EXPECT_EQ(return_target.accelerator_count(), 1);
+ EXPECT_EQ(escape_target.accelerator_count(), 0);
+
+ // Hitting the escape key.
+ EXPECT_TRUE(focus_manager->ProcessAccelerator(escape_accelerator));
+ EXPECT_EQ(return_target.accelerator_count(), 1);
+ EXPECT_EQ(escape_target.accelerator_count(), 1);
+
+ // Register another target for the return key.
+ TestAcceleratorTarget return_target2(true);
+ EXPECT_EQ(return_target2.accelerator_count(), 0);
+ focus_manager->RegisterAccelerator(return_accelerator, &return_target2);
+ EXPECT_EQ(&return_target2,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+
+ // Hitting the return key; return_target2 has the priority.
+ EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
+ EXPECT_EQ(return_target.accelerator_count(), 1);
+ EXPECT_EQ(return_target2.accelerator_count(), 1);
+
+ // Register a target that does not process the accelerator event.
+ TestAcceleratorTarget return_target3(false);
+ EXPECT_EQ(return_target3.accelerator_count(), 0);
+ focus_manager->RegisterAccelerator(return_accelerator, &return_target3);
+ EXPECT_EQ(&return_target3,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+
+ // Hitting the return key.
+ // Since the event handler of return_target3 returns false, return_target2
+ // should be called too.
+ EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
+ EXPECT_EQ(return_target.accelerator_count(), 1);
+ EXPECT_EQ(return_target2.accelerator_count(), 2);
+ EXPECT_EQ(return_target3.accelerator_count(), 1);
+
+ // Unregister return_target2.
+ focus_manager->UnregisterAccelerator(return_accelerator, &return_target2);
+ EXPECT_EQ(&return_target3,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+
+ // Hitting the return key. return_target3 and return_target should be called.
+ EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
+ EXPECT_EQ(return_target.accelerator_count(), 2);
+ EXPECT_EQ(return_target2.accelerator_count(), 2);
+ EXPECT_EQ(return_target3.accelerator_count(), 2);
+
+ // Unregister targets.
+ focus_manager->UnregisterAccelerator(return_accelerator, &return_target);
+ focus_manager->UnregisterAccelerator(return_accelerator, &return_target3);
+ focus_manager->UnregisterAccelerator(escape_accelerator, &escape_target);
+
+ // Now there is no target registered.
+ EXPECT_EQ(NULL,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+ EXPECT_EQ(NULL,
+ focus_manager->GetCurrentTargetForAccelerator(escape_accelerator));
+
+ // Hitting the return key and the escape key. Nothing should happen.
+ EXPECT_FALSE(focus_manager->ProcessAccelerator(return_accelerator));
+ EXPECT_EQ(return_target.accelerator_count(), 2);
+ EXPECT_EQ(return_target2.accelerator_count(), 2);
+ EXPECT_EQ(return_target3.accelerator_count(), 2);
+ EXPECT_FALSE(focus_manager->ProcessAccelerator(escape_accelerator));
+ EXPECT_EQ(escape_target.accelerator_count(), 1);
+}
+
+// Unregisters itself when its accelerator is invoked.
+class SelfUnregisteringAcceleratorTarget : public ui::AcceleratorTarget {
+ public:
+ SelfUnregisteringAcceleratorTarget(ui::Accelerator accelerator,
+ FocusManager* focus_manager)
+ : accelerator_(accelerator),
+ focus_manager_(focus_manager),
+ accelerator_count_(0) {
+ }
+
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) {
+ ++accelerator_count_;
+ focus_manager_->UnregisterAccelerator(accelerator, this);
+ return true;
+ }
+
+ int accelerator_count() const { return accelerator_count_; }
+
+ private:
+ ui::Accelerator accelerator_;
+ FocusManager* focus_manager_;
+ int accelerator_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(SelfUnregisteringAcceleratorTarget);
+};
+
+TEST_F(FocusManagerTest, CallsSelfDeletingAcceleratorTarget) {
+ FocusManager* focus_manager = GetFocusManager();
+ ui::Accelerator return_accelerator(ui::VKEY_RETURN, false, false, false);
+ SelfUnregisteringAcceleratorTarget target(return_accelerator, focus_manager);
+ EXPECT_EQ(target.accelerator_count(), 0);
+ EXPECT_EQ(NULL,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+
+ // Register the target.
+ focus_manager->RegisterAccelerator(return_accelerator, &target);
+ EXPECT_EQ(&target,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+
+ // Hitting the return key. The target will be unregistered.
+ EXPECT_TRUE(focus_manager->ProcessAccelerator(return_accelerator));
+ EXPECT_EQ(target.accelerator_count(), 1);
+ EXPECT_EQ(NULL,
+ focus_manager->GetCurrentTargetForAccelerator(return_accelerator));
+
+ // Hitting the return key again; nothing should happen.
+ EXPECT_FALSE(focus_manager->ProcessAccelerator(return_accelerator));
+ EXPECT_EQ(target.accelerator_count(), 1);
+}
+
+class MessageTrackingView : public View {
+ public:
+ MessageTrackingView() : accelerator_pressed_(false) {
+ }
+
+ virtual bool OnKeyPressed(const KeyEvent& e) {
+ keys_pressed_.push_back(e.key_code());
+ return true;
+ }
+
+ virtual bool OnKeyReleased(const KeyEvent& e) {
+ keys_released_.push_back(e.key_code());
+ return true;
+ }
+
+ virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) {
+ accelerator_pressed_ = true;
+ return true;
+ }
+
+ void Reset() {
+ accelerator_pressed_ = false;
+ keys_pressed_.clear();
+ keys_released_.clear();
+ }
+
+ const std::vector<ui::KeyboardCode>& keys_pressed() const {
+ return keys_pressed_;
+ }
+
+ const std::vector<ui::KeyboardCode>& keys_released() const {
+ return keys_released_;
+ }
+
+ bool accelerator_pressed() const {
+ return accelerator_pressed_;
+ }
+
+ private:
+ bool accelerator_pressed_;
+ std::vector<ui::KeyboardCode> keys_pressed_;
+ std::vector<ui::KeyboardCode> keys_released_;
+
+ DISALLOW_COPY_AND_ASSIGN(MessageTrackingView);
+};
+
+#if defined(OS_WIN)
+// This test is now Windows only. Linux Views port does not handle accelerator
+// keys in AcceleratorHandler anymore. The logic has been moved into
+// NativeWidgetGtk::OnKeyEvent().
+// Tests that the keyup messages are eaten for accelerators.
+TEST_F(FocusManagerTest, IgnoreKeyupForAccelerators) {
+ FocusManager* focus_manager = GetFocusManager();
+ MessageTrackingView* mtv = new MessageTrackingView();
+ mtv->AddAccelerator(ui::Accelerator(ui::VKEY_0, false, false, false));
+ mtv->AddAccelerator(ui::Accelerator(ui::VKEY_1, false, false, false));
+ content_view_->AddChildView(mtv);
+ focus_manager->SetFocusedView(mtv);
+
+ // First send a non-accelerator key sequence.
+ PostKeyDown(ui::VKEY_9);
+ PostKeyUp(ui::VKEY_9);
+ AcceleratorHandler accelerator_handler;
+ MessageLoopForUI::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
+ MessageLoopForUI::current()->RunWithDispatcher(&accelerator_handler);
+ // Make sure we get a key-up and key-down.
+ ASSERT_EQ(1U, mtv->keys_pressed().size());
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed()[0]);
+ ASSERT_EQ(1U, mtv->keys_released().size());
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_released()[0]);
+ EXPECT_FALSE(mtv->accelerator_pressed());
+ mtv->Reset();
+
+ // Same thing with repeat and more than one key at once.
+ PostKeyDown(ui::VKEY_9);
+ PostKeyDown(ui::VKEY_9);
+ PostKeyDown(ui::VKEY_8);
+ PostKeyDown(ui::VKEY_9);
+ PostKeyDown(ui::VKEY_7);
+ PostKeyUp(ui::VKEY_9);
+ PostKeyUp(ui::VKEY_7);
+ PostKeyUp(ui::VKEY_8);
+ MessageLoopForUI::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
+ MessageLoopForUI::current()->RunWithDispatcher(&accelerator_handler);
+ // Make sure we get a key-up and key-down.
+ ASSERT_EQ(5U, mtv->keys_pressed().size());
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed()[0]);
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed()[1]);
+ EXPECT_EQ(ui::VKEY_8, mtv->keys_pressed()[2]);
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_pressed()[3]);
+ EXPECT_EQ(ui::VKEY_7, mtv->keys_pressed()[4]);
+ ASSERT_EQ(3U, mtv->keys_released().size());
+ EXPECT_EQ(ui::VKEY_9, mtv->keys_released()[0]);
+ EXPECT_EQ(ui::VKEY_7, mtv->keys_released()[1]);
+ EXPECT_EQ(ui::VKEY_8, mtv->keys_released()[2]);
+ EXPECT_FALSE(mtv->accelerator_pressed());
+ mtv->Reset();
+
+ // Now send an accelerator key sequence.
+ PostKeyDown(ui::VKEY_0);
+ PostKeyUp(ui::VKEY_0);
+ MessageLoopForUI::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
+ MessageLoopForUI::current()->RunWithDispatcher(&accelerator_handler);
+ EXPECT_TRUE(mtv->keys_pressed().empty());
+ EXPECT_TRUE(mtv->keys_released().empty());
+ EXPECT_TRUE(mtv->accelerator_pressed());
+ mtv->Reset();
+
+ // Same thing with repeat and more than one key at once.
+ PostKeyDown(ui::VKEY_0);
+ PostKeyDown(ui::VKEY_1);
+ PostKeyDown(ui::VKEY_1);
+ PostKeyDown(ui::VKEY_0);
+ PostKeyDown(ui::VKEY_0);
+ PostKeyUp(ui::VKEY_1);
+ PostKeyUp(ui::VKEY_0);
+ MessageLoopForUI::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask());
+ MessageLoopForUI::current()->RunWithDispatcher(&accelerator_handler);
+ EXPECT_TRUE(mtv->keys_pressed().empty());
+ EXPECT_TRUE(mtv->keys_released().empty());
+ EXPECT_TRUE(mtv->accelerator_pressed());
+ mtv->Reset();
+}
+#endif
+
+#if defined(OS_WIN) && !defined(USE_AURA)
+// Test that the focus manager is created successfully for the first view
+// window parented to a native dialog.
+TEST_F(FocusManagerTest, CreationForNativeRoot) {
+ // Create a window class.
+ WNDCLASSEX class_ex;
+ memset(&class_ex, 0, sizeof(class_ex));
+ class_ex.cbSize = sizeof(WNDCLASSEX);
+ class_ex.lpfnWndProc = &DefWindowProc;
+ class_ex.lpszClassName = L"TestWindow";
+ ATOM atom = RegisterClassEx(&class_ex);
+ ASSERT_TRUE(atom);
+
+ // Create a native dialog window.
+ HWND hwnd = CreateWindowEx(0, class_ex.lpszClassName, NULL,
+ WS_OVERLAPPEDWINDOW, 0, 0, 200, 200,
+ NULL, NULL, NULL, NULL);
+ ASSERT_TRUE(hwnd);
+
+ // Create a view window parented to native dialog.
+ scoped_ptr<Widget> widget1(new Widget);
+ Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
+ params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.parent = hwnd;
+ params.bounds = gfx::Rect(0, 0, 100, 100);
+ params.top_level = true; // This is top level in views hierarchy.
+ widget1->Init(params);
+
+ // Get the focus manager directly from the first window. Should exist
+ // because the first window is the root widget.
+ views::FocusManager* focus_manager1 = widget1->GetFocusManager();
+ EXPECT_TRUE(focus_manager1);
+
+ // Create another view window parented to the first view window.
+ scoped_ptr<Widget> widget2(new Widget);
+ params.parent = widget1->GetNativeView();
+ params.top_level = false; // This is child widget.
+ widget2->Init(params);
+
+ // Access the shared focus manager directly from the second window.
+ views::FocusManager* focus_manager2 = widget2->GetFocusManager();
+ EXPECT_EQ(focus_manager2, focus_manager1);
+
+ // Access the shared focus manager indirectly from the first window handle.
+ gfx::NativeWindow native_window = widget1->GetNativeWindow();
+ views::Widget* widget =
+ views::Widget::GetWidgetForNativeWindow(native_window);
+ EXPECT_EQ(widget->GetFocusManager(), focus_manager1);
+
+ // Access the shared focus manager indirectly from the second window handle.
+ native_window = widget2->GetNativeWindow();
+ widget = views::Widget::GetWidgetForNativeWindow(native_window);
+ EXPECT_EQ(widget->GetFocusManager(), focus_manager1);
+
+ // Access the shared focus manager indirectly from the first view handle.
+ gfx::NativeView native_view = widget1->GetNativeView();
+ widget = views::Widget::GetTopLevelWidgetForNativeView(native_view);
+ EXPECT_EQ(widget->GetFocusManager(), focus_manager1);
+
+ // Access the shared focus manager indirectly from the second view handle.
+ native_view = widget2->GetNativeView();
+ widget = views::Widget::GetTopLevelWidgetForNativeView(native_view);
+ EXPECT_EQ(widget->GetFocusManager(), focus_manager1);
+
+ DestroyWindow(hwnd);
+}
+#endif
+
+class FocusManagerDtorTest : public FocusManagerTest {
+ protected:
+ typedef std::vector<std::string> DtorTrackVector;
+
+ class FocusManagerDtorTracked : public FocusManager {
+ public:
+ FocusManagerDtorTracked(Widget* widget, DtorTrackVector* dtor_tracker)
+ : FocusManager(widget),
+ dtor_tracker_(dtor_tracker) {
+ }
+
+ virtual ~FocusManagerDtorTracked() {
+ dtor_tracker_->push_back("FocusManagerDtorTracked");
+ }
+
+ DtorTrackVector* dtor_tracker_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FocusManagerDtorTracked);
+ };
+
+ class TestFocusManagerFactory : public FocusManagerFactory {
+ public:
+ explicit TestFocusManagerFactory(DtorTrackVector* dtor_tracker)
+ : dtor_tracker_(dtor_tracker) {
+ }
+
+ FocusManager* CreateFocusManager(Widget* widget) OVERRIDE {
+ return new FocusManagerDtorTracked(widget, dtor_tracker_);
+ }
+
+ private:
+ DtorTrackVector* dtor_tracker_;
+ DISALLOW_COPY_AND_ASSIGN(TestFocusManagerFactory);
+ };
+
+ class NativeButtonDtorTracked : public NativeTextButton {
+ public:
+ NativeButtonDtorTracked(const string16& text,
+ DtorTrackVector* dtor_tracker)
+ : NativeTextButton(NULL, text),
+ dtor_tracker_(dtor_tracker) {
+ };
+ virtual ~NativeButtonDtorTracked() {
+ dtor_tracker_->push_back("NativeButtonDtorTracked");
+ }
+
+ DtorTrackVector* dtor_tracker_;
+ };
+
+ class WindowDtorTracked : public Widget {
+ public:
+ explicit WindowDtorTracked(DtorTrackVector* dtor_tracker)
+ : dtor_tracker_(dtor_tracker) {
+ }
+
+ virtual ~WindowDtorTracked() {
+ dtor_tracker_->push_back("WindowDtorTracked");
+ }
+
+ DtorTrackVector* dtor_tracker_;
+ };
+
+ virtual void SetUp() {
+ ViewsTestBase::SetUp();
+ FocusManagerFactory::Install(new TestFocusManagerFactory(&dtor_tracker_));
+ // Create WindowDtorTracked that uses FocusManagerDtorTracked.
+ window_ = new WindowDtorTracked(&dtor_tracker_);
+ Widget::InitParams params;
+ params.delegate = this;
+ params.bounds = gfx::Rect(0, 0, 100, 100);
+ window_->Init(params);
+
+ tracked_focus_manager_ =
+ static_cast<FocusManagerDtorTracked*>(GetFocusManager());
+ window_->Show();
+ }
+
+ virtual void TearDown() {
+ if (window_) {
+ window_->Close();
+ RunPendingMessages();
+ }
+ FocusManagerFactory::Install(NULL);
+ ViewsTestBase::TearDown();
+ }
+
+ FocusManager* tracked_focus_manager_;
+ DtorTrackVector dtor_tracker_;
+};
+
+TEST_F(FocusManagerDtorTest, FocusManagerDestructedLast) {
+ // Setup views hierarchy.
+ TabbedPane* tabbed_pane = new TabbedPane();
+ content_view_->AddChildView(tabbed_pane);
+
+ NativeButtonDtorTracked* button = new NativeButtonDtorTracked(
+ ASCIIToUTF16("button"), &dtor_tracker_);
+ tabbed_pane->AddTab(ASCIIToUTF16("Awesome tab"), button);
+
+ // Close the window.
+ window_->Close();
+ RunPendingMessages();
+
+ // Test window, button and focus manager should all be destructed.
+ ASSERT_EQ(3, static_cast<int>(dtor_tracker_.size()));
+
+ // Focus manager should be the last one to destruct.
+ ASSERT_STREQ("FocusManagerDtorTracked", dtor_tracker_[2].c_str());
+
+ // Clear window_ so that we don't try to close it again.
+ window_ = NULL;
+}
+
+} // namespace views
diff --git a/ui/views/focus/focus_search.cc b/ui/views/focus/focus_search.cc
new file mode 100644
index 0000000..eac3f21
--- /dev/null
+++ b/ui/views/focus/focus_search.cc
@@ -0,0 +1,272 @@
+// Copyright (c) 2011 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 "base/logging.h"
+#include "views/focus/focus_manager.h"
+#include "views/focus/focus_search.h"
+#include "views/view.h"
+
+namespace views {
+
+FocusSearch::FocusSearch(View* root, bool cycle, bool accessibility_mode)
+ : root_(root),
+ cycle_(cycle),
+ accessibility_mode_(accessibility_mode) {
+}
+
+View* FocusSearch::FindNextFocusableView(View* starting_view,
+ bool reverse,
+ Direction direction,
+ bool check_starting_view,
+ FocusTraversable** focus_traversable,
+ View** focus_traversable_view) {
+ *focus_traversable = NULL;
+ *focus_traversable_view = NULL;
+
+ if (!root_->has_children()) {
+ NOTREACHED();
+ // Nothing to focus on here.
+ return NULL;
+ }
+
+ View* initial_starting_view = starting_view;
+ int starting_view_group = -1;
+ if (starting_view)
+ starting_view_group = starting_view->GetGroup();
+
+ if (!starting_view) {
+ // Default to the first/last child
+ starting_view = reverse ? root_->child_at(root_->child_count() - 1) :
+ root_->child_at(0);
+ // If there was no starting view, then the one we select is a potential
+ // focus candidate.
+ check_starting_view = true;
+ } else {
+ // The starting view should be a direct or indirect child of the root.
+ DCHECK(root_->Contains(starting_view));
+ }
+
+ View* v = NULL;
+ if (!reverse) {
+ v = FindNextFocusableViewImpl(starting_view, check_starting_view,
+ true,
+ (direction == DOWN) ? true : false,
+ starting_view_group,
+ focus_traversable,
+ focus_traversable_view);
+ } else {
+ // If the starting view is focusable, we don't want to go down, as we are
+ // traversing the view hierarchy tree bottom-up.
+ bool can_go_down = (direction == DOWN) && !IsFocusable(starting_view);
+ v = FindPreviousFocusableViewImpl(starting_view, check_starting_view,
+ true,
+ can_go_down,
+ starting_view_group,
+ focus_traversable,
+ focus_traversable_view);
+ }
+
+ // Don't set the focus to something outside of this view hierarchy.
+ if (v && v != root_ && !root_->Contains(v))
+ v = NULL;
+
+ // If |cycle_| is true, prefer to keep cycling rather than returning NULL.
+ if (cycle_ && !v && initial_starting_view) {
+ v = FindNextFocusableView(NULL, reverse, direction, check_starting_view,
+ focus_traversable, focus_traversable_view);
+ DCHECK(IsFocusable(v));
+ return v;
+ }
+
+ // Doing some sanity checks.
+ if (v) {
+ DCHECK(IsFocusable(v));
+ return v;
+ }
+ if (*focus_traversable) {
+ DCHECK(*focus_traversable_view);
+ return NULL;
+ }
+ // Nothing found.
+ return NULL;
+}
+
+bool FocusSearch::IsViewFocusableCandidate(View* v, int skip_group_id) {
+ return IsFocusable(v) &&
+ (v->IsGroupFocusTraversable() || skip_group_id == -1 ||
+ v->GetGroup() != skip_group_id);
+}
+
+bool FocusSearch::IsFocusable(View* v) {
+ if (accessibility_mode_)
+ return v && v->IsAccessibilityFocusableInRootView();
+
+ return v && v->IsFocusableInRootView();
+}
+
+View* FocusSearch::FindSelectedViewForGroup(View* view) {
+ if (view->IsGroupFocusTraversable() ||
+ view->GetGroup() == -1) // No group for that view.
+ return view;
+
+ View* selected_view = view->GetSelectedViewForGroup(view->GetGroup());
+ if (selected_view)
+ return selected_view;
+
+ // No view selected for that group, default to the specified view.
+ return view;
+}
+
+View* FocusSearch::GetParent(View* v) {
+ return root_->Contains(v) ? v->parent() : NULL;
+}
+
+// Strategy for finding the next focusable view:
+// - keep going down the first child, stop when you find a focusable view or
+// a focus traversable view (in that case return it) or when you reach a view
+// with no children.
+// - go to the right sibling and start the search from there (by invoking
+// FindNextFocusableViewImpl on that view).
+// - if the view has no right sibling, go up the parents until you find a parent
+// with a right sibling and start the search from there.
+View* FocusSearch::FindNextFocusableViewImpl(
+ View* starting_view,
+ bool check_starting_view,
+ bool can_go_up,
+ bool can_go_down,
+ int skip_group_id,
+ FocusTraversable** focus_traversable,
+ View** focus_traversable_view) {
+ if (check_starting_view) {
+ if (IsViewFocusableCandidate(starting_view, skip_group_id)) {
+ View* v = FindSelectedViewForGroup(starting_view);
+ // The selected view might not be focusable (if it is disabled for
+ // example).
+ if (IsFocusable(v))
+ return v;
+ }
+
+ *focus_traversable = starting_view->GetFocusTraversable();
+ if (*focus_traversable) {
+ *focus_traversable_view = starting_view;
+ return NULL;
+ }
+ }
+
+ // First let's try the left child.
+ if (can_go_down) {
+ if (starting_view->has_children()) {
+ View* v = FindNextFocusableViewImpl(starting_view->child_at(0),
+ true, false, true, skip_group_id,
+ focus_traversable,
+ focus_traversable_view);
+ if (v || *focus_traversable)
+ return v;
+ }
+ }
+
+ // Then try the right sibling.
+ View* sibling = starting_view->GetNextFocusableView();
+ if (sibling) {
+ View* v = FindNextFocusableViewImpl(sibling,
+ true, false, true, skip_group_id,
+ focus_traversable,
+ focus_traversable_view);
+ if (v || *focus_traversable)
+ return v;
+ }
+
+ // Then go up to the parent sibling.
+ if (can_go_up) {
+ View* parent = GetParent(starting_view);
+ while (parent) {
+ sibling = parent->GetNextFocusableView();
+ if (sibling) {
+ return FindNextFocusableViewImpl(sibling,
+ true, true, true,
+ skip_group_id,
+ focus_traversable,
+ focus_traversable_view);
+ }
+ parent = GetParent(parent);
+ }
+ }
+
+ // We found nothing.
+ return NULL;
+}
+
+// Strategy for finding the previous focusable view:
+// - keep going down on the right until you reach a view with no children, if it
+// it is a good candidate return it.
+// - start the search on the left sibling.
+// - if there are no left sibling, start the search on the parent (without going
+// down).
+View* FocusSearch::FindPreviousFocusableViewImpl(
+ View* starting_view,
+ bool check_starting_view,
+ bool can_go_up,
+ bool can_go_down,
+ int skip_group_id,
+ FocusTraversable** focus_traversable,
+ View** focus_traversable_view) {
+ // Let's go down and right as much as we can.
+ if (can_go_down) {
+ // Before we go into the direct children, we have to check if this view has
+ // a FocusTraversable.
+ *focus_traversable = starting_view->GetFocusTraversable();
+ if (*focus_traversable) {
+ *focus_traversable_view = starting_view;
+ return NULL;
+ }
+
+ if (starting_view->has_children()) {
+ View* view =
+ starting_view->child_at(starting_view->child_count() - 1);
+ View* v = FindPreviousFocusableViewImpl(view, true, false, true,
+ skip_group_id,
+ focus_traversable,
+ focus_traversable_view);
+ if (v || *focus_traversable)
+ return v;
+ }
+ }
+
+ // Then look at this view. Here, we do not need to see if the view has
+ // a FocusTraversable, since we do not want to go down any more.
+ if (check_starting_view &&
+ IsViewFocusableCandidate(starting_view, skip_group_id)) {
+ View* v = FindSelectedViewForGroup(starting_view);
+ // The selected view might not be focusable (if it is disabled for
+ // example).
+ if (IsFocusable(v))
+ return v;
+ }
+
+ // Then try the left sibling.
+ View* sibling = starting_view->GetPreviousFocusableView();
+ if (sibling) {
+ return FindPreviousFocusableViewImpl(sibling,
+ true, true, true,
+ skip_group_id,
+ focus_traversable,
+ focus_traversable_view);
+ }
+
+ // Then go up the parent.
+ if (can_go_up) {
+ View* parent = GetParent(starting_view);
+ if (parent)
+ return FindPreviousFocusableViewImpl(parent,
+ true, true, false,
+ skip_group_id,
+ focus_traversable,
+ focus_traversable_view);
+ }
+
+ // We found nothing.
+ return NULL;
+}
+
+} // namespace views
diff --git a/ui/views/focus/focus_search.h b/ui/views/focus/focus_search.h
new file mode 100644
index 0000000..9fe37a0
--- /dev/null
+++ b/ui/views/focus/focus_search.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2011 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 UI_VIEWS_FOCUS_FOCUS_SEARCH_H_
+#define UI_VIEWS_FOCUS_FOCUS_SEARCH_H_
+#pragma once
+
+#include "views/view.h"
+
+namespace views {
+
+class FocusTraversable;
+
+// FocusSearch is an object that implements the algorithm to find the
+// next view to focus.
+class VIEWS_EXPORT FocusSearch {
+ public:
+ // The direction in which the focus traversal is going.
+ // TODO (jcampan): add support for lateral (left, right) focus traversal. The
+ // goal is to switch to focusable views on the same level when using the arrow
+ // keys (ala Windows: in a dialog box, arrow keys typically move between the
+ // dialog OK, Cancel buttons).
+ enum Direction {
+ UP = 0,
+ DOWN
+ };
+
+ // Constructor.
+ // - |root| is the root of the view hierarchy to traverse. Focus will be
+ // trapped inside.
+ // - |cycle| should be true if you want FindNextFocusableView to cycle back
+ // to the first view within this root when the traversal reaches
+ // the end. If this is true, then if you pass a valid starting
+ // view to FindNextFocusableView you will always get a valid view
+ // out, even if it's the same view.
+ // - |accessibility_mode| should be true if full keyboard accessibility is
+ // needed and you want to check IsAccessibilityFocusableInRootView(),
+ // rather than IsFocusableInRootView().
+ FocusSearch(View* root, bool cycle, bool accessibility_mode);
+ virtual ~FocusSearch() {}
+
+ // Finds the next view that should be focused and returns it. If a
+ // FocusTraversable is found while searching for the focusable view,
+ // returns NULL and sets |focus_traversable| to the FocusTraversable
+ // and |focus_traversable_view| to the view associated with the
+ // FocusTraversable.
+ //
+ // Return NULL if the end of the focus loop is reached, unless this object
+ // was initialized with |cycle|=true, in which case it goes back to the
+ // beginning when it reaches the end of the traversal.
+ // - |starting_view| is the view that should be used as the starting point
+ // when looking for the previous/next view. It may be NULL (in which case
+ // the first/last view should be used depending if normal/reverse).
+ // - |reverse| whether we should find the next (reverse is false) or the
+ // previous (reverse is true) view.
+ // - |direction| specifies whether we are traversing down (meaning we should
+ // look into child views) or traversing up (don't look at child views).
+ // - |check_starting_view| is true if starting_view may obtain the next focus.
+ // - |focus_traversable| is set to the focus traversable that should be
+ // traversed if one is found (in which case the call returns NULL).
+ // - |focus_traversable_view| is set to the view associated with the
+ // FocusTraversable set in the previous parameter (it is used as the
+ // starting view when looking for the next focusable view).
+ virtual View* FindNextFocusableView(View* starting_view,
+ bool reverse,
+ Direction direction,
+ bool check_starting_view,
+ FocusTraversable** focus_traversable,
+ View** focus_traversable_view);
+
+ private:
+ // Convenience method that returns true if a view is focusable and does not
+ // belong to the specified group.
+ bool IsViewFocusableCandidate(View* v, int skip_group_id);
+
+ // Convenience method; returns true if a view is not NULL and is focusable
+ // (checking IsAccessibilityFocusableInRootView() if accessibility_mode_ is
+ // true).
+ bool IsFocusable(View* v);
+
+ // Returns the view selected for the group of the selected view. If the view
+ // does not belong to a group or if no view is selected in the group, the
+ // specified view is returned.
+ View* FindSelectedViewForGroup(View* view);
+
+ // Get the parent, but stay within the root. Returns NULL if asked for
+ // the parent of root_.
+ View* GetParent(View* view);
+
+ // Returns the next focusable view or view containing a FocusTraversable
+ // (NULL if none was found), starting at the starting_view.
+ // |check_starting_view|, |can_go_up| and |can_go_down| controls the
+ // traversal of the views hierarchy. |skip_group_id| specifies a group_id,
+ // -1 means no group. All views from a group are traversed in one pass.
+ View* FindNextFocusableViewImpl(View* starting_view,
+ bool check_starting_view,
+ bool can_go_up,
+ bool can_go_down,
+ int skip_group_id,
+ FocusTraversable** focus_traversable,
+ View** focus_traversable_view);
+
+ // Same as FindNextFocusableViewImpl but returns the previous focusable view.
+ View* FindPreviousFocusableViewImpl(View* starting_view,
+ bool check_starting_view,
+ bool can_go_up,
+ bool can_go_down,
+ int skip_group_id,
+ FocusTraversable** focus_traversable,
+ View** focus_traversable_view);
+
+ View* root_;
+ bool cycle_;
+ bool accessibility_mode_;
+
+ DISALLOW_COPY_AND_ASSIGN(FocusSearch);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_FOCUS_SEARCH_H_
diff --git a/ui/views/focus/view_storage.cc b/ui/views/focus/view_storage.cc
new file mode 100644
index 0000000..3b9ec10
--- /dev/null
+++ b/ui/views/focus/view_storage.cc
@@ -0,0 +1,115 @@
+// Copyright (c) 2011 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/focus/view_storage.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace views {
+
+// static
+ViewStorage* ViewStorage::GetInstance() {
+ return Singleton<ViewStorage>::get();
+}
+
+ViewStorage::ViewStorage() : view_storage_next_id_(0) {
+}
+
+ViewStorage::~ViewStorage() {
+ STLDeleteContainerPairSecondPointers(view_to_ids_.begin(),
+ view_to_ids_.end());
+}
+
+int ViewStorage::CreateStorageID() {
+ return view_storage_next_id_++;
+}
+
+void ViewStorage::StoreView(int storage_id, View* view) {
+ DCHECK(view);
+ std::map<int, View*>::iterator iter = id_to_view_.find(storage_id);
+
+ if (iter != id_to_view_.end()) {
+ NOTREACHED();
+ RemoveView(storage_id);
+ }
+
+ id_to_view_[storage_id] = view;
+
+ std::vector<int>* ids = NULL;
+ std::map<View*, std::vector<int>*>::iterator id_iter =
+ view_to_ids_.find(view);
+ if (id_iter == view_to_ids_.end()) {
+ ids = new std::vector<int>();
+ view_to_ids_[view] = ids;
+ } else {
+ ids = id_iter->second;
+ }
+ ids->push_back(storage_id);
+}
+
+View* ViewStorage::RetrieveView(int storage_id) {
+ std::map<int, View*>::iterator iter = id_to_view_.find(storage_id);
+ if (iter == id_to_view_.end())
+ return NULL;
+ return iter->second;
+}
+
+void ViewStorage::RemoveView(int storage_id) {
+ EraseView(storage_id, false);
+}
+
+void ViewStorage::ViewRemoved(View* removed) {
+ // Let's first retrieve the ids for that view.
+ std::map<View*, std::vector<int>*>::iterator ids_iter =
+ view_to_ids_.find(removed);
+
+ if (ids_iter == view_to_ids_.end()) {
+ // That view is not in the view storage.
+ return;
+ }
+
+ std::vector<int>* ids = ids_iter->second;
+ DCHECK(!ids->empty());
+ EraseView((*ids)[0], true);
+}
+
+void ViewStorage::EraseView(int storage_id, bool remove_all_ids) {
+ // Remove the view from id_to_view_location_.
+ std::map<int, View*>::iterator view_iter = id_to_view_.find(storage_id);
+ if (view_iter == id_to_view_.end())
+ return;
+
+ View* view = view_iter->second;
+ id_to_view_.erase(view_iter);
+
+ // Also update view_to_ids_.
+ std::map<View*, std::vector<int>*>::iterator ids_iter =
+ view_to_ids_.find(view);
+ DCHECK(ids_iter != view_to_ids_.end());
+ std::vector<int>* ids = ids_iter->second;
+
+ if (remove_all_ids) {
+ for (size_t i = 0; i < ids->size(); ++i) {
+ view_iter = id_to_view_.find((*ids)[i]);
+ if (view_iter != id_to_view_.end())
+ id_to_view_.erase(view_iter);
+ }
+ ids->clear();
+ } else {
+ std::vector<int>::iterator id_iter =
+ std::find(ids->begin(), ids->end(), storage_id);
+ DCHECK(id_iter != ids->end());
+ ids->erase(id_iter);
+ }
+
+ if (ids->empty()) {
+ delete ids;
+ view_to_ids_.erase(ids_iter);
+ }
+}
+
+} // namespace views
diff --git a/ui/views/focus/view_storage.h b/ui/views/focus/view_storage.h
new file mode 100644
index 0000000..7d748e2
--- /dev/null
+++ b/ui/views/focus/view_storage.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2011 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 UI_VIEWS_FOCUS_VIEW_STORAGE_H_
+#define UI_VIEWS_FOCUS_VIEW_STORAGE_H_
+#pragma once
+
+#include "base/memory/singleton.h"
+#include "views/view.h"
+
+// This class is a simple storage place for storing/retrieving views. It is
+// used for example in the FocusManager to store/restore focused views when the
+// main window becomes active/inactive.
+// It automatically removes a view from the storage if the view is removed from
+// the tree hierarchy.
+//
+// To use it, you first need to create a view storage id that can then be used
+// to store/retrieve views.
+
+namespace views {
+
+class VIEWS_EXPORT ViewStorage {
+ public:
+ // Returns the global ViewStorage instance.
+ // It is guaranted to be non NULL.
+ static ViewStorage* GetInstance();
+
+ // Returns a unique storage id that can be used to store/retrieve views.
+ int CreateStorageID();
+
+ // Associates |view| with the specified |storage_id|.
+ void StoreView(int storage_id, View* view);
+
+ // Returns the view associated with |storage_id| if any, NULL otherwise.
+ View* RetrieveView(int storage_id);
+
+ // Removes the view associated with |storage_id| if any.
+ void RemoveView(int storage_id);
+
+ // Notifies the ViewStorage that a view was removed from its parent somewhere.
+ void ViewRemoved(View* removed);
+
+ size_t view_count() const { return view_to_ids_.size(); }
+
+ private:
+ friend struct DefaultSingletonTraits<ViewStorage>;
+
+ ViewStorage();
+ ~ViewStorage();
+
+ // Removes the view associated with |storage_id|. If |remove_all_ids| is true,
+ // all other mapping pointing to the same view are removed as well.
+ void EraseView(int storage_id, bool remove_all_ids);
+
+ // Next id for the view storage.
+ int view_storage_next_id_;
+
+ // The association id to View used for the view storage.
+ std::map<int, View*> id_to_view_;
+
+ // Association View to id, used to speed up view notification removal.
+ std::map<View*, std::vector<int>*> view_to_ids_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewStorage);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_VIEW_STORAGE_H_
diff --git a/ui/views/focus/widget_focus_manager.cc b/ui/views/focus/widget_focus_manager.cc
new file mode 100644
index 0000000..57cd2cd
--- /dev/null
+++ b/ui/views/focus/widget_focus_manager.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2011 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/focus/widget_focus_manager.h"
+
+#include "base/memory/singleton.h"
+
+namespace views {
+
+// WidgetFocusManager ----------------------------------------------------------
+
+// static
+WidgetFocusManager* WidgetFocusManager::GetInstance() {
+ return Singleton<WidgetFocusManager>::get();
+}
+
+void WidgetFocusManager::AddFocusChangeListener(
+ WidgetFocusChangeListener* listener) {
+ focus_change_listeners_.AddObserver(listener);
+}
+
+void WidgetFocusManager::RemoveFocusChangeListener(
+ WidgetFocusChangeListener* listener) {
+ focus_change_listeners_.RemoveObserver(listener);
+}
+
+void WidgetFocusManager::OnWidgetFocusEvent(gfx::NativeView focused_before,
+ gfx::NativeView focused_now) {
+ if (enabled_) {
+ FOR_EACH_OBSERVER(WidgetFocusChangeListener, focus_change_listeners_,
+ OnNativeFocusChange(focused_before, focused_now));
+ }
+}
+
+WidgetFocusManager::WidgetFocusManager() : enabled_(true) {}
+
+WidgetFocusManager::~WidgetFocusManager() {}
+
+// AutoNativeNotificationDisabler ----------------------------------------------
+
+AutoNativeNotificationDisabler::AutoNativeNotificationDisabler() {
+ WidgetFocusManager::GetInstance()->DisableNotifications();
+}
+
+AutoNativeNotificationDisabler::~AutoNativeNotificationDisabler() {
+ WidgetFocusManager::GetInstance()->EnableNotifications();
+}
+
+} // namespace views
diff --git a/ui/views/focus/widget_focus_manager.h b/ui/views/focus/widget_focus_manager.h
new file mode 100644
index 0000000..7a0143b
--- /dev/null
+++ b/ui/views/focus/widget_focus_manager.h
@@ -0,0 +1,82 @@
+// Copyright (c) 2011 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 UI_VIEWS_FOCUS_WIDGET_FOCUS_MANAGER_H_
+#define UI_VIEWS_FOCUS_WIDGET_FOCUS_MANAGER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/observer_list.h"
+#include "ui/gfx/native_widget_types.h"
+#include "views/views_export.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace views {
+
+// This interface should be implemented by classes that want to be notified when
+// the native focus is about to change. Listeners implementing this interface
+// will be invoked for all native focus changes across the entire Chrome
+// application. FocusChangeListeners are only called for changes within the
+// children of a single top-level native-view.
+class WidgetFocusChangeListener {
+ public:
+ virtual void OnNativeFocusChange(gfx::NativeView focused_before,
+ gfx::NativeView focused_now) = 0;
+
+ protected:
+ virtual ~WidgetFocusChangeListener() {}
+};
+
+class VIEWS_EXPORT WidgetFocusManager {
+ public:
+ // Returns the singleton instance.
+ static WidgetFocusManager* GetInstance();
+
+ // Adds/removes a WidgetFocusChangeListener |listener| to the set of
+ // active listeners.
+ void AddFocusChangeListener(WidgetFocusChangeListener* listener);
+ void RemoveFocusChangeListener(WidgetFocusChangeListener* listener);
+
+ // To be called when native-focus shifts from |focused_before| to
+ // |focused_now|.
+ // TODO(port) : Invocations to this routine are only implemented for
+ // the Win32 platform. Calls need to be placed appropriately for
+ // non-Windows environments.
+ void OnWidgetFocusEvent(gfx::NativeView focused_before,
+ gfx::NativeView focused_now);
+
+ // Enable/Disable notification of registered listeners during calls
+ // to OnWidgetFocusEvent. Used to prevent unwanted focus changes from
+ // propagating notifications.
+ void EnableNotifications() { enabled_ = true; }
+ void DisableNotifications() { enabled_ = false; }
+
+ private:
+ friend struct DefaultSingletonTraits<WidgetFocusManager>;
+
+ WidgetFocusManager();
+ ~WidgetFocusManager();
+
+ ObserverList<WidgetFocusChangeListener> focus_change_listeners_;
+
+ bool enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(WidgetFocusManager);
+};
+
+// A basic helper class that is used to disable native focus change
+// notifications within a scope.
+class VIEWS_EXPORT AutoNativeNotificationDisabler {
+ public:
+ AutoNativeNotificationDisabler();
+ ~AutoNativeNotificationDisabler();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AutoNativeNotificationDisabler);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_FOCUS_WIDGET_FOCUS_MANAGER_H_
diff --git a/ui/views/layout/box_layout.cc b/ui/views/layout/box_layout.cc
new file mode 100644
index 0000000..5441e87
--- /dev/null
+++ b/ui/views/layout/box_layout.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2011 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/layout/box_layout.h"
+
+#include "ui/gfx/insets.h"
+#include "ui/gfx/rect.h"
+#include "views/view.h"
+
+namespace views {
+
+BoxLayout::BoxLayout(BoxLayout::Orientation orientation,
+ int inside_border_horizontal_spacing,
+ int inside_border_vertical_spacing,
+ int between_child_spacing)
+ : orientation_(orientation),
+ inside_border_horizontal_spacing_(inside_border_horizontal_spacing),
+ inside_border_vertical_spacing_(inside_border_vertical_spacing),
+ between_child_spacing_(between_child_spacing) {
+}
+
+BoxLayout::~BoxLayout() {
+}
+
+void BoxLayout::Layout(View* host) {
+ gfx::Rect child_area(host->GetLocalBounds());
+ child_area.Inset(host->GetInsets());
+ child_area.Inset(inside_border_horizontal_spacing_,
+ inside_border_vertical_spacing_);
+ int x = child_area.x();
+ int y = child_area.y();
+ for (int i = 0; i < host->child_count(); ++i) {
+ View* child = host->child_at(i);
+ if (child->IsVisible()) {
+ gfx::Rect bounds(x, y, child_area.width(), child_area.height());
+ gfx::Size size(child->GetPreferredSize());
+ if (orientation_ == kHorizontal) {
+ bounds.set_width(size.width());
+ x += size.width() + between_child_spacing_;
+ } else {
+ bounds.set_height(size.height());
+ y += size.height() + between_child_spacing_;
+ }
+ // Clamp child view bounds to |child_area|.
+ child->SetBoundsRect(bounds.Intersect(child_area));
+ }
+ }
+}
+
+gfx::Size BoxLayout::GetPreferredSize(View* host) {
+ gfx::Rect bounds;
+ int position = 0;
+ for (int i = 0; i < host->child_count(); ++i) {
+ View* child = host->child_at(i);
+ if (child->IsVisible()) {
+ gfx::Size size(child->GetPreferredSize());
+ if (orientation_ == kHorizontal) {
+ gfx::Rect child_bounds(position, 0, size.width(), size.height());
+ bounds = bounds.Union(child_bounds);
+ position += size.width();
+ } else {
+ gfx::Rect child_bounds(0, position, size.width(), size.height());
+ bounds = bounds.Union(child_bounds);
+ position += size.height();
+ }
+ position += between_child_spacing_;
+ }
+ }
+ gfx::Insets insets(host->GetInsets());
+ return gfx::Size(
+ bounds.width() + insets.width() + 2 * inside_border_horizontal_spacing_,
+ bounds.height() + insets.height() + 2 * inside_border_vertical_spacing_);
+}
+
+} // namespace views
diff --git a/ui/views/layout/box_layout.h b/ui/views/layout/box_layout.h
new file mode 100644
index 0000000..81e5a94
--- /dev/null
+++ b/ui/views/layout/box_layout.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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 UI_VIEWS_LAYOUT_BOX_LAYOUT_H_
+#define UI_VIEWS_LAYOUT_BOX_LAYOUT_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "views/layout/layout_manager.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace views {
+
+class View;
+
+// A Layout manager that arranges child views vertically or horizontally in a
+// side-by-side fashion with spacing around and between the child views. The
+// child views are always sized according to their preferred size. If the
+// host's bounds provide insufficient space, child views will be clamped.
+// Excess space will not be distributed.
+class VIEWS_EXPORT BoxLayout : public LayoutManager {
+ public:
+ enum Orientation {
+ kHorizontal,
+ kVertical,
+ };
+
+ // Use |inside_border_horizontal_spacing| and
+ // |inside_border_vertical_spacing| to add additional space between the child
+ // view area and the host view border. |between_child_spacing| controls the
+ // space in between child views.
+ BoxLayout(Orientation orientation,
+ int inside_border_horizontal_spacing,
+ int inside_border_vertical_spacing,
+ int between_child_spacing);
+ virtual ~BoxLayout();
+
+ // Overridden from views::LayoutManager:
+ virtual void Layout(View* host) OVERRIDE;
+ virtual gfx::Size GetPreferredSize(View* host) OVERRIDE;
+
+ private:
+ const Orientation orientation_;
+
+ // Spacing between child views and host view border.
+ const int inside_border_horizontal_spacing_;
+ const int inside_border_vertical_spacing_;
+
+ // Spacing to put in between child views.
+ const int between_child_spacing_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(BoxLayout);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_LAYOUT_BOX_LAYOUT_H_
diff --git a/ui/views/layout/box_layout_unittest.cc b/ui/views/layout/box_layout_unittest.cc
new file mode 100644
index 0000000..12ea2b5
--- /dev/null
+++ b/ui/views/layout/box_layout_unittest.cc
@@ -0,0 +1,111 @@
+// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h"
+#include "views/layout/box_layout.h"
+#include "views/view.h"
+
+class StaticSizedView : public views::View {
+ public:
+ explicit StaticSizedView(const gfx::Size& size)
+ : size_(size) { }
+
+ virtual gfx::Size GetPreferredSize() {
+ return size_;
+ }
+
+ private:
+ gfx::Size size_;
+};
+
+class BoxLayoutTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ host_.reset(new views::View);
+ }
+
+ scoped_ptr<views::View> host_;
+ scoped_ptr<views::BoxLayout> layout_;
+};
+
+TEST_F(BoxLayoutTest, Empty) {
+ layout_.reset(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 10, 10, 20));
+ EXPECT_EQ(gfx::Size(20, 20), layout_->GetPreferredSize(host_.get()));
+}
+
+TEST_F(BoxLayoutTest, AlignmentHorizontal) {
+ layout_.reset(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+ views::View* v1 = new StaticSizedView(gfx::Size(10, 20));
+ host_->AddChildView(v1);
+ views::View* v2 = new StaticSizedView(gfx::Size(10, 10));
+ host_->AddChildView(v2);
+ EXPECT_EQ(gfx::Size(20, 20), layout_->GetPreferredSize(host_.get()));
+ host_->SetBounds(0, 0, 20, 20);
+ layout_->Layout(host_.get());
+ EXPECT_EQ(gfx::Rect(0, 0, 10, 20), v1->bounds());
+ EXPECT_EQ(gfx::Rect(10, 0, 10, 20), v2->bounds());
+}
+
+TEST_F(BoxLayoutTest, AlignmentVertical) {
+ layout_.reset(new views::BoxLayout(views::BoxLayout::kVertical, 0, 0, 0));
+ views::View* v1 = new StaticSizedView(gfx::Size(20, 10));
+ host_->AddChildView(v1);
+ views::View* v2 = new StaticSizedView(gfx::Size(10, 10));
+ host_->AddChildView(v2);
+ EXPECT_EQ(gfx::Size(20, 20), layout_->GetPreferredSize(host_.get()));
+ host_->SetBounds(0, 0, 20, 20);
+ layout_->Layout(host_.get());
+ EXPECT_EQ(gfx::Rect(0, 0, 20, 10), v1->bounds());
+ EXPECT_EQ(gfx::Rect(0, 10, 20, 10), v2->bounds());
+}
+
+TEST_F(BoxLayoutTest, Spacing) {
+ layout_.reset(new views::BoxLayout(views::BoxLayout::kHorizontal, 7, 7, 8));
+ views::View* v1 = new StaticSizedView(gfx::Size(10, 20));
+ host_->AddChildView(v1);
+ views::View* v2 = new StaticSizedView(gfx::Size(10, 20));
+ host_->AddChildView(v2);
+ EXPECT_EQ(gfx::Size(42, 34), layout_->GetPreferredSize(host_.get()));
+ host_->SetBounds(0, 0, 100, 100);
+ layout_->Layout(host_.get());
+ EXPECT_EQ(gfx::Rect(7, 7, 10, 86), v1->bounds());
+ EXPECT_EQ(gfx::Rect(25, 7, 10, 86), v2->bounds());
+}
+
+TEST_F(BoxLayoutTest, Overflow) {
+ layout_.reset(new views::BoxLayout(views::BoxLayout::kHorizontal, 0, 0, 0));
+ views::View* v1 = new StaticSizedView(gfx::Size(20, 20));
+ host_->AddChildView(v1);
+ views::View* v2 = new StaticSizedView(gfx::Size(10, 20));
+ host_->AddChildView(v2);
+ host_->SetBounds(0, 0, 10, 10);
+ layout_->Layout(host_.get());
+ EXPECT_EQ(gfx::Rect(0, 0, 10, 10), v1->bounds());
+ EXPECT_EQ(gfx::Rect(0, 0, 0, 0), v2->bounds());
+}
+
+TEST_F(BoxLayoutTest, NoSpace) {
+ layout_.reset(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 10, 10, 10));
+ views::View* childView = new StaticSizedView(gfx::Size(20, 20));
+ host_->AddChildView(childView);
+ host_->SetBounds(0, 0, 10, 10);
+ layout_->Layout(host_.get());
+ EXPECT_EQ(gfx::Rect(0, 0, 0, 0), childView->bounds());
+}
+
+TEST_F(BoxLayoutTest, InvisibleChild) {
+ layout_.reset(
+ new views::BoxLayout(views::BoxLayout::kHorizontal, 10, 10, 10));
+ views::View* v1 = new StaticSizedView(gfx::Size(20, 20));
+ v1->SetVisible(false);
+ host_->AddChildView(v1);
+ views::View* v2 = new StaticSizedView(gfx::Size(10, 10));
+ host_->AddChildView(v2);
+ EXPECT_EQ(gfx::Size(30, 30), layout_->GetPreferredSize(host_.get()));
+ host_->SetBounds(0, 0, 30, 30);
+ layout_->Layout(host_.get());
+ EXPECT_EQ(gfx::Rect(10, 10, 10, 10), v2->bounds());
+}
diff --git a/ui/views/layout/fill_layout.cc b/ui/views/layout/fill_layout.cc
new file mode 100644
index 0000000..13fbcce
--- /dev/null
+++ b/ui/views/layout/fill_layout.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 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/layout/fill_layout.h"
+
+#include "base/logging.h"
+
+namespace views {
+
+FillLayout::FillLayout() {
+}
+
+FillLayout::~FillLayout() {
+}
+
+void FillLayout::Layout(View* host) {
+ if (!host->has_children())
+ return;
+
+ View* frame_view = host->child_at(0);
+ frame_view->SetBounds(0, 0, host->width(), host->height());
+}
+
+gfx::Size FillLayout::GetPreferredSize(View* host) {
+ DCHECK_EQ(1, host->child_count());
+ return host->child_at(0)->GetPreferredSize();
+}
+
+} // namespace views
diff --git a/ui/views/layout/fill_layout.h b/ui/views/layout/fill_layout.h
new file mode 100644
index 0000000..604d9b5
--- /dev/null
+++ b/ui/views/layout/fill_layout.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011 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 UI_VIEWS_LAYOUT_FILL_LAYOUT_H_
+#define UI_VIEWS_LAYOUT_FILL_LAYOUT_H_
+#pragma once
+
+#include "views/layout/layout_manager.h"
+#include "views/view.h"
+
+namespace views {
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// FillLayout
+// A simple LayoutManager that causes the associated view's one child to be
+// sized to match the bounds of its parent.
+//
+///////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT FillLayout : public LayoutManager {
+ public:
+ FillLayout();
+ virtual ~FillLayout();
+
+ // Overridden from LayoutManager:
+ virtual void Layout(View* host);
+ virtual gfx::Size GetPreferredSize(View* host);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FillLayout);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_LAYOUT_FILL_LAYOUT_H_
diff --git a/ui/views/layout/grid_layout.cc b/ui/views/layout/grid_layout.cc
new file mode 100644
index 0000000..55a50ceb
--- /dev/null
+++ b/ui/views/layout/grid_layout.cc
@@ -0,0 +1,1070 @@
+// Copyright (c) 2011 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/layout/grid_layout.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "ui/gfx/insets.h"
+#include "views/layout/layout_constants.h"
+#include "views/view.h"
+
+namespace views {
+
+// LayoutElement ------------------------------------------------------
+
+// A LayoutElement has a size and location along one axis. It contains
+// methods that are used along both axis.
+class LayoutElement {
+ public:
+ // Invokes ResetSize on all the layout elements.
+ template <class T>
+ static void ResetSizes(std::vector<T*>* elements) {
+ // Reset the layout width of each column.
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ (*i)->ResetSize();
+ }
+ }
+
+ // Sets the location of each element to be the sum of the sizes of the
+ // preceding elements.
+ template <class T>
+ static void CalculateLocationsFromSize(std::vector<T*>* elements) {
+ // Reset the layout width of each column.
+ int location = 0;
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ (*i)->SetLocation(location);
+ location += (*i)->Size();
+ }
+ }
+
+ // Distributes delta among the resizable elements.
+ // Each resizable element is given ResizePercent / total_percent * delta
+ // pixels extra of space.
+ template <class T>
+ static void DistributeDelta(int delta, std::vector<T*>* elements) {
+ if (delta == 0)
+ return;
+
+ float total_percent = 0;
+ int resize_count = 0;
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ total_percent += (*i)->ResizePercent();
+ resize_count++;
+ }
+ if (total_percent == 0) {
+ // None of the elements are resizable, return.
+ return;
+ }
+ int remaining = delta;
+ int resized = resize_count;
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ T* element = *i;
+ if (element->ResizePercent() > 0) {
+ int to_give;
+ if (--resized == 0) {
+ to_give = remaining;
+ } else {
+ to_give = static_cast<int>(delta *
+ (element->resize_percent_ / total_percent));
+ remaining -= to_give;
+ }
+ element->SetSize(element->Size() + to_give);
+ }
+ }
+ }
+
+ // Returns the sum of the size of the elements from start to start + length.
+ template <class T>
+ static int TotalSize(int start, int length, std::vector<T*>* elements) {
+ DCHECK(start >= 0 && length > 0 &&
+ start + length <= static_cast<int>(elements->size()));
+ int size = 0;
+ for (int i = start, max = start + length; i < max; ++i) {
+ size += (*elements)[i]->Size();
+ }
+ return size;
+ }
+
+ explicit LayoutElement(float resize_percent)
+ : resize_percent_(resize_percent) {
+ DCHECK(resize_percent >= 0);
+ }
+
+ virtual ~LayoutElement() {}
+
+ void SetLocation(int location) {
+ location_ = location;
+ }
+
+ int Location() {
+ return location_;
+ }
+
+ // Adjusts the size of this LayoutElement to be the max of the current size
+ // and the specified size.
+ virtual void AdjustSize(int size) {
+ size_ = std::max(size_, size);
+ }
+
+ // Resets the size to the initial size. This sets the size to 0, but
+ // subclasses that have a different initial size should override.
+ virtual void ResetSize() {
+ SetSize(0);
+ }
+
+ void SetSize(int size) {
+ size_ = size;
+ }
+
+ int Size() {
+ return size_;
+ }
+
+ void SetResizePercent(float percent) {
+ resize_percent_ = percent;
+ }
+
+ float ResizePercent() {
+ return resize_percent_;
+ }
+
+ bool IsResizable() {
+ return resize_percent_ > 0;
+ }
+
+ private:
+ float resize_percent_;
+ int location_;
+ int size_;
+
+ DISALLOW_COPY_AND_ASSIGN(LayoutElement);
+};
+
+// Column -------------------------------------------------------------
+
+// As the name implies, this represents a Column. Column contains default
+// values for views originating in this column.
+class Column : public LayoutElement {
+ public:
+ Column(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width,
+ size_t index,
+ bool is_padding)
+ : LayoutElement(resize_percent),
+ h_align_(h_align),
+ v_align_(v_align),
+ size_type_(size_type),
+ same_size_column_(-1),
+ fixed_width_(fixed_width),
+ min_width_(min_width),
+ index_(index),
+ is_padding_(is_padding),
+ master_column_(NULL) {}
+
+ virtual ~Column() {}
+
+ GridLayout::Alignment h_align() { return h_align_; }
+ GridLayout::Alignment v_align() { return v_align_; }
+
+ virtual void ResetSize();
+
+ private:
+ friend class ColumnSet;
+ friend class GridLayout;
+
+ Column* GetLastMasterColumn();
+
+ // Determines the max size of all linked columns, and sets each column
+ // to that size. This should only be used for the master column.
+ void UnifySameSizedColumnSizes();
+
+ virtual void AdjustSize(int size);
+
+ const GridLayout::Alignment h_align_;
+ const GridLayout::Alignment v_align_;
+ const GridLayout::SizeType size_type_;
+ int same_size_column_;
+ const int fixed_width_;
+ const int min_width_;
+
+ // Index of this column in the ColumnSet.
+ const size_t index_;
+
+ const bool is_padding_;
+
+ // If multiple columns have their sizes linked, one is the
+ // master column. The master column is identified by the
+ // master_column field being equal to itself. The master columns
+ // same_size_columns field contains the set of Columns with the
+ // the same size. Columns who are linked to other columns, but
+ // are not the master column have their master_column pointing to
+ // one of the other linked columns. Use the method GetLastMasterColumn
+ // to resolve the true master column.
+ std::vector<Column*> same_size_columns_;
+ Column* master_column_;
+
+ DISALLOW_COPY_AND_ASSIGN(Column);
+};
+
+void Column::ResetSize() {
+ if (size_type_ == GridLayout::FIXED) {
+ SetSize(fixed_width_);
+ } else {
+ SetSize(min_width_);
+ }
+}
+
+Column* Column::GetLastMasterColumn() {
+ if (master_column_ == NULL) {
+ return NULL;
+ }
+ if (master_column_ == this) {
+ return this;
+ }
+ return master_column_->GetLastMasterColumn();
+}
+
+void Column::UnifySameSizedColumnSizes() {
+ DCHECK(master_column_ == this);
+
+ // Accumulate the size first.
+ int size = 0;
+ for (std::vector<Column*>::iterator i = same_size_columns_.begin();
+ i != same_size_columns_.end(); ++i) {
+ size = std::max(size, (*i)->Size());
+ }
+
+ // Then apply it.
+ for (std::vector<Column*>::iterator i = same_size_columns_.begin();
+ i != same_size_columns_.end(); ++i) {
+ (*i)->SetSize(size);
+ }
+}
+
+void Column::AdjustSize(int size) {
+ if (size_type_ == GridLayout::USE_PREF)
+ LayoutElement::AdjustSize(size);
+}
+
+// Row -------------------------------------------------------------
+
+class Row : public LayoutElement {
+ public:
+ Row(bool fixed_height, int height, float resize_percent,
+ ColumnSet* column_set)
+ : LayoutElement(resize_percent),
+ fixed_height_(fixed_height),
+ height_(height),
+ column_set_(column_set),
+ max_ascent_(0),
+ max_descent_(0) {
+ }
+
+ virtual ~Row() {}
+
+ virtual void ResetSize() {
+ max_ascent_ = max_descent_ = 0;
+ SetSize(height_);
+ }
+
+ ColumnSet* column_set() {
+ return column_set_;
+ }
+
+ // Adjusts the size to accomodate the specified ascent/descent.
+ void AdjustSizeForBaseline(int ascent, int descent) {
+ max_ascent_ = std::max(ascent, max_ascent_);
+ max_descent_ = std::max(descent, max_descent_);
+ AdjustSize(max_ascent_ + max_descent_);
+ }
+
+ int max_ascent() const {
+ return max_ascent_;
+ }
+
+ int max_descent() const {
+ return max_descent_;
+ }
+
+ private:
+ const bool fixed_height_;
+ const int height_;
+ // The column set used for this row; null for padding rows.
+ ColumnSet* column_set_;
+
+ int max_ascent_;
+ int max_descent_;
+
+ DISALLOW_COPY_AND_ASSIGN(Row);
+};
+
+// ViewState -------------------------------------------------------------
+
+// Identifies the location in the grid of a particular view, along with
+// placement information and size information.
+struct ViewState {
+ ViewState(ColumnSet* column_set, View* view, int start_col, int start_row,
+ int col_span, int row_span, GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align, int pref_width, int pref_height)
+ : column_set(column_set),
+ view(view),
+ start_col(start_col),
+ start_row(start_row),
+ col_span(col_span),
+ row_span(row_span),
+ h_align(h_align),
+ v_align(v_align),
+ pref_width_fixed(pref_width > 0),
+ pref_height_fixed(pref_height > 0),
+ pref_width(pref_width),
+ pref_height(pref_height),
+ remaining_width(0),
+ remaining_height(0),
+ baseline(-1) {
+ DCHECK(view && start_col >= 0 && start_row >= 0 && col_span > 0 &&
+ row_span > 0 && start_col < column_set->num_columns() &&
+ (start_col + col_span) <= column_set->num_columns());
+ }
+
+ ColumnSet* const column_set;
+ View* const view;
+ const int start_col;
+ const int start_row;
+ const int col_span;
+ const int row_span;
+ const GridLayout::Alignment h_align;
+ const GridLayout::Alignment v_align;
+
+ // If true, the pref_width/pref_height were explicitly set and the view's
+ // preferred size is ignored.
+ const bool pref_width_fixed;
+ const bool pref_height_fixed;
+
+ // The preferred width/height. These are reset during the layout process.
+ int pref_width;
+ int pref_height;
+
+ // Used during layout. Gives how much width/height has not yet been
+ // distributed to the columns/rows the view is in.
+ int remaining_width;
+ int remaining_height;
+
+ // The baseline. Only used if the view is vertically aligned along the
+ // baseline.
+ int baseline;
+};
+
+static bool CompareByColumnSpan(const ViewState* v1, const ViewState* v2) {
+ return v1->col_span < v2->col_span;
+}
+
+static bool CompareByRowSpan(const ViewState* v1, const ViewState* v2) {
+ return v1->row_span < v2->row_span;
+}
+
+// ColumnSet -------------------------------------------------------------
+
+ColumnSet::ColumnSet(int id) : id_(id) {
+}
+
+ColumnSet::~ColumnSet() {
+ STLDeleteElements(&columns_);
+}
+
+void ColumnSet::AddPaddingColumn(float resize_percent, int width) {
+ AddColumn(GridLayout::FILL, GridLayout::FILL, resize_percent,
+ GridLayout::FIXED, width, width, true);
+}
+
+void ColumnSet::AddColumn(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width) {
+ AddColumn(h_align, v_align, resize_percent, size_type, fixed_width,
+ min_width, false);
+}
+
+
+void ColumnSet::LinkColumnSizes(int first, ...) {
+ va_list marker;
+ va_start(marker, first);
+ DCHECK(first >= 0 && first < num_columns());
+ for (int last = first, next = va_arg(marker, int); next != -1;
+ next = va_arg(marker, int)) {
+ DCHECK(next >= 0 && next < num_columns());
+ columns_[last]->same_size_column_ = next;
+ last = next;
+ }
+ va_end(marker);
+}
+
+void ColumnSet::AddColumn(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width,
+ bool is_padding) {
+ Column* column = new Column(h_align, v_align, resize_percent, size_type,
+ fixed_width, min_width, columns_.size(),
+ is_padding);
+ columns_.push_back(column);
+}
+
+void ColumnSet::AddViewState(ViewState* view_state) {
+ // view_states are ordered by column_span (in ascending order).
+ std::vector<ViewState*>::iterator i = lower_bound(view_states_.begin(),
+ view_states_.end(),
+ view_state,
+ CompareByColumnSpan);
+ view_states_.insert(i, view_state);
+}
+
+void ColumnSet::CalculateMasterColumns() {
+ for (std::vector<Column*>::iterator i = columns_.begin();
+ i != columns_.end(); ++i) {
+ Column* column = *i;
+ int same_size_column_index = column->same_size_column_;
+ if (same_size_column_index != -1) {
+ DCHECK(same_size_column_index >= 0 &&
+ same_size_column_index < static_cast<int>(columns_.size()));
+ Column* master_column = column->master_column_;
+ Column* same_size_column = columns_[same_size_column_index];
+ Column* same_size_column_master = same_size_column->master_column_;
+ if (master_column == NULL) {
+ // Current column is not linked to any other column.
+ if (same_size_column_master == NULL) {
+ // Both columns are not linked.
+ column->master_column_ = column;
+ same_size_column->master_column_ = column;
+ column->same_size_columns_.push_back(same_size_column);
+ column->same_size_columns_.push_back(column);
+ } else {
+ // Column to link to is linked with other columns.
+ // Add current column to list of linked columns in other columns
+ // master column.
+ same_size_column->GetLastMasterColumn()->
+ same_size_columns_.push_back(column);
+ // And update the master column for the current column to that
+ // of the same sized column.
+ column->master_column_ = same_size_column;
+ }
+ } else {
+ // Current column is already linked with another column.
+ if (same_size_column_master == NULL) {
+ // Column to link with is not linked to any other columns.
+ // Update it's master_column.
+ same_size_column->master_column_ = column;
+ // Add linked column to list of linked column.
+ column->GetLastMasterColumn()->same_size_columns_.
+ push_back(same_size_column);
+ } else if (column->GetLastMasterColumn() !=
+ same_size_column->GetLastMasterColumn()) {
+ // The two columns are already linked with other columns.
+ std::vector<Column*>* same_size_columns =
+ &(column->GetLastMasterColumn()->same_size_columns_);
+ std::vector<Column*>* other_same_size_columns =
+ &(same_size_column->GetLastMasterColumn()->same_size_columns_);
+ // Add all the columns from the others master to current columns
+ // master.
+ same_size_columns->insert(same_size_columns->end(),
+ other_same_size_columns->begin(),
+ other_same_size_columns->end());
+ // The other master is no longer a master, clear its vector of
+ // linked columns, and reset its master_column.
+ other_same_size_columns->clear();
+ same_size_column->GetLastMasterColumn()->master_column_ = column;
+ }
+ }
+ }
+ }
+ AccumulateMasterColumns();
+}
+
+void ColumnSet::AccumulateMasterColumns() {
+ DCHECK(master_columns_.empty());
+ for (std::vector<Column*>::iterator i = columns_.begin();
+ i != columns_.end(); ++i) {
+ Column* column = *i;
+ Column* master_column = column->GetLastMasterColumn();
+ if (master_column &&
+ find(master_columns_.begin(), master_columns_.end(),
+ master_column) == master_columns_.end()) {
+ master_columns_.push_back(master_column);
+ }
+ // At this point, GetLastMasterColumn may not == master_column
+ // (may have to go through a few Columns)_. Reset master_column to
+ // avoid hops.
+ column->master_column_ = master_column;
+ }
+}
+
+void ColumnSet::UnifySameSizedColumnSizes() {
+ for (std::vector<Column*>::iterator i = master_columns_.begin();
+ i != master_columns_.end(); ++i) {
+ (*i)->UnifySameSizedColumnSizes();
+ }
+}
+
+void ColumnSet::UpdateRemainingWidth(ViewState* view_state) {
+ for (int i = view_state->start_col; i < view_state->col_span; ++i) {
+ view_state->remaining_width -= columns_[i]->Size();
+ }
+}
+
+void ColumnSet::DistributeRemainingWidth(ViewState* view_state) {
+ // This is nearly the same as that for rows, but differs in so far as how
+ // Rows and Columns are treated. Rows have two states, resizable or not.
+ // Columns have three, resizable, USE_PREF or not resizable. This results
+ // in slightly different handling for distributing unaccounted size.
+ int width = view_state->remaining_width;
+ if (width <= 0) {
+ // The columns this view is in are big enough to accommodate it.
+ return;
+ }
+
+ // Determine which columns are resizable, and which have a size type
+ // of USE_PREF.
+ int resizable_columns = 0;
+ int pref_size_columns = 0;
+ int start_col = view_state->start_col;
+ int max_col = view_state->start_col + view_state->col_span;
+ float total_resize = 0;
+ for (int i = start_col; i < max_col; ++i) {
+ if (columns_[i]->IsResizable()) {
+ total_resize += columns_[i]->ResizePercent();
+ resizable_columns++;
+ } else if (columns_[i]->size_type_ == GridLayout::USE_PREF) {
+ pref_size_columns++;
+ }
+ }
+
+ if (resizable_columns > 0) {
+ // There are resizable columns, give them the remaining width. The extra
+ // width is distributed using the resize values of each column.
+ int remaining_width = width;
+ for (int i = start_col, resize_i = 0; i < max_col; ++i) {
+ if (columns_[i]->IsResizable()) {
+ resize_i++;
+ int delta = (resize_i == resizable_columns) ? remaining_width :
+ static_cast<int>(width * columns_[i]->ResizePercent() /
+ total_resize);
+ remaining_width -= delta;
+ columns_[i]->SetSize(columns_[i]->Size() + delta);
+ }
+ }
+ } else if (pref_size_columns > 0) {
+ // None of the columns are resizable, distribute the width among those
+ // that use the preferred size.
+ int to_distribute = width / pref_size_columns;
+ for (int i = start_col; i < max_col; ++i) {
+ if (columns_[i]->size_type_ == GridLayout::USE_PREF) {
+ width -= to_distribute;
+ if (width < to_distribute)
+ to_distribute += width;
+ columns_[i]->SetSize(columns_[i]->Size() + to_distribute);
+ }
+ }
+ }
+}
+
+int ColumnSet::LayoutWidth() {
+ int width = 0;
+ for (std::vector<Column*>::iterator i = columns_.begin();
+ i != columns_.end(); ++i) {
+ width += (*i)->Size();
+ }
+ return width;
+}
+
+int ColumnSet::GetColumnWidth(int start_col, int col_span) {
+ return LayoutElement::TotalSize(start_col, col_span, &columns_);
+}
+
+void ColumnSet::ResetColumnXCoordinates() {
+ LayoutElement::CalculateLocationsFromSize(&columns_);
+}
+
+void ColumnSet::CalculateSize() {
+ gfx::Size pref;
+ // Reset the preferred and remaining sizes.
+ for (std::vector<ViewState*>::iterator i = view_states_.begin();
+ i != view_states_.end(); ++i) {
+ ViewState* view_state = *i;
+ if (!view_state->pref_width_fixed || !view_state->pref_height_fixed) {
+ pref = view_state->view->GetPreferredSize();
+ if (!view_state->pref_width_fixed)
+ view_state->pref_width = pref.width();
+ if (!view_state->pref_height_fixed)
+ view_state->pref_height = pref.height();
+ }
+ view_state->remaining_width = pref.width();
+ view_state->remaining_height = pref.height();
+ }
+
+ // Let layout element reset the sizes for us.
+ LayoutElement::ResetSizes(&columns_);
+
+ // Distribute the size of each view with a col span == 1.
+ std::vector<ViewState*>::iterator view_state_iterator =
+ view_states_.begin();
+ for (; view_state_iterator != view_states_.end() &&
+ (*view_state_iterator)->col_span == 1; ++view_state_iterator) {
+ ViewState* view_state = *view_state_iterator;
+ Column* column = columns_[view_state->start_col];
+ column->AdjustSize(view_state->pref_width);
+ view_state->remaining_width -= column->Size();
+ }
+
+ // Make sure all linked columns have the same size.
+ UnifySameSizedColumnSizes();
+
+ // Distribute the size of each view with a column span > 1.
+ for (; view_state_iterator != view_states_.end(); ++view_state_iterator) {
+ ViewState* view_state = *view_state_iterator;
+
+ // Update the remaining_width from columns this view_state touches.
+ UpdateRemainingWidth(view_state);
+
+ // Distribute the remaining width.
+ DistributeRemainingWidth(view_state);
+
+ // Update the size of linked columns.
+ // This may need to be combined with previous step.
+ UnifySameSizedColumnSizes();
+ }
+}
+
+void ColumnSet::Resize(int delta) {
+ LayoutElement::DistributeDelta(delta, &columns_);
+}
+
+// GridLayout -------------------------------------------------------------
+
+GridLayout::GridLayout(View* host)
+ : host_(host),
+ calculated_master_columns_(false),
+ remaining_row_span_(0),
+ current_row_(-1),
+ next_column_(0),
+ current_row_col_set_(NULL),
+ top_inset_(0),
+ bottom_inset_(0),
+ left_inset_(0),
+ right_inset_(0),
+ adding_view_(false) {
+ DCHECK(host);
+}
+
+GridLayout::~GridLayout() {
+ STLDeleteElements(&column_sets_);
+ STLDeleteElements(&view_states_);
+ STLDeleteElements(&rows_);
+}
+
+// static
+GridLayout* GridLayout::CreatePanel(View* host) {
+ GridLayout* layout = new GridLayout(host);
+ layout->SetInsets(kPanelVertMargin, kPanelHorizMargin,
+ kPanelVertMargin, kPanelHorizMargin);
+ return layout;
+}
+
+void GridLayout::SetInsets(int top, int left, int bottom, int right) {
+ top_inset_ = top;
+ bottom_inset_ = bottom;
+ left_inset_ = left;
+ right_inset_ = right;
+}
+
+void GridLayout::SetInsets(const gfx::Insets& insets) {
+ SetInsets(insets.top(), insets.left(), insets.bottom(), insets.right());
+}
+
+ColumnSet* GridLayout::AddColumnSet(int id) {
+ DCHECK(GetColumnSet(id) == NULL);
+ ColumnSet* column_set = new ColumnSet(id);
+ column_sets_.push_back(column_set);
+ return column_set;
+}
+
+void GridLayout::StartRowWithPadding(float vertical_resize, int column_set_id,
+ float padding_resize, int padding) {
+ AddPaddingRow(padding_resize, padding);
+ StartRow(vertical_resize, column_set_id);
+}
+
+void GridLayout::StartRow(float vertical_resize, int column_set_id) {
+ ColumnSet* column_set = GetColumnSet(column_set_id);
+ DCHECK(column_set);
+ AddRow(new Row(false, 0, vertical_resize, column_set));
+}
+
+void GridLayout::AddPaddingRow(float vertical_resize, int pixel_count) {
+ AddRow(new Row(true, pixel_count, vertical_resize, NULL));
+}
+
+void GridLayout::SkipColumns(int col_count) {
+ DCHECK(col_count > 0);
+ next_column_ += col_count;
+ DCHECK(current_row_col_set_ &&
+ next_column_ <= current_row_col_set_->num_columns());
+ SkipPaddingColumns();
+}
+
+void GridLayout::AddView(View* view) {
+ AddView(view, 1, 1);
+}
+
+void GridLayout::AddView(View* view, int col_span, int row_span) {
+ DCHECK(current_row_col_set_ &&
+ next_column_ < current_row_col_set_->num_columns());
+ Column* column = current_row_col_set_->columns_[next_column_];
+ AddView(view, col_span, row_span, column->h_align(), column->v_align());
+}
+
+void GridLayout::AddView(View* view, int col_span, int row_span,
+ Alignment h_align, Alignment v_align) {
+ AddView(view, col_span, row_span, h_align, v_align, 0, 0);
+}
+
+void GridLayout::AddView(View* view, int col_span, int row_span,
+ Alignment h_align, Alignment v_align,
+ int pref_width, int pref_height) {
+ DCHECK(current_row_col_set_ && col_span > 0 && row_span > 0 &&
+ (next_column_ + col_span) <= current_row_col_set_->num_columns());
+ // We don't support baseline alignment of views spanning rows. Please add if
+ // you need it.
+ DCHECK(v_align != BASELINE || row_span == 1);
+ ViewState* state =
+ new ViewState(current_row_col_set_, view, next_column_, current_row_,
+ col_span, row_span, h_align, v_align, pref_width,
+ pref_height);
+ AddViewState(state);
+}
+
+static void CalculateSize(int pref_size, GridLayout::Alignment alignment,
+ int* location, int* size) {
+ if (alignment != GridLayout::FILL) {
+ int available_size = *size;
+ *size = std::min(*size, pref_size);
+ switch (alignment) {
+ case GridLayout::LEADING:
+ // Nothing to do, location already points to start.
+ break;
+ case GridLayout::BASELINE: // If we were asked to align on baseline, but
+ // the view doesn't have a baseline, fall back
+ // to center.
+ case GridLayout::CENTER:
+ *location += (available_size - *size) / 2;
+ break;
+ case GridLayout::TRAILING:
+ *location = *location + available_size - *size;
+ break;
+ default:
+ NOTREACHED();
+ }
+ }
+}
+
+void GridLayout::Installed(View* host) {
+ DCHECK(host_ == host);
+}
+
+void GridLayout::Uninstalled(View* host) {
+ DCHECK(host_ == host);
+}
+
+void GridLayout::ViewAdded(View* host, View* view) {
+ DCHECK(host_ == host && adding_view_);
+}
+
+void GridLayout::ViewRemoved(View* host, View* view) {
+ DCHECK(host_ == host);
+}
+
+void GridLayout::Layout(View* host) {
+ DCHECK(host_ == host);
+ // SizeRowsAndColumns sets the size and location of each row/column, but
+ // not of the views.
+ gfx::Size pref;
+ SizeRowsAndColumns(true, host_->width(), host_->height(), &pref);
+
+ // Size each view.
+ for (std::vector<ViewState*>::iterator i = view_states_.begin();
+ i != view_states_.end(); ++i) {
+ ViewState* view_state = *i;
+ ColumnSet* column_set = view_state->column_set;
+ View* view = (*i)->view;
+ DCHECK(view);
+ int x = column_set->columns_[view_state->start_col]->Location() +
+ left_inset_;
+ int width = column_set->GetColumnWidth(view_state->start_col,
+ view_state->col_span);
+ CalculateSize(view_state->pref_width, view_state->h_align,
+ &x, &width);
+ int y = rows_[view_state->start_row]->Location() + top_inset_;
+ int height = LayoutElement::TotalSize(view_state->start_row,
+ view_state->row_span, &rows_);
+ if (view_state->v_align == BASELINE && view_state->baseline != -1) {
+ y += rows_[view_state->start_row]->max_ascent() - view_state->baseline;
+ height = view_state->pref_height;
+ } else {
+ CalculateSize(view_state->pref_height, view_state->v_align, &y, &height);
+ }
+ view->SetBounds(x, y, width, height);
+ }
+}
+
+gfx::Size GridLayout::GetPreferredSize(View* host) {
+ DCHECK(host_ == host);
+ gfx::Size out;
+ SizeRowsAndColumns(false, 0, 0, &out);
+ return out;
+}
+
+int GridLayout::GetPreferredHeightForWidth(View* host, int width) {
+ DCHECK(host_ == host);
+ gfx::Size pref;
+ SizeRowsAndColumns(false, width, 0, &pref);
+ return pref.height();
+}
+
+void GridLayout::SizeRowsAndColumns(bool layout, int width, int height,
+ gfx::Size* pref) {
+ // Make sure the master columns have been calculated.
+ CalculateMasterColumnsIfNecessary();
+ pref->SetSize(0, 0);
+ if (rows_.empty())
+ return;
+
+ // Calculate the preferred width of each of the columns. Some views'
+ // preferred heights are derived from their width, as such we need to
+ // calculate the size of the columns first.
+ for (std::vector<ColumnSet*>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ (*i)->CalculateSize();
+ pref->set_width(std::max(pref->width(), (*i)->LayoutWidth()));
+ }
+ pref->set_width(pref->width() + left_inset_ + right_inset_);
+
+ // Go over the columns again and set them all to the size we settled for.
+ width = width ? width : pref->width();
+ for (std::vector<ColumnSet*>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ // We're doing a layout, divy up any extra space.
+ (*i)->Resize(width - (*i)->LayoutWidth() - left_inset_ - right_inset_);
+ // And reset the x coordinates.
+ (*i)->ResetColumnXCoordinates();
+ }
+
+ // Reset the height of each row.
+ LayoutElement::ResetSizes(&rows_);
+
+ // Do the following:
+ // . If the view is aligned along it's baseline, obtain the baseline from the
+ // view and update the rows ascent/descent.
+ // . Reset the remaining_height of each view state.
+ // . If the width the view will be given is different than it's pref, ask
+ // for the height given a particularly width.
+ for (std::vector<ViewState*>::iterator i= view_states_.begin();
+ i != view_states_.end() ; ++i) {
+ ViewState* view_state = *i;
+ view_state->remaining_height = view_state->pref_height;
+
+ if (view_state->v_align == BASELINE)
+ view_state->baseline = view_state->view->GetBaseline();
+
+ if (view_state->h_align == FILL) {
+ // The view is resizable. As the pref height may vary with the width,
+ // ask for the pref again.
+ int actual_width =
+ view_state->column_set->GetColumnWidth(view_state->start_col,
+ view_state->col_span);
+ if (actual_width != view_state->pref_width &&
+ !view_state->pref_height_fixed) {
+ // The width this view will get differs from it's preferred. Some Views
+ // pref height varies with it's width; ask for the preferred again.
+ view_state->pref_height =
+ view_state->view->GetHeightForWidth(actual_width);
+ view_state->remaining_height = view_state->pref_height;
+ }
+ }
+ }
+
+ // Update the height/ascent/descent of each row from the views.
+ std::vector<ViewState*>::iterator view_states_iterator = view_states_.begin();
+ for (; view_states_iterator != view_states_.end() &&
+ (*view_states_iterator)->row_span == 1; ++view_states_iterator) {
+ ViewState* view_state = *view_states_iterator;
+ Row* row = rows_[view_state->start_row];
+ row->AdjustSize(view_state->remaining_height);
+ if (view_state->baseline != -1 &&
+ view_state->baseline <= view_state->pref_height) {
+ row->AdjustSizeForBaseline(view_state->baseline,
+ view_state->pref_height - view_state->baseline);
+ }
+ view_state->remaining_height = 0;
+ }
+
+ // Distribute the height of each view with a row span > 1.
+ for (; view_states_iterator != view_states_.end(); ++view_states_iterator) {
+ ViewState* view_state = *view_states_iterator;
+
+ // Update the remaining_width from columns this view_state touches.
+ UpdateRemainingHeightFromRows(view_state);
+
+ // Distribute the remaining height.
+ DistributeRemainingHeight(view_state);
+ }
+
+ // Update the location of each of the rows.
+ LayoutElement::CalculateLocationsFromSize(&rows_);
+
+ // We now know the preferred height, set it here.
+ pref->set_height(rows_[rows_.size() - 1]->Location() +
+ rows_[rows_.size() - 1]->Size() + top_inset_ + bottom_inset_);
+
+ if (layout && height != pref->height()) {
+ // We're doing a layout, and the height differs from the preferred height,
+ // divy up the extra space.
+ LayoutElement::DistributeDelta(height - pref->height(), &rows_);
+
+ // Reset y locations.
+ LayoutElement::CalculateLocationsFromSize(&rows_);
+ }
+}
+
+void GridLayout::CalculateMasterColumnsIfNecessary() {
+ if (!calculated_master_columns_) {
+ calculated_master_columns_ = true;
+ for (std::vector<ColumnSet*>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ (*i)->CalculateMasterColumns();
+ }
+ }
+}
+
+void GridLayout::AddViewState(ViewState* view_state) {
+ DCHECK(view_state->view && (view_state->view->parent() == NULL ||
+ view_state->view->parent() == host_));
+ if (!view_state->view->parent()) {
+ adding_view_ = true;
+ host_->AddChildView(view_state->view);
+ adding_view_ = false;
+ }
+ remaining_row_span_ = std::max(remaining_row_span_, view_state->row_span);
+ next_column_ += view_state->col_span;
+ current_row_col_set_->AddViewState(view_state);
+ // view_states are ordered by row_span (in ascending order).
+ std::vector<ViewState*>::iterator i = lower_bound(view_states_.begin(),
+ view_states_.end(),
+ view_state,
+ CompareByRowSpan);
+ view_states_.insert(i, view_state);
+ SkipPaddingColumns();
+}
+
+ColumnSet* GridLayout::GetColumnSet(int id) {
+ for (std::vector<ColumnSet*>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ if ((*i)->id_ == id) {
+ return *i;
+ }
+ }
+ return NULL;
+}
+
+void GridLayout::AddRow(Row* row) {
+ current_row_++;
+ remaining_row_span_--;
+ DCHECK(remaining_row_span_ <= 0 ||
+ row->column_set() == NULL ||
+ row->column_set() == GetLastValidColumnSet());
+ next_column_ = 0;
+ rows_.push_back(row);
+ current_row_col_set_ = row->column_set();
+ SkipPaddingColumns();
+}
+
+void GridLayout::UpdateRemainingHeightFromRows(ViewState* view_state) {
+ for (int i = 0, start_row = view_state->start_row;
+ i < view_state->row_span; ++i) {
+ view_state->remaining_height -= rows_[i + start_row]->Size();
+ }
+}
+
+void GridLayout::DistributeRemainingHeight(ViewState* view_state) {
+ int height = view_state->remaining_height;
+ if (height <= 0)
+ return;
+
+ // Determine the number of resizable rows the view touches.
+ int resizable_rows = 0;
+ int start_row = view_state->start_row;
+ int max_row = view_state->start_row + view_state->row_span;
+ for (int i = start_row; i < max_row; ++i) {
+ if (rows_[i]->IsResizable()) {
+ resizable_rows++;
+ }
+ }
+
+ if (resizable_rows > 0) {
+ // There are resizable rows, give the remaining height to them.
+ int to_distribute = height / resizable_rows;
+ for (int i = start_row; i < max_row; ++i) {
+ if (rows_[i]->IsResizable()) {
+ height -= to_distribute;
+ if (height < to_distribute) {
+ // Give all slop to the last column.
+ to_distribute += height;
+ }
+ rows_[i]->SetSize(rows_[i]->Size() + to_distribute);
+ }
+ }
+ } else {
+ // None of the rows are resizable, divy the remaining height up equally
+ // among all rows the view touches.
+ int each_row_height = height / view_state->row_span;
+ for (int i = start_row; i < max_row; ++i) {
+ height -= each_row_height;
+ if (height < each_row_height)
+ each_row_height += height;
+ rows_[i]->SetSize(rows_[i]->Size() + each_row_height);
+ }
+ view_state->remaining_height = 0;
+ }
+}
+
+void GridLayout::SkipPaddingColumns() {
+ if (!current_row_col_set_)
+ return;
+ while (next_column_ < current_row_col_set_->num_columns() &&
+ current_row_col_set_->columns_[next_column_]->is_padding_) {
+ next_column_++;
+ }
+}
+
+ColumnSet* GridLayout::GetLastValidColumnSet() {
+ for (int i = current_row_ - 1; i >= 0; --i) {
+ if (rows_[i]->column_set())
+ return rows_[i]->column_set();
+ }
+ return NULL;
+}
+
+} // namespace views
diff --git a/ui/views/layout/grid_layout.h b/ui/views/layout/grid_layout.h
new file mode 100644
index 0000000..6c38585
--- /dev/null
+++ b/ui/views/layout/grid_layout.h
@@ -0,0 +1,371 @@
+// Copyright (c) 2011 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 UI_VIEWS_LAYOUT_GRID_LAYOUT_H_
+#define UI_VIEWS_LAYOUT_GRID_LAYOUT_H_
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include "views/layout/layout_manager.h"
+#include "views/view.h"
+
+namespace gfx {
+class Insets;
+}
+
+// GridLayout is a LayoutManager that positions child Views in a grid. You
+// define the structure of the Grid first, then add the Views.
+// The following creates a trivial grid with two columns separated by
+// a column with padding:
+// ColumnSet* columns = layout->AddColumnSet(0); // Give this column an
+// // identifier of 0.
+// columns->AddColumn(FILL, // Views are horizontally resized to fill column.
+// FILL, // Views starting in this column are vertically
+// // resized.
+// 1, // This column has a resize weight of 1.
+// USE_PREF, // Use the preferred size of the view.
+// 0, // Ignored for USE_PREF.
+// 0); // A minimum width of 0.
+// columns->AddPaddingColumn(0, // The padding column is not resizable.
+// 10); // And has a width of 10 pixels.
+// columns->AddColumn(FILL, FILL, 0, USE_PREF, 0, 0);
+// Now add the views:
+// // First start a row.
+// layout->StartRow(0, // This row isn't vertically resizable.
+// 0); // The column set to use for this row.
+// layout->AddView(v1);
+// Notice you need not skip over padding columns, that's done for you.
+// layout->AddView(v2);
+//
+// When adding a Column you give it the default alignment for all views
+// originating in that column. You can override this for specific views
+// when adding them. For example, the following forces a View to have
+// a horizontal and vertical alignment of leading regardless of that defined
+// for the column:
+// layout->AddView(v1, 1, 1, LEADING, LEADING);
+//
+// If the View using GridLayout is given a size bigger than the preferred,
+// columns and rows with a resize percent > 0 are resized. Each column/row
+// is given resize_percent / total_resize_percent * extra_pixels extra
+// pixels. Only Views with an Alignment of FILL are given extra space, others
+// are aligned in the provided space.
+//
+// GridLayout allows you to define multiple column sets. When you start a
+// new row you specify the id of the column set the row is to use.
+//
+// GridLayout allows you to force columns to have the same width. This is
+// done using the LinkColumnSizes method.
+//
+// AddView takes care of adding the View to the View the GridLayout was
+// created with.
+namespace views {
+
+class Column;
+class ColumnSet;
+class Row;
+class View;
+
+struct ViewState;
+
+class VIEWS_EXPORT GridLayout : public LayoutManager {
+ public:
+ // An enumeration of the possible alignments supported by GridLayout.
+ enum Alignment {
+ // Leading equates to left along the horizontal axis, and top along the
+ // vertical axis.
+ LEADING,
+
+ // Centers the view along the axis.
+ CENTER,
+
+ // Trailing equals to right along the horizontal axis, and bottom along
+ // the vertical axis.
+ TRAILING,
+
+ // The view is resized to fill the space.
+ FILL,
+
+ // The view is aligned along the baseline. This is only valid for the
+ // vertical axis.
+ BASELINE
+ };
+
+ // An enumeration of the possible ways the size of a column may be obtained.
+ enum SizeType {
+ // The column size is fixed.
+ FIXED,
+
+ // The preferred size of the view is used to determine the column size.
+ USE_PREF
+ };
+
+ explicit GridLayout(View* host);
+ virtual ~GridLayout();
+
+ // Creates a GridLayout with kPanel*Margin insets.
+ static GridLayout* CreatePanel(View* host);
+
+ // Sets the insets. All views are placed relative to these offsets.
+ void SetInsets(int top, int left, int bottom, int right);
+ void SetInsets(const gfx::Insets& insets);
+
+ // Creates a new column set with the specified id and returns it.
+ // The id is later used when starting a new row.
+ // GridLayout takes ownership of the ColumnSet and will delete it when
+ // the GridLayout is deleted.
+ ColumnSet* AddColumnSet(int id);
+
+ // Adds a padding row. Padding rows typically don't have any views, and
+ // but are used to provide vertical white space between views.
+ // Size specifies the height of the row.
+ void AddPaddingRow(float vertical_resize, int size);
+
+ // A convenience for AddPaddingRow followed by StartRow.
+ void StartRowWithPadding(float vertical_resize, int column_set_id,
+ float padding_resize, int padding);
+
+ // Starts a new row with the specified column set.
+ void StartRow(float vertical_resize, int column_set_id);
+
+ // Advances past columns. Use this when the current column should not
+ // contain any views.
+ void SkipColumns(int col_count);
+
+ // Adds a view using the default alignment from the column. The added
+ // view has a column and row span of 1.
+ // As a convenience this adds the view to the host. The view becomes owned
+ // by the host, and NOT this GridLayout.
+ void AddView(View* view);
+
+ // Adds a view using the default alignment from the column.
+ // As a convenience this adds the view to the host. The view becomes owned
+ // by the host, and NOT this GridLayout.
+ void AddView(View* view, int col_span, int row_span);
+
+ // Adds a view with the specified alignment and spans.
+ // As a convenience this adds the view to the host. The view becomes owned
+ // by the host, and NOT this GridLayout.
+ void AddView(View* view, int col_span, int row_span, Alignment h_align,
+ Alignment v_align);
+
+ // Adds a view with the specified alignment and spans. If
+ // pref_width/pref_height is > 0 then the preferred width/height of the view
+ // is fixed to the specified value.
+ // As a convenience this adds the view to the host. The view becomes owned
+ // by the host, and NOT this GridLayout.
+ void AddView(View* view, int col_span, int row_span,
+ Alignment h_align, Alignment v_align,
+ int pref_width, int pref_height);
+
+ // Notification we've been installed on a particular host. Checks that host
+ // is the same as the View supplied in the constructor.
+ virtual void Installed(View* host);
+
+ // Notification we've been uninstalled on a particular host. Checks that host
+ // is the same as the View supplied in the constructor.
+ virtual void Uninstalled(View* host);
+
+ // Notification that a view has been added.
+ virtual void ViewAdded(View* host, View* view);
+
+ // Notification that a view has been removed.
+ virtual void ViewRemoved(View* host, View* view);
+
+ // Layouts out the components.
+ virtual void Layout(View* host);
+
+ // Returns the preferred size for the GridLayout.
+ virtual gfx::Size GetPreferredSize(View* host);
+
+ virtual int GetPreferredHeightForWidth(View* host, int width);
+
+ private:
+ // As both Layout and GetPreferredSize need to do nearly the same thing,
+ // they both call into this method. This sizes the Columns/Rows as
+ // appropriate. If layout is true, width/height give the width/height the
+ // of the host, otherwise they are ignored.
+ void SizeRowsAndColumns(bool layout, int width, int height, gfx::Size* pref);
+
+ // Calculates the master columns of all the column sets. See Column for
+ // a description of what a master column is.
+ void CalculateMasterColumnsIfNecessary();
+
+ // This is called internally from AddView. It adds the ViewState to the
+ // appropriate structures, and updates internal fields such as next_column_.
+ void AddViewState(ViewState* view_state);
+
+ // Returns the column set for the specified id, or NULL if one doesn't exist.
+ ColumnSet* GetColumnSet(int id);
+
+ // Adds the Row to rows_, as well as updating next_column_,
+ // current_row_col_set ...
+ void AddRow(Row* row);
+
+ // As the name says, updates the remaining_height of the ViewState for
+ // all Rows the supplied ViewState touches.
+ void UpdateRemainingHeightFromRows(ViewState* state);
+
+ // If the view state's remaining height is > 0, it is distributed among
+ // the rows the view state touches. This is used during layout to make
+ // sure the Rows can accommodate a view.
+ void DistributeRemainingHeight(ViewState* state);
+
+ // Advances next_column_ past any padding columns.
+ void SkipPaddingColumns();
+
+ // Returns the column set of the last non-padding row.
+ ColumnSet* GetLastValidColumnSet();
+
+ // The view we were created with. We don't own this.
+ View* const host_;
+
+ // Whether or not we've calculated the master/linked columns.
+ bool calculated_master_columns_;
+
+ // Used to verify a view isn't added with a row span that expands into
+ // another column structure.
+ int remaining_row_span_;
+
+ // Current row.
+ int current_row_;
+
+ // Current column.
+ int next_column_;
+
+ // Column set for the current row. This is null for padding rows.
+ ColumnSet* current_row_col_set_;
+
+ // Insets.
+ int top_inset_;
+ int bottom_inset_;
+ int left_inset_;
+ int right_inset_;
+
+ // Set to true when adding a View.
+ bool adding_view_;
+
+ // ViewStates. This is ordered by row_span in ascending order.
+ std::vector<ViewState*> view_states_;
+
+ // ColumnSets.
+ std::vector<ColumnSet*> column_sets_;
+
+ // Rows.
+ std::vector<Row*> rows_;
+
+ DISALLOW_COPY_AND_ASSIGN(GridLayout);
+};
+
+// ColumnSet is used to define a set of columns. GridLayout may have any
+// number of ColumnSets. You don't create a ColumnSet directly, instead
+// use the AddColumnSet method of GridLayout.
+class VIEWS_EXPORT ColumnSet {
+ public:
+ ~ColumnSet();
+
+ // Adds a column for padding. When adding views, padding columns are
+ // automatically skipped. For example, if you create a column set with
+ // two columns separated by a padding column, the first AddView automatically
+ // skips past the padding column. That is, to add two views, do:
+ // layout->AddView(v1); layout->AddView(v2);, not:
+ // layout->AddView(v1); layout->SkipColumns(1); layout->AddView(v2);
+ void AddPaddingColumn(float resize_percent, int width);
+
+ // Adds a column. The alignment gives the default alignment for views added
+ // with no explicit alignment. fixed_width gives a specific width for the
+ // column, and is only used if size_type == FIXED. min_width gives the
+ // minimum width for the column.
+ //
+ // If none of the columns in a columnset are resizable, the views are only
+ // made as wide as the widest views in each column, even if extra space is
+ // provided. In other words, GridLayout does not automatically resize views
+ // unless the column is marked as resizable.
+ void AddColumn(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width);
+
+ // Forces the specified columns to have the same size. The size of
+ // linked columns is that of the max of the specified columns. This
+ // must end with -1. For example, the following forces the first and
+ // second column to have the same size:
+ // LinkColumnSizes(0, 1, -1);
+ void LinkColumnSizes(int first, ...);
+
+ // ID of this ColumnSet.
+ int id() const { return id_; }
+
+ int num_columns() { return static_cast<int>(columns_.size()); }
+
+ private:
+ friend class GridLayout;
+
+ explicit ColumnSet(int id);
+
+ void AddColumn(GridLayout::Alignment h_align,
+ GridLayout::Alignment v_align,
+ float resize_percent,
+ GridLayout::SizeType size_type,
+ int fixed_width,
+ int min_width,
+ bool is_padding);
+
+ void AddViewState(ViewState* view_state);
+
+ // Set description of these.
+ void CalculateMasterColumns();
+ void AccumulateMasterColumns();
+
+ // Sets the size of each linked column to be the same.
+ void UnifySameSizedColumnSizes();
+
+ // Updates the remaining width field of the ViewState from that of the
+ // columns the view spans.
+ void UpdateRemainingWidth(ViewState* view_state);
+
+ // Makes sure the columns touched by view state are big enough for the
+ // view.
+ void DistributeRemainingWidth(ViewState* view_state);
+
+ // Returns the total size needed for this ColumnSet.
+ int LayoutWidth();
+
+ // Returns the width of the specified columns.
+ int GetColumnWidth(int start_col, int col_span);
+
+ // Updates the x coordinate of each column from the previous ones.
+ // NOTE: this doesn't include the insets.
+ void ResetColumnXCoordinates();
+
+ // Calculate the preferred width of each view in this column set, as well
+ // as updating the remaining_width.
+ void CalculateSize();
+
+ // Distributes delta amoung the resizable columns.
+ void Resize(int delta);
+
+ // ID for this columnset.
+ const int id_;
+
+ // The columns.
+ std::vector<Column*> columns_;
+
+ // The ViewStates. This is sorted based on column_span in ascending
+ // order.
+ std::vector<ViewState*> view_states_;
+
+ // The master column of those columns that are linked. See Column
+ // for a description of what the master column is.
+ std::vector<Column*> master_columns_;
+
+ DISALLOW_COPY_AND_ASSIGN(ColumnSet);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_LAYOUT_GRID_LAYOUT_H_
diff --git a/ui/views/layout/grid_layout_unittest.cc b/ui/views/layout/grid_layout_unittest.cc
new file mode 100644
index 0000000..542d7c5
--- /dev/null
+++ b/ui/views/layout/grid_layout_unittest.cc
@@ -0,0 +1,606 @@
+// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h"
+#include "views/layout/grid_layout.h"
+#include "views/view.h"
+
+using views::ColumnSet;
+using views::GridLayout;
+using views::View;
+
+static void ExpectViewBoundsEquals(int x, int y, int w, int h,
+ const View* view) {
+ EXPECT_EQ(x, view->x());
+ EXPECT_EQ(y, view->y());
+ EXPECT_EQ(w, view->width());
+ EXPECT_EQ(h, view->height());
+}
+
+class SettableSizeView : public View {
+ public:
+ explicit SettableSizeView(const gfx::Size& pref) {
+ pref_ = pref;
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ return pref_;
+ }
+
+ private:
+ gfx::Size pref_;
+};
+
+// A view with fixed circumference that trades height for width.
+class FlexibleView : public View {
+ public:
+ explicit FlexibleView(int circumference) {
+ circumference_ = circumference;
+ }
+
+ virtual gfx::Size GetPreferredSize() {
+ return gfx::Size(0, circumference_ / 2);
+ }
+
+ virtual int GetHeightForWidth(int width) {
+ return std::max(0, circumference_ / 2 - width);
+ }
+
+ private:
+ int circumference_;
+};
+
+class GridLayoutTest : public testing::Test {
+ public:
+ virtual void SetUp() {
+ layout = new GridLayout(&host);
+ }
+
+ virtual void TearDown() {
+ delete layout;
+ }
+
+ virtual void RemoveAll() {
+ for (int i = host.child_count() - 1; i >= 0; i--)
+ host.RemoveChildView(host.child_at(i));
+ }
+
+ void GetPreferredSize() {
+ pref = layout->GetPreferredSize(&host);
+ }
+
+ gfx::Size pref;
+ gfx::Rect bounds;
+ View host;
+ GridLayout* layout;
+};
+
+class GridLayoutAlignmentTest : public testing::Test {
+ public:
+ GridLayoutAlignmentTest() :
+ host(),
+ v1(gfx::Size(10, 20)),
+ layout(new GridLayout(&host)) {}
+
+ virtual void SetUp() {
+ }
+
+ virtual void TearDown() {
+ delete layout;
+ }
+
+ virtual void RemoveAll() {
+ for (int i = host.child_count() - 1; i >= 0; i--)
+ host.RemoveChildView(host.child_at(i));
+ }
+
+ void TestAlignment(GridLayout::Alignment alignment, gfx::Rect* bounds) {
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(alignment, alignment, 1, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(1, 0);
+ layout->AddView(&v1);
+ gfx::Size pref = layout->GetPreferredSize(&host);
+ EXPECT_EQ(gfx::Size(10, 20), pref);
+ host.SetBounds(0, 0, 100, 100);
+ layout->Layout(&host);
+ *bounds = v1.bounds();
+ RemoveAll();
+ }
+
+ View host;
+ SettableSizeView v1;
+ GridLayout* layout;
+};
+
+TEST_F(GridLayoutAlignmentTest, Fill) {
+ gfx::Rect bounds;
+ TestAlignment(GridLayout::FILL, &bounds);
+ EXPECT_EQ(gfx::Rect(0, 0, 100, 100), bounds);
+}
+
+TEST_F(GridLayoutAlignmentTest, Leading) {
+ gfx::Rect bounds;
+ TestAlignment(GridLayout::LEADING, &bounds);
+ EXPECT_EQ(gfx::Rect(0, 0, 10, 20), bounds);
+}
+
+TEST_F(GridLayoutAlignmentTest, Center) {
+ gfx::Rect bounds;
+ TestAlignment(GridLayout::CENTER, &bounds);
+ EXPECT_EQ(gfx::Rect(45, 40, 10, 20), bounds);
+}
+
+TEST_F(GridLayoutAlignmentTest, Trailing) {
+ gfx::Rect bounds;
+ TestAlignment(GridLayout::TRAILING, &bounds);
+ EXPECT_EQ(gfx::Rect(90, 80, 10, 20), bounds);
+}
+
+TEST_F(GridLayoutTest, TwoColumns) {
+ SettableSizeView v1(gfx::Size(10, 20));
+ SettableSizeView v2(gfx::Size(20, 20));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(30, 20), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 10, 20, &v1);
+ ExpectViewBoundsEquals(10, 0, 20, 20, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan1) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(10, 40));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1, 2, 1);
+ layout->StartRow(0, 0);
+ layout->AddView(&v2);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(100, 60), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(0, 20, 10, 40, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan2) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(10, 20));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1, 2, 1);
+ layout->StartRow(0, 0);
+ layout->SkipColumns(1);
+ layout->AddView(&v2);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(100, 40), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(90, 20, 10, 20, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, ColSpan3) {
+ SettableSizeView v1(gfx::Size(100, 20));
+ SettableSizeView v2(gfx::Size(10, 20));
+ SettableSizeView v3(gfx::Size(10, 20));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1, 2, 1);
+ layout->StartRow(0, 0);
+ layout->AddView(&v2);
+ layout->AddView(&v3);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(100, 40), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(0, 20, 10, 20, &v2);
+ ExpectViewBoundsEquals(50, 20, 10, 20, &v3);
+
+ RemoveAll();
+}
+
+
+TEST_F(GridLayoutTest, ColSpan4) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0,
+ GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0,
+ GridLayout::USE_PREF, 0, 0);
+
+ SettableSizeView v1(gfx::Size(10, 10));
+ SettableSizeView v2(gfx::Size(10, 10));
+ SettableSizeView v3(gfx::Size(25, 20));
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+ layout->StartRow(0, 0);
+ layout->AddView(&v3, 2, 1);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(25, 30), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 10, 10, &v1);
+ ExpectViewBoundsEquals(12, 0, 10, 10, &v2);
+ ExpectViewBoundsEquals(0, 10, 25, 20, &v3);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, SameSizeColumns) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(10, 10));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ c1->LinkColumnSizes(0, 1, -1);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+
+ gfx::Size pref = layout->GetPreferredSize(&host);
+ EXPECT_EQ(gfx::Size(100, 20), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 50, 20, &v1);
+ ExpectViewBoundsEquals(50, 0, 10, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, HorizontalResizeTest1) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(10, 10));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::FILL, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+
+ host.SetBounds(0, 0, 110, 20);
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 100, 20, &v1);
+ ExpectViewBoundsEquals(100, 0, 10, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, HorizontalResizeTest2) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(10, 10));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::FILL, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ c1->AddColumn(GridLayout::TRAILING, GridLayout::LEADING,
+ 1, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+ layout->AddView(&v2);
+
+ host.SetBounds(0, 0, 120, 20);
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 80, 20, &v1);
+ ExpectViewBoundsEquals(110, 0, 10, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, TestVerticalResize1) {
+ SettableSizeView v1(gfx::Size(50, 20));
+ SettableSizeView v2(gfx::Size(10, 10));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ c1->AddColumn(GridLayout::FILL, GridLayout::FILL,
+ 1, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(1, 0);
+ layout->AddView(&v1);
+ layout->StartRow(0, 0);
+ layout->AddView(&v2);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(50, 30), pref);
+
+ host.SetBounds(0, 0, 50, 100);
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 50, 90, &v1);
+ ExpectViewBoundsEquals(0, 90, 50, 10, &v2);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, Insets) {
+ SettableSizeView v1(gfx::Size(10, 20));
+ ColumnSet* c1 = layout->AddColumnSet(0);
+ layout->SetInsets(1, 2, 3, 4);
+ c1->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ layout->StartRow(0, 0);
+ layout->AddView(&v1);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(16, 24), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(2, 1, 10, 20, &v1);
+
+ RemoveAll();
+}
+
+TEST_F(GridLayoutTest, FixedSize) {
+ layout->SetInsets(2, 2, 2, 2);
+
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ int column_count = 4;
+ int title_width = 100;
+ int row_count = 2;
+ int pref_width = 10;
+ int pref_height = 20;
+
+ for (int i = 0; i < column_count; ++i) {
+ set->AddColumn(views::GridLayout::CENTER,
+ views::GridLayout::CENTER,
+ 0,
+ views::GridLayout::FIXED,
+ title_width,
+ title_width);
+ }
+
+ for (int row = 0; row < row_count; ++row) {
+ layout->StartRow(0, 0);
+ for (int col = 0; col < column_count; ++col) {
+ layout->AddView(new SettableSizeView(gfx::Size(pref_width, pref_height)));
+ }
+ }
+
+ layout->Layout(&host);
+
+ for (int i = 0; i < column_count; ++i) {
+ for (int row = 0; row < row_count; ++row) {
+ View* view = host.child_at(row * column_count + i);
+ ExpectViewBoundsEquals(
+ 2 + title_width * i + (title_width - pref_width) / 2,
+ 2 + pref_height * row,
+ pref_width,
+ pref_height, view);
+ }
+ }
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(column_count * title_width + 4,
+ row_count * pref_height + 4), pref);
+}
+
+TEST_F(GridLayoutTest, RowSpanWithPaddingRow) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(views::GridLayout::CENTER,
+ views::GridLayout::CENTER,
+ 0,
+ views::GridLayout::FIXED,
+ 10,
+ 10);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(10, 10)), 1, 2);
+ layout->AddPaddingRow(0, 10);
+}
+
+TEST_F(GridLayoutTest, RowSpan) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::LEADING,
+ 0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+ set->AddColumn(views::GridLayout::LEADING,
+ views::GridLayout::LEADING,
+ 0,
+ views::GridLayout::USE_PREF,
+ 0,
+ 0);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(20, 10)));
+ layout->AddView(new SettableSizeView(gfx::Size(20, 40)), 1, 2);
+ layout->StartRow(1, 0);
+ views::View* s3 = new SettableSizeView(gfx::Size(20, 10));
+ layout->AddView(s3);
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(40, 40), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 10, 20, 10, s3);
+}
+
+TEST_F(GridLayoutTest, RowSpan2) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0,GridLayout::USE_PREF, 0, 0);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(20, 20)));
+ views::View* s3 = new SettableSizeView(gfx::Size(64, 64));
+ layout->AddView(s3, 1, 3);
+
+ layout->AddPaddingRow(0, 10);
+
+ layout->StartRow(0, 0);
+ layout->AddView(new SettableSizeView(gfx::Size(10, 20)));
+
+ GetPreferredSize();
+ EXPECT_EQ(gfx::Size(84, 64), pref);
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(20, 0, 64, 64, s3);
+}
+
+TEST_F(GridLayoutTest, FixedViewWidth) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0,GridLayout::USE_PREF, 0, 0);
+
+ layout->StartRow(0, 0);
+ View* view = new SettableSizeView(gfx::Size(30, 40));
+ layout->AddView(view, 1, 1, GridLayout::LEADING, GridLayout::LEADING, 10, 0);
+
+ GetPreferredSize();
+ EXPECT_EQ(10, pref.width());
+ EXPECT_EQ(40, pref.height());
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 10, 40, view);
+}
+
+TEST_F(GridLayoutTest, FixedViewHeight) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0, GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(GridLayout::LEADING, GridLayout::LEADING,
+ 0,GridLayout::USE_PREF, 0, 0);
+
+ layout->StartRow(0, 0);
+ View* view = new SettableSizeView(gfx::Size(30, 40));
+ layout->AddView(view, 1, 1, GridLayout::LEADING, GridLayout::LEADING, 0, 10);
+
+ GetPreferredSize();
+ EXPECT_EQ(30, pref.width());
+ EXPECT_EQ(10, pref.height());
+
+ host.SetBounds(0, 0, pref.width(), pref.height());
+ layout->Layout(&host);
+ ExpectViewBoundsEquals(0, 0, 30, 10, view);
+}
+
+// Make sure that for views that span columns the underlying columns are resized
+// based on the resize percent of the column.
+TEST_F(GridLayoutTest, ColumnSpanResizing) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 2, views::GridLayout::USE_PREF, 0, 0);
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 4, views::GridLayout::USE_PREF, 0, 0);
+
+ layout->StartRow(0, 0);
+ // span_view spans two columns and is twice as big the views added below.
+ View* span_view = new SettableSizeView(gfx::Size(12, 40));
+ layout->AddView(span_view, 2, 1, GridLayout::LEADING, GridLayout::LEADING);
+
+ layout->StartRow(0, 0);
+ View* view1 = new SettableSizeView(gfx::Size(2, 40));
+ View* view2 = new SettableSizeView(gfx::Size(4, 40));
+ layout->AddView(view1);
+ layout->AddView(view2);
+
+ host.SetBounds(0, 0, 12, 80);
+ layout->Layout(&host);
+
+ ExpectViewBoundsEquals(0, 0, 12, 40, span_view);
+
+ // view1 should be 4 pixels wide
+ // column_pref + (remaining_width * column_resize / total_column_resize) =
+ // 2 + (6 * 2 / 6).
+ ExpectViewBoundsEquals(0, 40, 4, 40, view1);
+
+ // And view2 should be 8 pixels wide:
+ // 4 + (6 * 4 / 6).
+ ExpectViewBoundsEquals(4, 40, 8, 40, view2);
+}
+
+// Check that GetPreferredSize() takes resizing of columns into account when
+// there is additional space in the case we have column sets of different
+// preferred sizes.
+TEST_F(GridLayoutTest, ColumnResizingOnGetPreferredSize) {
+ views::ColumnSet* set = layout->AddColumnSet(0);
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 1, views::GridLayout::USE_PREF, 0, 0);
+
+ set = layout->AddColumnSet(1);
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 1, views::GridLayout::USE_PREF, 0, 0);
+
+ set = layout->AddColumnSet(2);
+ set->AddColumn(views::GridLayout::FILL, views::GridLayout::CENTER,
+ 1, views::GridLayout::USE_PREF, 0, 0);
+
+ // Make a row containing a flexible view that trades width for height.
+ layout->StartRow(0, 0);
+ View* view1 = new FlexibleView(100);
+ layout->AddView(view1, 1, 1, GridLayout::FILL, GridLayout::LEADING);
+
+ // The second row contains a view of fixed size that will enforce a column
+ // width of 20 pixels.
+ layout->StartRow(0, 1);
+ View* view2 = new SettableSizeView(gfx::Size(20, 20));
+ layout->AddView(view2, 1, 1, GridLayout::FILL, GridLayout::LEADING);
+
+ // Add another flexible view in row three in order to ensure column set
+ // ordering doesn't influence sizing behaviour.
+ layout->StartRow(0, 2);
+ View* view3 = new FlexibleView(40);
+ layout->AddView(view3, 1, 1, GridLayout::FILL, GridLayout::LEADING);
+
+ // We expect a height of 50: 30 from the variable width view in the first row
+ // plus 20 from the statically sized view in the second row. The flexible
+ // view in the third row should contribute no height.
+ EXPECT_EQ(gfx::Size(20, 50), layout->GetPreferredSize(&host));
+}
diff --git a/ui/views/layout/layout_constants.h b/ui/views/layout/layout_constants.h
new file mode 100644
index 0000000..3d9b69a
--- /dev/null
+++ b/ui/views/layout/layout_constants.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2011 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 UI_VIEWS_LAYOUT_LAYOUT_CONSTANTS_H_
+#define UI_VIEWS_LAYOUT_LAYOUT_CONSTANTS_H_
+#pragma once
+
+// This file contains some constants we use to implement our standard panel
+// layout.
+// see: spec 21/4
+
+namespace views {
+
+// Left or right margin.
+const int kPanelHorizMargin = 13;
+
+// Top or bottom margin.
+const int kPanelVertMargin = 13;
+
+// If some UI has some sub UI. Indent horizontally by the following value.
+const int kPanelHorizIndentation = 24;
+
+// When several controls are aligned vertically, the baseline should be spaced
+// by the following number of pixels.
+const int kPanelVerticalSpacing = 32;
+
+// Vertical spacing between sub UI.
+const int kPanelSubVerticalSpacing = 24;
+
+// Vertical spacing between a label and some control.
+const int kLabelToControlVerticalSpacing = 8;
+
+// Small horizontal spacing between controls that are logically related.
+const int kRelatedControlSmallHorizontalSpacing = 8;
+
+// Horizontal spacing between controls that are logically related.
+const int kRelatedControlHorizontalSpacing = 8;
+
+// Vertical spacing between controls that are logically related.
+const int kRelatedControlVerticalSpacing = 8;
+
+// Small vertical spacing between controls that are logically related.
+const int kRelatedControlSmallVerticalSpacing = 4;
+
+// Horizontal spacing between controls that are logically unrelated.
+const int kUnrelatedControlHorizontalSpacing = 12;
+
+// Larger horizontal spacing between unrelated controls.
+const int kUnrelatedControlLargeHorizontalSpacing = 20;
+
+// Vertical spacing between controls that are logically unrelated.
+const int kUnrelatedControlVerticalSpacing = 20;
+
+// Larger vertical spacing between unrelated controls.
+const int kUnrelatedControlLargeVerticalSpacing = 30;
+
+// Vertical spacing between the edge of the window and the
+// top or bottom of a button.
+const int kButtonVEdgeMargin = 6;
+
+// Vertical spacing between the edge of the window and the
+// left or right of a button.
+const int kButtonHEdgeMargin = 7;
+
+// Horizontal spacing between buttons that are logically related.
+const int kRelatedButtonHSpacing = 6;
+
+} // namespace views
+
+#endif // UI_VIEWS_LAYOUT_LAYOUT_CONSTANTS_H_
diff --git a/ui/views/layout/layout_manager.cc b/ui/views/layout/layout_manager.cc
new file mode 100644
index 0000000..c62c6bb
--- /dev/null
+++ b/ui/views/layout/layout_manager.cc
@@ -0,0 +1,30 @@
+// Copyright (c) 2011 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/layout/layout_manager.h"
+
+#include "views/view.h"
+
+namespace views {
+
+LayoutManager::~LayoutManager() {
+}
+
+void LayoutManager::Installed(View* host) {
+}
+
+void LayoutManager::Uninstalled(View* host) {
+}
+
+int LayoutManager::GetPreferredHeightForWidth(View* host, int width) {
+ return GetPreferredSize(host).height();
+}
+
+void LayoutManager::ViewAdded(View* host, View* view) {
+}
+
+void LayoutManager::ViewRemoved(View* host, View* view) {
+}
+
+} // namespace views
diff --git a/ui/views/layout/layout_manager.h b/ui/views/layout/layout_manager.h
new file mode 100644
index 0000000..1241c80
--- /dev/null
+++ b/ui/views/layout/layout_manager.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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 UI_VIEWS_LAYOUT_LAYOUT_MANAGER_H_
+#define UI_VIEWS_LAYOUT_LAYOUT_MANAGER_H_
+#pragma once
+
+#include "views/views_export.h"
+
+namespace gfx {
+class Size;
+}
+
+namespace views {
+
+class View;
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// LayoutManager interface
+//
+// The LayoutManager interface provides methods to handle the sizing of
+// the children of a View according to implementation-specific heuristics.
+//
+/////////////////////////////////////////////////////////////////////////////
+class VIEWS_EXPORT LayoutManager {
+ public:
+ virtual ~LayoutManager();
+
+ // Notification that this LayoutManager has been installed on a particular
+ // host.
+ virtual void Installed(View* host);
+
+ // Notification that this LayoutManager has been uninstalled on a particular
+ // host.
+ virtual void Uninstalled(View* host);
+
+ // Lay out the children of |host| according to implementation-specific
+ // heuristics. The graphics used during painting is provided to allow for
+ // string sizing.
+ virtual void Layout(View* host) = 0;
+
+ // Return the preferred size which is the size required to give each
+ // children their respective preferred size.
+ virtual gfx::Size GetPreferredSize(View* host) = 0;
+
+ // Returns the preferred height for the specified width. The default
+ // implementation returns the value from GetPreferredSize.
+ virtual int GetPreferredHeightForWidth(View* host, int width);
+
+ // Notification that a view has been added.
+ virtual void ViewAdded(View* host, View* view);
+
+ // Notification that a view has been removed.
+ virtual void ViewRemoved(View* host, View* view);
+};
+
+} // namespace views
+
+#endif // UI_VIEWS_LAYOUT_LAYOUT_MANAGER_H_