1
1
mirror of https://github.com/danbee/persephone synced 2025-03-04 08:39:11 +00:00

Add preference window with host and port

This commit is contained in:
Daniel Barber 2019-02-15 16:56:56 -05:00
parent 201c7effad
commit 84d209a967
Signed by: danbarber
GPG Key ID: 931D8112E0103DD8
11 changed files with 308 additions and 28 deletions

View File

@ -22,6 +22,10 @@
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; }; E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; };
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; }; E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; };
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */; };
E41EA46F221715910068EF46 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46E221715910068EF46 /* Preferences.swift */; };
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; };
E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; };
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; }; E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; };
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; }; E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
E4A642DA22090CBE00067D21 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* Status.swift */; }; E4A642DA22090CBE00067D21 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* Status.swift */; };
@ -122,6 +126,10 @@
E41B22E921FB966C00D544F6 /* capabilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = capabilities.h; sourceTree = "<group>"; }; E41B22E921FB966C00D544F6 /* capabilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = capabilities.h; sourceTree = "<group>"; };
E41B22EA21FB966C00D544F6 /* queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = "<group>"; }; E41B22EA21FB966C00D544F6 /* queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = "<group>"; };
E41B22EB21FB966C00D544F6 /* playlist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = playlist.h; sourceTree = "<group>"; }; E41B22EB21FB966C00D544F6 /* playlist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = playlist.h; sourceTree = "<group>"; };
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
E41EA46E221715910068EF46 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; }; E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; }; E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; };
E4A642D922090CBE00067D21 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; }; E4A642D922090CBE00067D21 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
@ -167,6 +175,8 @@
E40786382110CE70006887B1 /* PersephoneUITests */, E40786382110CE70006887B1 /* PersephoneUITests */,
E40786192110CE6E006887B1 /* Products */, E40786192110CE6E006887B1 /* Products */,
E41B22BE21FB6B3300D544F6 /* Frameworks */, E41B22BE21FB6B3300D544F6 /* Frameworks */,
E42A8F3922176D6400A13ED9 /* LICENSE.md */,
E42A8F3A22176D6400A13ED9 /* README.md */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
@ -184,6 +194,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E40FE717221B48CE00A4223F /* Layouts */, E40FE717221B48CE00A4223F /* Layouts */,
E41EA46D221715820068EF46 /* Models */,
E407861F2110CE70006887B1 /* Assets.xcassets */, E407861F2110CE70006887B1 /* Assets.xcassets */,
E408D3B7220DE8CC0006D9BE /* Extensions */, E408D3B7220DE8CC0006D9BE /* Extensions */,
E4D1B598220BA3C90026F233 /* Resources */, E4D1B598220BA3C90026F233 /* Resources */,
@ -309,6 +320,14 @@
path = mpd; path = mpd;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E41EA46D221715820068EF46 /* Models */ = {
isa = PBXGroup;
children = (
E41EA46E221715910068EF46 /* Preferences.swift */,
);
path = Models;
sourceTree = "<group>";
};
E4A642DB220912FA00067D21 /* MPDClient */ = { E4A642DB220912FA00067D21 /* MPDClient */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -343,6 +362,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */, E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */,
E4E8CC932206097F0024217A /* NotificationsController.swift */, E4E8CC932206097F0024217A /* NotificationsController.swift */,
E4E8CC912204F4B80024217A /* QueueViewController.swift */, E4E8CC912204F4B80024217A /* QueueViewController.swift */,
E465049921E94DF500A70F4C /* WindowController.swift */, E465049921E94DF500A70F4C /* WindowController.swift */,
@ -472,7 +492,9 @@
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */,
E40786202110CE70006887B1 /* Assets.xcassets in Resources */, E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
E42A8F3C22176D6400A13ED9 /* README.md in Resources */,
E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */, E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */,
E40786232110CE70006887B1 /* Main.storyboard in Resources */, E40786232110CE70006887B1 /* Main.storyboard in Resources */,
); );
@ -507,11 +529,13 @@
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */, E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */, E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */, E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */,
E41EA46F221715910068EF46 /* Preferences.swift in Sources */,
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */, E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */, E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */, E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
E4E8CC9A22075D370024217A /* Song.swift in Sources */, E4E8CC9A22075D370024217A /* Song.swift in Sources */,
E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */, E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */,
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */, E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */, E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,

View File

@ -10,15 +10,46 @@ import Cocoa
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate { class AppDelegate: NSObject, NSApplicationDelegate {
var preferences = Preferences()
static let mpdClient = MPDClient( static let mpdClient = MPDClient(
withDelegate: NotificationsController() withDelegate: NotificationsController()
) )
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
AppDelegate.mpdClient.connect() connect()
preferences.addObserver(self, forKeyPath: "mpdHost")
preferences.addObserver(self, forKeyPath: "mpdPort")
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_ aNotification: Notification) {
disconnect()
}
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?
) {
switch keyPath {
case "mpdHost", "mpdPort":
disconnect()
connect()
default:
break
}
}
func connect() {
AppDelegate.mpdClient.connect(
host: preferences.mpdHostOrDefault,
port: preferences.mpdPortOrDefault
)
}
func disconnect() {
AppDelegate.mpdClient.disconnect() AppDelegate.mpdClient.disconnect()
} }
} }

View File

@ -25,6 +25,13 @@ class AlbumViewController: NSViewController,
name: Notification.loadedAlbums, name: Notification.loadedAlbums,
object: AppDelegate.mpdClient object: AppDelegate.mpdClient
) )
NotificationCenter.default.addObserver(
self,
selector: #selector(clearAlbums(_:)),
name: Notification.willDisconnect,
object: AppDelegate.mpdClient
)
} }
override func viewWillLayout() { override func viewWillLayout() {
@ -42,6 +49,12 @@ class AlbumViewController: NSViewController,
albumCollectionView.reloadData() albumCollectionView.reloadData()
} }
@objc func clearAlbums(_ notification: Notification) {
self.albums = []
albumCollectionView.reloadData()
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return albums.count return albums.count
} }

View File

@ -10,7 +10,15 @@ import Foundation
class NotificationsController: MPDClientDelegate { class NotificationsController: MPDClientDelegate {
let notificationQueue = DispatchQueue.main let notificationQueue = DispatchQueue.main
func didConnect(mpdClient: MPDClient) {
sendNotification(name: Notification.didConnect)
}
func willDisconnect(mpdClient: MPDClient) {
sendNotification(name: Notification.willDisconnect)
}
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State) { func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State) {
sendNotification( sendNotification(
name: Notification.stateChanged, name: Notification.stateChanged,
@ -39,7 +47,7 @@ class NotificationsController: MPDClientDelegate {
) )
} }
private func sendNotification(name: Notification.Name, userInfo: [AnyHashable : Any]) { private func sendNotification(name: Notification.Name, userInfo: [AnyHashable : Any] = [:]) {
self.notificationQueue.async { self.notificationQueue.async {
NotificationCenter.default.post( NotificationCenter.default.post(
name: name, name: name,

View File

@ -0,0 +1,36 @@
//
// PreferencesViewController.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/14.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class PreferencesViewController: NSViewController {
var preferences = Preferences()
override func viewDidLoad() {
super.viewDidLoad()
if let mpdHost = preferences.mpdHost {
mpdHostField.stringValue = mpdHost
}
if let mpdPort = preferences.mpdPort {
mpdPortField.stringValue = "\(mpdPort)"
}
}
@IBAction func updateMpdHost(_ sender: NSTextField) {
preferences.mpdHost = sender.stringValue
}
@IBAction func updateMpdPort(_ sender: NSTextField) {
preferences.mpdPort = sender.integerValue
}
@IBOutlet var mpdHostField: NSTextField!
@IBOutlet var mpdPortField: NSTextField!
}

View File

@ -50,6 +50,13 @@ class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineV
name: Notification.queuePosChanged, name: Notification.queuePosChanged,
object: AppDelegate.mpdClient object: AppDelegate.mpdClient
) )
NotificationCenter.default.addObserver(
self,
selector: #selector(clearQueue(_:)),
name: Notification.willDisconnect,
object: AppDelegate.mpdClient
)
} }
@IBAction func playTrack(_ sender: Any) { @IBAction func playTrack(_ sender: Any) {
@ -95,6 +102,12 @@ class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineV
) )
} }
@objc func clearQueue(_ notification: Notification) {
self.queue = []
queueView.reloadData()
}
func setQueueIcon(_ state: MPDClient.Status.State) { func setQueueIcon(_ state: MPDClient.Status.State) {
switch state { switch state {
case .playing: case .playing:

View File

@ -9,6 +9,9 @@
import Foundation import Foundation
extension Notification { extension Notification {
static let didConnect = Notification.Name("MPDClientDidConnect")
static let willDisconnect = Notification.Name("MPDClientWillDisconnect")
static let stateChanged = Notification.Name("MPDClientStateChanged") static let stateChanged = Notification.Name("MPDClientStateChanged")
static let queueChanged = Notification.Name("MPDClientQueueChanged") static let queueChanged = Notification.Name("MPDClientQueueChanged")
static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged") static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged")

View File

@ -12,10 +12,8 @@ import mpdclient
class MPDClient { class MPDClient {
var delegate: MPDClientDelegate? var delegate: MPDClientDelegate?
let HOST = "localhost"
let PORT: UInt32 = 6600
private var connection: OpaquePointer? private var connection: OpaquePointer?
private var isConnected: Bool = false
private var status: Status? private var status: Status?
private var queue: [Song] = [] private var queue: [Song] = []
@ -46,30 +44,40 @@ class MPDClient {
self.delegate = delegate self.delegate = delegate
} }
func connect() { func connect(host: String, port: Int) {
guard let connection = mpd_connection_new(HOST, PORT, 0) commandQueue.async { [unowned self] in
else { return } guard let connection = mpd_connection_new(host, UInt32(port), 10000),
mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS
else { return }
guard let status = mpd_run_status(connection) self.isConnected = true
else { return }
self.connection = connection guard let status = mpd_run_status(connection)
self.status = Status(status) else { return }
fetchQueue() self.connection = connection
self.status = Status(status)
fetchAllAlbums() self.fetchQueue()
self.fetchAllAlbums()
self.idle()
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state) self.delegate?.didConnect(mpdClient: self)
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue) self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song) self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
idle() self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
}
} }
func disconnect() { func disconnect() {
guard isConnected else { return }
noIdle() noIdle()
commandQueue.async { [unowned self] in commandQueue.async { [unowned self] in
self.delegate?.willDisconnect(mpdClient: self)
mpd_connection_free(self.connection) mpd_connection_free(self.connection)
self.isConnected = false
} }
} }
@ -102,6 +110,8 @@ class MPDClient {
} }
func playTrack(queuePos: Int) { func playTrack(queuePos: Int) {
guard isConnected else { return }
noIdle() noIdle()
commandQueue.async { [unowned self] in commandQueue.async { [unowned self] in
mpd_run_play_pos(self.connection, UInt32(queuePos)) mpd_run_play_pos(self.connection, UInt32(queuePos))
@ -110,6 +120,8 @@ class MPDClient {
} }
func queueCommand(command: Command) { func queueCommand(command: Command) {
guard isConnected else { return }
noIdle() noIdle()
commandQueue.async { [unowned self] in commandQueue.async { [unowned self] in
self.sendCommand(command: command) self.sendCommand(command: command)
@ -236,15 +248,15 @@ class MPDClient {
} }
} }
func getLastErrorMessage() -> String! { func getLastErrorMessage() -> String? {
if mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS { if mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS {
return "no error" return nil
} }
if let errorMessage = mpd_connection_get_error_message(connection) { if let errorMessage = mpd_connection_get_error_message(connection) {
return String(cString: errorMessage) return String(cString: errorMessage)
} }
return "no error" return nil
} }
} }

View File

@ -9,6 +9,9 @@
import Foundation import Foundation
protocol MPDClientDelegate { protocol MPDClientDelegate {
func didConnect(mpdClient: MPDClient)
func willDisconnect(mpdClient: MPDClient)
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State) func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State)
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
func didUpdateQueuePos(mpdClient: MPDClient, song: Int) func didUpdateQueuePos(mpdClient: MPDClient, song: Int)

View File

@ -0,0 +1,47 @@
//
// Preferences.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/15.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
struct Preferences {
let preferences = UserDefaults.standard
var mpdHost: String? {
get {
return preferences.string(forKey: "mpdHost")
}
set {
preferences.set(newValue, forKey: "mpdHost")
}
}
var mpdPort: Int? {
get {
return preferences.value(forKey: "mpdPort") as? Int
}
set {
if (newValue.map { $0 > 0 } ?? false) {
preferences.set(newValue, forKey: "mpdPort")
} else {
preferences.removeObject(forKey: "mpdPort")
}
}
}
var mpdHostOrDefault: String {
return mpdHost ?? "127.0.0.1"
}
var mpdPortOrDefault: Int {
return mpdPort ?? 6600
}
func addObserver(_ observer: NSObject, forKeyPath keyPath: String) {
preferences.addObserver(observer, forKeyPath: keyPath, options: .new, context: nil)
}
}

View File

@ -24,7 +24,12 @@
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/> <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/> <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW">
<connections>
<action selector="preferencesItemSelected:" target="Voe-Tx-rLC" id="bxe-zW-cho"/>
<segue destination="xYu-7w-E5x" kind="show" id="OTW-56-v9E"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/> <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz"> <menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
@ -680,7 +685,7 @@
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="75" y="0.0"/> <point key="canvasLocation" x="115" y="-366"/>
</scene> </scene>
<!--Window Controller--> <!--Window Controller-->
<scene sceneID="R2V-B0-nI4"> <scene sceneID="R2V-B0-nI4">
@ -703,10 +708,10 @@
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="EBk-sD-nG7"> <segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="EBk-sD-nG7">
<font key="font" metaFont="system"/> <font key="font" metaFont="system"/>
<segments> <segments>
<segment image="prevTrackButton" width="32"/> <segment image="prevTrackButton" width="32" enabled="NO"/>
<segment image="playButton" width="48" tag="1"/> <segment image="playButton" width="48" enabled="NO" tag="1"/>
<segment image="stopButton" width="32"/> <segment image="stopButton" width="32" enabled="NO"/>
<segment image="nextTrackButton" width="32"/> <segment image="nextTrackButton" width="32" enabled="NO"/>
</segments> </segments>
<connections> <connections>
<action selector="handleTransportControl:" target="B8D-0N-5wS" id="HTN-5E-y75"/> <action selector="handleTransportControl:" target="B8D-0N-5wS" id="HTN-5E-y75"/>
@ -758,6 +763,91 @@
</objects> </objects>
<point key="canvasLocation" x="74" y="873"/> <point key="canvasLocation" x="74" y="873"/>
</scene> </scene>
<!--Window Controller-->
<scene sceneID="Rpk-bo-5kf">
<objects>
<windowController id="xYu-7w-E5x" sceneMemberID="viewController">
<window key="window" title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="3FN-my-6kU">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="245" y="301" width="416" height="100"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<connections>
<outlet property="delegate" destination="xYu-7w-E5x" id="gZw-NF-8zl"/>
</connections>
</window>
<connections>
<segue destination="nYi-sw-ZNp" kind="relationship" relationship="window.shadowedContentViewController" id="607-3F-gJf"/>
</connections>
</windowController>
<customObject id="0sd-8B-etN" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="915" y="-89"/>
</scene>
<!--Preferences View Controller-->
<scene sceneID="xTC-Y5-Agk">
<objects>
<viewController id="nYi-sw-ZNp" customClass="PreferencesViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="Uwt-Lw-ILP">
<rect key="frame" x="0.0" y="0.0" width="420" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wPm-sJ-e9E">
<rect key="frame" x="162" y="58" width="184" height="22"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" alignment="left" placeholderString="127.0.0.1" drawsBackground="YES" id="MSX-mn-2ma">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="updateMpdHost:" target="nYi-sw-ZNp" id="Y7x-N9-6ag"/>
</connections>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="IbX-oV-soD">
<rect key="frame" x="162" y="26" width="80" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="6600" drawsBackground="YES" id="i9j-nB-bqq">
<numberFormatter key="formatter" formatterBehavior="custom10_4" numberStyle="decimal" usesGroupingSeparator="NO" minimumIntegerDigits="1" maximumIntegerDigits="2000000000" maximumFractionDigits="3" id="UiQ-gi-Hbp">
<real key="minimum" value="0.0"/>
<real key="maximum" value="65535"/>
</numberFormatter>
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="updateMpdPort:" target="nYi-sw-ZNp" id="406-EC-aO2"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kvB-99-zwY">
<rect key="frame" x="76" y="62" width="80" height="17"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Host:" id="AVi-g9-Irz">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AU9-wN-kbU">
<rect key="frame" x="77" y="30" width="77" height="17"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Port:" id="DgA-xT-2ir">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<connections>
<outlet property="mpdHostField" destination="wPm-sJ-e9E" id="PR7-oL-tVQ"/>
<outlet property="mpdPortField" destination="IbX-oV-soD" id="VLG-DK-5N6"/>
</connections>
</viewController>
<customObject id="lzf-yO-5pP" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="917" y="217"/>
</scene>
<!--Queue View Controller--> <!--Queue View Controller-->
<scene sceneID="QcX-dC-cTZ"> <scene sceneID="QcX-dC-cTZ">
<objects> <objects>