246 lines
9.5 KiB
Swift
Executable File
246 lines
9.5 KiB
Swift
Executable File
//
|
|
// ViewController.swift
|
|
// TagTunes
|
|
//
|
|
// Created by Kim Wittenburg on 28.08.15.
|
|
// Copyright © 2015 Kim Wittenburg. All rights reserved.
|
|
//
|
|
|
|
import SearchAPI
|
|
import AppKitPlus
|
|
|
|
class MainViewController: NSViewController, SearchDelegate, LookupQueueDelegate {
|
|
|
|
// MARK: Types
|
|
|
|
/// This struct contains *magic numbers* like references to storyboard
|
|
/// identifiers.
|
|
private struct Constants {
|
|
|
|
/// The identifier of the segue that embeds the
|
|
/// `OutlineContentViewController` in its parent view.
|
|
static let OutlineContentViewControllerEmbedSegueIdentifier = "embedOutlineContentViewController"
|
|
|
|
/// The identifier of the segue that embeds the `LookupViewController` in
|
|
/// its parent view.
|
|
static let LookupViewControllerEmbedSegueIdentifier = "embedLookupViewController"
|
|
|
|
/// The space between the lookup panel and its superview when it's
|
|
/// visible.
|
|
static let LookupPanelExpandedBottomSpace: CGFloat = -4
|
|
|
|
/// The space between the superview and the lookup panel, if it's not
|
|
/// visible.
|
|
static let LookupPanelCollapsedBottomSpace: CGFloat = -82
|
|
|
|
/// The delay between the lookup controller completing and it being
|
|
/// hidden.
|
|
static let LookupPanelHideDelay: NSTimeInterval = 2
|
|
|
|
/// This constant is used for KVO observations on a `Preferences`
|
|
/// instance.
|
|
static var PreferencesKVOContext = "PreferencesKVOContext"
|
|
}
|
|
|
|
// MARK: Properties
|
|
|
|
/// The view controller that manages the display of the content.
|
|
internal var contentViewController: ContentViewController!
|
|
|
|
// MARK: View Life Cycle
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
Preferences.sharedPreferences.addObserver(self, forKeyPath: "useCensoredNames", options: [], context: &Constants.PreferencesKVOContext)
|
|
Preferences.sharedPreferences.addObserver(self, forKeyPath: "caseSensitive", options: [], context: &Constants.PreferencesKVOContext)
|
|
}
|
|
|
|
override func prepareForSegue(segue: NSStoryboardSegue, sender: AnyObject?) {
|
|
if segue.identifier == Constants.OutlineContentViewControllerEmbedSegueIdentifier {
|
|
contentViewController = segue.destinationController as? ContentViewController
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
Preferences.sharedPreferences.removeObserver(self, forKeyPath: "useCensoredNames")
|
|
Preferences.sharedPreferences.removeObserver(self, forKeyPath: "caseSensitiv")
|
|
}
|
|
|
|
|
|
// MARK: Notifications
|
|
|
|
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
|
|
if context == &Constants.PreferencesKVOContext {
|
|
contentViewController.updateAllItems()
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: Searching
|
|
|
|
func searchController(searchController: SearchController, didSelectSearchResult searchResult: TagTunesItem) {
|
|
contentViewController.addItem(searchResult)
|
|
}
|
|
|
|
// MARK: Lookup Panel
|
|
|
|
func lookupQueue(lookupQueue: LookupQueue, willBeginLookupForTracks tracks: [TagTunesTrack]) {
|
|
}
|
|
|
|
func lookupQueue(lookupQueue: LookupQueue, completedLookupForTracks tracks: [TagTunesTrack]) {
|
|
var newAlbumItems = [AlbumItem]()
|
|
for track in tracks {
|
|
if case .Found(let result) = track.lookupState {
|
|
if let album = result.collection.map(contentViewController.itemForEntity) as? AlbumItem {
|
|
album.addAssociatedTrack(track, forChildEntity: result)
|
|
} else if let collection = result.collection {
|
|
let album = AlbumItem(entity: collection)
|
|
album.addAssociatedTrack(track, forChildEntity: result)
|
|
contentViewController.addItem(album)
|
|
newAlbumItems.append(album)
|
|
} else {
|
|
fatalError("Lookup returned a song without album.")
|
|
}
|
|
} else {
|
|
NSApp.unsortedTracksController.addTracks([track]) // TODO: Show failure reason
|
|
}
|
|
}
|
|
for album in newAlbumItems {
|
|
album.beginLoadingChildren()
|
|
}
|
|
}
|
|
|
|
func lookupQueueDidFinishLookup(lookupQueue: LookupQueue) {
|
|
}
|
|
|
|
// MARK: Saving
|
|
|
|
@IBAction func save(sender: AnyObject?) {
|
|
var items = [AnyObject]()
|
|
for item in contentViewController.selectedItems {
|
|
if let group = item as? TagTunesGroupItem {
|
|
for child in group.children {
|
|
for track in child.associatedTracks {
|
|
saveQueue.addOperation(SaveOperation(track: track, entity: child))
|
|
}
|
|
}
|
|
items.append(group)
|
|
} else if let entity = item as? TagTunesEntityItem {
|
|
for track in entity.associatedTracks {
|
|
saveQueue.addOperation(SaveOperation(track: track, entity: entity))
|
|
}
|
|
items.append(entity)
|
|
} else if let track = item as? TagTunesTrack {
|
|
if let entity = track.entity {
|
|
saveQueue.addOperation(SaveOperation(track: track, entity: entity))
|
|
items.append(track)
|
|
}
|
|
}
|
|
}
|
|
contentViewController.removeItems(items)
|
|
}
|
|
|
|
// TODO: Use Operations for this
|
|
/// Exports the artwork of the first selected item to a file. This methods
|
|
/// presents a `NSSavePanel` so the user can specify where to save the
|
|
/// artwork.
|
|
@IBAction func exportArtwork(sender: AnyObject?) {
|
|
let item: TagTunesItem
|
|
let object = contentViewController.selectedItems.first
|
|
if let track = object as? TagTunesTrack {
|
|
if let theItem = track.entity {
|
|
item = theItem
|
|
} else {
|
|
return
|
|
}
|
|
} else if let theItem = object as? TagTunesItem {
|
|
item = theItem
|
|
} else {
|
|
return
|
|
}
|
|
var album: Album
|
|
switch item {
|
|
case let songItem as SongItem:
|
|
album = songItem.album
|
|
case let albumItem as AlbumItem:
|
|
album = albumItem.album
|
|
default: return
|
|
}
|
|
let savePanel = NSSavePanel()
|
|
savePanel.allowedFileTypes = ["jpg"]
|
|
savePanel.nameFieldStringValue = Preferences.sharedPreferences.useCensoredNames ? album.censoredName : album.name
|
|
savePanel.beginSheetModalForWindow(view.window!) { response -> Void in
|
|
if response == NSModalResponseOK {
|
|
let bitmapRep = album.artwork.optimalArtworkImageForSize(CGFloat.max)?.representations[0] as? NSBitmapImageRep
|
|
guard bitmapRep != nil else {
|
|
let alert = NSAlert()
|
|
alert.messageText = NSLocalizedString("The artwork could not be saved.", comment: "Error message informing the user that an artwork could not be saved to a file.")
|
|
alert.informativeText = NSLocalizedString("Please check your network connection. Also make sure that you have write permissions to the destination you selected.", comment: "Informative text for the 'The artwork could not be saved.' error.")
|
|
alert.addButtonWithTitle(NSLocalizedString("OK", comment: "Button title"))
|
|
alert.beginSheetModalForWindow(self.view.window!, completionHandler: nil)
|
|
return
|
|
}
|
|
let data = bitmapRep!.representationUsingType(.NSJPEGFileType, properties: [:])
|
|
data?.writeToURL(savePanel.URL!, atomically: false)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// MARK: Other Actions
|
|
|
|
/// Removes the selected items from the outline view.
|
|
@IBAction internal func delete(sender: AnyObject?) {
|
|
self.contentViewController.removeSelectedItems()
|
|
}
|
|
|
|
/// Shows the currently selected item in the iTunes store.
|
|
@IBAction internal func showInITunesStore(sender: AnyObject?) {
|
|
if let item = contentViewController.clickedItems.first as? TagTunesItem {
|
|
NSWorkspace.sharedWorkspace().openURL(item.entity.viewURL)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - User Interface Validations
|
|
|
|
extension MainViewController: NSUserInterfaceValidations {
|
|
|
|
func validateUserInterfaceItem(anItem: NSValidatedUserInterfaceItem) -> Bool {
|
|
if anItem.action() == #selector(MainViewController.save(_:)) {
|
|
return canSave
|
|
} else if anItem.action() == #selector(MainViewController.exportArtwork(_:)) {
|
|
return canExportArtworks
|
|
} else if anItem.action() == #selector(MainViewController.delete(_:)) {
|
|
return canDelete
|
|
} else if anItem.action() == #selector(MainViewController.showInITunesStore(_:)) {
|
|
return canShowInITunesStore
|
|
}
|
|
return false
|
|
}
|
|
|
|
/// Returns whether it is currently valid to invoke `save(_:)`.
|
|
private var canSave: Bool {
|
|
return !contentViewController.selectedItems.isEmpty
|
|
}
|
|
|
|
/// Returns whether it is currently valid to invoke `exportArtworks(_:)`.
|
|
private var canExportArtworks: Bool {
|
|
return contentViewController.selectedItems.count == 1
|
|
}
|
|
|
|
/// Returns whether it is currently valid to invoke
|
|
/// `delete(_:)`.
|
|
private var canDelete: Bool {
|
|
return !contentViewController.selectedItems.isEmpty
|
|
}
|
|
|
|
private var canShowInITunesStore: Bool {
|
|
return contentViewController.clickedItems.count == 1 && contentViewController.clickedItems.first is TagTunesItem
|
|
}
|
|
|
|
}
|
|
|