| 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 |