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

Can drag an album onto the queue

This commit is contained in:
Daniel Barber 2019-07-19 12:02:37 -04:00
parent 23a1a2e8cf
commit 53b759b723
Signed by: danbarber
GPG Key ID: 931D8112E0103DD8
11 changed files with 145 additions and 60 deletions

View File

@ -35,6 +35,8 @@
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; }; E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; };
E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E423563F228623D2001216D6 /* QueueSongTitleView.swift */; }; E4235640228623D2001216D6 /* QueueSongTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E423563F228623D2001216D6 /* QueueSongTitleView.swift */; };
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; }; E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; };
E42A4D4F22E20D7D001C6CAD /* MPDTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42A4D4E22E20D7D001C6CAD /* MPDTag.swift */; };
E42A4D5122E2167E001C6CAD /* MPDClient+Songs.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */; };
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; }; E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; };
E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; }; E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; };
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; }; E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; };
@ -233,6 +235,8 @@
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; }; E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; };
E423563F228623D2001216D6 /* QueueSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongTitleView.swift; sourceTree = "<group>"; }; E423563F228623D2001216D6 /* QueueSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongTitleView.swift; sourceTree = "<group>"; };
E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; }; E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; };
E42A4D4E22E20D7D001C6CAD /* MPDTag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDTag.swift; sourceTree = "<group>"; };
E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Songs.swift"; sourceTree = "<group>"; };
E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; 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>"; }; E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = "<group>"; }; E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = "<group>"; };
@ -438,6 +442,7 @@
E41E5304223BFB0700173814 /* MPDClient+Error.swift */, E41E5304223BFB0700173814 /* MPDClient+Error.swift */,
E41E5302223BF9C300173814 /* MPDClient+Idle.swift */, E41E5302223BF9C300173814 /* MPDClient+Idle.swift */,
E41E5300223BF99300173814 /* MPDClient+Queue.swift */, E41E5300223BF99300173814 /* MPDClient+Queue.swift */,
E42A4D5022E2167E001C6CAD /* MPDClient+Songs.swift */,
E41E5306223C019100173814 /* MPDClient+Status.swift */, E41E5306223C019100173814 /* MPDClient+Status.swift */,
E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */, E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */,
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */, E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */,
@ -635,6 +640,7 @@
E4EB2378220F10B8008C70C0 /* MPDPair.swift */, E4EB2378220F10B8008C70C0 /* MPDPair.swift */,
E4E8CC9922075D370024217A /* MPDSong.swift */, E4E8CC9922075D370024217A /* MPDSong.swift */,
E4A642D922090CBE00067D21 /* MPDStatus.swift */, E4A642D922090CBE00067D21 /* MPDStatus.swift */,
E42A4D4E22E20D7D001C6CAD /* MPDTag.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -897,6 +903,7 @@
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */, E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */,
E4B11BA92274EDE30075461B /* Loading.swift in Sources */, E4B11BA92274EDE30075461B /* Loading.swift in Sources */,
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */, E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */,
E42A4D4F22E20D7D001C6CAD /* MPDTag.swift in Sources */,
E43AC1F522C6A4F4001E483C /* DraggedAlbum.swift in Sources */, E43AC1F522C6A4F4001E483C /* DraggedAlbum.swift in Sources */,
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */, E40FE71B221B904300A4223F /* NSEvent.swift in Sources */,
E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */, E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */,
@ -984,6 +991,7 @@
E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */, E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */,
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */, E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */,
E489E39D22B9CF0000CA8CBD /* NSView.swift in Sources */, E489E39D22B9CF0000CA8CBD /* NSView.swift in Sources */,
E42A4D5122E2167E001C6CAD /* MPDClient+Songs.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@ -11,7 +11,7 @@ import AppKit
extension AlbumViewController: NSCollectionViewDelegate { extension AlbumViewController: NSCollectionViewDelegate {
func registerForDragAndDrop(_ collectionView: NSCollectionView) { func registerForDragAndDrop(_ collectionView: NSCollectionView) {
collectionView.registerForDraggedTypes([.albumPasteboardType]) collectionView.registerForDraggedTypes([.albumPasteboardType])
collectionView.setDraggingSourceOperationMask(.every, forLocal: true) collectionView.setDraggingSourceOperationMask(.copy, forLocal: true)
} }
func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt index: Int) -> NSPasteboardWriting? { func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt index: Int) -> NSPasteboardWriting? {

View File

@ -24,7 +24,7 @@ class QueueViewController: NSViewController {
queueView.dataSource = dataSource queueView.dataSource = dataSource
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
queueView.registerForDraggedTypes([.songPasteboardType]) queueView.registerForDraggedTypes([.songPasteboardType, .albumPasteboardType])
queueView.draggingDestinationFeedbackStyle = .regular queueView.draggingDestinationFeedbackStyle = .regular
} }

View File

@ -57,47 +57,68 @@ class QueueDataSource: NSObject, NSOutlineViewDataSource {
var newQueuePos = index - 1 var newQueuePos = index - 1
guard newQueuePos >= 0, guard newQueuePos >= 0,
let draggingTypes = info.draggingPasteboard.types, let draggingTypes = info.draggingPasteboard.types
draggingTypes.contains(.songPasteboardType),
let pasteboardItem = info.draggingPasteboard.pasteboardItems?.first,
let draggedSong = pasteboardItem.draggedSong(forType: .songPasteboardType)
else { return [] } else { return [] }
switch draggedSong.type { if draggingTypes.contains(.songPasteboardType) {
case let .queueItem(queuePos): guard let pasteboardItem = info.draggingPasteboard.pasteboardItems?.first,
if newQueuePos > queuePos { newQueuePos -= 1 } let draggedSong = pasteboardItem.draggedSong(forType: .songPasteboardType)
guard queuePos != newQueuePos
else { return [] } else { return [] }
return .move switch draggedSong.type {
case .albumSongItem: case let .queueItem(queuePos):
if newQueuePos > queuePos { newQueuePos -= 1 }
guard queuePos != newQueuePos
else { return [] }
return .move
case .albumSongItem:
return .copy
}
} else if draggingTypes.contains(.albumPasteboardType) {
return .copy return .copy
} }
return []
} }
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool { func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
var newQueuePos = index - 1 var newQueuePos = index - 1
guard let draggingTypes = info.draggingPasteboard.types, guard let draggingTypes = info.draggingPasteboard.types
draggingTypes.contains(.songPasteboardType),
let data = info.draggingPasteboard.data(forType: .songPasteboardType),
let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data)
else { return false } else { return false }
switch draggedSong.type { if draggingTypes.contains(.songPasteboardType) {
case let .queueItem(queuePos): guard let data = info.draggingPasteboard.data(forType: .songPasteboardType),
if newQueuePos > queuePos { newQueuePos -= 1 } let draggedSong = try? PropertyListDecoder().decode(DraggedSong.self, from: data)
guard queuePos != newQueuePos
else { return false } else { return false }
App.mpdClient.moveSongInQueue(at: queuePos, to: newQueuePos) switch draggedSong.type {
return true case let .queueItem(queuePos):
case let .albumSongItem(uri): if newQueuePos > queuePos { newQueuePos -= 1 }
App.mpdClient.addSongToQueue(songUri: uri, at: newQueuePos)
guard queuePos != newQueuePos
else { return false }
App.mpdClient.moveSongInQueue(at: queuePos, to: newQueuePos)
return true
case let .albumSongItem(uri):
App.mpdClient.addSongToQueue(songUri: uri, at: newQueuePos)
return true
}
} else if draggingTypes.contains(.albumPasteboardType) {
guard let data = info.draggingPasteboard.data(forType: .albumPasteboardType),
let draggedAlbum = try? PropertyListDecoder().decode(DraggedAlbum.self, from: data)
else { return false }
let mpdAlbum = MPDClient.MPDAlbum(title: draggedAlbum.title, artist: draggedAlbum.artist)
App.mpdClient.addAlbumToQueue(album: mpdAlbum, at: newQueuePos)
return true return true
} }
return false
} }
func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) { func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {

View File

@ -101,16 +101,7 @@ extension MPDClient {
func albumSongs(for album: MPDAlbum, callback: ([MPDSong]) -> Void) { func albumSongs(for album: MPDAlbum, callback: ([MPDSong]) -> Void) {
guard isConnected else { return } guard isConnected else { return }
var songs: [MPDSong] = [] let songs = searchSongs([MPDTag.album: album.title, MPDTag.artist: album.artist])
mpd_search_db_songs(self.connection, true)
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title)
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
mpd_search_commit(self.connection)
while let song = mpd_recv_song(self.connection) {
songs.append(MPDSong(song))
}
callback(songs) callback(songs)
} }

View File

@ -81,6 +81,12 @@ extension MPDClient {
else { return } else { return }
sendAddSongToQueue(uri: songUri, at: queuePos) sendAddSongToQueue(uri: songUri, at: queuePos)
case .addAlbumToQueue:
guard let album = userData["album"] as? MPDAlbum,
let queuePos = userData["queuePos"] as? Int
else { return }
sendAddAlbumToQueue(album: album, at: queuePos)
// Album commands // Album commands
case .fetchAllAlbums: case .fetchAllAlbums:
allAlbums() allAlbums()

View File

@ -38,6 +38,10 @@ extension MPDClient {
enqueueCommand(command: .addSongToQueue, userData: ["uri": songUri, "queuePos": queuePos]) enqueueCommand(command: .addSongToQueue, userData: ["uri": songUri, "queuePos": queuePos])
} }
func addAlbumToQueue(album: MPDAlbum, at queuePos: Int) {
enqueueCommand(command: .addAlbumToQueue, userData: ["album": album, "queuePos": queuePos])
}
func sendPlayTrack(at queuePos: Int) { func sendPlayTrack(at queuePos: Int) {
mpd_run_play_pos(self.connection, UInt32(queuePos)) mpd_run_play_pos(self.connection, UInt32(queuePos))
} }
@ -80,4 +84,16 @@ extension MPDClient {
func sendAddSongToQueue(uri: String, at queuePos: Int) { func sendAddSongToQueue(uri: String, at queuePos: Int) {
mpd_run_add_id_to(self.connection, uri, UInt32(queuePos)) mpd_run_add_id_to(self.connection, uri, UInt32(queuePos))
} }
func sendAddAlbumToQueue(album: MPDAlbum, at queuePos: Int) {
let songs = searchSongs([MPDTag.album: album.title, MPDTag.artist: album.artist])
var insertPos = UInt32(queuePos)
for song in songs {
mpd_run_add_id_to(self.connection, song.uri, insertPos)
insertPos += 1
}
}
} }

View File

@ -0,0 +1,28 @@
//
// MPDClient+Songs.swift
// Persephone
//
// Created by Daniel Barber on 2019/7/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import mpdclient
extension MPDClient {
func searchSongs(_ terms: [MPDClient.MPDTag: String]) -> [MPDSong] {
var songs: [MPDSong] = []
mpd_search_db_songs(self.connection, true)
for (tag, term) in terms {
mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, tag.mpdTag(), term)
}
mpd_search_commit(self.connection)
while let song = mpd_recv_song(self.connection) {
songs.append(MPDSong(song))
}
return songs
}
}

View File

@ -35,6 +35,7 @@ extension MPDClient {
case removeSong case removeSong
case moveSongInQueue case moveSongInQueue
case addSongToQueue case addSongToQueue
case addAlbumToQueue
// Album commands // Album commands
case fetchAllAlbums case fetchAllAlbums

View File

@ -13,26 +13,6 @@ extension MPDClient {
class MPDSong { class MPDSong {
let song: OpaquePointer let song: OpaquePointer
enum TagType: Int {
case unknown = -1
case artist, album, albumArtist, title, track, name,
genre, date, composer, performer, comment, disc
case musicBrainzArtistId
case musicBrainzAlbumId
case musicBrainzAlbumArtistId
case musicBrainzTrackId
case musicBrainzReleaseTrackId
case originalDate
case artistSort
case albumArtistSort
case albumSort
case tagCount
}
init(_ song: OpaquePointer) { init(_ song: OpaquePointer) {
self.song = song self.song = song
} }
@ -68,10 +48,8 @@ extension MPDClient {
} }
} }
func getTag(_ tagType: TagType) -> String { func getTag(_ tagType: MPDTag) -> String {
let mpdTagType = mpd_tag_type(rawValue: Int32(tagType.rawValue)) guard let tag = mpd_song_get_tag(song, tagType.mpdTag(), 0)
guard let tag = mpd_song_get_tag(song, mpdTagType, 0)
else { return "" } else { return "" }
return String(cString: tag) return String(cString: tag)

View File

@ -0,0 +1,36 @@
//
// MPDTag.swift
// Persephone
//
// Created by Daniel Barber on 2019/7/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import mpdclient
extension MPDClient {
enum MPDTag: Int {
case unknown = -1
case artist, album, albumArtist, title, track, name,
genre, date, composer, performer, comment, disc
case musicBrainzArtistId
case musicBrainzAlbumId
case musicBrainzAlbumArtistId
case musicBrainzTrackId
case musicBrainzReleaseTrackId
case originalDate
case artistSort
case albumArtistSort
case albumSort
case tagCount
func mpdTag() -> mpd_tag_type {
return mpd_tag_type(rawValue: Int32(self.rawValue))
}
}
}