PageRenderTime 90ms CodeModel.GetById 25ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/google-toolbox-for-mac/AppKit/GTMLargeTypeWindow.m

http://macfuse.googlecode.com/
Objective C | 412 lines | 329 code | 50 blank | 33 comment | 25 complexity | 29a64f9dbe07ba5ce116c6eb7551c049 MD5 | raw file
  1//
  2//  GTMLargeTypeWindow.m
  3//
  4//  Copyright 2008 Google Inc.
  5//
  6//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
  7//  use this file except in compliance with the License.  You may obtain a copy
  8//  of the License at
  9//
 10//  http://www.apache.org/licenses/LICENSE-2.0
 11//
 12//  Unless required by applicable law or agreed to in writing, software
 13//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 14//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 15//  License for the specific language governing permissions and limitations under
 16//  the License.
 17//
 18
 19#import <QuartzCore/QuartzCore.h>
 20
 21#import "GTMLargeTypeWindow.h"
 22#import "GTMGeometryUtils.h"
 23#import "GTMNSBezierPath+RoundRect.h"
 24#import "GTMMethodCheck.h"
 25#import "GTMTypeCasting.h"
 26
 27// How far to inset the text from the edge of the window
 28static const CGFloat kEdgeInset = 16.0;
 29
 30// Give us an alpha value for our backing window
 31static const CGFloat kTwoThirdsAlpha = 0.66;
 32
 33// Amount of time to do copy animations
 34static NSTimeInterval gGTMLargeTypeWindowCopyAnimationDuration = 0.5;
 35
 36// Amount of time to do fade animations
 37static NSTimeInterval gGTMLargeTypeWindowFadeAnimationDuration = 0.333;
 38
 39@interface GTMLargeTypeCopyAnimation : NSAnimation {
 40 @private
 41  NSView *view_;
 42}
 43- (id)initWithView:(NSView *)view
 44          duration:(NSTimeInterval)duration
 45    animationCurve:(NSAnimationCurve)animationCurve;
 46@end
 47
 48@interface GTMLargeTypeBackgroundView : NSView <NSAnimationDelegate> {
 49  CIFilter *transition_;
 50  GTMLargeTypeCopyAnimation *animation_;
 51}
 52- (void)animateCopyWithDuration:(NSTimeInterval)duration;
 53@end
 54
 55@interface GTMLargeTypeWindow (GTMLargeTypeWindowPrivate)
 56+ (CGSize)displaySize;
 57- (void)animateWithEffect:(NSString*)effect;
 58@end
 59
 60@implementation GTMLargeTypeWindow
 61
 62- (id)initWithString:(NSString *)string {
 63  if ([string length] == 0) {
 64    _GTMDevLog(@"GTMLargeTypeWindow got an empty string");
 65    [self release];
 66    return nil;
 67  }
 68  CGSize displaySize = [[self class] displaySize];
 69  NSMutableAttributedString *attrString
 70    = [[[NSMutableAttributedString alloc] initWithString:string] autorelease];
 71
 72  NSRange fullRange = NSMakeRange(0, [string length]);
 73  [attrString addAttribute:NSForegroundColorAttributeName
 74                     value:[NSColor whiteColor]
 75                     range:fullRange];
 76
 77  NSMutableParagraphStyle *style
 78    = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease];
 79  [style setAlignment:NSCenterTextAlignment];
 80  [attrString addAttribute:NSParagraphStyleAttributeName
 81                     value:style
 82                     range:fullRange];
 83
 84  NSShadow *textShadow = [[[NSShadow alloc] init] autorelease];
 85  [textShadow setShadowOffset:NSMakeSize( 5, -5 )];
 86  [textShadow setShadowBlurRadius:10];
 87  [textShadow setShadowColor:[NSColor colorWithCalibratedWhite:0
 88                                                         alpha:kTwoThirdsAlpha]];
 89  [attrString addAttribute:NSShadowAttributeName
 90                     value:textShadow
 91                     range:fullRange];
 92
 93  // Try and find a size that fits without iterating too many times.
 94  // We start going 50 pixels at a time, then 10, then 1
 95  int size = -26;  // start at 24 (-26 + 50)
 96  int offsets[] = { 50, 10, 1 };
 97  for (size_t i = 0; i < sizeof(offsets) / sizeof(int); ++i) {
 98    for(size = size + offsets[i]; size >= 24 && size < 300; size += offsets[i]) {
 99      NSFont *font = [NSFont boldSystemFontOfSize:size] ;
100      [attrString addAttribute:NSFontAttributeName
101                         value:font
102                         range:fullRange];
103      NSSize textSize = [attrString size];
104      NSSize maxAdvanceSize = [font maximumAdvancement];
105      if (textSize.width + maxAdvanceSize.width > displaySize.width ||
106        textSize.height > displaySize.height) {
107        size = size - offsets[i];
108        break;
109      }
110    }
111  }
112
113  // Bounds check our values
114  if (size > 300) {
115    size = 300;
116  } else if (size < 24) {
117    size = 24;
118  }
119  [attrString addAttribute:NSFontAttributeName
120                     value:[NSFont boldSystemFontOfSize:size]
121                     range:fullRange];
122  return [self initWithAttributedString:attrString];
123}
124
125- (id)initWithAttributedString:(NSAttributedString *)attrString {
126  if ([attrString length] == 0) {
127    _GTMDevLog(@"GTMLargeTypeWindow got an empty string");
128    [self release];
129    return nil;
130  }
131  CGSize displaySize = [[self class] displaySize];
132  NSRect frame = NSMakeRect(0, 0, displaySize.width, 0);
133  NSTextView *textView = [[[NSTextView alloc] initWithFrame:frame] autorelease];
134  [textView setEditable:NO];
135  [textView setSelectable:NO];
136  [textView setDrawsBackground:NO];
137  [[textView textStorage] setAttributedString:attrString];
138  [textView sizeToFit];
139
140  return [self initWithContentView:textView];
141}
142
143- (id)initWithImage:(NSImage*)image {
144  if (!image) {
145    _GTMDevLog(@"GTMLargeTypeWindow got an empty image");
146    [self release];
147    return nil;
148  }
149  NSRect rect = GTMNSRectOfSize([image size]);
150  NSImageView *imageView
151    = [[[NSImageView alloc] initWithFrame:rect] autorelease];
152  [imageView setImage:image];
153  return [self initWithContentView:imageView];
154}
155
156- (id)initWithContentView:(NSView *)view {
157  NSRect bounds = NSZeroRect;
158  if (view) {
159    bounds = [view bounds];
160  }
161  if (!view || bounds.size.height <= 0 || bounds.size.width <= 0) {
162    _GTMDevLog(@"GTMLargeTypeWindow got an empty view");
163    [self release];
164    return nil;
165  }
166  NSRect screenRect = [[NSScreen mainScreen] frame];
167  NSRect windowRect = GTMNSAlignRectangles([view frame],
168                                           screenRect,
169                                           GTMRectAlignCenter);
170  windowRect = NSInsetRect(windowRect, -kEdgeInset, -kEdgeInset);
171  windowRect = NSIntegralRect(windowRect);
172  NSUInteger mask = NSBorderlessWindowMask | NSNonactivatingPanelMask;
173  self = [super initWithContentRect:windowRect
174                          styleMask:mask
175                            backing:NSBackingStoreBuffered
176                              defer:NO];
177  if (self) {
178    [self setFrame:GTMNSAlignRectangles(windowRect,
179                                        screenRect,
180                                        GTMRectAlignCenter)
181           display:YES];
182    [self setBackgroundColor:[NSColor clearColor]];
183    [self setOpaque:NO];
184    [self setLevel:NSFloatingWindowLevel];
185    [self setHidesOnDeactivate:NO];
186
187    GTMLargeTypeBackgroundView *content
188      = [[[GTMLargeTypeBackgroundView alloc] initWithFrame:NSZeroRect]
189         autorelease];
190    [self setHasShadow:YES];
191    [self setContentView:content];
192    [self setAlphaValue:0];
193    [self setIgnoresMouseEvents:YES];
194    [view setFrame:GTMNSAlignRectangles([view frame],
195                                        [content frame],
196                                        GTMRectAlignCenter)];
197    [content addSubview:view];
198    [content setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
199    [self setInitialFirstResponder:view];
200  }
201  return self;
202}
203
204+ (NSTimeInterval)copyAnimationDuration {
205  return gGTMLargeTypeWindowCopyAnimationDuration;
206}
207
208+ (void)setCopyAnimationDuration:(NSTimeInterval)duration {
209  gGTMLargeTypeWindowCopyAnimationDuration = duration;
210}
211
212+ (NSTimeInterval)fadeAnimationDuration {
213  return gGTMLargeTypeWindowFadeAnimationDuration;
214}
215
216+ (void)setFadeAnimationDuration:(NSTimeInterval)duration {
217  gGTMLargeTypeWindowFadeAnimationDuration = duration;
218}
219
220- (void)copy:(id)sender {
221  id firstResponder = [self initialFirstResponder];
222  if ([firstResponder respondsToSelector:@selector(textStorage)]) {
223    NSPasteboard *pb = [NSPasteboard generalPasteboard];
224    [pb declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:self];
225    [pb setString:[[firstResponder textStorage] string]
226        forType:NSStringPboardType];
227  }
228
229  // Give the user some feedback that a copy has occurred
230  NSTimeInterval dur = [[self class] copyAnimationDuration];
231  GTMLargeTypeBackgroundView *view
232    = GTM_STATIC_CAST(GTMLargeTypeBackgroundView, [self contentView]);
233  [view animateCopyWithDuration:dur];
234}
235
236- (BOOL)canBecomeKeyWindow {
237  return YES;
238}
239
240- (BOOL)performKeyEquivalent:(NSEvent *)theEvent {
241  NSString *chars = [theEvent charactersIgnoringModifiers];
242  NSUInteger flags = ([theEvent modifierFlags] &
243                      NSDeviceIndependentModifierFlagsMask);
244  BOOL isValid = (flags == NSCommandKeyMask) && [chars isEqualToString:@"c"];
245  if (isValid) {
246    [self copy:self];
247  }
248  return isValid;
249}
250
251- (void)keyDown:(NSEvent *)theEvent {
252  [self close];
253}
254
255- (void)resignKeyWindow {
256  [super resignKeyWindow];
257  if([self isVisible]) {
258    [self close];
259  }
260}
261
262- (void)makeKeyAndOrderFront:(id)sender {
263  [super makeKeyAndOrderFront:sender];
264  [self animateWithEffect:NSViewAnimationFadeInEffect];
265}
266
267- (void)orderFront:(id)sender {
268  [super orderFront:sender];
269  [self animateWithEffect:NSViewAnimationFadeInEffect];
270}
271
272- (void)orderOut:(id)sender {
273  [self animateWithEffect:NSViewAnimationFadeOutEffect];
274  [super orderOut:sender];
275}
276
277+ (CGSize)displaySize {
278  NSRect screenRect = [[NSScreen mainScreen] frame];
279  // This is just a rough calculation to make us fill a good proportion
280  // of the main screen.
281  CGFloat width = (NSWidth(screenRect) * 11.0 / 12.0) - (2.0 * kEdgeInset);
282  CGFloat height = (NSHeight(screenRect) * 11.0 / 12.0) - (2.0 * kEdgeInset);
283  return CGSizeMake(width, height);
284}
285
286- (void)animateWithEffect:(NSString*)effect {
287  NSDictionary *fadeIn = [NSDictionary dictionaryWithObjectsAndKeys:
288                          self, NSViewAnimationTargetKey,
289                          effect, NSViewAnimationEffectKey,
290                          nil];
291  NSArray *animation = [NSArray arrayWithObject:fadeIn];
292  NSViewAnimation *viewAnim
293    = [[[NSViewAnimation alloc] initWithViewAnimations:animation] autorelease];
294  [viewAnim setDuration:[[self class] fadeAnimationDuration]];
295  [viewAnim setAnimationBlockingMode:NSAnimationBlocking];
296  [viewAnim startAnimation];
297}
298
299@end
300
301@implementation GTMLargeTypeBackgroundView
302GTM_METHOD_CHECK(NSBezierPath, gtm_appendBezierPathWithRoundRect:cornerRadius:);
303
304- (void)dealloc {
305  // If we get released while animating, we'd better clean up.
306  [animation_ stopAnimation];
307  [animation_ release];
308  [transition_ release];
309  [super dealloc];
310}
311
312- (BOOL)isOpaque {
313  return NO;
314}
315
316- (void)drawRect:(NSRect)rect {
317  rect = [self bounds];
318  NSBezierPath *roundRect = [NSBezierPath bezierPath];
319  CGFloat minRadius = MIN(NSWidth(rect), NSHeight(rect)) * 0.5f;
320
321  [roundRect gtm_appendBezierPathWithRoundRect:rect
322                                  cornerRadius:MIN(minRadius, 32)];
323  [roundRect addClip];
324  if (transition_) {
325    NSNumber *val = [NSNumber numberWithFloat:[animation_ currentValue]];
326    [transition_ setValue:val forKey:@"inputTime"];
327    CIImage *outputCIImage = [transition_ valueForKey:@"outputImage"];
328    [outputCIImage drawInRect:rect
329                     fromRect:rect
330                    operation:NSCompositeSourceOver
331                     fraction:1.0];
332  } else {
333    [[NSColor colorWithDeviceWhite:0 alpha:kTwoThirdsAlpha] set];
334
335    NSRectFill(rect);
336  }
337}
338
339- (void)animateCopyWithDuration:(NSTimeInterval)duration {
340  // This does a photocopy swipe to show folks that their copy has succceeded
341  // Store off a copy of our background
342  NSRect bounds = [self bounds];
343  NSBitmapImageRep *rep = [self bitmapImageRepForCachingDisplayInRect:bounds];
344  NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithBitmapImageRep:rep];
345  [NSGraphicsContext saveGraphicsState];
346  [NSGraphicsContext setCurrentContext:context];
347  [self drawRect:bounds];
348  [NSGraphicsContext restoreGraphicsState];
349  CIVector *extent = [CIVector vectorWithX:bounds.origin.x
350                                         Y:bounds.origin.y
351                                         Z:bounds.size.width
352                                         W:bounds.size.height];
353  CIFilter *transition = [CIFilter filterWithName:@"CICopyMachineTransition"];
354  [transition setDefaults];
355  [transition setValue:extent
356                 forKey:@"inputExtent"];
357  CIImage *image = [[CIImage alloc] initWithBitmapImageRep:rep];
358
359  [transition setValue:image forKey:@"inputImage"];
360  [transition setValue:image forKey:@"inputTargetImage"];
361  [transition setValue:[NSNumber numberWithInt:0]
362                 forKey:@"inputTime"];
363  [transition valueForKey:@"outputImage"];
364  [image release];
365  transition_ = [transition retain];
366  animation_ = [[GTMLargeTypeCopyAnimation alloc] initWithView:self
367                                                      duration:duration
368                                                animationCurve:NSAnimationLinear];
369  [animation_ setFrameRate:0.0f];
370  [animation_ setDelegate:self];
371  [animation_ setAnimationBlockingMode:NSAnimationBlocking];
372  [animation_ startAnimation];
373}
374
375- (void)animationDidEnd:(NSAnimation*)animation {
376  [animation_ release];
377  animation_ = nil;
378  [transition_ release];
379  transition_ = nil;
380  [self display];
381}
382
383- (float)animation:(NSAnimation*)animation
384  valueForProgress:(NSAnimationProgress)progress {
385  // This gives us half the copy animation, so we don't swing back
386  // Don't want too much gratuitous effect
387  // 0.6 is required by experimentation. 0.5 doesn't do it
388  return progress * 0.6f;
389}
390@end
391
392@implementation GTMLargeTypeCopyAnimation
393- (id)initWithView:(NSView *)view
394          duration:(NSTimeInterval)duration
395    animationCurve:(NSAnimationCurve)animationCurve {
396  if ((self = [super initWithDuration:duration
397                       animationCurve:animationCurve])) {
398    view_ = [view retain];
399  }
400  return self;
401}
402
403- (void)dealloc {
404  [view_ release];
405  [super dealloc];
406}
407
408- (void)setCurrentProgress:(NSAnimationProgress)progress {
409  [super setCurrentProgress:progress];
410  [view_ display];
411}
412@end