Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(169)

Side by Side Diff: chrome/browser/ui/cocoa/tabs/tab_view.mm

Issue 7080064: [Mac] Refactor the logic of tab dragging out of TabView and into a new helper. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "chrome/browser/ui/cocoa/tabs/tab_view.h" 5 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
6 6
7 #include "base/logging.h" 7 #include "base/logging.h"
8 #import "base/mac/mac_util.h"
9 #include "base/mac/scoped_cftyperef.h"
10 #include "chrome/browser/themes/theme_service.h" 8 #include "chrome/browser/themes/theme_service.h"
11 #import "chrome/browser/ui/cocoa/nsview_additions.h" 9 #import "chrome/browser/ui/cocoa/nsview_additions.h"
12 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h" 10 #import "chrome/browser/ui/cocoa/tabs/tab_controller.h"
13 #import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h" 11 #import "chrome/browser/ui/cocoa/tabs/tab_window_controller.h"
14 #import "chrome/browser/ui/cocoa/themed_window.h" 12 #import "chrome/browser/ui/cocoa/themed_window.h"
15 #import "chrome/browser/ui/cocoa/view_id_util.h" 13 #import "chrome/browser/ui/cocoa/view_id_util.h"
16 #include "grit/generated_resources.h" 14 #include "grit/generated_resources.h"
17 #include "grit/theme_resources.h" 15 #include "grit/theme_resources.h"
18 #include "grit/theme_resources_standard.h" 16 #include "grit/theme_resources_standard.h"
19 #include "ui/base/l10n/l10n_util.h" 17 #include "ui/base/l10n/l10n_util.h"
(...skipping 12 matching lines...) Expand all
32 const NSTimeInterval kHoverHoldDuration = 0.02; 30 const NSTimeInterval kHoverHoldDuration = 0.02;
33 const NSTimeInterval kHoverHideDuration = 0.4; 31 const NSTimeInterval kHoverHideDuration = 0.4;
34 const NSTimeInterval kAlertShowDuration = 0.4; 32 const NSTimeInterval kAlertShowDuration = 0.4;
35 const NSTimeInterval kAlertHoldDuration = 0.4; 33 const NSTimeInterval kAlertHoldDuration = 0.4;
36 const NSTimeInterval kAlertHideDuration = 0.4; 34 const NSTimeInterval kAlertHideDuration = 0.4;
37 35
38 // The default time interval in seconds between glow updates (when 36 // The default time interval in seconds between glow updates (when
39 // increasing/decreasing). 37 // increasing/decreasing).
40 const NSTimeInterval kGlowUpdateInterval = 0.025; 38 const NSTimeInterval kGlowUpdateInterval = 0.025;
41 39
42 const CGFloat kTearDistance = 36.0;
43 const NSTimeInterval kTearDuration = 0.333;
44
45 // This is used to judge whether the mouse has moved during rapid closure; if it 40 // This is used to judge whether the mouse has moved during rapid closure; if it
46 // has moved less than the threshold, we want to close the tab. 41 // has moved less than the threshold, we want to close the tab.
47 const CGFloat kRapidCloseDist = 2.5; 42 const CGFloat kRapidCloseDist = 2.5;
48 43
49 } // namespace 44 } // namespace
50 45
51 @interface TabView(Private) 46 @interface TabView(Private)
52 47
53 - (void)resetLastGlowUpdateTime; 48 - (void)resetLastGlowUpdateTime;
54 - (NSTimeInterval)timeElapsedSinceLastGlowUpdate; 49 - (NSTimeInterval)timeElapsedSinceLastGlowUpdate;
55 - (void)adjustGlowValue; 50 - (void)adjustGlowValue;
56 // TODO(davidben): When we stop supporting 10.5, this can be removed.
57 - (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache;
58 - (NSBezierPath*)bezierPathForRect:(NSRect)rect; 51 - (NSBezierPath*)bezierPathForRect:(NSRect)rect;
59 52
60 @end // TabView(Private) 53 @end // TabView(Private)
61 54
62 @implementation TabView 55 @implementation TabView
63 56
64 @synthesize state = state_; 57 @synthesize state = state_;
65 @synthesize hoverAlpha = hoverAlpha_; 58 @synthesize hoverAlpha = hoverAlpha_;
66 @synthesize alertAlpha = alertAlpha_; 59 @synthesize alertAlpha = alertAlpha_;
67 @synthesize closing = closing_; 60 @synthesize closing = closing_;
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
146 // works well enough. 139 // works well enough.
147 NSRect hitRect = NSInsetRect(frame, frame.size.height / 3.0f, 0); 140 NSRect hitRect = NSInsetRect(frame, frame.size.height / 3.0f, 0);
148 if (![closeButton_ isHidden]) 141 if (![closeButton_ isHidden])
149 if (NSPointInRect(viewPoint, [closeButton_ frame])) return closeButton_; 142 if (NSPointInRect(viewPoint, [closeButton_ frame])) return closeButton_;
150 if (NSPointInRect(aPoint, hitRect)) return self; 143 if (NSPointInRect(aPoint, hitRect)) return self;
151 return nil; 144 return nil;
152 } 145 }
153 146
154 // Returns |YES| if this tab can be torn away into a new window. 147 // Returns |YES| if this tab can be torn away into a new window.
155 - (BOOL)canBeDragged { 148 - (BOOL)canBeDragged {
156 if ([self isClosing]) 149 return [controller_ tabCanBeDragged:controller_];
157 return NO;
158 NSWindowController* controller = [sourceWindow_ windowController];
159 if ([controller isKindOfClass:[TabWindowController class]]) {
160 TabWindowController* realController =
161 static_cast<TabWindowController*>(controller);
162 return [realController isTabDraggable:self];
163 }
164 return YES;
165 }
166
167 // Returns an array of controllers that could be a drop target, ordered front to
168 // back. It has to be of the appropriate class, and visible (obviously). Note
169 // that the window cannot be a target for itself.
170 - (NSArray*)dropTargetsForController:(TabWindowController*)dragController {
171 NSMutableArray* targets = [NSMutableArray array];
172 NSWindow* dragWindow = [dragController window];
173 for (NSWindow* window in [NSApp orderedWindows]) {
174 if (window == dragWindow) continue;
175 if (![window isVisible]) continue;
176 // Skip windows on the wrong space.
177 if ([window respondsToSelector:@selector(isOnActiveSpace)]) {
178 if (![window performSelector:@selector(isOnActiveSpace)])
179 continue;
180 } else {
181 // TODO(davidben): When we stop supporting 10.5, this can be
182 // removed.
183 //
184 // We don't cache the workspace of |dragWindow| because it may
185 // move around spaces.
186 if ([self getWorkspaceID:dragWindow useCache:NO] !=
187 [self getWorkspaceID:window useCache:YES])
188 continue;
189 }
190 NSWindowController* controller = [window windowController];
191 if ([controller isKindOfClass:[TabWindowController class]]) {
192 TabWindowController* realController =
193 static_cast<TabWindowController*>(controller);
194 if ([realController canReceiveFrom:dragController])
195 [targets addObject:controller];
196 }
197 }
198 return targets;
199 }
200
201 // Call to clear out transient weak references we hold during drags.
202 - (void)resetDragControllers {
203 draggedController_ = nil;
204 dragWindow_ = nil;
205 dragOverlay_ = nil;
206 sourceController_ = nil;
207 sourceWindow_ = nil;
208 targetController_ = nil;
209 workspaceIDCache_.clear();
210 }
211
212 // Sets whether the window background should be visible or invisible when
213 // dragging a tab. The background should be invisible when the mouse is over a
214 // potential drop target for the tab (the tab strip). It should be visible when
215 // there's no drop target so the window looks more fully realized and ready to
216 // become a stand-alone window.
217 - (void)setWindowBackgroundVisibility:(BOOL)shouldBeVisible {
218 if (chromeIsVisible_ == shouldBeVisible)
219 return;
220
221 // There appears to be a race-condition in CoreAnimation where if we use
222 // animators to set the alpha values, we can't guarantee that we cancel them.
223 // This has the side effect of sometimes leaving the dragged window
224 // translucent or invisible. As a result, don't animate the alpha change.
225 [[draggedController_ overlayWindow] setAlphaValue:1.0];
226 if (targetController_) {
227 [dragWindow_ setAlphaValue:0.0];
228 [[draggedController_ overlayWindow] setHasShadow:YES];
229 [[targetController_ window] makeMainWindow];
230 } else {
231 [dragWindow_ setAlphaValue:0.5];
232 [[draggedController_ overlayWindow] setHasShadow:NO];
233 [[draggedController_ window] makeMainWindow];
234 }
235 chromeIsVisible_ = shouldBeVisible;
236 } 150 }
237 151
238 // Handle clicks and drags in this button. We get here because we have 152 // Handle clicks and drags in this button. We get here because we have
239 // overridden acceptsFirstMouse: and the click is within our bounds. 153 // overridden acceptsFirstMouse: and the click is within our bounds.
240 - (void)mouseDown:(NSEvent*)theEvent { 154 - (void)mouseDown:(NSEvent*)theEvent {
241 if ([self isClosing]) 155 if ([self isClosing])
242 return; 156 return;
243 157
244 NSPoint downLocation = [theEvent locationInWindow]; 158 // Record the point at which this event happened. This is used by other mouse
159 // events that are dispatched from |-maybeStartDrag::|.
160 mouseDownPoint_ = [theEvent locationInWindow];
245 161
246 // Record the state of the close button here, because selecting the tab will 162 // Record the state of the close button here, because selecting the tab will
247 // unhide it. 163 // unhide it.
248 BOOL closeButtonActive = [closeButton_ isHidden] ? NO : YES; 164 BOOL closeButtonActive = ![closeButton_ isHidden];
249 165
250 // During the tab closure animation (in particular, during rapid tab closure), 166 // During the tab closure animation (in particular, during rapid tab closure),
251 // we may get incorrectly hit with a mouse down. If it should have gone to the 167 // we may get incorrectly hit with a mouse down. If it should have gone to the
252 // close button, we send it there -- it should then track the mouse, so we 168 // close button, we send it there -- it should then track the mouse, so we
253 // don't have to worry about mouse ups. 169 // don't have to worry about mouse ups.
254 if (closeButtonActive && [controller_ inRapidClosureMode]) { 170 if (closeButtonActive && [controller_ inRapidClosureMode]) {
255 NSPoint hitLocation = [[self superview] convertPoint:downLocation 171 NSPoint hitLocation = [[self superview] convertPoint:mouseDownPoint_
256 fromView:nil]; 172 fromView:nil];
257 if ([self hitTest:hitLocation] == closeButton_) { 173 if ([self hitTest:hitLocation] == closeButton_) {
258 [closeButton_ mouseDown:theEvent]; 174 [closeButton_ mouseDown:theEvent];
259 return; 175 return;
260 } 176 }
261 } 177 }
262 178
263 [self resetDragControllers];
264
265 // Resolve overlay back to original window.
266 sourceWindow_ = [self window];
267 if ([sourceWindow_ isKindOfClass:[NSPanel class]]) {
268 sourceWindow_ = [sourceWindow_ parentWindow];
269 }
270
271 sourceWindowFrame_ = [sourceWindow_ frame];
272 sourceTabFrame_ = [self frame];
273 sourceController_ = [sourceWindow_ windowController];
274 tabWasDragged_ = NO;
275 tearTime_ = 0.0;
276 draggingWithinTabStrip_ = YES;
277 chromeIsVisible_ = NO;
278
279 // If there's more than one potential window to be a drop target, we want to
280 // treat a drag of a tab just like dragging around a tab that's already
281 // detached. Note that unit tests might have |-numberOfTabs| reporting zero
282 // since the model won't be fully hooked up. We need to be prepared for that
283 // and not send them into the "magnetic" codepath.
284 NSArray* targets = [self dropTargetsForController:sourceController_];
285 moveWindowOnDrag_ =
286 ([sourceController_ numberOfTabs] < 2 && ![targets count]) ||
287 ![self canBeDragged] ||
288 ![sourceController_ tabDraggingAllowed];
289 // If we are dragging a tab, a window with a single tab should immediately
290 // snap off and not drag within the tab strip.
291 if (!moveWindowOnDrag_)
292 draggingWithinTabStrip_ = [sourceController_ numberOfTabs] > 1;
293
294 dragOrigin_ = [NSEvent mouseLocation];
295
296 // If the tab gets torn off, the tab controller will be removed from the tab 179 // If the tab gets torn off, the tab controller will be removed from the tab
297 // strip and then deallocated. This will also result in *us* being 180 // strip and then deallocated. This will also result in *us* being
298 // deallocated. Both these are bad, so we prevent this by retaining the 181 // deallocated. Both these are bad, so we prevent this by retaining the
299 // controller. 182 // controller.
300 scoped_nsobject<TabController> controller([controller_ retain]); 183 scoped_nsobject<TabController> controller([controller_ retain]);
301 184
302 // Because we move views between windows, we need to handle the event loop 185 // Try to initiate a drag. This will spin a custom event loop and may
303 // ourselves. Ideally we should use the standard event loop. 186 // dispatch other mouse events.
304 while (1) { 187 [controller_ maybeStartDrag:theEvent forTab:controller];
305 const NSUInteger mask =
306 NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSKeyUpMask;
307 theEvent =
308 [NSApp nextEventMatchingMask:mask
309 untilDate:[NSDate distantFuture]
310 inMode:NSDefaultRunLoopMode dequeue:YES];
311 NSEventType type = [theEvent type];
312 if (type == NSKeyUp) {
313 if ([theEvent keyCode] == kVK_Escape) {
314 // Cancel the drag and restore the previous state.
315 if (draggingWithinTabStrip_) {
316 // Simply pretend the tab wasn't dragged (far enough).
317 tabWasDragged_ = NO;
318 } else {
319 [targetController_ removePlaceholder];
320 if ([sourceController_ numberOfTabs] < 2) {
321 // Revert to a single-tab window.
322 targetController_ = nil;
323 } else {
324 // Change the target to the source controller.
325 targetController_ = sourceController_;
326 [targetController_ insertPlaceholderForTab:self
327 frame:sourceTabFrame_
328 yStretchiness:0];
329 }
330 }
331 // Call the |mouseUp:| code to end the drag.
332 [self mouseUp:theEvent];
333 break;
334 }
335 } else if (type == NSLeftMouseDragged) {
336 [self mouseDragged:theEvent];
337 } else if (type == NSLeftMouseUp) {
338 NSPoint upLocation = [theEvent locationInWindow];
339 CGFloat dx = upLocation.x - downLocation.x;
340 CGFloat dy = upLocation.y - downLocation.y;
341 188
342 // During rapid tab closure (mashing tab close buttons), we may get hit 189 // The custom loop has ended, so clear the point.
343 // with a mouse down. As long as the mouse up is over the close button, 190 mouseDownPoint_ = NSZeroPoint;
344 // and the mouse hasn't moved too much, we close the tab.
345 if (closeButtonActive &&
346 (dx*dx + dy*dy) <= kRapidCloseDist*kRapidCloseDist &&
347 [controller inRapidClosureMode]) {
348 NSPoint hitLocation =
349 [[self superview] convertPoint:[theEvent locationInWindow]
350 fromView:nil];
351 if ([self hitTest:hitLocation] == closeButton_) {
352 [controller closeTab:self];
353 break;
354 }
355 }
356
357 [self mouseUp:theEvent];
358 break;
359 } else {
360 // TODO(viettrungluu): [crbug.com/23830] We can receive right-mouse-ups
361 // (and maybe even others?) for reasons I don't understand. So we
362 // explicitly check for both events we're expecting, and log others. We
363 // should figure out what's going on.
364 LOG(WARNING) << "Spurious event received of type " << type << ".";
365 }
366 }
367 } 191 }
368 192
369 - (void)mouseDragged:(NSEvent*)theEvent { 193 - (void)mouseDragged:(NSEvent*)theEvent {
370 // Special-case this to keep the logic below simpler. 194 [controller_ continueDrag:theEvent];
371 if (moveWindowOnDrag_) {
372 if ([sourceController_ windowMovementAllowed]) {
373 NSPoint thisPoint = [NSEvent mouseLocation];
374 NSPoint origin = sourceWindowFrame_.origin;
375 origin.x += (thisPoint.x - dragOrigin_.x);
376 origin.y += (thisPoint.y - dragOrigin_.y);
377 [sourceWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)];
378 } // else do nothing.
379 return;
380 }
381
382 // First, go through the magnetic drag cycle. We break out of this if
383 // "stretchiness" ever exceeds a set amount.
384 tabWasDragged_ = YES;
385
386 if (draggingWithinTabStrip_) {
387 NSPoint thisPoint = [NSEvent mouseLocation];
388 CGFloat stretchiness = thisPoint.y - dragOrigin_.y;
389 stretchiness = copysign(sqrtf(fabs(stretchiness))/sqrtf(kTearDistance),
390 stretchiness) / 2.0;
391 CGFloat offset = thisPoint.x - dragOrigin_.x;
392 if (fabsf(offset) > 100) stretchiness = 0;
393 [sourceController_ insertPlaceholderForTab:self
394 frame:NSOffsetRect(sourceTabFrame_,
395 offset, 0)
396 yStretchiness:stretchiness];
397 // Check that we haven't pulled the tab too far to start a drag. This
398 // can include either pulling it too far down, or off the side of the tab
399 // strip that would cause it to no longer be fully visible.
400 BOOL stillVisible = [sourceController_ isTabFullyVisible:self];
401 CGFloat tearForce = fabs(thisPoint.y - dragOrigin_.y);
402 if ([sourceController_ tabTearingAllowed] &&
403 (tearForce > kTearDistance || !stillVisible)) {
404 draggingWithinTabStrip_ = NO;
405 // When you finally leave the strip, we treat that as the origin.
406 dragOrigin_.x = thisPoint.x;
407 } else {
408 // Still dragging within the tab strip, wait for the next drag event.
409 return;
410 }
411 }
412
413 // Do not start dragging until the user has "torn" the tab off by
414 // moving more than 3 pixels.
415 NSDate* targetDwellDate = nil; // The date this target was first chosen.
416
417 NSPoint thisPoint = [NSEvent mouseLocation];
418
419 // Iterate over possible targets checking for the one the mouse is in.
420 // If the tab is just in the frame, bring the window forward to make it
421 // easier to drop something there. If it's in the tab strip, set the new
422 // target so that it pops into that window. We can't cache this because we
423 // need the z-order to be correct.
424 NSArray* targets = [self dropTargetsForController:draggedController_];
425 TabWindowController* newTarget = nil;
426 for (TabWindowController* target in targets) {
427 NSRect windowFrame = [[target window] frame];
428 if (NSPointInRect(thisPoint, windowFrame)) {
429 [[target window] orderFront:self];
430 NSRect tabStripFrame = [[target tabStripView] frame];
431 tabStripFrame.origin = [[target window]
432 convertBaseToScreen:tabStripFrame.origin];
433 if (NSPointInRect(thisPoint, tabStripFrame)) {
434 newTarget = target;
435 }
436 break;
437 }
438 }
439
440 // If we're now targeting a new window, re-layout the tabs in the old
441 // target and reset how long we've been hovering over this new one.
442 if (targetController_ != newTarget) {
443 targetDwellDate = [NSDate date];
444 [targetController_ removePlaceholder];
445 targetController_ = newTarget;
446 if (!newTarget) {
447 tearTime_ = [NSDate timeIntervalSinceReferenceDate];
448 tearOrigin_ = [dragWindow_ frame].origin;
449 }
450 }
451
452 // Create or identify the dragged controller.
453 if (!draggedController_) {
454 // Get rid of any placeholder remaining in the original source window.
455 [sourceController_ removePlaceholder];
456
457 // Detach from the current window and put it in a new window. If there are
458 // no more tabs remaining after detaching, the source window is about to
459 // go away (it's been autoreleased) so we need to ensure we don't reference
460 // it any more. In that case the new controller becomes our source
461 // controller.
462 draggedController_ = [sourceController_ detachTabToNewWindow:self];
463 dragWindow_ = [draggedController_ window];
464 [dragWindow_ setAlphaValue:0.0];
465 if (![sourceController_ hasLiveTabs]) {
466 sourceController_ = draggedController_;
467 sourceWindow_ = dragWindow_;
468 }
469
470 // If dragging the tab only moves the current window, do not show overlay
471 // so that sheets stay on top of the window.
472 // Bring the target window to the front and make sure it has a border.
473 [dragWindow_ setLevel:NSFloatingWindowLevel];
474 [dragWindow_ setHasShadow:YES];
475 [dragWindow_ orderFront:nil];
476 [dragWindow_ makeMainWindow];
477 [draggedController_ showOverlay];
478 dragOverlay_ = [draggedController_ overlayWindow];
479 // Force the new tab button to be hidden. We'll reset it on mouse up.
480 [draggedController_ showNewTabButton:NO];
481 tearTime_ = [NSDate timeIntervalSinceReferenceDate];
482 tearOrigin_ = sourceWindowFrame_.origin;
483 }
484
485 // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by
486 // some weird circumstance that doesn't first go through mouseDown:. We
487 // really shouldn't go any farther.
488 if (!draggedController_ || !sourceController_)
489 return;
490
491 // When the user first tears off the window, we want slide the window to
492 // the current mouse location (to reduce the jarring appearance). We do this
493 // by calling ourselves back with additional mouseDragged calls (not actual
494 // events). |tearProgress| is a normalized measure of how far through this
495 // tear "animation" (of length kTearDuration) we are and has values [0..1].
496 // We use sqrt() so the animation is non-linear (slow down near the end
497 // point).
498 NSTimeInterval tearProgress =
499 [NSDate timeIntervalSinceReferenceDate] - tearTime_;
500 tearProgress /= kTearDuration; // Normalize.
501 tearProgress = sqrtf(MAX(MIN(tearProgress, 1.0), 0.0));
502
503 // Move the dragged window to the right place on the screen.
504 NSPoint origin = sourceWindowFrame_.origin;
505 origin.x += (thisPoint.x - dragOrigin_.x);
506 origin.y += (thisPoint.y - dragOrigin_.y);
507
508 if (tearProgress < 1) {
509 // If the tear animation is not complete, call back to ourself with the
510 // same event to animate even if the mouse isn't moving. We need to make
511 // sure these get cancelled in mouseUp:.
512 [NSObject cancelPreviousPerformRequestsWithTarget:self];
513 [self performSelector:@selector(mouseDragged:)
514 withObject:theEvent
515 afterDelay:1.0f/30.0f];
516
517 // Set the current window origin based on how far we've progressed through
518 // the tear animation.
519 origin.x = (1 - tearProgress) * tearOrigin_.x + tearProgress * origin.x;
520 origin.y = (1 - tearProgress) * tearOrigin_.y + tearProgress * origin.y;
521 }
522
523 if (targetController_) {
524 // In order to "snap" two windows of different sizes together at their
525 // toolbar, we can't just use the origin of the target frame. We also have
526 // to take into consideration the difference in height.
527 NSRect targetFrame = [[targetController_ window] frame];
528 NSRect sourceFrame = [dragWindow_ frame];
529 origin.y = NSMinY(targetFrame) +
530 (NSHeight(targetFrame) - NSHeight(sourceFrame));
531 }
532 [dragWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)];
533
534 // If we're not hovering over any window, make the window fully
535 // opaque. Otherwise, find where the tab might be dropped and insert
536 // a placeholder so it appears like it's part of that window.
537 if (targetController_) {
538 if (![[targetController_ window] isKeyWindow]) {
539 // && ([targetDwellDate timeIntervalSinceNow] < -REQUIRED_DWELL)) {
540 [[targetController_ window] orderFront:nil];
541 targetDwellDate = nil;
542 }
543
544 // Compute where placeholder should go and insert it into the
545 // destination tab strip.
546 TabView* draggedTabView = (TabView*)[draggedController_ activeTabView];
547 NSRect tabFrame = [draggedTabView frame];
548 tabFrame.origin = [dragWindow_ convertBaseToScreen:tabFrame.origin];
549 tabFrame.origin = [[targetController_ window]
550 convertScreenToBase:tabFrame.origin];
551 tabFrame = [[targetController_ tabStripView]
552 convertRect:tabFrame fromView:nil];
553 [targetController_ insertPlaceholderForTab:self
554 frame:tabFrame
555 yStretchiness:0];
556 [targetController_ layoutTabs];
557 } else {
558 [dragWindow_ makeKeyAndOrderFront:nil];
559 }
560
561 // Adjust the visibility of the window background. If there is a drop target,
562 // we want to hide the window background so the tab stands out for
563 // positioning. If not, we want to show it so it looks like a new window will
564 // be realized.
565 BOOL chromeShouldBeVisible = targetController_ == nil;
566 [self setWindowBackgroundVisibility:chromeShouldBeVisible];
567 } 195 }
568 196
569 - (void)mouseUp:(NSEvent*)theEvent { 197 - (void)mouseUp:(NSEvent*)theEvent {
570 // The drag/click is done. If the user dragged the mouse, finalize the drag
571 // and clean up.
572
573 // Fire the action to select the tab. 198 // Fire the action to select the tab.
574 if ([[controller_ target] respondsToSelector:[controller_ action]]) 199 if ([[controller_ target] respondsToSelector:[controller_ action]])
575 [[controller_ target] performSelector:[controller_ action] 200 [[controller_ target] performSelector:[controller_ action]
576 withObject:self]; 201 withObject:self];
577 202
578 // Special-case this to keep the logic below simpler. 203 // Check for rapid tab closure.
579 if (moveWindowOnDrag_) 204 if ([theEvent type] == NSLeftMouseUp) {
580 return; 205 NSPoint upLocation = [theEvent locationInWindow];
206 CGFloat dx = upLocation.x - mouseDownPoint_.x;
207 CGFloat dy = upLocation.y - mouseDownPoint_.y;
581 208
582 // Cancel any delayed -mouseDragged: requests that may still be pending. 209 // During rapid tab closure (mashing tab close buttons), we may get hit
583 [NSObject cancelPreviousPerformRequestsWithTarget:self]; 210 // with a mouse down. As long as the mouse up is over the close button,
211 // and the mouse hasn't moved too much, we close the tab.
212 if (![closeButton_ isHidden] &&
213 (dx*dx + dy*dy) <= kRapidCloseDist*kRapidCloseDist &&
214 [controller_ inRapidClosureMode]) {
215 NSPoint hitLocation =
216 [[self superview] convertPoint:[theEvent locationInWindow]
217 fromView:nil];
218 if ([self hitTest:hitLocation] == closeButton_) {
219 [controller_ closeTab:self];
220 }
221 }
222 }
584 223
585 // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by 224 // Messaging the drag controller with |-endDrag:| would seem like the right
586 // some weird circumstance that doesn't first go through mouseDown:. We 225 // thing to do here. But, when a tab has been detached, the controller's
587 // really shouldn't go any farther. 226 // target is nil until the drag is finalized. Since |-mouseUp:| gets called
588 if (!sourceController_) 227 // via the manual event loop inside -[TabStripDragController
589 return; 228 // maybeStartDrag:forTab:], the drag controller can end the dragging session
590 229 // itself directly after calling this.
591 // We are now free to re-display the new tab button in the window we're
592 // dragging. It will show when the next call to -layoutTabs (which happens
593 // indrectly by several of the calls below, such as removing the placeholder).
594 [draggedController_ showNewTabButton:YES];
595
596 if (draggingWithinTabStrip_) {
597 if (tabWasDragged_) {
598 // Move tab to new location.
599 DCHECK([sourceController_ numberOfTabs]);
600 TabWindowController* dropController = sourceController_;
601 [dropController moveTabView:[dropController activeTabView]
602 fromController:nil];
603 }
604 } else if (targetController_) {
605 // Move between windows. If |targetController_| is nil, we're not dropping
606 // into any existing window.
607 NSView* draggedTabView = [draggedController_ activeTabView];
608 [targetController_ moveTabView:draggedTabView
609 fromController:draggedController_];
610 // Force redraw to avoid flashes of old content before returning to event
611 // loop.
612 [[targetController_ window] display];
613 [targetController_ showWindow:nil];
614 [draggedController_ removeOverlay];
615 } else {
616 // Only move the window around on screen. Make sure it's set back to
617 // normal state (fully opaque, has shadow, has key, etc).
618 [draggedController_ removeOverlay];
619 // Don't want to re-show the window if it was closed during the drag.
620 if ([dragWindow_ isVisible]) {
621 [dragWindow_ setAlphaValue:1.0];
622 [dragOverlay_ setHasShadow:NO];
623 [dragWindow_ setHasShadow:YES];
624 [dragWindow_ makeKeyAndOrderFront:nil];
625 }
626 [[draggedController_ window] setLevel:NSNormalWindowLevel];
627 [draggedController_ removePlaceholder];
628 }
629 [sourceController_ removePlaceholder];
630 chromeIsVisible_ = YES;
631
632 [self resetDragControllers];
633 } 230 }
634 231
635 - (void)otherMouseUp:(NSEvent*)theEvent { 232 - (void)otherMouseUp:(NSEvent*)theEvent {
636 if ([self isClosing]) 233 if ([self isClosing])
637 return; 234 return;
638 235
639 // Support middle-click-to-close. 236 // Support middle-click-to-close.
640 if ([theEvent buttonNumber] == 2) { 237 if ([theEvent buttonNumber] == 2) {
641 // |-hitTest:| takes a location in the superview's coordinates. 238 // |-hitTest:| takes a location in the superview's coordinates.
642 NSPoint upLocation = 239 NSPoint upLocation =
(...skipping 322 matching lines...) Expand 10 before | Expand all | Expand 10 after
965 } 562 }
966 } 563 }
967 564
968 if (nextUpdate < kNoUpdate) 565 if (nextUpdate < kNoUpdate)
969 [self performSelector:_cmd withObject:nil afterDelay:nextUpdate]; 566 [self performSelector:_cmd withObject:nil afterDelay:nextUpdate];
970 567
971 [self resetLastGlowUpdateTime]; 568 [self resetLastGlowUpdateTime];
972 [self setNeedsDisplay:YES]; 569 [self setNeedsDisplay:YES];
973 } 570 }
974 571
975 // Returns the workspace id of |window|. If |useCache|, then lookup
976 // and remember the value in |workspaceIDCache_| until the end of the
977 // current drag.
978 - (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache {
979 CGWindowID windowID = [window windowNumber];
980 if (useCache) {
981 std::map<CGWindowID, int>::iterator iter =
982 workspaceIDCache_.find(windowID);
983 if (iter != workspaceIDCache_.end())
984 return iter->second;
985 }
986
987 int workspace = -1;
988 // It's possible to query in bulk, but probably not necessary.
989 base::mac::ScopedCFTypeRef<CFArrayRef> windowIDs(CFArrayCreate(
990 NULL, reinterpret_cast<const void **>(&windowID), 1, NULL));
991 base::mac::ScopedCFTypeRef<CFArrayRef> descriptions(
992 CGWindowListCreateDescriptionFromArray(windowIDs));
993 DCHECK(CFArrayGetCount(descriptions.get()) <= 1);
994 if (CFArrayGetCount(descriptions.get()) > 0) {
995 CFDictionaryRef dict = static_cast<CFDictionaryRef>(
996 CFArrayGetValueAtIndex(descriptions.get(), 0));
997 DCHECK(CFGetTypeID(dict) == CFDictionaryGetTypeID());
998
999 // Sanity check the ID.
1000 CFNumberRef otherIDRef = (CFNumberRef)base::mac::GetValueFromDictionary(
1001 dict, kCGWindowNumber, CFNumberGetTypeID());
1002 CGWindowID otherID;
1003 if (otherIDRef &&
1004 CFNumberGetValue(otherIDRef, kCGWindowIDCFNumberType, &otherID) &&
1005 otherID == windowID) {
1006 // And then get the workspace.
1007 CFNumberRef workspaceRef = (CFNumberRef)base::mac::GetValueFromDictionary(
1008 dict, kCGWindowWorkspace, CFNumberGetTypeID());
1009 if (!workspaceRef ||
1010 !CFNumberGetValue(workspaceRef, kCFNumberIntType, &workspace)) {
1011 workspace = -1;
1012 }
1013 } else {
1014 NOTREACHED();
1015 }
1016 }
1017 if (useCache) {
1018 workspaceIDCache_[windowID] = workspace;
1019 }
1020 return workspace;
1021 }
1022
1023 // Returns the bezier path used to draw the tab given the bounds to draw it in. 572 // Returns the bezier path used to draw the tab given the bounds to draw it in.
1024 - (NSBezierPath*)bezierPathForRect:(NSRect)rect { 573 - (NSBezierPath*)bezierPathForRect:(NSRect)rect {
1025 const CGFloat lineWidth = [self cr_lineWidth]; 574 const CGFloat lineWidth = [self cr_lineWidth];
1026 const CGFloat halfLineWidth = lineWidth / 2.0; 575 const CGFloat halfLineWidth = lineWidth / 2.0;
1027 576
1028 // Outset by halfLineWidth in order to draw on pixels rather than on borders 577 // Outset by halfLineWidth in order to draw on pixels rather than on borders
1029 // (which would cause blurry pixels). Subtract lineWidth of height to 578 // (which would cause blurry pixels). Subtract lineWidth of height to
1030 // compensate, otherwise clipping will occur. 579 // compensate, otherwise clipping will occur.
1031 rect = NSInsetRect(rect, -halfLineWidth, -halfLineWidth); 580 rect = NSInsetRect(rect, -halfLineWidth, -halfLineWidth);
1032 rect.size.height -= lineWidth; 581 rect.size.height -= lineWidth;
(...skipping 28 matching lines...) Expand all
1061 topRight.y) 610 topRight.y)
1062 controlPoint2:NSMakePoint(bottomRight.x - baseControlPointOutset, 611 controlPoint2:NSMakePoint(bottomRight.x - baseControlPointOutset,
1063 bottomRight.y)]; 612 bottomRight.y)];
1064 [path lineToPoint:NSMakePoint(bottomRight.x + lineWidth, bottomRight.y)]; 613 [path lineToPoint:NSMakePoint(bottomRight.x + lineWidth, bottomRight.y)];
1065 [path lineToPoint:NSMakePoint(bottomRight.x + lineWidth, 614 [path lineToPoint:NSMakePoint(bottomRight.x + lineWidth,
1066 bottomRight.y - (2 * lineWidth))]; 615 bottomRight.y - (2 * lineWidth))];
1067 return path; 616 return path;
1068 } 617 }
1069 618
1070 @end // @implementation TabView(Private) 619 @end // @implementation TabView(Private)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698