blob: 9241723fdc469d5d25393836d717a44602871328 [file] [log] [blame]
// 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/gfx/chrome_canvas.h"
#include "chrome/common/gfx/chrome_font.h"
#include "chrome/common/os_exchange_data.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/common/resource_bundle.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<views::TableColumn>& columns,
const std::vector<int> 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::TableColumn>(),
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<BookmarkNode*> 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<BookmarkTableModel*>(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::SetAltText(const std::wstring& alt_text) {
if (alt_text == alt_text_)
return;
alt_text_ = alt_text;
if (!GetNativeControlHWND())
return;
RECT alt_text_bounds = GetAltTextBounds().ToRECT();
InvalidateRect(GetNativeControlHWND(), &alt_text_bounds, FALSE);
}
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() {
PaintAltText();
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<BookmarkNode*> 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<OSExchangeData> data = new OSExchangeData;
BookmarkDragData(nodes_to_drag).Write(profile_, data);
scoped_refptr<BaseDragSource> 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<int>(drop_info_->data().elements.size());
} else {
// else, move.
std::vector<BookmarkNode*> 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<int>(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<views::TableColumn> 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();
}
void BookmarkTableView::PaintAltText() {
if (alt_text_.empty())
return;
HDC dc = GetDC(GetNativeControlHWND());
ChromeFont font = GetAltTextFont();
gfx::Rect bounds = GetAltTextBounds();
ChromeCanvas canvas(bounds.width(), bounds.height(), false);
canvas.DrawStringInt(alt_text_, font, SK_ColorDKGRAY, 0, 0, bounds.width(),
bounds.height());
canvas.getTopPlatformDevice().drawToHDC(dc, bounds.x(), bounds.y(), NULL);
ReleaseDC(GetNativeControlHWND(), dc);
}
gfx::Rect BookmarkTableView::GetAltTextBounds() {
static const int kXOffset = 16;
DCHECK(GetNativeControlHWND());
CRect client_rect;
GetClientRect(GetNativeControlHWND(), client_rect);
ChromeFont font = GetAltTextFont();
return gfx::Rect(kXOffset, content_offset(), client_rect.Width() - kXOffset,
std::max(kImageSize, font.height()));
}
ChromeFont BookmarkTableView::GetAltTextFont() {
return ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::BaseFont);
}