// 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 "chrome/browser/views/bookmark_table_view.h" #include "base/base_drag_source.h" #include "chrome/browser/bookmarks/bookmark_utils.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/bookmarks/bookmark_table_model.h" #include "chrome/browser/profile.h" #include "chrome/common/drag_drop_types.h" #include "chrome/common/os_exchange_data.h" #include "chrome/common/pref_names.h" #include "chrome/common/pref_service.h" #include "chrome/views/view_constants.h" #include "generated_resources.h" namespace { // Height of the drop indicator used when dropping between rows. const int kDropHighlightHeight = 2; int GetWidthOfColumn(const std::vector& columns, const std::vector widths, int column_id) { for (size_t i = 0; i < columns.size(); ++i) { if (columns[i].id == column_id) return widths[i]; } NOTREACHED(); return -1; } } // namespace void BookmarkTableView::DropInfo::Scrolled() { view_->UpdateDropInfo(); } BookmarkTableView::BookmarkTableView(Profile* profile, BookmarkTableModel* model) : views::TableView(model, std::vector(), views::ICON_AND_TEXT, false, true, true), profile_(profile), show_path_column_(false) { UpdateColumns(); } // static void BookmarkTableView::RegisterUserPrefs(PrefService* prefs) { prefs->RegisterIntegerPref(prefs::kBookmarkTableNameWidth1, -1); prefs->RegisterIntegerPref(prefs::kBookmarkTableURLWidth1, -1); prefs->RegisterIntegerPref(prefs::kBookmarkTableNameWidth2, -1); prefs->RegisterIntegerPref(prefs::kBookmarkTableURLWidth2, -1); prefs->RegisterIntegerPref(prefs::kBookmarkTablePathWidth, -1); } bool BookmarkTableView::CanDrop(const OSExchangeData& data) { if (!parent_node_ || !profile_->GetBookmarkModel()->IsLoaded()) return false; BookmarkDragData drag_data; if (!drag_data.Read(data)) return false; // Don't allow the user to drop an ancestor of the parent node onto the // parent node. This would create a cycle, which is definitely a no-no. std::vector nodes = drag_data.GetNodes(profile_); for (size_t i = 0; i < nodes.size(); ++i) { if (parent_node_->HasAncestor(nodes[i])) return false; } drop_info_.reset(new DropInfo(this)); drop_info_->SetData(drag_data); return true; } void BookmarkTableView::OnDragEntered(const views::DropTargetEvent& event) { } int BookmarkTableView::OnDragUpdated(const views::DropTargetEvent& event) { if (!parent_node_ || !drop_info_.get()) { drop_info_.reset(NULL); return false; } drop_info_->Update(event); return UpdateDropInfo(); } void BookmarkTableView::OnDragExited() { SetDropPosition(DropPosition()); drop_info_.reset(); } int BookmarkTableView::OnPerformDrop(const views::DropTargetEvent& event) { OnPerformDropImpl(); int drop_operation = drop_info_->drop_operation(); SetDropPosition(DropPosition()); drop_info_.reset(); return drop_operation; } BookmarkTableModel* BookmarkTableView::bookmark_table_model() const { return static_cast(model()); } void BookmarkTableView::SaveColumnConfiguration() { PrefService* prefs = profile_->GetPrefs(); if (!prefs) return; if (show_path_column_) { prefs->SetInteger(prefs::kBookmarkTableNameWidth2, GetColumnWidth(IDS_BOOKMARK_TABLE_TITLE)); prefs->SetInteger(prefs::kBookmarkTableURLWidth2, GetColumnWidth(IDS_BOOKMARK_TABLE_URL)); prefs->SetInteger(prefs::kBookmarkTablePathWidth, GetColumnWidth(IDS_BOOKMARK_TABLE_PATH)); } else { prefs->SetInteger(prefs::kBookmarkTableNameWidth1, GetColumnWidth(IDS_BOOKMARK_TABLE_TITLE)); prefs->SetInteger(prefs::kBookmarkTableURLWidth1, GetColumnWidth(IDS_BOOKMARK_TABLE_URL)); } } void BookmarkTableView::SetShowPathColumn(bool show_path_column) { if (show_path_column == show_path_column_) return; SaveColumnConfiguration(); show_path_column_ = show_path_column; UpdateColumns(); } void BookmarkTableView::PostPaint() { if (!drop_info_.get() || drop_info_->position().index == -1 || drop_info_->position().on) { return; } RECT bounds = GetDropBetweenHighlightRect(drop_info_->position().index); HDC dc = GetDC(GetNativeControlHWND()); HBRUSH brush = CreateSolidBrush(GetSysColor(COLOR_WINDOWTEXT)); FillRect(dc, &bounds, brush); DeleteObject(brush); ReleaseDC(GetNativeControlHWND(), dc); } LRESULT BookmarkTableView::OnNotify(int w_param, LPNMHDR l_param) { switch(l_param->code) { case LVN_BEGINDRAG: BeginDrag(); return 0; // Return value doesn't matter for this message. } return TableView::OnNotify(w_param, l_param); } int BookmarkTableView::UpdateDropInfo() { DropPosition position = CalculateDropPosition(drop_info_->last_y()); drop_info_->set_drop_operation(CalculateDropOperation(position)); if (drop_info_->drop_operation() == DragDropTypes::DRAG_NONE) position = DropPosition(); SetDropPosition(position); return drop_info_->drop_operation(); } void BookmarkTableView::BeginDrag() { std::vector nodes_to_drag; for (TableView::iterator i = SelectionBegin(); i != SelectionEnd(); ++i) nodes_to_drag.push_back(bookmark_table_model()->GetNodeForRow(*i)); if (nodes_to_drag.empty()) return; // Nothing to drag. // Reverse the nodes so that they are put on the clipboard in visual order. // We do this as SelectionBegin starts at the end of the visual order. std::reverse(nodes_to_drag.begin(), nodes_to_drag.end()); scoped_refptr data = new OSExchangeData; BookmarkDragData(nodes_to_drag).Write(profile_, data); scoped_refptr drag_source(new BaseDragSource); DWORD effects; DoDragDrop(data, drag_source, DROPEFFECT_LINK | DROPEFFECT_COPY | DROPEFFECT_MOVE, &effects); } int BookmarkTableView::CalculateDropOperation(const DropPosition& position) { if (drop_info_->data().IsFromProfile(profile_)) { // Data from the same profile. Prefer move, but do copy if the user wants // that. if (drop_info_->is_control_down()) return DragDropTypes::DRAG_COPY; int real_drop_index; BookmarkNode* drop_parent = GetDropParentAndIndex(position, &real_drop_index); if (!bookmark_utils::IsValidDropLocation( profile_, drop_info_->data(), drop_parent, real_drop_index)) { return DragDropTypes::DRAG_NONE; } return DragDropTypes::DRAG_MOVE; } // We're going to copy, but return an operation compatible with the source // operations so that the user can drop. return bookmark_utils::PreferredDropOperation( drop_info_->source_operations(), DragDropTypes::DRAG_COPY | DragDropTypes::DRAG_LINK); } void BookmarkTableView::OnPerformDropImpl() { int drop_index; BookmarkNode* drop_parent = GetDropParentAndIndex( drop_info_->position(), &drop_index); BookmarkModel* model = profile_->GetBookmarkModel(); int min_selection; int max_selection; // If the data is not from this profile we return an operation compatible // with the source. As such, we need to need to check the data here too. if (!drop_info_->data().IsFromProfile(profile_) || drop_info_->drop_operation() == DragDropTypes::DRAG_COPY) { bookmark_utils::CloneDragData(model, drop_info_->data().elements, drop_parent, drop_index); min_selection = drop_index; max_selection = drop_index + static_cast(drop_info_->data().elements.size()); } else { // else, move. std::vector nodes = drop_info_->data().GetNodes(profile_); if (nodes.empty()) return; for (size_t i = 0; i < nodes.size(); ++i) { model->Move(nodes[i], drop_parent, drop_index); // Reset the drop_index, just in case the index didn't really change. drop_index = drop_parent->IndexOfChild(nodes[i]) + 1; } min_selection = drop_parent->IndexOfChild(nodes[0]); max_selection = min_selection + static_cast(nodes.size()); } if (drop_info_->position().on) { // The user dropped on a folder, select it. int index = parent_node_->IndexOfChild(drop_parent); if (index != -1) Select(index); } else if (min_selection < RowCount() && max_selection <= RowCount()) { // Select the moved/copied rows. Select(min_selection); if (min_selection + 1 < max_selection) { // SetSelectedState doesn't send notification, so we manually do it. for (int i = min_selection + 1; i < max_selection; ++i) SetSelectedState(i, true); if (observer()) observer()->OnSelectionChanged(); } } } void BookmarkTableView::SetDropPosition(const DropPosition& position) { if (drop_info_->position().equals(position)) return; UpdateDropIndicator(drop_info_->position(), false); drop_info_->set_position(position); UpdateDropIndicator(drop_info_->position(), true); } void BookmarkTableView::UpdateDropIndicator(const DropPosition& position, bool turn_on) { if (position.index == -1) return; if (position.on) { ListView_SetItemState(GetNativeControlHWND(), position.index, turn_on ? LVIS_DROPHILITED : 0, LVIS_DROPHILITED); } else { RECT bounds = GetDropBetweenHighlightRect(position.index); InvalidateRect(GetNativeControlHWND(), &bounds, FALSE); } } BookmarkTableView::DropPosition BookmarkTableView::CalculateDropPosition(int y) { HWND hwnd = GetNativeControlHWND(); int row_count = RowCount(); int top_index = ListView_GetTopIndex(hwnd); if (row_count == 0 || top_index < 0) return DropPosition(0, false); for (int i = top_index; i < row_count; ++i) { RECT bounds; ListView_GetItemRect(hwnd, i, &bounds, LVIR_BOUNDS); if (y < bounds.top) return DropPosition(i, false); if (y < bounds.bottom) { if (bookmark_table_model()->GetNodeForRow(i)->is_folder()) { if (y < bounds.top + views::kDropBetweenPixels) return DropPosition(i, false); if (y >= bounds.bottom - views::kDropBetweenPixels) return DropPosition(i + 1, false); return DropPosition(i, true); } if (y < (bounds.bottom - bounds.top) / 2 + bounds.top) return DropPosition(i, false); return DropPosition(i + 1, false); } } return DropPosition(row_count, false); } BookmarkNode* BookmarkTableView::GetDropParentAndIndex( const DropPosition& position, int* index) { if (position.on) { BookmarkNode* parent = parent_node_->GetChild(position.index); *index = parent->GetChildCount(); return parent; } *index = position.index; return parent_node_; } RECT BookmarkTableView::GetDropBetweenHighlightRect(int index) { RECT bounds = { 0 }; if (RowCount() == 0) { bounds.top = content_offset(); bounds.left = 0; bounds.right = width(); } else if (index >= RowCount()) { ListView_GetItemRect(GetNativeControlHWND(), index - 1, &bounds, LVIR_BOUNDS); bounds.top = bounds.bottom - kDropHighlightHeight / 2; } else { ListView_GetItemRect(GetNativeControlHWND(), index, &bounds, LVIR_BOUNDS); bounds.top -= kDropHighlightHeight / 2; } bounds.bottom = bounds.top + kDropHighlightHeight; return bounds; } void BookmarkTableView::UpdateColumns() { PrefService* prefs = profile_->GetPrefs(); views::TableColumn name_column = views::TableColumn(IDS_BOOKMARK_TABLE_TITLE, views::TableColumn::LEFT, -1); views::TableColumn url_column = views::TableColumn(IDS_BOOKMARK_TABLE_URL, views::TableColumn::LEFT, -1); views::TableColumn path_column = views::TableColumn(IDS_BOOKMARK_TABLE_PATH, views::TableColumn::LEFT, -1); std::vector columns; if (show_path_column_) { int name_width = -1; int url_width = -1; int path_width = -1; if (prefs) { name_width = prefs->GetInteger(prefs::kBookmarkTableNameWidth2); url_width = prefs->GetInteger(prefs::kBookmarkTableURLWidth2); path_width = prefs->GetInteger(prefs::kBookmarkTablePathWidth); } if (name_width != -1 && url_width != -1 && path_width != -1) { name_column.width = name_width; url_column.width = url_width; path_column.width = path_width; } else { name_column.percent = .5; url_column.percent = .25; path_column.percent= .25; } columns.push_back(name_column); columns.push_back(url_column); columns.push_back(path_column); } else { int name_width = -1; int url_width = -1; if (prefs) { name_width = prefs->GetInteger(prefs::kBookmarkTableNameWidth1); url_width = prefs->GetInteger(prefs::kBookmarkTableURLWidth1); } if (name_width != -1 && url_width != -1) { name_column.width = name_width; url_column.width = url_width; } else { name_column.percent = .5; url_column.percent = .5; } columns.push_back(name_column); columns.push_back(url_column); } SetColumns(columns); for (size_t i = 0; i < columns.size(); ++i) SetColumnVisibility(columns[i].id, true); OnModelChanged(); }