blob: b51cff75bf1b56c1ae8718166acca00217b9cd5b [file] [log] [blame]
// Copyright 2012 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/ui/bookmarks/bookmark_drag_drop.h"
#include "base/bind.h"
#include "base/message_loop/message_loop_current.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/bookmarks/bookmark_utils.h"
#include "chrome/grit/platform_locale_settings.h"
#include "components/bookmarks/browser/base_bookmark_model_observer.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node_data.h"
#include "components/bookmarks/browser/bookmark_utils.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_palette.h"
#include "ui/gfx/font.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/render_text.h"
#include "ui/resources/grit/ui_resources.h"
#include "ui/views/drag_utils.h"
#include "ui/views/style/platform_style.h"
#include "ui/views/style/typography.h"
#include "ui/views/style/typography_provider.h"
#include "ui/views/widget/widget.h"
using bookmarks::BookmarkModel;
using bookmarks::BookmarkNode;
namespace chrome {
namespace {
class BookmarkDragHelper;
// Generates a bookmark drag and drop chip image.
class BookmarkDragImageSource : public gfx::CanvasImageSource {
public:
// These DIP measurements come from the Bookmarks Drag Drop spec.
static constexpr int kContainerWidth = 172;
static constexpr int kContainerHeight = 40;
static constexpr int kContainerRadius = kContainerHeight / 2;
static constexpr SkColor kContainerColor = gfx::kGoogleBlue500;
static constexpr int kIconContainerRadius = 12;
static constexpr int kIconSize = 16;
static constexpr SkColor kIconContainerColor = SK_ColorWHITE;
static constexpr int kTitlePadding = 12;
static constexpr int kCountPadding = 5;
static constexpr int kCountContainerRadius = 12;
static constexpr SkColor kCountContainerColor = gfx::kGoogleRed500;
static constexpr gfx::Size kBookmarkDragImageSize =
gfx::Size(kContainerWidth, kContainerHeight + kCountContainerRadius);
static constexpr int kDragImageOffsetX = kContainerWidth / 2;
static constexpr int kDragImageOffsetY = 0.9 * kContainerHeight;
BookmarkDragImageSource(const base::string16& title,
const gfx::ImageSkia& icon,
size_t count)
: gfx::CanvasImageSource(kBookmarkDragImageSize, false),
title_(title),
icon_(icon),
count_(count) {}
private:
// gfx::CanvasImageSource overrides:
void Draw(gfx::Canvas* canvas) override {
cc::PaintFlags paint_flags;
paint_flags.setAntiAlias(true);
// Draw background.
gfx::RectF container_rect(0, kCountContainerRadius, kContainerWidth,
kContainerHeight);
paint_flags.setColor(kContainerColor);
canvas->DrawRoundRect(container_rect, kContainerRadius, paint_flags);
// Draw icon container.
paint_flags.setColor(kIconContainerColor);
canvas->DrawCircle(
gfx::PointF(kContainerRadius, kContainerRadius + kCountContainerRadius),
kIconContainerRadius, paint_flags);
// Draw icon image.
canvas->DrawImageInt(
icon_, kContainerRadius - kIconSize / 2,
kContainerRadius + kIconContainerRadius - kIconSize / 2);
// Draw bookmark title.
gfx::FontList font_list = views::style::GetFont(
views::style::CONTEXT_LABEL, views::style::STYLE_PRIMARY);
gfx::Rect text_rect(kBookmarkDragImageSize);
text_rect.Inset(kContainerRadius + kIconContainerRadius + kTitlePadding,
kCountContainerRadius,
kContainerRadius - kIconContainerRadius, 0);
canvas->DrawStringRectWithFlags(title_, font_list, SK_ColorWHITE, text_rect,
gfx::Canvas::TEXT_ALIGN_LEFT);
if (count_ <= 1)
return;
// Draw bookmark count if more than 1 bookmark is dragged.
base::string16 count = base::NumberToString16(count_);
auto render_text = gfx::RenderText::CreateFor(gfx::Typesetter::BROWSER);
render_text->SetFontList(font_list);
render_text->SetCursorEnabled(false);
render_text->SetColor(SK_ColorWHITE);
render_text->SetText(count);
render_text->SetHorizontalAlignment(gfx::ALIGN_CENTER);
// We measure the count text size to determine container width, as the
// container is a rounded rect behind the text.
int count_width = render_text->GetStringSize().width();
int count_container_width =
std::max(kCountContainerRadius * 2, count_width + 2 * kCountPadding);
// Draw the count container.
gfx::Rect count_container_rect(
container_rect.right() - count_container_width, 0,
count_container_width, kCountContainerRadius * 2);
paint_flags.setColor(kCountContainerColor);
canvas->DrawRoundRect(gfx::RectF(count_container_rect),
kCountContainerRadius, paint_flags);
// Draw the count text.
render_text->SetDisplayRect(count_container_rect);
render_text->Draw(canvas);
}
const base::string16 title_;
const gfx::ImageSkia icon_;
const int count_;
};
constexpr gfx::Size BookmarkDragImageSource::kBookmarkDragImageSize;
// Helper class that takes a drag request, loads the icon from the bookmark
// model and then launches a system drag with a generated drag image.
// Owns itself.
class BookmarkDragHelper : public bookmarks::BaseBookmarkModelObserver {
public:
static base::WeakPtr<BookmarkDragHelper> Create(
Profile* profile,
const BookmarkDragParams& params,
DoBookmarkDragCallback do_drag_callback) {
base::WeakPtr<BookmarkDragHelper> ptr =
(new BookmarkDragHelper(profile, params, std::move(do_drag_callback)))
->GetWeakPtr();
ptr->Start(params.nodes.at(params.drag_node_index));
return ptr;
}
private:
BookmarkDragHelper(Profile* profile,
const BookmarkDragParams& params,
DoBookmarkDragCallback do_drag_callback)
: model_(BookmarkModelFactory::GetForBrowserContext(profile)),
count_(params.nodes.size()),
native_view_(params.view),
source_(params.source),
do_drag_callback_(std::move(do_drag_callback)),
observer_(this),
weak_factory_(this) {
observer_.Add(model_);
// Set up our OLE machinery.
bookmarks::BookmarkNodeData bookmark_drag_data(params.nodes);
bookmark_drag_data.Write(profile->GetPath(), &drag_data_);
operation_ = ui::DragDropTypes::DRAG_COPY | ui::DragDropTypes::DRAG_LINK;
if (bookmarks::CanAllBeEditedByUser(model_->client(), params.nodes))
operation_ |= ui::DragDropTypes::DRAG_MOVE;
}
void Start(const BookmarkNode* drag_node) {
drag_node_id_ = drag_node->id();
gfx::ImageSkia icon;
if (drag_node->is_url()) {
const gfx::Image& image = model_->GetFavicon(drag_node);
// If favicon is not loaded, the above call will initiate loading, and
// drag will proceed in BookmarkNodeFaviconChanged(). In rare cases,
// BookmarkNodeFaviconChanged() will never be called (e.g unfortunate
// bookmark deletion timing) and we intentionally leak at most one request
// in these cases which will clean up next drag.
if (!drag_node->is_favicon_loaded())
return;
icon = image.AsImageSkia();
} else {
icon = GetBookmarkFolderIcon(
ui::NativeTheme::GetInstanceForNativeUi()->GetSystemColor(
ui::NativeTheme::kColorId_LabelEnabledColor));
}
OnBookmarkIconLoaded(drag_node, icon);
}
void OnBookmarkIconLoaded(const BookmarkNode* drag_node,
const gfx::ImageSkia& icon) {
gfx::ImageSkia drag_image(
std::make_unique<BookmarkDragImageSource>(
drag_node->GetTitle(),
icon.isNull()
? *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
IDR_DEFAULT_FAVICON)
: icon,
count_),
BookmarkDragImageSource::kBookmarkDragImageSize);
drag_data_.provider().SetDragImage(
drag_image, gfx::Vector2d(BookmarkDragImageSource::kDragImageOffsetX,
BookmarkDragImageSource::kDragImageOffsetY));
std::move(do_drag_callback_)
.Run(drag_data_, native_view_, source_, operation_);
delete this;
}
base::WeakPtr<BookmarkDragHelper> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
// bookmarks::BaseBookmarkModelObserver overrides:
void BookmarkModelChanged() override {}
void BookmarkModelBeingDeleted(BookmarkModel* model) override { delete this; }
void BookmarkNodeFaviconChanged(BookmarkModel* model,
const BookmarkNode* node) override {
if (node->id() != drag_node_id_)
return;
const gfx::Image& image = model_->GetFavicon(node);
DCHECK(node->is_favicon_loaded());
OnBookmarkIconLoaded(node, image.AsImageSkia());
}
BookmarkModel* model_;
int64_t drag_node_id_ = -1;
int count_;
gfx::NativeView native_view_;
ui::DragDropTypes::DragEventSource source_;
int operation_;
DoBookmarkDragCallback do_drag_callback_;
ui::OSExchangeData drag_data_;
ScopedObserver<bookmarks::BookmarkModel, bookmarks::BookmarkModelObserver>
observer_;
base::WeakPtrFactory<BookmarkDragHelper> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(BookmarkDragHelper);
};
void DoDragImpl(const ui::OSExchangeData& drag_data,
gfx::NativeView native_view,
ui::DragDropTypes::DragEventSource source,
int operation) {
// Allow nested run loop so we get DnD events as we drag this around.
base::MessageLoopCurrent::ScopedNestableTaskAllower nestable_task_allower;
views::Widget* widget = views::Widget::GetWidgetForNativeView(native_view);
DCHECK(widget);
widget->RunShellDrag(nullptr, drag_data, gfx::Point(), operation, source);
}
void DragBookmarksImpl(Profile* profile,
const BookmarkDragParams& params,
DoBookmarkDragCallback do_drag_callback) {
DCHECK(!params.nodes.empty());
static base::NoDestructor<base::WeakPtr<BookmarkDragHelper>> g_drag_helper;
if (*g_drag_helper)
delete g_drag_helper->get();
DCHECK(!*g_drag_helper);
// Cleaned up in
// BookmarkDragHelper::BookmarkIconLoaded()/BookmarkModelBeingDeleted(), or
// above when a new drag is initiated before the favicon loads.
*g_drag_helper =
BookmarkDragHelper::Create(profile, params, std::move(do_drag_callback));
}
} // namespace
void DragBookmarks(Profile* profile, const BookmarkDragParams& params) {
DragBookmarksImpl(profile, params, base::BindOnce(&DoDragImpl));
}
void DragBookmarksForTest(Profile* profile,
const BookmarkDragParams& params,
DoBookmarkDragCallback do_drag_callback) {
DragBookmarksImpl(profile, params, std::move(do_drag_callback));
}
} // namespace chrome