From bbe0f861dd4e3ab99aa3d555d3fc5db5ee5ae39d Mon Sep 17 00:00:00 2001 From: stuconnolly Date: Mon, 24 May 2010 18:07:43 +0000 Subject: Merge export redesign branch back into trunk. Includes a completely redesign approach to all export data types based on the use of NSOperation subclasses. CSV, SQL, XML and dot export types are currently functional, while the source files for PDF and HTML export types exist they are to be implemented, but are currently hidden from the interface. Also includes the following: - Completely redesigned export interface. - The ability to customize CSV NULL values. - The ability to specify whether the UTF-8 BOM should be used in SQL dumps. - The ability to specify whether BLOB fields are output as hex or plain text during SQL dumps. Defaults to hex. - Exporting currently selected tables via the tables list context menu. Outstanding issues: - Not all progress indicators for all export types are functional (or functioning correctly). - A few issues related to the introduction of only exporting the content and create and drop syntax of specific tables during SQL dumps. Needs some serious testing and benchmarking to ensure it replicates the current export functionality. --- Source/SPExportController.m | 766 +++++++++++++++++++++++++++----------------- 1 file changed, 477 insertions(+), 289 deletions(-) (limited to 'Source/SPExportController.m') diff --git a/Source/SPExportController.m b/Source/SPExportController.m index 559e8071..83c87ea6 100644 --- a/Source/SPExportController.m +++ b/Source/SPExportController.m @@ -5,6 +5,7 @@ // sequel-pro // // Created by Ben Perry (benperry.com.au) on 21/02/09. +// Modified by Stuart Connolly (stuconnolly.com) // // 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 @@ -22,39 +23,68 @@ // // More info at +#import + #import "SPExportController.h" -#import "SPCSVExporter.h" +#import "SPExportInitializer.h" #import "TablesList.h" #import "SPTableData.h" -#import "TableDocument.h" +#import "TableContent.h" #import "SPArrayAdditions.h" #import "SPStringAdditions.h" #import "SPConstants.h" +#import "SPGrowlController.h" @interface SPExportController (PrivateAPI) -- (void)_initializeExportUsingSelectedOptions; -- (BOOL)_exportTables:(NSArray *)exportTables asType:(SPExportType)type toMultipleFiles:(BOOL)multipleFiles; +- (void)_toggleExportButton; +- (void)_resizeWindowByHeightDelta:(NSInteger)delta; @end @implementation SPExportController @synthesize connection; +@synthesize exportToMultipleFiles; @synthesize exportCancelled; +#pragma mark - +#pragma mark Initialization + /** * Initializes an instance of SPExportController */ - (id)init { - if ((self = [super init])) { + if (self = [super initWithWindowNibName:@"ExportDialog"]) { + [self setExportCancelled:NO]; + [self setExportToMultipleFiles:YES]; + + exportType = 0; + exportTableCount = 0; + currentTableExportIndex = 0; + + exportFilename = @""; + exportTypeLabel = @""; + + createCustomFilename = NO; + sqlPreviousConnectionEncodingViaLatin1 = NO; tables = [[NSMutableArray alloc] init]; + exporters = [[NSMutableArray alloc] init]; operationQueue = [[NSOperationQueue alloc] init]; - tableExportMapping = [NSMutableDictionary dictionary]; - nibObjectsToRelease = [[NSMutableArray alloc] init]; + + showAdvancedView = NO; + + heightOffset = 0; + windowMinWidth = [[self window] minSize].width; + windowMinHeigth = [[self window] minSize].height; + + prefs = [NSUserDefaults standardUserDefaults]; + + // Default filename tokens + availableFilenameTokens = @"host,database,table,date,time"; } return self; @@ -64,9 +94,12 @@ * Upon awakening select the first toolbar item */ - (void)awakeFromNib -{ +{ // Upon awakening select the SQL tab [exportToolbar setSelectedItemIdentifier:[[[exportToolbar items] objectAtIndex:0] itemIdentifier]]; + + // Select the 'selected tables' option + [exportInputMatrix selectCellAtRow:2 column:0]; } #pragma mark - @@ -76,52 +109,166 @@ * Display the export window allowing the user to select what and of what type to export. */ - (void)export -{ +{ + [self exportTables:nil asFormat:0]; +} - // If the dialog hasn't been loaded yet, do so, retaining a reference to the top-level objects that need releasing. - if (!exportWindow) - { - NSArray *exportDialogTopLevelObjects = nil; - NSNib *nibLoader = [[NSNib alloc] initWithNibNamed:@"ExportDialog" bundle:[NSBundle mainBundle]]; - [nibLoader instantiateNibWithOwner:self topLevelObjects:&exportDialogTopLevelObjects]; - [nibObjectsToRelease addObjectsFromArray:exportDialogTopLevelObjects]; - [nibLoader release]; +/** + * Displays the export window with the supplied tables and export type/format selected. + */ +- (void)exportTables:(NSArray *)exportTables asFormat:(SPExportType)format +{ + [self refreshTableList:self]; + + if (exportTables && format) { + + // Select the correct tab according to the supplied export type + [exportToolbar setSelectedItemIdentifier:[[[exportToolbar items] objectAtIndex:(format - 1)] itemIdentifier]]; + + // Select the 'selected tables' source option + [exportInputMatrix selectCellAtRow:2 column:0]; + + // Disable all tables + for (NSMutableArray *table in tables) + { + [table replaceObjectAtIndex:1 withObject:[NSNumber numberWithBool:NO]]; + [table replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:NO]]; + [table replaceObjectAtIndex:3 withObject:[NSNumber numberWithBool: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:[NSNumber numberWithBool:YES]]; + [table replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:YES]]; + [table replaceObjectAtIndex:3 withObject:[NSNumber numberWithBool:YES]]; + } + } + } + + [exportTableList reloadData]; + + // Ensure interface validation + [self switchTab:[[exportToolbar items] objectAtIndex:(format - 1)]]; } - NSUInteger i; + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); - [tables removeAllObjects]; + // 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()]; - MCPResult *queryResult = (MCPResult *)[[self connection] listTables]; + [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. + */ +- (void)openExportErrorsSheetWithString:(NSString *)errors +{ + [errorsTextView setString:@""]; + [errorsTextView setString:errors]; - if ([queryResult numOfRows]) [queryResult dataSeek:0]; + [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] + window:[tableDocumentInstance parentWindow] + notificationName:@"Export Finished"]; +} + +/** + * Expands the custom filename format based on the selected tokens. + */ +- (NSString *)expandCustomFilenameFormatFromString:(NSString *)format usingTableName:(NSString *)table +{ + NSMutableString *string = [NSMutableString stringWithString:format]; - for ( i = 0 ; i < [queryResult numOfRows] ; i++ ) - { - [tables addObject:[NSMutableArray arrayWithObjects: - [NSNumber numberWithBool:YES], - NSArrayObjectAtIndex([queryResult fetchRowAsArray], 0), - nil]]; + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + + [dateFormatter setDateStyle:NSDateFormatterShortStyle]; + [dateFormatter setTimeStyle:NSDateFormatterNoStyle]; + + [string replaceOccurrencesOfString:@"host" withString:[tableDocumentInstance host] + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; + + [string replaceOccurrencesOfString:@"database" withString:[tableDocumentInstance database] + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; + + if (table) { + [string replaceOccurrencesOfString:@"table" withString:table + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; + } + else { + [string replaceOccurrencesOfString:@"table" withString:@"" + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; } - [exportTableList reloadData]; - - [exportPathField setStringValue:NSHomeDirectory()]; + [string replaceOccurrencesOfString:@"date" withString:[dateFormatter stringFromDate:[NSDate date]] + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; - [NSApp beginSheet:exportWindow - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:nil]; + [dateFormatter setDateStyle:NSDateFormatterNoStyle]; + [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; + + [string replaceOccurrencesOfString:@"time" withString:[dateFormatter stringFromDate:[NSDate date]] + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; + + // Strip comma separators + [string replaceOccurrencesOfString:@"," withString:@"" + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; + + // Replace colons with hyphens + [string replaceOccurrencesOfString:@":" withString:@"-" + options:NSLiteralSearch + range:NSMakeRange(0, [string length])]; + + [dateFormatter release]; + + return string; } /** - * Closes the export dialog + * Closes the export dialog. */ - (IBAction)closeSheet:(id)sender { - [NSApp endSheet:exportWindow returnCode:[sender tag]]; - [exportWindow orderOut:self]; + if ([sender window] == [self window]) { + + // Close the advanced options view if it's open + [exportAdvancedOptionsView setHidden:YES]; + [exportAdvancedOptionsViewButton setState:NSOffState]; + + [self _resizeWindowByHeightDelta:0]; + } + + [NSApp endSheet:[sender window] returnCode:[sender tag]]; + [[sender window] orderOut:self]; } /** @@ -130,20 +277,70 @@ - (IBAction)switchTab:(id)sender { if ([sender isKindOfClass:[NSToolbarItem class]]) { - [exportTabBar selectTabViewItemWithIdentifier:[[sender label] lowercaseString]]; + + currentToolbarItem = sender; + + NSString *label = [[currentToolbarItem label] lowercaseString]; + + [exportTabBar selectTabViewItemWithIdentifier:label]; + + BOOL isSQL = [label isEqualToString:@"sql"]; + BOOL isCSV = [label isEqualToString:@"csv"]; + BOOL isXML = [label isEqualToString:@"xml"]; + BOOL isHTML = [label isEqualToString:@"html"]; + BOOL isPDF = [label isEqualToString:@"pdf"]; + BOOL isDot = [label isEqualToString:@"dot"]; + + BOOL disable = (isCSV || isXML || isHTML || isPDF || isDot); + + [exportFilePerTableCheck setHidden:(isSQL || isDot)]; + [exportFilePerTableNote setHidden:(isSQL || isDot)]; + + [exportTableList setEnabled:(!isDot)]; + [exportSelectAllTablesButton setEnabled:(!isDot)]; + [exportDeselectAllTablesButton setEnabled:(!isDot)]; + [exportRefreshTablesButton setEnabled:(!isDot)]; + + [[exportInputMatrix cellAtRow:2 column:0] setEnabled:(!isDot)]; + + if (isDot) { + // Disable all source checkboxes + [[exportInputMatrix cellAtRow:0 column:0] setEnabled:NO]; + [[exportInputMatrix cellAtRow:1 column:0] setEnabled:NO]; + } + else { + // Enable/disable the 'filtered result' and 'query result' options + [[exportInputMatrix cellAtRow:0 column:0] setEnabled:((disable) && ([[tableContentInstance currentResult] count] > 1))]; + [[exportInputMatrix cellAtRow:1 column:0] setEnabled:((disable) && ([[customQueryInstance currentResult] count] > 1))]; + } - [exportFilePerTableCheck setHidden:[[sender label] isEqualToString:@"Excel"]]; - [exportFilePerTableNote setHidden:[[sender label] isEqualToString:@"Excel"]]; + [[exportTableList tableColumnWithIdentifier:@"structure"] setHidden:disable]; + [[exportTableList tableColumnWithIdentifier:@"drop"] setHidden:disable]; + + [[[exportTableList tableColumnWithIdentifier:@"content"] headerCell] setStringValue:(disable) ? @"" : @"C"]; + + [exportCSVNULLValuesAsTextField setStringValue:[prefs stringForKey:SPNullValue]]; } } /** - * + * Enables/disables and shows/hides various interface controls depending on the selected item. */ - (IBAction)switchInput:(id)sender { if ([sender isKindOfClass:[NSMatrix class]]) { - [exportTableList setEnabled:([[sender selectedCell] tag] == 3)]; + + BOOL isSelectedTables = ([[sender selectedCell] tag] == SPTableExport); + + [exportFilePerTableCheck setHidden:(!isSelectedTables)]; + [exportFilePerTableNote setHidden:(!isSelectedTables)]; + + [exportTableList setEnabled:isSelectedTables]; + [exportSelectAllTablesButton setEnabled:isSelectedTables]; + [exportDeselectAllTablesButton setEnabled:isSelectedTables]; + [exportRefreshTablesButton setEnabled:isSelectedTables]; + + availableFilenameTokens = ([[sender selectedCell] tag] == SPQueryExport) ? @"host,database,date,time" : @"host,database,table,date,time"; } } @@ -164,24 +361,161 @@ } /** - * + * Opens the open panel when user selects to change the output path. */ - (IBAction)changeExportOutputPath:(id)sender { + [exportCustomFilenameTokenField setStringValue:@""]; + [exportCustomFilenameTokensField setStringValue:availableFilenameTokens]; + NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:NO]; [panel setCanChooseDirectories:YES]; [panel setCanCreateDirectories:YES]; + [panel setAccessoryView:exportCustomFilenameView]; + + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES); - [panel beginSheetForDirectory:NSHomeDirectory() + [panel beginSheetForDirectory:([paths count] > 0) ? [paths objectAtIndex:0] : NSHomeDirectory() file:nil - modalForWindow:exportWindow + modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(savePanelDidEnd:returnCode:contextInfo:) contextInfo:nil]; } +/** + * Refreshes the table list. + */ +- (IBAction)refreshTableList:(id)sender +{ + NSUInteger i; + + [tables removeAllObjects]; + + // For all modes, retrieve table and view names + NSArray *tablesAndViews = [tablesListInstance allTableAndViewNames]; + + for (id itemName in tablesAndViews) { + [tables addObject:[NSMutableArray arrayWithObjects: + itemName, + [NSNumber numberWithBool:YES], + [NSNumber numberWithBool:YES], + [NSNumber numberWithBool:YES], + [NSNumber numberWithInt:SPTableTypeTable], + nil]]; + } + + // For SQL only, add procedures and functions + if ([[[currentToolbarItem label] lowercaseString] isEqualToString:@"sql"]) { + NSArray *procedures = [tablesListInstance allProcedureNames]; + + for (id procName in procedures) + { + [tables addObject:[NSMutableArray arrayWithObjects: + procName, + [NSNumber numberWithBool:YES], + [NSNumber numberWithBool:YES], + [NSNumber numberWithBool:YES], + [NSNumber numberWithInt:SPTableTypeProc], + nil]]; + } + + NSArray *functions = [tablesListInstance allFunctionNames]; + + for (id funcName in functions) + { + [tables addObject:[NSMutableArray arrayWithObjects: + funcName, + [NSNumber numberWithBool:YES], + [NSNumber numberWithBool:YES], + [NSNumber numberWithBool:YES], + [NSNumber numberWithInt:SPTableTypeFunc], + nil]]; + } + } + + [exportTableList reloadData]; +} + +/** + * Selects or de-selects all tables. + */ +- (IBAction)selectDeselectAllTables:(id)sender +{ + [self refreshTableList:self]; + + for (NSMutableArray *table in tables) + { + [table replaceObjectAtIndex:2 withObject:[NSNumber numberWithBool:[sender tag]]]; + } + + [exportTableList reloadData]; + + [self _toggleExportButton]; +} + +/** + * Toggles the state of the custom filename format token fields. + */ +- (IBAction)toggleCustomFilenameFormat:(id)sender +{ + [exportCustomFilenameTokenField setEnabled:[sender state]]; + [exportCustomFilenameTokensField setEnabled:[sender state]]; +} + +/** + * Toggles the display of the advanced options box. + */ +- (IBAction)toggleAdvancedExportOptionsView:(id)sender +{ + showAdvancedView = !showAdvancedView; + + if (showAdvancedView) { + [exportAdvancedOptionsViewButton setState:NSOnState]; + [self _resizeWindowByHeightDelta:([exportAdvancedOptionsView frame].size.height + 10)]; + [exportAdvancedOptionsView setHidden:NO]; + } + else { + [exportAdvancedOptionsViewButton setState:NSOffState]; + [self _resizeWindowByHeightDelta:0]; + [exportAdvancedOptionsView setHidden:YES]; + } +} + +/** + * Toggles the export button when choosing to include or table structures in an SQL export. + */ +- (IBAction)toggleSQLIncludeStructure:(id)sender +{ + [[exportTableList tableColumnWithIdentifier:@"structure"] setHidden:(![sender state])]; + + [self _toggleExportButton]; +} + +/** + * Toggles the export button when choosing to include or exclude table contents in an SQL export. + */ +- (IBAction)toggleSQLIncludeContent:(id)sender +{ + [sender setTag:[sender state]]; + + [self selectDeselectAllTables:sender]; + + [self _toggleExportButton]; +} + +/** + * Toggles the export button when choosing to include or exclude table drop syntax in an SQL export. + */ +- (IBAction)toggleSQLIncludeDropSyntax:(id)sender +{ + [[exportTableList tableColumnWithIdentifier:@"drop"] setHidden:(![sender state])]; + + [self _toggleExportButton]; +} + #pragma mark - #pragma mark Table view datasource methods @@ -190,30 +524,32 @@ return [tables count]; } -- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ - return NSArrayObjectAtIndex([tables objectAtIndex:rowIndex], ([[aTableColumn identifier] isEqualToString:@"switch"]) ? 0 : 1); +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + return NSArrayObjectAtIndex([tables objectAtIndex:rowIndex], [exportTableList columnWithIdentifier:[tableColumn identifier]]); } -- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex -{ - [[tables objectAtIndex:rowIndex] replaceObjectAtIndex:0 withObject:anObject]; +- (void)tableView:(NSTableView *)tableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex +{ + [[tables objectAtIndex:rowIndex] replaceObjectAtIndex:[exportTableList columnWithIdentifier:[tableColumn identifier]] withObject:anObject]; + + [self _toggleExportButton]; } #pragma mark - #pragma mark Table view delegate methods -- (BOOL)tableView:(NSTableView *)aTableView shouldSelectRow:(NSInteger)rowIndex +- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(NSInteger)rowIndex { - return (aTableView != exportTableList); + return (tableView != exportTableList); } -- (BOOL)tableView:(NSTableView *)aTableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row +- (BOOL)tableView:(NSTableView *)tableView shouldTrackCell:(NSCell *)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { - return (aTableView == exportTableList); + return (tableView == exportTableList); } -- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex +- (void)tableView:(NSTableView *)tableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)rowIndex { [aCell setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; } @@ -223,7 +559,7 @@ - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar { - NSMutableArray *items = [NSMutableArray arrayWithCapacity:6]; + NSMutableArray *items = [NSMutableArray array]; for (NSToolbarItem *item in [toolbar items]) { @@ -234,21 +570,16 @@ } #pragma mark - -#pragma mark SPExporterDataAccess protocol methods +#pragma mark Text field delegate methods -/** - * This method is part of the SPExporterDataAccess protocol. It is called when an expoter complete it's data - * conversion process and the operation is effectively complete. The resulting data can be accessed via - * SPExporter's exportData method. - */ -- (void)exporterDataConversionProcessComplete:(SPExporter *)exporter -{ - // Do something with the data... - - // If there are no more operations in the queue, close the progress sheet - if ([[operationQueue operations] count] == 0) { - [NSApp endSheet:exportProgressWindow returnCode:0]; - [exportProgressWindow orderOut:self]; +- (void)controlTextDidChange:(NSNotification *)notification +{ + if ([notification object] == exportCustomFilenameTokenField) { + + // Create the table name, but since this is only an example, use the first table in the list + NSString *filename = [self expandCustomFilenameFormatFromString:[exportCustomFilenameTokenField stringValue] usingTableName:[[tablesListInstance tables] objectAtIndex:1]]; + + [exportCustomFilenameExampleTextField setStringValue:[NSString stringWithFormat:@"%@: %@", NSLocalizedString(@"Example", @"example label"), filename]]; } } @@ -256,7 +587,7 @@ #pragma mark Other /** - * Invoked when the user + * Invoked when the user dismissing the export dialog and starts the export process if required. */ - (void)sheetDidEnd:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { @@ -264,12 +595,12 @@ if (returnCode == NSOKButton) { // Initialize the export after half a second to give the export sheet a chance to close - [self performSelector:@selector(_initializeExportUsingSelectedOptions) withObject:nil afterDelay:0.5]; + [self performSelector:@selector(initializeExportUsingSelectedOptions) withObject:nil afterDelay:0.5]; } } /** - * Invoked when the user dismisses the save panel. Updates the selected directory is they clicked OK. + * Invoked when the user dismisses the save panel. Updates the selected directory if they clicked OK. */ - (void)savePanelDidEnd:(NSSavePanel *)panel returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo { @@ -286,247 +617,104 @@ - (void)dealloc { [tables release], tables = nil; + [exporters release], exporters = nil; [operationQueue release], operationQueue = nil; - for (id retainedObject in nibObjectsToRelease) [retainedObject release]; - [nibObjectsToRelease release], nibObjectsToRelease = nil; + + if (sqlPreviousConnectionEncoding) [sqlPreviousConnectionEncoding release], sqlPreviousConnectionEncoding = nil; [super dealloc]; } -@end - -@implementation SPExportController (PrivateAPI) +#pragma mark - +#pragma mark Private API /** - * + * Enables or disables the export button based on the state of various interface controls. */ -- (void)_initializeExportUsingSelectedOptions +- (void)_toggleExportButton { - // First determine what type of export the user selected - SPExportType exportType = 0; + NSString *label = [[currentToolbarItem label] lowercaseString]; - for (NSToolbarItem *item in [exportToolbar items]) - { - if ([[item itemIdentifier] isEqualToString:[exportToolbar selectedItemIdentifier]]) { - exportType = [item tag]; - break; - } - } - - // Determine what data to use (filtered result, custom query result or selected table(s)) for the export operation - SPExportSource exportSource = ([exportInputMatrix selectedRow] + 1); - - NSMutableArray *exportTables = [NSMutableArray array]; - - // Get the data depending on the source - switch (exportSource) - { - case SP_FILTERED_EXPORT: - - break; - case SP_CUSTOM_QUERY_EXPORT: - - break; - case SP_TABLE_EXPORT: - // Create an array of tables to export - for (NSMutableArray *table in tables) - { - if ([[table objectAtIndex:0] boolValue]) { - [exportTables addObject:[table objectAtIndex:1]]; - } + BOOL isSQL = [label isEqualToString:@"sql"]; + BOOL isCSV = [label isEqualToString:@"csv"]; + BOOL isXML = [label isEqualToString:@"xml"]; + BOOL isHTML = [label isEqualToString:@"html"]; + BOOL isPDF = [label isEqualToString:@"pdf"]; + + if (isCSV || isXML || isHTML || isPDF) { + [exportButton setEnabled:NO]; + + // Only enable the button if at least one table is selected + for (NSArray *table in tables) + { + if ([NSArrayObjectAtIndex(table, 2) boolValue]) { + [exportButton setEnabled:YES]; + break; } - - break; + } } - - // Begin the export based on the type - switch (exportSource) - { - case SP_FILTERED_EXPORT: - - break; - case SP_CUSTOM_QUERY_EXPORT: - - break; - case SP_TABLE_EXPORT: - [self _exportTables:exportTables asType:exportType toMultipleFiles:[exportFilePerTableCheck state]]; - break; + else if (isSQL) { + BOOL structureEnabled = [exportSQLIncludeStructureCheck state]; + BOOL contentEnabled = [exportSQLIncludeContentCheck state]; + BOOL dropEnabled = [exportSQLIncludeDropSyntaxCheck state]; + + // Disable if all are unchecked + if ((!contentEnabled) && (!structureEnabled) && (!dropEnabled)) { + [exportButton setEnabled:NO]; + } + // 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)) { + [exportButton setEnabled:NO]; + } + else { + [exportButton setEnabled:(contentEnabled || (structureEnabled || dropEnabled))]; + } } } /** - * Exports the contents' of the supplied array of tables. Note that this method currently only supports - * exporting in CSV and XML formats. + * Resizes the export window's height by the supplied delta, while retaining the position of + * all interface controls. */ -- (BOOL)_exportTables:(NSArray *)exportTables asType:(SPExportType)type toMultipleFiles:(BOOL)multipleFiles +- (void)_resizeWindowByHeightDelta:(NSInteger)delta { - NSUInteger i; + NSUInteger scrollMask = [exportTablelistScrollView autoresizingMask]; + NSUInteger buttonBarMask = [exportTableListButtonBar autoresizingMask]; + NSUInteger tabBarMask = [exportTabBar autoresizingMask]; + NSUInteger buttonMask = [exportAdvancedOptionsViewButton autoresizingMask]; + NSUInteger textFieldMask = [exportAdvancedOptionsViewLabelButton autoresizingMask]; + NSUInteger advancedViewMask = [exportAdvancedOptionsView autoresizingMask]; - NSMutableString *errors = [NSMutableString string]; + NSRect frame = [[self window] frame]; - NSDictionary *tableDetails = nil; - //NSStringEncoding encoding = [[self connection] encoding]; + [exportTablelistScrollView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportTableListButtonBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportTabBar setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportAdvancedOptionsViewButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportAdvancedOptionsViewLabelButton setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; + [exportAdvancedOptionsView setAutoresizingMask:NSViewNotSizable | NSViewMinYMargin]; - // Reset the interface - [exportProgressTitle setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Exporting %@", @"text showing that the application is importing a supplied format"), @"CSV"]]; - [exportProgressText setStringValue:NSLocalizedString(@"Writing...", @"text showing that app is writing text file")]; - [exportProgressText displayIfNeeded]; - [exportProgressIndicator setDoubleValue:0]; - [exportProgressIndicator displayIfNeeded]; + NSInteger newMinHeight = (windowMinHeigth - heightOffset + delta < windowMinHeigth) ? windowMinHeigth : windowMinHeigth - heightOffset + delta; - // Open the progress sheet - [NSApp beginSheet:exportProgressWindow - modalForWindow:[tableDocumentInstance parentWindow] - modalDelegate:self - didEndSelector:nil - contextInfo:nil]; - - // Add a dump header to the dump file - NSMutableString *csvLineEnd = [NSMutableString stringWithString:[exportCSVLinesTerminatedField stringValue]]; - - [csvLineEnd replaceOccurrencesOfString:@"\\t" withString:@"\t" - options:NSLiteralSearch - range:NSMakeRange(0, [csvLineEnd length])]; - - [csvLineEnd replaceOccurrencesOfString:@"\\n" withString:@"\n" - options:NSLiteralSearch - range:NSMakeRange(0, [csvLineEnd length])]; + [[self window] setMinSize:NSMakeSize(windowMinWidth, newMinHeight)]; - [csvLineEnd replaceOccurrencesOfString:@"\\r" withString:@"\r" - options:NSLiteralSearch - range:NSMakeRange(0, [csvLineEnd length])]; + frame.origin.y += heightOffset; + frame.size.height -= heightOffset; - NSUInteger tableCount = [exportTables count]; + heightOffset = delta; - // If - if ((type == SP_CSV_EXPORT) && (!multipleFiles) && (tableCount > 1)) { - - } + frame.origin.y -= heightOffset; + frame.size.height += heightOffset; - /*if ([exportTables count] > 1) { - [infoString setString:[NSString stringWithFormat:@"Host: %@ Database: %@ Generation Time: %@%@%@", - [tableDocumentInstance host], [tableDocumentInstance database], [NSDate date], csvLineEnd, csvLineEnd]]; - }*/ - - // Loop through the tables - for (i = 0 ; i < tableCount; i++) - { - if ([self exportCancelled]) break; - - // Update the progress text and reset the progress bar to indeterminate status - NSString *tableName = [exportTables objectAtIndex:i]; - - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %l (%@): fetching data...", @"text showing that app is fetching data for table dump"), (unsigned long)(i + 1), (unsigned long)tableCount, tableName]]; - [exportProgressText displayIfNeeded]; - - [exportProgressIndicator setIndeterminate:YES]; - [exportProgressIndicator setUsesThreadedAnimation:YES]; - [exportProgressIndicator startAnimation:self]; - - // For CSV exports of more than one table, output the name of the table - /*if (tableCount > 1) { - [fileHandle writeData:[[NSString stringWithFormat:@"Table %@%@%@", tableName, csvLineEnd, csvLineEnd] dataUsingEncoding:encoding]]; - }*/ - - // Determine whether this table is a table or a view via the create table command, and get the table details - MCPResult *queryResult = [connection queryString:[NSString stringWithFormat:@"SHOW CREATE TABLE %@", [tableName backtickQuotedString]]]; - [queryResult setReturnDataAsStrings:YES]; - - if ([queryResult numOfRows]) { - tableDetails = [NSDictionary dictionaryWithDictionary:[queryResult fetchRowAsDictionary]]; - - tableDetails = [NSDictionary dictionaryWithDictionary:([tableDetails objectForKey:@"Create View"]) ? [tableDataInstance informationForView:tableName] : [tableDataInstance informationForTable:tableName]]; - } - - // Retrieve the table details via the data class, and use it to build an array containing column numeric status - NSMutableArray *tableColumnNumericStatus = [NSMutableArray array]; - - for (NSDictionary *column in [tableDetails objectForKey:@"columns"]) - { - NSString *tableColumnTypeGrouping = [column objectForKey:@"typegrouping"]; - - [tableColumnNumericStatus addObject:[NSNumber numberWithBool:([tableColumnTypeGrouping isEqualToString:@"bit"] || - [tableColumnTypeGrouping isEqualToString:@"integer"] || - [tableColumnTypeGrouping isEqualToString:@"float"])]]; - } - - // Use low memory export? - BOOL useLowMemoryBlockingStreaming = ([exportProcessLowMemory state] == NSOnState); - - // Make a streaming request for the data - MCPStreamingResult *queryResultStreaming = [connection streamingQueryString:[NSString stringWithFormat:@"SELECT * FROM %@", [tableName backtickQuotedString]] useLowMemoryBlockingStreaming:useLowMemoryBlockingStreaming]; - - // Note any errors during retrieval - if ([connection queryErrored]) { - [errors appendString:[NSString stringWithFormat:@"%@\n", [connection getLastErrorMessage]]]; - } - - SPExporter *exporter = nil; - SPCSVExporter *csvExporter = nil; - - // Based on the type of export create a new instance of the corresponding exporter and set it's specific options - switch (type) - { - case SP_SQL_EXPORT: - - break; - case SP_CSV_EXPORT: - csvExporter = [[SPCSVExporter alloc] initWithDelegate:self]; - - [csvExporter setCsvOutputFieldNames:[exportCSVIncludeFieldNamesCheck state]]; - [csvExporter setCsvFieldSeparatorString:[exportCSVFieldsTerminatedField stringValue]]; - [csvExporter setCsvEnclosingCharacterString:[exportCSVFieldsWrappedField stringValue]]; - [csvExporter setCsvLineEndingString:[exportCSVLinesTerminatedField stringValue]]; - [csvExporter setCsvEscapeString:[exportCSVFieldsEscapedField stringValue]]; - - [csvExporter setExportOutputEncoding:[MCPConnection encodingForMySQLEncoding:[[tableDocumentInstance connectionEncoding] UTF8String]]]; - [csvExporter setCsvNULLString:[[NSUserDefaults standardUserDefaults] objectForKey:SPNullValue]]; - - [csvExporter setCsvTableColumnNumericStatus:tableColumnNumericStatus]; - - // Assign the data to the exporter - [csvExporter setCsvDataResult:queryResultStreaming]; - - exporter = csvExporter; - - break; - case SP_XML_EXPORT: - - break; - case SP_PDF_EXPORT: - - break; - case SP_HTML_EXPORT: - - break; - case SP_EXCEL_EXPORT: - - break; - } - - // Update the progress text and set the progress bar back to determinate - [exportProgressText setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Table %lu of %lu (%@): Writing...", @"text showing that app is writing data for table export"), (unsigned long)(i + 1), (unsigned long)tableCount, tableName]]; - [exportProgressText displayIfNeeded]; - - [exportProgressIndicator stopAnimation:self]; - [exportProgressIndicator setUsesThreadedAnimation:NO]; - [exportProgressIndicator setIndeterminate:NO]; - [exportProgressIndicator setDoubleValue:0]; - [exportProgressIndicator displayIfNeeded]; - - // Start the actual data conversion process by placing the exporter on the operation queue. - // Note that although it is highly likely there is no guarantee that the operation will executed - // as soon as it's placed on the queue. There may be a delay if the queue is already executing it's - // maximum number of concurrent operations. See the docs for more details. - [operationQueue addOperation:exporter]; - - if (csvExporter) [csvExporter release]; - - // Add a spacer to the file - //[fileHandle writeData:[[NSString stringWithFormat:@"%@%@%@", csvLineEnd, csvLineEnd, csvLineEnd] dataUsingEncoding:encoding]]; - } + [[self window] setFrame:frame display:YES animate:YES]; - return YES; + [exportTablelistScrollView setAutoresizingMask:scrollMask]; + [exportTableListButtonBar setAutoresizingMask:buttonBarMask]; + [exportTabBar setAutoresizingMask:tabBarMask]; + [exportAdvancedOptionsViewButton setAutoresizingMask:buttonMask]; + [exportAdvancedOptionsViewLabelButton setAutoresizingMask:textFieldMask]; + [exportAdvancedOptionsView setAutoresizingMask:advancedViewMask]; } @end -- cgit v1.2.3