OLD | NEW |
| (Empty) |
1 // Copyright (c) 2010 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 "chrome/browser/views/bookmark_table_view.h" | |
6 | |
7 #include <commctrl.h> | |
8 | |
9 #include "app/drag_drop_types.h" | |
10 #include "app/l10n_util.h" | |
11 #include "app/os_exchange_data.h" | |
12 #include "app/os_exchange_data_provider_win.h" | |
13 #include "app/resource_bundle.h" | |
14 #include "base/base_drag_source.h" | |
15 #include "chrome/browser/bookmarks/bookmark_utils.h" | |
16 #include "chrome/browser/bookmarks/bookmark_model.h" | |
17 #include "chrome/browser/bookmarks/bookmark_table_model.h" | |
18 #include "chrome/browser/pref_service.h" | |
19 #include "chrome/browser/profile.h" | |
20 #include "chrome/common/pref_names.h" | |
21 #include "gfx/canvas.h" | |
22 #include "grit/generated_resources.h" | |
23 #include "views/controls/table/table_view_observer.h" | |
24 #include "views/view_constants.h" | |
25 | |
26 namespace { | |
27 | |
28 // Height of the drop indicator used when dropping between rows. | |
29 const int kDropHighlightHeight = 2; | |
30 | |
31 int GetWidthOfColumn(const std::vector<TableColumn>& columns, | |
32 const std::vector<int> widths, | |
33 int column_id) { | |
34 for (size_t i = 0; i < columns.size(); ++i) { | |
35 if (columns[i].id == column_id) | |
36 return widths[i]; | |
37 } | |
38 NOTREACHED(); | |
39 return -1; | |
40 } | |
41 | |
42 } // namespace | |
43 | |
44 void BookmarkTableView::DropInfo::Scrolled() { | |
45 view_->UpdateDropInfo(); | |
46 } | |
47 | |
48 BookmarkTableView::BookmarkTableView(Profile* profile, | |
49 BookmarkTableModel* model) | |
50 : views::TableView(model, std::vector<TableColumn>(), | |
51 views::ICON_AND_TEXT, false, true, true), | |
52 profile_(profile), | |
53 show_path_column_(false) { | |
54 UpdateColumns(); | |
55 } | |
56 | |
57 bool BookmarkTableView::CanDrop(const OSExchangeData& data) { | |
58 if (!parent_node_ || !profile_->GetBookmarkModel()->IsLoaded()) | |
59 return false; | |
60 | |
61 BookmarkDragData drag_data; | |
62 if (!drag_data.Read(data)) | |
63 return false; | |
64 | |
65 // Don't allow the user to drop an ancestor of the parent node onto the | |
66 // parent node. This would create a cycle, which is definitely a no-no. | |
67 std::vector<const BookmarkNode*> nodes = drag_data.GetNodes(profile_); | |
68 for (size_t i = 0; i < nodes.size(); ++i) { | |
69 if (parent_node_->HasAncestor(nodes[i])) | |
70 return false; | |
71 } | |
72 | |
73 drop_info_.reset(new DropInfo(this)); | |
74 drop_info_->SetData(drag_data); | |
75 return true; | |
76 } | |
77 | |
78 void BookmarkTableView::OnDragEntered(const views::DropTargetEvent& event) { | |
79 } | |
80 | |
81 int BookmarkTableView::OnDragUpdated(const views::DropTargetEvent& event) { | |
82 if (!parent_node_ || !drop_info_.get()) { | |
83 drop_info_.reset(NULL); | |
84 return false; | |
85 } | |
86 | |
87 drop_info_->Update(event); | |
88 return UpdateDropInfo(); | |
89 } | |
90 | |
91 void BookmarkTableView::OnDragExited() { | |
92 SetDropPosition(DropPosition()); | |
93 drop_info_.reset(); | |
94 } | |
95 | |
96 int BookmarkTableView::OnPerformDrop(const views::DropTargetEvent& event) { | |
97 OnPerformDropImpl(); | |
98 int drop_operation = drop_info_->drop_operation(); | |
99 SetDropPosition(DropPosition()); | |
100 drop_info_.reset(); | |
101 return drop_operation; | |
102 } | |
103 | |
104 BookmarkTableModel* BookmarkTableView::bookmark_table_model() const { | |
105 return static_cast<BookmarkTableModel*>(model()); | |
106 } | |
107 | |
108 void BookmarkTableView::SaveColumnConfiguration() { | |
109 PrefService* prefs = profile_->GetPrefs(); | |
110 if (!prefs) | |
111 return; | |
112 | |
113 if (show_path_column_) { | |
114 prefs->SetInteger(prefs::kBookmarkTableNameWidth2, | |
115 GetColumnWidth(IDS_BOOKMARK_TABLE_TITLE)); | |
116 prefs->SetInteger(prefs::kBookmarkTableURLWidth2, | |
117 GetColumnWidth(IDS_BOOKMARK_TABLE_URL)); | |
118 prefs->SetInteger(prefs::kBookmarkTablePathWidth, | |
119 GetColumnWidth(IDS_BOOKMARK_TABLE_PATH)); | |
120 } else { | |
121 prefs->SetInteger(prefs::kBookmarkTableNameWidth1, | |
122 GetColumnWidth(IDS_BOOKMARK_TABLE_TITLE)); | |
123 prefs->SetInteger(prefs::kBookmarkTableURLWidth1, | |
124 GetColumnWidth(IDS_BOOKMARK_TABLE_URL)); | |
125 } | |
126 } | |
127 | |
128 void BookmarkTableView::SetShowPathColumn(bool show_path_column) { | |
129 if (show_path_column == show_path_column_) | |
130 return; | |
131 | |
132 SaveColumnConfiguration(); | |
133 | |
134 show_path_column_ = show_path_column; | |
135 UpdateColumns(); | |
136 } | |
137 | |
138 void BookmarkTableView::PostPaint() { | |
139 PaintAltText(); | |
140 | |
141 if (!drop_info_.get() || drop_info_->position().index == -1 || | |
142 drop_info_->position().on) { | |
143 return; | |
144 } | |
145 | |
146 RECT bounds = GetDropBetweenHighlightRect(drop_info_->position().index); | |
147 HDC dc = GetDC(GetNativeControlHWND()); | |
148 HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_WINDOWTEXT)); | |
149 FillRect(dc, &bounds, brush); | |
150 DeleteObject(brush); | |
151 ReleaseDC(GetNativeControlHWND(), dc); | |
152 } | |
153 | |
154 LRESULT BookmarkTableView::OnNotify(int w_param, LPNMHDR l_param) { | |
155 switch (l_param->code) { | |
156 case LVN_BEGINDRAG: | |
157 BeginDrag(); | |
158 return 0; // Return value doesn't matter for this message. | |
159 } | |
160 | |
161 return TableView::OnNotify(w_param, l_param); | |
162 } | |
163 | |
164 int BookmarkTableView::UpdateDropInfo() { | |
165 DropPosition position = CalculateDropPosition(drop_info_->last_y()); | |
166 | |
167 drop_info_->set_drop_operation(CalculateDropOperation(position)); | |
168 | |
169 if (drop_info_->drop_operation() == DragDropTypes::DRAG_NONE) | |
170 position = DropPosition(); | |
171 | |
172 SetDropPosition(position); | |
173 | |
174 return drop_info_->drop_operation(); | |
175 } | |
176 | |
177 void BookmarkTableView::BeginDrag() { | |
178 std::vector<const BookmarkNode*> nodes_to_drag; | |
179 for (TableView::iterator i = SelectionBegin(); i != SelectionEnd(); ++i) | |
180 nodes_to_drag.push_back(bookmark_table_model()->GetNodeForRow(*i)); | |
181 if (nodes_to_drag.empty()) | |
182 return; // Nothing to drag. | |
183 | |
184 // Reverse the nodes so that they are put on the clipboard in visual order. | |
185 // We do this as SelectionBegin starts at the end of the visual order. | |
186 std::reverse(nodes_to_drag.begin(), nodes_to_drag.end()); | |
187 | |
188 OSExchangeData data; | |
189 BookmarkDragData(nodes_to_drag).Write(profile_, &data); | |
190 scoped_refptr<BaseDragSource> drag_source(new BaseDragSource); | |
191 DWORD effects; | |
192 DoDragDrop(OSExchangeDataProviderWin::GetIDataObject(data), drag_source, | |
193 DROPEFFECT_LINK | DROPEFFECT_COPY | DROPEFFECT_MOVE, &effects); | |
194 } | |
195 | |
196 int BookmarkTableView::CalculateDropOperation(const DropPosition& position) { | |
197 if (drop_info_->data().IsFromProfile(profile_)) { | |
198 // Data from the same profile. Prefer move, but do copy if the user wants | |
199 // that. | |
200 if (drop_info_->is_control_down()) | |
201 return DragDropTypes::DRAG_COPY; | |
202 | |
203 int real_drop_index; | |
204 const BookmarkNode* drop_parent = GetDropParentAndIndex(position, | |
205 &real_drop_index); | |
206 if (!bookmark_utils::IsValidDropLocation( | |
207 profile_, drop_info_->data(), drop_parent, real_drop_index)) { | |
208 return DragDropTypes::DRAG_NONE; | |
209 } | |
210 return DragDropTypes::DRAG_MOVE; | |
211 } | |
212 // We're going to copy, but return an operation compatible with the source | |
213 // operations so that the user can drop. | |
214 return bookmark_utils::PreferredDropOperation( | |
215 drop_info_->source_operations(), | |
216 DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); | |
217 } | |
218 | |
219 void BookmarkTableView::OnPerformDropImpl() { | |
220 int drop_index; | |
221 const BookmarkNode* drop_parent = GetDropParentAndIndex( | |
222 drop_info_->position(), &drop_index); | |
223 BookmarkModel* model = profile_->GetBookmarkModel(); | |
224 int min_selection; | |
225 int max_selection; | |
226 // If the data is not from this profile we return an operation compatible | |
227 // with the source. As such, we need to need to check the data here too. | |
228 if (!drop_info_->data().IsFromProfile(profile_) || | |
229 drop_info_->drop_operation() == DragDropTypes::DRAG_COPY) { | |
230 bookmark_utils::CloneDragData(model, drop_info_->data().elements, | |
231 drop_parent, drop_index); | |
232 min_selection = drop_index; | |
233 max_selection = drop_index + | |
234 static_cast<int>(drop_info_->data().elements.size()); | |
235 } else { | |
236 // else, move. | |
237 std::vector<const BookmarkNode*> nodes = | |
238 drop_info_->data().GetNodes(profile_); | |
239 if (nodes.empty()) | |
240 return; | |
241 | |
242 for (size_t i = 0; i < nodes.size(); ++i) { | |
243 model->Move(nodes[i], drop_parent, drop_index); | |
244 // Reset the drop_index, just in case the index didn't really change. | |
245 drop_index = drop_parent->IndexOfChild(nodes[i]) + 1; | |
246 } | |
247 min_selection = drop_parent->IndexOfChild(nodes[0]); | |
248 max_selection = min_selection + static_cast<int>(nodes.size()); | |
249 } | |
250 if (drop_info_->position().on) { | |
251 // The user dropped on a folder, select it. | |
252 int index = parent_node_->IndexOfChild(drop_parent); | |
253 if (index != -1) | |
254 Select(index); | |
255 } else if (min_selection < RowCount() && max_selection <= RowCount()) { | |
256 // Select the moved/copied rows. | |
257 Select(min_selection); | |
258 if (min_selection + 1 < max_selection) { | |
259 // SetSelectedState doesn't send notification, so we manually do it. | |
260 for (int i = min_selection + 1; i < max_selection; ++i) | |
261 SetSelectedState(i, true); | |
262 if (observer()) | |
263 observer()->OnSelectionChanged(); | |
264 } | |
265 } | |
266 } | |
267 | |
268 void BookmarkTableView::SetDropPosition(const DropPosition& position) { | |
269 if (drop_info_->position().equals(position)) | |
270 return; | |
271 | |
272 UpdateDropIndicator(drop_info_->position(), false); | |
273 | |
274 drop_info_->set_position(position); | |
275 | |
276 UpdateDropIndicator(drop_info_->position(), true); | |
277 } | |
278 | |
279 void BookmarkTableView::UpdateDropIndicator(const DropPosition& position, | |
280 bool turn_on) { | |
281 if (position.index == -1) | |
282 return; | |
283 | |
284 if (position.on) { | |
285 ListView_SetItemState(GetNativeControlHWND(), position.index, | |
286 turn_on ? LVIS_DROPHILITED : 0, LVIS_DROPHILITED); | |
287 } else { | |
288 RECT bounds = GetDropBetweenHighlightRect(position.index); | |
289 InvalidateRect(GetNativeControlHWND(), &bounds, FALSE); | |
290 } | |
291 } | |
292 | |
293 BookmarkTableView::DropPosition | |
294 BookmarkTableView::CalculateDropPosition(int y) { | |
295 HWND hwnd = GetNativeControlHWND(); | |
296 int row_count = RowCount(); | |
297 int top_index = ListView_GetTopIndex(hwnd); | |
298 if (row_count == 0 || top_index < 0) | |
299 return DropPosition(0, false); | |
300 | |
301 for (int i = top_index; i < row_count; ++i) { | |
302 RECT bounds; | |
303 ListView_GetItemRect(hwnd, i, &bounds, LVIR_BOUNDS); | |
304 if (y < bounds.top) | |
305 return DropPosition(i, false); | |
306 if (y < bounds.bottom) { | |
307 if (bookmark_table_model()->GetNodeForRow(i)->is_folder()) { | |
308 if (y < bounds.top + views::kDropBetweenPixels) | |
309 return DropPosition(i, false); | |
310 if (y >= bounds.bottom - views::kDropBetweenPixels) | |
311 return DropPosition(i + 1, false); | |
312 return DropPosition(i, true); | |
313 } | |
314 if (y < (bounds.bottom - bounds.top) / 2 + bounds.top) | |
315 return DropPosition(i, false); | |
316 return DropPosition(i + 1, false); | |
317 } | |
318 } | |
319 return DropPosition(row_count, false); | |
320 } | |
321 | |
322 const BookmarkNode* BookmarkTableView::GetDropParentAndIndex( | |
323 const DropPosition& position, | |
324 int* index) { | |
325 if (position.on) { | |
326 const BookmarkNode* parent = parent_node_->GetChild(position.index); | |
327 *index = parent->GetChildCount(); | |
328 return parent; | |
329 } | |
330 *index = position.index; | |
331 return parent_node_; | |
332 } | |
333 | |
334 RECT BookmarkTableView::GetDropBetweenHighlightRect(int index) { | |
335 RECT bounds = { 0 }; | |
336 if (RowCount() == 0) { | |
337 bounds.top = content_offset(); | |
338 bounds.left = 0; | |
339 bounds.right = width(); | |
340 } else if (index >= RowCount()) { | |
341 ListView_GetItemRect(GetNativeControlHWND(), index - 1, &bounds, | |
342 LVIR_BOUNDS); | |
343 bounds.top = bounds.bottom - kDropHighlightHeight / 2; | |
344 } else { | |
345 ListView_GetItemRect(GetNativeControlHWND(), index, &bounds, LVIR_BOUNDS); | |
346 bounds.top -= kDropHighlightHeight / 2; | |
347 } | |
348 bounds.bottom = bounds.top + kDropHighlightHeight; | |
349 return bounds; | |
350 } | |
351 | |
352 void BookmarkTableView::UpdateColumns() { | |
353 PrefService* prefs = profile_->GetPrefs(); | |
354 TableColumn name_column = | |
355 TableColumn(IDS_BOOKMARK_TABLE_TITLE, TableColumn::LEFT, -1); | |
356 TableColumn url_column = | |
357 TableColumn(IDS_BOOKMARK_TABLE_URL, TableColumn::LEFT, -1); | |
358 TableColumn path_column = | |
359 TableColumn(IDS_BOOKMARK_TABLE_PATH, TableColumn::LEFT, -1); | |
360 | |
361 std::vector<TableColumn> columns; | |
362 if (show_path_column_) { | |
363 int name_width = -1; | |
364 int url_width = -1; | |
365 int path_width = -1; | |
366 if (prefs) { | |
367 name_width = prefs->GetInteger(prefs::kBookmarkTableNameWidth2); | |
368 url_width = prefs->GetInteger(prefs::kBookmarkTableURLWidth2); | |
369 path_width = prefs->GetInteger(prefs::kBookmarkTablePathWidth); | |
370 } | |
371 if (name_width != -1 && url_width != -1 && path_width != -1) { | |
372 name_column.width = name_width; | |
373 url_column.width = url_width; | |
374 path_column.width = path_width; | |
375 } else { | |
376 name_column.percent = .5; | |
377 url_column.percent = .25; | |
378 path_column.percent= .25; | |
379 } | |
380 columns.push_back(name_column); | |
381 columns.push_back(url_column); | |
382 columns.push_back(path_column); | |
383 } else { | |
384 int name_width = -1; | |
385 int url_width = -1; | |
386 if (prefs) { | |
387 name_width = prefs->GetInteger(prefs::kBookmarkTableNameWidth1); | |
388 url_width = prefs->GetInteger(prefs::kBookmarkTableURLWidth1); | |
389 } | |
390 if (name_width != -1 && url_width != -1) { | |
391 name_column.width = name_width; | |
392 url_column.width = url_width; | |
393 } else { | |
394 name_column.percent = .5; | |
395 url_column.percent = .5; | |
396 } | |
397 columns.push_back(name_column); | |
398 columns.push_back(url_column); | |
399 } | |
400 SetColumns(columns); | |
401 for (size_t i = 0; i < columns.size(); ++i) | |
402 SetColumnVisibility(columns[i].id, true); | |
403 OnModelChanged(); | |
404 } | |
OLD | NEW |