| Index: chrome/browser/ui/cocoa/tab_view.mm
|
| ===================================================================
|
| --- chrome/browser/ui/cocoa/tab_view.mm (revision 71805)
|
| +++ chrome/browser/ui/cocoa/tab_view.mm (working copy)
|
| @@ -1,1056 +0,0 @@
|
| -// Copyright (c) 2010 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.
|
| -
|
| -#import "chrome/browser/ui/cocoa/tab_view.h"
|
| -
|
| -#include "base/logging.h"
|
| -#import "base/mac/mac_util.h"
|
| -#include "base/mac/scoped_cftyperef.h"
|
| -#include "chrome/browser/accessibility/browser_accessibility_state.h"
|
| -#include "chrome/browser/themes/browser_theme_provider.h"
|
| -#import "chrome/browser/ui/cocoa/tab_controller.h"
|
| -#import "chrome/browser/ui/cocoa/tab_window_controller.h"
|
| -#import "chrome/browser/ui/cocoa/themed_window.h"
|
| -#import "chrome/browser/ui/cocoa/view_id_util.h"
|
| -#include "grit/theme_resources.h"
|
| -
|
| -namespace {
|
| -
|
| -// Constants for inset and control points for tab shape.
|
| -const CGFloat kInsetMultiplier = 2.0/3.0;
|
| -const CGFloat kControlPoint1Multiplier = 1.0/3.0;
|
| -const CGFloat kControlPoint2Multiplier = 3.0/8.0;
|
| -
|
| -// The amount of time in seconds during which each type of glow increases, holds
|
| -// steady, and decreases, respectively.
|
| -const NSTimeInterval kHoverShowDuration = 0.2;
|
| -const NSTimeInterval kHoverHoldDuration = 0.02;
|
| -const NSTimeInterval kHoverHideDuration = 0.4;
|
| -const NSTimeInterval kAlertShowDuration = 0.4;
|
| -const NSTimeInterval kAlertHoldDuration = 0.4;
|
| -const NSTimeInterval kAlertHideDuration = 0.4;
|
| -
|
| -// The default time interval in seconds between glow updates (when
|
| -// increasing/decreasing).
|
| -const NSTimeInterval kGlowUpdateInterval = 0.025;
|
| -
|
| -const CGFloat kTearDistance = 36.0;
|
| -const NSTimeInterval kTearDuration = 0.333;
|
| -
|
| -// This is used to judge whether the mouse has moved during rapid closure; if it
|
| -// has moved less than the threshold, we want to close the tab.
|
| -const CGFloat kRapidCloseDist = 2.5;
|
| -
|
| -} // namespace
|
| -
|
| -@interface TabView(Private)
|
| -
|
| -- (void)resetLastGlowUpdateTime;
|
| -- (NSTimeInterval)timeElapsedSinceLastGlowUpdate;
|
| -- (void)adjustGlowValue;
|
| -// TODO(davidben): When we stop supporting 10.5, this can be removed.
|
| -- (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache;
|
| -- (NSBezierPath*)bezierPathForRect:(NSRect)rect;
|
| -
|
| -@end // TabView(Private)
|
| -
|
| -@implementation TabView
|
| -
|
| -@synthesize state = state_;
|
| -@synthesize hoverAlpha = hoverAlpha_;
|
| -@synthesize alertAlpha = alertAlpha_;
|
| -@synthesize closing = closing_;
|
| -
|
| -- (id)initWithFrame:(NSRect)frame {
|
| - self = [super initWithFrame:frame];
|
| - if (self) {
|
| - [self setShowsDivider:NO];
|
| - // TODO(alcor): register for theming
|
| - }
|
| - return self;
|
| -}
|
| -
|
| -- (void)awakeFromNib {
|
| - [self setShowsDivider:NO];
|
| -
|
| - // It is desirable for us to remove the close button from the cocoa hierarchy,
|
| - // so that VoiceOver does not encounter it.
|
| - // TODO(dtseng): crbug.com/59978.
|
| - // Retain in case we remove it from its superview.
|
| - closeButtonRetainer_.reset([closeButton_ retain]);
|
| - if (BrowserAccessibilityState::GetInstance()->IsAccessibleBrowser()) {
|
| - // The superview gives up ownership of the closeButton here.
|
| - [closeButton_ removeFromSuperview];
|
| - }
|
| -}
|
| -
|
| -- (void)dealloc {
|
| - // Cancel any delayed requests that may still be pending (drags or hover).
|
| - [NSObject cancelPreviousPerformRequestsWithTarget:self];
|
| - [super dealloc];
|
| -}
|
| -
|
| -// Called to obtain the context menu for when the user hits the right mouse
|
| -// button (or control-clicks). (Note that -rightMouseDown: is *not* called for
|
| -// control-click.)
|
| -- (NSMenu*)menu {
|
| - if ([self isClosing])
|
| - return nil;
|
| -
|
| - // Sheets, being window-modal, should block contextual menus. For some reason
|
| - // they do not. Disallow them ourselves.
|
| - if ([[self window] attachedSheet])
|
| - return nil;
|
| -
|
| - return [controller_ menu];
|
| -}
|
| -
|
| -// Overridden so that mouse clicks come to this view (the parent of the
|
| -// hierarchy) first. We want to handle clicks and drags in this class and
|
| -// leave the background button for display purposes only.
|
| -- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent {
|
| - return YES;
|
| -}
|
| -
|
| -- (void)mouseEntered:(NSEvent*)theEvent {
|
| - isMouseInside_ = YES;
|
| - [self resetLastGlowUpdateTime];
|
| - [self adjustGlowValue];
|
| -}
|
| -
|
| -- (void)mouseMoved:(NSEvent*)theEvent {
|
| - hoverPoint_ = [self convertPoint:[theEvent locationInWindow]
|
| - fromView:nil];
|
| - [self setNeedsDisplay:YES];
|
| -}
|
| -
|
| -- (void)mouseExited:(NSEvent*)theEvent {
|
| - isMouseInside_ = NO;
|
| - hoverHoldEndTime_ =
|
| - [NSDate timeIntervalSinceReferenceDate] + kHoverHoldDuration;
|
| - [self resetLastGlowUpdateTime];
|
| - [self adjustGlowValue];
|
| -}
|
| -
|
| -- (void)setTrackingEnabled:(BOOL)enabled {
|
| - [closeButton_ setTrackingEnabled:enabled];
|
| -}
|
| -
|
| -// Determines which view a click in our frame actually hit. It's either this
|
| -// view or our child close button.
|
| -- (NSView*)hitTest:(NSPoint)aPoint {
|
| - NSPoint viewPoint = [self convertPoint:aPoint fromView:[self superview]];
|
| - NSRect frame = [self frame];
|
| -
|
| - // Reduce the width of the hit rect slightly to remove the overlap
|
| - // between adjacent tabs. The drawing code in TabCell has the top
|
| - // corners of the tab inset by height*2/3, so we inset by half of
|
| - // that here. This doesn't completely eliminate the overlap, but it
|
| - // works well enough.
|
| - NSRect hitRect = NSInsetRect(frame, frame.size.height / 3.0f, 0);
|
| - if (![closeButton_ isHidden])
|
| - if (NSPointInRect(viewPoint, [closeButton_ frame])) return closeButton_;
|
| - if (NSPointInRect(aPoint, hitRect)) return self;
|
| - return nil;
|
| -}
|
| -
|
| -// Returns |YES| if this tab can be torn away into a new window.
|
| -- (BOOL)canBeDragged {
|
| - if ([self isClosing])
|
| - return NO;
|
| - NSWindowController* controller = [sourceWindow_ windowController];
|
| - if ([controller isKindOfClass:[TabWindowController class]]) {
|
| - TabWindowController* realController =
|
| - static_cast<TabWindowController*>(controller);
|
| - return [realController isTabDraggable:self];
|
| - }
|
| - return YES;
|
| -}
|
| -
|
| -// Returns an array of controllers that could be a drop target, ordered front to
|
| -// back. It has to be of the appropriate class, and visible (obviously). Note
|
| -// that the window cannot be a target for itself.
|
| -- (NSArray*)dropTargetsForController:(TabWindowController*)dragController {
|
| - NSMutableArray* targets = [NSMutableArray array];
|
| - NSWindow* dragWindow = [dragController window];
|
| - for (NSWindow* window in [NSApp orderedWindows]) {
|
| - if (window == dragWindow) continue;
|
| - if (![window isVisible]) continue;
|
| - // Skip windows on the wrong space.
|
| - if ([window respondsToSelector:@selector(isOnActiveSpace)]) {
|
| - if (![window performSelector:@selector(isOnActiveSpace)])
|
| - continue;
|
| - } else {
|
| - // TODO(davidben): When we stop supporting 10.5, this can be
|
| - // removed.
|
| - //
|
| - // We don't cache the workspace of |dragWindow| because it may
|
| - // move around spaces.
|
| - if ([self getWorkspaceID:dragWindow useCache:NO] !=
|
| - [self getWorkspaceID:window useCache:YES])
|
| - continue;
|
| - }
|
| - NSWindowController* controller = [window windowController];
|
| - if ([controller isKindOfClass:[TabWindowController class]]) {
|
| - TabWindowController* realController =
|
| - static_cast<TabWindowController*>(controller);
|
| - if ([realController canReceiveFrom:dragController])
|
| - [targets addObject:controller];
|
| - }
|
| - }
|
| - return targets;
|
| -}
|
| -
|
| -// Call to clear out transient weak references we hold during drags.
|
| -- (void)resetDragControllers {
|
| - draggedController_ = nil;
|
| - dragWindow_ = nil;
|
| - dragOverlay_ = nil;
|
| - sourceController_ = nil;
|
| - sourceWindow_ = nil;
|
| - targetController_ = nil;
|
| - workspaceIDCache_.clear();
|
| -}
|
| -
|
| -// Sets whether the window background should be visible or invisible when
|
| -// dragging a tab. The background should be invisible when the mouse is over a
|
| -// potential drop target for the tab (the tab strip). It should be visible when
|
| -// there's no drop target so the window looks more fully realized and ready to
|
| -// become a stand-alone window.
|
| -- (void)setWindowBackgroundVisibility:(BOOL)shouldBeVisible {
|
| - if (chromeIsVisible_ == shouldBeVisible)
|
| - return;
|
| -
|
| - // There appears to be a race-condition in CoreAnimation where if we use
|
| - // animators to set the alpha values, we can't guarantee that we cancel them.
|
| - // This has the side effect of sometimes leaving the dragged window
|
| - // translucent or invisible. As a result, don't animate the alpha change.
|
| - [[draggedController_ overlayWindow] setAlphaValue:1.0];
|
| - if (targetController_) {
|
| - [dragWindow_ setAlphaValue:0.0];
|
| - [[draggedController_ overlayWindow] setHasShadow:YES];
|
| - [[targetController_ window] makeMainWindow];
|
| - } else {
|
| - [dragWindow_ setAlphaValue:0.5];
|
| - [[draggedController_ overlayWindow] setHasShadow:NO];
|
| - [[draggedController_ window] makeMainWindow];
|
| - }
|
| - chromeIsVisible_ = shouldBeVisible;
|
| -}
|
| -
|
| -// Handle clicks and drags in this button. We get here because we have
|
| -// overridden acceptsFirstMouse: and the click is within our bounds.
|
| -- (void)mouseDown:(NSEvent*)theEvent {
|
| - if ([self isClosing])
|
| - return;
|
| -
|
| - NSPoint downLocation = [theEvent locationInWindow];
|
| -
|
| - // Record the state of the close button here, because selecting the tab will
|
| - // unhide it.
|
| - BOOL closeButtonActive = [closeButton_ isHidden] ? NO : YES;
|
| -
|
| - // During the tab closure animation (in particular, during rapid tab closure),
|
| - // we may get incorrectly hit with a mouse down. If it should have gone to the
|
| - // close button, we send it there -- it should then track the mouse, so we
|
| - // don't have to worry about mouse ups.
|
| - if (closeButtonActive && [controller_ inRapidClosureMode]) {
|
| - NSPoint hitLocation = [[self superview] convertPoint:downLocation
|
| - fromView:nil];
|
| - if ([self hitTest:hitLocation] == closeButton_) {
|
| - [closeButton_ mouseDown:theEvent];
|
| - return;
|
| - }
|
| - }
|
| -
|
| - // Fire the action to select the tab.
|
| - if ([[controller_ target] respondsToSelector:[controller_ action]])
|
| - [[controller_ target] performSelector:[controller_ action]
|
| - withObject:self];
|
| -
|
| - [self resetDragControllers];
|
| -
|
| - // Resolve overlay back to original window.
|
| - sourceWindow_ = [self window];
|
| - if ([sourceWindow_ isKindOfClass:[NSPanel class]]) {
|
| - sourceWindow_ = [sourceWindow_ parentWindow];
|
| - }
|
| -
|
| - sourceWindowFrame_ = [sourceWindow_ frame];
|
| - sourceTabFrame_ = [self frame];
|
| - sourceController_ = [sourceWindow_ windowController];
|
| - tabWasDragged_ = NO;
|
| - tearTime_ = 0.0;
|
| - draggingWithinTabStrip_ = YES;
|
| - chromeIsVisible_ = NO;
|
| -
|
| - // If there's more than one potential window to be a drop target, we want to
|
| - // treat a drag of a tab just like dragging around a tab that's already
|
| - // detached. Note that unit tests might have |-numberOfTabs| reporting zero
|
| - // since the model won't be fully hooked up. We need to be prepared for that
|
| - // and not send them into the "magnetic" codepath.
|
| - NSArray* targets = [self dropTargetsForController:sourceController_];
|
| - moveWindowOnDrag_ =
|
| - ([sourceController_ numberOfTabs] < 2 && ![targets count]) ||
|
| - ![self canBeDragged] ||
|
| - ![sourceController_ tabDraggingAllowed];
|
| - // If we are dragging a tab, a window with a single tab should immediately
|
| - // snap off and not drag within the tab strip.
|
| - if (!moveWindowOnDrag_)
|
| - draggingWithinTabStrip_ = [sourceController_ numberOfTabs] > 1;
|
| -
|
| - dragOrigin_ = [NSEvent mouseLocation];
|
| -
|
| - // If the tab gets torn off, the tab controller will be removed from the tab
|
| - // strip and then deallocated. This will also result in *us* being
|
| - // deallocated. Both these are bad, so we prevent this by retaining the
|
| - // controller.
|
| - scoped_nsobject<TabController> controller([controller_ retain]);
|
| -
|
| - // Because we move views between windows, we need to handle the event loop
|
| - // ourselves. Ideally we should use the standard event loop.
|
| - while (1) {
|
| - theEvent =
|
| - [NSApp nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask
|
| - untilDate:[NSDate distantFuture]
|
| - inMode:NSDefaultRunLoopMode dequeue:YES];
|
| - NSEventType type = [theEvent type];
|
| - if (type == NSLeftMouseDragged) {
|
| - [self mouseDragged:theEvent];
|
| - } else if (type == NSLeftMouseUp) {
|
| - NSPoint upLocation = [theEvent locationInWindow];
|
| - CGFloat dx = upLocation.x - downLocation.x;
|
| - CGFloat dy = upLocation.y - downLocation.y;
|
| -
|
| - // During rapid tab closure (mashing tab close buttons), we may get hit
|
| - // with a mouse down. As long as the mouse up is over the close button,
|
| - // and the mouse hasn't moved too much, we close the tab.
|
| - if (closeButtonActive &&
|
| - (dx*dx + dy*dy) <= kRapidCloseDist*kRapidCloseDist &&
|
| - [controller inRapidClosureMode]) {
|
| - NSPoint hitLocation =
|
| - [[self superview] convertPoint:[theEvent locationInWindow]
|
| - fromView:nil];
|
| - if ([self hitTest:hitLocation] == closeButton_) {
|
| - [controller closeTab:self];
|
| - break;
|
| - }
|
| - }
|
| -
|
| - [self mouseUp:theEvent];
|
| - break;
|
| - } else {
|
| - // TODO(viettrungluu): [crbug.com/23830] We can receive right-mouse-ups
|
| - // (and maybe even others?) for reasons I don't understand. So we
|
| - // explicitly check for both events we're expecting, and log others. We
|
| - // should figure out what's going on.
|
| - LOG(WARNING) << "Spurious event received of type " << type << ".";
|
| - }
|
| - }
|
| -}
|
| -
|
| -- (void)mouseDragged:(NSEvent*)theEvent {
|
| - // Special-case this to keep the logic below simpler.
|
| - if (moveWindowOnDrag_) {
|
| - if ([sourceController_ windowMovementAllowed]) {
|
| - NSPoint thisPoint = [NSEvent mouseLocation];
|
| - NSPoint origin = sourceWindowFrame_.origin;
|
| - origin.x += (thisPoint.x - dragOrigin_.x);
|
| - origin.y += (thisPoint.y - dragOrigin_.y);
|
| - [sourceWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)];
|
| - } // else do nothing.
|
| - return;
|
| - }
|
| -
|
| - // First, go through the magnetic drag cycle. We break out of this if
|
| - // "stretchiness" ever exceeds a set amount.
|
| - tabWasDragged_ = YES;
|
| -
|
| - if (draggingWithinTabStrip_) {
|
| - NSPoint thisPoint = [NSEvent mouseLocation];
|
| - CGFloat stretchiness = thisPoint.y - dragOrigin_.y;
|
| - stretchiness = copysign(sqrtf(fabs(stretchiness))/sqrtf(kTearDistance),
|
| - stretchiness) / 2.0;
|
| - CGFloat offset = thisPoint.x - dragOrigin_.x;
|
| - if (fabsf(offset) > 100) stretchiness = 0;
|
| - [sourceController_ insertPlaceholderForTab:self
|
| - frame:NSOffsetRect(sourceTabFrame_,
|
| - offset, 0)
|
| - yStretchiness:stretchiness];
|
| - // Check that we haven't pulled the tab too far to start a drag. This
|
| - // can include either pulling it too far down, or off the side of the tab
|
| - // strip that would cause it to no longer be fully visible.
|
| - BOOL stillVisible = [sourceController_ isTabFullyVisible:self];
|
| - CGFloat tearForce = fabs(thisPoint.y - dragOrigin_.y);
|
| - if ([sourceController_ tabTearingAllowed] &&
|
| - (tearForce > kTearDistance || !stillVisible)) {
|
| - draggingWithinTabStrip_ = NO;
|
| - // When you finally leave the strip, we treat that as the origin.
|
| - dragOrigin_.x = thisPoint.x;
|
| - } else {
|
| - // Still dragging within the tab strip, wait for the next drag event.
|
| - return;
|
| - }
|
| - }
|
| -
|
| - // Do not start dragging until the user has "torn" the tab off by
|
| - // moving more than 3 pixels.
|
| - NSDate* targetDwellDate = nil; // The date this target was first chosen.
|
| -
|
| - NSPoint thisPoint = [NSEvent mouseLocation];
|
| -
|
| - // Iterate over possible targets checking for the one the mouse is in.
|
| - // If the tab is just in the frame, bring the window forward to make it
|
| - // easier to drop something there. If it's in the tab strip, set the new
|
| - // target so that it pops into that window. We can't cache this because we
|
| - // need the z-order to be correct.
|
| - NSArray* targets = [self dropTargetsForController:draggedController_];
|
| - TabWindowController* newTarget = nil;
|
| - for (TabWindowController* target in targets) {
|
| - NSRect windowFrame = [[target window] frame];
|
| - if (NSPointInRect(thisPoint, windowFrame)) {
|
| - [[target window] orderFront:self];
|
| - NSRect tabStripFrame = [[target tabStripView] frame];
|
| - tabStripFrame.origin = [[target window]
|
| - convertBaseToScreen:tabStripFrame.origin];
|
| - if (NSPointInRect(thisPoint, tabStripFrame)) {
|
| - newTarget = target;
|
| - }
|
| - break;
|
| - }
|
| - }
|
| -
|
| - // If we're now targeting a new window, re-layout the tabs in the old
|
| - // target and reset how long we've been hovering over this new one.
|
| - if (targetController_ != newTarget) {
|
| - targetDwellDate = [NSDate date];
|
| - [targetController_ removePlaceholder];
|
| - targetController_ = newTarget;
|
| - if (!newTarget) {
|
| - tearTime_ = [NSDate timeIntervalSinceReferenceDate];
|
| - tearOrigin_ = [dragWindow_ frame].origin;
|
| - }
|
| - }
|
| -
|
| - // Create or identify the dragged controller.
|
| - if (!draggedController_) {
|
| - // Get rid of any placeholder remaining in the original source window.
|
| - [sourceController_ removePlaceholder];
|
| -
|
| - // Detach from the current window and put it in a new window. If there are
|
| - // no more tabs remaining after detaching, the source window is about to
|
| - // go away (it's been autoreleased) so we need to ensure we don't reference
|
| - // it any more. In that case the new controller becomes our source
|
| - // controller.
|
| - draggedController_ = [sourceController_ detachTabToNewWindow:self];
|
| - dragWindow_ = [draggedController_ window];
|
| - [dragWindow_ setAlphaValue:0.0];
|
| - if (![sourceController_ hasLiveTabs]) {
|
| - sourceController_ = draggedController_;
|
| - sourceWindow_ = dragWindow_;
|
| - }
|
| -
|
| - // If dragging the tab only moves the current window, do not show overlay
|
| - // so that sheets stay on top of the window.
|
| - // Bring the target window to the front and make sure it has a border.
|
| - [dragWindow_ setLevel:NSFloatingWindowLevel];
|
| - [dragWindow_ setHasShadow:YES];
|
| - [dragWindow_ orderFront:nil];
|
| - [dragWindow_ makeMainWindow];
|
| - [draggedController_ showOverlay];
|
| - dragOverlay_ = [draggedController_ overlayWindow];
|
| - // Force the new tab button to be hidden. We'll reset it on mouse up.
|
| - [draggedController_ showNewTabButton:NO];
|
| - tearTime_ = [NSDate timeIntervalSinceReferenceDate];
|
| - tearOrigin_ = sourceWindowFrame_.origin;
|
| - }
|
| -
|
| - // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by
|
| - // some weird circumstance that doesn't first go through mouseDown:. We
|
| - // really shouldn't go any farther.
|
| - if (!draggedController_ || !sourceController_)
|
| - return;
|
| -
|
| - // When the user first tears off the window, we want slide the window to
|
| - // the current mouse location (to reduce the jarring appearance). We do this
|
| - // by calling ourselves back with additional mouseDragged calls (not actual
|
| - // events). |tearProgress| is a normalized measure of how far through this
|
| - // tear "animation" (of length kTearDuration) we are and has values [0..1].
|
| - // We use sqrt() so the animation is non-linear (slow down near the end
|
| - // point).
|
| - NSTimeInterval tearProgress =
|
| - [NSDate timeIntervalSinceReferenceDate] - tearTime_;
|
| - tearProgress /= kTearDuration; // Normalize.
|
| - tearProgress = sqrtf(MAX(MIN(tearProgress, 1.0), 0.0));
|
| -
|
| - // Move the dragged window to the right place on the screen.
|
| - NSPoint origin = sourceWindowFrame_.origin;
|
| - origin.x += (thisPoint.x - dragOrigin_.x);
|
| - origin.y += (thisPoint.y - dragOrigin_.y);
|
| -
|
| - if (tearProgress < 1) {
|
| - // If the tear animation is not complete, call back to ourself with the
|
| - // same event to animate even if the mouse isn't moving. We need to make
|
| - // sure these get cancelled in mouseUp:.
|
| - [NSObject cancelPreviousPerformRequestsWithTarget:self];
|
| - [self performSelector:@selector(mouseDragged:)
|
| - withObject:theEvent
|
| - afterDelay:1.0f/30.0f];
|
| -
|
| - // Set the current window origin based on how far we've progressed through
|
| - // the tear animation.
|
| - origin.x = (1 - tearProgress) * tearOrigin_.x + tearProgress * origin.x;
|
| - origin.y = (1 - tearProgress) * tearOrigin_.y + tearProgress * origin.y;
|
| - }
|
| -
|
| - if (targetController_) {
|
| - // In order to "snap" two windows of different sizes together at their
|
| - // toolbar, we can't just use the origin of the target frame. We also have
|
| - // to take into consideration the difference in height.
|
| - NSRect targetFrame = [[targetController_ window] frame];
|
| - NSRect sourceFrame = [dragWindow_ frame];
|
| - origin.y = NSMinY(targetFrame) +
|
| - (NSHeight(targetFrame) - NSHeight(sourceFrame));
|
| - }
|
| - [dragWindow_ setFrameOrigin:NSMakePoint(origin.x, origin.y)];
|
| -
|
| - // If we're not hovering over any window, make the window fully
|
| - // opaque. Otherwise, find where the tab might be dropped and insert
|
| - // a placeholder so it appears like it's part of that window.
|
| - if (targetController_) {
|
| - if (![[targetController_ window] isKeyWindow]) {
|
| - // && ([targetDwellDate timeIntervalSinceNow] < -REQUIRED_DWELL)) {
|
| - [[targetController_ window] orderFront:nil];
|
| - targetDwellDate = nil;
|
| - }
|
| -
|
| - // Compute where placeholder should go and insert it into the
|
| - // destination tab strip.
|
| - TabView* draggedTabView = (TabView*)[draggedController_ selectedTabView];
|
| - NSRect tabFrame = [draggedTabView frame];
|
| - tabFrame.origin = [dragWindow_ convertBaseToScreen:tabFrame.origin];
|
| - tabFrame.origin = [[targetController_ window]
|
| - convertScreenToBase:tabFrame.origin];
|
| - tabFrame = [[targetController_ tabStripView]
|
| - convertRect:tabFrame fromView:nil];
|
| - [targetController_ insertPlaceholderForTab:self
|
| - frame:tabFrame
|
| - yStretchiness:0];
|
| - [targetController_ layoutTabs];
|
| - } else {
|
| - [dragWindow_ makeKeyAndOrderFront:nil];
|
| - }
|
| -
|
| - // Adjust the visibility of the window background. If there is a drop target,
|
| - // we want to hide the window background so the tab stands out for
|
| - // positioning. If not, we want to show it so it looks like a new window will
|
| - // be realized.
|
| - BOOL chromeShouldBeVisible = targetController_ == nil;
|
| - [self setWindowBackgroundVisibility:chromeShouldBeVisible];
|
| -}
|
| -
|
| -- (void)mouseUp:(NSEvent*)theEvent {
|
| - // The drag/click is done. If the user dragged the mouse, finalize the drag
|
| - // and clean up.
|
| -
|
| - // Special-case this to keep the logic below simpler.
|
| - if (moveWindowOnDrag_)
|
| - return;
|
| -
|
| - // Cancel any delayed -mouseDragged: requests that may still be pending.
|
| - [NSObject cancelPreviousPerformRequestsWithTarget:self];
|
| -
|
| - // TODO(pinkerton): http://crbug.com/25682 demonstrates a way to get here by
|
| - // some weird circumstance that doesn't first go through mouseDown:. We
|
| - // really shouldn't go any farther.
|
| - if (!sourceController_)
|
| - return;
|
| -
|
| - // We are now free to re-display the new tab button in the window we're
|
| - // dragging. It will show when the next call to -layoutTabs (which happens
|
| - // indrectly by several of the calls below, such as removing the placeholder).
|
| - [draggedController_ showNewTabButton:YES];
|
| -
|
| - if (draggingWithinTabStrip_) {
|
| - if (tabWasDragged_) {
|
| - // Move tab to new location.
|
| - DCHECK([sourceController_ numberOfTabs]);
|
| - TabWindowController* dropController = sourceController_;
|
| - [dropController moveTabView:[dropController selectedTabView]
|
| - fromController:nil];
|
| - }
|
| - } else if (targetController_) {
|
| - // Move between windows. If |targetController_| is nil, we're not dropping
|
| - // into any existing window.
|
| - NSView* draggedTabView = [draggedController_ selectedTabView];
|
| - [targetController_ moveTabView:draggedTabView
|
| - fromController:draggedController_];
|
| - // Force redraw to avoid flashes of old content before returning to event
|
| - // loop.
|
| - [[targetController_ window] display];
|
| - [targetController_ showWindow:nil];
|
| - [draggedController_ removeOverlay];
|
| - } else {
|
| - // Only move the window around on screen. Make sure it's set back to
|
| - // normal state (fully opaque, has shadow, has key, etc).
|
| - [draggedController_ removeOverlay];
|
| - // Don't want to re-show the window if it was closed during the drag.
|
| - if ([dragWindow_ isVisible]) {
|
| - [dragWindow_ setAlphaValue:1.0];
|
| - [dragOverlay_ setHasShadow:NO];
|
| - [dragWindow_ setHasShadow:YES];
|
| - [dragWindow_ makeKeyAndOrderFront:nil];
|
| - }
|
| - [[draggedController_ window] setLevel:NSNormalWindowLevel];
|
| - [draggedController_ removePlaceholder];
|
| - }
|
| - [sourceController_ removePlaceholder];
|
| - chromeIsVisible_ = YES;
|
| -
|
| - [self resetDragControllers];
|
| -}
|
| -
|
| -- (void)otherMouseUp:(NSEvent*)theEvent {
|
| - if ([self isClosing])
|
| - return;
|
| -
|
| - // Support middle-click-to-close.
|
| - if ([theEvent buttonNumber] == 2) {
|
| - // |-hitTest:| takes a location in the superview's coordinates.
|
| - NSPoint upLocation =
|
| - [[self superview] convertPoint:[theEvent locationInWindow]
|
| - fromView:nil];
|
| - // If the mouse up occurred in our view or over the close button, then
|
| - // close.
|
| - if ([self hitTest:upLocation])
|
| - [controller_ closeTab:self];
|
| - }
|
| -}
|
| -
|
| -- (void)drawRect:(NSRect)dirtyRect {
|
| - NSGraphicsContext* context = [NSGraphicsContext currentContext];
|
| - [context saveGraphicsState];
|
| -
|
| - BrowserThemeProvider* themeProvider =
|
| - static_cast<BrowserThemeProvider*>([[self window] themeProvider]);
|
| - [context setPatternPhase:[[self window] themePatternPhase]];
|
| -
|
| - NSRect rect = [self bounds];
|
| - NSBezierPath* path = [self bezierPathForRect:rect];
|
| -
|
| - BOOL selected = [self state];
|
| - // Don't draw the window/tab bar background when selected, since the tab
|
| - // background overlay drawn over it (see below) will be fully opaque.
|
| - BOOL hasBackgroundImage = NO;
|
| - if (!selected) {
|
| - // ThemeProvider::HasCustomImage is true only if the theme provides the
|
| - // image. However, even if the theme doesn't provide a tab background, the
|
| - // theme machinery will make one if given a frame image. See
|
| - // BrowserThemePack::GenerateTabBackgroundImages for details.
|
| - hasBackgroundImage = themeProvider &&
|
| - (themeProvider->HasCustomImage(IDR_THEME_TAB_BACKGROUND) ||
|
| - themeProvider->HasCustomImage(IDR_THEME_FRAME));
|
| -
|
| - NSColor* backgroundImageColor = hasBackgroundImage ?
|
| - themeProvider->GetNSImageColorNamed(IDR_THEME_TAB_BACKGROUND, true) :
|
| - nil;
|
| -
|
| - if (backgroundImageColor) {
|
| - [backgroundImageColor set];
|
| - [path fill];
|
| - } else {
|
| - // Use the window's background color rather than |[NSColor
|
| - // windowBackgroundColor]|, which gets confused by the fullscreen window.
|
| - // (The result is the same for normal, non-fullscreen windows.)
|
| - [[[self window] backgroundColor] set];
|
| - [path fill];
|
| - [[NSColor colorWithCalibratedWhite:1.0 alpha:0.3] set];
|
| - [path fill];
|
| - }
|
| - }
|
| -
|
| - [context saveGraphicsState];
|
| - [path addClip];
|
| -
|
| - // Use the same overlay for the selected state and for hover and alert glows;
|
| - // for the selected state, it's fully opaque.
|
| - CGFloat hoverAlpha = [self hoverAlpha];
|
| - CGFloat alertAlpha = [self alertAlpha];
|
| - if (selected || hoverAlpha > 0 || alertAlpha > 0) {
|
| - // Draw the selected background / glow overlay.
|
| - [context saveGraphicsState];
|
| - CGContextRef cgContext = static_cast<CGContextRef>([context graphicsPort]);
|
| - CGContextBeginTransparencyLayer(cgContext, 0);
|
| - if (!selected) {
|
| - // The alert glow overlay is like the selected state but at most at most
|
| - // 80% opaque. The hover glow brings up the overlay's opacity at most 50%.
|
| - CGFloat backgroundAlpha = 0.8 * alertAlpha;
|
| - backgroundAlpha += (1 - backgroundAlpha) * 0.5 * hoverAlpha;
|
| - CGContextSetAlpha(cgContext, backgroundAlpha);
|
| - }
|
| - [path addClip];
|
| - [context saveGraphicsState];
|
| - [super drawBackground];
|
| - [context restoreGraphicsState];
|
| -
|
| - // Draw a mouse hover gradient for the default themes.
|
| - if (!selected && hoverAlpha > 0) {
|
| - if (themeProvider && !hasBackgroundImage) {
|
| - scoped_nsobject<NSGradient> glow([NSGradient alloc]);
|
| - [glow initWithStartingColor:[NSColor colorWithCalibratedWhite:1.0
|
| - alpha:1.0 * hoverAlpha]
|
| - endingColor:[NSColor colorWithCalibratedWhite:1.0
|
| - alpha:0.0]];
|
| -
|
| - NSPoint point = hoverPoint_;
|
| - point.y = NSHeight(rect);
|
| - [glow drawFromCenter:point
|
| - radius:0.0
|
| - toCenter:point
|
| - radius:NSWidth(rect) / 3.0
|
| - options:NSGradientDrawsBeforeStartingLocation];
|
| -
|
| - [glow drawInBezierPath:path relativeCenterPosition:hoverPoint_];
|
| - }
|
| - }
|
| -
|
| - CGContextEndTransparencyLayer(cgContext);
|
| - [context restoreGraphicsState];
|
| - }
|
| -
|
| - BOOL active = [[self window] isKeyWindow] || [[self window] isMainWindow];
|
| - CGFloat borderAlpha = selected ? (active ? 0.3 : 0.2) : 0.2;
|
| - NSColor* borderColor = [NSColor colorWithDeviceWhite:0.0 alpha:borderAlpha];
|
| - NSColor* highlightColor = themeProvider ? themeProvider->GetNSColor(
|
| - themeProvider->UsingDefaultTheme() ?
|
| - BrowserThemeProvider::COLOR_TOOLBAR_BEZEL :
|
| - BrowserThemeProvider::COLOR_TOOLBAR, true) : nil;
|
| -
|
| - // Draw the top inner highlight within the currently selected tab if using
|
| - // the default theme.
|
| - if (selected && themeProvider && themeProvider->UsingDefaultTheme()) {
|
| - NSAffineTransform* highlightTransform = [NSAffineTransform transform];
|
| - [highlightTransform translateXBy:1.0 yBy:-1.0];
|
| - scoped_nsobject<NSBezierPath> highlightPath([path copy]);
|
| - [highlightPath transformUsingAffineTransform:highlightTransform];
|
| - [highlightColor setStroke];
|
| - [highlightPath setLineWidth:1.0];
|
| - [highlightPath stroke];
|
| - highlightTransform = [NSAffineTransform transform];
|
| - [highlightTransform translateXBy:-2.0 yBy:0.0];
|
| - [highlightPath transformUsingAffineTransform:highlightTransform];
|
| - [highlightPath stroke];
|
| - }
|
| -
|
| - [context restoreGraphicsState];
|
| -
|
| - // Draw the top stroke.
|
| - [context saveGraphicsState];
|
| - [borderColor set];
|
| - [path setLineWidth:1.0];
|
| - [path stroke];
|
| - [context restoreGraphicsState];
|
| -
|
| - // Mimic the tab strip's bottom border, which consists of a dark border
|
| - // and light highlight.
|
| - if (!selected) {
|
| - [path addClip];
|
| - NSRect borderRect = rect;
|
| - borderRect.origin.y = 1;
|
| - borderRect.size.height = 1;
|
| - [borderColor set];
|
| - NSRectFillUsingOperation(borderRect, NSCompositeSourceOver);
|
| -
|
| - borderRect.origin.y = 0;
|
| - [highlightColor set];
|
| - NSRectFillUsingOperation(borderRect, NSCompositeSourceOver);
|
| - }
|
| -
|
| - [context restoreGraphicsState];
|
| -}
|
| -
|
| -- (void)viewDidMoveToWindow {
|
| - [super viewDidMoveToWindow];
|
| - if ([self window]) {
|
| - [controller_ updateTitleColor];
|
| - }
|
| -}
|
| -
|
| -- (void)setClosing:(BOOL)closing {
|
| - closing_ = closing; // Safe because the property is nonatomic.
|
| - // When closing, ensure clicks to the close button go nowhere.
|
| - if (closing) {
|
| - [closeButton_ setTarget:nil];
|
| - [closeButton_ setAction:nil];
|
| - }
|
| -}
|
| -
|
| -- (void)startAlert {
|
| - // Do not start a new alert while already alerting or while in a decay cycle.
|
| - if (alertState_ == tabs::kAlertNone) {
|
| - alertState_ = tabs::kAlertRising;
|
| - [self resetLastGlowUpdateTime];
|
| - [self adjustGlowValue];
|
| - }
|
| -}
|
| -
|
| -- (void)cancelAlert {
|
| - if (alertState_ != tabs::kAlertNone) {
|
| - alertState_ = tabs::kAlertFalling;
|
| - alertHoldEndTime_ =
|
| - [NSDate timeIntervalSinceReferenceDate] + kGlowUpdateInterval;
|
| - [self resetLastGlowUpdateTime];
|
| - [self adjustGlowValue];
|
| - }
|
| -}
|
| -
|
| -- (BOOL)accessibilityIsIgnored {
|
| - return NO;
|
| -}
|
| -
|
| -- (NSArray*)accessibilityActionNames {
|
| - NSArray* parentActions = [super accessibilityActionNames];
|
| -
|
| - return [parentActions arrayByAddingObject:NSAccessibilityPressAction];
|
| -}
|
| -
|
| -- (NSArray*)accessibilityAttributeNames {
|
| - NSMutableArray* attributes =
|
| - [[super accessibilityAttributeNames] mutableCopy];
|
| - [attributes addObject:NSAccessibilityTitleAttribute];
|
| - [attributes addObject:NSAccessibilityEnabledAttribute];
|
| -
|
| - return attributes;
|
| -}
|
| -
|
| -- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute {
|
| - if ([attribute isEqual:NSAccessibilityTitleAttribute])
|
| - return NO;
|
| -
|
| - if ([attribute isEqual:NSAccessibilityEnabledAttribute])
|
| - return NO;
|
| -
|
| - return [super accessibilityIsAttributeSettable:attribute];
|
| -}
|
| -
|
| -- (id)accessibilityAttributeValue:(NSString*)attribute {
|
| - if ([attribute isEqual:NSAccessibilityRoleAttribute])
|
| - return NSAccessibilityButtonRole;
|
| -
|
| - if ([attribute isEqual:NSAccessibilityTitleAttribute])
|
| - return [controller_ title];
|
| -
|
| - if ([attribute isEqual:NSAccessibilityEnabledAttribute])
|
| - return [NSNumber numberWithBool:YES];
|
| -
|
| - if ([attribute isEqual:NSAccessibilityChildrenAttribute]) {
|
| - // The subviews (icon and text) are clutter; filter out everything but
|
| - // useful controls.
|
| - NSArray* children = [super accessibilityAttributeValue:attribute];
|
| - NSMutableArray* okChildren = [NSMutableArray array];
|
| - for (id child in children) {
|
| - if ([child isKindOfClass:[NSButtonCell class]])
|
| - [okChildren addObject:child];
|
| - }
|
| -
|
| - return okChildren;
|
| - }
|
| -
|
| - return [super accessibilityAttributeValue:attribute];
|
| -}
|
| -
|
| -- (ViewID)viewID {
|
| - return VIEW_ID_TAB;
|
| -}
|
| -
|
| -@end // @implementation TabView
|
| -
|
| -@implementation TabView (TabControllerInterface)
|
| -
|
| -- (void)setController:(TabController*)controller {
|
| - controller_ = controller;
|
| -}
|
| -
|
| -@end // @implementation TabView (TabControllerInterface)
|
| -
|
| -@implementation TabView(Private)
|
| -
|
| -- (void)resetLastGlowUpdateTime {
|
| - lastGlowUpdate_ = [NSDate timeIntervalSinceReferenceDate];
|
| -}
|
| -
|
| -- (NSTimeInterval)timeElapsedSinceLastGlowUpdate {
|
| - return [NSDate timeIntervalSinceReferenceDate] - lastGlowUpdate_;
|
| -}
|
| -
|
| -- (void)adjustGlowValue {
|
| - // A time interval long enough to represent no update.
|
| - const NSTimeInterval kNoUpdate = 1000000;
|
| -
|
| - // Time until next update for either glow.
|
| - NSTimeInterval nextUpdate = kNoUpdate;
|
| -
|
| - NSTimeInterval elapsed = [self timeElapsedSinceLastGlowUpdate];
|
| - NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
|
| -
|
| - // TODO(viettrungluu): <http://crbug.com/30617> -- split off the stuff below
|
| - // into a pure function and add a unit test.
|
| -
|
| - CGFloat hoverAlpha = [self hoverAlpha];
|
| - if (isMouseInside_) {
|
| - // Increase hover glow until it's 1.
|
| - if (hoverAlpha < 1) {
|
| - hoverAlpha = MIN(hoverAlpha + elapsed / kHoverShowDuration, 1);
|
| - [self setHoverAlpha:hoverAlpha];
|
| - nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
|
| - } // Else already 1 (no update needed).
|
| - } else {
|
| - if (currentTime >= hoverHoldEndTime_) {
|
| - // No longer holding, so decrease hover glow until it's 0.
|
| - if (hoverAlpha > 0) {
|
| - hoverAlpha = MAX(hoverAlpha - elapsed / kHoverHideDuration, 0);
|
| - [self setHoverAlpha:hoverAlpha];
|
| - nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
|
| - } // Else already 0 (no update needed).
|
| - } else {
|
| - // Schedule update for end of hold time.
|
| - nextUpdate = MIN(hoverHoldEndTime_ - currentTime, nextUpdate);
|
| - }
|
| - }
|
| -
|
| - CGFloat alertAlpha = [self alertAlpha];
|
| - if (alertState_ == tabs::kAlertRising) {
|
| - // Increase alert glow until it's 1 ...
|
| - alertAlpha = MIN(alertAlpha + elapsed / kAlertShowDuration, 1);
|
| - [self setAlertAlpha:alertAlpha];
|
| -
|
| - // ... and having reached 1, switch to holding.
|
| - if (alertAlpha >= 1) {
|
| - alertState_ = tabs::kAlertHolding;
|
| - alertHoldEndTime_ = currentTime + kAlertHoldDuration;
|
| - nextUpdate = MIN(kAlertHoldDuration, nextUpdate);
|
| - } else {
|
| - nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
|
| - }
|
| - } else if (alertState_ != tabs::kAlertNone) {
|
| - if (alertAlpha > 0) {
|
| - if (currentTime >= alertHoldEndTime_) {
|
| - // Stop holding, then decrease alert glow (until it's 0).
|
| - if (alertState_ == tabs::kAlertHolding) {
|
| - alertState_ = tabs::kAlertFalling;
|
| - nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
|
| - } else {
|
| - DCHECK_EQ(tabs::kAlertFalling, alertState_);
|
| - alertAlpha = MAX(alertAlpha - elapsed / kAlertHideDuration, 0);
|
| - [self setAlertAlpha:alertAlpha];
|
| - nextUpdate = MIN(kGlowUpdateInterval, nextUpdate);
|
| - }
|
| - } else {
|
| - // Schedule update for end of hold time.
|
| - nextUpdate = MIN(alertHoldEndTime_ - currentTime, nextUpdate);
|
| - }
|
| - } else {
|
| - // Done the alert decay cycle.
|
| - alertState_ = tabs::kAlertNone;
|
| - }
|
| - }
|
| -
|
| - if (nextUpdate < kNoUpdate)
|
| - [self performSelector:_cmd withObject:nil afterDelay:nextUpdate];
|
| -
|
| - [self resetLastGlowUpdateTime];
|
| - [self setNeedsDisplay:YES];
|
| -}
|
| -
|
| -// Returns the workspace id of |window|. If |useCache|, then lookup
|
| -// and remember the value in |workspaceIDCache_| until the end of the
|
| -// current drag.
|
| -- (int)getWorkspaceID:(NSWindow*)window useCache:(BOOL)useCache {
|
| - CGWindowID windowID = [window windowNumber];
|
| - if (useCache) {
|
| - std::map<CGWindowID, int>::iterator iter =
|
| - workspaceIDCache_.find(windowID);
|
| - if (iter != workspaceIDCache_.end())
|
| - return iter->second;
|
| - }
|
| -
|
| - int workspace = -1;
|
| - // It's possible to query in bulk, but probably not necessary.
|
| - base::mac::ScopedCFTypeRef<CFArrayRef> windowIDs(CFArrayCreate(
|
| - NULL, reinterpret_cast<const void **>(&windowID), 1, NULL));
|
| - base::mac::ScopedCFTypeRef<CFArrayRef> descriptions(
|
| - CGWindowListCreateDescriptionFromArray(windowIDs));
|
| - DCHECK(CFArrayGetCount(descriptions.get()) <= 1);
|
| - if (CFArrayGetCount(descriptions.get()) > 0) {
|
| - CFDictionaryRef dict = static_cast<CFDictionaryRef>(
|
| - CFArrayGetValueAtIndex(descriptions.get(), 0));
|
| - DCHECK(CFGetTypeID(dict) == CFDictionaryGetTypeID());
|
| -
|
| - // Sanity check the ID.
|
| - CFNumberRef otherIDRef = (CFNumberRef)base::mac::GetValueFromDictionary(
|
| - dict, kCGWindowNumber, CFNumberGetTypeID());
|
| - CGWindowID otherID;
|
| - if (otherIDRef &&
|
| - CFNumberGetValue(otherIDRef, kCGWindowIDCFNumberType, &otherID) &&
|
| - otherID == windowID) {
|
| - // And then get the workspace.
|
| - CFNumberRef workspaceRef = (CFNumberRef)base::mac::GetValueFromDictionary(
|
| - dict, kCGWindowWorkspace, CFNumberGetTypeID());
|
| - if (!workspaceRef ||
|
| - !CFNumberGetValue(workspaceRef, kCFNumberIntType, &workspace)) {
|
| - workspace = -1;
|
| - }
|
| - } else {
|
| - NOTREACHED();
|
| - }
|
| - }
|
| - if (useCache) {
|
| - workspaceIDCache_[windowID] = workspace;
|
| - }
|
| - return workspace;
|
| -}
|
| -
|
| -// Returns the bezier path used to draw the tab given the bounds to draw it in.
|
| -- (NSBezierPath*)bezierPathForRect:(NSRect)rect {
|
| - // Outset by 0.5 in order to draw on pixels rather than on borders (which
|
| - // would cause blurry pixels). Subtract 1px of height to compensate, otherwise
|
| - // clipping will occur.
|
| - rect = NSInsetRect(rect, -0.5, -0.5);
|
| - rect.size.height -= 1.0;
|
| -
|
| - NSPoint bottomLeft = NSMakePoint(NSMinX(rect), NSMinY(rect) + 2);
|
| - NSPoint bottomRight = NSMakePoint(NSMaxX(rect), NSMinY(rect) + 2);
|
| - NSPoint topRight =
|
| - NSMakePoint(NSMaxX(rect) - kInsetMultiplier * NSHeight(rect),
|
| - NSMaxY(rect));
|
| - NSPoint topLeft =
|
| - NSMakePoint(NSMinX(rect) + kInsetMultiplier * NSHeight(rect),
|
| - NSMaxY(rect));
|
| -
|
| - CGFloat baseControlPointOutset = NSHeight(rect) * kControlPoint1Multiplier;
|
| - CGFloat bottomControlPointInset = NSHeight(rect) * kControlPoint2Multiplier;
|
| -
|
| - // Outset many of these values by 1 to cause the fill to bleed outside the
|
| - // clip area.
|
| - NSBezierPath* path = [NSBezierPath bezierPath];
|
| - [path moveToPoint:NSMakePoint(bottomLeft.x - 1, bottomLeft.y - 2)];
|
| - [path lineToPoint:NSMakePoint(bottomLeft.x - 1, bottomLeft.y)];
|
| - [path lineToPoint:bottomLeft];
|
| - [path curveToPoint:topLeft
|
| - controlPoint1:NSMakePoint(bottomLeft.x + baseControlPointOutset,
|
| - bottomLeft.y)
|
| - controlPoint2:NSMakePoint(topLeft.x - bottomControlPointInset,
|
| - topLeft.y)];
|
| - [path lineToPoint:topRight];
|
| - [path curveToPoint:bottomRight
|
| - controlPoint1:NSMakePoint(topRight.x + bottomControlPointInset,
|
| - topRight.y)
|
| - controlPoint2:NSMakePoint(bottomRight.x - baseControlPointOutset,
|
| - bottomRight.y)];
|
| - [path lineToPoint:NSMakePoint(bottomRight.x + 1, bottomRight.y)];
|
| - [path lineToPoint:NSMakePoint(bottomRight.x + 1, bottomRight.y - 2)];
|
| - return path;
|
| -}
|
| -
|
| -@end // @implementation TabView(Private)
|
|
|