1// Copyright (c) 2011 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#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_folder_view.h"
6
7#include "chrome/browser/bookmarks/bookmark_pasteboard_helper_mac.h"
8#include "chrome/browser/profiles/profile.h"
9#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
10#import "chrome/browser/ui/cocoa/bookmarks/bookmark_folder_target.h"
11#import "chrome/browser/ui/cocoa/browser_window_controller.h"
12#include "content/public/browser/user_metrics.h"
13
14using content::UserMetricsAction;
15
16#import "third_party/mozilla/NSPasteboard+Utils.h"
17
18@interface BookmarkBarFolderView()
19
20@property(readonly, nonatomic) id<BookmarkButtonControllerProtocol> controller;
21
22@end
23
24@implementation BookmarkBarFolderView
25
26- (void)awakeFromNib {
27  NSArray* types = [NSArray arrayWithObjects:
28                    NSStringPboardType,
29                    NSHTMLPboardType,
30                    NSURLPboardType,
31                    kBookmarkButtonDragType,
32                    kBookmarkDictionaryListPboardType,
33                    nil];
34  [self registerForDraggedTypes:types];
35}
36
37- (void)dealloc {
38  [self unregisterDraggedTypes];
39  [super dealloc];
40}
41
42- (id<BookmarkButtonControllerProtocol>)controller {
43  // When needed for testing, set the local data member |controller_| to
44  // the test controller.
45  return controller_ ? controller_ : [[self window] windowController];
46}
47
48- (void)drawRect:(NSRect)rect {
49  // TODO(jrg): copied from bookmark_bar_view but orientation changed.
50  // Code dup sucks but I'm not sure I can take 16 lines and make it
51  // generic for horiz vs vertical while keeping things simple.
52  // TODO(jrg): when throwing it all away and using animations, try
53  // hard to make a common routine for both.
54  // http://crbug.com/35966, http://crbug.com/35968
55
56  // Draw the bookmark-button-dragging drop indicator if necessary.
57  if (dropIndicatorShown_) {
58    const CGFloat kBarHeight = 1;
59    const CGFloat kBarHorizPad = 4;
60    const CGFloat kBarOpacity = 0.85;
61
62    NSRect uglyBlackBar =
63        NSMakeRect(kBarHorizPad, dropIndicatorPosition_,
64                   NSWidth([self bounds]) - 2*kBarHorizPad,
65                   kBarHeight);
66    NSColor* uglyBlackBarColor = [NSColor blackColor];
67    [[uglyBlackBarColor colorWithAlphaComponent:kBarOpacity] setFill];
68    [[NSBezierPath bezierPathWithRect:uglyBlackBar] fill];
69  }
70}
71
72// TODO(mrossetti,jrg): Identical to -[BookmarkBarView
73// dragClipboardContainsBookmarks].  http://crbug.com/35966
74// Shim function to assist in unit testing.
75- (BOOL)dragClipboardContainsBookmarks {
76  return bookmark_pasteboard_helper_mac::PasteboardContainsBookmarks(
77      bookmark_pasteboard_helper_mac::kDragPasteboard);
78}
79
80// Virtually identical to [BookmarkBarView draggingEntered:].
81// TODO(jrg): find a way to share code.  Lack of multiple inheritance
82// makes things more of a pain but there should be no excuse for laziness.
83// http://crbug.com/35966
84- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)info {
85  inDrag_ = YES;
86  if (![[self controller] draggingAllowed:info])
87    return NSDragOperationNone;
88  if ([[info draggingPasteboard] dataForType:kBookmarkButtonDragType] ||
89      [self dragClipboardContainsBookmarks] ||
90      [[info draggingPasteboard] containsURLData]) {
91    // Find the position of the drop indicator.
92    BOOL showIt = [[self controller]
93                   shouldShowIndicatorShownForPoint:[info draggingLocation]];
94    if (!showIt) {
95      if (dropIndicatorShown_) {
96        dropIndicatorShown_ = NO;
97        [self setNeedsDisplay:YES];
98      }
99    } else {
100      CGFloat y =
101      [[self controller]
102       indicatorPosForDragToPoint:[info draggingLocation]];
103
104      // Need an update if the indicator wasn't previously shown or if it has
105      // moved.
106      if (!dropIndicatorShown_ || dropIndicatorPosition_ != y) {
107        dropIndicatorShown_ = YES;
108        dropIndicatorPosition_ = y;
109        [self setNeedsDisplay:YES];
110      }
111    }
112
113    [[self controller] draggingEntered:info];  // allow hover-open to work
114    return [info draggingSource] ? NSDragOperationMove : NSDragOperationCopy;
115  }
116  return NSDragOperationNone;
117}
118
119- (void)draggingExited:(id<NSDraggingInfo>)info {
120  [[self controller] draggingExited:info];
121
122  // Regardless of the type of dragging which ended, we need to get rid of the
123  // drop indicator if one was shown.
124  if (dropIndicatorShown_) {
125    dropIndicatorShown_ = NO;
126    [self setNeedsDisplay:YES];
127  }
128}
129
130- (void)draggingEnded:(id<NSDraggingInfo>)info {
131  // Awkwardness since views open and close out from under us.
132  if (inDrag_) {
133    inDrag_ = NO;
134  }
135
136  [self draggingExited:info];
137}
138
139- (BOOL)wantsPeriodicDraggingUpdates {
140  // TODO(jrg): This should probably return |YES| and the controller should
141  // slide the existing bookmark buttons interactively to the side to make
142  // room for the about-to-be-dropped bookmark.
143  // http://crbug.com/35968
144  return NO;
145}
146
147- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)info {
148  // For now it's the same as draggingEntered:.
149  // TODO(jrg): once we return YES for wantsPeriodicDraggingUpdates,
150  // this should ping the [self controller] to perform animations.
151  // http://crbug.com/35968
152  return [self draggingEntered:info];
153}
154
155- (BOOL)prepareForDragOperation:(id<NSDraggingInfo>)info {
156  return YES;
157}
158
159// This code is practically identical to the same function in BookmarkBarView
160// with the only difference being how the controller is retrieved.
161// TODO(mrossetti,jrg): http://crbug.com/35966
162// Implement NSDraggingDestination protocol method
163// performDragOperation: for URLs.
164- (BOOL)performDragOperationForURL:(id<NSDraggingInfo>)info {
165  NSPasteboard* pboard = [info draggingPasteboard];
166  DCHECK([pboard containsURLData]);
167
168  NSArray* urls = nil;
169  NSArray* titles = nil;
170  [pboard getURLs:&urls andTitles:&titles convertingFilenames:YES];
171
172  return [[self controller] addURLs:urls
173                         withTitles:titles
174                                 at:[info draggingLocation]];
175}
176
177// This code is practically identical to the same function in BookmarkBarView
178// with the only difference being how the controller is retrieved.
179// http://crbug.com/35966
180// Implement NSDraggingDestination protocol method
181// performDragOperation: for bookmark buttons.
182- (BOOL)performDragOperationForBookmarkButton:(id<NSDraggingInfo>)info {
183  BOOL doDrag = NO;
184  NSData* data = [[info draggingPasteboard]
185                   dataForType:kBookmarkButtonDragType];
186  // [info draggingSource] is nil if not the same application.
187  if (data && [info draggingSource]) {
188    BookmarkButton* button = nil;
189    [data getBytes:&button length:sizeof(button)];
190
191    // If we're dragging from one profile to another, disallow moving (only
192    // allow copying). Each profile has its own bookmark model, so one way to
193    // check whether we are dragging across profiles is to see if the
194    // |BookmarkNode| corresponding to |button| exists in this profile. If it
195    // does, we're dragging within a profile; otherwise, we're dragging across
196    // profiles.
197    const BookmarkModel* const model = [[self controller] bookmarkModel];
198    const BookmarkNode* const source_node = [button bookmarkNode];
199    const BookmarkNode* const target_node =
200        model->GetNodeByID(source_node->id());
201
202    BOOL copy =
203        !([info draggingSourceOperationMask] & NSDragOperationMove) ||
204        (source_node != target_node);
205    doDrag = [[self controller] dragButton:button
206                                        to:[info draggingLocation]
207                                      copy:copy];
208    content::RecordAction(UserMetricsAction("BookmarkBarFolder_DragEnd"));
209  }
210  return doDrag;
211}
212
213- (BOOL)performDragOperation:(id<NSDraggingInfo>)info {
214  if ([[self controller] dragBookmarkData:info])
215    return YES;
216  NSPasteboard* pboard = [info draggingPasteboard];
217  if ([pboard dataForType:kBookmarkButtonDragType] &&
218      [self performDragOperationForBookmarkButton:info])
219    return YES;
220  if ([pboard containsURLData] && [self performDragOperationForURL:info])
221    return YES;
222  return NO;
223}
224
225@end
226