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

Side by Side Diff: views/controls/table/native_table_win.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/native_table_win.h ('k') | views/controls/table/native_table_wrapper.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/native_table_win.h"
6
7 #include <commctrl.h>
8 #include <windowsx.h>
9
10 #include <algorithm>
11
12 #include "base/logging.h"
13 #include "base/win/scoped_gdi_object.h"
14 #include "skia/ext/skia_utils_win.h"
15 #include "ui/base/l10n/l10n_util.h"
16 #include "ui/base/l10n/l10n_util_win.h"
17 #include "ui/base/models/table_model.h"
18 #include "ui/base/win/hwnd_util.h"
19 #include "ui/gfx/canvas_skia.h"
20 #include "ui/gfx/favicon_size.h"
21 #include "ui/gfx/icon_util.h"
22 #include "ui/views/widget/widget.h"
23 #include "views/controls/table/table_view2.h"
24 #include "views/controls/table/table_view_observer.h"
25
26 namespace views {
27
28 // Added to column width to prevent truncation.
29 const int kListViewTextPadding = 15;
30 // Additional column width necessary if column has icons.
31 const int kListViewIconWidthAndPadding = 18;
32
33 // static
34 const int NativeTableWin::kImageSize = 18;
35
36 ////////////////////////////////////////////////////////////////////////////////
37 // NativeTableWin, public:
38
39 NativeTableWin::NativeTableWin(TableView2* table)
40 : ignore_listview_change_(false),
41 table_(table),
42 content_offset_(0),
43 header_original_handler_(NULL),
44 original_handler_(NULL) {
45 // Associates the actual HWND with the table so the table is the one
46 // considered as having the focus (not the wrapper) when the HWND is
47 // focused directly (with a click for example).
48 set_focus_view(table);
49 }
50
51 NativeTableWin::~NativeTableWin() {
52 }
53
54 ////////////////////////////////////////////////////////////////////////////////
55 // NativeTableWin, NativeTableWrapper implementation:
56
57 int NativeTableWin::GetRowCount() const {
58 if (!native_view())
59 return 0;
60 return ListView_GetItemCount(native_view());
61 }
62
63 void NativeTableWin::InsertColumn(const ui::TableColumn& tc, int index) {
64 if (!native_view())
65 return;
66
67 LVCOLUMN column = { 0 };
68 column.mask = LVCF_TEXT|LVCF_FMT;
69 column.pszText = const_cast<LPWSTR>(tc.title.c_str());
70 switch (tc.alignment) {
71 case ui::TableColumn::LEFT:
72 column.fmt = LVCFMT_LEFT;
73 break;
74 case ui::TableColumn::RIGHT:
75 column.fmt = LVCFMT_RIGHT;
76 break;
77 case ui::TableColumn::CENTER:
78 column.fmt = LVCFMT_CENTER;
79 break;
80 default:
81 NOTREACHED();
82 }
83 if (tc.width != -1) {
84 column.mask |= LVCF_WIDTH;
85 column.cx = tc.width;
86 }
87 column.mask |= LVCF_SUBITEM;
88 // Sub-items are 1s indexed.
89 column.iSubItem = index + 1;
90 SendMessage(native_view(), LVM_INSERTCOLUMN, index,
91 reinterpret_cast<LPARAM>(&column));
92 }
93
94 void NativeTableWin::RemoveColumn(int index) {
95 if (!native_view())
96 return;
97 SendMessage(native_view(), LVM_DELETECOLUMN, index, 0);
98 if (table_->model()->RowCount() > 0)
99 OnRowsChanged(0, table_->model()->RowCount() - 1);
100 }
101
102 View* NativeTableWin::GetView() {
103 return this;
104 }
105
106 void NativeTableWin::SetFocus() {
107 // Focus the associated HWND.
108 OnFocus();
109 }
110
111 gfx::NativeView NativeTableWin::GetTestingHandle() const {
112 return native_view();
113 }
114
115 int NativeTableWin::GetColumnWidth(int column_index) const {
116 if (!native_view())
117 return 0;
118 return ListView_GetColumnWidth(native_view(), column_index);
119 }
120
121 void NativeTableWin::SetColumnWidth(int column_index, int width) {
122 if (!native_view())
123 return;
124 ListView_SetColumnWidth(native_view(), column_index, width);
125 }
126
127 int NativeTableWin::GetSelectedRowCount() const {
128 if (!native_view())
129 return 0;
130 return ListView_GetSelectedCount(native_view());
131 }
132
133 int NativeTableWin::GetFirstSelectedRow() const {
134 if (!native_view())
135 return -1;
136 return ListView_GetNextItem(native_view(), -1, LVNI_ALL | LVIS_SELECTED);
137 }
138
139 int NativeTableWin::GetFirstFocusedRow() const {
140 if (!native_view())
141 return -1;
142 return ListView_GetNextItem(native_view(), -1, LVNI_ALL | LVIS_FOCUSED);
143 }
144
145 void NativeTableWin::ClearSelection() {
146 if (native_view())
147 ListView_SetItemState(native_view(), -1, 0, LVIS_SELECTED);
148 }
149
150 void NativeTableWin::ClearRowFocus() {
151 if (native_view())
152 ListView_SetItemState(native_view(), -1, 0, LVIS_FOCUSED);
153 }
154
155 void NativeTableWin::SetSelectedState(int model_row, bool state) {
156 if (!native_view())
157 return;
158
159 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
160 ListView_SetItemState(native_view(), model_row,
161 state ? LVIS_SELECTED : 0, LVIS_SELECTED);
162 // Make the selected row visible.
163 ListView_EnsureVisible(native_view(), model_row, FALSE);
164 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
165 }
166
167 void NativeTableWin::SetFocusState(int model_row, bool state) {
168 if (!native_view())
169 return;
170 ListView_SetItemState(native_view(), model_row,
171 state ? LVIS_FOCUSED : 0, LVIS_FOCUSED)
172 }
173
174 bool NativeTableWin::IsRowSelected(int model_row) const {
175 if (!native_view())
176 return false;
177 return ListView_GetItemState(native_view(), model_row, LVIS_SELECTED) ==
178 LVIS_SELECTED;
179 }
180
181 bool NativeTableWin::IsRowFocused(int model_row) const {
182 if (!native_view())
183 return false;
184 return ListView_GetItemState(native_view(), model_row, LVIS_FOCUSED) ==
185 LVIS_FOCUSED;
186 }
187
188 void NativeTableWin::OnRowsChanged(int start, int length) {
189 if (!native_view())
190 return;
191
192 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
193 UpdateListViewCache(start, length, false);
194 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
195 }
196
197 void NativeTableWin::OnRowsAdded(int start, int length) {
198 if (!native_view())
199 return;
200
201 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
202 UpdateListViewCache(start, length, true);
203 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
204 }
205
206 void NativeTableWin::OnRowsRemoved(int start, int length) {
207 if (!native_view())
208 return;
209
210 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
211
212 bool had_selection = (GetSelectedRowCount() > 0);
213 int old_row_count = GetRowCount();
214 if (start == 0 && length == GetRowCount()) {
215 // Everything was removed.
216 ListView_DeleteAllItems(native_view());
217 } else {
218 for (int i = 0; i < length; ++i)
219 ListView_DeleteItem(native_view(), start);
220 }
221
222 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
223
224 // If the row count goes to zero and we had a selection LVN_ITEMCHANGED isn't
225 // invoked, so we handle it here.
226 //
227 // When the model is set to NULL all the rows are removed. We don't notify
228 // the delegate in this case as setting the model to NULL is usually done as
229 // the last step before being deleted and callers shouldn't have to deal with
230 // getting a selection change when the model is being reset.
231 if (table_->model() && table_->observer() && had_selection &&
232 GetRowCount() == 0) {
233 table_->observer()->OnSelectionChanged();
234 }
235 }
236
237 gfx::Rect NativeTableWin::GetBounds() const {
238 RECT native_bounds;
239 if (!native_view() || GetClientRect(native_view(), &native_bounds))
240 return gfx::Rect();
241 return gfx::Rect(native_bounds);
242 }
243
244 ////////////////////////////////////////////////////////////////////////////////
245 // NativeTableWin, View overrides:
246
247 gfx::Size NativeTableWin::GetPreferredSize() {
248 SIZE sz = {0};
249 SendMessage(native_view(), BCM_GETIDEALSIZE, 0,
250 reinterpret_cast<LPARAM>(&sz));
251
252 return gfx::Size(sz.cx, sz.cy);
253 }
254
255 ////////////////////////////////////////////////////////////////////////////////
256 // NativeTableWin, NativeControlWin overrides:
257
258 bool NativeTableWin::ProcessMessage(UINT message, WPARAM w_param,
259 LPARAM l_param, LRESULT* result) {
260 if (message == WM_NOTIFY) {
261 LPNMHDR hdr = reinterpret_cast<LPNMHDR>(l_param);
262 switch (hdr->code) {
263 case NM_CUSTOMDRAW: {
264 // Draw notification. dwDragState indicates the current stage of
265 // drawing.
266 *result = OnCustomDraw(reinterpret_cast<NMLVCUSTOMDRAW*>(hdr));
267 return true;
268 }
269
270 case LVN_ITEMCHANGED: {
271 // Notification that the state of an item has changed. The state
272 // includes such things as whether the item is selected.
273 NMLISTVIEW* state_change = reinterpret_cast<NMLISTVIEW*>(hdr);
274 if ((state_change->uChanged & LVIF_STATE) != 0) {
275 if ((state_change->uOldState & LVIS_SELECTED) !=
276 (state_change->uNewState & LVIS_SELECTED)) {
277 // Selected state of the item changed.
278 OnSelectedStateChanged();
279 }
280 }
281 break;
282 }
283
284 case HDN_BEGINTRACKW:
285 case HDN_BEGINTRACKA:
286 // Prevent clicks so columns cannot be resized.
287 if (!table_->resizable_columns())
288 return true;
289 break;
290
291 case NM_DBLCLK:
292 OnDoubleClick();
293 break;
294
295 case LVN_COLUMNCLICK: {
296 const ui::TableColumn& column = table_->GetVisibleColumnAt(
297 reinterpret_cast<NMLISTVIEW*>(hdr)->iSubItem);
298 break;
299 }
300
301 case LVN_MARQUEEBEGIN: // We don't want the marquee selection.
302 return true;
303
304 case LVN_GETINFOTIP: {
305 // This is called when the user hovers items in column zero.
306 // * If the text in this column is not fully visible, the dwFlags
307 // field will be set to 0, and pszText will contain the full text.
308 // If you return without making any changes, this text will be
309 // displayed in a "labeltip" - a bubble that's overlaid (at the
310 // correct alignment!) on the item. If you return with a different
311 // pszText, it will be displayed as a tooltip if nonempty.
312 // * Otherwise, dwFlags will be LVGIT_UNFOLDED and pszText will be
313 // empty. On return, if pszText is nonempty, it will be displayed
314 // as a labeltip if dwFlags has been changed to 0 (even if it bears
315 // no resemblance to the item text), or as a tooltip otherwise.
316 //
317 // Once the tooltip for an item has been obtained, this will not be
318 // called again until the user hovers a different item. If after that
319 // the original item is hovered a second time, this will be called.
320 //
321 // When the user hovers items in other columns, they will be "unfolded"
322 // (displayed as labeltips) when necessary, but this function will
323 // never be called.
324 //
325 // Changing the LVS_EX_INFOTIP extended style to LVS_EX_LABELTIP will
326 // cause all of the above to be true except that this function will not
327 // be called when dwFlags would be LVGIT_UNFOLDED. Removing it entirely
328 // will disable all of the above behavior.
329 NMLVGETINFOTIP* info_tip = reinterpret_cast<NMLVGETINFOTIP*>(hdr);
330 string16 tooltip = table_->model()->GetTooltip(info_tip->iItem);
331 CHECK_GE(info_tip->cchTextMax, 2);
332 if (tooltip.length() >= static_cast<size_t>(info_tip->cchTextMax)) {
333 // Elide the tooltip if necessary.
334 tooltip.erase(info_tip->cchTextMax - 2); // Ellipsis + '\0'
335 const wchar_t kEllipsis = L'\x2026';
336 tooltip += kEllipsis;
337 }
338 if (!tooltip.empty())
339 wcscpy_s(info_tip->pszText, tooltip.length() + 1, tooltip.c_str());
340 return true;
341 }
342
343 default:
344 break;
345 }
346 }
347
348 return NativeControlWin::ProcessMessage(message, w_param, l_param, result);
349 }
350
351 ////////////////////////////////////////////////////////////////////////////////
352 // NativeTableWin, protected:
353
354 void NativeTableWin::CreateNativeControl() {
355 int style = WS_CHILD | LVS_REPORT | LVS_SHOWSELALWAYS;
356 if (table_->single_selection())
357 style |= LVS_SINGLESEL;
358 // If there's only one column and the title string is empty, don't show a
359 // header.
360 if (table_->GetVisibleColumnCount() == 1U) {
361 if (table_->GetVisibleColumnAt(1).title.empty())
362 style |= LVS_NOCOLUMNHEADER;
363 }
364 HWND hwnd = ::CreateWindowEx(WS_EX_CLIENTEDGE | GetAdditionalRTLStyle(),
365 WC_LISTVIEW,
366 L"",
367 style,
368 0, 0, width(), height(),
369 table_->GetWidget()->GetNativeView(),
370 NULL, NULL, NULL);
371 ui::CheckWindowCreated(hwnd);
372
373 // Reduce overdraw/flicker artifacts by double buffering. Support tooltips
374 // and display elided items completely on hover (see comments in OnNotify()
375 // under LVN_GETINFOTIP). Make the selection extend across the row.
376 ListView_SetExtendedListViewStyle(hwnd,
377 LVS_EX_DOUBLEBUFFER | LVS_EX_INFOTIP | LVS_EX_FULLROWSELECT);
378 l10n_util::AdjustUIFontForWindow(hwnd);
379
380 NativeControlCreated(hwnd);
381 // native_view() is now valid.
382
383 // Add the columns.
384 for (size_t i = 0; i < table_->GetVisibleColumnCount(); ++i)
385 InsertColumn(table_->GetVisibleColumnAt(i), i);
386
387 if (table_->model())
388 UpdateListViewCache(0, table_->model()->RowCount(), true);
389
390 if (table_->type() == ICON_AND_TEXT) {
391 HIMAGELIST image_list =
392 ImageList_Create(kImageSize, kImageSize, ILC_COLOR32, 2, 2);
393 // We create 2 phony images because we are going to switch images at every
394 // refresh in order to force a refresh of the icon area (somehow the clip
395 // rect does not include the icon).
396 gfx::CanvasSkia canvas(kImageSize, kImageSize, false);
397 // Make the background completely transparent.
398 canvas.sk_canvas()->drawColor(SK_ColorBLACK, SkXfermode::kClear_Mode);
399 {
400 base::win::ScopedHICON empty_icon(
401 IconUtil::CreateHICONFromSkBitmap(canvas.ExtractBitmap()));
402 ImageList_AddIcon(image_list, empty_icon);
403 ImageList_AddIcon(image_list, empty_icon);
404 }
405 ListView_SetImageList(native_view(), image_list, LVSIL_SMALL);
406 }
407
408 if (!table_->resizable_columns()) {
409 // To disable the resizing of columns we'll filter the events happening on
410 // the header. We also need to intercept the HDM_LAYOUT to size the header
411 // for the Chrome headers.
412 HWND header = ListView_GetHeader(native_view());
413 DCHECK(header);
414 SetWindowLongPtr(header, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
415 header_original_handler_ =
416 ui::SetWindowProc(header, &NativeTableWin::TableHeaderWndProc);
417 }
418
419 SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
420 original_handler_ =
421 ui::SetWindowProc(hwnd, &NativeTableWin::TableWndProc);
422
423 // Bug 964884: detach the IME attached to this window.
424 // We should attach IMEs only when we need to input CJK strings.
425 ::ImmAssociateContextEx(hwnd, NULL, 0);
426
427 UpdateContentOffset();
428 }
429
430 ////////////////////////////////////////////////////////////////////////////////
431 // NativeTableWin, private:
432
433 void NativeTableWin::Select(int model_row) {
434 if (!native_view())
435 return;
436
437 DCHECK(model_row >= 0 && model_row < table_->model()->RowCount());
438 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(FALSE), 0);
439 ignore_listview_change_ = true;
440
441 // Unselect everything.
442 ClearSelection();
443
444 // Select the specified item.
445 SetSelectedState(model_row, true);
446 SetFocusState(model_row, true);
447
448 // Make it visible.
449 ListView_EnsureVisible(native_view(), model_row, FALSE);
450 ignore_listview_change_ = false;
451 SendMessage(native_view(), WM_SETREDRAW, static_cast<WPARAM>(TRUE), 0);
452 if (table_->observer())
453 table_->observer()->OnSelectionChanged();
454 }
455
456 void NativeTableWin::OnSelectedStateChanged() {
457 if (!ignore_listview_change_ && table_->observer())
458 table_->observer()->OnSelectionChanged();
459 }
460
461 void NativeTableWin::OnDoubleClick() {
462 if (!ignore_listview_change_ && table_->observer())
463 table_->observer()->OnDoubleClick();
464 }
465
466 void NativeTableWin::OnMiddleClick() {
467 if (!ignore_listview_change_ && table_->observer())
468 table_->observer()->OnMiddleClick();
469 }
470
471 bool NativeTableWin::OnKeyDown(ui::KeyboardCode virtual_keycode) {
472 if (!ignore_listview_change_ && table_->observer())
473 table_->observer()->OnKeyDown(virtual_keycode);
474 return false; // Let the key event be processed as ususal.
475 }
476
477 LRESULT NativeTableWin::OnCustomDraw(NMLVCUSTOMDRAW* draw_info) {
478 switch (draw_info->nmcd.dwDrawStage) {
479 case CDDS_PREPAINT: {
480 return CDRF_NOTIFYITEMDRAW;
481 }
482 case CDDS_ITEMPREPAINT: {
483 // The list-view is about to paint an item, tell it we want to
484 // notified when it paints every subitem.
485 LRESULT r = CDRF_NOTIFYSUBITEMDRAW;
486 if (table_->type() == ICON_AND_TEXT)
487 r |= CDRF_NOTIFYPOSTPAINT;
488 return r;
489 }
490 case CDDS_ITEMPREPAINT | CDDS_SUBITEM: {
491 // TODO(jcampan): implement custom colors and fonts.
492 return CDRF_DODEFAULT;
493 }
494 case CDDS_ITEMPOSTPAINT: {
495 DCHECK(table_->type() == ICON_AND_TEXT);
496 int view_index = static_cast<int>(draw_info->nmcd.dwItemSpec);
497 // We get notifications for empty items, just ignore them.
498 if (view_index >= table_->model()->RowCount())
499 return CDRF_DODEFAULT;
500 LRESULT r = CDRF_DODEFAULT;
501 // First let's take care of painting the right icon.
502 if (table_->type() == ICON_AND_TEXT) {
503 SkBitmap image = table_->model()->GetIcon(view_index);
504 if (!image.isNull()) {
505 // Get the rect that holds the icon.
506 RECT icon_rect, client_rect;
507 if (ListView_GetItemRect(native_view(), view_index, &icon_rect,
508 LVIR_ICON) &&
509 GetClientRect(native_view(), &client_rect)) {
510 RECT intersection;
511 // Client rect includes the header but we need to make sure we don't
512 // paint into it.
513 client_rect.top += content_offset_;
514 // Make sure the region need to paint is visible.
515 if (IntersectRect(&intersection, &icon_rect, &client_rect)) {
516 gfx::CanvasSkia canvas(icon_rect.right - icon_rect.left,
517 icon_rect.bottom - icon_rect.top, false);
518
519 // It seems the state in nmcd.uItemState is not correct.
520 // We'll retrieve it explicitly.
521 int selected = ListView_GetItemState(
522 native_view(), view_index, LVIS_SELECTED | LVIS_DROPHILITED);
523 bool drop_highlight = ((selected & LVIS_DROPHILITED) != 0);
524 int bg_color_index;
525 if (!IsEnabled())
526 bg_color_index = COLOR_3DFACE;
527 else if (drop_highlight)
528 bg_color_index = COLOR_HIGHLIGHT;
529 else if (selected)
530 bg_color_index = HasFocus() ? COLOR_HIGHLIGHT : COLOR_3DFACE;
531 else
532 bg_color_index = COLOR_WINDOW;
533 // NOTE: This may be invoked without the ListView filling in the
534 // background (or rather windows paints background, then invokes
535 // this twice). As such, we always fill in the background.
536 canvas.sk_canvas()->drawColor(
537 skia::COLORREFToSkColor(GetSysColor(bg_color_index)),
538 SkXfermode::kSrc_Mode);
539 // + 1 for padding (we declared the image as 18x18 in the list-
540 // view when they are 16x16 so we get an extra pixel of padding).
541 canvas.DrawBitmapInt(image, 0, 0,
542 image.width(), image.height(),
543 1, 1,
544 gfx::kFaviconSize, gfx::kFaviconSize, true);
545
546 // Only paint the visible region of the icon.
547 RECT to_draw = { intersection.left - icon_rect.left,
548 intersection.top - icon_rect.top,
549 0, 0 };
550 to_draw.right = to_draw.left +
551 (intersection.right - intersection.left);
552 to_draw.bottom = to_draw.top +
553 (intersection.bottom - intersection.top);
554 skia::DrawToNativeContext(canvas.sk_canvas(), draw_info->nmcd.hdc,
555 intersection.left, intersection.top,
556 &to_draw);
557 r = CDRF_SKIPDEFAULT;
558 }
559 }
560 }
561 }
562 return r;
563 }
564 default:
565 return CDRF_DODEFAULT;
566 }
567 }
568
569 void NativeTableWin::UpdateListViewCache(int start, int length, bool add) {
570 LVITEM item = {0};
571 int start_column = 0;
572 int max_row = start + length;
573 if (add) {
574 item.mask |= LVIF_PARAM;
575 for (int i = start; i < max_row; ++i) {
576 item.iItem = i;
577 item.lParam = i;
578 ignore_listview_change_ = true;
579 ListView_InsertItem(native_view(), &item);
580 ignore_listview_change_ = false;
581 }
582 }
583
584 memset(&item, 0, sizeof(LVITEM));
585 item.stateMask = 0;
586 item.mask = LVIF_TEXT;
587 if (table_->type() == ICON_AND_TEXT)
588 item.mask |= LVIF_IMAGE;
589
590 for (size_t j = start_column; j < table_->GetVisibleColumnCount(); ++j) {
591 ui::TableColumn col = table_->GetVisibleColumnAt(j);
592 int max_text_width = ListView_GetStringWidth(native_view(),
593 col.title.c_str());
594 for (int i = start; i < max_row; ++i) {
595 item.iItem = i;
596 item.iSubItem = j;
597 string16 text = table_->model()->GetText(i, col.id);
598 item.pszText = const_cast<LPWSTR>(text.c_str());
599 item.iImage = 0;
600 ListView_SetItem(native_view(), &item);
601
602 // Compute width in px, using current font.
603 int string_width = ListView_GetStringWidth(native_view(), item.pszText);
604 // The width of an icon belongs to the first column.
605 if (j == 0 && table_->type() == ICON_AND_TEXT)
606 string_width += kListViewIconWidthAndPadding;
607 max_text_width = std::max(string_width, max_text_width);
608 }
609
610 // ListView_GetStringWidth must be padded or else truncation will occur
611 // (MSDN). 15px matches the Win32/LVSCW_AUTOSIZE_USEHEADER behavior.
612 max_text_width += kListViewTextPadding;
613
614 // Protect against partial update.
615 if (max_text_width > col.min_visible_width ||
616 (start == 0 && length == table_->model()->RowCount())) {
617 col.min_visible_width = max_text_width;
618 }
619 }
620 }
621
622 void NativeTableWin::UpdateContentOffset() {
623 content_offset_ = 0;
624
625 if (!native_view())
626 return;
627
628 HWND header = ListView_GetHeader(native_view());
629 if (!header)
630 return;
631
632 POINT origin = {0, 0};
633 MapWindowPoints(header, native_view(), &origin, 1);
634
635 RECT header_bounds;
636 GetWindowRect(header, &header_bounds);
637
638 content_offset_ = origin.y + header_bounds.bottom - header_bounds.top;
639 }
640
641 static int GetViewIndexFromMouseEvent(HWND window, LPARAM l_param) {
642 int x = GET_X_LPARAM(l_param);
643 int y = GET_Y_LPARAM(l_param);
644 LVHITTESTINFO hit_info = {0};
645 hit_info.pt.x = x;
646 hit_info.pt.y = y;
647 return ListView_HitTest(window, &hit_info);
648 }
649
650 // static
651 LRESULT CALLBACK NativeTableWin::TableWndProc(HWND window,
652 UINT message,
653 WPARAM w_param,
654 LPARAM l_param) {
655 NativeTableWin* native_table_win = reinterpret_cast<NativeTableWin*>(
656 GetWindowLongPtr(window, GWLP_USERDATA));
657 TableView2* table = native_table_win->table_;
658
659 // Is the mouse down on the table?
660 static bool in_mouse_down = false;
661 // Should we select on mouse up?
662 static bool select_on_mouse_up = false;
663
664 // If the mouse is down, this is the location of the mouse down message.
665 static int mouse_down_x, mouse_down_y;
666
667 switch (message) {
668 case WM_CONTEXTMENU: {
669 // This addresses two problems seen with context menus in right to left
670 // locales:
671 // 1. The mouse coordinates in the l_param were occasionally wrong in
672 // weird ways. This is most often seen when right clicking on the
673 // list-view twice in a row.
674 // 2. Right clicking on the icon would show the scrollbar menu.
675 //
676 // As a work around this uses the position of the cursor and ignores
677 // the position supplied in the l_param.
678 if (base::i18n::IsRTL() &&
679 (GET_X_LPARAM(l_param) != -1 || GET_Y_LPARAM(l_param) != -1)) {
680 POINT screen_point;
681 GetCursorPos(&screen_point);
682 POINT table_point = screen_point;
683 RECT client_rect;
684 if (ScreenToClient(window, &table_point) &&
685 GetClientRect(window, &client_rect) &&
686 PtInRect(&client_rect, table_point)) {
687 // The point is over the client area of the table, handle it ourself.
688 // But first select the row if it isn't already selected.
689 LVHITTESTINFO hit_info = {0};
690 hit_info.pt.x = table_point.x;
691 hit_info.pt.y = table_point.y;
692 int view_index = ListView_HitTest(window, &hit_info);
693 // TODO(jcampan): fix code below
694 // if (view_index != -1 && table->IsItemSelected(view_index))
695 // table->Select(view_index);
696 // table->OnContextMenu(screen_point);
697 return 0; // So that default processing doesn't occur.
698 }
699 }
700 // else case: default handling is fine, so break and let the default
701 // handler service the request (which will likely calls us back with
702 // OnContextMenu).
703 break;
704 }
705
706 case WM_CANCELMODE: {
707 if (in_mouse_down) {
708 in_mouse_down = false;
709 return 0;
710 }
711 break;
712 }
713
714 case WM_ERASEBKGND:
715 // We make WM_ERASEBKGND do nothing (returning 1 indicates we handled
716 // the request). We do this so that the table view doesn't flicker during
717 // resizing.
718 return 1;
719
720 case WM_KEYDOWN: {
721 if (!table->single_selection() && w_param == 'A' &&
722 GetKeyState(VK_CONTROL) < 0 && table->model()->RowCount() > 0) {
723 // Select everything.
724 ListView_SetItemState(window, -1, LVIS_SELECTED, LVIS_SELECTED);
725 // And make the first row focused.
726 ListView_SetItemState(window, 0, LVIS_FOCUSED, LVIS_FOCUSED);
727 return 0;
728 } else if (w_param == VK_DELETE && table->observer()) {
729 table->observer()->OnTableView2Delete(table);
730 return 0;
731 }
732 // else case: fall through to default processing.
733 break;
734 }
735
736 case WM_LBUTTONDBLCLK: {
737 if (w_param == MK_LBUTTON)
738 native_table_win->OnDoubleClick();
739 return 0;
740 }
741
742 case WM_MBUTTONDOWN: {
743 if (w_param == MK_MBUTTON) {
744 int view_index = GetViewIndexFromMouseEvent(window, l_param);
745 if (view_index != -1) {
746 // Clear all and select the row that was middle clicked.
747 native_table_win->Select(view_index);
748 native_table_win->OnMiddleClick();
749 }
750 }
751 return 0;
752 }
753
754 case WM_LBUTTONUP: {
755 if (in_mouse_down) {
756 in_mouse_down = false;
757 ReleaseCapture();
758 ::SetFocus(window);
759 if (select_on_mouse_up) {
760 int view_index = GetViewIndexFromMouseEvent(window, l_param);
761 if (view_index != -1)
762 native_table_win->Select(view_index);
763 }
764 return 0;
765 }
766 break;
767 }
768
769 case WM_LBUTTONDOWN: {
770 // ListView treats clicking on an area outside the text of a column as
771 // drag to select. This is confusing when the selection is shown across
772 // the whole row. For this reason we override the default handling for
773 // mouse down/move/up and treat the whole row as draggable. That is, no
774 // matter where you click in the row we'll attempt to start dragging.
775 //
776 // Only do custom mouse handling if no other mouse buttons are down.
777 if ((w_param | (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) ==
778 (MK_LBUTTON | MK_CONTROL | MK_SHIFT)) {
779 if (in_mouse_down)
780 return 0;
781
782 int view_index = GetViewIndexFromMouseEvent(window, l_param);
783 if (view_index != -1) {
784 native_table_win->ignore_listview_change_ = true;
785 in_mouse_down = true;
786 select_on_mouse_up = false;
787 mouse_down_x = GET_X_LPARAM(l_param);
788 mouse_down_y = GET_Y_LPARAM(l_param);
789 bool select = true;
790 if (w_param & MK_CONTROL) {
791 select = false;
792 if (!native_table_win->IsRowSelected(view_index)) {
793 if (table->single_selection()) {
794 // Single selection mode and the row isn't selected, select
795 // only it.
796 native_table_win->Select(view_index);
797 } else {
798 // Not single selection, add this row to the selection.
799 native_table_win->SetSelectedState(view_index, true);
800 }
801 } else {
802 // Remove this row from the selection.
803 native_table_win->SetSelectedState(view_index, false);
804 }
805 ListView_SetSelectionMark(window, view_index);
806 } else if (!table->single_selection() && w_param & MK_SHIFT) {
807 int mark_view_index = ListView_GetSelectionMark(window);
808 if (mark_view_index != -1) {
809 // Unselect everything.
810 ListView_SetItemState(window, -1, 0, LVIS_SELECTED);
811 select = false;
812
813 // Select from mark to mouse down location.
814 for (int i = std::min(view_index, mark_view_index),
815 max_i = std::max(view_index, mark_view_index); i <= max_i;
816 ++i) {
817 native_table_win->SetSelectedState(i, true);
818 }
819 }
820 }
821 // Make the row the user clicked on the focused row.
822 ListView_SetItemState(window, view_index, LVIS_FOCUSED,
823 LVIS_FOCUSED);
824 if (select) {
825 if (!native_table_win->IsRowSelected(view_index)) {
826 // Clear all.
827 ListView_SetItemState(window, -1, 0, LVIS_SELECTED);
828 // And select the row the user clicked on.
829 native_table_win->SetSelectedState(view_index, true);
830 } else {
831 // The item is already selected, don't clear the state right away
832 // in case the user drags. Instead wait for mouse up, then only
833 // select the row the user clicked on.
834 select_on_mouse_up = true;
835 }
836 ListView_SetSelectionMark(window, view_index);
837 }
838 native_table_win->ignore_listview_change_ = false;
839 table->observer()->OnSelectionChanged();
840 SetCapture(window);
841 return 0;
842 }
843 // else case, continue on to default handler
844 }
845 break;
846 }
847
848 case WM_MOUSEMOVE: {
849 if (in_mouse_down) {
850 int x = GET_X_LPARAM(l_param);
851 int y = GET_Y_LPARAM(l_param);
852 if (View::ExceededDragThreshold(x - mouse_down_x, y - mouse_down_y)) {
853 // We're about to start drag and drop, which results in no mouse up.
854 // Release capture and reset state.
855 ReleaseCapture();
856 in_mouse_down = false;
857
858 NMLISTVIEW details;
859 memset(&details, 0, sizeof(details));
860 details.hdr.code = LVN_BEGINDRAG;
861 SendMessage(::GetParent(window), WM_NOTIFY, 0,
862 reinterpret_cast<LPARAM>(&details));
863 }
864 return 0;
865 }
866 break;
867 }
868
869 default:
870 break;
871 }
872
873 DCHECK(native_table_win->original_handler_);
874 return CallWindowProc(native_table_win->original_handler_, window, message,
875 w_param, l_param);
876 }
877
878 LRESULT CALLBACK NativeTableWin::TableHeaderWndProc(HWND window, UINT message,
879 WPARAM w_param,
880 LPARAM l_param) {
881 NativeTableWin* native_table_win = reinterpret_cast<NativeTableWin*>(
882 GetWindowLongPtr(window, GWLP_USERDATA));
883
884 switch (message) {
885 case WM_SETCURSOR:
886 if (!native_table_win->table_->resizable_columns())
887 // Prevents the cursor from changing to the resize cursor.
888 return TRUE;
889 break;
890 // TODO(jcampan): we should also block single click messages on the
891 // separator as right now columns can still be resized.
892 case WM_LBUTTONDBLCLK:
893 if (!native_table_win->table_->resizable_columns())
894 // Prevents the double-click on the column separator from auto-resizing
895 // the column.
896 return TRUE;
897 break;
898 default:
899 break;
900 }
901 DCHECK(native_table_win->header_original_handler_);
902 return CallWindowProc(native_table_win->header_original_handler_,
903 window, message, w_param, l_param);
904 }
905
906 ////////////////////////////////////////////////////////////////////////////////
907 // NativeTableWrapper, public:
908
909 // static
910 NativeTableWrapper* NativeTableWrapper::CreateNativeWrapper(TableView2* table) {
911 return new NativeTableWin(table);
912 }
913
914 } // namespace views
OLDNEW
« no previous file with comments | « views/controls/table/native_table_win.h ('k') | views/controls/table/native_table_wrapper.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698