diff --git a/Configurations/Omni-Global-Settings.xcconfig b/Configurations/Omni-Global-Settings.xcconfig index e1aec458..5b5aeeaf 100644 --- a/Configurations/Omni-Global-Settings.xcconfig +++ b/Configurations/Omni-Global-Settings.xcconfig @@ -141,13 +141,13 @@ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES CLANG_ANALYZER_DEADCODE_DEADSTORES = YES CLANG_ANALYZER_NONNULL = YES CLANG_ANALYZER_GCD = YES -CLANG_ANALYZER_MALLOC = YES +CLANG_ANALYZER_MEMORY_MANAGEMENT = YES // Static Analyzer - Checks - Objective-C CLANG_ANALYZER_OBJC_ATSYNC = YES CLANG_ANALYZER_OBJC_NSCFERROR = YES CLANG_ANALYZER_OBJC_INCOMP_METHOD_TYPES = YES -CLANG_ANALYZER_OBJC_CFNUMBER = YES +CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE CLANG_ANALYZER_OBJC_COLLECTIONS = YES CLANG_ANALYZER_OBJC_UNUSED_IVARS = YES CLANG_ANALYZER_OBJC_SELF_INIT = YES diff --git a/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSImage-OAExtensions.m b/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSImage-OAExtensions.m index 700032af..51801097 100644 --- a/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSImage-OAExtensions.m +++ b/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSImage-OAExtensions.m @@ -68,8 +68,7 @@ - (id)_initWithContentsOfFile_replacement:(NSString *)fileName; if (size.width != rint(size.width) || size.height != rint(size.height)) NSLog(@"Image %@ has non-integral size %@", fileName, NSStringFromSize(size)); - OBPOSTCONDITION(size.width == rint(size.width)); - OBPOSTCONDITION(size.height == rint(size.height)); + OBASSERT_IF(OFURLContainsURL([[NSBundle mainBundle] bundleURL], [NSURL fileURLWithPath:fileName]), size.width == rint(size.width) && size.height == rint(size.height), "Our resources should be integral-sized"); return self; } diff --git a/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSLayoutManager-OAExtensions.m b/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSLayoutManager-OAExtensions.m index 605b6fb4..bc36bc1b 100644 --- a/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSLayoutManager-OAExtensions.m +++ b/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSLayoutManager-OAExtensions.m @@ -166,8 +166,9 @@ + (CGFloat)heightForAttributes:(NSDictionary *)attributes; } NSNumber *heightNumber = [HeightForAttributesCache objectForKey:attributes]; - if (heightNumber) + if (heightNumber != nil) { return [heightNumber cgFloatValue]; + } // NOTE: This doesn't account for custom layout done by subclasses OBASSERT(self == [NSLayoutManager class]); diff --git a/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSTableView-OAExtensions.h b/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSTableView-OAExtensions.h index 9108242a..82aab00e 100644 --- a/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSTableView-OAExtensions.h +++ b/Frameworks/OmniAppKit/OpenStepExtensions.subproj/NSTableView-OAExtensions.h @@ -1,4 +1,4 @@ -// Copyright 1997-2015 Omni Development, Inc. All rights reserved. +// Copyright 1997-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -10,11 +10,11 @@ #import #import -typedef enum _OATableViewRowVisibility { +typedef NS_ENUM(NSInteger, OATableViewRowVisibility) { OATableViewRowVisibilityLeaveUnchanged, OATableViewRowVisibilityScrollToVisible, OATableViewRowVisibilityScrollToMiddleIfNotVisible -} OATableViewRowVisibility; +}; #import diff --git a/Frameworks/OmniAppKit/OpenStepExtensions.subproj/OAFontDescriptor.m b/Frameworks/OmniAppKit/OpenStepExtensions.subproj/OAFontDescriptor.m index 27dd1ad3..5ce627a1 100644 --- a/Frameworks/OmniAppKit/OpenStepExtensions.subproj/OAFontDescriptor.m +++ b/Frameworks/OmniAppKit/OpenStepExtensions.subproj/OAFontDescriptor.m @@ -523,7 +523,7 @@ - (CGFloat)size; } NSNumber *fontSize = [_attributes objectForKey:(id)kCTFontSizeAttribute]; - if (fontSize) + if (fontSize != nil) return [fontSize cgFloatValue]; @@ -557,7 +557,7 @@ - (NSInteger)weight; NSNumber *weightNumber = [self _coreTextFontWeight]; - if (weightNumber) { + if (weightNumber != nil) { CGFloat weight = [weightNumber cgFloatValue]; return _weightToFontManagerWeight(weight); } @@ -584,7 +584,7 @@ static CTFontSymbolicTraits _lookupSymbolicTraits(OAFontDescriptor *self) NSDictionary *traits = [self->_attributes objectForKey:(id)kCTFontTraitsAttribute]; NSNumber *symbolicTraitsNumber = [traits objectForKey:(id)kCTFontSymbolicTrait]; - if (symbolicTraitsNumber) { + if (symbolicTraitsNumber != nil) { OBASSERT(sizeof(CTFontSymbolicTraits) == sizeof(unsigned int)); CTFontSymbolicTraits result = [symbolicTraitsNumber unsignedIntValue]; return _clearClassMask(result); diff --git a/Frameworks/OmniAppKit/Preferences.subproj/OAPreferenceClientRecord.m b/Frameworks/OmniAppKit/Preferences.subproj/OAPreferenceClientRecord.m index 7d4083ae..5a2d6f86 100644 --- a/Frameworks/OmniAppKit/Preferences.subproj/OAPreferenceClientRecord.m +++ b/Frameworks/OmniAppKit/Preferences.subproj/OAPreferenceClientRecord.m @@ -1,4 +1,4 @@ -// Copyright 1997-2016 Omni Development, Inc. All rights reserved. +// Copyright 1997-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -122,7 +122,8 @@ - (OAPreferenceClient *)newClientInstanceInController:(OAPreferenceController *) // This method was replaced by -willBecomeCurrentPreferenceClient (and the -did variant was added) OBASSERT_NOT_IMPLEMENTED(clientInstance, becomeCurrentPreferenceClient); - + + OBASSERT(clientInstance); return clientInstance; } diff --git a/Frameworks/OmniAppKit/Widgets.subproj/OASpringLoadHelper.m b/Frameworks/OmniAppKit/Widgets.subproj/OASpringLoadHelper.m index 5117e214..b556e7d7 100644 --- a/Frameworks/OmniAppKit/Widgets.subproj/OASpringLoadHelper.m +++ b/Frameworks/OmniAppKit/Widgets.subproj/OASpringLoadHelper.m @@ -1,4 +1,4 @@ -// Copyright 2003-2015 Omni Development, Inc. All rights reserved. +// Copyright 2003-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -83,7 +83,7 @@ - (void)_startSpringTimer; // As of 10.5, Finder's spring-loaded folder preference is in the global domain under com.apple.springing.delay, as seconds. NSNumber *springLoadedFolderDelayNumber = [[NSUserDefaults standardUserDefaults] objectForKey:@"com.apple.springing.delay"]; - if (springLoadedFolderDelayNumber) { + if (springLoadedFolderDelayNumber != nil) { // Might be a different type than we expect; be careful. if ([springLoadedFolderDelayNumber respondsToSelector:@selector(doubleValue)]) springingDelaySeconds = [springLoadedFolderDelayNumber doubleValue]; diff --git a/Frameworks/OmniAppKit/Widgets.subproj/OAWebPageViewer.h b/Frameworks/OmniAppKit/Widgets.subproj/OAWebPageViewer.h index 365708b9..53d7a6d1 100644 --- a/Frameworks/OmniAppKit/Widgets.subproj/OAWebPageViewer.h +++ b/Frameworks/OmniAppKit/Widgets.subproj/OAWebPageViewer.h @@ -30,6 +30,7 @@ - (void)loadPath:(NSString *)path; - (void)loadRequest:(NSURLRequest *)request; +- (void)loadCachedHTML:(NSURL *)cachedFileURL forWebURL:(NSURL *)webURL; // will use the cached html but still attempt to retrieve relative path linked css and images from the web. /// on a successful load, success will be true, and error will be nil. If a failure, success will be false and error set. URL is set in all cases and points to the original request's url. - (void)loadRequest:(NSURLRequest *)request onCompletion:(void (^)(BOOL success, NSURL *url, NSError *error))completionBlock; diff --git a/Frameworks/OmniAppKit/Widgets.subproj/OAWebPageViewer.m b/Frameworks/OmniAppKit/Widgets.subproj/OAWebPageViewer.m index addf2bd0..e41015ce 100644 --- a/Frameworks/OmniAppKit/Widgets.subproj/OAWebPageViewer.m +++ b/Frameworks/OmniAppKit/Widgets.subproj/OAWebPageViewer.m @@ -115,6 +115,17 @@ - (void)loadRequest:(NSURLRequest *)request onCompletion:(void (^)(BOOL success, [self loadRequest:request]; } +- (void)loadCachedHTML:(NSURL *)cachedFileURL forWebURL:(NSURL *)webURL; +{ + NSURL *baseURL = [[webURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]; + NSString *htmlString = [NSString stringWithContentsOfURL:cachedFileURL encoding:kCFStringEncodingUTF8 error:nil]; + if (htmlString) { + [[_webView mainFrame] loadHTMLString:htmlString baseURL:baseURL]; + } else { + [self loadRequest:[NSURLRequest requestWithURL:webURL]]; + } +} + #pragma mark - #pragma mark Accessors @@ -255,6 +266,13 @@ - (void)webView:(WebView *)aWebView decidePolicyForNavigationAction:(NSDictionar } } + // Cached news urls are in the user's Library folder + NSURL *userLibrary = [[NSFileManager defaultManager] URLForDirectory:NSLibraryDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; + if ([[url path] hasPrefix:[userLibrary path]]) { + [listener use]; + return; + } + // Initial content WebNavigationType webNavigationType = [[actionInformation objectForKey:WebActionNavigationTypeKey] intValue]; if (webNavigationType == WebNavigationTypeOther || webNavigationType == WebNavigationTypeReload) { diff --git a/Frameworks/OmniBase/NSError-OBExtensions.m b/Frameworks/OmniBase/NSError-OBExtensions.m index a9b9f3a7..bf091efe 100644 --- a/Frameworks/OmniBase/NSError-OBExtensions.m +++ b/Frameworks/OmniBase/NSError-OBExtensions.m @@ -183,8 +183,8 @@ - (id)initWithPropertyList:(NSDictionary *)propertyList; NSString *domain = [propertyList objectForKey:@"domain"]; NSNumber *code = [propertyList objectForKey:@"code"]; - OBASSERT(domain); - OBASSERT(code); + OBASSERT(domain != nil); + OBASSERT(code != nil); NSDictionary *userInfo = [propertyList objectForKey:@"userInfo"]; if (userInfo) { diff --git a/Frameworks/OmniBase/NSError-OBUtilities.h b/Frameworks/OmniBase/NSError-OBUtilities.h index e9b8b42b..286fd8eb 100644 --- a/Frameworks/OmniBase/NSError-OBUtilities.h +++ b/Frameworks/OmniBase/NSError-OBUtilities.h @@ -19,10 +19,10 @@ extern "C" { @class NSString, NSError; -extern NSString * const OBErrorDomain; -extern NSString * const OBFileNameAndNumberErrorKey; +extern NSErrorDomain const OBErrorDomain; +extern NSErrorUserInfoKey const OBFileNameAndNumberErrorKey; -enum { +typedef NS_ERROR_ENUM(OBErrorDomain, OBError) { OBErrorChained = 1, /* skip code zero since that is often defined to be 'no error' */ }; diff --git a/Frameworks/OmniBase/assertions.h b/Frameworks/OmniBase/assertions.h index 0917d51a..98e13c3a 100644 --- a/Frameworks/OmniBase/assertions.h +++ b/Frameworks/OmniBase/assertions.h @@ -139,13 +139,13 @@ extern void OBLogAssertionFailure(const char *type, const char *expression, cons // Scalar-taking variants that also do the test at compile time to just signal clang attributes. The input must be a scalar l-value to avoid evaluation of code. This will mark the value as referenced, though, so we don't get unused variable warnings. #define OBASSERT_NULL(pointer, ...) do { \ - if (pointer) { \ + if ((pointer) != NULL) { \ void *valuePtr __attribute__((unused)) = &pointer; /* have compiler check that it is an l-value */ \ OBInvokeAssertionFailureHandler("OBASSERT_NULL", #pointer, __FILE__, __LINE__, @"" __VA_ARGS__); \ } \ } while(NO); #define OBASSERT_NOTNULL(pointer, ...) do { \ - if (!pointer) { \ + if ((pointer) == NULL) { \ void *valuePtr __attribute__((unused)) = &pointer; /* have compiler check that it is an l-value */ \ OBInvokeAssertionFailureHandler("OBASSERT_NOTNULL", #pointer, __FILE__, __LINE__, @"" __VA_ARGS__); \ } \ diff --git a/Frameworks/OmniDataObjects/Errors.h b/Frameworks/OmniDataObjects/Errors.h index 36d449e1..9f215474 100644 --- a/Frameworks/OmniDataObjects/Errors.h +++ b/Frameworks/OmniDataObjects/Errors.h @@ -17,7 +17,7 @@ extern NSErrorUserInfoKey const ODODetailedErrorsKey; // If multiple errors occu extern NSErrorUserInfoKey const ODODetailedErrorsKey; // if multiple validation errors occur in one operation, they are collected in an array and added with this key to the "top-level error" of the operation -typedef NS_ENUM(NSInteger, ODOError) { +typedef NS_ERROR_ENUM(ODOErrorDomain, ODOError) { ODONoError = 0, ODOUnableToLoadModel, diff --git a/Frameworks/OmniDataObjects/ODOObject-Internal.m b/Frameworks/OmniDataObjects/ODOObject-Internal.m index 6d1e7e6b..703db82c 100644 --- a/Frameworks/OmniDataObjects/ODOObject-Internal.m +++ b/Frameworks/OmniDataObjects/ODOObject-Internal.m @@ -482,6 +482,10 @@ _Nullable CFArrayRef ODOObjectCreateDifferenceRecordFromSnapshot(ODOObject *self continue; } + if (flags.transient && flags.calculated && ![[entity instanceClass] shouldIncludeSnapshotForTransientCalculatedProperty:prop]) { + continue; + } + id oldValue = (id)CFArrayGetValueAtIndex(snapshot, propertyIndex); id newValue = _ODOObjectValueAtIndex(self, propertyIndex); diff --git a/Frameworks/OmniDataObjects/OmniDataObjects.xcodeproj/project.pbxproj b/Frameworks/OmniDataObjects/OmniDataObjects.xcodeproj/project.pbxproj index 374019e0..94b7594b 100644 --- a/Frameworks/OmniDataObjects/OmniDataObjects.xcodeproj/project.pbxproj +++ b/Frameworks/OmniDataObjects/OmniDataObjects.xcodeproj/project.pbxproj @@ -1284,6 +1284,9 @@ DevelopmentTeam = 34YW5XSRB7; DevelopmentTeamName = "The Omni Group"; }; + 34B77C0F1EC10F6C00BBBA65 = { + LastSwiftMigration = 0920; + }; 8DC2EF4F0486A6940098B216 = { LastSwiftMigration = 0900; }; @@ -1841,7 +1844,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.omnigroup.framework.OmniDataObjects; PRODUCT_NAME = OmniDataObjects; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1857,7 +1860,7 @@ MODULEMAP_FILE = OmniDataObjects.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.omnigroup.framework.OmniDataObjects; PRODUCT_NAME = OmniDataObjects; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -1887,7 +1890,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.omnigroup.framework.OmniDataObjects; PRODUCT_NAME = OmniDataObjects; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -1902,7 +1905,7 @@ MODULEMAP_FILE = OmniDataObjects.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.omnigroup.framework.OmniDataObjects; PRODUCT_NAME = OmniDataObjects; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -2022,7 +2025,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.omnigroup.framework.OmniDataObjects; PRODUCT_NAME = OmniDataObjects; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -2038,7 +2041,7 @@ MODULEMAP_FILE = OmniDataObjects.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.omnigroup.framework.OmniDataObjects; PRODUCT_NAME = OmniDataObjects; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Frameworks/OmniDocumentStore/ODSErrors.h b/Frameworks/OmniDocumentStore/ODSErrors.h index 11045ec3..46d0f818 100644 --- a/Frameworks/OmniDocumentStore/ODSErrors.h +++ b/Frameworks/OmniDocumentStore/ODSErrors.h @@ -1,4 +1,4 @@ -// Copyright 2010-2013 The Omni Group. All rights reserved. +// Copyright 2010-2017 The Omni Group. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -9,7 +9,9 @@ #import -enum { +extern NSErrorDomain const ODSErrorDomain; + +typedef NS_ERROR_ENUM(ODSErrorDomain, ODSError) { // skip zero since it means 'no error' to AppleScript (actually the first 10-ish are defined in NSScriptCommand) _ODSNoError = 0, @@ -18,7 +20,5 @@ enum { }; -extern NSString * const ODSErrorDomain; - #define ODSErrorWithInfo(error, code, description, suggestion, ...) _OBError(error, ODSErrorDomain, code, __FILE__, __LINE__, NSLocalizedDescriptionKey, description, NSLocalizedRecoverySuggestionErrorKey, (suggestion), ## __VA_ARGS__) #define ODSError(error, code, description, reason) ODSErrorWithInfo((error), (code), (description), (reason), nil) diff --git a/Frameworks/OmniDocumentStore/ODSErrors.m b/Frameworks/OmniDocumentStore/ODSErrors.m index b9aed618..a375ee3a 100644 --- a/Frameworks/OmniDocumentStore/ODSErrors.m +++ b/Frameworks/OmniDocumentStore/ODSErrors.m @@ -1,4 +1,4 @@ -// Copyright 2010-2015 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -9,4 +9,4 @@ RCS_ID("$Id$") -NSString * const ODSErrorDomain = @"com.omnigroup.frameworks.OmniDocumentStore.ErrorDomain"; +NSErrorDomain const ODSErrorDomain = @"com.omnigroup.frameworks.OmniDocumentStore.ErrorDomain"; diff --git a/Frameworks/OmniDocumentStore/ODSFilter.h b/Frameworks/OmniDocumentStore/ODSFilter.h index 7a944af3..3cb38bde 100644 --- a/Frameworks/OmniDocumentStore/ODSFilter.h +++ b/Frameworks/OmniDocumentStore/ODSFilter.h @@ -1,4 +1,4 @@ -// Copyright 2010-2015 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -10,7 +10,7 @@ #import @class NSPredicate, NSSet; -@class ODSStore, ODSStore, ODSScope, ODSFolderItem; +@class ODSStore, ODSScope, ODSFolderItem; @interface ODSFilter : NSObject diff --git a/Frameworks/OmniDocumentStore/ODSFilter.m b/Frameworks/OmniDocumentStore/ODSFilter.m index ea399a58..ac48e5c2 100644 --- a/Frameworks/OmniDocumentStore/ODSFilter.m +++ b/Frameworks/OmniDocumentStore/ODSFilter.m @@ -18,6 +18,7 @@ @interface ODSFilter () @property(nonatomic,copy) NSSet *unfilteredSourceItems; +@property (nonatomic, readwrite) NSSet *filteredItems; @end @implementation ODSFilter @@ -25,8 +26,6 @@ @implementation ODSFilter // The incoming items from the document store OFSetBinding *_sourceItemsBinding; NSSet *_sourceItems; - - NSPredicate *_filterPredicate; } - (instancetype)_initWithBindingSourcePoint:(OFBindingPoint *)sourceBindingPoint; @@ -89,27 +88,17 @@ - (void)setFilterPredicate:(NSPredicate *)filterPredicate; if (filterPredicate == _filterPredicate) return; - - [self willChangeValueForKey:OFValidateKeyPath(self, filteredItems)]; - _filterPredicate = filterPredicate; - - [self didChangeValueForKey:OFValidateKeyPath(self, filteredItems)]; + [self _updateFilteredItems]; } -+ (NSSet *)keyPathsForValuesAffectingFilteredItems; -{ - return [NSSet setWithObject:UnfilteredSourceItems]; -} - -- (NSSet *)filteredItems; +- (void)_updateFilteredItems { OBPRECONDITION([NSThread isMainThread]); // We want to fire KVO only on the main thread - if (_filterPredicate) - return [_sourceItems filteredSetUsingPredicate:_filterPredicate]; + self.filteredItems = [_sourceItems filteredSetUsingPredicate:_filterPredicate]; else - return _sourceItems; + self.filteredItems = _sourceItems; } #pragma mark - Private @@ -130,6 +119,7 @@ - (void)setUnfilteredSourceItems:(NSSet *)newItems; [self willChangeValueForKey:UnfilteredSourceItems]; _sourceItems = [newItems copy]; [self didChangeValueForKey:UnfilteredSourceItems]; + [self _updateFilteredItems]; } @end diff --git a/Frameworks/OmniDocumentStore/ODSFolderItem.m b/Frameworks/OmniDocumentStore/ODSFolderItem.m index f5004eef..d82a16bb 100644 --- a/Frameworks/OmniDocumentStore/ODSFolderItem.m +++ b/Frameworks/OmniDocumentStore/ODSFolderItem.m @@ -1,4 +1,4 @@ -// Copyright 2010-2015 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -216,9 +216,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N self.userModificationDate = date; } else if ([keyPath isEqualToString:ODSItemIsDownloadingBinding]) { NSNumber *oldValue = change[NSKeyValueChangeOldKey]; - OBASSERT(oldValue); + OBASSERT_NOTNULL(oldValue); NSNumber *newValue = change[NSKeyValueChangeNewKey]; - OBASSERT(newValue); + OBASSERT_NOTNULL(newValue); if ([oldValue boolValue] == NO && [newValue boolValue]) self.isDownloading = YES; @@ -226,9 +226,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N self.isDownloading = [_childItems any:^BOOL(ODSItem *child) { return child.isDownloading; }] != nil; } else if ([keyPath isEqualToString:ODSItemIsDownloadedBinding]) { NSNumber *oldValue = change[NSKeyValueChangeOldKey]; - OBASSERT(oldValue); + OBASSERT_NOTNULL(oldValue); NSNumber *newValue = change[NSKeyValueChangeNewKey]; - OBASSERT(newValue); + OBASSERT_NOTNULL(newValue); if ([oldValue boolValue] == NO && [newValue boolValue]) self.isDownloaded = YES; @@ -236,9 +236,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N self.isDownloaded = [_childItems all:^BOOL(ODSItem *child) { return child.isDownloaded; }]; } else if ([keyPath isEqualToString:ODSItemIsUploadingBinding]) { NSNumber *oldValue = change[NSKeyValueChangeOldKey]; - OBASSERT(oldValue); + OBASSERT_NOTNULL(oldValue); NSNumber *newValue = change[NSKeyValueChangeNewKey]; - OBASSERT(newValue); + OBASSERT_NOTNULL(newValue); if ([oldValue boolValue] == NO && [newValue boolValue]) self.isUploading = YES; @@ -246,9 +246,9 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N self.isUploading = [_childItems any:^BOOL(ODSItem *child) { return child.isUploading; }] != nil; } else if ([keyPath isEqualToString:ODSItemIsUploadedBinding]) { NSNumber *oldValue = change[NSKeyValueChangeOldKey]; - OBASSERT(oldValue); + OBASSERT_NOTNULL(oldValue); NSNumber *newValue = change[NSKeyValueChangeNewKey]; - OBASSERT(newValue); + OBASSERT_NOTNULL(newValue); if ([oldValue boolValue] == NO && [newValue boolValue]) self.isUploaded = YES; @@ -256,16 +256,16 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N self.isUploaded = [_childItems all:^BOOL(ODSItem *child) { return child.isUploaded; }]; } else if ([keyPath isEqualToString:ODSItemUploadedSizeBinding]) { NSNumber *oldValue = change[NSKeyValueChangeOldKey]; - OBASSERT(oldValue); + OBASSERT_NOTNULL(oldValue); NSNumber *newValue = change[NSKeyValueChangeNewKey]; - OBASSERT(newValue); + OBASSERT_NOTNULL(newValue); self.uploadedSize = self.uploadedSize + [newValue unsignedLongLongValue] - [oldValue unsignedLongLongValue]; } else if ([keyPath isEqualToString:ODSItemDownloadedSizeBinding]) { NSNumber *oldValue = change[NSKeyValueChangeOldKey]; - OBASSERT(oldValue); + OBASSERT_NOTNULL(oldValue); NSNumber *newValue = change[NSKeyValueChangeNewKey]; - OBASSERT(newValue); + OBASSERT_NOTNULL(newValue); self.downloadedSize = self.downloadedSize + [newValue unsignedLongLongValue] - [oldValue unsignedLongLongValue]; } else { diff --git a/Frameworks/OmniDocumentStore/ODSScope.m b/Frameworks/OmniDocumentStore/ODSScope.m index dc895edc..6473cca8 100644 --- a/Frameworks/OmniDocumentStore/ODSScope.m +++ b/Frameworks/OmniDocumentStore/ODSScope.m @@ -987,7 +987,7 @@ - (NSURL *)_moveURL:(NSURL *)sourceURL avoidingFileNames:(NSSet *)usedFilenames NSLog(@"Error checking if source URL %@ is a directory: %@", [sourceURL absoluteString], [resourceError toPropertyList]); // not fatal... } - OBASSERT(sourceIsDirectory); + OBASSERT_NOTNULL(sourceIsDirectory); NSString *fileName = [sourceURL lastPathComponent]; __autoreleasing NSString *baseName = nil; @@ -1264,7 +1264,7 @@ - (void)takeItems:(NSSet *)items toFolder:(ODSFolderItem *)folderItem ignoringFi [self _moveItems:items toFolder:folderItem ignoringFileItems:ignoredFileItems completionHandler:completionHandler]; } -- (void)moveItems:(NSSet *)items toFolder:(ODSFolderItem *)folderItem completionHandler:(void (^)(NSSet *movedFileItems, NSArray *errorsOrNil))completionHandler; +- (void)moveItems:(NSSet *)items toFolder:(ODSFolderItem *)folderItem completionHandler:(void (^ _Nullable)(NSSet *movedFileItems, NSArray *errorsOrNil))completionHandler; { OBPRECONDITION([items all:^BOOL(ODSItem *item) { return item.scope == self; }], "All the items should belong to this scope already"); diff --git a/Frameworks/OmniDocumentStore/ODSStore-Internal.h b/Frameworks/OmniDocumentStore/ODSStore-Internal.h index f7dcd512..5bb4cb84 100644 --- a/Frameworks/OmniDocumentStore/ODSStore-Internal.h +++ b/Frameworks/OmniDocumentStore/ODSStore-Internal.h @@ -1,4 +1,4 @@ -// Copyright 2010-2016 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -9,6 +9,8 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface ODSStore () - (void)_fileItem:(ODSFileItem *)fileItem willMoveToURL:(NSURL *)newURL; - (void)_fileItemEdit:(ODSFileItemEdit *)fileItemEdit willCopyToURL:(NSURL *)newURL; @@ -17,3 +19,5 @@ @end OB_HIDDEN NSString *ODSPathExtensionForFileType(NSString *fileType, BOOL *outIsPackage); + +NS_ASSUME_NONNULL_END diff --git a/Frameworks/OmniDocumentStore/ODSStore.h b/Frameworks/OmniDocumentStore/ODSStore.h index 7768289a..2ff99141 100644 --- a/Frameworks/OmniDocumentStore/ODSStore.h +++ b/Frameworks/OmniDocumentStore/ODSStore.h @@ -14,17 +14,19 @@ @protocol ODSStoreDelegate; @class ODSFileItem, ODSFolderItem, ODSScope, ODSLocalDirectoryScope; -typedef enum { +NS_ASSUME_NONNULL_BEGIN + +typedef NS_ENUM(NSInteger, ODSDocumentType) { ODSDocumentTypeNormal, ODSDocumentTypeTemplate, ODSDocumentTypeOther, -} ODSDocumentType; +}; @interface ODSStore : NSObject - initWithDelegate:(id )delegate; -@property(nonatomic,readonly) NSArray *scopes; +@property(nonatomic,readonly) NSArray <__kindof ODSScope *> *scopes; - (void)addScope:(ODSScope *)scope; - (void)removeScope:(ODSScope *)scope; @@ -32,29 +34,33 @@ typedef enum { @property(nonatomic,readonly) ODSScope *trashScope; @property(nonatomic,readonly) ODSScope *templateScope; -@property(nonatomic,readonly) NSSet *mergedFileItems; // All the file items from all scopes. +@property(nonatomic,readonly) NSSet <__kindof ODSFileItem *> *mergedFileItems; // All the file items from all scopes. - (Class)fileItemClassForURL:(NSURL *)fileURL; // Defaults to asking the delegate. The URL may not exist yet! - (BOOL)canViewFileTypeWithIdentifier:(NSString *)fileType; -- (ODSFileItem *)preferredFileItemForNextAutomaticDownload:(NSSet *)fileItems; +- (nullable ODSFileItem *)preferredFileItemForNextAutomaticDownload:(NSSet <__kindof ODSFileItem *> *)fileItems; - (void)addAfterInitialDocumentScanAction:(void (^)(void))action; -- (void)moveItems:(NSSet *)items fromScope:(ODSScope *)fromScope toScope:(ODSScope *)toScope inFolder:(ODSFolderItem *)parentFolder completionHandler:(void (^)(NSSet *movedFileItems, NSArray *errorsOrNil))completionHandler; +- (void)moveItems:(NSSet <__kindof ODSFileItem *> *)items fromScope:(ODSScope *)fromScope toScope:(ODSScope *)toScope inFolder:(ODSFolderItem *)parentFolder completionHandler:(void (^ _Nullable)(NSSet <__kindof ODSFileItem *> * _Nullable movedFileItems, NSArray * _Nullable errorsOrNil))completionHandler; -- (void)makeFolderFromItems:(NSSet *)items inParentFolder:(ODSFolderItem *)parentFolder ofScope:(ODSScope *)scope completionHandler:(void (^)(ODSFolderItem *createdFolder, NSArray *errorsOrNil))completionHandler; +- (void)makeFolderFromItems:(NSSet <__kindof ODSFileItem *> *)items inParentFolder:(ODSFolderItem *)parentFolder ofScope:(ODSScope *)scope completionHandler:(void (^)(ODSFolderItem * _Nullable createdFolder, NSArray * _Nullable errorsOrNil))completionHandler; -- (void)scanItemsWithCompletionHandler:(void (^)(void))completionHandler; +- (void)scanItemsWithCompletionHandler:(void (^ _Nullable)(void))completionHandler; - (void)startDeferringScanRequests; -- (void)stopDeferringScanRequests:(void (^)(void))completionHandler; +- (void)stopDeferringScanRequests:(void (^ _Nullable)(void))completionHandler; -- (ODSFileItem *)fileItemWithURL:(NSURL *)url; +- (nullable ODSFileItem *)fileItemWithURL:(NSURL *)url; @property(readonly,nonatomic) NSString *documentTypeForNewFiles; -- (NSString *)documentTypeForNewFilesOfType:(ODSDocumentType)type; +- (nullable NSString *)documentTypeForNewFilesOfType:(ODSDocumentType)type; - (NSString *)defaultFilenameForDocumentType:(ODSDocumentType)type isDirectory:(BOOL *)outIsDirectory; - (NSURL *)temporaryURLForCreatingNewDocumentOfType:(ODSDocumentType)type; -- (void)moveNewTemporaryDocumentAtURL:(NSURL *)fileURL toScope:(ODSScope *)scope folder:(ODSFolderItem *)folder documentType:(ODSDocumentType)type completionHandler:(void (^)(ODSFileItem *createdFileItem, NSError *error))handler; +// IF the provided documentName is nil, then we will use the default name for that new document type. +- (void)moveNewTemporaryDocumentAtURL:(NSURL *)fileURL toScope:(ODSScope *)scope folder:(nullable ODSFolderItem *)folder documentType:(ODSDocumentType)type documentName:(nullable)documentName completionHandler:(void (^)(ODSFileItem *createdFileItem, NSError *error))handler; +- (void)moveNewTemporaryDocumentAtURL:(NSURL *)fileURL toScope:(ODSScope *)scope folder:(nullable ODSFolderItem *)folder documentType:(ODSDocumentType)type completionHandler:(void (^)(ODSFileItem *createdFileItem, NSError *error))handler; @end +NS_ASSUME_NONNULL_END + diff --git a/Frameworks/OmniDocumentStore/ODSStore.m b/Frameworks/OmniDocumentStore/ODSStore.m index 9a17b25b..2dd581e7 100644 --- a/Frameworks/OmniDocumentStore/ODSStore.m +++ b/Frameworks/OmniDocumentStore/ODSStore.m @@ -48,6 +48,8 @@ RCS_ID("$Id$"); +NS_ASSUME_NONNULL_BEGIN + OBDEPRECATED_METHOD(-createNewDocument:); OBDEPRECATED_METHOD(-createdNewDocument:templateURL:completionHandler:); OBDEPRECATED_METHOD(-urlForNewDocumentOfType:); // could write a wrapper that asks the scope, but it has this now. @@ -219,7 +221,7 @@ - (BOOL)canViewFileTypeWithIdentifier:(NSString *)fileType; return [delegate documentStore:self canViewFileTypeWithIdentifier:fileType]; } -- (ODSFileItem *)preferredFileItemForNextAutomaticDownload:(NSSet *)fileItems; +- (nullable ODSFileItem *)preferredFileItemForNextAutomaticDownload:(NSSet <__kindof ODSFileItem *> *)fileItems; { id delegate = _weak_delegate; @@ -238,7 +240,7 @@ - (void)addAfterInitialDocumentScanAction:(void (^)(void))action; [self _flushAfterInitialDocumentScanActions]; } -- (void)moveItems:(NSSet *)items fromScope:(ODSScope *)fromScope toScope:(ODSScope *)toScope inFolder:(ODSFolderItem *)parentFolder completionHandler:(void (^)(NSSet *movedFileItems, NSArray *errorsOrNil))completionHandler; +- (void)moveItems:(NSSet <__kindof ODSFileItem *> *)items fromScope:(ODSScope *)fromScope toScope:(ODSScope *)toScope inFolder:(ODSFolderItem *)parentFolder completionHandler:(void (^ _Nullable)(NSSet <__kindof ODSFileItem *> * _Nullable movedFileItems, NSArray * _Nullable errorsOrNil))completionHandler; { OBPRECONDITION([NSThread isMainThread]); // since we'll send the completion handler back to the main thread, make sure we came from there OBPRECONDITION(fromScope); @@ -263,14 +265,14 @@ - (void)moveItems:(NSSet *)items fromScope:(ODSScope *)fromScope toScope:(ODSSco return; } - [toScope takeItems:items toFolder:parentFolder ignoringFileItems:nil completionHandler:^(NSSet *movedFileItems, NSArray *errorsOrNil) { + [toScope takeItems:items toFolder:parentFolder ignoringFileItems:nil completionHandler:^(NSSet <__kindof ODSFileItem *> * _Nullable movedFileItems, NSArray * _Nullable errorsOrNil) { OBASSERT([NSThread isMainThread]); [fromScope finishRelinquishingMovedItems:movedFileItems]; if (completionHandler) completionHandler(movedFileItems, errorsOrNil); }]; } else { - [toScope moveItems:items toFolder:parentFolder completionHandler:^(NSSet *movedFileItems, NSArray *errorsOrNil){ + [toScope moveItems:items toFolder:parentFolder completionHandler:^(NSSet <__kindof ODSFileItem *> * _Nullable movedFileItems, NSArray * _Nullable errorsOrNil){ OBASSERT([NSThread isMainThread]); if (completionHandler) completionHandler(movedFileItems, errorsOrNil); @@ -278,7 +280,7 @@ - (void)moveItems:(NSSet *)items fromScope:(ODSScope *)fromScope toScope:(ODSSco } } -- (void)makeFolderFromItems:(NSSet *)items inParentFolder:(ODSFolderItem *)parentFolder ofScope:(ODSScope *)scope completionHandler:(void (^)(ODSFolderItem *createdFolder, NSArray *errorsOrNil))completionHandler; +- (void)makeFolderFromItems:(NSSet <__kindof ODSFileItem *> *)items inParentFolder:(ODSFolderItem *)parentFolder ofScope:(ODSScope *)scope completionHandler:(void (^)(ODSFolderItem * _Nullable createdFolder, NSArray * _Nullable errorsOrNil))completionHandler; { OBPRECONDITION([NSThread isMainThread]); // since we'll send the completion handler back to the main thread, make sure we came from there OBPRECONDITION(scope); @@ -287,14 +289,14 @@ - (void)makeFolderFromItems:(NSSet *)items inParentFolder:(ODSFolderItem *)paren completionHandler = [completionHandler copy]; - [scope makeFolderFromItems:items inParentFolder:parentFolder completionHandler:^(ODSFolderItem *createdFolder, NSArray *errorsOrNil){ + [scope makeFolderFromItems:items inParentFolder:parentFolder completionHandler:^(ODSFolderItem * _Nullable createdFolder, NSArray * _Nullable errorsOrNil){ OBASSERT([NSThread isMainThread]); if (completionHandler) completionHandler(createdFolder, errorsOrNil); }]; } -- (void)scanItemsWithCompletionHandler:(void (^)(void))completionHandler; +- (void)scanItemsWithCompletionHandler:(void (^ _Nullable)(void))completionHandler; { OBPRECONDITION([NSThread isMainThread]); @@ -334,7 +336,7 @@ - (void)startDeferringScanRequests; DEBUG_STORE(@"_deferScanRequestCount = %ld", _deferScanRequestCount); } -- (void)stopDeferringScanRequests:(void (^)(void))completionHandler; +- (void)stopDeferringScanRequests:(void (^ _Nullable)(void))completionHandler; { OBPRECONDITION([NSThread isMainThread]); OBPRECONDITION(_deferScanRequestCount > 0); @@ -353,7 +355,7 @@ - (void)stopDeferringScanRequests:(void (^)(void))completionHandler; } } -- (ODSFileItem *)fileItemWithURL:(NSURL *)url; +- (nullable ODSFileItem *)fileItemWithURL:(NSURL *)url; { // We have a union of the scanned items, but it is better to let the scopes handle this than to have the logic in two spots. for (ODSScope *scope in _scopes) { @@ -364,7 +366,7 @@ - (ODSFileItem *)fileItemWithURL:(NSURL *)url; return nil; } -- (NSString *)documentTypeForNewFilesOfType:(ODSDocumentType)type; +- (nullable NSString *)documentTypeForNewFilesOfType:(ODSDocumentType)type; { id delegate = _weak_delegate; @@ -432,41 +434,48 @@ - (NSURL *)temporaryURLForCreatingNewDocumentOfType:(ODSDocumentType)type; return [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:temporaryFilename] isDirectory:isDirectory]; } -- (void)moveNewTemporaryDocumentAtURL:(NSURL *)fileURL toScope:(ODSScope *)scope folder:(ODSFolderItem *)folder documentType:(ODSDocumentType)type completionHandler:(void (^)(ODSFileItem *createdFileItem, NSError *error))handler; +- (void)moveNewTemporaryDocumentAtURL:(NSURL *)fileURL toScope:(ODSScope *)scope folder:(nullable ODSFolderItem *)folder documentType:(ODSDocumentType)type documentName:(nullable)documentName completionHandler:(void (^)(ODSFileItem *createdFileItem, NSError *error))handler; { NSString *documentType = [self documentTypeForNewFilesOfType:type]; id delegate = _weak_delegate; - NSString *baseName = nil; - // LMTODO: incorporate "other" - switch (type) { - case ODSDocumentTypeTemplate: - baseName = [delegate documentStoreBaseNameForNewTemplateFiles:self]; - break; - case ODSDocumentTypeNormal: - baseName = [delegate documentStoreBaseNameForNewFiles:self]; - break; - case ODSDocumentTypeOther: - if ([delegate respondsToSelector:@selector(documentStoreBaseNameForNewOtherFiles:)]) { - baseName = [delegate documentStoreBaseNameForNewOtherFiles:self]; - } else { + NSString *baseName = documentName; + if (baseName == nil) { + // LMTODO: incorporate "other" + switch (type) { + case ODSDocumentTypeTemplate: + baseName = [delegate documentStoreBaseNameForNewTemplateFiles:self]; + break; + case ODSDocumentTypeNormal: baseName = [delegate documentStoreBaseNameForNewFiles:self]; - } - break; - default: - break; - } - if (!baseName) { - OBASSERT_NOT_REACHED("No delegate? You probably want one to provide a better base untitled document name."); - baseName = @"My Document"; + break; + case ODSDocumentTypeOther: + if ([delegate respondsToSelector:@selector(documentStoreBaseNameForNewOtherFiles:)]) { + baseName = [delegate documentStoreBaseNameForNewOtherFiles:self]; + } else { + baseName = [delegate documentStoreBaseNameForNewFiles:self]; + } + break; + default: + break; + } + if (!baseName) { + OBASSERT_NOT_REACHED("No delegate? You probably want one to provide a better base untitled document name."); + baseName = @"My Document"; + } } [scope addDocumentInFolder:folder baseName:baseName fileType:documentType fromURL:fileURL option:ODSStoreAddByMovingTemporarySourceToAvailableDestinationURL completionHandler:handler]; } +- (void)moveNewTemporaryDocumentAtURL:(NSURL *)fileURL toScope:(ODSScope *)scope folder:(nullable ODSFolderItem *)folder documentType:(ODSDocumentType)type completionHandler:(void (^)(ODSFileItem *createdFileItem, NSError *error))handler; +{ + [self moveNewTemporaryDocumentAtURL:fileURL toScope:scope folder:folder documentType:type documentName:nil completionHandler:handler]; +} + #pragma mark - NSObject (NSKeyValueObserving) -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; +- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary *)change context:(void * _Nullable)context; { OBPRECONDITION([NSThread isMainThread]); @@ -603,4 +612,6 @@ - (void)_willRemoveFileItems:(NSArray *)fileItems; @end +NS_ASSUME_NONNULL_END + diff --git a/Frameworks/OmniFileExchange/OFXContainerAgent.m b/Frameworks/OmniFileExchange/OFXContainerAgent.m index f6c02173..cac55c7b 100644 --- a/Frameworks/OmniFileExchange/OFXContainerAgent.m +++ b/Frameworks/OmniFileExchange/OFXContainerAgent.m @@ -537,7 +537,7 @@ - (OFXFileSnapshotTransfer *)prepareUploadTransferForFileItem:(OFXFileItem *)fil BOOL hasBeenEdited = NO; __autoreleasing NSError *sameContentsError; NSNumber *same = [fileItem hasSameContentsAsLocalDocumentAtURL:fileItem.localDocumentURL error:&sameContentsError]; - if (!same) { + if (same == nil) { if ([sameContentsError causedByMissingFile]) { // Race between uploading and a local deletion. We should have a scan queued now or soon that will set fileItem.hasBeenLocallyDeleted. } else @@ -813,7 +813,7 @@ - (BOOL)finishedScan:(OFXContainerScan *)_scan error:(NSError **)outError; __autoreleasing NSError *error; NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:&error]; NSNumber *inode = attributes[NSFileSystemFileNumber]; - if (!inode) { + if (inode == nil) { // Something was moved/deleted while we were scanning -- try again. if ([error causedByMissingFile]) { DEBUG_SCAN(2, @" ... file missing at %@, must rescan", fileURL); @@ -836,7 +836,7 @@ - (BOOL)finishedScan:(OFXContainerScan *)_scan error:(NSError **)outError; [[_documentIndex copyLocalRelativePathToFileItem] enumerateKeysAndObjectsUsingBlock:^(NSString *localRelativePath, OFXFileItem *fileItem, BOOL *stop) { NSNumber *inode = fileItem.inode; - if (!inode) + if (inode == nil) return; // Locally missing NSURL *updatedURL = inodeToScannedURL[inode]; if (!updatedURL) @@ -878,7 +878,7 @@ - (BOOL)finishedScan:(OFXContainerScan *)_scan error:(NSError **)outError; if (!fileItem.remoteState.missing && fileItem.isValidToUpload && !fileItem.isUploading && !fileItem.isDownloading) { __autoreleasing NSError *hasSameContentsError; NSNumber *same = [fileItem hasSameContentsAsLocalDocumentAtURL:fileURL error:&hasSameContentsError]; - if (!same) { + if (same == nil) { // The file might have been renamed or deleted and we need to rescan. Or, there might be a sandbox-induced permission error, in which case we should hopefully pause on the next rescan due to the error. [hasSameContentsError log:@"While scanning, error checking for changes in contents for %@", fileURL]; diff --git a/Frameworks/OmniFileExchange/OFXDocumentStoreScope.m b/Frameworks/OmniFileExchange/OFXDocumentStoreScope.m index 76067f0e..a1b0ea55 100644 --- a/Frameworks/OmniFileExchange/OFXDocumentStoreScope.m +++ b/Frameworks/OmniFileExchange/OFXDocumentStoreScope.m @@ -261,7 +261,7 @@ static void _updateFlagFromAttributes(ODSFileItem *fileItem, NSString *bindingKe { BOOL value; NSNumber *attributeValue = [metadata valueForKey:attributeKey]; - if (!attributeValue) { + if (attributeValue == nil) { OBASSERT(metadata == nil); // OK if we don't have a metadata item at all value = defaultValue; } else { diff --git a/Frameworks/OmniFileExchange/OFXFileItem.m b/Frameworks/OmniFileExchange/OFXFileItem.m index a3a0a796..06069938 100644 --- a/Frameworks/OmniFileExchange/OFXFileItem.m +++ b/Frameworks/OmniFileExchange/OFXFileItem.m @@ -1105,7 +1105,7 @@ - (BOOL)_validateDownloadCommitToURL:(NSURL *)_updatedLocalDocumentURL contentSa // We *do* want to force autosave on other presenters here since we are trying to do conflict detection. __autoreleasing NSError *sameError; NSNumber *same = [_snapshot hasSameContentsAsLocalDocumentAtURL:newReadingURL coordinator:coordinator withChanges:YES error:&sameError]; - if (!same) { + if (same == nil) { if ([sameError causedByMissingFile]) { // The file has been deleted locally or possibly moved just as we are committing. In the case of deletion, this will act as a resurrection. In the case of a move, we'll end up with a new file. return YES; diff --git a/Frameworks/OmniFileExchange/OFXFileSnapshot.m b/Frameworks/OmniFileExchange/OFXFileSnapshot.m index edf0a610..9eb65e6c 100644 --- a/Frameworks/OmniFileExchange/OFXFileSnapshot.m +++ b/Frameworks/OmniFileExchange/OFXFileSnapshot.m @@ -390,7 +390,7 @@ - (instancetype)initWithTargetLocalSnapshotURL:(NSURL *)localTargetURL forNewLoc NSDate *creationDate = nil; NSNumber *creationTimeNumber = versionContents[kOFXContents_FileCreationTime]; - if (creationTimeNumber) { + if (creationTimeNumber != nil) { NSTimeInterval creationTimestamp = creationTimeNumber.doubleValue; creationDate = [NSDate dateWithTimeIntervalSinceReferenceDate:creationTimestamp]; } @@ -608,7 +608,7 @@ - (NSUInteger)version; OBPRECONDITION([self _checkInvariants]); NSNumber *versionNumber = _versionDictionary[kOFXVersion_NumberKey]; - OBASSERT(versionNumber); + OBASSERT_NOTNULL(versionNumber); return [versionNumber unsignedLongValue]; } diff --git a/Frameworks/OmniFileExchange/OFXServerAccount.m b/Frameworks/OmniFileExchange/OFXServerAccount.m index f0cb943c..0ac82b64 100644 --- a/Frameworks/OmniFileExchange/OFXServerAccount.m +++ b/Frameworks/OmniFileExchange/OFXServerAccount.m @@ -770,7 +770,7 @@ - (void)clearError; return nil; NSNumber *version = propertyList[@"version"]; - if (!version) { + if (version == nil) { OFXError(outError, OFXServerAccountCannotLoad, @"Info.plist has no \"version\".", nil); return nil; } diff --git a/Frameworks/OmniFileExchange/OFXUploadRenameFileSnapshot.m b/Frameworks/OmniFileExchange/OFXUploadRenameFileSnapshot.m index 3a9d388f..3dfc605b 100644 --- a/Frameworks/OmniFileExchange/OFXUploadRenameFileSnapshot.m +++ b/Frameworks/OmniFileExchange/OFXUploadRenameFileSnapshot.m @@ -1,4 +1,4 @@ -// Copyright 2013-2014 Omni Development, Inc. All rights reserved. +// Copyright 2013-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -30,7 +30,7 @@ - (BOOL)prepareToUploadRename:(NSError **)outError; NSMutableDictionary *versionDictionary = [self.versionDictionary mutableCopy]; NSNumber *oldVersion = versionDictionary[kOFXVersion_NumberKey]; - OBASSERT(oldVersion); + OBASSERT_NOTNULL(oldVersion); versionDictionary[kOFXVersion_NumberKey] = @([oldVersion unsignedLongValue] + 1); return [self _updateVersionDictionary:versionDictionary reason:@"init upload rename" error:outError]; diff --git a/Frameworks/OmniFoundation/Crypto/OFCMSUtilities.swift b/Frameworks/OmniFoundation/Crypto/OFCMSUtilities.swift index 85e9dbfb..099b1701 100644 --- a/Frameworks/OmniFoundation/Crypto/OFCMSUtilities.swift +++ b/Frameworks/OmniFoundation/Crypto/OFCMSUtilities.swift @@ -67,7 +67,7 @@ enum CMSRecipientIdentifier { case OFCMSRIDSubjectKeyIdentifier: return .keyIdentifier(ski: blob1! as Data); default: - throw NSError(domain: OFErrorDomain, code: OFCMSFormatError, userInfo: nil); + throw OFError(.OFCMSFormatError) } } @@ -236,7 +236,7 @@ struct CertificateIdentifiers { self.ski = nil } } else { - throw NSError(domain: OFErrorDomain, code: OFASN1Error, userInfo: ["function": "OFSecCertificateGetIdentifiers"]) + throw OFError(.OFASN1Error, userInfo: ["function": "OFSecCertificateGetIdentifiers"]) } } @@ -283,7 +283,7 @@ class CMSPasswordRecipient : CMSRecipient { if let kek = kek, let info = info, let recip = OFProduceRIForCMSPWRI(kek, cek, info, []) { return recip; } else { - throw NSError(domain: OFErrorDomain, code: OFKeyNotAvailable, userInfo: nil); + throw OFError(.OFKeyNotAvailable) } } @@ -298,7 +298,7 @@ class CMSPasswordRecipient : CMSRecipient { func unwrap(password: String, data: Data) throws -> Data { guard let info = info else { // Shouldn't happen in normal operation - throw NSError(domain: OFErrorDomain, code: OFKeyNotAvailable, userInfo: nil); + throw OFError(.OFKeyNotAvailable) } var error : NSError?; @@ -380,13 +380,13 @@ class CMSKEKRecipient : CMSRecipient { func recipientInfo(wrapping cek: Data) throws -> Data { guard let key = kek else { - throw NSError(domain: OFErrorDomain, code: OFKeyNotAvailable, userInfo: nil); + throw OFError(.OFKeyNotAvailable) } if let rinfo = OFProduceRIForCMSKEK(key, cek, keyIdentifier, []) { return rinfo; } else { - throw NSError(domain: OFErrorDomain, code: OFKeyNotAvailable, userInfo: nil); + throw OFError(.OFKeyNotAvailable) } } @@ -447,7 +447,7 @@ class CMSPKRecipient : CMSRecipient { func recipientInfo(wrapping cek: Data) throws -> Data { guard let encryptionKey = try cert?.publicKey() else { - throw NSError(domain: OFErrorDomain, code: OFKeyNotAvailable, userInfo: nil); + throw OFError(.OFKeyNotAvailable) } var errbuf : NSError? = nil; @@ -466,7 +466,7 @@ class CMSPKRecipient : CMSRecipient { var issuer, serial, ski: NSData?; if !OFSecCertificateGetIdentifiers(certificate, &issuer, &serial, &ski) { - throw NSError(domain: OFErrorDomain, code: OFASN1Error, userInfo: [ NSLocalizedDescriptionKey: "Could not parse X.509 certificate" ]); + throw OFError(.OFASN1Error, userInfo: [ NSLocalizedDescriptionKey: "Could not parse X.509 certificate" ]) } if let ski : Data = ski as Data? { @@ -766,7 +766,7 @@ class OFCMSUnwrapper { if let err = keyAccessError { throw err; } - throw NSError(domain: OFErrorDomain, code: OFKeyNotAvailable, userInfo: nil); + throw OFError(.OFKeyNotAvailable) } } @@ -787,34 +787,32 @@ class OFCMSUnwrapper { var failureCount : Int = 0; - prompting: while true { - let password = try keySource.promptForPassword(withCount: failureCount, hint: passwordHint); + while true { + let password = try keySource.promptForPassword(withCount: failureCount, hint: passwordHint) // Probably only one password recipient, but potentially several - var passwordUseError : NSError? = nil; - var anyNotApplicable : Bool = false; + var passwordUseError: Error? = nil + var anyNotApplicable: Bool = false for (passwordRecipient, wrappedKey) in passwordRecipients { do { - let aCek = try passwordRecipient.unwrap(password: password, data:wrappedKey); - return (aCek, passwordRecipient); - } catch let err as NSError { - if err.domain == OFErrorDomain && err.code == OFKeyNotApplicable { - // Incorrect password. Re-prompt unless another recipient matches. - anyNotApplicable = true; - } else { - passwordUseError = err; - } + let aCek = try passwordRecipient.unwrap(password: password, data: wrappedKey) + return (aCek, passwordRecipient) + } catch OFError.OFKeyNotApplicable { + // Incorrect password. Re-prompt unless another recipient matches. + anyNotApplicable = true + } catch let err { + passwordUseError = err } } if anyNotApplicable { // Incorrect password. Re-prompt. - failureCount += 1; + failureCount += 1 } else { // We failed for a reason other than an incorrect password. - throw passwordUseError!; // (This could only be nil if we have no passwordRecipients, but we check for that.) + throw passwordUseError! // (This could only be nil if we have no passwordRecipients, but we check for that.) } - }; + } } /** The workhorse function: repeatedly unwraps the content contained in the receiver, stopping when we reach a content-type that we can't unwrap. */ @@ -832,7 +830,7 @@ class OFCMSUnwrapper { case OFCMSContentType_compressedData: try decompress(); case OFCMSContentType_Unknown: - throw NSError(domain: OFErrorDomain, code: OFUnsupportedCMSFeature, userInfo: [NSLocalizedFailureReasonErrorKey: NSLocalizedString("Unexpected CMS content-type", tableName: "OmniFoundation", bundle: OFBundle, comment: "Document decryption error - unknown content-type found while unwrapping a Cryptographic Message Syntax object")]); + throw OFError(.OFUnsupportedCMSFeature, userInfo: [NSLocalizedFailureReasonErrorKey: NSLocalizedString("Unexpected CMS content-type", tableName: "OmniFoundation", bundle: OFBundle, comment: "Document decryption error - unknown content-type found while unwrapping a Cryptographic Message Syntax object")]) default: return; } @@ -901,7 +899,7 @@ class OFCMSUnwrapper { // Check that authenticated attributes includes the inner content type (see RFC 5083). For security reasons the content type attribute is only allowed to be missing if the content type is 'data'. if !attrs.sawMatchingContentType && innerType != OFCMSContentType_data { - throw NSError(domain: OFErrorDomain, code: OFCMSFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Content-Type missing" ]); + throw OFError(.OFCMSFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Content-Type missing" ]) } let (contentKey, usedRecipient, allRecipients) = try self.recoverContentKey(recipientBlobs: recipientBlobs); @@ -949,7 +947,7 @@ class OFCMSUnwrapper { if innerContentLocation.length != 0 { cms = OFASN1UnwrapOctetString(cms!, innerContentLocation); if cms == nil { - throw NSError(domain: OFErrorDomain, code: OFCMSFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Problem with SignedData.encapsulatedContent" ]); + throw OFError(.OFCMSFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Problem with SignedData.encapsulatedContent" ]) } contentRange = NSRange(location: 0, length: cms!.count); } else { @@ -1083,12 +1081,12 @@ class OFCMSUnwrapper { } private func unexpectedNullContent() -> Error { - return NSError(domain: OFErrorDomain, code: OFEncryptedDocumentFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Unexpected null content" ]); + return OFError(.OFEncryptedDocumentFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Unexpected null content" ]) } private func checkVersion(_ version: Int32, _ location: String, min: Int32, max: Int32) throws { if (version < min || version > max) { - throw NSError(domain: OFErrorDomain, code: OFUnsupportedCMSFeature, userInfo: [ NSLocalizedFailureReasonErrorKey: "Unsupported \(location) version (expected \(min)-\(max), found \(version))" ]); + throw OFError(.OFUnsupportedCMSFeature, userInfo: [ NSLocalizedFailureReasonErrorKey: "Unsupported \(location) version (expected \(min)-\(max), found \(version))" ]) } } diff --git a/Frameworks/OmniFoundation/DataStructures.subproj/OFBijection.h b/Frameworks/OmniFoundation/DataStructures.subproj/OFBijection.h index 6588ba4c..272a0c31 100644 --- a/Frameworks/OmniFoundation/DataStructures.subproj/OFBijection.h +++ b/Frameworks/OmniFoundation/DataStructures.subproj/OFBijection.h @@ -1,4 +1,4 @@ -// Copyright 2013-2015 Omni Development, Inc. All rights reserved. +// Copyright 2013-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -10,31 +10,35 @@ #import #import -@interface OFBijection : NSObject +NS_ASSUME_NONNULL_BEGIN + +@interface OFBijection<__covariant KeyType, __covariant ObjectType> : NSObject + (instancetype)bijection; + (instancetype)bijectionWithObject:(id)anObject forKey:(id)aKey; + (instancetype)bijectionWithObjects:(NSArray *)objects forKeys:(NSArray *)keys; + (instancetype)bijectionWithObjectsAndKeys:(id)anObject, ... NS_REQUIRES_NIL_TERMINATION; -+ (instancetype)bijectionWithDictionary:(NSDictionary *)dictionary; ++ (instancetype)bijectionWithDictionary:(NSDictionary *)dictionary; - (id)init; -- (id)initWithObject:(id)anObject forKey:(id)aKey; -- (id)initWithObjects:(NSArray *)objects forKeys:(NSArray *)keys; // designated initializer +- (id)initWithObject:(ObjectType)anObject forKey:(KeyType)aKey; +- (id)initWithObjects:(NSArray *)objects forKeys:(NSArray *)keys; // designated initializer - (id)initWithObjectsAndKeys:(id)anObject, ... NS_REQUIRES_NIL_TERMINATION; -- (id)initWithDictionary:(NSDictionary *)dictionary; +- (id)initWithDictionary:(NSDictionary *)dictionary; - (NSUInteger)count; -- (id)objectForKey:(id)aKey; -- (id)keyForObject:(id)anObject; +- (nullable ObjectType)objectForKey:(KeyType)aKey; +- (nullable KeyType)keyForObject:(ObjectType)anObject; -- (id)objectForKeyedSubscript:(id)aKey; +- (nullable ObjectType)objectForKeyedSubscript:(KeyType)aKey; -- (NSArray *)allKeys; -- (NSArray *)allObjects; +- (NSArray *)allKeys; +- (NSArray *)allObjects; - (BOOL)isEqualToBijection:(OFBijection *)bijection; -- (OFBijection *)invertedBijection; +- (OFBijection *)invertedBijection; @end + +NS_ASSUME_NONNULL_END diff --git a/Frameworks/OmniFoundation/DataStructures.subproj/OFMutableBijection.h b/Frameworks/OmniFoundation/DataStructures.subproj/OFMutableBijection.h index 10c9589d..cfdb0106 100644 --- a/Frameworks/OmniFoundation/DataStructures.subproj/OFMutableBijection.h +++ b/Frameworks/OmniFoundation/DataStructures.subproj/OFMutableBijection.h @@ -1,4 +1,4 @@ -// Copyright 2013-2014 Omni Development, Inc. All rights reserved. +// Copyright 2013-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -9,13 +9,17 @@ #import -@interface OFMutableBijection : OFBijection +NS_ASSUME_NONNULL_BEGIN -- (void)setObject:(id)anObject forKey:(id)aKey; -- (void)setKey:(id)aKey forObject:(id)anObject; +@interface OFMutableBijection<__covariant KeyType, __covariant ObjectType> : OFBijection -- (void)setObject:(id)anObject forKeyedSubscript:(id)aKey; +- (void)setObject:(nullable ObjectType)anObject forKey:(KeyType)aKey; +- (void)setKey:(nullable KeyType)aKey forObject:(ObjectType)anObject; + +- (void)setObject:(nullable ObjectType)anObject forKeyedSubscript:(KeyType)aKey; - (void)invert; @end + +NS_ASSUME_NONNULL_END diff --git a/Frameworks/OmniFoundation/FileManagement.subproj/OFDocumentEncryption.swift b/Frameworks/OmniFoundation/FileManagement.subproj/OFDocumentEncryption.swift index ea4bf9c7..7ac0e7f5 100644 --- a/Frameworks/OmniFoundation/FileManagement.subproj/OFDocumentEncryption.swift +++ b/Frameworks/OmniFoundation/FileManagement.subproj/OFDocumentEncryption.swift @@ -30,7 +30,7 @@ class OFDocumentEncryptionSettings : NSObject { do { unwrapped = try helper.unwrap(input: wrapper); } catch let e as NSError { - if e.domain == OFErrorDomain && (e.code == OFKeyNotAvailable || e.code == OFKeyNotApplicable) { + if e.domain == OFErrorDomain && (e.code == OFError.OFKeyNotAvailable.rawValue || e.code == OFError.OFKeyNotApplicable.rawValue) { info.pointee = OFDocumentEncryptionSettings(from: helper); } throw e; @@ -784,10 +784,9 @@ class OFCMSFileWrapper { } /// Convenience for generating an error when we don't recognize or expect a given content-type. - private func unexpectedContentTypeError(_ ct: OFCMSContentType) -> NSError { - return NSError(domain: OFErrorDomain, - code: OFUnsupportedCMSFeature, - userInfo: [NSLocalizedFailureReasonErrorKey: NSLocalizedString("Unexpected CMS content-type", tableName: "OmniFoundation", bundle: OFBundle, comment: "Document decryption error - unexpected content-type found while unwrapping a Cryptographic Message Syntax object")]); + private func unexpectedContentTypeError(_ ct: OFCMSContentType) -> Error { + return OFError(.OFUnsupportedCMSFeature, + userInfo: [NSLocalizedFailureReasonErrorKey: NSLocalizedString("Unexpected CMS content-type", tableName: "OmniFoundation", bundle: OFBundle, comment: "Document decryption error - unexpected content-type found while unwrapping a Cryptographic Message Syntax object")]) } /// PackageIndex represents the table-of-contents object of an encrypted file wrapper. @@ -880,7 +879,7 @@ class OFCMSFileWrapper { static func unserialize(_ input: Data) throws -> PackageIndex { let reader = try OFXMLReader(data: input); guard let rtelt = reader.elementQName() else { - throw NSError(domain: OFErrorDomain, code: OFXMLDocumentNoRootElementError, userInfo: nil); + throw OFError(.OFXMLDocumentNoRootElementError) } guard rtelt.name == "index", rtelt.namespace == encryptedContentIndexNamespaceURI else { throw miscFormatError(reason: "Incorrect root element: \(rtelt.shortDescription()!)"); @@ -920,9 +919,9 @@ class OFCMSFileWrapper { if elementName.name == "file" && elementName.namespace == encryptedContentIndexNamespaceURI { guard let memberName = try reader.getAttributeValue(fileNameAttr), let memberLocation = try reader.getAttributeValue(fileLocationAttr) else { - throw NSError(domain: OFErrorDomain, code: OFEncryptedDocumentFormatError, userInfo: [ + throw OFError(.OFEncryptedDocumentFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Missing attribute" - ]); + ]) } var memberOptions : OFCMSOptions = []; if let optionality = try reader.getAttributeValue(fileOptionalAttr), (optionality as NSString).boolValue { @@ -932,9 +931,9 @@ class OFCMSFileWrapper { try reader.skipCurrentElement(); } else if elementName.name == "key" && elementName.namespace == encryptedContentIndexNamespaceURI { guard let keyName = try reader.getAttributeValue(keyIdAttr) else { - throw NSError(domain: OFErrorDomain, code: OFEncryptedDocumentFormatError, userInfo: [ + throw OFError(.OFEncryptedDocumentFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Missing attribute" - ]); + ]) } do { try reader.openElement(); @@ -944,13 +943,13 @@ class OFCMSFileWrapper { keys[ try NSData(hexString:keyName) as Data ] = keyValueData as Data; } catch let e as NSError { - throw NSError(domain: OFErrorDomain, code: OFEncryptedDocumentFormatError, userInfo: [NSUnderlyingErrorKey: e]); + throw OFError(.OFEncryptedDocumentFormatError, userInfo: [NSUnderlyingErrorKey: e]) } } else if elementName.name == "directory" && elementName.namespace == encryptedContentIndexNamespaceURI { guard let dirName = try reader.getAttributeValue(fileNameAttr) else { - throw NSError(domain: OFErrorDomain, code: OFEncryptedDocumentFormatError, userInfo: [ + throw OFError(.OFEncryptedDocumentFormatError, userInfo: [ NSLocalizedFailureReasonErrorKey: "Missing attribute" - ]); + ]) } directoryStack.append(DirectoryEntry(realName: dirName, files: files, directories: directories)); @@ -971,7 +970,7 @@ class OFCMSFileWrapper { private let NSFileExtendedAttributes = "NSFileExtendedAttributes"; private -func missingFileError(filename: String) -> NSError { +func missingFileError(filename: String) -> Error { let msg = NSString(format: NSLocalizedString("The encrypted item \"%@\" is missing or unreadable.", tableName: "OmniFoundation", bundle: OFBundle, comment: "Document decryption error message - a file within the encrypted file wrapper can't be read") as NSString, filename) as String; return miscFormatError(reason: msg); @@ -991,11 +990,11 @@ func uglyHexify(_ cid: Data) -> String { } private -func miscFormatError(reason: String? = nil, underlying: NSError? = nil) -> NSError { +func miscFormatError(reason: String? = nil, underlying: NSError? = nil) -> Error { var userInfo: [String: AnyObject] = [:]; if let underlyingError = underlying { - if underlyingError.domain == OFErrorDomain && underlyingError.code == OFEncryptedDocumentFormatError && reason == nil { + if underlyingError.domain == OFErrorDomain && underlyingError.code == OFError.OFEncryptedDocumentFormatError.rawValue && reason == nil { return underlyingError; } userInfo[NSUnderlyingErrorKey] = underlyingError; @@ -1005,7 +1004,7 @@ func miscFormatError(reason: String? = nil, underlying: NSError? = nil) -> NSErr userInfo[NSLocalizedFailureReasonErrorKey] = message as NSString; } - return NSError(domain: OFErrorDomain, code: OFEncryptedDocumentFormatError, userInfo: userInfo.isEmpty ? nil : userInfo); + return OFError(.OFEncryptedDocumentFormatError, userInfo: userInfo) } private extension OFXMLReader { diff --git a/Frameworks/OmniFoundation/FileManagement.subproj/OFUTI.m b/Frameworks/OmniFoundation/FileManagement.subproj/OFUTI.m index c04c8593..b2aa0dd9 100644 --- a/Frameworks/OmniFoundation/FileManagement.subproj/OFUTI.m +++ b/Frameworks/OmniFoundation/FileManagement.subproj/OFUTI.m @@ -449,7 +449,9 @@ BOOL OFTypeConformsToOneOfTypesInArray(NSString *type, NSArray *type if (type == nil) { return NO; } - + if ([types containsObject:type]) { + return YES; // Avoid eventually calling UTTypeConformsTo when possible. + } for (NSString *checkType in types) { if (_TypeConformsToType(type, checkType)) { return YES; diff --git a/Frameworks/OmniFoundation/FileManagement.subproj/OFUTI.swift b/Frameworks/OmniFoundation/FileManagement.subproj/OFUTI.swift index 56d50f86..b007d5d6 100644 --- a/Frameworks/OmniFoundation/FileManagement.subproj/OFUTI.swift +++ b/Frameworks/OmniFoundation/FileManagement.subproj/OFUTI.swift @@ -76,6 +76,33 @@ public struct UTI { self.rawFileType = fileType } + public static func fileTypePreperringNative(_ fileExtension: String) -> String? { + if let uti = try? fileType(forPathExtension: fileExtension, isDirectory: nil, preferringNative: true) { + return uti.rawFileType + } + + return nil + } + + public static func conforms(_ fileType: String?, to uti: String) -> Bool { + guard let fileType = fileType else { return false } + + return UTTypeConformsTo(fileType as NSString, uti as NSString) + } + + public static func conforms(_ fileType: String?, toAnyOf types: [String]) -> Bool { + guard let fileType = fileType else { return false } + + if types.contains(fileType) { + return true // Avoid eventually calling UTTypeConformsTo when possible. + } + for uti in types { + if conforms(fileType, to: uti) { + return true + } + } + return false + } // NOTE: We could define the typical '~=' pattern comparison operator, but have chosen not to, since the two types passed in are the same. This would make it too easy to swap the order of the arguments to the operator and not be checking the desire condition. diff --git a/Frameworks/OmniFoundation/Formatters.subproj/OFTimeSpanFormatter.m b/Frameworks/OmniFoundation/Formatters.subproj/OFTimeSpanFormatter.m index e89ba630..b56c918f 100644 --- a/Frameworks/OmniFoundation/Formatters.subproj/OFTimeSpanFormatter.m +++ b/Frameworks/OmniFoundation/Formatters.subproj/OFTimeSpanFormatter.m @@ -816,8 +816,8 @@ - (BOOL)getObjectValue:(out id *)obj forString:(NSString *)string errorDescripti // Eat more whitespace [scanner scanCharactersFromSet:whitespaceCharacterSet intoString:NULL]; - NSNumber *numberValue; - if (!(numberValue = [self _scanNumberFromScanner:scanner])) { + NSNumber *numberValue = [self _scanNumberFromScanner:scanner]; + if (numberValue == nil) { if (gotAnythingValid) break; // if we get a ., we may still have a valid partial string. @@ -835,7 +835,6 @@ - (BOOL)getObjectValue:(out id *)obj forString:(NSString *)string errorDescripti number = [numberValue floatValue]; if ([scanner scanString:@"/" intoString:NULL]) { - NSNumber *denominator; if ([scanner isAtEnd]) { if (gotAnythingValid) break; @@ -845,7 +844,9 @@ - (BOOL)getObjectValue:(out id *)obj forString:(NSString *)string errorDescripti return YES; } } - if (!(denominator = [self _scanNumberFromScanner:scanner])) { + + NSNumber *denominator = [self _scanNumberFromScanner:scanner]; + if (denominator == nil) { if (gotAnythingValid) break; if (error) diff --git a/Frameworks/OmniFoundation/OFCDSAUtilities.h b/Frameworks/OmniFoundation/OFCDSAUtilities.h index e5e3d72f..ee2ee241 100644 --- a/Frameworks/OmniFoundation/OFCDSAUtilities.h +++ b/Frameworks/OmniFoundation/OFCDSAUtilities.h @@ -1,4 +1,4 @@ -// Copyright 2009-2016 Omni Development, Inc. All rights reserved. +// Copyright 2009-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -21,7 +21,7 @@ */ #if OF_ENABLE_CDSA -extern NSString * const OFCDSAErrorDomain; +extern NSErrorDomain const OFCDSAErrorDomain; NSString *OFStringFromCSSMReturn(CSSM_RETURN code); BOOL OFErrorFromCSSMReturn(NSError **outError, CSSM_RETURN errcode, NSString *function); NSData *OFGetAppleKeyDigest(const CSSM_KEY *pkey, CSSM_CC_HANDLE optionalContext, NSError **outError); diff --git a/Frameworks/OmniFoundation/OFCDSAUtilities.m b/Frameworks/OmniFoundation/OFCDSAUtilities.m index bb8bb63b..f4508b98 100644 --- a/Frameworks/OmniFoundation/OFCDSAUtilities.m +++ b/Frameworks/OmniFoundation/OFCDSAUtilities.m @@ -1,4 +1,4 @@ -// Copyright 2005, 2007, 2010-2014 Omni Development, Inc. All rights reserved. +// Copyright 2005-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -70,8 +70,7 @@ return [NSString stringWithFormat:@"%@ (%d)", errorString, code]; } -NSString * const OFCDSAErrorDomain = @"com.omnigroup.OmniFoundation.CDSA"; -#define OFCDSAErrorDomain ( @"com.omnigroup.OmniFoundation.CDSA" ) // Same +NSErrorDomain const OFCDSAErrorDomain = @"com.omnigroup.OmniFoundation.CDSA"; BOOL OFErrorFromCSSMReturn(NSError **outError, CSSM_RETURN errcode, NSString *function) { diff --git a/Frameworks/OmniFoundation/OFErrors.h b/Frameworks/OmniFoundation/OFErrors.h index 164b2d3b..915b1215 100644 --- a/Frameworks/OmniFoundation/OFErrors.h +++ b/Frameworks/OmniFoundation/OFErrors.h @@ -11,7 +11,9 @@ @class NSString; -enum { +extern NSErrorDomain const OFErrorDomain; + +typedef NS_ERROR_ENUM(OFErrorDomain, OFError) { // Zero typically means no error OFCacheFileUnableToWriteError = 1, OFFilterDataCommandReturnedErrorCodeError, @@ -79,10 +81,8 @@ enum { // This key holds the exit status of a process which has exited -#define OFProcessExitStatusErrorKey (@"OFExitStatus") -#define OFProcessExitSignalErrorKey (@"OFExitSignal") - -extern NSString * const OFErrorDomain; +extern NSErrorUserInfoKey const OFProcessExitStatusErrorKey; +extern NSErrorUserInfoKey const OFProcessExitSignalErrorKey; #define OFErrorWithInfoAndDomain(error, domain, code, description, suggestion, ...) _OBError(error, domain, code, __FILE__, __LINE__, NSLocalizedDescriptionKey, description, NSLocalizedRecoverySuggestionErrorKey, (suggestion), ## __VA_ARGS__) #define OFErrorWithInfo(error, code, description, suggestion, ...) OFErrorWithInfoAndDomain(error, OFErrorDomain, code, description, (suggestion), ## __VA_ARGS__) diff --git a/Frameworks/OmniFoundation/OFErrors.m b/Frameworks/OmniFoundation/OFErrors.m index 805683e8..fb44e88b 100644 --- a/Frameworks/OmniFoundation/OFErrors.m +++ b/Frameworks/OmniFoundation/OFErrors.m @@ -1,4 +1,4 @@ -// Copyright 2007-2008, 2010 Omni Development, Inc. All rights reserved. +// Copyright 2007-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -10,4 +10,7 @@ RCS_ID("$Id$") -NSString * const OFErrorDomain = @"com.omnigroup.framework.OmniFoundation.ErrorDomain"; +NSErrorDomain const OFErrorDomain = @"com.omnigroup.framework.OmniFoundation.ErrorDomain"; + +NSErrorUserInfoKey const OFProcessExitStatusErrorKey = @"OFExitStatus"; +NSErrorUserInfoKey const OFProcessExitSignalErrorKey = @"OFExitSignal"; diff --git a/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSObject-OFAppleScriptExtensions.m b/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSObject-OFAppleScriptExtensions.m index 84b5a9ef..2a16c99b 100644 --- a/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSObject-OFAppleScriptExtensions.m +++ b/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSObject-OFAppleScriptExtensions.m @@ -193,7 +193,7 @@ - (NSDictionary *)_mappingForEnumeration:(NSString *)typeName; code = [typeInfo objectForKey:value]; NSString *nameForCode = [[terminology objectForKey:value] objectForKey:@"Name"]; NSNumber *numberForCode = [NSNumber numberWithLong:[code fourCharCodeValue]]; - if (!nameForCode || !numberForCode) { + if (nameForCode == nil || numberForCode == nil) { NSLog(@"warning: name is '%@' for code '%@' (%@) in enumeration %@ of %@", nameForCode, code, value, type, path); continue; } diff --git a/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSURL-OFExtensions.m b/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSURL-OFExtensions.m index 0a5ae0c7..7ebf761e 100644 --- a/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSURL-OFExtensions.m +++ b/Frameworks/OmniFoundation/OpenStepExtensions.subproj/NSURL-OFExtensions.m @@ -622,7 +622,7 @@ static void _OFScanDirectory(NSURL *directoryURL, BOOL shouldRecurse, } // NSFileManager hands back non-standardized URLs even if the input is standardized. - OBASSERT(isDirectoryValue); + OBASSERT(isDirectoryValue != nil); BOOL isDirectory = [isDirectoryValue boolValue]; fileURL = [scanDirectoryURL URLByAppendingPathComponent:[fileURL lastPathComponent] isDirectory:isDirectory]; @@ -698,8 +698,9 @@ OFScanPathExtensionIsPackage OFIsPackageWithKnownPackageExtensions(NSSet * _Null return YES; NSNumber *cachedValue = extensionToIsPackage[pathExtension]; - if (cachedValue) + if (cachedValue != nil) { return [cachedValue boolValue]; + } __block BOOL foundPackage = NO; OFUTIEnumerateKnownTypesForTagPreferringNative((NSString *)kUTTagClassFilenameExtension, pathExtension, nil/*conformingToUTIOrNil*/, ^(NSString *typeIdentifier, BOOL *stop){ diff --git a/Frameworks/OmniFoundation/Tests/OFArrayTests.m b/Frameworks/OmniFoundation/Tests/OFArrayTests.m index 6c11dca3..e78428b6 100644 --- a/Frameworks/OmniFoundation/Tests/OFArrayTests.m +++ b/Frameworks/OmniFoundation/Tests/OFArrayTests.m @@ -1,4 +1,4 @@ -// Copyright 2004-2008, 2010-2014 Omni Development, Inc. All rights reserved. +// Copyright 2004-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -269,7 +269,7 @@ - (void)testSortedUsingSelector break; } } - objs[ix] = n? n : [NSNumber numberWithInt:nums[ix]]; + objs[ix] = (n != nil) ? n : [NSNumber numberWithInt:nums[ix]]; [a insertObject:objs[ix] inArraySortedUsingSelector:@selector(compare:)]; XCTAssertTrue([a isSortedUsingSelector:@selector(compare:)], @"Array: %@", a); diff --git a/Frameworks/OmniFoundation/Tests/OFCMSTest.swift b/Frameworks/OmniFoundation/Tests/OFCMSTest.swift index 7ee3d3fc..193ff79a 100644 --- a/Frameworks/OmniFoundation/Tests/OFCMSTest.swift +++ b/Frameworks/OmniFoundation/Tests/OFCMSTest.swift @@ -26,7 +26,7 @@ class keysource : OFCMSKeySource { var buf = Array(repeating: Int8(0), count: 128); let rv = readpassphrase("foo", &buf, buf.count, 0); if rv == nil { - throw NSError(domain: OFErrorDomain, code: OFKeyNotAvailable, userInfo: nil); + throw OFError(.OFKeyNotAvailable) } else { guard let str = NSString(bytes: buf, length: Int(strlen(buf)), encoding: String.Encoding.utf8.rawValue) else { throw NSError(domain: NSCocoaErrorDomain, code: NSFileReadUnknownStringEncodingError, userInfo: nil); @@ -57,7 +57,7 @@ class keysource : OFCMSKeySource { } } - throw NSError(domain: OFErrorDomain, code: OFKeyNotAvailable, userInfo: nil); + throw OFError(.OFKeyNotAvailable) } #endif } diff --git a/Frameworks/OmniFoundation/XML/OFXMLElement.m b/Frameworks/OmniFoundation/XML/OFXMLElement.m index 6fe1db72..f7b7845e 100644 --- a/Frameworks/OmniFoundation/XML/OFXMLElement.m +++ b/Frameworks/OmniFoundation/XML/OFXMLElement.m @@ -501,7 +501,11 @@ static void ApplyBlock(OFXMLElement *self, void (^block)(id child)) } } else if (self->_child.single) { id child = self->_child.single; - block(child); + if ([child isKindOfClass:[OFXMLElement class]]) { + ApplyBlock(child, block); + } else { + block(child); + } } } @@ -531,7 +535,7 @@ - (NSUInteger)attributeCount; return 0; } -- (nullable NSArray *)attributeNames; +- (nullable NSArray *)attributeNames; { if (_multipleAttributes) { return _attribute.multiple.order; diff --git a/Frameworks/OmniInspector/OIInspectorController.m b/Frameworks/OmniInspector/OIInspectorController.m index b1aa55ac..e7339567 100644 --- a/Frameworks/OmniInspector/OIInspectorController.m +++ b/Frameworks/OmniInspector/OIInspectorController.m @@ -594,6 +594,7 @@ - (void)_setFloatingExpandedness:(BOOL)expanded updateInspector:(BOOL)updateInsp [view setAutoresizingMask:NSViewNotSizable]; [[self containerView] addSubview:view positioned:NSWindowBelow relativeTo:headingButton]; [window setFrame:windowFrame display:YES animate:animate]; + [window layoutIfNeeded]; [view setAutoresizingMask:NSViewHeightSizable | NSViewMinXMargin | NSViewMaxXMargin]; [[NSNotificationCenter defaultCenter] postNotificationName:OIWorkspaceWillChangeNotification object:self]; diff --git a/Frameworks/OmniSoftwareUpdate/Common/OSUInstaller.sh b/Frameworks/OmniSoftwareUpdate/Common/OSUInstaller.sh index 3e2f8f27..3aae5492 100755 --- a/Frameworks/OmniSoftwareUpdate/Common/OSUInstaller.sh +++ b/Frameworks/OmniSoftwareUpdate/Common/OSUInstaller.sh @@ -1,10 +1,11 @@ #!/bin/zsh -f +# +# $Id$ -# Die on any error -set -e +setopt ERR_EXIT echo "args: $*" -id +/usr/bin/id || true SOURCE=$1 DEST=$2 diff --git a/Frameworks/OmniSoftwareUpdate/NSApplication-OSUNewsSupport.m b/Frameworks/OmniSoftwareUpdate/NSApplication-OSUNewsSupport.m index e9325a84..26fad353 100644 --- a/Frameworks/OmniSoftwareUpdate/NSApplication-OSUNewsSupport.m +++ b/Frameworks/OmniSoftwareUpdate/NSApplication-OSUNewsSupport.m @@ -37,12 +37,13 @@ @implementation NSApplication (OSUNewsSupport) - (IBAction)showNews:(id)sender; { - NSURL *newsURL = [OSUChecker sharedUpdateChecker].currentNewsURL; - OBASSERT(newsURL != nil); - if (! newsURL) { + NSURL *cacheURL = [OSUChecker sharedUpdateChecker].cachedNewsURL; + OBASSERT(cacheURL != nil); + if (!cacheURL) { // don't open to an empty URL. return; } + NSURL *webURL = [OSUChecker sharedUpdateChecker].currentNewsURL; OAWebPageViewer *webViewer = [OAWebPageViewer sharedViewerNamed:@"News"]; @@ -53,34 +54,31 @@ - (IBAction)showNews:(id)sender; [[webViewer window] setMinSize:NSMakeSize(800, 400)]; [[webViewer window] setMaxSize:NSMakeSize(800, FLT_MAX)]; - webViewer.usesWebPageTitleForWindowTitle = YES; + webViewer.usesWebPageTitleForWindowTitle = NO; webViewer.mediaStyle = @"release-notes"; - [webViewer loadRequest:[NSURLRequest requestWithURL:newsURL] onCompletion:^(BOOL success, NSURL *url, NSError *error) { - if (success) { - [OSUChecker sharedUpdateChecker].unreadNewsAvailable = NO; - } else { - -#if 0 && defined(DEUB_kilodelta) - NSLog(@"failed to NEWS URL: %@", url); -#endif - // include the newsURL as the baseURL so that the URL is considered "approved" by OAWebPageViewer. Otherwise, OAWebPageViewer won't be displayed. - [webViewer.webView.mainFrame loadAlternateHTMLString:[self _displayableNewsHTMLForError:error] baseURL:nil forUnreachableURL:newsURL]; - [webViewer showWindow:nil]; + [webViewer loadRequest:[NSURLRequest requestWithURL:webURL] onCompletion:^(BOOL success, NSURL *url, NSError *error) { + if (success + || ![error.userInfo[NSURLErrorFailingURLStringErrorKey] isEqualToString:[webURL path]]) { // probably just failed to load an asset + [OSUChecker sharedUpdateChecker].unreadNewsAvailable = NO; + [[NSFileManager defaultManager] removeItemAtURL:cacheURL error:nil]; // now that we've actually shown the real thing, don't want to keep the ugly cached thing around + } else { + // do not attempt to show error messages for urls other than the main url, because that will prevent anything being seen when only some small detail may be missing + [webViewer loadCachedHTML:cacheURL forWebURL:webURL]; + [OSUChecker sharedUpdateChecker].unreadNewsAvailable = NO; } - }]; + }]; } - (BOOL)replacement_validateMenuItem:(NSMenuItem *)menuItem { - // Validate the News menu item to only show when there is a currentNewsURL. + // Validate the News menu item to only show when there is a currentNewsURL and a cached file to show. if (menuItem.action == @selector(showNews:)) { - NSString *newsURLString = [[[OSUChecker sharedUpdateChecker] currentNewsURL] absoluteString]; - if ([NSString isEmptyString:newsURLString]) { - menuItem.hidden = YES; - return NO; - } else { + if ([OSUChecker sharedUpdateChecker].currentNewsIsCached) { menuItem.hidden = NO; return YES; + } else { + menuItem.hidden = YES; + return NO; } } diff --git a/Frameworks/OmniSoftwareUpdate/OSUChecker.h b/Frameworks/OmniSoftwareUpdate/OSUChecker.h index 9b9cc5a8..da976296 100644 --- a/Frameworks/OmniSoftwareUpdate/OSUChecker.h +++ b/Frameworks/OmniSoftwareUpdate/OSUChecker.h @@ -1,4 +1,4 @@ -// Copyright 2001-2016 Omni Development, Inc. All rights reserved. +// Copyright 2001-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -61,6 +61,8 @@ extern NSString * const OSUNewsAnnouncementHasBeenReadNotification; @property(readwrite) BOOL unreadNewsAvailable; @property(readonly) NSURL *currentNewsURL; +- (BOOL)currentNewsIsCached; +- (NSURL *)cachedNewsURL; #pragma mark Subclass opportunities diff --git a/Frameworks/OmniSoftwareUpdate/OSUChecker.m b/Frameworks/OmniSoftwareUpdate/OSUChecker.m index 416ded3b..6d3ea807 100644 --- a/Frameworks/OmniSoftwareUpdate/OSUChecker.m +++ b/Frameworks/OmniSoftwareUpdate/OSUChecker.m @@ -129,6 +129,7 @@ - (OSUPrivacyNoticeResult)checker:(OSUChecker *)checker runPrivacyNoticePanelHav @interface OSUChecker () @property(nonatomic,retain) id target; @property(nonatomic,retain) NSDateFormatter *dateFormatter; +@property(nonatomic,weak) NSURLSessionTask *newsCacheTask; @end @implementation OSUChecker @@ -501,6 +502,26 @@ - (BOOL)unreadNewsAvailable return [[OSUPreferences unreadNews] boolValue]; } +- (void)_cacheCurrentNews +{ + if (self.newsCacheTask) { + return; + } + __weak OSUChecker *weakSelf = self; + self.newsCacheTask = [[NSURLSession sharedSession] dataTaskWithURL:[self currentNewsURL] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + OSUChecker *strongSelf = weakSelf; + if (!data) { + return; + } + [data writeToURL:[strongSelf cachedNewsURL] atomically:YES]; + [[NSNotificationCenter defaultCenter] postNotificationName:OSUNewsAnnouncementNotification + object:strongSelf + userInfo:@{@"OSUNewsAnnouncementURL":[strongSelf cachedNewsURL]}]; + strongSelf.newsCacheTask = nil; + }]; + [self.newsCacheTask resume]; +} + - (void)setUnreadNewsAvailable:(BOOL)unreadNewsAvailable { BOOL originalUnreadValue = self.unreadNewsAvailable; @@ -514,6 +535,23 @@ - (void)setUnreadNewsAvailable:(BOOL)unreadNewsAvailable } } +- (BOOL)currentNewsIsCached +{ + BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self cachedNewsURL].path]; + if (!fileExists) { + [self _cacheCurrentNews]; + return NO; + } + return YES; +} + +- (NSURL *)cachedNewsURL +{ + NSURL *cacheURL = [[NSFileManager defaultManager] URLForDirectory:NSCachesDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:nil]; + cacheURL = [cacheURL URLByAppendingPathComponent:@"GraffleNewsAnnouncement.html"]; + return cacheURL; +} + - (NSURL *)currentNewsURL { NSString *urlString = [[OSUPreferences currentNewsURL] stringValue]; @@ -528,9 +566,7 @@ - (void)setCurrentNewsURL:(NSURL *)currentNewsURL if (currentNewsURL != self.currentNewsURL) { [[OSUPreferences currentNewsURL] setStringValue:[currentNewsURL absoluteString]]; self.unreadNewsAvailable = YES; - [[NSNotificationCenter defaultCenter] postNotificationName:OSUNewsAnnouncementNotification - object:self - userInfo:@{@"OSUNewsAnnouncementURL":currentNewsURL}]; + [self _cacheCurrentNews]; } } @@ -786,6 +822,9 @@ - (void)_initiateCheck; if (_currentCheckOperation) return; + // clear cached news url to avoid using stale data + [[NSFileManager defaultManager] removeItemAtURL:[self cachedNewsURL] error:nil]; + [self _beginLoadingURLInitiatedByUser:NO]; } diff --git a/Frameworks/OmniSoftwareUpdate/OSURunTime.m b/Frameworks/OmniSoftwareUpdate/OSURunTime.m index 1de5af5b..468a0016 100644 --- a/Frameworks/OmniSoftwareUpdate/OSURunTime.m +++ b/Frameworks/OmniSoftwareUpdate/OSURunTime.m @@ -108,7 +108,7 @@ void OSURunTimeApplicationActivated(NSString *appIdentifier, NSString *bundleVer [newScope setObject:version forKey:OSUVersionKey]; // Run time - if (startClockTimeNumber) { + if (startClockTimeNumber != nil) { unsigned startClockTime = [startClockTimeNumber doubleValue]; // The clock can go "backwards" if the machine is restarted between runs of the app (possibly if the last run crashed) since OSUGetCurrentClockTime() returns the system clock. @@ -122,7 +122,7 @@ void OSURunTimeApplicationActivated(NSString *appIdentifier, NSString *bundleVer totalRunTimeNumber = nil; } - NSTimeInterval totalRunTime = totalRunTimeNumber ? [totalRunTimeNumber doubleValue] : 0.0; + NSTimeInterval totalRunTime = (totalRunTimeNumber != nil) ? [totalRunTimeNumber doubleValue] : 0.0; totalRunTime += (currentClockTime - startClockTime); @@ -230,7 +230,7 @@ static void _OSURunTimeAddStatisticsToInfo(NSMutableDictionary *info, NSDictiona // We'll report integral minutes instead of seconds. We can go more than 8000 years with this w/o overflowing 32 bits. NSNumber *runTimeNumber = [scope objectForKey:OSUTotalRunTimeKey]; - NSTimeInterval runTime = runTimeNumber ? [runTimeNumber doubleValue] : 0.0; + NSTimeInterval runTime = (runTimeNumber != nil) ? [runTimeNumber doubleValue] : 0.0; unsigned long runMinutes = (unsigned long)floor(runTime / 60.0); [info setObject:[NSString stringWithFormat:@"%lu", runMinutes] forKey:[NSString stringWithFormat:@"%@runmin", prefix]]; diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocument.m b/Frameworks/OmniUI/iPad-Document/OUIDocument.m index 1edc89c3..7db15d70 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocument.m +++ b/Frameworks/OmniUI/iPad-Document/OUIDocument.m @@ -547,7 +547,7 @@ - (void)updateChangeCountWithToken:(id)changeCountToken forSaveOperation:(UIDocu OBASSERT([changeCountToken count] == 2); // the two keys we put in NSNumber *editorStateCount = [(NSDictionary *)changeCountToken objectForKey:ViewStateChangeTokenKey]; - OBASSERT(editorStateCount); + OBASSERT_NOTNULL(editorStateCount); _savedViewStateChangeCount = [editorStateCount unsignedIntegerValue]; id originalToken = [(NSDictionary *)changeCountToken objectForKey:OriginalChangeTokenKey]; diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentAppController.h b/Frameworks/OmniUI/iPad-Document/OUIDocumentAppController.h index d67bcff7..69cf23af 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentAppController.h +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentAppController.h @@ -41,6 +41,8 @@ - (IBAction)makeNewDocument:(id)sender; - (void)makeNewDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem; +// this will create a new document from the existing document and preserve the filename, documentStore, scope, and parentFolder for the newly created document. +- (void)makeNewDocumentWithFileItem:(ODSFileItem *)fileItem; - (IBAction)closeDocument:(id)sender; - (void)closeAndDismissDocumentWithCompletionHandler:(void(^)(void))completionHandler; - (void)closeDocumentWithCompletionHandler:(void(^)(void))completionHandler; diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentAppController.m b/Frameworks/OmniUI/iPad-Document/OUIDocumentAppController.m index b7917cfa..bdb8afab 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentAppController.m +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentAppController.m @@ -510,25 +510,37 @@ - (void)importDocumentFromURL:(NSURL *)url; }]; } -- (void)makeNewDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem +- (void)_makeNewDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem preserveFileName:(BOOL)preserveFileName; { __block OUIDocumentPicker *docPicker = [self documentPicker]; [self _closeAllDocumentsBeforePerformingBlock:^{ - [docPicker navigateToScope:[docPicker localDocumentsScope] animated:NO]; + if (!preserveFileName) { + [docPicker navigateToScope:[docPicker localDocumentsScope] animated:NO]; + } if (docPicker.selectedScopeViewController != nil) { - [docPicker.selectedScopeViewController newDocumentWithTemplateFileItem:templateFileItem documentType:ODSDocumentTypeNormal completion:nil]; + [docPicker.selectedScopeViewController newDocumentWithTemplateFileItem:templateFileItem documentType:ODSDocumentTypeNormal preserveDocumentName:preserveFileName completion:nil]; } else { - [self _enqueueBackgroundMakingOfNewDocumentWithTemplateFileItem:templateFileItem]; + [self _enqueueBackgroundMakingOfNewDocumentWithTemplateFileItem:templateFileItem preserveFileName:preserveFileName]; } }]; } +- (void)makeNewDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem +{ + [self _makeNewDocumentWithTemplateFileItem:templateFileItem preserveFileName:NO]; +} + +- (void)makeNewDocumentWithFileItem:(ODSFileItem *)fileItem; +{ + [self _makeNewDocumentWithTemplateFileItem:fileItem preserveFileName:YES]; +} + // LMTODO: Stolen from OUIDocumentPickerViewController newDocumentWithTemplateFileItem:documentType:completion: // We should maybe refactor so that code calls this code? -- (void)_enqueueBackgroundMakingOfNewDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem +- (void)_enqueueBackgroundMakingOfNewDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem preserveFileName:(BOOL)preserveFileName; { // Instead of duplicating the template file item's URL (if we have one), we always read it into a OUIDocument and save it out, letting the document know that this is for the purposes of instantiating a new document. The OUIDocument may do extra work in this case that wouldn't get done if we just cloned the file (and this lets the work be done atomically by saving the new file to a temporary location before moving to a visible location). - ODSStore *documentStore = self.documentPicker.documentStore; + ODSStore *documentStore = preserveFileName && templateFileItem ? templateFileItem.scope.documentStore : self.documentPicker.documentStore; NSURL *temporaryURL = [documentStore temporaryURLForCreatingNewDocumentOfType:ODSDocumentTypeNormal]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; @@ -575,8 +587,10 @@ - (void)_enqueueBackgroundMakingOfNewDocumentWithTemplateFileItem:(ODSFileItem * return; } - ODSScope *localScope = [documentStore defaultUsableScope]; - [documentStore moveNewTemporaryDocumentAtURL:temporaryURL toScope:localScope folder:nil documentType:ODSDocumentTypeNormal completionHandler:^(ODSFileItem *createdFileItem, NSError *error){ + ODSScope *localScope = preserveFileName && templateFileItem ? templateFileItem.scope : [documentStore defaultUsableScope]; + ODSFolderItem *folderItem = preserveFileName && templateFileItem ? templateFileItem.parentFolder : nil; + NSString *documentName = preserveFileName && templateFileItem ? templateFileItem.name : nil; + [documentStore moveNewTemporaryDocumentAtURL:temporaryURL toScope:localScope folder:folderItem documentType:ODSDocumentTypeNormal documentName:documentName completionHandler:^(ODSFileItem *createdFileItem, NSError *error){ if (createdFileItem != nil) { [self openDocument:createdFileItem]; } else { @@ -1364,18 +1378,36 @@ - (NSArray *)_viewableFileTypes; return roleByFileType; } +static NSSet *ViewableFileTypes() +{ + static dispatch_once_t onceToken; + static NSSet *viewableFileTypes = nil; + + dispatch_once(&onceToken, ^{ + // Make a set all our declared UTIs, for fast contains-checking in canViewFileTypeWithIdentifier. + NSDictionary *roleByFileType = RoleByFileType(); + viewableFileTypes = [NSSet setWithArray:roleByFileType.allKeys]; + }); + + return viewableFileTypes; +} + - (BOOL)canViewFileTypeWithIdentifier:(NSString *)uti; { OBPRECONDITION(!uti || [uti isEqualToString:[uti lowercaseString]]); // our cache uses lowercase keys. if (uti == nil) return NO; - - for (NSString *candidateUTI in RoleByFileType()) { + + NSSet *viewableFileTypes = ViewableFileTypes(); + if ([viewableFileTypes containsObject:uti]) { + return YES; // Performance fix: avoid calling OFTypeConformsTo, which calls UTTypeConformsTo, which is slow, when possible. + } + + for (NSString *candidateUTI in viewableFileTypes) { if (OFTypeConformsTo(uti, candidateUTI)) return YES; } - return NO; } diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentExporter.m b/Frameworks/OmniUI/iPad-Document/OUIDocumentExporter.m index 9c459dc9..2a9aa427 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentExporter.m +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentExporter.m @@ -456,7 +456,7 @@ - (void)printDocument:(ODSFileItem *)fileItem; - (BOOL)_canUseOpenInWithExportType:(NSString *)exportType; { NSNumber *value = [[OUIDocumentExporter openInMapCache] objectForKey:exportType]; - if (value) { + if (value != nil) { // We have a cached value, so immediately return it. return [value boolValue]; } diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerItemMetadataView.h b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerItemMetadataView.h index 53891aa6..6f1b0695 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerItemMetadataView.h +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerItemMetadataView.h @@ -1,4 +1,4 @@ -// Copyright 2010-2015 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -9,43 +9,30 @@ #import +NS_ASSUME_NONNULL_BEGIN + @class OUIDocumentNameTextField; // Flatten the view hierarchy for the name/date and possible iCloud status icon for fewer composited layers while scrolling. @interface OUIDocumentPickerItemMetadataView : UIView -+ (UIColor *)defaultBackgroundColor; - -@property(nonatomic,copy) NSString *name; -@property(nonatomic,strong) UIImage *nameBadgeImage; -@property(nonatomic) BOOL showsImage; -@property(nonatomic,copy) NSString *dateString; -@property(nonatomic,assign) BOOL showsProgress; -@property(nonatomic,assign) double progress; -@property(nonatomic,assign) BOOL isSmallSize; -// OUIDocumentRenameSession becomes the delegate of this while renaming -@property(nonatomic,retain) IBOutlet OUIDocumentNameTextField *nameTextField; -@property(nonatomic,retain) IBOutlet UILabel *dateLabel; -@property(nonatomic,readonly) UIImageView *nameBadgeImageView; -@property(nonatomic, retain) IBOutlet UIProgressView *transferProgressView; -@property (nonatomic, retain) IBOutlet UIView *topHairlineView; -@property(nonatomic, retain) UIView *startSnap; // for animating to/from large size when renaming -@property(nonatomic, retain) UIView *endSnap; // for animating to/from large size when renaming - -// constraints -@property (nonatomic,retain) IBOutlet NSLayoutConstraint *leadingHorizPadding; -@property (nonatomic,retain) IBOutlet NSLayoutConstraint *trailingHorizPadding; -@property (nonatomic,retain) IBOutlet NSLayoutConstraint *nameToDatePadding; -@property (nonatomic,retain) IBOutlet NSLayoutConstraint *nameHeightConstraint; -@property (nonatomic,retain) IBOutlet NSLayoutConstraint *dateHeightConstraint; -@property (nonatomic,retain) IBOutlet NSLayoutConstraint *imageAspectRatioConstraint; -@property (nonatomic,retain) IBOutlet NSLayoutConstraint *topPadding; -@property (nonatomic,retain) IBOutlet NSLayoutConstraint *bottomPadding; +@property (class, nonatomic, readonly) UIColor *defaultBackgroundColor; +@property (nullable, nonatomic, copy) NSString *name; +@property (nullable, nonatomic, copy) NSString *dateString; +@property (nullable, nonatomic, strong) UIImage *nameBadgeImage; +@property (nonatomic) BOOL showsImage; +@property (nonatomic) BOOL showsProgress; +@property (nonatomic) float progress; +@property (nonatomic) BOOL isSmallSize; @property (nonatomic) BOOL doubleSizeFonts; +@property (nonatomic, readonly) BOOL isEditing; -- (BOOL)isEditing; -- (UIView*)viewForScalingStartFrame:(CGRect)startFrame endFrame:(CGRect)endFrame; +// OUIDocumentRenameSession becomes the delegate of this while renaming +@property (nonatomic, readonly) OUIDocumentNameTextField *nameTextField; +@property (nullable, nonatomic, readonly) UIProgressView *transferProgressView; + +- (UIView *)viewForScalingStartFrame:(CGRect)startFrame endFrame:(CGRect)endFrame; - (void)animationsToPerformAlongsideScalingToHeight:(CGFloat)height; @end @@ -61,3 +48,5 @@ @end +NS_ASSUME_NONNULL_END + diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerItemMetadataView.m b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerItemMetadataView.m index d3c88677..614947df 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerItemMetadataView.m +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerItemMetadataView.m @@ -1,4 +1,4 @@ -// Copyright 2010-2016 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -7,8 +7,8 @@ #import +@import OmniUI; #import -#import #import #import "OUIDocumentParameters.h" @@ -35,11 +35,24 @@ - (CGRect)clearButtonRectForBounds:(CGRect)bounds; @end @interface OUIDocumentPickerItemMetadataView () -@property (nonatomic) BOOL needsConstraintsForAnimation; -- (CGFloat)_nameLabelFontSize; -- (CGFloat)_detailLabelFontSize; -- (CGFloat)_nameHeight; -- (CGFloat)_dateHeight; + +@property (nonatomic, readonly) UILabel *dateLabel; +@property (nonatomic, readonly) UIImageView *nameBadgeImageView; +@property (nonatomic, readonly) UIView *topHairlineView; + +@property (nonatomic) UIView *startSnap; // for animating to/from large size when renaming +@property (nonatomic) UIView *endSnap; // for animating to/from large size when renaming + +@property (nonatomic, readonly) CGFloat topPadding; +@property (nonatomic, readonly) CGFloat bottomPadding; +@property (nonatomic, readonly) CGFloat leftPadding; +@property (nonatomic, readonly) CGFloat rightPadding; +@property (nonatomic, readonly) CGFloat nameLabelFontSize; +@property (nonatomic, readonly) CGFloat detailLabelFontSize; +@property (nonatomic, readonly) CGFloat nameHeight; +@property (nonatomic, readonly) CGFloat dateHeight; +@property (nonatomic, readonly) CGFloat nameToPreviewPadding; + @end @implementation OUIDocumentPickerItemMetadataView @@ -67,24 +80,19 @@ - (void)commonInit _topHairlineView.opaque = NO; _nameTextField.textAlignment = NSTextAlignmentLeft; - //_nameTextField.lineBreakMode = NSLineBreakByTruncatingTail; - _nameTextField.font = [UIFont systemFontOfSize:[self _nameLabelFontSize]]; + _nameTextField.font = [UIFont systemFontOfSize:self.nameLabelFontSize]; _nameTextField.textColor = OAMakeUIColor(kOUIDocumentPickerItemViewNameLabelColor); _nameTextField.clearButtonMode = UITextFieldViewModeWhileEditing; _nameTextField.autocapitalizationType = UITextAutocapitalizationTypeWords; _nameTextField.spellCheckingType = UITextSpellCheckingTypeNo; _nameTextField.returnKeyType = UIReturnKeyDone; - _dateLabel.font = [UIFont systemFontOfSize:[self _detailLabelFontSize]]; + _dateLabel.font = [UIFont systemFontOfSize:self.detailLabelFontSize]; _dateLabel.textColor = OAMakeUIColor(kOUIDocumentPickerItemViewDetailLabelColor); - - _nameBadgeImageView.alpha = 0; - _nameBadgeImageView.hidden = YES; - + _showsImage = NO; self.backgroundColor = [[self class] defaultBackgroundColor]; - self.opaque = NO; } - initWithFrame:(CGRect)frame; @@ -97,70 +105,53 @@ - (void)commonInit return self; } +- (void)_setFrameIfNeeded:(CGRect)frame view:(UIView *)view +{ + if (!CGRectEqualToRect(view.frame, frame)) { + view.frame = frame; + } +} + + - (void)createSubviews { - NSMutableArray *constraints = [NSMutableArray array]; - - // top hairline _topHairlineView = [[UIView alloc] init]; + _topHairlineView.translatesAutoresizingMaskIntoConstraints = NO; [self addSubview:_topHairlineView]; - [constraints addObject:[_topHairlineView.topAnchor constraintEqualToAnchor:self.topAnchor]]; - [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_topHairlineView]|" - options:kNilOptions - metrics:nil - views:NSDictionaryOfVariableBindings(_topHairlineView)]]; - [constraints addObject:[NSLayoutConstraint constraintWithItem:_topHairlineView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0f - constant:1.0 / [self contentScaleFactor]]]; - - // labels and status image + _nameTextField = [[OUIDocumentNameTextField alloc] init]; + _nameTextField.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_nameTextField]; + _dateLabel = [[UILabel alloc] init]; - [_dateLabel setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical]; - [_dateLabel setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - [_nameTextField setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; - [_nameTextField setContentCompressionResistancePriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; - [_dateLabel setContentHuggingPriority:UILayoutPriorityDefaultLow forAxis:UILayoutConstraintAxisHorizontal]; - - UIStackView *labels = [[UIStackView alloc] initWithArrangedSubviews:@[_nameTextField, _dateLabel]]; - labels.axis = UILayoutConstraintAxisVertical; - - _nameBadgeImageView = [[UIImageView alloc] init]; - _nameBadgeImageView.contentMode = UIViewContentModeScaleAspectFit; - _nameBadgeImageView.translatesAutoresizingMaskIntoConstraints = NO; - - // put these things in a stack view because then when we hide the statusImage, the labels will automatically grow to fill the space - UIStackView *horizontalStackView = [[UIStackView alloc] initWithArrangedSubviews:@[labels, _nameBadgeImageView]]; - - horizontalStackView.axis = UILayoutConstraintAxisHorizontal; - - [self addSubview:horizontalStackView]; + _dateLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_dateLabel]; - self.topPadding = [horizontalStackView.topAnchor constraintEqualToAnchor:horizontalStackView.superview.topAnchor]; - self.topPadding.constant = [self topPaddingAmount]; - self.bottomPadding = [horizontalStackView.superview.bottomAnchor constraintEqualToAnchor:horizontalStackView.bottomAnchor]; - self.bottomPadding.constant = [self bottomPaddingAmount]; - [constraints addObjectsFromArray:@[self.topPadding, self.bottomPadding]]; + [self setNeedsLayout]; +} - self.leadingHorizPadding = [horizontalStackView.leadingAnchor constraintEqualToAnchor:horizontalStackView.superview.leadingAnchor]; - self.leadingHorizPadding.constant = [self _nameToPreviewPadding]; - self.trailingHorizPadding = [_nameBadgeImageView.trailingAnchor constraintEqualToAnchor:horizontalStackView.superview.trailingAnchor]; - self.trailingHorizPadding.constant = -[self bottomPaddingAmount]; +- (void)_ensureNameBadgeImageView +{ + if (!_nameBadgeImageView) { + _nameBadgeImageView = [[UIImageView alloc] init]; + _nameBadgeImageView.translatesAutoresizingMaskIntoConstraints = NO; + _nameBadgeImageView.contentMode = UIViewContentModeScaleAspectFit; + _nameBadgeImageView.alpha = 0; + _nameBadgeImageView.hidden = YES; - [constraints addObjectsFromArray:@[self.leadingHorizPadding, self.trailingHorizPadding]]; - - self.transferProgressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar]; - self.transferProgressView.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:self.transferProgressView]; - [constraints addObject:[self.transferProgressView.widthAnchor constraintEqualToAnchor:self.widthAnchor]]; - [constraints addObject:[self.transferProgressView.centerXAnchor constraintEqualToAnchor:self.centerXAnchor]]; - [constraints addObject:[self.transferProgressView.centerYAnchor constraintEqualToAnchor:self.topAnchor]]; - - [NSLayoutConstraint activateConstraints:constraints]; + [self addSubview:_nameBadgeImageView]; + [self setNeedsLayout]; + } +} + +- (void)_ensureTransferProgressView +{ + if (!_transferProgressView) { + _transferProgressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar]; + _transferProgressView.translatesAutoresizingMaskIntoConstraints = NO; + [self addSubview:_transferProgressView]; + [self setNeedsLayout]; + } } - (NSString *)name; @@ -177,25 +168,14 @@ - (UIImage *)nameBadgeImage; { return _nameBadgeImageView.image; } + - (void)setNameBadgeImage:(UIImage *)nameBadgeImage; { - _nameBadgeImageView.image = nameBadgeImage; - self.showsImage = (nameBadgeImage != nil); if (nameBadgeImage) { - self.imageAspectRatioConstraint = [NSLayoutConstraint constraintWithItem:self.nameBadgeImageView - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:self.nameBadgeImageView - attribute:NSLayoutAttributeHeight - multiplier:nameBadgeImage.size.width/nameBadgeImage.size.height - constant:0.0f]; - [NSLayoutConstraint activateConstraints:@[self.imageAspectRatioConstraint]]; - } else { - if (self.imageAspectRatioConstraint) { - [NSLayoutConstraint deactivateConstraints:@[self.imageAspectRatioConstraint]]; - self.imageAspectRatioConstraint = nil; - } + [self _ensureNameBadgeImageView]; } + _nameBadgeImageView.image = nameBadgeImage; + self.showsImage = (nameBadgeImage != nil); } - (BOOL)showsImage; @@ -207,6 +187,9 @@ - (void)setShowsImage:(BOOL)flag; { if (flag != _showsImage) { _showsImage = flag; + if (flag) { + [self _ensureNameBadgeImageView]; + } if (_showsImage) { [UIView performWithoutAnimation:^{ _nameBadgeImageView.alpha = 0; @@ -233,70 +216,64 @@ - (void)setDoubleSizeFonts:(BOOL)doubleSizeFonts { if (doubleSizeFonts != _doubleSizeFonts) { _doubleSizeFonts = doubleSizeFonts; - self.nameHeightConstraint.constant = doubleSizeFonts ? [self _nameHeight] * 2 : [self _nameHeight]; - self.dateHeightConstraint.constant = doubleSizeFonts ? [self _dateHeight] * 2: [self _dateHeight]; - self.leadingHorizPadding.constant = doubleSizeFonts ? self.leadingHorizPadding.constant * 2 : self.leadingHorizPadding.constant / 2; - self.trailingHorizPadding.constant = doubleSizeFonts ? self.trailingHorizPadding.constant * 2 : self.trailingHorizPadding.constant / 2; - self.topPadding.constant = doubleSizeFonts ? [self topPaddingAmount] * 2 : [self topPaddingAmount]; - self.bottomPadding.constant = doubleSizeFonts ? [self bottomPaddingAmount] * 2 : [self bottomPaddingAmount]; [self _resetLabelFontSizes]; + [self setNeedsLayout]; } self.nameTextField.useLargerClearButton = doubleSizeFonts; } -- (CGFloat)verticalPaddingAmount -{ - return self.isSmallSize ? kOUIDocumentPickerItemViewNameToTopPaddingSmallSize : kOUIDocumentPickerItemViewNameToTopPaddingLargeSize; -} - -- (CGFloat)topPaddingAmount -{ - return [self verticalPaddingAmount]; -} - -- (CGFloat)bottomPaddingAmount -{ - return [self verticalPaddingAmount] / kOUIDocumentPickerItemViewTopToBottomPaddingRatio; -} - - (NSString *)dateString; { return _dateLabel.text; } + - (void)setDateString:(NSString *)dateString; { - _dateLabel.text = dateString; + if (!OFISEQUAL(_dateLabel.text, dateString)) { + _dateLabel.text = dateString; + [self invalidateIntrinsicContentSize]; + [self setNeedsLayout]; + } } - (BOOL)showsProgress; { - return !self.transferProgressView.hidden; + return _transferProgressView && !_transferProgressView.hidden; } + - (void)setShowsProgress:(BOOL)showsProgress; { - self.transferProgressView.hidden = !showsProgress; + if (showsProgress) { + [self _ensureTransferProgressView]; + } + _transferProgressView.hidden = !showsProgress; } -- (double)progress; +- (float)progress; { - if (self.showsProgress) - return self.transferProgressView.progress; + if (self.showsProgress) { + return _transferProgressView.progress; + } return 0.0; } -- (void)setProgress:(double)progress; + +- (void)setProgress:(float)progress; { - OBPRECONDITION(self.transferProgressView || progress == 0.0 || progress == 1.0); - - self.transferProgressView.progress = progress; + OBPRECONDITION(_transferProgressView || progress == 0.0 || progress == 1.0); + if (progress > 0.0) { + [self _ensureTransferProgressView]; + _transferProgressView.progress = progress; + } } #pragma mark - Scaling Animation Support + - (BOOL)isEditing { return self.nameTextField.isFirstResponder; } -- (UIView*)viewForScalingStartFrame:(CGRect)startFrame endFrame:(CGRect)endFrame +- (UIView *)viewForScalingStartFrame:(CGRect)startFrame endFrame:(CGRect)endFrame { if (!CGRectEqualToRect(startFrame, self.frame)) { self.frame = startFrame; @@ -306,8 +283,6 @@ - (UIView*)viewForScalingStartFrame:(CGRect)startFrame endFrame:(CGRect)endFrame self.frame = endFrame; self.doubleSizeFonts = endFrame.size.width > startFrame.size.width; - [self setNeedsUpdateConstraints]; - [self updateConstraintsIfNeeded]; [self setNeedsLayout]; [self layoutIfNeeded]; self.endSnap = [self snapshotViewAfterScreenUpdates:YES]; @@ -342,6 +317,74 @@ - (void)animationsToPerformAlongsideScalingToHeight:(CGFloat)height #pragma mark - UIView subclass +- (void)layoutSubviews +{ + [super layoutSubviews]; + + CGRect bounds = self.bounds; + CGFloat width = CGRectGetWidth(bounds); + if (width < 1.0) { + return; + } + + CGFloat height = CGRectGetHeight(bounds); + if (height < 1.0) { + height = self.intrinsicContentSize.height; + } + + CGFloat minX = CGRectGetMinX(bounds); + CGFloat maxX = CGRectGetMaxX(bounds); + CGFloat minY = CGRectGetMinY(bounds); + CGFloat maxY = CGRectGetMaxY(bounds); + CGFloat topPadding = self.topPadding; + CGFloat bottomPadding = self.bottomPadding; + CGFloat rightPadding = self.rightPadding; + CGFloat leftPadding = self.leftPadding; + + // Top hairline + CGRect hairlineFrame = CGRectMake(minX, minY, width, 1.0 / self.contentScaleFactor); + [self _setFrameIfNeeded:hairlineFrame view:_topHairlineView]; + + // Progress view + if (_transferProgressView) { + CGFloat progressHeight = CGRectGetHeight(_transferProgressView.bounds); + CGRect progressFrame = CGRectMake(minX, -0.5, width, progressHeight); + [self _setFrameIfNeeded:progressFrame view:_transferProgressView]; + } + + CGFloat maxXForLabels = maxX - rightPadding; + + // Image view + BOOL imageViewIsHidden = !_nameBadgeImageView || _nameBadgeImageView.isHidden || _nameBadgeImageView.image == nil; + if (!imageViewIsHidden) { + CGFloat maxImageHeight = (height - topPadding) - bottomPadding; + CGSize imageSize = _nameBadgeImageView.image.size; + if (imageSize.height > maxImageHeight) { + CGFloat multiplier = maxImageHeight / imageSize.height; + imageSize.height = imageSize.height * multiplier; + imageSize.width = imageSize.width * multiplier; + } + CGFloat imageOriginX = (maxX - rightPadding) - imageSize.width; + CGRect imageFrame = CGRectMake(imageOriginX, topPadding, imageSize.width, imageSize.height); + [self _setFrameIfNeeded:imageFrame view:_nameBadgeImageView]; + maxXForLabels = CGRectGetMinX(imageFrame) - rightPadding; + } + + // Date + BOOL showDate = ![NSString isEmptyString:self.dateString]; + CGFloat textWidth = maxXForLabels - leftPadding; + if (showDate) { + CGFloat dateHeight = self.dateHeight; + CGFloat dateOriginY = (maxY - bottomPadding) - dateHeight; + CGRect dateFrame = CGRectMake(leftPadding, dateOriginY, textWidth, dateHeight); + [self _setFrameIfNeeded:dateFrame view:self.dateLabel]; + } + + // Name + CGRect nameFrame = CGRectMake(leftPadding, topPadding, textWidth, self.nameHeight); + [self _setFrameIfNeeded:nameFrame view:_nameTextField]; +} + - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; { UIView *hit = [super hitTest:point withEvent:event]; @@ -356,34 +399,40 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // Our callers only obey the height we specify, so we don't compute a width for our ideal layout (which is expensive). - (CGSize)sizeThatFits:(CGSize)size; { - return CGSizeMake(size.width, [self _nameToPreviewPadding] + [self _nameHeight] + kOUIDocumentPickerItemViewNameToDatePadding + [self _dateHeight] + [self _nameToPreviewPadding]); + if ([NSString isEmptyString:self.dateString]) { + return CGSizeMake(size.width, self.topPadding + self.nameHeight + self.bottomPadding); + } + return CGSizeMake(size.width, self.topPadding + self.nameHeight + kOUIDocumentPickerItemViewNameToDatePadding + self.dateHeight + self.bottomPadding); +} + +- (CGSize)intrinsicContentSize +{ + CGSize size = [self sizeThatFits:CGSizeMake(CGRectGetWidth(self.bounds), CGFLOAT_MAX)]; + return CGSizeMake(UIViewNoIntrinsicMetric, size.height); } - (void)setIsSmallSize:(BOOL)isSmallSize; { - _isSmallSize = isSmallSize; + if (_isSmallSize == isSmallSize) { + return; + } + _isSmallSize = isSmallSize; [self _resetLabelFontSizes]; - - self.leadingHorizPadding.constant = [self _nameToPreviewPadding]; - self.trailingHorizPadding.constant = -[self bottomPaddingAmount]; - self.nameToDatePadding.constant = kOUIDocumentPickerItemViewNameToDatePadding; - self.nameHeightConstraint.constant = [self _nameHeight]; - self.dateHeightConstraint.constant = [self _dateHeight]; - self.topPadding.constant = [self topPaddingAmount]; - self.bottomPadding.constant = [self bottomPaddingAmount]; + [self setNeedsLayout]; } #pragma mark - Private - - (void)_resetLabelFontSizes; { - _nameTextField.font = [UIFont systemFontOfSize:[self _nameLabelFontSize]]; - _dateLabel.font = [UIFont systemFontOfSize:[self _detailLabelFontSize]]; + _nameTextField.font = [UIFont systemFontOfSize:self.nameLabelFontSize]; + _dateLabel.font = [UIFont systemFontOfSize:self.detailLabelFontSize]; } -- (CGFloat)_nameLabelFontSize; +#pragma mark - Layout + +- (CGFloat)nameLabelFontSize; { CGFloat fontSize; if (self.isSmallSize) { @@ -400,7 +449,7 @@ - (CGFloat)_nameLabelFontSize; return fontSize; } -- (CGFloat)_detailLabelFontSize; +- (CGFloat)detailLabelFontSize; { CGFloat fontSize; if (self.isSmallSize) { @@ -416,7 +465,37 @@ - (CGFloat)_detailLabelFontSize; return fontSize; } -- (CGFloat)_nameToPreviewPadding; +- (CGFloat)leftPadding +{ + return self.doubleSizeFonts ? self.nameToPreviewPadding * 2 : self.nameToPreviewPadding; +} + +- (CGFloat)rightPadding +{ + return self.doubleSizeFonts ? self.nameToPreviewPadding * 2 : self.nameToPreviewPadding; +} + +- (CGFloat)_verticalPaddingAmount +{ + return self.isSmallSize ? kOUIDocumentPickerItemViewNameToTopPaddingSmallSize : kOUIDocumentPickerItemViewNameToTopPaddingLargeSize; +} + +- (CGFloat)topPadding +{ + return self.doubleSizeFonts ? [self _verticalPaddingAmount] * 2 : [self _verticalPaddingAmount]; +} + +- (CGFloat)_bottomPaddingAmount +{ + return [self _verticalPaddingAmount] / kOUIDocumentPickerItemViewTopToBottomPaddingRatio; +} + +- (CGFloat)bottomPadding +{ + return self.doubleSizeFonts ? [self _bottomPaddingAmount] * 2 : [self _bottomPaddingAmount]; +} + +- (CGFloat)nameToPreviewPadding; { if (self.isSmallSize) { return kOUIDocumentPickerItemSmallViewNameToPreviewPadding; @@ -425,18 +504,18 @@ - (CGFloat)_nameToPreviewPadding; } } -- (CGFloat)_nameHeight; +- (CGFloat)nameHeight; { if (self.doubleSizeFonts) { - return MAX(ceil([[UIFont systemFontOfSize:[self _nameLabelFontSize]] lineHeight]), 32.0); + return MAX(ceil([UIFont systemFontOfSize:self.nameLabelFontSize].lineHeight), 32.0); } else { - return MAX(ceil([[UIFont systemFontOfSize:[self _nameLabelFontSize]] lineHeight]), 16.0); + return MAX(ceil([UIFont systemFontOfSize:self.nameLabelFontSize].lineHeight), 16.0); } } -- (CGFloat)_dateHeight; +- (CGFloat)dateHeight; { - return ceil([[UIFont systemFontOfSize:[self _detailLabelFontSize]] lineHeight]); + return ceil([UIFont systemFontOfSize:self.detailLabelFontSize].lineHeight); } @end diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerScrollView.m b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerScrollView.m index 71215767..b9e782f7 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerScrollView.m +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerScrollView.m @@ -865,7 +865,7 @@ static LayoutInfo _updateLayoutAndSetContentSize(OUIDocumentPickerScrollView *se - (void)layoutSubviews; { - if (_renameSession) { + if (_renameSession || !self.window) { return; } LayoutInfo layoutInfo = _updateLayout(self); @@ -904,7 +904,9 @@ - (void)layoutSubviews; frame.origin.x = (CGRectGetWidth(contentRect) / 2) - (frame.size.width / 2); frame.origin.y = fmax((CGRectGetHeight(_topControls.frame) / 2) - (frame.size.height / 2), [self _verticalPadding]); frame = CGRectIntegral(frame); - _topControls.frame = frame; + if (!CGRectEqualToRect(_topControls.frame, frame)) { + _topControls.frame = frame; + } } if (_titleViewForCompactWidth) { @@ -922,7 +924,10 @@ - (void)layoutSubviews; _titleViewForCompactWidth.frame = frame; if (CGRectGetMaxY(_topControls.frame) < CGRectGetMaxY(_titleViewForCompactWidth.frame)) { - _topControls.frame = CGRectUnion(_topControls.frame, _titleViewForCompactWidth.frame); + frame = CGRectUnion(_topControls.frame, _titleViewForCompactWidth.frame); + if (!CGRectEqualToRect(_topControls.frame, frame)) { + _topControls.frame = frame; + } } } @@ -1028,7 +1033,9 @@ - (void)layoutSubviews; OBASSERT([unusedFileItemViews containsObjectIdenticalTo:itemView] ^ [unusedGroupItemViews containsObjectIdenticalTo:itemView]); [unusedFileItemViews removeObjectIdenticalTo:itemView]; [unusedGroupItemViews removeObjectIdenticalTo:itemView]; - itemView.frame = frame; + if (!CGRectEqualToRect(itemView.frame, frame)) { + itemView.frame = frame; + } DEBUG_LAYOUT(@" kept view %@", [itemView shortDescription]); } else { // This item needs a view! @@ -1051,6 +1058,7 @@ - (void)layoutSubviews; }]; // Now, assign views to visibile or nearly visible items that don't have them. First, union the two lists. + BOOL delegateRespondsToSelectorWillDisplayItemView = [self.delegate respondsToSelector:@selector(documentPickerScrollView:willDisplayItemView:)]; for (ODSItem *item in visibleItemsWithoutView) { NSMutableArray *itemViews = nil; @@ -1067,13 +1075,16 @@ - (void)layoutSubviews; // Make the view start out at the "original" position instead of flying from where ever it was last left. [UIView performWithoutAnimation:^{ itemView.hidden = NO; - itemView.frame = [self _frameForItem:item layoutInfo:layoutInfo]; + CGRect frame = [self _frameForItem:item layoutInfo:layoutInfo]; + if (!CGRectEqualToRect(itemView.frame, frame)) { + itemView.frame = frame; + } itemView.shrunken = ([_itemsBeingAdded member:item] != nil); [itemView setEditing:_flags.isEditing animated:NO]; itemView.item = item; }]; - if ([self.delegate respondsToSelector:@selector(documentPickerScrollView:willDisplayItemView:)]) + if (delegateRespondsToSelectorWillDisplayItemView) [self.delegate documentPickerScrollView:self willDisplayItemView:itemView]; [itemViews removeLastObject]; @@ -1103,19 +1114,20 @@ - (void)layoutSubviews; else fileItemView.draggingState = OUIDocumentPickerItemViewNoneDraggingState; } - + // Any remaining unused item views should have no item and be hidden. + BOOL delegateRespondsToSelectorWillEndDisplayingItemView = [self.delegate respondsToSelector:@selector(documentPickerScrollView:willEndDisplayingItemView:)]; for (OUIDocumentPickerFileItemView *view in unusedFileItemViews) { view.hidden = YES; [view prepareForReuse]; - if ([self.delegate respondsToSelector:@selector(documentPickerScrollView:willEndDisplayingItemView:)]) + if (delegateRespondsToSelectorWillEndDisplayingItemView) [self.delegate documentPickerScrollView:self willEndDisplayingItemView:view]; } for (OUIDocumentPickerGroupItemView *view in unusedGroupItemViews) { view.hidden = YES; [view prepareForReuse]; - if ([self.delegate respondsToSelector:@selector(documentPickerScrollView:willEndDisplayingItemView:)]) + if (delegateRespondsToSelectorWillEndDisplayingItemView) [self.delegate documentPickerScrollView:self willEndDisplayingItemView:view]; } }); diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerViewController.h b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerViewController.h index 3c605be4..19f58f51 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerViewController.h +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerViewController.h @@ -69,6 +69,7 @@ - (void)scrollItemsToVisible:(id )items animated:(BOOL)animated completion:(void (^)(void))completion; - (IBAction)newDocument:(id)sender; +- (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem documentType:(ODSDocumentType)type preserveDocumentName:(BOOL)preserveDocumentName completion:(void (^)(void))completion; - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem documentType:(ODSDocumentType)type completion:(void (^)(void))completion; - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem; - (IBAction)duplicateDocument:(id)sender; diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerViewController.m b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerViewController.m index 2203d248..8462b812 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerViewController.m +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentPickerViewController.m @@ -465,7 +465,7 @@ - (IBAction)newDocument:(id)sender; } } -- (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem documentType:(ODSDocumentType)type completion:(void (^)(void))completion; +- (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem documentType:(ODSDocumentType)type preserveDocumentName:(BOOL)preserveDocumentName completion:(void (^)(void))completion; { OUIInteractionLock *lock = [OUIInteractionLock applicationLock]; @@ -480,8 +480,12 @@ - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem document activityIndicator = [OUIActivityIndicator showActivityIndicatorInView:view withColor:UIColor.whiteColor]; } + ODSScope *documentScope = preserveDocumentName ? templateFileItem.scope : _documentScope; + ODSStore *documentStore = preserveDocumentName ? templateFileItem.scope.documentStore : _documentStore; + ODSFolderItem *folderItem = preserveDocumentName ? templateFileItem.parentFolder : _folderItem; + // Instead of duplicating the template file item's URL (if we have one), we always read it into a OUIDocument and save it out, letting the document know that this is for the purposes of instantiating a new document. The OUIDocument may do extra work in this case that wouldn't get done if we just cloned the file (and this lets the work be done atomically by saving the new file to a temporary location before moving to a visible location). - NSURL *temporaryURL = [_documentStore temporaryURLForCreatingNewDocumentOfType:type]; + NSURL *temporaryURL = [documentStore temporaryURLForCreatingNewDocumentOfType:type]; completion = [completion copy]; void (^cleanup)(void) = ^{ @@ -498,29 +502,29 @@ - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem document cleanup(); return; } - + ODSFileItem *fileItemToRevealFrom; if (templateFileItem != nil && ![[templateFileItem scope] isExternal]) { fileItemToRevealFrom = templateFileItem; } else { fileItemToRevealFrom = createdFileItem; } - + // We want the file item to have a new date, but this is the wrong place to do it. Want to do it in the document picker before it creates the item. // [[NSFileManager defaultManager] touchItemAtURL:createdItem.fileURL error:NULL]; - + [self _revealAndActivateNewDocumentFileItem:createdFileItem fileItemToRevealFrom:fileItemToRevealFrom completionHandler:^{ cleanup(); }]; }]; } copy]; - + NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ Class cls = [[OUIDocumentAppController controller] documentClassForURL:temporaryURL]; - + // This reads the document immediately, which is why we dispatch to a background queue before calling it. We do file coordination on behalf of the document here since we don't get the benefit of UIDocument's efforts during our synchronous read. - + __block OUIDocument *document; __autoreleasing NSError *readError; @@ -538,12 +542,12 @@ - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem document } else { document = [[cls alloc] initWithContentsOfTemplateAtURL:nil toBeSavedToURL:temporaryURL error:&readError]; } - + if (!document) { finish(nil, readError); return; } - + [[NSOperationQueue mainQueue] addOperationWithBlock:^{ // Save the document to our temporary location [document saveToURL:temporaryURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL saveSuccess){ @@ -551,7 +555,7 @@ - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem document [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [document closeWithCompletionHandler:^(BOOL closeSuccess){ [document didClose]; - + if (!saveSuccess) { // The document instance should have gotten the real error presented some other way NSError *cancelledError = [NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]; @@ -559,7 +563,8 @@ - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem document return; } - [_documentStore moveNewTemporaryDocumentAtURL:temporaryURL toScope:_documentScope folder:_folderItem documentType:type completionHandler:^(ODSFileItem *createdFileItem, NSError *error){ + NSString *documentName = preserveDocumentName ? templateFileItem.name : nil; + [_documentStore moveNewTemporaryDocumentAtURL:temporaryURL toScope:documentScope folder:folderItem documentType:type documentName:documentName completionHandler:^(ODSFileItem *createdFileItem, NSError *error){ finish(createdFileItem, error); }]; }]; @@ -569,6 +574,11 @@ - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem document }]; } +- (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem documentType:(ODSDocumentType)type completion:(void (^)(void))completion; +{ + [self newDocumentWithTemplateFileItem:templateFileItem documentType:type preserveDocumentName:NO completion:completion]; +} + - (void)newDocumentWithTemplateFileItem:(ODSFileItem *)templateFileItem; { [self newDocumentWithTemplateFileItem:templateFileItem documentType:ODSDocumentTypeNormal completion:NULL]; diff --git a/Frameworks/OmniUI/iPad-Document/OUIDocumentTitleView.m b/Frameworks/OmniUI/iPad-Document/OUIDocumentTitleView.m index 764adc47..6522465a 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIDocumentTitleView.m +++ b/Frameworks/OmniUI/iPad-Document/OUIDocumentTitleView.m @@ -37,7 +37,9 @@ @implementation OUIDocumentTitleView static void _commonInit(OUIDocumentTitleView *self) { self.autoresizingMask = UIViewAutoresizingFlexibleHeight; - + + NSBundle *bundle = OMNI_BUNDLE; + self->_documentTitleLabel = [[UILabel alloc] init]; self->_documentTitleLabel.translatesAutoresizingMaskIntoConstraints = NO; self->_documentTitleLabel.textAlignment = NSTextAlignmentCenter; @@ -51,36 +53,36 @@ static void _commonInit(OUIDocumentTitleView *self) [self->_documentTitleButton setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; [self->_documentTitleButton addTarget:self action:@selector(_documentTitleButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; self->_documentTitleButton.hidden = YES; - self->_documentTitleButton.accessibilityHint = NSLocalizedStringFromTableInBundle(@"Edits the document's title.", @"OmniUIDocument", OMNI_BUNDLE, @"title view edit button item accessibility hint."); + self->_documentTitleButton.accessibilityHint = NSLocalizedStringFromTableInBundle(@"Edits the document's title.", @"OmniUIDocument", bundle, @"title view edit button item accessibility hint."); self->_titleColor = [UIColor blackColor]; [self _updateTitles]; - UIImage *syncImage = [UIImage imageNamed:@"OmniPresenceToolbarIcon" inBundle:OMNI_BUNDLE compatibleWithTraitCollection:nil]; + UIImage *syncImage = [UIImage imageNamed:@"OmniPresenceToolbarIcon" inBundle:bundle compatibleWithTraitCollection:nil]; self->_syncButton = [UIButton buttonWithType:UIButtonTypeSystem]; [self->_syncButton setImage:syncImage forState:UIControlStateNormal]; self->_syncButton.translatesAutoresizingMaskIntoConstraints = NO; [self->_syncButton setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; [self->_syncButton setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - self->_syncButton.accessibilityLabel = NSLocalizedStringFromTableInBundle(@"Sync Now", @"OmniUIDocument", OMNI_BUNDLE, @"Presence toolbar item accessibility label."); + self->_syncButton.accessibilityLabel = NSLocalizedStringFromTableInBundle(@"Sync Now", @"OmniUIDocument", bundle, @"Presence toolbar item accessibility label."); [self->_syncButton addTarget:self action:@selector(_syncButtonTapped:) forControlEvents:UIControlEventTouchUpInside]; self->_syncButton.hidden = YES; - UIImage *closeDocumentImage = [UIImage imageNamed:@"OUIToolbarDocumentClose" inBundle:OMNI_BUNDLE compatibleWithTraitCollection:nil]; + UIImage *closeDocumentImage = [UIImage imageNamed:@"OUIToolbarDocumentClose" inBundle:bundle compatibleWithTraitCollection:nil]; self->_closeDocumentButton = [UIButton buttonWithType:UIButtonTypeSystem]; [self->_closeDocumentButton setImage:closeDocumentImage forState:UIControlStateNormal]; self->_closeDocumentButton.translatesAutoresizingMaskIntoConstraints = NO; [self->_closeDocumentButton setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; [self->_closeDocumentButton setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; - self->_closeDocumentButton.accessibilityLabel = NSLocalizedStringFromTableInBundle(@"Close Document", @"OmniUIDocument", OMNI_BUNDLE, @"Close Document toolbar item accessibility label."); + self->_closeDocumentButton.accessibilityLabel = NSLocalizedStringFromTableInBundle(@"Close Document", @"OmniUIDocument", bundle, @"Close Document toolbar item accessibility label."); [self->_closeDocumentButton addTarget:self action:@selector(_closeDocument:) forControlEvents:UIControlEventTouchUpInside]; self->_closeDocumentButton.hidden = YES; - self.closeDocumentBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"OUIToolbarDocumentClose" inBundle:OMNI_BUNDLE compatibleWithTraitCollection:nil] + self.closeDocumentBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"OUIToolbarDocumentClose" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(_closeDocument:)]; self.closeDocumentBarButtonItem.accessibilityIdentifier = @"BackToDocuments"; - self.syncBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"OmniPresenceToolbarIcon" inBundle:OMNI_BUNDLE compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(_syncButtonTapped:)]; + self.syncBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"OmniPresenceToolbarIcon" inBundle:bundle compatibleWithTraitCollection:nil] style:UIBarButtonItemStylePlain target:self action:@selector(_syncButtonTapped:)]; self->_hideTitle = YES; diff --git a/Frameworks/OmniUI/iPad-Document/OUIExportOptionsCollectionViewLayout.h b/Frameworks/OmniUI/iPad-Document/OUIExportOptionsCollectionViewLayout.h index 6c5c310f..5112c2d0 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIExportOptionsCollectionViewLayout.h +++ b/Frameworks/OmniUI/iPad-Document/OUIExportOptionsCollectionViewLayout.h @@ -1,4 +1,4 @@ -// Copyright 2010-2015 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be diff --git a/Frameworks/OmniUI/iPad-Document/OUIExportOptionsCollectionViewLayout.m b/Frameworks/OmniUI/iPad-Document/OUIExportOptionsCollectionViewLayout.m index ac9684ad..c65291e1 100644 --- a/Frameworks/OmniUI/iPad-Document/OUIExportOptionsCollectionViewLayout.m +++ b/Frameworks/OmniUI/iPad-Document/OUIExportOptionsCollectionViewLayout.m @@ -1,4 +1,4 @@ -// Copyright 2010-2015 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be diff --git a/Frameworks/OmniUI/iPad/Inspectors/OUIInspectorSlice.m b/Frameworks/OmniUI/iPad/Inspectors/OUIInspectorSlice.m index a400eb89..94e1ddb8 100644 --- a/Frameworks/OmniUI/iPad/Inspectors/OUIInspectorSlice.m +++ b/Frameworks/OmniUI/iPad/Inspectors/OUIInspectorSlice.m @@ -174,10 +174,13 @@ - (UIView *)contentView { _contentView.translatesAutoresizingMaskIntoConstraints = NO; [self.view addSubview:_contentView]; - [_contentView.topAnchor constraintEqualToAnchor:self.view.topAnchor]; - [_contentView.rightAnchor constraintEqualToAnchor:self.view.rightAnchor]; - [_contentView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor]; - [_contentView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor]; + // These constraints were created but never activated. Many inspector slices (if not all) are adding their parts to self.view instead of self.contentView, and they wind up occluded by contentView and unable to receive touches. Let's see if we can get by without this view, but we may need to do some cleanup later (remove this view, or fix all the slices to use it properly). +// NSArray *constraints = @[ +// [_contentView.topAnchor constraintEqualToAnchor:self.view.topAnchor], +// [_contentView.rightAnchor constraintEqualToAnchor:self.view.rightAnchor], +// [_contentView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor], +// [_contentView.leftAnchor constraintEqualToAnchor:self.view.leftAnchor]]; +// [NSLayoutConstraint activateConstraints:constraints]; } return _contentView; @@ -204,7 +207,7 @@ - (void)setGroupPosition:(OUIInspectorSliceGroupPosition)newValue; _groupPosition = newValue; if (self.isViewLoaded) { - UIView *view = self.contentView; + UIView *view = self.view; if ([view respondsToSelector:@selector(setInspectorSliceGroupPosition:)]) { [(id)view setInspectorSliceGroupPosition:_groupPosition]; } diff --git a/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.h b/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.h index e7f45949..8fbc91a5 100644 --- a/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.h +++ b/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.h @@ -1,4 +1,4 @@ -// Copyright 2011-2012, 2014 Omni Development, Inc. All rights reserved. +// Copyright 2011-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be @@ -16,6 +16,7 @@ @property (readwrite, retain) IBOutlet OUINoteTextView *textView; @property (nonatomic, retain) IBOutlet UIButton *enterFullScreenButton; +@property (nonatomic, retain) IBOutlet UIImageView *enterFullScreenButtonBackground; - (IBAction)enterFullScreen:(id)sender; diff --git a/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.m b/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.m index 43ecd900..00c94c2e 100644 --- a/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.m +++ b/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.m @@ -36,6 +36,7 @@ - (void)viewDidLoad; [super viewDidLoad]; self.enterFullScreenButton.alpha = 0.0; + self.enterFullScreenButtonBackground.alpha = 0.0; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidChange:) name:UIKeyboardDidChangeFrameNotification object:nil]; } @@ -46,14 +47,16 @@ - (void)viewDidAppear:(BOOL)animated; // We can't ask for the presentationController before first knowing it's presented or the reciever will cacahe a presentationController until next present/dismiss cycle. This can result in the default full-screen presentation controller being cached if we havne't setup the transitioningDelegate yet. BOOL isCurrentlyPresented = (self.inspector.viewController.presentingViewController != nil); - BOOL isUsingInspectorPresentationController = (isCurrentlyPresented && [self.inspector.viewController.presentationController isKindOfClass:[OUIInspectorPresentationController class]]); // If we're already fullscreen, no need for the enter full screen button - if (self.view.window.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact && isUsingInspectorPresentationController) { + if (self.presentingViewController.traitCollection.horizontalSizeClass == UIUserInterfaceSizeClassCompact && isCurrentlyPresented) { self.enterFullScreenButton.hidden = YES; + self.enterFullScreenButtonBackground.hidden = YES; } else { self.enterFullScreenButton.hidden = NO; + self.enterFullScreenButtonBackground.hidden = NO; self.enterFullScreenButton.alpha = EnterFullScreenButtonStandardAlpha; // fade in + self.enterFullScreenButtonBackground.alpha = EnterFullScreenButtonStandardAlpha; } // Larger left/right insets @@ -168,7 +171,8 @@ - (void)setEnterFullScreenButtonAlpha:(CGFloat)alpha animated:(BOOL)animated; } self.enterFullScreenButton.alpha = alpha; - + self.enterFullScreenButtonBackground.alpha = alpha; + if (animated) { [UIView commitAnimations]; } diff --git a/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.xib b/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.xib index 751cbff3..260fc80d 100644 --- a/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.xib +++ b/Frameworks/OmniUI/iPad/Inspectors/OUINoteInspectorPane.xib @@ -1,13 +1,18 @@ - - + + + + + - + + + @@ -20,7 +25,7 @@ - + @@ -33,11 +38,11 @@ - - + + - + @@ -48,7 +53,7 @@ - + diff --git a/Frameworks/OmniUI/iPad/Inspectors/OUIPaletteColorPicker.m b/Frameworks/OmniUI/iPad/Inspectors/OUIPaletteColorPicker.m index d5ccd362..4a9cc4e9 100644 --- a/Frameworks/OmniUI/iPad/Inspectors/OUIPaletteColorPicker.m +++ b/Frameworks/OmniUI/iPad/Inspectors/OUIPaletteColorPicker.m @@ -149,7 +149,7 @@ - (void)_rebuildThemesViews; CGRect viewBounds = view.bounds; viewBounds.size.width = 320; - CGFloat xOffset = view.safeAreaInsets.left; + CGFloat xOffset = view.safeAreaInsets.left + kInterThemeSpacing; // This should be view.safeAreaInsets.top + kInterThemeSpacing, but when I get here bounds.origin.y is negative view.safeAreaInsets.top, and setting y to that value doubles the required top margin CGFloat yOffset = kInterThemeSpacing; NSMutableArray *themeViews = [NSMutableArray array]; diff --git a/Frameworks/OmniUI/iPad/MultiPane/MultiPaneController.swift b/Frameworks/OmniUI/iPad/MultiPane/MultiPaneController.swift index a6b78b5a..5577d657 100644 --- a/Frameworks/OmniUI/iPad/MultiPane/MultiPaneController.swift +++ b/Frameworks/OmniUI/iPad/MultiPane/MultiPaneController.swift @@ -36,6 +36,9 @@ /// called before showing a pane if the pane is .embeded and the displayMode is .multi. Called before willShowPane, as a false return value can preempt showing the pane. @objc optional func shouldShowPane(at location: MultiPaneLocation, multiPaneController: MultiPaneController) -> Bool + + /// called after updating button items. + @objc optional func didUpdateDisplayButtonItems(_ multiPaneController: MultiPaneController) } @objc (OUIMultiPaneNavigationDelegate) public protocol MultiPaneNavigationDelegate { @@ -658,6 +661,7 @@ extension MultiPaneDisplayMode: CustomStringConvertible { leftPaneDisplayButton.image = image leftPaneDisplayButton.title = nil } + layoutDelegate?.didUpdateDisplayButtonItems?(self) } private func setupDividerIfNeeded(onPane pane: Pane) { diff --git a/Frameworks/OmniUI/iPad/OUIFullScreenNoteTextViewController.m b/Frameworks/OmniUI/iPad/OUIFullScreenNoteTextViewController.m index 4de88bf7..4f4f847e 100644 --- a/Frameworks/OmniUI/iPad/OUIFullScreenNoteTextViewController.m +++ b/Frameworks/OmniUI/iPad/OUIFullScreenNoteTextViewController.m @@ -1,4 +1,4 @@ -// Copyright 2014-2016 Omni Development, Inc. All rights reserved. +// Copyright 2014-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be diff --git a/Frameworks/OmniUI/iPad/OUIKeyboardNotifier.m b/Frameworks/OmniUI/iPad/OUIKeyboardNotifier.m index 7197ebed..f6068cd0 100644 --- a/Frameworks/OmniUI/iPad/OUIKeyboardNotifier.m +++ b/Frameworks/OmniUI/iPad/OUIKeyboardNotifier.m @@ -310,7 +310,7 @@ static void _updateAccessoryToolbarViewFrame(OUIKeyboardNotifier *self, NSDictio NSNumber *durationNumber = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey]; NSNumber *curveNumber = [userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey]; - if (!durationNumber || !curveNumber) { + if (durationNumber == nil || curveNumber == nil) { OBASSERT((durationNumber == nil) == (curveNumber == nil)); durationNumber = [NSNumber numberWithDouble:0.25]; curveNumber = [NSNumber numberWithInt:UIViewAnimationCurveEaseInOut]; diff --git a/Frameworks/OmniUI/iPad/OUINavigationController.m b/Frameworks/OmniUI/iPad/OUINavigationController.m index 8539caa7..f16a89b7 100644 --- a/Frameworks/OmniUI/iPad/OUINavigationController.m +++ b/Frameworks/OmniUI/iPad/OUINavigationController.m @@ -65,7 +65,9 @@ - (void)_constrainAccessoryAndBackgroundView - (void)_constrainNewAccessoryView:(UIView *)newAccessory { - [newAccessory.centerXAnchor constraintEqualToAnchor:self.accessoryAndBackgroundBar.centerXAnchor].active = YES; + [newAccessory.trailingAnchor constraintEqualToAnchor:self.accessoryAndBackgroundBar.safeAreaLayoutGuide.trailingAnchor].active = YES; + [newAccessory.leadingAnchor constraintEqualToAnchor:self.accessoryAndBackgroundBar.safeAreaLayoutGuide.leadingAnchor].active = YES; + NSLayoutConstraint *topConstraint = [newAccessory.topAnchor constraintEqualToAnchor:self.accessoryAndBackgroundBar.topAnchor constant:BOTTOM_SPACING_BELOW_ACCESSORY]; NSLayoutConstraint *bottomConstraint = [newAccessory.bottomAnchor constraintEqualToAnchor:self.accessoryAndBackgroundBar.bottomAnchor constant:-BOTTOM_SPACING_BELOW_ACCESSORY]; [NSLayoutConstraint deactivateConstraints:self.topAndBottomConstraints]; diff --git a/Frameworks/OmniUI/iPad/OUIUndoBarButtonItem.h b/Frameworks/OmniUI/iPad/OUIUndoBarButtonItem.h index 83d08fd5..8cc2e78b 100644 --- a/Frameworks/OmniUI/iPad/OUIUndoBarButtonItem.h +++ b/Frameworks/OmniUI/iPad/OUIUndoBarButtonItem.h @@ -1,4 +1,4 @@ -// Copyright 2010-2016 Omni Development, Inc. All rights reserved. +// Copyright 2010-2017 Omni Development, Inc. All rights reserved. // // This software may only be used and reproduced according to the // terms in the file OmniSourceLicense.html, which should be diff --git a/Frameworks/OmniUI/iPad/UITableView-OUIExtensions.m b/Frameworks/OmniUI/iPad/UITableView-OUIExtensions.m index 80c18967..6213579e 100644 --- a/Frameworks/OmniUI/iPad/UITableView-OUIExtensions.m +++ b/Frameworks/OmniUI/iPad/UITableView-OUIExtensions.m @@ -127,6 +127,7 @@ void OUITableViewAdjustHeightToFitContents(UITableView *tableView) OBPRECONDITION(tableView); OBPRECONDITION(tableView.autoresizingMask == 0); + [tableView layoutIfNeeded]; CGSize contentSize = tableView.contentSize; UIEdgeInsets contentInsets = tableView.contentInset; diff --git a/version b/version index b388b99c..95c9a09f 100644 --- a/version +++ b/version @@ -1 +1 @@ -299743 +300398