├── English.lproj └── InfoPlist.strings ├── Examples ├── ImageLab │ ├── Credits.rtf │ ├── English.lproj │ │ └── MainMenu.xib │ ├── ImageLab-Info.plist │ ├── ImageLabAppDelegate.h │ ├── ImageLabAppDelegate.m │ ├── Read Me.rtf │ ├── TimedImageView.h │ ├── TimedImageView.m │ ├── main.m │ └── test.tiff ├── LineNumberView │ ├── Controller.h │ ├── Controller.m │ ├── Credits.rtf │ ├── English.lproj │ │ ├── InfoPlist.strings │ │ └── MainMenu.xib │ ├── LineNumberView-Info.plist │ ├── MarkerLineNumberView.h │ ├── MarkerLineNumberView.m │ ├── Read Me.rtf │ └── main.m ├── ModalResponder │ ├── Controller.h │ ├── Controller.m │ ├── Credits.rtf │ ├── English.lproj │ │ ├── InfoPlist.strings │ │ └── MainMenu.xib │ ├── ModalResponder-Info.plist │ ├── Read Me.rtf │ └── main.m ├── StickyRowTableView Revue │ ├── Controller.h │ ├── Controller.m │ ├── Credits.rtf │ ├── English.lproj │ │ ├── InfoPlist.strings │ │ └── MainMenu.xib │ ├── Read Me.rtf │ ├── StickyRowTableView Revue-Info.plist │ └── main.m ├── TimerLab │ ├── Credits.rtf │ ├── English.lproj │ │ └── MainMenu.xib │ ├── Read Me.rtf │ ├── TimerLab-Info.plist │ ├── TimerLabAppDelegate.h │ ├── TimerLabAppDelegate.m │ └── main.m ├── Window Effects │ ├── Controller.h │ ├── Controller.m │ ├── English.lproj │ │ ├── InfoPlist.strings │ │ └── MainMenu.xib │ ├── Window Effects-Info.plist │ └── main.m └── iToonz │ ├── Controller.h │ ├── Controller.m │ ├── Credits.rtf │ ├── English.lproj │ └── MainMenu.xib │ ├── Read Me.rtf │ ├── iToonz-Info.plist │ └── main.m ├── Info.plist ├── NSImage-NoodleExtensions.h ├── NSImage-NoodleExtensions.m ├── NSIndexSet-NoodleExtensions.h ├── NSIndexSet-NoodleExtensions.m ├── NSObject-NoodlePerformWhenIdle.h ├── NSObject-NoodlePerformWhenIdle.m ├── NSResponder-NoodleModalExtensions.h ├── NSResponder-NoodleModalExtensions.m ├── NSTableView-NoodleExtensions.h ├── NSTableView-NoodleExtensions.m ├── NSTimer-NoodleExtensions.h ├── NSTimer-NoodleExtensions.m ├── NSWindow-NoodleEffects.h ├── NSWindow-NoodleEffects.m ├── NoodleCustomImageRep.h ├── NoodleCustomImageRep.m ├── NoodleGlue.h ├── NoodleGlue.m ├── NoodleIPhoneTableView.h ├── NoodleIPhoneTableView.m ├── NoodleKit.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── NoodleKit_Prefix.pch ├── NoodleLineNumberMarker.h ├── NoodleLineNumberMarker.m ├── NoodleLineNumberView.h ├── NoodleLineNumberView.m ├── NoodleTableView.h ├── NoodleTableView.m ├── README.md └── version.plist /English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Examples/ImageLab/Credits.rtf: -------------------------------------------------------------------------------- 1 | Read Me.rtf -------------------------------------------------------------------------------- /Examples/ImageLab/ImageLab-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.yourcompany.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | LSMinimumSystemVersion 22 | ${MACOSX_DEPLOYMENT_TARGET} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Examples/ImageLab/ImageLabAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLabAppDelegate.h 3 | // ImageLab 4 | // 5 | // Created by Paul Kim on 3/20/11. 6 | // Copyright 2011 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | @interface ImageLabAppDelegate : NSObject 32 | { 33 | IBOutlet NSImageView *imageView; 34 | IBOutlet NSTextField *timeLabel; 35 | IBOutlet NSButton *recacheIndicator; 36 | NSImage *testImage; 37 | NSWindow *window; 38 | } 39 | 40 | @property (assign) IBOutlet NSWindow *window; 41 | 42 | - (IBAction)switchImage:sender; 43 | - (IBAction)redraw:sender; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Examples/ImageLab/ImageLabAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // ImageLabAppDelegate.m 3 | // ImageLab 4 | // 5 | // Created by Paul Kim on 3/20/11. 6 | // Copyright 2011-2012 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import "ImageLabAppDelegate.h" 30 | #import "NoodleCustomImageRep.h" 31 | #import 32 | 33 | @implementation ImageLabAppDelegate 34 | 35 | @synthesize window; 36 | 37 | - (NSImage *)lockFocusImage 38 | { 39 | NSImage *image; 40 | NSSize size; 41 | CGFloat diameter; 42 | 43 | image = [[testImage copy] autorelease]; 44 | 45 | size = [image size]; 46 | diameter = size.width / 2.0; 47 | 48 | [image lockFocus]; 49 | 50 | [[NSColor blackColor] set]; 51 | [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(diameter / 2.0, diameter / 2.0, diameter, diameter)] fill]; 52 | 53 | [image unlockFocus]; 54 | 55 | return image; 56 | } 57 | 58 | - (NSImage *)customRepImage 59 | { 60 | NoodleCustomImageRep *rep; 61 | NSSize size; 62 | NSImage *image; 63 | 64 | size = [testImage size]; 65 | 66 | rep = [NoodleCustomImageRep imageRepWithDrawBlock: 67 | ^(NoodleCustomImageRep *blockRep) 68 | { 69 | NSSize repSize; 70 | CGFloat diameter; 71 | 72 | repSize = [blockRep size]; 73 | diameter = repSize.width / 2.0; 74 | 75 | [testImage drawInRect:NSMakeRect(0.0, 0.0, size.width, size.height) fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0]; 76 | 77 | [[NSColor blackColor] set]; 78 | [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(diameter / 2.0, diameter / 2.0, diameter, diameter)] fill]; 79 | 80 | [recacheIndicator setImage:[[[NSImage imageNamed:NSImageNameStatusAvailable] copy] autorelease]]; 81 | }]; 82 | [rep setSize:size]; 83 | image = [[[NSImage alloc] initWithSize:size] autorelease]; 84 | [image addRepresentation:rep]; 85 | 86 | return image; 87 | } 88 | 89 | - (NSImage *)lockFocusDrawnImage 90 | { 91 | NSImage *image; 92 | NSSize size; 93 | 94 | size = NSMakeSize(10.0, 10.0); 95 | 96 | image = [[[NSImage alloc] initWithSize:size] autorelease]; 97 | 98 | [image lockFocus]; 99 | 100 | [[NSGraphicsContext currentContext] setShouldAntialias:NO]; 101 | 102 | [[NSColor blueColor] set]; 103 | NSRectFill(NSMakeRect(1.0, 1.0, 8.0, 8.0)); 104 | 105 | [image unlockFocus]; 106 | return image; 107 | } 108 | 109 | - (NSImage *)customRepDrawnImage 110 | { 111 | NoodleCustomImageRep *rep; 112 | NSSize size; 113 | NSImage *image; 114 | 115 | size = NSMakeSize(10.0, 10.0); 116 | 117 | rep = [NoodleCustomImageRep imageRepWithDrawBlock: 118 | ^(NoodleCustomImageRep *blockRep) 119 | { 120 | [[NSColor blueColor] set]; 121 | NSRectFill(NSMakeRect(1.0, 1.0, 8.0, 8.0)); 122 | 123 | [recacheIndicator setImage:[[[NSImage imageNamed:NSImageNameStatusAvailable] copy] autorelease]]; 124 | }]; 125 | [rep setSize:size]; 126 | image = [[[NSImage alloc] initWithSize:size] autorelease]; 127 | [image addRepresentation:rep]; 128 | 129 | return image; 130 | } 131 | 132 | - (NSImage *)coreImageTIFFRep 133 | { 134 | NSImage *image; 135 | NSSize size; 136 | CIImage *input, *output; 137 | CIFilter *filter; 138 | NSCIImageRep *rep; 139 | CGRect extent; 140 | CGAffineTransform transform; 141 | 142 | size = [testImage size]; 143 | 144 | input = [CIImage imageWithData:[testImage TIFFRepresentation]]; 145 | filter = [CIFilter filterWithName:@"CIPointillize" keysAndValues: 146 | @"inputImage", input, 147 | @"inputRadius", [NSNumber numberWithFloat:(float)(size.width / 10.0)], 148 | @"inputCenter", [CIVector vectorWithX:size.width / 2.0 Y:size.height / 2.0], 149 | nil]; 150 | output = [filter valueForKey:@"outputImage"]; 151 | 152 | extent = [output extent]; 153 | transform = CGAffineTransformMakeScale(size.width / extent.size.width, size.height / extent.size.height); 154 | transform = CGAffineTransformTranslate(transform, -extent.origin.x, -extent.origin.y); 155 | output = [output imageByApplyingTransform:transform]; 156 | 157 | image = [[[NSImage alloc] initWithSize:size] autorelease]; 158 | rep = [NSCIImageRep imageRepWithCIImage:output]; 159 | [rep setSize:size]; 160 | [image addRepresentation:rep]; 161 | 162 | return image; 163 | } 164 | 165 | - (NSImage *)coreImageCustomImageRep 166 | { 167 | NoodleCustomImageRep *rep; 168 | NSSize size; 169 | NSImage *image; 170 | __block id label; 171 | 172 | label = timeLabel; 173 | 174 | size = [testImage size]; 175 | 176 | rep = [NoodleCustomImageRep imageRepWithDrawBlock: 177 | ^(NoodleCustomImageRep *blockRep) 178 | { 179 | CGImageRef cgImage; 180 | CIImage *input, *output; 181 | CIFilter *filter; 182 | NSRect rect; 183 | 184 | rect.origin = NSMakePoint(0.0, 0.0); 185 | rect.size = [blockRep size]; 186 | 187 | cgImage = [testImage CGImageForProposedRect:&rect 188 | context:[NSGraphicsContext currentContext] 189 | hints:nil]; 190 | input = [CIImage imageWithCGImage:cgImage]; 191 | filter = [CIFilter filterWithName:@"CIPointillize" keysAndValues: 192 | @"inputImage", input, 193 | @"inputRadius", [NSNumber numberWithFloat:(float)(NSWidth(rect) / 10.0)], 194 | @"inputCenter", [CIVector vectorWithX:NSWidth(rect) / 2.0 Y:NSHeight(rect) / 2.0], 195 | nil]; 196 | output = [filter valueForKey:@"outputImage"]; 197 | 198 | [output drawInRect:rect fromRect:NSRectFromCGRect([output extent]) operation:NSCompositeCopy fraction:1.0]; 199 | 200 | [recacheIndicator setImage:[[[NSImage imageNamed:NSImageNameStatusAvailable] copy] autorelease]]; 201 | }]; 202 | 203 | [rep setSize:size]; 204 | image = [[[NSImage alloc] initWithSize:size] autorelease]; 205 | [image addRepresentation:rep]; 206 | 207 | return image; 208 | } 209 | 210 | 211 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 212 | { 213 | testImage = [[NSImage imageNamed:@"test"] copy]; 214 | [recacheIndicator setImage:[[[NSImage imageNamed:NSImageNameStatusNone] copy] autorelease]]; 215 | } 216 | 217 | - (IBAction)switchImage:(id)sender 218 | { 219 | NSInteger tag; 220 | NSImage *image; 221 | 222 | tag = [sender selectedTag]; 223 | 224 | image = nil; 225 | switch (tag) 226 | { 227 | case 0: 228 | image = testImage; 229 | break; 230 | case 1: 231 | image = [self lockFocusImage]; 232 | break; 233 | case 2: 234 | image = [self customRepImage]; 235 | break; 236 | case 3: 237 | image = [self lockFocusDrawnImage]; 238 | break; 239 | case 4: 240 | image = [self customRepDrawnImage]; 241 | break; 242 | case 5: 243 | image = [self coreImageTIFFRep]; 244 | break; 245 | case 6: 246 | image = [self coreImageCustomImageRep]; 247 | break; 248 | } 249 | [imageView setObjectValue:image]; 250 | } 251 | 252 | - (IBAction)redraw:sender 253 | { 254 | [imageView display]; 255 | } 256 | 257 | @end 258 | -------------------------------------------------------------------------------- /Examples/ImageLab/Read Me.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf350 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Monaco;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1} 5 | {\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid101\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid2}} 6 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}} 7 | \margl1440\margr1440\vieww19360\viewh15600\viewkind0 8 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural 9 | 10 | \f0\b\fs24 \cf0 ImageLab 11 | \b0 \ 12 | \ 13 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 14 | \cf0 \ 15 | This is a sample program that is a companion piece to my blog article on NSImage here: {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/blog/2011/04/15/the-proper-care-and-feeding-of-nsimage"}}{\fldrslt http://www.noodlesoft.com/blog/2011/04/15/the-proper-care-and-feeding-of-nsimage}}\ 16 | \ 17 | This program shows different ways, both good and bad, of using NSImage. Please see the companion article as it goes into much more detail. This project also demonstrates use of NoodleCustomImageRep.\ 18 | \ 19 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 20 | 21 | \b \cf0 Notes 22 | \b0 \ 23 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 24 | \ls1\ilvl0\cf0 \ 25 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 26 | \cf0 \ 27 | \ 28 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 29 | 30 | \b \cf0 Possible Improvements\ 31 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 32 | 33 | \b0 \cf0 \ 34 | \ 35 | \ 36 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 37 | 38 | \b \cf0 Contact 39 | \b0 \ 40 | \ 41 | Just go {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/about.php"}}{\fldrslt www.noodlesoft.com}} and shoot me an email. Or visit the blog article linked above and leave a comment. Bugs, suggestions and other feedback appreciated.\ 42 | \ 43 | \ 44 | 45 | \b License 46 | \b0 \ 47 | \ 48 | I am releasing this under the MIT license.\ 49 | \ 50 | ____________________________________\ 51 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 52 | 53 | \f1 \cf0 Copyright (c) 2011 Noodlesoft, LLC. All Rights Reserved.\ 54 | \ 55 | Permission is hereby granted, free of charge, to any person\ 56 | obtaining a copy of this software and associated documentation\ 57 | files (the "Software"), to deal in the Software without\ 58 | restriction, including without limitation the rights to use,\ 59 | copy, modify, merge, publish, distribute, sublicense, and/or sell\ 60 | copies of the Software, and to permit persons to whom the\ 61 | Software is furnished to do so, subject to the following\ 62 | conditions:\ 63 | \ 64 | The above copyright notice and this permission notice shall be\ 65 | included in all copies or substantial portions of the Software.\ 66 | \ 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\ 68 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\ 69 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\ 70 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\ 71 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\ 72 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\ 73 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\ 74 | OTHER DEALINGS IN THE SOFTWARE.\ 75 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 76 | \ls2\ilvl0 77 | \f0\b \cf0 \ 78 | } -------------------------------------------------------------------------------- /Examples/ImageLab/TimedImageView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimedImageView.h 3 | // ImageLab 4 | // 5 | // Created by Paul Kim on 3/20/11. 6 | // Copyright 2011 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // 9 | // Permission is hereby granted, free of charge, to any person 10 | // obtaining a copy of this software and associated documentation 11 | // files (the "Software"), to deal in the Software without 12 | // restriction, including without limitation the rights to use, 13 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | // copies of the Software, and to permit persons to whom the 15 | // Software is furnished to do so, subject to the following 16 | // conditions: 17 | // 18 | // The above copyright notice and this permission notice shall be 19 | // included in all copies or substantial portions of the Software. 20 | // 21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 23 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 25 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 26 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 28 | // OTHER DEALINGS IN THE SOFTWARE. 29 | 30 | #import 31 | 32 | 33 | @interface TimedImageView : NSImageView 34 | { 35 | IBOutlet id label; 36 | IBOutlet id redrawIndicator; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Examples/ImageLab/TimedImageView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimedImageView.m 3 | // ImageLab 4 | // 5 | // Created by Paul Kim on 3/20/11. 6 | // Copyright 2011 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import "TimedImageView.h" 30 | 31 | 32 | @implementation TimedImageView 33 | 34 | - (void)updateLabel:(NSTimeInterval)time 35 | { 36 | [label setStringValue:[NSString stringWithFormat:@"%.2f ms", time]]; 37 | } 38 | 39 | - (void)drawRect:(NSRect)rect 40 | { 41 | NSTimeInterval time, diff; 42 | 43 | [redrawIndicator setImage:[[[NSImage imageNamed:NSImageNameStatusNone] copy] autorelease]]; 44 | 45 | time = [NSDate timeIntervalSinceReferenceDate]; 46 | 47 | [super drawRect:rect]; 48 | 49 | diff = [NSDate timeIntervalSinceReferenceDate] - time; 50 | 51 | [self updateLabel:diff * 1000]; 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /Examples/ImageLab/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ImageLab 4 | // 5 | // Created by Paul Kim on 3/20/11. 6 | // Copyright 2011 Noodlesoft, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Examples/ImageLab/test.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrNoodle/NoodleKit/b124f51a9e89778a4c9c59b900f4a7660dcfa650/Examples/ImageLab/test.tiff -------------------------------------------------------------------------------- /Examples/LineNumberView/Controller.h: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.h 3 | // Line View Test 4 | // 5 | // Created by Paul Kim on 9/28/08. 6 | // Copyright (c) 2008 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import 31 | 32 | @class NoodleLineNumberView; 33 | 34 | @interface Controller : NSObject 35 | { 36 | IBOutlet NSScrollView *scrollView; 37 | IBOutlet NSTextView *scriptView; 38 | NoodleLineNumberView *lineNumberView; 39 | } 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /Examples/LineNumberView/Controller.m: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.m 3 | // Line View Test 4 | // 5 | // Created by Paul Kim on 9/28/08. 6 | // Copyright (c) 2008 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import "Controller.h" 31 | #import "NoodleLineNumberView.h" 32 | #import "NoodleLineNumberMarker.h" 33 | #import "MarkerLineNumberView.h" 34 | 35 | @implementation Controller 36 | 37 | 38 | - (void)awakeFromNib 39 | { 40 | lineNumberView = [[MarkerLineNumberView alloc] initWithScrollView:scrollView]; 41 | [scrollView setVerticalRulerView:lineNumberView]; 42 | [scrollView setHasHorizontalRuler:NO]; 43 | [scrollView setHasVerticalRuler:YES]; 44 | [scrollView setRulersVisible:YES]; 45 | 46 | [scriptView setFont:[NSFont userFixedPitchFontOfSize:[NSFont smallSystemFontSize]]]; 47 | } 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Examples/LineNumberView/Credits.rtf: -------------------------------------------------------------------------------- 1 | Read Me.rtf -------------------------------------------------------------------------------- /Examples/LineNumberView/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrNoodle/NoodleKit/b124f51a9e89778a4c9c59b900f4a7660dcfa650/Examples/LineNumberView/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Examples/LineNumberView/LineNumberView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.noodlesoft.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | LSMinimumSystemVersion 22 | ${MACOSX_DEPLOYMENT_TARGET} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Examples/LineNumberView/MarkerLineNumberView.h: -------------------------------------------------------------------------------- 1 | // 2 | // MarkerTextView.h 3 | // Line View Test 4 | // 5 | // Created by Paul Kim on 10/4/08. 6 | // Copyright (c) 2008 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import 31 | #import "NoodleLineNumberView.h" 32 | 33 | @interface MarkerLineNumberView : NoodleLineNumberView 34 | { 35 | NSImage *markerImage; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Examples/LineNumberView/MarkerLineNumberView.m: -------------------------------------------------------------------------------- 1 | // 2 | // MarkerTextView.m 3 | // Line View Test 4 | // 5 | // Created by Paul Kim on 10/4/08. 6 | // Copyright (c) 2008 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import "MarkerLineNumberView.h" 31 | #import "NoodleLineNumberMarker.h" 32 | 33 | #define CORNER_RADIUS 3.0 34 | #define MARKER_HEIGHT 13.0 35 | 36 | @implementation MarkerLineNumberView 37 | 38 | - (void)dealloc 39 | { 40 | [markerImage release]; 41 | 42 | [super dealloc]; 43 | } 44 | 45 | - (void)setRuleThickness:(CGFloat)thickness 46 | { 47 | [super setRuleThickness:thickness]; 48 | 49 | // Overridden to reset the size of the marker image forcing it to redraw with the new width. 50 | // If doing this in a non-subclass of NoodleLineNumberView, you can set it to post frame 51 | // notifications and listen for them. 52 | [markerImage setSize:NSMakeSize(thickness, MARKER_HEIGHT)]; 53 | } 54 | 55 | - (void)drawMarkerImageIntoRep:(id)rep 56 | { 57 | NSBezierPath *path; 58 | NSRect rect; 59 | 60 | rect = NSMakeRect(1.0, 2.0, [rep size].width - 2.0, [rep size].height - 3.0); 61 | 62 | path = [NSBezierPath bezierPath]; 63 | [path moveToPoint:NSMakePoint(NSMaxX(rect), NSMinY(rect) + NSHeight(rect) / 2)]; 64 | [path lineToPoint:NSMakePoint(NSMaxX(rect) - 5.0, NSMaxY(rect))]; 65 | 66 | [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect) + CORNER_RADIUS, NSMaxY(rect) - CORNER_RADIUS) radius:CORNER_RADIUS startAngle:90 endAngle:180]; 67 | 68 | [path appendBezierPathWithArcWithCenter:NSMakePoint(NSMinX(rect) + CORNER_RADIUS, NSMinY(rect) + CORNER_RADIUS) radius:CORNER_RADIUS startAngle:180 endAngle:270]; 69 | [path lineToPoint:NSMakePoint(NSMaxX(rect) - 5.0, NSMinY(rect))]; 70 | [path closePath]; 71 | 72 | [[NSColor colorWithCalibratedRed:0.003 green:0.56 blue:0.85 alpha:1.0] set]; 73 | [path fill]; 74 | 75 | [[NSColor colorWithCalibratedRed:0 green:0.44 blue:0.8 alpha:1.0] set]; 76 | 77 | [path setLineWidth:2.0]; 78 | [path stroke]; 79 | } 80 | 81 | - (NSImage *)markerImageWithSize:(NSSize)size 82 | { 83 | if (markerImage == nil) 84 | { 85 | NSCustomImageRep *rep; 86 | 87 | markerImage = [[NSImage alloc] initWithSize:size]; 88 | rep = [[NSCustomImageRep alloc] initWithDrawSelector:@selector(drawMarkerImageIntoRep:) delegate:self]; 89 | [rep setSize:size]; 90 | [markerImage addRepresentation:rep]; 91 | [rep release]; 92 | } 93 | return markerImage; 94 | } 95 | 96 | - (void)mouseDown:(NSEvent *)theEvent 97 | { 98 | NSPoint location; 99 | NSUInteger line; 100 | 101 | location = [self convertPoint:[theEvent locationInWindow] fromView:nil]; 102 | line = [self lineNumberForLocation:location.y]; 103 | 104 | if (line != NSNotFound) 105 | { 106 | NoodleLineNumberMarker *marker; 107 | 108 | marker = [self markerAtLine:line]; 109 | 110 | if (marker != nil) 111 | { 112 | [self removeMarker:marker]; 113 | } 114 | else 115 | { 116 | marker = [[NoodleLineNumberMarker alloc] initWithRulerView:self 117 | lineNumber:line 118 | image:[self markerImageWithSize:NSMakeSize([self ruleThickness], MARKER_HEIGHT)] 119 | imageOrigin:NSMakePoint(0, MARKER_HEIGHT / 2)]; 120 | [self addMarker:marker]; 121 | [marker release]; 122 | } 123 | [self setNeedsDisplay:YES]; 124 | } 125 | } 126 | 127 | @end 128 | -------------------------------------------------------------------------------- /Examples/LineNumberView/Read Me.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf470 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Monaco;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1} 5 | {\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid101\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid2} 6 | {\list\listtemplateid3\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid201\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid3} 7 | {\list\listtemplateid4\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid301\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid4} 8 | {\list\listtemplateid5\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid401\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid5} 9 | {\list\listtemplateid6\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid501\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid6} 10 | {\list\listtemplateid7\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid601\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid7}} 11 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}{\listoverride\listid4\listoverridecount0\ls4}{\listoverride\listid5\listoverridecount0\ls5}{\listoverride\listid6\listoverridecount0\ls6}{\listoverride\listid7\listoverridecount0\ls7}} 12 | \vieww16040\viewh15820\viewkind0 13 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc 14 | 15 | \f0\b\fs24 \cf0 Line View Test 16 | \b0 \ 17 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 18 | \cf0 \ 19 | This is a sample project and test harness for NoodleLineNumberView.\ 20 | \ 21 | NoodleLineNumberView is an NSRulerView subclass that will show line numbers when the document view of the scrollview is an NSTextView. It numbers logical lines, not visual ones. NoodleLineNumberMarker works in tandem with NoodleLineNumberView to display markers at specific lines.\ 22 | \ 23 | A discussion of this project can be found at: {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/blog/2008/10/05/displaying-line-numbers-with-nstextview/"}}{\fldrslt http://www.noodlesoft.com/blog/2008/10/05/displaying-line-numbers-with-nstextview/}}\ 24 | \ 25 | Notes:\ 26 | \ 27 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural 28 | \ls1\ilvl0\cf0 {\listtext \'95 }The view will expand it's width to accommodate the widths of the labels as needed.\ 29 | {\listtext \'95 }The included subclass (MarkerLineNumberView) shows how to deal with markers. It also shows how to use an NSCustomImageRep to do the drawing. This allows you to reset the size of the image and have the drawing adjust as needed (this happens if the line number view changes width because the line numbers gained or lost a digit). If you decide to implement most of this stuff in an external class (not a subclass), you can set the line number view to post frame changed notifications and listen for them.\ 30 | {\listtext \'95 }Note that markers are tied to numerical lines, not semantic ones. So, if you have a marker at line 50 and insert a new line at line 49, the marker will not shift to line 51 to point at the same line of text but will stay at line 50 pointing at whatever text is there now. Contrast with XCode where the markers move with insertions and deletions of lines (at least as best as it can). This is logic that you'll have to supply yourself.\ 31 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 32 | \cf0 \ 33 | To integrate NoodleLineNumberView, just create one and set it as the vertical ruler of the scrollview. Depending on the order of operations, you may need to set the client view of the NoodleLineNumberView to the NSTextView manually.\ 34 | \ 35 | This project actually uses a subclass of NoodleLineNumberView called MarkerLineNumberView. This class shows how one can integrate adding markers. Just click in the line number view to toggle a marker. It's more of an example than a reusable class since your markers may look different and you may have a different UI for adding them. Nonetheless, it shows the basics of how to do it.\ 36 | \ 37 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 38 | 39 | \b \cf0 Possible Performance Improvements\ 40 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 41 | 42 | \b0 \cf0 \ 43 | For performance, NoodleLineView keeps an array of the character indices for the start of each line. This gets recalculated whenever the text changes but at least it's cached for redisplays (such as scrolling around). It also only redraw the labels for the lines that are showing.\ 44 | \ 45 | It seems peppy enough for me, testing on a Powerbook 12" (G4) on Leopard. If you feel the need to optimize it further (I advise you Shark it first instead of making assumptions, though), here are some areas that could be improved:\ 46 | \ 47 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural 48 | \ls2\ilvl0\cf0 {\listtext \'95 }\strike \strikec0 Right now, the character indices are recalculated every time the text changes. I only listen for a 49 | \f1 NSTextDidChangeNotification 50 | \f0 which is pretty coarse as there's no notion of what changed. You need to hook in deeper to get finer grained details of what characters were affected and see if the line indices need to be recalculated at all (check for whether a line ending was added or removed) or what particular lines should be recalculated (if a line was changed, only recalculate the lines after it).\strike0\striked0 (4/12/2012) This is now implemented. Checks the edited range and only recalculates the lines from that point onwards.\ 51 | {\listtext \'95 }The layout coordinates of each line can also be cached. If you tie into NSLayoutManager's delegate methods, you can find out when the layout has been invalidated. This would be helpful for cases where the view is scrolled or otherwise redisplayed without having the layout changed. Invalidations would happen in cases like when the view is resized in which case, you recalculate and recache. I have my doubts about whether this will save much but Shark it and find out.\ 52 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 53 | \cf0 \ 54 | The first two were not done because I wanted this subclass to be self-contained and not interfere with any delegates that may already be in place.\ 55 | \ 56 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 57 | 58 | \b \cf0 Contact 59 | \b0 \ 60 | \ 61 | Just go {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/about.php"}}{\fldrslt www.noodlesoft.com}} and shoot me an email. Or visit the blog article linked above and leave a comment. Bugs, suggestions and other feedback appreciated.\ 62 | \ 63 | \ 64 | 65 | \b License 66 | \b0 \ 67 | \ 68 | I am releasing this under the MIT license.\ 69 | \ 70 | ____________________________________\ 71 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 72 | 73 | \f1 \cf0 Copyright (c) 2008-2009 Noodlesoft, LLC. All Rights Reserved.\ 74 | \ 75 | Permission is hereby granted, free of charge, to any person\ 76 | obtaining a copy of this software and associated documentation\ 77 | files (the "Software"), to deal in the Software without\ 78 | restriction, including without limitation the rights to use,\ 79 | copy, modify, merge, publish, distribute, sublicense, and/or sell\ 80 | copies of the Software, and to permit persons to whom the\ 81 | Software is furnished to do so, subject to the following\ 82 | conditions:\ 83 | \ 84 | The above copyright notice and this permission notice shall be\ 85 | included in all copies or substantial portions of the Software.\ 86 | \ 87 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\ 88 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\ 89 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\ 90 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\ 91 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\ 92 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\ 93 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\ 94 | OTHER DEALINGS IN THE SOFTWARE.\ 95 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural 96 | \ls3\ilvl0 97 | \f0\b \cf0 \ 98 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 99 | \cf0 \ 100 | Changelog 101 | \b0 \ 102 | \ 103 | 0.4.1:\ 104 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural 105 | \ls4\ilvl0\cf0 {\listtext \'95 }Fixed display glitch when scrolling after the line number view resizes when linked against/running on 10.4. Apparently, NSRulerView's 106 | \f1 setRuleThickness: 107 | \f0 does not like non-integral values.\ 108 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 109 | \cf0 \ 110 | 0.4:\ 111 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural 112 | \ls5\ilvl0\cf0 {\listtext \'95 }Changed default font to use label font. Not sure if semantically, that's what XCode uses but it matches for the time being.\ 113 | {\listtext \'95 }Fixed display bugs introduced in 0.3 when line number view resizes itself.\ 114 | {\listtext \'95 }Added methods for setting various colors. The alternate text color is the color used by the line number label when a marker is drawn under it.\ 115 | {\listtext \'95 }Miscellaneous tweaks.\ 116 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 117 | \cf0 \ 118 | 0.3:\ 119 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural 120 | \ls6\ilvl0\cf0 {\listtext \'95 }Now listens for NSTextStorage's NSTextStorageDidProcessEditingNotification instead of NSTextDidChangeNotification. The former includes programmatic changes to the text.\ 121 | {\listtext \'95 }Was not taking text storage's inset into account when lining up labels. OSAScriptView, for one, had a non-zero inset. Should be fixed now.\ 122 | {\listtext \'95 }Calculates lines lazily allowing multiple programmatic text changes to be batched up.\ 123 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 124 | \cf0 \ 125 | 0.2:\ 126 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\pardirnatural 127 | \ls7\ilvl0\cf0 {\listtext \'95 }Initial public release\ 128 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural 129 | \cf0 \ 130 | } -------------------------------------------------------------------------------- /Examples/LineNumberView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Line View Test 4 | // 5 | // Created by Paul Kim on 10/5/08. 6 | // Copyright Noodlesoft, LLC 2008. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Examples/ModalResponder/Controller.h: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.h 3 | // ModalResponderTest 4 | // 5 | // Created by Paul Kim on 3/6/08. 6 | // Copyright 2008-2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface Controller : NSObject 13 | { 14 | IBOutlet id window; 15 | IBOutlet id alert; 16 | IBOutlet id field; 17 | } 18 | 19 | - (IBAction)doModal:sender; 20 | - (IBAction)doSheet:sender; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Examples/ModalResponder/Controller.m: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.m 3 | // ModalResponderTest 4 | // 5 | // Created by Paul Kim on 3/6/08. 6 | // Copyright 2008-2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | 9 | #import "Controller.h" 10 | 11 | 12 | @implementation Controller 13 | 14 | - (IBAction)doModal:sender 15 | { 16 | NSInteger returnCode; 17 | 18 | [field setStringValue:@""]; 19 | 20 | returnCode = [NSApp runModalForWindow:alert]; 21 | 22 | if (returnCode == NSOKButton) 23 | { 24 | [field setStringValue:@"OK!"]; 25 | } 26 | else 27 | { 28 | [field setStringValue:@"Cancel"]; 29 | } 30 | } 31 | 32 | 33 | - (IBAction)doSheet:sender 34 | { 35 | [field setStringValue:@""]; 36 | 37 | [NSApp beginSheet:alert modalForWindow:window modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nil]; 38 | //PENDING 39 | /* 40 | NSLog(@"ALERT: %@", alert); 41 | NSLog(@"PARENT: %@", [alert parentWindow]); 42 | NSLog(@"SHEET: %@", [[alert parentWindow] attachedSheet]); 43 | NSLog(@"WINDOW: %@", window); 44 | NSLog(@"CHILD: %@", [window childWindows]); 45 | NSLog(@"ALERT CHILD: %@", [alert childWindows]); 46 | NSLog(@"ATTACHED: %@", [window attachedSheet]); 47 | */ 48 | } 49 | 50 | - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 51 | { 52 | if (returnCode == NSOKButton) 53 | { 54 | [field setStringValue:@"OK!"]; 55 | } 56 | else 57 | { 58 | [field setStringValue:@"Cancel"]; 59 | } 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /Examples/ModalResponder/Credits.rtf: -------------------------------------------------------------------------------- 1 | Read Me.rtf -------------------------------------------------------------------------------- /Examples/ModalResponder/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrNoodle/NoodleKit/b124f51a9e89778a4c9c59b900f4a7660dcfa650/Examples/ModalResponder/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Examples/ModalResponder/ModalResponder-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.noodlesoft.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | LSMinimumSystemVersion 22 | ${MACOSX_DEPLOYMENT_TARGET} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Examples/ModalResponder/Read Me.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf110 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Monaco;\f2\fmodern\fcharset0 Courier; 3 | } 4 | {\colortbl;\red255\green255\blue255;} 5 | \margl1440\margr1440\vieww15820\viewh15260\viewkind0 6 | \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\ql\qnatural\pardirnatural 7 | 8 | \f0\b\fs24 \cf0 ModalResponder 9 | \b0 \ 10 | \ 11 | This is a simple demo of an NSResponder category which deals with modal windows and sheets. If you are creating a modal window or sheet that has "OK" and "Cancel" buttons, you most likely end up writing some glue method that just orders the window out and returns a code. This category makes it so that all you have to do is hook up the buttons to either the 12 | \f1\fs22 -confirmModal: 13 | \f0\fs24 or 14 | \f1\fs22 -cancelModal: 15 | \f0\fs24 actions on the First Responder in Interface Builder. The methods will set a return code of NSOKButton and NSCancelButton respectively. No extra glue though you may have to add the methods to NSResponder in IB.\ 16 | \ 17 | For more details check out the related blog post at http://www.noodlesoft.com/blog/2008/03/10/modal-glue/\ 18 | \ 19 | Enjoy.\ 20 | \ 21 | Paul Kim\ 22 | http://www.noodlesoft.com\ 23 | \ 24 | ========================================\ 25 | \ 26 | This code is provided for free under the terms of the MIT license:\ 27 | \ 28 | \pard\pardeftab720\sl360\sa180\ql\qnatural 29 | 30 | \f2 \cf0 Copyright (c) 2008-2009 Noodlesoft, LLC. All Rights Reserved\ 31 | Permission is hereby granted, free of charge, to any person obtaining a copy\uc0\u8232 of this software and associated documentation files (the "Software"), to deal\u8232 in the Software without restriction, including without limitation the rights\u8232 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\u8232 copies of the Software, and to permit persons to whom the Software is\u8232 furnished to do so, subject to the following conditions:\ 32 | The above copyright notice and this permission notice shall be included in\uc0\u8232 all copies or substantial portions of the Software.\ 33 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\uc0\u8232 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\u8232 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\u8232 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\u8232 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\u8232 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\u8232 THE SOFTWARE.\ 34 | } -------------------------------------------------------------------------------- /Examples/ModalResponder/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // ModalResponderTest 4 | // 5 | // Created by Paul Kim on 3/6/08. 6 | // Copyright __MyCompanyName__ 2008. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Examples/StickyRowTableView Revue/Controller.h: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.h 3 | // 4 | // Created by Paul Kim on 8/21/09. 5 | // Copyright 2009-2012 Noodlesoft, LLC. All rights reserved. 6 | // 7 | // Permission is hereby granted, free of charge, to any person 8 | // obtaining a copy of this software and associated documentation 9 | // files (the "Software"), to deal in the Software without 10 | // restriction, including without limitation the rights to use, 11 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the 13 | // Software is furnished to do so, subject to the following 14 | // conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be 17 | // included in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | // OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import 30 | 31 | @class NoodleTableView; 32 | 33 | @interface Controller : NSObject 34 | { 35 | IBOutlet NoodleTableView *_stickyRowTableView; 36 | IBOutlet NSTableView *_iPhoneTableView; 37 | NSMutableArray *_names; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /Examples/StickyRowTableView Revue/Controller.m: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.m 3 | // 4 | // Created by Paul Kim on 8/21/09. 5 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 6 | // 7 | // Permission is hereby granted, free of charge, to any person 8 | // obtaining a copy of this software and associated documentation 9 | // files (the "Software"), to deal in the Software without 10 | // restriction, including without limitation the rights to use, 11 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the 13 | // Software is furnished to do so, subject to the following 14 | // conditions: 15 | // 16 | // The above copyright notice and this permission notice shall be 17 | // included in all copies or substantial portions of the Software. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | // OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | #import "Controller.h" 30 | #import "NoodleTableView.h" 31 | 32 | @implementation Controller 33 | 34 | - (void)awakeFromNib 35 | { 36 | NSString *fileContents; 37 | NSUInteger i, count; 38 | NSString *temp, *prefix, *currentPrefix; 39 | NSArray *words; 40 | 41 | fileContents = [NSString stringWithContentsOfFile:@"/usr/share/dict/propernames" usedEncoding:NULL error:NULL]; 42 | words = [fileContents componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; 43 | 44 | [self willChangeValueForKey:@"names"]; 45 | 46 | words = [words sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]; 47 | _names = [[NSMutableArray alloc] init]; 48 | 49 | count = [words count]; 50 | currentPrefix = nil; 51 | for (i = 0; i < count; i++) 52 | { 53 | temp = [words objectAtIndex:i]; 54 | 55 | if ([temp length] > 0) 56 | { 57 | prefix = [temp substringToIndex:1]; 58 | 59 | if ((currentPrefix == nil) || 60 | ([currentPrefix caseInsensitiveCompare:prefix] != NSOrderedSame)) 61 | { 62 | currentPrefix = [prefix uppercaseString]; 63 | [_names addObject:currentPrefix]; 64 | } 65 | [_names addObject:temp]; 66 | } 67 | } 68 | 69 | [_stickyRowTableView setShowsStickyRowHeader:YES]; 70 | [_stickyRowTableView reloadData]; 71 | [_iPhoneTableView reloadData]; 72 | } 73 | 74 | - (BOOL)_isHeader:(NSInteger)rowIndex 75 | { 76 | return ((rowIndex == 0) || 77 | [[[_names objectAtIndex:rowIndex] substringToIndex:1] caseInsensitiveCompare:[[_names objectAtIndex:rowIndex - 1] substringToIndex:1]] != NSOrderedSame); 78 | } 79 | 80 | #pragma mark NSTableDataSource methods 81 | 82 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView 83 | { 84 | return [_names count]; 85 | } 86 | 87 | - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex 88 | { 89 | return [_names objectAtIndex:rowIndex]; 90 | } 91 | 92 | - (BOOL)tableView:(NSTableView *)tableView isGroupRow:(NSInteger)row 93 | { 94 | return [self _isHeader:row]; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /Examples/StickyRowTableView Revue/Credits.rtf: -------------------------------------------------------------------------------- 1 | Read Me.rtf -------------------------------------------------------------------------------- /Examples/StickyRowTableView Revue/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrNoodle/NoodleKit/b124f51a9e89778a4c9c59b900f4a7660dcfa650/Examples/StickyRowTableView Revue/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Examples/StickyRowTableView Revue/Read Me.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf110 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Monaco;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1} 5 | {\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid101\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid2} 6 | {\list\listtemplateid3\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid201\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid3} 7 | {\list\listtemplateid4\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid301\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid4} 8 | {\list\listtemplateid5\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid401\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid5}} 9 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}{\listoverride\listid4\listoverridecount0\ls4}{\listoverride\listid5\listoverridecount0\ls5}} 10 | \margl1440\margr1440\vieww19360\viewh17460\viewkind0 11 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural 12 | 13 | \f0\b\fs24 \cf0 NoodleStickyRowTableView Test 14 | \b0 \ 15 | version 0.18369\ 16 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 17 | \cf0 \ 18 | This is a sample project and test harness for the sticky row header feature of NoodleTableView.\ 19 | \ 20 | NoodleTableView is an NSTableView subclass where group rows (this can be overridden) will "stick" to the top of the view when they scroll off. This is like how section headers will stick to the top in UITableViews on the iPhone.\ 21 | \ 22 | Almost all of it is accomplished via an NSTableView category. The reason it was done this way was so that (a) you could integrate it into your own NSTableView subclass easily and (b) you could gain the functionality in NSOutlineView and its subclasses as well. To get the basic functionality, all you need to do is subclass 23 | \f1 -drawRect: 24 | \f0 and call 25 | \f1 -drawStickyRowHeader 26 | \f0 after your call to 27 | \f1 super 28 | \f0 . Look at the NoodleTableView class for details.\ 29 | \ 30 | This project also includes NoodleIPhoneTableView which overrides a bit more to simulate the look and feel of UITableView.\ 31 | \ 32 | A blog post on this can be found at: {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/blog/2009/09/25/sticky-section-headers-in-nstableview"}}{\fldrslt http://www.noodlesoft.com/blog/2009/09/25/sticky-section-headers-in-nstableview}}\ 33 | \ 34 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 35 | 36 | \b \cf0 Notes 37 | \b0 \ 38 | \ 39 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 40 | \ls1\ilvl0\cf0 {\listtext \'95 }As noted in the article linked above, limitations in being able to get a visual cache of a group row forced me to compromise and use a custom look for the sticky rows in the default implementation. As shown in the NoodleIPhoneTableView class, you can override all the drawing but it requires a bit more work.\ 41 | {\listtext \'95 }The sticky row header is implemented using an NSButton. The row is drawn into an image that is set on the button. The button is adjusted as needed, including whether it is enabled and therefore able to accept mouse clicks.\ 42 | {\listtext \'95 }To keep things in a category, the auxiliary button is put in the view hierarchy. It is tagged so that it can be retrieved later. This is further exploited where, for optimization purposes, the alternate image property of NSButton, which would otherwise go unused, is used as a cache.\ 43 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 44 | \cf0 \ 45 | \ 46 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 47 | 48 | \b \cf0 Possible Improvements\ 49 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 50 | 51 | \b0 \cf0 \ 52 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 53 | \ls2\ilvl0\cf0 {\listtext \'95 }You can keep track of the last sticky row that was cached and reuse its image if it hasn't changed. I had actually implemented this but the performance gains were actually very minimal. For purposes of clarity, I took the code out but you can implement this if you need to eek out the last bit of performance.\ 54 | {\listtext \'95 }The fade-in transition is position based (it fades in as you scroll down). It might be better to make it time-based.\ 55 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 56 | \cf0 \ 57 | \ 58 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 59 | 60 | \b \cf0 Contact 61 | \b0 \ 62 | \ 63 | Just go {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/about.php"}}{\fldrslt www.noodlesoft.com}} and shoot me an email. Or visit the blog article linked above and leave a comment. Bugs, suggestions and other feedback appreciated.\ 64 | \ 65 | \ 66 | 67 | \b License 68 | \b0 \ 69 | \ 70 | I am releasing this under the MIT license.\ 71 | \ 72 | ____________________________________\ 73 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 74 | 75 | \f1 \cf0 Copyright (c) 2009 Noodlesoft, LLC. All Rights Reserved.\ 76 | \ 77 | Permission is hereby granted, free of charge, to any person\ 78 | obtaining a copy of this software and associated documentation\ 79 | files (the "Software"), to deal in the Software without\ 80 | restriction, including without limitation the rights to use,\ 81 | copy, modify, merge, publish, distribute, sublicense, and/or sell\ 82 | copies of the Software, and to permit persons to whom the\ 83 | Software is furnished to do so, subject to the following\ 84 | conditions:\ 85 | \ 86 | The above copyright notice and this permission notice shall be\ 87 | included in all copies or substantial portions of the Software.\ 88 | \ 89 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\ 90 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\ 91 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\ 92 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\ 93 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\ 94 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\ 95 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\ 96 | OTHER DEALINGS IN THE SOFTWARE.\ 97 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 98 | \ls3\ilvl0 99 | \f0\b \cf0 \ 100 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 101 | \cf0 \ 102 | Changelog 103 | \b0 \ 104 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 105 | \ls4\ilvl0\cf0 \ 106 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 107 | \cf0 \ 108 | 0.18369:\ 109 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 110 | \ls5\ilvl0\cf0 {\listtext \'95 }Initial public release\ 111 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 112 | \cf0 \ 113 | } -------------------------------------------------------------------------------- /Examples/StickyRowTableView Revue/StickyRowTableView Revue-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.noodlesoft.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | LSMinimumSystemVersion 22 | ${MACOSX_DEPLOYMENT_TARGET} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Examples/StickyRowTableView Revue/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // NoodleStickyRowTableViewTest 4 | // 5 | // Created by Paul Kim on 8/23/09. 6 | // Copyright Noodlesoft, LLC 2009. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Examples/TimerLab/Credits.rtf: -------------------------------------------------------------------------------- 1 | Read Me.rtf -------------------------------------------------------------------------------- /Examples/TimerLab/Read Me.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf320 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Monaco;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1} 5 | {\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid101\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid2} 6 | {\list\listtemplateid3\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid201\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid3}} 7 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}} 8 | \margl1440\margr1440\vieww19360\viewh17460\viewkind0 9 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural 10 | 11 | \f0\b\fs24 \cf0 TimerLab 12 | \b0 \ 13 | \ 14 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 15 | \cf0 \ 16 | This is a sample project and test harness for the my NSTimer category.\ 17 | \ 18 | Normally, if you create an NSTimer and time is suspended (putting the machine to sleep) or changed (setting the system clock), NSTimer will try and compensate. In a sense, the time given to NSTimer is relative. The problem with this is that sometimes when you tell a timer to fire on a specific date at a specific time, you want it to fire on that specific date at that specific time. Thus, this category was created.\ 19 | \ 20 | The basic approach is to store the original fire date of the timer and re-set it whenever the machine wakes or the system clock is changed. Since at least one new instance variable is needed, it makes sense to do an NSTimer subclass. Unfortunately, NSRunLoop doesn't seem to work with anything but NSTimer directly. I suspect it's calling some private methods on NSTimer that I'm not privy to.\ 21 | \ 22 | So instead, it's done as a category. By using Snow Leopard's "associative references" ({\field{\*\fldinst{HYPERLINK "http://developer.apple.com/mac/library/documentation/cocoa/conceptual/objectivec/articles/ocAssociativeReferences.html"}}{\fldrslt http://developer.apple.com/mac/library/documentation/cocoa/conceptual/objectivec/articles/ocAssociativeReferences.html}}), I can add the equivalent of instance variables to NSTimer. And since, I was mucking with NSTimer, I also went ahead and added support for blocks.\ 23 | \ 24 | The test program allows you to test the regular behavior and the newer "absolute" behavior side by side. Set a time and click "Start timer". Now, put your machine to sleep or mess with the system clock. You'll see the regular timer adjust while the "absolute" timer will stay put.\ 25 | \ 26 | A blog post on this can be found at: {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/blog/2010/07/01/playing-with-nstimer/"}}{\fldrslt http://www.noodlesoft.com/blog/2010/07/01/playing-with-nstimer/}}\ 27 | \ 28 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 29 | 30 | \b \cf0 Notes 31 | \b0 \ 32 | \ 33 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 34 | \ls1\ilvl0\cf0 {\listtext \'95 }Because of technical limitations of being a category, once you create an "absolute" timer, calling -setFireDate: may not work like how you'd want. If you need to change the fire date, create a new timer.\ 35 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 36 | \cf0 \ 37 | \ 38 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 39 | 40 | \b \cf0 Possible Improvements\ 41 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 42 | 43 | \b0 \cf0 \ 44 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 45 | \ls2\ilvl0\cf0 {\listtext \'95 }Add support for repeating timers.\ 46 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 47 | \cf0 \ 48 | \ 49 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 50 | 51 | \b \cf0 Contact 52 | \b0 \ 53 | \ 54 | Just go {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/about.php"}}{\fldrslt www.noodlesoft.com}} and shoot me an email. Or visit the blog article linked above and leave a comment. Bugs, suggestions and other feedback appreciated.\ 55 | \ 56 | \ 57 | 58 | \b License 59 | \b0 \ 60 | \ 61 | I am releasing this under the MIT license.\ 62 | \ 63 | ____________________________________\ 64 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 65 | 66 | \f1 \cf0 Copyright (c) 2010 Noodlesoft, LLC. All Rights Reserved.\ 67 | \ 68 | Permission is hereby granted, free of charge, to any person\ 69 | obtaining a copy of this software and associated documentation\ 70 | files (the "Software"), to deal in the Software without\ 71 | restriction, including without limitation the rights to use,\ 72 | copy, modify, merge, publish, distribute, sublicense, and/or sell\ 73 | copies of the Software, and to permit persons to whom the\ 74 | Software is furnished to do so, subject to the following\ 75 | conditions:\ 76 | \ 77 | The above copyright notice and this permission notice shall be\ 78 | included in all copies or substantial portions of the Software.\ 79 | \ 80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\ 81 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\ 82 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\ 83 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\ 84 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\ 85 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\ 86 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\ 87 | OTHER DEALINGS IN THE SOFTWARE.\ 88 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 89 | \ls3\ilvl0 90 | \f0\b \cf0 \ 91 | } -------------------------------------------------------------------------------- /Examples/TimerLab/TimerLab-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.yourcompany.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | LSMinimumSystemVersion 22 | ${MACOSX_DEPLOYMENT_TARGET} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Examples/TimerLab/TimerLabAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // TimeLabAppDelegate.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 6/30/10. 6 | // Copyright 2010 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | @interface TimerLabAppDelegate : NSObject 32 | { 33 | IBOutlet NSWindow *_window; 34 | IBOutlet NSDatePicker *_picker; 35 | IBOutlet NSDatePicker *_datePicker; 36 | IBOutlet NSDatePicker *_timePicker; 37 | IBOutlet NSTextField *_initialDateField; 38 | IBOutlet NSTextField *_initialFireDateField; 39 | IBOutlet NSTextField *_currentFireDateField; 40 | IBOutlet NSTextField *_fireDateField; 41 | 42 | IBOutlet NSTextField *_regularInitialDateField; 43 | IBOutlet NSTextField *_regularInitialFireDateField; 44 | IBOutlet NSTextField *_regularCurrentFireDateField; 45 | IBOutlet NSTextField *_regularFireDateField; 46 | 47 | 48 | NSTimer *_absoluteTimer; 49 | NSTimer *_regularTimer; 50 | } 51 | 52 | @property (assign) IBOutlet NSWindow *window; 53 | 54 | - (IBAction)startTimer:(id)sender; 55 | 56 | - (IBAction)syncPickers:(id)sender; 57 | 58 | - (IBAction)refresh:(id)sender; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /Examples/TimerLab/TimerLabAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // TimeLabAppDelegate.m 3 | // TimerLab 4 | // 5 | // Created by Paul Kim on 6/30/10. 6 | // Copyright 2010 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import "TimerLabAppDelegate.h" 30 | #import 31 | 32 | @implementation TimerLabAppDelegate 33 | 34 | @synthesize window = _window; 35 | 36 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 37 | { 38 | [_picker setObjectValue:[NSDate date]]; 39 | [self syncPickers:_picker]; 40 | 41 | // We use -delayedRefresh: here instead of refresh: because NSTimer will be watching for these same notifications 42 | // and we can't guarantee the order in which the observers are notified (we want the refresh to happen after 43 | // the timer adjusts). 44 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(delayedRefresh:) name:NSSystemTimeZoneDidChangeNotification object:nil]; 45 | [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(delayedRefresh:) name:NSWorkspaceDidWakeNotification object:nil]; 46 | } 47 | 48 | 49 | - (IBAction)startTimer:(id)sender 50 | { 51 | NSDate *date; 52 | 53 | date = [NSDate date]; 54 | [_initialDateField setObjectValue:date]; 55 | [_regularInitialDateField setObjectValue:date]; 56 | 57 | date = [_picker objectValue]; 58 | 59 | [_initialFireDateField setObjectValue:date]; 60 | [_regularInitialFireDateField setObjectValue:date]; 61 | 62 | [_absoluteTimer invalidate]; 63 | [_absoluteTimer release]; 64 | _absoluteTimer = [[NSTimer alloc] initWithAbsoluteFireDate:date block: 65 | ^ (NSTimer *timer) 66 | { 67 | [_fireDateField setObjectValue:[NSDate date]]; 68 | }]; 69 | 70 | [_fireDateField setObjectValue:nil]; 71 | [_currentFireDateField setObjectValue:[_absoluteTimer fireDate]]; 72 | 73 | [[NSRunLoop currentRunLoop] addTimer:_absoluteTimer forMode:NSDefaultRunLoopMode]; 74 | 75 | [_regularTimer invalidate]; 76 | [_regularTimer release]; 77 | _regularTimer = [[NSTimer alloc] initWithFireDate:date interval:0 repeats:NO block: 78 | ^ (NSTimer *timer) 79 | { 80 | [_regularFireDateField setObjectValue:[NSDate date]]; 81 | }]; 82 | 83 | [_regularFireDateField setObjectValue:nil]; 84 | [_regularCurrentFireDateField setObjectValue:[_regularTimer fireDate]]; 85 | 86 | [[NSRunLoop currentRunLoop] addTimer:_regularTimer forMode:NSDefaultRunLoopMode]; 87 | } 88 | 89 | - (IBAction)syncPickers:(id)sender 90 | { 91 | NSDate *date; 92 | 93 | date = [sender objectValue]; 94 | [_picker setObjectValue:date]; 95 | [_datePicker setObjectValue:date]; 96 | [_timePicker setObjectValue:date]; 97 | } 98 | 99 | - (IBAction)refresh:(id)sender 100 | { 101 | [_currentFireDateField setObjectValue:[_absoluteTimer fireDate]]; 102 | [_regularCurrentFireDateField setObjectValue:[_regularTimer fireDate]]; 103 | } 104 | 105 | - (IBAction)delayedRefresh:(id)sender 106 | { 107 | [self performSelector:@selector(refresh:) withObject:nil afterDelay:1.0]; 108 | } 109 | 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Examples/TimerLab/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TimerLab 4 | // 5 | // Created by Paul Kim on 6/30/10. 6 | // Copyright 2010 Noodlesoft, LLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Examples/Window Effects/Controller.h: -------------------------------------------------------------------------------- 1 | /* Controller */ 2 | 3 | #import 4 | 5 | @interface Controller : NSObject 6 | { 7 | IBOutlet id _window; 8 | } 9 | 10 | - (IBAction)toggleWindow:sender; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Examples/Window Effects/Controller.m: -------------------------------------------------------------------------------- 1 | #import "Controller.h" 2 | #import "NSWindow-NoodleEffects.h" 3 | 4 | @implementation Controller 5 | 6 | - (IBAction)toggleWindow:sender 7 | { 8 | NSRect rect; 9 | 10 | rect = [sender convertRect:[sender bounds] toView:nil]; 11 | rect.origin = [[sender window] convertBaseToScreen:rect.origin]; 12 | 13 | if ([_window isVisible]) 14 | { 15 | [_window zoomOffToRect:rect]; 16 | } 17 | else 18 | { 19 | [_window zoomOnFromRect:rect]; 20 | } 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Examples/Window Effects/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrNoodle/NoodleKit/b124f51a9e89778a4c9c59b900f4a7660dcfa650/Examples/Window Effects/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /Examples/Window Effects/Window Effects-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.noodlesoft.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | LSMinimumSystemVersion 22 | ${MACOSX_DEPLOYMENT_TARGET} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Examples/Window Effects/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WindowZoom 4 | // 5 | // Created by Paul Kim on 6/18/07. 6 | // Copyright __MyCompanyName__ 2007. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Examples/iToonz/Controller.h: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 10/21/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | @class NoodleTableView; 32 | 33 | @interface Controller : NSObject 34 | { 35 | IBOutlet NoodleTableView *_tableView; 36 | NSArray *_entries; 37 | id _number; 38 | } 39 | 40 | @property (readonly) NSArray *entries; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Examples/iToonz/Controller.m: -------------------------------------------------------------------------------- 1 | // 2 | // Controller.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 10/21/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Created by Paul Kim on 10/20/09. 9 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 10 | // 11 | // Permission is hereby granted, free of charge, to any person 12 | // obtaining a copy of this software and associated documentation 13 | // files (the "Software"), to deal in the Software without 14 | // restriction, including without limitation the rights to use, 15 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | // copies of the Software, and to permit persons to whom the 17 | // Software is furnished to do so, subject to the following 18 | // conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be 21 | // included in all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 25 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 27 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 | // OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | #import "Controller.h" 33 | #import "NoodleTableView.h" 34 | 35 | #define ARTIST_KEY @"artist" 36 | #define ARTWORK_KEY @"artwork" 37 | #define ALBUM_KEY @"album" 38 | #define SONGCOUNT_KEY @"songCount" 39 | 40 | @implementation Controller 41 | 42 | 43 | //@synthesize window; 44 | @synthesize entries = _entries; 45 | 46 | - (void)awakeFromNib 47 | { 48 | [_tableView setIntercellSpacing:NSMakeSize(0.0, 0.0)]; 49 | [_tableView setShowsStickyRowHeader:YES]; 50 | [_tableView setRowSpanningEnabledForCapableColumns:YES]; 51 | _number = [NSNumber numberWithInt:5]; 52 | } 53 | 54 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 55 | { 56 | _entries = [[NSArray alloc] initWithObjects: 57 | [NSDictionary dictionaryWithObjectsAndKeys: 58 | @"Mr Disco", ARTIST_KEY, 59 | [NSImage imageNamed:NSImageNameFolderBurnable], ARTWORK_KEY, 60 | @"Burn Baby Burn", ALBUM_KEY, 61 | [NSNumber numberWithInteger:9], SONGCOUNT_KEY, nil], 62 | [NSDictionary dictionaryWithObjectsAndKeys: 63 | @"Pierre LeMac", ARTIST_KEY, 64 | [NSImage imageNamed:NSImageNameBonjour], ARTWORK_KEY, 65 | @"Bonjour Ma Cherie", ALBUM_KEY, 66 | [NSNumber numberWithInteger:13], SONGCOUNT_KEY, nil], 67 | [NSDictionary dictionaryWithObjectsAndKeys: 68 | @"M.C. Mac", ARTIST_KEY, 69 | [NSImage imageNamed:NSImageNameDotMac], ARTWORK_KEY, 70 | @"Dot Mackin'", ALBUM_KEY, 71 | [NSNumber numberWithInteger:7], SONGCOUNT_KEY, nil], 72 | [NSDictionary dictionaryWithObjectsAndKeys: 73 | @"M.C. Mac", ARTIST_KEY, 74 | [NSImage imageNamed:NSImageNameFolderSmart], ARTWORK_KEY, 75 | @"You Think You're So Smart", ALBUM_KEY, 76 | [NSNumber numberWithInteger:14], SONGCOUNT_KEY, nil], 77 | [NSDictionary dictionaryWithObjectsAndKeys: 78 | @"ComputerHead", ARTIST_KEY, 79 | [NSImage imageNamed:NSImageNameComputer], ARTWORK_KEY, 80 | @"Cancel Computer", ALBUM_KEY, 81 | [NSNumber numberWithInteger:12], SONGCOUNT_KEY, nil], 82 | nil]; 83 | 84 | [(NoodleTableColumn *)[_tableView tableColumnWithIdentifier:@"Album"] setRowSpanningEnabled:NO]; 85 | [_tableView reloadData]; 86 | } 87 | 88 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView 89 | { 90 | return [[_entries valueForKeyPath:@"@sum.songCount"] integerValue]; 91 | } 92 | 93 | - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex 94 | { 95 | NSInteger tally, songCount; 96 | id identifier; 97 | 98 | tally = 0; 99 | identifier = [aTableColumn identifier]; 100 | for (NSDictionary *dict in _entries) 101 | { 102 | songCount = [[dict objectForKey:SONGCOUNT_KEY] integerValue]; 103 | 104 | if (rowIndex < tally + songCount) 105 | { 106 | if ([identifier isEqual:@"Artwork"]) 107 | { 108 | return [dict objectForKey:ARTWORK_KEY]; 109 | } 110 | else if ([identifier isEqual:@"Album"]) 111 | { 112 | return [dict objectForKey:ALBUM_KEY]; 113 | } 114 | else if ([identifier isEqual:@"Artist"]) 115 | { 116 | return [dict objectForKey:ARTIST_KEY]; 117 | } 118 | else if ([identifier isEqual:@"Song"]) 119 | { 120 | return [NSString stringWithFormat:@"Song #%d", rowIndex - tally + 1]; 121 | } 122 | } 123 | tally += songCount; 124 | } 125 | return nil; 126 | } 127 | 128 | - (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn 129 | { 130 | if ([[tableColumn identifier] isEqual:@"Album"]) 131 | { 132 | [(NoodleTableColumn *)tableColumn setRowSpanningEnabled:![(NoodleTableColumn *)tableColumn isRowSpanningEnabled]]; 133 | [_tableView reloadData]; 134 | } 135 | } 136 | 137 | - (BOOL)tableView:(NSTableView *)tableView isStickyRow:(NSInteger)row 138 | { 139 | id value, newValue; 140 | NSTableColumn *column; 141 | 142 | column = [tableView tableColumnWithIdentifier:@"Artist"]; 143 | value = [self tableView:tableView objectValueForTableColumn:column row:row]; 144 | 145 | if (row > 0) 146 | { 147 | newValue = [self tableView:tableView objectValueForTableColumn:column row:row - 1]; 148 | if (![value isEqual:newValue]) 149 | { 150 | return YES; 151 | } 152 | return NO; 153 | } 154 | return YES; 155 | } 156 | 157 | - (BOOL)tableView:(NSTableView *)tableView shouldDisplayCellInStickyRowHeaderForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex 158 | { 159 | return [[tableColumn identifier] isEqual:@"Artist"]; 160 | } 161 | 162 | @end 163 | -------------------------------------------------------------------------------- /Examples/iToonz/Credits.rtf: -------------------------------------------------------------------------------- 1 | Read Me.rtf -------------------------------------------------------------------------------- /Examples/iToonz/Read Me.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf110 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;\f1\fnil\fcharset0 Monaco;} 3 | {\colortbl;\red255\green255\blue255;} 4 | {\*\listtable{\list\listtemplateid1\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid1\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid1} 5 | {\list\listtemplateid2\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid101\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid2} 6 | {\list\listtemplateid3\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid201\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid3} 7 | {\list\listtemplateid4\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid301\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid4} 8 | {\list\listtemplateid5\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid401\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid5} 9 | {\list\listtemplateid6\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid501\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid6} 10 | {\list\listtemplateid7\listhybrid{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace360\levelindent0{\*\levelmarker \{disc\}}{\leveltext\leveltemplateid601\'01\uc0\u8226 ;}{\levelnumbers;}\fi-360\li720\lin720 }{\listname ;}\listid7}} 11 | {\*\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}{\listoverride\listid2\listoverridecount0\ls2}{\listoverride\listid3\listoverridecount0\ls3}{\listoverride\listid4\listoverridecount0\ls4}{\listoverride\listid5\listoverridecount0\ls5}{\listoverride\listid6\listoverridecount0\ls6}{\listoverride\listid7\listoverridecount0\ls7}} 12 | \margl1440\margr1440\vieww19360\viewh17460\viewkind0 13 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc\pardirnatural 14 | 15 | \f0\b\fs24 \cf0 iToonz 16 | \b0 \ 17 | version 0.68\ 18 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 19 | \cf0 \ 20 | This is a sample project and test harness for the row spanning feature of NoodleTableView.\ 21 | \ 22 | The row spanning feature allows a tableview to have cells span multiple rows. An example of this can be seen in iTune's "Artwork" column.\ 23 | \ 24 | This all started from a blog post by Jesper here: {\field{\*\fldinst{HYPERLINK "http://waffle.wootest.net/2009/10/04/artwork-column-in-cocoa/"}}{\fldrslt http://waffle.wootest.net/2009/10/04/artwork-column-in-cocoa/}}\ 25 | \ 26 | This was followed by Jacob Xiao's post here: {\field{\*\fldinst{HYPERLINK "http://likethought.com/lockfocus/2009/10/another-way-to-mimic-the-artwork-column-in-cocoa/"}}{\fldrslt http://likethought.com/lockfocus/2009/10/another-way-to-mimic-the-artwork-column-in-cocoa/}}\ 27 | \ 28 | These posts inspired me to implement a generalized version that should work with any type of cell. It was Jacob's approach that I ended up using in my version. Thanks to both Jesper and Jacob for the inspiration for this.\ 29 | \ 30 | You can view my post here: {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/blog/2009/10/20/yet-another-way-to-mimic-the-artwork-column-in-cocoa"}}{\fldrslt http://www.noodlesoft.com/blog/2009/10/20/yet-another-way-to-mimic-the-artwork-column-in-cocoa}}\ 31 | \ 32 | Note that the original class in the blog post has been merged with the NoodleStickyRowTableView into NoodleTableView. As a result, both features can be used concurrently. This project also demonstrates how to specify via a delegate method which indicates which cells will be displayed in the sticky row header.\ 33 | \ 34 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 35 | 36 | \b \cf0 Notes 37 | \b0 \ 38 | \ 39 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 40 | \ls1\ilvl0\cf0 {\listtext \'95 }As mentioned above, you can use any NSCell subclass.\ 41 | {\listtext \'95 }"Spans" are determined by contiguous ranges of rows with the same object value for a particular column. It is assumed that the same object value will result in the same visual output for a particular column cell.\ 42 | {\listtext \'95 }Internally, a special cell is used. It renders the full cell and then draws out each row's slice into the tableview. This also allows for an optimization to only do the full render once instead of for every row slice.\ 43 | {\listtext \'95 }If the first column is set to span rows, horizontal grid lines will only be drawn for the last row in a span.\ 44 | {\listtext \'95 }Span cells will not highlight.\ 45 | {\listtext \'95 }Clicking on span cells will do nothing.\ 46 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 47 | \cf0 \ 48 | To integrate this functionality, first off, you need to use NoodleTableView. For any column that you want to have row spanning, make that column be an instance of NoodleTableColumn. You can then either set the rowSpanningEnabled property or call -setRowSpanningEnabledForCapableColumns: which will enable it for every NoodleTableColumn in the tableview.\ 49 | \ 50 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 51 | 52 | \b \cf0 Possible Improvements\ 53 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 54 | 55 | \b0 \cf0 \ 56 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 57 | \ls2\ilvl0\cf0 {\listtext \'95 }Provide delegate hooks for determining the range of a span.\ 58 | {\listtext \'95 }Have mouse clicks on span cells select the first row in the span. This is how iTunes works though it only does it if you click the artwork itself, not the cell in general. Possibly something left up to the application to implement.\ 59 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 60 | \cf0 \ 61 | \ 62 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 63 | 64 | \b \cf0 Contact 65 | \b0 \ 66 | \ 67 | Just go {\field{\*\fldinst{HYPERLINK "http://www.noodlesoft.com/about.php"}}{\fldrslt www.noodlesoft.com}} and shoot me an email. Or visit the blog article linked above and leave a comment. Bugs, suggestions and other feedback appreciated.\ 68 | \ 69 | \ 70 | 71 | \b License 72 | \b0 \ 73 | \ 74 | I am releasing this under the MIT license.\ 75 | \ 76 | ____________________________________\ 77 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 78 | 79 | \f1 \cf0 Copyright (c) 2009 Noodlesoft, LLC. All Rights Reserved.\ 80 | \ 81 | Permission is hereby granted, free of charge, to any person\ 82 | obtaining a copy of this software and associated documentation\ 83 | files (the "Software"), to deal in the Software without\ 84 | restriction, including without limitation the rights to use,\ 85 | copy, modify, merge, publish, distribute, sublicense, and/or sell\ 86 | copies of the Software, and to permit persons to whom the\ 87 | Software is furnished to do so, subject to the following\ 88 | conditions:\ 89 | \ 90 | The above copyright notice and this permission notice shall be\ 91 | included in all copies or substantial portions of the Software.\ 92 | \ 93 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\ 94 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES\ 95 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\ 96 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\ 97 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\ 98 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\ 99 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\ 100 | OTHER DEALINGS IN THE SOFTWARE.\ 101 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 102 | \ls3\ilvl0 103 | \f0\b \cf0 \ 104 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 105 | \cf0 \ 106 | Changelog 107 | \b0 \ 108 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 109 | \ls4\ilvl0\cf0 \ 110 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 111 | \cf0 0.68:\ 112 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 113 | \ls5\ilvl0\cf0 {\listtext \'95 }Fixed crash when clicking on a non-span cell and dragging to a span cell.\ 114 | {\listtext \'95 }Fixed highlighting behavior. Span cells should no longer highlight.\ 115 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 116 | \cf0 \ 117 | 0.37:\ 118 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 119 | \ls6\ilvl0\cf0 {\listtext \'95 }Fixed drawing loop causing high CPU usage.\ 120 | {\listtext \'95 }Revamped grid code.\ 121 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 122 | \cf0 \ 123 | 0.04:\ 124 | \pard\tx220\tx720\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\li720\fi-720\ql\qnatural\pardirnatural 125 | \ls7\ilvl0\cf0 {\listtext \'95 }Initial public release\ 126 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural\pardirnatural 127 | \cf0 \ 128 | } -------------------------------------------------------------------------------- /Examples/iToonz/iToonz-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | com.yourcompany.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | APPL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | LSMinimumSystemVersion 22 | ${MACOSX_DEPLOYMENT_TARGET} 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /Examples/iToonz/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // NoodleStickyRowTableViewTest 4 | // 5 | // Created by Paul Kim on 8/23/09. 6 | // Copyright Noodlesoft, LLC 2009. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | return NSApplicationMain(argc, (const char **) argv); 14 | } 15 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleName 10 | ${PRODUCT_NAME} 11 | CFBundleIconFile 12 | 13 | CFBundleIdentifier 14 | com.noodlesoft.${PRODUCT_NAME:rfc1034Identifier} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundlePackageType 18 | FMWK 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | CFBundleShortVersionString 24 | 1.0 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /NSImage-NoodleExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage-NoodleExtensions.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 3/24/07. 6 | // Copyright 2007-2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | 32 | /* 33 | This category provides methods for dealing with flipped images. These should draw images correctly regardless of 34 | whether the current context or the current image are flipped. Unless you know what you are doing, these should be used 35 | in lieu of the normal NSImage drawing/compositing methods. 36 | 37 | For more details, check out the related blog post at http://www.noodlesoft.com/blog/2009/02/02/understanding-flipped-coordinate-systems/ 38 | */ 39 | 40 | @interface NSImage (NoodleExtensions) 41 | 42 | /*! 43 | @method drawAdjustedAtPoint:fromRect:operation:fraction: 44 | @abstract Draws all or part of the image at the specified point in the current coordinate system. Unlike other methods in NSImage, this will orient the image properly in flipped coordinate systems. 45 | @param point The location in the current coordinate system at which to draw the image. 46 | @param srcRect The source rectangle specifying the portion of the image you want to draw. The coordinates of this rectangle are specified in the image's own coordinate system. If you pass in NSZeroRect, the entire image is drawn. 47 | @param op The compositing operation to use when drawing the image. See the NSCompositingOperation constants. 48 | @param delta The opacity of the image, specified as a value from 0.0 to 1.0. Specifying a value of 0.0 draws the image as fully transparent while a value of 1.0 draws the image as fully opaque. Values greater than 1.0 are interpreted as 1.0. 49 | @discussion The image content is drawn at its current resolution and is not scaled unless the CTM of the current coordinate system itself contains a scaling factor. The image is otherwise positioned and oriented using the current coordinate system, except that it takes the flipped status into account, drawing right-side-up in a such a case. 50 | 51 | Unlike the compositeToPoint:fromRect:operation: and compositeToPoint:fromRect:operation:fraction: methods, this method checks the rectangle you pass to the srcRect parameter and makes sure it does not lie outside the image bounds. 52 | */ 53 | - (void)drawAdjustedAtPoint:(NSPoint)aPoint fromRect:(NSRect)srcRect operation:(NSCompositingOperation)op fraction:(CGFloat)delta; 54 | 55 | /*! 56 | @method drawAdjustedInRect:fromRect:operation:fraction: 57 | @abstract Draws all or part of the image in the specified rectangle in the current coordinate system. Unlike other methods in NSImage, this will orient the image properly in flipped coordinate systems. 58 | @param dstRect The rectangle in which to draw the image, specified in the current coordinate system. 59 | @param srcRect The source rectangle specifying the portion of the image you want to draw. The coordinates of this rectangle must be specified using the image's own coordinate system. If you pass in NSZeroRect, the entire image is drawn. 60 | @param op The compositing operation to use when drawing the image. See the NSCompositingOperation constants. 61 | @param delta The opacity of the image, specified as a value from 0.0 to 1.0. Specifying a value of 0.0 draws the image as fully transparent while a value of 1.0 draws the image as fully opaque. Values greater than 1.0 are interpreted as 1.0. 62 | @discussion If the srcRect and dstRect rectangles have different sizes, the source portion of the image is scaled to fit the specified destination rectangle. The image is otherwise positioned and oriented using the current coordinate system, except that it takes the flipped status into account, drawing right-side-up in a such a case. 63 | 64 | Unlike the compositeToPoint:fromRect:operation: and compositeToPoint:fromRect:operation:fraction: methods, this method checks the rectangle you pass to the srcRect parameter and makes sure it does not lie outside the image bounds. 65 | */ 66 | - (void)drawAdjustedInRect:(NSRect)dstRect fromRect:(NSRect)srcRect operation:(NSCompositingOperation)op fraction:(CGFloat)delta; 67 | 68 | /*! 69 | @method unflippedImage 70 | @abstract Returns a version of the receiver but unflipped. 71 | @discussion This does not actually flip the image but returns an image with the same orientation but with an unflipped coordinate system internally (isFlipped returns NO). If the image is already unflipped, this method returns self. 72 | */ 73 | - (NSImage *)unflippedImage; 74 | 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /NSImage-NoodleExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSImage-NoodleExtensions.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 3/24/07. 6 | // Copyright 2007-2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import "NSImage-NoodleExtensions.h" 30 | 31 | 32 | @implementation NSImage (NoodleExtensions) 33 | 34 | - (void)drawAdjustedAtPoint:(NSPoint)aPoint fromRect:(NSRect)srcRect operation:(NSCompositingOperation)op fraction:(CGFloat)delta 35 | { 36 | NSSize size = [self size]; 37 | 38 | [self drawAdjustedInRect:NSMakeRect(aPoint.x, aPoint.y, size.width, size.height) fromRect:srcRect operation:op fraction:delta]; 39 | } 40 | 41 | - (void)drawAdjustedInRect:(NSRect)dstRect fromRect:(NSRect)srcRect operation:(NSCompositingOperation)op fraction:(CGFloat)delta 42 | { 43 | NSGraphicsContext *context; 44 | BOOL contextIsFlipped; 45 | 46 | context = [NSGraphicsContext currentContext]; 47 | contextIsFlipped = [context isFlipped]; 48 | 49 | if (contextIsFlipped) 50 | { 51 | NSAffineTransform *transform; 52 | 53 | [context saveGraphicsState]; 54 | 55 | // Flip the coordinate system back. 56 | transform = [NSAffineTransform transform]; 57 | [transform translateXBy:0 yBy:NSMaxY(dstRect)]; 58 | [transform scaleXBy:1 yBy:-1]; 59 | [transform concat]; 60 | 61 | // The transform above places the y-origin right where the image should be drawn. 62 | dstRect.origin.y = 0.0; 63 | } 64 | 65 | [self drawInRect:dstRect fromRect:srcRect operation:op fraction:delta]; 66 | 67 | if (contextIsFlipped) 68 | { 69 | [context restoreGraphicsState]; 70 | } 71 | } 72 | 73 | - (NSImage *)unflippedImage 74 | { 75 | if ([self isFlipped]) 76 | { 77 | NSImage *newImage; 78 | 79 | newImage = [[[NSImage alloc] initWithSize:[self size]] autorelease]; 80 | [newImage lockFocus]; 81 | [self drawAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeCopy fraction:1.0]; 82 | [newImage unlockFocus]; 83 | 84 | return newImage; 85 | } 86 | return self; 87 | } 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /NSIndexSet-NoodleExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSet-NoodleExtensions.h 3 | // NoodleRowSpanningTableViewTest 4 | // 5 | // Created by Paul Kim on 10/20/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | @interface NoodleIndexSetEnumerator : NSObject 32 | { 33 | NSUInteger *_indexes; 34 | NSUInteger _count; 35 | NSUInteger _currentIndex; 36 | } 37 | 38 | // Returns NSNotFound when there are no more indexes. 39 | - (NSUInteger)nextIndex; 40 | 41 | @end 42 | 43 | 44 | @interface NSIndexSet (NoodleExtensions) 45 | 46 | - (NoodleIndexSetEnumerator *)indexEnumerator; 47 | 48 | @end 49 | 50 | -------------------------------------------------------------------------------- /NSIndexSet-NoodleExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSIndexSet-NoodleExtensions.m 3 | // NoodleRowSpanningTableViewTest 4 | // 5 | // Created by Paul Kim on 10/20/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import "NSIndexSet-NoodleExtensions.h" 30 | 31 | @interface NoodleIndexSetEnumerator () 32 | 33 | + enumeratorWithIndexSet:(NSIndexSet *)set; 34 | - initWithIndexSet:(NSIndexSet *)set; 35 | 36 | @end 37 | 38 | @implementation NoodleIndexSetEnumerator 39 | 40 | + enumeratorWithIndexSet:(NSIndexSet *)set 41 | { 42 | return [[[[self class] alloc] initWithIndexSet:set] autorelease]; 43 | } 44 | 45 | - initWithIndexSet:(NSIndexSet *)set 46 | { 47 | if ((self = [super init]) != nil) 48 | { 49 | _currentIndex = 0; 50 | _count = [set count]; 51 | _indexes = (NSUInteger *)malloc(sizeof(NSUInteger) * _count); 52 | 53 | [set getIndexes:_indexes maxCount:_count inIndexRange:nil]; 54 | } 55 | return self; 56 | } 57 | 58 | - (void)dealloc 59 | { 60 | free(_indexes); 61 | _indexes = NULL; 62 | [super dealloc]; 63 | } 64 | 65 | - (void)finalize 66 | { 67 | free(_indexes); 68 | [super finalize]; 69 | } 70 | 71 | - (NSUInteger)nextIndex 72 | { 73 | if (_currentIndex < _count) 74 | { 75 | NSUInteger i; 76 | 77 | i = _indexes[_currentIndex]; 78 | _currentIndex++; 79 | 80 | return i; 81 | } 82 | return NSNotFound; 83 | } 84 | 85 | @end 86 | 87 | @implementation NSIndexSet (NoodleExtensions) 88 | 89 | - (NoodleIndexSetEnumerator *)indexEnumerator 90 | { 91 | return [NoodleIndexSetEnumerator enumeratorWithIndexSet:self]; 92 | } 93 | 94 | @end 95 | 96 | 97 | -------------------------------------------------------------------------------- /NSObject-NoodlePerformWhenIdle.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject-IdleExtensions.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 12/30/07. 6 | // Copyright 2007 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | @interface NSObject (NoodlePerformWhenIdle) 29 | 30 | - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterSystemIdleTime:(NSTimeInterval)delay; 31 | 32 | - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterSystemIdleTime:(NSTimeInterval)delay withinTimeLimit:(NSTimeInterval)maxTime; 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /NSObject-NoodlePerformWhenIdle.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject-IdleExtensions.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 12/30/07. 6 | // Copyright 2007-2012 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "NSObject-NoodlePerformWhenIdle.h" 27 | #import 28 | 29 | @implementation NSObject (NoodlePerformWhenIdle) 30 | 31 | // Heard somewhere that this prototype may be missing in some cases so adding it here just in case. 32 | CG_EXTERN CFTimeInterval CGEventSourceSecondsSinceLastEventType( CGEventSourceStateID source, CGEventType eventType ) AVAILABLE_MAC_OS_X_VERSION_10_4_AND_LATER; 33 | 34 | 35 | // Semi-private method. Used by the public methods 36 | - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterSystemIdleTime:(NSTimeInterval)delay withinTimeLimit:(NSTimeInterval)maxTime startTime:(NSTimeInterval)startTime 37 | { 38 | CFTimeInterval idleTime; 39 | NSTimeInterval timeSinceInitialCall; 40 | 41 | timeSinceInitialCall = [NSDate timeIntervalSinceReferenceDate] - startTime; 42 | 43 | if (maxTime > 0) 44 | { 45 | if (timeSinceInitialCall >= maxTime) 46 | { 47 | [self performSelector:aSelector withObject:anArgument]; 48 | return; 49 | } 50 | } 51 | 52 | idleTime = CGEventSourceSecondsSinceLastEventType(kCGEventSourceStateHIDSystemState, kCGAnyInputEventType); 53 | if (idleTime < delay) 54 | { 55 | NSTimeInterval fireTime; 56 | NSMethodSignature *signature; 57 | NSInvocation *invocation; 58 | 59 | signature = [self methodSignatureForSelector:@selector(performSelector:withObject:afterSystemIdleTime:withinTimeLimit:startTime:)]; 60 | invocation = [NSInvocation invocationWithMethodSignature:signature]; 61 | [invocation setSelector:@selector(performSelector:withObject:afterSystemIdleTime:withinTimeLimit:startTime:)]; 62 | [invocation setTarget:self]; 63 | [invocation setArgument:&aSelector atIndex:2]; 64 | [invocation setArgument:&anArgument atIndex:3]; 65 | [invocation setArgument:&delay atIndex:4]; 66 | [invocation setArgument:&maxTime atIndex:5]; 67 | [invocation setArgument:&startTime atIndex:6]; 68 | 69 | fireTime = delay - idleTime; 70 | if (maxTime > 0) 71 | { 72 | fireTime = MIN(fireTime, maxTime - timeSinceInitialCall); 73 | } 74 | 75 | // Not idle for long enough. Set a timer and check back later 76 | [NSTimer scheduledTimerWithTimeInterval:fireTime invocation:invocation repeats:NO]; 77 | } 78 | else 79 | { 80 | [self performSelector:aSelector withObject:anArgument]; 81 | } 82 | } 83 | 84 | 85 | - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterSystemIdleTime:(NSTimeInterval)delay 86 | { 87 | [self performSelector:aSelector withObject:anArgument afterSystemIdleTime:delay withinTimeLimit:-1]; 88 | } 89 | 90 | 91 | - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterSystemIdleTime:(NSTimeInterval)delay withinTimeLimit:(NSTimeInterval)maxTime 92 | { 93 | SInt32 version; 94 | 95 | // NOTE: Even though CGEventSourceSecondsSinceLastEventType exists on Tiger, 96 | // it appears to hang on some Tiger systems. For now, only enabling for Leopard or later. 97 | if ((Gestalt(gestaltSystemVersion, &version) == noErr) && (version >= 0x1050)) 98 | { 99 | NSTimeInterval startTime; 100 | 101 | startTime = [NSDate timeIntervalSinceReferenceDate]; 102 | 103 | [self performSelector:aSelector withObject:anArgument afterSystemIdleTime:delay withinTimeLimit:maxTime startTime:startTime]; 104 | } 105 | else 106 | { 107 | // For pre-10.5, just call it after a delay. Change this if you want to throw an exception 108 | // instead. 109 | [self performSelector:aSelector withObject:anArgument afterDelay:delay]; 110 | } 111 | } 112 | 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /NSResponder-NoodleModalExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSResponder-ModalExtensions.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 3/6/08. 6 | // Copyright 2008-2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | /* 29 | These categories provide simple methods to handle alert dialogs (window or sheet form). Hook your "OK" and "Cancel" 30 | buttons to these methods to the first responder in IB and you're done. The modal session will return NSOKButton or 31 | NSCancelButton. I'll leave it to you to figure out which one does what. 32 | 33 | For more details, check out the related blog post at http://www.noodlesoft.com/blog/2008/03/10/modal-glue/ 34 | */ 35 | 36 | 37 | @interface NSResponder (NoodleModalExtensions) 38 | 39 | - (void)confirmModal:(id)sender; 40 | - (void)cancelModal:(id)sender; 41 | 42 | @end 43 | 44 | @interface NSWindow (NoodleModalExtensions) 45 | 46 | - (void)confirmModal:(id)sender; 47 | - (void)cancelModal:(id)sender; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /NSResponder-NoodleModalExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSResponder-ModalExtensions.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 3/6/08. 6 | // Copyright 2008-2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "NSResponder-NoodleModalExtensions.h" 27 | 28 | 29 | @implementation NSResponder (NoodleModalExtensions) 30 | 31 | 32 | - (void)confirmModal:(id)sender 33 | { 34 | [[self nextResponder] confirmModal:sender]; 35 | } 36 | 37 | - (void)cancelModal:(id)sender 38 | { 39 | [[self nextResponder] cancelModal:sender]; 40 | } 41 | 42 | @end 43 | 44 | @implementation NSWindow (NoodleModalExtensions) 45 | 46 | - (BOOL)stopModalWindowOrSheetWithCode:(NSInteger)returnCode sender:(id)sender 47 | { 48 | if ([NSApp modalWindow] == self) 49 | { 50 | [self orderOut:sender]; 51 | [NSApp stopModalWithCode:returnCode]; 52 | return YES; 53 | } 54 | else if ([self isSheet]) 55 | { 56 | [self orderOut:sender]; 57 | [NSApp endSheet:self returnCode:returnCode]; 58 | return YES; 59 | } 60 | else if ([self attachedSheet] != nil) 61 | { 62 | NSWindow *sheet; 63 | 64 | sheet = [self attachedSheet]; 65 | [sheet orderOut:sender]; 66 | [NSApp endSheet:sheet returnCode:returnCode]; 67 | } 68 | return NO; 69 | } 70 | 71 | - (void)confirmModal:(id)sender 72 | { 73 | if (![self stopModalWindowOrSheetWithCode:NSOKButton sender:sender]) 74 | { 75 | [[self nextResponder] confirmModal:sender]; 76 | } 77 | } 78 | 79 | - (void)cancelModal:(id)sender 80 | { 81 | if (![self stopModalWindowOrSheetWithCode:NSCancelButton sender:sender]) 82 | { 83 | [[self nextResponder] cancelModal:self]; 84 | } 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /NSTableView-NoodleExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView-NoodleExtensions.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 10/22/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | typedef NSUInteger NoodleStickyRowTransition; 32 | 33 | enum 34 | { 35 | NoodleStickyRowTransitionNone, 36 | NoodleStickyRowTransitionFadeIn 37 | }; 38 | 39 | 40 | @interface NSTableView (NoodleExtensions) 41 | 42 | #pragma mark Sticky Row Header methods 43 | // Note: see NoodleTableView's -drawRect on how to hook in this functionality in a subclass 44 | 45 | /* 46 | Currently set to any groups rows (as dictated by the delegate). The 47 | delegate can implement -tableView:isStickyRow: to override this. 48 | */ 49 | - (BOOL)isRowSticky:(NSInteger)rowIndex; 50 | 51 | /* 52 | Does the actual drawing of the sticky row. Override if you want a custom look. 53 | You shouldn't invoke this directly. See -drawStickyRowHeader. 54 | */ 55 | - (void)drawStickyRow:(NSInteger)row clipRect:(NSRect)clipRect; 56 | 57 | /* 58 | Draws the sticky row at the top of the table. You have to override -drawRect 59 | and call this method, that being all you need to get the sticky row stuff 60 | to work in your subclass. Look at NoodleStickyRowTableView. 61 | Note that you shouldn't need to override this. To modify the look of the row, 62 | override -drawStickyRow: instead. 63 | */ 64 | - (void)drawStickyRowHeader; 65 | 66 | /* 67 | Returns the rect of the sticky view header. Will return NSZeroRect if there is no current 68 | sticky row. 69 | */ 70 | - (NSRect)stickyRowHeaderRect; 71 | 72 | /* 73 | Does an animated scroll to the current sticky row. Clicking on the sticky 74 | row header will trigger this. 75 | */ 76 | - (IBAction)scrollToStickyRow:(id)sender; 77 | 78 | /* 79 | Returns what kind of transition you want when the row becomes sticky. Fade-in 80 | is the default. 81 | */ 82 | - (NoodleStickyRowTransition)stickyRowHeaderTransition; 83 | 84 | #pragma mark Row Spanning methods 85 | 86 | /* 87 | Returns the range of the span at the given column and row indexes. The span is determined by 88 | a range of contiguous rows having the same object value. 89 | */ 90 | - (NSRange)rangeOfRowSpanAtColumn:(NSInteger)columnIndex row:(NSInteger)rowIndex; 91 | 92 | @end 93 | 94 | @class NoodleRowSpanningCell; 95 | 96 | @interface NSTableColumn (NoodleExtensions) 97 | 98 | #pragma mark Row Spanning methods 99 | /* 100 | Returns whether this column will try to consolidate rows into spans. 101 | */ 102 | - (BOOL)isRowSpanningEnabled; 103 | 104 | /* 105 | Returns the cell used to draw the spanning regions. Default implementation returns nil. 106 | */ 107 | - (NoodleRowSpanningCell *)spanningCell; 108 | 109 | @end 110 | 111 | 112 | @interface NSOutlineView (NoodleExtensions) 113 | 114 | #pragma mark Sticky Row Header methods 115 | /* 116 | Currently set to any groups rows (or as dictated by the delegate). The 117 | delegate can implement -outlineView:isStickyRow: to override this. 118 | */ 119 | - (BOOL)isRowSticky:(NSInteger)rowIndex; 120 | 121 | @end 122 | 123 | 124 | @interface NSObject (NoodleStickyRowDelegate) 125 | 126 | /* 127 | Allows the delegate to specify if a row is sticky. By default, group rows 128 | are sticky. The delegate can override that by implementing this method. 129 | */ 130 | - (BOOL)tableView:(NSTableView *)tableView isStickyRow:(NSInteger)rowIndex; 131 | 132 | /* 133 | Allows the delegate to specify whether a certain cell should be drawn in the sticky row header 134 | */ 135 | - (BOOL)tableView:(NSTableView *)tableView shouldDisplayCellInStickyRowHeaderForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex; 136 | 137 | /* 138 | Same as above but for outline views. 139 | */ 140 | - (BOOL)outlineView:(NSOutlineView *)outlineView isStickyItem:(id)item; 141 | 142 | @end 143 | 144 | -------------------------------------------------------------------------------- /NSTableView-NoodleExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView-NoodleExtensions.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 10/22/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import "NSTableView-NoodleExtensions.h" 30 | #import "NSImage-NoodleExtensions.h" 31 | 32 | #define NOODLE_STICKY_ROW_VIEW_TAG 233931134 33 | 34 | void NoodleClearRect(NSRect rect) 35 | { 36 | [[NSColor clearColor] set]; 37 | NSRectFill(rect); 38 | } 39 | 40 | @interface NSTableView (NoodlePrivate) 41 | 42 | #pragma mark Sticky Row Header methods 43 | 44 | // Returns index of the sticky row previous to the first visible row. 45 | - (NSInteger)_previousStickyRow; 46 | 47 | // Returns index of the sticky row after the first visible row. 48 | - (NSInteger)_nextStickyRow; 49 | 50 | - (void)_updateStickyRowHeaderImageWithRow:(NSInteger)row; 51 | 52 | // Returns the view used for the sticky row header 53 | - (id)_stickyRowHeaderView; 54 | 55 | @end 56 | 57 | 58 | @implementation NSTableView (NoodleExtensions) 59 | 60 | #pragma mark Sticky Row Header methods 61 | 62 | - (BOOL)isRowSticky:(NSInteger)rowIndex 63 | { 64 | id delegate; 65 | 66 | delegate = [self delegate]; 67 | 68 | if ([delegate respondsToSelector:@selector(tableView:isStickyRow:)]) 69 | { 70 | return [delegate tableView:self isStickyRow:rowIndex]; 71 | } 72 | else if ([delegate respondsToSelector:@selector(tableView:isGroupRow:)]) 73 | { 74 | return [delegate tableView:self isGroupRow:rowIndex]; 75 | } 76 | return NO; 77 | } 78 | 79 | - (void)drawStickyRowHeader 80 | { 81 | id stickyView; 82 | NSInteger row; 83 | 84 | stickyView = [self _stickyRowHeaderView]; 85 | row = [self _previousStickyRow]; 86 | if (row != -1) 87 | { 88 | [stickyView setFrame:[self stickyRowHeaderRect]]; 89 | [self _updateStickyRowHeaderImageWithRow:row]; 90 | } 91 | else 92 | { 93 | [stickyView setFrame:NSZeroRect]; 94 | } 95 | } 96 | 97 | - (IBAction)scrollToStickyRow:(id)sender 98 | { 99 | NSInteger row; 100 | 101 | row = [self _previousStickyRow]; 102 | if (row != -1) 103 | { 104 | [self scrollRowToVisible:row]; 105 | } 106 | } 107 | 108 | - (id)_stickyRowHeaderView 109 | { 110 | NSButton *view; 111 | 112 | view = [self viewWithTag:NOODLE_STICKY_ROW_VIEW_TAG]; 113 | 114 | if (view == nil) 115 | { 116 | view = [[NSButton alloc] initWithFrame:NSZeroRect]; 117 | [view setEnabled:YES]; 118 | [view setBordered:NO]; 119 | [view setImagePosition:NSImageOnly]; 120 | [view setTitle:nil]; 121 | [[view cell] setHighlightsBy:NSNoCellMask]; 122 | [[view cell] setShowsStateBy:NSNoCellMask]; 123 | [[view cell] setImageScaling:NSImageScaleNone]; 124 | [[view cell] setImageDimsWhenDisabled:NO]; 125 | 126 | [view setTag:NOODLE_STICKY_ROW_VIEW_TAG]; 127 | 128 | [view setTarget:self]; 129 | [view setAction:@selector(scrollToStickyRow:)]; 130 | 131 | [self addSubview:view]; 132 | [view release]; 133 | } 134 | return view; 135 | } 136 | 137 | - (void)drawStickyRow:(NSInteger)row clipRect:(NSRect)clipRect 138 | { 139 | NSRect rowRect, cellRect; 140 | NSCell *cell; 141 | NSInteger colIndex, count; 142 | id delegate; 143 | 144 | delegate = [self delegate]; 145 | 146 | if (![delegate respondsToSelector:@selector(tableView:shouldDisplayCellInStickyRowHeaderForTableColumn:row:)]) 147 | { 148 | delegate = nil; 149 | } 150 | 151 | rowRect = [self rectOfRow:row]; 152 | 153 | [[[self backgroundColor] highlightWithLevel:0.5] set]; 154 | NSRectFill(rowRect); 155 | 156 | // PENDING: -drawRow:clipRect: is too smart for its own good. If the row is not visible, 157 | // this method won't draw anything. Useless for row caching. 158 | // [self drawRow:row clipRect:rowRect]; 159 | 160 | count = [self numberOfColumns]; 161 | for (colIndex = 0; colIndex < count; colIndex++) 162 | { 163 | if ((delegate == nil) || 164 | [delegate tableView:self shouldDisplayCellInStickyRowHeaderForTableColumn:[[self tableColumns] objectAtIndex:colIndex] row:row]) 165 | { 166 | cell = [self preparedCellAtColumn:colIndex row:row]; 167 | cellRect = [self frameOfCellAtColumn:colIndex row:row]; 168 | [cell drawWithFrame:cellRect inView:self]; 169 | } 170 | } 171 | 172 | [[self gridColor] set]; 173 | [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMinX(rowRect), NSMaxY(rowRect)) toPoint:NSMakePoint(NSMaxX(rowRect), NSMaxY(rowRect))]; 174 | } 175 | 176 | - (NoodleStickyRowTransition)stickyRowHeaderTransition 177 | { 178 | return NoodleStickyRowTransitionFadeIn; 179 | } 180 | 181 | - (void)_updateStickyRowHeaderImageWithRow:(NSInteger)row 182 | { 183 | NSImage *image; 184 | NSRect rowRect, visibleRect, imageRect; 185 | CGFloat offset, alpha; 186 | NSAffineTransform *transform; 187 | id stickyView; 188 | NoodleStickyRowTransition transition; 189 | BOOL isSelected; 190 | 191 | rowRect = [self rectOfRow:row]; 192 | imageRect = NSMakeRect(0.0, 0.0, NSWidth(rowRect), NSHeight(rowRect)); 193 | stickyView = [self _stickyRowHeaderView]; 194 | 195 | isSelected = [self isRowSelected:row]; 196 | if (isSelected) 197 | { 198 | [self deselectRow:row]; 199 | } 200 | 201 | // Optimization: instead of creating a new image each time (and since we can't 202 | // add ivars in a category), just use the image in the sticky view. We're going 203 | // to put it there in the end anyways, why not reuse it? 204 | image = [stickyView image]; 205 | 206 | if ((image == nil) || !NSEqualSizes(rowRect.size, [image size])) 207 | { 208 | image = [[NSImage alloc] initWithSize:rowRect.size]; 209 | [image setFlipped:[self isFlipped]]; 210 | [stickyView setImage:image]; 211 | [image release]; 212 | } 213 | 214 | visibleRect = [self visibleRect]; 215 | 216 | // Calculate a distance between the row header and the actual sticky row and normalize it 217 | // over the row height (plus some extra). We use this to do the fade in effect as you 218 | // scroll away from the sticky row. 219 | offset = (NSMinY(visibleRect) - NSMinY(rowRect)) / (NSHeight(rowRect) * 1.25); 220 | 221 | // When the button is disabled, it passes through to the view underneath. So, until the 222 | // original header view is mostly out of view, allow mouse events to pass through. After 223 | // that, the header is clickable. 224 | if (offset < 0.5) 225 | { 226 | [stickyView setEnabled:NO]; 227 | } 228 | else 229 | { 230 | [stickyView setEnabled:YES]; 231 | } 232 | 233 | // Row is drawn in tableview coord space. 234 | transform = [NSAffineTransform transform]; 235 | [transform translateXBy:-NSMinX(rowRect) yBy:-NSMinY(rowRect)]; 236 | 237 | transition = [self stickyRowHeaderTransition]; 238 | if (transition == NoodleStickyRowTransitionFadeIn) 239 | { 240 | // Since we want to adjust the transparency based on position, we draw the row into an 241 | // image which we then draw with alpha into the final image. 242 | NSImage *rowImage; 243 | 244 | // Optimization: Since this is a category and we can't add any ivars, we instead use 245 | // the unused alt image of the sticky view (which is a button) as a cache so we don't 246 | // have to keep creating images. Yes, a little hackish. 247 | rowImage = [stickyView alternateImage]; 248 | if ((rowImage == nil) || !NSEqualSizes(rowRect.size, [rowImage size])) 249 | { 250 | rowImage = [[NSImage alloc] initWithSize:rowRect.size]; 251 | [rowImage setFlipped:[self isFlipped]]; 252 | 253 | [stickyView setAlternateImage:rowImage]; 254 | [rowImage release]; 255 | } 256 | 257 | // Draw the original image 258 | [rowImage lockFocus]; 259 | NoodleClearRect(imageRect); 260 | 261 | [transform concat]; 262 | [self drawStickyRow:row clipRect:rowRect]; 263 | 264 | [rowImage unlockFocus]; 265 | 266 | alpha = MIN(offset, 0.9); 267 | 268 | // Draw it with transparency in the final image 269 | [image lockFocus]; 270 | 271 | NoodleClearRect(imageRect); 272 | [rowImage drawAdjustedAtPoint:NSZeroPoint fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:alpha]; 273 | 274 | [image unlockFocus]; 275 | } 276 | else if (transition == NoodleStickyRowTransitionNone) 277 | { 278 | [image lockFocus]; 279 | NoodleClearRect(imageRect); 280 | 281 | [transform concat]; 282 | [self drawStickyRow:row clipRect:rowRect]; 283 | 284 | [image unlockFocus]; 285 | } 286 | else 287 | { 288 | [image lockFocus]; 289 | NoodleClearRect(imageRect); 290 | 291 | [@"You returned a bad NoodleStickyRowTransition value. Tsk. Tsk." drawInRect:imageRect withAttributes:nil]; 292 | 293 | [image unlockFocus]; 294 | } 295 | 296 | if (isSelected) 297 | { 298 | [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:YES]; 299 | } 300 | 301 | } 302 | 303 | - (NSInteger)_previousStickyRow 304 | { 305 | NSRect visibleRect; 306 | NSInteger row; 307 | 308 | visibleRect = [self visibleRect]; 309 | row = [self rowAtPoint:visibleRect.origin]; 310 | 311 | while (row >= 0) 312 | { 313 | if ([self isRowSticky:row]) 314 | { 315 | return row; 316 | } 317 | row--; 318 | } 319 | return -1; 320 | } 321 | 322 | - (NSInteger)_nextStickyRow 323 | { 324 | NSRect visibleRect; 325 | NSInteger row; 326 | NSInteger numberOfRows; 327 | 328 | visibleRect = [self visibleRect]; 329 | row = [self rowAtPoint:visibleRect.origin]; 330 | if (row != -1) 331 | { 332 | numberOfRows = [self numberOfRows]; 333 | while (++row < numberOfRows) 334 | { 335 | if ([self isRowSticky:row]) 336 | { 337 | return row; 338 | } 339 | } 340 | } 341 | return -1; 342 | } 343 | 344 | - (NSRect)stickyRowHeaderRect 345 | { 346 | NSInteger row; 347 | 348 | row = [self _previousStickyRow]; 349 | 350 | if (row != -1) 351 | { 352 | NSInteger nextGroupRow; 353 | NSRect visibleRect, rowRect; 354 | 355 | rowRect = [self rectOfRow:row]; 356 | visibleRect = [self visibleRect]; 357 | 358 | // Move it to the top of the visible area 359 | rowRect.origin.y = NSMinY(visibleRect); 360 | 361 | nextGroupRow = [self _nextStickyRow]; 362 | if (nextGroupRow != -1) 363 | { 364 | NSRect nextRect; 365 | 366 | // "Push" the row up if it's butting up against the next sticky row 367 | nextRect = [self rectOfRow:nextGroupRow]; 368 | if (NSMinY(nextRect) < NSMaxY(rowRect)) 369 | { 370 | rowRect.origin.y = NSMinY(nextRect) - NSHeight(rowRect); 371 | } 372 | } 373 | return rowRect; 374 | } 375 | return NSZeroRect; 376 | } 377 | 378 | #pragma mark Row Spanning methods 379 | 380 | - (NSRange)rangeOfRowSpanAtColumn:(NSInteger)columnIndex row:(NSInteger)rowIndex 381 | { 382 | id dataSource, objectValue, originalObjectValue; 383 | NSInteger i, start, end, count; 384 | NSTableColumn *column; 385 | 386 | dataSource = [self dataSource]; 387 | 388 | column = [[self tableColumns] objectAtIndex:columnIndex]; 389 | 390 | if ([column isRowSpanningEnabled]) 391 | { 392 | originalObjectValue = [dataSource tableView:self objectValueForTableColumn:column row:rowIndex]; 393 | 394 | // Figure out the span of this cell. We determine this by going up and down finding contiguous rows with 395 | // the same object value. 396 | i = rowIndex; 397 | while (i-- > 0) 398 | { 399 | objectValue = [dataSource tableView:self objectValueForTableColumn:column row:i]; 400 | 401 | if (![objectValue isEqual:originalObjectValue]) 402 | { 403 | break; 404 | } 405 | } 406 | start = i + 1; 407 | 408 | count = [self numberOfRows]; 409 | i = rowIndex + 1; 410 | while (i < count) 411 | { 412 | objectValue = [dataSource tableView:self objectValueForTableColumn:column row:i]; 413 | 414 | if (![objectValue isEqual:originalObjectValue]) 415 | { 416 | break; 417 | } 418 | i++; 419 | } 420 | end = i - 1; 421 | 422 | return NSMakeRange(start, end - start + 1); 423 | } 424 | return NSMakeRange(rowIndex, 1); 425 | } 426 | 427 | @end 428 | 429 | @implementation NSTableColumn (NoodleExtensions) 430 | 431 | #pragma mark Row Spanning methods 432 | 433 | - (BOOL)isRowSpanningEnabled 434 | { 435 | return NO; 436 | } 437 | 438 | - (NoodleRowSpanningCell *)spanningCell 439 | { 440 | return nil; 441 | } 442 | 443 | @end 444 | 445 | @implementation NSOutlineView (NoodleExtensions) 446 | 447 | #pragma mark Sticky Row Header methods 448 | 449 | - (BOOL)isRowSticky:(NSInteger)rowIndex 450 | { 451 | id delegate; 452 | 453 | delegate = [self delegate]; 454 | 455 | if ([delegate respondsToSelector:@selector(outlineView:isStickyItem:)]) 456 | { 457 | return [delegate outlineView:self isStickyItem:[self itemAtRow:rowIndex]]; 458 | } 459 | else if ([delegate respondsToSelector:@selector(outlineView:isGroupItem:)]) 460 | { 461 | return [delegate outlineView:self isGroupItem:[self itemAtRow:rowIndex]]; 462 | } 463 | return NO; 464 | } 465 | 466 | @end 467 | 468 | -------------------------------------------------------------------------------- /NSTimer-NoodleExtensions.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimer-NoodleExtensions.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 6/29/10. 6 | // Copyright 2010 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | #if defined(NS_BLOCKS_AVAILABLE) && (NS_BLOCKS_AVAILABLE == 1) 29 | 30 | typedef void (^NoodleTimerBlock)(NSTimer *timer); 31 | 32 | 33 | @interface NSTimer (NoodleExtensions) 34 | 35 | /* 36 | Creates and schedules a timer which will *not* adjust its fire date when the machine is put to sleep or if the clock 37 | is changed. It will fire on the given date to the best of its abilities. If the time has somehow passed (the fire date 38 | occurred when the machine was asleep or the clock was suddenly set to a time past the fire time), the timer will fire 39 | immediately upon wake/clock change. 40 | 41 | Note that calling -setFireTime: may not work properly on this timer. A new timer should be created if you wish to have 42 | it fire at a different time after initial creation. 43 | 44 | For more details, check out the related blog post at http://www.noodlesoft.com/blog/2010/07/01/playing-with-nstimer/ 45 | */ 46 | + (NSTimer *)scheduledTimerWithAbsoluteFireDate:(NSDate *)fireDate target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo; 47 | + (NSTimer *)scheduledTimerWithAbsoluteFireDate:(NSDate *)fireDate block:(NoodleTimerBlock)block; 48 | 49 | 50 | + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(NoodleTimerBlock)block; 51 | 52 | + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(NoodleTimerBlock)block; 53 | 54 | - (id)initWithAbsoluteFireDate:(NSDate *)date target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo; 55 | 56 | - (id)initWithAbsoluteFireDate:(NSDate *)date block:(NoodleTimerBlock)block; 57 | 58 | - (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(NoodleTimerBlock)block; 59 | 60 | @end 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /NSTimer-NoodleExtensions.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTimer-NoodleExtensions.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 6/29/10. 6 | // Copyright 2010 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "NSTimer-NoodleExtensions.h" 27 | #import 28 | #import "NoodleGlue.h" 29 | 30 | #if defined(NS_BLOCKS_AVAILABLE) && (NS_BLOCKS_AVAILABLE == 1) 31 | 32 | static char originalDateKey; 33 | static char observerKey; 34 | 35 | @implementation NSTimer (NoodleExtensions) 36 | 37 | + (NSTimer *)scheduledTimerWithAbsoluteFireDate:(NSDate *)fireDate target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo 38 | { 39 | __block NSTimer *timer; 40 | 41 | timer = [[[NSTimer alloc] initWithAbsoluteFireDate:fireDate target:target selector:aSelector userInfo:userInfo] autorelease]; 42 | 43 | if (timer != nil) 44 | { 45 | [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; 46 | } 47 | return timer; 48 | } 49 | 50 | + (NSTimer *)scheduledTimerWithAbsoluteFireDate:(NSDate *)fireDate block:(NoodleTimerBlock)block 51 | { 52 | NoodleGlue *glue; 53 | 54 | glue = [NoodleGlue glueWithBlock: 55 | ^(NoodleGlue *blockGlue, id object) 56 | { 57 | block(object); 58 | }]; 59 | 60 | return [self scheduledTimerWithAbsoluteFireDate:fireDate target:glue selector:@selector(invoke:) userInfo:nil]; 61 | } 62 | 63 | 64 | + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(NoodleTimerBlock)block 65 | { 66 | NoodleGlue *glue; 67 | 68 | glue = [NoodleGlue glueWithBlock: 69 | ^(NoodleGlue *blockGlue, id object) 70 | { 71 | block(object); 72 | }]; 73 | 74 | return [self scheduledTimerWithTimeInterval:seconds target:glue selector:@selector(invoke:) userInfo:nil repeats:repeats]; 75 | } 76 | 77 | + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(NoodleTimerBlock)block 78 | { 79 | NoodleGlue *glue; 80 | 81 | glue = [NoodleGlue glueWithBlock: 82 | ^(NoodleGlue *blockGlue, id object) 83 | { 84 | block(object); 85 | }]; 86 | 87 | return [self timerWithTimeInterval:seconds target:glue selector:@selector(invoke:) userInfo:nil repeats:repeats]; 88 | } 89 | 90 | - (id)initWithAbsoluteFireDate:(NSDate *)date target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo 91 | { 92 | self = [self initWithFireDate:date interval:0 target:target selector:aSelector userInfo:userInfo repeats:NO]; 93 | 94 | if (self != nil) 95 | { 96 | __block NSTimer *blockSelf; 97 | id observer; 98 | 99 | blockSelf = self; 100 | objc_setAssociatedObject(self, &originalDateKey, date, OBJC_ASSOCIATION_RETAIN); 101 | 102 | // We create a special observer object instead of using self. Since we are doing a category here, we can't 103 | // override -invalidate or -dealloc and do proper unregistering from notifications. Instead, we create an 104 | // intermediary observer that handles the notifications and unregisters itself when it is dealloced. We set 105 | // this observer as an associated object which is the only place where it is retained. 106 | // Note that the timer variable used is declared __block so that it is not retained by the block which would 107 | // result in a retain cycle. 108 | observer = [NoodleGlue glueWithBlock: 109 | ^(NoodleGlue *glue, id object) { 110 | [blockSelf setFireDate:(NSDate *)objc_getAssociatedObject(blockSelf, &originalDateKey)]; 111 | } 112 | cleanupBlock: 113 | ^(NoodleGlue *glue) { 114 | [[NSNotificationCenter defaultCenter] removeObserver:glue]; 115 | [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:glue]; 116 | }]; 117 | 118 | objc_setAssociatedObject(self, &observerKey, observer, OBJC_ASSOCIATION_RETAIN); 119 | 120 | [[NSNotificationCenter defaultCenter] addObserver:observer selector:@selector(invoke:) name:NSSystemTimeZoneDidChangeNotification object:nil]; 121 | [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:observer selector:@selector(invoke:) name:NSWorkspaceDidWakeNotification object:nil]; 122 | } 123 | return self; 124 | } 125 | 126 | - (id)initWithAbsoluteFireDate:(NSDate *)date block:(NoodleTimerBlock)block 127 | { 128 | NoodleGlue *glue; 129 | 130 | glue = [NoodleGlue glueWithBlock: 131 | ^(NoodleGlue *blockGlue, id object) 132 | { 133 | block(object); 134 | }]; 135 | return [self initWithAbsoluteFireDate:date target:glue selector:@selector(invoke:) userInfo:nil]; 136 | } 137 | 138 | - (id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds repeats:(BOOL)repeats block:(NoodleTimerBlock)block 139 | { 140 | NoodleGlue *glue; 141 | 142 | glue = [NoodleGlue glueWithBlock: 143 | ^(NoodleGlue *blockGlue, id object) 144 | { 145 | block(object); 146 | }]; 147 | return [self initWithFireDate:date interval:seconds target:glue selector:@selector(invoke:) userInfo:nil repeats:repeats]; 148 | } 149 | 150 | 151 | @end 152 | 153 | #endif 154 | -------------------------------------------------------------------------------- /NSWindow-NoodleEffects.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSWindow-Zoom.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 6/18/07. 6 | // Copyright 2007-2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import 31 | 32 | /* 33 | Provides a "zoom" animation for windows when ordering on and off screen. 34 | 35 | For more details, check out the related blog posts at http://www.noodlesoft.com/blog/2007/06/30/animation-in-the-time-of-tiger-part-1/ and http://www.noodlesoft.com/blog/2007/09/20/animation-in-the-time-of-tiger-part-3/ 36 | */ 37 | 38 | @interface NSWindow (NoodleEffects) 39 | 40 | - (void)animateToFrame:(NSRect)frameRect duration:(NSTimeInterval)duration; 41 | 42 | - (void)zoomOnFromRect:(NSRect)startRect; 43 | - (void)zoomOffToRect:(NSRect)endRect; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /NSWindow-NoodleEffects.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSWindow-Zoom.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 6/18/07. 6 | // Copyright 2007-2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import "NSWindow-NoodleEffects.h" 31 | #import 32 | 33 | 34 | @implementation NSWindow (NoodleEffects) 35 | 36 | - (void)animateToFrame:(NSRect)frameRect duration:(NSTimeInterval)duration 37 | { 38 | NSViewAnimation *animation; 39 | 40 | animation = [[NSViewAnimation alloc] initWithViewAnimations: 41 | [NSArray arrayWithObject:[NSDictionary dictionaryWithObjectsAndKeys: 42 | self, NSViewAnimationTargetKey, 43 | [NSValue valueWithRect:frameRect], NSViewAnimationEndFrameKey, nil]]]; 44 | 45 | [animation setDuration:duration]; 46 | [animation setAnimationBlockingMode:NSAnimationBlocking]; 47 | [animation setAnimationCurve:NSAnimationLinear]; 48 | [animation startAnimation]; 49 | 50 | [animation release]; 51 | } 52 | 53 | - (NSWindow *)_createZoomWindowWithRect:(NSRect)rect 54 | { 55 | NSWindow *zoomWindow; 56 | NSImageView *imageView; 57 | NSImage *image; 58 | NSRect frame; 59 | BOOL isOneShot; 60 | 61 | frame = [self frame]; 62 | 63 | isOneShot = [self isOneShot]; 64 | if (isOneShot) 65 | { 66 | [self setOneShot:NO]; 67 | } 68 | 69 | if ([self windowNumber] <= 0) 70 | { 71 | CGFloat alpha; 72 | 73 | // Force creation of window device by putting it on-screen. We make it transparent to minimize the chance of 74 | // visible flicker. 75 | alpha = [self alphaValue]; 76 | [self setAlphaValue:0.0]; 77 | [self orderBack:self]; 78 | [self orderOut:self]; 79 | [self setAlphaValue:alpha]; 80 | } 81 | 82 | image = [[NSImage alloc] initWithSize:frame.size]; 83 | [image lockFocus]; 84 | // Grab the window's pixels 85 | NSCopyBits([self gState], NSMakeRect(0.0, 0.0, frame.size.width, frame.size.height), NSZeroPoint); 86 | [image unlockFocus]; 87 | [image setDataRetained:YES]; 88 | [image setCacheMode:NSImageCacheNever]; 89 | 90 | zoomWindow = [[NSWindow alloc] initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; 91 | [zoomWindow setBackgroundColor:[NSColor colorWithDeviceWhite:0.0 alpha:0.0]]; 92 | [zoomWindow setHasShadow:[self hasShadow]]; 93 | [zoomWindow setLevel:[self level]]; 94 | [zoomWindow setOpaque:NO]; 95 | [zoomWindow setReleasedWhenClosed:YES]; 96 | [zoomWindow useOptimizedDrawing:YES]; 97 | 98 | imageView = [[NSImageView alloc] initWithFrame:[zoomWindow contentRectForFrameRect:frame]]; 99 | [imageView setImage:image]; 100 | [imageView setImageFrameStyle:NSImageFrameNone]; 101 | [imageView setImageScaling:NSScaleToFit]; 102 | [imageView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; 103 | 104 | [zoomWindow setContentView:imageView]; 105 | [image release]; 106 | [imageView release]; 107 | 108 | // Reset one shot flag 109 | [self setOneShot:isOneShot]; 110 | 111 | return zoomWindow; 112 | } 113 | 114 | - (void)zoomOnFromRect:(NSRect)startRect 115 | { 116 | NSRect frame; 117 | NSWindow *zoomWindow; 118 | 119 | if ([self isVisible]) 120 | { 121 | return; 122 | } 123 | 124 | frame = [self frame]; 125 | 126 | zoomWindow = [self _createZoomWindowWithRect:startRect]; 127 | 128 | [zoomWindow orderFront:self]; 129 | 130 | [zoomWindow animateToFrame:frame duration:[zoomWindow animationResizeTime:frame] * 0.4]; 131 | 132 | [self makeKeyAndOrderFront:self]; 133 | [zoomWindow close]; 134 | } 135 | 136 | - (void)zoomOffToRect:(NSRect)endRect 137 | { 138 | NSRect frame; 139 | NSWindow *zoomWindow; 140 | 141 | frame = [self frame]; 142 | 143 | if (![self isVisible]) 144 | { 145 | return; 146 | } 147 | 148 | zoomWindow = [self _createZoomWindowWithRect:frame]; 149 | 150 | [zoomWindow orderFront:self]; 151 | [self orderOut:self]; 152 | 153 | [zoomWindow animateToFrame:endRect duration:[zoomWindow animationResizeTime:endRect] * 0.4]; 154 | 155 | [zoomWindow close]; 156 | } 157 | 158 | @end 159 | -------------------------------------------------------------------------------- /NoodleCustomImageRep.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleCustomImageRep.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 3/16/11. 6 | // Copyright 2011 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | @class NoodleCustomImageRep; 32 | 33 | /* 34 | This image rep just provides a way to specify the image drawing via a block. 35 | 36 | For more details, check out the related blog post at http://www.noodlesoft.com/blog/2011/04/15/the-proper-care-and-feeding-of-nsimage 37 | */ 38 | @interface NoodleCustomImageRep : NSImageRep 39 | { 40 | void (^_drawBlock)(NoodleCustomImageRep *); 41 | } 42 | 43 | @property (readwrite, copy) void (^drawBlock)(NoodleCustomImageRep *rep); 44 | 45 | + (id)imageRepWithDrawBlock:(void (^)(NoodleCustomImageRep *))block; 46 | 47 | - (id)initWithDrawBlock:(void (^)(NoodleCustomImageRep *))block; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /NoodleCustomImageRep.m: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleCustomImageRep.m 3 | // ImageCacheTest 4 | // 5 | // Created by Paul Kim on 3/16/11. 6 | // Copyright 2011 Noodlesoft, LLC. All rights reserved. 7 | // 8 | 9 | #import "NoodleCustomImageRep.h" 10 | 11 | 12 | @implementation NoodleCustomImageRep 13 | 14 | @synthesize drawBlock = _drawBlock; 15 | 16 | + (id)imageRepWithDrawBlock:(void (^)(NoodleCustomImageRep *))block 17 | { 18 | return [[[[self class] alloc] initWithDrawBlock:block] autorelease]; 19 | } 20 | 21 | - (id)initWithDrawBlock:(void (^)(NoodleCustomImageRep *))block 22 | { 23 | if ((self = [super init]) != nil) 24 | { 25 | [self setDrawBlock:block]; 26 | } 27 | return self; 28 | } 29 | 30 | #pragma mark NSCopying method 31 | 32 | - (id)copyWithZone:(NSZone *)zone 33 | { 34 | NoodleCustomImageRep *copy; 35 | 36 | copy = [super copyWithZone:zone]; 37 | 38 | // NSImageRep uses NSCopyObject so we have to force a copy here 39 | copy->_drawBlock = [_drawBlock copy]; 40 | 41 | return copy; 42 | } 43 | 44 | - (void)dealloc 45 | { 46 | [self setDrawBlock:nil]; 47 | 48 | [super dealloc]; 49 | } 50 | 51 | #pragma mark NSImageRep methods 52 | 53 | - (BOOL)draw 54 | { 55 | if (_drawBlock != NULL) 56 | { 57 | _drawBlock(self); 58 | 59 | return YES; 60 | } 61 | return NO; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /NoodleGlue.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleBlockAction.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 6/30/10. 6 | // Copyright 2010 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import 27 | 28 | #if defined(NS_BLOCKS_AVAILABLE) && (NS_BLOCKS_AVAILABLE == 1) 29 | 30 | @class NoodleGlue; 31 | 32 | typedef void (^NoodleGlueBlock)(NoodleGlue *glue, id object); 33 | typedef void (^NoodleGlueCleanupBlock)(NoodleGlue *glue); 34 | 35 | /* 36 | In those cases where you need to pass some target object that will get some method called on it, instead of defining a 37 | new method or class to handle it, just use a block and stuff it into one of these objects and pass this object along 38 | instead. 39 | 40 | Common cases are for notifications (though blocks can be used there directly, you can provide a cleanup block here 41 | such that the object automatically unregisters itself from notifications when it is dealloc'ed/finalized). Can 42 | also be used with timers or other places that take a target/action. 43 | 44 | Things to be aware of: 45 | - Most of the time, you probably don't want this object retaining any objects it references (think about how much 46 | of the glue code you write operates). Use "__block" on any objects you don't want to be retained. 47 | - You still need to memory manage this object yourself. There's no magic about it. If you set it as a notification 48 | observer, you need to retain it somewhere because the notification center won't (or if using GC, keep a strong 49 | reference somewhere). 50 | 51 | For more details, check out the related blog post at http://www.noodlesoft.com/blog/2010/07/01/playing-with-nstimer/ 52 | */ 53 | @interface NoodleGlue : NSObject 54 | { 55 | NoodleGlueBlock _glueBlock; 56 | NoodleGlueCleanupBlock _cleanupBlock; 57 | } 58 | 59 | @property (readwrite, copy) NoodleGlueBlock glueBlock; 60 | @property (readwrite, copy) NoodleGlueCleanupBlock cleanupBlock; 61 | 62 | + (NoodleGlue *)glueWithBlock:(NoodleGlueBlock)glueBlock; 63 | + (NoodleGlue *)glueWithBlock:(NoodleGlueBlock)glueBlock cleanupBlock:(NoodleGlueCleanupBlock)cleanupBlock; 64 | 65 | // Initializes a glue object. glueBlock will be invoked when this object's -invoke: method is called with the argument 66 | // to -invoke: passed on as a parameter. cleanupBlock is invoked when this object is dealloc'ed/finalized with the 67 | // glue object being dealloc'ed sent in as a parameter. 68 | - (id)initWithBlock:(NoodleGlueBlock)glueBlock cleanupBlock:(NoodleGlueCleanupBlock)cleanupBlock; 69 | 70 | // Invokes the main block. When using this in a target/selector situation, use this as the selector. 71 | - (void)invoke:(id)object; 72 | 73 | @end 74 | 75 | /* 76 | NSObject category which, through the use of NoodleGlue and associative references, allows you to assign a block 77 | to be invoked when the object is deallocated. 78 | 79 | This code is more proof of concept than anything you'd want to use in production. For one, it's not threadsafe. 80 | 81 | For more details, check out the related blog post at http://www.noodlesoft.com/blog/2010/07/05/fun-with-glue/ 82 | */ 83 | @interface NSObject (NoodleCleanupGlue) 84 | 85 | // Sets a block to be invoked when the object is deallocated/collected. Will return an identifier that you can 86 | // use to remove the block later. Note that you need to retain this identifier if you intend to use it later. 87 | // Also, treat the identifier as an opaque object. Its actual type/formatting/structure may change in future 88 | // versions. 89 | - (id)addCleanupBlock:(void (^)(id object))block; 90 | 91 | // Removes the cleanup block using the identifier returned from a previous call to -addCleanupBlock: 92 | - (void)removeCleanupBlock:(id)identifier; 93 | 94 | @end 95 | 96 | 97 | #endif -------------------------------------------------------------------------------- /NoodleGlue.m: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleBlockAction.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 6/30/10. 6 | // Copyright 2010 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | 26 | #import "NoodleGlue.h" 27 | #import 28 | 29 | #if defined(NS_BLOCKS_AVAILABLE) && (NS_BLOCKS_AVAILABLE == 1) 30 | 31 | 32 | @implementation NoodleGlue 33 | 34 | @synthesize glueBlock = _glueBlock; 35 | @synthesize cleanupBlock = _cleanupBlock; 36 | 37 | + (NoodleGlue *)glueWithBlock:(NoodleGlueBlock)glueBlock 38 | { 39 | return [self glueWithBlock:glueBlock cleanupBlock:nil]; 40 | } 41 | 42 | + (NoodleGlue *)glueWithBlock:(NoodleGlueBlock)glueBlock cleanupBlock:(NoodleGlueCleanupBlock)cleanupBlock 43 | { 44 | return [[[NoodleGlue alloc] initWithBlock:glueBlock cleanupBlock:cleanupBlock] autorelease]; 45 | } 46 | 47 | - (id)initWithBlock:(NoodleGlueBlock)glueBlock cleanupBlock:(NoodleGlueCleanupBlock)cleanupBlock 48 | { 49 | if ((self = [super init]) != nil) 50 | { 51 | _glueBlock = [glueBlock copy]; 52 | _cleanupBlock = [cleanupBlock copy]; 53 | } 54 | return self; 55 | } 56 | 57 | - (void)dealloc 58 | { 59 | if (_cleanupBlock != NULL) 60 | { 61 | _cleanupBlock(self); 62 | } 63 | 64 | [_glueBlock release]; 65 | [_cleanupBlock release]; 66 | 67 | [super dealloc]; 68 | } 69 | 70 | - (void)finalize 71 | { 72 | if (_cleanupBlock != NULL) 73 | { 74 | _cleanupBlock(self); 75 | } 76 | 77 | [super finalize]; 78 | } 79 | 80 | - (void)invoke:(id)object 81 | { 82 | _glueBlock(self, object); 83 | } 84 | 85 | @end 86 | 87 | 88 | @implementation NSObject (NoodleCleanupGlue) 89 | 90 | static char cleanupGlueKey; 91 | 92 | - (id)addCleanupBlock:(void (^)(id object))block 93 | { 94 | NSMutableDictionary *glueTable; 95 | NoodleGlue *glue; 96 | __block __weak id blockSelf; 97 | id key; 98 | 99 | blockSelf = self; 100 | glue = [[NoodleGlue alloc] initWithBlock:nil 101 | cleanupBlock: 102 | ^(NoodleGlue *glue) 103 | { 104 | block(blockSelf); 105 | }]; 106 | 107 | 108 | glueTable = objc_getAssociatedObject(self, &cleanupGlueKey); 109 | 110 | if (glueTable == nil) 111 | { 112 | glueTable = [NSMutableDictionary dictionary]; 113 | objc_setAssociatedObject(self, &cleanupGlueKey, glueTable, OBJC_ASSOCIATION_RETAIN); 114 | } 115 | 116 | key = [NSString stringWithFormat:@"%p", glue]; 117 | [glueTable setObject:glue forKey:key]; 118 | 119 | [glue release]; 120 | 121 | return key; 122 | } 123 | 124 | - (void)removeCleanupBlock:(id)identifier 125 | { 126 | NSMutableDictionary *glueTable; 127 | 128 | glueTable = objc_getAssociatedObject(self, &cleanupGlueKey); 129 | 130 | if (glueTable != nil) 131 | { 132 | NoodleGlue *glue; 133 | 134 | glue = [glueTable objectForKey:identifier]; 135 | 136 | // Clear the cleanup block since we don't want it to be invoked when it gets released when it's removed 137 | // from the table 138 | [glue setCleanupBlock:nil]; 139 | [glueTable removeObjectForKey:identifier]; 140 | 141 | if ([glueTable count] == 0) 142 | { 143 | objc_setAssociatedObject(self, &cleanupGlueKey, nil, OBJC_ASSOCIATION_RETAIN); 144 | } 145 | } 146 | } 147 | 148 | @end 149 | 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /NoodleIPhoneTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleIPhoneTableView.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 9/22/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | /* 32 | Simulates UITableView's look and feel. 33 | 34 | For more details, see the related blog post at http://www.noodlesoft.com/blog/2009/09/25/sticky-section-headers-in-nstableview/ 35 | */ 36 | 37 | @interface NoodleIPhoneTableView : NSTableView 38 | { 39 | BOOL _isDrawingStickyRow; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /NoodleIPhoneTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleIPhoneTableView.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 9/22/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import "NoodleIPhoneTableView.h" 30 | #import "NSTableView-NoodleExtensions.h" 31 | 32 | @implementation NoodleIPhoneTableView 33 | 34 | - (id)initWithFrame:(NSRect)frameRect 35 | { 36 | if ((self = [super initWithFrame:frameRect]) != nil) 37 | { 38 | [self setGridColor:[NSColor colorWithCalibratedWhite:0.849 alpha:1.0]]; 39 | [self setGridStyleMask:NSTableViewSolidHorizontalGridLineMask]; 40 | } 41 | return self; 42 | } 43 | 44 | - (id)initWithCoder:(NSCoder *)decoder 45 | { 46 | if ((self = [super initWithCoder:decoder]) != nil) 47 | { 48 | [self setGridColor:[NSColor colorWithCalibratedWhite:0.849 alpha:1.0]]; 49 | [self setGridStyleMask:NSTableViewSolidHorizontalGridLineMask]; 50 | } 51 | return self; 52 | } 53 | 54 | - (void)drawRect:(NSRect)rect 55 | { 56 | [super drawRect:rect]; 57 | 58 | [self drawStickyRowHeader]; 59 | } 60 | 61 | // Since we are going to ensure that the regular and sticky versions of a row 62 | // look the same, no transition is needed here. 63 | - (NoodleStickyRowTransition)stickyRowHeaderTransition 64 | { 65 | return NoodleStickyRowTransitionNone; 66 | } 67 | 68 | - (void)drawRow:(NSInteger)rowIndex clipRect:(NSRect)clipRect 69 | { 70 | if ([self isRowSticky:rowIndex]) 71 | { 72 | NSRect rowRect, cellRect; 73 | NSUInteger colIndex, count; 74 | NSCell *cell; 75 | NSGradient *gradient; 76 | NSAttributedString *attrString; 77 | NSShadow *textShadow; 78 | NSBezierPath *path; 79 | NSDictionary *attributes; 80 | 81 | rowRect = [self rectOfRow:rowIndex]; 82 | 83 | if (!_isDrawingStickyRow) 84 | { 85 | // Note that NSTableView will still draw the special background that it does 86 | // for group row so we re-draw the background over it. 87 | [self drawBackgroundInClipRect:rowRect]; 88 | 89 | if (NSIntersectsRect(rowRect, [self stickyRowHeaderRect])) 90 | { 91 | // You can barely notice it but if the sticky view is showing, the actual 92 | // row it represents is still seen underneath. We check for this and don't 93 | // draw the row in such a case. 94 | return; 95 | } 96 | } 97 | 98 | gradient = [[NSGradient alloc] initWithStartingColor: 99 | [NSColor colorWithCalibratedRed:0.490 green:0.556 blue:0.600 alpha:0.9] 100 | endingColor:[NSColor colorWithCalibratedRed:0.665 green:0.706 blue:0.738 alpha:0.9]]; 101 | 102 | [gradient drawInRect:rowRect angle:90]; 103 | [gradient release]; 104 | 105 | textShadow = [[NSShadow alloc] init]; 106 | [textShadow setShadowOffset:NSMakeSize(1.0, -1.0)]; 107 | [textShadow setShadowColor:[NSColor colorWithCalibratedWhite:0.5 alpha:1.0]]; 108 | [textShadow setShadowBlurRadius:0.0]; 109 | 110 | attributes = [NSDictionary dictionaryWithObjectsAndKeys: 111 | [NSFont fontWithName:@"Helvetica-Bold" size:16.0], 112 | NSFontAttributeName, 113 | textShadow, 114 | NSShadowAttributeName, 115 | [NSColor whiteColor], 116 | NSForegroundColorAttributeName, 117 | nil]; 118 | [textShadow release]; 119 | 120 | count = [self numberOfColumns]; 121 | for (colIndex = 0; colIndex < count; colIndex++) 122 | { 123 | cell = [self preparedCellAtColumn:colIndex row:rowIndex]; 124 | 125 | attrString = [[NSAttributedString alloc] initWithString:[cell stringValue] attributes:attributes]; 126 | 127 | cellRect = [self frameOfCellAtColumn:colIndex row:rowIndex]; 128 | 129 | [cell setAttributedStringValue:attrString]; 130 | [cell drawWithFrame:cellRect inView:self]; 131 | 132 | [attrString release]; 133 | } 134 | 135 | [[NSColor colorWithCalibratedWhite:0.5 alpha:1.0] set]; 136 | path = [NSBezierPath bezierPath]; 137 | [path moveToPoint:NSMakePoint(NSMinX(rowRect), NSMinY(rowRect))]; 138 | [path lineToPoint:NSMakePoint(NSMaxX(rowRect), NSMinY(rowRect))]; 139 | [path moveToPoint:NSMakePoint(NSMinX(rowRect), NSMaxY(rowRect))]; 140 | [path lineToPoint:NSMakePoint(NSMaxX(rowRect), NSMaxY(rowRect))]; 141 | [path stroke]; 142 | } 143 | else 144 | { 145 | [super drawRow:rowIndex clipRect:clipRect]; 146 | } 147 | } 148 | 149 | - (void)drawStickyRow:(NSInteger)row clipRect:(NSRect)clipRect 150 | { 151 | _isDrawingStickyRow = YES; 152 | [self drawRow:row clipRect:clipRect]; 153 | _isDrawingStickyRow = NO; 154 | } 155 | 156 | 157 | @end 158 | -------------------------------------------------------------------------------- /NoodleKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NoodleKit_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'NoodleKit' target in the 'NoodleKit' project. 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /NoodleLineNumberMarker.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleLineNumberMarker.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 9/30/08. 6 | // Copyright (c) 2008 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import 31 | 32 | /* 33 | Marker for NoodleLineNumberView. 34 | 35 | For more details, see the related blog post at: http://www.noodlesoft.com/blog/2008/10/05/displaying-line-numbers-with-nstextview/ 36 | */ 37 | 38 | @interface NoodleLineNumberMarker : NSRulerMarker 39 | { 40 | NSUInteger _lineNumber; 41 | } 42 | 43 | - (id)initWithRulerView:(NSRulerView *)aRulerView lineNumber:(CGFloat)line image:(NSImage *)anImage imageOrigin:(NSPoint)imageOrigin; 44 | 45 | - (void)setLineNumber:(NSUInteger)line; 46 | - (NSUInteger)lineNumber; 47 | 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /NoodleLineNumberMarker.m: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleLineNumberMarker.m 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 9/30/08. 6 | // Copyright (c) 2008 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import "NoodleLineNumberMarker.h" 31 | 32 | 33 | @implementation NoodleLineNumberMarker 34 | 35 | - (id)initWithRulerView:(NSRulerView *)aRulerView lineNumber:(CGFloat)line image:(NSImage *)anImage imageOrigin:(NSPoint)imageOrigin 36 | { 37 | if ((self = [super initWithRulerView:aRulerView markerLocation:0.0 image:anImage imageOrigin:imageOrigin]) != nil) 38 | { 39 | _lineNumber = line; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)setLineNumber:(NSUInteger)line 45 | { 46 | _lineNumber = line; 47 | } 48 | 49 | - (NSUInteger)lineNumber 50 | { 51 | return _lineNumber; 52 | } 53 | 54 | #pragma mark NSCoding methods 55 | 56 | #define NOODLE_LINE_CODING_KEY @"line" 57 | 58 | - (id)initWithCoder:(NSCoder *)decoder 59 | { 60 | if ((self = [super initWithCoder:decoder]) != nil) 61 | { 62 | if ([decoder allowsKeyedCoding]) 63 | { 64 | _lineNumber = [[decoder decodeObjectForKey:NOODLE_LINE_CODING_KEY] unsignedIntegerValue]; 65 | } 66 | else 67 | { 68 | _lineNumber = [[decoder decodeObject] unsignedIntegerValue]; 69 | } 70 | } 71 | return self; 72 | } 73 | 74 | - (void)encodeWithCoder:(NSCoder *)encoder 75 | { 76 | [super encodeWithCoder:encoder]; 77 | 78 | if ([encoder allowsKeyedCoding]) 79 | { 80 | [encoder encodeObject:[NSNumber numberWithUnsignedInteger:_lineNumber] forKey:NOODLE_LINE_CODING_KEY]; 81 | } 82 | else 83 | { 84 | [encoder encodeObject:[NSNumber numberWithUnsignedInteger:_lineNumber]]; 85 | } 86 | } 87 | 88 | 89 | #pragma mark NSCopying methods 90 | 91 | - (id)copyWithZone:(NSZone *)zone 92 | { 93 | id copy; 94 | 95 | copy = [super copyWithZone:zone]; 96 | [copy setLineNumber:_lineNumber]; 97 | 98 | return copy; 99 | } 100 | 101 | 102 | @end 103 | -------------------------------------------------------------------------------- /NoodleLineNumberView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleLineNumberView.h 3 | // NoodleKit 4 | // 5 | // Created by Paul Kim on 9/28/08. 6 | // Copyright (c) 2008-2012 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | // 29 | 30 | #import 31 | 32 | /* 33 | Displays line numbers for an NSTextView. 34 | 35 | For more details, see the related blog post at: http://www.noodlesoft.com/blog/2008/10/05/displaying-line-numbers-with-nstextview/ 36 | */ 37 | 38 | @class NoodleLineNumberMarker; 39 | 40 | @interface NoodleLineNumberView : NSRulerView 41 | { 42 | // Array of character indices for the beginning of each line 43 | NSMutableArray *_lineIndices; 44 | // When text is edited, this is the start of the editing region. All line calculations after this point are invalid 45 | // and need to be recalculated. 46 | NSUInteger _invalidCharacterIndex; 47 | 48 | // Maps line numbers to markers 49 | NSMutableDictionary *_linesToMarkers; 50 | 51 | NSFont *_font; 52 | NSColor *_textColor; 53 | NSColor *_alternateTextColor; 54 | NSColor *_backgroundColor; 55 | } 56 | 57 | @property (readwrite, retain) NSFont *font; 58 | @property (readwrite, retain) NSColor *textColor; 59 | @property (readwrite, retain) NSColor *alternateTextColor; 60 | @property (readwrite, retain) NSColor *backgroundColor; 61 | 62 | - (id)initWithScrollView:(NSScrollView *)aScrollView; 63 | 64 | - (NSUInteger)lineNumberForLocation:(CGFloat)location; 65 | - (NoodleLineNumberMarker *)markerAtLine:(NSUInteger)line; 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /NoodleTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleRowSpanningTableView.h 3 | // NoodleRowSpanningTableViewTest 4 | // 5 | // Created by Paul Kim on 10/20/09. 6 | // Copyright 2009-2012 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import 30 | 31 | /* 32 | This NSTableView subclass provides a couple of neat features. 33 | 34 | Sticky Row Headers 35 | ================== 36 | This allows you to specify certain rows (it uses group rows by default) to "stick" to the top of the tableview 37 | scroll area when it is scrolled out of view. This is similar to how section headers work in UITableView in the 38 | iPhone SDK. The bulk of the implementation and delegate API are in the NSTableView-NoodleExtensions category. 39 | 40 | To enable this feature, just set the showsStickyRowHeader property. 41 | 42 | For more details, see the related blog post at http://www.noodlesoft.com/blog/2009/09/25/sticky-section-headers-in-nstableview/ 43 | 44 | 45 | Row Spanning Columns 46 | ==================== 47 | 48 | Row Spanning Columns are columns whose cells are allow to span across multiple rows. The span is determined by 49 | a contiguous section of rows that have the same object value. The cells within such a span are consolidated into 50 | a single special cell. An example of this can be seen in the Artwork column in iTunes. 51 | 52 | For any columns where you want to have this take effect, just change the column class to NoodleTableColumn and 53 | set the rowSpanningEnabled property on it. You can alternatively call -setRowSpanningEnabledForCapablyColumns 54 | on the tableview in your -awakeFromNib to enable all the NoodleTableColumns in your table in one fell swoop. 55 | 56 | For more details, see the related blog post at http://www.noodlesoft.com/blog/2009/10/20/yet-another-way-to-mimic-the-artwork-column-in-cocoa/ 57 | */ 58 | @interface NoodleTableView : NSTableView 59 | { 60 | BOOL _showsStickyRowHeader; 61 | 62 | BOOL _hasSpanningColumns; 63 | BOOL _isDrawingStickyRow; 64 | } 65 | 66 | @property (readwrite, assign) BOOL showsStickyRowHeader; 67 | 68 | #pragma mark Row Spanning methods 69 | 70 | /* 71 | Enables/disables row spanning for all NoodleTableColumns in the receiver. Note that row spanning is not enabled 72 | by default so if you want row spanning, call this from -awakeFromNib is a good idea. 73 | */ 74 | - (void)setRowSpanningEnabledForCapableColumns:(BOOL)flag; 75 | 76 | @end 77 | 78 | 79 | @class NoodleRowSpanningCell; 80 | 81 | /* 82 | Special table column that enables row spanning functionality. Just set your columns in IB to use this class and 83 | enable it by calling -setRowSpaningEnabled: 84 | */ 85 | @interface NoodleTableColumn :NSTableColumn 86 | { 87 | BOOL _spanningEnabled; 88 | NoodleRowSpanningCell *_cell; 89 | 90 | } 91 | 92 | @property (getter=isRowSpanningEnabled, setter=setRowSpanningEnabled:) BOOL rowSpanningEnabled; 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /NoodleTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NoodleRowSpanningTableView.m 3 | // NoodleRowSpanningTableViewTest 4 | // 5 | // Created by Paul Kim on 10/20/09. 6 | // Copyright 2009 Noodlesoft, LLC. All rights reserved. 7 | // 8 | // Permission is hereby granted, free of charge, to any person 9 | // obtaining a copy of this software and associated documentation 10 | // files (the "Software"), to deal in the Software without 11 | // restriction, including without limitation the rights to use, 12 | // copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | // copies of the Software, and to permit persons to whom the 14 | // Software is furnished to do so, subject to the following 15 | // conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | // OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | #import "NoodleTableView.h" 30 | #import "NSTableView-NoodleExtensions.h" 31 | #import "NSImage-NoodleExtensions.h" 32 | #import "NSIndexSet-NoodleExtensions.h" 33 | 34 | /* 35 | Internal cell class. Wraps around another cell. Draws the inner cell in its "full frame" but when drawing in 36 | the tableview, draws each row sliver from the full image. 37 | */ 38 | @interface NoodleRowSpanningCell : NSCell 39 | { 40 | NSRect _fullFrame; 41 | NSCell *_cell; 42 | NSImage *_cachedImage; 43 | NSColor *_backgroundColor; 44 | NSInteger _startIndex; 45 | NSInteger _lastStartIndex; 46 | NSInteger _endIndex; 47 | NSInteger _lastEndIndex; 48 | } 49 | 50 | @property NSRect fullFrame; 51 | @property (assign) NSCell *cell; 52 | @property (copy) NSColor *backgroundColor; 53 | @property NSInteger startIndex; 54 | @property NSInteger endIndex; 55 | 56 | @end 57 | 58 | @implementation NoodleRowSpanningCell 59 | 60 | @synthesize fullFrame = _fullFrame; 61 | @synthesize cell = _cell; 62 | @synthesize backgroundColor = _backgroundColor; 63 | @synthesize startIndex = _startIndex; 64 | @synthesize endIndex = _endIndex; 65 | 66 | - (void)_clearOutCaches 67 | { 68 | _startIndex = -1; 69 | _endIndex = -1; 70 | _lastStartIndex = -1; 71 | _lastEndIndex = -1; 72 | _cell = nil; 73 | } 74 | 75 | - (void)dealloc 76 | { 77 | [self _clearOutCaches]; 78 | [_backgroundColor release]; 79 | [_cachedImage release]; 80 | 81 | [super dealloc]; 82 | } 83 | 84 | - (id)copyWithZone:(NSZone *)zone 85 | { 86 | NoodleRowSpanningCell *copy; 87 | 88 | copy = [super copyWithZone:zone]; 89 | 90 | // super will copy the pointer across (via NSCopyObject()) but we need to retain or copy the actual instances 91 | copy->_cachedImage = [_cachedImage copy]; 92 | copy->_backgroundColor = [_backgroundColor copy]; 93 | 94 | return copy; 95 | } 96 | 97 | - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView 98 | { 99 | // Draw the full span of the cell into a cached image and then pull out the correct sliver as needed 100 | 101 | if ((_startIndex != _lastStartIndex) || (_endIndex != _lastEndIndex) || (_cachedImage == nil)) 102 | { 103 | // If the indices have changed, we are dealing with a new span so recache the cell's full image 104 | NSAffineTransform *transform; 105 | NSColor *color; 106 | 107 | if ((_cachedImage == nil) || !NSEqualSizes(_fullFrame.size, [_cachedImage size])) 108 | { 109 | [_cachedImage release]; 110 | _cachedImage = [[NSImage alloc] initWithSize:_fullFrame.size]; 111 | [_cachedImage setFlipped:[controlView isFlipped]]; 112 | } 113 | 114 | [_cachedImage lockFocus]; 115 | 116 | transform = [NSAffineTransform transform]; 117 | [transform translateXBy:-NSMinX(_fullFrame) yBy:-NSMinY(_fullFrame)]; 118 | [transform concat]; 119 | 120 | color = _backgroundColor; 121 | if (color == nil) 122 | { 123 | color = [NSColor clearColor]; 124 | } 125 | [color set]; 126 | NSRectFill(_fullFrame); 127 | 128 | [_cell drawWithFrame:_fullFrame inView:controlView]; 129 | 130 | [_cachedImage unlockFocus]; 131 | 132 | _lastStartIndex = _startIndex; 133 | _lastEndIndex = _endIndex; 134 | } 135 | 136 | // Now draw the sliver for the current row in the right spot 137 | [_cachedImage drawAdjustedInRect:cellFrame 138 | fromRect:NSMakeRect(NSMinX(cellFrame) - NSMinX(_fullFrame), 139 | NSMinY(cellFrame) - NSMinY(_fullFrame), 140 | NSWidth(cellFrame), NSHeight(cellFrame)) 141 | operation:NSCompositeSourceOver fraction:1.0]; 142 | } 143 | 144 | @end 145 | 146 | @implementation NoodleTableColumn : NSTableColumn 147 | 148 | @synthesize rowSpanningEnabled = _spanningEnabled; 149 | 150 | #define SPANNING_ENABLED_KEY @"spanningEnabled" 151 | 152 | - (void)encodeWithCoder:(NSCoder *)encoder 153 | { 154 | [super encodeWithCoder:encoder]; 155 | 156 | if ([encoder allowsKeyedCoding]) 157 | { 158 | [encoder encodeObject:[NSNumber numberWithBool:_spanningEnabled] forKey:SPANNING_ENABLED_KEY]; 159 | } 160 | else 161 | { 162 | [encoder encodeObject:[NSNumber numberWithBool:_spanningEnabled]]; 163 | } 164 | } 165 | 166 | - (id)initWithCoder:(NSCoder *)decoder 167 | { 168 | if ((self = [super initWithCoder:decoder]) != nil) 169 | { 170 | id value; 171 | 172 | if ([decoder allowsKeyedCoding]) 173 | { 174 | value = [decoder decodeObjectForKey:SPANNING_ENABLED_KEY]; 175 | } 176 | else 177 | { 178 | value = [decoder decodeObject]; 179 | } 180 | 181 | if (value != nil) 182 | { 183 | _spanningEnabled = [value boolValue]; 184 | } 185 | } 186 | return self; 187 | } 188 | 189 | - (void)dealloc 190 | { 191 | [_cell release]; 192 | [super dealloc]; 193 | } 194 | 195 | - (NoodleRowSpanningCell *)spanningCell 196 | { 197 | if (_cell == nil) 198 | { 199 | _cell = [[NoodleRowSpanningCell alloc] initTextCell:@""]; 200 | } 201 | return _cell; 202 | } 203 | 204 | @end 205 | 206 | 207 | @implementation NoodleTableView 208 | 209 | @synthesize showsStickyRowHeader = _showsStickyRowHeader; 210 | 211 | #pragma mark NSCoding methods 212 | 213 | #define SHOWS_STICKY_ROW_HEADER_KEY @"showsStickyRowHeader" 214 | 215 | - (void)encodeWithCoder:(NSCoder *)encoder 216 | { 217 | [super encodeWithCoder:encoder]; 218 | 219 | if ([encoder allowsKeyedCoding]) 220 | { 221 | [encoder encodeObject:[NSNumber numberWithBool:_showsStickyRowHeader] forKey:SHOWS_STICKY_ROW_HEADER_KEY]; 222 | } 223 | else 224 | { 225 | [encoder encodeObject:[NSNumber numberWithBool:_showsStickyRowHeader]]; 226 | } 227 | } 228 | 229 | - (id)initWithCoder:(NSCoder *)decoder 230 | { 231 | if ((self = [super initWithCoder:decoder]) != nil) 232 | { 233 | id value; 234 | 235 | if ([decoder allowsKeyedCoding]) 236 | { 237 | value = [decoder decodeObjectForKey:SHOWS_STICKY_ROW_HEADER_KEY]; 238 | } 239 | else 240 | { 241 | value = [decoder decodeObject]; 242 | } 243 | _showsStickyRowHeader = [value boolValue]; 244 | 245 | for (NSTableColumn *column in [self tableColumns]) 246 | { 247 | if ([column isKindOfClass:[NoodleTableColumn class]]) 248 | { 249 | _hasSpanningColumns = YES; 250 | break; 251 | } 252 | } 253 | } 254 | return self; 255 | } 256 | 257 | - (void)addTableColumn:(NSTableColumn *)column 258 | { 259 | [super addTableColumn:column]; 260 | 261 | if ([column isKindOfClass:[NoodleTableColumn class]]) 262 | { 263 | _hasSpanningColumns = YES; 264 | } 265 | } 266 | 267 | - (void)removeTableColumn:(NSTableColumn *)column 268 | { 269 | [super removeTableColumn:column]; 270 | 271 | for (NSTableColumn *column in [self tableColumns]) 272 | { 273 | if ([column isKindOfClass:[NoodleTableColumn class]]) 274 | { 275 | _hasSpanningColumns = YES; 276 | break; 277 | } 278 | } 279 | } 280 | 281 | #pragma mark Row Spanning methods 282 | 283 | - (void)setRowSpanningEnabledForCapableColumns:(BOOL)flag 284 | { 285 | for (id column in [self tableColumns]) 286 | { 287 | if ([column respondsToSelector:@selector(setRowSpanningEnabled:)]) 288 | { 289 | [column setRowSpanningEnabled:flag]; 290 | } 291 | } 292 | } 293 | 294 | // Does the actual work of drawing the grid. Originally, was trying to set the grid mask and calling super's 295 | // -drawGridInClipRect: method on specific regions to get the effect I wanted but all the setting of the masks 296 | // ended up sucking down CPU cycles as it got into a loop queueing up tons of redraw requests. 297 | - (void)_drawGrid:(NSUInteger)gridMask inClipRect:(NSRect)aRect 298 | { 299 | NSRect rect; 300 | 301 | [[self gridColor] set]; 302 | 303 | if ((gridMask & NSTableViewSolidHorizontalGridLineMask) != 0) 304 | { 305 | NSRange range; 306 | NSInteger i; 307 | 308 | range = [self rowsInRect:aRect]; 309 | for (i = range.location; i < NSMaxRange(range); i++) 310 | { 311 | rect = [self rectOfRow:i]; 312 | if (NSMaxY(rect) <= NSMaxY(aRect)) 313 | { 314 | rect.origin.x = NSMinX(aRect); 315 | rect.size.width = NSWidth(aRect); 316 | rect.origin.y -= 0.5; 317 | [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMinX(rect), NSMaxY(rect)) toPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))]; 318 | } 319 | } 320 | } 321 | if ((gridMask & NSTableViewSolidVerticalGridLineMask) != 0) 322 | { 323 | NoodleIndexSetEnumerator *enumerator; 324 | NSInteger i; 325 | 326 | enumerator = [[self columnIndexesInRect:aRect] indexEnumerator]; 327 | while ((i = [enumerator nextIndex]) != NSNotFound) 328 | { 329 | rect = [self rectOfColumn:i]; 330 | if (NSMaxX(rect) <= NSMaxX(aRect)) 331 | { 332 | rect.origin.y = NSMinY(aRect); 333 | rect.size.height = NSHeight(aRect); 334 | rect.origin.x -= 0.5; 335 | [NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(rect), NSMinY(rect)) toPoint:NSMakePoint(NSMaxX(rect), NSMaxY(rect))]; 336 | } 337 | } 338 | } 339 | } 340 | 341 | - (void)drawGridInClipRect:(NSRect)aRect 342 | { 343 | NSUInteger origGridMask; 344 | 345 | origGridMask = [self gridStyleMask]; 346 | 347 | if (_hasSpanningColumns && ((origGridMask & NSTableViewSolidHorizontalGridLineMask) != 0)) 348 | { 349 | // Rules: 350 | // - No spanning cell should have grid lines within it. Only at the bottom. 351 | // - Non-spanning cells inherit their grid lines from the closest spanning cell to the left (or to the right 352 | // if there are none to the left. 353 | 354 | NSRange range, spanRange; 355 | NSInteger columnIndex, endColumnIndex, startColumnIndex, row; 356 | NSMutableIndexSet *columnIndexes; 357 | NoodleIndexSetEnumerator *enumerator; 358 | NSRect topLeft, bottomRight, rect; 359 | 360 | // Grab the indexes of all the spanning columns. 361 | columnIndex = 0; 362 | columnIndexes = [NSMutableIndexSet indexSet]; 363 | for (NSTableColumn *column in [self tableColumns]) 364 | { 365 | if ([column isRowSpanningEnabled]) 366 | { 367 | [columnIndexes addIndex:columnIndex]; 368 | } 369 | columnIndex++; 370 | } 371 | 372 | // Hard to explain but we calculate regions going from left to right, defining regions horizontally 373 | // from one spanning column to the next and vertically by row spans. We first draw the non-horizontal 374 | // grid lines within a span (taking into account precedence rules concerning columns as noted above). 375 | // Then we draw the horizontal grid line at the bottom of a span. We are trying to find the maximal 376 | // regions to send to the grid drawing routine as doing it cell by cell incurs a bit of overhead. 377 | startColumnIndex = 0; 378 | enumerator = [columnIndexes indexEnumerator]; 379 | while ((columnIndex = [enumerator nextIndex]) != NSNotFound) 380 | { 381 | // This column is the right edge of this region (which is up to the next spanning column) 382 | endColumnIndex = [columnIndexes indexGreaterThanIndex:columnIndex]; 383 | if (endColumnIndex == NSNotFound) 384 | { 385 | endColumnIndex = [self numberOfColumns] - 1; 386 | } 387 | else 388 | { 389 | endColumnIndex--; 390 | } 391 | 392 | range = [self rowsInRect:aRect]; 393 | 394 | row = range.location; 395 | while (row < NSMaxRange(range)) 396 | { 397 | spanRange = [self rangeOfRowSpanAtColumn:columnIndex row:row]; 398 | 399 | // Get the rects of the top left of our region (the start column and start row of the span) 400 | // to the bottom right (the end column and the row in the span just before the last one). 401 | topLeft = [self frameOfCellAtColumn:startColumnIndex row:spanRange.location]; 402 | bottomRight = [self frameOfCellAtColumn:endColumnIndex row:NSMaxRange(spanRange) - 2]; 403 | 404 | rect = NSIntersectionRect(aRect, NSUnionRect(topLeft, bottomRight)); 405 | 406 | if (spanRange.length > 1) 407 | { 408 | // Draw span region without horizontal grid lines 409 | [self _drawGrid:origGridMask & ~NSTableViewSolidHorizontalGridLineMask inClipRect:rect]; 410 | 411 | // Now, calculate the region at the last row of the span 412 | topLeft = [self frameOfCellAtColumn:startColumnIndex row:NSMaxRange(spanRange) - 1]; 413 | bottomRight = [self frameOfCellAtColumn:endColumnIndex row:NSMaxRange(spanRange) - 1]; 414 | 415 | // Draw bottom of span with horizontal grid lines 416 | rect = NSIntersectionRect(aRect, NSUnionRect(topLeft, bottomRight) ); 417 | [self _drawGrid:origGridMask inClipRect:rect]; 418 | } 419 | else 420 | { 421 | // Not a span row or just a single row. Either way, draw with horizontal grid lines 422 | [self _drawGrid:origGridMask inClipRect:rect]; 423 | } 424 | // Advance to the next row span 425 | row = NSMaxRange(spanRange); 426 | } 427 | // Advance to the next span column region 428 | startColumnIndex = endColumnIndex + 1; 429 | } 430 | } 431 | else 432 | { 433 | // We only need the special logic when we have row spanning columns and drawing horizontal lines. Otherwise, 434 | // just call super. 435 | [super drawGridInClipRect:aRect]; 436 | } 437 | } 438 | 439 | - (NSCell *)preparedCellAtColumn:(NSInteger)columnIndex row:(NSInteger)rowIndex 440 | { 441 | NSTableColumn *column; 442 | 443 | column = [[self tableColumns] objectAtIndex:columnIndex]; 444 | 445 | if (!_isDrawingStickyRow && [column isRowSpanningEnabled]) 446 | { 447 | NSRange range; 448 | 449 | range = [self rangeOfRowSpanAtColumn:columnIndex row:rowIndex]; 450 | 451 | if (range.length >= 1) 452 | { 453 | // Here is where we insert our special cell for row spanning behavior 454 | NoodleRowSpanningCell *spanningCell; 455 | NSCell *cell; 456 | NSInteger start, end; 457 | BOOL wasSelected; 458 | 459 | start = range.location; 460 | end = NSMaxRange(range) - 1; 461 | 462 | // Want to draw cell in its unhighlighted state since spanning cells aren't selectable. Unfortuantely, 463 | // can't just setHighlight:NO on it because NSTableView sets other attributes (like text color). 464 | // Instead, we deselect the row, grab the cell, then set it back (if it was selected before). 465 | wasSelected = [self isRowSelected:start]; 466 | [self deselectRow:start]; 467 | 468 | cell = [super preparedCellAtColumn:columnIndex row:start]; 469 | 470 | if (wasSelected) 471 | { 472 | [self selectRowIndexes:[NSIndexSet indexSetWithIndex:start] byExtendingSelection:YES]; 473 | } 474 | 475 | spanningCell = [column spanningCell]; 476 | [spanningCell setCell:cell]; 477 | 478 | // The full frame is the rect encompassing the first and last rows of the span. 479 | [spanningCell setFullFrame:NSUnionRect([self frameOfCellAtColumn:columnIndex row:start], 480 | [self frameOfCellAtColumn:columnIndex row:end])]; 481 | [spanningCell setStartIndex:start]; 482 | [spanningCell setEndIndex:end]; 483 | 484 | [spanningCell setBackgroundColor:[self backgroundColor]]; 485 | 486 | return spanningCell; 487 | } 488 | } 489 | return [super preparedCellAtColumn:columnIndex row:rowIndex]; 490 | } 491 | 492 | - (void)mouseDown:(NSEvent *)event 493 | { 494 | if (_hasSpanningColumns) 495 | { 496 | // Eat up any clicks on spanning cells. In the future, may want to consider having clicks select the 497 | // first row in the span (like when clicking on the artwork in iTunes). 498 | 499 | NSPoint point; 500 | NSInteger columnIndex; 501 | NSTableColumn *column; 502 | 503 | point = [event locationInWindow]; 504 | point = [self convertPointFromBase:point]; 505 | 506 | columnIndex = [self columnAtPoint:point]; 507 | column = [[self tableColumns] objectAtIndex:columnIndex]; 508 | if ([column isRowSpanningEnabled]) 509 | { 510 | return; 511 | } 512 | } 513 | [super mouseDown:event]; 514 | } 515 | 516 | 517 | - (void)drawRect:(NSRect)dirtyRect 518 | { 519 | [super drawRect:dirtyRect]; 520 | 521 | // All that needs to be done to enable the sticky row header functionality (bulk of the work 522 | // done in the NSTableView category) 523 | if ([self showsStickyRowHeader]) 524 | { 525 | [self drawStickyRowHeader]; 526 | } 527 | 528 | if (_hasSpanningColumns) 529 | { 530 | // Clean up any cached data. Don't want to keep stale cache data around. 531 | for (NSTableColumn *column in [self tableColumns]) 532 | { 533 | if ([column isRowSpanningEnabled]) 534 | { 535 | [[column spanningCell] _clearOutCaches]; 536 | } 537 | } 538 | } 539 | } 540 | 541 | - (void)drawStickyRow:(NSInteger)row clipRect:(NSRect)clipRect 542 | { 543 | _isDrawingStickyRow = YES; 544 | [super drawStickyRow:row clipRect:clipRect]; 545 | _isDrawingStickyRow = NO; 546 | } 547 | 548 | @end 549 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NoodleKit 2 | ========= 3 | 4 | This is a random collection of classes and categories that I am making public. Most of this code has been posted on my blog: 5 | 6 | The project is primarily structured to build a framework. There are targets for various examples showing how the different classes are used. Some of the examples also contain a Read Me file so check those out for more details on the specific classes. 7 | 8 | This framework is meant to be built/used on 10.6 and later and should support 64-bit. 9 | 10 | This code is maintained at . Please post any issues and requests there. 11 | 12 | What nifty stuff is in here? 13 | ---------------------------- 14 | 15 | #### NSObject-NoodlePerformWhenIdle 16 | NSObject category for calling a method when the user has been idle for the specified amount of time. Useful for putting up non-critical alerts and purging memory caches, among other things. 17 | 18 | 19 | #### NSIndexSet-NoodleExtensions 20 | Provides an enumerator to cycle through the indexes in an NSIndexSet. Not featured directly in any blog article but used for the "Row Spanning Columns" feature (see below). 21 | 22 | #### NSTimer-NoodleExtensions 23 | Allows you to create timers that treat the fire date as absolute. Normally, NSTimer will adjust the time if you put the machine to sleep. This category makes it such that it will fire on the date you told it to originally. 24 | 25 | 26 | #### NoodleGlue 27 | Little class that allows you to plug a block into some code that requires a target/selector. Check the NSTimer category too see how it can be used. 28 | 29 | 30 | #### NSObject-NoodleCleanupGlue 31 | A category on NSObject that allows you to add a block that will be executed when the object is deallocated. It is based on NoodleGlue and it is lumped into the same source file with it. 32 | 33 | 34 | #### NSResponder-NoodleModalExtensions 35 | NSResponder category providing methods that will dismiss a dialog and return the proper code for whatever button (OK/Cancel) was clicked. Just hook your dialog buttons up to these methods in IB and you're set. Alleviates having to write that glue code every time. 36 | 37 | 38 | #### NSImage-NoodleExtensions 39 | NSImage category providing methods to draw NSImages with correct orientation and scaling regardless of the flipped status of the image or the context being drawn into. 40 | 41 | 42 | #### NoodleCustomImageRep 43 | NSImageRep subclass that allows you to specify the drawing via a block. Handy for drawing images without having to create a new subclass of NSImageRep. 44 | 45 | 46 | #### NSWindow-NoodleEffects 47 | Provides a basic zoom effect for NSWindow. 48 | 49 | 50 | 51 | #### NoodleLineNumberView, NoodleLineNumberMarker 52 | Adds line numbers (and corresponding markers) to NSTextView. 53 | 54 | 55 | #### NSTableView-NoodleExtensions, NoodleTableView, NoodleIPhoneTableView 56 | The NSTableView category and NoodleTableView are a consolidation of the sticky row header tableview 57 | and row spanning tableview featured on my blog. 58 | 59 | #####Sticky Row Headers 60 | An NSTableView category that does sticky row headers, like with UITableView on the iPhone. NoodleTableView implements the basic hooks to enable the feature while NoodleIPhoneTableView simulates the look and feel of UITableView. 61 | 62 | 63 | #####Row Spanning Columns 64 | Certain columns can be made to allow their cells to span across multiple rows. These spans are determined by contiguous sections of rows with the same object value. You can enable this in NoodleTableView by using NoodleTableColumns for any columns you want to exhibit this behavior. Remember to enable the property on each column or call -setRowSpanningEnabledForCapableColumns: to enable it for all NoodleTableColumns in the tableview. 65 | 66 | 67 | 68 | License 69 | ------- 70 | 71 | Copyright (c) 2007-2012 Noodlesoft, LLC. All Rights Reserved. 72 | 73 | Permission is hereby granted, free of charge, to any person 74 | obtaining a copy of this software and associated documentation 75 | files (the "Software"), to deal in the Software without 76 | restriction, including without limitation the rights to use, 77 | copy, modify, merge, publish, distribute, sublicense, and/or sell 78 | copies of the Software, and to permit persons to whom the 79 | Software is furnished to do so, subject to the following 80 | conditions: 81 | 82 | The above copyright notice and this permission notice shall be 83 | included in all copies or substantial portions of the Software. 84 | 85 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 87 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 88 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 89 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 91 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 92 | OTHER DEALINGS IN THE SOFTWARE. 93 | -------------------------------------------------------------------------------- /version.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildVersion 6 | 2 7 | CFBundleShortVersionString 8 | 1.0 9 | CFBundleVersion 10 | 1 11 | ProjectName 12 | DevToolsWizardTemplates 13 | SourceVersion 14 | 15920000 15 | 16 | 17 | --------------------------------------------------------------------------------