1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/cocoa/autofill/simple_grid_layout.h"
6
7#include <algorithm>
8
9#include "base/logging.h"
10#include "base/stl_util.h"
11
12namespace {
13const int kAutoColumnIdStart = 1000000;  // Starting ID for autogeneration.
14}
15
16// Encapsulates state for a single NSView in the layout
17class ViewState {
18 public:
19  ViewState(NSView* view, ColumnSet* column_set, int row, int column);
20
21  // Gets the current width of the column associated with this view.
22  float GetColumnWidth();
23
24  // Get the preferred height for specified width.
25  float GetHeightForWidth(float with);
26
27  Column* GetColumn() const { return column_set_->GetColumn(column_); }
28
29  int row_index()  { return row_; }
30  NSView* view() { return view_; }
31  float preferred_height() { return pref_height_; }
32  void set_preferred_height(float height) { pref_height_ = height; }
33
34 private:
35  NSView* view_;
36  ColumnSet* column_set_;
37  int row_;
38  int column_;
39  float pref_height_;
40};
41
42class LayoutElement {
43 public:
44  LayoutElement(float resize_percent, int fixed_width);
45  virtual ~LayoutElement() {}
46
47  template <class T>
48      static void ResetSizes(ScopedVector<T>* elements) {
49    // Reset the layout width of each column.
50    for (typename std::vector<T*>::iterator i = elements->begin();
51         i != elements->end(); ++i) {
52      (*i)->ResetSize();
53    }
54  }
55
56  template <class T>
57      static void CalculateLocationsFromSize(ScopedVector<T>* elements) {
58    // Reset the layout width of each column.
59    int location = 0;
60    for (typename std::vector<T*>::iterator i = elements->begin();
61         i != elements->end(); ++i) {
62      (*i)->SetLocation(location);
63      location += (*i)->Size();
64    }
65  }
66
67  float Size() { return size_; }
68
69  void ResetSize() {
70      size_ = fixed_width_;
71  }
72
73  void SetSize(float size) {
74    size_ = size;
75  }
76
77  float Location() const {
78    return location_;
79  }
80
81  // Adjusts the size of this LayoutElement to be the max of the current size
82  // and the specified size.
83  virtual void AdjustSize(float size) {
84    size_ = std::max(size_, size);
85  }
86
87  void SetLocation(float location) {
88    location_ = location;
89  }
90
91  bool IsResizable() {
92    return resize_percent_ > 0.0f;
93  }
94
95  float ResizePercent() {
96    return resize_percent_;
97  }
98
99 private:
100  float resize_percent_;
101  int fixed_width_;
102  float size_;
103  float location_;
104};
105
106LayoutElement::LayoutElement(float resize_percent, int fixed_width)
107    : resize_percent_(resize_percent),
108      fixed_width_(fixed_width),
109      size_(0),
110      location_(0) {
111}
112
113class Column : public LayoutElement {
114 public:
115   Column(float resize_percent, int fixed_width, bool is_padding);
116
117   bool is_padding() { return is_padding_; }
118
119  private:
120    const bool is_padding_;
121};
122
123Column::Column(float resize_percent, int fixed_width, bool is_padding)
124    : LayoutElement(resize_percent, fixed_width),
125      is_padding_(is_padding) {
126}
127
128class Row : public LayoutElement {
129 public:
130  Row(float resize_percent, int fixed_height, ColumnSet* column_set);
131
132  ColumnSet* column_set() { return column_set_; }
133
134 private:
135  ColumnSet* column_set_;
136};
137
138Row::Row(float resize_percent, int fixed_height, ColumnSet* column_set)
139    : LayoutElement(resize_percent, fixed_height),
140      column_set_(column_set) {
141}
142
143ViewState::ViewState(NSView* view, ColumnSet* column_set, int row, int column)
144    : view_(view),
145      column_set_(column_set),
146      row_(row),
147      column_(column) {}
148
149float ViewState::GetColumnWidth() {
150  return column_set_->GetColumnWidth(column_);
151}
152
153float ViewState::GetHeightForWidth(float width) {
154  // NSView doesn't have any way to make height fit size, get frame height.
155  return NSHeight([view_ frame]);
156}
157
158ColumnSet::ColumnSet(int id) : id_(id) {
159}
160
161ColumnSet::~ColumnSet() {
162}
163
164void ColumnSet::AddPaddingColumn(int fixed_width) {
165  columns_.push_back(new Column(0.0f, fixed_width, true));
166}
167
168void ColumnSet::AddColumn(float resize_percent) {
169  columns_.push_back(new Column(resize_percent, 0, false));
170}
171
172void ColumnSet::CalculateSize(float width) {
173  // Reset column widths
174  LayoutElement::ResetSizes(&columns_);
175  width = CalculateRemainingWidth(width);
176  DistributeRemainingWidth(width);
177}
178
179void ColumnSet::ResetColumnXCoordinates() {
180  LayoutElement::CalculateLocationsFromSize(&columns_);
181}
182
183float ColumnSet::CalculateRemainingWidth(float width) {
184  for (size_t i = 0; i < columns_.size(); ++i)
185    width -= columns_[i]->Size();
186
187  return width;
188}
189
190void ColumnSet::DistributeRemainingWidth(float width) {
191  float total_resize = 0.0f;
192  int resizable_columns = 0.0;
193
194  for (size_t i = 0; i < columns_.size(); ++i) {
195    if (columns_[i]->IsResizable()) {
196      total_resize += columns_[i]->ResizePercent();
197      resizable_columns++;
198    }
199  }
200
201  float remaining_width = width;
202  for (size_t i = 0; i < columns_.size(); ++i) {
203    if (columns_[i]->IsResizable()) {
204      float delta = (resizable_columns == 0) ? remaining_width :
205          (width * columns_[i]->ResizePercent() / total_resize);
206      remaining_width -= delta;
207      columns_[i]->SetSize(columns_[i]->Size() + delta);
208      resizable_columns--;
209    }
210  }
211}
212
213float ColumnSet::GetColumnWidth(int column_index) {
214  if (column_index < 0 || column_index >= num_columns())
215    return 0.0;
216  return columns_[column_index]->Size();
217}
218
219float ColumnSet::ColumnLocation(int column_index) {
220  if (column_index < 0 || column_index >= num_columns())
221    return 0.0;
222  return columns_[column_index]->Location();
223}
224
225SimpleGridLayout::SimpleGridLayout(NSView* host)
226    : next_column_(0),
227      current_auto_id_(kAutoColumnIdStart),
228      host_(host) {
229  [host_ frame];
230}
231
232SimpleGridLayout::~SimpleGridLayout() {
233}
234
235ColumnSet* SimpleGridLayout::AddColumnSet(int id) {
236  DCHECK(GetColumnSet(id) == NULL);
237  ColumnSet* column_set = new ColumnSet(id);
238  column_sets_.push_back(column_set);
239  return column_set;
240}
241
242ColumnSet* SimpleGridLayout::GetColumnSet(int id) {
243  for (ScopedVector<ColumnSet>::const_iterator i = column_sets_.begin();
244       i != column_sets_.end(); ++i) {
245    if ((*i)->id() == id) {
246      return *i;
247    }
248  }
249  return NULL;
250}
251
252void SimpleGridLayout::AddPaddingRow(int fixed_height) {
253  AddRow(new Row(0.0f, fixed_height, NULL));
254}
255
256void SimpleGridLayout::StartRow(float vertical_resize, int column_set_id) {
257  ColumnSet* column_set = GetColumnSet(column_set_id);
258  DCHECK(column_set);
259  AddRow(new Row(vertical_resize, 0, column_set));
260}
261
262ColumnSet* SimpleGridLayout::AddRow() {
263  AddRow(new Row(0, 0, AddColumnSet(current_auto_id_++)));
264  return column_sets_.back();
265}
266
267void SimpleGridLayout::SkipColumns(int col_count) {
268  DCHECK(col_count > 0);
269  next_column_ += col_count;
270  ColumnSet* current_row_col_set_ = GetLastValidColumnSet();
271  DCHECK(current_row_col_set_ &&
272         next_column_ <= current_row_col_set_->num_columns());
273  SkipPaddingColumns();
274}
275
276void SimpleGridLayout::AddView(NSView* view) {
277  [host_ addSubview:view];
278  DCHECK(next_column_ < GetLastValidColumnSet()->num_columns());
279  view_states_.push_back(
280      new ViewState(view,
281                    GetLastValidColumnSet(),
282                    rows_.size() - 1,
283                    next_column_++));
284  SkipPaddingColumns();
285}
286
287// Sizes elements to fit into the superViews bounds, according to constraints.
288void SimpleGridLayout::Layout(NSView* superView) {
289  SizeRowsAndColumns(NSWidth([superView bounds]));
290  for (std::vector<ViewState*>::iterator i = view_states_.begin();
291       i != view_states_.end(); ++i) {
292    ViewState* view_state = *i;
293    NSView* view = view_state->view();
294    NSRect frame = NSMakeRect(view_state->GetColumn()->Location(),
295                              rows_[view_state->row_index()]->Location(),
296                              view_state->GetColumn()->Size(),
297                              rows_[view_state->row_index()]->Size());
298    [view setFrame:NSIntegralRect(frame)];
299  }
300}
301
302void SimpleGridLayout::SizeRowsAndColumns(float width) {
303  // Size all columns first.
304  for (ScopedVector<ColumnSet>::iterator i = column_sets_.begin();
305       i != column_sets_.end(); ++i) {
306    (*i)->CalculateSize(width);
307    (*i)->ResetColumnXCoordinates();
308  }
309
310  // Reset the height of each row.
311  LayoutElement::ResetSizes(&rows_);
312
313  // For each ViewState, obtain the preferred height
314  for (std::vector<ViewState*>::iterator i= view_states_.begin();
315       i != view_states_.end() ; ++i) {
316    ViewState* view_state = *i;
317
318    // The view is resizable. As the pref height may vary with the width,
319    // ask for the pref again.
320    int actual_width = view_state->GetColumnWidth();
321
322    // The width this view will get differs from its preferred. Some Views
323    // pref height varies with its width; ask for the preferred again.
324    view_state->set_preferred_height(
325        view_state->GetHeightForWidth(actual_width));
326  }
327
328
329  // Make sure each row can accommodate all contained ViewStates.
330  std::vector<ViewState*>::iterator view_states_iterator = view_states_.begin();
331  for (; view_states_iterator != view_states_.end(); ++view_states_iterator) {
332    ViewState* view_state = *view_states_iterator;
333    Row* row = rows_[view_state->row_index()];
334    row->AdjustSize(view_state->preferred_height());
335  }
336
337  // Update the location of each of the rows.
338  LayoutElement::CalculateLocationsFromSize(&rows_);
339}
340
341void SimpleGridLayout::SkipPaddingColumns() {
342  ColumnSet* current_row_col_set_ = GetLastValidColumnSet();
343  while (next_column_ < current_row_col_set_->num_columns() &&
344         current_row_col_set_->GetColumn(next_column_)->is_padding()) {
345    next_column_++;
346  }
347}
348
349ColumnSet* SimpleGridLayout::GetLastValidColumnSet() {
350  for (int i = num_rows() - 1; i >= 0; --i) {
351    if (rows_[i]->column_set())
352      return rows_[i]->column_set();
353  }
354  return NULL;
355}
356
357float SimpleGridLayout::GetRowHeight(int row_index) {
358  if (row_index < 0 || row_index >= num_rows())
359    return 0.0;
360  return rows_[row_index]->Size();
361}
362
363float SimpleGridLayout::GetRowLocation(int row_index) const {
364  if (row_index < 0 || row_index >= num_rows())
365    return 0.0;
366  return rows_[row_index]->Location();
367}
368
369float SimpleGridLayout::GetPreferredHeightForWidth(float width) {
370  if (rows_.empty())
371    return 0.0f;
372
373  SizeRowsAndColumns(width);
374  return rows_.back()->Location() + rows_.back()->Size();
375}
376
377void SimpleGridLayout::AddRow(Row* row) {
378  next_column_ = 0;
379  rows_.push_back(row);
380}
381
382