Implemented censored names preference
Added case sensitivity preference Integer tags are not clearable anymore
This commit is contained in:
committed by
Kim Wittenburg
parent
e2bdcd21e5
commit
257a7811d6
@@ -4,5 +4,12 @@ Version 1.0
|
||||
|
||||
Version 1.1
|
||||
|
||||
Added:
|
||||
+ 'Try again' option for errors.
|
||||
+ Options for tags not to be saved
|
||||
+ Option to use censored names
|
||||
+ Option for case (in)sensitivity
|
||||
|
||||
Fixed:
|
||||
* Error descriptions are now understandable
|
||||
* Added support for OS X 10.11 El Capitan
|
||||
* Error descriptions are now more understandable
|
||||
@@ -24,37 +24,53 @@ public class AlbumCollection: CollectionType {
|
||||
|
||||
}
|
||||
|
||||
// MARK: Constants
|
||||
|
||||
/// Posted when an album is added to a collection. This notification is only
|
||||
/// posted if the album collection actually changed.
|
||||
public static let AlbumAddedNotificationName = "AlbumAddedNotificationName"
|
||||
|
||||
/// Posted when an album is removed from a collection. This notification is
|
||||
/// only posted if the album collection actually changed.
|
||||
public static let AlbumRemovedNotificationName = "AlbumRemovedNotificationName"
|
||||
|
||||
/// Posted when an album collection finished loading the tracks for an album.
|
||||
/// Receiving this notification does not mean that the tracks were actually
|
||||
/// loaded successfully. It just means that the network connection
|
||||
/// terminated. Use `errorForAlbum` to determine if an error occured while
|
||||
/// the tracks have been loaded.
|
||||
///
|
||||
/// - note: Since the actual `Album` instance in the album collection may
|
||||
/// change during loading its tracks it is preferred that you use the
|
||||
/// `AlbumIndexKey` of the notification to determine which album finished
|
||||
/// loading its tracks. You can however use the `AlbumKey` as well to access
|
||||
/// the `Album` instance that is currently present in the collection at the
|
||||
/// respective index.
|
||||
public static let AlbumFinishedLoadingNotificationName = "AlbumFinishedLoadingNotificationName"
|
||||
|
||||
/// Key in the `userInfo` dictionary of a notification. The associated value
|
||||
/// is an `Int` indicating the index of the album that is affected.
|
||||
public static let AlbumIndexKey = "AlbumIndexKey"
|
||||
|
||||
/// Key in the `userInfo` dictionary of a notification. The associated value
|
||||
/// is the `Album` instance that is affected.
|
||||
public static let AlbumKey = "AlbumKey"
|
||||
/// Notifications posted by an album collection. The `userInfo` of these
|
||||
/// notifications contains all keys specified in `Keys`.
|
||||
public struct Notifications {
|
||||
|
||||
/// Posted when an album is added to a collection. This notification is
|
||||
/// only posted if the album collection actually changed.
|
||||
public static let albumAdded = "AlbumAddedNotificationName"
|
||||
|
||||
/// Posted when an album is removed from a collection. This notification
|
||||
/// is only posted if the album collection actually changed.
|
||||
public static let albumRemoved = "AlbumRemovedNotificationName"
|
||||
|
||||
/// Posted when the album collection started a network request for an
|
||||
/// album's tracks.
|
||||
///
|
||||
/// Note that the values for the keys `Album` and `AlbumIndex` for the
|
||||
/// corresponding `AlbumFinishedLoading` notification may both be
|
||||
/// different.
|
||||
public static let albumStartedLoading = "AlbumStartedLoadingNotificationName"
|
||||
|
||||
/// Posted when an album collection finished loading the tracks for an
|
||||
/// album. Receiving this notification does not mean that the tracks were
|
||||
/// actually loaded successfully. It just means that the networ
|
||||
/// connection terminated. Use `errorForAlbum` to determine if an error
|
||||
/// occured while the tracks have been loaded.
|
||||
///
|
||||
/// - note: Since the actual `Album` instance in the album collection may
|
||||
/// change during loading its tracks it is preferred that you use the
|
||||
/// `AlbumIndexKey` of the notification to determine which album finished
|
||||
/// loading its tracks. You can however use the `AlbumKey` as well to
|
||||
/// access the `Album` instance that is currently present in the
|
||||
/// collection at the respective index.
|
||||
public static let albumFinishedLoading = "AlbumFinishedLoadingNotificationName"
|
||||
|
||||
/// These constants are available as keys in the `userInfo` dictionary
|
||||
/// for any notification an album collection may post.
|
||||
public struct Keys {
|
||||
|
||||
/// The `Album` instance affected by the notification.
|
||||
public static let album = "AlbumKey"
|
||||
|
||||
/// The index of the album affected by the notification.
|
||||
public static let albumIndex = "AlbumIndexKey"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Properties
|
||||
|
||||
@@ -94,10 +110,11 @@ public class AlbumCollection: CollectionType {
|
||||
public func addAlbum(album: Album, beginLoading flag: Bool = true) {
|
||||
if !albums.contains(album) {
|
||||
albums.append(album)
|
||||
let userInfo: [NSObject: AnyObject] = [AlbumCollection.Notifications.Keys.album: album, AlbumCollection.Notifications.Keys.albumIndex: albums.count-1]
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(AlbumCollection.Notifications.albumAdded, object: self, userInfo: userInfo)
|
||||
if flag {
|
||||
beginLoadingTracksForAlbum(album)
|
||||
}
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(AlbumCollection.AlbumAddedNotificationName, object: self, userInfo: [AlbumCollection.AlbumIndexKey: albums.count-1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +132,8 @@ public class AlbumCollection: CollectionType {
|
||||
let album = self[index]
|
||||
setAlbumState(nil, forAlbum: album)
|
||||
albums.removeAtIndex(index)
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(AlbumCollection.AlbumRemovedNotificationName, object: self, userInfo: [AlbumCollection.AlbumIndexKey: index])
|
||||
let userInfo: [NSObject: AnyObject] = [AlbumCollection.Notifications.Keys.album: album, AlbumCollection.Notifications.Keys.albumIndex: index]
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(AlbumCollection.Notifications.albumRemoved, object: self, userInfo: userInfo)
|
||||
}
|
||||
|
||||
/// Begins to load the tracks for the specified album. If there is already a
|
||||
@@ -123,11 +141,15 @@ public class AlbumCollection: CollectionType {
|
||||
/// specified album have been loaded or an error occured, a
|
||||
/// `AlbumFinishedLoadingNotification` is posted.
|
||||
public func beginLoadingTracksForAlbum(album: Album) {
|
||||
guard let albumIndex = albums.indexOf(album) else {
|
||||
return
|
||||
}
|
||||
let url = iTunesAPI.createAlbumLookupURLForId(album.id)
|
||||
let task = urlSession.dataTaskWithURL(url) { (data, response, error) -> Void in
|
||||
var albumIndex = self.albums.indexOf(album)!
|
||||
var newAlbumIndex = self.albums.indexOf(album)!
|
||||
defer {
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(AlbumCollection.AlbumFinishedLoadingNotificationName, object: self, userInfo: [AlbumCollection.AlbumIndexKey: albumIndex])
|
||||
let userInfo: [NSObject: AnyObject] = [AlbumCollection.Notifications.Keys.album: self.albums[albumIndex], AlbumCollection.Notifications.Keys.albumIndex: albumIndex]
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(AlbumCollection.Notifications.albumFinishedLoading, object: self, userInfo: userInfo)
|
||||
}
|
||||
guard error == nil else {
|
||||
if error!.code != NSUserCancelledError {
|
||||
@@ -137,9 +159,8 @@ public class AlbumCollection: CollectionType {
|
||||
}
|
||||
do {
|
||||
let newAlbum = try iTunesAPI.parseAPIData(data!)[0]
|
||||
albumIndex = self.albums.indexOf(album)!
|
||||
self.albums.removeAtIndex(albumIndex)
|
||||
self.albums.insert(newAlbum, atIndex: albumIndex)
|
||||
self.albums.removeAtIndex(newAlbumIndex)
|
||||
self.albums.insert(newAlbum, atIndex: newAlbumIndex)
|
||||
self.setAlbumState(.Normal, forAlbum: album)
|
||||
} catch let error as NSError {
|
||||
self.setAlbumState(.Error(error), forAlbum: album)
|
||||
@@ -149,6 +170,9 @@ public class AlbumCollection: CollectionType {
|
||||
}
|
||||
setAlbumState(.Loading(task), forAlbum: album)
|
||||
task.resume()
|
||||
let userInfo: [NSObject: AnyObject] = [AlbumCollection.Notifications.Keys.album: album, AlbumCollection.Notifications.Keys.albumIndex: albumIndex]
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(AlbumCollection.Notifications.albumStartedLoading, object: self, userInfo: userInfo)
|
||||
|
||||
}
|
||||
|
||||
/// Cancels the request to load the tracks for the specified album and sets
|
||||
|
||||
@@ -118,7 +118,7 @@ public class AlbumTableCellView: AdvancedTableCellView {
|
||||
/// - loading: `true` if a loading indicator should be displayed at the
|
||||
/// album view.
|
||||
public func setupForAlbum(album: Album, loading: Bool, error: NSError?) {
|
||||
textField?.stringValue = album.name
|
||||
textField?.stringValue = Preferences.sharedPreferences.useCensoredNames ? album.censoredName : album.name
|
||||
secondaryTextField?.stringValue = album.artistName
|
||||
asyncImageView.downloadImageFromURL(album.artwork.hiResURL)
|
||||
if loading {
|
||||
@@ -160,7 +160,7 @@ public class AlbumTableCellView: AdvancedTableCellView {
|
||||
/// - selectable: `true` if the search result can be selected, `false`
|
||||
/// otherwise.
|
||||
public func setupForSearchResult(searchResult: SearchResult, selectable: Bool) {
|
||||
textField?.stringValue = searchResult.name
|
||||
textField?.stringValue = Preferences.sharedPreferences.useCensoredNames ? searchResult.censoredName : searchResult.name
|
||||
textField?.textColor = NSColor.controlTextColor()
|
||||
secondaryTextField?.stringValue = searchResult.artistName
|
||||
asyncImageView.downloadImageFromURL(searchResult.artwork.hiResURL)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="8173.3" systemVersion="14F27" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="8191" systemVersion="15A282a" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8173.3"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8191"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
@@ -731,6 +732,7 @@ CA
|
||||
<tabView key="tabView" type="noTabsNoBorder" id="UTE-Sb-ieY">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
<font key="font" metaFont="message"/>
|
||||
<tabViewItems/>
|
||||
<connections>
|
||||
@@ -755,14 +757,15 @@ CA
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="kl3-n8-T9b">
|
||||
<rect key="frame" x="18" y="92" width="105" height="18"/>
|
||||
<rect key="frame" x="18" y="92" width="103" height="18"/>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="check" title="Save Artwork" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="q2l-4t-mL0">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="saveArtworkStateChanged:" target="tzd-4a-CRb" id="qQu-K2-wWk"/>
|
||||
<binding destination="aSx-iH-PLA" name="value" keyPath="saveArtwork" id="1An-Cl-B1c"/>
|
||||
<binding destination="aSx-iH-PLA" name="value" keyPath="sharedPreferences.saveArtwork" id="Pif-70-Zto"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1fj-p7-sMs">
|
||||
@@ -770,6 +773,7 @@ CA
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="89" id="WSP-Sc-NcB"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="push" title="Choose…" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="A5S-ps-EYW">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -779,17 +783,19 @@ CA
|
||||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="MSr-08-ucR">
|
||||
<rect key="frame" x="18" y="40" width="151" height="18"/>
|
||||
<rect key="frame" x="18" y="40" width="147" height="18"/>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="check" title="Keep Search Results" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="uED-ee-Oc7">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<binding destination="aSx-iH-PLA" name="value" keyPath="keepSearchResults" id="Nwn-Ik-Zdy"/>
|
||||
<binding destination="aSx-iH-PLA" name="value" keyPath="sharedPreferences.keepSearchResults" id="52h-XX-KDr"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QNf-Uy-bMT">
|
||||
<rect key="frame" x="30" y="20" width="402" height="14"/>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" title="If checked the search results are not removed if a result is added." id="Lnf-PQ-PX4">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -798,12 +804,13 @@ CA
|
||||
</textField>
|
||||
<pathControl verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pT8-NA-x3W">
|
||||
<rect key="frame" x="20" y="64" width="313" height="22"/>
|
||||
<animations/>
|
||||
<pathCell key="cell" selectable="YES" alignment="left" id="Sgq-Mk-WnH">
|
||||
<font key="font" metaFont="system"/>
|
||||
<url key="url" string="file:///Applications/"/>
|
||||
</pathCell>
|
||||
<connections>
|
||||
<binding destination="aSx-iH-PLA" name="value" keyPath="artworkTarget" id="wEo-ga-KAB"/>
|
||||
<binding destination="aSx-iH-PLA" name="value" keyPath="sharedPreferences.artworkTarget" id="k2W-Kf-xpK"/>
|
||||
</connections>
|
||||
</pathControl>
|
||||
</subviews>
|
||||
@@ -821,6 +828,7 @@ CA
|
||||
<constraint firstItem="QNf-Uy-bMT" firstAttribute="trailing" secondItem="1fj-p7-sMs" secondAttribute="trailing" id="tri-hX-smF"/>
|
||||
<constraint firstItem="QNf-Uy-bMT" firstAttribute="leading" secondItem="MSr-08-ucR" secondAttribute="leading" constant="12" id="tyS-Y5-FOc"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="width">
|
||||
<real key="value" value="450"/>
|
||||
@@ -836,7 +844,7 @@ CA
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="RtZ-4O-Pgk" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<customObject id="aSx-iH-PLA" customClass="Preferences" customModule="TagTunes" customModuleProvider="target"/>
|
||||
<customObject id="aSx-iH-PLA" userLabel="Preferences" customClass="PreferencesSingleton" customModule="TagTunes" customModuleProvider="target"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1031" y="1051"/>
|
||||
</scene>
|
||||
@@ -845,17 +853,18 @@ CA
|
||||
<objects>
|
||||
<viewController title="Tags" id="qlM-h3-Tfw" customClass="TagsPreferencesViewController" customModule="TagTunes" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="caz-ze-bnI" customClass="PreferenceView" customModule="AppKitPlus">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="325"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="345"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="0Za-Q2-FET">
|
||||
<rect key="frame" x="18" y="289" width="157" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Use Censored Names" bezelStyle="regularSquare" imagePosition="left" enabled="NO" state="on" inset="2" id="UOJ-Ol-UjW">
|
||||
<rect key="frame" x="18" y="309" width="154" height="18"/>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="check" title="Use Censored Names" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="UOJ-Ol-UjW">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<binding destination="7Ts-a9-Cpv" name="value" keyPath="useCensoredNames" id="LVi-CS-iz7">
|
||||
<binding destination="7Ts-a9-Cpv" name="value" keyPath="sharedPreferences.useCensoredNames" id="oYs-AD-vbO">
|
||||
<dictionary key="options">
|
||||
<bool key="NSConditionallySetsEnabled" value="NO"/>
|
||||
</dictionary>
|
||||
@@ -864,21 +873,34 @@ CA
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wkM-Sb-z0R">
|
||||
<rect key="frame" x="18" y="241" width="414" height="34"/>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Some tags are not provided by the iTunes Search API. TagTunes can only clear these tags." id="8L8-Nr-zgu">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<scrollView fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="23" horizontalPageScroll="10" verticalLineScroll="23" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cTv-EP-MQX">
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="Rru-2X-J52">
|
||||
<rect key="frame" x="18" y="289" width="111" height="18"/>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="check" title="Case Sensitive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="Ttb-GR-JAy">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<binding destination="7Ts-a9-Cpv" name="value" keyPath="sharedPreferences.caseSensitive" id="lFI-W5-ve4"/>
|
||||
</connections>
|
||||
</button>
|
||||
<scrollView autohidesScrollers="YES" horizontalLineScroll="23" horizontalPageScroll="10" verticalLineScroll="23" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cTv-EP-MQX">
|
||||
<rect key="frame" x="20" y="20" width="410" height="213"/>
|
||||
<clipView key="contentView" ambiguous="YES" id="awx-Qa-0a0">
|
||||
<clipView key="contentView" id="awx-Qa-0a0">
|
||||
<rect key="frame" x="1" y="1" width="408" height="211"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" multipleSelection="NO" autosaveColumns="NO" rowHeight="21" rowSizeStyle="automatic" viewBased="YES" id="WCb-HH-YPh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="408" height="211"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -900,8 +922,9 @@ CA
|
||||
<rect key="frame" x="1" y="1" width="292" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vww-dF-uIe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="17"/>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="vww-dF-uIe">
|
||||
<rect key="frame" x="0.0" y="0.0" width="211" height="17"/>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="hxe-jr-7rI">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -914,6 +937,7 @@ CA
|
||||
<constraint firstAttribute="trailing" secondItem="vww-dF-uIe" secondAttribute="trailing" constant="83" id="Zem-p3-9DR"/>
|
||||
<constraint firstItem="vww-dF-uIe" firstAttribute="leading" secondItem="Zbr-i2-c1G" secondAttribute="leading" constant="2" id="ib0-Lg-5Ts"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<connections>
|
||||
<outlet property="textField" destination="vww-dF-uIe" id="a8W-tw-hGo"/>
|
||||
</connections>
|
||||
@@ -928,7 +952,7 @@ CA
|
||||
</tableHeaderCell>
|
||||
<popUpButtonCell key="dataCell" type="bevel" title="Ignore" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="bezel" imageScaling="proportionallyDown" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" selectedItem="idt-SA-nnz" id="1Sz-1n-thR">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="3zQ-Pc-Wgf">
|
||||
<items>
|
||||
<menuItem title="Save" id="dA3-hx-uqV"/>
|
||||
@@ -945,6 +969,7 @@ CA
|
||||
<popUpButton identifier="popupCell" id="Cxj-hO-zFk">
|
||||
<rect key="frame" x="296" y="1" width="110" height="21"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<animations/>
|
||||
<popUpButtonCell key="cell" type="bevel" title="Ignore" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" imageScaling="proportionallyDown" inset="2" selectedItem="Zxi-Bn-dST" id="Och-nm-1Kl">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -969,25 +994,36 @@ CA
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Q38-Np-JBF">
|
||||
<rect key="frame" x="1" y="196" width="408" height="16"/>
|
||||
<animations/>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="Q38-Np-JBF">
|
||||
<rect key="frame" x="1" y="-15" width="0.0" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="255-EG-UwP">
|
||||
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="255-EG-UwP">
|
||||
<rect key="frame" x="-15" y="17" width="16" height="0.0"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="Rru-2X-J52" firstAttribute="leading" secondItem="0Za-Q2-FET" secondAttribute="leading" id="3LQ-xG-Kah"/>
|
||||
<constraint firstItem="0Za-Q2-FET" firstAttribute="top" secondItem="caz-ze-bnI" secondAttribute="top" constant="20" symbolic="YES" id="4hi-D6-aIL"/>
|
||||
<constraint firstItem="wkM-Sb-z0R" firstAttribute="top" secondItem="0Za-Q2-FET" secondAttribute="bottom" constant="16" id="AFD-39-mPY"/>
|
||||
<constraint firstItem="cTv-EP-MQX" firstAttribute="leading" secondItem="caz-ze-bnI" secondAttribute="leading" constant="20" symbolic="YES" id="7Ck-cS-n92"/>
|
||||
<constraint firstAttribute="bottom" secondItem="cTv-EP-MQX" secondAttribute="bottom" constant="20" symbolic="YES" id="Lvm-eR-ruw"/>
|
||||
<constraint firstItem="wkM-Sb-z0R" firstAttribute="top" secondItem="Rru-2X-J52" secondAttribute="bottom" constant="16" id="OIO-Wt-F3d"/>
|
||||
<constraint firstAttribute="trailing" secondItem="wkM-Sb-z0R" secondAttribute="trailing" constant="20" symbolic="YES" id="PzL-bB-J8O"/>
|
||||
<constraint firstItem="0Za-Q2-FET" firstAttribute="leading" secondItem="wkM-Sb-z0R" secondAttribute="leading" id="bGY-79-Gc5"/>
|
||||
<constraint firstItem="cTv-EP-MQX" firstAttribute="top" secondItem="wkM-Sb-z0R" secondAttribute="bottom" constant="8" symbolic="YES" id="cWo-Xz-1Al"/>
|
||||
<constraint firstAttribute="trailing" secondItem="cTv-EP-MQX" secondAttribute="trailing" constant="20" symbolic="YES" id="dMs-jm-ikl"/>
|
||||
<constraint firstItem="Rru-2X-J52" firstAttribute="top" secondItem="0Za-Q2-FET" secondAttribute="bottom" constant="6" symbolic="YES" id="w50-BO-5gG"/>
|
||||
<constraint firstItem="0Za-Q2-FET" firstAttribute="leading" secondItem="caz-ze-bnI" secondAttribute="leading" constant="20" symbolic="YES" id="zoK-yx-jUT"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="width">
|
||||
<real key="value" value="450"/>
|
||||
@@ -1002,9 +1038,9 @@ CA
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="w7s-ZN-87L" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
<customObject id="7Ts-a9-Cpv" customClass="Preferences" customModule="TagTunes" customModuleProvider="target"/>
|
||||
<customObject id="7Ts-a9-Cpv" userLabel="Preferences" customClass="PreferencesSingleton" customModule="TagTunes" customModuleProvider="target"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-470" y="1149.5"/>
|
||||
<point key="canvasLocation" x="-470" y="1159.5"/>
|
||||
</scene>
|
||||
<!--Main View Controller-->
|
||||
<scene sceneID="hIz-AP-VOD">
|
||||
@@ -1016,6 +1052,7 @@ CA
|
||||
<subviews>
|
||||
<searchField wantsLayer="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JF0-Xb-DqY">
|
||||
<rect key="frame" x="20" y="348" width="652" height="22"/>
|
||||
<animations/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" sendsWholeSearchString="YES" id="iQi-K0-yFr">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -1034,6 +1071,7 @@ CA
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" autosaveColumns="NO" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="p3C-E5-sdk" id="1Vy-Gq-TWU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="650" height="0.0"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -1058,15 +1096,19 @@ CA
|
||||
</connections>
|
||||
</outlineView>
|
||||
</subviews>
|
||||
<animations/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="vEh-oN-xXy">
|
||||
<animations/>
|
||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="vEh-oN-xXy">
|
||||
<rect key="frame" x="1" y="303" width="650" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="iqM-dh-v73">
|
||||
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="iqM-dh-v73">
|
||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<animations/>
|
||||
</scroller>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
@@ -1079,6 +1121,7 @@ CA
|
||||
<constraint firstItem="zXR-px-hjg" firstAttribute="top" secondItem="JF0-Xb-DqY" secondAttribute="bottom" constant="8" symbolic="YES" id="pKp-Ad-Sr5"/>
|
||||
<constraint firstItem="zXR-px-hjg" firstAttribute="trailing" secondItem="JF0-Xb-DqY" secondAttribute="trailing" id="r36-CQ-K22"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="outlineView" destination="1Vy-Gq-TWU" id="vRG-b2-ocW"/>
|
||||
|
||||
@@ -26,6 +26,19 @@
|
||||
<string>public.app-category.music</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>mzstatic.com</key>
|
||||
<dict>
|
||||
<key>NSIncludesSubdomains</key>
|
||||
<true/>
|
||||
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>Copyright © 2015 Kim Wittenburg. All rights reserved.</string>
|
||||
<key>NSMainStoryboardFile</key>
|
||||
|
||||
@@ -33,6 +33,10 @@ internal class MainViewController: NSViewController {
|
||||
static let pasteboardType = "public.item.tagtunes"
|
||||
}
|
||||
|
||||
private struct KVOContexts {
|
||||
static var preferencesContext = "KVOPreferencesContext"
|
||||
}
|
||||
|
||||
internal enum Section: String {
|
||||
case SearchResults = "SearchResults"
|
||||
case Albums = "Albums"
|
||||
@@ -59,6 +63,8 @@ internal class MainViewController: NSViewController {
|
||||
/// The URL task currently loading the search results
|
||||
private var searchTask: NSURLSessionTask?
|
||||
|
||||
private var searchTerm: String?
|
||||
|
||||
/// If `true` the search section is displayed at the top of the
|
||||
/// `outlineView`.
|
||||
internal var showsSearch: Bool = false
|
||||
@@ -73,17 +79,27 @@ internal class MainViewController: NSViewController {
|
||||
|
||||
// MARK: View Life Cycle
|
||||
|
||||
private var observerObject: NSObjectProtocol?
|
||||
// Proxy objects that act as `NSNotificationCenter` observers.
|
||||
private var observerProxies = [NSObjectProtocol]()
|
||||
|
||||
override internal func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
NSNotificationCenter.defaultCenter().addObserverForName(AlbumCollection.AlbumFinishedLoadingNotificationName, object: albumCollection, queue: NSOperationQueue.mainQueue(), usingBlock: albumCollectionDidFinishLoadingTracks)
|
||||
|
||||
let startedLoadingTracksObserver = NSNotificationCenter.defaultCenter().addObserverForName(AlbumCollection.Notifications.albumStartedLoading, object: albumCollection, queue: NSOperationQueue.mainQueue(), usingBlock: albumCollectionDidBeginLoadingTracks)
|
||||
let finishedLoadingTracksObserver = NSNotificationCenter.defaultCenter().addObserverForName(AlbumCollection.Notifications.albumFinishedLoading, object: albumCollection, queue: NSOperationQueue.mainQueue(), usingBlock: albumCollectionDidFinishLoadingTracks)
|
||||
|
||||
observerProxies.append(startedLoadingTracksObserver)
|
||||
observerProxies.append(finishedLoadingTracksObserver)
|
||||
|
||||
Preferences.sharedPreferences.addObserver(self, forKeyPath: "useCensoredNames", options: [], context: &KVOContexts.preferencesContext)
|
||||
Preferences.sharedPreferences.addObserver(self, forKeyPath: "caseSensitive", options: [], context: &KVOContexts.preferencesContext)
|
||||
|
||||
outlineView.setDraggingSourceOperationMask(.Move, forLocal: true)
|
||||
outlineView.registerForDraggedTypes([OutlineViewConstants.pasteboardType])
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let observer = observerObject {
|
||||
for observer in observerProxies {
|
||||
NSNotificationCenter.defaultCenter().removeObserver(observer)
|
||||
}
|
||||
}
|
||||
@@ -192,6 +208,7 @@ internal class MainViewController: NSViewController {
|
||||
cancelSearch()
|
||||
if let url = iTunesAPI.createAlbumSearchURLForTerm(term) {
|
||||
showsSearch = true
|
||||
searchTerm = term
|
||||
searchTask = urlSession.dataTaskWithURL(url, completionHandler: processSearchResults)
|
||||
searchTask?.resume()
|
||||
} else {
|
||||
@@ -216,6 +233,7 @@ internal class MainViewController: NSViewController {
|
||||
if let theData = data where error == nil {
|
||||
do {
|
||||
let searchResults = try iTunesAPI.parseAPIData(theData).map { SearchResult(representedAlbum: $0) }
|
||||
searchTerm = nil
|
||||
self.searchResults = searchResults
|
||||
} catch let theError as NSError {
|
||||
error = theError
|
||||
@@ -257,10 +275,6 @@ internal class MainViewController: NSViewController {
|
||||
outlineView.reloadData()
|
||||
}
|
||||
|
||||
private func albumCollectionDidFinishLoadingTracks(notification: NSNotification) {
|
||||
outlineView.reloadData()
|
||||
}
|
||||
|
||||
// MARK: Albums
|
||||
|
||||
private func saveTracks(tracks: [Track: [iTunesTrack]]) {
|
||||
@@ -307,11 +321,7 @@ internal class MainViewController: NSViewController {
|
||||
if errorCount > 0 {
|
||||
dispatch_sync(dispatch_get_main_queue()) {
|
||||
let alert = NSAlert()
|
||||
if errorCount == 1 {
|
||||
alert.messageText = NSLocalizedString("1 artwork could not be saved.", comment: "Error message indicating that one of the artworks could not be saved.")
|
||||
} else {
|
||||
alert.messageText = String(format: NSLocalizedString("%d artworks could not be saved.", comment: "Error message indicating that n artworks could not be saved."), errorCount)
|
||||
}
|
||||
alert.messageText = String(format: NSLocalizedString("%d artworks could not be saved.", comment: "Error message indicating that n artworks could not be saved."), errorCount)
|
||||
alert.informativeText = NSLocalizedString("Please check your privileges for the folder you set in the preferences and try again.", comment: "Informative text for 'artwork(s) could not be saved' errors")
|
||||
alert.alertStyle = .WarningAlertStyle
|
||||
alert.addButtonWithTitle("OK")
|
||||
@@ -320,6 +330,22 @@ internal class MainViewController: NSViewController {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
private func albumCollectionDidBeginLoadingTracks(notification: NSNotification) {
|
||||
outlineView.reloadData()
|
||||
}
|
||||
|
||||
private func albumCollectionDidFinishLoadingTracks(notification: NSNotification) {
|
||||
outlineView.reloadData()
|
||||
}
|
||||
|
||||
override internal func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
|
||||
if context == &KVOContexts.preferencesContext {
|
||||
outlineView.reloadData()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
|
||||
/// Adds the current iTunes selection
|
||||
@@ -465,7 +491,7 @@ extension MainViewController {
|
||||
|
||||
override internal func attemptRecoveryFromError(error: NSError, optionIndex recoveryOptionIndex: Int, delegate: AnyObject?, didRecoverSelector: Selector, contextInfo: UnsafeMutablePointer<Void>) {
|
||||
let didRecover = attemptRecoveryFromError(error, optionIndex: recoveryOptionIndex)
|
||||
// TODO: Notify the delegate
|
||||
delegate?.performSelector(didRecoverSelector, withObject: didRecover, withObject: contextInfo as! AnyObject)
|
||||
}
|
||||
|
||||
override internal func attemptRecoveryFromError(error: NSError, optionIndex recoveryOptionIndex: Int) -> Bool {
|
||||
@@ -473,13 +499,15 @@ extension MainViewController {
|
||||
return true
|
||||
}
|
||||
// TODO: Implementation
|
||||
if error == searchError {
|
||||
|
||||
if let term = searchTerm where error == searchError || error.userInfo[NSUnderlyingErrorKey] === searchError {
|
||||
beginSearchForTerm(term)
|
||||
return true
|
||||
} else {
|
||||
for album in albumCollection {
|
||||
let albumError = albumCollection.errorForAlbum(album)
|
||||
if error == albumError {
|
||||
|
||||
if error == albumError || error.userInfo[NSUnderlyingErrorKey] === albumError {
|
||||
albumCollection.beginLoadingTracksForAlbum(album)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,17 +81,28 @@ internal class TagsPreferencesViewController: NSViewController, NSTableViewDataS
|
||||
if tag.isReturnedBySearchAPI {
|
||||
popupButton?.addItemWithTitle(NSLocalizedString("Save", comment: "Menu item title for a tag that is going to be saved"))
|
||||
}
|
||||
popupButton?.addItemsWithTitles([
|
||||
NSLocalizedString("Clear", comment: "Menu item title for a tag that is going to be cleared"),
|
||||
NSLocalizedString("Ignore", comment: "Menu item title for a tag that is not going to be saved")])
|
||||
if tag.clearable {
|
||||
popupButton?.addItemWithTitle(NSLocalizedString("Clear", comment: "Menu item title for a tag that is going to be cleared"))
|
||||
}
|
||||
popupButton?.addItemWithTitle(NSLocalizedString("Ignore", comment: "Menu item title for a tag that is not going to be saved"))
|
||||
|
||||
var selectedIndex: Int
|
||||
switch Preferences.sharedPreferences.tagSavingBehaviors[tag]! {
|
||||
case .Save:
|
||||
selectedIndex = 0
|
||||
case .Clear:
|
||||
selectedIndex = tag.isReturnedBySearchAPI ? 1 : 0
|
||||
selectedIndex = 1
|
||||
if !tag.isReturnedBySearchAPI {
|
||||
--selectedIndex
|
||||
}
|
||||
case .Ignore:
|
||||
selectedIndex = tag.isReturnedBySearchAPI ? 2 : 1
|
||||
selectedIndex = 2
|
||||
if !tag.isReturnedBySearchAPI {
|
||||
--selectedIndex
|
||||
}
|
||||
if !tag.clearable {
|
||||
--selectedIndex
|
||||
}
|
||||
}
|
||||
popupButton?.selectItemAtIndex(selectedIndex)
|
||||
return popupButton
|
||||
@@ -105,9 +116,22 @@ internal class TagsPreferencesViewController: NSViewController, NSTableViewDataS
|
||||
var savingBehavior = Preferences.sharedPreferences.tagSavingBehaviors[tag]!
|
||||
switch selectedIndex {
|
||||
case 0:
|
||||
savingBehavior = tag.isReturnedBySearchAPI ? .Save : .Clear
|
||||
if tag.isReturnedBySearchAPI {
|
||||
savingBehavior = .Save
|
||||
} else if tag.clearable {
|
||||
savingBehavior = .Clear
|
||||
} else {
|
||||
savingBehavior = .Ignore
|
||||
}
|
||||
case 1:
|
||||
savingBehavior = tag.isReturnedBySearchAPI ? .Clear : .Ignore
|
||||
if tag.isReturnedBySearchAPI {
|
||||
if tag.clearable {
|
||||
savingBehavior = .Clear
|
||||
} else {
|
||||
savingBehavior = .Ignore
|
||||
}
|
||||
}
|
||||
savingBehavior = .Ignore
|
||||
case 2:
|
||||
savingBehavior = .Ignore
|
||||
default:
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
|
||||
import Cocoa
|
||||
|
||||
/// Internal class to be used in IB to bind to the shared preferences.
|
||||
@objc internal class PreferencesSingleton: NSObject {
|
||||
|
||||
internal dynamic var sharedPreferences: Preferences {
|
||||
return Preferences.sharedPreferences
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// A custom interface for the `NSUserDefaults`. It is recommended to use this
|
||||
/// class insted of accessing the user defaults directly to prevent errors due
|
||||
/// to misspelled strings.
|
||||
@@ -17,7 +26,7 @@ import Cocoa
|
||||
|
||||
// MARK: Types
|
||||
|
||||
private struct UserDefaultsConstants {
|
||||
internal struct UserDefaultsConstants {
|
||||
|
||||
static let saveArtworkKey = "Save Artwork"
|
||||
|
||||
@@ -29,6 +38,8 @@ import Cocoa
|
||||
|
||||
static let useCensoredNamesKey = "Use Censored Names"
|
||||
|
||||
static let caseSensitive = "Case Sensitive"
|
||||
|
||||
static let tagSavingBehaviorsKey = "Tag Saving Behaviors"
|
||||
}
|
||||
|
||||
@@ -58,7 +69,8 @@ import Cocoa
|
||||
UserDefaultsConstants.saveArtworkKey: false,
|
||||
UserDefaultsConstants.keepSearchResultsKey: false,
|
||||
UserDefaultsConstants.removeSavedAlbumsKey: false,
|
||||
UserDefaultsConstants.useCensoredNamesKey: false
|
||||
UserDefaultsConstants.useCensoredNamesKey: false,
|
||||
UserDefaultsConstants.caseSensitive: true
|
||||
])
|
||||
if NSUserDefaults.standardUserDefaults().dictionaryForKey(UserDefaultsConstants.tagSavingBehaviorsKey) == nil {
|
||||
var savingBehaviors: [Track.Tag: TagSavingBehavior] = [:]
|
||||
@@ -122,6 +134,16 @@ import Cocoa
|
||||
}
|
||||
}
|
||||
|
||||
/// If `true` TagTunes ignores cases when comparing track titles and albums.
|
||||
public dynamic var caseSensitive: Bool {
|
||||
set {
|
||||
NSUserDefaults.standardUserDefaults().setBool(newValue, forKey: UserDefaultsConstants.caseSensitive)
|
||||
}
|
||||
get {
|
||||
return NSUserDefaults.standardUserDefaults().boolForKey(UserDefaultsConstants.caseSensitive)
|
||||
}
|
||||
}
|
||||
|
||||
/// The ways different tags are saved (or not saved).
|
||||
public var tagSavingBehaviors: [Track.Tag: TagSavingBehavior] {
|
||||
set {
|
||||
|
||||
@@ -28,21 +28,20 @@ public class Track: iTunesType {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public var clearable: Bool {
|
||||
switch self {
|
||||
case .Year, .TrackNumber, .TrackCount, .DiscNumber, .DiscCount:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a string identifying the respective tag that can be displayed
|
||||
/// to the user.
|
||||
public var localizedName: String {
|
||||
return NSLocalizedString(self.rawValue, comment: "")
|
||||
}
|
||||
|
||||
/// Returns the object that should be saved to *clear* the tag.
|
||||
public var clearedValue: AnyObject? {
|
||||
switch self {
|
||||
case .Year, .TrackNumber, .TrackCount, .DiscNumber, .DiscCount:
|
||||
return ""
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
return NSLocalizedString("Tag: \(self.rawValue)", comment: "")
|
||||
}
|
||||
|
||||
/// Returns an array of all tags.
|
||||
@@ -153,11 +152,10 @@ public class Track: iTunesType {
|
||||
case .Save:
|
||||
track.setValue(value, forKey: tag.rawValue)
|
||||
case .Clear:
|
||||
track.setValue(tag.clearedValue, forKey: tag.rawValue)
|
||||
track.setValue("", forKey: tag.rawValue)
|
||||
case .Ignore:
|
||||
break
|
||||
}
|
||||
track.setValue(value, forKey: tag.rawValue)
|
||||
}
|
||||
|
||||
/// Returns `true` if all `associatedTrack`s contain the same values as the
|
||||
@@ -165,13 +163,22 @@ public class Track: iTunesType {
|
||||
public var saved: Bool {
|
||||
let components = NSCalendar.currentCalendar().components(.Year, fromDate: releaseDate)
|
||||
for track in associatedTracks {
|
||||
guard track.name == name && track.artist == artistName && track.year == components.year && track.trackNumber == trackNumber && track.trackCount == trackCount && track.discNumber == discNumber && track.discCount == discCount && track.genre == genre && track.album == album.name && track.albumArtist == album.artistName && track.composer == "" else {
|
||||
let trackName = Preferences.sharedPreferences.useCensoredNames ? censoredName : name
|
||||
let albumName = Preferences.sharedPreferences.useCensoredNames ? album.censoredName : album.name
|
||||
let options = Preferences.sharedPreferences.caseSensitive ? [] : NSStringCompareOptions.CaseInsensitiveSearch
|
||||
guard track.name.compare(trackName, options: options, range: nil, locale: nil) == .OrderedSame else {
|
||||
return false
|
||||
}
|
||||
guard track.album.compare(albumName, options: options, range: nil, locale: nil) == .OrderedSame else {
|
||||
return false
|
||||
}
|
||||
guard track.artist == artistName && track.year == components.year && track.trackNumber == trackNumber && track.trackCount == trackCount && track.discNumber == discNumber && track.discCount == discCount && track.genre == genre && track.albumArtist == album.artistName && track.composer == "" else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension Track: Hashable {
|
||||
|
||||
@@ -100,10 +100,10 @@ public class TrackTableCellView: AdvancedTableCellView {
|
||||
public func setupForTrack(track: Track) {
|
||||
style = track.album.hasSameArtistNameAsTracks ? .Simple : .CompactSubtitle
|
||||
|
||||
textField?.stringValue = track.name
|
||||
textField?.stringValue = Preferences.sharedPreferences.useCensoredNames ? track.censoredName : track.name
|
||||
if track.associatedTracks.isEmpty {
|
||||
textField?.textColor = NSColor.disabledControlTextColor()
|
||||
} else if track.associatedTracks.count > 1 || track.associatedTracks[0].name != track.name {
|
||||
} else if track.associatedTracks.count > 1 || track.associatedTracks[0].name.compare(track.name, options: Preferences.sharedPreferences.caseSensitive ? [] : .CaseInsensitiveSearch, range: nil, locale: nil) != .OrderedSame {
|
||||
textField?.textColor = NSColor.redColor()
|
||||
} else {
|
||||
textField?.textColor = NSColor.controlTextColor()
|
||||
|
||||
Reference in New Issue
Block a user