// // $Id$ // // SPQueryFavoriteManager.m // sequel-pro // // Created by Stuart Connolly (stuconnolly.com) on Aug 23, 2009 // Copyright (c) 2009 Stuart Connolly. All rights reserved. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // // More info at #import "SPQueryFavoriteManager.h" #import "SPEncodingPopupAccessory.h" #define DEFAULT_QUERY_FAVORITE_FILE_EXTENSION @"sql" #define DEFAULT_SEQUELPRO_FILE_EXTENSION @"spf" #define QUERY_FAVORITES_PB_DRAG_TYPE @"SequelProQueryFavoritesPasteboard" @implementation SPQueryFavoriteManager /** * Initialize the manager with the supplied delegate */ - (id)initWithDelegate:(id)managerDelegate { if ((self = [super initWithWindowNibName:@"QueryFavoriteManager"])) { delegate = managerDelegate; prefs = [NSUserDefaults standardUserDefaults]; delegateRespondsToFavoriteUpdates = [delegate respondsToSelector:@selector(queryFavoritesHaveBeenUpdated:)]; } return self; } /** * Upon awakening bind the query text view's background colour. */ - (void)awakeFromNib { [favoriteQueryTextView setAllowsDocumentBackgroundColorChange:YES]; NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary]; [bindingOptions setObject:NSUnarchiveFromDataTransformerName forKey:@"NSValueTransformerName"]; [favoriteQueryTextView bind:@"backgroundColor" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:@"values.CustomQueryEditorBackgroundColor" options:bindingOptions]; // Select the first query [queryFavoritesController setSelectionIndex:0]; // Register drag types [favoritesTableView registerForDraggedTypes:[NSArray arrayWithObject:QUERY_FAVORITES_PB_DRAG_TYPE]]; } #pragma mark - #pragma mark Accessor methods /** * Returns the query favorites array. */ - (NSMutableArray *)queryFavorites { return [queryFavoritesController arrangedObjects]; } /** * This method is only implemented to be compatible with CMTextView. */ - (id)customQueryInstance { return [[[NSApp mainWindow] delegate] valueForKey:@"customQueryInstance"]; } #pragma mark - #pragma mark IBAction methods /** * Adds a query favorite */ - (IBAction)addQueryFavorite:(id)sender { NSMutableDictionary *favorite = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"New Favorite", @"", nil] forKeys:[NSArray arrayWithObjects:@"name", @"query", nil]]; [queryFavoritesController addObject:favorite]; [queryFavoritesController setSelectionIndex:([[queryFavoritesController arrangedObjects] count] - 1)]; [favoritesTableView reloadData]; [favoritesTableView scrollRowToVisible:[favoritesTableView selectedRow]]; [prefs synchronize]; // Inform the delegate that the query favorites have been updated if (delegateRespondsToFavoriteUpdates) { [delegate queryFavoritesHaveBeenUpdated:self]; } } /** * Removes a query favorite */ - (IBAction)removeQueryFavorite:(id)sender { NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Remove selected query favorites?", @"remove selected query favorites message") defaultButton:NSLocalizedString(@"Cancel", @"cancel button") alternateButton:NSLocalizedString(@"Remove", @"remove button") otherButton:nil informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to remove all selected query favorites? This action cannot be undone.", @"remove all selected query favorites informative message")]; [alert setAlertStyle:NSCriticalAlertStyle]; NSArray *buttons = [alert buttons]; // Change the alert's cancel button to have the key equivalent of return [[buttons objectAtIndex:0] setKeyEquivalent:@"\r"]; [[buttons objectAtIndex:1] setKeyEquivalent:@""]; [alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"removeSelectedFavorites"]; } /** * Removes all query favorites */ - (IBAction)removeAllQueryFavorites:(id)sender { NSAlert *alert = [NSAlert alertWithMessageText:NSLocalizedString(@"Remove all query favorites?", @"remove all query favorites message") defaultButton:NSLocalizedString(@"Cancel", @"cancel button") alternateButton:NSLocalizedString(@"Remove All", @"remove all button") otherButton:nil informativeTextWithFormat:NSLocalizedString(@"Are you sure you want to remove all of your saved query favorites? This action cannot be undone.", @"remove all query favorites informative message")]; [alert setAlertStyle:NSCriticalAlertStyle]; NSArray *buttons = [alert buttons]; // Change the alert's cancel button to have the key equivalent of return [[buttons objectAtIndex:0] setKeyEquivalent:@"\r"]; [[buttons objectAtIndex:1] setKeyEquivalent:@""]; [alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:@"removeAllFavorites"]; } /** * Copies a query favorite */ - (IBAction)copyQueryFavorite:(id)sender { if ([favoritesTableView numberOfSelectedRows] == 1) { NSMutableDictionary *favorite = [NSMutableDictionary dictionaryWithObjects:[NSArray arrayWithObjects:[[favoriteNameTextField stringValue] stringByAppendingFormat:@" Copy"], [favoriteQueryTextView string], nil] forKeys:[NSArray arrayWithObjects:@"name", @"query", nil]]; [queryFavoritesController addObject:favorite]; [queryFavoritesController setSelectionIndex:([[queryFavoritesController arrangedObjects] count] - 1)]; [favoritesTableView reloadData]; [favoritesTableView scrollRowToVisible:[favoritesTableView selectedRow]]; // Inform the delegate that the query favorites have been updated if (delegateRespondsToFavoriteUpdates) { [delegate queryFavoritesHaveBeenUpdated:self]; } } } /** * Saves the currently selected query favorite to a user specified file. */ - (IBAction)saveFavoriteToFile:(id)sender { NSSavePanel *panel = [NSSavePanel savePanel]; [panel setRequiredFileType:DEFAULT_QUERY_FAVORITE_FILE_EXTENSION]; [panel setExtensionHidden:NO]; [panel setAllowsOtherFileTypes:YES]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel setAccessoryView:[SPEncodingPopupAccessory encodingAccessory:[prefs integerForKey:@"lastSqlFileEncoding"] includeDefaultEntry:NO encodingPopUp:&encodingPopUp]]; [encodingPopUp setEnabled:YES]; [panel beginSheetForDirectory:nil file:[favoriteNameTextField stringValue] modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:@"saveQuery"]; } - (IBAction)exportFavorites:(id)sender { NSSavePanel *panel = [NSSavePanel savePanel]; [panel setRequiredFileType:DEFAULT_SEQUELPRO_FILE_EXTENSION]; [panel setExtensionHidden:NO]; [panel setAllowsOtherFileTypes:NO]; [panel setCanSelectHiddenExtension:YES]; [panel setCanCreateDirectories:YES]; [panel beginSheetForDirectory:nil file:nil modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:@"exportFavorites"]; } - (IBAction)importFavoritesByAdding:(id)sender { } - (IBAction)importFavoritesByReplacing:(id)sender { } /** * Closes the query favorite manager */ - (IBAction)closeQueryManagerSheet:(id)sender { // Ensure taht last changes will be written to prefs [[self window] makeFirstResponder:favoritesTableView]; [prefs synchronize]; [NSApp endSheet:[self window] returnCode:0]; [[self window] orderOut:self]; // Inform the delegate that the query favorites have been updated if (delegateRespondsToFavoriteUpdates) { [delegate queryFavoritesHaveBeenUpdated:self]; } } #pragma mark - #pragma mark SplitView delegate methods /** * Return the maximum possible size of the splitview. */ - (float)splitView:(NSSplitView *)sender constrainMaxCoordinate:(float)proposedMax ofSubviewAt:(int)offset { return (proposedMax - 220); } /** * Return the minimum possible size of the splitview. */ - (float)splitView:(NSSplitView *)sender constrainMinCoordinate:(float)proposedMin ofSubviewAt:(int)offset { return (proposedMin + 120); } #pragma mark - #pragma mark TableView datasource methods /** * Returns the number of query favorites. */ - (int)numberOfRowsInTableView:(NSTableView *)aTableView { return [[queryFavoritesController arrangedObjects] count]; } /** * Returns the value for the requested table column and row index. */ - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex { return [[[queryFavoritesController arrangedObjects] objectAtIndex:rowIndex] objectForKey:[aTableColumn identifier]]; } #pragma mark - #pragma mark Menu validation /** * Menu item validation. */ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { SEL action = [menuItem action]; if ( (action == @selector(copyQueryFavorite:)) || (action == @selector(saveFavoriteToFile:))) { return ([favoritesTableView numberOfSelectedRows] == 1); } else if ( (action == @selector(removeQueryFavorite:)) || ( action == @selector(exportFavorites:))) { return ([favoritesTableView numberOfSelectedRows] > 0); } else if (action == @selector(removeAllQueryFavorites:)) { return ([[queryFavoritesController arrangedObjects] count] > 0); } return YES; } #pragma mark - #pragma mark TableView drag & drop delegate methods /** * Return whether or not the supplied rows can be written. */ - (BOOL)tableView:(NSTableView *)tableView writeRows:(NSArray *)rows toPasteboard:(NSPasteboard *)pboard { if ([rows count] == 1) { NSArray *pboardTypes = [NSArray arrayWithObject:QUERY_FAVORITES_PB_DRAG_TYPE]; NSInteger originalRow = [[rows objectAtIndex:0] intValue]; [pboard declareTypes:pboardTypes owner:nil]; [pboard setString:[[NSNumber numberWithInt:originalRow] stringValue] forType:QUERY_FAVORITES_PB_DRAG_TYPE]; return YES; } return NO; } /** * Validate the proposed drop of the supplied rows. */ - (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id )info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation { NSArray *pboardTypes = [[info draggingPasteboard] types]; if (([pboardTypes count] > 1) && (row != -1)) { if (([pboardTypes containsObject:QUERY_FAVORITES_PB_DRAG_TYPE]) && (operation == NSTableViewDropAbove)) { NSInteger originalRow = [[[info draggingPasteboard] stringForType:QUERY_FAVORITES_PB_DRAG_TYPE] intValue]; if ((row != originalRow) && (row != (originalRow + 1))) { return NSDragOperationMove; } } } return NSDragOperationNone; } /** * Return whether or not to accept the drop of the supplied rows. */ - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id )info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation { NSInteger originalRow = [[[info draggingPasteboard] stringForType:QUERY_FAVORITES_PB_DRAG_TYPE] intValue]; NSInteger destinationRow = row; if (destinationRow > originalRow) destinationRow--; NSMutableDictionary *draggedRow = [NSMutableDictionary dictionaryWithDictionary:[[queryFavoritesController arrangedObjects] objectAtIndex:originalRow]]; [queryFavoritesController removeObjectAtArrangedObjectIndex:originalRow]; [queryFavoritesController insertObject:draggedRow atArrangedObjectIndex:destinationRow]; [favoritesTableView reloadData]; [favoritesTableView selectRowIndexes:[NSIndexSet indexSetWithIndex:destinationRow] byExtendingSelection:NO]; return YES; } #pragma mark - #pragma mark Other /** * Sheet did end method */ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(NSString *)contextInfo { if ([contextInfo isEqualToString:@"removeAllFavorites"]) { if (returnCode == NSAlertAlternateReturn) { [queryFavoritesController removeObjects:[queryFavoritesController arrangedObjects]]; } } if([contextInfo isEqualToString:@"removeSelectedFavorites"]) { if (returnCode == NSAlertAlternateReturn) { NSIndexSet *indexes = [favoritesTableView selectedRowIndexes]; // get last index NSUInteger currentIndex = [indexes lastIndex]; while (currentIndex != NSNotFound) { [queryFavoritesController removeObjectAtArrangedObjectIndex:currentIndex]; // get next index (beginning from the end) currentIndex = [indexes indexLessThanIndex:currentIndex]; } [favoritesTableView reloadData]; [prefs synchronize]; // Set focus to favorite list to avoid an unstable state [[self window] makeFirstResponder:favoritesTableView]; // Inform the delegate that the query favorites have been updated if (delegateRespondsToFavoriteUpdates) [delegate queryFavoritesHaveBeenUpdated:self]; } } } /** * Save panel did end method. */ - (void)savePanelDidEnd:(NSSavePanel *)panel returnCode:(int)returnCode contextInfo:(NSString *)contextInfo { if([contextInfo isEqualToString:@"saveQuery"]) { if (returnCode == NSOKButton) { NSError *error = nil; [prefs setInteger:[[encodingPopUp selectedItem] tag] forKey:@"lastSqlFileEncoding"]; [prefs synchronize]; [[favoriteQueryTextView string] writeToFile:[panel filename] atomically:YES encoding:[[encodingPopUp selectedItem] tag] error:&error]; if (error) [[NSAlert alertWithError:error] runModal]; } } else if([contextInfo isEqualToString:@"exportFavorites"]) { if (returnCode == NSOKButton) { // Build a SPF with format = "query favorites" NSMutableDictionary *spfdata = [NSMutableDictionary dictionary]; NSMutableArray *favoriteData = [NSMutableArray array]; NSMutableDictionary *data = [NSMutableDictionary dictionary]; [spfdata setObject:[NSNumber numberWithInt:1] forKey:@"version"]; [spfdata setObject:@"query favorites" forKey:@"format"]; [spfdata setObject:[NSNumber numberWithBool:NO] forKey:@"encrypted"]; NSIndexSet *indexes = [favoritesTableView selectedRowIndexes]; // get last index NSUInteger currentIndex = [indexes lastIndex]; while (currentIndex != NSNotFound) { [favoriteData addObject:[[self queryFavorites] objectAtIndex:currentIndex]]; // get next index (beginning from the end) currentIndex = [indexes indexLessThanIndex:currentIndex]; } [data setObject:favoriteData forKey:@"queryFavorites"]; [spfdata setObject:data forKey:@"data"]; NSString *err = nil; NSData *plist = [NSPropertyListSerialization dataFromPropertyList:spfdata format:NSPropertyListXMLFormat_v1_0 errorDescription:&err]; if(err != nil) { NSAlert *alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:NSLocalizedString(@"Error while converting query favorite data", @"error while converting query favorite data")] defaultButton:NSLocalizedString(@"OK", @"OK button") alternateButton:nil otherButton:nil informativeTextWithFormat:err]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert runModal]; return; } NSError *error = nil; [plist writeToFile:[panel filename] options:NSAtomicWrite error:&error]; if (error) [[NSAlert alertWithError:error] runModal]; } } } @end