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