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