diff --git a/Submariner/PasteboardType+Submariner.swift b/Submariner/PasteboardType+Submariner.swift index 8a90437..ecc84a9 100644 --- a/Submariner/PasteboardType+Submariner.swift +++ b/Submariner/PasteboardType+Submariner.swift @@ -20,8 +20,6 @@ extension NSPasteboard.PasteboardType { /// /// Usually multiple of these will exist on a pasteboard. static let libraryItem = NSPasteboard.PasteboardType(rawValue: "com.submarinerapp.item-url-string") - /// Tracks from the tracklist, represented as an index set. - static let tracklistIndices = NSPasteboard.PasteboardType(rawValue: "com.submarinerapp.tracklist-indices") /// The index of a row, as a raw integer in a Data wrapper. static let rowIndex = NSPasteboard.PasteboardType(rawValue: "com.submarinerapp.row-index") } diff --git a/Submariner/SBPlaylist.swift b/Submariner/SBPlaylist.swift index 31a8ce4..abadcbf 100644 --- a/Submariner/SBPlaylist.swift +++ b/Submariner/SBPlaylist.swift @@ -52,6 +52,11 @@ public class SBPlaylist: SBResource { trackIDs?.append(contentsOf: additionalIDs) } + func add(tracks: [SBTrack], at row: Int) { + let additionalIDs = tracks.map { $0.objectID.uriRepresentation() } + trackIDs?.insert(contentsOf: additionalIDs, at: row) + } + func remove(indices: IndexSet) { trackIDs?.remove(atOffsets: indices) } diff --git a/Submariner/SBPlaylistController.swift b/Submariner/SBPlaylistController.swift index 6382df7..30f982e 100644 --- a/Submariner/SBPlaylistController.swift +++ b/Submariner/SBPlaylistController.swift @@ -142,27 +142,45 @@ import Cocoa } func tableView(_ tableView: NSTableView, validateDrop info: any NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { - // internal drop track - if row != -1 && dropOperation == .above && info.draggingPasteboard.libraryItems() != nil { - return .move + guard row != -1 && dropOperation == .above else { + return [] } + if let sourceTable = info.draggingSource as? SBTableView, sourceTable == tracksTableView { + return .move + } else if info.draggingPasteboard.libraryItems() != nil { + return .copy + } return [] } func tableView(_ tableView: NSTableView, acceptDrop info: any NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { - if info.draggingSourceOperationMask.contains(.move) { + // XXX: For some reason, draggingSourceOperationMask has all bits set? + if let sourceTable = info.draggingSource as? SBTableView, sourceTable == tracksTableView { let indices = info.draggingPasteboard.rowIndices() - playlist.moveTracks(fromOffsets: indices, toOffset: row) - tracksController.rearrangeObjects() - tracksTableView.reloadData() - - // submit changes to server, this uses createPlaylist behind the scenes since we can reorder with it - if let server = playlist.server, let playlistID = playlist.itemId { - server.updatePlaylist(ID: playlistID, tracks: tracks) + // change selection to match new indices, since Array.move doesn't return them + var newRow = row + for index in indices { + if index < newRow { + newRow -= 1 + } } + let lastRow = newRow + indices.count - 1 + let newRange = newRow...lastRow + let newIndexSet = IndexSet(integersIn: newRange) + tracksTableView.selectRowIndexes(newIndexSet, byExtendingSelection: false) + } else if let tracks = info.draggingPasteboard.libraryItems(managedObjectContext: self.managedObjectContext) { + playlist.add(tracks: tracks, at: row) + } + + tracksController.rearrangeObjects() + tracksTableView.reloadData() + + // submit changes to server, this uses createPlaylist behind the scenes since we can reorder with it + if let server = playlist.server, let playlistID = playlist.itemId { + server.updatePlaylist(ID: playlistID, tracks: tracks) } return true } diff --git a/Submariner/SBTableView+DragImage.swift b/Submariner/SBTableView+DragImage.swift index 4cef387..c2e15fc 100644 --- a/Submariner/SBTableView+DragImage.swift +++ b/Submariner/SBTableView+DragImage.swift @@ -32,7 +32,9 @@ extension SBTableView { func setDragImage(of item: NSDraggingItem, for row: Int, at screenPoint: NSPoint) { // Find the left edge of the row by converting the header view origin to // the table view coordinate - var headerOrigin = convert(headerView?.frame.origin ?? NSZeroPoint, fromDescendant: headerView) + // If the header view doesn't exist (i.e. tracklist), let's use our superview, + // since we can't use ourself. + var headerOrigin = convert(headerView?.frame.origin ?? NSZeroPoint, fromDescendant: headerView ?? self.superview) // Generate the drag image from the current row with all the columns var p: NSPoint = .zero diff --git a/Submariner/SBTracklistController.swift b/Submariner/SBTracklistController.swift index c4360ed..a296129 100644 --- a/Submariner/SBTracklistController.swift +++ b/Submariner/SBTracklistController.swift @@ -27,7 +27,7 @@ import Cocoa title = "Tracklist" - playlistTableView.registerForDraggedTypes([.libraryItems, .libraryItem, .tracklistIndices]) + playlistTableView.registerForDraggedTypes([.libraryItems, .libraryItem]) notificationObserver = NotificationCenter.default.addObserver(forName: .SBPlayerPlaylistUpdated, object: nil, @@ -126,18 +126,12 @@ import Cocoa // #MARK: - NSTableView Delegate - // FIXME: Replace with tableView:pasteboardWriterForRow:? - func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool { - do { - let data = try NSKeyedArchiver.archivedData(withRootObject: rowIndexes, requiringSecureCoding: true) - - pboard.declareTypes([.tracklistIndices], owner: self) - pboard.setData(data, forType: .tracklistIndices) - - return true - } catch { - return false + func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> (any NSPasteboardWriting)? { + if tableView == playlistTableView { + let track = SBPlayer.sharedInstance().playlist[row] + return SBLibraryItemPasteboardWriter(item: track, index: row) } + return nil } func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation { @@ -145,23 +139,20 @@ import Cocoa return [] } - if let types = info.draggingPasteboard.types { - if types.contains(.tracklistIndices) { - return .move - } else if types.contains(.libraryItems) || types.contains(.libraryItem) { // for cursor - return .copy - } + if let sourceTable = info.draggingSource as? SBTableView, sourceTable == playlistTableView { + return .move + } else if info.draggingPasteboard.libraryItems() != nil { + return .copy } - return [] } static let allowedClasses = [NSIndexSet.self, NSArray.self, NSURL.self] func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool { - if let data = info.draggingPasteboard.data(forType: .tracklistIndices), - let rowIndexes = try? NSKeyedUnarchiver.unarchivedObject(ofClasses: SBTracklistController.allowedClasses, - from: data) as? IndexSet { + // XXX: For some reason, draggingSourceOperationMask has all bits set? + if let sourceTable = info.draggingSource as? SBTableView, sourceTable == playlistTableView { + let rowIndexes = info.draggingPasteboard.rowIndices() SBPlayer.sharedInstance().move(trackIndexSet: rowIndexes, index: row) // change selection to match new indices, since Array.move doesn't return them