1// Copyright 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/cocoa/bookmarks/bookmark_drag_drop_cocoa.h" 6 7#import <Cocoa/Cocoa.h> 8 9#include <cmath> 10 11#include "base/logging.h" 12#include "base/mac/scoped_nsobject.h" 13#include "base/message_loop/message_loop.h" 14#include "base/strings/string16.h" 15#include "base/strings/sys_string_conversions.h" 16#include "chrome/browser/bookmarks/bookmark_model.h" 17#include "chrome/browser/bookmarks/bookmark_model_factory.h" 18#include "chrome/browser/bookmarks/bookmark_node_data.h" 19#include "chrome/browser/profiles/profile.h" 20#include "chrome/browser/ui/bookmarks/bookmark_drag_drop.h" 21#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h" 22#include "grit/ui_resources.h" 23#include "ui/base/resource/resource_bundle.h" 24#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" 25 26namespace chrome { 27 28namespace { 29 30// Make a drag image from the drop data. 31NSImage* MakeDragImage(BookmarkModel* model, 32 const std::vector<const BookmarkNode*>& nodes) { 33 if (nodes.size() == 1) { 34 const BookmarkNode* node = nodes[0]; 35 const gfx::Image& favicon = model->GetFavicon(node); 36 return DragImageForBookmark( 37 favicon.IsEmpty() ? nil : favicon.ToNSImage(), node->GetTitle()); 38 } else { 39 // TODO(feldstein): Do something better than this. Should have badging 40 // and a single drag image. 41 // http://crbug.com/37264 42 return [NSImage imageNamed:NSImageNameMultipleDocuments]; 43 } 44} 45 46// Draws string |title| within box |frame|, positioning it at the origin. 47// Truncates text with fading if it is too long to fit horizontally. 48// Based on code from GradientButtonCell but simplified where possible. 49void DrawTruncatedTitle(NSAttributedString* title, NSRect frame) { 50 NSSize size = [title size]; 51 if (std::floor(size.width) <= NSWidth(frame)) { 52 [title drawAtPoint:frame.origin]; 53 return; 54 } 55 56 // Gradient is about twice our line height long. 57 CGFloat gradient_width = std::min(size.height * 2, NSWidth(frame) / 4); 58 NSRect solid_part, gradient_part; 59 NSDivideRect(frame, &gradient_part, &solid_part, gradient_width, NSMaxXEdge); 60 CGContextRef context = static_cast<CGContextRef>( 61 [[NSGraphicsContext currentContext] graphicsPort]); 62 CGContextBeginTransparencyLayerWithRect(context, NSRectToCGRect(frame), 0); 63 { // Draw text clipped to frame. 64 gfx::ScopedNSGraphicsContextSaveGState scoped_state; 65 [NSBezierPath clipRect:frame]; 66 [title drawAtPoint:frame.origin]; 67 } 68 69 NSColor* color = [NSColor blackColor]; 70 NSColor* alpha_color = [color colorWithAlphaComponent:0.0]; 71 base::scoped_nsobject<NSGradient> mask( 72 [[NSGradient alloc] initWithStartingColor:color endingColor:alpha_color]); 73 // Draw the gradient mask. 74 CGContextSetBlendMode(context, kCGBlendModeDestinationIn); 75 [mask drawFromPoint:NSMakePoint(NSMaxX(frame) - gradient_width, 76 NSMinY(frame)) 77 toPoint:NSMakePoint(NSMaxX(frame), 78 NSMinY(frame)) 79 options:NSGradientDrawsBeforeStartingLocation]; 80 CGContextEndTransparencyLayer(context); 81} 82 83} // namespace 84 85NSImage* DragImageForBookmark(NSImage* favicon, const base::string16& title) { 86 // If no favicon, use a default. 87 if (!favicon) { 88 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); 89 favicon = rb.GetNativeImageNamed(IDR_DEFAULT_FAVICON).ToNSImage(); 90 } 91 92 // If no title, just use icon. 93 if (title.empty()) 94 return favicon; 95 NSString* ns_title = base::SysUTF16ToNSString(title); 96 97 // Set the look of the title. 98 NSDictionary* attrs = 99 [NSDictionary dictionaryWithObject:[NSFont systemFontOfSize: 100 [NSFont smallSystemFontSize]] 101 forKey:NSFontAttributeName]; 102 base::scoped_nsobject<NSAttributedString> rich_title( 103 [[NSAttributedString alloc] initWithString:ns_title attributes:attrs]); 104 105 // Set up sizes and locations for rendering. 106 const CGFloat kIconMargin = 2.0; // Gap between icon and text. 107 CGFloat text_left = [favicon size].width + kIconMargin; 108 NSSize drag_image_size = [favicon size]; 109 NSSize text_size = [rich_title size]; 110 CGFloat max_text_width = bookmarks::kDefaultBookmarkWidth - text_left; 111 text_size.width = std::min(text_size.width, max_text_width); 112 drag_image_size.width = text_left + text_size.width; 113 114 // Render the drag image. 115 NSImage* drag_image = 116 [[[NSImage alloc] initWithSize:drag_image_size] autorelease]; 117 [drag_image lockFocus]; 118 [favicon drawAtPoint:NSZeroPoint 119 fromRect:NSZeroRect 120 operation:NSCompositeSourceOver 121 fraction:0.7]; 122 NSRect target_text_rect = NSMakeRect(text_left, 0, 123 text_size.width, drag_image_size.height); 124 DrawTruncatedTitle(rich_title, target_text_rect); 125 [drag_image unlockFocus]; 126 127 return drag_image; 128} 129 130void DragBookmarks(Profile* profile, 131 const std::vector<const BookmarkNode*>& nodes, 132 gfx::NativeView view) { 133 DCHECK(!nodes.empty()); 134 135 // Allow nested message loop so we get DnD events as we drag this around. 136 bool was_nested = base::MessageLoop::current()->IsNested(); 137 base::MessageLoop::current()->SetNestableTasksAllowed(true); 138 139 BookmarkNodeData drag_data(nodes); 140 drag_data.SetOriginatingProfile(profile); 141 drag_data.WriteToClipboard(ui::CLIPBOARD_TYPE_DRAG); 142 143 // Synthesize an event for dragging, since we can't be sure that 144 // [NSApp currentEvent] will return a valid dragging event. 145 NSWindow* window = [view window]; 146 NSPoint position = [window mouseLocationOutsideOfEventStream]; 147 NSTimeInterval event_time = [[NSApp currentEvent] timestamp]; 148 NSEvent* drag_event = [NSEvent mouseEventWithType:NSLeftMouseDragged 149 location:position 150 modifierFlags:NSLeftMouseDraggedMask 151 timestamp:event_time 152 windowNumber:[window windowNumber] 153 context:nil 154 eventNumber:0 155 clickCount:1 156 pressure:1.0]; 157 158 // TODO(avi): Do better than this offset. 159 NSImage* drag_image = chrome::MakeDragImage( 160 BookmarkModelFactory::GetForProfile(profile), nodes); 161 NSSize image_size = [drag_image size]; 162 position.x -= std::floor(image_size.width / 2); 163 position.y -= std::floor(image_size.height / 5); 164 [window dragImage:drag_image 165 at:position 166 offset:NSZeroSize 167 event:drag_event 168 pasteboard:[NSPasteboard pasteboardWithName:NSDragPboard] 169 source:nil 170 slideBack:YES]; 171 172 base::MessageLoop::current()->SetNestableTasksAllowed(was_nested); 173} 174 175} // namespace chrome 176