summaryrefslogtreecommitdiffstats
path: root/views/controls/table/group_table_view.cc
blob: b5d569861e8f1bb345d87231ea8a502737184297 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "views/controls/table/group_table_view.h"

#include "app/gfx/chrome_canvas.h"
#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/task.h"

namespace views {

static const COLORREF kSeparatorLineColor = RGB(208, 208, 208);
static const int kSeparatorLineThickness = 1;

const char GroupTableView::kViewClassName[] = "views/GroupTableView";

GroupTableView::GroupTableView(GroupTableModel* model,
                               const std::vector<TableColumn>& columns,
                               TableTypes table_type,
                               bool single_selection,
                               bool resizable_columns,
                               bool autosize_columns)
    : TableView(model, columns, table_type, false, resizable_columns,
                autosize_columns),
    model_(model),
    ALLOW_THIS_IN_INITIALIZER_LIST(sync_selection_factory_(this)) {
}

GroupTableView::~GroupTableView() {
}

void GroupTableView::SyncSelection() {
  int index = 0;
  int row_count = model_->RowCount();
  GroupRange group_range;
  while (index < row_count) {
    model_->GetGroupRangeForItem(index, &group_range);
    if (group_range.length == 1) {
      // No synching required for single items.
      index++;
    }  else {
      // We need to select the group if at least one item is selected.
      bool should_select = false;
      for (int i = group_range.start;
           i < group_range.start + group_range.length; ++i) {
        if (IsItemSelected(i)) {
          should_select = true;
          break;
        }
      }
      if (should_select) {
        for (int i = group_range.start;
             i < group_range.start + group_range.length; ++i) {
          SetSelectedState(i, true);
        }
      }
      index += group_range.length;
    }
  }
}

void GroupTableView::OnKeyDown(unsigned short virtual_keycode) {
  // In a list view, multiple items can be selected but only one item has the
  // focus. This creates a problem when the arrow keys are used for navigating
  // between items in the list view. An example will make this more clear:
  //
  // Suppose we have 5 items in the list view, and three of these items are
  // part of one group:
  //
  // Index0: ItemA (No Group)
  // Index1: ItemB (GroupX)
  // Index2: ItemC (GroupX)
  // Index3: ItemD (GroupX)
  // Index4: ItemE (No Group)
  //
  // When GroupX is selected (say, by clicking on ItemD with the mouse),
  // GroupTableView::SyncSelection() will make sure ItemB, ItemC and ItemD are
  // selected. Also, the item with the focus will be ItemD (simply because
  // this is the item the user happened to click on). If then the UP arrow is
  // pressed once, the focus will be switched to ItemC and not to ItemA and the
  // end result is that we are stuck in GroupX even though the intention was to
  // switch to ItemA.
  //
  // For that exact reason, we need to set the focus appropriately when we
  // detect that one of the arrow keys is pressed. Thus, when it comes time
  // for the list view control to actually switch the focus, the right item
  // will be selected.
  if ((virtual_keycode != VK_UP) && (virtual_keycode != VK_DOWN)) {
    TableView::OnKeyDown(virtual_keycode);
    return;
  }

  // We start by finding the index of the item with the focus. If no item
  // currently has the focus, then this routine doesn't do anything.
  int focused_index;
  int row_count = model_->RowCount();
  for (focused_index = 0; focused_index < row_count; focused_index++) {
    if (ItemHasTheFocus(focused_index)) {
      break;
    }
  }

  if (focused_index == row_count) {
    return;
  }
  DCHECK_LT(focused_index, row_count);

  // Nothing to do if the item which has the focus is not part of a group.
  GroupRange group_range;
  model_->GetGroupRangeForItem(focused_index, &group_range);
  if (group_range.length == 1) {
    return;
  }

  // If the user pressed the UP key, then the focus should be set to the
  // topmost element in the group. If the user pressed the DOWN key, the focus
  // should be set to the bottommost element.
  if (virtual_keycode == VK_UP) {
    SetFocusOnItem(group_range.start);
  } else {
    DCHECK_EQ(virtual_keycode, VK_DOWN);
    SetFocusOnItem(group_range.start + group_range.length - 1);
  }
}

void GroupTableView::PrepareForSort() {
  GroupRange range;
  int row_count = RowCount();
  model_index_to_range_start_map_.clear();
  for (int model_row = 0; model_row < row_count;) {
    model_->GetGroupRangeForItem(model_row, &range);
    for (int range_counter = 0; range_counter < range.length; range_counter++)
      model_index_to_range_start_map_[range_counter + model_row] = model_row;
    model_row += range.length;
  }
}

int GroupTableView::CompareRows(int model_row1, int model_row2) {
  int range1 = model_index_to_range_start_map_[model_row1];
  int range2 = model_index_to_range_start_map_[model_row2];
  if (range1 == range2) {
    // The two rows are in the same group, sort so that items in the same group
    // always appear in the same order.
    return model_row1 - model_row2;
  }
  // Sort by the first entry of each of the groups.
  return TableView::CompareRows(range1, range2);
}

void GroupTableView::OnSelectedStateChanged() {
  // The goal is to make sure all items for a same group are in a consistent
  // state in term of selection. When a user clicks an item, several selection
  // messages are sent, possibly including unselecting all currently selected
  // items. For that reason, we post a task to be performed later, after all
  // selection messages have been processed. In the meantime we just ignore all
  // selection notifications.
  if (sync_selection_factory_.empty()) {
    MessageLoop::current()->PostTask(FROM_HERE,
        sync_selection_factory_.NewRunnableMethod(
            &GroupTableView::SyncSelection));
  }
  TableView::OnSelectedStateChanged();
}

// Draws the line separator betweens the groups.
void GroupTableView::PostPaint(int model_row, int column, bool selected,
                               const gfx::Rect& bounds, HDC hdc) {
  GroupRange group_range;
  model_->GetGroupRangeForItem(model_row, &group_range);

  // We always paint a vertical line at the end of the last cell.
  HPEN hPen = CreatePen(PS_SOLID, kSeparatorLineThickness, kSeparatorLineColor);
  HPEN hPenOld = (HPEN) SelectObject(hdc, hPen);
  int x = static_cast<int>(bounds.right() - kSeparatorLineThickness);
  MoveToEx(hdc, x, bounds.y(), NULL);
  LineTo(hdc, x, bounds.bottom());

  // We paint a separator line after the last item of a group.
  if (model_row == (group_range.start + group_range.length - 1)) {
    int y = static_cast<int>(bounds.bottom() - kSeparatorLineThickness);
    MoveToEx(hdc, 0, y, NULL);
    LineTo(hdc, bounds.width(), y);
  }
  SelectObject(hdc, hPenOld);
  DeleteObject(hPen);
}

std::string GroupTableView::GetClassName() const {
  return kViewClassName;
}

}  // namespace views