3 // testScreenshotForKCD
5 // Created by Hori,Masaki on 2016/03/27.
6 // Copyright © 2016年 Hori,Masaki. All rights reserved.
9 #import "HMScreenshotListViewController.h"
11 #import "HMScreenshotModel.h"
12 #import "HMScreenshotInformation.h"
14 #import "HMAppDelegate.h"
15 #import "HMUserDefaults.h"
17 #import <Quartz/Quartz.h>
19 @interface NSFileManager (KCDExtension)
20 - (NSString *)_web_pathWithUniqueFilenameForPath:(NSString *)path;
23 @interface HMCacheVersionInfo : NSObject <NSCopying>
24 @property (strong) NSString *fullpath;
25 @property (strong) NSNumber *version;
28 @implementation HMCacheVersionInfo
30 - (instancetype)copyWithZone:(NSZone *)zone
32 HMCacheVersionInfo *result = [[self class] new];
33 result.fullpath = self.fullpath;
34 result.version = self.version;
39 return [self.fullpath hash];
41 - (BOOL)isEqual:(id)object
43 if([super isEqual:object]) return YES;
44 if(![object isMemberOfClass:[self class]]) return NO;
45 return [self.fullpath isEqualToString:[object fullpath]];
50 @interface HMScreenshotListViewController () <NSSplitViewDelegate>
52 @property (weak, nonatomic) IBOutlet NSArrayController *screenshotsController;
53 @property (strong) NSMutableArray *deletedPaths;
55 @property (weak, nonatomic) IBOutlet IKImageBrowserView *browser;
56 @property (strong, nonatomic) IBOutlet NSMenu *contextMenu;
57 @property (weak, nonatomic) IBOutlet NSButton *shareButton;
60 @property (weak, nonatomic) IBOutlet NSView *standardView;
61 @property (weak, nonatomic) IBOutlet NSView *editorView;
67 @implementation HMScreenshotListViewController
69 - (nullable instancetype)initWithCoder:(NSCoder *)coder
71 self = [super initWithCoder:coder];
73 _screenshots = [HMScreenshotModel new];
74 _screenshots.screenshots = [self loadCache];
75 _deletedPaths = [NSMutableArray new];
83 [self.browser setCanControlQuickLookPanel:YES];
85 NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO];
86 self.screenshots.sortDescriptors = @[sortDescriptor];
87 self.screenshots.selectedIndexes = [NSIndexSet indexSetWithIndex:0];
89 [self performSelector:@selector(reloadData:)
94 - (NSString *)screenshotSaveDirectoryPath
96 HMAppDelegate *appDelegate =[[NSApplication sharedApplication] delegate];
97 NSString *parentDirctory = appDelegate.screenShotSaveDirectory;
98 NSBundle *mainBundle = [NSBundle mainBundle];
99 NSDictionary *localizedInfoDictionary = [mainBundle localizedInfoDictionary];
100 NSString *saveDirectoryName = localizedInfoDictionary[@"CFBundleName"];
101 NSString *path = [parentDirctory stringByAppendingPathComponent:saveDirectoryName];
103 NSFileManager *fm = [NSFileManager defaultManager];
105 NSError *error = nil;
106 if(![fm fileExistsAtPath:path isDirectory:&isDir]) {
107 BOOL ok = [fm createDirectoryAtPath:path
108 withIntermediateDirectories:NO
112 NSLog(@"Can not create screenshot save directory.");
113 return parentDirctory;
116 NSLog(@"%@ is regular file, not direcory.", path);
117 return parentDirctory;
123 - (void)registerScreenshot:(NSBitmapImageRep *)image fromOnScreen:(NSRect)screenRect
125 dispatch_queue_t queue = dispatch_queue_create("Screenshot queue", DISPATCH_QUEUE_SERIAL);
126 dispatch_async(queue, ^{
127 NSData *imageData = [image representationUsingType:NSJPEGFileType properties:@{}];
129 NSBundle *mainBundle = [NSBundle mainBundle];
130 NSDictionary *infoList = [mainBundle localizedInfoDictionary];
131 NSString *filename = [infoList objectForKey:@"CFBundleName"];
132 if([filename length] == 0) {
135 filename = [filename stringByAppendingPathExtension:@"jpg"];
136 NSString *path = [[self screenshotSaveDirectoryPath] stringByAppendingPathComponent:filename];
137 path = [[NSFileManager defaultManager] _web_pathWithUniqueFilenameForPath:path];
138 [imageData writeToFile:path atomically:NO];
140 dispatch_async(dispatch_get_main_queue(), ^{
141 HMScreenshotInformation *info = [HMScreenshotInformation new];
143 info.version = [self cacheVersionForPath:path];
145 [self.screenshotsController insertObject:info atArrangedObjectIndex:0];
146 self.screenshotsController.selectedObjects = @[info];
148 if(HMStandardDefaults.showsListWindowAtScreenshot) {
149 [self.view.window makeKeyAndOrderFront:nil];
158 NSMutableArray *screenshotNames = [NSMutableArray new];
159 NSMutableArray *currentArray = [self.screenshots.screenshots mutableCopy];
160 NSFileManager *fm = [NSFileManager defaultManager];
162 NSError *error = nil;
163 NSArray *files = [fm contentsOfDirectoryAtPath:self.screenshotSaveDirectoryPath error:&error];
165 NSLog(@"%s: error -> %@", __PRETTY_FUNCTION__, error);
168 NSArray *imageTypes = [NSImage imageTypes];
169 NSWorkspace *ws = [NSWorkspace sharedWorkspace];
170 for(NSString *file in files) {
171 NSString *fullpath = [self.screenshotSaveDirectoryPath stringByAppendingPathComponent:file];
172 NSString *fileType = [ws typeOfFile:fullpath error:NULL];
173 if([imageTypes containsObject:fileType]) {
174 [screenshotNames addObject:fullpath];
179 NSMutableArray *deleteObjects = [NSMutableArray new];
180 for(HMScreenshotInformation *info in self.screenshots.screenshots) {
181 if(![screenshotNames containsObject:info.path]) {
182 [self incrementCacheVersionForPath:info.path];
183 [deleteObjects addObject:info];
186 [currentArray removeObjectsInArray:deleteObjects];
189 for(NSString *path in screenshotNames) {
190 NSUInteger index = [self.screenshots.screenshots indexOfObjectPassingTest:^(HMScreenshotInformation *obj, NSUInteger idx, BOOL *stop) {
191 if([path isEqualToString:obj.path]) {
197 if(index == NSNotFound) {
198 HMScreenshotInformation *info = [HMScreenshotInformation new];
200 [currentArray addObject:info];
203 self.screenshots.screenshots = [currentArray copy];
204 self.screenshots.selectedIndexes = [NSIndexSet indexSetWithIndex:0];
211 NSString *path = [self screenshotSaveDirectoryPath];
212 path = [path stringByAppendingPathComponent:@"Cache.db"];
213 NSURL *url = [NSURL fileURLWithPath:path];
215 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.screenshots.screenshots];
216 NSError *error = nil;
217 BOOL ok = [data writeToURL:url
218 options:NSDataWritingAtomic
222 [[NSApplication sharedApplication] presentError:error];
224 NSLog(@"Can not write error. %@", error);
228 - (NSArray *)loadCache
230 NSString *path = [self screenshotSaveDirectoryPath];
231 path = [path stringByAppendingPathComponent:@"Cache.db"];
232 NSURL *url = [NSURL fileURLWithPath:path];
234 NSData *data = [NSData dataWithContentsOfURL:url];
235 if(!data) return [NSArray new];
237 id loaded = [NSKeyedUnarchiver unarchiveObjectWithData:data];
238 if(![loaded isKindOfClass:[NSArray class]]) {
239 return [NSArray new];
245 - (IBAction)reloadData:(id)sender
250 - (IBAction)delete:(id)sender
252 NSArray<HMScreenshotInformation *> *informations = [self.screenshotsController.selectedObjects copy];
253 NSMutableArray<NSString *> *paths = [NSMutableArray array];
254 for(HMScreenshotInformation *info in informations) {
255 [paths addObject:info.path];
257 NSMutableArray<NSString *> *opsixPathes = [NSMutableArray array];
258 for(NSString *path in paths) {
259 [opsixPathes addObject:[NSString stringWithFormat:@"(\"%@\" as POSIX file)", path]];
261 NSString *pathListString = [opsixPathes componentsJoinedByString:@" , "];
262 pathListString = [@"{ " stringByAppendingString:pathListString];
263 pathListString = [pathListString stringByAppendingString:@" }"];
265 NSString *scriptTmplate =
266 @"tell application \"Finder\"\n"
269 NSString *script = [NSString stringWithFormat:scriptTmplate, pathListString];
270 NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
271 if(!appleScript) NSBeep();
272 [appleScript executeAndReturnError:nil];
274 NSIndexSet *selectionIndexes = self.screenshotsController.selectionIndexes;
275 [self.screenshotsController removeObjectsAtArrangedObjectIndexes:selectionIndexes];
276 for(NSString *path in paths) {
277 [self incrementCacheVersionForPath:path];
281 NSInteger selectionIndex = selectionIndexes.firstIndex;
282 NSUInteger count = [self.screenshotsController.arrangedObjects count];
283 if(count == 0) return;
284 if(count <= selectionIndex) {
285 selectionIndex = count - 1;
287 self.screenshotsController.selectionIndex = selectionIndex;
289 - (IBAction)revealInFinder:(id)sender
291 NSArray<HMScreenshotInformation *> *informations = [self.screenshotsController.selectedObjects copy];
292 NSMutableArray<NSString *> *paths = [NSMutableArray array];
293 for(HMScreenshotInformation *info in informations) {
294 [paths addObject:info.path];
296 NSMutableArray<NSURL *> *pathURLs = [NSMutableArray array];
297 for(NSString *path in paths) {
298 [pathURLs addObject:[NSURL fileURLWithPath:path]];
301 NSWorkspace *ws = [NSWorkspace sharedWorkspace];
302 [ws activateFileViewerSelectingURLs:pathURLs];
305 - (IBAction)registerImage:(id)sender
307 if(![sender respondsToSelector:@selector(image)]) return;
309 NSImage *image = [sender image];
311 dispatch_queue_t queue = dispatch_queue_create("Screenshot queue", DISPATCH_QUEUE_SERIAL);
312 dispatch_async(queue, ^{
313 NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:image.TIFFRepresentation];
314 NSData *imageData = [imageRep representationUsingType:NSJPEGFileType properties:@{}];
316 NSBundle *mainBundle = [NSBundle mainBundle];
317 NSDictionary *infoList = [mainBundle localizedInfoDictionary];
318 NSString *filename = [infoList objectForKey:@"CFBundleName"];
319 if([filename length] == 0) {
322 filename = [filename stringByAppendingPathExtension:@"jpg"];
323 NSString *path = [[self screenshotSaveDirectoryPath] stringByAppendingPathComponent:filename];
324 path = [[NSFileManager defaultManager] _web_pathWithUniqueFilenameForPath:path];
325 [imageData writeToFile:path atomically:NO];
327 dispatch_async(dispatch_get_main_queue(), ^{
328 HMScreenshotInformation *info = [HMScreenshotInformation new];
330 info.version = [self cacheVersionForPath:path];
332 [self.screenshotsController insertObject:info atArrangedObjectIndex:0];
333 self.screenshotsController.selectedObjects = @[info];
335 if(HMStandardDefaults.showsListWindowAtScreenshot) {
336 [self.view.window makeKeyAndOrderFront:nil];
343 - (void)incrementCacheVersionForPath:(NSString *)fullpath
345 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"fullpath = %@", fullpath];
346 NSArray *filteredArray = [self.deletedPaths filteredArrayUsingPredicate:predicate];
347 if(filteredArray.count == 0) {
348 HMCacheVersionInfo *info = [HMCacheVersionInfo new];
349 info.fullpath = fullpath;
351 [self.deletedPaths addObject:info];
353 HMCacheVersionInfo *info = filteredArray[0];
354 info.version = @(info.version.unsignedIntegerValue + 1);
357 - (NSUInteger)cacheVersionForPath:(NSString *)fullpath
359 NSPredicate *predicate = [NSPredicate predicateWithFormat:@"fullpath = %@", fullpath];
360 NSArray *filteredArray = [self.deletedPaths filteredArrayUsingPredicate:predicate];
361 if(filteredArray.count == 0) {
365 HMCacheVersionInfo *cacheInfo = filteredArray[0];
366 return cacheInfo.version.unsignedIntegerValue;
370 - (void)prepareForSegue:(NSStoryboardSegue *)segue sender:(nullable id)sender
372 NSViewController *v = segue.destinationController;
373 v.representedObject = self.screenshots;
376 #pragma mark - NSSplitViewDelegate
378 const CGFloat leftMinWidth = 299;
379 const CGFloat rightMinWidth = 400;
381 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex
383 if(dividerIndex == 0) {
386 return proposedMinimumPosition;
388 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex
390 if(dividerIndex == 0) {
391 NSSize size = splitView.frame.size;
392 CGFloat rightWidth = size.width - proposedPosition;
393 if(rightWidth < rightMinWidth) {
394 return size.width - rightMinWidth;
397 return proposedPosition;
399 - (void)splitView:(NSSplitView *)splitView resizeSubviewsWithOldSize:(NSSize)oldSize
401 [splitView adjustSubviews];
403 NSView *leftView = splitView.subviews[0];
404 NSView *rightView = splitView.subviews[1];
406 if(NSWidth(leftView.frame) < leftMinWidth) {
407 NSRect leftRect = leftView.frame;
408 leftRect.size.width = leftMinWidth;
409 leftView.frame = leftRect;
411 NSRect rightRect = rightView.frame;
412 rightRect.size.width = NSWidth(splitView.frame) - NSWidth(leftRect) - splitView.dividerThickness;
413 rightRect.origin.x = NSWidth(leftRect) + splitView.dividerThickness;
414 rightView.frame = rightRect;
417 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view
419 NSView *leftView = splitView.subviews[0];
420 NSView *rightView = splitView.subviews[1];
422 if(leftView == view) {
423 if(NSWidth(leftView.frame) < leftMinWidth) return NO;
425 if(rightView == view) {
426 if(NSWidth(leftView.frame) >= leftMinWidth) return NO;
433 #pragma mark - IKImageBrowserDelegate
434 - (void)imageBrowser:(IKImageBrowserView *) aBrowser cellWasRightClickedAtIndex:(NSUInteger) index withEvent:(NSEvent *) event
436 [NSMenu popUpContextMenu:self.contextMenu withEvent:event forView:aBrowser];