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