diff --git a/Submariner.xcodeproj/project.pbxproj b/Submariner.xcodeproj/project.pbxproj index b0cb08d..b39f700 100644 --- a/Submariner.xcodeproj/project.pbxproj +++ b/Submariner.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 3E04F6462B7DE4C300E24E56 /* SBPreviousTrackCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E04F6452B7DE4C300E24E56 /* SBPreviousTrackCommand.swift */; }; 3E04F6482B7DE4D000E24E56 /* SBNextTrackCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E04F6472B7DE4D000E24E56 /* SBNextTrackCommand.swift */; }; 3E0DAD5C29CA2A5600D895E2 /* SBTrackListLengthTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0DAD5B29CA2A5600D895E2 /* SBTrackListLengthTransformer.swift */; }; + 3E0FDDDA2C24FDD000910E49 /* MutableCollection+Move.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E0FDDD92C24FDD000910E49 /* MutableCollection+Move.swift */; }; 3E189E4028EF5BAB0062ACA0 /* SBAudioMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E189E3F28EF5BAB0062ACA0 /* SBAudioMetadata.swift */; }; 3E1B785E2ACE5039008927C6 /* SBInspectorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1B785D2ACE5039008927C6 /* SBInspectorController.swift */; }; 3E1C6C432A5264570085B578 /* SBOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E1C6C422A5264570085B578 /* SBOperation.swift */; }; @@ -42,7 +43,6 @@ 3E53DBBA27E4192B00DB4C84 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E53DBB927E4192B00DB4C84 /* AVFoundation.framework */; }; 3E53DBBC27E41B6400DB4C84 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E53DBBB27E41B6400DB4C84 /* CoreMedia.framework */; }; 3E5C42E029846E25009B9699 /* SBOnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E5C42DE29846E25009B9699 /* SBOnboardingController.swift */; }; - 3E69507D2A707A2A006E5868 /* NSIndexSet+Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E69507C2A707A2A006E5868 /* NSIndexSet+Array.swift */; }; 3E69A2FF28B02D86009800D8 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E69A2FD28B02D86009800D8 /* UserNotifications.framework */; }; 3E702DE32A3E8E1F005F7184 /* SBAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E702DE22A3E8E1F005F7184 /* SBAppDelegate.swift */; }; 3E702DE72A428A1B005F7184 /* Synchronized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E702DE62A428A1B005F7184 /* Synchronized.swift */; }; @@ -180,6 +180,7 @@ 3E04F6452B7DE4C300E24E56 /* SBPreviousTrackCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBPreviousTrackCommand.swift; sourceTree = ""; }; 3E04F6472B7DE4D000E24E56 /* SBNextTrackCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBNextTrackCommand.swift; sourceTree = ""; }; 3E0DAD5B29CA2A5600D895E2 /* SBTrackListLengthTransformer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBTrackListLengthTransformer.swift; sourceTree = ""; }; + 3E0FDDD92C24FDD000910E49 /* MutableCollection+Move.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MutableCollection+Move.swift"; sourceTree = ""; }; 3E189E3228EBBAC40062ACA0 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; }; 3E189E3F28EF5BAB0062ACA0 /* SBAudioMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBAudioMetadata.swift; sourceTree = ""; }; 3E1B785D2ACE5039008927C6 /* SBInspectorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBInspectorController.swift; sourceTree = ""; }; @@ -205,7 +206,6 @@ 3E5C41DB297F2E15009B9699 /* Submariner v3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Submariner v3.xcdatamodel"; sourceTree = ""; }; 3E5C42DE29846E25009B9699 /* SBOnboardingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBOnboardingController.swift; sourceTree = ""; }; 3E6126EB2AD7363100B2A1E2 /* Submariner v4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Submariner v4.xcdatamodel"; sourceTree = ""; }; - 3E69507C2A707A2A006E5868 /* NSIndexSet+Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSIndexSet+Array.swift"; sourceTree = ""; }; 3E69A2FD28B02D86009800D8 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; }; 3E702DE22A3E8E1F005F7184 /* SBAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SBAppDelegate.swift; sourceTree = ""; }; 3E702DE62A428A1B005F7184 /* Synchronized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Synchronized.swift; sourceTree = ""; }; @@ -605,10 +605,10 @@ 3E87E90F2B4364CF00E85000 /* Collection+IndexSet.swift */, 3E04F63A2B7CA48400E24E56 /* Data+Random.swift */, 3EB2BCC22992D94E00DC5056 /* Data+Type.swift */, + 3E0FDDD92C24FDD000910E49 /* MutableCollection+Move.swift */, 4C87EDAE139CDA050064DE2E /* NSBezierPath+PXRoundedRectangleAdditions.h */, 4C87EDAF139CDA050064DE2E /* NSBezierPath+PXRoundedRectangleAdditions.m */, 3EB2BCC82992E85F00DC5056 /* NSImage+Additions.swift */, - 3E69507C2A707A2A006E5868 /* NSIndexSet+Array.swift */, 3E87E9132B460FFB00E85000 /* NSManagedObjectContext+Fetch.swift */, 4CFB3E05139CEA76008DC01A /* NSOutlineView+Expand.h */, 4CFB3E06139CEA76008DC01A /* NSOutlineView+Expand.m */, @@ -934,6 +934,7 @@ 3EC03B4C29F4F2E0001FDE50 /* SBServer.swift in Sources */, 3E04F6422B7DE48400E24E56 /* SBPlayCommand.swift in Sources */, 3E2F86CB28DD36E600C5CE23 /* SBMonospaceTextField.swift in Sources */, + 3E0FDDDA2C24FDD000910E49 /* MutableCollection+Move.swift in Sources */, 3EB2BCCB2992F03A00DC5056 /* SBSearchResult.swift in Sources */, 3EB2BCC52992DA8E00DC5056 /* String+File.swift in Sources */, 3EB2BCCF2992F3CA00DC5056 /* SBVolumeButton.swift in Sources */, @@ -943,7 +944,6 @@ 4CFAFC7D13A0F7E000E82B57 /* SBAnimatedView.m in Sources */, 3EC03B3629F4F2E0001FDE50 /* SBEpisode.swift in Sources */, 3EC03AC529F42C95001FDE50 /* URL+Parameters.swift in Sources */, - 3E69507D2A707A2A006E5868 /* NSIndexSet+Array.swift in Sources */, 3EC03B6329F5B0E2001FDE50 /* SBServer+CoreDataProperties.swift in Sources */, 3EC03B4429F4F2E0001FDE50 /* SBGroup.swift in Sources */, 3EB2BCC32992D94E00DC5056 /* Data+Type.swift in Sources */, diff --git a/Submariner/MutableCollection+Move.swift b/Submariner/MutableCollection+Move.swift new file mode 100644 index 0000000..1c388c6 --- /dev/null +++ b/Submariner/MutableCollection+Move.swift @@ -0,0 +1,29 @@ +// +// MutableCollection+Move.swift +// Submariner +// +// Created by Calvin Buckley on 2024-06-20. +// +// Copyright (c) 2024 Calvin Buckley +// SPDX-License-Identifier: BSD-3-Clause +// + +import Foundation + +extension MutableCollection { + mutating func moveReturningNewIndices(fromOffsets offsets: IndexSet, toOffset offset: Int) -> IndexSet { + self.move(fromOffsets: offsets, toOffset: offset) + + var newRow = offset + for index in offsets { + if index < newRow { + newRow -= 1 + } + } + let lastRow = newRow + offsets.count - 1 + let newRange = newRow...lastRow + let newIndexSet = IndexSet(integersIn: newRange) + + return newIndexSet + } +} diff --git a/Submariner/NSIndexSet+Array.swift b/Submariner/NSIndexSet+Array.swift deleted file mode 100644 index 2bbeb22..0000000 --- a/Submariner/NSIndexSet+Array.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// IndexSet+Array.swift -// Submariner -// -// Created by Calvin Buckley on 2023-07-25. -// Copyright © 2023 Submariner Developers. All rights reserved. -// - -import Foundation - -@objc extension NSIndexSet { - @objc func toArray() -> [Int] { - return Array(self) - } -} diff --git a/Submariner/SBPlayer.swift b/Submariner/SBPlayer.swift index b099865..5ab3ff1 100644 --- a/Submariner/SBPlayer.swift +++ b/Submariner/SBPlayer.swift @@ -488,7 +488,7 @@ extension NSNotification.Name { NotificationCenter.default.post(name: .SBPlayerPlaylistUpdated, object: self) } - @objc(moveTrackIndexSet:toIndex:) func move(trackIndexSet: IndexSet, index: Int) { + @objc(moveTrackIndexSet:toIndex:) func move(trackIndexSet: IndexSet, index: Int) -> IndexSet { // TODO: Avoid making a copy of the playlist here, and calculate the new offset instead if let currentIndex = self.currentIndex { var playlistWithIndices = playlist.enumerated().map { ($0, $1) } @@ -497,8 +497,9 @@ extension NSNotification.Name { return i == currentIndex } } - playlist.move(fromOffsets: trackIndexSet, toOffset: index) + let newIndexSet = playlist.moveReturningNewIndices(fromOffsets: trackIndexSet, toOffset: index) NotificationCenter.default.post(name: .SBPlayerPlaylistUpdated, object: self) + return newIndexSet } // #MARK: - Playlist+Playback Frontend Helpers diff --git a/Submariner/SBPlaylist.swift b/Submariner/SBPlaylist.swift index abadcbf..87a660a 100644 --- a/Submariner/SBPlaylist.swift +++ b/Submariner/SBPlaylist.swift @@ -61,8 +61,8 @@ public class SBPlaylist: SBResource { trackIDs?.remove(atOffsets: indices) } - @objc(moveIndices:toRow:) func moveTracks(fromOffsets indices: IndexSet, toOffset row: Int) { - trackIDs?.move(fromOffsets: indices, toOffset: row) + @objc(moveIndices:toRow:) func moveTracks(fromOffsets indices: IndexSet, toOffset row: Int) -> IndexSet? { + return trackIDs?.moveReturningNewIndices(fromOffsets: indices, toOffset: row) } // #MARK: - Core Data insert compatibility shim diff --git a/Submariner/SBPlaylistController.swift b/Submariner/SBPlaylistController.swift index 30f982e..8a48c1b 100644 --- a/Submariner/SBPlaylistController.swift +++ b/Submariner/SBPlaylistController.swift @@ -158,19 +158,9 @@ import Cocoa // 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) - - // 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 - } + if let newIndexSet = playlist.moveTracks(fromOffsets: indices, toOffset: row) { + tracksTableView.selectRowIndexes(newIndexSet, byExtendingSelection: false) } - 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) } diff --git a/Submariner/SBTracklistController.swift b/Submariner/SBTracklistController.swift index a296129..ff42382 100644 --- a/Submariner/SBTracklistController.swift +++ b/Submariner/SBTracklistController.swift @@ -153,18 +153,7 @@ import Cocoa // 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 - var newRow = row - for index in rowIndexes { - if index < newRow { - newRow -= 1 - } - } - let lastRow = newRow + rowIndexes.count - 1 - let newRange = newRow...lastRow - let newIndexSet = IndexSet(integersIn: newRange) + let newIndexSet = SBPlayer.sharedInstance().move(trackIndexSet: rowIndexes, index: row) playlistTableView.selectRowIndexes(newIndexSet, byExtendingSelection: false) } else if let tracks = info.draggingPasteboard.libraryItems(managedObjectContext: self.managedObjectContext) { // handles both kinds of library track