Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(89)

Side by Side Diff: ui/views/controls/table/table_view_views.h

Issue 9187027: Attempt 2 at: Adds a trivial views based table implementation (only supports single (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #ifndef UI_VIEWS_CONTROLS_TABLE_TABLE_VIEW_VIEWS_H_ 5 #ifndef UI_VIEWS_CONTROLS_TABLE_TABLE_VIEW_VIEWS_H_
6 #define UI_VIEWS_CONTROLS_TABLE_TABLE_VIEW_VIEWS_H_ 6 #define UI_VIEWS_CONTROLS_TABLE_TABLE_VIEW_VIEWS_H_
7 #pragma once 7 #pragma once
8 8
9 #include <map>
10 #include <vector> 9 #include <vector>
11 10
12 #include "base/gtest_prod_util.h"
13 #include "base/string16.h"
14 #include "build/build_config.h"
15 #include "third_party/skia/include/core/SkColor.h"
16 #include "ui/base/keycodes/keyboard_codes.h" 11 #include "ui/base/keycodes/keyboard_codes.h"
17 #include "ui/base/models/table_model_observer.h" 12 #include "ui/base/models/table_model_observer.h"
13 #include "ui/gfx/font.h"
14 #include "ui/views/view.h"
18 #include "ui/views/views_export.h" 15 #include "ui/views/views_export.h"
19 16
20 #if defined(OS_WIN)
21 #include <windows.h>
22
23 // TODO(port): remove the ifdef when native_control.h is ported.
24 #include "ui/views/controls/native_control.h"
25
26 typedef struct tagNMLVCUSTOMDRAW NMLVCUSTOMDRAW;
27 #endif // defined(OS_WIN)
28
29 namespace gfx {
30 class Font;
31 }
32
33 namespace ui { 17 namespace ui {
34 struct TableColumn; 18 struct TableColumn;
35 class TableModel; 19 class TableModel;
36 } 20 }
37 21
38 // A TableView is a view that displays multiple rows with any number of columns. 22 // A TableView is a view that displays multiple rows with any number of columns.
39 // TableView is driven by a TableModel. The model returns the contents 23 // TableView is driven by a TableModel. The model returns the contents
40 // to display. TableModel also has an Observer which is used to notify 24 // to display. TableModel also has an Observer which is used to notify
41 // TableView of changes to the model so that the display may be updated 25 // TableView of changes to the model so that the display may be updated
42 // appropriately. 26 // appropriately.
43 // 27 //
44 // TableView itself has an observer that is notified when the selection 28 // TableView itself has an observer that is notified when the selection
45 // changes. 29 // changes.
46 // 30 //
47 // Tables may be sorted either by directly invoking SetSortDescriptors or by 31 // Tables may be sorted either by directly invoking SetSortDescriptors or by
48 // marking the column as sortable and the user doing a gesture to sort the 32 // marking the column as sortable and the user doing a gesture to sort the
49 // contents. TableView itself maintains the sort so that the underlying model 33 // contents. TableView itself maintains the sort so that the underlying model
50 // isn't effected. 34 // isn't effected.
51 // 35 //
52 // When a table is sorted the model coordinates do not necessarily match the 36 // When a table is sorted the model coordinates do not necessarily match the
53 // view coordinates. All table methods are in terms of the model. If you need to 37 // view coordinates. All table methods are in terms of the model. If you need to
54 // convert to view coordinates use model_to_view. 38 // convert to view coordinates use model_to_view.
55 // 39 //
56 // Sorting is done by a locale sensitive string sort. You can customize the 40 // Sorting is done by a locale sensitive string sort. You can customize the
57 // sort by way of overriding CompareValues. 41 // sort by way of overriding CompareValues.
58 // 42 //
59 // TableView is a wrapper around the window type ListView in report mode. 43 // TableView is a wrapper around the window type ListView in report mode.
60 namespace views { 44 namespace views {
61 45
62 class ListView;
63 class ListViewParent;
64 class TableView;
65 class TableViewObserver; 46 class TableViewObserver;
66 47
67 // The cells in the first column of a table can contain: 48 // The cells in the first column of a table can contain:
68 // - only text 49 // - only text
69 // - a small icon (16x16) and some text 50 // - a small icon (16x16) and some text
70 // - a check box and some text 51 // - a check box and some text
71 enum TableTypes { 52 enum TableTypes {
72 TEXT_ONLY = 0, 53 TEXT_ONLY = 0,
73 ICON_AND_TEXT, 54 ICON_AND_TEXT,
74 CHECK_BOX_AND_TEXT 55 CHECK_BOX_AND_TEXT
75 }; 56 };
76 57
77 // Returned from SelectionBegin/SelectionEnd 58 class VIEWS_EXPORT TableView : public views::View,
78 class VIEWS_EXPORT TableSelectionIterator {
79 public:
80 TableSelectionIterator(TableView* view, int view_index);
81 TableSelectionIterator& operator=(const TableSelectionIterator& other);
82 bool operator==(const TableSelectionIterator& other);
83 bool operator!=(const TableSelectionIterator& other);
84 TableSelectionIterator& operator++();
85 int operator*();
86
87 private:
88 void UpdateModelIndexFromViewIndex();
89
90 TableView* table_view_;
91 int view_index_;
92
93 // The index in terms of the model. This is returned from the * operator. This
94 // is cached to avoid dependencies on the view_to_model mapping.
95 int model_index_;
96 };
97
98 #if defined(OS_WIN)
99 // TODO(port): Port TableView.
100 class VIEWS_EXPORT TableView : public NativeControl,
101 public ui::TableModelObserver { 59 public ui::TableModelObserver {
102 public: 60 public:
103 typedef TableSelectionIterator iterator;
104
105 // A helper struct for GetCellColors. Set |color_is_set| to true if color is
106 // set. See OnCustomDraw for more details on why we need this.
107 struct ItemColor {
108 bool color_is_set;
109 SkColor color;
110 };
111
112 // Describes a sorted column.
113 struct SortDescriptor {
114 SortDescriptor() : column_id(-1), ascending(true) {}
115 SortDescriptor(int column_id, bool ascending)
116 : column_id(column_id),
117 ascending(ascending) { }
118
119 // ID of the sorted column.
120 int column_id;
121
122 // Is the sort ascending?
123 bool ascending;
124 };
125
126 typedef std::vector<SortDescriptor> SortDescriptors;
127
128 // Creates a new table using the model and columns specified. 61 // Creates a new table using the model and columns specified.
129 // The table type applies to the content of the first column (text, icon and 62 // The table type applies to the content of the first column (text, icon and
130 // text, checkbox and text). 63 // text, checkbox and text).
131 // When autosize_columns is true, columns always fill the available width. If 64 // When autosize_columns is true, columns always fill the available width. If
132 // false, columns are not resized when the table is resized. An extra empty 65 // false, columns are not resized when the table is resized. An extra empty
133 // column at the right fills the remaining space. 66 // column at the right fills the remaining space.
134 // When resizable_columns is true, users can resize columns by dragging the 67 // When resizable_columns is true, users can resize columns by dragging the
135 // separator on the column header. NOTE: Right now this is always true. The 68 // separator on the column header. NOTE: Right now this is always true. The
136 // code to set it false is still in place to be a base for future, better 69 // code to set it false is still in place to be a base for future, better
137 // resizing behavior (see http://b/issue?id=874646 ), but no one uses or 70 // resizing behavior (see http://b/issue?id=874646 ), but no one uses or
138 // tests the case where this flag is false. 71 // tests the case where this flag is false.
139 // Note that setting both resizable_columns and autosize_columns to false is 72 // Note that setting both resizable_columns and autosize_columns to false is
140 // probably not a good idea, as there is no way for the user to increase a 73 // probably not a good idea, as there is no way for the user to increase a
141 // column's size in that case. 74 // column's size in that case.
142 TableView(ui::TableModel* model, const std::vector<ui::TableColumn>& columns, 75 TableView(ui::TableModel* model,
143 TableTypes table_type, bool single_selection, 76 const std::vector<ui::TableColumn>& columns,
144 bool resizable_columns, bool autosize_columns); 77 TableTypes table_type,
78 bool single_selection,
79 bool resizable_columns,
80 bool autosize_columns);
145 virtual ~TableView(); 81 virtual ~TableView();
146 82
147 // Assigns a new model to the table view, detaching the old one if present. 83 // Assigns a new model to the table view, detaching the old one if present.
148 // If |model| is NULL, the table view cannot be used after this call. This 84 // If |model| is NULL, the table view cannot be used after this call. This
149 // should be called in the containing view's destructor to avoid destruction 85 // should be called in the containing view's destructor to avoid destruction
150 // issues when the model needs to be deleted before the table. 86 // issues when the model needs to be deleted before the table.
151 void SetModel(ui::TableModel* model); 87 void SetModel(ui::TableModel* model);
152 ui::TableModel* model() const { return model_; } 88 ui::TableModel* model() const { return model_; }
153 89
154 // Resorts the contents. 90 // Returns a new ScrollPane that contains the receiver.
155 void SetSortDescriptors(const SortDescriptors& sort_descriptors); 91 View* CreateParentIfNecessary();
156
157 // Current sort.
158 const SortDescriptors& sort_descriptors() const { return sort_descriptors_; }
159 92
160 // Returns the number of rows in the TableView. 93 // Returns the number of rows in the TableView.
161 int RowCount() const; 94 int RowCount() const;
162 95
163 // Returns the number of selected rows. 96 // Returns the number of selected rows.
164 int SelectedRowCount(); 97 int SelectedRowCount();
165 98
166 // Selects the specified item, making sure it's visible. 99 // Selects the specified item, making sure it's visible.
167 void Select(int model_row); 100 void Select(int model_row);
168 101
169 // Sets the selected state of an item (without sending any selection
170 // notifications). Note that this routine does NOT set the focus to the
171 // item at the given index.
172 void SetSelectedState(int model_row, bool state);
173
174 // Sets the focus to the item at the given index.
175 void SetFocusOnItem(int model_row);
176
177 // Returns the first selected row in terms of the model. 102 // Returns the first selected row in terms of the model.
178 int FirstSelectedRow(); 103 int FirstSelectedRow();
179 104
180 // Returns true if the item at the specified index is selected.
181 bool IsItemSelected(int model_row);
182
183 // Returns true if the item at the specified index has the focus.
184 bool ItemHasTheFocus(int model_row);
185
186 // Returns an iterator over the selection. The iterator proceeds from the
187 // last index to the first.
188 //
189 // NOTE: the iterator iterates over the visual order (but returns coordinates
190 // in terms of the model).
191 iterator SelectionBegin();
192 iterator SelectionEnd();
193
194 // ui::TableModelObserver methods.
195 virtual void OnModelChanged();
196 virtual void OnItemsChanged(int start, int length);
197 virtual void OnItemsAdded(int start, int length);
198 virtual void OnItemsRemoved(int start, int length);
199
200 void SetObserver(TableViewObserver* observer) { 105 void SetObserver(TableViewObserver* observer) {
201 table_view_observer_ = observer; 106 table_view_observer_ = observer;
202 } 107 }
203 TableViewObserver* observer() const { return table_view_observer_; } 108 TableViewObserver* observer() const { return table_view_observer_; }
204 109
205 // Replaces the set of known columns without changing the current visible 110 // View overrides:
206 // columns. 111 virtual void Layout() OVERRIDE;
207 void SetColumns(const std::vector<ui::TableColumn>& columns); 112 virtual gfx::Size GetPreferredSize() OVERRIDE;
208 void AddColumn(const ui::TableColumn& col); 113 virtual bool OnKeyPressed(const KeyEvent& event) OVERRIDE;
209 bool HasColumn(int id); 114 virtual bool OnMousePressed(const MouseEvent& event) OVERRIDE;
210 115
211 // Sets which columns (by id) are displayed. All transient size and position 116 // ui::TableModelObserver methods.
212 // information is lost. 117 virtual void OnModelChanged() OVERRIDE;
213 void SetVisibleColumns(const std::vector<int>& columns); 118 virtual void OnItemsChanged(int start, int length) OVERRIDE;
214 void SetColumnVisibility(int id, bool is_visible); 119 virtual void OnItemsAdded(int start, int length) OVERRIDE;
215 bool IsColumnVisible(int id) const; 120 virtual void OnItemsRemoved(int start, int length) OVERRIDE;
216
217 // Resets the size of the columns based on the sizes passed to the
218 // constructor. Your normally needn't invoked this, it's done for you the
219 // first time the TableView is given a valid size.
220 void ResetColumnSizes();
221
222 // Sometimes we may want to size the TableView to a specific width and
223 // height.
224 virtual gfx::Size GetPreferredSize();
225 void SetPreferredSize(const gfx::Size& size);
226
227 // Is the table sorted?
228 bool is_sorted() const { return !sort_descriptors_.empty(); }
229
230 // Maps from the index in terms of the model to that of the view.
231 int ModelToView(int model_index) const;
232
233 // Maps from the index in terms of the view to that of the model.
234 int ViewToModel(int view_index) const;
235
236 // Sets the text to display on top of the table. This is useful if the table
237 // is empty and you want to inform the user why.
238 void SetAltText(const string16& alt_text);
239 121
240 protected: 122 protected:
241 // Overriden to return the position of the first selected row. 123 // View overrides:
242 virtual gfx::Point GetKeyboardContextMenuLocation() OVERRIDE; 124 virtual gfx::Point GetKeyboardContextMenuLocation() OVERRIDE;
243 125 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE;
244 // Subclasses that want to customize the colors of a particular row/column, 126 virtual void OnFocus() OVERRIDE;
245 // must invoke this passing in true. The default value is false, such that 127 virtual void OnBlur() OVERRIDE;
246 // GetCellColors is never invoked.
247 void SetCustomColorsEnabled(bool custom_colors_enabled);
248
249 // Notification from the ListView that the selected state of an item has
250 // changed.
251 virtual void OnSelectedStateChanged();
252
253 // Notification from the ListView that the used double clicked the table.
254 virtual void OnDoubleClick();
255
256 // Notification from the ListView that the user middle clicked the table.
257 virtual void OnMiddleClick();
258
259 // Overridden from NativeControl. Notifies the observer.
260 virtual bool OnKeyDown(ui::KeyboardCode virtual_keycode) OVERRIDE;
261
262 // View override.
263 virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
264
265 // Invoked to customize the colors or font at a particular cell. If you
266 // change the colors or font, return true. This is only invoked if
267 // SetCustomColorsEnabled(true) has been invoked.
268 virtual bool GetCellColors(int model_row,
269 int column,
270 ItemColor* foreground,
271 ItemColor* background,
272 LOGFONT* logfont);
273
274 // Subclasses that want to perform some custom painting (on top of the regular
275 // list view painting) should return true here and implement the PostPaint
276 // method.
277 virtual bool ImplementPostPaint() { return false; }
278 // Subclasses can implement in this method extra-painting for cells.
279 virtual void PostPaint(int model_row, int column, bool selected,
280 const gfx::Rect& bounds, HDC device_context) { }
281 virtual void PostPaint() {}
282
283 virtual HWND CreateNativeControl(HWND parent_container);
284
285 virtual LRESULT OnNotify(int w_param, LPNMHDR l_param);
286
287 // Used to sort the two rows. Returns a value < 0, == 0 or > 0 indicating
288 // whether the row2 comes before row1, row2 is the same as row1 or row1 comes
289 // after row2. This invokes CompareValues on the model with the sorted column.
290 virtual int CompareRows(int model_row1, int model_row2);
291
292 // Called before sorting. This does nothing and is intended for subclasses
293 // that need to cache state used during sorting.
294 virtual void PrepareForSort() {}
295
296 // Returns the width of the specified column by id, or -1 if the column isn't
297 // visible.
298 int GetColumnWidth(int column_id);
299
300 // Returns the offset from the top of the client area to the start of the
301 // content.
302 int content_offset() const { return content_offset_; }
303
304 // Draws the alt_text_. Does nothing if there is no alt_text_.
305 void PaintAltText();
306
307 // Size (width and height) of images.
308 static const int kImageSize;
309 128
310 private: 129 private:
311 // Direction of a sort. 130 // Invoked when the number of rows changes in some way.
312 enum SortDirection { 131 void NumRowsChanged();
313 ASCENDING_SORT,
314 DESCENDING_SORT,
315 NO_SORT
316 };
317 132
318 // We need this wrapper to pass the table view to the windows proc handler 133 // Returns the bounds of the specified row.
319 // when subclassing the list view and list view header, as the reinterpret 134 gfx::Rect GetRowBounds(int row);
320 // cast from GetWindowLongPtr would break the pointer if it is pointing to a
321 // subclass (in the OO sense of TableView).
322 struct TableViewWrapper {
323 explicit TableViewWrapper(TableView* view) : table_view(view) { }
324 TableView* table_view;
325 };
326
327 friend class ListViewParent;
328 friend class TableSelectionIterator;
329 friend class GroupModelTableViewTest;
330 FRIEND_TEST_ALL_PREFIXES(GroupModelTableViewTest, ShiftSelectAcrossGroups);
331 FRIEND_TEST_ALL_PREFIXES(GroupModelTableViewTest, ShiftSelectSameGroup);
332
333 LRESULT OnCustomDraw(NMLVCUSTOMDRAW* draw_info);
334
335 // Invoked when the user clicks on a column to toggle the sort order. If
336 // column_id is the primary sorted column the direction of the sort is
337 // toggled, otherwise column_id is made the primary sorted column.
338 void ToggleSortOrder(int column_id);
339
340 // Updates the lparam of each of the list view items to be the model index.
341 // If length is > 0, all items with an index >= start get offset by length.
342 // This is used during sorting to determine how the items were sorted.
343 void UpdateItemsLParams(int start, int length);
344
345 // Does the actual sort and updates the mappings (view_to_model and
346 // model_to_view) appropriately.
347 void SortItemsAndUpdateMapping();
348
349 // Selects multiple items from the current view row to the marked view row
350 // (implements shift-click behavior). |view_index| is the most recent row
351 // that the user clicked on, and so there is no guarantee that
352 // |view_index| > |mark_view_index| or vice-versa. Returns false if the
353 // selection attempt was rejected because it crossed group boundaries.
354 bool SelectMultiple(int view_index, int mark_view_index);
355
356 // Method invoked by ListView to compare the two values. Invokes CompareRows.
357 static int CALLBACK SortFunc(LPARAM model_index_1_p,
358 LPARAM model_index_2_p,
359 LPARAM table_view_param);
360
361 // Method invoked by ListView when sorting back to natural state. Returns
362 // model_index_1_p - model_index_2_p.
363 static int CALLBACK NaturalSortFunc(LPARAM model_index_1_p,
364 LPARAM model_index_2_p,
365 LPARAM table_view_param);
366
367 // Resets the sort image displayed for the specified column.
368 void ResetColumnSortImage(int column_id, SortDirection direction);
369
370 // Adds a new column.
371 void InsertColumn(const ui::TableColumn& tc, int index);
372
373 // Update headers and internal state after columns have changed
374 void OnColumnsChanged();
375
376 // Updates the ListView with values from the model. See UpdateListViewCache0
377 // for a complete description.
378 // This turns off redrawing, and invokes UpdateListViewCache0 to do the
379 // actual updating.
380 void UpdateListViewCache(int start, int length, bool add);
381
382 // Updates ListView with values from the model.
383 // If add is true, this adds length items starting at index start.
384 // If add is not true, the items are not added, the but the values in the
385 // range start - [start + length] are updated from the model.
386 void UpdateListViewCache0(int start, int length, bool add);
387
388 // Returns the index of the selected item before |view_index|, or -1 if
389 // |view_index| is the first selected item.
390 //
391 // WARNING: this returns coordinates in terms of the view, NOT the model.
392 int PreviousSelectedViewIndex(int view_index);
393
394 // Returns the last selected view index in the table view, or -1 if the table
395 // is empty, or nothing is selected.
396 //
397 // WARNING: this returns coordinates in terms of the view, NOT the model.
398 int LastSelectedViewIndex();
399
400 // The TableColumn visible at position pos.
401 const ui::TableColumn& GetColumnAtPosition(int pos);
402
403 // Window procedure of the list view class. We subclass the list view to
404 // ignore WM_ERASEBKGND, which gives smoother painting during resizing.
405 static LRESULT CALLBACK TableWndProc(HWND window,
406 UINT message,
407 WPARAM w_param,
408 LPARAM l_param);
409
410 // Window procedure of the header class. We subclass the header of the table
411 // to disable resizing of columns.
412 static LRESULT CALLBACK TableHeaderWndProc(HWND window, UINT message,
413 WPARAM w_param, LPARAM l_param);
414
415 // Updates content_offset_ from the position of the header.
416 void UpdateContentOffset();
417
418 // Reloads the groups from the model if there is one and it has groups.
419 void UpdateGroups();
420
421 // Returns the bounds of the alt text.
422 gfx::Rect GetAltTextBounds();
423
424 // Returns the font used for alt text.
425 gfx::Font GetAltTextFont();
426
427 // Overriden in order to update the column sizes, which can only be sized
428 // accurately when the native control is available.
429 virtual void VisibilityChanged(View* starting_from, bool is_visible);
430 135
431 ui::TableModel* model_; 136 ui::TableModel* model_;
432 TableTypes table_type_; 137
138 const TableTypes table_type_;
139
433 TableViewObserver* table_view_observer_; 140 TableViewObserver* table_view_observer_;
434 141
435 // An ordered list of id's into |all_columns_| representing current visible 142 int selected_row_;
436 // columns.
437 std::vector<int> visible_columns_;
438 143
439 // Mapping of an int id to a TableColumn representing all possible columns. 144 gfx::Font font_;
440 std::map<int, ui::TableColumn> all_columns_;
441 145
442 // Cached value of columns_.size() 146 int row_height_;
443 int column_count_;
444
445 // Selection mode.
446 bool single_selection_;
447
448 // If true, any events that would normally be propagated to the observer
449 // are ignored. For example, if this is true and the selection changes in
450 // the listview, the observer is not notified.
451 bool ignore_listview_change_;
452
453 // Reflects the value passed to SetCustomColorsEnabled.
454 bool custom_colors_enabled_;
455
456 // Whether or not columns should automatically be resized to fill the
457 // the available width when the list view is resized.
458 bool autosize_columns_;
459
460 // Whether or not the user can resize columns.
461 bool resizable_columns_;
462
463 // Whether the column sizes have been determined.
464 bool column_sizes_valid_;
465
466 // NOTE: While this has the name View in it, it's not a view. Rather it's
467 // a wrapper around the List-View window.
468 HWND list_view_;
469
470 // The list view's header original proc handler. It is required when
471 // subclassing.
472 WNDPROC header_original_handler_;
473
474 // Window procedure of the listview before we subclassed it.
475 WNDPROC original_handler_;
476
477 // A wrapper around 'this' used when "subclassing" the list view and header.
478 TableViewWrapper table_view_wrapper_;
479
480 // A custom font we use when overriding the font type for a specific cell.
481 HFONT custom_cell_font_;
482
483 // The preferred size of the table view.
484 gfx::Size preferred_size_;
485
486 int content_offset_;
487
488 // Current sort.
489 SortDescriptors sort_descriptors_;
490
491 // Mappings used when sorted.
492 scoped_array<int> view_to_model_;
493 scoped_array<int> model_to_view_;
494
495 string16 alt_text_;
496 147
497 DISALLOW_COPY_AND_ASSIGN(TableView); 148 DISALLOW_COPY_AND_ASSIGN(TableView);
498 }; 149 };
499 #endif // defined(OS_WIN)
500 150
501 } // namespace views 151 } // namespace views
502 152
503 #endif // UI_VIEWS_CONTROLS_TABLE_TABLE_VIEW_VIEWS_H_ 153 #endif // UI_VIEWS_CONTROLS_TABLE_TABLE_VIEW_VIEWS_H_
OLDNEW
« no previous file with comments | « ui/views/controls/table/table_view_unittest.cc ('k') | ui/views/controls/table/table_view_views.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698