├── NSFont+Height.h ├── NSImage+Transform.zip ├── NSView+FocusRing.h ├── NSArray+CountedSet.h ├── NSData+HexString.h ├── NSMenuItem+MoreStates.h ├── NSArray+Reversing.h ├── NSString+DomainName.h ├── NSURL+FileDisplayName.h ├── NSURL+OAuth.h ├── NSData+SSYCryptoDigest.h ├── NSSegmentedControl+FitTextNice.h ├── NSHTTPURLResponse+Descriptions.h ├── NSPersistentStore+MoreDescrips.h ├── NSNumber+ChangeTypesSymbols.h ├── NSSet+SSYJsonClasses.h ├── NSString+Clipboard.h ├── NS(Attributed)String+Geometrics ├── English.lproj │ ├── InfoPlist.strings │ └── MainMenu.nib │ │ ├── keyedobjects.nib │ │ ├── info.nib │ │ └── classes.nib ├── NSView+GreenArrows.h ├── StringGeometricsDemo_Prefix.pch ├── main.m ├── StringMeasuringDemo.h └── Info.plist ├── NSArray+Whitespace.h ├── NSTextView+LineBreakControl.h ├── NSSplitView+Fraction.h ├── NSDictionary+Subdictionary.h ├── README.md ├── NSArray+CountedSet.m ├── NSArray+SafeGetters.h ├── NSMenu+Populating.h ├── NSData+Base64.h ├── NSURL+FileDisplayName.m ├── NSManagedObjectModel+Versions.h ├── NSMenuItem+MoreStates.m ├── SMLinkedView.h ├── NSIndexSet+MoreRanges.h ├── NSTableView+Scrolling.h ├── NSDate+Microsoft1601Epoch.h ├── NSString+OAuth.h ├── NSFont+Height.m ├── NSString+Data.h ├── NSNumber+SomeMore.m ├── NSArray+Integers.h ├── NSObject+SSYCheckType.h ├── NSWorkspace+AppleShoulda.h ├── NSMenu+Populating.m ├── NSTabView+Safe.m ├── NSMenu+RepresentMore.m ├── SSYTransformStringToAttributed.h ├── NSNumber+ChangeTypesSymbols.m ├── NSArray+Indexing.m ├── NSString+Base64.h ├── NSView+ActiveControl.m ├── NSMenu+RepresentMore.h ├── NSWindow+Screening.h ├── SSYTransformShortToBool.h ├── NSDocument+SSYAutosaveBetter.h ├── NSFileHandle+SSYExtras.h ├── NSHTTPURLResponse+Descriptions.m ├── NSArray+SSYPathUtils.h ├── NSOperationQueue+Depends.m ├── NSTabView+Safe.h ├── NSWindow+Screening.m ├── NSNumber+BooleanDisplay.h ├── NSArray+Select1.m ├── NSData+MBBase64.h ├── SMViewLinking.m ├── NSArray+Reversing.m ├── NSBundle+AppIcon.h ├── NSView+ActiveControl.h ├── SSYTransformMaxUIntegerToEmptyString.h ├── NSView+SSYDarkMode.h ├── NSPersistentStore+MoreDescrips.m ├── NSSet+Identicalness.h ├── NSUndoManager+SSYAdds.h ├── NSNumber+SomeMore.h ├── NSObject+SSYBindingsHelp.m ├── NSOperationQueue+Depends.h ├── NSArray+Select1.h ├── NSData+HexCharStrings.h ├── NSPopUpButton+Populating.h ├── NSSet+Identicalness.m ├── NSArray+Indexing.h ├── NSInvocation+Nesting.h ├── NSArray+Integers.m ├── NSError+SuggestMountVolume.h ├── NSView+FocusRing.m ├── NSInvocation+MoreDescriptions.h ├── NSDictionary+Readable.h ├── NSNumber+BooleanDisplay.m ├── NSString+Data.m ├── NSBundle+MainApp.h ├── SSY+Countability.h ├── NSRunningApplication+SSYHideReliably.h ├── SSYTransformMaxUIntegerToEmptyString.m ├── NSData+Stream.h ├── NSPredicateEditor+AooleForgot.m ├── NSSet+Classify.m ├── NSMigrationManager+10_5_BugPatch.h ├── NSWindow+Sizing.h ├── NSString+VarArgs.h ├── License.txt ├── NSPredicate+SSYMore.h ├── NSMenu+Ancestry.m ├── NSArray+Whitespace.m ├── NSData+SockAddr.h ├── NSMenuItem+Font.h ├── NSDate+LongLong1970.h ├── NSString+MoreComparisons.m ├── SSYTransformShortToBool.m ├── NSEntityDescription+SSYMavericksBugFix.h ├── NSMenu+PopOntoView.h ├── NSDictionary+Subdictionary.m ├── NSObject+MoreDescriptions.h ├── NSString+RangeDebug.h ├── NSString+UserAgents.h ├── NSDictionary+Histogram.m ├── NSFileHandle+SSYExtras.m ├── NSDictionary+Histogram.h ├── NSString+TimeIntervals.h ├── NSData+SockAddr.m ├── NSArray+SafeGetters.m ├── NSBundle+HelperPaths.m ├── NSImage+Transform.h ├── NSDictionary+Readable.m ├── NSMenu+Ancestry.h ├── NSError+SuggestMountVolume.m ├── NSManagedObjectModel+Versions.m ├── NSFileManager+TempFile.m ├── NSDictionary+DoNil.m ├── NSMenu+PopOntoView.m ├── NSTextView+Configurations.h ├── NSEntityDescription+SSYMavericksBugFix.m ├── NSPredicateEditor+AppleForgot.h ├── NSString+MoreComparisons.h ├── CategoriesObjCTests └── Info.plist ├── NSTextView+LineBreakControl.m ├── NSInvocation+Nesting.m ├── NSString+Clipboard.m ├── SSYTransformStringToAttributed.m ├── NSDate+LongLong1970.m ├── NSString+DomainName.m ├── NSData+Stream.m ├── NSSet+SSYJsonClasses.m ├── NSURL+OAuth.m ├── NSObject+ScriptingPatches.h ├── NSObject+SSYCheckType.m ├── NSString+SSYDotSuffix.m ├── SSYTransformCollectionEmptiness.m ├── NSDictionary+DoNil.h ├── NSView+SSYDarkMode.m ├── NSObject+SSYBindingsHelp.h ├── NSArray+SSYPathUtils.m ├── NSData+HexCharStrings.m ├── NSImage+Merge.h ├── SSYTransformCollectionEmptiness.h ├── NSPopUpButton+Populating.m ├── NSBundle+AppIcon.m ├── NSDocument+SSYAutosaveBetter.m ├── NSSegmentedControl+FitTextNice.m ├── NSFileManager+TempFile.h ├── NSSet+Classify.h ├── NSIndexSet+MoreRanges.m ├── NSProcessInfo+SSYMoreInfo.h ├── NSScanner+GeeWhiz.m ├── NSFileManager+SSYFileDescriptor.h ├── NSString+SSYDotSuffix.h ├── NSUserDefaults+MoreTypes.h ├── NSMenuItem+Font.m ├── NSManagedObject+Attributes.m ├── NSTableView+ContextMenu.h ├── NSTextView+Configurations.m ├── NSDate+NiceFormats.h ├── NSHelpManager+HelpForHelp.h ├── NSSet+SimpleMutations.m ├── NSString+SSYCaseness.m ├── NSTableView+Autosave.h ├── NSDate+SafeCompare.h ├── NSData+SSYCryptoDigest.m ├── NSDocumentController+FrontOrder.h ├── NSBundle+SSYMotherApp.h ├── NSError+LowLevel.h ├── NSArray+SortDescriptorsHelp.h ├── NSDate+Microsoft1601Epoch.m ├── NSSet+SimpleMutations.h ├── NSHelpManager+HelpForHelp.m ├── NSDocumentController+MoreRecents.h ├── NSArray+SortDescriptorsHelp.m ├── NSObject+DoNil.m ├── NSString+TimeIntervals.m ├── NSTableView+Scrolling.m ├── NSError+MyDomain.h ├── NSData+HexString.m ├── NSScanner+GeeWhiz.h ├── NSData+Crypt.h ├── NSObject+RecklessPerformSelector.h ├── NSDocumentController+MoreRecents.m ├── NSCountedSet+Votes.h ├── NSUserDefaults+SSYOtherApps.h ├── NSString+RangeDebug.m ├── NSDate+SafeCompare.m ├── NSArray+SSYDisjoint.h ├── NSBundle+SSYMotherApp.m ├── NSString+OAuth.m ├── NSTableView+ContextMenu.m ├── NSPredicate+SSYMore.m ├── NSObject+RecklessPerformSelector.m ├── NSTabViewItem+SSYTabHierarchy.h ├── NSCountedSet+Votes.m ├── NSManagedObject+Debug.h ├── NSData+Base64.m ├── NSObject+DoNil.h ├── NSImage+SSYDarkMode.h ├── NSManagedObject+Attributes.h ├── NSRunningApplication+SSYHideReliably.m ├── NSArray+SSYDisjoint.m ├── NSTableView+MoreSizing.h ├── NSCharacterSet+SSYMoreCS.h ├── NSBundle+HelperPaths.h ├── NSMutableSet+CoreDataOrderGlue.h ├── NSString+RSS.h ├── NSError+MyDomain.m ├── NSString+SSYCaseness.h ├── NSManagedObject+Debug.m ├── NSBundle+MainApp.m ├── NSFileManager+SSYObscureShackles.h ├── NSUserDefaults+MoreTypes.m ├── NSDocumentController+SSYFixLaunchServicesBug.m └── NSDate+NiceFormats.m /NSFont+Height.h: -------------------------------------------------------------------------------- 1 | 2 | @interface NSFont (Height) 3 | 4 | - (CGFloat)tableRowHeight ; 5 | 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /NSImage+Transform.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrykrinock/CategoriesObjC/HEAD/NSImage+Transform.zip -------------------------------------------------------------------------------- /NSView+FocusRing.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSView (FocusRing) 4 | 5 | - (void)drawFocusRing ; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /NSArray+CountedSet.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSArray (CountedSet) 5 | 6 | - (NSCountedSet*)countedSet ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSData+HexString.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSData (HexString) 5 | 6 | - (NSString*)lowercaseHexString ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSMenuItem+MoreStates.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSMenuItem (MoreStates) 5 | 6 | - (void)toggleState ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSArray+Reversing.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSArray (Reversing) 5 | 6 | - (NSArray*)arrayByReversingOrder ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSString+DomainName.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSString (DomainName) 5 | 6 | - (BOOL)isValidLabelRFC1035 ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSURL+FileDisplayName.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSURL (FileDisplayName) 5 | 6 | - (NSString*)fileDisplayName ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSURL+OAuth.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSURL (OAuth) 5 | 6 | - (NSString*)normalizedUrlForOAuth ; 7 | 8 | @end 9 | 10 | -------------------------------------------------------------------------------- /NSData+SSYCryptoDigest.h: -------------------------------------------------------------------------------- 1 | @interface NSData (SSYCryptoDigest) 2 | 3 | - (NSData *)md5Digest; 4 | - (NSData *)sha1Digest; 5 | - (NSData *)sha256Digest; 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /NSSegmentedControl+FitTextNice.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSSegmentedControl (FitTextNice) 5 | 6 | - (void)fitTextNice ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSHTTPURLResponse+Descriptions.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSHTTPURLResponse (Descriptions) 5 | 6 | - (NSString*)longDescription ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSPersistentStore+MoreDescrips.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "NSObject+MoreDescriptions.h" 3 | 4 | @interface NSPersistentStore (MoreDescrips) 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /NSNumber+ChangeTypesSymbols.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSNumber (ChangeTypesSymbols) 5 | 6 | - (NSString*)changeTypeDisplaySymbol ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSSet+SSYJsonClasses.h: -------------------------------------------------------------------------------- 1 | NS_ASSUME_NONNULL_BEGIN 2 | 3 | @interface NSSet (SSYJsonClasses) 4 | 5 | + (NSSet*)jsonClasses ; 6 | 7 | @end 8 | 9 | NS_ASSUME_NONNULL_END 10 | -------------------------------------------------------------------------------- /NSString+Clipboard.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSString (Clipboard) 5 | 6 | + (NSString*)clipboard ; 7 | 8 | - (void)copyToClipboard ; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/English.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrykrinock/CategoriesObjC/HEAD/NS(Attributed)String+Geometrics/English.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /NSArray+Whitespace.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSArray (Whitespace) 5 | 6 | - (NSArray*)arrayByTrimmingWhitespaceFromStringsAndRemovingEmptyStrings ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSTextView+LineBreakControl.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSTextView (LineBreakControl) 5 | 6 | - (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /NSSplitView+Fraction.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSSplitView (Fraction) 5 | 6 | - (CGFloat)fraction; 7 | - (void)setFraction:(CGFloat)newFract; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /NSDictionary+Subdictionary.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSDictionary (Subdictionary) 5 | 6 | - (NSDictionary*)subdictionaryWithKeys:(NSArray*)keys ; 7 | 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/English.lproj/MainMenu.nib/keyedobjects.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jerrykrinock/CategoriesObjC/HEAD/NS(Attributed)String+Geometrics/English.lproj/MainMenu.nib/keyedobjects.nib -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/NSView+GreenArrows.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSView (ShowDimensions) 5 | 6 | - (void)showGreenArrowsWithHeight:(float)overallHeight ; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ClassesObjC 2 | =========== 3 | 4 | My general-purpose Objective-C categories which I use in various projects. Now requires macOS 10.12 or later. For older projects, use branch FrozenForMacOS10.10. 5 | -------------------------------------------------------------------------------- /NSArray+CountedSet.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+CountedSet.h" 2 | 3 | 4 | @implementation NSArray (CountedSet) 5 | 6 | - (NSCountedSet*)countedSet { 7 | return [[[NSCountedSet alloc] initWithArray:self] autorelease] ; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /NSArray+SafeGetters.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSArray (SafeGetters) 5 | 6 | - (id)firstObjectSafely ; 7 | 8 | - (id)lastObjectSafely ; 9 | 10 | - (id)objectSafelyAtIndex:(NSInteger)index ; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /NSMenu+Populating.h: -------------------------------------------------------------------------------- 1 | #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060) 2 | 3 | @interface NSMenu (Populating) 4 | 5 | /*! 6 | @details Apple added this method in macOS 10.6 7 | */ 8 | - (void)removeAllItems ; 9 | 10 | @end 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /NSData+Base64.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSData (Base64) 4 | 5 | - (NSData*)dataBase64Encoded ; 6 | - (NSData*)dataBase64Decoded ; 7 | - (NSString*)stringBase64Encoded ; 8 | - (NSString*)stringBase64Decoded ; 9 | 10 | @end 11 | 12 | -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/StringGeometricsDemo_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'StringDrawingMeasurer2' target in the 'StringDrawingMeasurer2' project 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | -------------------------------------------------------------------------------- /NSURL+FileDisplayName.m: -------------------------------------------------------------------------------- 1 | #import "NSURL+FileDisplayName.h" 2 | 3 | 4 | @implementation NSURL (FileDisplayName) 5 | 6 | - (NSString*)fileDisplayName { 7 | return [[[self path] lastPathComponent] stringByDeletingPathExtension] ; 8 | } 9 | 10 | @end 11 | 12 | -------------------------------------------------------------------------------- /NSManagedObjectModel+Versions.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSManagedObjectModel (Versions) 5 | 6 | + (NSManagedObjectModel*)managedObjectModelWithMomdName:(NSString*)momdName 7 | versionName:(NSString*)versionName ; 8 | 9 | @end 10 | -------------------------------------------------------------------------------- /NSMenuItem+MoreStates.m: -------------------------------------------------------------------------------- 1 | #import "NSMenuItem+MoreStates.h" 2 | 3 | 4 | @implementation NSMenuItem (MoreStates) 5 | 6 | - (void)toggleState { 7 | [self setState:([self state] == NSControlStateValueOn) ? NSControlStateValueOff : NSControlStateValueOn] ; 8 | } 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /SMLinkedView.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "SMViewLinking.h" 3 | 4 | 5 | @interface SMLinkedView : NSView { 6 | NSMutableArray *linkedViews; 7 | SMViewLinkingLinkedResizingMask linkedResizingMask; 8 | NSSize linkedMinSize, linkedMaxSize; 9 | } 10 | @end 11 | -------------------------------------------------------------------------------- /NSIndexSet+MoreRanges.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSIndexSet (MoreRanges) 5 | 6 | /*! 7 | @brief Returns an index set of indexes that are in the 8 | receiver and also are in a given range. 9 | */ 10 | - (NSIndexSet*)indexesInRange:(NSRange)range ; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /NSTableView+Scrolling.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSTableView (Scrolling) 4 | 5 | - (NSRect)visibleRowsRect ; 6 | 7 | - (void)scrollRowToTop:(NSInteger)row 8 | plusExtraPoints:(CGFloat)extraPoints ; 9 | 10 | - (void)scrollRowPoint:(NSPoint)point ; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /NSDate+Microsoft1601Epoch.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | extern NSTimeInterval const constIntMSWindowsTicksPerSecond ; 4 | 5 | @interface NSDate (Microsoft1601Epoch) 6 | 7 | + (NSDate*)dateWithMicrosecondsSince1601:(long long)ticks ; 8 | 9 | - (long long)microsecondsSince1601 ; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /NSString+OAuth.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSString (OAuth) 5 | 6 | - (NSString*)HMACSHA1SignatureWithSecret:(NSString *)secret ; 7 | 8 | + stringOAuthWithQueryDictionary:(NSDictionary*)dictionary ; 9 | 10 | - (NSString*)stringByPercentEscapeEncodingForOAuth ; 11 | 12 | @end 13 | 14 | -------------------------------------------------------------------------------- /NSFont+Height.m: -------------------------------------------------------------------------------- 1 | #import "NSFont+Height.h" 2 | 3 | @implementation NSFont (Height) 4 | 5 | - (CGFloat)tableRowHeight { 6 | NSLayoutManager* lm = [[NSLayoutManager alloc] init] ; 7 | CGFloat dlhff = [lm defaultLineHeightForFont:self] ; 8 | [lm release] ; 9 | return dlhff + 1.0 ; 10 | } 11 | 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NSString+Data.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (Data) 4 | 5 | // Convenience methods that Apple should have provided 6 | 7 | + (NSString*)stringWithData:(NSData*)data 8 | encoding:(NSStringEncoding)encoding ; 9 | 10 | + (NSString*)stringWithDataUTF8:(NSData*)data ; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /NSNumber+SomeMore.m: -------------------------------------------------------------------------------- 1 | #import "NSNumber+SomeMore.h" 2 | 3 | @implementation NSNumber (SomeMore) 4 | 5 | - (NSNumber*)negateBoolValue { 6 | return [NSNumber numberWithBool:![self boolValue]] ; 7 | } 8 | 9 | - (NSNumber*)plus1 { 10 | return [NSNumber numberWithInteger:([self integerValue] + 1)] ; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NSArray+Integers.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSArray (Integers) 5 | 6 | /*! 7 | @brief Returns an array of NSNumbers whose -integerValues 8 | span a given range, each value being one more than the previous 9 | value. 10 | */ 11 | + (NSArray*)arrayWithRange:(NSRange)range ; 12 | 13 | @end 14 | 15 | -------------------------------------------------------------------------------- /NSObject+SSYCheckType.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSObject (SSYCheckType) 4 | 5 | - (NSError*)errorIfNotClass:(Class)expectedClass 6 | code:(NSInteger)code 7 | label:(NSString*)label 8 | priorError:(NSError*)priorError ; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /NSWorkspace+AppleShoulda.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief Methods which Apple should have provided in NSWorkspace 6 | */ 7 | @interface NSWorkspace (AppleShoulda) 8 | 9 | + (NSString*)appNameForBundleIdentifier:(NSString*)bundleIdentifier ; 10 | 11 | - (NSArray*)mountedLocalVolumeNames ; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NSMenu+Populating.m: -------------------------------------------------------------------------------- 1 | #if (MAC_OS_X_VERSION_MIN_REQUIRED < 1060) 2 | 3 | @implementation NSMenu (Populating) 4 | 5 | - (void)removeAllItems { 6 | NSArray* items = [self itemArray] ; 7 | NSInteger i ; 8 | NSInteger N = [items count] ; 9 | for (i=N-1; i>=0; i--) { 10 | [self removeItemAtIndex:i] ; 11 | } 12 | } 13 | 14 | @end 15 | 16 | #endif -------------------------------------------------------------------------------- /NSTabView+Safe.m: -------------------------------------------------------------------------------- 1 | #import "NSTabView+Safe.h" 2 | 3 | 4 | @implementation NSTabView (Safe) 5 | 6 | - (void)selectTabViewItemSafelyWithIdentifier:(NSString*)identifier { 7 | NSInteger index = [self indexOfTabViewItemWithIdentifier:identifier] ; 8 | if (index != NSNotFound) { 9 | [self selectTabViewItemAtIndex:index] ; 10 | } 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NSMenu+RepresentMore.m: -------------------------------------------------------------------------------- 1 | #import "NSMenu+RepresentMore.h" 2 | 3 | @implementation NSMenu (RepresentMore) 4 | 5 | - (NSMenuItem*)itemWithRepresentedObject:(id)object { 6 | for (NSMenuItem* item in [self itemArray]) { 7 | if ([object isEqual:[item representedObject]]) { 8 | return item ; 9 | } 10 | } 11 | 12 | return nil ; 13 | } 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SSYTransformStringToAttributed.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief A no-frills transformer for binding to NSTextView 6 | (which requires an attributed string) to a data model property 7 | which is a regular, non-attributed NSString. 8 | */ 9 | @interface SSYTransformStringToAttributed : NSValueTransformer { 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // StringDrawingMeasurer 4 | // 5 | // Created by Jerry Krinock on 07/06/14. 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 | -------------------------------------------------------------------------------- /NSNumber+ChangeTypesSymbols.m: -------------------------------------------------------------------------------- 1 | #import "NSNumber+ChangeTypesSymbols.h" 2 | #import "NSString+LocalizeSSY.h" 3 | #import "SSYModelChangeTypes.h" 4 | 5 | @implementation NSNumber (ChangeTypesSymbols) 6 | 7 | - (NSString*)changeTypeDisplaySymbol { 8 | return [SSYModelChangeTypes symbolForAction:(SSYModelChangeAction)[self integerValue]] ; 9 | } 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /NSArray+Indexing.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+Indexing.h" 2 | #import "SSYIndexee.h" 3 | 4 | @implementation NSArray (Indexing) 5 | 6 | - (void)fixIndexesContiguousStartingAtIndex:(NSInteger)index { 7 | for (NSInteger i=index; i<[self count]; i++) { 8 | [(NSObject *)[self objectAtIndex:i] setIndex:[NSNumber numberWithInteger:i]] ; 9 | } 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /NSString+Base64.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (Base64) 4 | 5 | - (NSString*)stringBase64Encoded ; 6 | - (NSString*)stringBase64Decoded ; 7 | - (NSData*)dataBase64Encoded ; 8 | - (NSData*)dataBase64Decoded ; 9 | 10 | 11 | + (NSCharacterSet*)base64CharacterSet ; 12 | // This is actually 65 characters because it includes the filler "=" 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /NSView+ActiveControl.m: -------------------------------------------------------------------------------- 1 | @implementation NSView (ActiveControl) 2 | 3 | - (BOOL)isTheActiveControl { 4 | NSWindow* window_ = [self window] ; 5 | if (![window_ isMainWindow]) { 6 | return NO ; 7 | } 8 | if (![window_ isKeyWindow]) { 9 | return NO ; 10 | } 11 | if ([window_ firstResponder] != self) { 12 | return NO ; 13 | } 14 | 15 | return YES ; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /NSMenu+RepresentMore.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSMenu (RepresentMore) 5 | 6 | /*! 7 | @brief Iterates through the receiver's -itemArray and returns 8 | the item whose representedObject -isEqual to a given object 9 | 10 | @details If no such object is found, returns nil 11 | */ 12 | - (NSMenuItem*)itemWithRepresentedObject:(id)object ; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /NSWindow+Screening.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /* 4 | @brief Replacement for deprecated -convertScreenToBase: 5 | @details Why does Apple deprecate stuff you need without providing a 6 | replacement? I do agree that -convertScreenToBase: was poorly named! */ 7 | @interface NSWindow (Screening) 8 | 9 | - (NSPoint)pointFromScreenPoint:(NSPoint)screenPoint ; 10 | 11 | @end 12 | -------------------------------------------------------------------------------- /SSYTransformShortToBool.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /*! 4 | @brief A transformer to transform a short to a BOOL, because 5 | I don't trust whatever bindings might do.  Transforms 6 | a value <= 0 to a NO and a >0 to YES.  Reverse-transforms 7 | YES to 1 and NO to 0. 8 | */ 9 | @interface SSYTransformShortToBool : NSValueTransformer { 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /NSDocument+SSYAutosaveBetter.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | extern NSString* SSYDontAutosaveKey ; 4 | 5 | @interface NSDocument (SSYAutosaveBetter) 6 | 7 | /*! 8 | @brief Like -[NSDocument isInViewingMode], except returns YES if receiver's 9 | fileURL's path contains "/Backups.backupdb/" or "/.DocumentRevisions-V100/" 10 | */ 11 | - (BOOL)ssy_isInViewingMode ; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NSFileHandle+SSYExtras.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSFileHandle (SSYExtras) 5 | 6 | /*! 7 | @brief An augmentation upon +fileHandleForWritingAtPath: which first 8 | creates the file if it does not exist, or clears out all existing data 9 | if the file does exist 10 | */ 11 | + (NSFileHandle*)clearateFileHandleForWritingAtPath:(NSString*)path ; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NSHTTPURLResponse+Descriptions.m: -------------------------------------------------------------------------------- 1 | #import "NSHTTPURLResponse+Descriptions.h" 2 | 3 | 4 | @implementation NSHTTPURLResponse (Descriptions) 5 | 6 | - (NSString*)longDescription { 7 | return [NSString stringWithFormat: 8 | @"<%@: %p statusCode=%ld Headers: \n%@>\n", 9 | [self class], 10 | self, 11 | (long)[self statusCode], 12 | [self allHeaderFields]] ; 13 | } 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NSArray+SSYPathUtils.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSArray (SSYPathUtils) 4 | 5 | /*! 6 | @brief Returns an array of strings, interpreted as filesystem paths, 7 | which replicates the receiver except that any members (paths) which are 8 | descendants of other paths in the array have been removed. 9 | */ 10 | - (NSArray*)pathsByRemovingDescendants ; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /NSOperationQueue+Depends.m: -------------------------------------------------------------------------------- 1 | #import "NSOperationQueue+Depends.h" 2 | 3 | 4 | @implementation NSOperationQueue (Depends) 5 | 6 | - (void)addAtEndOperation:(NSOperation*)operation { 7 | NSOperation* priorOperation = [[self operations] lastObject] ; 8 | if (priorOperation) { 9 | [operation addDependency:priorOperation] ; 10 | } 11 | 12 | [self addOperation:operation] ; 13 | } 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NSTabView+Safe.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSTabView (Safe) 5 | 6 | /*! 7 | @brief A safer version of selectTabViewItemWithIdentifier: which 8 | performs no-op if the a tab view item with the given identifier does 9 | not exist in the receiver, instead of raising an exception. 10 | */ 11 | - (void)selectTabViewItemSafelyWithIdentifier:(NSString*)identifier ; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NSWindow+Screening.m: -------------------------------------------------------------------------------- 1 | #import "NSWindow+Screening.h" 2 | 3 | @implementation NSWindow (Screening) 4 | 5 | - (NSPoint)pointFromScreenPoint:(NSPoint)screenPoint { 6 | NSRect screenRect = NSZeroRect ; 7 | screenRect.origin = screenPoint ; 8 | NSRect pointRect = [self convertRectFromScreen:screenRect] ; 9 | NSPoint windowPoint = pointRect.origin ; 10 | return windowPoint ; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NSNumber+BooleanDisplay.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief Category for transforming a Boolean value to a human-readable word 6 | */ 7 | @interface NSNumber (BooleanDisplay) 8 | 9 | /*! 10 | @brief Returns the localized word "Yes" if the -boolValue of the 11 | receiver is YES and "No" if the -boolValue of the receiver is NO. 12 | */ 13 | - (NSString*)booleanDisplayName ; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NSArray+Select1.m: -------------------------------------------------------------------------------- 1 | @implementation NSArray (Select1) 2 | 3 | - (id)select1 { 4 | id answer ; 5 | if ([self count] == 1) { 6 | answer = [self objectAtIndex:0] ; 7 | } 8 | else if ([self count] == 0) { 9 | answer = NSBindingSelectionMarker.noSelectionMarker ; 10 | } 11 | else { 12 | answer = NSBindingSelectionMarker.multipleValuesSelectionMarker ; 13 | } 14 | 15 | return answer ; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /NSData+MBBase64.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /* Code written by Milo, aka MiloBird, http://www.milobird.com/blog/ 4 | was copied from http://www.cocoadev.com/index.pl?BaseSixtyFour 5 | */ 6 | 7 | @interface NSData (MBBase64) 8 | 9 | + (id)dataByDecodingBase64String:(NSString *)string; 10 | // Padding '=' characters are optional. Whitespace is ignored. 11 | 12 | - (NSString *)stringEncodedBase64 ; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /SMViewLinking.m: -------------------------------------------------------------------------------- 1 | #import "SMViewLinking.h" 2 | 3 | 4 | // constants 5 | NSString *SMViewLinkingDestViewKeyName = 6 | @"SMViewLinkingDestViewKeyName"; 7 | NSString *SMViewLinkingSourceBorderKeyName = 8 | @"SMViewLinkingSourceBorderKeyName"; 9 | NSString *SMViewLinkingDestBorderKeyName = 10 | @"SMViewLinkingDestBorderKeyName"; 11 | NSString *SMViewLinkingDistanceKeyName = 12 | @"SMViewLinkingDistanceKeyName"; 13 | 14 | -------------------------------------------------------------------------------- /NSArray+Reversing.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+Reversing.h" 2 | 3 | @implementation NSArray (Reversing) 4 | 5 | - (NSArray*)arrayByReversingOrder { 6 | NSMutableArray* array = [[NSMutableArray alloc] init] ; 7 | for (id object in self) { 8 | [array insertObject:object 9 | atIndex:0] ; 10 | } 11 | 12 | NSArray* output = [array copy] ; 13 | [array release] ; 14 | 15 | return [output autorelease] ; 16 | } 17 | 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /NSBundle+AppIcon.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSBundle (AppIcon) 5 | 6 | /*! 7 | @brief Returns the path to the application's icon file, derived 8 | from the .icns file specified by "CFBundleIconFile" in the application's 9 | Info.plist. 10 | */ 11 | - (NSString*)appIconPath ; 12 | 13 | /*! 14 | @brief Returns the image in the file specified by -appIconPath. 15 | */ 16 | - (NSImage*)appIcon ; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /NSView+ActiveControl.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSView (ActiveControl) 5 | 6 | /*! 7 | @brief Returns YES if the receiver's window is the mainWindow, 8 | and is the keyWindow, and if the receiver is the first responder 9 | of its window. 10 | 11 | @details For example, use this to determine if the receiver 12 | should be highlighted or just secondary. 13 | */ 14 | - (BOOL)isTheActiveControl ; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /SSYTransformMaxUIntegerToEmptyString.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /*! 4 | @brief A nonreversible transformer which converts a number 5 | object created from an unsigned integer to the natural string 6 | representation of the integer. 7 | 8 | @details If the -unsignedIntValue of the input is NSUIntegerMax, 9 | returns an empty string. 10 | */ 11 | @interface SSYTransformMaxUIntegerToEmptyString : NSValueTransformer {} 12 | @end 13 | -------------------------------------------------------------------------------- /NSView+SSYDarkMode.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface NSView (SSYDarkMode) 6 | 7 | /*! 8 | @brief Returns indication of whether or not the receiver seems to be 9 | being viewed in macOS Dark Mode 10 | 11 | @details The _SSY suffix is in case Apple implements this in a future 12 | version of Cocoa. 13 | */ 14 | @property (readonly) BOOL isDarkMode_SSY; 15 | 16 | @end 17 | 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /NSPersistentStore+MoreDescrips.m: -------------------------------------------------------------------------------- 1 | #import "NSPersistentStore+MoreDescrips.h" 2 | 3 | 4 | @implementation NSPersistentStore (MoreDescrips) 5 | 6 | - (NSString*)longDescription { 7 | NSString* type = [self type] ; 8 | NSString* path = [[self URL] path] ; 9 | NSString* desc = [NSString stringWithFormat: 10 | @"<%@ %@>", 11 | type ? type : @"nil-store-type", 12 | path ? path : @"nil-store-path"] ; 13 | return desc ; 14 | } 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /NSSet+Identicalness.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSSet (Identicalness) 4 | 5 | /*! 6 | @brief Returns YES if the receiver and a given set have 7 | exactly the same members, meaning the same pointer values 8 | 9 | @details Apple's documentation of -isEqualToSet: states that objects are 10 | compared using -isEqual:. This method is for when you need a more strict 11 | comparison. 12 | */ 13 | - (BOOL)isIdenticalToSet:(NSSet*)set ; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /NSUndoManager+SSYAdds.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief This is my attempt to create an undo manager which 6 | whose undo action will be the first undo action set in an undo 7 | grouping. 8 | 9 | @details http://www.cocoabuilder.com/archive/message/cocoa/2005/8/22/144791 10 | 11 | I AM NOW USING SSYDooDooUndoManager INSTEAD OF THIS. IT IS SIMILAR. 12 | */ 13 | @interface NSUndoManager (SSYAdds) 14 | 15 | - (void)logUndoStack ; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /NSNumber+SomeMore.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSNumber (SomeMore) 5 | 6 | /*! 7 | @brief Returns a new NSNumber with a boolValue equal 8 | to the opposite of the boolValue of the receiver. 9 | */ 10 | - (NSNumber*)negateBoolValue ; 11 | 12 | /*! 13 | @brief Returns a new NSNumber whose integer value is one 14 | more than that of the receiver. 15 | 16 | @result An NSNumber made using +numberWithInt: 17 | */ 18 | - (NSNumber*)plus1 ; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /NSObject+SSYBindingsHelp.m: -------------------------------------------------------------------------------- 1 | #import "NSObject+SSYBindingsHelp.h" 2 | 3 | @implementation NSObject (SSYBindingsHelp) 4 | 5 | - (void)pushBindingValue:(id)value 6 | forKey:(NSString*)key { 7 | NSDictionary* bindingsInfo = [self infoForBinding:key] ; 8 | id object = [bindingsInfo objectForKey:NSObservedObjectKey] ; 9 | NSString* bindingsPath = [bindingsInfo objectForKey:NSObservedKeyPathKey] ; 10 | [object setValue:value 11 | forKeyPath:bindingsPath] ; 12 | } 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /NSOperationQueue+Depends.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSOperationQueue (Depends) 5 | 6 | /*! 7 | @brief Adds to the receiver's operation queue a new operation 8 | which will not begin until all operations currently in the queue 9 | have completed. 10 | 11 | @details Does this by setting the new operation to be dependent 12 | upon all existing operations before adding to the queue. 13 | */ 14 | - (void)addAtEndOperation:(NSOperation*)operation ; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /NSArray+Select1.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSArray (Select1) 5 | 6 | /*! 7 | @brief Returns the single member object if the receiver's 8 | count is 1; otherwise returns NSNoSelectionMarker or 9 | NSMultipleValuesMarker as applicable. 10 | 11 | @details This is useful in setting detail object values 12 | in a master-detail view, to show different placeholders, 13 | when the detail view's cell isSSYTokenFieldCell. 14 | */ 15 | - (id)select1 ; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /NSData+HexCharStrings.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSData(HexCharacterStrings) 4 | 5 | /*! 6 | @brief Method for converting a string containing hex character 7 | such as @"00a34f52 ff0001" to an NSData. 8 | 9 | @details Useful on the strings you get when you NSLog the 10 | description of an NSData, for reproducing bugs. 11 | @param string Some restrictions apply. See source code :) 12 | */ 13 | + (NSData*)dataWithHexCharacterString:(NSString*)string ; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /NSPopUpButton+Populating.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSPopUpButton (Populating) 5 | 6 | - (void)populateTitles:(NSArray*)titles 7 | target:(id)target 8 | action:(SEL)action ; 9 | 10 | // Because this category is also used in Bookdog, 11 | #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) 12 | 13 | /*! 14 | @brief Sets tag of first item in receiver's itemArray 15 | to 0, tag of 2nd item to 1, etc. 16 | */ 17 | - (void)tagItemsAsPositioned ; 18 | 19 | #endif 20 | 21 | @end 22 | 23 | -------------------------------------------------------------------------------- /NSSet+Identicalness.m: -------------------------------------------------------------------------------- 1 | #import "NSSet+Identicalness.h" 2 | 3 | @implementation NSSet (Identicalness) 4 | 5 | - (BOOL)isIdenticalToSet:(NSSet*)set { 6 | if ([self count] != [set count]) { 7 | return NO ; 8 | } 9 | for (id object in self) { 10 | id match = [set member:object] ; 11 | if (!match) { 12 | return NO ; 13 | } 14 | if (match != object) { 15 | return NO ; 16 | } 17 | } 18 | 19 | return YES ; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /NSArray+Indexing.h: -------------------------------------------------------------------------------- 1 | // 2 | #import 3 | 4 | 5 | @interface NSArray (Indexing) 6 | 7 | /*! 8 | @brief Iterates through an array whose objects each conform to the 9 | SSYIndexee protocol, starting with a given index, and sets the index 10 | attribute of each object to equal its index in the receiver. 11 | 12 | @param index The index of the first object whose index attribute 13 | may be modified. 14 | */ 15 | - (void)fixIndexesContiguousStartingAtIndex:(NSInteger)index ; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /NSInvocation+Nesting.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSInvocation (Nesting) 5 | 6 | /*! 7 | @brief Returns an invocation which, when invoked, will invoke, 8 | in order and one at a time, the invocations in a given array 9 | of invocations. 10 | */ 11 | + (NSInvocation*)invocationWithInvocations:(NSArray*)invocations ; 12 | 13 | /*! 14 | @brief Returns whether or not the receiver is a product of 15 | +invocationWithInvocations or equivalent. 16 | */ 17 | - (BOOL)hasEggs ; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /NSArray+Integers.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+Integers.h" 2 | 3 | 4 | @implementation NSArray (Integers) 5 | 6 | + (NSArray*)arrayWithRange:(NSRange)range { 7 | NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:range.length] ; 8 | NSInteger i ; 9 | for (i=range.location; i<(range.location + range.length); i++) { 10 | [array addObject:[NSNumber numberWithInteger:i]] ; 11 | } 12 | 13 | NSArray* answer = [[array copy] autorelease] ; 14 | [array release] ; 15 | 16 | return answer ; 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /NSError+SuggestMountVolume.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSError (SuggestMountVolume) 4 | 5 | /*! 6 | @brief If the receiver's -userInfo contains a value for key @"Path", 7 | and if this path begins with @"/Volumes/SomeVolume/", and if SomeVolume 8 | is apparently not mounted, returns a replica of the receiver with a 9 | localized recovery suggestion to mount SomeVolume added; otherwise, 10 | returns the receiver. 11 | */ 12 | - (NSError*)maybeAddMountVolumeRecoverySuggestion ; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /NSView+FocusRing.m: -------------------------------------------------------------------------------- 1 | @implementation NSView (FocusRing) 2 | 3 | - (void)drawFocusRing { 4 | [[NSColor keyboardFocusIndicatorColor] set]; 5 | NSRect rect = [self visibleRect] ; 6 | [NSGraphicsContext saveGraphicsState]; 7 | NSSetFocusRingStyle(NSFocusRingOnly); 8 | NSFrameRect(rect); 9 | [NSGraphicsContext restoreGraphicsState]; 10 | // The above code is from: 11 | // http://www.cocoabuilder.com/archive/message/cocoa/2003/4/7/88648 12 | // The remainder of that message applies to pre-Leopard only. 13 | } 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NSInvocation+MoreDescriptions.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSInvocation (MoreDescriptions) 5 | 6 | /*! 7 | @brief Like -description except appends the receiver's target, 8 | selector name and arguments.  Handy for debugging. 9 | 10 | @details The -shortDescription of the target and arguments 11 | are given. Otherwise, you'd have a "long long" description, 12 | which would usually be too long. 13 | */ 14 | - (NSString*)longDescription ; 15 | 16 | - (NSInteger)countOfArguments; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /NSDictionary+Readable.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSDictionary (Readable) 5 | 6 | /*! 7 | @brief Returns a string of the form "key: value", containing 8 | -description of key and value, separated by newlines. 9 | 10 | @details You can't use this in a binding key path, probably 11 | because bindings interprets the key path myDictionary.readable 12 | to be [myDictionary objectForKey:@"readable"]. For bindings, 13 | therefore, use SSYTransformDicToString 14 | */ 15 | - (NSString*)readable ; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /NSNumber+BooleanDisplay.m: -------------------------------------------------------------------------------- 1 | #import "NSNumber+BooleanDisplay.h" 2 | #import "NSString+LocalizeSSY.h" 3 | 4 | @implementation NSNumber (BooleanDisplay) 5 | 6 | - (NSString*)booleanDisplayName { 7 | BOOL boolValue = [self boolValue] ; 8 | NSString* string ; 9 | 10 | if (boolValue == YES) { 11 | string = [NSString localize:@"yes"]; 12 | } else if (boolValue == NO) { 13 | string = [NSString localize:@"no"]; 14 | } else { 15 | string = @""; 16 | } 17 | 18 | return string ; 19 | } 20 | 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /NSString+Data.m: -------------------------------------------------------------------------------- 1 | #import "NSString+Data.h" 2 | 3 | @implementation NSString (Data) 4 | 5 | + (NSString*)stringWithData:(NSData*)data 6 | encoding:(NSStringEncoding)encoding { 7 | if (!data) { 8 | return nil ; 9 | } 10 | return [[[NSString alloc] initWithData:data 11 | encoding:encoding] autorelease] ; 12 | } 13 | 14 | + (NSString*)stringWithDataUTF8:(NSData*)data { 15 | if (!data) { 16 | return nil ; 17 | } 18 | return [self stringWithData:data 19 | encoding:NSUTF8StringEncoding] ; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/English.lproj/MainMenu.nib/info.nib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IBFramework Version 6 | 629 7 | IBOldestOS 8 | 5 9 | IBOpenObjects 10 | 11 | IBSystem Version 12 | 9C31 13 | targetFramework 14 | IBCocoaFramework 15 | 16 | 17 | -------------------------------------------------------------------------------- /NSBundle+MainApp.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSBundle (MainApp) 4 | 5 | /*! 6 | @brief Returns the bundle of the outermost parent application (directory 7 | name ending in ".app") containing the currently-running main bundle 8 | 9 | @details Typically you use this in a helper app so you can get resources 10 | in the bundle of a main (parent) app. 11 | 12 | The answer is cached for efficiency. This assumes that the bundle will not 13 | move while running. This is a common assumption in macOS. 14 | */ 15 | + (NSBundle*)mainAppBundle ; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /SSY+Countability.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief A formal protocol which declares that, duh, 6 | NSArray and NSSet both implement -count. 7 | 8 | @details Methods that want a parameter to take either an NSArray or 9 | NSSet can declare it with type (NSObject *) 10 | */ 11 | @protocol SSYCountability 12 | 13 | - (NSInteger)count ; 14 | 15 | @end 16 | 17 | 18 | @interface NSArray (DeclareSSYCountability) 19 | 20 | @end 21 | 22 | @interface NSSet (DeclareSSYCountability) 23 | 24 | @end 25 | 26 | -------------------------------------------------------------------------------- /NSRunningApplication+SSYHideReliably.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSRunningApplication (SSYHideReliably) 4 | 5 | /*! 6 | @brief Repeatedly tells an application to hide, for a specified interval 7 | 8 | @details This is a wrapper around -[NSRunningApplication hide], which can 9 | be reliably sent immediately after the target app has been launched, and 10 | is useful for apps such as Google Chrome which seem to ignore the -g and -j 11 | arguments to /usr/bin/open. 12 | */ 13 | - (void)hideReliablyWithGuardInterval:(NSTimeInterval)guardInterval; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /SSYTransformMaxUIntegerToEmptyString.m: -------------------------------------------------------------------------------- 1 | #import "SSYTransformMaxUIntegerToEmptyString.h" 2 | 3 | 4 | @implementation SSYTransformMaxUIntegerToEmptyString 5 | 6 | + (Class)transformedValueClass { 7 | return [NSString class] ; 8 | } 9 | 10 | + (BOOL)allowsReverseTransformation { 11 | return NO ; 12 | } 13 | 14 | - (id)transformedValue:(id)number { 15 | if ([number unsignedIntegerValue] == NSUIntegerMax) { 16 | return @"" ; 17 | } 18 | else { 19 | return [NSString stringWithFormat:@"%qu", (unsigned long long)[number unsignedIntegerValue]] ; 20 | } 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /NSData+Stream.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /*! 4 | @brief A category for reading from and writing NSData 5 | objects to unix FILE streams such as the 'stdin', 'stdout' 6 | and 'stderr' macros defined in stdio.h. 7 | */ 8 | @interface NSData (Stream) 9 | 10 | /*! 11 | @brief Returns an autoreleased NSData object 12 | containing the bytes from a given stream. 13 | */ 14 | + (NSData*)dataWithStream:(FILE*)stream ; 15 | 16 | /*! 17 | @brief Writes the bytes of the receiver to a 18 | given stream. 19 | */ 20 | - (void)writeToStream:(FILE*)stream ; 21 | 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /NSPredicateEditor+AooleForgot.m: -------------------------------------------------------------------------------- 1 | #import "NSPredicateEditor+AppleForgot.h" 2 | #import "NSView+Layout.h" 3 | 4 | 5 | @implementation NSPredicateEditor (AppleForgot) 6 | 7 | - (void)changeRowCountTo:(NSInteger)count { 8 | // Remove all existing rows 9 | NSRange range = NSMakeRange(0, [self numberOfRows]) ; 10 | NSIndexSet* indexSet = [NSIndexSet indexSetWithIndexesInRange:range] ; 11 | [self removeRowsAtIndexes:indexSet 12 | includeSubrows:YES]; 13 | 14 | // Add new rows 15 | for (NSInteger i=0; i 2 | 3 | 4 | /*! 5 | @brief A Workaround for Core Data store migration in applications 6 | built on 10.6 but that must also run on 10.5 7 | 8 | @details See: 9 | "Workaround for Core Data store migration in applications built on 10 | 10.6 but that must also run on 10.5", available here: 11 | http://developer.apple.com/mac/library/releasenotes/Cocoa/MigrationCrashBuild106Run105/index.html 12 | 13 | This class is only required if building apps that must run on 10.5 in 10.6. 14 | */ 15 | @interface NSMigrationManager (_10_5_BugPatch) 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /NSWindow+Sizing.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSWindow (Sizing) 4 | 5 | - (void)setFrameToFitContentViewThenDisplay:(BOOL)display ; 6 | 7 | - (void)setFrameToFitContentThenDisplay:(BOOL)display ; 8 | 9 | #if 0 10 | /*! 11 | @brief Returns the current height of the window's toolbar 12 | 13 | @details 14 | @result 15 | */ 16 | - (CGFloat)toolbarHeight ; 17 | #endif 18 | 19 | /*! 20 | @brief Returns the current height of the window's title bar 21 | plus that of the window's toolbar 22 | 23 | @details 24 | @result 25 | */ 26 | - (CGFloat)tootlebarHeight ; 27 | 28 | @end 29 | 30 | -------------------------------------------------------------------------------- /NSString+VarArgs.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (VarArgs) 4 | 5 | - (NSInteger)countOccurrencesOfSubstring:(NSString*)substring ; 6 | 7 | /* 8 | Returns the count of % characters in the receiver, or the 9 | index of the highest placeholder + 1, (1-10), whichever is larger. 10 | */ 11 | - (NSInteger)countMaxPlaceholders ; 12 | 13 | + (NSString *)replacePlaceholdersInString:(NSString*)s 14 | argPtr_p:(va_list*)argPtr_p ; 15 | 16 | 17 | /* 18 | returns a string representation of the integer. Examples: "1" "42", "-579". 19 | */ 20 | + (NSString*)stringWithInt:(NSInteger)i ; 21 | 22 | @end 23 | 24 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | Copyright 2012 Jerome Krinock 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use the files in this repository except in compliance with the License. You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 8 | 9 | If you need a different license, let me know. -------------------------------------------------------------------------------- /NSPredicate+SSYMore.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSPredicate (SSYMore) 5 | 6 | /*! 7 | @brief Returns a predicate which is satisfied if the values 8 | of the subject object, for a given dictionary of keys, each equal 9 | the values in the dictionary 10 | */ 11 | + (NSPredicate*)andPredicateWithDictionary:(NSDictionary*)dictionary ; 12 | 13 | /*! 14 | @brief Returns a predicate which is satisfied if the value of a 15 | subject object for a given key equals one of the values in a given set 16 | */ 17 | + (NSPredicate*)orPredicateWithKeyPath:(NSString*)keyPath 18 | values:(NSSet*)values ; 19 | @end 20 | -------------------------------------------------------------------------------- /NSMenu+Ancestry.m: -------------------------------------------------------------------------------- 1 | #import "NSMenu+Ancestry.h" 2 | 3 | @implementation NSMenu (Ancestry) 4 | 5 | - (NSMenuItem*)supermenuItem { 6 | NSMenu* supermenu = [self supermenu] ; 7 | NSArray* items = [supermenu itemArray] ; 8 | NSMenuItem* supermenuItem = nil ; 9 | for (NSMenuItem* item in items) { 10 | if ([item submenu] == self) { 11 | supermenuItem = item ; 12 | break ; 13 | } 14 | } 15 | 16 | return supermenuItem ; 17 | } 18 | 19 | - (NSInteger)supertag { 20 | NSMenuItem* supermenuItem = [self supermenuItem] ; 21 | if (supermenuItem) { 22 | return [supermenuItem tag] ; 23 | } 24 | 25 | return NSNotFound ; 26 | } 27 | 28 | @end -------------------------------------------------------------------------------- /NSArray+Whitespace.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+Whitespace.h" 2 | 3 | @implementation NSArray (Whitespace) 4 | 5 | - (NSArray*)arrayByTrimmingWhitespaceFromStringsAndRemovingEmptyStrings { 6 | NSMutableArray* a = [[NSMutableArray alloc] init] ; 7 | NSCharacterSet* whitespaceSet = [NSCharacterSet whitespaceCharacterSet] ; 8 | for (NSString* oldString in self) { 9 | NSString* newString = [oldString stringByTrimmingCharactersInSet:whitespaceSet] ; 10 | if ([newString length] > 0) { 11 | [a addObject:newString] ; 12 | } 13 | } 14 | 15 | NSArray* output = [a copy] ; 16 | [a release] ; 17 | 18 | return [output autorelease] ; 19 | } 20 | 21 | @end -------------------------------------------------------------------------------- /NSData+SockAddr.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSData (SockAddr) 5 | 6 | /*! 7 | @brief Assuming that the receiver's bytes are a sockaddr 8 | data struture, extracts the IPv4 address as a string in "dot" 9 | notation. 10 | 11 | @details Was adapted from code in an Apple Tech QA: 12 | * http://developer.apple.com/qa/qa2001/qa1298.html 13 | May crash if the receiver's bytes are not a sockaddr 14 | data structure. 15 | @result A string such as @"10.0.1.219", or nil if the 16 | receiver was a sockaddr data structure but did not contain an 17 | IPv4 address. 18 | */ 19 | - (NSString*)dottedIPv4Address ; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NSMenuItem+Font.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSMenuItem (Font) 5 | 6 | /*! 7 | @brief Sets the text font size and color of the receiver 8 | 9 | @details This method operates by reading the receiver's title and 10 | then setting its attributed title. Therefore, you must set the 11 | title first, and *then* send this message, and *then* do not make 12 | any further changes to either title or attributedTitle. 13 | @param color Set to nil for default font color (black) 14 | @param size Set to 0.0 for default font size 15 | */ 16 | - (void)setFontColor:(NSColor*)color 17 | size:(CGFloat)size ; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /NSDate+LongLong1970.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSDate (LongLong1970) 4 | 5 | /*! 6 | @brief Returns the NSDate represented by a given value of 7 | microseconds since 1970 8 | */ 9 | + (NSDate*)dateWithLongLongMicrosecondsSince1970:(NSNumber*)value ; 10 | 11 | /*! 12 | @brief Returns a number object whose long long value is the number 13 | of microseconds since 1970 represented by the receiver. 14 | */ 15 | - (NSNumber*)longLongMicrosecondsSince1970 ; 16 | 17 | /*! 18 | @brief Returns the number of microseconds since 1970 at the current 19 | time. 20 | */ 21 | + (NSNumber*)longLongMicrosecondsSince1970 ; 22 | 23 | @end 24 | 25 | -------------------------------------------------------------------------------- /NSString+MoreComparisons.m: -------------------------------------------------------------------------------- 1 | @implementation NSString (MoreComparisons) 2 | 3 | + (BOOL)isEqualHandlesNilString1:(NSString*)string1 4 | string2:(NSString*)string2 { 5 | BOOL isEqual = YES ; 6 | if (string1) { 7 | if (!string2) { 8 | // Documentation for -isEqualToString does not state if 9 | // the argument can be nil, so for safety I handle that 10 | // here, without invoking it. 11 | isEqual = NO ; 12 | } 13 | else { 14 | isEqual = [string1 isEqualToString:string2] ; 15 | } 16 | } 17 | else if (string2) { 18 | // oldValue is nil but newValue is not 19 | isEqual = NO ; 20 | } 21 | 22 | return isEqual ; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /SSYTransformShortToBool.m: -------------------------------------------------------------------------------- 1 | #import "SSYTransformShortToBool.h" 2 | 3 | 4 | @implementation SSYTransformShortToBool 5 | 6 | + (Class)transformedValueClass { 7 | return [NSNumber class] ; 8 | } 9 | 10 | + (BOOL)allowsReverseTransformation { 11 | return YES ; 12 | } 13 | 14 | - (id)transformedValue:(id)shorty { 15 | short shortValue = [shorty shortValue] ; 16 | BOOL boolValue = (shortValue > 0) ; 17 | return [NSNumber numberWithBool:boolValue] ; 18 | } 19 | 20 | - (id)reverseTransformedValue:(id)booly { 21 | BOOL boolValue = [booly boolValue] ; 22 | short shortValue = boolValue ? 1 : 0 ; 23 | return [NSNumber numberWithShort:shortValue] ; 24 | } 25 | 26 | @end -------------------------------------------------------------------------------- /NSEntityDescription+SSYMavericksBugFix.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSEntityDescription (SSYMavericksBugFix) 4 | 5 | /* 6 | @brief Method to be used in place of -entityForName:inManagedObjectContext: 7 | which does not work properly in macOS 10.9 if the passed-in name is not a 8 | constant string. 9 | 10 | @details See this post for more information: 11 | http://stackoverflow.com/questions/19626858/over-optimization-bug-in-10-9-core-data-entity-description-methods 12 | */ 13 | + (NSEntityDescription*)SSY_entityForName:(NSString*)name 14 | inManagedObjectContext:(NSManagedObjectContext*)managedObjectContext ; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /NSMenu+PopOntoView.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSMenu (PopOntoView) 5 | 6 | /*! 7 | @brief Pops the receiver onto the screen at an arbitrary point 8 | 9 | @details The receiver must be populated before invoking this method 10 | or nothing will happen. 11 | @param view The view whose frame will be used as a reference point 12 | to locate the menu 13 | @param origin The point in the given view at which the top left 14 | corner of the menu will be drawn 15 | @param pullsDown YES to pull down, NO to pop up 16 | */ 17 | - (void)popOntoView:(NSView*)view 18 | atPoint:(NSPoint)origin 19 | pullsDown:(BOOL)pullsDown ; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NSDictionary+Subdictionary.m: -------------------------------------------------------------------------------- 1 | #import "NSDictionary+Subdictionary.h" 2 | 3 | 4 | @implementation NSDictionary (Subdictionary) 5 | 6 | - (NSDictionary*)subdictionaryWithKeys:(NSArray*)keys { 7 | // Probably premature optimization… 8 | // if ([[NSSet setWithArray:[self allKeys]] isEqualToSet:[NSSet setWithArray:keys]]) { 9 | // return self ; 10 | // } 11 | 12 | NSMutableDictionary* mutant = [[NSMutableDictionary alloc] init] ; 13 | for (id key in keys) { 14 | [mutant setValue:[self objectForKey:key] 15 | forKey:key] ; 16 | } 17 | 18 | NSDictionary* answer = [[mutant copy] autorelease] ; 19 | [mutant release] ; 20 | 21 | return answer ; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /NSObject+MoreDescriptions.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief Including this category allows you to safely 6 | invoke -shortDescription on any object. 7 | */ 8 | @interface NSObject (MoreDescriptions) 9 | 10 | /*! 11 | @brief Subclasses can override to return a long 12 | description.  Default implementation returns 13 | the normal -description. 14 | */ 15 | - (NSString*)longDescription ; 16 | 17 | /*! 18 | @brief Subclasses can override to return a short 19 | description.  Default implementation returns 20 | the normal -description. 21 | */ 22 | - (NSString*)shortDescription ; 23 | 24 | - (NSString*)deepNiceDescription ; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /NSString+RangeDebug.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | // UNFORTUNATELY THIS SEEMS TO NOT WORK for detecting bad ranges, at least in Snow Leopard. 4 | // My replacement -substringWithRange: DOES run if the range is OK, but if the range is 5 | // not OK, the exception is raised by Cocoa first, and the method itself never runs. 6 | 7 | // Probably what happens is that Cocoa's bad-range detector is wired in to execute before 8 | // -substringWithRange: is invoked. Oh well, this was a nice try! 9 | 10 | #define NSSTRING_RANGE_DEBUG 0 11 | 12 | #if NSSTRING_RANGE_DEBUG 13 | #warning Compiling with NSString (RangeDebug) 14 | 15 | @interface NSString (RangeDebug) 16 | 17 | @end 18 | 19 | #endif -------------------------------------------------------------------------------- /NSString+UserAgents.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSString (UserAgents) 5 | 6 | /*! 7 | @brief Assuming that the receiver is a HTTP UserAgent string 8 | tries to extract the name of the web browser 9 | 10 | @details May return nil if name cannot be extracted. 11 | 12 | Determining the browser from a User-Agent string is actually 13 | quite difficult, and 100% accuracy is probably impossible. 14 | These people are trying really hard to be accurate: 15 | 16 | http://www.browserscope.org/ 17 | 18 | Unit Test is available for this category. 19 | */ 20 | 21 | - (NSString*)browserNameFromUserAgentStringAmongCandidates:(NSArray*)candidates ; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /NSDictionary+Histogram.m: -------------------------------------------------------------------------------- 1 | #import "NSDictionary+Histogram.h" 2 | 3 | 4 | @implementation NSMutableDictionary (Histogram) 5 | 6 | - (void)addInteger:(NSInteger)value 7 | toKey:(NSString*)key { 8 | if (value != 0) { 9 | id currentObject = [self objectForKey:key] ; 10 | NSInteger newValue ; 11 | if ([currentObject respondsToSelector:@selector(integerValue)]) { 12 | newValue = [(NSNumber*)currentObject integerValue] + value ; 13 | } 14 | else { 15 | newValue = value ; 16 | } 17 | 18 | [self setObject:[NSNumber numberWithInteger:newValue] 19 | forKey:key] ; 20 | } 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /NSFileHandle+SSYExtras.m: -------------------------------------------------------------------------------- 1 | #import "NSFileHandle+SSYExtras.h" 2 | 3 | 4 | @implementation NSFileHandle (SSYExtras) 5 | 6 | + (NSFileHandle*)clearateFileHandleForWritingAtPath:(NSString*)path { 7 | NSFileManager* fileManager = [NSFileManager defaultManager] ; 8 | NSFileHandle* fileHandle = nil ; 9 | if ([fileManager fileExistsAtPath:path]) { 10 | fileHandle = [NSFileHandle fileHandleForWritingAtPath:path] ; 11 | [fileHandle truncateFileAtOffset:0LL] ; 12 | } 13 | else { 14 | [fileManager createFileAtPath:path 15 | contents:[NSData data] 16 | attributes:nil] ; 17 | fileHandle = [NSFileHandle fileHandleForWritingAtPath:path] ; 18 | } 19 | 20 | return fileHandle ; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /NSDictionary+Histogram.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSMutableDictionary (Histogram) 5 | 6 | /*! 7 | @brief Adds a given integer value of the object 8 | to the number object for a given key. 9 | 10 | @details If the receiver does not have a value for the given key, 11 | or if the current object for the given key does not respond to the 12 | selector 'integerValue', then this current object is overwritten with an 13 | NSNumber object whose integer value is the given integer. 14 | 15 | If the values of your keys are never going to be negative, consider using 16 | NSCountedSet instead. 17 | */ 18 | - (void)addInteger:(NSInteger)value 19 | toKey:(NSString*)key ; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NSString+TimeIntervals.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSString (TimeIntervals) 5 | 6 | /*! 7 | @brief Returns a string such as "0.001 seconds", "0.020 seconds", 8 | "0.500 seconds", "30 seconds", "1 minutes", "2 hours", 9 | "66 hours", always rounding down to the nearest unit, unless interval 10 | is less than one second. 11 | 12 | @details If, for example, interval is 62.918, returns "1 minutes". 13 | Units word is always plural. 14 | 15 | @param longForm If YES, you'll get "seconds", "minute", "hours". 16 | If NO, you'll get "secs", "mins", "hrs" 17 | */ 18 | + (NSString*)stringWithUnitsForTimeInterval:(NSTimeInterval)interval 19 | longForm:(BOOL)longForm ; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NSData+SockAddr.m: -------------------------------------------------------------------------------- 1 | #import "NSData+SockAddr.h" 2 | #include 3 | 4 | @implementation NSData (SockAddr) 5 | 6 | - (NSString*)dottedIPv4Address { 7 | struct sockaddr* socketAddress = (struct sockaddr *)[self bytes]; 8 | NSString* dottedIPv4Address = nil ; 9 | 10 | /* Only continue if this is an IPv4 address. */ 11 | if (socketAddress && socketAddress->sa_family == AF_INET) { 12 | char buffer[256] ; 13 | if (inet_ntop(AF_INET, &((struct sockaddr_in *) 14 | socketAddress)->sin_addr, buffer, sizeof(buffer))) { 15 | dottedIPv4Address = [NSString stringWithCString:buffer 16 | encoding:NSASCIIStringEncoding] ; 17 | } 18 | } 19 | 20 | return dottedIPv4Address ; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /NSArray+SafeGetters.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+SafeGetters.h" 2 | 3 | 4 | @implementation NSArray (SafeGetters) 5 | 6 | - (id)firstObjectSafely { 7 | id answer = nil ; 8 | 9 | if ([self count] > 0) { 10 | answer = [self objectAtIndex:0] ; 11 | } 12 | 13 | return answer ; 14 | } 15 | 16 | - (id)lastObjectSafely { 17 | id answer = nil ; 18 | 19 | NSInteger count = [self count] ; 20 | if (count > 0) { 21 | answer = [self objectAtIndex:(count-1)] ; 22 | } 23 | 24 | return answer ; 25 | } 26 | 27 | - (id)objectSafelyAtIndex:(NSInteger)index { 28 | id answer = nil ; 29 | 30 | if ((index >= 0) && (index < [self count])) { 31 | answer = [self objectAtIndex:index] ; 32 | } 33 | 34 | return answer ; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /NSBundle+HelperPaths.m: -------------------------------------------------------------------------------- 1 | #import "NSBundle+HelperPaths.h" 2 | 3 | 4 | @implementation NSBundle (HelperPaths) 5 | 6 | - (NSString*)pathForHelper:(NSString*)helperName { 7 | NSString* bundlePath = [self bundlePath] ; 8 | NSString* path = [[[bundlePath stringByAppendingPathComponent:@"Contents"] stringByAppendingPathComponent:@"Helpers"] stringByAppendingPathComponent:helperName] ; 9 | 10 | return path ; 11 | } 12 | 13 | - (NSString*)pathForMacOS:(NSString*)helperName { 14 | NSString* bundlePath = [self bundlePath] ; 15 | NSString* path = [[[bundlePath stringByAppendingPathComponent:@"Contents"] stringByAppendingPathComponent:@"MacOS"] stringByAppendingPathComponent:helperName] ; 16 | 17 | return path ; 18 | } 19 | 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NSImage+Transform.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSImage (Transform) 5 | 6 | /*! 7 | @brief Rotates an image clockwise around its center by a given 8 | angle in degrees and returns the new image. 9 | 10 | @details The width and height of the returned image are, 11 | respectively, the height and width of the receiver. 12 | 13 | I have not yet tested this with a non-square image. 14 | 15 | Consider another way to draw images rotated: 16 | 17 | CGContextRotateCTM(UIGraphicsGetCurrentContext(), M_PI / 2.0); 18 | [img drawAtPoint...]; 19 | -- 20 | David Duncan 21 | Apple DTS Animation and Printing 22 | */ 23 | - (NSImage*)imageRotatedByDegrees:(CGFloat)degrees ; 24 | 25 | - (NSImage*)darkenedImage ; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /NSDictionary+Readable.m: -------------------------------------------------------------------------------- 1 | @implementation NSDictionary (Readable) 2 | 3 | - (NSString*)readable { 4 | NSMutableString* list = [[NSMutableString alloc] init] ; 5 | BOOL atLeastOne = NO ; 6 | 7 | // Create string, formatted as list 8 | for (id key in self) { 9 | NSString* lineItem = [[NSString alloc] initWithFormat:@"%@: %@\n", 10 | key, 11 | [self objectForKey:key]] ; 12 | [list appendString:lineItem] ; 13 | [lineItem release] ; 14 | atLeastOne = YES ; 15 | } 16 | 17 | if (atLeastOne) { 18 | // delete trailing newline 19 | [list deleteCharactersInRange:NSMakeRange([list length] - 1, 1)] ; 20 | } 21 | 22 | NSString* output = [list copy] ; 23 | [list release] ; 24 | return [output autorelease] ; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /NSMenu+Ancestry.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSMenu (Ancestry) 5 | 6 | /*! 7 | @brief Returns the menu item to which the receiver is a submenu. 8 | 9 | @details It does this by finding the menu item in the receiver's 10 | supermenu's itemArray whose submenu is the receiver itself. 11 | If the receiver has no supermenu, returns nil. 12 | */ 13 | - (NSMenuItem*)supermenuItem ; 14 | 15 | /*! 16 | @brief Returns the tag of the menu item in the receiver's supermenu's 17 | itemArray whose submenu is the receiver. 18 | 19 | @details If the receiver has no supermenu, returns NSNotFound.  20 | You can use this like the "tag" of a menu item, although it is not really. 21 | */ 22 | - (NSInteger)supertag ; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /NSError+SuggestMountVolume.m: -------------------------------------------------------------------------------- 1 | #import "NSError+SuggestMountVolume.h" 2 | #import "NSString+MorePaths.h" 3 | #import "NSError+InfoAccess.h" 4 | 5 | @implementation NSError (SuggestMountVolume) 6 | 7 | - (NSError*)maybeAddMountVolumeRecoverySuggestion { 8 | NSError* error = self ; 9 | NSString* path = [[self userInfo] objectForKey:@"Path"] ; 10 | NSString* volumePath = [path volumePath] ; 11 | if (volumePath) { 12 | if (![[NSFileManager defaultManager] fileExistsAtPath:volumePath]) { 13 | NSString* msg = [NSString stringWithFormat: 14 | @"Mount the volume '%@'", 15 | [[volumePath pathComponents] objectAtIndex:2]] ; 16 | error = [self errorByAddingLocalizedRecoverySuggestion:msg] ; 17 | } 18 | } 19 | 20 | return error ; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /NSManagedObjectModel+Versions.m: -------------------------------------------------------------------------------- 1 | #import "NSManagedObjectModel+Versions.h" 2 | #import "NSBundle+MainApp.h" 3 | 4 | 5 | @implementation NSManagedObjectModel (Versions) 6 | 7 | + (NSManagedObjectModel*)managedObjectModelWithMomdName:(NSString*)momdName 8 | versionName:(NSString*)versionName { 9 | NSString* momdPath = [[NSBundle mainAppBundle] pathForResource:momdName 10 | ofType:@"momd"] ; 11 | NSBundle* modelBundle = [NSBundle bundleWithPath:momdPath] ; 12 | 13 | NSString* modelPath = [modelBundle pathForResource:versionName 14 | ofType:@"mom"] ; 15 | NSManagedObjectModel* model = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]] ; 16 | 17 | return [model autorelease] ; 18 | } 19 | @end 20 | -------------------------------------------------------------------------------- /NSFileManager+TempFile.m: -------------------------------------------------------------------------------- 1 | #import "NSFileManager+TempFile.h" 2 | #import "SSYUuid.h" 3 | 4 | @implementation NSFileManager (TempFile) 5 | 6 | - (NSURL*)temporarySiblingForFileUrl:(NSURL*)fileUrl { 7 | NSString* path = [fileUrl path] ; 8 | do { 9 | path = [path stringByAppendingPathExtension:@"temp"] ; 10 | } while ([self fileExistsAtPath:path]) ; 11 | 12 | return [NSURL fileURLWithPath:path] ; 13 | } 14 | 15 | - (NSString*)temporaryFilePath { 16 | NSString* tempFilename = [NSString stringWithFormat: 17 | @"%@|%@", 18 | [[NSProcessInfo processInfo] processName], 19 | [SSYUuid compactUuid]] ; 20 | NSString* tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:tempFilename] ; 21 | 22 | return tempPath ; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /NSDictionary+DoNil.m: -------------------------------------------------------------------------------- 1 | #import "NSDictionary+DoNil.h" 2 | 3 | @implementation NSDictionary (DoNil) 4 | 5 | + (BOOL)isEqualHandlesNilDic1:(NSDictionary*)dic1 6 | Dic2:(NSDictionary*)dic2 { 7 | BOOL isEqual = NO ; 8 | if (dic1) { 9 | if (!dic2) { 10 | // Documentation for -isEqual does not state if 11 | // the argument can be nil, so for safety I handle that 12 | // here, without invoking it. 13 | 14 | // dic2 is nil but dic1 is not 15 | // Leave isEqual as initialized, to NO. 16 | } 17 | else { 18 | isEqual = [dic1 isEqualToDictionary:dic2] ; 19 | } 20 | } 21 | else if (dic2) { 22 | // dic1 is nil but dic2 is not 23 | // Leave isEqual as initialized, to NO. 24 | } 25 | else { 26 | isEqual = YES ; 27 | } 28 | 29 | return isEqual ; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /NSMenu+PopOntoView.m: -------------------------------------------------------------------------------- 1 | #import "NSMenu+PopOntoView.h" 2 | 3 | @implementation NSMenu (PopOntoView) 4 | 5 | - (void)popOntoView:(NSView*)view 6 | atPoint:(NSPoint)origin 7 | pullsDown:(BOOL)pullsDown { 8 | NSRect frame = [view frame] ; 9 | frame.origin = origin ; 10 | 11 | if (pullsDown) { 12 | [self insertItemWithTitle:@"" 13 | action:NULL 14 | keyEquivalent:@"" 15 | atIndex:0] ; 16 | } 17 | 18 | NSPopUpButtonCell *popUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" 19 | pullsDown:pullsDown] ; 20 | [popUpButtonCell setMenu:self] ; 21 | if (!pullsDown) { 22 | [popUpButtonCell selectItem:nil] ; 23 | } 24 | 25 | [popUpButtonCell performClickWithFrame:frame 26 | inView:view] ; 27 | [popUpButtonCell release] ; 28 | } 29 | @end 30 | -------------------------------------------------------------------------------- /NSTextView+Configurations.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /* Configuring an NSTextView in an NSScrollView for horizontal scrolling, 5 | vertical scrolling, or both is so complicated that when I figured it out 6 | I decided to write a category for it. No more trial-and-error with 7 | stupid checkboxes in Interface Builder!! 8 | 9 | For this to work, the NSTextView must be enclosed in an NSScrollView, 10 | with an invisible NSLayoutManager, NSText, NSTextStorage etc. 11 | This is the way it comes off the Library Palette in Interface Builder 3. 12 | 13 | Has only been tested with horizontal=YES and vertical=YES so far. 14 | */ 15 | 16 | 17 | @interface NSTextView (Configurations) 18 | 19 | - (void)configureScrollingHorizontal:(BOOL)horizontal 20 | vertical:(BOOL)vertical ; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /NSEntityDescription+SSYMavericksBugFix.m: -------------------------------------------------------------------------------- 1 | #import "NSEntityDescription+SSYMavericksBugFix.h" 2 | 3 | @implementation NSEntityDescription (SSYMavericksBugFix) 4 | 5 | + (NSEntityDescription*)SSY_entityForName:(NSString*)name 6 | inManagedObjectContext:(NSManagedObjectContext*)managedObjectContext { 7 | NSManagedObjectModel* mom = [[managedObjectContext persistentStoreCoordinator] managedObjectModel] ; 8 | NSDictionary* entities = [[NSDictionary alloc] initWithDictionary:[mom entitiesByName]] ; 9 | NSEntityDescription* entityDescription = [entities objectForKey:name] ; 10 | if (!entityDescription) { 11 | NSLog(@"Internal Error 282-1983 %@ %@", name, [entities allKeys] ); 12 | } 13 | [entities release] ; // Sorry, ARC users 14 | return entityDescription ; 15 | } 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /NSPredicateEditor+AppleForgot.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSPredicateEditor (AppleForgot) 4 | 5 | /*! 6 | @brief Removes all rows, including the root row, and adds a specified 7 | number of new "clean" rows 8 | 9 | @details This method must be called before setting the object value, which is 10 | the predicate, of a NSPredicateEditor. It seems to be an oversight that 11 | Apple did not build this into the implementation of 12 | -[NSPredicatEditor setObjectValue:], but even in year 2023 macOS 14 it is 13 | still necessary. 14 | 15 | For our purposes, probably leaving dirty rows would be OK because 16 | setting the object value (predicate) will wipe out the old row contents, but 17 | it seems like better hygiene to remove them. 18 | */ 19 | - (void)changeRowCountTo:(NSInteger)count ; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NSString+MoreComparisons.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSString (MoreComparisons) 5 | 6 | /*! 7 | @brief Compares two string values for equality 8 | 9 | @details Use this instead of -isEqualToString 10 | if it is possible that both strings are nil, because, 11 | unlike -isEqualToString, it will give the correct answer 12 | of YES. 13 | @param string1 One of two strings to be compared. May be nil. 14 | @param string2 One of two strings to be compared. May be nil. 15 | @result If neither argument is nil, the value returned by sending 16 | -isEqualToString: to either of them. If one argument is nil and the other 17 | is not nil, NO. If both arguments are nil, YES. 18 | Otherwise, NO 19 | */ 20 | + (BOOL)isEqualHandlesNilString1:(NSString*)string1 21 | string2:(NSString*)string2 ; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/StringMeasuringDemo.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import "NSView+GreenArrows.h" 3 | 4 | @interface StringMeasuringDemo : NSObject { 5 | IBOutlet NSTextView* textView ; 6 | IBOutlet NSView* placeholderView ; 7 | IBOutlet NSTextField* heightOfTextView ; 8 | IBOutlet NSTextField* heightOfTextField ; 9 | IBOutlet NSTextField* textFieldFontReadout ; 10 | 11 | NSTextField* _textField ; 12 | 13 | NSAttributedString* _attributedString ; 14 | } 15 | 16 | - (NSAttributedString *)attributedString; 17 | - (void)setAttributedString:(NSAttributedString *)value; 18 | 19 | - (float)windowWidth; 20 | - (void)setWindowWidth:(float)width ; 21 | 22 | - (float)windowHeight; 23 | - (void)setWindowHeight:(float)height ; 24 | 25 | - (IBAction)setStringReadMe:(id)sender ; 26 | - (IBAction)setStringOutline:(id)sender ; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /CategoriesObjCTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /NSTextView+LineBreakControl.m: -------------------------------------------------------------------------------- 1 | @implementation NSTextView (LineBreakControl) 2 | 3 | - (void)setLineBreakMode:(NSLineBreakMode)lineBreakMode { 4 | NSMutableParagraphStyle* paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy] ; 5 | [paragraphStyle setLineBreakMode:lineBreakMode] ; 6 | NSMutableDictionary* attributes = [[[self textStorage] attributesAtIndex:0 7 | effectiveRange:NULL] mutableCopy] ; 8 | [attributes setObject:paragraphStyle 9 | forKey:NSParagraphStyleAttributeName] ; 10 | [paragraphStyle release] ; 11 | NSAttributedString* attributedString = [[NSAttributedString alloc] initWithString:[self string] 12 | attributes:attributes] ; 13 | [attributes release] ; 14 | [[self textStorage] setAttributedString:attributedString] ; 15 | [attributedString release] ; 16 | } 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /NSInvocation+Nesting.m: -------------------------------------------------------------------------------- 1 | #import "NSInvocation+Nesting.h" 2 | #import "NSInvocation+Quick.h" 3 | 4 | @implementation NSInvocation (Nesting) 5 | 6 | + (void)invokeInvocations:(NSArray*)invocations { 7 | for (NSInvocation* invocation in invocations) { 8 | [invocation invoke] ; 9 | } 10 | } 11 | 12 | 13 | + (NSInvocation*)invocationWithInvocations:(NSArray*)invocations { 14 | NSInvocation* invocation = [NSInvocation invocationWithTarget:self 15 | selector:@selector(invokeInvocations:) 16 | retainArguments:YES 17 | argumentAddresses:&invocations] ; 18 | return invocation ; 19 | } 20 | 21 | - (BOOL)hasEggs { 22 | if (!([self selector] == @selector(invokeInvocations:))) { 23 | return NO ; 24 | } 25 | if (!([self target] == [NSInvocation class])) { 26 | return NO ; 27 | } 28 | 29 | return YES ; 30 | } 31 | 32 | @end -------------------------------------------------------------------------------- /NSString+Clipboard.m: -------------------------------------------------------------------------------- 1 | #import "NSString+Clipboard.h" 2 | #import 3 | 4 | @implementation NSString (Clipboard) 5 | 6 | + (NSString*)clipboard { 7 | NSPasteboard* pasteboard = [NSPasteboard generalPasteboard] ; 8 | NSArray* supportedTypes = [NSArray arrayWithObject:NSPasteboardTypeString] ; 9 | NSString* type = [pasteboard availableTypeFromArray:supportedTypes] ; 10 | NSString* value = [pasteboard stringForType:type]; 11 | return value ; 12 | } 13 | 14 | - (void)copyToClipboard { 15 | NSPasteboard* pasteboard = [NSPasteboard generalPasteboard] ; 16 | [pasteboard declareTypes:[NSArray arrayWithObjects:NSPasteboardTypeString, nil] 17 | owner:nil] ; 18 | // Above, we can say owner:nil since we are going to provide data immediately 19 | [pasteboard setString:self 20 | forType:NSPasteboardTypeString] ; 21 | } 22 | 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /SSYTransformStringToAttributed.m: -------------------------------------------------------------------------------- 1 | #import "SSYTransformStringToAttributed.h" 2 | 3 | 4 | @implementation SSYTransformStringToAttributed 5 | 6 | + (Class)transformedValueClass { 7 | return [NSAttributedString class] ; 8 | } 9 | 10 | + (BOOL)allowsReverseTransformation { 11 | return YES ; 12 | } 13 | 14 | - (id)transformedValue:(id)string { 15 | NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys: 16 | [NSFont systemFontOfSize:12.0], NSFontAttributeName, 17 | [NSColor controlTextColor ], NSForegroundColorAttributeName, 18 | nil] ; 19 | if (!string) { 20 | string = @"" ; 21 | } 22 | return [[[NSAttributedString alloc] initWithString:string 23 | attributes:attributes] autorelease] ; 24 | } 25 | 26 | - (id)reverseTransformedValue:(id)attributedString { 27 | return [attributedString string] ; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /NSDate+LongLong1970.m: -------------------------------------------------------------------------------- 1 | #import "NSDate+LongLong1970.h" 2 | 3 | 4 | @implementation NSDate (LongLong1970) 5 | 6 | + (NSDate*)dateWithLongLongMicrosecondsSince1970:(NSNumber*)value { 7 | NSDate* date = nil ; 8 | if ([value respondsToSelector:@selector(longLongValue)]) { 9 | long long microseconds1970 = [value longLongValue] ; 10 | NSTimeInterval seconds1970 = microseconds1970/1000000.0 ; 11 | date = [NSDate dateWithTimeIntervalSince1970:seconds1970] ; 12 | } 13 | 14 | return date ; 15 | } 16 | 17 | - (NSNumber*)longLongMicrosecondsSince1970 { 18 | NSTimeInterval seconds1970 = [self timeIntervalSince1970] ; 19 | long long microseconds1970 = seconds1970 * 1000000 ; 20 | return [NSNumber numberWithLongLong:microseconds1970] ; 21 | } 22 | 23 | + (NSNumber*)longLongMicrosecondsSince1970 { 24 | return [[NSDate date] longLongMicrosecondsSince1970] ; 25 | } 26 | 27 | @end 28 | 29 | -------------------------------------------------------------------------------- /NSString+DomainName.m: -------------------------------------------------------------------------------- 1 | #import "NSString+DomainName.h" 2 | 3 | 4 | @implementation NSString (DomainName) 5 | 6 | - (BOOL)isValidLabelRFC1035 { 7 | // Make sure that the domain name is a valid "segment in accordance 8 | // with RFC2396. Briefly, it must contain only characters a-z, A-Z, 9 | // 0-9 and -, but - is not allowed to be consecutive and not allowed 10 | // at the end, and the whole thing must be 63 characters max length. 11 | // Some quick tests indicate that -[NSURL URLWithString:] seems to 12 | // take care of the characters but does not check the length. 13 | if ([self length] > 63) { 14 | return NO ; 15 | } 16 | 17 | NSURL* junkUrl = [[NSURL alloc] initWithString:[@"http://" stringByAppendingString:self]] ; 18 | if (!junkUrl) { 19 | [junkUrl release] ; 20 | return NO ; 21 | } 22 | 23 | [junkUrl release] ; 24 | return YES ; 25 | } 26 | 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /NSData+Stream.m: -------------------------------------------------------------------------------- 1 | #import "NSData+Stream.h" 2 | 3 | 4 | @implementation NSData (Stream) 5 | 6 | #define STDIN_CHUNK_SIZE 1024 7 | 8 | + (NSData*)dataWithStream:(FILE*)stream { 9 | NSMutableData* data = [[NSMutableData alloc] init] ; 10 | unsigned char *buf = NULL ; 11 | NSInteger inBytes; 12 | do { 13 | buf = (unsigned char*)malloc(STDIN_CHUNK_SIZE); 14 | inBytes = fread(buf, 1, STDIN_CHUNK_SIZE, stream); 15 | [data appendBytes:buf 16 | length:inBytes] ; 17 | free(buf) ; 18 | } while (inBytes > 0) ; 19 | NSData* output = [NSData dataWithData:data] ; 20 | [data release] ; 21 | 22 | return output ; 23 | } 24 | 25 | - (void)writeToStream:(FILE*)stream { 26 | NSInteger size = [self length] ; 27 | if (size > 0) { 28 | void* buffer = malloc(size) ; 29 | [self getBytes:buffer 30 | length:size] ; 31 | fwrite(buffer, 1, size, stream) ; 32 | free(buffer) ; 33 | } 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /NSSet+SSYJsonClasses.m: -------------------------------------------------------------------------------- 1 | #import "NSSet+SSYJsonClasses.h" 2 | 3 | @implementation NSSet (SSYJsonClasses) 4 | 5 | + (NSSet*)jsonClasses { 6 | return [NSSet setWithObjects: 7 | [NSString class], 8 | [NSDate class], 9 | [NSNumber class], 10 | [NSDictionary class], 11 | [NSArray class], 12 | nil 13 | ]; 14 | } 15 | 16 | @end 17 | 18 | /* Tried to do this in Swift but gave up because it seems like you might 19 | need to create a class named "Jsonable" for the elements of the set, 20 | write a hashing function to make it hashable, etc. 21 | 22 | @objc 23 | extension NSSet { 24 | class func jsonClasses() -> Set { // <- compiler does not like protocol in the angle brackets 25 | return Set([NSString.self, Date.self, NSNumber.self, [AnyHashable : Any].self, [AnyHashable].self]) 26 | } 27 | } 28 | */ 29 | -------------------------------------------------------------------------------- /NSURL+OAuth.m: -------------------------------------------------------------------------------- 1 | #import "NSURL+OAuth.h" 2 | 3 | 4 | @implementation NSURL (OAuth) 5 | 6 | - (NSString*)normalizedUrlForOAuth { 7 | NSString* scheme = [self scheme] ; 8 | NSString* host = [self host] ; 9 | NSNumber* portNumber = [self port] ; 10 | NSString* path = [self path] ; 11 | 12 | NSString* port ; 13 | if (portNumber) { 14 | port = [NSString stringWithFormat:@"%ld", (long)[portNumber integerValue]] ; 15 | } 16 | else { 17 | port = [scheme isEqualToString:@"http"] ? @"80" : @"443" ; 18 | } 19 | 20 | if ( 21 | ([scheme isEqualToString:@"http"] && ![port isEqualToString:@"80"]) 22 | || 23 | ([scheme isEqualToString:@"https"] && ![port isEqualToString:@"443"]) 24 | ) { 25 | host = [NSString stringWithFormat: 26 | @"%@:%@", 27 | host, 28 | port] ; 29 | } 30 | 31 | return [NSString stringWithFormat: 32 | @"%@://%@%@", 33 | scheme, 34 | host, 35 | path] ; 36 | } 37 | 38 | @end 39 | 40 | -------------------------------------------------------------------------------- /NSObject+ScriptingPatches.h: -------------------------------------------------------------------------------- 1 | @interface NSObject (NSObjectScriptingAdditions) 2 | 3 | //- (NSAppleEventDescriptor *) scriptingDescriptor; 4 | //- (NSAppleEventDescriptor *) scriptingTextDescriptor; 5 | //- (NSAppleEventDescriptor *) scriptingBooleanDescriptor; 6 | //- (NSAppleEventDescriptor *) scriptingDateDescriptor; 7 | //- (NSAppleEventDescriptor *) scriptingFileDescriptor; 8 | //- (NSAppleEventDescriptor *) scriptingIntegerDescriptor; 9 | //- (NSAppleEventDescriptor *) scriptingLocationDescriptor; 10 | //- (NSAppleEventDescriptor *) scriptingNumberDescriptor; 11 | //- (NSAppleEventDescriptor *) scriptingPointDescriptor; 12 | //- (NSAppleEventDescriptor *) scriptingRealDescriptor; 13 | - (NSAppleEventDescriptor *) scriptingRecordDescriptor; 14 | //- (NSAppleEventDescriptor *) scriptingRectangleDescriptor; 15 | //- (NSAppleEventDescriptor *) scriptingSpecifierDescriptor; 16 | //- (NSAppleEventDescriptor *) scriptingTypeDescriptor; 17 | 18 | @end -------------------------------------------------------------------------------- /NSObject+SSYCheckType.m: -------------------------------------------------------------------------------- 1 | #import "NSObject+SSYCheckType.h" 2 | #import "NSError+MyDomain.h" 3 | 4 | @implementation NSObject (SSYCheckType) 5 | 6 | - (NSError*)errorIfNotClass:(Class)expectedClass 7 | code:(NSInteger)code 8 | label:(NSString*)label 9 | priorError:(NSError*)priorError { 10 | NSError* error = priorError ; 11 | if (!priorError) { 12 | if (![self isKindOfClass:expectedClass]) { 13 | NSString* desc = [[NSString alloc] initWithFormat: 14 | @"%@ is %@, expected %@", 15 | label, 16 | self.className, 17 | expectedClass.className] ; 18 | error = SSYMakeError(code, desc) ; 19 | #if !__has_feature(objc_arc) 20 | [desc release] ; 21 | #endif 22 | } 23 | } 24 | 25 | return error ; 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /NSString+SSYDotSuffix.m: -------------------------------------------------------------------------------- 1 | #import "NSString+SSYDotSuffix.h" 2 | 3 | @implementation NSString (SSYFileExtensions) 4 | 5 | - (NSString*)stringByAppendingDotSuffix:(NSString*)suffix { 6 | NSString* answer ; 7 | if (suffix) { 8 | answer = [self stringByAppendingFormat:@".%@", suffix] ; 9 | } 10 | else { 11 | answer = self ; 12 | } 13 | 14 | return answer ; 15 | } 16 | 17 | - (NSString*)stringByDeletingDotSuffix { 18 | NSArray* components = [self componentsSeparatedByString:@"."] ; 19 | NSString* answer ; 20 | if (components.count < 2) { 21 | answer = self ; 22 | } 23 | else if (components.count == 2) { 24 | answer = components.firstObject ; 25 | } 26 | else { 27 | components = [components subarrayWithRange:NSMakeRange(0, components.count - 1)] ; 28 | answer = [components componentsJoinedByString:@"."] ; 29 | } 30 | 31 | return answer ; 32 | } 33 | @end 34 | -------------------------------------------------------------------------------- /SSYTransformCollectionEmptiness.m: -------------------------------------------------------------------------------- 1 | #import "SSYTransformCollectionEmptiness.h" 2 | #import "SSY+Countability.h" 3 | 4 | @implementation SSYTransformCollectionNotEmpty 5 | 6 | + (Class)transformedValueClass { 7 | return [NSNumber class] ; 8 | } 9 | 10 | + (BOOL)allowsReverseTransformation { 11 | return NO ; 12 | } 13 | 14 | - (id)transformedValue:(NSObject *)collection { 15 | BOOL booly = [collection count] > 0 ; 16 | return [NSNumber numberWithBool:booly] ; 17 | } 18 | 19 | @end 20 | 21 | 22 | #if 0 23 | NOT USED AT THIS TIME 24 | @implementation SSYTransformCollectionIsEmpty 25 | 26 | + (Class)transformedValueClass { 27 | return [NSNumber class] ; 28 | } 29 | 30 | + (BOOL)allowsReverseTransformation { 31 | return NO ; 32 | } 33 | 34 | - (id)transformedValue:(NSObject *)collection { 35 | BOOL booly = [collection count] == 0 ; 36 | return [NSNumber numberWithBool:booly] ; 37 | } 38 | 39 | @end 40 | #endif -------------------------------------------------------------------------------- /NSDictionary+DoNil.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSDictionary (DoNil) 5 | 6 | /*! 7 | @brief Compares two dictionaries or nil values for equality 8 | 9 | @details Use this instead of -isEqualToDictionary if it is possible that both 10 | objects are nil and/or if either contains subdictionaries which should also be 11 | traversed and checkd for equality. Note that -[NSDictionary isEqualToDictionary] 12 | will not handle either of these cases as expected. 13 | @param dic1 One of two dictionaries to be compared. May be nil. 14 | @param dic2 One of two dictionaries to be compared. May be nil. 15 | @result If neither argument is nil, the value which you'd get 16 | by sending -isEqualToDictionary: to either of them. If one parameter is nil 17 | and the parameter is not nil, NO. If both arguments are nil, YES. 18 | */ 19 | + (BOOL)isEqualHandlesNilDic1:(NSDictionary*)dic1 20 | Dic2:(NSDictionary*)dic2 ; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /NSView+SSYDarkMode.m: -------------------------------------------------------------------------------- 1 | #import "NSView+SSYDarkMode.h" 2 | 3 | @implementation NSView (SSYDarkMode) 4 | 5 | - (BOOL)isDarkMode_SSY { 6 | BOOL answer; 7 | if (@available(macOS 10.14, *)) { 8 | /* https://stackoverflow.com/questions/51672124/how-can-it-be-detected-dark-mode-on-macos-10-14 */ 9 | NSAppearanceName basicAppearance = [self.effectiveAppearance bestMatchFromAppearancesWithNames:@[ 10 | NSAppearanceNameAqua, 11 | NSAppearanceNameDarkAqua 12 | ]]; 13 | answer = [basicAppearance isEqualToString:NSAppearanceNameDarkAqua]; 14 | } else { 15 | answer = NO; 16 | } 17 | 18 | return answer; 19 | } 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | com.yourcompany.StringDrawingMeasurer2 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | NSMainNibFile 24 | MainMenu 25 | NSPrincipalClass 26 | NSApplication 27 | 28 | 29 | -------------------------------------------------------------------------------- /NSObject+SSYBindingsHelp.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSObject (SSYBindingsHelp) 5 | 6 | /*! 7 | @brief Pushes a given value through to any bound model object 8 | which may exist for a given key 9 | 10 | @details This is typically used in control classes to implement 11 | the "reverse" binding when the user changes the control value, 12 | pushing the new value into the data model. It may be invoked in 13 | -mouseDown:, -keyDown:, -controlTextDidEndEditing. Invoking it 14 | in a custom setter causes unnecessary pushing to occur whenever, 15 | say, a selection in a table is changed. Besides wasted CPU cycles, 16 | this can cause model values to be copied from one model object 17 | to another when changing selection if, for example, your control 18 | supports multiple selections. 19 | See discussion with Quincey Morris: 20 | http://lists.apple.com/archives/cocoa-dev/2012/Jun/msg00460.html 21 | */ 22 | - (void)pushBindingValue:(id)value 23 | forKey:(NSString*)key ; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /NSArray+SSYPathUtils.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+SSYPathUtils.h" 2 | #import "NSString+MorePaths.h" 3 | #import "NSArray+SSYMutations.h" 4 | 5 | @implementation NSArray (SSYPathUtils) 6 | 7 | - (NSArray*)pathsByRemovingDescendants { 8 | NSArray* sortedPaths = [self sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] ; 9 | NSMutableSet* pathsToRemove = [NSMutableSet new] ; 10 | for (NSString* path in sortedPaths) { 11 | if ([pathsToRemove member:path] == nil) { 12 | for (NSString* root in sortedPaths) { 13 | if ([pathsToRemove member:root] == nil) { 14 | if ([path pathIsDescendantOf:root]) { 15 | [pathsToRemove addObject:path] ; 16 | break ; 17 | } 18 | } 19 | } 20 | } 21 | } 22 | 23 | NSArray* answer = [self arrayByRemovingObjectsFromSet:pathsToRemove] ; 24 | [pathsToRemove release] ; 25 | 26 | return answer ; 27 | } 28 | 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /NSData+HexCharStrings.m: -------------------------------------------------------------------------------- 1 | #import "NSData+HexCharStrings.h" 2 | 3 | @implementation NSData(HexCharacterStrings) 4 | 5 | + (NSData*)dataWithHexCharacterString:(NSString*)string { 6 | NSScanner* outerScanner = [[NSScanner alloc] initWithString:string] ; 7 | NSMutableData* data = [[NSMutableData alloc] init] ; 8 | while (![outerScanner isAtEnd]) { 9 | NSString* piece = nil ; 10 | [outerScanner scanCharactersFromSet:[NSCharacterSet alphanumericCharacterSet] 11 | intoString:&piece] ; 12 | NSInteger loc = 0 ; 13 | while (loc < [piece length]) { 14 | const char* byteString = [[piece substringWithRange:NSMakeRange(loc, 2)] UTF8String] ; 15 | int16_t byte ; 16 | sscanf(byteString, "%hx", &byte) ; 17 | // Adjustment for endian on PowerPC may be needed here? 18 | loc += 2 ; 19 | [data appendBytes:&byte 20 | length:1] ; 21 | } 22 | } 23 | [outerScanner release] ; 24 | 25 | NSData* answer = [data copy] ; 26 | [data release] ; 27 | 28 | return [answer autorelease] ; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /NSImage+Merge.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSImage (Merge) 5 | 6 | /*! 7 | @brief Returns an image constructed by tiling a given array 8 | of images side-by-side or top-to-bottom. 9 | 10 | @param spacingX Spacing which will be applied horizontally between 11 | images, and at the left and right borders. 12 | @param spacingY Spacing which will be applied vertitally between 13 | images, and at the bottom and top borders. 14 | @param vertically YES to tile the given images from top 15 | to bottom, starting with the first image in the array at the top. 16 | NO to tile the given images from left to right, starting with 17 | the first image in the array at the left. 18 | */ 19 | + (NSImage*)imageByTilingImages:(NSArray*)images 20 | spacingX:(CGFloat)spacingY 21 | spacingY:(CGFloat)spacingY 22 | vertically:(BOOL)vertically ; 23 | 24 | - (NSImage*)imageBorderedWithInset:(CGFloat)inset ; 25 | 26 | - (NSImage*)imageBorderedWithOutset:(CGFloat)outset ; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /SSYTransformCollectionEmptiness.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /*! 4 | @brief A nonreversible transformer which reduces a collection 5 | to a BOOL stating whether or not the collection is empty. 6 | 7 | @details My first attempt at this was to bind to ...aCollection.count 8 | and then use SSYTransformShortToBool, but that raised an exception: 9 | [<_NSFaultingMutableSet 0x24bfd60> addObserver:forKeyPath:options:context:] is not supported. Key path: count 10 | My guess is that this error occured because SSYTransformCollectionNotEmpty is 11 | reversible, and it was trying to setCount: 12 | 13 | So, now I use this nonreversible transformer instead. 14 | */ 15 | @interface SSYTransformCollectionNotEmpty : NSValueTransformer {} 16 | @end 17 | 18 | #if 0 19 | NOT USED AT THIS TIME 20 | /*! 21 | @brief A nonreversible transformer which reduces a collection 22 | to a BOOL stating whether or not the collection is empty. 23 | */ 24 | @interface SSYTransformCollectionIsEmpty : NSValueTransformer {} 25 | @end 26 | #endif -------------------------------------------------------------------------------- /NSPopUpButton+Populating.m: -------------------------------------------------------------------------------- 1 | @implementation NSPopUpButton (Populating) 2 | 3 | - (void)populateTitles:(NSArray*)titles 4 | target:(id)target 5 | action:(SEL)action { 6 | [self removeAllItems] ; 7 | 8 | NSMenu* menu = [self menu] ; 9 | [self setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSControlSizeSmall]]] ; 10 | NSMenuItem* menuItem ; 11 | NSInteger i = 0 ; 12 | NSEnumerator * e = [titles objectEnumerator] ; 13 | NSString* title ; 14 | while ((title = [e nextObject])) { 15 | menuItem = [menu insertItemWithTitle:title 16 | action:action 17 | keyEquivalent:@"" 18 | atIndex:i ] ; 19 | [menuItem setTarget:target] ; 20 | [menuItem setTag:i++] ; 21 | } 22 | } 23 | 24 | // Because this category is also used in Bookdog, 25 | #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) 26 | 27 | - (void)tagItemsAsPositioned { 28 | NSInteger i=0 ; 29 | for(NSMenuItem* item in [self itemArray]) { 30 | [item setTag:i++] ; 31 | } 32 | } 33 | 34 | #endif 35 | 36 | # 37 | @end 38 | -------------------------------------------------------------------------------- /NSBundle+AppIcon.m: -------------------------------------------------------------------------------- 1 | #import "NSBundle+AppIcon.h" 2 | #import "NSBundle+MainApp.h" 3 | 4 | @implementation NSBundle (AppIcon) 5 | 6 | - (NSString*)appIconPath { 7 | // Oddly, I can't find a constant for the bundle icon file. 8 | // Compare to kCFBundleNameKey, which is apparently "CFBundleName". 9 | NSString* iconFilename = [[NSBundle mainAppBundle] objectForInfoDictionaryKey:@"CFBundleIconFile"] ; 10 | // I do not use -pathForImageResource, in case the Resources also contains 11 | // an image file, for example a png, with the same name. I want the .icns. 12 | NSString* iconBasename = [iconFilename stringByDeletingPathExtension] ; 13 | NSString* iconExtension = [iconFilename pathExtension] ; // Should be "icns", but for some reason it's in Info.plist 14 | return [[NSBundle mainBundle] pathForResource:iconBasename 15 | ofType:iconExtension] ; 16 | } 17 | 18 | - (NSImage*)appIcon { 19 | NSImage* appIcon = [[NSImage alloc] initWithContentsOfFile:[self appIconPath]] ; 20 | return [appIcon autorelease] ; 21 | } 22 | 23 | @end -------------------------------------------------------------------------------- /NSDocument+SSYAutosaveBetter.m: -------------------------------------------------------------------------------- 1 | #import "NSDocument+SSYAutosaveBetter.h" 2 | 3 | NSString* SSYDontAutosaveKey = @"dontAutosave" ; 4 | 5 | @implementation NSDocument (SSYAutosaveBetter) 6 | 7 | 8 | - (BOOL)ssy_isInViewingMode { 9 | BOOL isInViewingMode ; 10 | isInViewingMode = [self isInViewingMode] ; 11 | // But Apple's implementation does not always work as I expect, 12 | // so I add another possibility… 13 | if (!isInViewingMode) { 14 | NSString* path = [[self fileURL] path] ; 15 | // A newly-duplicated document will not have a path yet. 16 | if (path) { 17 | if ([path rangeOfString:@"/.DocumentRevisions-V100/"].location != NSNotFound) { 18 | isInViewingMode = YES ; 19 | } 20 | // Added in BookMacster 1.17 21 | if ([path rangeOfString:@"/Backups.backupdb/"].location != NSNotFound) { 22 | isInViewingMode = YES ; 23 | } 24 | } 25 | } 26 | 27 | return isInViewingMode ; 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /NSSegmentedControl+FitTextNice.m: -------------------------------------------------------------------------------- 1 | #import "NS(Attributed)String+Geometrics.h" 2 | 3 | 4 | @implementation NSSegmentedControl (FitTextNice) 5 | 6 | - (void)fitTextNice { 7 | NSInteger N = [self segmentCount] ; 8 | NSInteger i ; 9 | 10 | CGFloat totalWidthAvailable = 0.0 ; 11 | for (i=0; i 2 | 3 | 4 | @interface NSFileManager (TempFile) 5 | 6 | /*! 7 | @brief Returns a file URL which is the same as a given URL 8 | except for the appending of at least one ".temp" filename 9 | extensions. 10 | 11 | @details Additional ".temp" filename extensions are appended 12 | if the file URL created is that of a path which already 13 | exists in the fileysystem 14 | */ 15 | - (NSURL*)temporarySiblingForFileUrl:(NSURL*)fileUrl ; 16 | 17 | /*! 18 | @brief Composes and returns a path which may be used to 19 | create a new, unique, temporary file. 20 | 21 | @details The filename is of the form "processName|uuid" 22 | where processName is the processName given by NSWorkspace 23 | for the current process, and uuid is a compact uuid given by 24 | SSYUuid. The filename is in the temporary directory 25 | returned by NSTemporaryDirectory(). There is no filename 26 | "extension". (You can append one if you want one.) 27 | */ 28 | - (NSString*)temporaryFilePath ; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /NSSet+Classify.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSSet (Classify) 5 | 6 | /*! 7 | @brief Copies each element of the receiver into a mutable set containing 8 | other elements of the same class and inserts the resulting mutable sets 9 | into a give mutable dictionary. 10 | 11 | @details The keys in the dictionary are strings, class names obtained 12 | by using NSStringFromClass().  The values are mutable sets. 13 | 14 | This method is designed so that it may be invoked in succession upon 15 | different sets with the same dictionary argument.  In 16 | the end, the dictionary will contain one key/value pair for each 17 | class of object found in all of the receiver arrays. 18 | 19 | Note that because the output dictionary contains sets, duplicate 20 | objects will appear only once per set. 21 | 22 | @param dic A mutable dictionary to which mutable sets of 23 | classified objects will be added. 24 | */ 25 | - (void)classifyByClassIntoSetsInDictionary:(NSMutableDictionary*)dic ; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /NSIndexSet+MoreRanges.m: -------------------------------------------------------------------------------- 1 | #import "NSIndexSet+MoreRanges.h" 2 | 3 | 4 | @implementation NSIndexSet (MoreRanges) 5 | 6 | - (NSIndexSet*)indexesInRange:(NSRange)range { 7 | NSInteger max = range.location + range.length ; 8 | NSInteger priorIndex = range.location - 1 ; 9 | NSMutableIndexSet* newSet = [[NSMutableIndexSet alloc] init] ; 10 | while (YES) { 11 | NSInteger index ; 12 | // It seems to be an undocumented fact of -indexGreaterThanIndex: that 13 | // if the index parameter is < 0, the method returns NSNotFound. 14 | // We work around that with the following branch: 15 | if (priorIndex < 0) { 16 | index = [self firstIndex] ; 17 | } 18 | else { 19 | index = [self indexGreaterThanIndex:priorIndex] ; 20 | } 21 | 22 | if (index == NSNotFound) { 23 | break ; 24 | } 25 | 26 | if (index > max) { 27 | break ; 28 | } 29 | 30 | [newSet addIndex:index] ; 31 | priorIndex = index ; 32 | } 33 | 34 | NSIndexSet* answer = [newSet copy] ; 35 | [newSet release] ; 36 | 37 | return [answer autorelease] ; 38 | } 39 | 40 | 41 | 42 | @end -------------------------------------------------------------------------------- /NSProcessInfo+SSYMoreInfo.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSProcessInfo (SSYMoreInfo) 5 | 6 | /*! 7 | @brief Returns a human-readable dictionary of geeky information 8 | about the current process including arguments, environment, and 9 | information about the parent process; useful for debugging. 10 | */ 11 | - (NSDictionary*)geekyProcessInfo ; 12 | 13 | /*! 14 | @brief 15 | 16 | @details I'm not sure why this does not work. It gives me a number which 17 | is ~1000 times bigger than the number given by ps -axww -o vsz -o rss, and 18 | 128 times bigger than that of Activity Monitor. Example: 19 | 20 | Activity Monitor "Memory" for this process = 38.5 MB 21 | `ps -axww -o vsz -o pid` prints 5039944 for this process 22 | This method returns 5,155,213,312 which looks to me like 5.1 GB. 23 | 24 | @param error_p Pointer which will, upon return, if an error 25 | occurred and said pointer is not NULL, point to an NSError 26 | describing said error. 27 | */ 28 | - (NSInteger)currentMemorySizeError_p:(NSError**)error_p; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /NSScanner+GeeWhiz.m: -------------------------------------------------------------------------------- 1 | @implementation NSScanner (GeeWhiz) 2 | 3 | - (BOOL)tryScanPastString:(NSString*)target { 4 | BOOL foundTarget = NO ; 5 | #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) 6 | NSUInteger startLoc = [self scanLocation] ; 7 | #else 8 | NSUInteger startLoc = [self scanLocation] ; 9 | #endif 10 | [self scanUpToString:target intoString:NULL] ; 11 | if ([self scanString:target intoString:NULL]) { 12 | foundTarget = YES ; 13 | } 14 | else { 15 | [self setScanLocation:startLoc] ; 16 | } 17 | 18 | return foundTarget ; 19 | } 20 | 21 | - (BOOL)scanUpToAndThenLeapOverString:(NSString*)stopString intoString:(NSString**)stringValue { 22 | [self scanUpToString:stopString 23 | intoString:stringValue] ; 24 | // Note that we ignore the result of the above, which will be NO if the scanner 25 | // was initially at end or parked at beginning of stopString and YES otherwise. 26 | // That's a rather useless and confusing result. 27 | BOOL result = [self scanString:stopString intoString:NULL] ; 28 | 29 | return result ; 30 | } 31 | 32 | @end 33 | 34 | 35 | -------------------------------------------------------------------------------- /NSFileManager+SSYFileDescriptor.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | extern NSString* const SSYFileManagerFileDescriptorErrorDomain ; 4 | 5 | @interface NSFileManager (SSYFileDescriptor) 6 | 7 | /*! 8 | @brief Returns the path of a given file descriptor in a given 9 | process 10 | 11 | @details Although it is much more efficient to remember the path 12 | of a file when you open it, this method is useful to find the new 13 | path to a file after it as been moved (renamed) by another process. 14 | @param fileDescriptor The file descriptor of the desired file 15 | @param pid The pid of the process which has the desired file open 16 | with the given descriptor. You may pass 0 to specify the current process. 17 | @param error_p If not NULL and if an error occurs, upon return, 18 | will point to an error object encapsulating what went wrong. 19 | @result The full path to the file, or nil if an error occurred. 20 | */ 21 | + (NSString*)pathForFileDescriptor:(NSInteger)fileDescriptor 22 | pid:(pid_t)pid 23 | error_p:(NSError**)error_p ; 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /NSString+SSYDotSuffix.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (SSYDotSuffix) 4 | 5 | /*! 6 | @brief Returns a replica of the receiver with a given suffix, separated 7 | from the original string by a dot '.' character, or if the given suffix is 8 | nil, the receiver itself 9 | 10 | @details This is a replacement for stringByAppendingPathExtension: in macOS 11 | 10.12 Sierra, wherein stringByAppendingPathExtension: will return nil if the 12 | parameter contains any space characters. 13 | */ 14 | - (NSString*)stringByAppendingDotSuffix:(NSString*)suffix ; 15 | 16 | /*! 17 | @brief Returns a replica of the receiver with the last dot '.' character in 18 | it, and any trailing characters after that dot '.' removed, or if the receiver 19 | does not contain any dot '.' charcters, the receiver itself 20 | 21 | @details This is a replacement for stringByDeletingPathExtension in macOS 22 | 10.12 Sierra, wherein stringByDeletingPathExtension will not recognize a path 23 | extension which contains anyspace characters. 24 | */ 25 | - (NSString*)stringByDeletingDotSuffix ; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /NSUserDefaults+MoreTypes.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSUserDefaults (MoreTypes) 5 | 6 | - (void)setColor:(NSColor*)aColor 7 | forKey:(NSString*)aKey ; 8 | 9 | - (NSColor*)colorForKey:(NSString*)aKey ; 10 | 11 | /*! 12 | @brief Copies a color in User Defaults which was produced with the 13 | deprecated (in macOS 10.13) method +[NSArchiver archivedDataWithRootObject:], 14 | such as those produced by previous versions of this method, and writes it to 15 | a new key, using a secure coding method 16 | +[NSKeyedArchiver archivedDataWithRootObject:requiringSecureCoding:error:] 17 | which is readable using -colorForKey: in this version. 18 | 19 | @details This method should be used to upgrade users' User Defaults when 20 | first shipping a version of your app with this version. We create a new key 21 | instead of overwriting the value in the old key in case some users later 22 | downgrade to the previous version, which will be unable to read the new value. 23 | */ 24 | - (void)upgradeDeprecatedArchiveDataForOldKey:(NSString*)oldKey 25 | newKey:(NSString*)newKey; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /NSMenuItem+Font.m: -------------------------------------------------------------------------------- 1 | #import "NSMenuItem+Font.h" 2 | 3 | @implementation NSMenuItem (Font) 4 | 5 | - (void)setFontColor:(NSColor*)color 6 | size:(CGFloat)size { 7 | // Documentation for -menuOfFontSize: says that nil is supposed to give default 8 | // menu font size, but it gives 13 instead of 14. So, I hard-code 14.0. 9 | if (size == 0) { 10 | size = 14.0 ; 11 | } 12 | NSFont* font = [NSFont menuFontOfSize:size] ; 13 | NSString* title = [self title] ; 14 | NSDictionary* fontAttribute = [NSDictionary dictionaryWithObjectsAndKeys: 15 | font, NSFontAttributeName, 16 | nil] ; 17 | NSMutableAttributedString* newTitle = [[NSMutableAttributedString alloc] initWithString:title 18 | attributes:fontAttribute] ; 19 | if (color) { 20 | NSDictionary* moreAttributes = [NSDictionary dictionaryWithObjectsAndKeys: 21 | color, NSForegroundColorAttributeName, 22 | nil] ; 23 | NSRange range = NSMakeRange(0, [title length]) ; 24 | [newTitle addAttributes:moreAttributes 25 | range:range] ; 26 | } 27 | 28 | [self setAttributedTitle:newTitle] ; 29 | [newTitle release] ; 30 | } 31 | 32 | @end -------------------------------------------------------------------------------- /NSManagedObject+Attributes.m: -------------------------------------------------------------------------------- 1 | #import "NSManagedObject+Attributes.h" 2 | 3 | 4 | @implementation NSManagedObject (Attributes) 5 | 6 | - (NSArray*)allAttributes { 7 | return [[[self entity] attributesByName] allKeys] ; 8 | } 9 | 10 | - (NSDictionary*)attributesDictionaryWithNulls:(BOOL)withNulls { 11 | NSDictionary* answer ; 12 | if (withNulls) { 13 | answer = [self dictionaryWithValuesForKeys:[self allAttributes]] ; 14 | // Note: -dictionaryWithValuesForKeys: puts in the NSNulls. 15 | } 16 | else { 17 | NSMutableDictionary* attributesDic = [[NSMutableDictionary alloc] init] ; 18 | for (id attributeName in [self allAttributes]) { 19 | id value = [self valueForKey:attributeName] ; 20 | if (value) { 21 | [attributesDic setValue:value 22 | forKey:attributeName] ; 23 | } 24 | } 25 | 26 | answer = [attributesDic autorelease] ; 27 | } 28 | 29 | return answer ; 30 | } 31 | 32 | - (void)setAttributes:(NSDictionary*)attributes { 33 | for (id attributeName in attributes) { 34 | id value = [attributes valueForKey:attributeName] ; 35 | [self setValue:value 36 | forKey:attributeName] ; 37 | } 38 | } 39 | 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /NSTableView+ContextMenu.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief This class implements -menuForEvent: such that, if the (sub)class 6 | implements -menuForTableColumnIndex:rowIndex:, it will extract the relevant 7 | row and column from event and forward the message to 8 | -menuForTableColumnIndex:rowIndex:. Otherwise, it invokes Apple's 9 | method -menuForEvent:. 10 | 11 | @details This is so that you don't have to re-implement the row 12 | and column extraction in every damned subclass of NSTableView. 13 | Uses technique given in http://developer.apple.com/samplecode/MethodReplacement/ 14 | */ 15 | @interface NSTableView (ContextMenu) 16 | 17 | @end 18 | 19 | @interface NSTableView (ContextMenuImplementor) 20 | 21 | /*! 22 | @brief Subclasses or categories may implement to return a contextual 23 | menu for a given row and column.  If implemented, this method will be 24 | invoked by -menuForEvent:. 25 | 26 | @details The idea is that implementing this method will be simpler than 27 | implementing -menuForEvent:. 28 | */ 29 | - (NSMenu*)menuForTableColumnIndex:(NSInteger)iCol 30 | rowIndex:(NSInteger)iRow ; 31 | 32 | @end 33 | 34 | -------------------------------------------------------------------------------- /NSTextView+Configurations.m: -------------------------------------------------------------------------------- 1 | 2 | NSSize const unlimitedSize = {CGFLOAT_MAX, CGFLOAT_MAX} ; 3 | 4 | @implementation NSTextView (Configurations) 5 | 6 | - (void)configureScrollingHorizontal:(BOOL)horizontal 7 | vertical:(BOOL)vertical { 8 | [self setHorizontallyResizable:horizontal] ; 9 | [self setVerticallyResizable:vertical] ; 10 | [self setAutoresizingMask:NSViewNotSizable] ; 11 | NSTextContainer *textContainer = [self textContainer] ; 12 | [textContainer setContainerSize:unlimitedSize] ; 13 | [textContainer setWidthTracksTextView:!horizontal] ; 14 | [textContainer setHeightTracksTextView:!vertical] ; 15 | NSSize contentSize = [[self enclosingScrollView] contentSize] ; 16 | [self setMinSize:contentSize] ; 17 | NSSize size ; 18 | size.width = horizontal ? CGFLOAT_MAX : contentSize.width ; 19 | if (vertical) { 20 | size.width -= [[[self enclosingScrollView] verticalScroller] frame].size.height ; 21 | } 22 | size.height = vertical ? CGFLOAT_MAX : contentSize.height ; 23 | if (horizontal) { 24 | size.height -= [[[self enclosingScrollView] horizontalScroller] frame].size.height ; 25 | } 26 | [self setMaxSize:size] ; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /NSDate+NiceFormats.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSDate (NiceFormats) 5 | 6 | /*! 7 | @brief Returns a string representation of the receiver formatted 8 | with medium date style and short time style 9 | */ 10 | - (NSString*)medDateShortTimeString ; 11 | 12 | /*! 13 | @brief Returns a string representation of the receiver formatted 14 | as "YYYY-MM-DD HH:mm:ss" 15 | */ 16 | - (NSString*)geekDateTimeString ; 17 | 18 | /*! 19 | @brief Returns a string representation of the receiver formatted 20 | as @"YYYY-MM-DD HH:mm:ss.SSS", where SSS is milliseconds. 21 | */ 22 | - (NSString*)geekDateTimeStringMilli ; 23 | 24 | /*! 25 | @brief Returns a string representation of the receiver formatted 26 | as @"HH:mm:ss". 27 | */ 28 | - (NSString*)hourMinuteSecond ; 29 | 30 | /*! 31 | @brief Returns a string representation of the receiver formatted 32 | as YYYYMMDDHHmmss±HHmm 33 | @details The result may be used in file names. 34 | */ 35 | - (NSString*)compactDateTimeString ; 36 | 37 | /*! 38 | @brief Returns the current date as formatted by 39 | -medDateShortTimeString 40 | */ 41 | + (NSString*)currentDateFormattedConcisely ; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /NSHelpManager+HelpForHelp.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSHelpManager (HelpForHelp) 4 | /* Provides a workaround for Apple Bug 4212853, duplicate 3946514, 5 | which is that help anchors will not be scrolled to on a newly- 6 | opened page which has a link to an external CSS document. This 7 | bug is apparently in Help Viewer because it is seen whether 8 | you use Cocoa, Carbon or AppleScript direct from Script Editor. 9 | The first two methods are replacments for -openHeloAnchor:inBook: 10 | and the last one is a little bonus. */ 11 | 12 | + (void)openAnchor:(NSString*)anchor 13 | neighboringAnchor:(NSString*)neighboringAnchor ; 14 | // neighboringAnchor is any other anchor on same page as anchor 15 | 16 | + (void)openAnchor:(NSString*)anchor ; 17 | // If anchor is simply an anchor, may or may not scroll depending 18 | // on whether or not Apple bug 4212853 applies 19 | // If anchor is of the form @"anchor|neighboringAnchor", 20 | // that is two anchors separated by a pipe, then it invokes 21 | // openAnchor:neighboringAnchor and tries the bug workaround. 22 | + (void)openPage:(NSString*)page ; 23 | // Cocoa wrapper for function only available in Carbon. 24 | 25 | @end 26 | 27 | -------------------------------------------------------------------------------- /NSSet+SimpleMutations.m: -------------------------------------------------------------------------------- 1 | #import "NSSet+SimpleMutations.h" 2 | 3 | 4 | @implementation NSSet (SimpleMutations) 5 | 6 | - (NSSet*)setByRemovingObject:(id)object { 7 | NSMutableSet* mutant = [self mutableCopy] ; 8 | [mutant removeObject:object] ; 9 | NSSet* newSet = [NSSet setWithSet:mutant] ; 10 | [mutant release] ; 11 | 12 | return newSet ; 13 | } 14 | 15 | - (NSSet*)setByRemovingObjectsFromSet:(NSSet*)objects { 16 | NSMutableSet* mutant = [self mutableCopy] ; 17 | [mutant minusSet:objects] ; 18 | NSSet* newSet = [NSSet setWithSet:mutant] ; 19 | [mutant release] ; 20 | 21 | return newSet ; 22 | } 23 | 24 | - (NSSet*)setByTruncatingToCount:(NSInteger)count { 25 | if ([self count] <= count) { 26 | return self ; 27 | } 28 | 29 | NSMutableSet* mutaset = [[NSMutableSet alloc] init] ; 30 | NSInteger i = 0 ; 31 | for (id object in self) { 32 | if (i < count) { 33 | [mutaset addObject:object] ; 34 | i++ ; 35 | } 36 | else { 37 | break ; 38 | } 39 | } 40 | 41 | NSSet* set = [mutaset copy] ; 42 | [mutaset release] ; 43 | [set autorelease] ; 44 | 45 | return set ; 46 | } 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /NSString+SSYCaseness.m: -------------------------------------------------------------------------------- 1 | #import "NSString+SSYCaseness.h" 2 | 3 | @implementation NSString (SSYCaseness) 4 | 5 | - (NSComparisonResult)compareCase:(NSString*)other { 6 | NSString* selfUpper = [self uppercaseString] ; 7 | NSString* otherUpper = [other uppercaseString] ; 8 | NSUInteger end = MIN([self length], [other length]) ; 9 | for (NSUInteger i=0; i [other length]) { 28 | return NSOrderedDescending ; 29 | } 30 | 31 | if ([self length] < [other length]) { 32 | return NSOrderedAscending ; 33 | } 34 | 35 | return NSOrderedSame ; 36 | } 37 | 38 | @end 39 | 40 | -------------------------------------------------------------------------------- /NSTableView+Autosave.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief Alternative if you don't like the way that NSTableView's 6 | setAutosaveTableColumns:YES and setAutosaveName: make a mess of your 7 | app's preferences file. Has methods for encoding and decoding 8 | the state of an NSTableView into a dictionary. 9 | 10 | @details This is the equivalent of NSWindow methods 11 | -stringWithSavedFrame and -setFrameFromString: which 12 | NSTableView does not provide. 13 | 14 | At this time, the autosave state includes only: 15 | * width 16 | * userDefinedAttribute 17 | Other attributes like column ordering, sort ordering, and identification 18 | of hidden columns could be added to the dictionary. 19 | */ 20 | @interface NSTableView (Autosave) 21 | 22 | /*! 23 | @brief Returns a dictionary encoding the current autosave state 24 | of the receiver. 25 | */ 26 | - (NSDictionary*)currentState ; 27 | 28 | /*! 29 | @brief Restores the state of the receiver as decoded from a 30 | given autosave state. 31 | 32 | @details Subclasses typically override to set a default 33 | state if the autosaveState parameter is nil. 34 | */ 35 | - (void)restoreFromAutosaveState:(NSDictionary*)autosaveState ; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /NSDate+SafeCompare.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @ @brief defined to be NSOrderedAscending 6 | 7 | @details So I don't have to look at documentation every 8 | time I use -[NSDate compare:] 9 | */ 10 | #define NSDateIsEarlierThan NSOrderedAscending 11 | 12 | /*! 13 | @ @brief defined to be NSOrderedDescending 14 | 15 | @details So I don't have to look at documentation every 16 | time I use -[NSDate compare:] 17 | */ 18 | #define NSDateIsLaterThan NSOrderedDescending 19 | 20 | /* 21 | NSDate's instance methods such as -laterDate: may not give the 22 | sensible result when one of the dates is nil. These class methods 23 | give sensible results in all cases. 24 | */ 25 | @interface NSDate (SafeCompare) 26 | 27 | /*! 28 | @brief Returns the later of two dates, even if either is nil. 29 | 30 | @details If either date is nil, returns the other date.  If 31 | both dates are nil, returns nil. 32 | @result date1, date2, or nil. 33 | */ 34 | + (NSDate*)laterDate:(NSDate*)date1 35 | date:(NSDate*)date2 ; 36 | 37 | + (BOOL)isEqualHandlesNilDate1:(NSDate*)date1 38 | date2:(NSDate*)date2 39 | tolerance:(NSTimeInterval)tolerance ; 40 | 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /NSData+SSYCryptoDigest.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @implementation NSData (SSYCryptoDigest) 4 | 5 | - (NSData *)md5Digest { 6 | NSMutableData* hash = [NSMutableData dataWithLength: CC_MD5_DIGEST_LENGTH] ; 7 | /* Using MD5 hash is deprecated in macOS 10.15. But it is still used 8 | still used by Pinboard to generate a bookmark's exide from its URL, 9 | and by Chrome to generate the checksum for its bookmarks file. 10 | So, I ignore the deprecation*/ 11 | #pragma clang diagnostic push 12 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 13 | CC_MD5([self bytes], (CC_LONG)[self length], [hash mutableBytes]) ; 14 | #pragma clang diagnostic pop 15 | return hash ; 16 | } 17 | 18 | - (NSData *)sha1Digest { 19 | NSMutableData* hash = [NSMutableData dataWithLength: CC_SHA1_DIGEST_LENGTH] ; 20 | CC_SHA1([self bytes], (CC_LONG)[self length], [hash mutableBytes]) ; 21 | return hash ; 22 | } 23 | 24 | - (NSData *)sha256Digest { 25 | NSMutableData* hash = [NSMutableData dataWithLength: CC_SHA256_DIGEST_LENGTH] ; 26 | CC_SHA256([self bytes], (CC_LONG)[self length], [hash mutableBytes]) ; 27 | return hash ; 28 | } 29 | 30 | @end 31 | 32 | // Reference: https://www.mikeash.com/pyblog/friday-qa-2012-08-10-a-tour-of-commoncrypto.html 33 | -------------------------------------------------------------------------------- /NSDocumentController+FrontOrder.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSDocumentController (FrontOrder) 4 | 5 | /* 6 | @brief Returns the result of -documents, except if the current document is 7 | not at index 0 in that array, then returns a rearranged version of that array 8 | which has the current document removed and then reinserted at index 0 9 | 10 | @details Although the order of the array returned by 11 | -[NSDocumentController documents] is not documented, in my experience, its 12 | order is the order in which documents were opened, even if a later-opened 13 | document is now the current document. The "current document" here is the 14 | frontmost, active document, the one returned by -currentDocument. 15 | */ 16 | - (NSArray*)frontOrderDocuments ; 17 | 18 | /* 19 | @brief Returns the frontmost of the receiver's open documents which is of 20 | the receiver's default types, more aggressively than Cocoa's -currentDocument 21 | 22 | @details -[NSDocumntController currentDocument] will return nil, for example, 23 | during for the brief period between the time that a first document is added to 24 | the document controller the time that it opens any windows, or something like 25 | that. 26 | */ 27 | - (NSDocument*)currentDefaultDocumentAggressively ; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /NSBundle+SSYMotherApp.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSBundle (SSYMotherApp) 4 | 5 | /*! 6 | @brief Returns the string value of key "SSYMotherAppName" in the info 7 | dictionary of the current application's main bundle 8 | 9 | @details If the key "SSYMotherAppName" does not exist, returns nil. 10 | */ 11 | - (NSString*)motherAppName ; 12 | 13 | /*! 14 | @brief Returns a bundle identifier obtained by replacing the last 15 | "dot" component in the receiver's bundle identifier with the string value of 16 | the key "SSYMotherAppName" in the info dictionary of the current application's 17 | main bundle 18 | 19 | @details If the key "SSYMotherAppName" does not exist, returns nil. 20 | */ 21 | - (NSString*)motherAppBundleIdentifier ; 22 | 23 | /*! 24 | @brief Returns the full path to a (possibly nonexistent) folder in the 25 | current user's Application Support directory whose name is the string value 26 | of key "SSYMotherAppName" in the info dictionary of the current application's 27 | main bundle 28 | 29 | @details If the key "SSYMotherAppName" does not exist, uses instead the value 30 | of "CFBundleName", and if that does not exist, simply returns the path to the 31 | user's Application Support directory. 32 | */ 33 | - (NSString*)applicationSupportPathForMotherApp ; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /NSError+LowLevel.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | extern NSString* const SSYAppleScriptErrorDomain ; 4 | 5 | @interface NSError (LowLevel) 6 | /*! 7 | @brief Given a macOS system error code, tries to return 8 | an error with one of the macOS system domains and 9 | localized description. 10 | 11 | @details If you give it a known combination of domain and code, 12 | and nil userInfo, -[NSError errorWithDomain:code:userInfo:] will 13 | sometimes fill in the localized description, at least for errors 14 | in NSOSStatusErrorDomain and NSMachErrorDomain.  This method 15 | tries to use that capability, but I need more documentation to 16 | make it work better. 17 | @param code An error code returned by a system function 18 | */ 19 | + (NSError*)errorWithMacErrorCode:(OSStatus) code ; 20 | 21 | /*! 22 | @brief Given a POSIX error code, such as errno, returns 23 | an error with NSPOSIXErrorDomain and maybe a 24 | localized description. 25 | 26 | @details Uses +errorWithMacErrorCode: under the hood. 27 | @param code An error code returned by a system function 28 | */ 29 | + (NSError*)errorWithPosixErrorCode:(OSStatus)code ; 30 | 31 | + (NSError*)errorWithHTTPStatusCode:(NSInteger)code 32 | prettyFunction:(const char*)prettyFunction ; 33 | 34 | + (NSError*)errorWithAppleScriptErrorDictionary:(NSDictionary*)dic ; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /NSArray+SortDescriptorsHelp.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSArray (SortByKey) 5 | 6 | /*! 7 | @brief Returns an array consisting of a single sort descriptor, set 8 | to sort ascending with -localizedCaseInsensitiveCompare: by a given key. 9 | 10 | @details The objects in the array to which this sort descriptor 11 | is applied must be strings. NSNumber does not respond to 12 | -localizedCaseInsensitiveCompare:. 13 | */ 14 | + (NSArray*)sortDescriptorsForStringValueForKey:(NSString*)key ; 15 | 16 | /*! 17 | @brief Returns a copy of the receiver, sorted by the value of 18 | a given key using -localizedCaseInsensitiveCompare: 19 | 20 | */ 21 | - (NSArray*)arraySortedByStringValueForKey:(NSString*)key ; 22 | 23 | 24 | /*! 25 | @brief Returns a copy of the receiver, sorted by the value of 26 | a given key using -compare: 27 | 28 | @param details If keyPath is nil, defaults to @"description" 29 | */ 30 | - (NSArray*)arraySortedByKeyPath:(NSString*)keyPath ; 31 | 32 | 33 | @end 34 | 35 | 36 | @interface NSMutableArray (SortByKey) 37 | 38 | /*! 39 | @brief Sorts the array by a given key, using -localizedCaseInsensitiveCompare 40 | 41 | @details Note that due to -localizedCaseInsensitiveCompare, this 42 | is appropriate for strings but not for numbers. 43 | */ 44 | - (void)sortByStringValueForKey:(NSString*)key ; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NSDate+Microsoft1601Epoch.m: -------------------------------------------------------------------------------- 1 | #import "NSDate+Microsoft1601Epoch.h" 2 | 3 | NSTimeInterval const constIntMicrosecondsPerSecond = 1e6 ; 4 | 5 | /* I got this constant from here: 6 | http://stackoverflow.com/questions/611816/what-does-windows-filetime-contain-trying-to-convert-to-a-php-date-time 7 | A rounded-off version is here: 8 | http://blogs.msdn.com/brada/archive/2004/03/20/93332.aspx 9 | Note that it's a little more than converting 369 years to 10 | seconds using Calculator.app. Probably due to leap years, etc. 11 | But this constant is not used at this time. See next one! 12 | */ 13 | #define MICROSOFT_TICKS_FROM_1601_TO_1970 116444735995904000 14 | 15 | /* 16 | To further add to the confusion, Google Chrome uses microseconds 17 | instead of Microsoft ticks, so this constant is the former 18 | divided by 10. 19 | */ 20 | #define MICROSECONDS_FROM_1601_TO_1970 11644473599590400 21 | 22 | @implementation NSDate (Microsoft1601Epoch) 23 | 24 | + (NSDate*)dateWithMicrosecondsSince1601:(long long)microseconds { 25 | microseconds -= MICROSECONDS_FROM_1601_TO_1970 ; 26 | NSTimeInterval timeInterval = microseconds/constIntMicrosecondsPerSecond ; 27 | return [NSDate dateWithTimeIntervalSince1970:timeInterval] ; 28 | } 29 | 30 | - (long long)microsecondsSince1601 { 31 | return constIntMicrosecondsPerSecond *[self timeIntervalSince1970] + MICROSECONDS_FROM_1601_TO_1970 ; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /NSSet+SimpleMutations.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSSet (SimpleMutations) 5 | 6 | /*! 7 | @brief Returns a new set, equal to the receiver except that 8 | any object which -isEqual to a given object has been removed 9 | @details A complement to Apple's -setByAddingObject: 10 | @param object The object to be removed.  It is 11 | ok if this object does not exist in the receiver. 12 | @result An autoreleased copy of the receiver, with one 13 | or more objects possibly removed. 14 | */ 15 | - (NSSet*)setByRemovingObject:(id)object ; 16 | 17 | /*! 18 | @brief Returns a new set, equal to the receiver except that 19 | any object which -isEqual to an object in a given set has been removed 20 | @details A complement to Apple's -setByAddingObjectsFromSet: 21 | @param objects The objects to be removed.  It is 22 | ok if this set contains objects which do not exist in the receiver. 23 | @result An autoreleased copy of the receiver, with one 24 | or more objects possibly removed. 25 | */ 26 | - (NSSet*)setByRemovingObjectsFromSet:(NSSet*)objects ; 27 | 28 | /* 29 | @brief If the receiver contains more objects than a given count, returns 30 | a clone of the receiver with objects arbitrarily removed so that its count is 31 | only the given count; otherwise, returns the receiver 32 | */ 33 | - (NSSet*)setByTruncatingToCount:(NSInteger)count ; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /NSHelpManager+HelpForHelp.m: -------------------------------------------------------------------------------- 1 | #import "NSHelpManager+HelpForHelp.h" 2 | #include 3 | 4 | @implementation NSHelpManager (HelpForHelp) 5 | 6 | + (void)openAnchor:(NSString*)anchor 7 | neighboringAnchor:(NSString*)neighboringAnchor { 8 | NSString* bookName = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"] ; 9 | 10 | AHLookupAnchor((CFStringRef)bookName, (CFStringRef)neighboringAnchor) ; 11 | NSLog(@"Attempting to work around Apple Bug 4212853. Supposedly this bug was fixed in Leopard.") ; 12 | [NSThread sleepUntilDate: [NSDate dateWithTimeIntervalSinceNow: 2.0]]; 13 | AHLookupAnchor((CFStringRef)bookName, (CFStringRef)anchor) ; 14 | } 15 | 16 | + (void)openAnchor:(NSString*)anchor { 17 | NSString* bookName = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"] ; 18 | 19 | NSArray* anchors = [anchor componentsSeparatedByString:@"|"] ; 20 | 21 | if ([anchors count] == 1) { 22 | AHLookupAnchor((CFStringRef)bookName, (CFStringRef)anchor) ; 23 | } 24 | else if ([anchors count] == 2) { 25 | [self openAnchor:[anchors objectAtIndex:0] 26 | neighboringAnchor:[anchors objectAtIndex:1] ] ; 27 | } 28 | } 29 | 30 | + (void)openPage:(NSString*)page { 31 | NSString* bookName = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleHelpBookName"] ; 32 | AHGotoPage ((CFStringRef)bookName, (CFStringRef)page, NULL); 33 | } 34 | 35 | 36 | @end 37 | 38 | -------------------------------------------------------------------------------- /NSDocumentController+MoreRecents.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSDocumentController (MoreRecents) 5 | 6 | /*! 7 | @brief Removes a document with a given URL from the receiver's 8 | list of Recent Documents 9 | 10 | @details Due to lack of sufficient API from Apple, this method 11 | actually removes *all* recent documents, then replaces all except 12 | the one specified. Seems to work OK, though. 13 | */ 14 | - (void)forgetRecentDocumentUrl:(NSURL*)url ; 15 | 16 | /*! 17 | @brief Returns display names of the current recent documents 18 | 19 | @result An array with per-item correspondence to the array you 20 | get from -recentDocumentURLs. 21 | */ 22 | - (NSArray*)recentDocumentDisplayNames ; 23 | 24 | /*! 25 | @brief Returns a menu suitable to be the submenu of an "Open Recent" 26 | menu item for the application. 27 | 28 | @details Pass in a target and action to specify an action message which 29 | will be sent when one of the items in the menu is clicked. The sender 30 | parameter of the action will be one of the menu items, and this item's 31 | representedObject will be the file URL of a recent document. 32 | 33 | Typically, your target should respond to the action by opening the 34 | document specified by the given file URL. 35 | */ 36 | - (NSMenu*)recentDocumentsSubmenuWithTarget:(id)target 37 | action:(SEL)action 38 | fontSize:(CGFloat)fontSize ; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /NSArray+SortDescriptorsHelp.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+SortDescriptorsHelp.h" 2 | 3 | @implementation NSArray (SortByKey) 4 | 5 | + (NSArray*)sortDescriptorsForStringValueForKey:(NSString*)key { 6 | NSSortDescriptor* sortDescriptor ; 7 | sortDescriptor = [[NSSortDescriptor alloc] initWithKey:key 8 | ascending:YES 9 | selector:@selector(localizedCaseInsensitiveCompare:)] ; 10 | NSArray* sortDescriptors ; 11 | sortDescriptors = [NSArray arrayWithObject:sortDescriptor] ; 12 | [sortDescriptor release] ; 13 | 14 | return sortDescriptors ; 15 | } 16 | 17 | - (NSArray*)arraySortedByStringValueForKey:(NSString*)key { 18 | return [self sortedArrayUsingDescriptors:[NSArray sortDescriptorsForStringValueForKey:key]] ; 19 | } 20 | 21 | - (NSArray*)arraySortedByKeyPath:(NSString*)keyPath { 22 | if (!keyPath) { 23 | keyPath = @"description" ; 24 | } 25 | 26 | NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc] initWithKey:keyPath 27 | ascending:YES] ; 28 | NSArray* orderedArray = [self sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; 29 | [sortDescriptor release] ; 30 | return orderedArray ; 31 | } 32 | 33 | @end 34 | 35 | @implementation NSMutableArray (SortByKey) 36 | 37 | - (void)sortByStringValueForKey:(NSString*)key { 38 | NSArray* sortDescriptors = [NSArray sortDescriptorsForStringValueForKey:key] ; 39 | [self sortUsingDescriptors:sortDescriptors] ; 40 | } 41 | 42 | @end -------------------------------------------------------------------------------- /NSObject+DoNil.m: -------------------------------------------------------------------------------- 1 | #import "NSObject+DoNil.h" 2 | 3 | @implementation NSObject (DoNil) 4 | 5 | + (BOOL)isEqualHandlesNilObject1:(id)object1 6 | object2:(id)object2 { 7 | BOOL isEqual = NO ; 8 | if (object1) { 9 | if (!object2) { 10 | // Documentation for -isEqual does not state if 11 | // the argument can be nil, so for safety I handle that 12 | // here, without invoking it. 13 | 14 | // object2 is nil but object1 is not 15 | // Leave isEqual as initialized, to NO. 16 | } 17 | else { 18 | isEqual = [object1 isEqual:object2] ; 19 | } 20 | } 21 | else if (object2) { 22 | // object1 is nil but object2 is not 23 | // Leave isEqual as initialized, to NO. 24 | } 25 | else { 26 | isEqual = YES ; 27 | } 28 | 29 | return isEqual ; 30 | } 31 | 32 | + (id)fillIfNil:(id)object { 33 | return object ? object : @"object_is_nil" ; 34 | } 35 | 36 | - (BOOL)isDifferentValue:(id)value 37 | forKeyPath:(id)key { 38 | return ![NSObject isEqualHandlesNilObject1:[self valueForKeyPath:key] 39 | object2:value] ; 40 | } 41 | 42 | // Because this category is also used in Bookdog, 43 | #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) 44 | 45 | - (BOOL)isAnyDifferentValueInDictionary:(NSDictionary*)newValues { 46 | for (id key in newValues) { 47 | if ([self isDifferentValue:[newValues objectForKey:key] 48 | forKeyPath:key]) { 49 | return YES ; 50 | } 51 | } 52 | 53 | return NO ; 54 | } 55 | 56 | #endif 57 | 58 | @end -------------------------------------------------------------------------------- /NSString+TimeIntervals.m: -------------------------------------------------------------------------------- 1 | #import "NSString+TimeIntervals.h" 2 | #import "NSString+LocalizeSSY.h" 3 | #import "NSString+VarArgs.h" 4 | #import "NSString+SSYExtraUtils.h" 5 | 6 | #define FIVE_HUNDRED_YEARS (500*365.25*24*3600) 7 | 8 | @implementation NSString (TimeIntervals) 9 | 10 | + (NSString*)stringWithUnitsForTimeInterval:(NSTimeInterval)interval 11 | longForm:(BOOL)longForm { 12 | if (interval < -FIVE_HUNDRED_YEARS) { 13 | return @"Far Past" ; 14 | } 15 | if (interval > FIVE_HUNDRED_YEARS) { 16 | return @"Far Future" ; 17 | } 18 | 19 | 20 | NSString* unitsKey ; 21 | NSString* format ; 22 | CGFloat absoluteInterval = fabs(interval) ; 23 | if (absoluteInterval < 1.0) { 24 | unitsKey = @"timeIntSecsX" ; 25 | format = @"%5.3f" ; 26 | } 27 | else if (absoluteInterval < 60.0) { 28 | unitsKey = @"timeIntSecsX" ; 29 | format = @"%0.1f" ; 30 | } 31 | else if (absoluteInterval < 3600.0) { 32 | unitsKey = @"timeIntMinsX" ; 33 | interval /= 60.0 ; 34 | format = @"%0.0f" ; 35 | } 36 | else { 37 | unitsKey = @"timeIntHoursX" ; 38 | interval /= 3600.0 ; 39 | format = @"%0.1g" ; 40 | } 41 | 42 | if (!longForm) { 43 | unitsKey = [unitsKey stringByReplacingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"X"] 44 | withString:@"AbbrX"] ; 45 | } 46 | 47 | NSString* numberString = [NSString stringWithFormat:format, interval] ; 48 | return [NSString localizeFormat: 49 | unitsKey, 50 | numberString] ; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /NSTableView+Scrolling.m: -------------------------------------------------------------------------------- 1 | #import "NSTableView+Scrolling.h" 2 | 3 | @implementation NSTableView (Scrolling) 4 | 5 | // Fix the fact that visibleRect includes top rows obscured by header 6 | - (NSRect)visibleRowsRect { 7 | NSRect visibleRect = [self visibleRect] ; 8 | NSRect headerRect = [[self headerView] frame] ; 9 | NSRect clipRect = [[self superview] frame] ; 10 | CGFloat v = visibleRect.size.height ; 11 | CGFloat y = visibleRect.origin.y ; 12 | CGFloat H = headerRect.size.height ; 13 | CGFloat C = clipRect.size.height ; 14 | 15 | visibleRect.size.height = C - H ; 16 | 17 | if (y < 0.01) { 18 | // Case 1. 19 | visibleRect.origin.y = v - visibleRect.size.height ; 20 | } 21 | else { 22 | // Case 2. 23 | visibleRect.origin.y += H ; 24 | } 25 | 26 | 27 | 28 | 29 | return visibleRect ; 30 | } 31 | 32 | - (void)scrollRowToTop:(NSInteger)row 33 | plusExtraPoints:(CGFloat)extraPoints { 34 | CGFloat y = 0 ; 35 | if ((row != NSNotFound) && (row >=0)) { 36 | CGFloat rowPitch = [self rowHeight] + [self intercellSpacing].height ; 37 | y += (row) * rowPitch ; 38 | 39 | y += extraPoints ; 40 | 41 | [self scrollRowPoint:NSMakePoint(0, y)] ; 42 | // Above is o low-level method that scrolls Content, 1 of 2 43 | } 44 | } 45 | 46 | - (void)scrollRowPoint:(NSPoint)point { 47 | point.y -= [[self headerView] frame].size.height ; 48 | [self scrollPoint:point] ; 49 | } 50 | 51 | @end -------------------------------------------------------------------------------- /NSError+MyDomain.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /* 4 | @brief Macro to make a simple error in domain +[NSError myDomain] which 5 | includes the name of the routine in which the error was created in its 6 | -userInfo 7 | 8 | @details Adds the current routine name, from __PRETTY_FUNCTION__, as an 9 | object in the userInfo dictionary. 10 | 11 | @param code__ NSInteger The errorCode of the desired error 12 | @param localizedDescription__ NSString*. The localizedDescription of the 13 | desired error 14 | */ 15 | #define SSYMakeError(code__,localizedDescription__) [NSError errorWithLocalizedDescription:localizedDescription__ code:code__ prettyFunction:__PRETTY_FUNCTION__] 16 | 17 | @interface NSError (MyDomain) 18 | 19 | /*! 20 | @brief Returns an error domain for the current process, which is used 21 | by the other error-generating methods in this category. 22 | 23 | @details For applications, this will be the main bundle identifier.  24 | For processes that don't have an [NSBundle mainAppBundle], this will be the 25 | executable name, specifically the last path component of the process' 26 | first command-line argument. 27 | */ 28 | + (NSString*)myDomain ; 29 | 30 | /*! 31 | @brief Method which underlies the SSYMakeError() macro. 32 | */ 33 | + (NSError*)errorWithLocalizedDescription:(NSString*)localizedDescription 34 | code:(NSInteger)code 35 | prettyFunction:(const char*)prettyFunction ; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /NSData+HexString.m: -------------------------------------------------------------------------------- 1 | #import "NSData+HexString.h" 2 | 3 | char ASCIIHexCharacterForNibble(short nibble, BOOL uppercaseLetters) { 4 | char answer ; 5 | 6 | short offset ; 7 | if (nibble < 10) { 8 | offset = 0x30 ; 9 | } 10 | else if (uppercaseLetters) { 11 | offset = 0x37 ; 12 | } 13 | else { 14 | offset = 0x57 ; 15 | } 16 | 17 | answer = (char)(nibble + offset) ; 18 | 19 | return answer ; 20 | } 21 | 22 | 23 | @implementation NSData (HexString) 24 | 25 | - (NSString*)lowercaseHexString { 26 | // Maybe this could have been done easier by chunking it up into 27 | // integer-size numbers and using format strings with %x, but 28 | // that might have raised endian issues. I think this way will 29 | // be endian-agnostic. 30 | NSInteger i ; 31 | char hashCString[33] ; 32 | for (i=0; i<[self length]; i++) { 33 | NSData* dataChunk = [self subdataWithRange:NSMakeRange(i, 1)] ; 34 | unsigned char oneByte ; 35 | [dataChunk getBytes:&oneByte 36 | length:1] ; 37 | NSInteger subscript ; 38 | subscript = 2*i ; 39 | hashCString[subscript] = ASCIIHexCharacterForNibble((oneByte & 0xf0) >> 4, NO) ; 40 | subscript = 2*i+1 ; 41 | hashCString[subscript] = ASCIIHexCharacterForNibble(oneByte & 0x0f, NO) ; 42 | } 43 | hashCString[32] = 0 ; // null termination 44 | 45 | NSString* hashedString = [[NSString alloc] initWithUTF8String:hashCString] ; 46 | #if !__has_feature(objc_arc) 47 | [hashedString autorelease]; 48 | #endif 49 | 50 | return hashedString; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /NSScanner+GeeWhiz.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSScanner (GeeWhiz) 4 | 5 | /*! 6 | @brief scans up to and then leaps over a given string 7 | 8 | @details A more useful version of Apples scanUpToString:intoString. Scans up to stopString. 9 | If stopString is present in the receiver's string, it returns that part in stringValue, and then 10 | leaps over stopString so that, upon return, receiver's scanLocation is after stopString. 11 | If the stopString is not present in the receiver's string, the remainder of the source string is 12 | put into stringValue, and the receiver’s scanLocation is advanced to the end. 13 | @param stopString string that is being scanned for 14 | @param stringValue Upon return, a pointer to the part of the receiver's string which 15 | was scanned BEFORE stopString was leaped over 16 | @result YES if stopString was found and leaped over, NO if not found. Note 17 | that this result differs from that of Apple's scanUpToString:intoString which returns 18 | "YES if the receiver scans any characters". That's not usually what I want! 19 | */ 20 | - (BOOL)scanUpToAndThenLeapOverString:(NSString*)stopString 21 | intoString:(NSString**)stringValue ; 22 | 23 | - (BOOL)tryScanPastString:(NSString*)target ; 24 | // Tries to scan past target. 25 | // If target is found, sets scanLocation to the next 26 | // character past target and returns YES. 27 | // If target not found, sets scanLocation back to where 28 | // it originally was and returns NO. 29 | 30 | @end 31 | 32 | -------------------------------------------------------------------------------- /NSData+Crypt.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSData (Crypt) 5 | 6 | // Utility method for generating keys from string passwords 7 | + (NSData*)dataKeyByteCount:(NSInteger)nKeyBytes 8 | from7BitString:(NSString*)password ; 9 | // nKeyBytes may be of 10 | // any length, although if you believe the fiction that 11 | // making it incredibly easy for the U.S. government to eavesdrop 12 | // is somehow protecting the world from terrorists, I believe USA 13 | // citizens are supposed to limit nKeyBytes to maximum 7 (56 bits) 14 | // or something stupid like that, in any exported product. 15 | 16 | // Password will be converted to a null-terminated UTF8 string and 17 | // the most significant bit of each byte will be ignored. 18 | // Therefore, if password is all 127-bit low ASCII, the number of 19 | // characters required is ceil(nKeyBytes/7). 20 | 21 | // Because the null-termination is detected, the NUL character 22 | // (Unicode U+0000) is not allowed in the password. (According to 23 | // Wikipedia, NUL is the only UTF8 sequence which includes the 24 | // byte 0x00.) 25 | 26 | // To determine the number of bytes in a password with non-ASCII 27 | // characters, you may use -[NSString lengthOfBytesUsingEncoding:] 28 | // with argument NSUTF8StringEncoding (requires Mac OS 10.4 or later). 29 | 30 | // Since RC4 is a symmetric stream encryption, the same method 31 | // is used for encrypting and decrypting. 32 | - (NSData*)cryptRC4WithKeyData:(NSData*)keyData ; 33 | // Data returned will be same byte count (-length) as receiver. 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /NSObject+RecklessPerformSelector.h: -------------------------------------------------------------------------------- 1 | // ©2015 Electro-Diagnostic Imaging, Inc. 2 | 3 | 4 | #import 5 | 6 | /*! 7 | @brief Workarounds for the fact that, in Xcode 6+, the various 8 | -[NSObject performSelector:…] methods cause warnings of the form 9 | "PerformSelector may cause a leak because its selector is unknown" 10 | 11 | @details Very sad that Apple feels it is necessary to put the training 12 | wheels on Objectrive-C. The methods in this category are adapted from these 13 | two posts: 14 | 15 | http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown 16 | http://stackoverflow.com/questions/21433873/performselector-may-cause-a-leak-because-its-selector-is-unknown-in-singleton-cl 17 | 18 | Be sure to use the correct method depending on whether your selector 19 | returns id or void. 20 | */ 21 | @interface NSObject (RecklessPerformSelector) 22 | 23 | - (void)recklessPerformVoidSelector:(SEL)selector ; 24 | 25 | - (id)recklessPerformSelector:(SEL)selector ; 26 | 27 | - (void)recklessPerformVoidSelector:(SEL)selector 28 | object:(id)object ; 29 | 30 | - (id)recklessPerformSelector:(SEL)selector 31 | object:(id)object ; 32 | 33 | - (void)recklessPerformVoidSelector:(SEL)selector 34 | object:(id)object1 35 | object:(id)object2 ; 36 | 37 | - (id)recklessPerformSelector:(SEL)selector 38 | object:(id)object1 39 | object:(id)object2 ; 40 | 41 | @end 42 | -------------------------------------------------------------------------------- /NSDocumentController+MoreRecents.m: -------------------------------------------------------------------------------- 1 | #import "NSDocumentController+MoreRecents.h" 2 | #import "NSURL+FileDisplayName.h" 3 | #import "NSMenuItem+Font.h" 4 | 5 | @implementation NSDocumentController (MoreRecents) 6 | 7 | - (void)forgetRecentDocumentUrl:(NSURL*)url { 8 | if (url) { 9 | NSArray* recentDocumentURLs = [self recentDocumentURLs] ; 10 | [self clearRecentDocuments:self] ; 11 | // Because noteNewRecentDocumentURL: adds to the top 12 | // of the list, I need a reverse enumeration to avoid 13 | // reversing the order of the remaining recent documents 14 | NSEnumerator* e = [recentDocumentURLs reverseObjectEnumerator] ; 15 | for (NSURL* aUrl in e) { 16 | if (![aUrl isEqual:url]) { 17 | [self noteNewRecentDocumentURL:aUrl] ; 18 | } 19 | } 20 | } 21 | } 22 | 23 | - (NSArray*)recentDocumentDisplayNames { 24 | return [[self recentDocumentURLs] valueForKey:@"fileDisplayName"] ; 25 | } 26 | 27 | - (NSMenu*)recentDocumentsSubmenuWithTarget:(id)target 28 | action:(SEL)action 29 | fontSize:(CGFloat)fontSize { 30 | NSMenu* submenu = [[NSMenu alloc] initWithTitle:NSLocalizedString(@"reecentDox", nil)] ; 31 | for (NSURL* url in [self recentDocumentURLs]) { 32 | NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:[url fileDisplayName] 33 | action:action 34 | keyEquivalent:@""] ; 35 | [item setFontColor:nil 36 | size:fontSize] ; 37 | [item setTarget:target] ; 38 | [item setRepresentedObject:url] ; 39 | [submenu addItem:item] ; 40 | [item release] ; 41 | } 42 | 43 | return [submenu autorelease] ; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NSCountedSet+Votes.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSCountedSet (Votes) 5 | 6 | #if 0 7 | /*! 8 | @brief Returns an array of the receiver's contents, ordered 9 | primarily by the count of each object, with higher counts 10 | first, and secondarily by comparing the objects with compare:. 11 | */ 12 | - (NSArray*)arrayOrderedByCount ; 13 | #endif 14 | 15 | /*! 16 | @brief Returns the member of the receiver which has the 17 | highest count 18 | 19 | @details Returns nil if there is more than one member with 20 | the highest count (a "tie").  Also returns nil if 21 | the receiver is empty. 22 | */ 23 | - (id)winner ; 24 | 25 | @end 26 | 27 | 28 | @interface NSDictionary (Subdictionaries) 29 | 30 | /*! 31 | @brief Assuming that the receiver's objects are also 32 | dictionaries (subdictionaries), returns a counted set of all 33 | the different values for a given key in all the subdictionaries. 34 | 35 | @details The count of each item in the returned set is equal 36 | to the number of subdictionaries which had an equal item as 37 | the object for the given key.  If none of the 38 | subdictionaries have an object for the given key and no 39 | defaultObject is given, returns an empty set. 40 | 41 | @param defaultObject An object which will be added to the 42 | result, one for each subdictionary in the receiver which has 43 | no object for the given key, or nil if you do not want any object 44 | added for missing objects. 45 | */ 46 | - (NSCountedSet*)objectsInSubdictionariesForKey:(id)key 47 | defaultObject:(id)defaultObject ; 48 | 49 | @end -------------------------------------------------------------------------------- /NSUserDefaults+SSYOtherApps.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSUserDefaults (SSYOtherApps) 4 | 5 | - (void)syncApplicationId:(NSString*)applicationId ; 6 | 7 | - (void)setValue:(id)value 8 | forKey:(NSString*)key 9 | applicationId:(NSString*)applicationId ; 10 | 11 | - (void)setAndSyncValue:(id)value 12 | forKey:(NSString*)key 13 | applicationId:(NSString*)applicationId ; 14 | 15 | - (id)valueForKey:(NSString*)key 16 | applicationId:(NSString*)applicationId ; 17 | 18 | - (id)syncAndGetValueForKey:(NSString*)key 19 | applicationId:(NSString*)applicationId ; 20 | 21 | - (id)valueForKeyPathArray:(NSArray*)keyPathArray 22 | applicationId:(NSString*)applicationId ; 23 | 24 | - (id)syncAndGetValueForKeyPathArray:(NSArray*)keyPathArray 25 | applicationId:(NSString*)applicationId ; 26 | 27 | /* 28 | @details If 'value' is nil, this method will be a no-op. To remove a value, 29 | you should instead use one of the -remove… methods in this category. */ 30 | - (void)setAndSyncValue:(id)value 31 | forKeyPathArray:(NSArray*)keyArray 32 | applicationId:(NSString*)applicationId ; 33 | 34 | - (void)removeAndSyncKey:(id)key 35 | applicationId:(NSString*)applicationId ; 36 | 37 | - (void)removeAndSyncKeyPathArray:(NSArray*)keyPathArray 38 | applicationId:(NSString*)applicationId; 39 | 40 | - (void) removeAndSyncKey:(id)key 41 | fromDictionaryAtKeyPathArray:(NSArray*)keyPathArray 42 | applicationId:(NSString*)applicationId ; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /NSString+RangeDebug.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import "NSString+RangeDebug.h" 3 | 4 | #if NSSTRING_RANGE_DEBUG 5 | 6 | @implementation NSString (RangeDebug) 7 | 8 | + (void)load { 9 | // Swap the implementations of one method with another. 10 | // When the message Xxx is sent to the object (either instance or class), 11 | // replacement_Xxx will be invoked instead. Conversely, 12 | // replacement_Xxx will invoke Xxx. 13 | 14 | // NOTE: Below, use class_getInstanceMethod or class_getClassMethod as appropriate!! 15 | NSLog(@"Replacing methods in %@", [self class]) ; 16 | Method originalMethod = class_getInstanceMethod(self, @selector(substringWithRange:)) ; 17 | Method replacedMethod = class_getInstanceMethod(self, @selector(replacement_substringWithRange:)) ; 18 | method_exchangeImplementations(originalMethod, replacedMethod) ; 19 | } 20 | 21 | - (NSString*)badRange { 22 | return @"OUT-OF-RANGE" ; 23 | } 24 | 25 | - (NSString*)replacement_substringWithRange:(NSRange)range { 26 | NSInteger min = range.location ; 27 | NSInteger max = range.location + range.length ; 28 | if ( 29 | (min < 0) 30 | || 31 | (min > [self length]) 32 | || 33 | (max < 0) 34 | || 35 | (max > [self length]) 36 | ) { 37 | NSLog(@"Requested substringWithRange %@ out of range for string of length %ld. Set breakpoint at -[NSString badRange] to debug if you don't recognize string:\n%@", NSStringFromRange(range), (long)[self length], self) ; 38 | return [self badRange] ; 39 | } 40 | 41 | // Due to the swap, this calls the original method 42 | return [self replacement_substringWithRange:range] ; 43 | } 44 | 45 | @end 46 | 47 | #endif -------------------------------------------------------------------------------- /NS(Attributed)String+Geometrics/English.lproj/MainMenu.nib/classes.nib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IBClasses 6 | 7 | 8 | CLASS 9 | NSView 10 | LANGUAGE 11 | ObjC 12 | SUPERCLASS 13 | NSResponder 14 | 15 | 16 | ACTIONS 17 | 18 | setStringOutline 19 | id 20 | setStringReadMe 21 | id 22 | test1 23 | id 24 | 25 | CLASS 26 | StringMeasuringDemo 27 | LANGUAGE 28 | ObjC 29 | OUTLETS 30 | 31 | heightOfTextField 32 | NSTextField 33 | heightOfTextView 34 | NSTextField 35 | placeholderView 36 | NSView 37 | textFieldFontReadout 38 | NSTextField 39 | textView 40 | NSTextView 41 | 42 | SUPERCLASS 43 | NSObject 44 | 45 | 46 | CLASS 47 | FirstResponder 48 | LANGUAGE 49 | ObjC 50 | SUPERCLASS 51 | NSObject 52 | 53 | 54 | IBVersion 55 | 1 56 | 57 | 58 | -------------------------------------------------------------------------------- /NSDate+SafeCompare.m: -------------------------------------------------------------------------------- 1 | #define NSDateIsLaterThan NSOrderedDescending 2 | #define NSDateIsEarlierThan NSOrderedAscending 3 | 4 | @implementation NSDate (SafeCompare) 5 | 6 | + (NSDate*)laterDate:(NSDate*)date1 7 | date:(NSDate*)date2 { 8 | NSDate* laterDate ; 9 | if (date1 != nil) { 10 | if (date2 != nil) { 11 | laterDate = [date1 laterDate:date2] ; 12 | } 13 | else { 14 | laterDate = date1 ; 15 | } 16 | } 17 | else { 18 | laterDate = date2 ; 19 | } 20 | 21 | return laterDate ; 22 | } 23 | 24 | + (BOOL)isEqualHandlesNilDate1:(NSDate*)date1 25 | date2:(NSDate*)date2 26 | tolerance:(NSTimeInterval)tolerance { 27 | BOOL isEqual = NO ; 28 | if (date1) { 29 | if (!date2) { 30 | // Documentation for -isEqual does not state if 31 | // the argument can be nil, so for safety I handle that 32 | // here, without invoking it. 33 | 34 | // date2 is nil but object1 is not 35 | // Leave isEqual as initialized, to NO. 36 | } 37 | else { 38 | if (tolerance == 0.0) { 39 | isEqual = [date1 isEqual:date2] ; 40 | } 41 | else { 42 | NSTimeInterval t1 = [date1 timeIntervalSinceReferenceDate] ; 43 | NSTimeInterval t2 = [date2 timeIntervalSinceReferenceDate] ; 44 | NSTimeInterval diff = fabs(t2 - t1) ; 45 | isEqual = diff <= tolerance ; 46 | } 47 | } 48 | } 49 | else if (date2) { 50 | // object1 is nil but object2 is not 51 | // Leave isEqual as initialized, to NO. 52 | } 53 | else { 54 | isEqual = YES ; 55 | } 56 | 57 | return isEqual ; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /NSArray+SSYDisjoint.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | /*! 4 | @brief Methods which allow you to put objects beyond the end of 5 | an array 6 | 7 | @details Use this when you need to populate an array out of order. 8 | */ 9 | @interface NSMutableArray (SSYDisjoint) 10 | 11 | /*! 12 | @brief Similar to -replaceObject:atIndex:, except that index may 13 | exceed the size of the array 14 | 15 | @details If index exceeds the size of an array, the skipped objects 16 | are filled in with a special SSYDisjoiningPlaceholder object. 17 | */ 18 | - (void)putObject:(id)object 19 | atIndex:(NSInteger)index ; 20 | 21 | /*! 22 | @brief Replaces the object at a given index with a SSYDisjoiningPlaceholder 23 | objects and then, starting at the end of the array, removes all contiguous 24 | SSYDisjoiningPlaceholder objects. 25 | 26 | @details This method is kind of the opposite of -putObject:atIndex:. It 27 | "removes" the object at a given index, then removes the placeholders, if 28 | any, which were "supporting" it. 29 | */ 30 | - (void)cleanObjectAtIndex:(NSInteger)index ; 31 | 32 | /*! 33 | @brief Finds the index of a given object within the receiver and, if 34 | found, invokes cleanObjectAtIndex: upon it; otherwise, does nothing. 35 | */ 36 | - (void)cleanObject:(id)object ; 37 | 38 | @end 39 | 40 | #if 0 41 | // TEST CODE 42 | 43 | NSMutableArray* a = [[NSMutableArray alloc] initWithObjects: 44 | @"zero", @"one", @"two", @"three", nil] ; 45 | [a putObject:@"six" 46 | atIndex:6] ; 47 | NSLog(@"a = %@", a) ; 48 | [a cleanObjectAtIndex:2] ; 49 | NSLog(@"a = %@", a) ; 50 | [a cleanObjectAtIndex:6] ; 51 | NSLog(@"a = %@", a) ; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /NSBundle+SSYMotherApp.m: -------------------------------------------------------------------------------- 1 | #import "NSBundle+SSYMotherApp.h" 2 | #import "NSBundle+MainApp.h" 3 | #import "NSString+SSYDotSuffix.h" 4 | 5 | @implementation NSBundle (SSYMotherApp) 6 | 7 | - (NSString*)motherAppName { 8 | return [[NSBundle mainAppBundle] objectForInfoDictionaryKey:@"SSYMotherAppName"] ; 9 | } 10 | 11 | - (NSString*)motherAppBundleIdentifier { 12 | NSString* motherAppName = [self motherAppName] ; 13 | NSString* answer = nil ; 14 | if (motherAppName) { 15 | NSString* bundleIdentifier = [self bundleIdentifier] ; 16 | answer = [bundleIdentifier stringByDeletingDotSuffix] ; 17 | answer = [answer stringByAppendingDotSuffix:motherAppName] ; 18 | } 19 | 20 | return answer ; 21 | } 22 | 23 | - (NSString*)applicationSupportPathForMotherApp { 24 | NSArray *paths = NSSearchPathForDirectoriesInDomains( 25 | NSApplicationSupportDirectory, 26 | NSUserDomainMask, 27 | YES 28 | ) ; 29 | NSString* userAppSupportPath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil ; 30 | NSString* motherAppName = [self motherAppName] ; 31 | if (!motherAppName) { 32 | motherAppName = [[NSBundle mainAppBundle] objectForInfoDictionaryKey:@"CFBundleName"] ; 33 | } 34 | 35 | NSString* answer ; 36 | if (motherAppName) { 37 | answer = [userAppSupportPath stringByAppendingPathComponent:motherAppName] ; 38 | } 39 | else { 40 | answer = userAppSupportPath ; 41 | } 42 | 43 | return answer ; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NSString+OAuth.m: -------------------------------------------------------------------------------- 1 | #import "NSString+OAuth.h" 2 | #import 3 | #import "NSString+URIQuery.h" 4 | #import "NSData+Base64.h" 5 | 6 | 7 | @implementation NSString (OAuth) 8 | 9 | - (NSString*)HMACSHA1SignatureWithSecret:(NSString *)secret { 10 | NSData *secretData = [secret dataUsingEncoding:NSUTF8StringEncoding] ; 11 | NSData *clearTextData = [self dataUsingEncoding:NSUTF8StringEncoding] ; 12 | unsigned char result[20]; 13 | CCHmac(kCCHmacAlgSHA1, 14 | [secretData bytes], 15 | [secretData length], 16 | [clearTextData bytes], 17 | [clearTextData length], 18 | result) ; 19 | 20 | NSData* signatureData = [NSData dataWithBytes:result 21 | length:20] ; 22 | 23 | return [signatureData stringBase64Encoded] ; 24 | } 25 | 26 | + (NSString*)stringOAuthWithQueryDictionary:(NSDictionary*)dictionary { 27 | NSMutableString* string = [NSMutableString string] ; 28 | NSUInteger countdown = [dictionary count] ; 29 | // OAuth specification says that keys must be ordered/sorted by 30 | // ASCII value. 31 | for (NSString* key in [[dictionary allKeys] sortedArrayUsingSelector:@selector(compare:)]) { 32 | [string appendFormat:@"%@=%@", 33 | [key encodePercentEscapesPerStandard:SSYPercentEscapeStandardRFC3986], 34 | [[dictionary valueForKey:key] encodePercentEscapesPerStandard:SSYPercentEscapeStandardRFC3986] 35 | ] ; 36 | countdown-- ; 37 | if (countdown > 0) { 38 | [string appendString:@"&"] ; 39 | } 40 | } 41 | return [NSString stringWithString:string] ; 42 | } 43 | 44 | - (NSString*)stringByPercentEscapeEncodingForOAuth { 45 | return [self encodePercentEscapesPerStandard:SSYPercentEscapeStandardRFC3986 46 | butNot:nil 47 | butAlso:nil] ; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /NSTableView+ContextMenu.m: -------------------------------------------------------------------------------- 1 | #import "NSTableView+ContextMenu.h" 2 | #import 3 | 4 | 5 | @implementation NSTableView (ContextMenu) 6 | 7 | #if 0 8 | #warning Disabled NSTableView(ContextMenu) 9 | #else 10 | + (void)load { 11 | // Swap the implementations of -menuForEvent: and -replacement_menuForEvent. 12 | // When the -menuForEvent: message is sent to any NSTableView instance, -replacement_menuForEvent will 13 | // be invoked instead. Conversely, -replacement_menuForEvent invokes -menuForEvent:. 14 | Method originalMethod = class_getInstanceMethod(self, @selector(menuForEvent:)) ; 15 | Method replacedMethod = class_getInstanceMethod(self, @selector(replacement_menuForEvent:)) ; 16 | method_exchangeImplementations(originalMethod, replacedMethod); 17 | } 18 | 19 | - (NSMenu*)replacement_menuForEvent:(NSEvent*)event { 20 | SEL selector = @selector(menuForTableColumnIndex:rowIndex:) ; 21 | 22 | NSMenu* menu ; 23 | 24 | if ([self respondsToSelector:selector]) { 25 | menu = nil ; 26 | 27 | NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil] ; 28 | NSInteger iCol = [self columnAtPoint:point]; 29 | NSInteger iRow = [self rowAtPoint:point]; 30 | 31 | if ((iCol >= 0) && (iRow >= 0)) { 32 | menu = [self menuForTableColumnIndex:iCol 33 | rowIndex:iRow]; 34 | } 35 | } 36 | else { 37 | // Call the original sendEvent: method, whose implementation was exchanged with our own. 38 | // Note: this ISN'T a recursive call, because this method should have been called through -sendEvent:. 39 | NSParameterAssert(_cmd == @selector(menuForEvent:)); 40 | menu = [self replacement_menuForEvent:event]; 41 | } 42 | 43 | return menu ; 44 | } 45 | 46 | #endif 47 | @end 48 | -------------------------------------------------------------------------------- /NSPredicate+SSYMore.m: -------------------------------------------------------------------------------- 1 | #import "NSPredicate+SSYMore.h" 2 | 3 | @implementation NSPredicate (SSYMore) 4 | 5 | + (NSPredicate*)andPredicateWithDictionary:(NSDictionary*)dictionary { 6 | 7 | NSMutableArray* subpredicates = [NSMutableArray array] ; 8 | for (NSString* key in dictionary) { 9 | NSExpression* lhs = [NSExpression expressionForKeyPath:key] ; 10 | NSExpression* rhs = [NSExpression expressionForConstantValue:[dictionary valueForKey:key]] ; 11 | NSPredicate* subpredicate = [NSComparisonPredicate predicateWithLeftExpression:lhs 12 | rightExpression:rhs 13 | modifier:NSDirectPredicateModifier 14 | type:NSEqualToPredicateOperatorType 15 | options:0] ; 16 | [subpredicates addObject:subpredicate] ; 17 | } 18 | 19 | NSPredicate* predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates] ; 20 | 21 | return predicate ; 22 | } 23 | 24 | + (NSPredicate*)orPredicateWithKeyPath:(NSString*)keyPath 25 | values:(NSSet*)values { 26 | NSMutableArray* subpredicates = [[NSMutableArray alloc] init] ; 27 | NSExpression* lhs = [NSExpression expressionForKeyPath:keyPath] ; 28 | for (id value in values) { 29 | NSExpression* rhs = [NSExpression expressionForConstantValue:value] ; 30 | NSPredicate* subpredicate = [NSComparisonPredicate predicateWithLeftExpression:lhs 31 | rightExpression:rhs 32 | modifier:NSDirectPredicateModifier 33 | type:NSEqualToPredicateOperatorType 34 | options:0] ; 35 | [subpredicates addObject:subpredicate] ; 36 | } 37 | 38 | NSPredicate* predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates] ; 39 | [subpredicates release] ; 40 | 41 | return predicate ; 42 | } 43 | 44 | @end -------------------------------------------------------------------------------- /NSObject+RecklessPerformSelector.m: -------------------------------------------------------------------------------- 1 | // ©2015 Electro-Diagnostic Imaging, Inc. 2 | 3 | 4 | #import "NSObject+RecklessPerformSelector.h" 5 | 6 | 7 | @implementation NSObject (RecklessPerformSelector) 8 | 9 | - (void)recklessPerformVoidSelector:(SEL)selector { 10 | IMP imp = [self methodForSelector:selector]; 11 | void (*func)(id, SEL) = (void (*)(id, SEL))imp; 12 | func(self, selector); 13 | } 14 | 15 | - (id)recklessPerformSelector:(SEL)selector { 16 | IMP imp = [self methodForSelector:selector]; 17 | id (*func)(id, SEL) = (id (*)(id, SEL))imp; 18 | return func(self, selector); 19 | } 20 | 21 | - (void)recklessPerformVoidSelector:(SEL)selector 22 | object:(id)object { 23 | IMP imp = [self methodForSelector:selector]; 24 | void (*func)(id, SEL, id) = (void (*)(id, SEL, id))imp; 25 | func(self, selector, object); 26 | } 27 | 28 | - (id)recklessPerformSelector:(SEL)selector 29 | object:(id)object { 30 | IMP imp = [self methodForSelector:selector]; 31 | id (*func)(id, SEL, id) = (id (*)(id, SEL, id))imp; 32 | return func(self, selector, object); 33 | } 34 | 35 | - (void)recklessPerformVoidSelector:(SEL)selector 36 | object:(id)object1 37 | object:(id)object2 { 38 | IMP imp = [self methodForSelector:selector]; 39 | void (*func)(id, SEL, id, id) = (void (*)(id, SEL, id, id))imp; 40 | func(self, selector, object1, object2); 41 | } 42 | 43 | - (id)recklessPerformSelector:(SEL)selector 44 | object:(id)object1 45 | object:(id)object2 { 46 | IMP imp = [self methodForSelector:selector]; 47 | id (*func)(id, SEL, id, id) = (id (*)(id, SEL, id, id))imp; 48 | return func(self, selector, object1, object2); 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /NSTabViewItem+SSYTabHierarchy.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | extern NSString* const constDiscontiguousTabViewHierarchyString ; 4 | 5 | /* 6 | @brief Methods for working with hierarchical tab views, which means tabs 7 | whose views or subviews of its tab view items also contain ("sub") tab views. 8 | */ 9 | @interface NSTabViewItem (SSYTabHierarchy) 10 | 11 | /* 12 | @brief Returns self, unless the receiver's view contains a (sub) tab view, 13 | then returns the selected tab view item of the first such sub tab view, unless 14 | the view of that item contans a (subsub) tab view, then returns the selected 15 | tab view item of the first subsub tab view, etc. recursively until a leaf tab 16 | view item is found. 17 | 18 | @details This method has some safety built into it. If self, or any leaf item 19 | found during the recursion, does not respond as required (typically because the 20 | items is a NSTabViewItem but not a SSYHierarchicalTabViewItem), this method 21 | stops and returns the deepest tab view item it has already found. No 22 | exception is raised. 23 | 24 | The phrase "first … tab view" means the first in the array -[NSView subviews]. 25 | You should probably have only one of these, unless you want to confuse users 26 | even more than having hierarchical tab view items already does :)) 27 | 28 | @result The tab view item returned is often the receiver itself. 29 | */ 30 | - (NSTabViewItem*)selectedLeafmostTabViewItem ; 31 | 32 | /* 33 | brief Searches the receiver's view and the descendant views (subviews, 34 | subsubviews, etc.) of this view until it finds an NSTabView, returning this 35 | item or nil if none was found 36 | 37 | @details The search is depth-first. This works as expected if there is only 38 | one such tab view in the descendant views. 39 | */ 40 | - (NSTabView*)deeperTabView ; 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /NSCountedSet+Votes.m: -------------------------------------------------------------------------------- 1 | #import "NSCountedSet+Votes.h" 2 | 3 | #if 0 4 | NSComparisonResult CompareCounts(id object1, id object2, void* countedSet) { 5 | NSInteger count1 = [(NSCountedSet*)countedSet countForObject:object1] ; 6 | NSInteger count2 = [(NSCountedSet*)countedSet countForObject:object2] ; 7 | NSComparisonResult result ; 8 | if (count1 < count2) { 9 | result = NSOrderedDescending ; 10 | } 11 | else if (count1 > count2) { 12 | result = NSOrderedAscending ; 13 | } 14 | else { 15 | result = [object1 compare:object2] ; 16 | } 17 | 18 | return result ; 19 | } 20 | #endif 21 | 22 | @implementation NSCountedSet (Votes) 23 | 24 | #if 0 25 | // This method seems to not work properly, then I decided that I didn't need it 26 | - (NSArray*)arrayOrderedByCount { 27 | NSArray* array = [self allObjects] ; 28 | array = [array sortedArrayUsingFunction:CompareCounts 29 | context:self] ; 30 | return array ; 31 | } 32 | #endif 33 | 34 | - (id)winner { 35 | id winner = nil ; 36 | NSInteger highestCount = 0 ; 37 | for (id object in self) { 38 | NSInteger count = [self countForObject:object] ; 39 | if (count > highestCount) { 40 | highestCount = count ; 41 | winner = object ; 42 | } 43 | else if (count == highestCount) { 44 | winner = nil ; 45 | } 46 | } 47 | 48 | return winner ; 49 | } 50 | 51 | @end 52 | 53 | 54 | @implementation NSDictionary (Subdictionaries) 55 | 56 | - (NSCountedSet*)objectsInSubdictionariesForKey:(id)key 57 | defaultObject:(id)defaultObject { 58 | NSCountedSet* objects = [NSCountedSet set] ; 59 | for (NSDictionary* subdictionary in [self allValues]) { 60 | id object = [subdictionary objectForKey:key] ; 61 | if (object) { 62 | [objects addObject:object] ; 63 | } 64 | else if (defaultObject) { 65 | [objects addObject:defaultObject] ; 66 | } 67 | } 68 | 69 | return objects ; 70 | } 71 | 72 | @end -------------------------------------------------------------------------------- /NSManagedObject+Debug.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSManagedObject (Debug) 5 | 6 | /*! 7 | @brief Truncates a given URI String Representation of a managed object, 8 | showing only some of the unique parts, in a string of 2-6 characters. 9 | Conserves console space when printing in logs during debugging. 10 | 11 | @details An managed object can be uniquely identified by its 12 | self.objectID.URIRepresentation.absoluteString. 13 | These typically look like this: 14 | * For permanent objects: 15 | * x-coredata://8E4A6EED-E4FE-4C6B-95AD-02E874FDAEAC/myEntityName/p139 16 | * For temporary objects: 17 | * x-coredata:///myEntityName/t77470F45-9092-4480-95AB-A6D79F1CE70537 18 | When you're debugging and want to log one of these things, it takes up too 19 | much space in your console output. 20 | For the permanent objects, only the last part, p139 in our example, are 21 | usually unique. For temporary objects, only the last few digits of that 22 | UUID at the end are usually unique. 23 | 24 | This method, if such an ID parses out, returns a string beginning with "t" or 25 | "p" to indicate whether it's temporary or permanent, followed by a few 26 | characters form the unique parts identified above. Otherwise, it returns 27 | the entire self.objectID.URIRepresentation.absoluteString without any 28 | truncation. 29 | */ 30 | + (NSString*)truncatedIDForManagedObjectWithUri:(NSString*)uriStringRep 31 | entityName:(NSString*)entityName; 32 | //TODO: Eliminate the `entityName` parameter. 33 | /* This could be done either by using +[NSManagedObject entity] if you can 34 | require macOS 10.12, or doing more granual scanning with that NSScanner. */ 35 | 36 | 37 | /*! 38 | @brief Returns the result of +truncatedIDForManagedObjectWithUri:entity: 39 | for the receiver 40 | */ 41 | - (NSString*)truncatedID ; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /NSData+Base64.m: -------------------------------------------------------------------------------- 1 | #import "NSData+Base64.h" 2 | #import "NSScanner+GeeWhiz.h" 3 | #import "NSString+Base64.h" 4 | #import "NSString+SSYExtraUtils.h" 5 | 6 | @implementation NSData (Base64) 7 | 8 | - (NSData*)dataBase64Encoded { 9 | NSString* encodedString = [self stringBase64Encoded] ; 10 | return [encodedString dataUsingEncoding:NSASCIIStringEncoding] ; 11 | } 12 | 13 | - (NSData*)dataBase64Decoded { 14 | NSString* encodedString = [[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding] ; 15 | NSData* decodedData = [encodedString dataBase64Decoded] ; 16 | [encodedString release] ; 17 | return decodedData ; 18 | } 19 | 20 | - (NSString*)stringBase64Encoded { 21 | /* I sneakily insert the binary into an XML serialization of an 22 | empty property list and then decode the property list. My data is 23 | always very small, so this works for me. */ 24 | 25 | NSData* xmlData = [NSPropertyListSerialization dataWithPropertyList:self 26 | format:NSPropertyListXMLFormat_v1_0 27 | options:0 28 | error:NULL] ; 29 | 30 | 31 | NSMutableString* xml = [[NSMutableString alloc] initWithData:xmlData encoding:NSASCIIStringEncoding] ; 32 | NSScanner* scanner = [[NSScanner alloc] initWithString:xml] ; 33 | NSString* b64 ; 34 | [scanner scanUpToAndThenLeapOverString:@"" intoString:NULL] ; 35 | [scanner scanUpToString:@"" intoString:&b64] ; 36 | [xml release] ; 37 | [scanner release] ; 38 | return [b64 trimNewlineFromEnd] ; 39 | } 40 | 41 | - (NSString*)stringBase64Decoded { 42 | NSData* decodedData = [self dataBase64Decoded] ; 43 | NSString* decodedString = [[NSString alloc] initWithData:decodedData encoding:NSASCIIStringEncoding] ; 44 | return [decodedString autorelease] ; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /NSObject+DoNil.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSObject (DoNil) 5 | 6 | /*! 7 | @brief Compares two object values for equality 8 | 9 | @details Use this instead of -isEqual if it is possible that both 10 | objects are nil, because, unlike -isEqual, it will give the expected answer 11 | of YES. 12 | @param object1 One of two objects to be compared. May be nil. 13 | @param object2 One of two objects to be compared. May be nil. 14 | @result If neither argument is nil, the value returned by sending 15 | -isEqual: to either of them. If one argument is nil and the other 16 | is not nil, NO. If both arguments are nil, YES. Otherwise, NO. 17 | */ 18 | + (BOOL)isEqualHandlesNilObject1:(id)object1 19 | object2:(id)object2 ; 20 | 21 | /*! 22 | @brief Returns the given object, unless it is nil, then returns 23 | a short string explaining the nil. 24 | 25 | @details Handy for avoiding exceptions and crashes 26 | */ 27 | + (id)fillIfNil:(id)object ; 28 | 29 | /*! 30 | @brief Returns whether or not a given value is different than the 31 | existing value for a given key path 32 | 33 | @details Uses isEqualHandlesNilObject1:object2:, so therefore it 34 | correctly gives expected answer if either existing or given value are 35 | nil. 36 | */ 37 | - (BOOL)isDifferentValue:(id)value 38 | forKeyPath:(id)keyPath ; 39 | 40 | // Because this category is also used in Bookdog, 41 | #if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) 42 | 43 | /*! 44 | @brief Iterates -isDifferentValue:forKey: over a dictionary of 45 | given values for given key paths s and returns YES if one or more of them 46 | indicate a difference. 47 | 48 | @param newValues A dictionary whose keys are key paths which the receiver 49 | responds to, and whose values are the "proposed" given values for their 50 | key. 51 | */ 52 | - (BOOL)isAnyDifferentValueInDictionary:(NSDictionary*)newValues ; 53 | 54 | #endif 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /NSImage+SSYDarkMode.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | NS_ASSUME_NONNULL_BEGIN 4 | 5 | @interface NSImage (SSYDarkMode) 6 | 7 | /*! 8 | @brief Replacement for NSImage draw:… methods with option to invert 9 | colors if in Dark Mode 10 | 11 | @details Cocoa takes care of Dark Mode for you by inverting luminance if you 12 | pass a NSImage with isTemplate = YES to a control such as a NSButton. But 13 | if you draw such a NSImage yourself, in the -drawRect: method of a control or 14 | view, or the -drawWithFrame method of a cell, you may need to do the 15 | inversion yourself. 16 | 17 | The "inversion" used in this method is a different than that used by Cocoa for 18 | template images. This method applies a simple color inversion. But Cocoa 19 | maps opacity of the image to brightness. While the two work the same for 20 | most black and white template images, it is different for colored images. 21 | For example, in Dark Mode, this method converts blue with opacity 1.0 to 22 | yellow, but Cocoa's method will convert it to white, due to the opacity. I'm 23 | wondering if it might not be better to convert dark blue to light blue. 24 | The obvious, simple algorithm to do this (reflect the brightness about 0.5) 25 | seems like it gives results which are frequently too dark. Deferred to 26 | future study, if I ever need this. 27 | 28 | @param inView An associated view, usually the view into which the 29 | receiver will be drawn, or the control view of the cell into which the 30 | receiver will be drawn. This view is only accessed to get its effective 31 | appearance, which is used to determine whether or not to draw in Dark Mode, 32 | If doInvert is NO, this parameter is ignored. 33 | */ 34 | - (void)drawInRect:(NSRect)frame 35 | operation:(NSCompositingOperation)operation 36 | fraction:(CGFloat)fraction 37 | invertIfDarkMode:(BOOL)doInvert 38 | inView:(NSView*)view; 39 | 40 | @end 41 | 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /NSManagedObject+Attributes.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSManagedObject (Attributes) 5 | 6 | /*! 7 | @brief Returns all attributes in the receiver's entity 8 | description, as an array of strings. 9 | */ 10 | - (NSArray*)allAttributes ; 11 | 12 | /*! 13 | @brief A dictionary containing all attribute keys of the 14 | receiver, and their values. 15 | 16 | @details Does not give relationships. 17 | 18 | Regarding whether or not you want withNulls, Ben Trumbull of 19 | Apple gave this advice: 20 | 21 | cocoa-dev@lists.apple.com 20081219 22 | http://www.cocoabuilder.com/archive/cocoa/225972-copying-managed-objects-from-app-to-doc-mocontext.html 23 | 24 | Ben: We tried omitting pairs with nil values, and stuff broke. Like views 25 | didn't get updated because iterating over the values during setting with 26 | (nil = missing) meant nil values didn't get reset. Like with undo or 27 | bindings. Your setter here has that issue. You may prefer that behavior, 28 | but we found it even more problematic than forcing clients to check for NSNull. 29 | 30 | Tip: If you want to copy both attributes and relationships, use 31 | -[NSKeyValueCodingProtocol dictionaryWithValuesForKeys:]. 32 | 33 | @param withNulls If YES, attributes whose values are nil will 34 | be represented in the result as keys whose values are instances of 35 | NSNull. If NO, such attributes will be omitted in the result. 36 | */ 37 | - (NSDictionary*)attributesDictionaryWithNulls:(BOOL)withNulls ; 38 | 39 | /*! 40 | @brief Sets the receiver's attributes from a dictionary 41 | 42 | @details All keys in 'attributes' are attempted 43 | to be set using setValue:forKey:. Thus, setValue:forUndefinedKey: 44 | will be invoked (and possibly an exception raised) if 'attributes' 45 | contains keys for which the receiver is not KVC compliant. 46 | 47 | attributes The dictionary of attributes to be set. 48 | */ 49 | - (void)setAttributes:(NSDictionary*)attributes ; 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /NSRunningApplication+SSYHideReliably.m: -------------------------------------------------------------------------------- 1 | #import "NSRunningApplication+SSYHideReliably.h" 2 | 3 | @implementation NSRunningApplication (SSYHideReliably) 4 | 5 | - (void)hideReliablyWithGuardInterval:(NSTimeInterval)guardInterval { 6 | /* 7 | What I would like to do in this method is to 8 | send -hide repeatedly until something indicates that the app is hidden, or a 9 | few-seconds timeout, whichever comes first. However, I've not been able to 10 | find any such indication that works. Following is a list of what I've tried. 11 | My test was to launch and hide Firefox 33.0 in macOS 10.10.1, date 20141119. 12 | Repeated with Chrome in macOS 10.14.2 on 20181210; same results. 13 | 14 | • The documentation of NSRunningApplication, at the beginning, explains how the 15 | -isHidden property is fixed for a run loop cycle. So, as expected, that does 16 | not work. I tried running the run loop, but I'm never sure how to do that, 17 | and it didn't work, and it would be a bad design even if it did seem to work. 18 | • The return value of -[NSRunningApplication hide] is supposed to indicate 19 | success or failure, but it does not work. It returns NO even when it succeeds. 20 | I suppose maybe someone at Apple did not read the documentation and used 21 | -isHidden as an indicator? 22 | • I tried to get around the run loop thing by invoking -isHidden with in a 23 | block, invoked by dispatch_sync(), but it always returned NO too, even if 24 | Firefox was successfully hidden. Apparently, secondary threads are still 25 | subject to the same run loop limitation in NSRunningApplication? 26 | 27 | So I gave up and used this stinking open-loop solution that seems to work. 28 | I test the -isHidden anyhow, in case Apple ever fixes it. 29 | */ 30 | NSDate* startTime = [NSDate date]; 31 | BOOL isHidden = NO; 32 | while ((-[startTime timeIntervalSinceNow] < guardInterval) && !isHidden) { 33 | isHidden = [self hide]; 34 | [NSThread sleepForTimeInterval:0.05]; 35 | } 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /NSArray+SSYDisjoint.m: -------------------------------------------------------------------------------- 1 | #import "NSArray+SSYDisjoint.h" 2 | 3 | @interface SSYDisjoiningPlaceholder : NSObject 4 | @end 5 | @implementation SSYDisjoiningPlaceholder 6 | @end 7 | 8 | @implementation NSMutableArray (SSYDisjoint) 9 | 10 | - (void)putObject:(id)object 11 | atIndex:(NSInteger)index { 12 | NSInteger count = [self count] ; 13 | if (index < count) { 14 | [self replaceObjectAtIndex:index 15 | withObject:object] ; 16 | } 17 | else { 18 | if (index > count) { 19 | SSYDisjoiningPlaceholder* placeholder = [[SSYDisjoiningPlaceholder alloc] init] ; 20 | for (NSInteger i = [self count] ; i < index ; i++) { 21 | [self addObject:placeholder] ; 22 | } 23 | [placeholder release] ; 24 | } 25 | 26 | [self addObject:object] ; 27 | } 28 | } 29 | 30 | - (void)cleanObjectAtIndex:(NSInteger)index { 31 | SSYDisjoiningPlaceholder* placeholder = [[SSYDisjoiningPlaceholder alloc] init] ; 32 | [self replaceObjectAtIndex:index 33 | withObject:placeholder] ; 34 | [placeholder release] ; 35 | 36 | NSInteger count = [self count] ; 37 | NSInteger lastIndexToKeep = count - 1 ; 38 | for ( ; lastIndexToKeep>=0; lastIndexToKeep--) { 39 | if (![[self objectAtIndex:lastIndexToKeep] isKindOfClass:[SSYDisjoiningPlaceholder class]]) { 40 | break ; 41 | } 42 | } 43 | 44 | NSInteger lengthToRemove = count - lastIndexToKeep - 1; 45 | if ((lengthToRemove > 0) && (lastIndexToKeep < count - 1)) { 46 | NSInteger firstIndexToRemove = lastIndexToKeep + 1 ; 47 | NSRange range = NSMakeRange(firstIndexToRemove, lengthToRemove) ; 48 | [self removeObjectsInRange:range] ; 49 | } 50 | } 51 | 52 | - (void)cleanObject:(id)object { 53 | NSInteger index = [self indexOfObject:object] ; 54 | if (index != NSNotFound) { 55 | [self cleanObjectAtIndex:index] ; 56 | } 57 | } 58 | 59 | @end 60 | 61 | -------------------------------------------------------------------------------- /NSTableView+MoreSizing.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | extern NSString* const constKeyMinWidthAnyColumn ; 5 | extern NSString* const constKeyMinWidthFirstColumn ; 6 | 7 | @interface NSTableView (MoreSizing) 8 | 9 | /*! 10 | @brief Attempts to set a given column to a given width, 11 | subject to optional minimum-width constraints on other columns 12 | which are in user defaults. 13 | 14 | @details The minimum column for other columns are values for 15 | these keys in -[NSUserDefaults standardUserDefaults]: 16 | * For the first column: constKeyMinWidthFirstColumn 17 | * For any column: constKeyMinWidthAnyColumn 18 | 19 | If these two keys are not present in user defaults, a default 20 | value of 0.0 is assumed. 21 | 22 | Uses -sizeLastColumnToFit first, to make sure that the table 23 | is "well formed", and then once more at the end, to make the 24 | final adjustment. 25 | */ 26 | - (void)tryToResizeColumn:(NSTableColumn*)targetColumn 27 | toWidth:(CGFloat)requestedWidthForTargetColumn ; 28 | 29 | /*! 30 | @brief Sets the widths of all columns in the receiver 31 | in proportion to the numbers in a given C array, without 32 | changing the (overall) width of the receiver itself 33 | 34 | @details The size of the array must be equal to the 35 | number of columns in the receiver. Test before sending 36 | with an assertion like this: 37 | NSAssert1(NUMBER_OF_COLUMNS == [self numberOfColumns], @"nCols=%d", [self numberOfColumns]) ; 38 | 39 | @param defaultWidths 40 | */ 41 | - (void)proportionWidths:(CGFloat[])defaultWidths ; 42 | 43 | /*! 44 | @brief If the current mouse location is inside any of the 45 | receiver's table columns, with an inset, returns that table 46 | column; otherwise, returns nil. 47 | 48 | @param inset The distance by which the mouse must be to 49 | the right of a column's left edge, or to the left of its 50 | right edge, for it to be considered "inside". 51 | 52 | */ 53 | - (NSTableColumn*)tableColumnOfCurrentMouseLocationWithInset:(CGFloat)inset ; 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /NSCharacterSet+SSYMoreCS.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSCharacterSet (SSYMoreCS) 4 | 5 | /*! 6 | @brief Returns a character set of the 95 ASCII 7 | characters that print. 8 | */ 9 | + (NSCharacterSet*)printingAsciiCharacterSet ; 10 | 11 | /*! 12 | @brief Returns a character set of the 95 characters 13 | that are legal in unix filenames. 14 | 15 | @details This is +printingAsciiCharacterSet, plus 16 | the space character, minus the forward slash. 17 | */ 18 | + (NSCharacterSet*)filenameLegalUnixCharacterSet ; 19 | 20 | /*! 21 | @brief Returns +filenameLegalUnixCharacterSet, 22 | minus the colon character, a total of 94 characters. 23 | */ 24 | + (NSCharacterSet*)filenameLegalMacUnixCharacterSet ; 25 | 26 | /*! 27 | @brief Prints a string containing one each of all the characters 28 | in the receiver. 29 | 30 | @details Warning: There are 65535 possible characters in Unicode, 31 | I believe.  This method is mainly for debugging. 32 | */ 33 | - (NSString*)stringOfAllCharacters ; 34 | 35 | /*! 36 | @brief Returns the character set of all Unicode characters which are 37 | not allowed to be in the 'host' portion of a URL. 38 | 39 | @details See http://en.wikipedia.org/wiki/Hostname ▸ Restrictions on valid host names 40 | */ 41 | + (NSCharacterSet*)characterSetNotAllowedInUrlHost ; 42 | 43 | /*! 44 | @brief Character set consisting of all the printing and non-printing characters you can 45 | type on a USA keyboard, using on the shift key as modifier. 46 | */ 47 | + (NSCharacterSet*)ssyUsaKeyboardCharacterSet ; 48 | 49 | + (NSCharacterSet*)ssyHexDigitsCharacterSet ; 50 | 51 | /*! 52 | @brief Returns the set of control characters, except for whitespace and 53 | newline characters. 54 | 55 | @details A user had 0x03 in a bookmark name. When encoded to JSON and then 56 | sent to my Firefox extension, Firefox barfed on it. Also, see 57 | http://stackoverflow.com/questions/24803275/how-do-i-remove-hidden-characters-from-a-nsstring 58 | */ 59 | + (NSCharacterSet*)zeroWidthAndIllegalCharacterSet; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /NSBundle+HelperPaths.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | /*! 5 | @brief Provides support for finding helper tools in 6 | a bundle's Contents/Helpers/ 7 | 8 | @details You can add a Copy Files Build Phase in Xcode for putting things 9 | in Contents/Helpers by setting the Destination popup to 'Wrapper' and then 10 | below that giving path "Contents/Helpers". 11 | 12 | The reason why you'd want to put helper tools in 13 | Contents/Helpers is in 14 | 15 | http://www.cocoabuilder.com/archive/message/cocoa/2009/3/26/233141 16 | 17 | Most pertinently, 18 | 19 | TO: cocoa-dev@lists.apple.com 20 | FROM : Jim Correia 21 | DATE : Thu Mar 26 00:52:10 2009 22 | 23 | On Mar 25, 2009, at 7:20 PM, Jerry Krinock wrote: 24 | 25 | > Use this code to build a Cocoa Command-Line tool and place 26 | > the product in Contents/MacOS of any application. Then 27 | > doubleclick it. Watch the log in the Terminal window and 28 | > un-hide and watch your dock. 29 | 30 | There are a couple of edge cases you will run into if you place 31 | auxiliary executables into the MacOS folder an execute them from there. 32 | 33 | Besides the one you mention, you can (in certain situations) end up 34 | with an incorrect entry in the LS database which will cause the wrong 35 | executable to be launched when the user double clicks on your app in 36 | the Finder. 37 | 38 | I recommend putting aux executables in 39 | 40 | .../Contents/Helpers/... 41 | 42 | as it avoids these issues. (And have filed an ER asking for this to 43 | become an officially sanctioned location for both bundled and 44 | unbundled helpers.) 45 | */ 46 | @interface NSBundle (HelperPaths) 47 | 48 | /* 49 | @brief Returns a path for a given tool name in the receiving bundle 50 | in directory Contents/Helpers/ 51 | */ 52 | - (NSString*)pathForHelper:(NSString*)helperName ; 53 | 54 | /* 55 | @brief Returns a path for a given executable name in the receiving bundle 56 | in directory Contents/MacOS/ 57 | */ 58 | - (NSString*)pathForMacOS:(NSString*)helperName ; 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /NSMutableSet+CoreDataOrderGlue.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | 4 | @interface NSMutableSet (CoreDataOrderGlue) 5 | 6 | /* This category on NSMutableSet is used to convert the (unordered) 7 | mutable set obtained from -[NSManagedObject mutableSetValueForKey] 8 | into an (ordered) array, and vice versa. The array is commonly 9 | required to display and edit data, at least in legacy applications 10 | getting a Core Data retrofit. 11 | 12 | Here is the problem. Because Core Data is built on a 13 | relational database, its to-many relationships are (unordered) sets. 14 | Many applications require ordered relationships. To add order, a 15 | 'position' attribute is normally added to each child. However, 16 | this model breaks down if children may have more than one parent, 17 | and thus different positions in each parent. The solution is to 18 | interpose a Glue object between the child and parent. 19 | The Glue object has a to-one relationship to a parent, 20 | a to-one relationship to a child, and a single attribute, the 21 | position, which is the index of the given child in the given 22 | parent. 23 | 24 | This category provides two methods of "glue code", to convert 25 | from Core Data's mutable set, via the Glue object, to the 26 | (ordered) array representation which is commonly required, 27 | and vice versa. 28 | 29 | API: The glueClass must be key-value compliant for orderKey 30 | and payloadKey. The payloadKey accesses the child value. */ 31 | 32 | // The first method is used in the getter of an NSManagedObject, 33 | // to get the array. (You can obtain the set, which is self, using 34 | // mutableSetValueForKey:) 35 | - (NSArray*)arrayWithOrderKey:(NSString*)orderKey 36 | payloadKey:(NSString*)payloadKey ; 37 | 38 | // The second method is used in the setter and is the Inverse 39 | // of the first method. It replaces all objects in Mutable Set 40 | // with the new objects from Array 41 | - (void)setContentsToArray:value 42 | glueClass:(Class)glueClass 43 | orderKey:(NSString*)orderKey 44 | payloadKey:(NSString*)payloadKey ; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NSString+RSS.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | BSD License 4 | 5 | Copyright (c) 2002, Brent Simmons 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, 12 | this list of conditions and the following disclaimer. 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | * Neither the name of ranchero.com or Brent Simmons nor the names of its 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 22 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 24 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 25 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 26 | OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | 32 | */ 33 | 34 | 35 | /* 36 | NSString+extras.h 37 | NetNewsWire 38 | 39 | Created by Brent Simmons on Fri Jun 14 2002. 40 | Copyright (c) 2002 Brent Simmons. All rights reserved. 41 | */ 42 | 43 | 44 | #import 45 | #import 46 | 47 | @interface NSString (RSSHelp) 48 | 49 | - (NSString *) trimWhiteSpace; 50 | 51 | - (NSString *) stripHTML; 52 | 53 | - (NSString *) ellipsizeAfterNWords: (NSInteger) n; 54 | 55 | + (BOOL) stringIsEmpty: (NSString *) s; 56 | 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /NSError+MyDomain.m: -------------------------------------------------------------------------------- 1 | #import "NSError+MyDomain.h" 2 | #import "NSError+InfoAccess.h" 3 | #import "NSBundle+MainApp.h" 4 | 5 | @implementation NSError (MyDomain) 6 | 7 | /* 8 | @details This method is for defensive programming, but it may be needed for 9 | the dylib in our Firefox extension. 10 | */ 11 | + (NSBundle*)mainAppBundle { 12 | NSBundle* answer = nil ; 13 | if ([[NSBundle class] respondsToSelector:@selector(mainAppBundle)]) { 14 | answer = [NSBundle mainAppBundle] ; 15 | } 16 | else { 17 | answer = [NSBundle mainBundle] ; 18 | } 19 | 20 | return answer ; 21 | } 22 | 23 | + (NSError*)errorWithLocalizedDescription:(NSString*)localizedDescription 24 | code:(NSInteger)code 25 | prettyFunction:(const char*)prettyFunction { 26 | NSDictionary* userInfo = nil ; 27 | if (localizedDescription) { 28 | NSString* const CFBundleVersionKey = @"Version of Main App Bundle" ; 29 | userInfo = [NSDictionary dictionaryWithObjectsAndKeys: 30 | localizedDescription, NSLocalizedDescriptionKey, 31 | [[self mainAppBundle] objectForInfoDictionaryKey:CFBundleVersionKey], CFBundleVersionKey, 32 | // The following gets added in -[SSYAlert support:]. It would be nice to do it here instead. 33 | // But then I'd have to #import SSYSystemDescriber into any project using this file. 34 | //[SSYSystemDescriber softwareVersionString], @"System Description", 35 | nil] ; 36 | } 37 | 38 | NSError* error = [NSError errorWithDomain:[self myDomain] 39 | code:code 40 | userInfo:userInfo] ; 41 | 42 | error = [error errorByAddingPrettyFunction:prettyFunction] ; 43 | error = [error errorByAddingTimestampNow] ; 44 | 45 | return error ; 46 | } 47 | 48 | + (NSString*)myDomain { 49 | NSString* domain = [[NSBundle mainBundle] bundleIdentifier] ; 50 | // Background/daemon/helper/tools will usually not have a bundle... 51 | if (!domain) { 52 | NSString* path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0] ; 53 | domain = [path lastPathComponent] ; 54 | } 55 | if (!domain) { 56 | domain = @"UnknownErrorDomain" ; 57 | } 58 | 59 | return domain ; 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /NSString+SSYCaseness.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface NSString (SSYCaseness) 4 | 5 | /*! 6 | @brief Determines whether the receiver or another given string 7 | has the first uppercase character in its sequence of characters 8 | 9 | @details Starting with the first character in each of the two 10 | strings, if the first character for which the receiver's characters 11 | is uppercase occurs before the first character for which the other 12 | string's character is uppercase, returns NSOrderedDescending. If 13 | the first character for which the other's characters is uppercase 14 | occurs before the first character for which the receiver's 15 | string's character is uppercase, returns NSOrderedAscending. 16 | Otherwise, returns NSOrderedSame. 17 | 18 | Test code for this method: 19 | 20 | - (void)cs1:(NSString*)s1 cs2:(NSString*)s2 { 21 | NSComparisonResult result = [s1 compareCase:s2] ; 22 | if (result == NSOrderedAscending) { 23 | NSLog(@"%@ <--up %@", s2, s1) ; 24 | } 25 | else if (result == NSOrderedDescending) { 26 | NSLog(@"%@ <--up %@", s1, s2) ; 27 | } 28 | else { 29 | NSLog(@"%@ SAME! %@", s1, s2) ; 30 | } 31 | } 32 | 33 | 34 | - (id)init { 35 | NSString* s1, *s2 ; 36 | s1 = @"" ; 37 | s2 = @"Hello, World!" ; 38 | [self cs1:s1 cs2:s2] ; 39 | 40 | s1 = @"heLLO, WORLD!" ; 41 | s2 = @"Hello, World!" ; 42 | [self cs1:s1 cs2:s2] ; 43 | 44 | s1 = @"" ; 45 | s2 = @"" ; 46 | [self cs1:s1 cs2:s2] ; 47 | 48 | s1 = @"bROWNDOG" ; 49 | s2 = @"Browndog" ; 50 | [self cs1:s1 cs2:s2] ; 51 | 52 | s1 = @"nobody" ; 53 | s2 = @"yabody" ; 54 | [self cs1:s1 cs2:s2] ; 55 | 56 | s1 = @"noBody" ; 57 | s2 = @"yabody" ; 58 | [self cs1:s1 cs2:s2] ; 59 | 60 | s1 = @"zzzzz" ; 61 | s2 = @"aaaaA" ; 62 | [self cs1:s1 cs2:s2] ; 63 | 64 | s1 = @"z" ; 65 | s2 = @"A" ; 66 | [self cs1:s1 cs2:s2] ; 67 | 68 | s1 = @"zzz" ; 69 | s2 = @"aaa" ; 70 | [self cs1:s1 cs2:s2] ; 71 | 72 | s1 = @"aaa" ; 73 | s2 = @"zzz" ; 74 | [self cs1:s1 cs2:s2] ; 75 | 76 | s1 = @"ZZ" ; 77 | s2 = @"AA" ; 78 | [self cs1:s1 cs2:s2] ; 79 | 80 | s1 = @"A" ; 81 | s2 = @"Z" ; 82 | [self cs1:s1 cs2:s2] ; 83 | 84 | exit(0) ; 85 | 86 | */ 87 | - (NSComparisonResult)compareCase:(NSString*)other ; 88 | 89 | @end 90 | -------------------------------------------------------------------------------- /NSManagedObject+Debug.m: -------------------------------------------------------------------------------- 1 | #import "NSManagedObject+Debug.h" 2 | 3 | 4 | @implementation NSManagedObject (Debug) 5 | 6 | - (NSString*)truncatedID { 7 | NSManagedObjectID* myID = [self objectID] ; 8 | NSString* uriStringRep = [[myID URIRepresentation] absoluteString] ; 9 | return [[self class] truncatedIDForManagedObjectWithUri:uriStringRep 10 | entityName:[[self entity] name]]; 11 | } 12 | 13 | + (NSString*)truncatedIDForManagedObjectWithUri:(NSString*)uriStringRep 14 | entityName:(NSString*)entityName { 15 | NSScanner* scanner = [[NSScanner alloc] initWithString:uriStringRep] ; 16 | NSString* slashEntityName = [[NSString alloc] initWithFormat: 17 | @"/%@", 18 | entityName] ; 19 | [scanner scanUpToString:slashEntityName 20 | intoString:NULL] ; 21 | [slashEntityName release] ; 22 | NSString* truncatedID = uriStringRep ; // Fail-safe default value 23 | if (![scanner isAtEnd]) { 24 | // The fact that the scanner is not at end means that 25 | // it must be at the beginning of the string slashEntityName, 26 | // so we can safely advance the scan location by 6. 27 | [scanner setScanLocation:[scanner scanLocation] + 6] ; 28 | if (![scanner isAtEnd]) { 29 | // Scan up to the final "/" 30 | [scanner scanUpToString:@"/" 31 | intoString:NULL] ; 32 | if (![scanner isAtEnd]) { 33 | NSInteger location = [scanner scanLocation] + 1 ; // +1 for the "/" 34 | NSInteger length = [uriStringRep length] - location ; 35 | if (length > 0) { 36 | NSRange truncatedIDRange = NSMakeRange(location, length) ; 37 | truncatedID = [uriStringRep substringWithRange:truncatedIDRange] ; 38 | if ([truncatedID length] > 5) { 39 | // It's a looooong temporary string, typically 40 | // truncatedID = "t77470F45-9092-4480-95AB-A6D79F1CE70537" 41 | NSString* end = [truncatedID substringFromIndex:([truncatedID length] - 4)] ; 42 | NSString* begin = [truncatedID substringToIndex:1] ; // first character, "t" 43 | truncatedID = [NSString stringWithFormat: 44 | @"%@'%@", 45 | begin, 46 | end] ; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | [scanner release] ; 53 | 54 | return truncatedID ; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /NSBundle+MainApp.m: -------------------------------------------------------------------------------- 1 | #import "NSBundle+MainApp.h" 2 | 3 | static NSBundle* mainAppBundle = nil ; 4 | 5 | @implementation NSBundle (MainApp) 6 | 7 | + (NSBundle*)mainAppBundle { 8 | @synchronized(self) { 9 | if (!mainAppBundle) { 10 | NSString* path = [[[NSProcessInfo processInfo] arguments] objectAtIndex:0]; 11 | /* See if path is a symbolic link, because if this process 12 | was launched by a symbolic link, starting in macOS 10.14, 13 | this path will be the symbolic link's path!! 14 | 15 | (Similarly, if this process is a bare executable, and was 16 | launched via symbolic link, [[NSBundle mainBundle] bundlePath] 17 | will return the parent of the symbolic link, which is useless 18 | for our purpose here.) I have not tested what 19 | [[NSBundle mainBundle] bundlePath] returns if this process is in a 20 | bundle but launched via a symbolic link. */ 21 | NSString* symlinkDestin = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath:path 22 | error:NULL]; 23 | if (symlinkDestin) { 24 | path = symlinkDestin; 25 | } 26 | 27 | /* Each iteration of the following loop clips off one path 28 | component at the end, so that `path` is eventually only "/" and 29 | the loop exits. Along the way, whenever we find a path ending 30 | in ".app", we store its bundle, because it is a candidate for 31 | being the mainAppBundle ew are looking for. The last such 32 | candidate is the winner. */ 33 | while (path.length > 4) { 34 | if ([path hasSuffix:@".app"]) { 35 | /* This path is a candidate. */ 36 | #if !__has_feature(objc_arc) 37 | [mainAppBundle release]; 38 | #endif 39 | mainAppBundle = [NSBundle bundleWithPath:path]; 40 | #if !__has_feature(objc_arc) 41 | [mainAppBundle retain]; 42 | #endif 43 | } 44 | 45 | path = [path stringByDeletingLastPathComponent]; 46 | } 47 | } 48 | } 49 | 50 | return mainAppBundle ; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /NSFileManager+SSYObscureShackles.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | extern NSString* const SSYObscureShacklesErrorDomain ; 4 | 5 | @interface NSFileManager (SSYObscureShackles) 6 | 7 | /*! 8 | @brief Tries to remove any unix File Flags (as in chflags and the "Locked" 9 | checkbox in Finder's Get Info) and any Access Control List entries (ACLs) and 10 | from a given path 11 | 12 | @details File Flags and ACLs are the second and third attributes that can 13 | prevent desired file operations. They are not as well known as the first one, 14 | POSIX permissions. Therefore, they don't cause trouble as often, but when 15 | they do, they can be a mystery. 16 | 17 | Because File Flags seem to trump ACLs, this method tries to remove any File 18 | Flags first, then ACLs. 19 | 20 | For more info on file flags, see man chflags(2). This method attampts to 21 | remove both System ("S*") and User ("U*") flags. The former will probably 22 | fail without elevated permissions, but you should get an error indicating 23 | such. File Flags show in Finder as the "Locked" checkbox in a file's Get Info. 24 | 25 | Actually, according to Chris Suter there may be a fourth (MAC Plug-In) and 26 | fifth (Kauth Plug-In) attributes that can cause trouble: 27 | 28 | Jerry: if I simply invoke acl_set_file() at the end of my method per your 29 | first suggestion, I get an errno=1, inadequate permissions, as you predicted 30 | and I kind of expected. 31 | 32 | Chris Suter: Yeah, you might also find the immutable flag prevents you from 33 | changing ACLs too, in which case you'll have to temporarily change it. 34 | It's also possible for a Mandatory Access Control (MAC) plug-in to 35 | deny you access (e.g. the TMSafetyNet MAC plugin will prevent you 36 | from removing ACLs on Time Machine backups) as will the plug-in used 37 | by the sandbox. A Kauth plug-in might also be able to prevent access. 38 | 39 | Kind regards, 40 | 41 | Chris Suter 42 | 43 | This method does not address MAC Plug-Ins or Kauth Plug-Ins. 44 | 45 | TODO: Address the issue of MAC Plug-Ins or Kauth Plug-Ins too. Learn the 46 | precedence so that multiple of these gremlins can be succesfully peeled off 47 | files that have more than one. 48 | */ 49 | - (BOOL)unshacklePath:(NSString*)path 50 | error_p:(NSError**)error ; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /NSUserDefaults+MoreTypes.m: -------------------------------------------------------------------------------- 1 | #import "NSUserDefaults+MoreTypes.h" 2 | 3 | @implementation NSUserDefaults (MoreTypes) 4 | 5 | - (void)setColor:(NSColor *)color forKey:(NSString *)key { 6 | NSError* error = nil; 7 | NSData* data = [NSKeyedArchiver archivedDataWithRootObject:color 8 | requiringSecureCoding:YES 9 | error:&error]; 10 | if (error) { 11 | NSLog(@"Internal error 382-5849 archiving color for key %@ in user defaults.", key); 12 | } 13 | [self setObject:data forKey:key] ; 14 | } 15 | 16 | - (NSColor*)colorForKey:(NSString *)key { 17 | NSColor* color = nil ; 18 | NSData* data = [self dataForKey:key] ; 19 | if (data != nil) { 20 | NSError* error = nil; 21 | color = (NSColor*)[NSKeyedUnarchiver unarchivedObjectOfClass:[NSColor class] 22 | fromData:data 23 | error:&error]; 24 | if (error) { 25 | NSLog(@"Internal error 382-5850 unarchiving color for key %@ in user defaults.", key); 26 | } 27 | } 28 | 29 | return color ; 30 | } 31 | 32 | - (void)upgradeDeprecatedArchiveDataForOldKey:(NSString*)oldKey 33 | newKey:(NSString*)newKey { 34 | NSColor* color = nil ; 35 | NSData* data = [[NSUserDefaults standardUserDefaults] dataForKey:oldKey] ; 36 | if (data) { 37 | @try { 38 | // Sorry, Apple: Need to get old color with deprecated method 39 | #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 40 | color = (NSColor*)[NSUnarchiver unarchiveObjectWithData:data] ; 41 | #pragma GCC diagnostic warning "-Wdeprecated-declarations" 42 | } @catch (NSException *exception) { 43 | NSLog(@"Exception upgrading color in user defaults for key %@, which was expected to be produced by +[NSArchiver archivedDataWithRootObject:]", oldKey); 44 | } 45 | if (!color || [color isKindOfClass:[NSColor class]]) { 46 | [self setColor:color 47 | forKey:newKey]; 48 | } else { 49 | NSLog(@"Error upgrading color in user defaults for key %@. Expected NSColor, found %@", oldKey, [color className]); 50 | } 51 | } 52 | } 53 | 54 | @end 55 | -------------------------------------------------------------------------------- /NSDocumentController+SSYFixLaunchServicesBug.m: -------------------------------------------------------------------------------- 1 | #import "NSDocumentController+SSYFixLaunchServicesBug.h" 2 | 3 | @implementation NSDocumentController (SSYFixLaunchServicesBug) 4 | 5 | - (NSString*)fixLaunchServicesBugForUrl:(NSURL*)url 6 | otherUrl:(NSURL* _Nullable)otherUrl 7 | typeName_p:(NSString**)typeName_p { 8 | NSString* result = nil; 9 | 10 | if (!url) { 11 | url = otherUrl; 12 | } 13 | 14 | if (url) { 15 | NSString* revisedTypeName = nil; 16 | NSString* extension = url.absoluteString.pathExtension; 17 | extension = [extension stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"/"]]; 18 | 19 | SEL mainAppBundleSelector = NSSelectorFromString(@"mainAppBundle"); 20 | /* -mainAppBundle is defined in NSBundle+MainApp.h */ 21 | NSBundle* bundle; 22 | if ([NSBundle respondsToSelector:mainAppBundleSelector]) { 23 | bundle = [NSBundle performSelector:mainAppBundleSelector]; 24 | } else { 25 | bundle = [NSBundle mainBundle]; 26 | } 27 | 28 | NSArray* docInfos = [bundle objectForInfoDictionaryKey:@"CFBundleDocumentTypes"]; 29 | for (NSDictionary* info in docInfos) { 30 | for (NSString* aExtension in [info objectForKey:@"CFBundleTypeExtensions"]) { 31 | if ([extension isEqualToString:aExtension]) { 32 | NSString* aType = [[info objectForKey:@"LSItemContentTypes"] firstObject]; 33 | if (aType) { 34 | revisedTypeName = [aType lowercaseString]; 35 | break; 36 | } 37 | } 38 | } 39 | if (revisedTypeName) { 40 | break; 41 | } 42 | } 43 | 44 | if (revisedTypeName && typeName_p) { 45 | if (![revisedTypeName isEqualToString:*typeName_p]) { 46 | result = [NSString stringWithFormat: 47 | @"Launch Services Bug: doc type %@ (was %@) for %@", 48 | revisedTypeName, 49 | *typeName_p, 50 | url.absoluteString]; 51 | *typeName_p = revisedTypeName; 52 | } 53 | } 54 | } 55 | 56 | return result; 57 | } 58 | @end 59 | -------------------------------------------------------------------------------- /NSDate+NiceFormats.m: -------------------------------------------------------------------------------- 1 | #import "NSDate+NiceFormats.h" 2 | #import "NSString+SSYExtraUtils.h" 3 | 4 | static NSDateFormatter* static_geekDateFormatter = nil; 5 | static NSDateFormatter* static_geekMilliDateFormatter = nil; 6 | 7 | @implementation NSDate (NiceFormats) 8 | 9 | - (NSString*)medDateShortTimeString { 10 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init] ; 11 | [dateFormatter setDateStyle:NSDateFormatterMediumStyle] ; 12 | [dateFormatter setTimeStyle:NSDateFormatterShortStyle] ; 13 | NSString* string = [dateFormatter stringFromDate:self]; 14 | 15 | #if !__has_feature(objc_arc) 16 | [dateFormatter release]; 17 | #endif 18 | 19 | return string ; 20 | } 21 | 22 | + (NSDateFormatter*)geekDateFormatter { 23 | if (!static_geekDateFormatter) { 24 | static_geekDateFormatter = [[NSDateFormatter alloc] init] ; 25 | [static_geekDateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"] ; 26 | } 27 | 28 | return static_geekDateFormatter ; 29 | } 30 | 31 | + (NSDateFormatter*)geekMilliDateFormatter { 32 | if (!static_geekMilliDateFormatter) { 33 | static_geekMilliDateFormatter = [[NSDateFormatter alloc] init] ; 34 | [static_geekMilliDateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"] ; 35 | } 36 | 37 | return static_geekMilliDateFormatter ; 38 | } 39 | 40 | - (NSString*)geekDateTimeString { 41 | return [[[self class] geekDateFormatter] stringFromDate:self] ; 42 | } 43 | 44 | - (NSString*)geekDateTimeStringMilli { 45 | return [[[self class] geekMilliDateFormatter] stringFromDate:self] ; 46 | } 47 | 48 | - (NSString*)hourMinuteSecond { 49 | return [[self geekDateTimeString] substringFromIndex:11] ; 50 | } 51 | 52 | - (NSString*)compactDateTimeString { 53 | // Remove spaces, dashs and colons from YYYY-MM-DD HH:MM:SS 54 | NSString* s1 = [[self geekDateTimeString] stringByReplacingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" :-"] 55 | withString:@""] ; 56 | return s1 ; 57 | } 58 | 59 | + (NSString*)currentDateFormattedConcisely { 60 | return [[NSDate date] medDateShortTimeString] ; 61 | } 62 | 63 | /* 64 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 65 | [dateFormatter setDateFormat:@"ss.SSSS"]; 66 | NSDate *date = [NSDate date]; 67 | NSString* secondsWithMilliseconds = [dateFormatter stringFromDate:date]; 68 | [dateFormatter release] ; 69 | */ 70 | 71 | @end 72 | --------------------------------------------------------------------------------