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

http://macfuse.googlecode.com/ · Objective C · 655 lines · 450 code · 80 blank · 125 comment · 70 complexity · 1a94270b2e1fee7b840d0033ef5e88d7 MD5 · raw file

  1. //
  2. // GTMUILocalizerAndLayoutTweaker.m
  3. //
  4. // Copyright 2009 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 "GTMUILocalizerAndLayoutTweaker.h"
  19. #import "GTMUILocalizer.h"
  20. // Controls if +wrapString:width:font: uses a subclassed TypeSetter to do
  21. // its work in one pass.
  22. #define GTM_USE_TYPESETTER 1
  23. // Helper that will try to do a SizeToFit on any UI items and do the special
  24. // case handling we also need to end up with a usable UI item. It also takes
  25. // an offset so we can slide the item if we need to.
  26. // Returns the change in the view's size.
  27. static NSSize SizeToFit(NSView *view, NSPoint offset);
  28. // Compare function for -[NSArray sortedArrayUsingFunction:context:]
  29. static NSInteger CompareFrameX(id view1, id view2, void *context);
  30. // Check if the view is anchored on the right (fixed right, flexible left).
  31. static BOOL IsRightAnchored(NSView *view);
  32. // Constant for a forced string wrap in button cells (Opt-Return in IB inserts
  33. // this into the string).
  34. NSString * const kForcedWrapString = @"\xA";
  35. // Radio and Checkboxes (NSButtonCell) appears to use two different layout
  36. // algorithms for sizeToFit calls and drawing calls when there is a forced word
  37. // wrap in the title. The result is a sizeToFit can tell you it all fits N
  38. // lines in the given rect, but at draw time, it draws as >N lines and never
  39. // gets as wide, resulting in a clipped control. This fudge factor is what is
  40. // added to try and avoid these by giving the size calls just enough slop to
  41. // handle the differences.
  42. // radar://7831901 different wrapping between sizeToFit and drawing
  43. static const CGFloat kWrapperStringSlop = 0.9;
  44. #if GTM_USE_TYPESETTER
  45. @interface GTMBreakRecordingTypeSetter : NSATSTypesetter {
  46. @private
  47. NSMutableArray *array_;
  48. }
  49. @end
  50. @implementation GTMBreakRecordingTypeSetter
  51. - (id)init {
  52. if ((self = [super init])) {
  53. array_ = [[NSMutableArray alloc] init];
  54. }
  55. return self;
  56. }
  57. - (void)dealloc {
  58. [array_ release];
  59. [super dealloc];
  60. }
  61. - (BOOL)shouldBreakLineByWordBeforeCharacterAtIndex:(NSUInteger)charIndex {
  62. [array_ addObject:[NSNumber numberWithUnsignedInteger:charIndex]];
  63. return YES;
  64. }
  65. - (NSArray*)breakArray {
  66. return array_;
  67. }
  68. @end
  69. #endif // GTM_USE_TYPESETTER
  70. @interface GTMUILocalizerAndLayoutTweaker (PrivateMethods)
  71. // Recursively walk the UI triggering Tweakers.
  72. - (void)tweakView:(NSView *)view;
  73. // Insert newlines so the string wraps to the given width using the requested
  74. // font.
  75. + (NSString*)wrapString:(NSString *)string
  76. width:(CGFloat)width
  77. font:(NSFont *)font;
  78. @end
  79. @interface GTMWidthBasedTweaker (InternalMethods)
  80. // Does the actual work to size and adjust the views within this Tweaker. The
  81. // offset is the amount this view should shift as part of it's resize.
  82. // Returns change in this view's width.
  83. - (CGFloat)tweakLayoutWithOffset:(NSPoint)offset;
  84. @end
  85. @implementation GTMUILocalizerAndLayoutTweaker
  86. - (void)awakeFromNib {
  87. if (uiObject_) {
  88. GTMUILocalizer *localizer = localizer_;
  89. if (!localizer) {
  90. NSBundle *bundle = [GTMUILocalizer bundleForOwner:localizerOwner_];
  91. localizer = [[[GTMUILocalizer alloc] initWithBundle:bundle] autorelease];
  92. }
  93. [self applyLocalizer:localizer tweakingUI:uiObject_];
  94. }
  95. }
  96. - (void)applyLocalizer:(GTMUILocalizer *)localizer
  97. tweakingUI:(id)uiObject {
  98. // Localize first
  99. [localizer localizeObject:uiObject recursively:YES];
  100. // Then tweak!
  101. [self tweakUI:uiObject];
  102. }
  103. - (void)tweakUI:(id)uiObject {
  104. // Figure out where we start
  105. NSView *startView;
  106. if ([uiObject isKindOfClass:[NSWindow class]]) {
  107. startView = [(NSWindow *)uiObject contentView];
  108. } else {
  109. _GTMDevAssert([uiObject isKindOfClass:[NSView class]],
  110. @"should have been a subclass of NSView");
  111. startView = (NSView *)uiObject;
  112. }
  113. // Tweak away!
  114. [self tweakView:startView];
  115. }
  116. - (void)tweakView:(NSView *)view {
  117. // If it's a alignment box, let it do its thing...
  118. if ([view isKindOfClass:[GTMWidthBasedTweaker class]]) {
  119. [(GTMWidthBasedTweaker *)view tweakLayoutWithOffset:NSZeroPoint];
  120. // Do our best to support TabViews. If the tabs need to resize, you are
  121. // probably better off manually running them through a tweaker and then fixing
  122. // up the parent view (and other tabs) to look right.
  123. } else if ([view isKindOfClass:[NSTabView class]]) {
  124. NSArray *tabViewItems = [(NSTabView *)view tabViewItems];
  125. NSTabViewItem *item = nil;
  126. GTM_FOREACH_OBJECT(item, tabViewItems) {
  127. [self tweakView:[item view]];
  128. }
  129. // Generically look for subviews...
  130. } else {
  131. NSArray *subviews = [view subviews];
  132. NSView *subview = nil;
  133. GTM_FOREACH_OBJECT(subview, subviews) {
  134. [self tweakView:subview];
  135. }
  136. }
  137. }
  138. + (NSString*)wrapString:(NSString *)string
  139. width:(CGFloat)width
  140. font:(NSFont *)font {
  141. // Set up the objects needed for the layout work.
  142. NSRect targetRect = NSMakeRect(0, 0, width, CGFLOAT_MAX);
  143. NSTextContainer* textContainer =
  144. [[[NSTextContainer alloc] initWithContainerSize:targetRect.size]
  145. autorelease];
  146. NSLayoutManager* layoutManager = [[[NSLayoutManager alloc] init] autorelease];
  147. NSTextStorage* textStorage =
  148. [[[NSTextStorage alloc] initWithString:string] autorelease];
  149. [textStorage addLayoutManager:layoutManager];
  150. [layoutManager addTextContainer:textContainer];
  151. // From playing in interface builder, the padding seems to be 2 on the line
  152. // fragments to get the same wrapping as what the NSCell will do in the end.
  153. [textContainer setLineFragmentPadding:2.0f];
  154. // Apply the font.
  155. [textStorage setFont:font];
  156. // Get the mutable string for the layout, remove any forced wraps in it.
  157. NSMutableString* workerStr = [textStorage mutableString];
  158. [workerStr replaceOccurrencesOfString:kForcedWrapString
  159. withString:@""
  160. options:NSLiteralSearch
  161. range:NSMakeRange(0, [workerStr length])];
  162. #if GTM_USE_TYPESETTER
  163. // Put in the recording type setter.
  164. GTMBreakRecordingTypeSetter *typeSetter =
  165. [[[GTMBreakRecordingTypeSetter alloc] init] autorelease];
  166. [layoutManager setTypesetter:typeSetter];
  167. // Make sure things are layed out (10.5 has a clean API for this, 10.4
  168. // doesn't).
  169. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  170. [layoutManager ensureLayoutForCharacterRange:NSMakeRange(0,
  171. [textStorage length])];
  172. #else
  173. [layoutManager lineFragmentRectForGlyphAtIndex:[layoutManager numberOfGlyphs]-1
  174. effectiveRange:NULL];
  175. #endif
  176. // Insert the breaks everywere the type setter got asked about breaks.
  177. NSEnumerator *reverseEnumerator =
  178. [[typeSetter breakArray] reverseObjectEnumerator];
  179. NSNumber *number;
  180. while ((number = [reverseEnumerator nextObject]) != nil) {
  181. [workerStr insertString:kForcedWrapString
  182. atIndex:[number unsignedIntegerValue]];
  183. }
  184. #else
  185. // Find out how tall lines would be for the layout loop.
  186. CGFloat lineHeight = [layoutManager defaultLineHeightForFont:font];
  187. targetRect.size.height = lineHeight;
  188. // Loop until all glyphs are layout out.
  189. NSUInteger numGlyphsUsed = 0;
  190. while (numGlyphsUsed < [layoutManager numberOfGlyphs]) {
  191. // See what fits in the current rect
  192. NSRange range = [layoutManager glyphRangeForBoundingRect:targetRect
  193. inTextContainer:textContainer];
  194. numGlyphsUsed = NSMaxRange(range);
  195. if (numGlyphsUsed < [layoutManager numberOfGlyphs]) {
  196. // Didn't all fit, add a break, and grow the rect to try again.
  197. NSRange charRange = [layoutManager glyphRangeForCharacterRange:range
  198. actualCharacterRange:nil];
  199. [workerStr insertString:kForcedWrapString atIndex:NSMaxRange(charRange)];
  200. targetRect.size.height += lineHeight;
  201. }
  202. }
  203. #endif // GTM_USE_TYPESETTER
  204. // Return the string with forced wraps
  205. return [[workerStr copy] autorelease];
  206. }
  207. + (NSSize)sizeToFitView:(NSView *)view {
  208. return SizeToFit(view, NSZeroPoint);
  209. }
  210. + (CGFloat)sizeToFitFixedWidthTextField:(NSTextField *)textField {
  211. NSRect initialFrame = [textField frame];
  212. NSRect sizeRect = NSMakeRect(0, 0, NSWidth(initialFrame), CGFLOAT_MAX);
  213. NSSize newSize = [[textField cell] cellSizeForBounds:sizeRect];
  214. newSize.width = NSWidth(initialFrame);
  215. [textField setFrameSize:newSize];
  216. return newSize.height - NSHeight(initialFrame);
  217. }
  218. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  219. + (CGFloat)sizeToFitFixedHeightTextField:(NSTextField *)textField {
  220. return [self sizeToFitFixedHeightTextField:textField minWidth:(CGFloat)0];
  221. }
  222. + (CGFloat)sizeToFitFixedHeightTextField:(NSTextField *)textField
  223. minWidth:(NSUInteger)minWidth {
  224. NSRect initialRect = [textField frame];
  225. NSCell *cell = [textField cell];
  226. NSSize titleSize = [cell titleRectForBounds:initialRect].size;
  227. // Find linebreak point, and keep trying them until we're under the height
  228. // requested.
  229. NSString *str = [textField stringValue];
  230. CFStringTokenizerRef tokenizer =
  231. CFStringTokenizerCreate(NULL,
  232. (CFStringRef)str,
  233. CFRangeMake(0, [str length]),
  234. kCFStringTokenizerUnitLineBreak,
  235. NULL);
  236. if (!tokenizer) {
  237. _GTMDevAssert(tokenizer, @"failed to get a tokenizer");
  238. return 0.0;
  239. }
  240. NSCell *workerCell = [[cell copy] autorelease];
  241. // Loop trying line break points until the height fits.
  242. while (1) {
  243. CFStringTokenizerTokenType tokenType =
  244. CFStringTokenizerAdvanceToNextToken(tokenizer);
  245. if (tokenType == kCFStringTokenizerTokenNone) {
  246. // Reached the end without ever find a good width, how?
  247. _GTMDevAssert(0, @"Failed to find a good size?!");
  248. [textField sizeToFit];
  249. break;
  250. }
  251. CFRange tokenRange = CFStringTokenizerGetCurrentTokenRange(tokenizer);
  252. NSRange subStringRange =
  253. NSMakeRange(0, tokenRange.location + tokenRange.length);
  254. NSString *subString = [str substringWithRange:subStringRange];
  255. // Find how wide the cell would be for this sub string.
  256. [workerCell setStringValue:subString];
  257. CGFloat testWidth = [workerCell cellSize].width;
  258. // Find the overall size if wrapped to this width.
  259. NSRect sizeRect = NSMakeRect(0, 0, testWidth, CGFLOAT_MAX);
  260. NSSize newSize = [cell cellSizeForBounds:sizeRect];
  261. if (newSize.height <= titleSize.height) {
  262. [textField setFrameSize:newSize];
  263. break;
  264. }
  265. }
  266. CFRelease(tokenizer);
  267. NSSize fixedSize = [textField frame].size;
  268. NSSize finalSize = NSMakeSize(fixedSize.width, NSHeight(initialRect));
  269. // Enforce the minWidth
  270. if (minWidth > fixedSize.width) {
  271. finalSize.width = minWidth;
  272. }
  273. // Make integral.
  274. finalSize.width = ceil(fixedSize.width);
  275. finalSize.height = ceil(fixedSize.height);
  276. if (!NSEqualSizes(fixedSize, finalSize)) {
  277. [textField setFrameSize:finalSize];
  278. }
  279. // Return how much things changed
  280. return finalSize.width - NSWidth(initialRect);
  281. }
  282. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  283. + (void)wrapButtonTitleForWidth:(NSButton *)button {
  284. NSCell *cell = [button cell];
  285. NSRect frame = [button frame];
  286. NSRect titleFrame = [cell titleRectForBounds:frame];
  287. NSString* newTitle = [self wrapString:[button title]
  288. width:NSWidth(titleFrame)
  289. font:[button font]];
  290. [button setTitle:newTitle];
  291. }
  292. + (void)wrapRadioGroupForWidth:(NSMatrix *)radioGroup {
  293. NSSize cellSize = [radioGroup cellSize];
  294. NSRect tmpRect = NSMakeRect(0, 0, cellSize.width, cellSize.height);
  295. NSFont *font = [radioGroup font];
  296. NSCell *cell;
  297. GTM_FOREACH_OBJECT(cell, [radioGroup cells]) {
  298. NSRect titleFrame = [cell titleRectForBounds:tmpRect];
  299. NSString* newTitle = [self wrapString:[cell title]
  300. width:NSWidth(titleFrame)
  301. font:font];
  302. [cell setTitle:newTitle];
  303. }
  304. }
  305. + (void)resizeWindowWithoutAutoResizingSubViews:(NSWindow*)window
  306. delta:(NSSize)delta {
  307. NSView *contentView = [window contentView];
  308. // Clear autosizesSubviews (saving the state).
  309. BOOL autoresizesSubviews = [contentView autoresizesSubviews];
  310. if (autoresizesSubviews) {
  311. [contentView setAutoresizesSubviews:NO];
  312. }
  313. NSRect rect = [contentView convertRect:[window frame] fromView:nil];
  314. rect.size.width += delta.width;
  315. rect.size.height += delta.height;
  316. rect = [contentView convertRect:rect toView:nil];
  317. [window setFrame:rect display:NO];
  318. // For some reason the content view is resizing, but some times not adjusting
  319. // its origin, so correct it manually.
  320. [contentView setFrameOrigin:NSMakePoint(0, 0)];
  321. // Restore autosizesSubviews.
  322. if (autoresizesSubviews) {
  323. [contentView setAutoresizesSubviews:YES];
  324. }
  325. }
  326. + (void)resizeViewWithoutAutoResizingSubViews:(NSView*)view
  327. delta:(NSSize)delta {
  328. // Clear autosizesSubviews (saving the state).
  329. BOOL autoresizesSubviews = [view autoresizesSubviews];
  330. if (autoresizesSubviews) {
  331. [view setAutoresizesSubviews:NO];
  332. }
  333. NSRect rect = [view frame];
  334. rect.size.width += delta.width;
  335. rect.size.height += delta.height;
  336. [view setFrame:rect];
  337. // Restore autosizesSubviews.
  338. if (autoresizesSubviews) {
  339. [view setAutoresizesSubviews:YES];
  340. }
  341. }
  342. @end
  343. @implementation GTMWidthBasedTweaker
  344. - (CGFloat)changedWidth {
  345. return widthChange_;
  346. }
  347. - (CGFloat)tweakLayoutWithOffset:(NSPoint)offset {
  348. NSArray *subviews = [self subviews];
  349. if (![subviews count]) {
  350. widthChange_ = 0.0;
  351. return widthChange_;
  352. }
  353. BOOL sumMode = NO;
  354. NSMutableArray *rightAlignedSubViews = nil;
  355. NSMutableArray *rightAlignedSubViewDeltas = nil;
  356. if ([subviews count] > 1) {
  357. // Check if the frames are in a row by seeing if when they are left aligned
  358. // they overlap. If they don't overlap in this case, it means they are
  359. // probably stacked instead.
  360. NSRect rect1 = [[subviews objectAtIndex:0] frame];
  361. NSRect rect2 = [[subviews objectAtIndex:1] frame];
  362. rect1.origin.x = rect2.origin.x = 0;
  363. if (NSIntersectsRect(rect1, rect2)) {
  364. // No, so walk them x order moving them along so they don't overlap.
  365. sumMode = YES;
  366. subviews = [subviews sortedArrayUsingFunction:CompareFrameX context:NULL];
  367. } else {
  368. // Since they are vertical, any views pinned to the right will have to be
  369. // shifted after we finish figuring out the final size.
  370. rightAlignedSubViews = [NSMutableArray array];
  371. rightAlignedSubViewDeltas = [NSMutableArray array];
  372. }
  373. }
  374. // Size our subviews
  375. NSView *subView;
  376. CGFloat finalDelta = sumMode ? 0 : -CGFLOAT_MAX;
  377. NSPoint subViewOffset = NSZeroPoint;
  378. GTM_FOREACH_OBJECT(subView, subviews) {
  379. if (sumMode) {
  380. subViewOffset.x = finalDelta;
  381. }
  382. CGFloat delta = SizeToFit(subView, subViewOffset).width;
  383. if (sumMode) {
  384. finalDelta += delta;
  385. } else {
  386. if (delta > finalDelta) {
  387. finalDelta = delta;
  388. }
  389. }
  390. // Track the right anchored subviews size changes so we can update them
  391. // once we know this view's size.
  392. if (IsRightAnchored(subView)) {
  393. [rightAlignedSubViews addObject:subView];
  394. #if CGFLOAT_IS_DOUBLE
  395. NSNumber *nsDelta = [NSNumber numberWithDouble:delta];
  396. #else
  397. NSNumber *nsDelta = [NSNumber numberWithFloat:delta];
  398. #endif
  399. [rightAlignedSubViewDeltas addObject:nsDelta];
  400. }
  401. }
  402. // Are we pinned to the right of our parent?
  403. BOOL rightAnchored = IsRightAnchored(self);
  404. // Adjust our size (turn off auto resize, because we just fixed up all the
  405. // objects within us).
  406. BOOL autoresizesSubviews = [self autoresizesSubviews];
  407. if (autoresizesSubviews) {
  408. [self setAutoresizesSubviews:NO];
  409. }
  410. NSRect selfFrame = [self frame];
  411. selfFrame.size.width += finalDelta;
  412. if (rightAnchored) {
  413. // Right side is anchored, so we need to slide back to the left.
  414. selfFrame.origin.x -= finalDelta;
  415. }
  416. selfFrame.origin.x += offset.x;
  417. selfFrame.origin.y += offset.y;
  418. [self setFrame:selfFrame];
  419. if (autoresizesSubviews) {
  420. [self setAutoresizesSubviews:autoresizesSubviews];
  421. }
  422. // Now spin over the list of right aligned view and their size changes
  423. // fixing up their positions so they are still right aligned in our final
  424. // view.
  425. for (NSUInteger lp = 0; lp < [rightAlignedSubViews count]; ++lp) {
  426. subView = [rightAlignedSubViews objectAtIndex:lp];
  427. CGFloat delta = [[rightAlignedSubViewDeltas objectAtIndex:lp] doubleValue];
  428. NSRect viewFrame = [subView frame];
  429. viewFrame.origin.x += -delta + finalDelta;
  430. [subView setFrame:viewFrame];
  431. }
  432. if (viewToSlideAndResize_) {
  433. NSRect viewFrame = [viewToSlideAndResize_ frame];
  434. if (!rightAnchored) {
  435. // If our right wasn't anchored, this view slides (we push it right).
  436. // (If our right was anchored, the assumption is the view is in front of
  437. // us so its x shouldn't move.)
  438. viewFrame.origin.x += finalDelta;
  439. }
  440. viewFrame.size.width -= finalDelta;
  441. [viewToSlideAndResize_ setFrame:viewFrame];
  442. }
  443. if (viewToSlide_) {
  444. NSRect viewFrame = [viewToSlide_ frame];
  445. // Move the view the same direction we moved.
  446. if (rightAnchored) {
  447. viewFrame.origin.x -= finalDelta;
  448. } else {
  449. viewFrame.origin.x += finalDelta;
  450. }
  451. [viewToSlide_ setFrame:viewFrame];
  452. }
  453. if (viewToResize_) {
  454. if ([viewToResize_ isKindOfClass:[NSWindow class]]) {
  455. NSWindow *window = (NSWindow *)viewToResize_;
  456. NSView *contentView = [window contentView];
  457. NSRect windowFrame = [contentView convertRect:[window frame]
  458. fromView:nil];
  459. windowFrame.size.width += finalDelta;
  460. windowFrame = [contentView convertRect:windowFrame toView:nil];
  461. [window setFrame:windowFrame display:YES];
  462. // For some reason the content view is resizing, but not adjusting its
  463. // origin, so correct it manually.
  464. [contentView setFrameOrigin:NSMakePoint(0, 0)];
  465. // TODO: should we update min size?
  466. } else {
  467. NSRect viewFrame = [viewToResize_ frame];
  468. viewFrame.size.width += finalDelta;
  469. [viewToResize_ setFrame:viewFrame];
  470. // TODO: should we check if this view is right anchored, and adjust its
  471. // x position also?
  472. }
  473. }
  474. widthChange_ = finalDelta;
  475. return widthChange_;
  476. }
  477. @end
  478. #pragma mark -
  479. static NSSize SizeToFit(NSView *view, NSPoint offset) {
  480. // If we've got one of us within us, recurse (for grids)
  481. if ([view isKindOfClass:[GTMWidthBasedTweaker class]]) {
  482. GTMWidthBasedTweaker *widthAlignmentBox = (GTMWidthBasedTweaker *)view;
  483. return NSMakeSize([widthAlignmentBox tweakLayoutWithOffset:offset], 0);
  484. }
  485. NSRect oldFrame = [view frame];
  486. NSRect fitFrame = oldFrame;
  487. NSRect newFrame = oldFrame;
  488. if ([view isKindOfClass:[NSTextField class]] &&
  489. [(NSTextField *)view isEditable]) {
  490. // Don't try to sizeToFit because edit fields really don't want to be sized
  491. // to what is in them as they are for users to enter things so honor their
  492. // current size.
  493. #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  494. } else if ([view isKindOfClass:[NSPathControl class]]) {
  495. // Don't try to sizeToFit because NSPathControls usually need to be able
  496. // to display any path, so they shouldn't tight down to whatever they
  497. // happen to be listing at the moment.
  498. #endif // MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
  499. } else {
  500. // Genericaly fire a sizeToFit if it has one.
  501. if ([view respondsToSelector:@selector(sizeToFit)]) {
  502. [view performSelector:@selector(sizeToFit)];
  503. fitFrame = [view frame];
  504. newFrame = fitFrame;
  505. if ([view isKindOfClass:[NSMatrix class]]) {
  506. NSMatrix *matrix = (NSMatrix *)view;
  507. // See note on kWrapperStringSlop for why this is done.
  508. NSCell *cell;
  509. GTM_FOREACH_OBJECT(cell, [matrix cells]) {
  510. if ([[cell title] rangeOfString:kForcedWrapString].location !=
  511. NSNotFound) {
  512. newFrame.size.width += kWrapperStringSlop;
  513. break;
  514. }
  515. }
  516. }
  517. }
  518. if ([view isKindOfClass:[NSButton class]]) {
  519. NSButton *button = (NSButton *)view;
  520. // -[NSButton sizeToFit] gives much worse results than IB's Size to Fit
  521. // option for standard push buttons.
  522. if (([button bezelStyle] == NSRoundedBezelStyle) &&
  523. ([[button cell] controlSize] == NSRegularControlSize)) {
  524. // This is the amount of padding IB adds over a sizeToFit, empirically
  525. // determined.
  526. const CGFloat kExtraPaddingAmount = 12.0;
  527. // Width is tricky, new buttons in IB are 96 wide, Carbon seems to have
  528. // defaulted to 70, Cocoa seems to like 82. But we go with 96 since
  529. // that's what IB is doing these days.
  530. const CGFloat kMinButtonWidth = (CGFloat)96.0;
  531. newFrame.size.width = NSWidth(newFrame) + kExtraPaddingAmount;
  532. if (NSWidth(newFrame) < kMinButtonWidth) {
  533. newFrame.size.width = kMinButtonWidth;
  534. }
  535. } else if ([button bezelStyle] == NSTexturedRoundedBezelStyle &&
  536. [[button cell] controlSize] == NSRegularControlSize) {
  537. // The round textured style needs to have a little extra padding,
  538. // otherwise the baseline of the text sinks by a few pixels.
  539. const CGFloat kExtraPaddingAmount = 4.0;
  540. newFrame.size.width += kExtraPaddingAmount;
  541. } else {
  542. // See note on kWrapperStringSlop for why this is done.
  543. NSString *title = [button title];
  544. if ([title rangeOfString:kForcedWrapString].location != NSNotFound) {
  545. newFrame.size.width += kWrapperStringSlop;
  546. }
  547. }
  548. }
  549. }
  550. // Apply the offset, and see if we need to change the frame (again).
  551. newFrame.origin.x += offset.x;
  552. newFrame.origin.y += offset.y;
  553. if (!NSEqualRects(fitFrame, newFrame)) {
  554. [view setFrame:newFrame];
  555. }
  556. // Return how much we changed size.
  557. return NSMakeSize(NSWidth(newFrame) - NSWidth(oldFrame),
  558. NSHeight(newFrame) - NSHeight(oldFrame));
  559. }
  560. static NSInteger CompareFrameX(id view1, id view2, void *context) {
  561. CGFloat x1 = [view1 frame].origin.x;
  562. CGFloat x2 = [view2 frame].origin.x;
  563. if (x1 < x2)
  564. return NSOrderedAscending;
  565. else if (x1 > x2)
  566. return NSOrderedDescending;
  567. else
  568. return NSOrderedSame;
  569. }
  570. static BOOL IsRightAnchored(NSView *view) {
  571. NSUInteger autoresizing = [view autoresizingMask];
  572. BOOL viewRightAnchored =
  573. ((autoresizing & (NSViewMinXMargin | NSViewMaxXMargin)) == NSViewMinXMargin);
  574. return viewRightAnchored;
  575. }