2016-08-30 23:25:34 +03:00
|
|
|
/**
|
|
|
|
* Tae Won Ha - http://taewon.de - @hataewon
|
|
|
|
* See LICENSE
|
|
|
|
*/
|
|
|
|
|
|
|
|
import Cocoa
|
|
|
|
import PureLayout
|
|
|
|
import RxSwift
|
2016-09-03 11:17:22 +03:00
|
|
|
import RxCocoa
|
2016-08-30 23:25:34 +03:00
|
|
|
|
2016-09-01 21:10:40 +03:00
|
|
|
class OpenQuicklyWindowComponent: WindowComponent, NSWindowDelegate, NSTableViewDelegate, NSTableViewDataSource {
|
2016-09-02 19:22:40 +03:00
|
|
|
|
|
|
|
private let searchField = NSTextField(forAutoLayout: ())
|
2016-09-03 00:35:18 +03:00
|
|
|
private let cwdControl = NSPathControl(forAutoLayout: ())
|
2016-09-03 11:17:22 +03:00
|
|
|
private let countField = NSTextField(forAutoLayout: ())
|
|
|
|
private let fileView = NSTableView.standardSourceListTableView()
|
|
|
|
|
|
|
|
private let fileItemService: FileItemService
|
|
|
|
|
|
|
|
private var count = 0
|
|
|
|
private var flatFiles = Observable<[FileItem]>.empty()
|
|
|
|
private var flatFilesDisposeBag = DisposeBag()
|
|
|
|
private let userInitiatedScheduler = ConcurrentDispatchQueueScheduler(globalConcurrentQueueQOS: .UserInitiated)
|
|
|
|
|
|
|
|
private var scoredItems = [ScoredFileItem]()
|
|
|
|
private var sortedScoredItems = [ScoredFileItem]()
|
2016-08-30 23:25:34 +03:00
|
|
|
|
2016-09-04 00:13:52 +03:00
|
|
|
private var cwdPathCompsCount = 0
|
2016-09-03 00:57:39 +03:00
|
|
|
private var cwd = NSURL(fileURLWithPath: NSHomeDirectory(), isDirectory: true) {
|
|
|
|
didSet {
|
2016-09-04 00:13:52 +03:00
|
|
|
self.cwdPathCompsCount = self.cwd.pathComponents!.count
|
2016-09-03 00:57:39 +03:00
|
|
|
self.cwdControl.URL = self.cwd
|
|
|
|
}
|
|
|
|
}
|
2016-09-04 00:13:52 +03:00
|
|
|
|
|
|
|
private let filterOpQueue = NSOperationQueue()
|
2016-09-03 00:57:39 +03:00
|
|
|
|
2016-09-03 11:17:22 +03:00
|
|
|
init(source: Observable<Any>, fileItemService: FileItemService) {
|
|
|
|
self.fileItemService = fileItemService
|
|
|
|
|
2016-08-30 23:25:34 +03:00
|
|
|
super.init(source: source, nibName: "OpenQuicklyWindow")
|
2016-09-01 21:10:40 +03:00
|
|
|
|
|
|
|
self.window.delegate = self
|
2016-09-04 00:13:52 +03:00
|
|
|
self.filterOpQueue.qualityOfService = .UserInitiated
|
|
|
|
self.filterOpQueue.name = "open-quickly-filter-operation-queue"
|
|
|
|
|
2016-09-03 11:17:22 +03:00
|
|
|
self.searchField.rx_text
|
|
|
|
.throttle(0.2, scheduler: MainScheduler.instance)
|
|
|
|
.distinctUntilChanged()
|
2016-09-04 00:13:52 +03:00
|
|
|
.flatMapLatest { [unowned self] pattern in
|
|
|
|
self.flatFiles.
|
|
|
|
self.filterOpQueue.addOperationWithBlock {
|
|
|
|
|
2016-09-03 11:17:22 +03:00
|
|
|
}
|
2016-09-04 00:13:52 +03:00
|
|
|
|
2016-09-03 11:17:22 +03:00
|
|
|
return self.flatFiles
|
|
|
|
}
|
2016-09-04 00:13:52 +03:00
|
|
|
.subscribe(onNext: { [unowned self] pattern in
|
|
|
|
NSLog("filtering \(pattern)")
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
// .subscribeOn(self.userInitiatedScheduler)
|
|
|
|
// .doOnNext { _ in
|
|
|
|
// self.scoredItems = []
|
|
|
|
// self.sortedScoredItems = []
|
|
|
|
// }
|
|
|
|
// .subscribeOn(MainScheduler.instance)
|
|
|
|
// .doOnNext { _ in
|
|
|
|
// self.fileView.reloadData()
|
|
|
|
// }
|
|
|
|
// .subscribeOn(self.userInitiatedScheduler)
|
|
|
|
// .flatMapLatest { [unowned self] pattern -> Observable<[ScoredFileItem]> in
|
|
|
|
// NSLog("Flat map start: \(pattern)")
|
|
|
|
// if pattern.characters.count == 0 {
|
|
|
|
// return self.flatFiles
|
|
|
|
// .map { fileItems in
|
|
|
|
// return fileItems.concurrentChunkMap(200) { ScoredFileItem(score: 0, url: $0.url) }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// let useFullPath = pattern.containsString("/")
|
|
|
|
// let cwdPath = self.cwd.path! + "/"
|
|
|
|
//
|
|
|
|
// let result: Observable<[ScoredFileItem]> = self.flatFiles
|
|
|
|
// .map { fileItems in
|
|
|
|
// return fileItems.concurrentChunkMap(200) { item in
|
|
|
|
// let url = item.url
|
|
|
|
// let target = useFullPath ? url.path!.stringByReplacingOccurrencesOfString(cwdPath, withString: "")
|
|
|
|
// : url.lastPathComponent!
|
|
|
|
//
|
|
|
|
// return ScoredFileItem(score: Scorer.score(target, pattern: pattern), url: url)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// NSLog("Flat map end: \(pattern)")
|
|
|
|
//
|
|
|
|
// return result
|
|
|
|
// }
|
|
|
|
// .doOnNext { [unowned self] items in
|
|
|
|
// self.scoredItems.appendContentsOf(items)
|
|
|
|
// self.sortedScoredItems = Array(self.scoredItems.sort(>)[0..<min(201, self.scoredItems.count)])
|
|
|
|
// }
|
|
|
|
// .observeOn(MainScheduler.instance)
|
|
|
|
// .subscribe(onNext: { [unowned self] items in
|
|
|
|
// self.fileView.reloadData()
|
|
|
|
// })
|
2016-09-03 11:17:22 +03:00
|
|
|
.addDisposableTo(self.disposeBag)
|
2016-08-30 23:25:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
override func addViews() {
|
2016-09-03 00:44:05 +03:00
|
|
|
let searchField = self.searchField
|
|
|
|
|
2016-09-03 11:17:22 +03:00
|
|
|
let fileView = self.fileView
|
2016-09-04 00:13:52 +03:00
|
|
|
fileView.intercellSpacing = CGSize(width: 4, height: 4)
|
2016-09-03 00:44:05 +03:00
|
|
|
fileView.setDataSource(self)
|
|
|
|
fileView.setDelegate(self)
|
2016-09-04 00:13:52 +03:00
|
|
|
|
2016-09-03 00:55:19 +03:00
|
|
|
let fileScrollView = NSScrollView.standardScrollView()
|
2016-09-03 11:17:22 +03:00
|
|
|
fileScrollView.autoresizesSubviews = true
|
2016-09-03 00:44:05 +03:00
|
|
|
fileScrollView.documentView = fileView
|
|
|
|
|
2016-09-03 00:35:18 +03:00
|
|
|
let cwdControl = self.cwdControl
|
|
|
|
cwdControl.pathStyle = .Standard
|
|
|
|
cwdControl.backgroundColor = NSColor.clearColor()
|
|
|
|
cwdControl.refusesFirstResponder = true
|
|
|
|
cwdControl.cell?.controlSize = .SmallControlSize
|
|
|
|
cwdControl.cell?.font = NSFont.systemFontOfSize(NSFont.smallSystemFontSize())
|
|
|
|
cwdControl.setContentCompressionResistancePriority(NSLayoutPriorityDefaultLow, forOrientation:.Horizontal)
|
|
|
|
|
2016-09-03 11:17:22 +03:00
|
|
|
let countField = self.countField
|
|
|
|
countField.editable = false
|
|
|
|
countField.bordered = false
|
|
|
|
countField.alignment = .Right
|
|
|
|
countField.backgroundColor = NSColor.clearColor()
|
|
|
|
countField.stringValue = "0 items"
|
|
|
|
|
2016-09-03 00:44:05 +03:00
|
|
|
let contentView = self.window.contentView!
|
|
|
|
contentView.addSubview(searchField)
|
|
|
|
contentView.addSubview(fileScrollView)
|
|
|
|
contentView.addSubview(cwdControl)
|
2016-09-03 11:17:22 +03:00
|
|
|
contentView.addSubview(countField)
|
2016-09-01 21:10:40 +03:00
|
|
|
|
2016-09-03 00:35:18 +03:00
|
|
|
searchField.autoPinEdgeToSuperviewEdge(.Top, withInset: 18)
|
|
|
|
searchField.autoPinEdgeToSuperviewEdge(.Right, withInset: 18)
|
|
|
|
searchField.autoPinEdgeToSuperviewEdge(.Left, withInset: 18)
|
|
|
|
|
2016-09-03 00:44:05 +03:00
|
|
|
fileScrollView.autoPinEdge(.Top, toEdge: .Bottom, ofView: searchField, withOffset: 18)
|
|
|
|
fileScrollView.autoPinEdge(.Right, toEdge: .Right, ofView: searchField)
|
|
|
|
fileScrollView.autoPinEdge(.Left, toEdge: .Left, ofView: searchField)
|
|
|
|
fileScrollView.autoSetDimension(.Height, toSize: 300)
|
|
|
|
|
2016-09-03 11:17:22 +03:00
|
|
|
cwdControl.autoPinEdge(.Top, toEdge: .Bottom, ofView: fileScrollView, withOffset: 18)
|
|
|
|
cwdControl.autoPinEdgeToSuperviewEdge(.Left, withInset: 18)
|
2016-09-03 00:35:18 +03:00
|
|
|
cwdControl.autoPinEdgeToSuperviewEdge(.Bottom, withInset: 18)
|
2016-09-03 11:17:22 +03:00
|
|
|
|
|
|
|
countField.autoPinEdge(.Top, toEdge: .Bottom, ofView: fileScrollView, withOffset: 18)
|
|
|
|
countField.autoPinEdgeToSuperviewEdge(.Right, withInset: 18)
|
|
|
|
countField.autoPinEdge(.Left, toEdge: .Right, ofView: cwdControl)
|
2016-08-30 23:25:34 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
override func subscription(source source: Observable<Any>) -> Disposable {
|
|
|
|
return NopDisposable.instance
|
|
|
|
}
|
2016-09-02 19:22:40 +03:00
|
|
|
|
2016-09-03 00:35:18 +03:00
|
|
|
func show(forMainWindow mainWindow: MainWindowComponent) {
|
2016-09-03 00:57:39 +03:00
|
|
|
self.cwd = mainWindow.cwd
|
2016-09-03 11:17:22 +03:00
|
|
|
let flatFiles = self.fileItemService.flatFileItems(ofUrl: self.cwd)
|
|
|
|
.subscribeOn(self.userInitiatedScheduler)
|
|
|
|
.replayAll()
|
2016-09-04 00:13:52 +03:00
|
|
|
flatFiles.connect().addDisposableTo(self.flatFilesDisposeBag)
|
2016-09-03 11:17:22 +03:00
|
|
|
|
|
|
|
flatFiles
|
|
|
|
.observeOn(MainScheduler.instance)
|
2016-09-04 00:13:52 +03:00
|
|
|
.subscribe(onNext: { [unowned self] items in
|
2016-09-03 11:17:22 +03:00
|
|
|
self.count += items.count
|
|
|
|
self.countField.stringValue = "\(self.count) items"
|
2016-09-04 00:13:52 +03:00
|
|
|
})
|
2016-09-03 11:17:22 +03:00
|
|
|
.addDisposableTo(self.flatFilesDisposeBag)
|
|
|
|
|
|
|
|
self.flatFiles = flatFiles
|
|
|
|
|
2016-09-03 00:35:18 +03:00
|
|
|
self.show()
|
2016-09-02 19:22:40 +03:00
|
|
|
self.searchField.becomeFirstResponder()
|
|
|
|
}
|
2016-08-30 23:25:34 +03:00
|
|
|
}
|
2016-09-03 00:44:05 +03:00
|
|
|
|
|
|
|
// MARK: - NSTableViewDataSource
|
|
|
|
extension OpenQuicklyWindowComponent {
|
|
|
|
|
|
|
|
func numberOfRowsInTableView(_: NSTableView) -> Int {
|
2016-09-03 11:17:22 +03:00
|
|
|
return self.sortedScoredItems.count
|
2016-09-03 00:44:05 +03:00
|
|
|
}
|
2016-09-04 00:13:52 +03:00
|
|
|
|
|
|
|
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
|
|
|
let url = self.sortedScoredItems[row].url
|
|
|
|
let pathComps = url.pathComponents!
|
|
|
|
let truncatedPathComps = pathComps[self.cwdPathCompsCount..<pathComps.count]
|
|
|
|
let name = truncatedPathComps.last!
|
|
|
|
let pathInfo = truncatedPathComps.dropLast().reverse().joinWithSeparator(" / ")
|
|
|
|
|
|
|
|
let result = NSMutableAttributedString(string: "\(name) — \(pathInfo)")
|
|
|
|
result.addAttribute(NSForegroundColorAttributeName, value: NSColor.lightGrayColor(),
|
|
|
|
range: NSRange(location:name.characters.count, length: pathInfo.characters.count + 3))
|
|
|
|
|
|
|
|
let cell = tableView.makeViewWithIdentifier("file-view-row", owner: self) as? ImageAndTextTableCell
|
|
|
|
?? ImageAndTextTableCell(withIdentifier: "file-view-row")
|
|
|
|
|
|
|
|
cell.textField.attributedStringValue = result
|
|
|
|
cell.imageView.image = self.fileItemService.icon(forUrl: url)
|
|
|
|
|
|
|
|
return cell
|
2016-09-03 00:44:05 +03:00
|
|
|
}
|
2016-09-04 00:13:52 +03:00
|
|
|
|
|
|
|
// func tableView(_: NSTableView, objectValueForTableColumn _: NSTableColumn?, row: Int) -> AnyObject? {
|
|
|
|
// let url = self.sortedScoredItems[row].url
|
|
|
|
// let pathComps = self.sortedScoredItems[row].url.pathComponents!
|
|
|
|
// let truncatedPathComps = pathComps[self.cwdPathCompsCount..<pathComps.count]
|
|
|
|
// let name = truncatedPathComps.last!
|
|
|
|
// let pathInfo = truncatedPathComps.dropLast().reverse().joinWithSeparator("/")
|
|
|
|
//
|
|
|
|
// let textAttachment = NSTextAttachment()
|
|
|
|
// textAttachment.image = self.fileItemService.icon(forUrl: url)
|
|
|
|
//
|
|
|
|
// let result: NSMutableAttributedString = NSAttributedString(attachment: textAttachment).mutableCopy() as! NSMutableAttributedString
|
|
|
|
// result.mutableString.appendString("\(name) — \(pathInfo)")
|
|
|
|
// result.addAttribute(NSForegroundColorAttributeName, value: NSColor.lightGrayColor(),
|
|
|
|
// range: NSRange(location:name.characters.count, length: pathInfo.characters.count + 3))
|
|
|
|
//
|
|
|
|
// return result
|
|
|
|
// }
|
2016-09-03 00:44:05 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - NSTableViewDelegate
|
|
|
|
extension OpenQuicklyWindowComponent {
|
|
|
|
|
|
|
|
func tableViewSelectionDidChange(_: NSNotification) {
|
2016-09-04 00:13:52 +03:00
|
|
|
// NSLog("\(#function): selection changed")
|
2016-09-03 00:44:05 +03:00
|
|
|
}
|
2016-09-04 00:13:52 +03:00
|
|
|
|
|
|
|
// func tableView(tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
|
|
|
|
// return 34
|
|
|
|
// }
|
2016-09-03 00:44:05 +03:00
|
|
|
}
|
2016-09-03 11:17:22 +03:00
|
|
|
|
|
|
|
// MARK: - NSWindowDelegate
|
|
|
|
extension OpenQuicklyWindowComponent {
|
|
|
|
|
2016-09-04 00:13:52 +03:00
|
|
|
func windowWillClose(notification: NSNotification) {
|
|
|
|
self.flatFilesDisposeBag = DisposeBag()
|
|
|
|
self.flatFiles = Observable.empty()
|
2016-09-03 11:17:22 +03:00
|
|
|
self.count = 0
|
2016-09-04 00:13:52 +03:00
|
|
|
|
2016-09-03 11:17:22 +03:00
|
|
|
self.scoredItems = []
|
|
|
|
self.sortedScoredItems = []
|
2016-09-04 00:13:52 +03:00
|
|
|
|
|
|
|
self.searchField.stringValue = ""
|
|
|
|
self.countField.stringValue = "0 items"
|
2016-09-03 11:17:22 +03:00
|
|
|
}
|
|
|
|
}
|