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