/core/externals/google-toolbox-for-mac/UnitTesting/GTMAppKit+UnitTesting.m

http://macfuse.googlecode.com/ · Objective C · 583 lines · 334 code · 85 blank · 164 comment · 21 complexity · 61cdd799ac722957b60ef51b6fd7fed5 MD5 · raw file

  1. //
  2. // GTMAppKit+UnitTesting.m
  3. //
  4. // Categories for making unit testing of graphics/UI easier.
  5. //
  6. // Copyright 2006-2008 Google Inc.
  7. //
  8. // Licensed under the Apache License, Version 2.0 (the "License"); you may not
  9. // use this file except in compliance with the License. You may obtain a copy
  10. // of the License at
  11. //
  12. // http://www.apache.org/licenses/LICENSE-2.0
  13. //
  14. // Unless required by applicable law or agreed to in writing, software
  15. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  16. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  17. // License for the specific language governing permissions and limitations under
  18. // the License.
  19. //
  20. #import "GTMDefines.h"
  21. #import "GTMAppKit+UnitTesting.h"
  22. #import "GTMGeometryUtils.h"
  23. #import "GTMMethodCheck.h"
  24. #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
  25. #define ENCODE_NSINTEGER(coder, i, key) [(coder) encodeInt:(i) forKey:(key)]
  26. #else
  27. #define ENCODE_NSINTEGER(coder, i, key) [(coder) encodeInteger:(i) forKey:(key)]
  28. #endif
  29. @implementation NSApplication (GTMUnitTestingAdditions)
  30. GTM_METHOD_CHECK(NSObject, gtm_unitTestEncodeState:);
  31. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  32. [super gtm_unitTestEncodeState:inCoder];
  33. ENCODE_NSINTEGER(inCoder, [[self mainWindow] windowNumber], @"ApplicationMainWindow");
  34. // Descend down into the windows allowing them to store their state
  35. NSWindow *window = nil;
  36. int i = 0;
  37. GTM_FOREACH_OBJECT(window, [self windows]) {
  38. if ([window isVisible]) {
  39. // Only record visible windows because invisible windows may be closing on us
  40. // This appears to happen differently in 64 bit vs 32 bit, and items
  41. // in the window may hold an extra retain count for a while until the
  42. // event loop is spun. To avoid all this, we just don't record non
  43. // visible windows.
  44. // See rdar://5851458 for details.
  45. [inCoder encodeObject:window forKey:[NSString stringWithFormat:@"Window %d", i]];
  46. i = i + 1;
  47. }
  48. }
  49. // and encode the menu bar
  50. NSMenu *mainMenu = [self mainMenu];
  51. if (mainMenu) {
  52. [inCoder encodeObject:mainMenu forKey:@"MenuBar"];
  53. }
  54. }
  55. @end
  56. @implementation NSWindow (GTMUnitTestingAdditions)
  57. - (CGImageRef)gtm_unitTestImage {
  58. return [[[self contentView] superview] gtm_unitTestImage];
  59. }
  60. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  61. [super gtm_unitTestEncodeState:inCoder];
  62. [inCoder encodeObject:[self title] forKey:@"WindowTitle"];
  63. [inCoder encodeBool:[self isVisible] forKey:@"WindowIsVisible"];
  64. // Do not record if window is key, because users running unit tests
  65. // and clicking around to other apps, could change this mid test causing
  66. // issues.
  67. // [inCoder encodeBool:[self isKeyWindow] forKey:@"WindowIsKey"];
  68. [inCoder encodeBool:[self isMainWindow] forKey:@"WindowIsMain"];
  69. [inCoder encodeObject:[self contentView] forKey:@"WindowContent"];
  70. if ([self toolbar]) {
  71. [inCoder encodeObject:[self toolbar] forKey:@"WindowToolbar"];
  72. }
  73. }
  74. @end
  75. @implementation NSControl (GTMUnitTestingAdditions)
  76. // Encodes the state of an object in a manner suitable for comparing
  77. // against a master state file so we can determine whether the
  78. // object is in a suitable state.
  79. //
  80. // Arguments:
  81. // inCoder - the coder to encode our state into
  82. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  83. [super gtm_unitTestEncodeState:inCoder];
  84. [inCoder encodeObject:[self class] forKey:@"ControlType"];
  85. [inCoder encodeObject:[self objectValue] forKey:@"ControlValue"];
  86. [inCoder encodeObject:[self selectedCell] forKey:@"ControlSelectedCell"];
  87. ENCODE_NSINTEGER(inCoder, [self tag], @"ControlTag");
  88. [inCoder encodeBool:[self isEnabled] forKey:@"ControlIsEnabled"];
  89. }
  90. @end
  91. @implementation NSButton (GTMUnitTestingAdditions)
  92. // Encodes the state of an object in a manner suitable for comparing
  93. // against a master state file so we can determine whether the
  94. // object is in a suitable state.
  95. //
  96. // Arguments:
  97. // inCoder - the coder to encode our state into
  98. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  99. [super gtm_unitTestEncodeState:inCoder];
  100. NSString *alternateTitle = [self alternateTitle];
  101. if (alternateTitle) {
  102. [inCoder encodeObject:alternateTitle forKey:@"ButtonAlternateTitle"];
  103. }
  104. }
  105. @end
  106. @implementation NSTextField (GTMUnitTestingAdditions)
  107. - (BOOL)gtm_shouldEncodeStateForSubviews {
  108. return NO;
  109. }
  110. // Encodes the state of an object in a manner suitable for comparing
  111. // against a master state file so we can determine whether the
  112. // object is in a suitable state.
  113. //
  114. // Arguments:
  115. // inCoder - the coder to encode our state into
  116. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  117. [super gtm_unitTestEncodeState:inCoder];
  118. id controlCell = [self cell];
  119. if ([controlCell isKindOfClass:[NSTextFieldCell class]]) {
  120. NSTextFieldCell *textFieldCell = controlCell;
  121. [inCoder encodeObject:[textFieldCell placeholderString]
  122. forKey:@"PlaceHolderString"];
  123. }
  124. }
  125. @end
  126. @implementation NSCell (GTMUnitTestingAdditions)
  127. // Encodes the state of an object in a manner suitable for comparing
  128. // against a master state file so we can determine whether the
  129. // object is in a suitable state.
  130. //
  131. // Arguments:
  132. // inCoder - the coder to encode our state into
  133. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  134. [super gtm_unitTestEncodeState:inCoder];
  135. BOOL isImageCell = NO;
  136. if ([self hasValidObjectValue]) {
  137. id val = [self objectValue];
  138. [inCoder encodeObject:val forKey:@"CellValue"];
  139. isImageCell = [val isKindOfClass:[NSImage class]];
  140. }
  141. if (!isImageCell) {
  142. // Image cells have a title that includes addresses that aren't going
  143. // to be constant, so we don't encode them. All the info we need
  144. // is going to be in the CellValue encoding.
  145. [inCoder encodeObject:[self title] forKey:@"CellTitle"];
  146. }
  147. ENCODE_NSINTEGER(inCoder, [self state], @"CellState");
  148. ENCODE_NSINTEGER(inCoder, [self tag], @"CellTag");
  149. }
  150. @end
  151. @implementation NSImage (GTMUnitTestingAdditions)
  152. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  153. [super gtm_unitTestEncodeState:inCoder];
  154. [inCoder encodeObject:NSStringFromSize([self size]) forKey:@"ImageSize"];
  155. [inCoder encodeObject:[self name] forKey:@"ImageName"];
  156. }
  157. - (CGImageRef)gtm_unitTestImage {
  158. // Create up a context
  159. NSSize size = [self size];
  160. NSRect rect = GTMNSRectOfSize(size);
  161. CGSize cgSize = GTMNSSizeToCGSize(size);
  162. CGContextRef contextRef = GTMCreateUnitTestBitmapContextOfSizeWithData(cgSize,
  163. NULL);
  164. NSGraphicsContext *bitmapContext
  165. = [NSGraphicsContext graphicsContextWithGraphicsPort:contextRef flipped:NO];
  166. _GTMDevAssert(bitmapContext, @"Couldn't create ns bitmap context");
  167. [NSGraphicsContext saveGraphicsState];
  168. [NSGraphicsContext setCurrentContext:bitmapContext];
  169. [self drawInRect:rect fromRect:rect operation:NSCompositeCopy fraction:1.0];
  170. CGImageRef image = CGBitmapContextCreateImage(contextRef);
  171. CFRelease(contextRef);
  172. [NSGraphicsContext restoreGraphicsState];
  173. return (CGImageRef)GTMCFAutorelease(image);
  174. }
  175. @end
  176. @implementation NSMenu (GTMUnitTestingAdditions)
  177. // Encodes the state of an object in a manner suitable for comparing
  178. // against a master state file so we can determine whether the
  179. // object is in a suitable state.
  180. //
  181. // Arguments:
  182. // inCoder - the coder to encode our state into
  183. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  184. [super gtm_unitTestEncodeState:inCoder];
  185. // Hack here to work around
  186. // rdar://5881796 Application menu item title wrong when accessed programatically
  187. // which causes us to have different results on x86_64 vs x386.
  188. // Hack is braced intentionally. We don't record the title of the
  189. // "application" menu or it's menu title because they are wrong on 32 bit.
  190. // They appear to work right on 64bit.
  191. {
  192. NSMenu *mainMenu = [NSApp mainMenu];
  193. NSMenu *appleMenu = [[mainMenu itemAtIndex:0] submenu];
  194. if (![self isEqual:appleMenu]) {
  195. [inCoder encodeObject:[self title] forKey:@"MenuTitle"];
  196. }
  197. }
  198. // Descend down into the menuitems allowing them to store their state
  199. NSMenuItem *menuItem = nil;
  200. int i = 0;
  201. GTM_FOREACH_OBJECT(menuItem, [self itemArray]) {
  202. [inCoder encodeObject:menuItem
  203. forKey:[NSString stringWithFormat:@"MenuItem %d", i]];
  204. ++i;
  205. }
  206. }
  207. @end
  208. @implementation NSMenuItem (GTMUnitTestingAdditions)
  209. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  210. [super gtm_unitTestEncodeState:inCoder];
  211. // Hack here to work around
  212. // rdar://5881796 Application menu item title wrong when accessed programatically
  213. // which causes us to have different results on x86_64 vs x386.
  214. // See comment above.
  215. {
  216. NSMenu *mainMenu = [NSApp mainMenu];
  217. NSMenuItem *appleMenuItem = [mainMenu itemAtIndex:0];
  218. if (![self isEqual:appleMenuItem]) {
  219. [inCoder encodeObject:[self title] forKey:@"MenuItemTitle"];
  220. }
  221. }
  222. [inCoder encodeObject:[self keyEquivalent] forKey:@"MenuItemKeyEquivalent"];
  223. [inCoder encodeBool:[self isSeparatorItem] forKey:@"MenuItemIsSeparator"];
  224. ENCODE_NSINTEGER(inCoder, [self state], @"MenuItemState");
  225. [inCoder encodeBool:[self isEnabled] forKey:@"MenuItemIsEnabled"];
  226. [inCoder encodeBool:[self isAlternate] forKey:@"MenuItemIsAlternate"];
  227. [inCoder encodeObject:[self toolTip] forKey:@"MenuItemTooltip"];
  228. ENCODE_NSINTEGER(inCoder, [self tag], @"MenuItemTag");
  229. ENCODE_NSINTEGER(inCoder, [self indentationLevel], @"MenuItemIndentationLevel");
  230. // Do our submenu if neccessary
  231. if ([self hasSubmenu]) {
  232. [inCoder encodeObject:[self submenu] forKey:@"MenuItemSubmenu"];
  233. }
  234. }
  235. @end
  236. @implementation NSTabView (GTMUnitTestingAdditions)
  237. // Encodes the state of an object in a manner suitable for comparing
  238. // against a master state file so we can determine whether the
  239. // object is in a suitable state.
  240. //
  241. // Arguments:
  242. // inCoder - the coder to encode our state into
  243. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  244. [super gtm_unitTestEncodeState:inCoder];
  245. NSTabViewItem *tab = nil;
  246. int i = 0;
  247. GTM_FOREACH_OBJECT(tab, [self tabViewItems]) {
  248. NSString *key = [NSString stringWithFormat:@"TabItem %d", i];
  249. [inCoder encodeObject:tab forKey:key];
  250. i = i + 1;
  251. }
  252. }
  253. @end
  254. @implementation NSTabViewItem (GTMUnitTestingAdditions)
  255. // Encodes the state of an object in a manner suitable for comparing
  256. // against a master state file so we can determine whether the
  257. // object is in a suitable state.
  258. //
  259. // Arguments:
  260. // inCoder - the coder to encode our state into
  261. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  262. [super gtm_unitTestEncodeState:inCoder];
  263. [inCoder encodeObject:[self label] forKey:@"TabLabel"];
  264. [inCoder encodeObject:[self view] forKey:@"TabView"];
  265. }
  266. @end
  267. @implementation NSToolbar (GTMUnitTestingAdditions)
  268. // Encodes the state of an object in a manner suitable for comparing
  269. // against a master state file so we can determine whether the
  270. // object is in a suitable state.
  271. //
  272. // Arguments:
  273. // inCoder - the coder to encode our state into
  274. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  275. [super gtm_unitTestEncodeState:inCoder];
  276. NSToolbarItem *item = nil;
  277. NSUInteger i = 0;
  278. GTM_FOREACH_OBJECT(item, [self items]) {
  279. NSString *key
  280. = [NSString stringWithFormat:@"ToolbarItem %lu", (unsigned long)i];
  281. [inCoder encodeObject:item forKey:key];
  282. i = i + 1;
  283. }
  284. }
  285. @end
  286. @implementation NSToolbarItem (GTMUnitTestingAdditions)
  287. // Encodes the state of an object in a manner suitable for comparing
  288. // against a master state file so we can determine whether the
  289. // object is in a suitable state.
  290. //
  291. // Arguments:
  292. // inCoder - the coder to encode our state into
  293. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  294. [super gtm_unitTestEncodeState:inCoder];
  295. [inCoder encodeObject:[self label] forKey:@"Label"];
  296. [inCoder encodeObject:[self paletteLabel] forKey:@"PaletteLabel"];
  297. [inCoder encodeObject:[self toolTip] forKey:@"ToolTip"];
  298. NSView *view = [self view];
  299. if (view) {
  300. [inCoder encodeObject:view forKey:@"View"];
  301. }
  302. }
  303. @end
  304. @implementation NSMatrix (GTMUnitTestingAdditions)
  305. // Encodes the state of an object in a manner suitable for comparing
  306. // against a master state file so we can determine whether the
  307. // object is in a suitable state.
  308. //
  309. // Arguments:
  310. // inCoder - the coder to encode our state into
  311. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  312. [super gtm_unitTestEncodeState:inCoder];
  313. ENCODE_NSINTEGER(inCoder, [self mode], @"MatrixMode");
  314. ENCODE_NSINTEGER(inCoder, [self numberOfRows], @"MatrixRowCount");
  315. ENCODE_NSINTEGER(inCoder, [self numberOfColumns], @"MatrixColumnCount");
  316. [inCoder encodeBool:[self allowsEmptySelection]
  317. forKey:@"MatrixAllowEmptySelection"];
  318. [inCoder encodeBool:[self isSelectionByRect] forKey:@"MatrixSelectionByRect"];
  319. [inCoder encodeBool:[self autosizesCells] forKey:@"MatrixAutosizesCells"];
  320. [inCoder encodeSize:[self intercellSpacing] forKey:@"MatrixIntercellSpacing"];
  321. [inCoder encodeObject:[self prototype] forKey:@"MatrixCellPrototype"];
  322. // Dump the list of cells
  323. NSCell *cell;
  324. long i = 0;
  325. GTM_FOREACH_OBJECT(cell, [self cells]) {
  326. [inCoder encodeObject:cell
  327. forKey:[NSString stringWithFormat:@"MatrixCell %ld", i]];
  328. ++i;
  329. }
  330. }
  331. @end
  332. @implementation NSBox (GTMUnitTestingAdditions)
  333. // Encodes the state of an object in a manner suitable for comparing
  334. // against a master state file so we can determine whether the
  335. // object is in a suitable state.
  336. //
  337. // Arguments:
  338. // inCoder - the coder to encode our state into
  339. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  340. [super gtm_unitTestEncodeState:inCoder];
  341. [inCoder encodeObject:[self title] forKey:@"BoxTitle"];
  342. ENCODE_NSINTEGER(inCoder, [self titlePosition], @"BoxTitlePosition");
  343. ENCODE_NSINTEGER(inCoder, [self boxType], @"BoxType");
  344. ENCODE_NSINTEGER(inCoder, [self borderType], @"BoxBorderType");
  345. // 10.5+ [inCoder encodeBool:[self isTransparent] forKey:@"BoxIsTransparent"];
  346. }
  347. @end
  348. @implementation NSSegmentedControl (GTMUnitTestingAdditions)
  349. // Encodes the state of an NSSegmentedControl and all its segments.
  350. //
  351. // Arguments:
  352. // inCoder - the coder to encode state into
  353. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  354. [super gtm_unitTestEncodeState:inCoder];
  355. NSInteger segmentCount = [self segmentCount];
  356. ENCODE_NSINTEGER(inCoder, segmentCount, @"SegmentCount");
  357. for (NSInteger i = 0; i < segmentCount; ++i) {
  358. NSString *key = [NSString stringWithFormat:@"Segment %ld", (long)i];
  359. [inCoder encodeObject:[self labelForSegment:i] forKey:key];
  360. }
  361. }
  362. @end
  363. @implementation NSComboBox (GTMUnitTestingAdditions)
  364. - (BOOL)gtm_shouldEncodeStateForSubviews {
  365. // Subclass of NSTextView, don't want subviews for the same reason.
  366. return NO;
  367. }
  368. // Encodes the state of an NSSegmentedControl and all its segments.
  369. //
  370. // Arguments:
  371. // inCoder - the coder to encode state into
  372. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  373. [super gtm_unitTestEncodeState:inCoder];
  374. NSInteger aCount = [self numberOfItems];
  375. ENCODE_NSINTEGER(inCoder, aCount, @"ComboBoxNumberOfItems");
  376. aCount = [self numberOfVisibleItems];
  377. ENCODE_NSINTEGER(inCoder, aCount, @"ComboBoxNumberOfVisibleItems");
  378. // Include the objectValues if it doesn't use a data source.
  379. if (![self usesDataSource]) {
  380. NSArray *objectValues = [self objectValues];
  381. for (NSUInteger i = 0; i < [objectValues count]; ++i) {
  382. id value = [objectValues objectAtIndex:i];
  383. if ([value isKindOfClass:[NSString class]]) {
  384. NSString *key = [NSString stringWithFormat:@"ComboBoxObjectValue %lu",
  385. (unsigned long)i];
  386. [inCoder encodeObject:value forKey:key];
  387. }
  388. }
  389. }
  390. }
  391. @end
  392. // A view that allows you to delegate out drawing using the formal
  393. // GTMUnitTestViewDelegate protocol above. This is useful when writing up unit
  394. // tests for visual elements.
  395. // Your test will often end up looking like this:
  396. // - (void)testFoo {
  397. // GTMAssertDrawingEqualToFile(self, NSMakeSize(200, 200), @"Foo", nil, nil);
  398. // }
  399. // and your testSuite will also implement the unitTestViewDrawRect method to do
  400. // it's actual drawing. The above creates a view of size 200x200 that draws
  401. // it's content using |self|'s unitTestViewDrawRect method and compares it to
  402. // the contents of the file Foo.tif to make sure it's valid
  403. @implementation GTMUnitTestView
  404. - (id)initWithFrame:(NSRect)frame
  405. drawer:(id<GTMUnitTestViewDrawer>)drawer
  406. contextInfo:(void*)contextInfo {
  407. self = [super initWithFrame:frame];
  408. if (self != nil) {
  409. drawer_ = [drawer retain];
  410. contextInfo_ = contextInfo;
  411. }
  412. return self;
  413. }
  414. - (void)dealloc {
  415. [drawer_ release];
  416. [super dealloc];
  417. }
  418. - (void)drawRect:(NSRect)rect {
  419. [drawer_ gtm_unitTestViewDrawRect:rect contextInfo:contextInfo_];
  420. }
  421. @end
  422. @implementation NSView (GTMUnitTestingAdditions)
  423. // Returns an image containing a representation of the object
  424. // suitable for use in comparing against a master image.
  425. // Does all of it's drawing with smoothfonts and antialiasing off
  426. // to avoid issues with font smoothing settings and antialias differences
  427. // between ppc and x86.
  428. //
  429. // Returns:
  430. // an image of the object
  431. - (CGImageRef)gtm_unitTestImage {
  432. // Create up a context
  433. NSRect bounds = [self bounds];
  434. CGSize cgSize = GTMNSSizeToCGSize(bounds.size);
  435. CGContextRef contextRef = GTMCreateUnitTestBitmapContextOfSizeWithData(cgSize,
  436. NULL);
  437. NSGraphicsContext *bitmapContext
  438. = [NSGraphicsContext graphicsContextWithGraphicsPort:contextRef flipped:NO];
  439. _GTMDevAssert(bitmapContext, @"Couldn't create ns bitmap context");
  440. // Save our state and turn off font smoothing and antialias.
  441. CGContextSaveGState(contextRef);
  442. CGContextSetShouldSmoothFonts(contextRef, false);
  443. CGContextSetShouldAntialias(contextRef, false);
  444. [self displayRectIgnoringOpacity:bounds inContext:bitmapContext];
  445. CGImageRef image = CGBitmapContextCreateImage(contextRef);
  446. CFRelease(contextRef);
  447. return (CGImageRef)GTMCFAutorelease(image);
  448. }
  449. // Returns whether gtm_unitTestEncodeState should recurse into subviews
  450. // of a particular view.
  451. // If you have "Full keyboard access" in the
  452. // Keyboard & Mouse > Keyboard Shortcuts preferences pane set to "Text boxes
  453. // and Lists only" that Apple adds a set of subviews to NSTextFields. So in the
  454. // case of NSTextFields we don't want to recurse into their subviews. There may
  455. // be other cases like this, so instead of specializing gtm_unitTestEncodeState: to
  456. // look for NSTextFields, NSTextFields will just not allow us to recurse into
  457. // their subviews.
  458. //
  459. // Returns:
  460. // should gtm_unitTestEncodeState pick up subview state.
  461. - (BOOL)gtm_shouldEncodeStateForSubviews {
  462. return YES;
  463. }
  464. // Encodes the state of an object in a manner suitable for comparing
  465. // against a master state file so we can determine whether the
  466. // object is in a suitable state.
  467. //
  468. // Arguments:
  469. // inCoder - the coder to encode our state into
  470. - (void)gtm_unitTestEncodeState:(NSCoder*)inCoder {
  471. [super gtm_unitTestEncodeState:inCoder];
  472. [inCoder encodeBool:[self isHidden] forKey:@"ViewIsHidden"];
  473. [inCoder encodeObject:[self toolTip] forKey:@"ViewToolTip"];
  474. NSArray *supportedAttrs = [self accessibilityAttributeNames];
  475. if ([supportedAttrs containsObject:NSAccessibilityHelpAttribute]) {
  476. NSString *help
  477. = [self accessibilityAttributeValue:NSAccessibilityHelpAttribute];
  478. [inCoder encodeObject:help forKey:@"ViewAccessibilityHelp"];
  479. }
  480. if ([supportedAttrs containsObject:NSAccessibilityDescriptionAttribute]) {
  481. NSString *description
  482. = [self accessibilityAttributeValue:NSAccessibilityDescriptionAttribute];
  483. [inCoder encodeObject:description forKey:@"ViewAccessibilityDescription"];
  484. }
  485. NSMenu *menu = [self menu];
  486. if (menu) {
  487. [inCoder encodeObject:menu forKey:@"ViewMenu"];
  488. }
  489. if ([self gtm_shouldEncodeStateForSubviews]) {
  490. NSView *subview = nil;
  491. int i = 0;
  492. GTM_FOREACH_OBJECT(subview, [self subviews]) {
  493. [inCoder encodeObject:subview forKey:[NSString stringWithFormat:@"ViewSubView %d", i]];
  494. i = i + 1;
  495. }
  496. }
  497. }
  498. @end