Archived
1
This repository has been archived on 2020-06-04. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
tagtunes/TagTunes/MainViewController.swift
Kim Wittenburg 0a485ff42a Stuff…
2019-02-01 22:59:01 +01:00

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
}
}