summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui
diff options
context:
space:
mode:
authorgroby@chromium.org <groby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-14 04:49:09 +0000
committergroby@chromium.org <groby@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-14 04:49:09 +0000
commit36986daae4fb3e5875adc7cddca2fb704b9057dd (patch)
tree0f271a77e2371c5bea14b8efd1f93a0c844ccf5b /chrome/browser/ui
parent1a057065240b4e43c4ff54eb3ce7ac00a7deef9a (diff)
downloadchromium_src-36986daae4fb3e5875adc7cddca2fb704b9057dd.zip
chromium_src-36986daae4fb3e5875adc7cddca2fb704b9057dd.tar.gz
chromium_src-36986daae4fb3e5875adc7cddca2fb704b9057dd.tar.bz2
[Autofill] Add basic version of GridLayout
R=sail@chromium.org BUG=157274 Review URL: https://chromiumcodereview.appspot.com/14646021 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@199901 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/ui')
-rw-r--r--chrome/browser/ui/cocoa/autofill/layout_view.h34
-rw-r--r--chrome/browser/ui/cocoa/autofill/layout_view.mm32
-rw-r--r--chrome/browser/ui/cocoa/autofill/layout_view_unittest.mm65
-rw-r--r--chrome/browser/ui/cocoa/autofill/simple_grid_layout.h140
-rw-r--r--chrome/browser/ui/cocoa/autofill/simple_grid_layout.mm381
-rw-r--r--chrome/browser/ui/cocoa/autofill/simple_grid_layout_unittest.mm397
6 files changed, 1049 insertions, 0 deletions
diff --git a/chrome/browser/ui/cocoa/autofill/layout_view.h b/chrome/browser/ui/cocoa/autofill/layout_view.h
new file mode 100644
index 0000000..4a9dc58
--- /dev/null
+++ b/chrome/browser/ui/cocoa/autofill/layout_view.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COCOA_AUTOFILL_LAYOUT_VIEW_H_
+#define CHROME_BROWSER_UI_COCOA_AUTOFILL_LAYOUT_VIEW_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/scoped_ptr.h"
+
+class SimpleGridLayout;
+
+// A view that carries its own layout manager. Re-layouts on setFrame:.
+@interface LayoutView : NSView {
+ @private
+ scoped_ptr<SimpleGridLayout> layout_;
+}
+
+// Sets a layout manager and takes ownership of it.
+- (void)setLayoutManager:(scoped_ptr<SimpleGridLayout>)layout;
+
+// Return a pointer to layout manager, still owned by the view.
+- (SimpleGridLayout*)layoutManager;
+
+// Re-layout subviews according to layout manager.
+- (void)performLayout;
+
+// Query the layout manager for the best height for specified |width|.
+- (CGFloat)preferredHeightForWidth:(CGFloat)width;
+
+@end
+
+#endif // CHROME_BROWSER_UI_COCOA_AUTOFILL_LAYOUT_VIEW_H_
diff --git a/chrome/browser/ui/cocoa/autofill/layout_view.mm b/chrome/browser/ui/cocoa/autofill/layout_view.mm
new file mode 100644
index 0000000..04881e2
--- /dev/null
+++ b/chrome/browser/ui/cocoa/autofill/layout_view.mm
@@ -0,0 +1,32 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/ui/cocoa/autofill/layout_view.h"
+
+#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h"
+
+@implementation LayoutView
+
+- (void)setLayoutManager:(scoped_ptr<SimpleGridLayout>)layout {
+ layout_ = layout.Pass();
+}
+
+- (SimpleGridLayout*)layoutManager {
+ return layout_.get();
+}
+
+- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
+ [super resizeSubviewsWithOldSize:oldBoundsSize];
+ [self performLayout];
+}
+
+- (void)performLayout {
+ layout_->Layout(self);
+}
+
+- (CGFloat)preferredHeightForWidth:(CGFloat)width {
+ return layout_->GetPreferredHeightForWidth(width);
+}
+
+@end
diff --git a/chrome/browser/ui/cocoa/autofill/layout_view_unittest.mm b/chrome/browser/ui/cocoa/autofill/layout_view_unittest.mm
new file mode 100644
index 0000000..abfcd227
--- /dev/null
+++ b/chrome/browser/ui/cocoa/autofill/layout_view_unittest.mm
@@ -0,0 +1,65 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/ui/cocoa/autofill/layout_view.h"
+
+#include "base/memory/scoped_nsobject.h"
+#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+#import "ui/base/test/ui_cocoa_test_helper.h"
+
+namespace {
+
+class LayoutViewTest : public ui::CocoaTest {
+ public:
+ LayoutViewTest() {
+ CocoaTest::SetUp();
+ view_.reset([[LayoutView alloc] initWithFrame:NSZeroRect]);
+ [view_ setLayoutManager:
+ scoped_ptr<SimpleGridLayout>(new SimpleGridLayout(view_))];
+ layout_ = [view_ layoutManager];
+ [[test_window() contentView] addSubview:view_];
+ }
+
+ protected:
+ scoped_nsobject<LayoutView> view_;
+ SimpleGridLayout* layout_; // weak, owned by view_.
+};
+
+} // namespace
+
+TEST_VIEW(LayoutViewTest, view_)
+
+TEST_F(LayoutViewTest, setFrameInvokesLayout) {
+ EXPECT_FLOAT_EQ(0, NSWidth([view_ frame]));
+ EXPECT_FLOAT_EQ(0, NSHeight([view_ frame]));
+
+ ColumnSet* cs = layout_->AddColumnSet(0);
+ cs->AddColumn(0.4);
+ cs->AddColumn(0.6);
+ layout_->StartRow(0, 0);
+ scoped_nsobject<NSView> childView1(
+ [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 0, 45)]);
+ scoped_nsobject<NSView> childView2(
+ [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 20, 55)]);
+ layout_->AddView(childView1);
+ layout_->AddView(childView2);
+
+ ASSERT_EQ(2U, [[view_ subviews] count]);
+ EXPECT_FLOAT_EQ(0, NSWidth([view_ frame]));
+ EXPECT_FLOAT_EQ(0, NSHeight([view_ frame]));
+
+ [view_ setFrame:NSMakeRect(0, 0, 40, 60)];
+ EXPECT_FLOAT_EQ(40, NSWidth([view_ frame]));
+ EXPECT_FLOAT_EQ(60, NSHeight([view_ frame]));
+
+ NSView* subview = [[view_ subviews] objectAtIndex:0];
+ EXPECT_FLOAT_EQ(16, NSWidth([subview frame]));
+ EXPECT_FLOAT_EQ(55, NSHeight([subview frame]));
+
+ subview = [[view_ subviews] objectAtIndex:1];
+ EXPECT_FLOAT_EQ(24, NSWidth([subview frame]));
+ EXPECT_FLOAT_EQ(55, NSHeight([subview frame]));
+} \ No newline at end of file
diff --git a/chrome/browser/ui/cocoa/autofill/simple_grid_layout.h b/chrome/browser/ui/cocoa/autofill/simple_grid_layout.h
new file mode 100644
index 0000000..e3614a4
--- /dev/null
+++ b/chrome/browser/ui/cocoa/autofill/simple_grid_layout.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_COCOA_AUTOFILL_SIMPLE_GRID_LAYOUT_H_
+#define CHROME_BROWSER_UI_COCOA_AUTOFILL_SIMPLE_GRID_LAYOUT_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
+
+class Column;
+class ColumnSet;
+class Row;
+class ViewState;
+
+// SimpleGridLayout is a layout manager that positions child views in a grid.
+// Each row has exactly one ColumnSet, ColumnSets can be shared between rows.
+// See ui/views/layout/grid_layout.h for more details - this is a very
+// simplified version of the views class.
+// This is based on GridLayout in ui/views/layout/grid_layout.h.
+// TODO(groby): Unify both classes, create cross-platform abstraction.
+// http://crbug.com/240461
+class SimpleGridLayout {
+ public:
+ SimpleGridLayout(NSView* host);
+ ~SimpleGridLayout();
+
+ // Creates a new column set with the specified id and returns it.
+ // The id is later used when starting a new row.
+ // Layout takes ownership of the ColumnSet and will delete it when
+ // it is deleted.
+ ColumnSet* AddColumnSet(int id);
+
+ // Returns the column set for the specified id, or NULL if one doesn't exist.
+ ColumnSet* GetColumnSet(int id);
+
+ // Adds a padding row. Padding rows typically don't have any views, but are
+ // used to provide vertical white space between views.
+ // |size| specifies the height of the row.
+ void AddPaddingRow(int size);
+
+ // Starts a new row with the specified column set.
+ void StartRow(float vertical_resize, int column_set_id);
+
+ // This is a convenience function that starts a new row,
+ // and returns a new ColumnSet associated with it. All rows created by this
+ // will have a height of 0 and resize_percent set to 1.0.
+ ColumnSet* AddRow();
+
+ // Advances past columns. Use this when the current column should not
+ // contain any views.
+ void SkipColumns(int col_count);
+
+ // TODO(groby): This currently *must* be called after a StartRow for the row
+ // the view is in. At some point, I'd like an AddView that just populates
+ // the next available slot, if possible.
+ void AddView(NSView* view);
+
+ // Layout all contained views according to constraints.
+ void Layout(NSView* superView);
+
+ void SizeRowsAndColumns(float width);
+
+ // Advances next_column_ past any padding columns.
+ void SkipPaddingColumns();
+
+ // Returns the column set of the last non-padding row.
+ ColumnSet* GetLastValidColumnSet();
+
+ // Get the height of a given row.
+ float GetRowHeight(int row);
+
+ // Y-position for a given row.
+ float GetRowLocation(int row_index) const;
+
+ // Get the preferred height for the given width.
+ float GetPreferredHeightForWidth(float with);
+
+ int num_rows() const { return static_cast<int>(rows_.size()); }
+
+ // These functions are mostly for testing & deviate from Views Layout class.
+ int next_column() { return next_column_; }
+ void AdvanceColumn() { next_column_++; }
+
+ private:
+ // Adds a new row, updating associated counters and positions.
+ void AddRow(Row* row);
+
+ // Next column in the current ColumnSet.
+ int next_column_;
+
+ int current_auto_id_; // Starting value for autogenerated columnset ids.
+ ScopedVector<ViewState> view_states_;
+ ScopedVector<ColumnSet> column_sets_;
+ ScopedVector<Row> rows_;
+
+ NSView* host_;
+};
+
+// ColumnSet is used to define a set of columns.
+// You don't create a ColumnSet directly, instead use the AddRow method
+// of SimpleGridLayout.
+class ColumnSet {
+ public:
+ explicit ColumnSet(int id);
+ ~ColumnSet();
+
+ void AddPaddingColumn(int fixed_width);
+ void AddColumn(float resize_percent);
+
+ void CalculateSize(float width);
+ void ResetColumnXCoordinates();
+
+ // ID of this ColumnSet.
+ int id() const { return id_; }
+
+ int num_columns() const { return static_cast<int>(columns_.size()); }
+
+ // Returns the width of the specified columns.
+ float GetColumnWidth(int column);
+
+ Column* GetColumn(int column_index) {
+ DCHECK(column_index >=0 && column_index < num_columns());
+ return columns_[column_index];
+ }
+
+ // These functions are mostly for testing & deviate from Views Layout class.
+ float ColumnLocation(int column_index);
+
+ private:
+ float CalculateRemainingWidth(float width);
+ void DistributeRemainingWidth(float width);
+
+ ScopedVector<Column> columns_;
+ int id_;
+};
+
+#endif // CHROME_BROWSER_UI_COCOA_AUTOFILL_SIMPLE_GRID_LAYOUT_H_
diff --git a/chrome/browser/ui/cocoa/autofill/simple_grid_layout.mm b/chrome/browser/ui/cocoa/autofill/simple_grid_layout.mm
new file mode 100644
index 0000000..6de0bbf
--- /dev/null
+++ b/chrome/browser/ui/cocoa/autofill/simple_grid_layout.mm
@@ -0,0 +1,381 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "base/stl_util.h"
+
+namespace {
+const int kAutoColumnIdStart = 1000000; // Starting ID for autogeneration.
+}
+
+// Encapsulates state for a single NSView in the layout
+class ViewState {
+ public:
+ ViewState(NSView* view, ColumnSet* column_set, int row, int column);
+
+ // Gets the current width of the column associated with this view.
+ float GetColumnWidth();
+
+ // Get the preferred height for specified width.
+ float GetHeightForWidth(float with);
+
+ Column* GetColumn() const { return column_set_->GetColumn(column_); }
+
+ int row_index() { return row_; }
+ NSView* view() { return view_; }
+ float preferred_height() { return pref_height_; }
+ void set_preferred_height(float height) { pref_height_ = height; }
+
+ private:
+ NSView* view_;
+ ColumnSet* column_set_;
+ int row_;
+ int column_;
+ float pref_height_;
+};
+
+class LayoutElement {
+ public:
+ LayoutElement(float resize_percent, int fixed_width);
+ virtual ~LayoutElement() {}
+
+ template <class T>
+ static void ResetSizes(ScopedVector<T>* elements) {
+ // Reset the layout width of each column.
+ for (typename std::vector<T*>::iterator i = elements->begin();
+ i != elements->end(); ++i) {
+ (*i)->ResetSize();
+ }
+ }
+
+ template <class T>
+ static void CalculateLocationsFromSize(ScopedVector<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();
+ }
+ }
+
+ float Size() { return size_; }
+
+ void ResetSize() {
+ size_ = fixed_width_;
+ }
+
+ void SetSize(float size) {
+ size_ = size;
+ }
+
+ float Location() const {
+ return location_;
+ }
+
+ // Adjusts the size of this LayoutElement to be the max of the current size
+ // and the specified size.
+ virtual void AdjustSize(float size) {
+ size_ = std::max(size_, size);
+ }
+
+ void SetLocation(float location) {
+ location_ = location;
+ }
+
+ bool IsResizable() {
+ return resize_percent_ > 0.0f;
+ }
+
+ float ResizePercent() {
+ return resize_percent_;
+ }
+
+ private:
+ float resize_percent_;
+ int fixed_width_;
+ float size_;
+ float location_;
+};
+
+LayoutElement::LayoutElement(float resize_percent, int fixed_width)
+ : resize_percent_(resize_percent),
+ fixed_width_(fixed_width),
+ size_(0),
+ location_(0) {
+}
+
+class Column : public LayoutElement {
+ public:
+ Column(float resize_percent, int fixed_width, bool is_padding);
+
+ bool is_padding() { return is_padding_; }
+
+ private:
+ const bool is_padding_;
+};
+
+Column::Column(float resize_percent, int fixed_width, bool is_padding)
+ : LayoutElement(resize_percent, fixed_width),
+ is_padding_(is_padding) {
+}
+
+class Row : public LayoutElement {
+ public:
+ Row(float resize_percent, int fixed_height, ColumnSet* column_set);
+
+ ColumnSet* column_set() { return column_set_; }
+
+ private:
+ ColumnSet* column_set_;
+};
+
+Row::Row(float resize_percent, int fixed_height, ColumnSet* column_set)
+ : LayoutElement(resize_percent, fixed_height),
+ column_set_(column_set) {
+}
+
+ViewState::ViewState(NSView* view, ColumnSet* column_set, int row, int column)
+ : view_(view),
+ column_set_(column_set),
+ row_(row),
+ column_(column) {}
+
+float ViewState::GetColumnWidth() {
+ return column_set_->GetColumnWidth(column_);
+}
+
+float ViewState::GetHeightForWidth(float width) {
+ // NSView doesn't have any way to make height fit size, get frame height.
+ return NSHeight([view_ frame]);
+}
+
+ColumnSet::ColumnSet(int id) : id_(id) {
+}
+
+ColumnSet::~ColumnSet() {
+}
+
+void ColumnSet::AddPaddingColumn(int fixed_width) {
+ columns_.push_back(new Column(0.0f, fixed_width, true));
+}
+
+void ColumnSet::AddColumn(float resize_percent) {
+ columns_.push_back(new Column(resize_percent, 0, false));
+}
+
+void ColumnSet::CalculateSize(float width) {
+ // Reset column widths
+ LayoutElement::ResetSizes(&columns_);
+ width = CalculateRemainingWidth(width);
+ DistributeRemainingWidth(width);
+}
+
+void ColumnSet::ResetColumnXCoordinates() {
+ LayoutElement::CalculateLocationsFromSize(&columns_);
+}
+
+float ColumnSet::CalculateRemainingWidth(float width) {
+ for (size_t i = 0; i < columns_.size(); ++i)
+ width -= columns_[i]->Size();
+
+ return width;
+}
+
+void ColumnSet::DistributeRemainingWidth(float width) {
+ float total_resize = 0.0f;
+ int resizable_columns = 0.0;
+
+ for (size_t i = 0; i < columns_.size(); ++i) {
+ if (columns_[i]->IsResizable()) {
+ total_resize += columns_[i]->ResizePercent();
+ resizable_columns++;
+ }
+ }
+
+ float remaining_width = width;
+ for (size_t i = 0; i < columns_.size(); ++i) {
+ if (columns_[i]->IsResizable()) {
+ float delta = (resizable_columns == 0) ? remaining_width :
+ (width * columns_[i]->ResizePercent() / total_resize);
+ remaining_width -= delta;
+ columns_[i]->SetSize(columns_[i]->Size() + delta);
+ resizable_columns--;
+ }
+ }
+}
+
+float ColumnSet::GetColumnWidth(int column_index) {
+ if (column_index < 0 || column_index >= num_columns())
+ return 0.0;
+ return columns_[column_index]->Size();
+}
+
+float ColumnSet::ColumnLocation(int column_index) {
+ if (column_index < 0 || column_index >= num_columns())
+ return 0.0;
+ return columns_[column_index]->Location();
+}
+
+SimpleGridLayout::SimpleGridLayout(NSView* host)
+ : next_column_(0),
+ current_auto_id_(kAutoColumnIdStart),
+ host_(host) {
+ [host_ frame];
+}
+
+SimpleGridLayout::~SimpleGridLayout() {
+}
+
+ColumnSet* SimpleGridLayout::AddColumnSet(int id) {
+ DCHECK(GetColumnSet(id) == NULL);
+ ColumnSet* column_set = new ColumnSet(id);
+ column_sets_.push_back(column_set);
+ return column_set;
+}
+
+ColumnSet* SimpleGridLayout::GetColumnSet(int id) {
+ for (ScopedVector<ColumnSet>::const_iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ if ((*i)->id() == id) {
+ return *i;
+ }
+ }
+ return NULL;
+}
+
+void SimpleGridLayout::AddPaddingRow(int fixed_height) {
+ AddRow(new Row(0.0f, fixed_height, NULL));
+}
+
+void SimpleGridLayout::StartRow(float vertical_resize, int column_set_id) {
+ ColumnSet* column_set = GetColumnSet(column_set_id);
+ DCHECK(column_set);
+ AddRow(new Row(vertical_resize, 0, column_set));
+}
+
+ColumnSet* SimpleGridLayout::AddRow() {
+ AddRow(new Row(0, 0, AddColumnSet(current_auto_id_++)));
+ return column_sets_.back();
+}
+
+void SimpleGridLayout::SkipColumns(int col_count) {
+ DCHECK(col_count > 0);
+ next_column_ += col_count;
+ ColumnSet* current_row_col_set_ = GetLastValidColumnSet();
+ DCHECK(current_row_col_set_ &&
+ next_column_ <= current_row_col_set_->num_columns());
+ SkipPaddingColumns();
+}
+
+void SimpleGridLayout::AddView(NSView* view) {
+ [host_ addSubview:view];
+ DCHECK(next_column_ < GetLastValidColumnSet()->num_columns());
+ view_states_.push_back(
+ new ViewState(view,
+ GetLastValidColumnSet(),
+ rows_.size() - 1,
+ next_column_++));
+ SkipPaddingColumns();
+}
+
+// Sizes elements to fit into the superViews bounds, according to constraints.
+void SimpleGridLayout::Layout(NSView* superView) {
+ SizeRowsAndColumns(NSWidth([superView bounds]));
+ for (std::vector<ViewState*>::iterator i = view_states_.begin();
+ i != view_states_.end(); ++i) {
+ ViewState* view_state = *i;
+ NSView* view = view_state->view();
+ NSRect frame = NSMakeRect(view_state->GetColumn()->Location(),
+ rows_[view_state->row_index()]->Location(),
+ view_state->GetColumn()->Size(),
+ rows_[view_state->row_index()]->Size());
+ [view setFrame:frame];
+ }
+}
+
+void SimpleGridLayout::SizeRowsAndColumns(float width) {
+ // Size all columns first.
+ for (ScopedVector<ColumnSet>::iterator i = column_sets_.begin();
+ i != column_sets_.end(); ++i) {
+ (*i)->CalculateSize(width);
+ (*i)->ResetColumnXCoordinates();
+ }
+
+ // Reset the height of each row.
+ LayoutElement::ResetSizes(&rows_);
+
+ // For each ViewState, obtain the preferred height
+ for (std::vector<ViewState*>::iterator i= view_states_.begin();
+ i != view_states_.end() ; ++i) {
+ ViewState* view_state = *i;
+
+ // The view is resizable. As the pref height may vary with the width,
+ // ask for the pref again.
+ int actual_width = view_state->GetColumnWidth();
+
+ // The width this view will get differs from its preferred. Some Views
+ // pref height varies with its width; ask for the preferred again.
+ view_state->set_preferred_height(
+ view_state->GetHeightForWidth(actual_width));
+ }
+
+
+ // Make sure each row can accommodate all contained ViewStates.
+ std::vector<ViewState*>::iterator view_states_iterator = view_states_.begin();
+ for (; view_states_iterator != view_states_.end(); ++view_states_iterator) {
+ ViewState* view_state = *view_states_iterator;
+ Row* row = rows_[view_state->row_index()];
+ row->AdjustSize(view_state->preferred_height());
+ }
+
+ // Update the location of each of the rows.
+ LayoutElement::CalculateLocationsFromSize(&rows_);
+}
+
+void SimpleGridLayout::SkipPaddingColumns() {
+ ColumnSet* current_row_col_set_ = GetLastValidColumnSet();
+ while (next_column_ < current_row_col_set_->num_columns() &&
+ current_row_col_set_->GetColumn(next_column_)->is_padding()) {
+ next_column_++;
+ }
+}
+
+ColumnSet* SimpleGridLayout::GetLastValidColumnSet() {
+ for (int i = num_rows() - 1; i >= 0; --i) {
+ if (rows_[i]->column_set())
+ return rows_[i]->column_set();
+ }
+ return NULL;
+}
+
+float SimpleGridLayout::GetRowHeight(int row_index) {
+ if (row_index < 0 || row_index >= num_rows())
+ return 0.0;
+ return rows_[row_index]->Size();
+}
+
+float SimpleGridLayout::GetRowLocation(int row_index) const {
+ if (row_index < 0 || row_index >= num_rows())
+ return 0.0;
+ return rows_[row_index]->Location();
+}
+
+float SimpleGridLayout::GetPreferredHeightForWidth(float width) {
+ if (rows_.empty())
+ return 0.0f;
+
+ SizeRowsAndColumns(width);
+ return rows_.back()->Location() + rows_.back()->Size();
+}
+
+void SimpleGridLayout::AddRow(Row* row) {
+ next_column_ = 0;
+ rows_.push_back(row);
+}
+
diff --git a/chrome/browser/ui/cocoa/autofill/simple_grid_layout_unittest.mm b/chrome/browser/ui/cocoa/autofill/simple_grid_layout_unittest.mm
new file mode 100644
index 0000000..d529211
--- /dev/null
+++ b/chrome/browser/ui/cocoa/autofill/simple_grid_layout_unittest.mm
@@ -0,0 +1,397 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_nsobject.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Layout objects as multiple columns in a single row.
+class ColumnLayout : public SimpleGridLayout {
+ public:
+ ColumnLayout(NSView* host) : SimpleGridLayout(host) { AddRow(); }
+ ~ColumnLayout() {}
+};
+
+class LayoutTest : public testing::Test {
+ public:
+ scoped_nsobject<NSView> CreateViewWithWidth(float width) {
+ NSRect frame = NSMakeRect(0, 0, width, 0);
+ scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:frame]);
+ return view;
+ }
+
+ scoped_nsobject<NSView> CreateViewWithHeight(float height) {
+ NSRect frame = NSMakeRect(0, 0, 0, height);
+ scoped_nsobject<NSView> view([[NSView alloc] initWithFrame:frame]);
+ return view;
+ }
+};
+
+TEST_F(LayoutTest, GridLayoutAddGetColumnSet) {
+ SimpleGridLayout layout(NULL);
+
+ // Non-existing id's are not found.
+ EXPECT_EQ(0L, layout.GetColumnSet(1));
+
+ // Created ColumnSets can be retrieved.
+ ColumnSet* set1 = layout.AddColumnSet(10);
+ ColumnSet* set2 = layout.AddColumnSet(11);
+ ColumnSet* set3 = layout.AddColumnSet(12);
+ EXPECT_EQ(set2, layout.GetColumnSet(11));
+ EXPECT_EQ(set1, layout.GetColumnSet(10));
+ EXPECT_EQ(set3, layout.GetColumnSet(12));
+}
+
+TEST_F(LayoutTest, AddRowAutogeneratesColumnSetId) {
+ SimpleGridLayout layout(NULL);
+
+ ColumnSet* set1 = layout.AddRow();
+ EXPECT_NE(0, set1->id());
+
+ ColumnSet* set2 = layout.AddRow();
+ EXPECT_NE(set1->id(), set2->id());
+}
+
+TEST_F(LayoutTest, AddRowResetsNextColumn) {
+ SimpleGridLayout layout(NULL);
+ layout.AddRow();
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.AddView(nil);
+ EXPECT_EQ(1, layout.next_column());
+
+ layout.AddRow();
+ EXPECT_EQ(0, layout.next_column());
+}
+
+TEST_F(LayoutTest, GetLastValidColumnSet) {
+ SimpleGridLayout layout(NULL);
+
+ EXPECT_EQ(NULL, layout.GetLastValidColumnSet());
+
+ ColumnSet* cs1 = layout.AddColumnSet(1);
+ ColumnSet* cs2 = layout.AddColumnSet(2);
+ layout.StartRow(0.0, 2);
+ EXPECT_EQ(cs2, layout.GetLastValidColumnSet());
+
+ layout.AddPaddingRow(10);
+ EXPECT_EQ(cs2, layout.GetLastValidColumnSet());
+
+ layout.StartRow(0.0, 1);
+ EXPECT_EQ(cs1, layout.GetLastValidColumnSet());
+}
+
+
+TEST_F(LayoutTest, AddRowCreatesNewColumnSet) {
+ SimpleGridLayout layout(NULL);
+
+ EXPECT_EQ(NULL, layout.GetLastValidColumnSet());
+
+ ColumnSet* old_column_set = layout.AddRow();
+ EXPECT_TRUE(old_column_set != NULL);
+ EXPECT_EQ(old_column_set, layout.GetLastValidColumnSet());
+
+ ColumnSet* new_column_set = layout.AddRow();
+ EXPECT_TRUE(new_column_set != NULL);
+ EXPECT_EQ(new_column_set, layout.GetLastValidColumnSet());
+}
+
+TEST_F(LayoutTest, AddPaddingRow) {
+ SimpleGridLayout layout(NULL);
+
+ ColumnSet* cs;
+ cs = layout.AddRow();
+ cs->AddColumn(1.0f);
+ scoped_nsobject<NSView> view1 = CreateViewWithHeight(30.0f);
+ layout.AddView(view1);
+
+ layout.AddPaddingRow(20);
+
+ cs = layout.AddRow();
+ cs->AddColumn(1.0f);
+ EXPECT_EQ(3, layout.num_rows());
+ scoped_nsobject<NSView> view2 = CreateViewWithHeight(10.0f);
+ layout.AddView(view2);
+
+ layout.SizeRowsAndColumns(0.0f);
+ EXPECT_FLOAT_EQ(30.0f, layout.GetRowHeight(0));
+ EXPECT_FLOAT_EQ(20.0f, layout.GetRowHeight(1));
+ EXPECT_FLOAT_EQ(10.0f, layout.GetRowHeight(2));
+}
+
+TEST_F(LayoutTest, RowsAccomodateHeightOfAllElements) {
+ SimpleGridLayout layout(NULL);
+
+ layout.AddRow();
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+
+ scoped_nsobject<NSView> view1 = CreateViewWithHeight(37.0f);
+ layout.AddView(view1);
+ layout.SizeRowsAndColumns(0.0f);
+ EXPECT_FLOAT_EQ(37.0f, layout.GetRowHeight(0));
+
+ scoped_nsobject<NSView> view2 = CreateViewWithHeight(26.0f);
+ layout.AddView(view2);
+ layout.SizeRowsAndColumns(0.0f);
+ EXPECT_FLOAT_EQ(37.0f, layout.GetRowHeight(0));
+
+ scoped_nsobject<NSView> view3 = CreateViewWithHeight(42.0f);
+ layout.AddView(view3);
+ layout.SizeRowsAndColumns(0.0f);
+ EXPECT_FLOAT_EQ(42.0f, layout.GetRowHeight(0));
+}
+
+TEST_F(LayoutTest, SizeRowsAdjustsRowLocations) {
+ SimpleGridLayout layout(NULL);
+
+ layout.AddRow();
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ scoped_nsobject<NSView> view1 = CreateViewWithHeight(30.0f);
+ layout.AddView(view1);
+
+ layout.AddRow();
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ scoped_nsobject<NSView> view2 = CreateViewWithHeight(40.0f);
+ layout.AddView(view2);
+
+ layout.SizeRowsAndColumns(0.0f);
+ EXPECT_FLOAT_EQ(0.0f, layout.GetRowLocation(0));
+ EXPECT_FLOAT_EQ(30.0f, layout.GetRowLocation(1));
+}
+
+TEST_F(LayoutTest, SimpleGridLayoutAdjustsViews) {
+ SimpleGridLayout layout(NULL);
+
+ layout.AddRow();
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddPaddingColumn(30);
+ layout.GetLastValidColumnSet()->AddColumn(0.4f);
+
+ scoped_nsobject<NSView> view1 = CreateViewWithHeight(22.0f);
+ scoped_nsobject<NSView> view2 = CreateViewWithHeight(20.0f);
+ layout.AddView(view1);
+ layout.AddView(view2);
+
+ layout.AddRow();
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ scoped_nsobject<NSView> view3 = CreateViewWithHeight(18.0f);
+ layout.AddView(view3);
+
+ scoped_nsobject<NSView> host_view = CreateViewWithWidth(150.0f);
+ layout.Layout(host_view);
+
+ EXPECT_FLOAT_EQ(72.0f, NSWidth([view1 frame]));
+ EXPECT_FLOAT_EQ(22.0f, NSHeight([view1 frame]));
+ EXPECT_FLOAT_EQ(0.0f, NSMinX([view1 frame]));
+ EXPECT_FLOAT_EQ(0.0f, NSMinY([view1 frame]));
+
+ EXPECT_FLOAT_EQ(48.0f, NSWidth([view2 frame]));
+ EXPECT_FLOAT_EQ(22.0f, NSHeight([view2 frame]));
+ EXPECT_FLOAT_EQ(102.0f, NSMinX([view2 frame]));
+ EXPECT_FLOAT_EQ(0.0f, NSMinY([view2 frame]));
+
+ EXPECT_FLOAT_EQ(150.0f, NSWidth([view3 frame]));
+ EXPECT_FLOAT_EQ(18.0f, NSHeight([view3 frame]));
+ EXPECT_FLOAT_EQ(0.0f, NSMinX([view3 frame]));
+ EXPECT_FLOAT_EQ(22.0f, NSMinY([view3 frame]));
+}
+
+TEST_F(LayoutTest, PreferredHeightForEmptyLayout) {
+ SimpleGridLayout layout(NULL);
+ EXPECT_FLOAT_EQ(0.0f, layout.GetPreferredHeightForWidth(100.0));
+}
+
+TEST_F(LayoutTest, SimpleGridLayoutHeightForWidth)
+{
+ SimpleGridLayout layout(NULL);
+ layout.AddRow();
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+
+ scoped_nsobject<NSView> view1 = CreateViewWithHeight(22.0f);
+ layout.AddView(view1);
+ EXPECT_FLOAT_EQ(22.0f, layout.GetPreferredHeightForWidth(100.0));
+
+ layout.AddPaddingRow(12);
+ EXPECT_FLOAT_EQ(34.0f, layout.GetPreferredHeightForWidth(100.0));
+
+ layout.AddRow();
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ scoped_nsobject<NSView> view2 = CreateViewWithHeight(13.0f);
+ layout.AddView(view2);
+ EXPECT_FLOAT_EQ(47.0f, layout.GetPreferredHeightForWidth(100.0));
+}
+
+TEST_F(LayoutTest, SkipColumns) {
+ ColumnLayout layout(NULL);
+
+ ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddColumn(0.4f);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddPaddingColumn(30);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+
+ // Skip a single column
+ EXPECT_EQ(0, layout.next_column());
+ layout.SkipColumns(1);
+ EXPECT_EQ(1, layout.next_column());
+
+ // Skip multiple columns
+ layout.SkipColumns(2);
+ EXPECT_EQ(3, layout.next_column());
+
+ // SkipColumns skips padding, too
+ layout.SkipColumns(1);
+ EXPECT_EQ(5, layout.next_column());
+}
+
+TEST_F(LayoutTest, SkipPaddingColumns) {
+ ColumnLayout layout(NULL);
+
+ ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddPaddingColumn(30);
+ layout.GetLastValidColumnSet()->AddColumn(0.4f);
+ layout.GetLastValidColumnSet()->AddPaddingColumn(30);
+ layout.GetLastValidColumnSet()->AddPaddingColumn(30);
+ layout.GetLastValidColumnSet()->AddColumn(0.4f);
+
+ // Don't skip over non-padding columns.
+ EXPECT_EQ(0, layout.next_column());
+ layout.SkipPaddingColumns();
+ EXPECT_EQ(0, layout.next_column());
+
+ // Skip a single padding column.
+ layout.AdvanceColumn();
+ EXPECT_EQ(1, layout.next_column());
+ layout.SkipPaddingColumns();
+ EXPECT_EQ(2, layout.next_column());
+
+ // Skip multiple padding columns.
+ layout.AdvanceColumn();
+ EXPECT_EQ(3, layout.next_column());
+ layout.SkipPaddingColumns();
+ EXPECT_EQ(5, layout.next_column());
+}
+
+TEST_F(LayoutTest, SizeColumnsAdjustLocationAndSize) {
+ ColumnLayout layout(NULL);
+
+ ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddPaddingColumn(30);
+ layout.GetLastValidColumnSet()->AddColumn(0.4f);
+
+ layout.SizeRowsAndColumns(130.0f);
+ EXPECT_FLOAT_EQ(0.0f, layout.GetLastValidColumnSet()->ColumnLocation(0));
+ EXPECT_FLOAT_EQ(60.0f, layout.GetLastValidColumnSet()->GetColumnWidth(0));
+ EXPECT_FLOAT_EQ(60.0f, layout.GetLastValidColumnSet()->ColumnLocation(1));
+ EXPECT_FLOAT_EQ(30.0f, layout.GetLastValidColumnSet()->GetColumnWidth(1));
+ EXPECT_FLOAT_EQ(90.0f, layout.GetLastValidColumnSet()->ColumnLocation(2));
+ EXPECT_FLOAT_EQ(40.0f, layout.GetLastValidColumnSet()->GetColumnWidth(2));
+}
+
+TEST_F(LayoutTest, AddViewSkipsPaddingColumns) {
+ ColumnLayout layout(NULL);
+
+ ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddPaddingColumn(30);
+ layout.GetLastValidColumnSet()->AddColumn(0.4f);
+
+ EXPECT_EQ(0, layout.next_column());
+ scoped_nsobject<NSView> view1 = CreateViewWithWidth(37.0f);
+ layout.AddView(view1);
+ EXPECT_EQ(2, layout.next_column());
+}
+
+TEST_F(LayoutTest, ColumnLayoutAdjustsViews) {
+ ColumnLayout layout(NULL);
+
+ ASSERT_TRUE(layout.GetLastValidColumnSet() != NULL);
+ layout.GetLastValidColumnSet()->AddColumn(0.6f);
+ layout.GetLastValidColumnSet()->AddPaddingColumn(30);
+ layout.GetLastValidColumnSet()->AddColumn(0.4f);
+
+ scoped_nsobject<NSView> view1 = CreateViewWithWidth(37.0f);
+ scoped_nsobject<NSView> view2 = CreateViewWithWidth(42.0f);
+ layout.AddView(view1);
+ layout.AddView(view2);
+
+ scoped_nsobject<NSView> host_view = CreateViewWithWidth(150.0f);
+ layout.Layout(host_view);
+
+ EXPECT_FLOAT_EQ(72.0f, NSWidth([view1 frame]));
+ EXPECT_FLOAT_EQ(48.0f, NSWidth([view2 frame]));
+}
+
+TEST_F(LayoutTest, ColumnSetAddPadding) {
+ ColumnSet columns(0);
+
+ columns.AddPaddingColumn(20);
+ EXPECT_EQ(1, columns.num_columns());
+
+ columns.CalculateSize(20.0f);
+ EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(0));
+
+ columns.CalculateSize(120.0f);
+ EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(0));
+}
+
+TEST_F(LayoutTest, ColumnSetOneFlexibleColumn) {
+ ColumnSet columns(0);
+
+ columns.AddColumn(1.0f);
+ EXPECT_EQ(1, columns.num_columns());
+
+ columns.CalculateSize(20.0f);
+ EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(0));
+
+ columns.CalculateSize(120.0f);
+ EXPECT_FLOAT_EQ(120.0f, columns.GetColumnWidth(0));
+}
+
+TEST_F(LayoutTest, ColumnSetTwoFlexibleColumns) {
+ ColumnSet columns(0);
+
+ columns.AddColumn(0.6f);
+ columns.AddColumn(0.4f);
+ EXPECT_EQ(2, columns.num_columns());
+
+ columns.CalculateSize(100.0f);
+ EXPECT_FLOAT_EQ(60.0f, columns.GetColumnWidth(0));
+ EXPECT_FLOAT_EQ(40.0f, columns.GetColumnWidth(1));
+
+ columns.CalculateSize(150.0f);
+ EXPECT_FLOAT_EQ(90.0f, columns.GetColumnWidth(0));
+ EXPECT_FLOAT_EQ(60.0f, columns.GetColumnWidth(1));
+}
+
+TEST_F(LayoutTest, ColumnSetMixedColumns) {
+ ColumnSet columns(0);
+
+ columns.AddColumn(0.6f);
+ columns.AddPaddingColumn(20);
+ columns.AddColumn(0.4f);
+ EXPECT_EQ(3, columns.num_columns());
+
+ columns.CalculateSize(100.0f);
+ EXPECT_FLOAT_EQ(48.0f, columns.GetColumnWidth(0));
+ EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(1));
+ EXPECT_FLOAT_EQ(32.0f, columns.GetColumnWidth(2));
+
+ columns.CalculateSize(200.0f);
+ EXPECT_FLOAT_EQ(108.0f, columns.GetColumnWidth(0));
+ EXPECT_FLOAT_EQ(20.0f, columns.GetColumnWidth(1));
+ EXPECT_FLOAT_EQ(72.0f, columns.GetColumnWidth(2));
+}
+
+} // namespace