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

Side by Side Diff: views/controls/table/table_view.cc

Issue 8655001: views: Move table and tree directories to ui/views/controls/. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: exclude native_widget_win_unittest too Created 9 years 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
« no previous file with comments | « views/controls/table/table_view.h ('k') | views/controls/table/table_view2.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2011 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 "views/controls/table/table_view.h"
6
7 #include <commctrl.h>
8 #include <windowsx.h>
9
10 #include <algorithm>
11
12 #include "base/i18n/rtl.h"
13 #include "base/string_util.h"
14 #include "base/win/scoped_gdi_object.h"
15 #include "skia/ext/skia_utils_win.h"
16 #include "third_party/skia/include/core/SkBitmap.h"
17 #include "third_party/skia/include/core/SkColorFilter.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/base/l10n/l10n_util_win.h"
20 #include "ui/base/models/table_model.h"
21 #include "ui/base/resource/resource_bundle.h"
22 #include "ui/base/win/hwnd_util.h"
23 #include "ui/gfx/canvas_skia.h"
24 #include "ui/gfx/favicon_size.h"
25 #include "ui/gfx/font.h"
26 #include "ui/gfx/icon_util.h"
27 #include "views/controls/native/native_view_host.h"
28 #include "views/controls/table/table_view_observer.h"
29
30 namespace {
31
32 int GetViewIndexFromPoint(HWND window, const gfx::Point& p) {
33 LVHITTESTINFO hit_info = {0};
34 hit_info.pt.x = p.x();
35 hit_info.pt.y = p.y();
36 return ListView_HitTest(window, &hit_info);
37 }
38
39 } // namespace
40
41 namespace views {
42
43 // Added to column width to prevent truncation.
44 const int kListViewTextPadding = 15;
45 // Additional column width necessary if column has icons.
46 const int kListViewIconWidthAndPadding = 18;
47
48 // TableView ------------------------------------------------------------------
49
50 // static
51 const int TableView::kImageSize = 18;
52
53 TableView::TableView(ui::TableModel* model,
54 const std::vector<ui::TableColumn>& columns,
55 TableTypes table_type,
56 bool single_selection,
57 bool resizable_columns,
58 bool autosize_columns)
59 : model_(model),
60 table_view_observer_(NULL),
61 visible_columns_(),
62 all_columns_(),
63 column_count_(static_cast<int>(columns.size())),
64 table_type_(table_type),
65 single_selection_(single_selection),
66 ignore_listview_change_(false),
67 custom_colors_enabled_(false),
68 autosize_columns_(autosize_columns),
69 resizable_columns_(resizable_columns),
70 list_view_(NULL),
71 header_original_handler_(NULL),
72 original_handler_(NULL),
73 ALLOW_THIS_IN_INITIALIZER_LIST(table_view_wrapper_(this)),
74 custom_cell_font_(NULL),
75 content_offset_(0) {
76 for (std::vector<ui::TableColumn>::const_iterator i = columns.begin();
77 i != columns.end(); ++i) {
78 AddColumn(*i);
79 visible_columns_.push_back(i->id);
80 }
81 }
82
83 TableView::~TableView() {
84 if (list_view_) {
85 if (model_)
86 model_->SetObserver(NULL);
87 }
88 if (custom_cell_font_)
89 DeleteObject(custom_cell_font_);
90 }
91
92 void TableView::SetModel(ui::TableModel* model) {
93 if (model == model_)
94 return;
95
96 if (list_view_ && model_)
97 model_->SetObserver(NULL);
98 model_ = model;
99 if (list_view_ && model_)
100 model_->SetObserver(this);
101 if (list_view_)
102 OnModelChanged();
103 }
104
105 void TableView::SetSortDescriptors(const SortDescriptors& sort_descriptors) {
106 if (!sort_descriptors_.empty()) {
107 ResetColumnSortImage(sort_descriptors_[0].column_id,
108 NO_SORT);
109 }
110 sort_descriptors_ = sort_descriptors;
111 if (!sort_descriptors_.empty()) {
112 ResetColumnSortImage(
113 sort_descriptors_[0].column_id,
114 sort_descriptors_[0].ascending ? ASCENDING_SORT : DESCENDING_SORT);
115 }
116 if (!list_view_)
117 return;
118
119 // For some reason we have to turn off/on redraw, otherwise the display
120 // isn't updated when done.
121 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
122
123 UpdateItemsLParams(0, 0);
124
125 SortItemsAndUpdateMapping();
126
127 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
128 }
129
130 int TableView::RowCount() const {
131 if (!list_view_)
132 return 0;
133 return ListView_GetItemCount(list_view_);
134 }
135
136 int TableView::SelectedRowCount() {
137 if (!list_view_)
138 return 0;
139 return ListView_GetSelectedCount(list_view_);
140 }
141
142 void TableView::Select(int model_row) {
143 if (!list_view_)
144 return;
145
146 DCHECK(model_row >= 0 && model_row < RowCount());
147 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
148 ignore_listview_change_ = true;
149
150 // Unselect everything.
151 ListView_SetItemState(list_view_, -1, 0, LVIS_SELECTED);
152
153 // Select the specified item.
154 int view_row = ModelToView(model_row);
155 ListView_SetItemState(list_view_, view_row, LVIS_SELECTED | LVIS_FOCUSED,
156 LVIS_SELECTED | LVIS_FOCUSED);
157
158 // Make it visible.
159 ListView_EnsureVisible(list_view_, view_row, FALSE);
160 ignore_listview_change_ = false;
161 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
162 if (table_view_observer_)
163 table_view_observer_->OnSelectionChanged();
164 }
165
166 void TableView::SetSelectedState(int model_row, bool state) {
167 if (!list_view_)
168 return;
169
170 DCHECK(model_row >= 0 && model_row < RowCount());
171
172 ignore_listview_change_ = true;
173
174 // Select the specified item.
175 ListView_SetItemState(list_view_, ModelToView(model_row),
176 state ? LVIS_SELECTED : 0, LVIS_SELECTED);
177
178 ignore_listview_change_ = false;
179 }
180
181 void TableView::SetFocusOnItem(int model_row) {
182 if (!list_view_)
183 return;
184
185 DCHECK(model_row >= 0 && model_row < RowCount());
186
187 ignore_listview_change_ = true;
188
189 // Set the focus to the given item.
190 ListView_SetItemState(list_view_, ModelToView(model_row), LVIS_FOCUSED,
191 LVIS_FOCUSED);
192
193 ignore_listview_change_ = false;
194 }
195
196 int TableView::FirstSelectedRow() {
197 if (!list_view_)
198 return -1;
199
200 int view_row = ListView_GetNextItem(list_view_, -1, LVNI_ALL | LVIS_SELECTED);
201 return view_row == -1 ? -1 : ViewToModel(view_row);
202 }
203
204 bool TableView::IsItemSelected(int model_row) {
205 if (!list_view_)
206 return false;
207
208 DCHECK(model_row >= 0 && model_row < RowCount());
209 return (ListView_GetItemState(list_view_, ModelToView(model_row),
210 LVIS_SELECTED) == LVIS_SELECTED);
211 }
212
213 bool TableView::ItemHasTheFocus(int model_row) {
214 if (!list_view_)
215 return false;
216
217 DCHECK(model_row >= 0 && model_row < RowCount());
218 return (ListView_GetItemState(list_view_, ModelToView(model_row),
219 LVIS_FOCUSED) == LVIS_FOCUSED);
220 }
221
222 TableView::iterator TableView::SelectionBegin() {
223 return TableView::iterator(this, LastSelectedViewIndex());
224 }
225
226 TableView::iterator TableView::SelectionEnd() {
227 return TableView::iterator(this, -1);
228 }
229
230 void TableView::OnItemsChanged(int start, int length) {
231 if (!list_view_)
232 return;
233
234 if (length == -1) {
235 DCHECK_GE(start, 0);
236 length = model_->RowCount() - start;
237 }
238 int row_count = RowCount();
239 DCHECK(start >= 0 && length > 0 && start + length <= row_count);
240 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
241 if (table_type_ == ICON_AND_TEXT) {
242 // The redraw event does not include the icon in the clip rect, preventing
243 // our icon from being repainted. So far the only way I could find around
244 // this is to change the image for the item. Even if the image does not
245 // exist, it causes the clip rect to include the icon's bounds so we can
246 // paint it in the post paint event.
247 LVITEM lv_item;
248 memset(&lv_item, 0, sizeof(LVITEM));
249 lv_item.mask = LVIF_IMAGE;
250 for (int i = start; i < start + length; ++i) {
251 // Retrieve the current icon index.
252 lv_item.iItem = ModelToView(i);
253 BOOL r = ListView_GetItem(list_view_, &lv_item);
254 DCHECK(r);
255 // Set the current icon index to the other image.
256 lv_item.iImage = (lv_item.iImage + 1) % 2;
257 DCHECK((lv_item.iImage == 0) || (lv_item.iImage == 1));
258 r = ListView_SetItem(list_view_, &lv_item);
259 DCHECK(r);
260 }
261 }
262 UpdateListViewCache(start, length, false);
263 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
264 }
265
266 void TableView::OnModelChanged() {
267 if (!list_view_)
268 return;
269
270 UpdateGroups();
271
272 int current_row_count = ListView_GetItemCount(list_view_);
273 if (current_row_count > 0)
274 OnItemsRemoved(0, current_row_count);
275 if (model_ && model_->RowCount())
276 OnItemsAdded(0, model_->RowCount());
277 }
278
279 void TableView::OnItemsAdded(int start, int length) {
280 if (!list_view_)
281 return;
282
283 DCHECK(start >= 0 && length > 0 && start <= RowCount());
284 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
285 UpdateListViewCache(start, length, true);
286 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
287 }
288
289 void TableView::OnItemsRemoved(int start, int length) {
290 if (!list_view_)
291 return;
292
293 if (start < 0 || length < 0 || start + length > RowCount()) {
294 NOTREACHED();
295 return;
296 }
297
298 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
299
300 bool had_selection = (SelectedRowCount() > 0);
301 int old_row_count = RowCount();
302 if (start == 0 && length == RowCount()) {
303 // Everything was removed.
304 ListView_DeleteAllItems(list_view_);
305 view_to_model_.reset(NULL);
306 model_to_view_.reset(NULL);
307 } else {
308 // Only a portion of the data was removed.
309 if (is_sorted()) {
310 int new_row_count = model_->RowCount();
311 std::vector<int> view_items_to_remove;
312 view_items_to_remove.reserve(length);
313 // Iterate through the elements, updating the view_to_model_ mapping
314 // as well as collecting the rows that need to be deleted.
315 for (int i = 0, removed_count = 0; i < old_row_count; ++i) {
316 int model_index = ViewToModel(i);
317 if (model_index >= start) {
318 if (model_index < start + length) {
319 // This item was removed.
320 view_items_to_remove.push_back(i);
321 model_index = -1;
322 } else {
323 model_index -= length;
324 }
325 }
326 if (model_index >= 0) {
327 view_to_model_[i - static_cast<int>(view_items_to_remove.size())] =
328 model_index;
329 }
330 }
331
332 // Update the model_to_view mapping from the updated view_to_model
333 // mapping.
334 for (int i = 0; i < new_row_count; ++i)
335 model_to_view_[view_to_model_[i]] = i;
336
337 // And finally delete the items. We do this backwards as the items were
338 // added ordered smallest to largest.
339 for (int i = length - 1; i >= 0; --i)
340 ListView_DeleteItem(list_view_, view_items_to_remove[i]);
341 } else {
342 for (int i = 0; i < length; ++i)
343 ListView_DeleteItem(list_view_, start);
344 }
345 }
346
347 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
348
349 // If the row count goes to zero and we had a selection LVN_ITEMCHANGED isn't
350 // invoked, so we handle it here.
351 //
352 // When the model is set to NULL all the rows are removed. We don't notify
353 // the delegate in this case as setting the model to NULL is usually done as
354 // the last step before being deleted and callers shouldn't have to deal with
355 // getting a selection change when the model is being reset.
356 if (model_ && table_view_observer_ && had_selection && RowCount() == 0)
357 table_view_observer_->OnSelectionChanged();
358 }
359
360 void TableView::AddColumn(const ui::TableColumn& col) {
361 DCHECK_EQ(0u, all_columns_.count(col.id));
362 all_columns_[col.id] = col;
363 }
364
365 void TableView::SetColumns(const std::vector<ui::TableColumn>& columns) {
366 // Remove the currently visible columns.
367 while (!visible_columns_.empty())
368 SetColumnVisibility(visible_columns_.front(), false);
369
370 all_columns_.clear();
371 for (std::vector<ui::TableColumn>::const_iterator i = columns.begin();
372 i != columns.end(); ++i) {
373 AddColumn(*i);
374 }
375
376 // Remove any sort descriptors that are no longer valid.
377 SortDescriptors sort = sort_descriptors();
378 for (SortDescriptors::iterator i = sort.begin(); i != sort.end();) {
379 if (all_columns_.count(i->column_id) == 0)
380 i = sort.erase(i);
381 else
382 ++i;
383 }
384 sort_descriptors_ = sort;
385 }
386
387 void TableView::OnColumnsChanged() {
388 column_count_ = static_cast<int>(visible_columns_.size());
389 ResetColumnSizes();
390 }
391
392 void TableView::SetColumnVisibility(int id, bool is_visible) {
393 bool changed = false;
394 for (std::vector<int>::iterator i = visible_columns_.begin();
395 i != visible_columns_.end(); ++i) {
396 if (*i == id) {
397 if (is_visible) {
398 // It's already visible, bail out early.
399 return;
400 } else {
401 int index = static_cast<int>(i - visible_columns_.begin());
402 // This could be called before the native list view has been created
403 // (in CreateNativeControl, called when the view is added to a
404 // Widget). In that case since the column is not in
405 // visible_columns_ it will not be added later on when it is created.
406 if (list_view_)
407 SendMessage(list_view_, LVM_DELETECOLUMN, index, 0);
408 visible_columns_.erase(i);
409 changed = true;
410 break;
411 }
412 }
413 }
414 if (is_visible) {
415 visible_columns_.push_back(id);
416 ui::TableColumn& column = all_columns_[id];
417 InsertColumn(column, column_count_);
418 if (column.min_visible_width == 0) {
419 // ListView_GetStringWidth must be padded or else truncation will occur.
420 column.min_visible_width = ListView_GetStringWidth(list_view_,
421 column.title.c_str()) +
422 kListViewTextPadding;
423 }
424 changed = true;
425 }
426 if (changed)
427 OnColumnsChanged();
428 }
429
430 void TableView::SetVisibleColumns(const std::vector<int>& columns) {
431 size_t old_count = visible_columns_.size();
432 size_t new_count = columns.size();
433 // remove the old columns
434 if (list_view_) {
435 for (std::vector<int>::reverse_iterator i = visible_columns_.rbegin();
436 i != visible_columns_.rend(); ++i) {
437 int index = static_cast<int>(i - visible_columns_.rend());
438 SendMessage(list_view_, LVM_DELETECOLUMN, index, 0);
439 }
440 }
441 visible_columns_ = columns;
442 // Insert the new columns.
443 if (list_view_) {
444 for (std::vector<int>::iterator i = visible_columns_.begin();
445 i != visible_columns_.end(); ++i) {
446 int index = static_cast<int>(i - visible_columns_.end());
447 InsertColumn(all_columns_[*i], index);
448 }
449 }
450 OnColumnsChanged();
451 }
452
453 bool TableView::IsColumnVisible(int id) const {
454 for (std::vector<int>::const_iterator i = visible_columns_.begin();
455 i != visible_columns_.end(); ++i)
456 if (*i == id) {
457 return true;
458 }
459 return false;
460 }
461
462 const ui::TableColumn& TableView::GetColumnAtPosition(int pos) {
463 return all_columns_[visible_columns_[pos]];
464 }
465
466 bool TableView::HasColumn(int id) {
467 return all_columns_.count(id) > 0;
468 }
469
470 gfx::Point TableView::GetKeyboardContextMenuLocation() {
471 int first_selected = FirstSelectedRow();
472 int y = height() / 2;
473 if (first_selected != -1) {
474 RECT cell_bounds;
475 RECT client_rect;
476 if (ListView_GetItemRect(GetNativeControlHWND(), first_selected,
477 &cell_bounds, LVIR_BOUNDS) &&
478 GetClientRect(GetNativeControlHWND(), &client_rect) &&
479 cell_bounds.bottom >= 0 && cell_bounds.bottom < client_rect.bottom) {
480 y = cell_bounds.bottom;
481 }
482 }
483 gfx::Point screen_loc(0, y);
484 if (base::i18n::IsRTL())
485 screen_loc.set_x(width());
486 ConvertPointToScreen(this, &screen_loc);
487 return screen_loc;
488 }
489
490 void TableView::SetCustomColorsEnabled(bool custom_colors_enabled) {
491 custom_colors_enabled_ = custom_colors_enabled;
492 }
493
494 bool TableView::GetCellColors(int model_row,
495 int column,
496 ItemColor* foreground,
497 ItemColor* background,
498 LOGFONT* logfont) {
499 return false;
500 }
501
502 // static
503 LRESULT CALLBACK TableView::TableWndProc(HWND window,
504 UINT message,
505 WPARAM w_param,
506 LPARAM l_param) {
507 TableView* table_view = reinterpret_cast<TableViewWrapper*>(
508 GetWindowLongPtr(window, GWLP_USERDATA))->table_view;
509
510 // Is the mouse down on the table?
511 static bool in_mouse_down = false;
512 // Should we select on mouse up?
513 static bool select_on_mouse_up = false;
514
515 // If the mouse is down, this is the location of the mouse down message.
516 static int mouse_down_x, mouse_down_y;
517
518 switch (message) {
519 case WM_CONTEXTMENU: {
520 // This addresses two problems seen with context menus in right to left
521 // locales:
522 // 1. The mouse coordinates in the l_param were occasionally wrong in
523 // weird ways. This is most often seen when right clicking on the
524 // list-view twice in a row.
525 // 2. Right clicking on the icon would show the scrollbar menu.
526 //
527 // As a work around this uses the position of the cursor and ignores
528 // the position supplied in the l_param.
529 if (base::i18n::IsRTL() &&
530 (GET_X_LPARAM(l_param) != -1 || GET_Y_LPARAM(l_param) != -1)) {
531 POINT screen_point;
532 GetCursorPos(&screen_point);
533 POINT table_point = screen_point;
534 RECT client_rect;
535 if (ScreenToClient(window, &table_point) &&
536 GetClientRect(window, &client_rect) &&
537 PtInRect(&client_rect, table_point)) {
538 // The point is over the client area of the table, handle it ourself.
539 // But first select the row if it isn't already selected.
540 int view_index =
541 GetViewIndexFromPoint(window, gfx::Point(table_point));
542 if (view_index != -1) {
543 int model_index = table_view->ViewToModel(view_index);
544 if (!table_view->IsItemSelected(model_index))
545 table_view->Select(model_index);
546 }
547 table_view->OnContextMenu(screen_point);
548 return 0; // So that default processing doesn't occur.
549 }
550 }
551 // else case: default handling is fine, so break and let the default
552 // handler service the request (which will likely calls us back with
553 // OnContextMenu).
554 break;
555 }
556
557 case WM_CANCELMODE: {
558 if (in_mouse_down) {
559 in_mouse_down = false;
560 return 0;
561 }
562 break;
563 }
564
565 case WM_ERASEBKGND:
566 // We make WM_ERASEBKGND do nothing (returning 1 indicates we handled
567 // the request). We do this so that the table view doesn't flicker during
568 // resizing.
569 return 1;
570
571 case WM_PAINT: {
572 LRESULT result = CallWindowProc(table_view->original_handler_, window,
573 message, w_param, l_param);
574 table_view->PostPaint();
575 return result;
576 }
577
578 case WM_KEYDOWN: {
579 if (!table_view->single_selection_ && w_param == 'A' &&
580 GetKeyState(VK_CONTROL) < 0 && table_view->RowCount() > 0) {
581 // Select everything.
582 ListView_SetItemState(window, -1, LVIS_SELECTED, LVIS_SELECTED);
583 // And make the first row focused.
584 ListView_SetItemState(window, 0, LVIS_FOCUSED, LVIS_FOCUSED);
585 return 0;
586 } else if (w_param == VK_DELETE && table_view->table_view_observer_) {
587 table_view->table_view_observer_->OnTableViewDelete(table_view);
588 return 0;
589 }
590 // else case: fall through to default processing.
591 break;
592 }
593
594 case WM_LBUTTONDBLCLK: {
595 if (w_param == MK_LBUTTON)
596 table_view->OnDoubleClick();
597 return 0;
598 }
599
600 case WM_MBUTTONDOWN: {
601 if (w_param == MK_MBUTTON) {
602 int view_index = GetViewIndexFromPoint(window, gfx::Point(l_param));
603 if (view_index != -1) {
604 int model_index = table_view->ViewToModel(view_index);
605 // Clear all and select the row that was middle clicked.
606 table_view->Select(model_index);
607 table_view->OnMiddleClick();
608 }
609 }
610 return 0;
611 }
612
613 case WM_LBUTTONUP: {
614 if (in_mouse_down) {
615 in_mouse_down = false;
616 ReleaseCapture();
617 SetFocus(window);
618 if (select_on_mouse_up) {
619 int view_index = GetViewIndexFromPoint(window, gfx::Point(l_param));
620 if (view_index != -1)
621 table_view->Select(table_view->ViewToModel(view_index));
622 }
623 return 0;
624 }
625 break;
626 }
627
628 case WM_LBUTTONDOWN: {
629 // ListView treats clicking on an area outside the text of a column as
630 // drag to select. This is confusing when the selection is shown across
631 // the whole row. For this reason we override the default handling for
632 // mouse down/move/up and treat the whole row as draggable. That is, no
633 // matter where you click in the row we'll attempt to start dragging.
634 //
635 // Only do custom mouse handling if no other mouse buttons are down.
636 if ((w_param | (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) ==
637 (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) {
638 if (in_mouse_down)
639 return 0;
640
641 int view_index = GetViewIndexFromPoint(window, gfx::Point(l_param));
642 if (view_index != -1) {
643 table_view->ignore_listview_change_ = true;
644 in_mouse_down = true;
645 select_on_mouse_up = false;
646 mouse_down_x = GET_X_LPARAM(l_param);
647 mouse_down_y = GET_Y_LPARAM(l_param);
648 int model_index = table_view->ViewToModel(view_index);
649 bool select = true;
650 if (w_param & MK_CONTROL) {
651 select = false;
652 if (!table_view->IsItemSelected(model_index)) {
653 if (table_view->single_selection_) {
654 // Single selection mode and the row isn't selected, select
655 // only it.
656 table_view->Select(model_index);
657 } else {
658 // Not single selection, add this row to the selection.
659 table_view->SetSelectedState(model_index, true);
660 }
661 } else {
662 // Remove this row from the selection.
663 table_view->SetSelectedState(model_index, false);
664 }
665 ListView_SetSelectionMark(window, view_index);
666 } else if (!table_view->single_selection_ && w_param & MK_SHIFT) {
667 int mark_view_index = ListView_GetSelectionMark(window);
668 if (mark_view_index != -1) {
669 // Unselect everything.
670 ListView_SetItemState(window, -1, 0, LVIS_SELECTED);
671 select = false;
672 if (!table_view->SelectMultiple(view_index, mark_view_index)) {
673 // Selection spans group boundary - reset selection to current.
674 table_view->SetSelectedState(model_index, true);
675 ListView_SetSelectionMark(window, view_index);
676 }
677 }
678 }
679 // Make the row the user clicked on the focused row.
680 ListView_SetItemState(window, view_index, LVIS_FOCUSED,
681 LVIS_FOCUSED);
682 if (select) {
683 if (!table_view->IsItemSelected(model_index)) {
684 // Clear all.
685 ListView_SetItemState(window, -1, 0, LVIS_SELECTED);
686 // And select the row the user clicked on.
687 table_view->SetSelectedState(model_index, true);
688 } else {
689 // The item is already selected, don't clear the state right away
690 // in case the user drags. Instead wait for mouse up, then only
691 // select the row the user clicked on.
692 select_on_mouse_up = true;
693 }
694 ListView_SetSelectionMark(window, view_index);
695 }
696 table_view->ignore_listview_change_ = false;
697 table_view->OnSelectedStateChanged();
698 SetCapture(window);
699 return 0;
700 }
701 // else case, continue on to default handler
702 }
703 break;
704 }
705
706 case WM_MOUSEMOVE: {
707 if (in_mouse_down) {
708 int x = GET_X_LPARAM(l_param);
709 int y = GET_Y_LPARAM(l_param);
710 if (View::ExceededDragThreshold(x - mouse_down_x, y - mouse_down_y)) {
711 // We're about to start drag and drop, which results in no mouse up.
712 // Release capture and reset state.
713 ReleaseCapture();
714 in_mouse_down = false;
715
716 NMLISTVIEW details;
717 memset(&details, 0, sizeof(details));
718 details.hdr.code = LVN_BEGINDRAG;
719 SendMessage(::GetParent(window), WM_NOTIFY, 0,
720 reinterpret_cast<LPARAM>(&details));
721 }
722 return 0;
723 }
724 break;
725 }
726
727 default:
728 break;
729 }
730 DCHECK(table_view->original_handler_);
731 return CallWindowProc(table_view->original_handler_, window, message, w_param,
732 l_param);
733 }
734
735 LRESULT CALLBACK TableView::TableHeaderWndProc(HWND window, UINT message,
736 WPARAM w_param, LPARAM l_param) {
737 TableView* table_view = reinterpret_cast<TableViewWrapper*>(
738 GetWindowLongPtr(window, GWLP_USERDATA))->table_view;
739
740 switch (message) {
741 case WM_SETCURSOR:
742 if (!table_view->resizable_columns_)
743 // Prevents the cursor from changing to the resize cursor.
744 return TRUE;
745 break;
746 case WM_LBUTTONDBLCLK:
747 if (!table_view->resizable_columns_)
748 // Prevents the double-click on the column separator from auto-resizing
749 // the column.
750 return TRUE;
751 break;
752 default:
753 break;
754 }
755 DCHECK(table_view->header_original_handler_);
756 return CallWindowProc(table_view->header_original_handler_,
757 window, message, w_param, l_param);
758 }
759
760 HWND TableView::CreateNativeControl(HWND parent_container) {
761 int style = WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS;
762 if (single_selection_)
763 style |= LVS_SINGLESEL;
764 // If there's only one column and the title string is empty, don't show a
765 // header.
766 if (all_columns_.size() == 1) {
767 std::map<int, ui::TableColumn>::const_iterator first = all_columns_.begin();
768 if (first->second.title.empty())
769 style |= LVS_NOCOLUMNHEADER;
770 }
771 list_view_ = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalRTLStyle(),
772 WC_LISTVIEW,
773 L"",
774 style,
775 0, 0, width(), height(),
776 parent_container, NULL, NULL, NULL);
777 ui::CheckWindowCreated(list_view_);
778
779 // Reduce overdraw/flicker artifacts by double buffering. Support tooltips
780 // and display elided items completely on hover (see comments in OnNotify()
781 // under LVN_GETINFOTIP). Make the selection extend across the row.
782 ListView_SetExtendedListViewStyle(list_view_,
783 LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT);
784 l10n_util::AdjustUIFontForWindow(list_view_);
785
786 // Add the columns.
787 for (std::vector<int>::iterator i = visible_columns_.begin();
788 i != visible_columns_.end(); ++i) {
789 InsertColumn(all_columns_[*i],
790 static_cast<int>(i - visible_columns_.begin()));
791 }
792
793 if (model_)
794 model_->SetObserver(this);
795
796 UpdateGroups();
797
798 // Set the # of rows.
799 if (model_)
800 UpdateListViewCache(0, model_->RowCount(), true);
801
802 if (table_type_ == ICON_AND_TEXT) {
803 HIMAGELIST image_list =
804 ImageList_Create(kImageSize, kImageSize, ILC_COLOR32, 2, 2);
805 // We create 2 phony images because we are going to switch images at every
806 // refresh in order to force a refresh of the icon area (somehow the clip
807 // rect does not include the icon).
808 gfx::CanvasSkia canvas(kImageSize, kImageSize, false);
809 // Make the background completely transparent.
810 canvas.sk_canvas()->drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
811 {
812 base::win::ScopedHICON empty_icon(
813 IconUtil::CreateHICONFromSkBitmap(canvas.ExtractBitmap()));
814 ImageList_AddIcon(image_list, empty_icon);
815 ImageList_AddIcon(image_list, empty_icon);
816 }
817 ListView_SetImageList(list_view_, image_list, LVSIL_SMALL);
818 }
819
820 if (!resizable_columns_) {
821 // To disable the resizing of columns we'll filter the events happening on
822 // the header. We also need to intercept the HDM_LAYOUT to size the header
823 // for the Chrome headers.
824 HWND header = ListView_GetHeader(list_view_);
825 DCHECK(header);
826 SetWindowLongPtr(header, GWLP_USERDATA,
827 reinterpret_cast<LONG_PTR>(&table_view_wrapper_));
828 header_original_handler_ = ui::SetWindowProc(header,
829 &TableView::TableHeaderWndProc);
830 }
831
832 SetWindowLongPtr(list_view_, GWLP_USERDATA,
833 reinterpret_cast<LONG_PTR>(&table_view_wrapper_));
834 original_handler_ =
835 ui::SetWindowProc(list_view_, &TableView::TableWndProc);
836
837 // Bug 964884: detach the IME attached to this window.
838 // We should attach IMEs only when we need to input CJK strings.
839 ::ImmAssociateContextEx(list_view_, NULL, 0);
840
841 UpdateContentOffset();
842 column_sizes_valid_ = false;
843
844 return list_view_;
845 }
846
847 void TableView::ToggleSortOrder(int column_id) {
848 SortDescriptors sort = sort_descriptors();
849 if (!sort.empty() && sort[0].column_id == column_id) {
850 sort[0].ascending = !sort[0].ascending;
851 } else {
852 SortDescriptor descriptor(column_id, true);
853 sort.insert(sort.begin(), descriptor);
854 if (sort.size() > 2) {
855 // Only persist two sort descriptors.
856 sort.resize(2);
857 }
858 }
859 SetSortDescriptors(sort);
860 }
861
862 void TableView::UpdateItemsLParams(int start, int length) {
863 LVITEM item;
864 memset(&item, 0, sizeof(LVITEM));
865 item.mask = LVIF_PARAM;
866 int row_count = RowCount();
867 for (int i = 0; i < row_count; ++i) {
868 item.iItem = i;
869 int model_index = ViewToModel(i);
870 if (length > 0 && model_index >= start)
871 model_index += length;
872 item.lParam = static_cast<LPARAM>(model_index);
873 ListView_SetItem(list_view_, &item);
874 }
875 }
876
877 void TableView::SortItemsAndUpdateMapping() {
878 if (!is_sorted()) {
879 ListView_SortItems(list_view_, &TableView::NaturalSortFunc, this);
880 view_to_model_.reset(NULL);
881 model_to_view_.reset(NULL);
882 return;
883 }
884
885 PrepareForSort();
886
887 // Sort the items.
888 ListView_SortItems(list_view_, &TableView::SortFunc, this);
889
890 model_->ClearCollator();
891
892 // Update internal mapping to match how items were actually sorted.
893 int row_count = RowCount();
894 model_to_view_.reset(new int[row_count]);
895 view_to_model_.reset(new int[row_count]);
896 LVITEM item;
897 memset(&item, 0, sizeof(LVITEM));
898 item.mask = LVIF_PARAM;
899 for (int i = 0; i < row_count; ++i) {
900 item.iItem = i;
901 ListView_GetItem(list_view_, &item);
902 int model_index = static_cast<int>(item.lParam);
903 view_to_model_[i] = model_index;
904 model_to_view_[model_index] = i;
905 }
906 }
907
908 bool TableView::SelectMultiple(int view_index, int mark_view_index) {
909 int group_id = 0;
910 if (model_->HasGroups()) {
911 group_id = model_->GetGroupID(ViewToModel(view_index));
912 if (group_id != model_->GetGroupID(ViewToModel(mark_view_index))) {
913 // User is trying to do a cross-group selection - bail out.
914 return false;
915 }
916 }
917
918 // Select from mark to mouse down location.
919 for (int i = std::min(view_index, mark_view_index),
920 max_i = std::max(view_index, mark_view_index); i <= max_i;
921 ++i) {
922 // Items between the view_index and mark_view_index are not necessarily in
923 // the same group, so don't select anything outside the group the user
924 // just clicked in.
925 if (model_->HasGroups() &&
926 model_->GetGroupID(ViewToModel(i)) != group_id) {
927 continue;
928 }
929 SetSelectedState(ViewToModel(i), true);
930 }
931 return true;
932 }
933
934 // static
935 int CALLBACK TableView::SortFunc(LPARAM model_index_1_p,
936 LPARAM model_index_2_p,
937 LPARAM table_view_param) {
938 int model_index_1 = static_cast<int>(model_index_1_p);
939 int model_index_2 = static_cast<int>(model_index_2_p);
940 TableView* table_view = reinterpret_cast<TableView*>(table_view_param);
941 return table_view->CompareRows(model_index_1, model_index_2);
942 }
943
944 // static
945 int CALLBACK TableView::NaturalSortFunc(LPARAM model_index_1_p,
946 LPARAM model_index_2_p,
947 LPARAM table_view_param) {
948 return model_index_1_p - model_index_2_p;
949 }
950
951 void TableView::ResetColumnSortImage(int column_id, SortDirection direction) {
952 if (!list_view_ || column_id == -1)
953 return;
954
955 std::vector<int>::const_iterator i =
956 std::find(visible_columns_.begin(), visible_columns_.end(), column_id);
957 if (i == visible_columns_.end())
958 return;
959
960 HWND header = ListView_GetHeader(list_view_);
961 if (!header)
962 return;
963
964 int column_index = static_cast<int>(i - visible_columns_.begin());
965 HDITEM header_item;
966 memset(&header_item, 0, sizeof(header_item));
967 header_item.mask = HDI_FORMAT;
968 Header_GetItem(header, column_index, &header_item);
969 header_item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
970 if (direction == ASCENDING_SORT)
971 header_item.fmt |= HDF_SORTUP;
972 else if (direction == DESCENDING_SORT)
973 header_item.fmt |= HDF_SORTDOWN;
974 Header_SetItem(header, column_index, &header_item);
975 }
976
977 void TableView::InsertColumn(const ui::TableColumn& tc, int index) {
978 if (!list_view_)
979 return;
980
981 LVCOLUMN column = { 0 };
982 column.mask = LVCF_TEXT|LVCF_FMT;
983 column.pszText = const_cast<LPWSTR>(tc.title.c_str());
984 switch (tc.alignment) {
985 case ui::TableColumn::LEFT:
986 column.fmt = LVCFMT_LEFT;
987 break;
988 case ui::TableColumn::RIGHT:
989 column.fmt = LVCFMT_RIGHT;
990 break;
991 case ui::TableColumn::CENTER:
992 column.fmt = LVCFMT_CENTER;
993 break;
994 default:
995 NOTREACHED();
996 }
997 if (tc.width != -1) {
998 column.mask |= LVCF_WIDTH;
999 column.cx = tc.width;
1000 }
1001 column.mask |= LVCF_SUBITEM;
1002 // Sub-items are 1s indexed.
1003 column.iSubItem = index + 1;
1004 SendMessage(list_view_, LVM_INSERTCOLUMN, index,
1005 reinterpret_cast<LPARAM>(&column));
1006 if (is_sorted() && sort_descriptors_[0].column_id == tc.id) {
1007 ResetColumnSortImage(
1008 tc.id,
1009 sort_descriptors_[0].ascending ? ASCENDING_SORT : DESCENDING_SORT);
1010 }
1011 }
1012
1013 LRESULT TableView::OnNotify(int w_param, LPNMHDR hdr) {
1014 if (!model_)
1015 return 0;
1016
1017 switch (hdr->code) {
1018 case NM_CUSTOMDRAW: {
1019 // Draw notification. dwDragState indicates the current stage of drawing.
1020 return OnCustomDraw(reinterpret_cast<NMLVCUSTOMDRAW*>(hdr));
1021 }
1022
1023 case LVN_ITEMCHANGED: {
1024 // Notification that the state of an item has changed. The state
1025 // includes such things as whether the item is selected.
1026 NMLISTVIEW* state_change = reinterpret_cast<NMLISTVIEW*>(hdr);
1027 if ((state_change->uChanged & LVIF_STATE) != 0) {
1028 if ((state_change->uOldState & LVIS_SELECTED) !=
1029 (state_change->uNewState & LVIS_SELECTED)) {
1030 // Selected state of the item changed.
1031 OnSelectedStateChanged();
1032 }
1033 }
1034 break;
1035 }
1036
1037 case HDN_BEGINTRACKW:
1038 case HDN_BEGINTRACKA:
1039 // Prevent clicks so columns cannot be resized.
1040 if (!resizable_columns_)
1041 return TRUE;
1042 break;
1043
1044 case NM_DBLCLK:
1045 OnDoubleClick();
1046 break;
1047
1048 case LVN_COLUMNCLICK: {
1049 const ui::TableColumn& column = GetColumnAtPosition(
1050 reinterpret_cast<NMLISTVIEW*>(hdr)->iSubItem);
1051 if (column.sortable)
1052 ToggleSortOrder(column.id);
1053 break;
1054 }
1055
1056 case LVN_MARQUEEBEGIN: // We don't want the marquee selection.
1057 return 1;
1058
1059 case LVN_GETINFOTIP: {
1060 // This is called when the user hovers items in column zero.
1061 // * If the text in this column is not fully visible, the dwFlags field
1062 // will be set to 0, and pszText will contain the full text. If you
1063 // return without making any changes, this text will be displayed in a
1064 // "labeltip" - a bubble that's overlaid (at the correct alignment!)
1065 // on the item. If you return with a different pszText, it will be
1066 // displayed as a tooltip if nonempty.
1067 // * Otherwise, dwFlags will be LVGIT_UNFOLDED and pszText will be
1068 // empty. On return, if pszText is nonempty, it will be displayed as
1069 // a labeltip if dwFlags has been changed to 0 (even if it bears no
1070 // resemblance to the item text), or as a tooltip otherwise.
1071 //
1072 // Once the tooltip for an item has been obtained, this will not be called
1073 // again until the user hovers a different item. If after that the
1074 // original item is hovered a second time, this will be called.
1075 //
1076 // When the user hovers items in other columns, they will be "unfolded"
1077 // (displayed as labeltips) when necessary, but this function will never
1078 // be called.
1079 //
1080 // Changing the LVS_EX_INFOTIP extended style to LVS_EX_LABELTIP will
1081 // cause all of the above to be true except that this function will not be
1082 // called when dwFlags would be LVGIT_UNFOLDED. Removing it entirely will
1083 // disable all of the above behavior.
1084 NMLVGETINFOTIP* info_tip = reinterpret_cast<NMLVGETINFOTIP*>(hdr);
1085 string16 tooltip =
1086 model_->GetTooltip(ViewToModel(info_tip->iItem));
1087 CHECK_GE(info_tip->cchTextMax, 2);
1088 if (tooltip.length() >= static_cast<size_t>(info_tip->cchTextMax)) {
1089 tooltip.erase(info_tip->cchTextMax - 2); // Ellipsis + '\0'
1090 const char16 kEllipsis = 0x2026;
1091 tooltip.push_back(kEllipsis);
1092 }
1093 if (!tooltip.empty())
1094 wcscpy_s(info_tip->pszText, tooltip.length() + 1, tooltip.c_str());
1095 return 1;
1096 }
1097
1098 default:
1099 break;
1100 }
1101 return 0;
1102 }
1103
1104 // Returns result, unless ascending is false in which case -result is returned.
1105 static int SwapCompareResult(int result, bool ascending) {
1106 return ascending ? result : -result;
1107 }
1108
1109 int TableView::CompareRows(int model_row1, int model_row2) {
1110 if (model_->HasGroups()) {
1111 // By default ListView sorts the elements regardless of groups. In such
1112 // a situation the groups display only the items they contain. This results
1113 // in the visual order differing from the item indices. I could not find
1114 // a way to iterate over the visual order in this situation. As a workaround
1115 // this forces the items to be sorted by groups as well, which means the
1116 // visual order matches the item indices.
1117 int g1 = model_->GetGroupID(model_row1);
1118 int g2 = model_->GetGroupID(model_row2);
1119 if (g1 != g2)
1120 return g1 - g2;
1121 }
1122 int sort_result = model_->CompareValues(
1123 model_row1, model_row2, sort_descriptors_[0].column_id);
1124 if (sort_result == 0 && sort_descriptors_.size() > 1 &&
1125 sort_descriptors_[1].column_id != -1) {
1126 // Try the secondary sort.
1127 return SwapCompareResult(
1128 model_->CompareValues(model_row1, model_row2,
1129 sort_descriptors_[1].column_id),
1130 sort_descriptors_[1].ascending);
1131 }
1132 return SwapCompareResult(sort_result, sort_descriptors_[0].ascending);
1133 }
1134
1135 int TableView::GetColumnWidth(int column_id) {
1136 if (!list_view_)
1137 return -1;
1138
1139 std::vector<int>::const_iterator i =
1140 std::find(visible_columns_.begin(), visible_columns_.end(), column_id);
1141 if (i == visible_columns_.end())
1142 return -1;
1143
1144 return ListView_GetColumnWidth(
1145 list_view_, static_cast<int>(i - visible_columns_.begin()));
1146 }
1147
1148 void TableView::PaintAltText() {
1149 if (alt_text_.empty())
1150 return;
1151
1152 HDC dc = GetDC(GetNativeControlHWND());
1153 gfx::Font font = GetAltTextFont();
1154 gfx::Rect bounds = GetAltTextBounds();
1155 gfx::CanvasSkia canvas(bounds.width(), bounds.height(), false);
1156 // Pad by 1 for halo.
1157 canvas.DrawStringWithHalo(alt_text_, font, SK_ColorDKGRAY, SK_ColorWHITE, 1,
1158 1, bounds.width() - 2, bounds.height() - 2,
1159 gfx::CanvasSkia::DefaultCanvasTextAlignment());
1160 skia::DrawToNativeContext(
1161 canvas.sk_canvas(), dc, bounds.x(), bounds.y(), NULL);
1162 ReleaseDC(GetNativeControlHWND(), dc);
1163 }
1164
1165 LRESULT TableView::OnCustomDraw(NMLVCUSTOMDRAW* draw_info) {
1166 switch (draw_info->nmcd.dwDrawStage) {
1167 case CDDS_PREPAINT: {
1168 return CDRF_NOTIFYITEMDRAW;
1169 }
1170 case CDDS_ITEMPREPAINT: {
1171 // The list-view is about to paint an item, tell it we want to
1172 // notified when it paints every subitem.
1173 LRESULT r = CDRF_NOTIFYSUBITEMDRAW;
1174 if (table_type_ == ICON_AND_TEXT)
1175 r |= CDRF_NOTIFYPOSTPAINT;
1176 return r;
1177 }
1178 case CDDS_ITEMPREPAINT | CDDS_SUBITEM: {
1179 // The list-view is painting a subitem. See if the colors should be
1180 // changed from the default.
1181 if (custom_colors_enabled_) {
1182 // At this time, draw_info->clrText and draw_info->clrTextBk are not
1183 // set. So we pass in an ItemColor to GetCellColors. If
1184 // ItemColor.color_is_set is true, then we use the provided color.
1185 ItemColor foreground = {0};
1186 ItemColor background = {0};
1187
1188 LOGFONT logfont;
1189 GetObject(GetWindowFont(list_view_), sizeof(logfont), &logfont);
1190
1191 if (GetCellColors(ViewToModel(
1192 static_cast<int>(draw_info->nmcd.dwItemSpec)),
1193 draw_info->iSubItem,
1194 &foreground,
1195 &background,
1196 &logfont)) {
1197 // TODO(tc): Creating/deleting a font for every cell seems like a
1198 // waste if the font hasn't changed. Maybe we should use a struct
1199 // with a bool like we do with colors?
1200 if (custom_cell_font_)
1201 DeleteObject(custom_cell_font_);
1202 l10n_util::AdjustUIFont(&logfont);
1203 custom_cell_font_ = CreateFontIndirect(&logfont);
1204 SelectObject(draw_info->nmcd.hdc, custom_cell_font_);
1205 draw_info->clrText = foreground.color_is_set
1206 ? skia::SkColorToCOLORREF(foreground.color)
1207 : CLR_DEFAULT;
1208 draw_info->clrTextBk = background.color_is_set
1209 ? skia::SkColorToCOLORREF(background.color)
1210 : CLR_DEFAULT;
1211 return CDRF_NEWFONT;
1212 }
1213 }
1214 return CDRF_DODEFAULT;
1215 }
1216 case CDDS_ITEMPOSTPAINT: {
1217 DCHECK((table_type_ == ICON_AND_TEXT) || (ImplementPostPaint()));
1218 int view_index = static_cast<int>(draw_info->nmcd.dwItemSpec);
1219 // We get notifications for empty items, just ignore them.
1220 if (view_index >= model_->RowCount())
1221 return CDRF_DODEFAULT;
1222 int model_index = ViewToModel(view_index);
1223 LRESULT r = CDRF_DODEFAULT;
1224 // First let's take care of painting the right icon.
1225 if (table_type_ == ICON_AND_TEXT) {
1226 SkBitmap image = model_->GetIcon(model_index);
1227 if (!image.isNull()) {
1228 // Get the rect that holds the icon.
1229 RECT icon_rect, client_rect;
1230 if (ListView_GetItemRect(list_view_, view_index, &icon_rect,
1231 LVIR_ICON) &&
1232 GetClientRect(list_view_, &client_rect)) {
1233 RECT intersection;
1234 // Client rect includes the header but we need to make sure we don't
1235 // paint into it.
1236 client_rect.top += content_offset_;
1237 // Make sure the region need to paint is visible.
1238 if (IntersectRect(&intersection, &icon_rect, &client_rect)) {
1239 gfx::CanvasSkia canvas(icon_rect.right - icon_rect.left,
1240 icon_rect.bottom - icon_rect.top, false);
1241
1242 // It seems the state in nmcd.uItemState is not correct.
1243 // We'll retrieve it explicitly.
1244 int selected = ListView_GetItemState(
1245 list_view_, view_index, LVIS_SELECTED | LVIS_DROPHILITED);
1246 bool drop_highlight = ((selected & LVIS_DROPHILITED) != 0);
1247 int bg_color_index;
1248 if (!IsEnabled())
1249 bg_color_index = COLOR_3DFACE;
1250 else if (drop_highlight)
1251 bg_color_index = COLOR_HIGHLIGHT;
1252 else if (selected)
1253 bg_color_index = HasFocus() ? COLOR_HIGHLIGHT : COLOR_3DFACE;
1254 else
1255 bg_color_index = COLOR_WINDOW;
1256 // NOTE: This may be invoked without the ListView filling in the
1257 // background (or rather windows paints background, then invokes
1258 // this twice). As such, we always fill in the background.
1259 canvas.sk_canvas()->drawColor(
1260 skia::COLORREFToSkColor(GetSysColor(bg_color_index)),
1261 SkXfermode::kSrc_Mode);
1262 // + 1 for padding (we declared the image as 18x18 in the list-
1263 // view when they are 16x16 so we get an extra pixel of padding).
1264 canvas.DrawBitmapInt(image, 0, 0,
1265 image.width(), image.height(),
1266 1, 1,
1267 gfx::kFaviconSize, gfx::kFaviconSize, true);
1268
1269 // Only paint the visible region of the icon.
1270 RECT to_draw = { intersection.left - icon_rect.left,
1271 intersection.top - icon_rect.top,
1272 0, 0 };
1273 to_draw.right = to_draw.left +
1274 (intersection.right - intersection.left);
1275 to_draw.bottom = to_draw.top +
1276 (intersection.bottom - intersection.top);
1277 skia::DrawToNativeContext(canvas.sk_canvas(), draw_info->nmcd.hdc,
1278 intersection.left, intersection.top,
1279 &to_draw);
1280 r = CDRF_SKIPDEFAULT;
1281 }
1282 }
1283 }
1284 }
1285 if (ImplementPostPaint()) {
1286 RECT cell_rect;
1287 if (ListView_GetItemRect(list_view_, view_index, &cell_rect,
1288 LVIR_BOUNDS)) {
1289 PostPaint(model_index, 0, false, gfx::Rect(cell_rect),
1290 draw_info->nmcd.hdc);
1291 r = CDRF_SKIPDEFAULT;
1292 }
1293 }
1294 return r;
1295 }
1296 default:
1297 return CDRF_DODEFAULT;
1298 }
1299 }
1300
1301 void TableView::UpdateListViewCache(int start, int length, bool add) {
1302 ignore_listview_change_ = true;
1303 UpdateListViewCache0(start, length, add);
1304 ignore_listview_change_ = false;
1305 }
1306
1307 void TableView::ResetColumnSizes() {
1308 if (!list_view_)
1309 return;
1310
1311 // See comment in TableColumn for what this does.
1312 int width = this->width();
1313 RECT native_bounds;
1314 if (GetClientRect(GetNativeControlHWND(), &native_bounds)) {
1315 int window_width = native_bounds.right - native_bounds.left;
1316 if (window_width > 0) {
1317 // Prefer the bounds of the window over our bounds, which may be
1318 // different.
1319 width = window_width;
1320 // Only set the flag when we know the true width of the table.
1321 column_sizes_valid_ = true;
1322 }
1323 }
1324
1325 float percent = 0;
1326 int fixed_width = 0;
1327 int autosize_width = 0;
1328
1329 for (std::vector<int>::const_iterator i = visible_columns_.begin();
1330 i != visible_columns_.end(); ++i) {
1331 ui::TableColumn& col = all_columns_[*i];
1332 int col_index = static_cast<int>(i - visible_columns_.begin());
1333 if (col.width == -1) {
1334 if (col.percent > 0) {
1335 percent += col.percent;
1336 } else {
1337 autosize_width += col.min_visible_width;
1338 }
1339 } else {
1340 fixed_width += ListView_GetColumnWidth(list_view_, col_index);
1341 }
1342 }
1343
1344 // Now do a pass to set the actual sizes of auto-sized and
1345 // percent-sized columns.
1346 int available_width = width - fixed_width - autosize_width;
1347 for (std::vector<int>::const_iterator i = visible_columns_.begin();
1348 i != visible_columns_.end(); ++i) {
1349 ui::TableColumn& col = all_columns_[*i];
1350 if (col.width == -1) {
1351 int col_index = static_cast<int>(i - visible_columns_.begin());
1352 if (col.percent > 0) {
1353 if (available_width > 0) {
1354 int col_width =
1355 static_cast<int>(available_width * (col.percent / percent));
1356 available_width -= col_width;
1357 percent -= col.percent;
1358 ListView_SetColumnWidth(list_view_, col_index, col_width);
1359 }
1360 } else {
1361 int col_width = col.min_visible_width;
1362 // If no "percent" columns, the last column acts as one, if auto-sized.
1363 if (percent == 0.f && available_width > 0 &&
1364 col_index == column_count_ - 1) {
1365 col_width += available_width;
1366 }
1367 ListView_SetColumnWidth(list_view_, col_index, col_width);
1368 }
1369 }
1370 }
1371 }
1372
1373 gfx::Size TableView::GetPreferredSize() {
1374 return preferred_size_;
1375 }
1376
1377 void TableView::SetPreferredSize(const gfx::Size& size) {
1378 preferred_size_ = size;
1379 PreferredSizeChanged();
1380 }
1381
1382 int TableView::ModelToView(int model_index) const {
1383 if (!model_to_view_.get())
1384 return model_index;
1385 DCHECK_GE(model_index, 0) << " negative model_index " << model_index;
1386 DCHECK_LT(model_index, RowCount()) << " out of bounds model_index " <<
1387 model_index;
1388 return model_to_view_[model_index];
1389 }
1390
1391 int TableView::ViewToModel(int view_index) const {
1392 if (!view_to_model_.get())
1393 return view_index;
1394 DCHECK_GE(view_index, 0) << " negative view_index " << view_index;
1395 DCHECK_LT(view_index, RowCount()) << " out of bounds view_index " <<
1396 view_index;
1397 return view_to_model_[view_index];
1398 }
1399
1400 void TableView::SetAltText(const string16& alt_text) {
1401 if (alt_text == alt_text_)
1402 return;
1403
1404 alt_text_ = alt_text;
1405 if (!GetNativeControlHWND())
1406 return;
1407
1408 RECT alt_text_bounds = GetAltTextBounds().ToRECT();
1409 InvalidateRect(GetNativeControlHWND(), &alt_text_bounds, FALSE);
1410 }
1411
1412 void TableView::UpdateListViewCache0(int start, int length, bool add) {
1413 if (is_sorted()) {
1414 if (add)
1415 UpdateItemsLParams(start, length);
1416 else
1417 UpdateItemsLParams(0, 0);
1418 }
1419
1420 LVITEM item = {0};
1421 if (add) {
1422 const bool has_groups = model_->HasGroups();
1423 for (int i = start; i < start + length; ++i) {
1424 item.mask = has_groups ? (LVIF_GROUPID | LVIF_PARAM) : LVIF_PARAM;
1425 item.iItem = i;
1426 if (has_groups)
1427 item.iGroupId = model_->GetGroupID(i);
1428 if (model_->ShouldIndent(i)) {
1429 item.mask |= LVIF_INDENT;
1430 item.iIndent = 1;
1431 }
1432 item.lParam = i;
1433 ListView_InsertItem(list_view_, &item);
1434 }
1435 }
1436
1437 memset(&item, 0, sizeof(LVITEM));
1438 item.mask =
1439 (table_type_ == ICON_AND_TEXT) ? (LVIF_IMAGE | LVIF_TEXT) : LVIF_TEXT;
1440 item.stateMask = 0;
1441 for (int j = 0; j < column_count_; ++j) {
1442 ui::TableColumn& col = all_columns_[visible_columns_[j]];
1443 int max_text_width = ListView_GetStringWidth(list_view_, col.title.c_str());
1444 for (int i = start; i < start + length; ++i) {
1445 // Set item.
1446 item.iItem = add ? i : ModelToView(i);
1447 item.iSubItem = j;
1448 string16 text = model_->GetText(i, visible_columns_[j]);
1449 item.pszText = const_cast<LPWSTR>(text.c_str());
1450 ListView_SetItem(list_view_, &item);
1451
1452 // Compute width in px, using current font.
1453 int string_width = ListView_GetStringWidth(list_view_, item.pszText);
1454 // The width of an icon belongs to the first column.
1455 if (j == 0 && table_type_ == ICON_AND_TEXT)
1456 string_width += kListViewIconWidthAndPadding;
1457 max_text_width = std::max(string_width, max_text_width);
1458 }
1459
1460 // ListView_GetStringWidth must be padded or else truncation will occur
1461 // (MSDN). 15px matches the Win32/LVSCW_AUTOSIZE_USEHEADER behavior.
1462 max_text_width += kListViewTextPadding;
1463
1464 // Protect against partial update.
1465 if (max_text_width > col.min_visible_width ||
1466 (start == 0 && length == model_->RowCount())) {
1467 col.min_visible_width = max_text_width;
1468 }
1469 }
1470
1471 if (is_sorted()) {
1472 // NOTE: As most of our tables are smallish I'm not going to optimize this.
1473 // If our tables become large and frequently update, then it'll make sense
1474 // to optimize this.
1475
1476 SortItemsAndUpdateMapping();
1477 }
1478 }
1479
1480 void TableView::OnDoubleClick() {
1481 if (!ignore_listview_change_ && table_view_observer_) {
1482 table_view_observer_->OnDoubleClick();
1483 }
1484 }
1485
1486 void TableView::OnMiddleClick() {
1487 if (!ignore_listview_change_ && table_view_observer_)
1488 table_view_observer_->OnMiddleClick();
1489 }
1490
1491 void TableView::OnSelectedStateChanged() {
1492 if (!ignore_listview_change_ && table_view_observer_) {
1493 table_view_observer_->OnSelectionChanged();
1494 }
1495 }
1496
1497 bool TableView::OnKeyDown(ui::KeyboardCode virtual_keycode) {
1498 if (!ignore_listview_change_ && table_view_observer_) {
1499 table_view_observer_->OnKeyDown(virtual_keycode);
1500 }
1501 return false; // Let the key event be processed as ususal.
1502 }
1503
1504 void TableView::OnBoundsChanged(const gfx::Rect& previous_bounds) {
1505 if (!list_view_)
1506 return;
1507 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
1508 Layout();
1509 if ((autosize_columns_ || !column_sizes_valid_) && width() > 0)
1510 ResetColumnSizes();
1511 UpdateContentOffset();
1512 SendMessage(list_view_, WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
1513 }
1514
1515 int TableView::PreviousSelectedViewIndex(int view_index) {
1516 DCHECK_GE(view_index, 0);
1517 if (!list_view_ || view_index <= 0)
1518 return -1;
1519
1520 int row_count = RowCount();
1521 if (row_count == 0)
1522 return -1; // Empty table, nothing can be selected.
1523
1524 // For some reason
1525 // ListView_GetNextItem(list_view_,item, LVNI_SELECTED | LVNI_ABOVE)
1526 // fails on Vista (always returns -1), so we iterate through the indices.
1527 view_index = std::min(view_index, row_count);
1528 while (--view_index >= 0 && !IsItemSelected(ViewToModel(view_index)));
1529 return view_index;
1530 }
1531
1532 int TableView::LastSelectedViewIndex() {
1533 return PreviousSelectedViewIndex(RowCount());
1534 }
1535
1536 void TableView::UpdateContentOffset() {
1537 content_offset_ = 0;
1538
1539 if (!list_view_)
1540 return;
1541
1542 HWND header = ListView_GetHeader(list_view_);
1543 if (!header)
1544 return;
1545
1546 POINT origin = {0, 0};
1547 MapWindowPoints(header, list_view_, &origin, 1);
1548
1549 RECT header_bounds;
1550 GetWindowRect(header, &header_bounds);
1551
1552 content_offset_ = origin.y + header_bounds.bottom - header_bounds.top;
1553 }
1554
1555 void TableView::UpdateGroups() {
1556 // Add the groups.
1557 if (model_ && model_->HasGroups()) {
1558 ListView_RemoveAllGroups(list_view_);
1559
1560 // Windows XP seems to disable groups if we remove them, so we
1561 // re-enable them.
1562 ListView_EnableGroupView(list_view_, true);
1563
1564 ui::TableModel::Groups groups = model_->GetGroups();
1565 LVGROUP group = { 0 };
1566 group.cbSize = sizeof(LVGROUP);
1567 group.mask = LVGF_HEADER | LVGF_ALIGN | LVGF_GROUPID;
1568 group.uAlign = LVGA_HEADER_LEFT;
1569 for (size_t i = 0; i < groups.size(); ++i) {
1570 group.pszHeader = const_cast<wchar_t*>(groups[i].title.c_str());
1571 group.iGroupId = groups[i].id;
1572 ListView_InsertGroup(list_view_, static_cast<int>(i), &group);
1573 }
1574 }
1575 }
1576
1577 gfx::Rect TableView::GetAltTextBounds() {
1578 static const int kXOffset = 16;
1579 DCHECK(GetNativeControlHWND());
1580 RECT client_rect_rect;
1581 GetClientRect(GetNativeControlHWND(), &client_rect_rect);
1582 gfx::Rect client_rect(client_rect_rect);
1583 gfx::Font font = GetAltTextFont();
1584 // Pad height by 2 for halo.
1585 return gfx::Rect(kXOffset, content_offset(), client_rect.width() - kXOffset,
1586 std::max(kImageSize, font.GetHeight() + 2));
1587 }
1588
1589 gfx::Font TableView::GetAltTextFont() {
1590 return ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
1591 }
1592
1593 void TableView::VisibilityChanged(View* starting_from, bool is_visible) {
1594 // GetClientRect as used by ResetColumnSize to obtain the total width
1595 // available to the columns only works when the native control is visible, so
1596 // update the column sizes in case we become visible. This depends on
1597 // VisibilityChanged() being called in post order on the view tree.
1598 if (is_visible && (autosize_columns_ || !column_sizes_valid_) && width() > 0)
1599 ResetColumnSizes();
1600 }
1601
1602
1603 //
1604 // TableSelectionIterator
1605 //
1606 TableSelectionIterator::TableSelectionIterator(TableView* view,
1607 int view_index)
1608 : table_view_(view),
1609 view_index_(view_index) {
1610 UpdateModelIndexFromViewIndex();
1611 }
1612
1613 TableSelectionIterator& TableSelectionIterator::operator=(
1614 const TableSelectionIterator& other) {
1615 view_index_ = other.view_index_;
1616 model_index_ = other.model_index_;
1617 return *this;
1618 }
1619
1620 bool TableSelectionIterator::operator==(const TableSelectionIterator& other) {
1621 return (other.view_index_ == view_index_);
1622 }
1623
1624 bool TableSelectionIterator::operator!=(const TableSelectionIterator& other) {
1625 return (other.view_index_ != view_index_);
1626 }
1627
1628 TableSelectionIterator& TableSelectionIterator::operator++() {
1629 view_index_ = table_view_->PreviousSelectedViewIndex(view_index_);
1630 UpdateModelIndexFromViewIndex();
1631 return *this;
1632 }
1633
1634 int TableSelectionIterator::operator*() {
1635 return model_index_;
1636 }
1637
1638 void TableSelectionIterator::UpdateModelIndexFromViewIndex() {
1639 if (view_index_ == -1)
1640 model_index_ = -1;
1641 else
1642 model_index_ = table_view_->ViewToModel(view_index_);
1643 }
1644
1645 } // namespace views
OLDNEW
« no previous file with comments | « views/controls/table/table_view.h ('k') | views/controls/table/table_view2.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698