Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
AudioCDWindowController.m
/* |
File: AudioCDWindowController.m |
Contains: NSWindowController subclass to handle the UI for the document |
Copyright: (c) Copyright 2005 Apple Computer, Inc. All rights reserved. |
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. |
("Apple") in consideration of your agreement to the following terms, and your |
use, installation, modification or redistribution of this Apple software |
constitutes acceptance of these terms. If you do not agree with these terms, |
please do not use, install, modify or redistribute this Apple software. |
In consideration of your agreement to abide by the following terms, and subject |
to these terms, Apple grants you a personal, non-exclusive license, under Apple's |
copyrights in this original Apple software (the "Apple Software"), to use, |
reproduce, modify and redistribute the Apple Software, with or without |
modifications, in source and/or binary forms; provided that if you redistribute |
the Apple Software in its entirety and without modifications, you must retain |
this notice and the following text and disclaimers in all such redistributions of |
the Apple Software. Neither the name, trademarks, service marks or logos of |
Apple Computer, Inc. may be used to endorse or promote products derived from the |
Apple Software without specific prior written permission from Apple. Except as |
expressly stated in this notice, no other rights or licenses, express or implied, |
are granted by Apple herein, including but not limited to any patent rights that |
may be infringed by your derivative works or by other works in which the Apple |
Software may be incorporated. |
The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO |
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED |
WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN |
COMBINATION WITH YOUR PRODUCTS. |
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE |
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION |
OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT |
(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN |
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
Change History (most recent first): |
1.0 (July 5th, 2005) |
*/ |
#import "AudioCDWindowController.h" |
#import "EABDictionaryHelpers.h" |
#import <unistd.h> |
// Helper defines to turn off notification handling while we're |
// modifying the document ourselves. |
#define EAB_DISABLE_NOTIFICATIONS \ |
NS_DURING \ |
disableNotifications = YES; |
#define EAB_ENABLE_NOTIFICATIONS \ |
NS_HANDLER \ |
disableNotifications = NO; \ |
[localException raise]; \ |
NS_ENDHANDLER \ |
disableNotifications = NO; |
// ------------------------------------------------------------------------- |
// Constants |
// ------------------------------------------------------------------------- |
#pragma mark Constants |
enum { |
EABDiscOptionsTab = 0, |
EABTrackOptionsTab = 1 |
}; |
enum { |
EABTrackOptionsPersonalitySingle = 0, |
EABTrackOptionsPersonalityMultiple = 1, |
EABTrackOptionsPersonalityNone = 2 |
}; |
// Internal drag types |
NSString * const EABTrackListRows = @"EABTrackListRows"; |
// Localized strings |
NSString * EABSingularSecond = @"second"; |
NSString * EABPluralSeconds = @"seconds"; |
NSString * EABSingularTrack = @"%u track"; |
NSString * EABPluralTracks = @"%u tracks"; |
NSString * EABDiscLength = @"Total time: %@"; |
NSString * EABTimeFormat = @"%m:%02s"; |
NSString * EABInvalid = @"(Invalid)"; |
NSString * EABTTAddTracks = @"Add tracks"; |
NSString * EABTTRemoveTracks = @"Remove tracks"; |
NSString * EABTTBurnDisc = @"Record these files to an audio CD."; |
@implementation AudioCDWindowController |
#pragma mark - |
#pragma mark Initialization |
// ------------------------------------------------------------------------- |
// initialize |
// ------------------------------------------------------------------------- |
// ObjC class initializer. |
// |
+ (void)initialize |
{ |
// Load the localized strings that we will need. |
EABSingularSecond = NSLocalizedString(EABSingularSecond,@"Noun, single second"); |
EABPluralSeconds = NSLocalizedString(EABPluralSeconds,@"Noun, multiple seconds"); |
EABSingularTrack = NSLocalizedString(EABSingularTrack,@"Format string, single track"); |
EABPluralTracks = NSLocalizedString(EABPluralTracks,@"Format string, multiple tracks"); |
EABDiscLength = NSLocalizedString(EABDiscLength,@"Format string, disc length"); |
EABTimeFormat = NSLocalizedString(EABTimeFormat,@"Format string, time"); |
EABInvalid = NSLocalizedString(EABInvalid,@"Warning text, used to indicate when ISRC is invalid"); |
EABTTAddTracks = NSLocalizedString(EABTTAddTracks,@"Tooltip - Add tracks"); |
EABTTRemoveTracks = NSLocalizedString(EABTTRemoveTracks,@"Tooltip - Remove tracks"); |
EABTTBurnDisc = NSLocalizedString(EABTTBurnDisc,@"Tooltip - Burn disc"); |
} |
- (void)dealloc |
{ |
// Log deallocations to make sure they're happening when we expect them to. |
//NSLog(@"-[AudioCDWindowController dealloc]"); |
// We're going away, so we no longer care about notifications. |
[[NSNotificationCenter defaultCenter] removeObserver:self]; |
[super dealloc]; |
} |
// ------------------------------------------------------------------------- |
// windowDidLoad |
// ------------------------------------------------------------------------- |
// NSWindowController - called when the window has finished loading. |
// |
- (void)windowDidLoad |
{ |
// Relay the message to our superclass. |
[super windowDidLoad]; |
// Initialize instance variables; these are cached items |
// from the document that we use frequently. |
document = (AudioCDDocument*)[self document]; |
// Set tool tips on various controls. |
[_buttonDiscAddTrack setToolTip:EABTTAddTracks]; |
[_buttonDiscRemoveTrack setToolTip:EABTTRemoveTracks]; |
[_buttonDiscBurn setToolTip:EABTTBurnDisc]; |
// Sign up for notifications on the document so that we'll know |
// when an undo/redo occurs. |
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(trackListDidChange:) |
name:EABAudioCDTrackListChanged object:document]; |
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(discPropertyDidChange:) |
name:EABAudioCDDiscPropertyChanged object:document]; |
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(trackPropertyDidChange:) |
name:EABAudioCDTrackPropertyChanged object:document]; |
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willUndoOrRedoChange:) |
name:NSUndoManagerWillUndoChangeNotification object:[document undoManager]]; |
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willUndoOrRedoChange:) |
name:NSUndoManagerWillRedoChangeNotification object:[document undoManager]]; |
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUndoOrRedoChange:) |
name:NSUndoManagerDidUndoChangeNotification object:[document undoManager]]; |
[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didUndoOrRedoChange:) |
name:NSUndoManagerDidRedoChangeNotification object:[document undoManager]]; |
// Register the drag types that the track table can accept. |
[_tableDiscTracks registerForDraggedTypes:[NSArray arrayWithObjects: |
EABTrackListRows, NSFilenamesPboardType, nil]]; |
// The burn button needs to be told to scale the icon, otherwise it |
// will try to use the image at full size (128x128) and clip!! |
NSSize buttonSize = [_buttonDiscBurn frame].size; |
float imageWidth = buttonSize.width; |
if (imageWidth > buttonSize.height - 20) |
imageWidth = buttonSize.height - 20; |
[[_buttonDiscBurn image] setScalesWhenResized:YES]; |
[[_buttonDiscBurn image] setSize:NSMakeSize(imageWidth,imageWidth)]; |
// Initialize the buttons and text fields for the incoming document, |
// starting out with nothing selected. |
NSDictionary *discInfo = [document discInfo]; |
[_buttonDiscBurn setEnabled:([document count] > 0) ? YES:NO]; |
[_buttonDiscAddTrack setEnabled:YES]; |
[_buttonDiscRemoveTrack setEnabled:NO]; |
[self updateDiscTrackTable]; |
[_tabviewMainOptions selectTabViewItemAtIndex:EABDiscOptionsTab]; |
[_checkboxDiscCDText setState:[[discInfo numberForKey:EABDiscCDTextEnabled] intValue]]; |
[_textDiscCDTextTitle setStringValue:[discInfo stringForKey:DRCDTextTitleKey]]; |
[_textDiscCDTextPerformer setStringValue:[discInfo stringForKey:DRCDTextPerformerKey]]; |
[_textDiscCDTextComposer setStringValue:[discInfo stringForKey:DRCDTextComposerKey]]; |
[_textDiscCDTextSongwriter setStringValue:[discInfo stringForKey:DRCDTextSongwriterKey]]; |
[_textDiscCDTextArranger setStringValue:[discInfo stringForKey:DRCDTextArrangerKey]]; |
[_textDiscCDTextMessage setStringValue:[discInfo stringForKey:DRCDTextSpecialMessageKey]]; |
[_textDiscCDTextDiscIdent setStringValue:[discInfo stringForKey:DRCDTextDiscIdentKey]]; |
[_popupDiscCDTextGenre selectItemAtIndex:[[discInfo numberForKey:DRCDTextGenreCodeKey] intValue]]; |
[_textDiscCDTextGenre setStringValue:[discInfo stringForKey:DRCDTextGenreKey]]; |
[_textDiscCDTextClosed setStringValue:[discInfo stringForKey:DRCDTextClosedKey]]; |
[_checkboxDiscMCN setState:[[discInfo numberForKey:EABDiscMCNEnabled] intValue]]; |
[_textDiscMCN setStringValue:[discInfo stringForKey:EABDiscMCN]]; |
[_tabviewTrackOptionsPersonality selectTabViewItemAtIndex:EABTrackOptionsPersonalityNone]; |
} |
// ------------------------------------------------------------------------------- |
// respondsToSelector |
// ------------------------------------------------------------------------------- |
// NSObject - overridden to print out who's asking us for methods. Sometimes |
// this is useful when adding new code. |
// |
#if 0 |
- (BOOL)respondsToSelector:(SEL)selector |
{ |
NSLog(@"AudioCDWindowController rts - %@", NSStringFromSelector(selector)); |
return [super respondsToSelector:selector]; |
} |
#endif |
#pragma mark - |
#pragma mark Miscellaneous |
// ------------------------------------------------------------------------------- |
// validateMenuItem: |
// ------------------------------------------------------------------------------- |
// NSMenuValidation - enables and disables the menu items that we're responsible for. |
// |
- (BOOL)validateMenuItem:(NSMenuItem *)item |
{ |
// Nothing's enabled while we're burning. |
if (burning) |
return NO; |
// Otherwise check based on the action. |
BOOL enabled = YES; |
SEL action = [item action]; |
if (action == @selector(discAddTrackRequest:) || action == @selector(discCDTextChanged:)) |
enabled = YES; |
if (action == @selector(discRemoveTrackRequest:) || action == @selector(delete:)) |
enabled = [_buttonDiscRemoveTrack isEnabled]; |
else if (action == @selector(discBurnRequest:)) |
enabled = [_buttonDiscBurn isEnabled]; |
return enabled; |
} |
// ------------------------------------------------------------------------------- |
// delete: |
// ------------------------------------------------------------------------------- |
// IBAction - equivalent to "Remove Tracks". |
// |
- (IBAction)delete:(id)sender |
{ |
[self discRemoveTrackRequest:sender]; |
} |
// ------------------------------------------------------------------------------- |
// isBurning |
// ------------------------------------------------------------------------------- |
// Returns YES if we're in the middle of burning, NO if not. |
// |
- (BOOL)isBurning |
{ |
return burning; |
} |
#pragma mark - |
#pragma mark Burning |
// ------------------------------------------------------------------------------- |
// discBurnRequest: |
// ------------------------------------------------------------------------------- |
// Called when the "Burn Disc" button is pressed, or when the "Burn Disc" menu |
// item is selected. |
// |
- (IBAction)discBurnRequest:(id)sender |
{ |
// If there are no tracks, you can't burn. |
if ([document count] == 0) |
return; |
// Display the standard burn panel as a sheet. |
DRBurnSetupPanel* bsp = [DRBurnSetupPanel setupPanel]; |
[bsp beginSetupSheetForWindow:[self window] |
modalDelegate:self |
didEndSelector:@selector(burnSetupPanelDidEnd:returnCode:contextInfo:) |
contextInfo:nil]; |
} |
// ------------------------------------------------------------------------------- |
// burnSetupPanelDidEnd:returnCode:contextInfo: |
// ------------------------------------------------------------------------------- |
// DRSetupPanel delegate - called when the burn setup panel has completed. |
// |
- (void)burnSetupPanelDidEnd:(DRBurnSetupPanel*)panel returnCode:(int)returnCode contextInfo:(void*)contextInfo |
{ |
// Hide the sheet. |
[panel orderOut:self]; |
// If the return code wasn't ok, don't do anything. |
if (returnCode != NSOKButton) return; |
// Get the burn object and fill in its properties. |
DRBurn *burn = [panel burnObject]; |
[document setBurnProperties:burn]; |
// Create the tracks. |
id layout = [document createLayoutForBurn]; |
// Start the burn, displaying progress in the standard progress panel. |
DRBurnProgressPanel *bpp = [DRBurnProgressPanel progressPanel]; |
[bpp setDelegate:self]; |
[bpp beginProgressSheetForBurn:burn layout:layout modalForWindow:[self window]]; |
} |
// ------------------------------------------------------------------------------- |
// burnProgressPanelWillBegin: |
// ------------------------------------------------------------------------------- |
// DRBurnProgressPanel delegate - called when the progress panel is starting. |
// |
- (void)burnProgressPanelWillBegin:(NSNotification*)notification |
{ |
// Remember that we're burning, and don't allow quitting or |
// closing of the document during a burn. |
burning = YES; |
} |
// ------------------------------------------------------------------------------- |
// burnProgressPanelDidFinish: |
// ------------------------------------------------------------------------------- |
// DRBurnProgressPanel delegate - called when the progress panel has finished. |
// |
- (void)burnProgressPanelDidFinish:(NSNotification*)notification |
{ |
// Whether the burn succeeded or failed, we're no longer burning. |
burning = NO; |
} |
// ------------------------------------------------------------------------------- |
// windowShouldClose |
// ------------------------------------------------------------------------------- |
// NSWindow delegate - called when the window is trying to decide whether it's |
// okay to close. |
// |
- (BOOL)windowShouldClose |
{ |
return !burning; // Don't close while burning. |
} |
#pragma mark - |
#pragma mark Track table |
// ------------------------------------------------------------------------------- |
// numberOfRowsInTableView: |
// ------------------------------------------------------------------------------- |
// NSTableDataSource method - returns the number of rows. |
// |
- (int)numberOfRowsInTableView:(NSTableView*)table |
{ |
if (table == _tableDiscTracks) |
return [document count]; |
else |
[NSException raise:NSInvalidArgumentException format:@"Unknown table view"]; |
// make the compiler happy |
return 0; |
} |
// ------------------------------------------------------------------------------- |
// tableView:objectValueForTableColumn:row: |
// ------------------------------------------------------------------------------- |
// NSTableDataSource method - returns the data to display. |
// |
- (id)tableView:(NSTableView*)table objectValueForTableColumn:(NSTableColumn*)column row:(int)row |
{ |
if (table == _tableDiscTracks) |
{ |
// Track number |
int trackNumber = (row+1); |
if (column == _tableColumnTrackNumber) |
return [NSNumber numberWithInt:trackNumber]; |
// Filename |
NSDictionary *trackInfo = [[document trackInfo] objectAtIndex:trackNumber]; |
if (column == _tableColumnFilename) |
return [[[trackInfo stringForKey:EABTrackFilePath] lastPathComponent] stringByDeletingPathExtension]; |
// Length |
if (column == _tableColumnLength) |
return [[DRMSF msfWithFrames:[[trackInfo numberForKey:EABTrackLength] intValue]] descriptionWithFormat:EABTimeFormat]; |
[NSException raise:NSInvalidArgumentException format:@"Unknown table column"]; |
} |
else |
[NSException raise:NSInvalidArgumentException format:@"Unknown table view"]; |
// make the compiler happy |
return nil; |
} |
// ------------------------------------------------------------------------------- |
// tableView:writeRows:toPasteboard: |
// ------------------------------------------------------------------------------- |
// NSTableDataSource method - validates an outgoing drag, and provides the data. |
// |
- (BOOL)tableView:(NSTableView*)table writeRows:(NSArray*)rows toPasteboard:(NSPasteboard*)pb |
{ |
// We only want to allow dragging of selected rows. |
NSEnumerator *iter = [rows objectEnumerator]; |
NSNumber *row; |
while ((row = [iter nextObject]) != nil) |
{ |
if (![table isRowSelected:[row intValue]]) |
return NO; |
} |
// Check the table. |
if (table == _tableDiscTracks) |
{ |
// Intra-table drag - data is the array of rows. |
[pb declareTypes:[NSArray arrayWithObject:EABTrackListRows] owner:nil]; |
[pb setPropertyList:rows forType:EABTrackListRows]; |
return YES; |
} |
else |
[NSException raise:NSInvalidArgumentException format:@"Unknown table view"]; |
// make the compiler happy |
return NO; |
} |
// ------------------------------------------------------------------------------- |
// tableView:validateDrop:proposedRow:proposedDropOperation: |
// ------------------------------------------------------------------------------- |
// NSTableDataSource method - validates an incoming drag to make sure the |
// table handles it correctly. |
// |
- (NSDragOperation)tableView:(NSTableView*)table validateDrop:(id <NSDraggingInfo>)info proposedRow:(int)row proposedDropOperation:(NSTableViewDropOperation)op |
{ |
// Make drops at the end of the table go to the end. |
if (row == -1) |
{ |
row = [table numberOfRows]; |
op = NSTableViewDropAbove; |
[table setDropRow:row dropOperation:op]; |
} |
// We don't ever want to drop onto a row, only between rows. |
if (op == NSTableViewDropOn) |
[table setDropRow:(row+1) dropOperation:NSTableViewDropAbove]; |
return NSTableViewDropAbove; |
} |
// ------------------------------------------------------------------------------- |
// tableView:acceptDrop:row:dropOperation: |
// ------------------------------------------------------------------------------- |
// NSTableDataSource method - receives a drag. |
// |
- (BOOL)tableView:(NSTableView*)table acceptDrop:(id <NSDraggingInfo>)info row:(int)dropRow dropOperation:(NSTableViewDropOperation)op; |
{ |
NSPasteboard *pb = [info draggingPasteboard]; |
NSDragOperation srcMask = [info draggingSourceOperationMask]; |
#if 0 |
// Log the source mask for debugging. |
NSLog(@"[info draggingSourceOperationMask] = %u = %s%s%s%s%s%s", (unsigned int)srcMask, |
(srcMask & NSDragOperationCopy) ? "Copy ":"", |
(srcMask & NSDragOperationLink) ? "Link ":"", |
(srcMask & NSDragOperationGeneric) ? "Generic ":"", |
(srcMask & NSDragOperationPrivate) ? "Private ":"", |
(srcMask & NSDragOperationMove) ? "Move ":"", |
(srcMask & NSDragOperationDelete) ? "Delete":""); |
#endif |
if (table == _tableDiscTracks) |
{ |
BOOL accepted = NO; |
NS_DURING |
NSArray *array; |
// - - - - - - - - - - - - - - - - - - |
// Intra-table drag - data is the array of rows. |
if (!accepted && (array = [pb propertyListForType:EABTrackListRows]) != NULL) |
{ |
NSEnumerator * iter = nil; |
BOOL isCopy = (srcMask & NSDragOperationMove) ? NO:YES; |
// Move the tracks. |
dropRow = [document moveTracks:array to:dropRow copying:isCopy]; |
// Select the newly-dragged items. |
iter = [array objectEnumerator]; |
[table deselectAll:self]; |
while ([iter nextObject] != NULL) |
[table selectRow:(dropRow++) byExtendingSelection:YES]; |
// Indicate that we finished the drag. |
accepted = YES; |
} |
// - - - - - - - - - - - - - - - - - - |
// File drop from another app (such as Finder) |
if (!accepted && (array = [pb propertyListForType:NSFilenamesPboardType]) != NULL) |
{ |
// We don't seem to get files from the Finder in any particular order, |
// which means that the same drop performed multiple times has essentially |
// (from the user's perspective) random ordering. That's not so great. |
// To make it deterministic, sort the incoming list before we add them. |
[self addFiles:[array sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] atIndex:dropRow]; |
accepted = YES; |
} |
NS_HANDLER |
// An exception occurred. Uh-oh. Update the track table so that |
// it stays consistent, and re-raise the exception. |
[self updateDiscTrackTable]; |
[localException raise]; |
NS_ENDHANDLER |
return accepted; |
} |
// Still here, so we didn't accept the drop. |
return NO; |
} |
// ------------------------------------------------------------------------------- |
// tableViewSelectionDidChange: |
// ------------------------------------------------------------------------------- |
// NSTableViewNotifications - delegate method called when the selection changes. |
// We use this to update the options tabview. |
// |
- (void)tableViewSelectionDidChange:(NSNotification*)notification |
{ |
NSTableView *table = (NSTableView*)[notification object]; |
if (table == _tableDiscTracks) |
{ |
// Update the options tab view. |
[self updateOptions]; |
// Activate a tab based on what the user clicked. |
int numSelectedRows = [_tableDiscTracks numberOfSelectedRows]; |
if (numSelectedRows == 0) |
{ |
// Switch to the "disc options" tab in the main view. |
[_tabviewMainOptions selectTabViewItemAtIndex:EABDiscOptionsTab]; |
// Disable the "remove track" button. |
[_buttonDiscRemoveTrack setEnabled:NO]; |
} |
else if (numSelectedRows == 1) |
{ |
// Switch to the "track options" tab in the main view. |
[_tabviewMainOptions selectTabViewItemAtIndex:EABTrackOptionsTab]; |
// Enable the "remove track" button. |
[_buttonDiscRemoveTrack setEnabled:YES]; |
} |
else |
{ |
// Switch to the "track options" tab in the main view. |
[_tabviewMainOptions selectTabViewItemAtIndex:EABTrackOptionsTab]; |
// Enable the "remove track" button. |
[_buttonDiscRemoveTrack setEnabled:YES]; |
} |
} |
else |
[NSException raise:NSInvalidArgumentException format:@"Unknown table view"]; |
} |
// ------------------------------------------------------------------------------- |
// updateOptions |
// ------------------------------------------------------------------------------- |
// Called when the options tabview needs updating. |
// |
- (void)updateOptions |
{ |
int numSelectedRows = [_tableDiscTracks numberOfSelectedRows]; |
// Update the disc options tab to reflect the values in the document. |
NSDictionary *discInfo = [document discInfo]; |
[_checkboxDiscCDText setState:[[discInfo numberForKey:EABDiscCDTextEnabled] intValue]]; |
[_textDiscCDTextTitle setStringValue:[discInfo stringForKey:DRCDTextTitleKey]]; |
[_textDiscCDTextPerformer setStringValue:[discInfo stringForKey:DRCDTextPerformerKey]]; |
[_textDiscCDTextComposer setStringValue:[discInfo stringForKey:DRCDTextComposerKey]]; |
[_textDiscCDTextSongwriter setStringValue:[discInfo stringForKey:DRCDTextSongwriterKey]]; |
[_textDiscCDTextArranger setStringValue:[discInfo stringForKey:DRCDTextArrangerKey]]; |
[_textDiscCDTextMessage setStringValue:[discInfo stringForKey:DRCDTextSpecialMessageKey]]; |
[_textDiscCDTextDiscIdent setStringValue:[discInfo stringForKey:DRCDTextDiscIdentKey]]; |
[_popupDiscCDTextGenre selectItemAtIndex:[[discInfo numberForKey:DRCDTextGenreCodeKey] intValue]]; |
[_textDiscCDTextGenre setStringValue:[discInfo stringForKey:DRCDTextGenreKey]]; |
[_textDiscCDTextClosed setStringValue:[discInfo stringForKey:DRCDTextClosedKey]]; |
[_checkboxDiscMCN setState:[[discInfo numberForKey:EABDiscMCNEnabled] intValue]]; |
[_textDiscMCN setStringValue:[discInfo stringForKey:EABDiscMCN]]; |
// Change the personality of the track options tab according to the selection, |
// and update the data in the tab view to reflect the values in the document. |
if (numSelectedRows == 0) |
{ |
// Nothing's selected. |
// Pop the track options tab over to its empty personality. |
[_tabviewTrackOptionsPersonality selectTabViewItemAtIndex:EABTrackOptionsPersonalityNone]; |
} |
else if (numSelectedRows == 1) |
{ |
NSDictionary *trackInfo = [document trackInfoForTrack:[_tableDiscTracks selectedRow]]; |
NSNumber *preGap = [trackInfo numberForKey:EABTrackPreGap]; |
// Switch to single-item personality. |
[_tabviewTrackOptionsPersonality selectTabViewItemAtIndex:EABTrackOptionsPersonalitySingle]; |
// Update the interface to reflect the document's values for the current selection. |
[_textSingleTrackFilename setStringValue:[[trackInfo stringForKey:EABTrackFilePath] lastPathComponent]]; |
[_textSingleTrackFilename setToolTip:[trackInfo stringForKey:EABTrackFilePath]]; |
[_checkboxSingleTrackPreEmphasis setState:[[trackInfo numberForKey:EABTrackPreEmphasisEnabled] intValue]]; |
[_textSingleTrackCDTextTitle setStringValue:[trackInfo stringForKey:DRCDTextTitleKey]]; |
[_textSingleTrackCDTextPerformer setStringValue:[trackInfo stringForKey:DRCDTextPerformerKey]]; |
[_textSingleTrackCDTextComposer setStringValue:[trackInfo stringForKey:DRCDTextComposerKey]]; |
[_textSingleTrackCDTextSongwriter setStringValue:[trackInfo stringForKey:DRCDTextSongwriterKey]]; |
[_textSingleTrackCDTextArranger setStringValue:[trackInfo stringForKey:DRCDTextArrangerKey]]; |
[_textSingleTrackCDTextMessage setStringValue:[trackInfo stringForKey:DRCDTextSpecialMessageKey]]; |
[_textSingleTrackCDTextClosed setStringValue:[trackInfo stringForKey:DRCDTextClosedKey]]; |
[_textSingleTrackPreGap setObjectValue:preGap]; |
if ([[_textSingleTrackPreGap stringValue] isEqualToString:@"1"]) |
[_textSingleTrackPreGapSeconds setStringValue:EABSingularSecond]; |
else |
[_textSingleTrackPreGapSeconds setStringValue:EABPluralSeconds]; |
[_checkboxSingleTrackISRC setState:[[trackInfo numberForKey:EABTrackISRCEnabled] intValue]]; |
[_textSingleTrackISRC setStringValue:[trackInfo stringForKey:EABTrackISRC]]; |
if ([document hasValidISRCForTrack:[_tableDiscTracks selectedRow]]) |
[_textSingleTrackISRCInvalid setStringValue:@""]; |
else |
[_textSingleTrackISRCInvalid setStringValue:EABInvalid]; |
[_checkboxSingleTrackISRCCDText setState:[[trackInfo numberForKey:EABTrackISRCInCDText] intValue]]; |
[_checkboxSingleTrackIndexPoints setState:[[trackInfo numberForKey:EABTrackIndexPointsEnabled] intValue]]; |
} |
else |
{ |
NSNumber *preGap = [self aggregateTrackNumberForKey:EABTrackPreGap]; |
// Switch to multiple-item personality. |
[_tabviewTrackOptionsPersonality selectTabViewItemAtIndex:EABTrackOptionsPersonalityMultiple]; |
// Reset the bulk modify flags, which are used to keep track of what's changed. |
bulkModifyCDTextTitle = NO; |
bulkModifyCDTextPerformer = NO; |
bulkModifyCDTextComposer = NO; |
bulkModifyCDTextSongwriter = NO; |
bulkModifyCDTextArranger = NO; |
bulkModifyCDTextMessage = NO; |
bulkModifyCDTextClosed = NO; |
[_textBulkCDTextTitle setStringValue:[self aggregateTrackStringForKey:DRCDTextTitleKey]]; |
[_textBulkCDTextPerformer setStringValue:[self aggregateTrackStringForKey:DRCDTextPerformerKey]]; |
[_textBulkCDTextComposer setStringValue:[self aggregateTrackStringForKey:DRCDTextComposerKey]]; |
[_textBulkCDTextSongwriter setStringValue:[self aggregateTrackStringForKey:DRCDTextSongwriterKey]]; |
[_textBulkCDTextArranger setStringValue:[self aggregateTrackStringForKey:DRCDTextArrangerKey]]; |
[_textBulkCDTextMessage setStringValue:[self aggregateTrackStringForKey:DRCDTextSpecialMessageKey]]; |
[_textBulkCDTextClosed setStringValue:[self aggregateTrackStringForKey:DRCDTextClosedKey]]; |
int preemp = [self aggregateTrackCheckboxStateForKey:EABTrackPreEmphasisEnabled]; |
[_checkboxBulkPreEmphasis setAllowsMixedState:(preemp == NSMixedState) ? YES:NO]; |
[_checkboxBulkPreEmphasis setState:preemp]; |
if (preGap == nil) |
[_textBulkPreGap setStringValue:@""]; |
else |
{ |
[_textBulkPreGap setObjectValue:preGap]; |
if ([[_textBulkPreGap stringValue] isEqualToString:@"1"]) |
[_textBulkPreGapSeconds setStringValue:EABSingularSecond]; |
else |
[_textBulkPreGapSeconds setStringValue:EABPluralSeconds]; |
} |
} |
} |
// ------------------------------------------------------------------------------- |
// aggregateTrackCheckboxStateForKey: |
// ------------------------------------------------------------------------------- |
// Helper method for the bulk-modify tab view item. |
// |
// Returns a checkbox state which is the aggregate of all selected rows in the |
// track table. |
// |
// If values are mixed, returns 2. |
// If all values are 1, returns 1. |
// If all values are 0, returns 0. |
// |
- (int)aggregateTrackCheckboxStateForKey:(NSString *)key |
{ |
NSNumber *val = [self aggregateTrackNumberForKey:key]; |
if (val == nil) |
return NSMixedState; |
else if ([val intValue] == 0) |
return NSOffState; |
else |
return NSOnState; |
} |
// ------------------------------------------------------------------------------- |
// aggregateTrackStringForKey: |
// ------------------------------------------------------------------------------- |
// Helper method for the bulk-modify tab view item. |
// |
// Returns a string which is the aggregate of the values for all selected rows |
// in the track table. nil is considered equivalent to the empty string. |
// |
// If values are mixed, returns an empty string. |
// If values are identical, returns the value. |
// |
- (NSString*)aggregateTrackStringForKey:(NSString*)key |
{ |
// Loop through and start looking at keys. |
unsigned i, count = [document count]; |
NSString* string = nil; |
BOOL firstTime = YES; |
for (i=0; i<count; ++i) |
{ |
// Only the selected tracks, please. |
if ([_tableDiscTracks isRowSelected:i]) |
{ |
NSDictionary *trackInfo = [document trackInfoForTrack:i]; |
NSString* thisString = [trackInfo stringForKey:key]; |
if (firstTime == YES) |
string = thisString; |
else if (![string isEqualToString:thisString]) |
return @""; // mixed, so return empty string |
firstTime = NO; |
} |
} |
return string; // all the same, return the string |
} |
// ------------------------------------------------------------------------------- |
// aggregateTrackNumberForKey: |
// ------------------------------------------------------------------------------- |
// Helper method for the bulk-modify tab view item. |
// |
// Returns a number which is the aggregate of the values for all selected rows |
// in the track table. nil is considered equivalent to NSNumber 0. |
// |
// If values are mixed, returns nil. |
// If values are identical, returns the value. |
// |
- (NSNumber*)aggregateTrackNumberForKey:(NSString*)key |
{ |
// Loop through and start looking at keys. |
unsigned i, count = [document count]; |
NSNumber* number = nil; |
BOOL firstTime = YES; |
for (i=0; i<count; ++i) |
{ |
// Only the selected tracks, please. |
if ([_tableDiscTracks isRowSelected:i]) |
{ |
NSDictionary *trackInfo = [document trackInfoForTrack:i]; |
NSNumber* thisNumber = [trackInfo numberForKey:key]; |
if (firstTime == YES) |
number = thisNumber; |
else if (![number isEqual:thisNumber]) |
return nil; // mixed, so return nil |
firstTime = NO; |
} |
} |
return number; // all the same, return the number |
} |
// ------------------------------------------------------------------------------- |
// updateDiscTrackTable |
// ------------------------------------------------------------------------------- |
// Helper method, called to update the table when one of the following occurs: |
// - The track list changes (add/remove/reorder) |
// - The selection changes |
// |
- (void)updateDiscTrackTable |
{ |
// Tell the table to reload. |
[_tableDiscTracks reloadData]; |
// Manually update the summary items below the table. |
NSString *format = [document count] == 1 ? EABSingularTrack:EABPluralTracks; |
[_textDiscTrackCount setStringValue:[NSString stringWithFormat:format, [document count]]]; |
[_textDiscLength setStringValue:[NSString stringWithFormat:EABDiscLength, [[document totalLength] descriptionWithFormat:EABTimeFormat]]]; |
// Manually enable/disable the "Remove Track" button. |
[_buttonDiscRemoveTrack setEnabled:([_tableDiscTracks numberOfSelectedRows] == 0) ? NO:YES]; |
// Manually enable/disable the "Burn Disc" button. |
[_buttonDiscBurn setEnabled:([document count] > 0) ? YES:NO]; |
} |
#pragma mark - |
#pragma mark Track table actions |
// ------------------------------------------------------------------------------- |
// discAddTrackRequest: |
// ------------------------------------------------------------------------------- |
// IBAction - called when the "Add Track" button is pushed, or when the |
// "Add Track" menu item is selected. |
// |
- (IBAction)discAddTrackRequest:(id)sender |
{ |
NSOpenPanel *panel = [NSOpenPanel openPanel]; |
[panel setAllowsMultipleSelection:YES]; |
[panel setCanChooseDirectories:NO]; |
[panel setCanChooseFiles:YES]; |
[panel setResolvesAliases:YES]; |
[panel setTitle:NSLocalizedString(@"Select one or more audio files.",@"Open panel - prompt when adding tracks")]; |
[panel setPrompt:NSLocalizedString(@"Select","Open panel - button when adding tracks")]; |
[panel beginSheetForDirectory:nil file:nil types:nil |
modalForWindow:[self window] |
modalDelegate:self |
didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) |
contextInfo:self]; |
} |
// ------------------------------------------------------------------------------- |
// openPanelDidEnd:returnCode:contextInfo: |
// ------------------------------------------------------------------------------- |
// NSOpenPanel delegate - called when the open panel from "Add Track" completes. |
// |
- (void)openPanelDidEnd:(NSOpenPanel*)panel returnCode:(int)returnCode contextInfo:(void *)contextInfo |
{ |
// Hide the panel. |
[panel orderOut:self]; |
// If the return code wasn't ok, don't do anything. |
if (returnCode != NSOKButton) return; |
// Get the filenames, then add the tracks at the end. |
NSArray *filenames = [panel filenames]; |
[self addFiles:[filenames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] atIndex:[document count]]; |
} |
// ------------------------------------------------------------------------------- |
// addFiles:atIndex: |
// ------------------------------------------------------------------------------- |
// Helper method which adds the specified list of files to the disc. May be invoked |
// by the following methods: |
// |
// - openPanelDidEnd:returnCode:contextInfo: |
// - tableView:acceptDrop:row:dropOperation: |
// |
// Currently adding isn't threaded; it blocks the whole UI while processing files. |
// This could be improved. |
// |
- (void)addFiles:(NSArray*)filenames atIndex:(unsigned)index |
{ |
unsigned int i = 0, count = [filenames count]; |
// Sanity check that we're not adding zero files. |
if (count == 0) |
return; |
// If we're adding just one file, do it and exit. |
if (count == 1) |
{ |
NS_DURING |
EAB_DISABLE_NOTIFICATIONS |
[document addTrackFromFile:[filenames objectAtIndex:0] atIndex:index]; |
EAB_ENABLE_NOTIFICATIONS |
NS_HANDLER |
NS_ENDHANDLER |
[self updateDiscTrackTable]; |
return; |
} |
// Otherwise we're doing more than one file, which can take |
// a while, so we're going to put up a sheet indicating progress |
// to the user. |
NS_DURING |
// Put up the progress sheet. |
[_textAddingFilename setStringValue:@""]; |
[_progressAdding setIndeterminate:NO]; |
[_progressAdding setMinValue:0.0]; |
[_progressAdding setMaxValue:(double)count]; |
[_progressAdding setDoubleValue:0.0]; |
[NSApp beginSheet:_panelAdding |
modalForWindow:[self window] |
modalDelegate:nil |
didEndSelector:nil |
contextInfo:nil]; |
// Go through the filenames and process them. |
for (i = 0; i < count; ++i) |
{ |
NSString *path = [filenames objectAtIndex:i]; |
// Update the display in the sheet. |
[_textAddingFilename setStringValue:[path lastPathComponent]]; |
[_textAddingFilename display]; |
[_progressAdding setDoubleValue:(double)(i+1)]; |
[_progressAdding display]; |
// Add the file. |
NS_DURING |
EAB_DISABLE_NOTIFICATIONS |
[document addTrackFromFile:path atIndex:index++]; |
EAB_ENABLE_NOTIFICATIONS |
NS_HANDLER |
NS_ENDHANDLER |
// Process a few events so that we're not totally unresponsive. |
// If we don't respond to certain asynchronous messages, we may get |
// disconnected from autodiskmount or other important system services. |
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; |
// Update the table as each file is processed. |
[self updateDiscTrackTable]; |
[_tableDiscTracks display]; |
} |
// Tear down the sheet. |
[NSApp endSheet:_panelAdding]; |
[_panelAdding orderOut:self]; |
NS_HANDLER |
// Make sure we tear down the sheet if a problem occurs. |
[NSApp endSheet:_panelAdding]; |
[_panelAdding orderOut:self]; |
NS_ENDHANDLER |
} |
// ------------------------------------------------------------------------------- |
// discRemoveTrackRequest: |
// ------------------------------------------------------------------------------- |
// Called when the "Remove Track" button is pressed, or when the "Remove Track" |
// menu item is selected. |
// |
- (IBAction)discRemoveTrackRequest:(id)sender |
{ |
// Only perform this action if there are selected rows. |
if ([_tableDiscTracks numberOfSelectedRows] != 0) |
{ |
// Loop (backwards!) through the selected rows. We temporarily disable |
// notifications while doing this. |
EAB_DISABLE_NOTIFICATIONS |
unsigned i, count = [document count]; |
for (i=count; i>0; --i) |
{ |
// Remove the selected tracks. |
if ([_tableDiscTracks isRowSelected:(i-1)]) |
[document removeTrackAtIndex:(i-1)]; |
} |
EAB_ENABLE_NOTIFICATIONS |
// We've changed the document details, so update. |
[self updateDiscTrackTable]; |
// Clear the selection. |
[_tableDiscTracks deselectAll:self]; |
} |
} |
#pragma mark - |
#pragma mark Disc options |
// ------------------------------------------------------------------------------- |
// discCDTextChanged: |
// ------------------------------------------------------------------------------- |
- (IBAction)discCDTextChanged:(id)sender |
{ |
id oldValue = nil, newValue = nil; |
NSString *key = nil; |
// If the user picked something from the genre code menu, copy the text |
// of the menu item into the "Genre Name" field. |
if (sender == _popupDiscCDTextGenre) |
{ |
[_textDiscCDTextGenre setStringValue:[[_popupDiscCDTextGenre selectedItem] title]]; |
[self discCDTextChanged:_textDiscCDTextGenre]; |
} |
// Find the key to use. |
if (sender == _textDiscCDTextTitle) key = DRCDTextTitleKey; |
if (sender == _textDiscCDTextPerformer) key = DRCDTextPerformerKey; |
if (sender == _textDiscCDTextComposer) key = DRCDTextComposerKey; |
if (sender == _textDiscCDTextSongwriter) key = DRCDTextSongwriterKey; |
if (sender == _textDiscCDTextArranger) key = DRCDTextArrangerKey; |
if (sender == _textDiscCDTextMessage) key = DRCDTextSpecialMessageKey; |
if (sender == _textDiscCDTextClosed) key = DRCDTextClosedKey; |
if (sender == _textDiscCDTextDiscIdent) key = DRCDTextDiscIdentKey; |
if (sender == _textDiscCDTextGenre) key = DRCDTextGenreKey; |
if (sender == _popupDiscCDTextGenre) key = DRCDTextGenreCodeKey; |
// Get the old value. |
oldValue = [[[document discPropertyForKey:key] retain] autorelease]; |
// Update the document with the values from the text fields. |
EAB_DISABLE_NOTIFICATIONS |
if (sender == _popupDiscCDTextGenre) |
[document setDiscProperty:[NSNumber numberWithInt:[sender indexOfSelectedItem]] forKey:key]; |
else |
[document setDiscProperty:[sender stringValue] forKey:key]; |
// Get the new value. |
newValue = [[[document discPropertyForKey:key] retain] autorelease]; |
// Compare old and new values, and if something changed then make sure |
// we set CD-Text to be enabled. |
if (oldValue != newValue && |
((oldValue != nil && newValue == nil) || (oldValue == nil && newValue != nil) || ![oldValue isEqual:newValue])) |
{ |
[document setDiscProperty:[NSNumber numberWithBool:YES] forKey:EABDiscCDTextEnabled]; |
[_checkboxDiscCDText setState:NSOnState]; |
} |
EAB_ENABLE_NOTIFICATIONS |
[self updateOptions]; |
} |
// ------------------------------------------------------------------------------- |
// discCDTextToggle: |
// ------------------------------------------------------------------------------- |
- (IBAction)discCDTextToggle:(id)sender |
{ |
EAB_DISABLE_NOTIFICATIONS |
[document setDiscProperty:[NSNumber numberWithBool:(BOOL)[_checkboxDiscCDText intValue]] forKey:EABDiscCDTextEnabled]; |
EAB_ENABLE_NOTIFICATIONS |
} |
// ------------------------------------------------------------------------------- |
// discMCNChanged: |
// ------------------------------------------------------------------------------- |
- (IBAction)discMCNChanged:(id)sender |
{ |
EAB_DISABLE_NOTIFICATIONS |
[document setDiscProperty:[_textDiscMCN stringValue] forKey:EABDiscMCN]; |
// Automatically turn off the checkbox when the string is empty. |
if ([[_textDiscMCN stringValue] isEqualToString:@""]) |
{ |
[document setDiscProperty:[NSNumber numberWithBool:NO] forKey:EABDiscMCNEnabled]; |
[_checkboxDiscMCN setState:NSOffState]; |
} |
// Automatically enable the checkbox when a valid ISRC is entered. |
else |
{ |
[document setDiscProperty:[NSNumber numberWithBool:YES] forKey:EABDiscMCNEnabled]; |
[_checkboxDiscMCN setState:NSOnState]; |
} |
EAB_ENABLE_NOTIFICATIONS |
} |
// ------------------------------------------------------------------------------- |
// discMCNToggle: |
// ------------------------------------------------------------------------------- |
- (IBAction)discMCNToggle:(id)sender |
{ |
EAB_DISABLE_NOTIFICATIONS |
[document setDiscProperty:[NSNumber numberWithBool:(BOOL)[_checkboxDiscMCN intValue]] forKey:EABDiscMCNEnabled]; |
EAB_ENABLE_NOTIFICATIONS |
} |
#pragma mark - |
#pragma mark Single track options |
// ------------------------------------------------------------------------------- |
// singleTrackShowFileRequest: |
// ------------------------------------------------------------------------------- |
- (IBAction)singleTrackShowFileRequest:(id)sender |
{ |
NSString *path = [document propertyForKey:EABTrackFilePath ofTrack:[_tableDiscTracks selectedRow]]; |
[[NSWorkspace sharedWorkspace] selectFile:path inFileViewerRootedAtPath:nil]; |
} |
// ------------------------------------------------------------------------------- |
// singleTrackCDTextChanged: |
// ------------------------------------------------------------------------------- |
- (IBAction)singleTrackCDTextChanged:(id)sender |
{ |
int trackIndex = [_tableDiscTracks selectedRow]; |
// Update the document. We temporarily disable notifications while doing this. |
EAB_DISABLE_NOTIFICATIONS |
[document setProperty:[_textSingleTrackCDTextTitle stringValue] forKey:DRCDTextTitleKey ofTrack:trackIndex]; |
[document setProperty:[_textSingleTrackCDTextPerformer stringValue] forKey:DRCDTextPerformerKey ofTrack:trackIndex]; |
[document setProperty:[_textSingleTrackCDTextComposer stringValue] forKey:DRCDTextComposerKey ofTrack:trackIndex]; |
[document setProperty:[_textSingleTrackCDTextSongwriter stringValue] forKey:DRCDTextSongwriterKey ofTrack:trackIndex]; |
[document setProperty:[_textSingleTrackCDTextArranger stringValue] forKey:DRCDTextArrangerKey ofTrack:trackIndex]; |
[document setProperty:[_textSingleTrackCDTextMessage stringValue] forKey:DRCDTextSpecialMessageKey ofTrack:trackIndex]; |
[document setProperty:[_textSingleTrackCDTextClosed stringValue] forKey:DRCDTextClosedKey ofTrack:trackIndex]; |
EAB_ENABLE_NOTIFICATIONS |
[self updateOptions]; |
} |
// ------------------------------------------------------------------------------- |
// singleTrackPreGapChanged: |
// ------------------------------------------------------------------------------- |
- (IBAction)singleTrackPreGapChanged:(id)sender |
{ |
EAB_DISABLE_NOTIFICATIONS |
[document setProperty:[NSNumber numberWithFloat:[_textSingleTrackPreGap floatValue]] |
forKey:EABTrackPreGap ofTrack:[_tableDiscTracks selectedRow]]; |
EAB_ENABLE_NOTIFICATIONS |
// Handle singular/plural consistency. |
if ([[_textSingleTrackPreGap stringValue] isEqualToString:@"1"]) |
[_textSingleTrackPreGapSeconds setStringValue:EABSingularSecond]; |
else |
[_textSingleTrackPreGapSeconds setStringValue:EABPluralSeconds]; |
} |
// ------------------------------------------------------------------------------- |
// singleTrackPreEmphasisToggle: |
// ------------------------------------------------------------------------------- |
- (IBAction)singleTrackPreEmphasisToggle:(id)sender |
{ |
EAB_DISABLE_NOTIFICATIONS |
[document setProperty:[NSNumber numberWithBool:(BOOL)[_checkboxSingleTrackPreEmphasis intValue]] |
forKey:EABTrackPreEmphasisEnabled ofTrack:[_tableDiscTracks selectedRow]]; |
EAB_ENABLE_NOTIFICATIONS |
} |
// ------------------------------------------------------------------------------- |
// singleTrackISRCToggle: |
// ------------------------------------------------------------------------------- |
- (IBAction)singleTrackISRCToggle:(id)sender |
{ |
EAB_DISABLE_NOTIFICATIONS |
[document setProperty:[NSNumber numberWithBool:(BOOL)[_checkboxSingleTrackISRC intValue]] |
forKey:EABTrackISRCEnabled ofTrack:[_tableDiscTracks selectedRow]]; |
[document setProperty:[NSNumber numberWithBool:(BOOL)[_checkboxSingleTrackISRCCDText intValue]] |
forKey:EABTrackISRCInCDText ofTrack:[_tableDiscTracks selectedRow]]; |
EAB_ENABLE_NOTIFICATIONS |
} |
// ------------------------------------------------------------------------------- |
// singleTrackISRCChanged: |
// ------------------------------------------------------------------------------- |
- (IBAction)singleTrackISRCChanged:(id)sender |
{ |
BOOL valid = [document hasValidISRCForTrack:[_tableDiscTracks selectedRow]]; |
EAB_DISABLE_NOTIFICATIONS |
[document setProperty:[_textSingleTrackISRC stringValue] |
forKey:EABTrackISRC ofTrack:[_tableDiscTracks selectedRow]]; |
// Automatically turn off the checkbox when the string is empty. |
if ([[_textSingleTrackISRC stringValue] isEqualToString:@""]) |
{ |
[document setProperty:[NSNumber numberWithBool:NO] |
forKey:EABTrackISRCEnabled ofTrack:[_tableDiscTracks selectedRow]]; |
[_checkboxSingleTrackISRC setState:NSOffState]; |
} |
// Automatically enable the checkbox when a valid ISRC is entered. |
else if (valid) |
{ |
[document setProperty:[NSNumber numberWithBool:YES] |
forKey:EABTrackISRCEnabled ofTrack:[_tableDiscTracks selectedRow]]; |
[_checkboxSingleTrackISRC setState:NSOnState]; |
} |
EAB_ENABLE_NOTIFICATIONS |
// Display validity of the ISRC. |
if (valid) |
[_textSingleTrackISRCInvalid setStringValue:@""]; |
else |
[_textSingleTrackISRCInvalid setStringValue:EABInvalid]; |
} |
// ------------------------------------------------------------------------------- |
// singleTrackIndexPointsToggle: |
// ------------------------------------------------------------------------------- |
- (IBAction)singleTrackIndexPointsToggle:(id)sender |
{ |
EAB_DISABLE_NOTIFICATIONS |
[document setProperty:[NSNumber numberWithBool:(BOOL)[_checkboxSingleTrackIndexPoints intValue]] |
forKey:EABTrackIndexPointsEnabled ofTrack:[_tableDiscTracks selectedRow]]; |
EAB_ENABLE_NOTIFICATIONS |
} |
// ------------------------------------------------------------------------------- |
// singleTrackIndexPointsChanged: |
// ------------------------------------------------------------------------------- |
- (IBAction)singleTrackIndexPointsChanged:(id)sender |
{ |
// There's no interface for actually setting index points yet - this is a stub |
// and is never invoked. |
EAB_DISABLE_NOTIFICATIONS |
EAB_ENABLE_NOTIFICATIONS |
} |
#pragma mark - |
#pragma mark Control delegates |
// ------------------------------------------------------------------------- |
// control:textShouldBeginEditing: |
// ------------------------------------------------------------------------- |
// Delegate method for the bulk modify text fields for CD-Text. |
// |
- (BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)text |
{ |
NSTextField * sender = (NSTextField*)control; |
// Set the bulk modification flag corresponding to the field that the user has modified. |
if (sender == _textBulkCDTextTitle) bulkModifyCDTextTitle = YES; |
else if (sender == _textBulkCDTextPerformer) bulkModifyCDTextPerformer = YES; |
else if (sender == _textBulkCDTextComposer) bulkModifyCDTextComposer = YES; |
else if (sender == _textBulkCDTextArranger) bulkModifyCDTextArranger = YES; |
else if (sender == _textBulkCDTextSongwriter) bulkModifyCDTextSongwriter = YES; |
else if (sender == _textBulkCDTextMessage) bulkModifyCDTextMessage = YES; |
else if (sender == _textBulkCDTextClosed) bulkModifyCDTextClosed = YES; |
// Okay, go ahead and begin editing. |
return YES; |
} |
// ------------------------------------------------------------------------- |
// controlTextDidChange: |
// ------------------------------------------------------------------------- |
// |
- (void)controlTextDidChange:(NSNotification *)notification |
{ |
id sender = [notification object]; |
if (sender == _textSingleTrackISRC) |
{ |
// Check the text as the user's editing it. |
NSString *isrc = [_textSingleTrackISRC stringValue]; |
if ([document isValidISRC:isrc incomplete:YES]) |
[_textSingleTrackISRCInvalid setStringValue:@""]; |
else |
[_textSingleTrackISRCInvalid setStringValue:EABInvalid]; |
} |
} |
#pragma mark - |
#pragma mark Multiple track options |
// ------------------------------------------------------------------------- |
// bulkCDTextChanged: |
// ------------------------------------------------------------------------- |
// Called when a CD-Text bulk modify field has finished editing. |
// |
- (IBAction)bulkCDTextChanged:(id)sender |
{ |
// Gather the values from any text fields whose checkboxes are set. |
NSString *keys[16], *values[16]; |
unsigned num = 0; |
if (bulkModifyCDTextTitle) |
{ |
keys[num] = DRCDTextTitleKey; |
values[num++] = [_textBulkCDTextTitle stringValue]; |
} |
if (bulkModifyCDTextPerformer) |
{ |
keys[num] = DRCDTextPerformerKey; |
values[num++] = [_textBulkCDTextPerformer stringValue]; |
} |
if (bulkModifyCDTextComposer) |
{ |
keys[num] = DRCDTextComposerKey; |
values[num++] = [_textBulkCDTextComposer stringValue]; |
} |
if (bulkModifyCDTextSongwriter) |
{ |
keys[num] = DRCDTextSongwriterKey; |
values[num++] = [_textBulkCDTextSongwriter stringValue]; |
} |
if (bulkModifyCDTextArranger) |
{ |
keys[num] = DRCDTextArrangerKey; |
values[num++] = [_textBulkCDTextArranger stringValue]; |
} |
if (bulkModifyCDTextMessage) |
{ |
keys[num] = DRCDTextSpecialMessageKey; |
values[num++] = [_textBulkCDTextMessage stringValue]; |
} |
if (bulkModifyCDTextClosed) |
{ |
keys[num] = DRCDTextClosedKey; |
values[num++] = [_textBulkCDTextClosed stringValue]; |
} |
// Loop through the selected tracks and update the document with the |
// modified values. We temporarily turn off notification handling |
// while we're doing this. |
EAB_DISABLE_NOTIFICATIONS |
unsigned i, j, count = [document count]; |
for (i=0; i<count; ++i) |
{ |
// Only the selected tracks, please. |
if ([_tableDiscTracks isRowSelected:i]) |
{ |
for (j=0; j<num; ++j) |
[document setProperty:values[j] forKey:keys[j] ofTrack:i]; |
} |
} |
EAB_ENABLE_NOTIFICATIONS |
[self updateDiscTrackTable]; |
} |
// ------------------------------------------------------------------------- |
// bulkPreEmphasisToggle: |
// ------------------------------------------------------------------------- |
// |
- (IBAction)bulkPreEmphasisToggle:(id)sender |
{ |
// Once the user has clicked it, the checkbox no longer allows a mixed state. |
[_checkboxBulkPreEmphasis setAllowsMixedState:NO]; |
// Get the new state from the checkbox. |
NSNumber *val = [NSNumber numberWithBool:(BOOL)[_checkboxBulkPreEmphasis intValue]]; |
// Loop through the selected tracks and update the document with the |
// modified values. |
EAB_DISABLE_NOTIFICATIONS |
unsigned i, count = [document count]; |
for (i=0; i<count; ++i) |
{ |
// Only the selected tracks, please. |
if ([_tableDiscTracks isRowSelected:i]) |
[document setProperty:val forKey:EABTrackPreEmphasisEnabled ofTrack:i]; |
} |
EAB_ENABLE_NOTIFICATIONS |
[self updateOptions]; |
} |
// ------------------------------------------------------------------------- |
// bulkPreGapChanged: |
// ------------------------------------------------------------------------- |
// |
- (IBAction)bulkPreGapChanged:(id)sender |
{ |
NSNumber *val = [NSNumber numberWithFloat:[_textBulkPreGap floatValue]]; |
// Loop through the selected tracks and update the document with the |
// modified values. |
EAB_DISABLE_NOTIFICATIONS |
unsigned i, count = [document count]; |
for (i=0; i<count; ++i) |
{ |
// Only the selected tracks, please. |
if ([_tableDiscTracks isRowSelected:i]) |
[document setProperty:val forKey:EABTrackPreGap ofTrack:i]; |
} |
EAB_ENABLE_NOTIFICATIONS |
[self updateOptions]; |
} |
#pragma mark - |
#pragma mark Notifications |
// ------------------------------------------------------------------------- |
// trackListDidChange: |
// ------------------------------------------------------------------------- |
// Notification received when the track list changes. |
// |
- (void)trackListDidChange:(NSNotification*)notification |
{ |
// Notifications are disabled when the UI itself is modifying properties. |
if (disableNotifications) return; |
// Record this event into the change list. |
id whichTracks = [[notification userInfo] objectForKey:EABWhichTrackKey]; |
if (whichTracks != nil) [listOfChanges addObject:whichTracks]; |
// Update the UI. |
[self updateDiscTrackTable]; |
} |
// ------------------------------------------------------------------------- |
// discPropertyDidChange: |
// ------------------------------------------------------------------------- |
// Notification received when a disc property changes. |
// |
- (void)discPropertyDidChange:(NSNotification*)notification |
{ |
// Notifications are disabled when the UI itself is modifying properties. |
if (disableNotifications) return; |
// Record this event into the change list. |
[listOfChanges addObject:[NSNumber numberWithInt:EABTracksRemoved]]; |
// Update the UI. |
[self updateOptions]; |
} |
// ------------------------------------------------------------------------- |
// trackPropertyDidChange: |
// ------------------------------------------------------------------------- |
// Notification received when a track property changes. |
// |
- (void)trackPropertyDidChange:(NSNotification*)notification |
{ |
// Notifications are disabled when the UI itself is modifying properties. |
if (disableNotifications) return; |
// Record this event into the change list. |
id whichTracks = [[notification userInfo] objectForKey:EABWhichTrackKey]; |
if (whichTracks != nil) [listOfChanges addObject:whichTracks]; |
// Update the UI. |
[self updateOptions]; |
} |
// ------------------------------------------------------------------------- |
// willUndoOrRedoChange: |
// ------------------------------------------------------------------------- |
// Notification received when an undo/redo is starting. |
// |
- (void)willUndoOrRedoChange:(NSNotification*)notification |
{ |
// Start tracking changes. |
[listOfChanges release]; |
listOfChanges = [[NSMutableArray alloc] initWithCapacity:[document count]]; |
} |
// ------------------------------------------------------------------------- |
// didUndoOrRedoChange: |
// ------------------------------------------------------------------------- |
// Notification received when an undo/redo has completed. We change |
// tabs, selections, etc in the UI to display the change(s) that occurred. |
// |
- (void)didUndoOrRedoChange:(NSNotification*)notification |
{ |
NSMutableArray *rowsToSelect = [NSMutableArray arrayWithCapacity:[document count]]; |
// Parse the list that we built up as we received notifications. |
NSEnumerator *iter = [listOfChanges objectEnumerator]; |
NSNumber *val; |
while ((val = [iter nextObject]) != nil) |
{ |
// Each object in the array is an NSNumber. |
int trackIndex = [val intValue]; |
if (trackIndex == EABTracksRemoved) |
[rowsToSelect removeAllObjects]; |
else if (![rowsToSelect containsObject:val]) |
[rowsToSelect addObject:val]; |
} |
// Select the changed rows. Since we're the tableview delegate, we'll notice |
// the selection change and automatically switch to the appropriate tab. |
[_tableDiscTracks deselectAll:self]; |
iter = [rowsToSelect objectEnumerator]; |
while ((val = [iter nextObject]) != nil) |
[_tableDiscTracks selectRow:[val intValue] byExtendingSelection:YES]; |
// Stop tracking changes. |
[listOfChanges release]; |
listOfChanges = nil; |
} |
@end |
Copyright © 2005 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2005-07-06