diff --git a/src/Catty/DataModel/Project/Project.h b/src/Catty/DataModel/Project/Project.h index 2e372b32d..1719d6ed0 100644 --- a/src/Catty/DataModel/Project/Project.h +++ b/src/Catty/DataModel/Project/Project.h @@ -73,7 +73,7 @@ + (BOOL)isLastUsedProject:(NSString* _Nonnull)projectName projectID:(NSString* _Nonnull)projectID; + (void)setLastUsedProject:(Project* _Nonnull)project; + (NSString* _Nonnull)basePath; -+ (NSArray* _Nonnull)allProjectNames; ++ (NSArray* _Nonnull)allProjectNames; + (NSArray* _Nonnull)allProjectLoadingInfos; + (NSString* _Nonnull)projectDirectoryNameForProjectName:(NSString* _Nonnull)projectName projectID:(NSString* _Nullable)projectID; diff --git a/src/Catty/DataModel/Project/Project.m b/src/Catty/DataModel/Project/Project.m index 5b964b742..d7e222f2a 100644 --- a/src/Catty/DataModel/Project/Project.m +++ b/src/Catty/DataModel/Project/Project.m @@ -420,7 +420,7 @@ + (NSString*)basePath } -+ (NSArray*)allProjectNames ++ (NSArray*)allProjectNames { NSArray *allProjectLoadingInfos = [[self class] allProjectLoadingInfos]; NSMutableArray *projectNames = [[NSMutableArray alloc] initWithCapacity:[allProjectLoadingInfos count]]; diff --git a/src/Catty/Helpers/ImageCache/RuntimeImageCache.m b/src/Catty/Helpers/ImageCache/RuntimeImageCache.m index fdffba229..743eef724 100644 --- a/src/Catty/Helpers/ImageCache/RuntimeImageCache.m +++ b/src/Catty/Helpers/ImageCache/RuntimeImageCache.m @@ -88,6 +88,12 @@ - (void)loadImageFromDiskWithPath:(NSString*)imagePath { dispatch_async(self.imageCacheQueue, ^{ UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; + if (image == nil) { + dispatch_sync(dispatch_get_main_queue(), ^{ + completion(nil, imagePath); + }); + return; + } CGSize imageSize = CGSizeMake(size.width, size.height); if (image.size.height > image.size.width) diff --git a/src/Catty/Helpers/Util/Util.h b/src/Catty/Helpers/Util/Util.h index 5252af0db..94badaa7d 100644 --- a/src/Catty/Helpers/Util/Util.h +++ b/src/Catty/Helpers/Util/Util.h @@ -111,8 +111,6 @@ if (__functor) __functor(__VA_ARGS__); \ + (NSString *_Nullable)normalizedDescriptionWithFormat:(NSString *_Nonnull)descriptionFormat formatParameter:(NSUInteger)formatParameter; -+ (NSString* _Nullable)uniqueName:(NSString* _Nullable)nameToCheck existingNames:(NSArray* _Nullable)existingNames; - + (CGFloat)detectCBLanguageVersionFromXMLWithPath:(NSString* _Nullable)xmlPath; + (double)radiansToDegree:(double)rad; diff --git a/src/Catty/Helpers/Util/Util.m b/src/Catty/Helpers/Util/Util.m index 6bcc6da94..2ea5bddc3 100644 --- a/src/Catty/Helpers/Util/Util.m +++ b/src/Catty/Helpers/Util/Util.m @@ -272,67 +272,6 @@ + (void)askUserForVariableNameAndPerformAction:(SEL)action showWithController:[Util topmostViewController]]; } -+ (NSString*)uniqueName:(NSString*)nameToCheck existingNames:(NSArray*)existingNames -{ - NSMutableString *uniqueName = [nameToCheck mutableCopy]; - unichar lastChar = [uniqueName characterAtIndex:([uniqueName length] - 1)]; - if (lastChar == 0x20) { - [uniqueName deleteCharactersInRange:NSMakeRange(([uniqueName length] - 1), 1)]; - } - - NSUInteger counter = 0; - NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"\\(\\d\\)" - options:NSRegularExpressionCaseInsensitive - error:NULL]; - NSArray *results = [regex matchesInString:uniqueName - options:0 - range:NSMakeRange(0, [uniqueName length])]; - if ([results count]) { - BOOL duplicate = NO; - for (NSString *existingName in existingNames) { - if ([existingName isEqualToString:uniqueName]) { - duplicate = YES; - break; - } - } - if (! duplicate) { - return [uniqueName copy]; - } - NSTextCheckingResult *lastOccurenceResult = [results lastObject]; - NSMutableString *lastOccurence = [(NSString*)[uniqueName substringWithRange:lastOccurenceResult.range] mutableCopy]; - [uniqueName replaceOccurrencesOfString:lastOccurence - withString:@"" - options:NSCaseInsensitiveSearch - range:NSMakeRange(0, [uniqueName length])]; - unichar lastChar = [uniqueName characterAtIndex:([uniqueName length] - 1)]; - if (lastChar == 0x20) { - [uniqueName deleteCharactersInRange:NSMakeRange(([uniqueName length] - 1), 1)]; - } - [lastOccurence replaceOccurrencesOfString:@"(" - withString:@"" - options:NSCaseInsensitiveSearch - range:NSMakeRange(0, [lastOccurence length])]; - [lastOccurence replaceOccurrencesOfString:@")" - withString:@"" - options:NSCaseInsensitiveSearch - range:NSMakeRange(0, [lastOccurence length])]; - counter = [lastOccurence integerValue]; - } - NSString *uniqueFinalName = [uniqueName copy]; - BOOL duplicate; - do { - duplicate = NO; - for (NSString *existingName in existingNames) { - if ([existingName isEqualToString:uniqueFinalName]) { - uniqueFinalName = [NSString stringWithFormat:@"%@ (%lu)", uniqueName, (unsigned long)++counter]; - duplicate = YES; - break; - } - } - } while (duplicate); - return uniqueFinalName; -} - + (CGFloat)detectCBLanguageVersionFromXMLWithPath:(NSString*)xmlPath { NSError *error; diff --git a/src/Catty/Helpers/Util/Util.swift b/src/Catty/Helpers/Util/Util.swift index 3c5893452..1264420d8 100644 --- a/src/Catty/Helpers/Util/Util.swift +++ b/src/Catty/Helpers/Util/Util.swift @@ -286,4 +286,29 @@ func synchronized(lock: AnyObject, closure: () -> Void) { } return nil } + + static func uniqueName(_ nameToCheck: String, existingNames: [String]) -> String { + var baseName = nameToCheck.trimmingCharacters(in: .whitespaces) + + if !existingNames.contains(baseName) { + return baseName + } + + var counter = 0 + let regex = try? NSRegularExpression(pattern: "\\((\\d+)\\)$", options: []) + if let match = regex?.firstMatch(in: baseName, options: [], range: NSRange(location: 0, length: baseName.count)) { + let numberStr = (baseName as NSString).substring(with: match.range(at: 1)) + counter = Int(numberStr) ?? 0 + baseName = (baseName as NSString).substring(to: match.range.location) + baseName = baseName.trimmingCharacters(in: .whitespaces) + } + + var uniqueName: String + repeat { + counter += 1 + uniqueName = "\(baseName) (\(counter))" + } while existingNames.contains(uniqueName) + + return uniqueName + } } diff --git a/src/Catty/IO/ProjectManager.swift b/src/Catty/IO/ProjectManager.swift index f8d6d8e9b..6ea9dc446 100644 --- a/src/Catty/IO/ProjectManager.swift +++ b/src/Catty/IO/ProjectManager.swift @@ -154,18 +154,18 @@ Util.alert(text: kLocalizedUnableToImportProject) return nil } - projectLoadingInfo.useOriginalName = true - guard let projectObject = Project(loadingInfo: projectLoadingInfo), - let newProjectName = Util.uniqueName(projectObject.header.programName, existingNames: Project.allProjectNames()) else { + projectLoadingInfo.useOriginalName = true + guard let projectObject = Project(loadingInfo: projectLoadingInfo) else { Project.removeProjectFromDisk(withProjectName: tempProjectName, projectID: kNoProjectIDYetPlaceholder) Util.alert(text: kLocalizedUnableToImportProject) return nil } + let newProjectName = Util.uniqueName(projectObject.header.programName, existingNames: Project.allProjectNames()) projectLoadingInfo.useOriginalName = false - let project = Project(loadingInfo: projectLoadingInfo) + project?.rename(toProjectName: newProjectName, andShowSaveNotification: false) return project diff --git a/src/Catty/ViewController/Continue&New/MaintainObject/MaintainLooks/LooksTableViewController.m b/src/Catty/ViewController/Continue&New/MaintainObject/MaintainLooks/LooksTableViewController.m index 131dd0771..a43b82178 100644 --- a/src/Catty/ViewController/Continue&New/MaintainObject/MaintainLooks/LooksTableViewController.m +++ b/src/Catty/ViewController/Continue&New/MaintainObject/MaintainLooks/LooksTableViewController.m @@ -454,6 +454,9 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath* self.selectedLookIndex = indexPath.row; NSString *lookImagePath = [[self.object.lookList objectAtIndex:self.selectedLookIndex] pathForScene: self.object.scene]; UIImage *image = [[UIImage alloc] initWithContentsOfFile:lookImagePath]; + if (image == nil) { + return; + } vc.editingImage = image; vc.editingPath = lookImagePath; NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait]; diff --git a/src/CattyTests/FileManager/ProjectManagerTests.swift b/src/CattyTests/FileManager/ProjectManagerTests.swift index 3549fad1b..2244d3d58 100644 --- a/src/CattyTests/FileManager/ProjectManagerTests.swift +++ b/src/CattyTests/FileManager/ProjectManagerTests.swift @@ -328,7 +328,7 @@ final class ProjectManagerTests: XCTestCase { XCTAssertEqual(sumProjectNamesBefore + 1, sumProjectNamesAfter) - XCTAssertTrue((Project.allProjectNames() as! [String]).contains("Tic-Tac-Toe Master")) + XCTAssertTrue((Project.allProjectNames()).contains("Tic-Tac-Toe Master")) } func testAddProjectFromFileWithInvalidUrl() { diff --git a/src/CattyTests/ViewController/Stage/StagePresenterSideMenuViewTests.swift b/src/CattyTests/ViewController/Stage/StagePresenterSideMenuViewTests.swift index 04a152dc0..c2215b1d2 100644 --- a/src/CattyTests/ViewController/Stage/StagePresenterSideMenuViewTests.swift +++ b/src/CattyTests/ViewController/Stage/StagePresenterSideMenuViewTests.swift @@ -29,6 +29,7 @@ final class StagePresenterSideMenuViewTests: XCTestCase { var view: StagePresenterSideMenuView! var project: Project! + var frame = CGRect(x: 0, y: 0, width: 1, height: 1) var delegateMock: StagePresenterSideMenuDelegate! override func setUp() { @@ -45,21 +46,21 @@ final class StagePresenterSideMenuViewTests: XCTestCase { func testPortrait() { project.header.landscapeMode = false - view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertFalse(view.landscape) } func testLandscape() { project.header.landscapeMode = true - view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertTrue(view.landscape) } func testAspectRatioMinimize() { project.header.screenMode = kCatrobatHeaderScreenModeMaximize - let view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + let view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertNotNil(view.aspectRatioButton) XCTAssertNotNil(view.aspectRatioLabel) @@ -71,7 +72,7 @@ final class StagePresenterSideMenuViewTests: XCTestCase { func testAspectRatioMaximize() { project.header.screenMode = kCatrobatHeaderScreenModeStretch - let view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + let view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertNotNil(view.aspectRatioButton) XCTAssertNotNil(view.aspectRatioLabel) @@ -85,7 +86,7 @@ final class StagePresenterSideMenuViewTests: XCTestCase { project.header.screenWidth = NSNumber(value: Util.screenWidth(true)) project.header.screenHeight = NSNumber(value: Util.screenHeight(true)) - let view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + let view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertTrue(view.aspectRatioButton!.isHidden) XCTAssertTrue(view.aspectRatioLabel!.isHidden) @@ -96,7 +97,7 @@ final class StagePresenterSideMenuViewTests: XCTestCase { project.header.screenHeight = NSNumber(value: Util.screenHeight(true)) project.header.landscapeMode = true - let view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + let view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertFalse(view.aspectRatioButton!.isHidden) XCTAssertTrue(view.aspectRatioLabel == nil) @@ -107,14 +108,14 @@ final class StagePresenterSideMenuViewTests: XCTestCase { project.header.screenHeight = NSNumber(value: Util.screenWidth(true)) project.header.landscapeMode = true - let view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + let view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertTrue(view.aspectRatioButton!.isHidden) XCTAssertTrue(view.aspectRatioLabel == nil) } func testRestart() { - let view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + let view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertFalse(view.landscape) project.header.landscapeMode = true @@ -127,7 +128,7 @@ final class StagePresenterSideMenuViewTests: XCTestCase { project.header.screenWidth = NSNumber(value: Util.screenWidth(true)) project.header.screenHeight = 10 - let view = StagePresenterSideMenuView(frame: .zero, delegate: delegateMock) + let view = StagePresenterSideMenuView(frame: frame, delegate: delegateMock) XCTAssertFalse(view.aspectRatioButton!.isHidden) XCTAssertFalse(view.aspectRatioLabel!.isHidden)