OSDN Git Service

洋上補給の補強増設用のショートネームをつけた
[kcd/KCD.git] / KCD / ScreenshotListViewController.swift
1 //
2 //  ScreenshotListViewController.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2016/12/30.
6 //  Copyright © 2016年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10
11 extension NSUserInterfaceItemIdentifier {
12     
13     static let item = NSUserInterfaceItemIdentifier("item")
14 }
15
16 final class ScreenshotListViewController: NSViewController {
17     
18     private static let maxImageSize = 800.0
19     private static let leftMergin = 8.0 + 1.0
20     private static let rightMergin = 8.0 + 1.0
21     
22     var screenshots: ScreenshotModel = ScreenshotModel()
23     
24     @IBOutlet private var screenshotsController: NSArrayController!
25     @IBOutlet private weak var collectionView: NSCollectionView!
26     
27     private var selectionObservation: NSKeyValueObservation?
28     
29     @objc dynamic var zoom: Double = UserDefaults.standard[.screenshotPreviewZoomValue] {
30         
31         didSet {
32             collectionView.reloadData()
33             UserDefaults.standard[.screenshotPreviewZoomValue] = zoom
34         }
35     }
36     @objc dynamic var maxZoom: Double = 1.0
37     
38     private var collectionVisibleDidChangeHandler: ((Set<IndexPath>) -> Void)?
39     private var reloadHandler: (() -> Void)?
40     private var collectionSelectionDidChangeHandler: ((Int) -> Void)?
41     private(set) var inLiveScrolling = false
42     private var arrangedInformations: [ScreenshotInformation] {
43         
44         return screenshotsController.arrangedObjects as? [ScreenshotInformation] ?? []
45     }
46     private var selectionInformations: [ScreenshotInformation] {
47         
48         return screenshotsController.selectedObjects as? [ScreenshotInformation] ?? []
49     }
50     
51     private var dirName: String {
52         
53         guard let name = Bundle.main.localizedInfoDictionary?["CFBundleName"] as? String,
54             !name.isEmpty else {
55                 
56                 return "KCD"
57         }
58         
59         return name
60     }
61     private var screenshotSaveDirectoryURL: URL {
62         
63         let parentURL = URL(fileURLWithPath: AppDelegate.shared.screenShotSaveDirectory)
64         let url = parentURL.appendingPathComponent(dirName)
65         let fm = FileManager.default
66         var isDir: ObjCBool = false
67         
68         do {
69             
70             if !fm.fileExists(atPath: url.path, isDirectory: &isDir) {
71                 
72                 try fm.createDirectory(at: url, withIntermediateDirectories: false)
73                 
74             } else if !isDir.boolValue {
75                 
76                 print("\(url) is regular file, not direcory.")
77                 return parentURL
78             }
79             
80         } catch {
81             
82             print("Can not create screenshot save directory.")
83             return parentURL
84         }
85         
86         return url
87     }
88     
89     var indexPathsOfItemsBeingDragged: Set<IndexPath>?
90     
91     // MARK: - Function
92     override func viewDidLoad() {
93         
94         super.viewDidLoad()
95         
96         let nib = NSNib(nibNamed: ScreenshotCollectionViewItem.nibName, bundle: nil)
97         collectionView.register(nib, forItemWithIdentifier: .item)
98         
99         screenshots.sortDescriptors = [NSSortDescriptor(key: #keyPath(ScreenshotInformation.creationDate), ascending: false)]
100         selectionObservation = collectionView.observe(\NSCollectionView.selectionIndexPaths) { [weak self] (_, _) in
101             
102             guard let `self` = self else { return }
103             
104             let selections = self.collectionView.selectionIndexPaths
105             let selectionIndexes = selections.reduce(into: IndexSet()) { $0.insert($1.item) }
106             self.screenshots.selectedIndexes = selectionIndexes
107             selectionIndexes.first.map { self.collectionSelectionDidChangeHandler?($0) }
108         }
109         collectionView.postsFrameChangedNotifications = true
110         
111         let nc = NotificationCenter.default
112         let scrollView = collectionView.enclosingScrollView
113         
114         nc.addObserver(forName: NSView.frameDidChangeNotification, object: collectionView, queue: nil, using: viewFrameDidChange)
115         nc.addObserver(forName: NSScrollView.didLiveScrollNotification,
116                        object: collectionView.enclosingScrollView, queue: nil) { _ in
117                         
118             let visibleItems = self.collectionView.indexPathsForVisibleItems()
119             self.collectionVisibleDidChangeHandler?(visibleItems)
120         }
121         nc.addObserver(forName: NSScrollView.willStartLiveScrollNotification, object: scrollView, queue: nil) { _ in
122             
123             self.inLiveScrolling = true
124         }
125         nc.addObserver(forName: NSScrollView.didEndLiveScrollNotification, object: scrollView, queue: nil) { _ in
126             
127             self.inLiveScrolling = false
128         }
129         nc.addObserver(forName: .didRegisterScreenshot,
130                        object: nil,
131                        queue: .main) { notification in
132                         
133                         guard let url = notification.userInfo?[ScreenshotRegister.screenshotURLKey] as? URL else { return }
134                         
135                         let info = ScreenshotInformation(url: url)
136                         
137                         self.screenshotsController.insert(info, atArrangedObjectIndex: 0)
138                         let set: Set<IndexPath> = [NSIndexPath(forItem: 0, inSection: 0) as IndexPath]
139                         self.collectionView.selectionIndexPaths = set
140                         
141                         self.collectionView.scrollToItems(at: set, scrollPosition: .nearestHorizontalEdge)
142                         if UserDefaults.standard[.showsListWindowAtScreenshot] {
143                             
144                             self.view.window?.makeKeyAndOrderFront(nil)
145                         }
146         }
147         
148         collectionView.setDraggingSourceOperationMask([.move, .copy, .delete], forLocal: false)
149         
150         viewFrameDidChange(nil)
151         
152         DispatchQueue.main.asyncAfter(deadline: .now() + 0.0001, execute: self.reloadData)
153     }
154     
155     override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
156         
157         guard let vc = segue.destinationController as? NSViewController else { return }
158         
159         vc.representedObject = screenshots
160     }
161     
162     func viewFrameDidChange(_ notification: Notification?) {
163         
164         maxZoom = calcMaxZoom()
165         if zoom > maxZoom { zoom = maxZoom }
166     }
167     
168     /// 画像の大きさの変化が自然になるようにzoom値から画像サイズを計算
169     private func sizeFrom(zoom: Double) -> CGFloat {
170         
171         if zoom < 0.5 { return CGFloat(type(of: self).maxImageSize * zoom * 0.6) }
172         
173         return CGFloat(type(of: self).maxImageSize * (0.8 * zoom * zoom * zoom  + 0.2))
174     }
175     
176     /// ビューの幅に合わせたzoomの最大値を計算
177     private func calcMaxZoom() -> Double {
178         
179         let effectiveWidth = Double(collectionView.frame.size.width) - type(of: self).leftMergin - type(of: self).rightMergin
180         
181         if effectiveWidth < 240 { return effectiveWidth / type(of: self).maxImageSize / 0.6 }
182         if effectiveWidth > 800 { return 1.0 }
183         
184         return pow((effectiveWidth / type(of: self).maxImageSize - 0.2) / 0.8, 1.0 / 3.0)
185     }
186     
187     private func reloadData() {
188         
189         Promise<[ScreenshotInformation]>()
190             .complete {
191                 Result(ScreenshotLoader(self.screenshotSaveDirectoryURL).merge(screenshots: []))
192             }
193             .future
194             .onSuccess { screenshots in
195                 
196                 DispatchQueue.main.async {
197                     self.screenshots.screenshots = screenshots
198                     
199                     self.collectionView.selectionIndexPaths = [NSIndexPath(forItem: 0, inSection: 0) as IndexPath]
200                     
201                     self.reloadHandler?()
202                 }
203         }
204     }
205     
206 }
207
208 // MARK: - IBAction
209 extension ScreenshotListViewController {
210     
211     @IBAction func reloadContent(_ sender: AnyObject?) {
212         
213         reloadData()
214     }
215     
216     @IBAction func reloadData(_ sender: AnyObject?) {
217         
218         reloadData()
219     }
220     
221     private func moveToTrash(_ urls: [URL]) {
222         
223         let list = urls.map { $0.path }
224             .map { "(\"\($0)\" as POSIX file)" }
225             .joined(separator: " , ")
226         let script = "tell application \"Finder\"\n"
227             + "    delete { \(list) }\n"
228             + "end tell"
229         
230         guard let aps = NSAppleScript(source: script) else { return }
231         
232         aps.executeAndReturnError(nil)
233     }
234     
235     @IBAction func delete(_ sender: AnyObject?) {
236         
237         let selectionURLs = selectionInformations.map { $0.url }
238         
239         let selectionIndexes = screenshotsController.selectionIndexes
240         screenshotsController.remove(atArrangedObjectIndexes: selectionIndexes)
241         reloadHandler?()
242         
243         guard var index = selectionIndexes.first else { return }
244         
245         if arrangedInformations.count <= index {
246             
247             index = arrangedInformations.count - 1
248         }
249         collectionView.selectionIndexPaths = [NSIndexPath(forItem: index, inSection: 0) as IndexPath]
250         
251         moveToTrash(selectionURLs)
252     }
253     
254     @IBAction func revealInFinder(_ sender: AnyObject?) {
255         
256         let urls = selectionInformations.map { $0.url }
257         NSWorkspace.shared.activateFileViewerSelecting(urls)
258     }
259 }
260
261 extension ScreenshotListViewController: NSCollectionViewDelegateFlowLayout {
262     
263     func collectionView(_ collectionView: NSCollectionView,
264                         layout collectionViewLayout: NSCollectionViewLayout,
265                         sizeForItemAt indexPath: IndexPath) -> NSSize {
266         
267         let size = sizeFrom(zoom: zoom)
268         
269         return NSSize(width: size, height: size)
270     }
271     
272     // Drag and Drop
273     func collectionView(_ collectionView: NSCollectionView, canDragItemsAt indexPaths: Set<IndexPath>, with event: NSEvent) -> Bool {
274         
275         return true
276     }
277     
278     func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
279         
280         return arrangedInformations[indexPath.item].url.absoluteURL as NSURL
281     }
282     
283     func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItemsAt indexPaths: Set<IndexPath>) {
284         
285         indexPathsOfItemsBeingDragged = indexPaths
286     }
287     
288     func collectionView(_ collectionView: NSCollectionView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, dragOperation operation: NSDragOperation) {
289         
290         defer { indexPathsOfItemsBeingDragged = nil }
291         
292         guard let dragged = indexPathsOfItemsBeingDragged else { return }
293         guard operation.contains(.move) || operation.contains(.delete) else { return }
294         
295         var indexes = IndexSet()
296         dragged.forEach { indexes.insert($0.item) }
297         
298         screenshotsController.remove(atArrangedObjectIndexes: indexes)
299     }
300     
301 }
302
303 @available(OSX 10.12.2, *)
304 private var kTouchBars: [Int: NSTouchBar] = [:]
305 @available(OSX 10.12.2, *)
306 private var kScrubbers: [Int: NSScrubber] = [:]
307 @available(OSX 10.12.2, *)
308 private var kPickers: [Int: NSSharingServicePickerTouchBarItem] = [:]
309
310 @available(OSX 10.12.2, *)
311 extension ScreenshotListViewController: NSTouchBarDelegate {
312     
313     static let ServicesItemIdentifier: NSTouchBarItem.Identifier
314         = NSTouchBarItem.Identifier(rawValue: "com.masakih.sharingTouchBarItem")
315     
316     @IBOutlet private var screenshotTouchBar: NSTouchBar! {
317         
318         get { return kTouchBars[hashValue] }
319         set { kTouchBars[hashValue] = newValue }
320     }
321     
322     @IBOutlet private var scrubber: NSScrubber! {
323         
324         get { return kScrubbers[hashValue] }
325         set { kScrubbers[hashValue] = newValue }
326     }
327     
328     @IBOutlet private var sharingItem: NSSharingServicePickerTouchBarItem! {
329         
330         get { return kPickers[hashValue] }
331         set { kPickers[hashValue] = newValue }
332     }
333     
334     override func makeTouchBar() -> NSTouchBar? {
335         
336         Bundle.main.loadNibNamed(NSNib.Name("ScreenshotTouchBar"), owner: self, topLevelObjects: nil)
337         let identifiers = self.screenshotTouchBar.defaultItemIdentifiers
338             + [type(of: self).ServicesItemIdentifier]
339         screenshotTouchBar.defaultItemIdentifiers = identifiers
340         
341         if collectionVisibleDidChangeHandler == nil {
342             
343             collectionVisibleDidChangeHandler = { [weak self] in
344                 
345                 guard let `self` = self else { return }
346                 guard let index = $0.first else { return }
347                 
348                 let middle = index.item + $0.count / 2
349                 
350                 if middle < self.arrangedInformations.count - 1 {
351                     
352                     self.scrubber.scrollItem(at: middle, to: .none)
353                 }
354             }
355         }
356         
357         if collectionSelectionDidChangeHandler == nil {
358             
359             collectionSelectionDidChangeHandler = { [weak self] in
360                 
361                 self?.scrubber.selectedIndex = $0
362             }
363         }
364         
365         if reloadHandler == nil {
366             
367             reloadHandler = { [weak self] in
368                 
369                 self?.scrubber.reloadData()
370             }
371         }
372         
373         return screenshotTouchBar
374     }
375     
376     func touchBar(_ touchBar: NSTouchBar,
377                   makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
378         
379         guard identifier == type(of: self).ServicesItemIdentifier else { return nil }
380         
381         if sharingItem == nil {
382             
383             sharingItem = NSSharingServicePickerTouchBarItem(identifier: identifier)
384             
385             if let w = view.window?.windowController as? NSSharingServicePickerTouchBarItemDelegate {
386                 
387                 sharingItem.delegate = w
388             }
389         }
390         return sharingItem
391     }
392 }
393
394 @available(OSX 10.12.2, *)
395 extension ScreenshotListViewController: NSScrubberDataSource, NSScrubberDelegate {
396     
397     func numberOfItems(for scrubber: NSScrubber) -> Int {
398         
399         return arrangedInformations.count
400     }
401     
402     func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView {
403         
404         guard case 0..<arrangedInformations.count = index else { return NSScrubberImageItemView() }
405         
406         let info = arrangedInformations[index]
407         let itemView = NSScrubberImageItemView()
408         
409         if let image = NSImage(contentsOf: info.url) {
410             
411             itemView.image = image
412         }
413         
414         return itemView
415     }
416     
417     func scrubber(_ scrubber: NSScrubber, didSelectItemAt selectedIndex: Int) {
418         
419         let p = NSIndexPath(forItem: selectedIndex, inSection: 0) as IndexPath
420         
421         collectionView.selectionIndexPaths = [p]
422     }
423     
424     func scrubber(_ scrubber: NSScrubber, didChangeVisibleRange visibleRange: NSRange) {
425         
426         if inLiveScrolling { return }
427         
428         let center = visibleRange.location + visibleRange.length / 2
429         let p = NSIndexPath(forItem: center, inSection: 0) as IndexPath
430         collectionView.scrollToItems(at: [p], scrollPosition: [.centeredVertically])
431     }
432 }