From b709d6c98c3feaa24e5e025d94d019a17271c988 Mon Sep 17 00:00:00 2001 From: Dan Barber Date: Wed, 20 Feb 2019 18:47:38 -0500 Subject: [PATCH] Move queue datasource and refactor view controller --- Persephone.xcodeproj/project.pbxproj | 25 +- .../Controllers/QueueViewController.swift | 288 +++++++----------- Persephone/DataSources/QueueDataSource.swift | 70 +++++ Persephone/Models/SongItem.swift | 15 + .../Resources/Base.lproj/Main.storyboard | 1 - 5 files changed, 224 insertions(+), 175 deletions(-) create mode 100644 Persephone/DataSources/QueueDataSource.swift create mode 100644 Persephone/Models/SongItem.swift diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index c17c54d..2c4c0db 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -36,6 +36,8 @@ E4E8CC9A22075D370024217A /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* Song.swift */; }; E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB2378220F10B8008C70C0 /* Pair.swift */; }; E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB237A220F7CF1008C70C0 /* Album.swift */; }; + E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; }; + E4F6B463221E125900ACF42A /* SongItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* SongItem.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -141,6 +143,8 @@ E4E8CC9922075D370024217A /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = ""; }; E4EB2378220F10B8008C70C0 /* Pair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pair.swift; sourceTree = ""; }; E4EB237A220F7CF1008C70C0 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = ""; }; + E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = ""; }; + E4F6B462221E125900ACF42A /* SongItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongItem.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -196,7 +200,8 @@ isa = PBXGroup; children = ( E40FE717221B48CE00A4223F /* Layouts */, - E41EA46D221715820068EF46 /* Models */, + E4F6B461221E124700ACF42A /* Models */, + E4F6B45E221E117600ACF42A /* DataSources */, E407861F2110CE70006887B1 /* Assets.xcassets */, E408D3B7220DE8CC0006D9BE /* Extensions */, E4D1B598220BA3C90026F233 /* Resources */, @@ -382,6 +387,22 @@ path = Resources; sourceTree = ""; }; + E4F6B45E221E117600ACF42A /* DataSources */ = { + isa = PBXGroup; + children = ( + E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */, + ); + path = DataSources; + sourceTree = ""; + }; + E4F6B461221E124700ACF42A /* Models */ = { + isa = PBXGroup; + children = ( + E4F6B462221E125900ACF42A /* SongItem.swift */, + ); + path = Models; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -526,6 +547,7 @@ files = ( E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */, E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */, + E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */, E4A642DA22090CBE00067D21 /* Status.swift in Sources */, E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */, E40FE719221B48E300A4223F /* AlbumViewLayout.swift in Sources */, @@ -533,6 +555,7 @@ E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */, E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */, E41EA46F221715910068EF46 /* Preferences.swift in Sources */, + E4F6B463221E125900ACF42A /* SongItem.swift in Sources */, E465049A21E94DF500A70F4C /* WindowController.swift in Sources */, E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */, E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */, diff --git a/Persephone/Controllers/QueueViewController.swift b/Persephone/Controllers/QueueViewController.swift index 4cb3b1c..9320389 100644 --- a/Persephone/Controllers/QueueViewController.swift +++ b/Persephone/Controllers/QueueViewController.swift @@ -8,29 +8,131 @@ import Cocoa -class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate { - struct SongItem { - var song: MPDClient.Song - var queuePos: Int - var isPlaying: Bool - } - - var queue: [SongItem] = [] - var queuePos: Int = -1 - - var queueIcon: NSImage? = nil +class QueueViewController: NSViewController, + NSOutlineViewDelegate { + var dataSource = QueueDataSource() let systemFontRegular = NSFont.systemFont(ofSize: 13, weight: .regular) let systemFontBold = NSFont.systemFont(ofSize: 13, weight: .bold) - + let playIcon = NSImage(named: "playButton") let pauseIcon = NSImage(named: "pauseButton") + @IBOutlet var queueView: NSOutlineView! + override func viewDidLoad() { super.viewDidLoad() - queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle + setupNotificationObservers() + queueView.dataSource = dataSource + queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle + } + + @IBAction func playTrack(_ sender: Any) { + if dataSource.queuePos >= 0 { + AppDelegate.mpdClient.playTrack(queuePos: dataSource.queuePos) + } + } + + @objc func stateChanged(_ notification: Notification) { + guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State + else { return } + + dataSource.setQueueIcon(state) + } + + @objc func queueChanged(_ notification: Notification) { + guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.Song] + else { return } + + dataSource.updateQueue(queue) + queueView.reloadData() + } + + @objc func queuePosChanged(_ notification: Notification) { + guard let queuePos = notification.userInfo?[Notification.queuePosKey] as? Int + else { return } + + dataSource.setQueuePos(queuePos) + + queueView.reloadData() + } + + func outlineView( + _ outlineView: NSOutlineView, + selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet + ) -> IndexSet { + if proposedSelectionIndexes.contains(0) { + return IndexSet() + } else { + return proposedSelectionIndexes + } + } + + func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { + if let songItem = item as? SongItem { + switch tableColumn?.identifier.rawValue { + case "songTitleColumn": + return cellForSongTitle(outlineView, with: songItem) + case "songArtistColumn": + return cellForSongArtist(outlineView, with: songItem) + default: + return nil + } + } else if tableColumn?.identifier.rawValue == "songTitleColumn" { + return cellForQueueHeading(outlineView) + } else { + return nil + } + } + + func cellForSongTitle(_ outlineView: NSOutlineView, with songItem: SongItem) -> NSView { + let cellView = outlineView.makeView( + withIdentifier: .queueSongTitle, + owner: self + ) as! NSTableCellView + + cellView.textField?.stringValue = songItem.song.getTag(.title) + if songItem.isPlaying { + cellView.textField?.font = systemFontBold + cellView.imageView?.image = dataSource.queueIcon + } else { + cellView.textField?.font = systemFontRegular + cellView.imageView?.image = nil + } + + return cellView + } + + func cellForSongArtist(_ outlineView: NSOutlineView, with songItem: SongItem) -> NSView { + let cellView = outlineView.makeView( + withIdentifier: .queueSongArtist, + owner: self + ) as! NSTableCellView + + cellView.textField?.stringValue = songItem.song.getTag(.artist) + if songItem.isPlaying { + cellView.textField?.font = systemFontBold + } else { + cellView.textField?.font = systemFontRegular + } + + return cellView + } + + func cellForQueueHeading(_ outlineView: NSOutlineView) -> NSView { + let cellView = outlineView.makeView( + withIdentifier: .queueHeading, + owner: self + ) as! NSTableCellView + + cellView.textField?.stringValue = "QUEUE" + + return cellView + } + + func setupNotificationObservers() { NotificationCenter.default.addObserver( self, selector: #selector(stateChanged(_:)), @@ -51,165 +153,5 @@ class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineV name: Notification.queuePosChanged, object: AppDelegate.mpdClient ) - - NotificationCenter.default.addObserver( - self, - selector: #selector(clearQueue(_:)), - name: Notification.willDisconnect, - object: AppDelegate.mpdClient - ) } - - @IBAction func playTrack(_ sender: Any) { - guard let view = sender as? NSOutlineView - else { return } - - let queuePos = view.selectedRow - 1 - - if queuePos >= 0 { - AppDelegate.mpdClient.playTrack(queuePos: queuePos) - } - } - - @objc func stateChanged(_ notification: Notification) { - guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.Status.State - else { return } - - setQueueIcon(state) - } - - @objc func queueChanged(_ notification: Notification) { - guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.Song] - else { return } - - var newQueue: [SongItem] = [] - - for (index, mpdSong) in queue.enumerated() { - let songItem = SongItem(song: mpdSong, queuePos: index, isPlaying: index == queuePos) - newQueue.append(songItem) - } - - self.queue = newQueue - queueView.reloadData() - } - - @objc func queuePosChanged(_ notification: Notification) { - guard let queuePos = notification.userInfo?[Notification.queuePosKey] as? Int - else { return } - - let oldSongRowPos = self.queuePos - let newSongRowPos = queuePos - self.queuePos = queuePos - - setQueuePos(oldSongRowPos: oldSongRowPos, newSongRowPos: newSongRowPos) - - queueView.reloadData() - } - - @objc func clearQueue(_ notification: Notification) { - self.queue = [] - - queueView.reloadData() - } - - func setQueueIcon(_ state: MPDClient.Status.State) { - switch state { - case .playing: - self.queueIcon = playIcon - case .paused: - self.queueIcon = pauseIcon - default: - self.queueIcon = nil - } - } - - func setQueuePos(oldSongRowPos: Int, newSongRowPos: Int) { - if oldSongRowPos >= 0 { - queue[oldSongRowPos].isPlaying = false - } - if newSongRowPos >= 0 { - queue[newSongRowPos].isPlaying = true - } - } - - func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { - return queue.count + 1 - } - - func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { - return false - } - - func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { - if index > 0 { - return queue[index - 1] - } else { - return false - } - } - - func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { - if let songItem = item as? SongItem { - switch tableColumn?.identifier.rawValue { - case "songTitleColumn": - let cellView = outlineView.makeView( - withIdentifier: .queueSongTitle, - owner: self - ) as! NSTableCellView - - cellView.textField?.stringValue = songItem.song.getTag(.title) - if songItem.isPlaying { - cellView.textField?.font = systemFontBold - cellView.imageView?.image = self.queueIcon - } else { - cellView.textField?.font = systemFontRegular - cellView.imageView?.image = nil - } - - return cellView - case "songArtistColumn": - let cellView = outlineView.makeView( - withIdentifier: .queueSongArtist, - owner: self - ) as! NSTableCellView - - cellView.textField?.stringValue = songItem.song.getTag(.artist) - if songItem.isPlaying { - cellView.textField?.font = systemFontBold - } else { - cellView.textField?.font = systemFontRegular - } - - return cellView - default: - return nil - } - } else { - if tableColumn?.identifier.rawValue == "songTitleColumn" { - let cellView = outlineView.makeView( - withIdentifier: .queueHeading, - owner: self - ) as! NSTableCellView - - cellView.textField?.stringValue = "QUEUE" - - return cellView - } else { - return nil - } - } - } - - func outlineView( - _ outlineView: NSOutlineView, - selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet - ) -> IndexSet { - if proposedSelectionIndexes.contains(0) { - return IndexSet() - } else { - return proposedSelectionIndexes - } - } - - @IBOutlet var queueView: NSOutlineView! } diff --git a/Persephone/DataSources/QueueDataSource.swift b/Persephone/DataSources/QueueDataSource.swift new file mode 100644 index 0000000..3313054 --- /dev/null +++ b/Persephone/DataSources/QueueDataSource.swift @@ -0,0 +1,70 @@ +// +// QueueDataSource.swift +// Persephone +// +// Created by Daniel Barber on 2019/2/20. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Cocoa + +class QueueDataSource: NSObject, NSOutlineViewDataSource { + var queue: [SongItem] = [] + var queuePos: Int = -1 + + var queueIcon: NSImage? = nil + + let playIcon = NSImage(named: "playButton") + let pauseIcon = NSImage(named: "pauseButton") + + func updateQueue(_ queue: [MPDClient.Song]) { + var newQueue: [SongItem] = [] + + for (index, mpdSong) in queue.enumerated() { + let songItem = SongItem(song: mpdSong, queuePos: index, isPlaying: index == queuePos) + newQueue.append(songItem) + } + + self.queue = newQueue + } + + func setQueuePos(_ queuePos: Int) { + let oldSongRowPos = self.queuePos + let newSongRowPos = queuePos + self.queuePos = queuePos + + if oldSongRowPos >= 0 { + queue[oldSongRowPos].isPlaying = false + } + if newSongRowPos >= 0 { + queue[newSongRowPos].isPlaying = true + } + } + + func setQueueIcon(_ state: MPDClient.Status.State) { + switch state { + case .playing: + queueIcon = playIcon + case .paused: + queueIcon = pauseIcon + default: + queueIcon = nil + } + } + + func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { + return queue.count + 1 + } + + func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { + return false + } + + func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { + if index > 0 { + return queue[index - 1] + } else { + return false + } + } +} diff --git a/Persephone/Models/SongItem.swift b/Persephone/Models/SongItem.swift new file mode 100644 index 0000000..f9ef949 --- /dev/null +++ b/Persephone/Models/SongItem.swift @@ -0,0 +1,15 @@ +// +// SongItem.swift +// Persephone +// +// Created by Daniel Barber on 2019/2/20. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Foundation + +struct SongItem { + var song: MPDClient.Song + var queuePos: Int + var isPlaying: Bool +} diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Resources/Base.lproj/Main.storyboard index 38f834d..dce8076 100644 --- a/Persephone/Resources/Base.lproj/Main.storyboard +++ b/Persephone/Resources/Base.lproj/Main.storyboard @@ -972,7 +972,6 @@ -