OLD | NEW |
| (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 | |
OLD | NEW |