Chromium Code Reviews| Index: chrome/browser/ui/cocoa/bookmarks/bookmark_drag_drop.mm |
| diff --git a/chrome/browser/ui/cocoa/bookmarks/bookmark_drag_drop.mm b/chrome/browser/ui/cocoa/bookmarks/bookmark_drag_drop.mm |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..dffc0218d7b6282b35dc151a724a33114ab7f66c |
| --- /dev/null |
| +++ b/chrome/browser/ui/cocoa/bookmarks/bookmark_drag_drop.mm |
| @@ -0,0 +1,183 @@ |
| +// 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.h" |
| + |
| +#import <Cocoa/Cocoa.h> |
| + |
| +#include "base/logging.h" |
| +#include "base/memory/scoped_nsobject.h" |
| +#include "base/message_loop.h" |
| +#include "base/string16.h" |
| +#include "base/sys_string_conversions.h" |
| +#include "chrome/browser/bookmarks/bookmark_model_factory.h" |
| +#include "chrome/browser/bookmarks/bookmark_model.h" |
| +#include "chrome/browser/bookmarks/bookmark_node_data.h" |
| +#include "chrome/browser/bookmarks/bookmark_utils.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h" |
| +#include "ui/base/resource/resource_bundle.h" |
| +#include "ui/gfx/mac/nsimage_cache.h" |
| +#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" |
| + |
| +namespace chrome { |
|
sail
2012/12/04 16:34:13
can this be moved below the "} // namespace" line
tfarina
2012/12/04 16:39:48
I did this way so I don't have to chrome::DragImag
sail
2012/12/04 16:41:50
Ahh, makes sense. Never mind then.
|
| + |
| +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]; |
| + 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]; |
| + 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:NSMakePoint(0, 0) |
| + 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; |
| +} |
| + |
| +} // namespace chrome |
| + |
| +namespace bookmark_utils { |
| + |
| +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 = MessageLoop::current()->IsNested(); |
| + MessageLoop::current()->SetNestableTasksAllowed(true); |
| + |
| + std::vector<BookmarkNodeData::Element> elements; |
|
tfarina
2012/12/04 16:15:53
I fold the StartDrag(...) function directly here.
|
| + for (std::vector<const BookmarkNode*>::const_iterator it = nodes.begin(); |
| + it != nodes.end(); ++it) { |
| + elements.push_back(BookmarkNodeData::Element(*it)); |
| + } |
| + |
| + WriteToPasteboard(kDragPasteboard, elements, profile->GetPath().value()); |
| + |
| + // 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 = |
| + 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]; |
| + |
| + MessageLoop::current()->SetNestableTasksAllowed(was_nested); |
| +} |
| + |
| +} // namespace bookmark_utils |