// // SPExportController.m // sequel-pro // // Created by Ben Perry (benperry.com.au) on February 12, 2009. // Copyright (c) 2010 Ben Perry. All rights reserved. // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. // // More info at #import "SPExportController.h" #import "SPExportInitializer.h" #import "SPTablesList.h" #import "SPTableData.h" #import "SPTableContent.h" #import "SPGrowlController.h" #import "SPExportFile.h" #import "SPAlertSheets.h" #import "SPExportFilenameUtilities.h" #import "SPExportFileNameTokenObject.h" #import "SPDatabaseDocument.h" #import "SPThreadAdditions.h" #import "SPCustomQuery.h" #import "SPExportController+SharedPrivateAPI.h" #import // Constants static const NSUInteger SPExportUIPadding = 20; static NSString * const SPTableViewStructureColumnID = @"structure"; static NSString * const SPTableViewContentColumnID = @"content"; static NSString * const SPTableViewDropColumnID = @"drop"; static const NSString *SPSQLExportStructureEnabled = @"SQLExportStructureEnabled"; static const NSString *SPSQLExportContentEnabled = @"SQLExportContentEnabled"; static const NSString *SPSQLExportDropEnabled = @"SQLExportDropEnabled"; @interface SPExportController (PrivateAPI) - (void)_switchTab; - (void)_checkForDatabaseChanges; - (void)_displayExportTypeOptions:(BOOL)display; - (void)_updateExportFormatInformation; - (void)_updateExportAdvancedOptionsLabel; - (void)_setPreviousExportFilenameAndPath; - (void)_toggleExportButton:(id)uiStateDict; - (void)_toggleExportButtonOnBackgroundThread; - (void)_toggleExportButtonWithBool:(NSNumber *)enable; - (void)_resizeWindowForCustomFilenameViewByHeightDelta:(NSInteger)delta; - (void)_resizeWindowForAdvancedOptionsViewByHeightDelta:(NSInteger)delta; - (void)_waitUntilQueueIsEmpty:(id)sender; - (void)_queueIsEmpty:(id)sender; @end @implementation SPExportController @synthesize connection; @synthesize exportToMultipleFiles; @synthesize exportCancelled; #pragma mark - #pragma mark Initialisation /** * Initializes an instance of SPExportController. */ - (id)init { if ((self = [super initWithWindowNibName:@"ExportDialog"])) { [self setExportCancelled:NO]; [self setExportToMultipleFiles:YES]; mainNibLoaded = NO; exportType = SPSQLExport; exportSource = SPTableExport; exportTableCount = 0; currentTableExportIndex = 0; exportFilename = [[NSMutableString alloc] init]; exportTypeLabel = @""; createCustomFilename = NO; previousConnectionEncodingViaLatin1 = NO; tables = [[NSMutableArray alloc] init]; exporters = [[NSMutableArray alloc] init]; exportFiles = [[NSMutableArray alloc] init]; operationQueue = [[NSOperationQueue alloc] init]; showAdvancedView = NO; showCustomFilenameView = NO; serverLowerCaseTableNameValue = NSNotFound; heightOffset1 = 0; heightOffset2 = 0; windowMinWidth = [[self window] minSize].width; windowMinHeigth = [[self window] minSize].height; prefs = [NSUserDefaults standardUserDefaults]; localizedTokenNames = [@{ SPFileNameHostTokenName: NSLocalizedString(@"Host", @"export filename host token"), SPFileNameDatabaseTokenName: NSLocalizedString(@"Database", @"export filename database token"), SPFileNameTableTokenName: NSLocalizedString(@"Table", @"table"), SPFileNameDateTokenName: NSLocalizedString(@"Date", @"export filename date token"), SPFileNameYearTokenName: NSLocalizedString(@"Year", @"export filename date token"), SPFileNameMonthTokenName: NSLocalizedString(@"Month", @"export filename date token"), SPFileNameDayTokenName: NSLocalizedString(@"Day", @"export filename date token"), SPFileNameTimeTokenName: NSLocalizedString(@"Time", @"export filename time token"), SPFileNameFavoriteTokenName: NSLocalizedString(@"Favorite", @"export filename favorite name token") } retain]; } return self; } /** * Upon awakening select the first toolbar item */ - (void)awakeFromNib { // As this controller also loads its own nib, it may call awakeFromNib multiple times; perform setup only once. if (mainNibLoaded) return; mainNibLoaded = YES; // Select the 'selected tables' option [exportInputPopUpButton selectItemAtIndex:SPTableExport]; // Select the SQL tab [[exportTypeTabBar tabViewItemAtIndex:0] setView:exporterView]; // By default a new SQL INSERT statement should be created every 250KiB of data [exportSQLInsertNValueTextField setIntegerValue:250]; // Prevents the background colour from changing when clicked [[exportCustomFilenameViewLabelButton cell] setHighlightsBy:NSNoCellMask]; // Set the progress indicator's max value [exportProgressIndicator setMaxValue:(NSInteger)[exportProgressIndicator bounds].size.width]; // Empty the tokenizing character set for the filename field [exportCustomFilenameTokenField setTokenizingCharacterSet:[NSCharacterSet characterSetWithCharactersInString:@""]]; // Accept Core Animation [exportOptionsTabBar wantsLayer]; [exportTablelistScrollView wantsLayer]; [exportTableListButtonBar wantsLayer]; } #pragma mark - #pragma mark Export methods /** * Displays the export window with the supplied tables and export type/format selected. * * @param exportTables The array of table names to be exported * @param format The export format to be used. See SPExportType constants. * @param source The source of the export. See SPExportSource constants. */ - (void)exportTables:(NSArray *)exportTables asFormat:(SPExportType)format usingSource:(SPExportSource)source { // Select the correct tab [exportTypeTabBar selectTabViewItemAtIndex:format]; [self _setPreviousExportFilenameAndPath]; [self updateDisplayedExportFilename]; [self refreshTableList:nil]; [exporters removeAllObjects]; [exportFiles removeAllObjects]; // If tables were supplied, select them if (exportTables) { // Disable all tables for (NSMutableArray *table in tables) { [table replaceObjectAtIndex:1 withObject:@NO]; [table replaceObjectAtIndex:2 withObject:@NO]; [table replaceObjectAtIndex:3 withObject:@NO]; } // Select the supplied tables for (NSMutableArray *table in tables) { for (NSString *exportTable in exportTables) { if ([exportTable isEqualToString:[table objectAtIndex:0]]) { [table replaceObjectAtIndex:1 withObject:@YES]; [table replaceObjectAtIndex:2 withObject:@YES]; [table replaceObjectAtIndex:3 withObject:@YES]; } } } [exportTableList reloadData]; } // Ensure interface validation [self _switchTab]; [self _updateExportAdvancedOptionsLabel]; [self setExportInput:source]; [NSApp beginSheet:[self window] modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nil]; } /** * Opens the errors sheet and displays the supplied errors string. * * @param errors The errors string to be displayed */ - (void)openExportErrorsSheetWithString:(NSString *)errors { [errorsTextView setString:@""]; [errorsTextView setString:errors]; [NSApp beginSheet:errorsWindow modalForWindow:[tableDocumentInstance parentWindow] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nil]; } /** * Displays the export finished Growl notification. */ - (void)displayExportFinishedGrowlNotification { // Export finished Growl notification [[SPGrowlController sharedGrowlController] notifyWithTitle:@"Export Finished" description:[NSString stringWithFormat:NSLocalizedString(@"Finished exporting to %@", @"description for finished exporting growl notification"), exportFilename] document:tableDocumentInstance notificationName:@"Export Finished"]; } #pragma mark - #pragma mark IB action methods /** * Opens the export dialog selecting the appropriate export type and source based on the current context. * For example, if either the table content view or custom query editor views are active and there is * data available, these options will be selected as the export source ('Filtered' or 'Query Result'). If * either of these views are not active then the default source are the currently selected tables. If no * tables are currently selected then all tables are checked. Note that in this instance the default export * type is SQL where as in the case of filtered or query result export the default type is CSV. * * @param sender The caller (can be anything or nil as it is not currently used). */ - (IBAction)export:(id)sender { SPExportType selectedExportType = SPSQLExport; SPExportSource selectedExportSource = SPTableExport; NSArray *selectedTables = [tablesListInstance selectedTableItems]; BOOL isCustomQuerySelected = ([tableDocumentInstance isCustomQuerySelected] && ([[customQueryInstance currentResult] count] > 1)); BOOL isContentSelected = ([[tableDocumentInstance selectedToolbarItemIdentifier] isEqualToString:SPMainToolbarTableContent] && ([[tableContentInstance currentResult] count] > 1)); if (isContentSelected) { selectedTables = nil; selectedExportType = SPCSVExport; selectedExportSource = SPFilteredExport; } else if (isCustomQuerySelected) { selectedTables = nil; selectedExportType = SPCSVExport; selectedExportSource = SPQueryExport; } else { selectedTables = ([selectedTables count]) ? selectedTables : nil; } [self exportTables:selectedTables asFormat:selectedExportType usingSource:selectedExportSource]; } /** * Closes the export dialog. */ - (IBAction)closeSheet:(id)sender { if ([sender window] == [self window]) { // Close the advanced options view if it's open [exportAdvancedOptionsView setHidden:YES]; [exportAdvancedOptionsViewButton setState:NSOffState]; showAdvancedView = NO; // Close the customize filename view if it's open [exportCustomFilenameView setHidden:YES]; [exportCustomFilenameViewButton setState:NSOffState]; showCustomFilenameView = NO; // If open close the advanced options view and custom filename view [self _resizeWindowForAdvancedOptionsViewByHeightDelta:0]; [self _resizeWindowForCustomFilenameViewByHeightDelta:0]; } [NSApp endSheet:[sender window] returnCode:[sender tag]]; [[sender window] orderOut:self]; } - (BOOL)setExportInput:(SPExportSource)input { SPExportSource actualInput = input; // Dot will always be a TableExport if(exportType == SPDotExport) { actualInput = SPTableExport; } //check if the type actually is valid else if(![[exportInputPopUpButton itemAtIndex:input] isEnabled]) { //...no, pick a valid one instead for (NSMenuItem *item in [exportInputPopUpButton itemArray]) { if([item isEnabled]) { actualInput = [exportInputPopUpButton indexOfItem:item]; goto set_input; } } // nothing found (should not happen) SPLog(@"did not find any valid export input!?"); return NO; } set_input: exportSource = actualInput; [exportInputPopUpButton selectItemAtIndex:exportSource]; BOOL isSelectedTables = (exportSource == SPTableExport); [exportFilePerTableCheck setHidden:(!isSelectedTables) || (exportType == SPSQLExport)]; [exportTableList setEnabled:isSelectedTables]; [exportSelectAllTablesButton setEnabled:isSelectedTables]; [exportDeselectAllTablesButton setEnabled:isSelectedTables]; [exportRefreshTablesButton setEnabled:isSelectedTables]; [self updateAvailableExportFilenameTokens]; // will also update the filename itself return (actualInput == input); } /** * Enables/disables and shows/hides various interface controls depending on the selected item. */ - (IBAction)switchInput:(id)sender { [self setExportInput:(SPExportSource)[exportInputPopUpButton indexOfSelectedItem]]; } /** * Cancel's the export operation by stopping the current table export loop and marking any current SPExporter * NSOperation subclasses as cancelled. */ - (IBAction)cancelExport:(id)sender { [self setExportCancelled:YES]; [exportProgressIndicator setIndeterminate:YES]; [exportProgressIndicator setUsesThreadedAnimation:YES]; [exportProgressIndicator startAnimation:self]; [exportProgressTitle setStringValue:NSLocalizedString(@"Cancelling...", @"cancelling task status message")]; [exportProgressText setStringValue:NSLocalizedString(@"Cleaning up...", @"cancelling export cleaning up message")]; // Disable the cancel button [sender setEnabled:NO]; // Cancel all of the currently running operations [operationQueue cancelAllOperations]; // async call [NSThread detachNewThreadWithName:SPCtxt(@"SPExportController cancelExport: waiting for empty queue", tableDocumentInstance) target:self selector:@selector(_waitUntilQueueIsEmpty:) object:sender]; } - (void)_waitUntilQueueIsEmpty:(id)sender { [sender retain]; [operationQueue waitUntilAllOperationsAreFinished]; [self performSelectorOnMainThread:@selector(_queueIsEmpty:) withObject:sender waitUntilDone:NO]; [sender release]; } - (void)_queueIsEmpty:(id)sender { // Loop the cached export file paths and remove them from disk if they exist for (SPExportFile *file in exportFiles) { [file delete]; } // Close the progress sheet [NSApp endSheet:exportProgressWindow returnCode:0]; [exportProgressWindow orderOut:self]; // Stop the progress indicator [exportProgressIndicator stopAnimation:self]; [exportProgressIndicator setUsesThreadedAnimation:NO]; // Re-enable the cancel button for future exports [sender setEnabled:YES]; // Finally get rid of all the exporters and files [exportFiles removeAllObjects]; [exporters removeAllObjects]; } /** * Opens the open panel when user selects to change the output path. */ - (IBAction)changeExportOutputPath:(id)sender { NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:NO]; [panel setCanChooseDirectories:YES]; [panel setCanCreateDirectories:YES]; [panel setDirectoryURL:[NSURL URLWithString:[exportPathField stringValue]]]; [panel beginSheetModalForWindow:[self window] completionHandler:^(NSInteger returnCode) { if (returnCode == NSFileHandlingPanelOKButton) { NSString *path = [[panel directoryURL] path]; if(!path) { @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"File panel ended with OK, but returned nil for path!? directoryURL=%@,isFileURL=%d",[panel directoryURL],[[panel directoryURL] isFileURL]] userInfo:nil]; } [exportPathField setStringValue:path]; [prefs setObject:path forKey:SPExportLastDirectory]; } }]; } /** * Refreshes the table list. */ - (IBAction)refreshTableList:(id)sender { NSMutableDictionary *tableDict = [[NSMutableDictionary alloc] init]; // Before refreshing the list, preserve the user's table selection, but only if it was triggered by the UI. if (sender) { for (NSMutableArray *item in tables) { [tableDict setObject:[NSArray arrayWithObjects: [item objectAtIndex:1], [item objectAtIndex:2], [item objectAtIndex:3], [item objectAtIndex:4], nil] forKey:[item objectAtIndex:0]]; } } [tables removeAllObjects]; // For all modes, retrieve table and view names { NSArray *tablesAndViews = [tablesListInstance allTableAndViewNames]; for (id itemName in tablesAndViews) { [tables addObject:[NSMutableArray arrayWithObjects: itemName, @YES, @YES, @YES, [NSNumber numberWithInt:SPTableTypeTable], nil]]; } } // The purpose of this extra { } is to limit visibility and thus catch copy&paste errors // For SQL only, add procedures and functions if (exportType == SPSQLExport) { // Procedures { NSArray *procedures = [tablesListInstance allProcedureNames]; for (id procName in procedures) { [tables addObject:[NSMutableArray arrayWithObjects: procName, @YES, @YES, @YES, [NSNumber numberWithInt:SPTableTypeProc], nil]]; } } // Functions { NSArray *functions = [tablesListInstance allFunctionNames]; for (id funcName in functions) { [tables addObject:[NSMutableArray arrayWithObjects: funcName, @YES, @YES, @YES, [NSNumber numberWithInt:SPTableTypeFunc], nil]]; } } } if (sender) { // Restore the user's table selection for (NSUInteger i = 0; i < [tables count]; i++) { NSMutableArray *oldSelection = [tableDict objectForKey:[[tables objectAtIndex:i] objectAtIndex:0]]; if (oldSelection) { NSMutableArray *newItem = [[NSMutableArray alloc] initWithArray:oldSelection]; [newItem insertObject:[[tables objectAtIndex:i] objectAtIndex:0] atIndex:0]; [tables replaceObjectAtIndex:i withObject:newItem]; [newItem release]; } } } [exportTableList reloadData]; [tableDict release]; } /** * Selects or de-selects all tables. */ - (IBAction)selectDeselectAllTables:(id)sender { BOOL toggleStructure = NO; BOOL toggleDropTable = NO; [self refreshTableList:nil]; // Determine whether the structure and drop items should also be toggled if (exportType == SPSQLExport) { if ([exportSQLIncludeStructureCheck state]) toggleStructure = YES; if ([exportSQLIncludeDropSyntaxCheck state]) toggleDropTable = YES; } for (NSMutableArray *table in tables) { if (toggleStructure) [table replaceObjectAtIndex:1 withObject:[NSNumber numberWithBool:[sender tag]]]; [table replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:[sender tag]]]; if (toggleDropTable) [table replaceObjectAtIndex:3 withObject:[NSNumber numberWithBool:[sender tag]]]; } [exportTableList reloadData]; [self _updateExportFormatInformation]; [self _toggleExportButtonOnBackgroundThread]; } /** * Updates the default filename extenstion based on the selected output compression format. */ - (IBAction)changeExportCompressionFormat:(id)sender { [self updateDisplayedExportFilename]; } /** * Toggles the state of the custom filename format token fields. */ - (IBAction)toggleCustomFilenameFormatView:(id)sender { showCustomFilenameView = (!showCustomFilenameView); [exportCustomFilenameViewButton setState:showCustomFilenameView]; [exportFilenameDividerBox setHidden:showCustomFilenameView]; [exportCustomFilenameView setHidden:(!showCustomFilenameView)]; [self _resizeWindowForCustomFilenameViewByHeightDelta:(showCustomFilenameView) ? [exportCustomFilenameView frame].size.height : 0]; } /** * Toggles the options available depending on the selected XML output format. */ - (IBAction)toggleXMLOutputFormat:(id)sender { if ([sender indexOfSelectedItem] == SPXMLExportMySQLFormat) { [exportXMLIncludeStructure setEnabled:YES]; [exportXMLIncludeContent setEnabled:YES]; [exportXMLNULLValuesAsTextField setEnabled:NO]; } else if ([sender indexOfSelectedItem] == SPXMLExportPlainFormat) { [exportXMLIncludeStructure setEnabled:NO]; [exportXMLIncludeContent setEnabled:NO]; [exportXMLNULLValuesAsTextField setEnabled:YES]; } } /** * Toggles the display of the advanced options box. */ - (IBAction)toggleAdvancedExportOptionsView:(id)sender { showAdvancedView = (!showAdvancedView); [exportAdvancedOptionsViewButton setState:showAdvancedView]; [exportAdvancedOptionsView setHidden:(!showAdvancedView)]; [self _updateExportAdvancedOptionsLabel]; [self _resizeWindowForAdvancedOptionsViewByHeightDelta:(showAdvancedView) ? ([exportAdvancedOptionsView frame].size.height + 10) : 0]; } /** * Toggles the export button when choosing to include or table structures in an SQL export. */ - (IBAction)toggleSQLIncludeStructure:(NSButton *)sender { if (![sender state]) { [exportSQLIncludeDropSyntaxCheck setState:NSOffState]; } [exportSQLIncludeDropSyntaxCheck setEnabled:[sender state]]; [exportSQLIncludeAutoIncrementValueButton setEnabled:[sender state]]; [[exportTableList tableColumnWithIdentifier:SPTableViewDropColumnID] setHidden:(![sender state])]; [[exportTableList tableColumnWithIdentifier:SPTableViewStructureColumnID] setHidden:(![sender state])]; [self _toggleExportButtonOnBackgroundThread]; } /** * Toggles the export button when choosing to include or exclude table contents in an SQL export. */ - (IBAction)toggleSQLIncludeContent:(NSButton *)sender { [[exportTableList tableColumnWithIdentifier:SPTableViewContentColumnID] setHidden:(![sender state])]; [self _toggleExportButtonOnBackgroundThread]; } /** * Toggles the export button when choosing to include or exclude table drop syntax in an SQL export. */ - (IBAction)toggleSQLIncludeDropSyntax:(NSButton *)sender { [[exportTableList tableColumnWithIdentifier:SPTableViewDropColumnID] setHidden:(![sender state])]; [self _toggleExportButtonOnBackgroundThread]; } /** * Toggles whether XML and CSV files should be combined into a single file. */ - (IBAction)toggleNewFilePerTable:(NSButton *)sender { [self _updateExportFormatInformation]; [self updateAvailableExportFilenameTokens]; } /** * Opens the export sheet, selecting custom query as the export source. */ - (IBAction)exportCustomQueryResultAsFormat:(id)sender { [self exportTables:nil asFormat:[sender tag] usingSource:SPQueryExport]; } #pragma mark - #pragma mark Other /** * Invoked when the user dismisses the export dialog. Starts the export process if required. */ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { // Perform the export if (returnCode == NSOKButton) { // Check whether to save the export filename. Save it if it's not blank and contains at least one // token - this suggests it's not a one-off filename if ([[exportCustomFilenameTokenField stringValue] length] < 1) { [prefs removeObjectForKey:SPExportFilenameFormatIntl]; } else { BOOL saveFilename = NO; NSArray *representedObjects = [exportCustomFilenameTokenField objectValue]; for (id aToken in representedObjects) { if ([aToken isKindOfClass:[SPExportFileNameTokenObject class]]) saveFilename = YES; } if (saveFilename) [prefs setObject:[NSKeyedArchiver archivedDataWithRootObject:representedObjects] forKey:SPExportFilenameFormatIntl]; } // If we are about to perform a table export, cache the current number of tables within the list, // refresh the list and then compare the numbers to accommodate situations where new tables are // added by external applications. if ((exportSource == SPTableExport) && (exportType != SPDotExport)) { // Give the export sheet a chance to close [self performSelector:@selector(_checkForDatabaseChanges) withObject:nil afterDelay:0.5]; } else { // Initialize the export after a short delay to give the alert a chance to close [self performSelector:@selector(initializeExportUsingSelectedOptions) withObject:nil afterDelay:0.5]; } } } - (void)tableListChangedAlertDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { // Perform the export ignoring the new tables if (returnCode == NSOKButton) { // Initialize the export after a short delay to give the alert a chance to close [self performSelector:@selector(initializeExportUsingSelectedOptions) withObject:nil afterDelay:0.5]; } else { // Cancel the export and redisplay the export dialog after a short delay [self performSelector:@selector(export:) withObject:self afterDelay:0.5]; } } /** * Menu item validation. */ - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { if ([menuItem action] == @selector(exportCustomQueryResultAsFormat:)) { return (([customQueryInstance currentResultRowCount] > 0) && (![tableDocumentInstance isProcessing])); } return YES; } #pragma mark - #pragma mark Private API /** * Changes the selected export format and updates the UI accordingly. */ - (void)_switchTab { // Selected export format NSString *type = [[[exportTypeTabBar selectedTabViewItem] identifier] lowercaseString]; // Determine the export type exportType = [exportTypeTabBar indexOfTabViewItemWithIdentifier:type]; // Determine what data to use (filtered result, custom query result or selected table(s)) for the export operation exportSource = (exportType == SPDotExport) ? SPTableExport : [exportInputPopUpButton indexOfSelectedItem]; [exportOptionsTabBar selectTabViewItemWithIdentifier:type]; BOOL isSQL = (exportType == SPSQLExport); BOOL isCSV = (exportType == SPCSVExport); BOOL isXML = (exportType == SPXMLExport); //BOOL isHTML = (exportType == SPHTMLExport); //BOOL isPDF = (exportType == SPPDFExport); BOOL isDot = (exportType == SPDotExport); BOOL enable = (isCSV || isXML /* || isHTML || isPDF */ || isDot); [exportFilePerTableCheck setHidden:(isSQL || isDot)]; [exportTableList setEnabled:(!isDot)]; [exportSelectAllTablesButton setEnabled:(!isDot)]; [exportDeselectAllTablesButton setEnabled:(!isDot)]; [exportRefreshTablesButton setEnabled:(!isDot)]; [[[exportInputPopUpButton menu] itemAtIndex:SPTableExport] setEnabled:(!isDot)]; [exportInputPopUpButton setEnabled:(!isDot)]; // When exporting to SQL, only the selected tables option should be enabled if (isSQL) { // Programmatically changing the selected item of a popup button does not fire it's action, so update // the selected export source manually. exportSource = SPTableExport; [exportInputPopUpButton selectItemAtIndex:SPTableExport]; [[[exportInputPopUpButton menu] itemAtIndex:SPFilteredExport] setEnabled:NO]; [[[exportInputPopUpButton menu] itemAtIndex:SPQueryExport] setEnabled:NO]; } else { // Enable/disable the 'filtered result' and 'query result' options // Note that the result count check is always greater than one as the first row is always the field names [[[exportInputPopUpButton menu] itemAtIndex:SPFilteredExport] setEnabled:((enable) && ([[tableContentInstance currentResult] count] > 1))]; [[[exportInputPopUpButton menu] itemAtIndex:SPQueryExport] setEnabled:((enable) && ([[customQueryInstance currentResult] count] > 1))]; } [[exportTableList tableColumnWithIdentifier:SPTableViewStructureColumnID] setHidden:(isSQL) ? (![exportSQLIncludeStructureCheck state]) : YES]; [[exportTableList tableColumnWithIdentifier:SPTableViewDropColumnID] setHidden:(isSQL) ? (![exportSQLIncludeDropSyntaxCheck state]) : YES]; [[[exportTableList tableColumnWithIdentifier:SPTableViewContentColumnID] headerCell] setStringValue:(enable) ? @"" : @"C"]; // Set the tooltip [[exportTableList tableColumnWithIdentifier:SPTableViewContentColumnID] setHeaderToolTip:(enable) ? @"" : NSLocalizedString(@"Include content", @"include content table column tooltip")]; // When switching to Dot export, ensure the server's lower_case_table_names value is checked the first time // to set the export's link case sensitivity setting if (isDot && serverLowerCaseTableNameValue == NSNotFound) { SPMySQLResult *caseResult = [connection queryString:@"SHOW VARIABLES LIKE 'lower_case_table_names'"]; [caseResult setReturnDataAsStrings:YES]; if ([caseResult numberOfRows] == 1) { serverLowerCaseTableNameValue = [[[caseResult getRowAsDictionary] objectForKey:@"Value"] integerValue]; } else { serverLowerCaseTableNameValue = 0; } [exportDotForceLowerTableNamesCheck setState:(serverLowerCaseTableNameValue == 0)?NSOffState:NSOnState]; } [exportCSVNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; [exportXMLNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; [self _displayExportTypeOptions:(isSQL || isCSV || isXML || isDot)]; [self updateAvailableExportFilenameTokens]; [self updateDisplayedExportFilename]; [self _updateExportFormatInformation]; } /** * Checks for changes in the current database, by refreshing the table list and warning the user if required. */ - (void)_checkForDatabaseChanges { NSUInteger i = [tables count]; [tablesListInstance updateTables:self]; NSUInteger j = [[tablesListInstance allTableAndViewNames] count]; // If this is an SQL export, include procs and functions if (exportType == SPSQLExport) { j += ([[tablesListInstance allProcedureNames] count] + [[tablesListInstance allFunctionNames] count]); } if (j > i) { NSUInteger diff = j - i; SPBeginAlertSheet(NSLocalizedString(@"The list of tables has changed", @"table list change alert message"), NSLocalizedString(@"Continue", @"continue button"), NSLocalizedString(@"Cancel", @"cancel button"), nil, [tableDocumentInstance parentWindow], self, @selector(tableListChangedAlertDidEnd:returnCode:contextInfo:), NULL, [NSString stringWithFormat:NSLocalizedString(@"The number of tables in this database has changed since the export dialog was opened. There are now %d additional table(s), most likely added by an external application.\n\nHow would you like to proceed?", @"table list change alert informative message"), diff]); } else { [self initializeExportUsingSelectedOptions]; } } /** * Toggles the display of the export type options view. * * @param display A BOOL indicating whether or not the view should be visible */ - (void)_displayExportTypeOptions:(BOOL)display { NSRect windowFrame = [[exportTablelistScrollView window] frame]; NSRect viewFrame = [exportTablelistScrollView frame]; NSRect barFrame = [exportTableListButtonBar frame]; NSUInteger padding = (2 * SPExportUIPadding); CGFloat width = (!display) ? (windowFrame.size.width - (padding + 2)) : (windowFrame.size.width - ([exportOptionsTabBar frame].size.width + (padding + 4))); [NSAnimationContext beginGrouping]; [[NSAnimationContext currentContext] setDuration:0.3]; [[exportOptionsTabBar animator] setHidden:(!display)]; [[exportTablelistScrollView animator] setFrame:NSMakeRect(viewFrame.origin.x, viewFrame.origin.y, width, viewFrame.size.height)]; [[exportTableListButtonBar animator] setFrame:NSMakeRect(barFrame.origin.x, barFrame.origin.y, width, barFrame.size.height)]; [NSAnimationContext endGrouping]; } /** * Updates the information note in the window based on the current export settings. */ - (void)_updateExportFormatInformation { NSString *noteText = @""; // If the selected format is XML, Dot, or multiple tables in one CSV file, display a warning note. switch (exportType) { case SPCSVExport: if ([exportFilePerTableCheck state]) break; NSUInteger numberOfTables = 0; for (NSMutableArray *eachTable in tables) { if ([[eachTable objectAtIndex:2] boolValue]) numberOfTables++; } if (numberOfTables <= 1) break; case SPXMLExport: case SPDotExport: noteText = NSLocalizedString(@"Import of the selected data is currently not supported.", @"Export file format cannot be imported warning"); break; default: break; } [exportFormatInfoText setStringValue:noteText]; } /** * Update the export advanced options label to show a summary if the options are hidden. */ - (void)_updateExportAdvancedOptionsLabel { if (showAdvancedView) { [exportAdvancedOptionsViewLabelButton setTitle:NSLocalizedString(@"Advanced", @"Advanced options short title")]; return; } NSMutableArray *optionsSummary = [NSMutableArray array]; if ([exportProcessLowMemoryButton state]) { [optionsSummary addObject:NSLocalizedString(@"Low memory", @"Low memory export summary")]; } else { [optionsSummary addObject:NSLocalizedString(@"Standard memory", @"Standard memory export summary")]; } if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] == SPNoCompression) { [optionsSummary addObject:NSLocalizedString(@"no compression", @"No compression export summary - within a sentence")]; } else if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] == SPGzipCompression) { [optionsSummary addObject:NSLocalizedString(@"Gzip compression", @"Gzip compression export summary - within a sentence")]; } else if ([exportOutputCompressionFormatPopupButton indexOfSelectedItem] == SPBzip2Compression) { [optionsSummary addObject:NSLocalizedString(@"bzip2 compression", @"bzip2 compression export summary - within a sentence")]; } [exportAdvancedOptionsViewLabelButton setTitle:[NSString stringWithFormat:@"%@ (%@)", NSLocalizedString(@"Advanced", @"Advanced options short title"), [optionsSummary componentsJoinedByString:@", "]]]; } /** * Sets the previous export filename and path if available. */ - (void)_setPreviousExportFilenameAndPath { id o; // Restore the export filename if it exists, and update the display if ((o = [prefs objectForKey:SPExportFilenameFormatIntl])) { [exportCustomFilenameTokenField setObjectValue:[NSKeyedUnarchiver unarchiveObjectWithData:o]]; } // If a directory has previously been selected, reselect it if ((o = [prefs objectForKey:SPExportLastDirectory])) { [exportPathField setStringValue:o]; } else { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSAllDomainsMask, YES); // If found the set the default path to the user's desktop, otherwise use their home directory [exportPathField setStringValue:([paths count] > 0) ? [paths objectAtIndex:0] : NSHomeDirectory()]; } } /** * Enables or disables the export button based on the state of various interface controls. * * @param uiStateDict A dictionary containing the state of various UI controls. */ - (void)_toggleExportButton:(id)uiStateDict { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; BOOL enable = NO; BOOL isSQL = (exportType == SPSQLExport); BOOL isCSV = (exportType == SPCSVExport); BOOL isXML = (exportType == SPXMLExport); BOOL isHTML = (exportType == SPHTMLExport); BOOL isPDF = (exportType == SPPDFExport); BOOL structureEnabled = [[uiStateDict objectForKey:SPSQLExportStructureEnabled] boolValue]; BOOL contentEnabled = [[uiStateDict objectForKey:SPSQLExportContentEnabled] boolValue]; BOOL dropEnabled = [[uiStateDict objectForKey:SPSQLExportDropEnabled] boolValue]; if (isCSV || isXML || isHTML || isPDF || (isSQL && ((!structureEnabled) || (!dropEnabled)))) { enable = NO; // Only enable the button if at least one table is selected for (NSArray *table in tables) { if ([NSArrayObjectAtIndex(table, 2) boolValue]) { enable = YES; break; } } } else if (isSQL) { // Disable if all are unchecked if ((!contentEnabled) && (!structureEnabled) && (!dropEnabled)) { enable = NO; } // If they are all checked, check to see if any of the tables are checked else if (contentEnabled && structureEnabled && dropEnabled) { // Only enable the button if at least one table is selected for (NSArray *table in tables) { if ([NSArrayObjectAtIndex(table, 1) boolValue] || [NSArrayObjectAtIndex(table, 2) boolValue] || [NSArrayObjectAtIndex(table, 3) boolValue]) { enable = YES; break; } } } // Disable if structure is unchecked, but content and drop are as dropping a // table then trying to insert into it is obviously an error. else if (contentEnabled && (!structureEnabled) && (dropEnabled)) { enable = NO; } else { enable = (contentEnabled || (structureEnabled || dropEnabled)); } } [self performSelectorOnMainThread:@selector(_toggleExportButtonWithBool:) withObject:[NSNumber numberWithBool:enable] waitUntilDone:NO]; [pool release]; } /** * Calls the above method on a background thread to determine whether or not the export button should be enabled. */ - (void)_toggleExportButtonOnBackgroundThread { NSMutableDictionary *uiStateDict = [[NSMutableDictionary alloc] init]; [uiStateDict setObject:[NSNumber numberWithInteger:[exportSQLIncludeStructureCheck state]] forKey:SPSQLExportStructureEnabled]; [uiStateDict setObject:[NSNumber numberWithInteger:[exportSQLIncludeContentCheck state]] forKey:SPSQLExportContentEnabled]; [uiStateDict setObject:[NSNumber numberWithInteger:[exportSQLIncludeDropSyntaxCheck state]] forKey:SPSQLExportDropEnabled]; [NSThread detachNewThreadWithName:SPCtxt(@"SPExportController export button updater",tableDocumentInstance) target:self selector:@selector(_toggleExportButton:) object:uiStateDict]; [uiStateDict release]; } /** * Enables or disables the export button based on the supplied number (boolean). * * @param enable A boolean indicating the state. */ - (void)_toggleExportButtonWithBool:(NSNumber *)enable { [exportButton setEnabled:[enable boolValue]]; } #pragma mark - - (void)dealloc { SPClear(tables); SPClear(exporters); SPClear(exportFiles); SPClear(operationQueue); SPClear(exportFilename); SPClear(localizedTokenNames); SPClear(previousConnectionEncoding); [super dealloc]; } @end