blob: d4dbb78fa68e00e86e1e615265e2825c8038e06b [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/cocoa/bookmarks/bookmark_drag_drop_cocoa.h"
#import <Cocoa/Cocoa.h>
#include <cmath>
#include "base/logging.h"
#include "base/mac/scoped_nsobject.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/string16.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/bookmarks/bookmark_node_data.h"
#include "chrome/browser/bookmarks/bookmark_pasteboard_helper_mac.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h"
#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
#include "grit/ui_resources.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
namespace chrome {
namespace {
// Make a drag image from the drop data.
NSImage* MakeDragImage(BookmarkModel* model,
const std::vector<const BookmarkNode*>& nodes) {
if (nodes.size() == 1) {
const BookmarkNode* node = nodes[0];
const gfx::Image& favicon = model->GetFavicon(node);
return DragImageForBookmark(
favicon.IsEmpty() ? nil : favicon.ToNSImage(), node->GetTitle());
} else {
// TODO(feldstein): Do something better than this. Should have badging
// and a single drag image.
// http://crbug.com/37264
return [NSImage imageNamed:NSImageNameMultipleDocuments];
}
}
// Draws string |title| within box |frame|, positioning it at the origin.
// Truncates text with fading if it is too long to fit horizontally.
// Based on code from GradientButtonCell but simplified where possible.
void DrawTruncatedTitle(NSAttributedString* title, NSRect frame) {
NSSize size = [title size];
if (std::floor(size.width) <= NSWidth(frame)) {
[title drawAtPoint:frame.origin];
return;
}
// Gradient is about twice our line height long.
CGFloat gradient_width = std::min(size.height * 2, NSWidth(frame) / 4);
NSRect solid_part, gradient_part;
NSDivideRect(frame, &gradient_part, &solid_part, gradient_width, NSMaxXEdge);
CGContextRef context = static_cast<CGContextRef>(
[[NSGraphicsContext currentContext] graphicsPort]);
CGContextBeginTransparencyLayerWithRect(context, NSRectToCGRect(frame), 0);
{ // Draw text clipped to frame.
gfx::ScopedNSGraphicsContextSaveGState scoped_state;
[NSBezierPath clipRect:frame];
[title drawAtPoint:frame.origin];
}
NSColor* color = [NSColor blackColor];
NSColor* alpha_color = [color colorWithAlphaComponent:0.0];
base::scoped_nsobject<NSGradient> mask(
[[NSGradient alloc] initWithStartingColor:color endingColor:alpha_color]);
// Draw the gradient mask.
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
[mask drawFromPoint:NSMakePoint(NSMaxX(frame) - gradient_width,
NSMinY(frame))
toPoint:NSMakePoint(NSMaxX(frame),
NSMinY(frame))
options:NSGradientDrawsBeforeStartingLocation];
CGContextEndTransparencyLayer(context);
}
} // namespace
NSImage* DragImageForBookmark(NSImage* favicon, const string16& title) {
// If no favicon, use a default.
if (!favicon) {
ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage();
}
// If no title, just use icon.
if (title.empty())
return favicon;
NSString* ns_title = base::SysUTF16ToNSString(title);
// Set the look of the title.
NSDictionary* attrs =
[NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:
[NSFont smallSystemFontSize]]
forKey:NSFontAttributeName];
base::scoped_nsobject<NSAttributedString> rich_title(
[[NSAttributedString alloc] initWithString:ns_title attributes:attrs]);
// Set up sizes and locations for rendering.
const CGFloat kIconMargin = 2.0; // Gap between icon and text.
CGFloat text_left = [favicon size].width + kIconMargin;
NSSize drag_image_size = [favicon size];
NSSize text_size = [rich_title size];
CGFloat max_text_width = bookmarks::kDefaultBookmarkWidth - text_left;
text_size.width = std::min(text_size.width, max_text_width);
drag_image_size.width = text_left + text_size.width;
// Render the drag image.
NSImage* drag_image =
[[[NSImage alloc] initWithSize:drag_image_size] autorelease];
[drag_image lockFocus];
[favicon drawAtPoint:NSZeroPoint
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:0.7];
NSRect target_text_rect = NSMakeRect(text_left, 0,
text_size.width, drag_image_size.height);
DrawTruncatedTitle(rich_title, target_text_rect);
[drag_image unlockFocus];
return drag_image;
}
void DragBookmarks(Profile* profile,
const std::vector<const BookmarkNode*>& nodes,
gfx::NativeView view) {
DCHECK(!nodes.empty());
// Allow nested message loop so we get DnD events as we drag this around.
bool was_nested = base::MessageLoop::current()->IsNested();
base::MessageLoop::current()->SetNestableTasksAllowed(true);
std::vector<BookmarkNodeData::Element> elements;
for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin();
it != nodes.end(); ++it) {
elements.push_back(BookmarkNodeData::Element(*it));
}
bookmark_pasteboard_helper_mac::WriteToPasteboard(
bookmark_pasteboard_helper_mac::kDragPasteboard,
elements,
profile->GetPath());
// Synthesize an event for dragging, since we can't be sure that
// [NSApp currentEvent] will return a valid dragging event.
NSWindow* window = [view window];
NSPoint position = [window mouseLocationOutsideOfEventStream];
NSTimeInterval event_time = [[NSApp currentEvent] timestamp];
NSEvent* drag_event = [NSEvent mouseEventWithType:NSLeftMouseDragged
location:position
modifierFlags:NSLeftMouseDraggedMask
timestamp:event_time
windowNumber:[window windowNumber]
context:nil
eventNumber:0
clickCount:1
pressure:1.0];
// TODO(avi): Do better than this offset.
NSImage* drag_image = chrome::MakeDragImage(
BookmarkModelFactory::GetForProfile(profile), nodes);
NSSize image_size = [drag_image size];
position.x -= std::floor(image_size.width / 2);
position.y -= std::floor(image_size.height / 5);
[window dragImage:drag_image
at:position
offset:NSZeroSize
event:drag_event
pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard]
source:nil
slideBack:YES];
base::MessageLoop::current()->SetNestableTasksAllowed(was_nested);
}
} // namespace chrome