From cd094c3a646955aa9cf70be11542feddd6ad8758 Mon Sep 17 00:00:00 2001 From: Daniel Barber Date: Fri, 7 Jun 2019 11:19:26 -0400 Subject: [PATCH] Implement top level song menu --- Persephone.xcodeproj/project.pbxproj | 4 + Persephone/AppDelegate.swift | 27 ++++- .../AlbumDetailView+NSTableViewDelegate.swift | 102 ++++++++++++++++++ Persephone/Controllers/AlbumDetailView.swift | 84 +-------------- .../Resources/Base.lproj/Main.storyboard | 20 ++++ Persephone/State/Actions/UIActions.swift | 4 + Persephone/State/Reducers/UIReducer.swift | 3 + Persephone/State/UIState.swift | 2 + 8 files changed, 163 insertions(+), 83 deletions(-) create mode 100644 Persephone/Controllers/AlbumDetailView+NSTableViewDelegate.swift diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index cd977d9..c32fa05 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -111,6 +111,7 @@ E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BC12275EE410075461B /* AlbumListActions.swift */; }; E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; }; E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; }; + E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */; }; E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; }; E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; }; E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* MPDSong.swift */; }; @@ -314,6 +315,7 @@ E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = ""; }; E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; }; E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = ""; }; + E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumDetailView+NSTableViewDelegate.swift"; sourceTree = ""; }; E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = ""; }; E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = ""; }; E4E8CC9922075D370024217A /* MPDSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDSong.swift; sourceTree = ""; }; @@ -683,6 +685,7 @@ isa = PBXGroup; children = ( E43B67A822909793007DCF55 /* AlbumDetailView.swift */, + E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */, E408D3C1220E134F0006D9BE /* AlbumViewController.swift */, E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */, E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */, @@ -940,6 +943,7 @@ E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */, E440519E227BB0720090CD6F /* UIReducer.swift in Sources */, E43B67AA22909793007DCF55 /* AlbumDetailView.swift in Sources */, + E4E7A6AD22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift in Sources */, E4FF7190227601B400D4C412 /* PreferencesReducer.swift in Sources */, E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */, E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */, diff --git a/Persephone/AppDelegate.swift b/Persephone/AppDelegate.swift index dd321b4..c828494 100644 --- a/Persephone/AppDelegate.swift +++ b/Persephone/AppDelegate.swift @@ -16,6 +16,11 @@ class AppDelegate: NSObject, MediaKeyTapDelegate { var mediaKeyTap: MediaKeyTap? + @IBOutlet weak var mainWindowMenuItem: NSMenuItem! + @IBOutlet weak var updateDatabaseMenuItem: NSMenuItem! + @IBOutlet weak var playSelectedSongMenuItem: NSMenuItem! + @IBOutlet weak var addSelectedSongToQueueMenuItem: NSMenuItem! + func applicationDidFinishLaunching(_ aNotification: Notification) { App.mpdServerController.connect() instantiateUserNotificationsController() @@ -97,6 +102,11 @@ class AppDelegate: NSObject, } } + func setSongMenuItemsState(selectedSong: Song?) { + playSelectedSongMenuItem.isEnabled = selectedSong != nil + addSelectedSongToQueueMenuItem.isEnabled = selectedSong != nil + } + func handle(mediaKey: MediaKey, event: KeyEvent) { switch mediaKey { case .playPause: @@ -125,8 +135,20 @@ class AppDelegate: NSObject, App.store.dispatch(MPDPrevTrackAction()) } - @IBOutlet weak var mainWindowMenuItem: NSMenuItem! - @IBOutlet weak var updateDatabaseMenuItem: NSMenuItem! + @IBAction func playSelectedSongAction(_ sender: NSMenuItem) { + guard let song = App.store.state.uiState.selectedSong + else { return } + + let queueLength = App.store.state.queueState.queue.count + App.store.dispatch(MPDAppendTrack(song: song.mpdSong)) + App.store.dispatch(MPDPlayTrack(queuePos: queueLength)) + } + @IBAction func addSelectedSongToQueueAction(_ sender: NSMenuItem) { + guard let song = App.store.state.uiState.selectedSong + else { return } + + App.store.dispatch(MPDAppendTrack(song: song.mpdSong)) + } } extension AppDelegate: StoreSubscriber { @@ -135,5 +157,6 @@ extension AppDelegate: StoreSubscriber { func newState(state: UIState) { updateDatabaseMenuItem.isEnabled = !state.databaseUpdating setMainWindowStateMenuItem(state: state.mainWindowState) + setSongMenuItemsState(selectedSong: state.selectedSong) } } diff --git a/Persephone/Controllers/AlbumDetailView+NSTableViewDelegate.swift b/Persephone/Controllers/AlbumDetailView+NSTableViewDelegate.swift new file mode 100644 index 0000000..162c73d --- /dev/null +++ b/Persephone/Controllers/AlbumDetailView+NSTableViewDelegate.swift @@ -0,0 +1,102 @@ +// +// AlbumDetailView+NSTableViewDelegate.swift +// Persephone +// +// Created by Daniel Barber on 2019/6/07. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import AppKit + +extension AlbumDetailView: NSTableViewDelegate { + func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { + if let song = dataSource.albumSongs[row].song { + switch tableColumn?.identifier.rawValue { + case "trackNumberColumn": + return cellForTrackNumber(tableView, with: song) + case "trackTitleColumn": + return cellForSongTitle(tableView, with: song) + case "trackDurationColumn": + return cellForSongDuration(tableView, with: song) + default: + return nil + } + } else if let disc = dataSource.albumSongs[row].disc { + return cellForDiscNumber(tableView, with: disc) + } + + return nil + } + + func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool { + return dataSource.albumSongs[row].disc != nil + } + + func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { + let view = AlbumDetailSongRowView() + + return view + } + + func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { + return dataSource.albumSongs[row].disc == nil + } + + func tableViewSelectionDidChange(_ notification: Notification) { + guard let tableView = notification.object as? NSTableView + else { return } + + if tableView.selectedRow >= 0 { + let song = dataSource.albumSongs[tableView.selectedRow].song + + App.store.dispatch(SetSelectedSong(selectedSong: song)) + } else { + App.store.dispatch(SetSelectedSong(selectedSong: nil)) + } + } + + func cellForDiscNumber(_ tableView: NSTableView, with disc: String) -> NSView { + let cellView = tableView.makeView( + withIdentifier: .discNumber, + owner: self + ) as! NSTableCellView + + cellView.textField?.stringValue = "Disc \(disc)" + + return cellView + } + + func cellForTrackNumber(_ tableView: NSTableView, with song: Song) -> NSView { + let cellView = tableView.makeView( + withIdentifier: .trackNumber, + owner: self + ) as! NSTableCellView + + cellView.textField?.stringValue = "\(song.trackNumber)." + + return cellView + } + + func cellForSongTitle(_ tableView: NSTableView, with song: Song) -> NSView { + let cellView = tableView.makeView( + withIdentifier: .songTitle, + owner: self + ) as! NSTableCellView + + cellView.textField?.stringValue = song.title + + return cellView + } + + func cellForSongDuration(_ tableView: NSTableView, with song: Song) -> NSView { + let cellView = tableView.makeView( + withIdentifier: .songDuration, + owner: self + ) as! NSTableCellView + + cellView.textField?.font = .timerFont + cellView.textField?.stringValue = song.duration.formattedTime + + return cellView + } +} diff --git a/Persephone/Controllers/AlbumDetailView.swift b/Persephone/Controllers/AlbumDetailView.swift index 6ec3667..54a82b1 100644 --- a/Persephone/Controllers/AlbumDetailView.swift +++ b/Persephone/Controllers/AlbumDetailView.swift @@ -6,7 +6,7 @@ // Copyright © 2019 Dan Barber. All rights reserved. // -import Cocoa +import AppKit class AlbumDetailView: NSViewController { var album: Album? @@ -60,6 +60,8 @@ class AlbumDetailView: NSViewController { albumTitle.stringValue = "" albumArtist.stringValue = "" albumCoverView.image = .defaultCoverArt + + App.store.dispatch(SetSelectedSong(selectedSong: nil)) } @IBAction func playAlbum(_ sender: NSButton) { @@ -140,83 +142,3 @@ class AlbumDetailView: NSViewController { self.album = album } } - -extension AlbumDetailView: NSTableViewDelegate { - func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - if let song = dataSource.albumSongs[row].song { - switch tableColumn?.identifier.rawValue { - case "trackNumberColumn": - return cellForTrackNumber(tableView, with: song) - case "trackTitleColumn": - return cellForSongTitle(tableView, with: song) - case "trackDurationColumn": - return cellForSongDuration(tableView, with: song) - default: - return nil - } - } else if let disc = dataSource.albumSongs[row].disc { - return cellForDiscNumber(tableView, with: disc) - } - - return nil - } - - func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool { - return dataSource.albumSongs[row].disc != nil - } - - func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { - let view = AlbumDetailSongRowView() - - return view - } - - func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { - return dataSource.albumSongs[row].disc == nil - } - - func cellForDiscNumber(_ tableView: NSTableView, with disc: String) -> NSView { - let cellView = tableView.makeView( - withIdentifier: .discNumber, - owner: self - ) as! NSTableCellView - - cellView.textField?.stringValue = "Disc \(disc)" - - return cellView - } - - func cellForTrackNumber(_ tableView: NSTableView, with song: Song) -> NSView { - let cellView = tableView.makeView( - withIdentifier: .trackNumber, - owner: self - ) as! NSTableCellView - - cellView.textField?.stringValue = "\(song.trackNumber)." - - return cellView - } - - func cellForSongTitle(_ tableView: NSTableView, with song: Song) -> NSView { - let cellView = tableView.makeView( - withIdentifier: .songTitle, - owner: self - ) as! NSTableCellView - - cellView.textField?.stringValue = song.title - - return cellView - } - - func cellForSongDuration(_ tableView: NSTableView, with song: Song) -> NSView { - let cellView = tableView.makeView( - withIdentifier: .songDuration, - owner: self - ) as! NSTableCellView - - cellView.textField?.font = .timerFont - cellView.textField?.stringValue = song.duration.formattedTime - - return cellView - } -} diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Resources/Base.lproj/Main.storyboard index 87d1b04..572256c 100644 --- a/Persephone/Resources/Base.lproj/Main.storyboard +++ b/Persephone/Resources/Base.lproj/Main.storyboard @@ -75,6 +75,24 @@ + + + + + + + + + + + + + + + + + + @@ -132,7 +150,9 @@ + + diff --git a/Persephone/State/Actions/UIActions.swift b/Persephone/State/Actions/UIActions.swift index 39702ba..16db011 100644 --- a/Persephone/State/Actions/UIActions.swift +++ b/Persephone/State/Actions/UIActions.swift @@ -17,3 +17,7 @@ struct MainWindowDidMinimizeAction: Action {} struct DatabaseUpdateStartedAction: Action {} struct DatabaseUpdateFinishedAction: Action {} + +struct SetSelectedSong: Action { + let selectedSong: Song? +} diff --git a/Persephone/State/Reducers/UIReducer.swift b/Persephone/State/Reducers/UIReducer.swift index d767c07..3011e98 100644 --- a/Persephone/State/Reducers/UIReducer.swift +++ b/Persephone/State/Reducers/UIReducer.swift @@ -27,6 +27,9 @@ func uiReducer(action: Action, state: UIState?) -> UIState { case is DatabaseUpdateFinishedAction: state.databaseUpdating = false + case let action as SetSelectedSong: + state.selectedSong = action.selectedSong + default: break } diff --git a/Persephone/State/UIState.swift b/Persephone/State/UIState.swift index 04aa518..46f436d 100644 --- a/Persephone/State/UIState.swift +++ b/Persephone/State/UIState.swift @@ -18,4 +18,6 @@ struct UIState: StateType { var mainWindowState: MainWindowState = .closed var databaseUpdating: Bool = false + + var selectedSong: Song? }